#include "lib.h"
#include "array.h"
+#include "str.h"
#include "stats-dist.h"
#include "time-util.h"
#include "event-filter.h"
#include "stats-settings.h"
#include "stats-metrics.h"
+#include <ctype.h>
+
struct stats_metrics {
pool_t pool;
struct event_filter *stats_filter; /* stats-only */
ARRAY(struct metric *) metrics;
};
+static void
+stats_metric_event(struct metric *metric, struct event *event, pool_t pool);
+static struct metric *
+stats_metric_sub_metric_alloc(struct metric *metric, const char *name, pool_t pool);
+
+/* This does not need to be unique as it's a display name */
+static const char *sub_metric_name_create(pool_t pool, const char *name)
+{
+ string_t *sub_name = str_new(pool, 32);
+ /* use up to 32 bytes */
+ for (const char *p = name; *p != '\0' && sub_name->used < 32;
+ p++) {
+ char c = *p;
+ if (!i_isalnum(c))
+ c = '_';
+ else
+ c = i_tolower(c);
+ str_append_c(sub_name, c);
+ }
+ return str_c(sub_name);
+}
+
static void
stats_metric_settings_to_query(const struct stats_metric_settings *set,
struct event_filter_query *query_r)
fields = t_strsplit_spaces(set->fields, " ");
metric = stats_metric_alloc(metrics->pool, set->name, fields);
+
+ if (*set->group_by != '\0')
+ metric->group_by = (const char *const *)p_strsplit_spaces(metrics->pool, set->group_by, " ");
+
array_push_back(&metrics->metrics, &metric);
stats_metric_settings_to_query(set, &query);
return metrics->combined_filter;
}
+static struct metric *
+stats_metric_get_sub_metric(struct metric *metric,
+ const struct metric_value *value)
+{
+ struct metric *const *sub_metrics;
+
+ /* lookup sub-metric */
+ array_foreach (&metric->sub_metrics, sub_metrics) {
+ if ((*sub_metrics)->group_value.type == METRIC_VALUE_TYPE_STR &&
+ memcmp((*sub_metrics)->group_value.hash, value->hash,
+ SHA1_RESULTLEN) == 0)
+ return *sub_metrics;
+ else if ((*sub_metrics)->group_value.type == METRIC_VALUE_TYPE_INT &&
+ (*sub_metrics)->group_value.intmax == value->intmax)
+ return *sub_metrics;
+ }
+ return NULL;
+}
+
+static struct metric *
+stats_metric_sub_metric_alloc(struct metric *metric, const char *name, pool_t pool)
+{
+ struct metric *sub_metric;
+ ARRAY_TYPE(const_string) fields;
+ t_array_init(&fields, metric->fields_count);
+ for (unsigned int i = 0; i < metric->fields_count; i++)
+ array_append(&fields, &metric->fields[i].field_key, 1);
+ array_append_zero(&fields);
+ sub_metric = stats_metric_alloc(pool, metric->name,
+ array_idx(&fields, 0));
+ sub_metric->sub_name = sub_metric_name_create(pool, name);
+ array_append(&metric->sub_metrics, &sub_metric, 1);
+ return sub_metric;
+}
+
+static void
+stats_metric_group_by(struct metric *metric, struct event *event, pool_t pool)
+{
+ struct metric *sub_metric;
+ const char *const *group = metric->group_by;
+ const struct event_field *field =
+ event_find_field(event, *group);
+ struct metric_value value;
+
+ /* ignore missing field */
+ if (field == NULL)
+ return;
+ switch (field->value_type) {
+ case EVENT_FIELD_VALUE_TYPE_STR:
+ value.type = METRIC_VALUE_TYPE_STR;
+ /* use sha1 of value to avoid excessive memory usage in case the
+ actual value is quite long */
+ sha1_get_digest(field->value.str, strlen(field->value.str),
+ value.hash);
+ break;
+ case EVENT_FIELD_VALUE_TYPE_INTMAX:
+ value.type = METRIC_VALUE_TYPE_INT;
+ value.intmax = field->value.intmax;
+ break;
+ case EVENT_FIELD_VALUE_TYPE_TIMEVAL:
+ return;
+ }
+
+ if (!array_is_created(&metric->sub_metrics))
+ p_array_init(&metric->sub_metrics, pool, 8);
+
+ sub_metric = stats_metric_get_sub_metric(metric, &value);
+
+ if (sub_metric == NULL) T_BEGIN {
+ const char *value_label;
+ if (value.type == METRIC_VALUE_TYPE_STR)
+ value_label = field->value.str;
+ else if (value.type == METRIC_VALUE_TYPE_INT)
+ value_label = dec2str(field->value.intmax);
+ else
+ i_unreached();
+ sub_metric = stats_metric_sub_metric_alloc(metric, value_label,
+ pool);
+ if (group[1] != NULL)
+ sub_metric->group_by = group+1;
+ sub_metric->group_value.intmax = value.intmax;
+ memcpy(sub_metric->group_value.hash, value.hash, SHA1_RESULTLEN);
+ } T_END;
+
+ /* sub-metrics are recursive, so each sub-metric can have additional
+ sub-metrics. */
+ stats_metric_event(sub_metric, event, pool);
+}
+
static void
-stats_metric_event(struct metric *metric, struct event *event)
+stats_metric_event(struct metric *metric, struct event *event, pool_t pool)
{
intmax_t duration;
}
stats_dist_add(metric->fields[i].stats, num);
}
+
+ if (metric->group_by != NULL)
+ stats_metric_group_by(metric, event, pool);
}
static void
/* process stats */
iter = event_filter_match_iter_init(metrics->stats_filter, event, ctx);
while ((metric = event_filter_match_iter_next(iter)) != NULL) T_BEGIN {
- stats_metric_event(metric, event);
+ stats_metric_event(metric, event, metrics->pool);
} T_END;
event_filter_match_iter_deinit(&iter);