blob: 91f048c1a31581d603610aed2302c2c3f5ff9384 [file] [log] [blame]
#ifndef AOS_EVENTS_LOGGING_LOG_BACKEND_H_
#define AOS_EVENTS_LOGGING_LOG_BACKEND_H_
#include <fcntl.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <memory>
#include <string>
#include <vector>
#include "absl/types/span.h"
#include "aos/events/logging/buffer_encoder.h"
#include "aos/time/time.h"
namespace aos::logger {
class WriteStats {
public:
// The maximum time for a single write call, or 0 if none have been performed.
std::chrono::nanoseconds max_write_time() const { return max_write_time_; }
// The number of bytes in the longest write call, or -1 if none have been
// performed.
int max_write_time_bytes() const { return max_write_time_bytes_; }
// The number of buffers in the longest write call, or -1 if none have been
// performed.
int max_write_time_messages() const { return max_write_time_messages_; }
// The total time spent in write calls.
std::chrono::nanoseconds total_write_time() const {
return total_write_time_;
}
// The total number of writes which have been performed.
int total_write_count() const { return total_write_count_; }
// The total number of messages which have been written.
int total_write_messages() const { return total_write_messages_; }
// The total number of bytes which have been written.
int total_write_bytes() const { return total_write_bytes_; }
void ResetStats() {
max_write_time_ = std::chrono::nanoseconds::zero();
max_write_time_bytes_ = -1;
max_write_time_messages_ = -1;
total_write_time_ = std::chrono::nanoseconds::zero();
total_write_count_ = 0;
total_write_messages_ = 0;
total_write_bytes_ = 0;
}
void UpdateStats(aos::monotonic_clock::duration duration, ssize_t written,
int iovec_size) {
if (duration > max_write_time_) {
max_write_time_ = duration;
max_write_time_bytes_ = written;
max_write_time_messages_ = iovec_size;
}
total_write_time_ += duration;
++total_write_count_;
total_write_messages_ += iovec_size;
total_write_bytes_ += written;
}
private:
std::chrono::nanoseconds max_write_time_ = std::chrono::nanoseconds::zero();
int max_write_time_bytes_ = -1;
int max_write_time_messages_ = -1;
std::chrono::nanoseconds total_write_time_ = std::chrono::nanoseconds::zero();
int total_write_count_ = 0;
int total_write_messages_ = 0;
int total_write_bytes_ = 0;
};
// Currently, all write operations only cares about out-of-space error. This is
// a simple representation of write result.
enum class WriteCode { kOk, kOutOfSpace };
struct WriteResult {
WriteCode code = WriteCode::kOk;
size_t messages_written = 0;
};
// Interface that abstract writing to log from media.
class LogSink {
public:
LogSink() = default;
virtual ~LogSink() = default;
LogSink(const LogSink &) = delete;
LogSink &operator=(const LogSink &) = delete;
// Try to open file. App will crash if there are other than out-of-space
// problems with backend media.
virtual WriteCode OpenForWrite() = 0;
// Close the file handler.
virtual WriteCode Close() = 0;
// Returns true if sink is open and need to be closed.
virtual bool is_open() const = 0;
// Peeks messages from queue and writes it to file. Returns code when
// out-of-space problem occurred along with number of messages from queue that
// was written.
virtual WriteResult Write(
const absl::Span<const absl::Span<const uint8_t>> &queue) = 0;
// Get access to statistics related to the write operations.
WriteStats *WriteStatistics() { return &write_stats_; }
// Name of the log sink.
virtual std::string_view name() const = 0;
private:
WriteStats write_stats_;
};
// Source for iovec with an additional flag that pointer and size of data is
// aligned and be ready for O_DIRECT operation.
struct AlignedIovec {
const uint8_t *data;
size_t size;
bool aligned;
AlignedIovec(const uint8_t *data, size_t size, bool aligned)
: data(data), size(size), aligned(aligned) {}
};
// Converts queue of pieces to write to the disk to the queue where every
// element is either aligned for O_DIRECT operation or marked as not aligned.
class QueueAligner {
public:
QueueAligner();
// Reads input queue and fills with aligned and unaligned pieces. It is easy
// to deal with smaller pieces and batch it during the write operation.
void FillAlignedQueue(
const absl::Span<const absl::Span<const uint8_t>> &queue);
const std::vector<AlignedIovec> &aligned_queue() const {
return aligned_queue_;
}
private:
std::vector<AlignedIovec> aligned_queue_;
};
// FileHandler is a replacement for bare filename in log writing and reading
// operations.
//
// There are a couple over-arching constraints on writing to keep track of.
// 1) The kernel is both faster and more efficient at writing large, aligned
// chunks with O_DIRECT set on the file. The alignment needed is specified
// by kSector and is file system dependent.
// 2) Not all encoders support generating round multiples of kSector of data.
// Rather than burden the API for detecting when that is the case, we want
// DetachedBufferWriter to be as efficient as it can at writing what given.
// 3) Some files are small and not updated frequently. They need to be
// flushed or we will lose data on power off. It is most efficient to write
// as much as we can aligned by kSector and then fall back to the non direct
// method when it has been flushed.
// 4) Not all filesystems support O_DIRECT, and different sizes may be optimal
// for different machines. The defaults should work decently anywhere and
// be tunable for faster systems.
class FileHandler : public LogSink {
public:
// Size of an aligned sector used to detect when the data is aligned enough to
// use O_DIRECT instead.
static constexpr size_t kSector = 512u;
explicit FileHandler(std::string filename, bool supports_odirect);
~FileHandler() override;
FileHandler(const FileHandler &) = delete;
FileHandler &operator=(const FileHandler &) = delete;
// Try to open file. App will crash if there are other than out-of-space
// problems with backend media.
WriteCode OpenForWrite() override;
// Close the file handler.
WriteCode Close() override;
// This will be true until Close() is called, unless the file couldn't be
// created due to running out of space.
bool is_open() const override { return fd_ != -1; }
// Peeks messages from queue and writes it to file. Returns code when
// out-of-space problem occurred along with number of messages from queue that
// was written.
//
// The spans can be aligned or not, and can have any lengths. This code will
// write faster if the spans passed in start at aligned addresses, and are
// multiples of kSector long (and the data written so far is also a multiple
// of kSector length).
WriteResult Write(
const absl::Span<const absl::Span<const uint8_t>> &queue) override;
// Name of the log sink mostly for informational purposes.
std::string_view name() const override { return filename_; }
// Number of bytes written in aligned mode. It is mostly for testing.
size_t written_aligned() const { return written_aligned_; }
protected:
// This is used by subclasses who need to access filename.
std::string_view filename() const { return filename_; }
private:
// Enables O_DIRECT on the open file if it is supported. Cheap to call if it
// is already enabled.
void EnableDirect();
// Disables O_DIRECT on the open file if it is supported. Cheap to call if it
// is already disabld.
void DisableDirect();
bool ODirectEnabled() const { return !!(flags_ & O_DIRECT); }
// Writes a chunk of iovecs. aligned is true if all the data is kSector byte
// aligned and multiples of it in length.
WriteCode WriteV(const std::vector<struct iovec> &iovec, bool aligned);
const std::string filename_;
int fd_ = -1;
// List of iovecs to use with writev. This is a member variable to avoid
// churn.
std::vector<struct iovec> iovec_;
QueueAligner queue_aligner_;
int total_write_bytes_ = 0;
int last_synced_bytes_ = 0;
size_t written_aligned_ = 0;
bool supports_odirect_ = true;
int flags_ = 0;
};
// Interface to decouple reading of logs and media (file system, memory or S3).
class LogSource {
public:
struct File {
std::string name;
size_t size;
};
virtual ~LogSource() = default;
// Provides a list of readable sources for log reading.
virtual std::vector<File> ListFiles() const = 0;
// Entry point for reading the content of log file.
virtual std::unique_ptr<DataDecoder> GetDecoder(
const std::string_view id) const = 0;
std::unique_ptr<DataDecoder> GetDecoder(const LogSource::File &id) const {
return GetDecoder(id.name);
}
};
// Interface to decouple log writing and media (file system or memory). It is
// handy to use for tests.
class LogBackend {
public:
virtual ~LogBackend() = default;
// Request file-like object from the log backend. It maybe a file on a disk or
// in memory. id is usually generated by log namer and looks like name of the
// file within a log folder.
virtual std::unique_ptr<LogSink> RequestFile(const std::string_view id) = 0;
};
// Implements requests log files from file system.
class FileBackend : public LogBackend, public LogSource {
public:
// base_name is the path to the folder where log files are.
explicit FileBackend(std::string_view base_name, bool supports_direct);
~FileBackend() override = default;
// Request file from a file system. It is not open yet.
std::unique_ptr<LogSink> RequestFile(const std::string_view id) override;
// List all files that looks like log files under base_name.
std::vector<File> ListFiles() const override;
// Open decoder to read the content of the file.
std::unique_ptr<DataDecoder> GetDecoder(
const std::string_view id) const override;
private:
const bool supports_odirect_;
const std::string base_name_;
const std::string_view separator_;
};
// Provides a file backend that supports renaming of the base log folder and
// temporary files.
class RenamableFileBackend : public LogBackend {
public:
// Adds call to rename, when closed.
class RenamableFileHandler final : public FileHandler {
public:
RenamableFileHandler(RenamableFileBackend *owner, std::string filename,
bool supports_odirect)
: FileHandler(std::move(filename), supports_odirect), owner_(owner) {}
~RenamableFileHandler() final = default;
// Closes and if needed renames file.
WriteCode Close() final;
private:
RenamableFileBackend *owner_;
};
explicit RenamableFileBackend(std::string_view base_name,
bool supports_odirect);
~RenamableFileBackend() = default;
// Request file from a file system. It is not open yet.
std::unique_ptr<LogSink> RequestFile(const std::string_view id) override;
// TODO (Alexei): it is called by Logger, and left here for compatibility.
// Logger should not call it.
std::string_view base_name() const { return base_name_; }
// If temp files are enabled, then this will write files with the .tmp
// suffix, and then rename them to the desired name after they are fully
// written.
//
// This is useful to enable incremental copying of the log files.
//
// Defaults to writing directly to the final filename.
void EnableTempFiles();
// Moves the current log location to the new name. Returns true if a change
// was made, false otherwise.
// Only renaming the folder is supported, not the file base name.
bool RenameLogBase(std::string_view new_base_name);
private:
// This function called after file closed, to adjust file names in case of
// base name was changed or temp files enabled.
WriteCode RenameFileAfterClose(std::string_view filename);
const bool supports_odirect_;
std::string base_name_;
std::string_view separator_;
bool use_temp_files_ = false;
std::string_view temp_suffix_;
std::string old_base_name_;
};
} // namespace aos::logger
#endif // AOS_EVENTS_LOGGING_LOG_BACKEND_H_