Mercurial > dovecot > core-2.2
changeset 13356:0b7ab4965c91
doveadm: Initial implementation of "stats top" command.
Currently it's hard coded to assume ANSI compatible terminal.
author | Timo Sirainen <tss@iki.fi> |
---|---|
date | Thu, 01 Sep 2011 10:39:19 +0300 |
parents | 87ce8bba8731 |
children | 03ad86ec4ebd |
files | src/doveadm/doveadm-stats.c src/doveadm/doveadm.c src/doveadm/doveadm.h |
diffstat | 3 files changed, 439 insertions(+), 9 deletions(-) [+] |
line wrap: on
line diff
--- a/src/doveadm/doveadm-stats.c Thu Sep 01 10:37:55 2011 +0300 +++ b/src/doveadm/doveadm-stats.c Thu Sep 01 10:39:19 2011 +0300 @@ -1,18 +1,56 @@ /* Copyright (c) 2011 Dovecot authors, see the included COPYING file */ #include "lib.h" +#include "array.h" #include "network.h" +#include "ioloop.h" #include "istream.h" +#include "hash.h" #include "str.h" #include "strescape.h" #include "write-full.h" #include "doveadm.h" #include "doveadm-print.h" +#include <stdio.h> +#include <stdlib.h> #include <unistd.h> +#include <sys/ioctl.h> +#include <termios.h> + +struct top_line { + char *id; + /* [headers_count] */ + const char **prev_values, **cur_values; + + unsigned int flip:1; +}; + +struct top_context { + const char *path; + int fd; + struct istream *input; + const char *sort_type; -static const char *const* -read_next_line(struct istream *input) + char **headers; + unsigned int headers_count; + + pool_t prev_pool, cur_pool; + /* id => struct top_line. */ + struct hash_table *sessions; + ARRAY_DEFINE(lines, struct top_line *); + int (*lines_sort)(struct top_line *const *, struct top_line *const *); + + unsigned int last_update_idx, user_idx; + unsigned int sort_idx1, sort_idx2; + + unsigned int flip:1; +}; + +static struct top_context *sort_ctx = NULL; + +static char ** +p_read_next_line(pool_t pool, struct istream *input) { const char *line; char **args; @@ -22,10 +60,16 @@ if (line == NULL) return NULL; - args = p_strsplit(pool_datastack_create(), line, "\t"); + args = p_strsplit(pool, line, "\t"); for (i = 0; args[i] != NULL; i++) args[i] = str_tabunescape(args[i]); - return (void *)args; + return args; +} + +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) @@ -83,12 +127,12 @@ path = optarg; break; default: - help(&doveadm_cmd_stats); + help(&doveadm_cmd_stats_dump); } } argv += optind - 1; if (argv[1] == NULL) - help(&doveadm_cmd_stats); + help(&doveadm_cmd_stats_dump); cmd = t_strdup_printf("EXPORT\t%s\n", t_strarray_join((const void *)(argv+1), "\t")); @@ -96,6 +140,390 @@ stats_dump(path, cmd); } -struct doveadm_cmd doveadm_cmd_stats = { +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_append(&ctx->lines, &line, 1); + } + hash_table_insert(ctx->sessions, line->id, line); + } + + if (ctx->input->stream_errno != 0) + i_fatal("read(%s) failed: %m", ctx->path); + i_fatal("read(%s): unexpected EOF", ctx->path); +} + +static void stats_drop_stale(struct top_context *ctx) +{ + struct hash_iterate_context *iter; + void *key, *value; + + iter = hash_table_iterate_init(ctx->sessions); + while (hash_table_iterate(iter, &key, &value)) { + struct top_line *line = value; + + if (line->flip != ctx->flip) + hash_table_remove(ctx->sessions, key); + } + 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", &ctx->sort_idx1) || + !stats_header_find(ctx, "disk_output", &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) { + i_snprintf(numstr, sizeof(numstr), "%llu", + (unsigned long long)(cur_num - prev_num)); + 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 */ + i_snprintf(numstr, sizeof(numstr), "%d", + (int)((cur_double - prev_double) * + (cur_time - prev_time) * 100)); + 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", + "disk_input", "disk_output" + }; + struct winsize ws; + struct top_line *const *lines; + unsigned int i, j, row, maxrow, count, indexes[N_ELEMENTS(names)]; + + /* 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_simple("USER"); + 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] = -1U; + } + + 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] == -1U) + 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; + + memset(&ctx, 0, sizeof(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); + ctx.sessions = + hash_table_create(default_pool, default_pool, 0, + str_hash, (hash_cmp_callback_t *)strcmp); + net_set_nonblock(ctx.fd, FALSE); + + ctx.input = i_stream_create_fd(ctx.fd, (size_t)-1, TRUE); + + 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); + (void)close(ctx.fd); +} + +static void cmd_stats_top(int argc, char *argv[]) +{ + const char *path, *sort_type; + int c; + + path = t_strconcat(doveadm_settings->base_dir, "/stats", NULL); + + while ((c = getopt(argc, argv, "s:")) > 0) { + switch (c) { + case 's': + path = optarg; + break; + default: + help(&doveadm_cmd_stats_top); + } + } + argv += optind - 1; + if (argv[1] == NULL) + sort_type = "disk"; + else if (argv[2] != NULL) + help(&doveadm_cmd_stats_top); + else + sort_type = argv[1]; + + doveadm_print_init(DOVEADM_PRINT_TYPE_TABLE); + stats_top(path, sort_type); +} + +struct doveadm_cmd doveadm_cmd_stats_dump = { cmd_stats_dump, "stats dump", "[-s <stats socket path>] <type> [<filter>]" }; + +struct doveadm_cmd doveadm_cmd_stats_top = { + cmd_stats_top, "stats top", "[-s <stats socket path>] [<sort field>]" +};
--- a/src/doveadm/doveadm.c Thu Sep 01 10:37:55 2011 +0300 +++ b/src/doveadm/doveadm.c Thu Sep 01 10:39:19 2011 +0300 @@ -260,7 +260,8 @@ &doveadm_cmd_mailbox_mutf7, &doveadm_cmd_sis_deduplicate, &doveadm_cmd_sis_find, - &doveadm_cmd_stats + &doveadm_cmd_stats_dump, + &doveadm_cmd_stats_top }; int main(int argc, char *argv[])
--- a/src/doveadm/doveadm.h Thu Sep 01 10:37:55 2011 +0300 +++ b/src/doveadm/doveadm.h Thu Sep 01 10:39:19 2011 +0300 @@ -26,7 +26,8 @@ extern struct doveadm_cmd doveadm_cmd_mailbox_mutf7; extern struct doveadm_cmd doveadm_cmd_sis_deduplicate; extern struct doveadm_cmd doveadm_cmd_sis_find; -extern struct doveadm_cmd doveadm_cmd_stats; +extern struct doveadm_cmd doveadm_cmd_stats_dump; +extern struct doveadm_cmd doveadm_cmd_stats_top; void doveadm_register_cmd(const struct doveadm_cmd *cmd);