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