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_gauge_is_created_and_updated, 24: prometheus_gauge_is_updated_separately_for_different_labels, 25: prometheus_counter_is_created_and_updated, 26: prometheus_counter_is_updated_separately_for_different_labels, 27: prometheus_histogram_is_created_and_updated, 28: prometheus_histogram_is_updated_separately_for_different_labels, 29: multiple_prometheus_metrics_are_updated]}, 30: {exometer, [parallel], [exometer_skips_non_metric_event, 31: exometer_gauge_is_created_and_updated, 32: exometer_gauge_is_updated_separately_for_different_labels, 33: exometer_spiral_is_created_and_updated, 34: exometer_spiral_is_updated_separately_for_different_labels, 35: exometer_histogram_is_created_and_updated, 36: exometer_histogram_is_updated_separately_for_different_labels, 37: multiple_exometer_metrics_are_updated]}, 38: {exometer_global, [parallel], [multiple_exometer_metrics_are_updated]}, 39: {prometheus_and_exometer, [parallel], [prometheus_and_exometer_metrics_are_updated]} 40: ]. 41: 42: init_per_group(Group, Config) -> 43: [application:ensure_all_started(App) || App <- apps(Group)], 44: mongoose_config:set_opts(#{hosts => [?HOST_TYPE], 45: host_types => [?HOST_TYPE2], 46: instrumentation => opts(Group)}), 47: Config1 = async_helper:start(Config, mongoose_instrument, start_link, []), 48: mongoose_instrument:persist(), 49: Config1 ++ extra_config(Group). 50: 51: end_per_group(_Group, Config) -> 52: async_helper:stop_all(Config), 53: mongoose_config:erase_opts(). 54: 55: init_per_testcase(Case, Config) -> 56: [{event, join_atoms(Case, event)} | Config]. 57: 58: end_per_testcase(_Case, _Config) -> 59: ok. 60: 61: apps(prometheus) -> [prometheus]; 62: apps(exometer) -> [exometer_core]; 63: apps(exometer_global) -> [exometer_core]; 64: apps(prometheus_and_exometer) -> apps(prometheus) ++ apps(exometer). 65: 66: opts(prometheus) -> #{prometheus => #{}}; 67: opts(exometer) -> #{exometer => #{all_metrics_are_global => false}}; 68: opts(exometer_global) -> #{exometer => #{all_metrics_are_global => true}}; 69: opts(prometheus_and_exometer) -> maps:merge(opts(prometheus), opts(exometer)). 70: 71: extra_config(exometer) -> [{prefix, ?HOST_TYPE}]; 72: extra_config(exometer_global) -> [{prefix, global}]; 73: extra_config(_Group) -> []. 74: 75: %% Test cases 76: 77: prometheus_skips_non_metric_event(Config) -> 78: Event = ?config(event, Config), 79: false = mongoose_instrument_prometheus:set_up(Event, ?LABELS, #{}), 80: false = mongoose_instrument_prometheus:set_up(Event, ?LABELS, #{loglevel => error}). 81: 82: prometheus_gauge_is_created_and_updated(Config) -> 83: Event = ?config(event, Config), 84: Metric = prom_name(Event, count), 85: 86: %% Prometheus gauge has no initial value, and reports the last registered value 87: ok = mongoose_instrument:set_up(Event, ?LABELS, #{metrics => #{count => gauge}}), 88: ?assertEqual(undefined, prometheus_gauge:value(Metric, [?HOST_TYPE])), 89: ok = mongoose_instrument:execute(Event, ?LABELS, #{count => 1}), 90: ?assertEqual(1, prometheus_gauge:value(Metric, [?HOST_TYPE])), 91: ok = mongoose_instrument:execute(Event, ?LABELS, #{count => 2}), 92: ?assertEqual(2, prometheus_gauge:value(Metric, [?HOST_TYPE])). 93: 94: prometheus_gauge_is_updated_separately_for_different_labels(Config) -> 95: Event = ?config(event, Config), 96: Metric = prom_name(Event, count), 97: ok = mongoose_instrument:set_up(Event, ?LABELS, #{metrics => #{count => gauge}}), 98: ok = mongoose_instrument:set_up(Event, ?LABELS2, #{metrics => #{count => gauge}}), 99: ok = mongoose_instrument:execute(Event, ?LABELS, #{count => 1}), 100: ok = mongoose_instrument:execute(Event, ?LABELS2, #{count => 2}), 101: ?assertEqual(1, prometheus_gauge:value(Metric, [?HOST_TYPE])), 102: ?assertEqual(2, prometheus_gauge:value(Metric, [?HOST_TYPE2])). 103: 104: prometheus_counter_is_created_and_updated(Config) -> 105: Event = ?config(event, Config), 106: Metric = prom_name(Event, count), 107: 108: %% Prometheus counter starts at zero, and reports the sum of all values 109: ok = mongoose_instrument:set_up(Event, ?LABELS, #{metrics => #{count => spiral}}), 110: ?assertEqual(0, prometheus_counter:value(Metric, [?HOST_TYPE])), 111: ok = mongoose_instrument:execute(Event, ?LABELS, #{count => 1}), 112: ?assertEqual(1, prometheus_counter:value(Metric, [?HOST_TYPE])), 113: ok = mongoose_instrument:execute(Event, ?LABELS, #{count => 2}), 114: ?assertEqual(3, prometheus_counter:value(Metric, [?HOST_TYPE])). 115: 116: prometheus_counter_is_updated_separately_for_different_labels(Config) -> 117: Event = ?config(event, Config), 118: Metric = prom_name(Event, count), 119: ok = mongoose_instrument:set_up(Event, ?LABELS, #{metrics => #{count => spiral}}), 120: ok = mongoose_instrument:set_up(Event, ?LABELS2, #{metrics => #{count => spiral}}), 121: ok = mongoose_instrument:execute(Event, ?LABELS, #{count => 1}), 122: ok = mongoose_instrument:execute(Event, ?LABELS2, #{count => 2}), 123: ?assertEqual(1, prometheus_counter:value(Metric, [?HOST_TYPE])), 124: ?assertEqual(2, prometheus_counter:value(Metric, [?HOST_TYPE2])). 125: 126: prometheus_histogram_is_created_and_updated(Config) -> 127: Event = ?config(event, Config), 128: Metric = prom_name(Event, time), 129: ok = mongoose_instrument:set_up(Event, ?LABELS, #{metrics => #{time => histogram}}), 130: 131: %% Prometheus histogram shows no value if there is no data 132: ?assertEqual(undefined, prometheus_histogram:value(Metric, [?HOST_TYPE])), 133: ok = mongoose_instrument:execute(Event, ?LABELS, #{time => 1}), 134: ?assertMatch({[1, 0|_], 1}, prometheus_histogram:value(Metric, [?HOST_TYPE])), 135: ok = mongoose_instrument:execute(Event, ?LABELS, #{time => 1}), 136: ?assertMatch({[2, 0|_], 2}, prometheus_histogram:value(Metric, [?HOST_TYPE])), 137: ok = mongoose_instrument:execute(Event, ?LABELS, #{time => 2}), 138: ?assertMatch({[2, 1|_], 4}, prometheus_histogram:value(Metric, [?HOST_TYPE])). 139: 140: prometheus_histogram_is_updated_separately_for_different_labels(Config) -> 141: Event = ?config(event, Config), 142: Metric = prom_name(Event, time), 143: ok = mongoose_instrument:set_up(Event, ?LABELS, #{metrics => #{time => histogram}}), 144: ok = mongoose_instrument:set_up(Event, ?LABELS2, #{metrics => #{time => histogram}}), 145: ok = mongoose_instrument:execute(Event, ?LABELS, #{time => 1}), 146: ok = mongoose_instrument:execute(Event, ?LABELS2, #{time => 2}), 147: ?assertMatch({[1, 0|_], 1}, prometheus_histogram:value(Metric, [?HOST_TYPE])), 148: ?assertMatch({[0, 1|_], 2}, prometheus_histogram:value(Metric, [?HOST_TYPE2])). 149: 150: multiple_prometheus_metrics_are_updated(Config) -> 151: Event = ?config(event, Config), 152: Counter = prom_name(Event, count), 153: Histogram = prom_name(Event, time), 154: ok = mongoose_instrument:set_up(Event, ?LABELS, #{metrics => #{count => spiral, 155: time => histogram}}), 156: %% Update both metrics 157: ok = mongoose_instrument:execute(Event, ?LABELS, #{count => 1, time => 2}), 158: ?assertEqual(1, prometheus_counter:value(Counter, [?HOST_TYPE])), 159: HistogramValue = prometheus_histogram:value(Histogram, [?HOST_TYPE]), 160: ?assertMatch({[0, 1|_], 2}, HistogramValue), 161: 162: %% Update only one metric 163: ok = mongoose_instrument:execute(Event, ?LABELS, #{count => 2}), 164: ?assertEqual(3, prometheus_counter:value(Counter, [?HOST_TYPE])), 165: ?assertEqual(HistogramValue, prometheus_histogram:value(Histogram, [?HOST_TYPE])), 166: 167: %% No update 168: ok = mongoose_instrument:execute(Event, ?LABELS, #{something => irrelevant}), 169: ?assertEqual(3, prometheus_counter:value(Counter, [?HOST_TYPE])), 170: ?assertEqual(HistogramValue, prometheus_histogram:value(Histogram, [?HOST_TYPE])). 171: 172: exometer_skips_non_metric_event(Config) -> 173: Event = ?config(event, Config), 174: false = mongoose_instrument_exometer:set_up(Event, ?LABELS, #{}), 175: false = mongoose_instrument_exometer:set_up(Event, ?LABELS, #{loglevel => error}). 176: 177: exometer_gauge_is_created_and_updated(Config) -> 178: Event = ?config(event, Config), 179: Metric = [?HOST_TYPE, Event, count], 180: 181: %% Exometer gauge starts at zero, and reports the last registered value 182: ok = mongoose_instrument:set_up(Event, ?LABELS, #{metrics => #{count => gauge}}), 183: ?assertEqual({ok, [{value, 0}]}, exometer:get_value(Metric, value)), 184: ok = mongoose_instrument:execute(Event, ?LABELS, #{count => 1}), 185: ?assertEqual({ok, [{value, 1}]}, exometer:get_value(Metric, value)), 186: ok = mongoose_instrument:execute(Event, ?LABELS, #{count => 2}), 187: ?assertEqual({ok, [{value, 2}]}, exometer:get_value(Metric, value)). 188: 189: exometer_gauge_is_updated_separately_for_different_labels(Config) -> 190: Event = ?config(event, Config), 191: Metric1 = [?HOST_TYPE, Event, count], 192: Metric2 = [<<"test_type">>, Event, count], 193: ok = mongoose_instrument:set_up(Event, ?LABELS, #{metrics => #{count => gauge}}), 194: ok = mongoose_instrument:set_up(Event, ?LABELS2, #{metrics => #{count => gauge}}), 195: ok = mongoose_instrument:execute(Event, ?LABELS, #{count => 1}), 196: ok = mongoose_instrument:execute(Event, ?LABELS2, #{count => 2}), 197: ?assertEqual({ok, [{value, 1}]}, exometer:get_value(Metric1, value)), 198: ?assertEqual({ok, [{value, 2}]}, exometer:get_value(Metric2, value)). 199: 200: exometer_spiral_is_created_and_updated(Config) -> 201: Event = ?config(event, Config), 202: Metric = [?HOST_TYPE, Event, count], 203: 204: %% Exometer spiral starts at zero, and reports the sum of all values 205: ok = mongoose_instrument:set_up(Event, ?LABELS, #{metrics => #{count => spiral}}), 206: ?assertEqual({ok, [{count, 0}]}, exometer:get_value(Metric, count)), 207: ok = mongoose_instrument:execute(Event, ?LABELS, #{count => 1}), 208: ?assertEqual({ok, [{count, 1}]}, exometer:get_value(Metric, count)), 209: ok = mongoose_instrument:execute(Event, ?LABELS, #{count => 2}), 210: ?assertEqual({ok, [{count, 3}]}, exometer:get_value(Metric, count)). 211: 212: exometer_spiral_is_updated_separately_for_different_labels(Config) -> 213: Event = ?config(event, Config), 214: Metric1 = [?HOST_TYPE, Event, count], 215: Metric2 = [<<"test_type">>, Event, count], 216: ok = mongoose_instrument:set_up(Event, ?LABELS, #{metrics => #{count => spiral}}), 217: ok = mongoose_instrument:set_up(Event, ?LABELS2, #{metrics => #{count => spiral}}), 218: ok = mongoose_instrument:execute(Event, ?LABELS, #{count => 1}), 219: ok = mongoose_instrument:execute(Event, ?LABELS2, #{count => 2}), 220: ?assertEqual({ok, [{count, 1}]}, exometer:get_value(Metric1, count)), 221: ?assertEqual({ok, [{count, 2}]}, exometer:get_value(Metric2, count)). 222: 223: exometer_histogram_is_created_and_updated(Config) -> 224: Event = ?config(event, Config), 225: Metric = [?HOST_TYPE, Event, time], 226: 227: %% Exometer mean value is zero if there is no data 228: ok = mongoose_instrument:set_up(Event, ?LABELS, #{metrics => #{time => histogram}}), 229: ?assertEqual({ok, [{mean, 0}]}, exometer:get_value(Metric, mean)), 230: ok = mongoose_instrument:execute(Event, ?LABELS, #{time => 1}), 231: ?assertEqual({ok, [{mean, 1}]}, exometer:get_value(Metric, mean)), 232: ok = mongoose_instrument:execute(Event, ?LABELS, #{time => 3}), 233: ?assertEqual({ok, [{mean, 2}]}, exometer:get_value(Metric, mean)). 234: 235: exometer_histogram_is_updated_separately_for_different_labels(Config) -> 236: Event = ?config(event, Config), 237: Metric1 = [?HOST_TYPE, Event, time], 238: Metric2 = [<<"test_type">>, Event, time], 239: ok = mongoose_instrument:set_up(Event, ?LABELS, #{metrics => #{time => histogram}}), 240: ok = mongoose_instrument:set_up(Event, ?LABELS2, #{metrics => #{time => histogram}}), 241: ok = mongoose_instrument:execute(Event, ?LABELS, #{time => 1}), 242: ok = mongoose_instrument:execute(Event, ?LABELS2, #{time => 3}), 243: ?assertEqual({ok, [{mean, 1}]}, exometer:get_value(Metric1, mean)), 244: ?assertEqual({ok, [{mean, 3}]}, exometer:get_value(Metric2, mean)). 245: 246: multiple_exometer_metrics_are_updated(Config) -> 247: Event = ?config(event, Config), 248: Prefix = ?config(prefix, Config), 249: Counter = [Prefix, Event, count], 250: Histogram = [Prefix, Event, time], 251: ok = mongoose_instrument:set_up(Event, ?LABELS, #{metrics => #{count => spiral, 252: time => histogram}}), 253: %% Update both metrics 254: ok = mongoose_instrument:execute(Event, ?LABELS, #{count => 1, time => 2}), 255: ?assertEqual({ok, [{count, 1}]}, exometer:get_value(Counter, count)), 256: ?assertEqual({ok, [{mean, 2}]}, exometer:get_value(Histogram, mean)), 257: 258: %% Update only one metric 259: ok = mongoose_instrument:execute(Event, ?LABELS, #{count => 5}), 260: ?assertEqual({ok, [{count, 6}]}, exometer:get_value(Counter, count)), 261: ?assertEqual({ok, [{mean, 2}]}, exometer:get_value(Histogram, mean)), 262: 263: %% No update 264: ok = mongoose_instrument:execute(Event, ?LABELS, #{something => irrelevant}), 265: ?assertEqual({ok, [{count, 6}]}, exometer:get_value(Counter, count)), 266: ?assertEqual({ok, [{mean, 2}]}, exometer:get_value(Histogram, mean)). 267: 268: prometheus_and_exometer_metrics_are_updated(Config) -> 269: Event = ?config(event, Config), 270: ok = mongoose_instrument:set_up(Event, ?LABELS, #{metrics => #{count => spiral, 271: time => histogram}}), 272: ok = mongoose_instrument:execute(Event, ?LABELS, #{count => 1, time => 2}), 273: ?assertEqual({ok, [{count, 1}]}, exometer:get_value([?HOST_TYPE, Event, count], count)), 274: ?assertEqual({ok, [{mean, 2}]}, exometer:get_value([?HOST_TYPE, Event, time], mean)), 275: ?assertEqual(1, prometheus_counter:value(prom_name(Event, count), [?HOST_TYPE])), 276: ?assertMatch({[0, 1|_], 2}, prometheus_histogram:value(prom_name(Event, time), [?HOST_TYPE])). 277: 278: %% Helpers 279: 280: join_atoms(A1, A2) -> 281: list_to_atom(join_atoms_to_list(A1, A2)). 282: 283: prom_name(EventName, MetricName) -> 284: join_atoms_to_list(EventName, MetricName). 285: 286: join_atoms_to_list(A1, A2) -> 287: atom_to_list(A1) ++ "_" ++ atom_to_list(A2).