summaryrefslogtreecommitdiffstats
path: root/src/doveadm/doveadm-oldstats.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/doveadm/doveadm-oldstats.c')
-rw-r--r--src/doveadm/doveadm-oldstats.c604
1 files changed, 604 insertions, 0 deletions
diff --git a/src/doveadm/doveadm-oldstats.c b/src/doveadm/doveadm-oldstats.c
new file mode 100644
index 0000000..4be575e
--- /dev/null
+++ b/src/doveadm/doveadm-oldstats.c
@@ -0,0 +1,604 @@
+/* Copyright (c) 2011-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "net.h"
+#include "ioloop.h"
+#include "istream.h"
+#include "hash.h"
+#include "str.h"
+#include "strescape.h"
+#include "strescape.h"
+#include "write-full.h"
+#include "doveadm.h"
+#include "doveadm-print.h"
+
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <termios.h>
+
+struct top_line {
+ char *id;
+ /* [headers_count] */
+ const char **prev_values, **cur_values;
+
+ bool flip:1;
+};
+
+struct top_context {
+ const char *path;
+ int fd;
+ struct istream *input;
+ const char *sort_type;
+
+ char **headers;
+ unsigned int headers_count;
+
+ pool_t prev_pool, cur_pool;
+ /* id => struct top_line. */
+ HASH_TABLE(char *, struct top_line *) sessions;
+ ARRAY(struct top_line *) lines;
+ int (*lines_sort)(struct top_line *const *, struct top_line *const *);
+
+ unsigned int last_update_idx, user_idx;
+ unsigned int sort_idx1, sort_idx2;
+
+ bool flip:1;
+};
+
+static struct top_context *sort_ctx = NULL;
+static const char *disk_input_field = "disk_input";
+static const char *disk_output_field = "disk_output";
+
+static char **
+p_read_next_line(pool_t pool, struct istream *input)
+{
+ const char *line;
+
+ line = i_stream_read_next_line(input);
+ if (line == NULL)
+ return NULL;
+
+ return p_strsplit_tabescaped(pool, line);
+}
+
+static const char *const*
+read_next_line(struct istream *input)
+{
+ return (const void *)p_read_next_line(pool_datastack_create(), input);
+}
+
+static void stats_dump(const char *path, const char *cmd)
+{
+ struct istream *input;
+ const char *const *args;
+ unsigned int i;
+ int fd;
+
+ fd = doveadm_connect(path);
+ net_set_nonblock(fd, FALSE);
+ if (write_full(fd, cmd, strlen(cmd)) < 0)
+ i_fatal("write(%s) failed: %m", path);
+
+ input = i_stream_create_fd_autoclose(&fd, SIZE_MAX);
+
+ /* read header */
+ args = read_next_line(input);
+ if (args == NULL)
+ i_fatal("read(%s) unexpectedly disconnected", path);
+ if (*args == NULL)
+ i_info("no statistics available");
+ else {
+ for (; *args != NULL; args++)
+ doveadm_print_header_simple(*args);
+
+ /* read lines */
+ do {
+ T_BEGIN {
+ args = read_next_line(input);
+ if (args != NULL && args[0] == NULL)
+ args = NULL;
+ if (args != NULL) {
+ for (i = 0; args[i] != NULL; i++)
+ doveadm_print(args[i]);
+ }
+ } T_END;
+ } while (args != NULL);
+ }
+ if (input->stream_errno != 0)
+ i_fatal("read(%s) failed: %s", path, i_stream_get_error(input));
+ i_stream_destroy(&input);
+}
+
+static void
+doveadm_cmd_stats_dump(struct doveadm_cmd_context* cctx)
+{
+ const char *path, *cmd;
+ const char *args[3] = {0};
+
+ if (!doveadm_cmd_param_str(cctx, "socket-path", &path))
+ path = t_strconcat(doveadm_settings->base_dir, "/old-stats", NULL);
+
+ if (!doveadm_cmd_param_str(cctx, "type", &args[0])) {
+ i_error("Missing type parameter");
+ doveadm_exit_code = EX_USAGE;
+ return;
+ }
+
+ /* purely optional */
+ if (!doveadm_cmd_param_str(cctx, "filter", &args[1]))
+ args[1] = NULL;
+
+ cmd = t_strdup_printf("EXPORT\t%s\n", t_strarray_join(args, "\t"));
+
+ doveadm_print_init(DOVEADM_PRINT_TYPE_TAB);
+ stats_dump(path, cmd);
+ return;
+}
+
+static void
+stats_line_set_prev_values(struct top_context *ctx,
+ const struct top_line *old_line,
+ struct top_line *line)
+{
+ const char **values;
+ unsigned int i;
+
+ if (old_line->prev_values == NULL ||
+ strcmp(old_line->cur_values[ctx->last_update_idx],
+ line->cur_values[ctx->last_update_idx]) != 0) {
+ values = old_line->cur_values;
+ } else {
+ values = old_line->prev_values;
+ }
+
+ /* line hasn't been updated, keep old values */
+ line->prev_values =
+ p_new(ctx->cur_pool, const char *, ctx->headers_count);
+ for (i = 0; i < ctx->headers_count; i++)
+ line->prev_values[i] = p_strdup(ctx->cur_pool, values[i]);
+}
+
+static void stats_read(struct top_context *ctx)
+{
+ struct top_line *old_line, *line;
+ unsigned int i;
+ char **args;
+
+ /* read lines */
+ while ((args = p_read_next_line(ctx->cur_pool, ctx->input)) != NULL) {
+ if (args[0] == NULL) {
+ /* end of stats */
+ return;
+ }
+ if (str_array_length((void *)args) != ctx->headers_count)
+ i_fatal("read(%s): invalid stats line", ctx->path);
+
+ line = p_new(ctx->cur_pool, struct top_line, 1);
+ line->id = args[0];
+ line->flip = ctx->flip;
+ line->cur_values = p_new(ctx->cur_pool, const char *, ctx->headers_count);
+ for (i = 0; i < ctx->headers_count; i++)
+ line->cur_values[i] = args[i];
+
+ old_line = hash_table_lookup(ctx->sessions, line->id);
+ if (old_line != NULL) {
+ stats_line_set_prev_values(ctx, old_line, line);
+ array_push_back(&ctx->lines, &line);
+ }
+ hash_table_update(ctx->sessions, line->id, line);
+ }
+
+ if (ctx->input->stream_errno != 0) {
+ i_fatal("read(%s) failed: %s", ctx->path,
+ i_stream_get_error(ctx->input));
+ }
+ i_fatal("read(%s): unexpected EOF", ctx->path);
+}
+
+static void stats_drop_stale(struct top_context *ctx)
+{
+ struct hash_iterate_context *iter;
+ char *id;
+ struct top_line *line;
+
+ iter = hash_table_iterate_init(ctx->sessions);
+ while (hash_table_iterate(iter, ctx->sessions, &id, &line)) {
+ if (line->flip != ctx->flip)
+ hash_table_remove(ctx->sessions, id);
+ }
+ hash_table_iterate_deinit(&iter);
+}
+
+static int get_double(const char *str, double *num_r)
+{
+ char *p;
+
+ *num_r = strtod(str, &p);
+ return *p == '\0' ? 0 : -1;
+}
+
+static double sort_cpu_diff(const struct top_line *line)
+{
+ double prev, cur, diff, prev_time, cur_time, time_multiplier;
+
+ if (get_double(line->prev_values[sort_ctx->last_update_idx], &prev_time) < 0 ||
+ get_double(line->cur_values[sort_ctx->last_update_idx], &cur_time) < 0)
+ i_fatal("sorting: invalid last_update value");
+ time_multiplier = (cur_time - prev_time) * 100;
+
+ if (get_double(line->prev_values[sort_ctx->sort_idx1], &prev) < 0 ||
+ get_double(line->cur_values[sort_ctx->sort_idx1], &cur) < 0)
+ i_fatal("sorting: not a double");
+
+ diff = cur - prev;
+
+ if (sort_ctx->sort_idx2 != 0) {
+ if (get_double(line->prev_values[sort_ctx->sort_idx2], &prev) < 0 ||
+ get_double(line->cur_values[sort_ctx->sort_idx2], &cur) < 0)
+ i_fatal("sorting: not a double");
+ diff += cur - prev;
+ }
+ return diff * time_multiplier;
+}
+
+static int sort_cpu(struct top_line *const *l1, struct top_line *const *l2)
+{
+ double d1, d2;
+
+ d1 = sort_cpu_diff(*l1);
+ d2 = sort_cpu_diff(*l2);
+ if (d1 < d2)
+ return -1;
+ if (d1 > d2)
+ return 1;
+ return strcmp((*l1)->cur_values[sort_ctx->user_idx],
+ (*l2)->cur_values[sort_ctx->user_idx]);
+}
+
+static double sort_num_diff(const struct top_line *line)
+{
+ uint64_t prev, cur, diff;
+
+ if (str_to_uint64(line->prev_values[sort_ctx->sort_idx1], &prev) < 0 ||
+ str_to_uint64(line->cur_values[sort_ctx->sort_idx1], &cur) < 0)
+ i_fatal("sorting: not a number");
+ diff = cur - prev;
+
+ if (sort_ctx->sort_idx2 != 0) {
+ if (str_to_uint64(line->prev_values[sort_ctx->sort_idx2], &prev) < 0 ||
+ str_to_uint64(line->cur_values[sort_ctx->sort_idx2], &cur) < 0)
+ i_fatal("sorting: not a number");
+ diff += cur - prev;
+ }
+ return diff;
+}
+
+static int sort_num(struct top_line *const *l1, struct top_line *const *l2)
+{
+ uint64_t n1, n2;
+
+ n1 = sort_num_diff(*l1);
+ n2 = sort_num_diff(*l2);
+ if (n1 < n2)
+ return -1;
+ if (n1 > n2)
+ return 1;
+ return strcmp((*l1)->cur_values[sort_ctx->user_idx],
+ (*l2)->cur_values[sort_ctx->user_idx]);
+}
+
+static bool
+stats_header_find(struct top_context *ctx, const char *name,
+ unsigned int *idx_r)
+{
+ unsigned int i;
+
+ for (i = 0; ctx->headers[i] != NULL; i++) {
+ if (strcmp(ctx->headers[i], name) == 0) {
+ *idx_r = i;
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+static void stats_top_get_sorting(struct top_context *ctx)
+{
+ if (stats_header_find(ctx, ctx->sort_type, &ctx->sort_idx1))
+ return;
+
+ if (strcmp(ctx->sort_type, "cpu") == 0) {
+ if (!stats_header_find(ctx, "user_cpu", &ctx->sort_idx1) ||
+ !stats_header_find(ctx, "sys_cpu", &ctx->sort_idx2))
+ i_fatal("cpu sort type is missing fields");
+ return;
+ }
+ if (strcmp(ctx->sort_type, "disk") == 0) {
+ if (!stats_header_find(ctx, disk_input_field, &ctx->sort_idx1) ||
+ !stats_header_find(ctx, disk_output_field, &ctx->sort_idx2))
+ i_fatal("disk sort type is missing fields");
+ return;
+ }
+ i_fatal("unknown sort type: %s", ctx->sort_type);
+}
+
+static bool stats_top_round(struct top_context *ctx)
+{
+#define TOP_CMD "EXPORT\tsession\tconnected\n"
+ const char *const *args;
+ pool_t tmp_pool;
+
+ if (write_full(ctx->fd, TOP_CMD, strlen(TOP_CMD)) < 0)
+ i_fatal("write(%s) failed: %m", ctx->path);
+
+ /* read header */
+ if (ctx->headers != NULL) {
+ args = read_next_line(ctx->input);
+ if (args == NULL)
+ i_fatal("read(%s) unexpectedly disconnected", ctx->path);
+ if (*args == NULL)
+ return TRUE;
+ if (str_array_length(args) != ctx->headers_count)
+ i_fatal("headers changed");
+ } else {
+ ctx->headers = p_read_next_line(default_pool, ctx->input);
+ if (ctx->headers == NULL)
+ i_fatal("read(%s) unexpectedly disconnected", ctx->path);
+ if (*ctx->headers == NULL) {
+ i_free_and_null(ctx->headers);
+ return FALSE;
+ }
+ ctx->headers_count = str_array_length((void *)ctx->headers);
+ if (!stats_header_find(ctx, "last_update", &ctx->last_update_idx))
+ i_fatal("last_update header missing");
+ if (!stats_header_find(ctx, "user", &ctx->user_idx))
+ i_fatal("user header missing");
+ stats_top_get_sorting(ctx);
+ }
+
+ array_clear(&ctx->lines);
+ p_clear(ctx->prev_pool);
+ tmp_pool = ctx->prev_pool;
+ ctx->prev_pool = ctx->cur_pool;
+ ctx->cur_pool = tmp_pool;
+
+ ctx->flip = !ctx->flip;
+ stats_read(ctx);
+ stats_drop_stale(ctx);
+
+ sort_ctx = ctx;
+ array_sort(&ctx->lines, *ctx->lines_sort);
+ sort_ctx = NULL;
+ return TRUE;
+}
+
+static void
+stats_top_output_diff(struct top_context *ctx,
+ const struct top_line *line, unsigned int i)
+{
+ uint64_t prev_num, cur_num;
+ double prev_double, cur_double, prev_time, cur_time;
+ char numstr[MAX_INT_STRLEN];
+
+ if (str_to_uint64(line->prev_values[i], &prev_num) == 0 &&
+ str_to_uint64(line->cur_values[i], &cur_num) == 0) {
+ if (i_snprintf(numstr, sizeof(numstr), "%"PRIu64,
+ (cur_num - prev_num)) < 0)
+ i_unreached();
+ doveadm_print(numstr);
+ } else if (get_double(line->prev_values[i], &prev_double) == 0 &&
+ get_double(line->cur_values[i], &cur_double) == 0 &&
+ get_double(line->prev_values[ctx->last_update_idx], &prev_time) == 0 &&
+ get_double(line->cur_values[ctx->last_update_idx], &cur_time) == 0) {
+ /* %CPU */
+ if (i_snprintf(numstr, sizeof(numstr), "%d",
+ (int)((cur_double - prev_double) *
+ (cur_time - prev_time) * 100)) < 0)
+ i_unreached();
+ doveadm_print(numstr);
+ } else {
+ doveadm_print(line->cur_values[i]);
+ }
+}
+
+static void stats_top_output(struct top_context *ctx)
+{
+ static const char *names[] = {
+ "user", "service", "user_cpu", "sys_cpu",
+ "", ""
+ };
+ struct winsize ws;
+ struct top_line *const *lines;
+ unsigned int i, j, row, maxrow, count, indexes[N_ELEMENTS(names)];
+
+ names[4] = disk_input_field;
+ names[5] = disk_output_field;
+
+ /* ANSI clear screen and move cursor to top of screen */
+ printf("\x1b[2J\x1b[1;1H"); fflush(stdout);
+ doveadm_print_deinit();
+ doveadm_print_init(DOVEADM_PRINT_TYPE_TABLE);
+
+ doveadm_print_header("USER", "USER", DOVEADM_PRINT_HEADER_FLAG_EXPAND);
+ doveadm_print_header_simple("SERVICE");
+ doveadm_print_header_simple("%CPU");
+ doveadm_print_header_simple("%SYS");
+ doveadm_print_header_simple("DISKIN");
+ doveadm_print_header_simple("DISKOUT");
+
+ if (!stats_top_round(ctx)) {
+ /* no connections yet */
+ return;
+ }
+
+ for (i = 0; i < N_ELEMENTS(names); i++) {
+ if (!stats_header_find(ctx, names[i], &indexes[i]))
+ indexes[i] = UINT_MAX;
+ }
+
+ if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) < 0)
+ ws.ws_row = 24;
+ maxrow = ws.ws_row-1;
+
+ lines = array_get(&ctx->lines, &count);
+ for (i = 0, row = 1; row < maxrow && i < count; i++, row++) {
+ for (j = 0; j < N_ELEMENTS(names); j++) {
+ if (indexes[j] == UINT_MAX)
+ doveadm_print("?");
+ else
+ stats_top_output_diff(ctx, lines[i], indexes[j]);
+ }
+ }
+}
+
+static void stats_top_start(struct top_context *ctx)
+{
+ struct timeout *to;
+
+ stats_top_output(ctx);
+ to = timeout_add(1000, stats_top_output, ctx);
+ io_loop_run(current_ioloop);
+ timeout_remove(&to);
+}
+
+static void stats_top(const char *path, const char *sort_type)
+{
+ struct top_context ctx;
+
+ i_zero(&ctx);
+ ctx.path = path;
+ ctx.fd = doveadm_connect(path);
+ ctx.prev_pool = pool_alloconly_create("stats top", 1024*16);
+ ctx.cur_pool = pool_alloconly_create("stats top", 1024*16);
+ i_array_init(&ctx.lines, 128);
+ hash_table_create(&ctx.sessions, default_pool, 0, str_hash, strcmp);
+ net_set_nonblock(ctx.fd, FALSE);
+
+ ctx.input = i_stream_create_fd(ctx.fd, SIZE_MAX);
+
+ if (strstr(sort_type, "cpu") != NULL)
+ ctx.lines_sort = sort_cpu;
+ else
+ ctx.lines_sort = sort_num;
+ ctx.sort_type = sort_type;
+
+ stats_top_start(&ctx);
+ i_stream_destroy(&ctx.input);
+ hash_table_destroy(&ctx.sessions);
+ array_free(&ctx.lines);
+ pool_unref(&ctx.prev_pool);
+ pool_unref(&ctx.cur_pool);
+ i_close_fd(&ctx.fd);
+}
+
+static void stats_reset(const char *path)
+{
+ const char **ptr ATTR_UNUSED;
+ int fd,ret;
+ string_t *cmd;
+ struct istream *input;
+ const char *line;
+
+ fd = doveadm_connect(path);
+ net_set_nonblock(fd, FALSE);
+ input = i_stream_create_fd(fd, SIZE_MAX);
+
+ cmd = t_str_new(10);
+ str_append(cmd, "RESET");
+/* XXX: Not supported yet.
+ for(ptr = items; *ptr; ptr++)
+ {
+ str_append_c(cmd, '\t');
+ str_append(cmd, *ptr);
+ }
+*/
+ str_append_c(cmd, '\n');
+
+ /* send command */
+ ret = write_full(fd, str_c(cmd), str_len(cmd));
+
+ if (ret < 0) {
+ i_close_fd(&fd);
+ i_error("write(%s) failed: %m", path);
+ return;
+ }
+
+ line = i_stream_read_next_line(input);
+
+ if (line == NULL) {
+ i_error("read(%s) failed: %s", path, i_stream_get_error(input));
+ } else if (!str_begins(line, "OK")) {
+ i_error("%s",line);
+ } else {
+ i_info("Stats reset");
+ }
+
+ i_stream_destroy(&input);
+ i_close_fd(&fd);
+}
+
+static void cmd_stats_top(struct doveadm_cmd_context *cctx)
+{
+ const char *path, *sort_type;
+ bool b;
+
+ if (!doveadm_cmd_param_str(cctx, "socket-path", &path)) {
+ path = t_strconcat(doveadm_settings->base_dir,
+ "/old-stats", NULL);
+ }
+ if (!doveadm_cmd_param_bool(cctx, "show-disk-io", &b) && b) {
+ disk_input_field = "read_bytes";
+ disk_output_field = "write_bytes";
+ }
+ if (!doveadm_cmd_param_str(cctx, "sort-field", &sort_type))
+ sort_type = "disk";
+
+ doveadm_print_init(DOVEADM_PRINT_TYPE_TABLE);
+ stats_top(path, sort_type);
+}
+
+static void cmd_stats_reset(struct doveadm_cmd_context *cctx)
+{
+ const char *path;
+
+ if (!doveadm_cmd_param_str(cctx, "socket-path", &path)) {
+ path = t_strconcat(doveadm_settings->base_dir,
+ "/old-stats", NULL);
+ }
+
+ stats_reset(path);
+}
+
+struct doveadm_cmd_ver2 doveadm_cmd_oldstats_dump_ver2 = {
+ .cmd = doveadm_cmd_stats_dump,
+ .name = "oldstats dump",
+ .usage = "[-s <stats socket path>] <type> [<filter>]",
+DOVEADM_CMD_PARAMS_START
+DOVEADM_CMD_PARAM('s', "socket-path", CMD_PARAM_STR, 0)
+DOVEADM_CMD_PARAM('\0', "type", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL)
+DOVEADM_CMD_PARAM('\0', "filter", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL)
+DOVEADM_CMD_PARAMS_END
+};
+
+struct doveadm_cmd_ver2 doveadm_cmd_oldstats_top_ver2 = {
+ .cmd = cmd_stats_top,
+ .name = "oldstats top",
+ .usage = "[-s <stats socket path>] [-b] [<sort field>]",
+DOVEADM_CMD_PARAMS_START
+DOVEADM_CMD_PARAM('s', "socket-path", CMD_PARAM_STR, 0)
+DOVEADM_CMD_PARAM('b', "show-disk-io", CMD_PARAM_BOOL, 0)
+DOVEADM_CMD_PARAM('\0', "sort-field", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL)
+DOVEADM_CMD_PARAMS_END
+};
+
+
+struct doveadm_cmd_ver2 doveadm_cmd_oldstats_reset_ver2 = {
+ .cmd = cmd_stats_reset,
+ .name = "oldstats reset",
+ .usage = "[-s <stats socket path>]",
+DOVEADM_CMD_PARAMS_START
+DOVEADM_CMD_PARAM('s', "socket-path", CMD_PARAM_STR, 0)
+DOVEADM_CMD_PARAMS_END
+};