blob: 20ade91cffaaf964d79e6347e4e9c8c715a4b22f [file] [log] [blame]
Alexei Strots01395492023-03-20 13:59:56 -07001#ifndef AOS_EVENTS_LOGGING_LOG_BACKEND_H_
2#define AOS_EVENTS_LOGGING_LOG_BACKEND_H_
3
4#include <fcntl.h>
5#include <sys/types.h>
6#include <sys/uio.h>
7
8#include <memory>
9#include <string>
10#include <vector>
11
12#include "absl/types/span.h"
13#include "aos/time/time.h"
14
15namespace aos::logger {
16
17class WriteStats {
18 public:
19 // The maximum time for a single write call, or 0 if none have been performed.
20 std::chrono::nanoseconds max_write_time() const { return max_write_time_; }
21 // The number of bytes in the longest write call, or -1 if none have been
22 // performed.
23 int max_write_time_bytes() const { return max_write_time_bytes_; }
24 // The number of buffers in the longest write call, or -1 if none have been
25 // performed.
26 int max_write_time_messages() const { return max_write_time_messages_; }
27 // The total time spent in write calls.
28 std::chrono::nanoseconds total_write_time() const {
29 return total_write_time_;
30 }
31 // The total number of writes which have been performed.
32 int total_write_count() const { return total_write_count_; }
33 // The total number of messages which have been written.
34 int total_write_messages() const { return total_write_messages_; }
35 // The total number of bytes which have been written.
36 int total_write_bytes() const { return total_write_bytes_; }
37
38 void ResetStats() {
39 max_write_time_ = std::chrono::nanoseconds::zero();
40 max_write_time_bytes_ = -1;
41 max_write_time_messages_ = -1;
42 total_write_time_ = std::chrono::nanoseconds::zero();
43 total_write_count_ = 0;
44 total_write_messages_ = 0;
45 total_write_bytes_ = 0;
46 }
47
48 void UpdateStats(aos::monotonic_clock::duration duration, ssize_t written,
49 int iovec_size) {
50 if (duration > max_write_time_) {
51 max_write_time_ = duration;
52 max_write_time_bytes_ = written;
53 max_write_time_messages_ = iovec_size;
54 }
55 total_write_time_ += duration;
56 ++total_write_count_;
57 total_write_messages_ += iovec_size;
58 total_write_bytes_ += written;
59 }
60
61 private:
62 std::chrono::nanoseconds max_write_time_ = std::chrono::nanoseconds::zero();
63 int max_write_time_bytes_ = -1;
64 int max_write_time_messages_ = -1;
65 std::chrono::nanoseconds total_write_time_ = std::chrono::nanoseconds::zero();
66 int total_write_count_ = 0;
67 int total_write_messages_ = 0;
68 int total_write_bytes_ = 0;
69};
70
71// Currently, all write operations only cares about out-of-space error. This is
72// a simple representation of write result.
73enum class WriteCode { kOk, kOutOfSpace };
74
75struct WriteResult {
76 WriteCode code = WriteCode::kOk;
77 size_t messages_written = 0;
78};
79
80// FileHandler is a replacement for bare filename in log writing and reading
81// operations.
82//
83// There are a couple over-arching constraints on writing to keep track of.
84// 1) The kernel is both faster and more efficient at writing large, aligned
85// chunks with O_DIRECT set on the file. The alignment needed is specified
86// by kSector and is file system dependent.
87// 2) Not all encoders support generating round multiples of kSector of data.
88// Rather than burden the API for detecting when that is the case, we want
89// DetachedBufferWriter to be as efficient as it can at writing what given.
90// 3) Some files are small and not updated frequently. They need to be
91// flushed or we will lose data on power off. It is most efficient to write
92// as much as we can aligned by kSector and then fall back to the non direct
93// method when it has been flushed.
94// 4) Not all filesystems support O_DIRECT, and different sizes may be optimal
95// for different machines. The defaults should work decently anywhere and
96// be tunable for faster systems.
97// TODO (Alexei): need 2 variations, to support systems with and without
98// O_DIRECT
99class FileHandler {
100 public:
101 // Size of an aligned sector used to detect when the data is aligned enough to
102 // use O_DIRECT instead.
103 static constexpr size_t kSector = 512u;
104
105 explicit FileHandler(std::string filename);
106 virtual ~FileHandler();
107
108 FileHandler(const FileHandler &) = delete;
109 FileHandler &operator=(const FileHandler &) = delete;
110
111 // Try to open file. App will crash if there are other than out-of-space
112 // problems with backend media.
113 virtual WriteCode OpenForWrite();
114
115 // Close the file handler.
116 virtual WriteCode Close();
117
118 // This will be true until Close() is called, unless the file couldn't be
119 // created due to running out of space.
120 bool is_open() const { return fd_ != -1; }
121
122 // Peeks messages from queue and writes it to file. Returns code when
123 // out-of-space problem occurred along with number of messages from queue that
124 // was written.
Austin Schuh3ebaf782023-04-07 16:03:28 -0700125 //
126 // The spans can be aligned or not, and can have any lengths. This code will
127 // write faster if the spans passed in start at aligned addresses, and are
128 // multiples of kSector long (and the data written so far is also a multiple
129 // of kSector length).
Alexei Strots01395492023-03-20 13:59:56 -0700130 virtual WriteResult Write(
131 const absl::Span<const absl::Span<const uint8_t>> &queue);
132
133 // TODO (Alexei): it is rather leaked abstraction.
134 // Path to the concrete log file.
135 std::string_view filename() const { return filename_; }
136
137 int fd() const { return fd_; }
138
139 // Get access to statistics related to the write operations.
140 WriteStats *WriteStatistics() { return &write_stats_; }
141
142 private:
143 // Enables O_DIRECT on the open file if it is supported. Cheap to call if it
144 // is already enabled.
145 void EnableDirect();
146 // Disables O_DIRECT on the open file if it is supported. Cheap to call if it
147 // is already disabld.
148 void DisableDirect();
149
150 bool ODirectEnabled() const { return !!(flags_ & O_DIRECT); }
151
152 // Writes a chunk of iovecs. aligned is true if all the data is kSector byte
153 // aligned and multiples of it in length, and counted_size is the sum of the
154 // sizes of all the chunks of data.
155 WriteCode WriteV(struct iovec *iovec_data, size_t iovec_size, bool aligned,
156 size_t counted_size);
157
158 const std::string filename_;
159
160 int fd_ = -1;
161
162 // List of iovecs to use with writev. This is a member variable to avoid
163 // churn.
164 std::vector<struct iovec> iovec_;
165
166 int total_write_bytes_ = 0;
167 int last_synced_bytes_ = 0;
168
169 bool supports_odirect_ = true;
170 int flags_ = 0;
171
172 WriteStats write_stats_;
173};
174
175// Class that decouples log writing and media (file system or memory). It is
176// handy to use for tests.
177class LogBackend {
178 public:
179 virtual ~LogBackend() = default;
180
181 // Request file-like object from the log backend. It maybe a file on a disk or
182 // in memory. id is usually generated by log namer and looks like name of the
183 // file within a log folder.
184 virtual std::unique_ptr<FileHandler> RequestFile(std::string_view id) = 0;
185};
186
187// Implements requests log files from file system.
188class FileBackend : public LogBackend {
189 public:
190 // base_name is the path to the folder where log files are.
191 explicit FileBackend(std::string_view base_name);
192 ~FileBackend() override = default;
193
194 // Request file from a file system. It is not open yet.
195 std::unique_ptr<FileHandler> RequestFile(std::string_view id) override;
196
197 private:
198 const std::string base_name_;
199 const std::string_view separator_;
200};
201
202// Provides a file backend that supports renaming of the base log folder and
203// temporary files.
204class RenamableFileBackend : public LogBackend {
205 public:
206 // Adds call to rename, when closed.
207 class RenamableFileHandler final : public FileHandler {
208 public:
209 RenamableFileHandler(RenamableFileBackend *owner, std::string filename)
210 : FileHandler(std::move(filename)), owner_(owner) {}
211 ~RenamableFileHandler() final = default;
212
213 // Returns false if not enough memory, true otherwise.
214 WriteCode Close() final;
215
216 private:
217 RenamableFileBackend *owner_;
218 };
219
220 explicit RenamableFileBackend(std::string_view base_name);
221 ~RenamableFileBackend() = default;
222
223 // Request file from a file system. It is not open yet.
224 std::unique_ptr<FileHandler> RequestFile(std::string_view id) override;
225
226 // TODO (Alexei): it is called by Logger, and left here for compatibility.
227 // Logger should not call it.
228 std::string_view base_name() { return base_name_; }
229
230 // If temp files are enabled, then this will write files with the .tmp
231 // suffix, and then rename them to the desired name after they are fully
232 // written.
233 //
234 // This is useful to enable incremental copying of the log files.
235 //
236 // Defaults to writing directly to the final filename.
237 void EnableTempFiles();
238
239 // Moves the current log location to the new name. Returns true if a change
240 // was made, false otherwise.
241 // Only renaming the folder is supported, not the file base name.
242 bool RenameLogBase(std::string_view new_base_name);
243
244 private:
245 // This function called after file closed, to adjust file names in case of
246 // base name was changed or temp files enabled.
247 WriteCode RenameFileAfterClose(std::string_view filename);
248
249 std::string base_name_;
250 std::string_view separator_;
251
252 bool use_temp_files_ = false;
253 std::string_view temp_suffix_;
254
255 std::string old_base_name_;
256};
257
258} // namespace aos::logger
259
260#endif // AOS_EVENTS_LOGGING_LOG_BACKEND_H_