diff options
Diffstat (limited to '')
-rw-r--r-- | src/doveadm/doveadm-stats.c | 343 |
1 files changed, 343 insertions, 0 deletions
diff --git a/src/doveadm/doveadm-stats.c b/src/doveadm/doveadm-stats.c new file mode 100644 index 0000000..ddb198b --- /dev/null +++ b/src/doveadm/doveadm-stats.c @@ -0,0 +1,343 @@ +/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "net.h" +#include "istream.h" +#include "str.h" +#include "strescape.h" +#include "write-full.h" +#include "master-service.h" +#include "doveadm.h" +#include "doveadm-print.h" +#include "stats-settings.h" + +#include <math.h> + +#define DOVEADM_DUMP_DEFAULT_FIELDS \ + "count sum min max avg median stddev %95" + +#define ADD_NAME_PARAM "name" +#define ADD_DESCR_PARAM "description" +#define ADD_FIELDS_PARAM "fields" +#define ADD_GROUPBY_PARAM "group-by" +#define ADD_FILTER_PARAM "filter" +#define ADD_EXPORTER_PARAM "exporter" +#define ADD_EXPORTERINCL_PARAM "exporter-include" + +enum doveadm_dump_field_type { + DOVEADM_DUMP_FIELD_TYPE_PASSTHROUGH = 0, + DOVEADM_DUMP_FIELD_TYPE_STDDEV, +}; + +struct stats_cmd_context { + string_t *cmd; + struct doveadm_cmd_context *cctx; + struct istream *input; + const char *path; + void *data; +}; + +struct dump_data { + const char **fields; + unsigned int field_count; + enum doveadm_dump_field_type *field_types; +}; + +struct stats_cmd_vfuncs { + int (*build_cmd)(struct stats_cmd_context *ctx, const char **error_r); + void (*process_response)(struct stats_cmd_context *ctx); +}; + +static int build_stats_dump_cmd(struct stats_cmd_context *ctx, const char **error_r); +static int build_stats_add_cmd(struct stats_cmd_context *ctx, const char **error_r); +static int build_stats_remove_cmd(struct stats_cmd_context *ctx, const char **error_r); + +static void stats_dump_process_response(struct stats_cmd_context *ctx); +static void stats_modify_process_response(struct stats_cmd_context *ctx); + +static void stats_send_cmd(struct stats_cmd_context *ctx); + +static struct stats_cmd_vfuncs dump_vfuncs = { + .build_cmd = build_stats_dump_cmd, + .process_response = stats_dump_process_response +}; + +static struct stats_cmd_vfuncs add_vfuncs = { + .build_cmd = build_stats_add_cmd, + .process_response = stats_modify_process_response +}; + +static struct stats_cmd_vfuncs remove_vfuncs = { + .build_cmd = build_stats_remove_cmd, + .process_response = stats_modify_process_response +}; + +static string_t *init_stats_cmd(void) +{ + string_t *cmd = t_str_new(128); + str_append(cmd, "VERSION\tstats-reader-client\t2\t0\n"); + return cmd; +} + +static void stats_exec_cmd(struct doveadm_cmd_context *cctx, + struct stats_cmd_vfuncs *vfuncs) +{ + struct stats_cmd_context ctx; + const char *build_cmd_error; + ctx.cctx = cctx; + if (vfuncs->build_cmd(&ctx, &build_cmd_error) < 0) { + i_error("%s", build_cmd_error); + return; + } + stats_send_cmd(&ctx); + vfuncs->process_response(&ctx); + i_stream_destroy(&ctx.input); +} + +static void handle_disconnection(struct stats_cmd_context *ctx) +{ + i_error("read(%s) failed: %s", ctx->path, + i_stream_get_disconnect_reason(ctx->input)); +} + +static void stats_send_cmd(struct stats_cmd_context *ctx) +{ + int fd; + const char *line; + if (!doveadm_cmd_param_str(ctx->cctx, "socket-path", &ctx->path)) + ctx->path = t_strconcat(doveadm_settings->base_dir, + "/stats-reader", NULL); + + fd = doveadm_connect(ctx->path); + net_set_nonblock(fd, FALSE); + if (write_full(fd, str_data(ctx->cmd), str_len(ctx->cmd)) < 0) + i_fatal("write(%s) failed %m", ctx->path); + ctx->input = i_stream_create_fd_autoclose(&fd, SIZE_MAX); + + if ((line = i_stream_read_next_line(ctx->input)) == NULL) + i_fatal("%s: Failed to read VERSION line", ctx->path); + else if (!version_string_verify(line, "stats-reader-server", 2)) { + i_fatal_status(EX_PROTOCOL, + "%s is not a compatible stats-reader socket", ctx->path); + } +} + +static void dump_timing(const char *const **args, + const enum doveadm_dump_field_type field_types[], + unsigned int fields_count) +{ + unsigned int i, args_count = str_array_length(*args); + + if (args_count > fields_count) + args_count = fields_count; + for (i = 0; i < args_count; i++) { + const char *value = (*args)[i]; + + switch (field_types[i]) { + case DOVEADM_DUMP_FIELD_TYPE_PASSTHROUGH: + break; + case DOVEADM_DUMP_FIELD_TYPE_STDDEV: { + double variance = strtod(value, NULL); + value = t_strdup_printf("%.02f", sqrt(variance)); + break; + } + } + doveadm_print(value); + } + *args += args_count; +} + +static int build_stats_dump_cmd(struct stats_cmd_context *ctx, + const char **error_r ATTR_UNUSED) +{ + bool reset; + struct dump_data *data = t_new(struct dump_data, 1); + const char *fields_raw; + const char **fields; + if (!doveadm_cmd_param_bool(ctx->cctx, "reset", &reset)) + reset = FALSE; + if (!doveadm_cmd_param_str(ctx->cctx, "fields", &fields_raw)) + fields_raw = DOVEADM_DUMP_DEFAULT_FIELDS; + + fields = t_strsplit_spaces(fields_raw, ", "); + data->fields = fields; + data->field_count = str_array_length(fields); + data->field_types = + t_malloc0(sizeof(enum doveadm_dump_field_type) * data->field_count); + ctx->data = data; + ctx->cmd = init_stats_cmd(); + str_append(ctx->cmd, reset ? "DUMP-RESET" : "DUMP"); + unsigned int i; + for (i = 0; i < data->field_count; i++) { + str_append_c(ctx->cmd, '\t'); + if (strcmp(fields[i], "stddev") == 0) { + data->field_types[i] = DOVEADM_DUMP_FIELD_TYPE_STDDEV; + str_append(ctx->cmd, "variance"); + } else { + str_append_tabescaped(ctx->cmd, fields[i]); + } + } + str_append_c(ctx->cmd, '\n'); + return 0; +} + +static void stats_dump_process_response(struct stats_cmd_context *ctx) +{ + unsigned int i; + char *line; + struct dump_data *data = ctx->data; + + doveadm_print_init(DOVEADM_PRINT_TYPE_TAB); + doveadm_print_header_simple("metric_name"); + doveadm_print_header_simple("field"); + for (i = 0; i < data->field_count; i++) + doveadm_print_header(data->fields[i], data->fields[i], + DOVEADM_PRINT_HEADER_FLAG_NUMBER); + + while ((line = i_stream_read_next_line(ctx->input)) != NULL) { + if (line[0] == '\0') + break; + T_BEGIN { + const char *const *args = + t_strsplit_tabescaped_inplace(line); + + const char *metric_name = args[0]; + doveadm_print(metric_name); args++; + doveadm_print("duration"); + dump_timing(&args, data->field_types, data->field_count); + while (*args != NULL) { + doveadm_print(metric_name); + doveadm_print(*args); args++; + dump_timing(&args, data->field_types, data->field_count); + } + } T_END; + } + if (line == NULL) + handle_disconnection(ctx); +} + +static int build_stats_add_cmd(struct stats_cmd_context *ctx, + const char **error_r) +{ + unsigned int i; + const char *parameter; + struct { + const char *name; + const char *default_val; + } params[] = { + { ADD_NAME_PARAM, "" }, + { ADD_DESCR_PARAM, "" }, + { ADD_FIELDS_PARAM, "" }, + { ADD_GROUPBY_PARAM, "" }, + { ADD_FILTER_PARAM, "" }, + { ADD_EXPORTER_PARAM, "" }, + /* Default exporter-include is to be modified + together with stats-settings */ + { ADD_EXPORTERINCL_PARAM, + STATS_METRIC_SETTINGS_DEFAULT_EXPORTER_INCLUDE }, + }; + + ctx->cmd = init_stats_cmd(); + str_append(ctx->cmd, "METRICS-ADD"); + + for (i = 0; i < N_ELEMENTS(params); i++) { + if (!doveadm_cmd_param_str(ctx->cctx, params[i].name, ¶meter)) + parameter = params[i].default_val; + if (parameter[0] == '\0' && + (strcmp(params[i].name, "name") == 0 || + strcmp(params[i].name, "filter") == 0)) { + *error_r = + t_strdup_printf("stats add: missing %s parameter", + params[i].name); + return -1; + } + str_append_c(ctx->cmd, '\t'); + str_append_tabescaped(ctx->cmd, parameter); + } + + str_append_c(ctx->cmd, '\n'); + return 0; +} + +static void stats_modify_process_response(struct stats_cmd_context *ctx) +{ + const char *line = i_stream_read_next_line(ctx->input); + if (line == NULL) { + handle_disconnection(ctx); + return; + } + if (line[0] == '-') + i_error("%s", ++line); + else if (line[0] != '+') + i_error("Invalid response: %s", line); +} + +static int build_stats_remove_cmd(struct stats_cmd_context *ctx, + const char **error_r) +{ + const char *name; + + ctx->cmd = init_stats_cmd(); + str_append(ctx->cmd, "METRICS-REMOVE\t"); + + if (!doveadm_cmd_param_str(ctx->cctx, "name", &name)) { + *error_r = "stats remove: missing name parameter"; + return -1; + } + str_append_tabescaped(ctx->cmd, name); + str_append_c(ctx->cmd, '\n'); + return 0; +} + +static void doveadm_cmd_stats_dump(struct doveadm_cmd_context *cctx) +{ + stats_exec_cmd(cctx, &dump_vfuncs); +} + +static void doveadm_cmd_stats_add(struct doveadm_cmd_context *cctx) +{ + stats_exec_cmd(cctx, &add_vfuncs); +} + +static void doveadm_cmd_stats_remove(struct doveadm_cmd_context *cctx) +{ + stats_exec_cmd(cctx, &remove_vfuncs); +} + +struct doveadm_cmd_ver2 doveadm_cmd_stats_dump_ver2 = { + .cmd = doveadm_cmd_stats_dump, + .name = "stats dump", + .usage = "[-s <stats socket path>] [-r] [-f <fields>]", +DOVEADM_CMD_PARAMS_START +DOVEADM_CMD_PARAM('s', "socket-path", CMD_PARAM_STR, 0) +DOVEADM_CMD_PARAM('r', "reset", CMD_PARAM_BOOL, 0) +DOVEADM_CMD_PARAM('f', "fields", CMD_PARAM_STR, 0) +DOVEADM_CMD_PARAMS_END +}; + +struct doveadm_cmd_ver2 doveadm_cmd_stats_add_ver2 = { + .cmd = doveadm_cmd_stats_add, + .name = "stats add", + .usage = "[--"ADD_DESCR_PARAM" <string>] " + "[--"ADD_EXPORTER_PARAM" <name> [--"ADD_EXPORTERINCL_PARAM" <fields>]] " + "[--"ADD_FIELDS_PARAM" <fields>] " + "[--"ADD_GROUPBY_PARAM" <fields>] <name> <filter>", +DOVEADM_CMD_PARAMS_START +DOVEADM_CMD_PARAM('\0', ADD_NAME_PARAM, CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) +DOVEADM_CMD_PARAM('\0', ADD_FILTER_PARAM, CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) +DOVEADM_CMD_PARAM('\0', ADD_EXPORTER_PARAM, CMD_PARAM_STR, 0) +DOVEADM_CMD_PARAM('\0', ADD_EXPORTERINCL_PARAM, CMD_PARAM_STR, 0) +DOVEADM_CMD_PARAM('\0', ADD_DESCR_PARAM, CMD_PARAM_STR, 0) +DOVEADM_CMD_PARAM('\0', ADD_FIELDS_PARAM, CMD_PARAM_STR, 0) +DOVEADM_CMD_PARAM('\0', ADD_GROUPBY_PARAM, CMD_PARAM_STR, 0) +DOVEADM_CMD_PARAMS_END +}; + +struct doveadm_cmd_ver2 doveadm_cmd_stats_remove_ver2 = { + .cmd = doveadm_cmd_stats_remove, + .name = "stats remove", + .usage = "<name>", +DOVEADM_CMD_PARAMS_START +DOVEADM_CMD_PARAM('\0', "name", CMD_PARAM_STR, CMD_PARAM_FLAG_POSITIONAL) +DOVEADM_CMD_PARAMS_END +}; |