blob: 79b580d936c10f0b71388c2888f34a41f34758fa [file] [log] [blame]
#ifndef AOS_LOGGING_BINARY_LOG_FILE_H_
#define AOS_LOGGING_BINARY_LOG_FILE_H_
#include <sys/types.h>
#include <stddef.h>
#include <stdint.h>
#include <algorithm>
#include "aos/logging/implementations.h"
namespace aos {
namespace logging {
namespace linux_code {
// What to align messages to. A macro because it gets used in attributes.
// This definition gets #undefed later. Use LogFileAccessor::kAlignment instead.
#define MESSAGE_ALIGNMENT 8
// File format: {
// LogFileMessageHeader header;
// char *name; // of the process that wrote the message
// void *message;
// } not crossing kPageSize boundaries into the file and aligned to
// MESSAGE_ALIGNMENT.
//
// Field sizes designed to fit the various values from LogMessage even on
// other machines (hopefully) because they're baked into the files. They are
// layed out so that all of the fields are aligned even though the whole thing
// is packed.
//
// A lot of the fields don't have comments because they're the same as the
// identically named fields in LogMessage.
struct __attribute__((aligned(MESSAGE_ALIGNMENT))) __attribute__((packed))
LogFileMessageHeader {
// Represents the type of an individual message.
enum class MessageType : uint16_t {
// char[] (no '\0' on the end).
kString,
kStructType,
kStruct,
kMatrix,
};
// Gets futex_set once this one has been written
// for readers keeping up with a live writer.
//
// Gets initialized to 0 by ftruncate.
//
// There will be something here after the last message on a "page" set to 2
// (by the futex_set) to indicate that the next message is on the next page.
aos_futex marker;
static_assert(sizeof(marker) == 4, "mutex changed size!");
static_assert(MESSAGE_ALIGNMENT >= alignof(aos_futex),
"MESSAGE_ALIGNMENT is too small");
uint32_t time_sec;
static_assert(sizeof(time_sec) >= sizeof(LogMessage::seconds),
"tv_sec won't fit");
uint32_t time_nsec;
static_assert(sizeof(time_nsec) >= sizeof(LogMessage::nseconds),
"tv_nsec won't fit");
int32_t source;
static_assert(sizeof(source) >= sizeof(LogMessage::source), "PIDs won't fit");
// Both including all of the bytes in that part of the message.
uint32_t name_size, message_size;
uint16_t sequence;
static_assert(sizeof(sequence) == sizeof(LogMessage::sequence),
"something changed");
MessageType type;
log_level level;
static_assert(sizeof(level) == 1, "log_level changed size!");
};
static_assert(std::is_pod<LogFileMessageHeader>::value,
"LogFileMessageHeader will to get dumped to a file");
static_assert(offsetof(LogFileMessageHeader, marker) == 0,
"marker has to be at the start so readers can find it");
// Handles the mmapping and munmapping for reading and writing log files.
class LogFileAccessor {
public:
LogFileAccessor(int fd, bool writable);
~LogFileAccessor() {
if (use_read_ == Maybe::kYes) {
delete[] current_;
}
}
// Asynchronously syncs all open mappings.
void Sync() const;
// Returns true iff we currently have the last page in the file mapped.
// This is fundamentally a racy question, so the return value may not be
// accurate by the time this method returns.
bool IsLastPage();
// Skips to the last page which is an even multiple of kSeekPages.
// This is fundamentally racy, so it may not actually be on the very last
// possible multiple of kSeekPages when it returns, but it should be close.
// This will never move backwards.
void SkipToLastSeekablePage();
size_t file_offset(const void *msg) {
return offset() + (static_cast<const char *>(msg) - current());
}
protected:
// The size of the chunks that get mmaped/munmapped together. Large enough so
// that not too much space is wasted and it's hopefully bigger than and a
// multiple of the system page size but small enough so that really large
// chunks of memory don't have to get mapped at the same time.
static const size_t kPageSize = 16384;
// What to align messages to, copied into an actual constant.
static const size_t kAlignment = MESSAGE_ALIGNMENT;
#undef MESSAGE_ALIGNMENT
// Pages which are multiples of this from the beginning of a file start with
// no saved state (ie struct types). This allows seeking immediately to the
// largest currently written interval of this number when following.
static const size_t kSeekPages = 256;
char *current() const { return current_; }
size_t position() const { return position_; }
off_t offset() const { return offset_; }
void IncrementPosition(size_t size) {
position_ += size;
AlignPosition();
}
void MapNextPage();
void Unmap(void *location);
// Advances position to the next (aligned) location.
void AlignPosition() {
position_ += kAlignment - (position_ % kAlignment);
}
protected:
bool definitely_use_read() const { return use_read_ == Maybe::kYes; }
bool definitely_use_mmap() const { return use_read_ == Maybe::kNo; }
private:
// Used for representing things that we might know to be true/false or we
// might not know (yet).
enum class Maybe { kUnknown, kYes, kNo };
const int fd_;
const bool writable_;
// Into the file. Always a multiple of kPageSize.
off_t offset_;
char *current_;
size_t position_;
Maybe is_last_page_ = Maybe::kUnknown;
// Use read instead of mmap (necessary for fds that don't support mmap).
Maybe use_read_ = Maybe::kUnknown;
};
class LogFileReader : public LogFileAccessor {
public:
LogFileReader(int fd) : LogFileAccessor(fd, false) {}
// May return NULL iff wait is false.
const LogFileMessageHeader *ReadNextMessage(bool wait);
private:
// Tries reading from the current page to see if it fails because the file
// isn't big enough.
void CheckCurrentPageReadable();
};
class LogFileWriter : public LogFileAccessor {
public:
LogFileWriter(int fd) : LogFileAccessor(fd, true) {}
// message_size should be the total number of bytes needed for the message.
LogFileMessageHeader *GetWritePosition(size_t message_size);
// Returns true exactly once for each unique cookie on each page where cached
// data should be cleared.
// Call with a non-zero next_message_size to determine if cached data should
// be forgotten before writing a next_message_size-sized message.
// cookie should be initialized to 0.
bool ShouldClearSeekableData(off_t *cookie, size_t next_message_size) const;
// Forces a move to a new page for the next message.
// This is important when there is cacheable data that needs to be re-written
// before a message which will spill over onto the next page but the cacheable
// message being refreshed is smaller and won't get to a new page by itself.
void ForceNewPage();
private:
bool NeedNewPageFor(size_t bytes) const;
};
} // namespace linux_code
} // namespace logging
} // namespace aos
#endif // AOS_LOGGING_BINARY_LOG_FILE_H_