blob: 1b1417ec018d22bd09b2c216007efa634fa52895 [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
Alexei Strotsbc082d82023-05-03 08:43:42 -070080// Interface that abstract writing to log from media.
81class LogSink {
82 public:
83 LogSink() = default;
84 virtual ~LogSink() = default;
85
86 LogSink(const LogSink &) = delete;
87 LogSink &operator=(const LogSink &) = delete;
88
89 // Try to open file. App will crash if there are other than out-of-space
90 // problems with backend media.
91 virtual WriteCode OpenForWrite() = 0;
92
93 // Close the file handler.
94 virtual WriteCode Close() = 0;
95
96 // Returns true if sink is open and need to be closed.
97 virtual bool is_open() const = 0;
98
99 // Peeks messages from queue and writes it to file. Returns code when
100 // out-of-space problem occurred along with number of messages from queue that
101 // was written.
102 virtual WriteResult Write(
103 const absl::Span<const absl::Span<const uint8_t>> &queue) = 0;
104
105 // Get access to statistics related to the write operations.
106 WriteStats *WriteStatistics() { return &write_stats_; }
107
108 // Name of the log sink.
109 virtual std::string_view name() const = 0;
110
111 private:
112 WriteStats write_stats_;
113};
114
115// Source for iovec with an additional flag that pointer and size of data is
Alexei Strotsa0b99d72023-04-11 15:12:42 -0700116// aligned and be ready for O_DIRECT operation.
117struct AlignedIovec {
118 const uint8_t *data;
119 size_t size;
120 bool aligned;
121
122 AlignedIovec(const uint8_t *data, size_t size, bool aligned)
123 : data(data), size(size), aligned(aligned) {}
124};
125
126// Converts queue of pieces to write to the disk to the queue where every
127// element is either aligned for O_DIRECT operation or marked as not aligned.
128class QueueAligner {
129 public:
130 QueueAligner();
131
132 // Reads input queue and fills with aligned and unaligned pieces. It is easy
133 // to deal with smaller pieces and batch it during the write operation.
134 void FillAlignedQueue(
135 const absl::Span<const absl::Span<const uint8_t>> &queue);
136
137 const std::vector<AlignedIovec> &aligned_queue() const {
138 return aligned_queue_;
139 }
140
141 private:
142 std::vector<AlignedIovec> aligned_queue_;
143};
144
Alexei Strots01395492023-03-20 13:59:56 -0700145// FileHandler is a replacement for bare filename in log writing and reading
146// operations.
147//
148// There are a couple over-arching constraints on writing to keep track of.
149// 1) The kernel is both faster and more efficient at writing large, aligned
150// chunks with O_DIRECT set on the file. The alignment needed is specified
151// by kSector and is file system dependent.
152// 2) Not all encoders support generating round multiples of kSector of data.
153// Rather than burden the API for detecting when that is the case, we want
154// DetachedBufferWriter to be as efficient as it can at writing what given.
155// 3) Some files are small and not updated frequently. They need to be
156// flushed or we will lose data on power off. It is most efficient to write
157// as much as we can aligned by kSector and then fall back to the non direct
158// method when it has been flushed.
159// 4) Not all filesystems support O_DIRECT, and different sizes may be optimal
160// for different machines. The defaults should work decently anywhere and
161// be tunable for faster systems.
Alexei Strotsbc082d82023-05-03 08:43:42 -0700162class FileHandler : public LogSink {
Alexei Strots01395492023-03-20 13:59:56 -0700163 public:
164 // Size of an aligned sector used to detect when the data is aligned enough to
165 // use O_DIRECT instead.
166 static constexpr size_t kSector = 512u;
167
168 explicit FileHandler(std::string filename);
Alexei Strotsbc082d82023-05-03 08:43:42 -0700169 ~FileHandler() override;
Alexei Strots01395492023-03-20 13:59:56 -0700170
171 FileHandler(const FileHandler &) = delete;
172 FileHandler &operator=(const FileHandler &) = delete;
173
174 // Try to open file. App will crash if there are other than out-of-space
175 // problems with backend media.
Alexei Strotsbc082d82023-05-03 08:43:42 -0700176 WriteCode OpenForWrite() override;
Alexei Strots01395492023-03-20 13:59:56 -0700177
178 // Close the file handler.
Alexei Strotsbc082d82023-05-03 08:43:42 -0700179 WriteCode Close() override;
Alexei Strots01395492023-03-20 13:59:56 -0700180
181 // This will be true until Close() is called, unless the file couldn't be
182 // created due to running out of space.
Alexei Strotsbc082d82023-05-03 08:43:42 -0700183 bool is_open() const override { return fd_ != -1; }
Alexei Strots01395492023-03-20 13:59:56 -0700184
185 // Peeks messages from queue and writes it to file. Returns code when
186 // out-of-space problem occurred along with number of messages from queue that
187 // was written.
Austin Schuh3ebaf782023-04-07 16:03:28 -0700188 //
189 // The spans can be aligned or not, and can have any lengths. This code will
190 // write faster if the spans passed in start at aligned addresses, and are
191 // multiples of kSector long (and the data written so far is also a multiple
192 // of kSector length).
Alexei Strotsbc082d82023-05-03 08:43:42 -0700193 WriteResult Write(
194 const absl::Span<const absl::Span<const uint8_t>> &queue) override;
Alexei Strots01395492023-03-20 13:59:56 -0700195
Alexei Strotsbc082d82023-05-03 08:43:42 -0700196 // Name of the log sink mostly for informational purposes.
197 std::string_view name() const override { return filename_; }
Alexei Strots01395492023-03-20 13:59:56 -0700198
Alexei Strotsa0b99d72023-04-11 15:12:42 -0700199 // Number of bytes written in aligned mode. It is mostly for testing.
200 size_t written_aligned() const { return written_aligned_; }
201
Alexei Strotsbc082d82023-05-03 08:43:42 -0700202 protected:
203 // This is used by subclasses who need to access filename.
204 std::string_view filename() const { return filename_; }
205
Alexei Strots01395492023-03-20 13:59:56 -0700206 private:
207 // Enables O_DIRECT on the open file if it is supported. Cheap to call if it
208 // is already enabled.
209 void EnableDirect();
210 // Disables O_DIRECT on the open file if it is supported. Cheap to call if it
211 // is already disabld.
212 void DisableDirect();
213
214 bool ODirectEnabled() const { return !!(flags_ & O_DIRECT); }
215
Alexei Strotsa0b99d72023-04-11 15:12:42 -0700216 // Writes a chunk of iovecs. aligned is true if all the data is kSector byte
217 // aligned and multiples of it in length.
218 WriteCode WriteV(const std::vector<struct iovec> &iovec, bool aligned);
Alexei Strots01395492023-03-20 13:59:56 -0700219
220 const std::string filename_;
221
222 int fd_ = -1;
223
224 // List of iovecs to use with writev. This is a member variable to avoid
225 // churn.
226 std::vector<struct iovec> iovec_;
227
Alexei Strotsa0b99d72023-04-11 15:12:42 -0700228 QueueAligner queue_aligner_;
229
Alexei Strots01395492023-03-20 13:59:56 -0700230 int total_write_bytes_ = 0;
231 int last_synced_bytes_ = 0;
232
Alexei Strotsa0b99d72023-04-11 15:12:42 -0700233 size_t written_aligned_ = 0;
234
Alexei Strots01395492023-03-20 13:59:56 -0700235 bool supports_odirect_ = true;
236 int flags_ = 0;
Alexei Strots01395492023-03-20 13:59:56 -0700237};
238
239// Class that decouples log writing and media (file system or memory). It is
240// handy to use for tests.
241class LogBackend {
242 public:
243 virtual ~LogBackend() = default;
244
245 // Request file-like object from the log backend. It maybe a file on a disk or
246 // in memory. id is usually generated by log namer and looks like name of the
247 // file within a log folder.
Alexei Strotsbc082d82023-05-03 08:43:42 -0700248 virtual std::unique_ptr<LogSink> RequestFile(std::string_view id) = 0;
Alexei Strots01395492023-03-20 13:59:56 -0700249};
250
251// Implements requests log files from file system.
252class FileBackend : public LogBackend {
253 public:
254 // base_name is the path to the folder where log files are.
255 explicit FileBackend(std::string_view base_name);
256 ~FileBackend() override = default;
257
258 // Request file from a file system. It is not open yet.
Alexei Strotsbc082d82023-05-03 08:43:42 -0700259 std::unique_ptr<LogSink> RequestFile(std::string_view id) override;
Alexei Strots01395492023-03-20 13:59:56 -0700260
261 private:
262 const std::string base_name_;
263 const std::string_view separator_;
264};
265
266// Provides a file backend that supports renaming of the base log folder and
267// temporary files.
268class RenamableFileBackend : public LogBackend {
269 public:
270 // Adds call to rename, when closed.
271 class RenamableFileHandler final : public FileHandler {
272 public:
273 RenamableFileHandler(RenamableFileBackend *owner, std::string filename)
274 : FileHandler(std::move(filename)), owner_(owner) {}
275 ~RenamableFileHandler() final = default;
276
Alexei Strotscaf17d32023-04-03 22:31:11 -0700277 // Closes and if needed renames file.
Alexei Strots01395492023-03-20 13:59:56 -0700278 WriteCode Close() final;
279
280 private:
281 RenamableFileBackend *owner_;
282 };
283
284 explicit RenamableFileBackend(std::string_view base_name);
285 ~RenamableFileBackend() = default;
286
287 // Request file from a file system. It is not open yet.
Alexei Strotsbc082d82023-05-03 08:43:42 -0700288 std::unique_ptr<LogSink> RequestFile(std::string_view id) override;
Alexei Strots01395492023-03-20 13:59:56 -0700289
290 // TODO (Alexei): it is called by Logger, and left here for compatibility.
291 // Logger should not call it.
Alexei Strotscaf17d32023-04-03 22:31:11 -0700292 std::string_view base_name() const { return base_name_; }
Alexei Strots01395492023-03-20 13:59:56 -0700293
294 // If temp files are enabled, then this will write files with the .tmp
295 // suffix, and then rename them to the desired name after they are fully
296 // written.
297 //
298 // This is useful to enable incremental copying of the log files.
299 //
300 // Defaults to writing directly to the final filename.
301 void EnableTempFiles();
302
303 // Moves the current log location to the new name. Returns true if a change
304 // was made, false otherwise.
305 // Only renaming the folder is supported, not the file base name.
306 bool RenameLogBase(std::string_view new_base_name);
307
308 private:
309 // This function called after file closed, to adjust file names in case of
310 // base name was changed or temp files enabled.
311 WriteCode RenameFileAfterClose(std::string_view filename);
312
313 std::string base_name_;
314 std::string_view separator_;
315
316 bool use_temp_files_ = false;
317 std::string_view temp_suffix_;
318
319 std::string old_base_name_;
320};
321
322} // namespace aos::logger
323
324#endif // AOS_EVENTS_LOGGING_LOG_BACKEND_H_