1: -module(mongoose_instrument_metrics_SUITE).
    2: -compile([export_all, nowarn_export_all]).
    3: 
    4: -include_lib("eunit/include/eunit.hrl").
    5: -include_lib("common_test/include/ct.hrl").
    6: 
    7: -define(LABELS, #{host_type => <<"localhost">>}).
    8: -define(LABELS2, #{host_type => <<"test type">>}).
    9: -define(HOST_TYPE, <<"localhost">>).
   10: -define(HOST_TYPE2, <<"test type">>).
   11: 
   12: %% Setup and teardown
   13: 
   14: all() ->
   15:     [{group, prometheus},
   16:      {group, exometer},
   17:      {group, exometer_global},
   18:      {group, prometheus_and_exometer}
   19:     ].
   20: 
   21: groups() ->
   22:     [{prometheus, [parallel], [prometheus_skips_non_metric_event,
   23:                                prometheus_counter_is_created_but_not_initialized,
   24:                                prometheus_counter_is_updated_separately_for_different_labels,
   25:                                prometheus_histogram_is_created_but_not_initialized,
   26:                                prometheus_histogram_is_updated_separately_for_different_labels,
   27:                                multiple_prometheus_metrics_are_updated]},
   28:      {exometer, [parallel], [exometer_skips_non_metric_event,
   29:                              exometer_spiral_is_created_and_initialized,
   30:                              exometer_spiral_is_updated_separately_for_different_labels,
   31:                              exometer_histogram_is_created_and_initialized,
   32:                              exometer_histogram_is_updated_separately_for_different_labels,
   33:                              multiple_exometer_metrics_are_updated]},
   34:      {exometer_global, [parallel], [multiple_exometer_metrics_are_updated]},
   35:      {prometheus_and_exometer, [parallel], [prometheus_and_exometer_metrics_are_updated]}
   36:     ].
   37: 
   38: init_per_group(Group, Config) ->
   39:     [application:ensure_all_started(App) || App <- apps(Group)],
   40:     mongoose_config:set_opts(#{hosts => [?HOST_TYPE],
   41:                                host_types => [?HOST_TYPE2],
   42:                                instrumentation => opts(Group)}),
   43:     Config1 = async_helper:start(Config, mongoose_instrument, start_link, []),
   44:     mongoose_instrument:persist(),
   45:     Config1 ++ extra_config(Group).
   46: 
   47: end_per_group(_Group, Config) ->
   48:     async_helper:stop_all(Config),
   49:     mongoose_config:erase_opts().
   50: 
   51: init_per_testcase(Case, Config) ->
   52:     [{event, join_atoms(Case, event)} | Config].
   53: 
   54: end_per_testcase(_Case, _Config) ->
   55:     ok.
   56: 
   57: apps(prometheus) -> [prometheus];
   58: apps(exometer) -> [exometer_core];
   59: apps(exometer_global) -> [exometer_core];
   60: apps(prometheus_and_exometer) -> apps(prometheus) ++ apps(exometer).
   61: 
   62: opts(prometheus) -> #{prometheus => #{}};
   63: opts(exometer) -> #{exometer => #{all_metrics_are_global => false}};
   64: opts(exometer_global) -> #{exometer => #{all_metrics_are_global => true}};
   65: opts(prometheus_and_exometer) -> maps:merge(opts(prometheus), opts(exometer)).
   66: 
   67: extra_config(exometer) -> [{prefix, ?HOST_TYPE}];
   68: extra_config(exometer_global) -> [{prefix, global}];
   69: extra_config(_Group) -> [].
   70: 
   71: %% Test cases
   72: 
   73: prometheus_skips_non_metric_event(Config) ->
   74:     Event = ?config(event, Config),
   75:     false = mongoose_instrument_prometheus:set_up(Event, ?LABELS, #{}),
   76:     false = mongoose_instrument_prometheus:set_up(Event, ?LABELS, #{loglevel => error}).
   77: 
   78: prometheus_counter_is_created_but_not_initialized(Config) ->
   79:     Event = ?config(event, Config),
   80:     Metric = prom_name(Event, count),
   81:     ok = mongoose_instrument:set_up(Event, ?LABELS, #{metrics => #{count => spiral}}),
   82:     ?assertEqual(undefined, prometheus_counter:value(Metric, [?HOST_TYPE])).
   83: 
   84: prometheus_counter_is_updated_separately_for_different_labels(Config) ->
   85:     Event = ?config(event, Config),
   86:     Metric = prom_name(Event, count),
   87:     ok = mongoose_instrument:set_up(Event, ?LABELS, #{metrics => #{count => spiral}}),
   88:     ok = mongoose_instrument:set_up(Event, ?LABELS2, #{metrics => #{count => spiral}}),
   89:     ok = mongoose_instrument:execute(Event, ?LABELS, #{count => 1}),
   90:     ok = mongoose_instrument:execute(Event, ?LABELS2, #{count => 2}),
   91:     ?assertEqual(1, prometheus_counter:value(Metric, [?HOST_TYPE])),
   92:     ?assertEqual(2, prometheus_counter:value(Metric, [?HOST_TYPE2])).
   93: 
   94: prometheus_histogram_is_created_but_not_initialized(Config) ->
   95:     Event = ?config(event, Config),
   96:     Metric = prom_name(Event, time),
   97:     ok = mongoose_instrument:set_up(Event, ?LABELS, #{metrics => #{time => histogram}}),
   98:     ?assertEqual(undefined, prometheus_histogram:value(Metric, [?HOST_TYPE])).
   99: 
  100: prometheus_histogram_is_updated_separately_for_different_labels(Config) ->
  101:     Event = ?config(event, Config),
  102:     Metric = prom_name(Event, time),
  103:     ok = mongoose_instrument:set_up(Event, ?LABELS, #{metrics => #{time => histogram}}),
  104:     ok = mongoose_instrument:set_up(Event, ?LABELS2, #{metrics => #{time => histogram}}),
  105:     ok = mongoose_instrument:execute(Event, ?LABELS, #{time => 1}),
  106:     ok = mongoose_instrument:execute(Event, ?LABELS2, #{time => 2}),
  107:     ?assertMatch({[1, 0|_], 1}, prometheus_histogram:value(Metric, [?HOST_TYPE])),
  108:     ?assertMatch({[0, 1|_], 2}, prometheus_histogram:value(Metric, [?HOST_TYPE2])).
  109: 
  110: multiple_prometheus_metrics_are_updated(Config) ->
  111:     Event = ?config(event, Config),
  112:     Counter = prom_name(Event, count),
  113:     Histogram = prom_name(Event, time),
  114:     ok = mongoose_instrument:set_up(Event, ?LABELS, #{metrics => #{count => spiral,
  115:                                                                    time => histogram}}),
  116:     %% Update both metrics
  117:     ok = mongoose_instrument:execute(Event, ?LABELS, #{count => 1, time => 2}),
  118:     ?assertEqual(1, prometheus_counter:value(Counter, [?HOST_TYPE])),
  119:     HistogramValue = prometheus_histogram:value(Histogram, [?HOST_TYPE]),
  120:     ?assertMatch({[0, 1|_], 2}, HistogramValue),
  121: 
  122:     %% Update only one metric
  123:     ok = mongoose_instrument:execute(Event, ?LABELS, #{count => 2}),
  124:     ?assertEqual(3, prometheus_counter:value(Counter, [?HOST_TYPE])),
  125:     ?assertEqual(HistogramValue, prometheus_histogram:value(Histogram, [?HOST_TYPE])),
  126: 
  127:     %% No update
  128:     ok = mongoose_instrument:execute(Event, ?LABELS, #{something => irrelevant}),
  129:     ?assertEqual(3, prometheus_counter:value(Counter, [?HOST_TYPE])),
  130:     ?assertEqual(HistogramValue, prometheus_histogram:value(Histogram, [?HOST_TYPE])).
  131: 
  132: exometer_skips_non_metric_event(Config) ->
  133:     Event = ?config(event, Config),
  134:     false = mongoose_instrument_exometer:set_up(Event, ?LABELS, #{}),
  135:     false = mongoose_instrument_exometer:set_up(Event, ?LABELS, #{loglevel => error}).
  136: 
  137: exometer_spiral_is_created_and_initialized(Config) ->
  138:     Event = ?config(event, Config),
  139:     Metric = [?HOST_TYPE, Event, count],
  140:     ok = mongoose_instrument:set_up(Event, ?LABELS, #{metrics => #{count => spiral}}),
  141:     ?assertEqual({ok, [{count, 0}]}, exometer:get_value(Metric, count)).
  142: 
  143: exometer_spiral_is_updated_separately_for_different_labels(Config) ->
  144:     Event = ?config(event, Config),
  145:     Metric1 = [?HOST_TYPE, Event, count],
  146:     Metric2 = [<<"test_type">>, Event, count],
  147:     ok = mongoose_instrument:set_up(Event, ?LABELS, #{metrics => #{count => spiral}}),
  148:     ok = mongoose_instrument:set_up(Event, ?LABELS2, #{metrics => #{count => spiral}}),
  149:     ok = mongoose_instrument:execute(Event, ?LABELS, #{count => 1}),
  150:     ok = mongoose_instrument:execute(Event, ?LABELS2, #{count => 2}),
  151:     ?assertEqual({ok, [{count, 1}]}, exometer:get_value(Metric1, count)),
  152:     ?assertEqual({ok, [{count, 2}]}, exometer:get_value(Metric2, count)).
  153: 
  154: exometer_histogram_is_created_and_initialized(Config) ->
  155:     Event = ?config(event, Config),
  156:     Metric = [?HOST_TYPE, Event, time],
  157:     ok = mongoose_instrument:set_up(Event, ?LABELS, #{metrics => #{time => histogram}}),
  158:     ?assertEqual({ok, [{mean, 0}]}, exometer:get_value(Metric, mean)).
  159: 
  160: exometer_histogram_is_updated_separately_for_different_labels(Config) ->
  161:     Event = ?config(event, Config),
  162:     Metric1 = [?HOST_TYPE, Event, time],
  163:     Metric2 = [<<"test_type">>, Event, time],
  164:     ok = mongoose_instrument:set_up(Event, ?LABELS, #{metrics => #{time => histogram}}),
  165:     ok = mongoose_instrument:set_up(Event, ?LABELS2, #{metrics => #{time => histogram}}),
  166:     ok = mongoose_instrument:execute(Event, ?LABELS, #{time => 1}),
  167:     ok = mongoose_instrument:execute(Event, ?LABELS2, #{time => 3}),
  168:     ?assertEqual({ok, [{mean, 1}]}, exometer:get_value(Metric1, mean)),
  169:     ?assertEqual({ok, [{mean, 3}]}, exometer:get_value(Metric2, mean)).
  170: 
  171: multiple_exometer_metrics_are_updated(Config) ->
  172:     Event = ?config(event, Config),
  173:     Prefix = ?config(prefix, Config),
  174:     Counter = [Prefix, Event, count],
  175:     Histogram = [Prefix, Event, time],
  176:     ok = mongoose_instrument:set_up(Event, ?LABELS, #{metrics => #{count => spiral,
  177:                                                                    time => histogram}}),
  178:     %% Update both metrics
  179:     ok = mongoose_instrument:execute(Event, ?LABELS, #{count => 1, time => 2}),
  180:     ?assertEqual({ok, [{count, 1}]}, exometer:get_value(Counter, count)),
  181:     ?assertEqual({ok, [{mean, 2}]}, exometer:get_value(Histogram, mean)),
  182: 
  183:     %% Update only one metric
  184:     ok = mongoose_instrument:execute(Event, ?LABELS, #{count => 5}),
  185:     ?assertEqual({ok, [{count, 6}]}, exometer:get_value(Counter, count)),
  186:     ?assertEqual({ok, [{mean, 2}]}, exometer:get_value(Histogram, mean)),
  187: 
  188:     %% No update
  189:     ok = mongoose_instrument:execute(Event, ?LABELS, #{something => irrelevant}),
  190:     ?assertEqual({ok, [{count, 6}]}, exometer:get_value(Counter, count)),
  191:     ?assertEqual({ok, [{mean, 2}]}, exometer:get_value(Histogram, mean)).
  192: 
  193: prometheus_and_exometer_metrics_are_updated(Config) ->
  194:     Event = ?config(event, Config),
  195:     ok = mongoose_instrument:set_up(Event, ?LABELS, #{metrics => #{count => spiral,
  196:                                                                    time => histogram}}),
  197:     ok = mongoose_instrument:execute(Event, ?LABELS, #{count => 1, time => 2}),
  198:     ?assertEqual({ok, [{count, 1}]}, exometer:get_value([?HOST_TYPE, Event, count], count)),
  199:     ?assertEqual({ok, [{mean, 2}]}, exometer:get_value([?HOST_TYPE, Event, time], mean)),
  200:     ?assertEqual(1, prometheus_counter:value(prom_name(Event, count), [?HOST_TYPE])),
  201:     ?assertMatch({[0, 1|_], 2}, prometheus_histogram:value(prom_name(Event, time), [?HOST_TYPE])).
  202: 
  203: %% Helpers
  204: 
  205: join_atoms(A1, A2) ->
  206:     list_to_atom(join_atoms_to_list(A1, A2)).
  207: 
  208: prom_name(EventName, MetricName) ->
  209:     join_atoms_to_list(EventName, MetricName).
  210: 
  211: join_atoms_to_list(A1, A2) ->
  212:     atom_to_list(A1) ++ "_" ++ atom_to_list(A2).