1 /* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */
2 
3 #include "stats-common.h"
4 #include "array.h"
5 #include "str.h"
6 #include "str-sanitize.h"
7 #include "stats-dist.h"
8 #include "time-util.h"
9 #include "event-filter.h"
10 #include "event-exporter.h"
11 #include "stats-settings.h"
12 #include "stats-metrics.h"
13 #include "settings-parser.h"
14 
15 #include <ctype.h>
16 
17 #define LOG_EXPORTER_LONG_FIELD_TRUNCATE_LEN 1000
18 
19 struct stats_metrics {
20 	pool_t pool;
21 	struct event_filter *filter; /* stats & export */
22 	ARRAY(struct exporter *) exporters;
23 	ARRAY(struct metric *) metrics;
24 };
25 
26 static void
27 stats_metric_event(struct metric *metric, struct event *event, pool_t pool);
28 static struct metric *
29 stats_metric_sub_metric_alloc(struct metric *metric, const char *name, pool_t pool);
30 
stats_exporters_add_set(struct stats_metrics * metrics,const struct stats_exporter_settings * set)31 static void stats_exporters_add_set(struct stats_metrics *metrics,
32 				    const struct stats_exporter_settings *set)
33 {
34 	struct exporter *exporter;
35 
36 	exporter = p_new(metrics->pool, struct exporter, 1);
37 	exporter->name = p_strdup(metrics->pool, set->name);
38 	exporter->transport_args = p_strdup(metrics->pool, set->transport_args);
39 	exporter->transport_timeout = set->transport_timeout;
40 	exporter->time_format = set->parsed_time_format;
41 
42 	/* TODO: The following should be plugable.
43 	 *
44 	 * Note: Make sure to mirror any changes to the below code in
45 	 * stats_exporter_settings_check().
46 	 */
47 	if (strcmp(set->format, "none") == 0) {
48 		exporter->format = event_export_fmt_none;
49 		exporter->format_mime_type = "application/octet-stream";
50 	} else if (strcmp(set->format, "json") == 0) {
51 		exporter->format = event_export_fmt_json;
52 		exporter->format_mime_type = "application/json";
53 	} else if (strcmp(set->format, "tab-text") == 0) {
54 		exporter->format = event_export_fmt_tabescaped_text;
55 		exporter->format_mime_type = "text/plain";
56 	} else {
57 		i_unreached();
58 	}
59 
60 	/* TODO: The following should be plugable.
61 	 *
62 	 * Note: Make sure to mirror any changes to the below code in
63 	 * stats_exporter_settings_check().
64 	 */
65 	if (strcmp(set->transport, "drop") == 0) {
66 		exporter->transport = event_export_transport_drop;
67 	} else if (strcmp(set->transport, "http-post") == 0) {
68 		exporter->transport = event_export_transport_http_post;
69 	} else if (strcmp(set->transport, "log") == 0) {
70 		exporter->transport = event_export_transport_log;
71 		exporter->format_max_field_len =
72 			LOG_EXPORTER_LONG_FIELD_TRUNCATE_LEN;
73 	} else {
74 		i_unreached();
75 	}
76 
77 	exporter->transport_args = set->transport_args;
78 
79 	array_push_back(&metrics->exporters, &exporter);
80 }
81 
82 static struct metric *
stats_metric_alloc(pool_t pool,const char * name,const struct stats_metric_settings * set,const char * const * fields)83 stats_metric_alloc(pool_t pool, const char *name,
84 		   const struct stats_metric_settings *set,
85 		   const char *const *fields)
86 {
87 	struct metric *metric = p_new(pool, struct metric, 1);
88 	metric->name = p_strdup(pool, name);
89 	metric->set = set;
90 	metric->duration_stats = stats_dist_init();
91 	metric->fields_count = str_array_length(fields);
92 	if (metric->fields_count > 0) {
93 	    metric->fields = p_new(pool, struct metric_field,
94 				   metric->fields_count);
95 		for (unsigned int i = 0; i < metric->fields_count; i++) {
96 			metric->fields[i].field_key = p_strdup(pool, fields[i]);
97 			metric->fields[i].stats = stats_dist_init();
98 		}
99 	}
100 	return metric;
101 }
102 
stats_metrics_add_set(struct stats_metrics * metrics,const struct stats_metric_settings * set)103 static void stats_metrics_add_set(struct stats_metrics *metrics,
104 				  const struct stats_metric_settings *set)
105 {
106 	struct exporter *exporter;
107 	struct metric *metric;
108 	const char *const *fields;
109 	const char *const *tmp;
110 
111 	fields = t_strsplit_spaces(set->fields, " ");
112 	metric = stats_metric_alloc(metrics->pool, set->metric_name, set, fields);
113 
114 	if (array_is_created(&set->parsed_group_by))
115 		metric->group_by = array_get(&set->parsed_group_by,
116 					     &metric->group_by_count);
117 
118 	array_push_back(&metrics->metrics, &metric);
119 
120 	event_filter_merge_with_context(metrics->filter, set->parsed_filter, metric);
121 
122 	/*
123 	 * Metrics may also be exported - make sure exporter info is set
124 	 */
125 
126 	if (set->exporter[0] == '\0')
127 		return; /* not exported */
128 
129 	array_foreach_elem(&metrics->exporters, exporter) {
130 		if (strcmp(set->exporter, exporter->name) == 0) {
131 			metric->export_info.exporter = exporter;
132 			break;
133 		}
134 	}
135 
136 	if (metric->export_info.exporter == NULL)
137 		i_panic("Could not find exporter (%s) for metric (%s)",
138 			set->exporter, set->metric_name);
139 
140 	/* Defaults */
141 	metric->export_info.include = EVENT_EXPORTER_INCL_NONE;
142 
143 	tmp = t_strsplit_spaces(set->exporter_include, " ");
144 	for (; *tmp != NULL; tmp++) {
145 		if (strcmp(*tmp, "name") == 0)
146 			metric->export_info.include |= EVENT_EXPORTER_INCL_NAME;
147 		else if (strcmp(*tmp, "hostname") == 0)
148 			metric->export_info.include |= EVENT_EXPORTER_INCL_HOSTNAME;
149 		else if (strcmp(*tmp, "timestamps") == 0)
150 			metric->export_info.include |= EVENT_EXPORTER_INCL_TIMESTAMPS;
151 		else if (strcmp(*tmp, "categories") == 0)
152 			metric->export_info.include |= EVENT_EXPORTER_INCL_CATEGORIES;
153 		else if (strcmp(*tmp, "fields") == 0)
154 			metric->export_info.include |= EVENT_EXPORTER_INCL_FIELDS;
155 		else
156 			i_warning("Ignoring unknown exporter include '%s'", *tmp);
157 	}
158 }
159 
160 static struct stats_metric_settings *
stats_metric_settings_dup(pool_t pool,const struct stats_metric_settings * src)161 stats_metric_settings_dup(pool_t pool, const struct stats_metric_settings *src)
162 {
163 	struct stats_metric_settings *set = p_new(pool, struct stats_metric_settings, 1);
164 
165 	set->metric_name = p_strdup(pool, src->metric_name);
166 	set->description = p_strdup(pool, src->description);
167 	set->fields = p_strdup(pool, src->fields);
168 	set->group_by = p_strdup(pool, src->group_by);
169 	set->filter = p_strdup(pool, src->filter);
170 	set->exporter = p_strdup(pool, src->exporter);
171 	set->exporter_include = p_strdup(pool, src->exporter_include);
172 
173 	return set;
174 }
175 
176 static struct metric *
stats_metrics_find(struct stats_metrics * metrics,const char * name,unsigned int * idx_r)177 stats_metrics_find(struct stats_metrics *metrics,
178 		   const char *name, unsigned int *idx_r)
179 {
180 	struct metric *const *m;
181 	array_foreach(&metrics->metrics, m) {
182 		if (strcmp((*m)->name, name) == 0) {
183 			*idx_r = array_foreach_idx(&metrics->metrics, m);
184 			return *m;
185 		}
186 	}
187 	return NULL;
188 }
189 
stats_metrics_add_dynamic(struct stats_metrics * metrics,struct stats_metric_settings * set,const char ** error_r)190 bool stats_metrics_add_dynamic(struct stats_metrics *metrics,
191 			       struct stats_metric_settings *set,
192 			       const char **error_r)
193 {
194 	unsigned int existing_idx ATTR_UNUSED;
195 	if (stats_metrics_find(metrics, set->metric_name, &existing_idx) != NULL) {
196 		*error_r = "Metric already exists";
197 		return FALSE;
198 	}
199 
200 	struct stats_metric_settings *_set =
201 		stats_metric_settings_dup(metrics->pool, set);
202         if (!stats_metric_setting_parser_info.check_func(_set, metrics->pool, error_r))
203 		return FALSE;
204 	stats_metrics_add_set(metrics, _set);
205 	return TRUE;
206 }
207 
stats_metrics_remove_dynamic(struct stats_metrics * metrics,const char * name)208 bool stats_metrics_remove_dynamic(struct stats_metrics *metrics,
209 				  const char *name)
210 {
211 	unsigned int m_idx;
212 	struct metric *m = stats_metrics_find(metrics, name, &m_idx);
213 	if (m != NULL) {
214 		array_delete(&metrics->metrics, m_idx, 1);
215 		return event_filter_remove_queries_with_context(metrics->filter, m);
216 	}
217 	return FALSE;
218 }
219 
220 static void
stats_metrics_add_from_settings(struct stats_metrics * metrics,const struct stats_settings * set)221 stats_metrics_add_from_settings(struct stats_metrics *metrics,
222 				const struct stats_settings *set)
223 {
224 	/* add all the exporters first */
225 	if (!array_is_created(&set->exporters)) {
226 		p_array_init(&metrics->exporters, metrics->pool, 0);
227 	} else {
228 		struct stats_exporter_settings *exporter_set;
229 
230 		p_array_init(&metrics->exporters, metrics->pool,
231 			     array_count(&set->exporters));
232 		array_foreach_elem(&set->exporters, exporter_set)
233 			stats_exporters_add_set(metrics, exporter_set);
234 	}
235 
236 	/* then add all the metrics */
237 	if (!array_is_created(&set->metrics)) {
238 		p_array_init(&metrics->metrics, metrics->pool, 0);
239 	} else {
240 		struct stats_metric_settings *metric_set;
241 
242 		p_array_init(&metrics->metrics, metrics->pool,
243 			     array_count(&set->metrics));
244 		array_foreach_elem(&set->metrics, metric_set) T_BEGIN {
245 			stats_metrics_add_set(metrics, metric_set);
246 		} T_END;
247 	}
248 }
249 
stats_metrics_init(const struct stats_settings * set)250 struct stats_metrics *stats_metrics_init(const struct stats_settings *set)
251 {
252 	struct stats_metrics *metrics;
253 	pool_t pool = pool_alloconly_create("stats metrics", 1024);
254 
255 	metrics = p_new(pool, struct stats_metrics, 1);
256 	metrics->pool = pool;
257 	metrics->filter = event_filter_create();
258 	stats_metrics_add_from_settings(metrics, set);
259 	return metrics;
260 }
261 
stats_metric_free(struct metric * metric)262 static void stats_metric_free(struct metric *metric)
263 {
264 	struct metric *sub_metric;
265 	stats_dist_deinit(&metric->duration_stats);
266 	for (unsigned int i = 0; i < metric->fields_count; i++)
267 		stats_dist_deinit(&metric->fields[i].stats);
268 	if (!array_is_created(&metric->sub_metrics))
269 		return;
270 	array_foreach_elem(&metric->sub_metrics, sub_metric)
271 		stats_metric_free(sub_metric);
272 }
273 
stats_export_deinit(void)274 static void stats_export_deinit(void)
275 {
276 	/* no need for event_export_transport_drop_deinit() - no-op */
277 	event_export_transport_http_post_deinit();
278 	/* no need for event_export_transport_log_deinit() - no-op */
279 }
280 
stats_metrics_deinit(struct stats_metrics ** _metrics)281 void stats_metrics_deinit(struct stats_metrics **_metrics)
282 {
283 	struct stats_metrics *metrics = *_metrics;
284 	struct metric *metric;
285 
286 	*_metrics = NULL;
287 
288 	stats_export_deinit();
289 
290 	array_foreach_elem(&metrics->metrics, metric)
291 		stats_metric_free(metric);
292 	event_filter_unref(&metrics->filter);
293 	pool_unref(&metrics->pool);
294 }
295 
stats_metric_reset(struct metric * metric)296 static void stats_metric_reset(struct metric *metric)
297 {
298 	struct metric *sub_metric;
299 	stats_dist_reset(metric->duration_stats);
300 	for (unsigned int i = 0; i < metric->fields_count; i++)
301 		stats_dist_reset(metric->fields[i].stats);
302 	if (!array_is_created(&metric->sub_metrics))
303 		return;
304 	array_foreach_elem(&metric->sub_metrics, sub_metric)
305 		stats_metric_reset(sub_metric);
306 }
307 
stats_metrics_reset(struct stats_metrics * metrics)308 void stats_metrics_reset(struct stats_metrics *metrics)
309 {
310 	struct metric *metric;
311 
312 	array_foreach_elem(&metrics->metrics, metric)
313 		stats_metric_reset(metric);
314 }
315 
316 struct event_filter *
stats_metrics_get_event_filter(struct stats_metrics * metrics)317 stats_metrics_get_event_filter(struct stats_metrics *metrics)
318 {
319 	return metrics->filter;
320 }
321 
322 static struct metric *
stats_metric_get_sub_metric(struct metric * metric,const struct metric_value * value)323 stats_metric_get_sub_metric(struct metric *metric,
324 			    const struct metric_value *value)
325 {
326 	struct metric *sub_metrics;
327 
328 	/* lookup sub-metric */
329 	array_foreach_elem(&metric->sub_metrics, sub_metrics) {
330 		switch (sub_metrics->group_value.type) {
331 		case METRIC_VALUE_TYPE_STR:
332 			if (memcmp(sub_metrics->group_value.hash, value->hash,
333 				   SHA1_RESULTLEN) == 0)
334 				return sub_metrics;
335 			break;
336 		case METRIC_VALUE_TYPE_INT:
337 			if (sub_metrics->group_value.intmax == value->intmax)
338 				return sub_metrics;
339 			break;
340 		case METRIC_VALUE_TYPE_BUCKET_INDEX:
341 			if (sub_metrics->group_value.intmax == value->intmax)
342 				return sub_metrics;
343 			break;
344 		}
345 	}
346 	return NULL;
347 }
348 
349 static struct metric *
stats_metric_sub_metric_alloc(struct metric * metric,const char * name,pool_t pool)350 stats_metric_sub_metric_alloc(struct metric *metric, const char *name, pool_t pool)
351 {
352 	struct metric *sub_metric;
353 	ARRAY_TYPE(const_string) fields;
354 	t_array_init(&fields, metric->fields_count);
355 	for (unsigned int i = 0; i < metric->fields_count; i++)
356 		array_append(&fields, &metric->fields[i].field_key, 1);
357 	array_append_zero(&fields);
358 	sub_metric = stats_metric_alloc(pool, metric->name, metric->set,
359 					array_idx(&fields, 0));
360 	sub_metric->sub_name = p_strdup(pool, str_sanitize_utf8(name, 32));
361 	array_append(&metric->sub_metrics, &sub_metric, 1);
362 	return sub_metric;
363 }
364 
365 static bool
stats_metric_group_by_discrete(const struct event_field * field,struct metric_value * value)366 stats_metric_group_by_discrete(const struct event_field *field,
367 			       struct metric_value *value)
368 {
369 	switch (field->value_type) {
370 	case EVENT_FIELD_VALUE_TYPE_STR:
371 		value->type = METRIC_VALUE_TYPE_STR;
372 		/* use sha1 of value to avoid excessive memory usage in case the
373 		   actual value is quite long */
374 		sha1_get_digest(field->value.str, strlen(field->value.str),
375 				value->hash);
376 		return TRUE;
377 	case EVENT_FIELD_VALUE_TYPE_INTMAX:
378 		value->type = METRIC_VALUE_TYPE_INT;
379 		value->intmax = field->value.intmax;
380 		return TRUE;
381 	case EVENT_FIELD_VALUE_TYPE_TIMEVAL:
382 		return FALSE;
383 	}
384 
385 	i_unreached();
386 }
387 
388 /* convert the value to a bucket index */
389 static bool
stats_metric_group_by_quantized(const struct event_field * field,struct metric_value * value,const struct stats_metric_settings_group_by * group_by)390 stats_metric_group_by_quantized(const struct event_field *field,
391 				struct metric_value *value,
392 				const struct stats_metric_settings_group_by *group_by)
393 {
394 	switch (field->value_type) {
395 	case EVENT_FIELD_VALUE_TYPE_STR:
396 	case EVENT_FIELD_VALUE_TYPE_TIMEVAL:
397 		return FALSE;
398 	case EVENT_FIELD_VALUE_TYPE_INTMAX:
399 		break;
400 	}
401 
402 	value->type = METRIC_VALUE_TYPE_BUCKET_INDEX;
403 
404 	for (unsigned int i = 0; i < group_by->num_ranges; i++) {
405 		if ((field->value.intmax <= group_by->ranges[i].min) ||
406 		    (field->value.intmax > group_by->ranges[i].max))
407 			continue;
408 
409 		value->intmax = i;
410 		return TRUE;
411 	}
412 
413 	i_panic("failed to find a matching bucket for '%s'=%jd",
414 		group_by->field, field->value.intmax);
415 }
416 
417 /* convert value to a bucket label */
418 static const char *
stats_metric_group_by_quantized_label(const struct event_field * field,const struct stats_metric_settings_group_by * group_by,const size_t bucket_index)419 stats_metric_group_by_quantized_label(const struct event_field *field,
420 				      const struct stats_metric_settings_group_by *group_by,
421 				      const size_t bucket_index)
422 {
423 	const struct stats_metric_settings_bucket_range *range = &group_by->ranges[bucket_index];
424 	const char *name = group_by->field;
425 	const char *label;
426 
427 	switch (field->value_type) {
428 	case EVENT_FIELD_VALUE_TYPE_STR:
429 	case EVENT_FIELD_VALUE_TYPE_TIMEVAL:
430 		i_unreached();
431 	case EVENT_FIELD_VALUE_TYPE_INTMAX:
432 		break;
433 	}
434 
435 	if (range->min == INTMAX_MIN)
436 		label = t_strdup_printf("%s_ninf_%jd", name, range->max);
437 	else if (range->max == INTMAX_MAX)
438 		label = t_strdup_printf("%s_%jd_inf", name, range->min + 1);
439 	else
440 		label = t_strdup_printf("%s_%jd_%jd", name,
441 					range->min + 1, range->max);
442 
443 	return label;
444 }
445 
446 static bool
stats_metric_group_by_get_value(const struct event_field * field,const struct stats_metric_settings_group_by * group_by,struct metric_value * value)447 stats_metric_group_by_get_value(const struct event_field *field,
448 				const struct stats_metric_settings_group_by *group_by,
449 				struct metric_value *value)
450 {
451 	switch (group_by->func) {
452 	case STATS_METRIC_GROUPBY_DISCRETE:
453 		if (!stats_metric_group_by_discrete(field, value))
454 			return FALSE;
455 		return TRUE;
456 	case STATS_METRIC_GROUPBY_QUANTIZED:
457 		if (!stats_metric_group_by_quantized(field, value, group_by))
458 			return FALSE;
459 		return TRUE;
460 	}
461 
462 	i_panic("unknown group-by function %d", group_by->func);
463 }
464 
465 static const char *
stats_metric_group_by_get_label(const struct event_field * field,const struct stats_metric_settings_group_by * group_by,const struct metric_value * value)466 stats_metric_group_by_get_label(const struct event_field *field,
467 				const struct stats_metric_settings_group_by *group_by,
468 				const struct metric_value *value)
469 {
470 	switch (group_by->func) {
471 	case STATS_METRIC_GROUPBY_DISCRETE:
472 		i_unreached();
473 	case STATS_METRIC_GROUPBY_QUANTIZED:
474 		return stats_metric_group_by_quantized_label(field, group_by,
475 							     value->intmax);
476 	}
477 
478 	i_panic("unknown group-by function %d", group_by->func);
479 }
480 
481 static void
stats_metric_group_by(struct metric * metric,struct event * event,pool_t pool)482 stats_metric_group_by(struct metric *metric, struct event *event, pool_t pool)
483 {
484 	const struct stats_metric_settings_group_by *group_by = &metric->group_by[0];
485 	const struct event_field *field =
486 		event_find_field_recursive(event, group_by->field);
487 	struct metric *sub_metric;
488 	struct metric_value value;
489 
490 	/* ignore missing field */
491 	if (field == NULL)
492 		return;
493 
494 	if (!stats_metric_group_by_get_value(field, group_by, &value))
495 		return;
496 
497 	if (!array_is_created(&metric->sub_metrics))
498 		p_array_init(&metric->sub_metrics, pool, 8);
499 
500 	sub_metric = stats_metric_get_sub_metric(metric, &value);
501 
502 	if (sub_metric == NULL) T_BEGIN {
503 		const char *value_label = NULL;
504 
505 		switch (value.type) {
506 		case METRIC_VALUE_TYPE_STR:
507 			value_label = field->value.str;
508 			break;
509 		case METRIC_VALUE_TYPE_INT:
510 			value_label = dec2str(field->value.intmax);
511 			break;
512 		case METRIC_VALUE_TYPE_BUCKET_INDEX:
513 			value_label = stats_metric_group_by_get_label(field,
514 								      group_by,
515 								      &value);
516 			break;
517 		}
518 
519 		sub_metric = stats_metric_sub_metric_alloc(metric, value_label,
520 							   pool);
521 		if (metric->group_by_count > 1) {
522 			sub_metric->group_by_count = metric->group_by_count - 1;
523 			sub_metric->group_by = &metric->group_by[1];
524 		}
525 		sub_metric->group_value.type = value.type;
526 		sub_metric->group_value.intmax = value.intmax;
527 		memcpy(sub_metric->group_value.hash, value.hash, SHA1_RESULTLEN);
528 	} T_END;
529 
530 	/* sub-metrics are recursive, so each sub-metric can have additional
531 	   sub-metrics. */
532 	stats_metric_event(sub_metric, event, pool);
533 }
534 
535 static void
stats_metric_event_field(struct event * event,const char * fieldname,struct stats_dist * stats)536 stats_metric_event_field(struct event *event, const char *fieldname,
537 			 struct stats_dist *stats)
538 {
539 	const struct event_field *field =
540 		event_find_field_recursive(event, fieldname);
541 	intmax_t num = 0;
542 
543 	if (field == NULL)
544 		return;
545 
546 	switch (field->value_type) {
547 	case EVENT_FIELD_VALUE_TYPE_STR:
548 		break;
549 	case EVENT_FIELD_VALUE_TYPE_INTMAX:
550 		num = field->value.intmax;
551 		break;
552 	case EVENT_FIELD_VALUE_TYPE_TIMEVAL:
553 		num = field->value.timeval.tv_sec * 1000000ULL +
554 			field->value.timeval.tv_usec;
555 		break;
556 	}
557 
558 	stats_dist_add(stats, num);
559 }
560 
561 static void
stats_metric_event(struct metric * metric,struct event * event,pool_t pool)562 stats_metric_event(struct metric *metric, struct event *event, pool_t pool)
563 {
564 	/* duration is special - we always add it */
565 	stats_metric_event_field(event, STATS_EVENT_FIELD_NAME_DURATION,
566 				 metric->duration_stats);
567 
568 	for (unsigned int i = 0; i < metric->fields_count; i++)
569 		stats_metric_event_field(event,
570 					 metric->fields[i].field_key,
571 					 metric->fields[i].stats);
572 
573 	if (metric->group_by != NULL)
574 		stats_metric_group_by(metric, event, pool);
575 }
576 
577 static void
stats_export_event(struct metric * metric,struct event * oldevent)578 stats_export_event(struct metric *metric, struct event *oldevent)
579 {
580 	const struct metric_export_info *info = &metric->export_info;
581 	const struct exporter *exporter = info->exporter;
582 	struct event *event;
583 
584 	i_assert(exporter != NULL);
585 
586 	event = event_flatten(oldevent);
587 
588 	T_BEGIN {
589 		buffer_t *buf;
590 
591 		buf = t_buffer_create(128);
592 
593 		exporter->format(metric, event, buf);
594 		exporter->transport(exporter, buf);
595 	} T_END;
596 
597 	event_unref(&event);
598 }
599 
stats_metrics_event(struct stats_metrics * metrics,struct event * event,const struct failure_context * ctx)600 void stats_metrics_event(struct stats_metrics *metrics, struct event *event,
601 			 const struct failure_context *ctx)
602 {
603 	struct event_filter_match_iter *iter;
604 	struct metric *metric;
605 	uintmax_t duration;
606 
607 	/* Note: Adding the field here means that it will get exported
608 	   below.  This is necessary to allow group-by functions to quantize
609 	   based on the event duration. */
610 	event_get_last_duration(event, &duration);
611 	event_add_int(event, STATS_EVENT_FIELD_NAME_DURATION, duration);
612 
613 	/* process stats & exports */
614 	iter = event_filter_match_iter_init(metrics->filter, event, ctx);
615 	while ((metric = event_filter_match_iter_next(iter)) != NULL) T_BEGIN {
616 		/* every metric is fed into stats */
617 		stats_metric_event(metric, event, metrics->pool);
618 
619 		/* some metrics are exported */
620 		if (metric->export_info.exporter != NULL)
621 			stats_export_event(metric, event);
622 	} T_END;
623 	event_filter_match_iter_deinit(&iter);
624 }
625 
626 struct stats_metrics_iter {
627 	struct stats_metrics *metrics;
628 	unsigned int idx;
629 };
630 
631 struct stats_metrics_iter *
stats_metrics_iterate_init(struct stats_metrics * metrics)632 stats_metrics_iterate_init(struct stats_metrics *metrics)
633 {
634 	struct stats_metrics_iter *iter;
635 
636 	iter = i_new(struct stats_metrics_iter, 1);
637 	iter->metrics = metrics;
638 	return iter;
639 }
640 
stats_metrics_iterate(struct stats_metrics_iter * iter)641 const struct metric *stats_metrics_iterate(struct stats_metrics_iter *iter)
642 {
643 	struct metric *const *metrics;
644 	unsigned int count;
645 
646 	metrics = array_get(&iter->metrics->metrics, &count);
647 	if (iter->idx >= count)
648 		return NULL;
649 	return metrics[iter->idx++];
650 }
651 
stats_metrics_iterate_deinit(struct stats_metrics_iter ** _iter)652 void stats_metrics_iterate_deinit(struct stats_metrics_iter **_iter)
653 {
654 	struct stats_metrics_iter *iter = *_iter;
655 
656 	*_iter = NULL;
657 	i_free(iter);
658 }
659