--- /dev/null
+/* Copyright (c) 2017 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"
+
+static void dump_timing(const char *const **args, 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++)
+ doveadm_print((*args)[i]);
+ *args += args_count;
+}
+
+static void stats_dump(const char *path, const char *const *fields)
+{
+ struct istream *input;
+ string_t *cmd = t_str_new(128);
+ unsigned int i, fields_count = str_array_length(fields);
+ char *line;
+ int fd;
+
+ fd = doveadm_connect(path);
+ net_set_nonblock(fd, FALSE);
+ str_append(cmd, "VERSION\tstats-reader-client\t2\t0\nDUMP");
+ for (i = 0; i < fields_count; i++) {
+ str_append_c(cmd, '\t');
+ str_append_tabescaped(cmd, fields[i]);
+ }
+ str_append_c(cmd, '\n');
+ if (write_full(fd, str_data(cmd), str_len(cmd)) < 0)
+ i_fatal("write(%s) failed: %m", path);
+
+ input = i_stream_create_fd_autoclose(&fd, (size_t)-1);
+ if ((line = i_stream_read_next_line(input)) == NULL)
+ i_fatal("%s: Failed to read VERSION line", path);
+ else if (!version_string_verify(line, "stats-reader-server", 2)) {
+ i_fatal_status(EX_PROTOCOL,
+ "%s is not a compatible stats-reader socket", path);
+ }
+
+ doveadm_print_header_simple("metric_name");
+ doveadm_print_header_simple("field");
+ for (i = 0; i < fields_count; i++)
+ doveadm_print_header_simple(fields[i]);
+
+ while ((line = i_stream_read_next_line(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, fields_count);
+ while (*args != NULL) {
+ doveadm_print(metric_name);
+ doveadm_print(*args); args++;
+ dump_timing(&args, fields_count);
+ }
+ } T_END;
+ }
+
+ 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;
+
+ if (!doveadm_cmd_param_str(cctx, "socket-path", &path))
+ path = t_strconcat(doveadm_settings->base_dir, "/stats-reader", NULL);
+
+ doveadm_print_init(DOVEADM_PRINT_TYPE_TAB);
+ const char *const fields[] = {
+ "count", "sum", "min", "max", "avg", "median", "%95", NULL
+ };
+ stats_dump(path, fields);
+ return;
+}
+
+struct doveadm_cmd_ver2 doveadm_cmd_stats_dump_ver2 = {
+ .cmd = doveadm_cmd_stats_dump,
+ .name = "stats dump",
+ .usage = "[-s <stats socket path>]",
+DOVEADM_CMD_PARAMS_START
+DOVEADM_CMD_PARAM('s', "socket-path", CMD_PARAM_STR, 0)
+DOVEADM_CMD_PARAMS_END
+};