blob: 6f79bc31be0bdeaeb4fb5a90b4cf344daadf96ba [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"
Philipp Schrader790cb542023-07-05 21:06:52 -070013
Alexei Strots01395492023-03-20 13:59:56 -070014#include "aos/time/time.h"
15
16namespace aos::logger {
17
18class WriteStats {
19 public:
20 // The maximum time for a single write call, or 0 if none have been performed.
21 std::chrono::nanoseconds max_write_time() const { return max_write_time_; }
22 // The number of bytes in the longest write call, or -1 if none have been
23 // performed.
24 int max_write_time_bytes() const { return max_write_time_bytes_; }
25 // The number of buffers in the longest write call, or -1 if none have been
26 // performed.
27 int max_write_time_messages() const { return max_write_time_messages_; }
28 // The total time spent in write calls.
29 std::chrono::nanoseconds total_write_time() const {
30 return total_write_time_;
31 }
32 // The total number of writes which have been performed.
33 int total_write_count() const { return total_write_count_; }
34 // The total number of messages which have been written.
35 int total_write_messages() const { return total_write_messages_; }
36 // The total number of bytes which have been written.
37 int total_write_bytes() const { return total_write_bytes_; }
38
39 void ResetStats() {
40 max_write_time_ = std::chrono::nanoseconds::zero();
41 max_write_time_bytes_ = -1;
42 max_write_time_messages_ = -1;
43 total_write_time_ = std::chrono::nanoseconds::zero();
44 total_write_count_ = 0;
45 total_write_messages_ = 0;
46 total_write_bytes_ = 0;
47 }
48
49 void UpdateStats(aos::monotonic_clock::duration duration, ssize_t written,
50 int iovec_size) {
51 if (duration > max_write_time_) {
52 max_write_time_ = duration;
53 max_write_time_bytes_ = written;
54 max_write_time_messages_ = iovec_size;
55 }
56 total_write_time_ += duration;
57 ++total_write_count_;
58 total_write_messages_ += iovec_size;
59 total_write_bytes_ += written;
60 }
61
62 private:
63 std::chrono::nanoseconds max_write_time_ = std::chrono::nanoseconds::zero();
64 int max_write_time_bytes_ = -1;
65 int max_write_time_messages_ = -1;
66 std::chrono::nanoseconds total_write_time_ = std::chrono::nanoseconds::zero();
67 int total_write_count_ = 0;
68 int total_write_messages_ = 0;
69 int total_write_bytes_ = 0;
70};
71
72// Currently, all write operations only cares about out-of-space error. This is
73// a simple representation of write result.
74enum class WriteCode { kOk, kOutOfSpace };
75
76struct WriteResult {
77 WriteCode code = WriteCode::kOk;
78 size_t messages_written = 0;
79};
80
Alexei Strotsbc082d82023-05-03 08:43:42 -070081// Interface that abstract writing to log from media.
82class LogSink {
83 public:
84 LogSink() = default;
85 virtual ~LogSink() = default;
86
87 LogSink(const LogSink &) = delete;
88 LogSink &operator=(const LogSink &) = delete;
89
90 // Try to open file. App will crash if there are other than out-of-space
91 // problems with backend media.
92 virtual WriteCode OpenForWrite() = 0;
93
94 // Close the file handler.
95 virtual WriteCode Close() = 0;
96
97 // Returns true if sink is open and need to be closed.
98 virtual bool is_open() const = 0;
99
100 // Peeks messages from queue and writes it to file. Returns code when
101 // out-of-space problem occurred along with number of messages from queue that
102 // was written.
103 virtual WriteResult Write(
104 const absl::Span<const absl::Span<const uint8_t>> &queue) = 0;
105
106 // Get access to statistics related to the write operations.
107 WriteStats *WriteStatistics() { return &write_stats_; }
108
109 // Name of the log sink.
110 virtual std::string_view name() const = 0;
111
112 private:
113 WriteStats write_stats_;
114};
115
116// Source for iovec with an additional flag that pointer and size of data is
Alexei Strotsa0b99d72023-04-11 15:12:42 -0700117// aligned and be ready for O_DIRECT operation.
118struct AlignedIovec {
119 const uint8_t *data;
120 size_t size;
121 bool aligned;
122
123 AlignedIovec(const uint8_t *data, size_t size, bool aligned)
124 : data(data), size(size), aligned(aligned) {}
125};
126
127// Converts queue of pieces to write to the disk to the queue where every
128// element is either aligned for O_DIRECT operation or marked as not aligned.
129class QueueAligner {
130 public:
131 QueueAligner();
132
133 // Reads input queue and fills with aligned and unaligned pieces. It is easy
134 // to deal with smaller pieces and batch it during the write operation.
135 void FillAlignedQueue(
136 const absl::Span<const absl::Span<const uint8_t>> &queue);
137
138 const std::vector<AlignedIovec> &aligned_queue() const {
139 return aligned_queue_;
140 }
141
142 private:
143 std::vector<AlignedIovec> aligned_queue_;
144};
145
Alexei Strots01395492023-03-20 13:59:56 -0700146// FileHandler is a replacement for bare filename in log writing and reading
147// operations.
148//
149// There are a couple over-arching constraints on writing to keep track of.
150// 1) The kernel is both faster and more efficient at writing large, aligned
151// chunks with O_DIRECT set on the file. The alignment needed is specified
152// by kSector and is file system dependent.
153// 2) Not all encoders support generating round multiples of kSector of data.
154// Rather than burden the API for detecting when that is the case, we want
155// DetachedBufferWriter to be as efficient as it can at writing what given.
156// 3) Some files are small and not updated frequently. They need to be
157// flushed or we will lose data on power off. It is most efficient to write
158// as much as we can aligned by kSector and then fall back to the non direct
159// method when it has been flushed.
160// 4) Not all filesystems support O_DIRECT, and different sizes may be optimal
161// for different machines. The defaults should work decently anywhere and
162// be tunable for faster systems.
Alexei Strotsbc082d82023-05-03 08:43:42 -0700163class FileHandler : public LogSink {
Alexei Strots01395492023-03-20 13:59:56 -0700164 public:
165 // Size of an aligned sector used to detect when the data is aligned enough to
166 // use O_DIRECT instead.
167 static constexpr size_t kSector = 512u;
168
169 explicit FileHandler(std::string filename);
Alexei Strotsbc082d82023-05-03 08:43:42 -0700170 ~FileHandler() override;
Alexei Strots01395492023-03-20 13:59:56 -0700171
172 FileHandler(const FileHandler &) = delete;
173 FileHandler &operator=(const FileHandler &) = delete;
174
175 // Try to open file. App will crash if there are other than out-of-space
176 // problems with backend media.
Alexei Strotsbc082d82023-05-03 08:43:42 -0700177 WriteCode OpenForWrite() override;
Alexei Strots01395492023-03-20 13:59:56 -0700178
179 // Close the file handler.
Alexei Strotsbc082d82023-05-03 08:43:42 -0700180 WriteCode Close() override;
Alexei Strots01395492023-03-20 13:59:56 -0700181
182 // This will be true until Close() is called, unless the file couldn't be
183 // created due to running out of space.
Alexei Strotsbc082d82023-05-03 08:43:42 -0700184 bool is_open() const override { return fd_ != -1; }
Alexei Strots01395492023-03-20 13:59:56 -0700185
186 // Peeks messages from queue and writes it to file. Returns code when
187 // out-of-space problem occurred along with number of messages from queue that
188 // was written.
Austin Schuh3ebaf782023-04-07 16:03:28 -0700189 //
190 // The spans can be aligned or not, and can have any lengths. This code will
191 // write faster if the spans passed in start at aligned addresses, and are
192 // multiples of kSector long (and the data written so far is also a multiple
193 // of kSector length).
Alexei Strotsbc082d82023-05-03 08:43:42 -0700194 WriteResult Write(
195 const absl::Span<const absl::Span<const uint8_t>> &queue) override;
Alexei Strots01395492023-03-20 13:59:56 -0700196
Alexei Strotsbc082d82023-05-03 08:43:42 -0700197 // Name of the log sink mostly for informational purposes.
198 std::string_view name() const override { return filename_; }
Alexei Strots01395492023-03-20 13:59:56 -0700199
Alexei Strotsa0b99d72023-04-11 15:12:42 -0700200 // Number of bytes written in aligned mode. It is mostly for testing.
201 size_t written_aligned() const { return written_aligned_; }
202
Alexei Strotsbc082d82023-05-03 08:43:42 -0700203 protected:
204 // This is used by subclasses who need to access filename.
205 std::string_view filename() const { return filename_; }
206
Alexei Strots01395492023-03-20 13:59:56 -0700207 private:
208 // Enables O_DIRECT on the open file if it is supported. Cheap to call if it
209 // is already enabled.
210 void EnableDirect();
211 // Disables O_DIRECT on the open file if it is supported. Cheap to call if it
212 // is already disabld.
213 void DisableDirect();
214
215 bool ODirectEnabled() const { return !!(flags_ & O_DIRECT); }
216
Alexei Strotsa0b99d72023-04-11 15:12:42 -0700217 // Writes a chunk of iovecs. aligned is true if all the data is kSector byte
218 // aligned and multiples of it in length.
219 WriteCode WriteV(const std::vector<struct iovec> &iovec, bool aligned);
Alexei Strots01395492023-03-20 13:59:56 -0700220
221 const std::string filename_;
222
223 int fd_ = -1;
224
225 // List of iovecs to use with writev. This is a member variable to avoid
226 // churn.
227 std::vector<struct iovec> iovec_;
228
Alexei Strotsa0b99d72023-04-11 15:12:42 -0700229 QueueAligner queue_aligner_;
230
Alexei Strots01395492023-03-20 13:59:56 -0700231 int total_write_bytes_ = 0;
232 int last_synced_bytes_ = 0;
233
Alexei Strotsa0b99d72023-04-11 15:12:42 -0700234 size_t written_aligned_ = 0;
235
Alexei Strots01395492023-03-20 13:59:56 -0700236 bool supports_odirect_ = true;
237 int flags_ = 0;
Alexei Strots01395492023-03-20 13:59:56 -0700238};
239
240// Class that decouples log writing and media (file system or memory). It is
241// handy to use for tests.
242class LogBackend {
243 public:
244 virtual ~LogBackend() = default;
245
246 // Request file-like object from the log backend. It maybe a file on a disk or
247 // in memory. id is usually generated by log namer and looks like name of the
248 // file within a log folder.
Alexei Strotsbc082d82023-05-03 08:43:42 -0700249 virtual std::unique_ptr<LogSink> RequestFile(std::string_view id) = 0;
Alexei Strots01395492023-03-20 13:59:56 -0700250};
251
252// Implements requests log files from file system.
253class FileBackend : public LogBackend {
254 public:
255 // base_name is the path to the folder where log files are.
256 explicit FileBackend(std::string_view base_name);
257 ~FileBackend() override = default;
258
259 // Request file from a file system. It is not open yet.
Alexei Strotsbc082d82023-05-03 08:43:42 -0700260 std::unique_ptr<LogSink> RequestFile(std::string_view id) override;
Alexei Strots01395492023-03-20 13:59:56 -0700261
262 private:
263 const std::string base_name_;
264 const std::string_view separator_;
265};
266
267// Provides a file backend that supports renaming of the base log folder and
268// temporary files.
269class RenamableFileBackend : public LogBackend {
270 public:
271 // Adds call to rename, when closed.
272 class RenamableFileHandler final : public FileHandler {
273 public:
274 RenamableFileHandler(RenamableFileBackend *owner, std::string filename)
275 : FileHandler(std::move(filename)), owner_(owner) {}
276 ~RenamableFileHandler() final = default;
277
Alexei Strotscaf17d32023-04-03 22:31:11 -0700278 // Closes and if needed renames file.
Alexei Strots01395492023-03-20 13:59:56 -0700279 WriteCode Close() final;
280
281 private:
282 RenamableFileBackend *owner_;
283 };
284
285 explicit RenamableFileBackend(std::string_view base_name);
286 ~RenamableFileBackend() = default;
287
288 // Request file from a file system. It is not open yet.
Alexei Strotsbc082d82023-05-03 08:43:42 -0700289 std::unique_ptr<LogSink> RequestFile(std::string_view id) override;
Alexei Strots01395492023-03-20 13:59:56 -0700290
291 // TODO (Alexei): it is called by Logger, and left here for compatibility.
292 // Logger should not call it.
Alexei Strotscaf17d32023-04-03 22:31:11 -0700293 std::string_view base_name() const { return base_name_; }
Alexei Strots01395492023-03-20 13:59:56 -0700294
295 // If temp files are enabled, then this will write files with the .tmp
296 // suffix, and then rename them to the desired name after they are fully
297 // written.
298 //
299 // This is useful to enable incremental copying of the log files.
300 //
301 // Defaults to writing directly to the final filename.
302 void EnableTempFiles();
303
304 // Moves the current log location to the new name. Returns true if a change
305 // was made, false otherwise.
306 // Only renaming the folder is supported, not the file base name.
307 bool RenameLogBase(std::string_view new_base_name);
308
309 private:
310 // This function called after file closed, to adjust file names in case of
311 // base name was changed or temp files enabled.
312 WriteCode RenameFileAfterClose(std::string_view filename);
313
314 std::string base_name_;
315 std::string_view separator_;
316
317 bool use_temp_files_ = false;
318 std::string_view temp_suffix_;
319
320 std::string old_base_name_;
321};
322
323} // namespace aos::logger
324
325#endif // AOS_EVENTS_LOGGING_LOG_BACKEND_H_