John Park | 33858a3 | 2018-09-28 23:05:48 -0700 | [diff] [blame] | 1 | #ifndef AOS_LOGGING_BINARY_LOG_FILE_H_ |
| 2 | #define AOS_LOGGING_BINARY_LOG_FILE_H_ |
brians | 343bc11 | 2013-02-10 01:53:46 +0000 | [diff] [blame] | 3 | |
brians | 343bc11 | 2013-02-10 01:53:46 +0000 | [diff] [blame] | 4 | #include <sys/types.h> |
Brian Silverman | 003ba4b | 2014-02-10 16:56:18 -0800 | [diff] [blame] | 5 | #include <stddef.h> |
| 6 | #include <stdint.h> |
brians | 343bc11 | 2013-02-10 01:53:46 +0000 | [diff] [blame] | 7 | |
| 8 | #include <algorithm> |
| 9 | |
John Park | 33858a3 | 2018-09-28 23:05:48 -0700 | [diff] [blame] | 10 | #include "aos/logging/implementations.h" |
brians | 343bc11 | 2013-02-10 01:53:46 +0000 | [diff] [blame] | 11 | |
| 12 | namespace aos { |
Brian Silverman | f665d69 | 2013-02-17 22:11:39 -0800 | [diff] [blame] | 13 | namespace logging { |
Brian Silverman | 003ba4b | 2014-02-10 16:56:18 -0800 | [diff] [blame] | 14 | namespace linux_code { |
| 15 | |
| 16 | // What to align messages to. A macro because it gets used in attributes. |
| 17 | // This definition gets #undefed later. Use LogFileAccessor::kAlignment instead. |
| 18 | #define MESSAGE_ALIGNMENT 8 |
brians | 343bc11 | 2013-02-10 01:53:46 +0000 | [diff] [blame] | 19 | |
| 20 | // File format: { |
| 21 | // LogFileMessageHeader header; |
Brian Silverman | f665d69 | 2013-02-17 22:11:39 -0800 | [diff] [blame] | 22 | // char *name; // of the process that wrote the message |
Brian Silverman | 003ba4b | 2014-02-10 16:56:18 -0800 | [diff] [blame] | 23 | // void *message; |
| 24 | // } not crossing kPageSize boundaries into the file and aligned to |
| 25 | // MESSAGE_ALIGNMENT. |
brians | 343bc11 | 2013-02-10 01:53:46 +0000 | [diff] [blame] | 26 | // |
Brian Silverman | f665d69 | 2013-02-17 22:11:39 -0800 | [diff] [blame] | 27 | // Field sizes designed to fit the various values from LogMessage even on |
Brian Silverman | 003ba4b | 2014-02-10 16:56:18 -0800 | [diff] [blame] | 28 | // other machines (hopefully) because they're baked into the files. They are |
| 29 | // layed out so that all of the fields are aligned even though the whole thing |
| 30 | // is packed. |
Brian Silverman | f665d69 | 2013-02-17 22:11:39 -0800 | [diff] [blame] | 31 | // |
| 32 | // A lot of the fields don't have comments because they're the same as the |
| 33 | // identically named fields in LogMessage. |
Brian Silverman | 003ba4b | 2014-02-10 16:56:18 -0800 | [diff] [blame] | 34 | struct __attribute__((aligned(MESSAGE_ALIGNMENT))) __attribute__((packed)) |
| 35 | LogFileMessageHeader { |
| 36 | // Represents the type of an individual message. |
| 37 | enum class MessageType : uint16_t { |
Brian Silverman | 88471dc | 2014-02-15 22:35:42 -0800 | [diff] [blame] | 38 | // char[] (no '\0' on the end). |
Brian Silverman | 003ba4b | 2014-02-10 16:56:18 -0800 | [diff] [blame] | 39 | kString, |
| 40 | kStructType, |
| 41 | kStruct, |
Brian Silverman | 664db1a | 2014-03-20 17:06:29 -0700 | [diff] [blame] | 42 | kMatrix, |
Brian Silverman | 003ba4b | 2014-02-10 16:56:18 -0800 | [diff] [blame] | 43 | }; |
| 44 | |
| 45 | // Gets futex_set once this one has been written |
| 46 | // for readers keeping up with a live writer. |
brians | 343bc11 | 2013-02-10 01:53:46 +0000 | [diff] [blame] | 47 | // |
Brian Silverman | 003ba4b | 2014-02-10 16:56:18 -0800 | [diff] [blame] | 48 | // Gets initialized to 0 by ftruncate. |
Brian Silverman | 65e569d | 2014-10-24 15:43:20 -0400 | [diff] [blame] | 49 | // |
Brian Silverman | 003ba4b | 2014-02-10 16:56:18 -0800 | [diff] [blame] | 50 | // There will be something here after the last message on a "page" set to 2 |
| 51 | // (by the futex_set) to indicate that the next message is on the next page. |
Brian Silverman | dc1eb27 | 2014-08-19 14:25:59 -0400 | [diff] [blame] | 52 | aos_futex marker; |
brians | 343bc11 | 2013-02-10 01:53:46 +0000 | [diff] [blame] | 53 | static_assert(sizeof(marker) == 4, "mutex changed size!"); |
Brian Silverman | dc1eb27 | 2014-08-19 14:25:59 -0400 | [diff] [blame] | 54 | static_assert(MESSAGE_ALIGNMENT >= alignof(aos_futex), |
Brian Silverman | 003ba4b | 2014-02-10 16:56:18 -0800 | [diff] [blame] | 55 | "MESSAGE_ALIGNMENT is too small"); |
brians | 343bc11 | 2013-02-10 01:53:46 +0000 | [diff] [blame] | 56 | |
Brian Silverman | f665d69 | 2013-02-17 22:11:39 -0800 | [diff] [blame] | 57 | uint32_t time_sec; |
| 58 | static_assert(sizeof(time_sec) >= sizeof(LogMessage::seconds), |
| 59 | "tv_sec won't fit"); |
| 60 | uint32_t time_nsec; |
| 61 | static_assert(sizeof(time_nsec) >= sizeof(LogMessage::nseconds), |
brians | 343bc11 | 2013-02-10 01:53:46 +0000 | [diff] [blame] | 62 | "tv_nsec won't fit"); |
| 63 | |
Brian Silverman | f665d69 | 2013-02-17 22:11:39 -0800 | [diff] [blame] | 64 | int32_t source; |
| 65 | static_assert(sizeof(source) >= sizeof(LogMessage::source), "PIDs won't fit"); |
Brian Silverman | 003ba4b | 2014-02-10 16:56:18 -0800 | [diff] [blame] | 66 | |
Brian Silverman | 88471dc | 2014-02-15 22:35:42 -0800 | [diff] [blame] | 67 | // Both including all of the bytes in that part of the message. |
Brian Silverman | 003ba4b | 2014-02-10 16:56:18 -0800 | [diff] [blame] | 68 | uint32_t name_size, message_size; |
| 69 | |
Brian Silverman | f665d69 | 2013-02-17 22:11:39 -0800 | [diff] [blame] | 70 | uint16_t sequence; |
| 71 | static_assert(sizeof(sequence) == sizeof(LogMessage::sequence), |
brians | 343bc11 | 2013-02-10 01:53:46 +0000 | [diff] [blame] | 72 | "something changed"); |
| 73 | |
Brian Silverman | 003ba4b | 2014-02-10 16:56:18 -0800 | [diff] [blame] | 74 | MessageType type; |
| 75 | |
| 76 | log_level level; |
| 77 | static_assert(sizeof(level) == 1, "log_level changed size!"); |
brians | 343bc11 | 2013-02-10 01:53:46 +0000 | [diff] [blame] | 78 | }; |
| 79 | static_assert(std::is_pod<LogFileMessageHeader>::value, |
| 80 | "LogFileMessageHeader will to get dumped to a file"); |
Brian Silverman | 003ba4b | 2014-02-10 16:56:18 -0800 | [diff] [blame] | 81 | static_assert(offsetof(LogFileMessageHeader, marker) == 0, |
| 82 | "marker has to be at the start so readers can find it"); |
brians | 343bc11 | 2013-02-10 01:53:46 +0000 | [diff] [blame] | 83 | |
| 84 | // Handles the mmapping and munmapping for reading and writing log files. |
| 85 | class LogFileAccessor { |
Brian Silverman | 003ba4b | 2014-02-10 16:56:18 -0800 | [diff] [blame] | 86 | public: |
| 87 | LogFileAccessor(int fd, bool writable); |
Brian Silverman | 65e569d | 2014-10-24 15:43:20 -0400 | [diff] [blame] | 88 | ~LogFileAccessor() { |
| 89 | if (use_read_ == Maybe::kYes) { |
| 90 | delete[] current_; |
| 91 | } |
| 92 | } |
Brian Silverman | 003ba4b | 2014-02-10 16:56:18 -0800 | [diff] [blame] | 93 | |
Brian Silverman | 003ba4b | 2014-02-10 16:56:18 -0800 | [diff] [blame] | 94 | // Asynchronously syncs all open mappings. |
| 95 | void Sync() const; |
| 96 | |
Brian Silverman | f5ca4d0 | 2015-03-01 16:52:24 -0500 | [diff] [blame] | 97 | // Returns true iff we currently have the last page in the file mapped. |
| 98 | // This is fundamentally a racy question, so the return value may not be |
| 99 | // accurate by the time this method returns. |
Brian Silverman | f778031 | 2014-02-16 17:26:15 -0800 | [diff] [blame] | 100 | bool IsLastPage(); |
Brian Silverman | 003ba4b | 2014-02-10 16:56:18 -0800 | [diff] [blame] | 101 | |
Brian Silverman | f5ca4d0 | 2015-03-01 16:52:24 -0500 | [diff] [blame] | 102 | // Skips to the last page which is an even multiple of kSeekPages. |
| 103 | // This is fundamentally racy, so it may not actually be on the very last |
| 104 | // possible multiple of kSeekPages when it returns, but it should be close. |
| 105 | // This will never move backwards. |
| 106 | void SkipToLastSeekablePage(); |
| 107 | |
Brian Silverman | f4452d7 | 2014-10-25 17:23:11 -0400 | [diff] [blame] | 108 | size_t file_offset(const void *msg) { |
| 109 | return offset() + (static_cast<const char *>(msg) - current()); |
| 110 | } |
| 111 | |
Brian Silverman | ab5ba47 | 2014-04-18 15:26:14 -0700 | [diff] [blame] | 112 | protected: |
brians | 343bc11 | 2013-02-10 01:53:46 +0000 | [diff] [blame] | 113 | // The size of the chunks that get mmaped/munmapped together. Large enough so |
| 114 | // that not too much space is wasted and it's hopefully bigger than and a |
Brian Silverman | 003ba4b | 2014-02-10 16:56:18 -0800 | [diff] [blame] | 115 | // multiple of the system page size but small enough so that really large |
| 116 | // chunks of memory don't have to get mapped at the same time. |
| 117 | static const size_t kPageSize = 16384; |
| 118 | // What to align messages to, copied into an actual constant. |
| 119 | static const size_t kAlignment = MESSAGE_ALIGNMENT; |
| 120 | #undef MESSAGE_ALIGNMENT |
Brian Silverman | f5ca4d0 | 2015-03-01 16:52:24 -0500 | [diff] [blame] | 121 | // Pages which are multiples of this from the beginning of a file start with |
| 122 | // no saved state (ie struct types). This allows seeking immediately to the |
| 123 | // largest currently written interval of this number when following. |
| 124 | static const size_t kSeekPages = 256; |
brians | 343bc11 | 2013-02-10 01:53:46 +0000 | [diff] [blame] | 125 | |
Brian Silverman | ab5ba47 | 2014-04-18 15:26:14 -0700 | [diff] [blame] | 126 | char *current() const { return current_; } |
| 127 | size_t position() const { return position_; } |
| 128 | off_t offset() const { return offset_; } |
| 129 | |
| 130 | void IncrementPosition(size_t size) { |
| 131 | position_ += size; |
| 132 | AlignPosition(); |
| 133 | } |
| 134 | |
| 135 | void MapNextPage(); |
| 136 | void Unmap(void *location); |
| 137 | |
| 138 | // Advances position to the next (aligned) location. |
| 139 | void AlignPosition() { |
| 140 | position_ += kAlignment - (position_ % kAlignment); |
| 141 | } |
| 142 | |
Brian Silverman | 65e569d | 2014-10-24 15:43:20 -0400 | [diff] [blame] | 143 | protected: |
| 144 | bool definitely_use_read() const { return use_read_ == Maybe::kYes; } |
| 145 | bool definitely_use_mmap() const { return use_read_ == Maybe::kNo; } |
| 146 | |
Brian Silverman | ab5ba47 | 2014-04-18 15:26:14 -0700 | [diff] [blame] | 147 | private: |
Brian Silverman | 65e569d | 2014-10-24 15:43:20 -0400 | [diff] [blame] | 148 | // Used for representing things that we might know to be true/false or we |
| 149 | // might not know (yet). |
| 150 | enum class Maybe { kUnknown, kYes, kNo }; |
| 151 | |
brians | 343bc11 | 2013-02-10 01:53:46 +0000 | [diff] [blame] | 152 | const int fd_; |
| 153 | const bool writable_; |
| 154 | |
Brian Silverman | 003ba4b | 2014-02-10 16:56:18 -0800 | [diff] [blame] | 155 | // Into the file. Always a multiple of kPageSize. |
| 156 | off_t offset_; |
brians | 343bc11 | 2013-02-10 01:53:46 +0000 | [diff] [blame] | 157 | char *current_; |
| 158 | size_t position_; |
| 159 | |
Brian Silverman | 65e569d | 2014-10-24 15:43:20 -0400 | [diff] [blame] | 160 | Maybe is_last_page_ = Maybe::kUnknown; |
| 161 | |
| 162 | // Use read instead of mmap (necessary for fds that don't support mmap). |
| 163 | Maybe use_read_ = Maybe::kUnknown; |
Brian Silverman | ab5ba47 | 2014-04-18 15:26:14 -0700 | [diff] [blame] | 164 | }; |
Brian Silverman | f778031 | 2014-02-16 17:26:15 -0800 | [diff] [blame] | 165 | |
Brian Silverman | ab5ba47 | 2014-04-18 15:26:14 -0700 | [diff] [blame] | 166 | class LogFileReader : public LogFileAccessor { |
| 167 | public: |
| 168 | LogFileReader(int fd) : LogFileAccessor(fd, false) {} |
| 169 | |
| 170 | // May return NULL iff wait is false. |
| 171 | const LogFileMessageHeader *ReadNextMessage(bool wait); |
| 172 | |
| 173 | private: |
Brian Silverman | 8a2e714 | 2014-03-12 22:17:34 -0700 | [diff] [blame] | 174 | // Tries reading from the current page to see if it fails because the file |
| 175 | // isn't big enough. |
| 176 | void CheckCurrentPageReadable(); |
Brian Silverman | ab5ba47 | 2014-04-18 15:26:14 -0700 | [diff] [blame] | 177 | }; |
brians | 343bc11 | 2013-02-10 01:53:46 +0000 | [diff] [blame] | 178 | |
Brian Silverman | ab5ba47 | 2014-04-18 15:26:14 -0700 | [diff] [blame] | 179 | class LogFileWriter : public LogFileAccessor { |
| 180 | public: |
| 181 | LogFileWriter(int fd) : LogFileAccessor(fd, true) {} |
| 182 | |
| 183 | // message_size should be the total number of bytes needed for the message. |
| 184 | LogFileMessageHeader *GetWritePosition(size_t message_size); |
Brian Silverman | f5ca4d0 | 2015-03-01 16:52:24 -0500 | [diff] [blame] | 185 | |
| 186 | // Returns true exactly once for each unique cookie on each page where cached |
| 187 | // data should be cleared. |
| 188 | // Call with a non-zero next_message_size to determine if cached data should |
| 189 | // be forgotten before writing a next_message_size-sized message. |
| 190 | // cookie should be initialized to 0. |
| 191 | bool ShouldClearSeekableData(off_t *cookie, size_t next_message_size) const; |
| 192 | |
| 193 | // Forces a move to a new page for the next message. |
| 194 | // This is important when there is cacheable data that needs to be re-written |
| 195 | // before a message which will spill over onto the next page but the cacheable |
| 196 | // message being refreshed is smaller and won't get to a new page by itself. |
| 197 | void ForceNewPage(); |
| 198 | |
| 199 | private: |
| 200 | bool NeedNewPageFor(size_t bytes) const; |
brians | 343bc11 | 2013-02-10 01:53:46 +0000 | [diff] [blame] | 201 | }; |
| 202 | |
Brian Silverman | 003ba4b | 2014-02-10 16:56:18 -0800 | [diff] [blame] | 203 | } // namespace linux_code |
Brian Silverman | f665d69 | 2013-02-17 22:11:39 -0800 | [diff] [blame] | 204 | } // namespace logging |
| 205 | } // namespace aos |
brians | 343bc11 | 2013-02-10 01:53:46 +0000 | [diff] [blame] | 206 | |
John Park | 33858a3 | 2018-09-28 23:05:48 -0700 | [diff] [blame] | 207 | #endif // AOS_LOGGING_BINARY_LOG_FILE_H_ |