From: Michal Bloch Date: Fri, 26 Aug 2022 15:54:18 +0000 (+0200) Subject: Integrate zlogutil into libdlogutil X-Git-Tag: accepted/tizen/unified/20220917.094322~1 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=bed40419621f9237c6a6c83ac4b42ede6ff4ae32;p=platform%2Fcore%2Fsystem%2Fdlog.git Integrate zlogutil into libdlogutil Change-Id: I910ba8eda65721afc1a687b4d76b927587cde09b Signed-off-by: Michal Bloch --- diff --git a/Makefile.am b/Makefile.am index 445ccd3..663726f 100644 --- a/Makefile.am +++ b/Makefile.am @@ -69,6 +69,7 @@ libdlogutil_la_SOURCES = \ src/libdlogutil/fd_info.c \ src/libdlogutil/fdi_pipe.c \ src/libdlogutil/fdi_logger.c \ + src/libdlogutil/fdi_zero_copy.c \ src/shared/backend_androidlogger.c \ src/shared/logcommon.c \ src/shared/logprint.c \ @@ -104,35 +105,6 @@ dlogutil_SOURCES = \ src/shared/ptrs_list.c \ src/shared/util_parser.c -bin_PROGRAMS += zlogutil - -zlogutil_CFLAGS = \ - $(AM_CFLAGS) \ - -fPIE - -zlogutil_CXXFLAGS = \ - $(AM_CFLAGS) \ - -fPIE - -zlogutil_LDFLAGS = \ - $(AM_LDFLAGS) \ - -pie - -zlogutil_SOURCES = \ - src/zlogutil/zlogutil.cpp \ - src/zlogutil/zlog_filter.cpp \ - src/zlogutil/zlog_list.cpp \ - src/zlogutil/zlog_file_writer.cpp \ - src/shared/ptrs_list.c \ - src/shared/logcommon.c \ - src/shared/logconfig.c \ - src/shared/parsers.c \ - src/shared/translate_syslog.c \ - src/shared/queued_entry.c \ - src/shared/queued_entry_timestamp.c \ - src/shared/logprint.c - - libexec_PROGRAMS = dlog-log-critical dlog_log_critical_CXXFLAGS = \ @@ -703,6 +675,7 @@ src_tests_logutil_pos_SOURCES = src/tests/logutil_pos.c \ src/libdlogutil/fd_info.c \ src/libdlogutil/fdi_pipe.c \ src/libdlogutil/fdi_logger.c \ + src/libdlogutil/fdi_zero_copy.c \ src/shared/backend_androidlogger.c \ src/shared/logcommon.c \ src/shared/logprint.c \ @@ -721,6 +694,7 @@ src_tests_logutil_neg_SOURCES = src/tests/logutil_neg.c \ src/libdlogutil/fd_info.c \ src/libdlogutil/fdi_pipe.c \ src/libdlogutil/fdi_logger.c \ + src/libdlogutil/fdi_zero_copy.c \ src/shared/backend_androidlogger.c \ src/shared/logcommon.c \ src/shared/logprint.c \ @@ -823,6 +797,7 @@ src_tests_libdlogutil_neg_SOURCES = src/tests/libdlogutil_neg.c \ src/shared/parsers.c \ src/libdlogutil/fdi_pipe.c \ src/libdlogutil/fdi_logger.c \ + src/libdlogutil/fdi_zero_copy.c \ src/shared/backend_androidlogger.c \ src/shared/queued_entry.c \ src/shared/translate_syslog.c @@ -844,6 +819,7 @@ src_tests_libdlogutil_pos_SOURCES = src/tests/libdlogutil_pos.c \ src/shared/parsers.c \ src/libdlogutil/fdi_pipe.c \ src/libdlogutil/fdi_logger.c \ + src/libdlogutil/fdi_zero_copy.c \ src/shared/backend_androidlogger.c \ src/shared/queued_entry.c \ src/shared/translate_syslog.c diff --git a/include/logcommon.h b/include/logcommon.h index 3c77d61..47897a0 100644 --- a/include/logcommon.h +++ b/include/logcommon.h @@ -61,6 +61,10 @@ #define DEFAULT_WAIT_PIPE_MS 500 #define MIN_WAIT_PIPE_MS 7 +#define NSEC_PER_SEC 1000000000 +#define NSEC_PER_MSEC 1000000 +#define USEC_PER_MSEC 1000 + #ifdef __cplusplus extern "C" { #endif diff --git a/include/zero_copy_backend.h b/include/zero_copy_backend.h index dd9035e..07a7a9e 100644 --- a/include/zero_copy_backend.h +++ b/include/zero_copy_backend.h @@ -3,6 +3,19 @@ #pragma once +#include + #include +#include +#include + #define ZERO_COPY_DEVICE_NAME "/dev/zlogger" + +static inline uint64_t get_zlog_clock(void) +{ + struct timespec begin; + clock_gettime(CLOCK_MONOTONIC, &begin); + return (uint64_t)begin.tv_nsec + (uint64_t)begin.tv_sec * NSEC_PER_SEC; +} + diff --git a/packaging/dlog.spec b/packaging/dlog.spec index dc83a9e..b14030e 100644 --- a/packaging/dlog.spec +++ b/packaging/dlog.spec @@ -202,8 +202,6 @@ install -m 0644 gcov-obj/* %{buildroot}%{_datadir}/gcov/obj/%{name} %manifest dlog.manifest %license LICENSE.APACHE2.0 LICENSE.MIT %attr(750,log,log) %{_bindir}/dlogutil -# Zlogutil is a temporary solution, to be integrated into Dlogutil -%attr(750,log,log) %{_bindir}/zlogutil %attr(755,log,log) %{_bindir}/dlogsend %attr(750,log,log) %{_bindir}/dlog_cleanup %attr(750,log,log) %{_bindir}/dlogmetrics diff --git a/src/libdlog/log_zero_copy.c b/src/libdlog/log_zero_copy.c index 1436db5..e3ab035 100644 --- a/src/libdlog/log_zero_copy.c +++ b/src/libdlog/log_zero_copy.c @@ -68,13 +68,6 @@ static inline int alloc_block(int tid) return r; } -static inline uint64_t get_local_clock(void) -{ - struct timespec begin; - clock_gettime(CLOCK_MONOTONIC, &begin); - return (uint64_t)begin.tv_nsec + ((uint64_t)begin.tv_sec) * 1000 * 1000 * 1000; -} - static inline struct zlogger_block *get_valid_block(int tid, size_t len) { if (blk != 0) { @@ -95,7 +88,11 @@ static int zero_copy_write(log_id_t log_id, log_priority prio, const char *tag, { // TODO: log_id and tp_mono ignored for now - uint64_t ts = get_local_clock(); + /* TODO: drop zlogger_entry and just write + * dlogutil_entry directly. Needs kernel support + * for stdout logs though. */ + + uint64_t ts = get_zlog_clock(); const int tid = gettid(); size_t hd_size = sizeof(struct zlogger_entry); size_t prio_size = 1; diff --git a/src/libdlogutil/fd_info.h b/src/libdlogutil/fd_info.h index 7753f4a..279ffff 100644 --- a/src/libdlogutil/fd_info.h +++ b/src/libdlogutil/fd_info.h @@ -74,6 +74,10 @@ struct fd_ops { /// Check whether there will be no more logs on this FD bool (*eof)(const struct fd_info *fdi); + /// Check how long until the FDI is ready to read (in ms). + /// This op can be NULL, in which case its main FD will be handled via epoll. + int (*poll_timeout)(const struct fd_info *fdi); + // Whether to filter logs on the client bool should_filter; }; diff --git a/src/libdlogutil/fdi_logger.c b/src/libdlogutil/fdi_logger.c index 092b719..64b11c9 100644 --- a/src/libdlogutil/fdi_logger.c +++ b/src/libdlogutil/fdi_logger.c @@ -339,5 +339,6 @@ const struct fd_ops ops_logger = { .get_capacity = logger_get_capacity, .get_usage = logger_get_usage, .eof = logger_eof, + .poll_timeout = NULL, .should_filter = true, }; diff --git a/src/libdlogutil/fdi_pipe.c b/src/libdlogutil/fdi_pipe.c index b002e96..b753c19 100644 --- a/src/libdlogutil/fdi_pipe.c +++ b/src/libdlogutil/fdi_pipe.c @@ -407,5 +407,6 @@ const struct fd_ops ops_pipe = { .get_capacity = pipe_get_capacity, .get_usage = pipe_get_usage, .eof = pipe_eof, + .poll_timeout = NULL, .should_filter = false, }; diff --git a/src/libdlogutil/fdi_zero_copy.c b/src/libdlogutil/fdi_zero_copy.c new file mode 100644 index 0000000..48a9939 --- /dev/null +++ b/src/libdlogutil/fdi_zero_copy.c @@ -0,0 +1,394 @@ +/* MIT License + * + * Copyright (c) 2022 Samsung Electronics Co., Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is furnished + * to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. */ + +// C +#include +#include + +// POSIX +#include + +// DLog +#include +#include +#include + +// dlogutil +#include "fd_info.h" +#include "fdi_zero_copy.h" + +static const uint64_t ZLOGGER_MONITOR_INTERVAL = 1 * NSEC_PER_SEC; + +static int zero_copy_get_capacity(struct fd_info *fdi, unsigned int *capacity) +{ + /* The kernel doesn't seem to have an interface to read this, + * but the constant has to be correct for everything else to + * work correctly, anyway. */ + + *capacity = (unsigned int) ZLOGGER_BUFFER_SIZE; + return 0; +} + +static int zero_copy_get_usage(struct fd_info *fdi, unsigned int *usage) +{ + /* Semantics not obvious. There are two relevant values really, + * logs inside a block and blocks themselves (in other backends + * each log is its own block and can be purged and allocated + * to programs separately). Since this backend is intended not + * to be read until something happens, this function is not too + * useful (since there's hopefully 100% usage by the time). */ + + return -ENOTSUP; +} + +static int zero_copy_clear(struct fd_info *fdi) +{ + /* No kernel interface. + * Production-only backend. */ + + return -ENOTSUP; +} + +static int zero_copy_create(struct fd_info *fdi, const struct log_config *conf, log_id_t id, list_head *used_paths, log_id_t *aliased) +{ + // We can't clear, so the flag is R not RW + fdi->fd = open(ZERO_COPY_DEVICE_NAME, O_RDONLY | O_CLOEXEC); + if (fdi->fd < 0) + return -errno; + + struct zero_copy_priv_data *const zpd = malloc(sizeof *zpd); + if (!zpd) { + close(fdi->fd); + fdi->fd = -1; + return -ENOMEM; + } + + zpd->this_read_ts = 0; + zpd->prev_read_ts = 0; + zpd->items = NULL; + zpd->dump = false; + zpd->monitor = false; + zpd->eof = false; + + fdi->priv_data = zpd; + + return 0; +} + +static void free_entry_list(struct zlog_entry_list **items) +{ + struct zlog_entry_list *item = *items; + while (item) { + /* I guess they can't, I guess they won't, I guess they front! + * That's how I know my life is out of luck, fool. */ + struct zlog_entry_list *const front = item; + item = front->next; + free(front->entry); + free(front); + } + + *items = NULL; +} + +static void zero_copy_destroy(struct fd_info *fdi) +{ + struct zero_copy_priv_data *const zpd = (struct zero_copy_priv_data *)fdi->priv_data; + if (!zpd) + return; + + close(fdi->fd); + free_entry_list(&zpd->items); + free(zpd); +} + +static int zero_copy_prepare_print(struct fd_info *fdi, int dump, bool monitor, struct log_filter *filter_object, char *compress) +{ + /* The backend doesn't allow us to do anything other than read, + * so at least reading requires no preparatory steps. */ + + struct zero_copy_priv_data *const zpd = (struct zero_copy_priv_data *)fdi->priv_data; + zpd->dump = monitor ? 0 : dump; + zpd->monitor = monitor; + zpd->prev_read_ts = monitor ? get_zlog_clock() : 0; + zpd->this_read_ts = zpd->prev_read_ts; + + return 0; +} + +static bool zero_copy_eof(const struct fd_info *fdi) +{ + const struct zero_copy_priv_data *const zpd = (const struct zero_copy_priv_data *)fdi->priv_data; + return zpd->eof; +} + +static int zero_copy_poll_timeout(const struct fd_info *fdi) +{ + const struct zero_copy_priv_data *const zpd = (const struct zero_copy_priv_data *)fdi->priv_data; + if (zpd->eof) + return -1; + + const uint64_t now = get_zlog_clock(); + const uint64_t time_since_last_read = zpd->prev_read_ts - now; + if (time_since_last_read > ZLOGGER_MONITOR_INTERVAL) + return 0; + + const uint64_t millisec_remaining = (ZLOGGER_MONITOR_INTERVAL - time_since_last_read) / NSEC_PER_MSEC; + if (millisec_remaining > INT_MAX) + return -1; + return millisec_remaining; +} + +static dlogutil_entry_s *alloc_dlogutil_entry_from_zlog_entry(struct zlogger_entry *ze) +{ + /* TODO: possibly this should be in shared/queued_entry.h? + * I don't like how that one is responsible for everything though. */ + + if (ze->len < 4) // prio, tag, nul, msg + return NULL; + + int32_t tag_len = 0; + for (int i = 1; i < ze->len; ++i) { + if (ze->data[i] != '\0') + continue; + tag_len = i - 1; + break; + } + if (!tag_len) + return NULL; + + const uint16_t len = sizeof (dlogutil_entry_s) + ze->len - 1; // FIXME: potential overflow + dlogutil_entry_s *const de = malloc(len); + if (!de) + return NULL; + + memcpy(de->msg, ze->data + 1, ze->len - 1); + de->len = len; + + de-> sec_sent_mono = ze->time / NSEC_PER_SEC; + de->nsec_sent_mono = ze->time % NSEC_PER_SEC; + + /* Arguably these two are the same as SENT, but maybe + * it's best to cull the proliferation of timestamps */ + de->sec_recv_mono = 0; + de->nsec_recv_mono = -1; + + de->sec_sent_real = 0; + de->nsec_sent_real = -1; + de->sec_recv_real = 0; + de->nsec_recv_real = -1; + + de->priority = ze->data[0]; + de->tag_len = tag_len; + + return de; +} + +static void append_dlogutil_entry_to_item_list(dlogutil_entry_s *de, struct zlog_entry_list **items) +{ + struct zlog_entry_list *const item = malloc(sizeof *item); + if (!item) { + free(de); // must consoom + return; + } + + item->entry = de; + item->next = *items; + *items = item; +} + +static void load_single_device(struct zlogger_block *zb, struct zlog_entry_list **items, uint64_t min_timestamp, uint64_t max_timestamp) +{ + /* TODO: document this mumbo jumbo. + * Requires expert earth magic to understand, probably. */ + + static size_t const BLOCK_COUNT = ZLOGGER_MAP_SIZE / sizeof *zb; + for (size_t i = 0; i < BLOCK_COUNT; i++, zb++) { + if (zb->head.offset > ZLOGGER_DATA_MAX) + continue; + + struct zlogger_entry *ze = (struct zlogger_entry *) zb->data; + + while ((uintptr_t) ze < (uintptr_t) zb->data + zb->head.offset) { + if (ze->len == 0) + break; + + if ((uintptr_t) ze + sizeof *ze + ze->len > (uintptr_t) zb->data + zb->head.offset) + break; + + unsigned short *const slt = (unsigned short *)&ze->time; + unsigned short const CRC = slt[0] + slt[1] + slt[2] + slt[3]; // TODO: zero_copy_backend.h, get_crc(ze) { return x >> 16 + x >> 48 ... } + if (CRC != ze->CRC) + break; + + if (ze->time >= min_timestamp && ze->time < max_timestamp) { + dlogutil_entry_s *const de = alloc_dlogutil_entry_from_zlog_entry(ze); + if (de) { + de->pid = zb->head.pid; + de->tid = zb->head.tid; + append_dlogutil_entry_to_item_list(de, items); + } else { + // TODO: consider -ENOMEM or something + } + } + + ze = (struct zlogger_entry *) ((uintptr_t) ze + sizeof *ze + ze->len); + } + } +} + +static void sort_items_list(struct zlog_entry_list **items) +{ + /* Stable radix sort with 256 buckets, + * done for each byte of the timestamp. */ + + struct zlog_entry_list *item_group = *items; + for (int step = 0; step < 64; step += 8) { + struct zlog_entry_list *begin[256]; + struct zlog_entry_list * end[256]; + memset(begin, 0, sizeof begin); + memset(end, 0, sizeof end); + + struct zlog_entry_list *item = item_group; + while (item) { + struct zlog_entry_list *const next = item->next; + + /* TODO: it would be good not to have to recalculate this. + * Perhaps have the item struct contain entry, next, timestamp? + * Maybe it's fine though, math is cheap. */ + const uint64_t timestamp64 = item->entry->sec_sent_mono * NSEC_PER_SEC + item->entry->nsec_sent_mono; + + int const bucket = (timestamp64 >> step) & 0xFF; + if (!begin[bucket]) + begin[bucket] = item; + else + end[bucket]->next = item; + + item->next = NULL; + end[bucket] = item; + item = next; + } + + struct zlog_entry_list *prev = NULL; + struct zlog_entry_list *next = NULL; + for (int bucket = 0; bucket < 256; bucket++) { + if (!begin[bucket]) + continue; + + if (!prev) { + prev = begin[bucket]; + next = end[bucket]; + continue; + } + + next->next = begin[bucket]; + next = end[bucket]; + } + item_group = prev; + } + *items = item_group; +} + +static int zero_copy_read(struct fd_info *fdi) +{ + struct zero_copy_priv_data *const zpd = (struct zero_copy_priv_data *)fdi->priv_data; + + if (zpd->items || zpd->eof) + return -EAGAIN; + + /* In theory logs can fit in the crack between + * the two time windows. This is mostly fine as + * storage is not robust anyway and the primary + * use case for this backend is dump-oriented. */ + const uint64_t now = get_zlog_clock(); + zpd->prev_read_ts = zpd->this_read_ts; + zpd->this_read_ts = now; + + for (size_t i = 0; i < ZLOGGER_DEVICE_COUNT; ++i) { + void *const addr = mmap(NULL, ZLOGGER_MAP_SIZE, PROT_READ, MAP_SHARED, fdi->fd, ZLOGGER_MAP_SIZE * i); + if (addr == MAP_FAILED) + break; + load_single_device(addr, &zpd->items, zpd->prev_read_ts, now); + munmap(addr, ZLOGGER_MAP_SIZE); + } + + sort_items_list(&zpd->items); + + if (zpd->dump) + zpd->eof = true; + + /* In theory this should return bytes read, + * but we don't really track it and it's not + * used anyway as long as it's > 0. */ + return 1; +} + +static dlogutil_entry_s *zero_copy_extract_entry(struct fd_info *fdi) +{ + struct zero_copy_priv_data *const zpd = (struct zero_copy_priv_data *) fdi->priv_data; + if (!zpd->items) + return NULL; + + struct zlog_entry_list *const item = zpd->items; + zpd->items = item->next; + + dlogutil_entry_s *const entry = item->entry; + free(item); + + if (zpd->dump) { + -- zpd->dump; + if (!zpd->dump) + free_entry_list(&zpd->items); + } + + return entry; +} + +static const dlogutil_entry_s *zero_copy_peek_entry(const struct fd_info *fdi) +{ + const struct zero_copy_priv_data *const zpd = (const struct zero_copy_priv_data *)fdi->priv_data; + if (!zpd->items) + return NULL; + + return zpd->items->entry; +} + +static bool zero_copy_has_log(struct fd_info *fdi) +{ + const struct zero_copy_priv_data *const zpd = (const struct zero_copy_priv_data *)fdi->priv_data; + return zpd->items != NULL; +} + +const struct fd_ops ops_zero_copy = { + .create = zero_copy_create, + .destroy = zero_copy_destroy, + .read = zero_copy_read, + .has_log = zero_copy_has_log, + .extract_entry = zero_copy_extract_entry, + .peek_entry = zero_copy_peek_entry, + .prepare_print = zero_copy_prepare_print, + .clear = zero_copy_clear, + .get_capacity = zero_copy_get_capacity, + .get_usage = zero_copy_get_usage, + .eof = zero_copy_eof, + .poll_timeout = zero_copy_poll_timeout, + .should_filter = true, +}; diff --git a/src/libdlogutil/fdi_zero_copy.h b/src/libdlogutil/fdi_zero_copy.h new file mode 100644 index 0000000..fad75d6 --- /dev/null +++ b/src/libdlogutil/fdi_zero_copy.h @@ -0,0 +1,44 @@ +/* MIT License + * + * Copyright (c) 2022 Samsung Electronics Co., Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is furnished + * to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. */ + +#pragma once + +#include "fd_info.h" + +extern const struct fd_ops ops_zero_copy; + +/* Not using dlog ptrs_list because that one is opaque + * while the sorting algo inspects the list contents. */ +struct zlog_entry_list { + dlogutil_entry_s *entry; + struct zlog_entry_list *next; +}; + +struct zero_copy_priv_data { + uint64_t prev_read_ts; + uint64_t this_read_ts; + struct zlog_entry_list *items; + int dump; + bool monitor; + bool eof; +}; + diff --git a/src/libdlogutil/lib.c b/src/libdlogutil/lib.c index 0b7bbd8..d1c60e2 100644 --- a/src/libdlogutil/lib.c +++ b/src/libdlogutil/lib.c @@ -177,17 +177,16 @@ EXPORT_API int dlogutil_config_connect(dlogutil_config_s *config, dlogutil_state return TIZEN_ERROR_IO_ERROR; if (backend == BACKEND_ZERO_COPY) { - /* At the moment, the zero-copy backend has a separate - * binary, `zlogutil`. It is in the process of being - * integrated into regular dlogutil, but not there yet. */ - return TIZEN_ERROR_NOT_SUPPORTED; + /* FIXME! There are no buffers under + * zero-copy backend, what now? */ + config->buffers = 1 << LOG_ID_MAIN; } __attribute__ ((cleanup(fdi_array_free))) struct fd_info **fdi_ptrs = NULL; log_id_t aliased[LOG_ID_MAX]; for (int i = 0; i < LOG_ID_MAX; ++i) aliased[i] = LOG_ID_INVALID; - int fdi_cnt = create_initial_fdis(&fdi_ptrs, config->buffers, backend == BACKEND_PIPE, config->mode == DLOGUTIL_MODE_COMPRESSED_MEMORY_DUMP, &conf, aliased); + int fdi_cnt = create_initial_fdis(&fdi_ptrs, config->buffers, backend, config->mode == DLOGUTIL_MODE_COMPRESSED_MEMORY_DUMP, &conf, aliased); if (fdi_cnt < 0) return TIZEN_ERROR_IO_ERROR; if (fdi_cnt == 0) diff --git a/src/libdlogutil/logretrieve.c b/src/libdlogutil/logretrieve.c index 842488b..7ef5162 100644 --- a/src/libdlogutil/logretrieve.c +++ b/src/libdlogutil/logretrieve.c @@ -26,9 +26,11 @@ #include "fdi_pipe.h" #include "fdi_logger.h" +#include "fdi_zero_copy.h" #include "logretrieve.h" // C +#include #include bool all_buffers_known_disabled(const struct log_config *conf, int buffers) @@ -38,7 +40,13 @@ bool all_buffers_known_disabled(const struct log_config *conf, int buffers) && !log_config_get_boolean(conf, "handle_kmsg", true); } -int create_initial_fdis(struct fd_info ***fdis, int enabled_buffers, bool is_pipe, bool is_compressed_memory, const struct log_config *conf, log_id_t aliased[LOG_ID_MAX]) +static const struct fd_ops *const backend_ops[] = { + [BACKEND_PIPE] = &ops_pipe, + [BACKEND_ANDROID_LOGGER] = &ops_logger, + [BACKEND_ZERO_COPY] = &ops_zero_copy, +}; + +int create_initial_fdis(struct fd_info ***fdis, int enabled_buffers, backend_t backend, bool is_compressed_memory, const struct log_config *conf, log_id_t aliased[LOG_ID_MAX]) { assert(fdis); assert(conf); @@ -78,8 +86,7 @@ int create_initial_fdis(struct fd_info ***fdis, int enabled_buffers, bool is_pip struct fd_info *fdi; if (is_compressed_memory) { /* Ignore `is_pipe`. This feature is done - * via pipes always, even on the Android - * Logger backend. */ + * via pipes always, even on other backends. */ fdi = fdi_create(&ops_pipe, i); } else { switch (i) { @@ -88,7 +95,7 @@ int create_initial_fdis(struct fd_info ***fdis, int enabled_buffers, bool is_pip fdi = fdi_create(&ops_pipe, i); break; default: - fdi = fdi_create(is_pipe ? &ops_pipe : &ops_logger, i); + fdi = fdi_create(backend_ops[backend], i); break; } } @@ -202,8 +209,13 @@ static int state_prepare_single_print(dlogutil_state_s *state, int nfds, char *c .events = EPOLLIN, }; state->enabled[nfds] = true; - epoll_ctl(state->epollfd, EPOLL_CTL_ADD, fd, &ev); - ++state->epoll_cnt; + + if (!state->data_fds[nfds]->ops->poll_timeout) { + epoll_ctl(state->epollfd, EPOLL_CTL_ADD, fd, &ev); + ++ state->epoll_cnt; + } else { + ++ state->non_epoll_cnt; + } return TIZEN_ERROR_NONE; } @@ -237,6 +249,7 @@ int dlogutil_state_init(dlogutil_state_s *state, struct fd_info ***data_fds_ptr, // TODO: optimize mode == DLOGUTIL_MODE_NONPRINTING case state->epollfd = -1; state->epoll_cnt = 0; + state->non_epoll_cnt = 0; state->enabled = NULL; state->filter_object = NULL; state->evs = NULL; @@ -280,9 +293,11 @@ int dlogutil_state_init(dlogutil_state_s *state, struct fd_info ***data_fds_ptr, return r; } - state->evs = calloc(state->epoll_cnt, sizeof *state->evs); - if (!state->evs) - return TIZEN_ERROR_OUT_OF_MEMORY; + if (state->epoll_cnt) { + state->evs = calloc(state->epoll_cnt, sizeof *state->evs); + if (!state->evs) + return TIZEN_ERROR_OUT_OF_MEMORY; + } return TIZEN_ERROR_NONE; } @@ -333,13 +348,21 @@ static void remove_drained_buffers(dlogutil_state_s *state) continue; state->enabled[i] = false; - epoll_ctl(state->epollfd, EPOLL_CTL_DEL, fdi->fd, NULL); - --state->epoll_cnt; + + if (!fdi->ops->poll_timeout) { + epoll_ctl(state->epollfd, EPOLL_CTL_DEL, fdi->fd, NULL); + -- state->epoll_cnt; + } else { + -- state->non_epoll_cnt; + } } } -static int refill_fdi_buffers(dlogutil_state_s *state, int timeout) +static int refill_fdi_buffers_regular(dlogutil_state_s *state, int timeout) { + if (!state->epoll_cnt) + return 0; + int const nfds = epoll_wait(state->epollfd, state->evs, state->epoll_cnt, timeout); if (nfds < 0) return -errno; @@ -354,6 +377,87 @@ static int refill_fdi_buffers(dlogutil_state_s *state, int timeout) return 0; } +static int how_long_would_we_have_to_wait(dlogutil_state_s *state) +{ + /* Pick the smallest timeout. We're using epoll timeout + * convention where -1 means infinite. This is mostly + * for future-proofing since the only client using this + * is the zero-copy backend which only has 1 FDI anyway. */ + + int ret = -1; + for (int i = 0; i < state->fd_count; ++i) { + if (!state->enabled[i]) + continue; + + const struct fd_info *const fdi = state->data_fds[i]; + if (fdi->ops->eof(fdi)) + continue; + + const int timeout = fdi->ops->poll_timeout(fdi); + if (timeout != -1 && (timeout < ret || ret == -1)) + ret = timeout; + } + return ret; +} + +static int refill_fdi_buffers_special(dlogutil_state_s *state, int timeout) +{ + if (!state->non_epoll_cnt) + return 0; + + /* Figure out how long we would have to wait. Sleep until then, or at least + * as long as we are allowed to (can't always pull off an all-nighter). */ + int needed_timeout = how_long_would_we_have_to_wait(state); + if (needed_timeout > timeout && timeout != -1) { + usleep(timeout * USEC_PER_MSEC); + return 0; + } + + // FIXME: can the sleep be interrupted via signals etc? + // also FIXME: can `needed_timeout` be -1 at this point? + + usleep(needed_timeout * USEC_PER_MSEC); + for (int i = 0; i < state->fd_count; ++i) { + if (!state->enabled[i]) + continue; + + struct fd_info *const fdi = state->data_fds[i]; + if (fdi->ops->eof(fdi)) + continue; + + int const rd = fdi->ops->read(fdi); + if (rd < 0 && rd != -EAGAIN) + return rd; + } + + return 0; +} + +static int refill_fdi_buffers(dlogutil_state_s *state, int timeout) +{ + int r; + + /* Both of the two modes below perform a timeout-limited + * sleep; they can't both do it at the same time or we + * would exceed the timeout. Fortunately they don't live + * together, so we can assume only one of the two will run. */ + assert(state->non_epoll_cnt == 0 || state->epoll_cnt == 0); + + /* Regular file descriptors are handled via a collective epoll + * to minimize the number of performed syscalls. */ + r = refill_fdi_buffers_regular(state, timeout); + if (r) + return r; + + /* Special needs file descriptors have their own + * means of figuring out if they're ready to refill. */ + r = refill_fdi_buffers_special(state, timeout); + if (r) + return r; + + return 0; +} + static void set_flush_target(dlogutil_state_s *state, bool all_buffers_empty, long last_log_age) { /* For the dumping modes, don't flush since that just removes the @@ -426,7 +530,7 @@ int do_print_once(dlogutil_state_s *state, int timeout, dlogutil_entry_s **out) const bool is_limited_dumping = state->mode == DLOGUTIL_MODE_DUMP && state->dump_size != DLOGUTIL_MAX_DUMP_SIZE; - while (state->epoll_cnt > 0) { + while (state->epoll_cnt > 0 || state->non_epoll_cnt > 0) { /* Note: sort_vector_time_span assumes that either now.tv_nsec == -1, * or now is the current CLOCK_MONOTONIC timestamp. If you change the clock, * make sure to change that function! */ diff --git a/src/libdlogutil/logretrieve.h b/src/libdlogutil/logretrieve.h index b28d51c..f4725de 100644 --- a/src/libdlogutil/logretrieve.h +++ b/src/libdlogutil/logretrieve.h @@ -41,11 +41,12 @@ typedef struct dlogutil_state { dlogutil_mode_e mode; int epollfd; int epoll_cnt; + int non_epoll_cnt; int fd_count; bool need_epoll; log_id_t aliased[LOG_ID_MAX]; } dlogutil_state_s; -int create_initial_fdis(struct fd_info ***fdis, int enabled_buffers, bool is_pipe, bool is_compressed_memory, const struct log_config *conf, log_id_t aliased[LOG_ID_MAX]); +int create_initial_fdis(struct fd_info ***fdis, int enabled_buffers, backend_t backend, bool is_compressed_memory, const struct log_config *conf, log_id_t aliased[LOG_ID_MAX]); int dlogutil_state_init(dlogutil_state_s *state, struct fd_info ***data_fds_ptr, int fd_count, dlogutil_config_s *config, bool sorting_needed, dlogutil_sorting_order_e real_sort_by, const struct log_config *conf, log_id_t aliased[LOG_ID_MAX]); int do_print_once(dlogutil_state_s *state, int timeout, dlogutil_entry_s **out); diff --git a/src/zlogutil/zlog_file_writer.cpp b/src/zlogutil/zlog_file_writer.cpp deleted file mode 100644 index c630e41..0000000 --- a/src/zlogutil/zlog_file_writer.cpp +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright 2022 Samsung Electronics Co., Ltd. All rights reserved -// SPDX-License-Identifier: MIT - -#include -#include -#include - -#include -#include -#include - -#include "zlog_file_writer.h" - -/* TODO: consider putting this in a constructor instead. - * On the other hand, we will have to use exceptions then, - * and we still need open/close for recycling. */ -int zlog_file_writer::open(const char *filename) -{ - if (_fp) - return -EBUSY; - - if (!filename || strlen(filename) == 0) { - _fp = stdout; - return 0; - } - - struct stat st; - if (lstat (filename, &st) == 0 && S_ISLNK(st.st_mode)) - return -ENOLINK; // not the most fortunate choice - - /* In theory there is a TOCTTOU issue here, but - * in practice the real zlog device can't just - * disappear and if we're pwned we can't do anything. */ - _fp = fopen(filename, "wb"); - if (!_fp) - return -errno; - - return 0; -} - -void zlog_file_writer::close(void) -{ - if (_fp && _fp != stdout) - fclose(_fp); - - _fp = nullptr; -} - -zlog_file_writer::~zlog_file_writer (void) -{ - close(); -} - -/* TODO: realistically, this class is just a wrapper for `FILE *`. - * Maybe some standard class does this better already? */ -int zlog_file_writer::printf(const char *fmt, ...) const -{ - if (!_fp) - return 0; - - va_list args; - va_start(args, fmt); - int r = vfprintf(_fp, fmt, args); - va_end(args); - - return r; -} diff --git a/src/zlogutil/zlog_file_writer.h b/src/zlogutil/zlog_file_writer.h deleted file mode 100644 index 3d81987..0000000 --- a/src/zlogutil/zlog_file_writer.h +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright 2022 Samsung Electronics Co., Ltd. All rights reserved -// SPDX-License-Identifier: MIT - -#pragma once - -#include - -class zlog_file_writer -{ -public: - int open(const char *filename); - void close(void); - int printf(const char *fmt, ...) const; - - ~ zlog_file_writer (void); -private: - FILE *_fp; -}; diff --git a/src/zlogutil/zlog_filter.cpp b/src/zlogutil/zlog_filter.cpp deleted file mode 100644 index 713ddd8..0000000 --- a/src/zlogutil/zlog_filter.cpp +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright 2022 Samsung Electronics Co., Ltd. All rights reserved -// SPDX-License-Identifier: MIT - -#include -#include - -#include -#include - -#include "zlog_filter.h" - -/* TODO: this is the client's equivalent to the dlog config file - * that usually contains limiter rules. Probably to be handled by - * libdlog though. */ -size_t zlog_filter::load_file(const char *filename) -{ - static const char *const WHITE_SPACE = " \t\n\r\f\v"; - std::ifstream ifs(filename); - std::string line; - - while (getline(ifs, line)) - { - line = line.erase(0, line.find_first_not_of(WHITE_SPACE)); - line = line.erase(line.find_last_not_of(WHITE_SPACE) + 1); - add_filter(line.c_str()); - } - - return _tag_map.size(); -} - -// TODO: missing features like T:=E and T*:E -bool zlog_filter::is_allowed(const char *tag, log_priority priority) const -{ - auto got = _tag_map.find(tag); - if (got != _tag_map.end()) - return priority >= got->second; - - got = _tag_map.find("*"); - if (got != _tag_map.end()) - return priority >= got->second; - - return false; -} - -int zlog_filter::add_filter(const char *token) -{ - std::string tag(token); - log_priority prio = DLOG_DEFAULT; - - auto pos = tag.find(":"); - if (pos != std::string::npos) - { - prio = filter_char_to_pri(tag[pos+1]); - tag = tag.substr(0, pos); - } - - if (tag.size() > ZLOGGER_TAG_MAX) - return -1; - - if (prio < DLOG_DEFAULT) - return -1; - - _tag_map[tag] = prio; - - return 0; -} - -size_t zlog_filter::get_count(void) const -{ - return _tag_map.size(); -} - diff --git a/src/zlogutil/zlog_filter.h b/src/zlogutil/zlog_filter.h deleted file mode 100644 index 78db897..0000000 --- a/src/zlogutil/zlog_filter.h +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2022 Samsung Electronics Co., Ltd. All rights reserved -// SPDX-License-Identifier: MIT - -#pragma once - -#include -#include - -#include - -class zlog_filter -{ -public: - size_t load_file(const char *filename); - bool is_allowed(const char *tag, log_priority priority) const; - int add_filter(const char *token); - size_t get_count(void) const; - -private: - std::unordered_map _tag_map; -}; diff --git a/src/zlogutil/zlog_list.cpp b/src/zlogutil/zlog_list.cpp deleted file mode 100644 index 943f100..0000000 --- a/src/zlogutil/zlog_list.cpp +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright 2022 Samsung Electronics Co., Ltd. All rights reserved -// SPDX-License-Identifier: MIT - -#include -#include -#include - -#include -#include -#include - -#include "zlog_list.h" - -zlog_list::zlog_list() -: _head(nullptr) -{ -} - -zlog_list::~zlog_list() -{ - while (_head) - { - zlog_t *l = _head; - _head = l->next; - free(l); - } -} - -unsigned int zlog_list::load(void) -{ - int fd = open(ZERO_COPY_DEVICE_NAME, O_RDONLY | O_CLOEXEC); - if (fd < 0) - return 0; - - unsigned int count = 0; - - for (int i = 0; i < ZLOGGER_DEVICE_COUNT; i++) { - void *addr = mmap(NULL, ZLOGGER_MAP_SIZE, PROT_READ, MAP_SHARED, fd, ZLOGGER_MAP_SIZE*i); - if (addr == MAP_FAILED) - break; - count += _load(addr, ZLOGGER_MAP_SIZE); - munmap(addr, ZLOGGER_MAP_SIZE); - } - - close(fd); - - return count; -} - -unsigned int zlog_list::_load(void *address, size_t length) -{ - /* TODO: document this mumbo jumbo. - * Requires expert earth magic to understand, probably. */ - - zlogger_block *b = reinterpret_cast(address); - int block_count = (int)(length / sizeof(zlogger_block)); - unsigned int count = 0; - - for (int i = 0; i < block_count; i++, b++) - { - if (b->head.offset > ZLOGGER_DATA_MAX) - continue; - - zlogger_entry *e = reinterpret_cast(b->data); - - while ((unsigned long)e < (unsigned long)b->data + b->head.offset) - { - if (e->len == 0) - break; - - if ((unsigned long)e + sizeof(zlogger_entry) + e->len > (unsigned long)b->data + b->head.offset) - break; - - unsigned short *slt = (unsigned short *)&e->time; - unsigned short CRC = slt[0] + slt[1] + slt[2] + slt[3]; - if (CRC != e->CRC) - break; - - zlog_t *l = reinterpret_cast(malloc(sizeof(zlog_t) + e->len)); - memcpy(&l->entry, e, sizeof(zlogger_entry) + e->len); - l->pid = b->head.pid; - l->tid = b->head.tid; - l->next = _head; - _head = l; - - e = reinterpret_cast((unsigned long)e + sizeof(zlogger_entry) + e->len); - count ++; - } - } - - return count; -} - -void zlog_list::sort(void) -{ - /* Stable radix sort with 256 buckets, - * done for each byte of the timestamp. */ - - zlog_t *log = _head; - for (int step = 0; step < 64; step += 8) - { - zlog_t *begin[256], *end[256]; - memset(begin, 0, sizeof(begin)); - memset(end, 0, sizeof(end)); - - zlog_t *l = log; - zlog_t *n = nullptr; - while (l != nullptr) - { - n = l->next; - int index = (l->entry.time >> step) & 0xFF; - if (begin[index] == nullptr) - begin[index] = l; - else - end[index]->next = l; - l->next = nullptr; - end[index] = l; - l = n; - } - - zlog_t *prev = nullptr; - zlog_t *next = nullptr; - for (int a = 0; a < 256; a++) - { - if (begin[a] == nullptr) - continue; - - if (prev == nullptr) - { - prev = begin[a]; - next = end[a]; - continue; - } - next->next = begin[a]; - next = end[a]; - } - log = prev; - } - _head = log; -} - diff --git a/src/zlogutil/zlog_list.h b/src/zlogutil/zlog_list.h deleted file mode 100644 index b0b7fca..0000000 --- a/src/zlogutil/zlog_list.h +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2022 Samsung Electronics Co., Ltd. All rights reserved -// SPDX-License-Identifier: MIT - -#pragma once - -#include - -struct zlog_t -{ - struct zlog_t *next; - unsigned short pid; - unsigned short tid; - struct zlogger_entry entry; -}; - -class zlog_list -{ -public: - zlog_list(); - ~zlog_list(); - unsigned int load(void); - void sort(void); - struct zlog_t *get_head_entry(void) - { - return _head; - } - -private: - unsigned int _load(void *address, size_t length); - - struct zlog_t *_head; -}; - diff --git a/src/zlogutil/zlogutil.cpp b/src/zlogutil/zlogutil.cpp deleted file mode 100644 index df10b3c..0000000 --- a/src/zlogutil/zlogutil.cpp +++ /dev/null @@ -1,236 +0,0 @@ -// Copyright 2022 Samsung Electronics Co., Ltd. All rights reserved -// SPDX-License-Identifier: MIT - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include -#include - -#include "zlog_filter.h" -#include "zlog_list.h" -#include "zlog_file_writer.h" - -#define ns_to_sec(ns) (ns * 1.0e-9) - -typedef enum { - LOG_FORMAT_NONE = 0, - LOG_FORMAT_KERNELTIME, - LOG_FORMAT_HWCLOCK, -} log_format_e; - -// TODO: could be configurable; requires a new --param or making zlogutil read config -static constexpr uint64_t NSEC_PER_SEC = 1000000000ULL; -static constexpr uint64_t MONITORING_INTERVAL = 1 * NSEC_PER_SEC; - -struct log_option { - std::string filename; - uint64_t start_time; - uint64_t end_time; - uint64_t hwc_offset; - log_format_e format; - zlog_filter user_filter; - zlog_filter policy_filter; - bool dump; - bool monitor; -}; - -static bool log_should_print_entry(zlogger_entry &entry, struct log_option &option) -{ - if (entry.time < option.start_time) - return false; - - if (entry.time > option.end_time) - return false; - - const log_priority pri = (log_priority)entry.data[0]; - const char *tag = &(entry.data[1]); - - if (option.policy_filter.get_count() > 0 && !option.policy_filter.is_allowed(tag, pri)) - return false; - - return option.user_filter.is_allowed(tag, pri); -} - -static int print_entry(zlog_file_writer *writer, struct log_option &option, zlog_t *log) -{ - log_priority pri = (log_priority)log->entry.data[0]; - char *tag = &log->entry.data[1]; - char *msg = strchr(tag, 0)+1; - - switch (option.format) - { - case LOG_FORMAT_KERNELTIME: - return writer->printf("%9.3f %c/%-8s(P%5u, T%5u): %s\n", ns_to_sec(log->entry.time), filter_pri_to_char(pri), tag, log->pid, log->tid, msg); - case LOG_FORMAT_HWCLOCK: - return writer->printf("%12.6f %c/%-8s(P%5u, T%5u): %s\n", ns_to_sec(log->entry.time + option.hwc_offset), filter_pri_to_char(pri), tag, log->pid, log->tid, msg); - case LOG_FORMAT_NONE: - return writer->printf("%s\n", msg); - default: - return -1; - } - - return 0; -} - -void trim_log_newlines(zlog_t *log) -{ - // TODO: consider trimming whitespace in general - int last_idx = log->entry.len - 2; - while (last_idx > 0 && (log->entry.data[last_idx] == '\n' || log->entry.data[last_idx] == '\r')) { - log->entry.data[last_idx] = 0; - last_idx--; - } -} - -void write_log(zlog_file_writer *writer, zlog_list &logs, struct log_option &option) -{ - for (zlog_t *log = logs.get_head_entry(); log; log = log->next) { - if (!log_should_print_entry(log->entry, option)) - continue; - - trim_log_newlines(log); - print_entry(writer, option, log); - } -} - -static log_format_e get_log_format(const char *str) -{ - std::map g_formats = { - {"kerneltime", LOG_FORMAT_KERNELTIME}, - {"hwclock" , LOG_FORMAT_HWCLOCK }, // TODO: not in base dlogutil - }; - return g_formats[str]; -} - -static bool get_time_offset(log_option &opt) -{ - /* I have no idea what this device is, - * but I'll trust that it knows about - * time more than I do, because it - * invented it, and then it perfected - * it so that no living timekeeper - * could best it in the ring of honor */ - FILE *fp = fopen("/sys/class/misc/zlogger/time", "r"); - if (!fp) - return false; - - int r = fscanf(fp, "%" SCNu64 " %" SCNu64 " %" SCNu64 - , &opt.start_time - , &opt.end_time - , &opt.hwc_offset - ); - - fclose(fp); - return r == 3; -} - -static inline bool is_zlog_on(void) -{ - return (access(ZERO_COPY_DEVICE_NAME, F_OK) == 0); -} - -static void print_help(const char *cmd) -{ - // TODO: diverges from regular dlogutil a lot - fprintf(stdout, "Usage: %s [OPTION]... [FILTER]...\n", cmd); - fprintf(stdout, "\nOption:\n" - " -f specify target file name\n" - " -v set time format as \"kerneltime\" or \"hwclock\"\n"); - fprintf(stdout, "\nFilter:\n" - " filterspecs are a series of [:priority]\n" - " where is a log component tag (or * for all) and priority is:\n" - " V Verbose\n" - " D Debug\n" - " I Info\n" - " W Warn\n" - " E Error\n" - " F Fatal\n" - " S Silent (suppress all output)\n\n" - " by itself means :V\n" - " If no filterspec is found, filter defaults to '*:V'\n\n"); -} - -int main(int argc, char *argv[]) -{ - if (!is_zlog_on()) { - fprintf(stderr, "Zero-copy kernel module not present"); - return EXIT_FAILURE; - } - - struct log_option opt = {}; - int c; - - opt.format = LOG_FORMAT_KERNELTIME; - while ((c = getopt(argc, argv, "dlf:v:t:zhm")) != -1) { - switch (c) { - case 'd': - opt.dump = true; - break; - case 'm': - opt.monitor = true; - break; - case 'f': - opt.filename = optarg; - break; - case 'v': - opt.format = get_log_format(optarg); - break; - case 'h': - default: - print_help(argv[0]); - return 0; - } - } - - for (int i = optind; i < argc; i++) - opt.user_filter.add_filter(argv[i]); - - if (opt.user_filter.get_count() == 0) - opt.user_filter.add_filter("*"); - - if (!opt.dump && !opt.monitor) { - opt.dump = true; - opt.monitor = true; - } - - if (!get_time_offset(opt)) - return EXIT_FAILURE; - - if (!opt.dump) - opt.start_time = opt.end_time - MONITORING_INTERVAL; - - auto writer = std::make_unique (); - int r = writer->open(opt.filename.c_str()); - if (r < 0) - return EXIT_FAILURE; - - do { - zlog_list logs; - size_t sz = logs.load(); - if (sz == 0) - return EXIT_FAILURE; - - logs.sort(); - - write_log(writer.get(), logs, opt); - - opt.start_time = opt.end_time; - opt.end_time += MONITORING_INTERVAL; - } while (opt.monitor); - - return EXIT_SUCCESS; -}