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 \
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 = \
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 \
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 \
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
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
#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
#pragma once
+#include <logcommon.h>
+
#include <linux/zlogger.h>
+#include <stdint.h>
+#include <time.h>
+
#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;
+}
+
%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
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) {
{
// 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;
/// 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;
};
.get_capacity = logger_get_capacity,
.get_usage = logger_get_usage,
.eof = logger_eof,
+ .poll_timeout = NULL,
.should_filter = true,
};
.get_capacity = pipe_get_capacity,
.get_usage = pipe_get_usage,
.eof = pipe_eof,
+ .poll_timeout = NULL,
.should_filter = false,
};
--- /dev/null
+/* 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 <errno.h>
+#include <string.h>
+
+// POSIX
+#include <sys/mman.h>
+
+// DLog
+#include <logconfig.h>
+#include <queued_entry_timestamp.h>
+#include <zero_copy_backend.h>
+
+// 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,
+};
--- /dev/null
+/* 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;
+};
+
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)
#include "fdi_pipe.h"
#include "fdi_logger.h"
+#include "fdi_zero_copy.h"
#include "logretrieve.h"
// C
+#include <assert.h>
#include <limits.h>
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);
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) {
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;
}
}
.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;
}
// 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;
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;
}
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;
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
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! */
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);
+++ /dev/null
-// Copyright 2022 Samsung Electronics Co., Ltd. All rights reserved
-// SPDX-License-Identifier: MIT
-
-#include <cerrno>
-#include <cstdarg>
-#include <cstring>
-
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <unistd.h>
-
-#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;
-}
+++ /dev/null
-// Copyright 2022 Samsung Electronics Co., Ltd. All rights reserved
-// SPDX-License-Identifier: MIT
-
-#pragma once
-
-#include <cstdio>
-
-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;
-};
+++ /dev/null
-// Copyright 2022 Samsung Electronics Co., Ltd. All rights reserved
-// SPDX-License-Identifier: MIT
-
-#include <cstring>
-#include <fstream>
-
-#include <logcommon.h>
-#include <zero_copy_backend.h>
-
-#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();
-}
-
+++ /dev/null
-// Copyright 2022 Samsung Electronics Co., Ltd. All rights reserved
-// SPDX-License-Identifier: MIT
-
-#pragma once
-
-#include <string>
-#include <unordered_map>
-
-#include <dlog.h>
-
-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 <std::string, log_priority> _tag_map;
-};
+++ /dev/null
-// Copyright 2022 Samsung Electronics Co., Ltd. All rights reserved
-// SPDX-License-Identifier: MIT
-
-#include <algorithm>
-#include <cstring>
-#include <memory>
-
-#include <fcntl.h>
-#include <sys/mman.h>
-#include <unistd.h>
-
-#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<zlogger_block *>(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<zlogger_entry *>(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<zlog_t *>(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<zlogger_entry *>((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;
-}
-
+++ /dev/null
-// Copyright 2022 Samsung Electronics Co., Ltd. All rights reserved
-// SPDX-License-Identifier: MIT
-
-#pragma once
-
-#include <zero_copy_backend.h>
-
-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;
-};
-
+++ /dev/null
-// Copyright 2022 Samsung Electronics Co., Ltd. All rights reserved
-// SPDX-License-Identifier: MIT
-
-#include <cinttypes>
-#include <cstdint>
-#include <cstdio>
-#include <cstdlib>
-#include <cstring>
-#include <map>
-#include <memory>
-#include <string>
-
-#include <fcntl.h>
-#include <sys/mman.h>
-#include <sys/shm.h>
-#include <sys/stat.h>
-#include <sys/ioctl.h>
-#include <unistd.h>
-
-#include <logcommon.h>
-#include <zero_copy_backend.h>
-
-#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 <std::string, log_format_e> 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 <filename> specify target file name\n"
- " -v <format> set time format as \"kerneltime\" or \"hwclock\"\n");
- fprintf(stdout, "\nFilter:\n"
- " filterspecs are a series of <tag>[:priority]\n"
- " where <tag> 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"
- " <tag> by itself means <tag>: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 <zlog_file_writer> ();
- 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;
-}