1: -module(service_mongoose_system_metrics_SUITE). 2: 3: -compile([export_all, nowarn_export_all]). 4: 5: -include_lib("common_test/include/ct.hrl"). 6: -include_lib("eunit/include/eunit.hrl"). 7: 8: -define(SERVER_URL, "http://localhost:8765"). 9: -define(ETS_TABLE, qs). 10: -define(TRACKING_ID, #{id => "G-7KQE4W9SVJ", secret => "Secret"}). 11: -define(TRACKING_ID_CI, #{id => "G-VB91V60SKT", secret => "Secret2"}). 12: -define(TRACKING_ID_EXTRA, #{id => "UA-EXTRA-TRACKING-ID", secret => "Secret3"}). 13: 14: -record(event, { 15: name = <<>>, 16: params = #{}, 17: client_id = <<>>, 18: instance_id = <<>>, 19: app_secret = <<>>}). 20: 21: -import(distributed_helper, [mim/0, mim2/0, mim3/0, rpc/4, 22: require_rpc_nodes/1 23: ]). 24: 25: -import(component_helper, [connect_component/1, 26: disconnect_component/2, 27: get_components/1]). 28: 29: -import(domain_helper, [host_type/0]). 30: -import(config_parser_helper, [mod_config/2, config/2]). 31: 32: suite() -> 33: require_rpc_nodes([mim]). 34: 35: all() -> 36: [ 37: system_metrics_are_not_reported_when_not_allowed, 38: periodic_report_available, 39: all_clustered_mongooses_report_the_same_client_id, 40: system_metrics_are_reported_to_google_analytics_when_mim_starts, 41: system_metrics_are_reported_to_configurable_google_analytics, 42: system_metrics_are_reported_to_a_json_file, 43: mongoose_version_is_reported, 44: cluster_uptime_is_reported, 45: xmpp_components_are_reported, 46: api_are_reported, 47: transport_mechanisms_are_reported, 48: outgoing_pools_are_reported, 49: xmpp_stanzas_counts_are_reported, 50: config_type_is_reported, 51: {group, module_opts}, 52: {group, log_transparency} 53: ]. 54: 55: groups() -> 56: [ 57: {module_opts, [], [ 58: module_opts_are_reported, 59: rdbms_module_opts_are_reported 60: ]}, 61: {log_transparency, [], [ 62: just_removed_from_config_logs_question, 63: in_config_unmodified_logs_request_for_agreement, 64: in_config_with_explicit_no_report_goes_off_silently, 65: in_config_with_explicit_reporting_goes_on_silently 66: ]} 67: ]. 68: 69: -define(APPS, [inets, crypto, ssl, ranch, cowlib, cowboy]). 70: 71: %%-------------------------------------------------------------------- 72: %% Suite configuration 73: %%-------------------------------------------------------------------- 74: init_per_suite(Config) -> 75: [ {ok, _} = application:ensure_all_started(App) || App <- ?APPS ], 76: http_helper:start(8765, "/[...]", fun handler_init/1), 77: Config1 = escalus:init_per_suite(Config), 78: Config2 = dynamic_services:save_services([mim(), mim2()], Config1), 79: ejabberd_node_utils:init(Config2). 80: 81: end_per_suite(Config) -> 82: http_helper:stop(), 83: dynamic_services:restore_services(Config), 84: escalus:end_per_suite(Config). 85: 86: %%-------------------------------------------------------------------- 87: %% Init & teardown 88: %%-------------------------------------------------------------------- 89: init_per_group(module_opts, Config) -> 90: dynamic_modules:save_modules(host_type(), Config); 91: init_per_group(log_transparency, Config) -> 92: logger_ct_backend:start(), 93: logger_ct_backend:capture(warning), 94: Config; 95: init_per_group(_GroupName, Config) -> 96: Config. 97: 98: end_per_group(module_opts, Config) -> 99: dynamic_modules:restore_modules(Config); 100: end_per_group(log_transparency, Config) -> 101: logger_ct_backend:stop_capture(), 102: Config; 103: end_per_group(_GroupName, Config) -> 104: Config. 105: 106: init_per_testcase(system_metrics_are_not_reported_when_not_allowed, Config) -> 107: create_events_collection(), 108: disable_system_metrics(mim()), 109: delete_prev_client_id(mim()), 110: Config; 111: init_per_testcase(all_clustered_mongooses_report_the_same_client_id, Config) -> 112: create_events_collection(), 113: distributed_helper:add_node_to_cluster(mim2(), Config), 114: enable_system_metrics(mim()), 115: enable_system_metrics(mim2()), 116: Config; 117: init_per_testcase(system_metrics_are_reported_to_configurable_google_analytics, Config) -> 118: create_events_collection(), 119: enable_system_metrics_with_configurable_tracking_id(mim()), 120: Config; 121: init_per_testcase(xmpp_components_are_reported, Config) -> 122: create_events_collection(), 123: Config1 = get_components(Config), 124: enable_system_metrics(mim()), 125: Config1; 126: init_per_testcase(xmpp_stanzas_counts_are_reported = CN, Config) -> 127: create_events_collection(), 128: enable_system_metrics(mim()), 129: Config1 = escalus:create_users(Config, escalus:get_users([alice, bob])), 130: escalus:init_per_testcase(CN, Config1); 131: init_per_testcase(rdbms_module_opts_are_reported = CN, Config) -> 132: case mongoose_helper:is_rdbms_enabled(host_type()) of 133: false -> 134: {skip, "RDBMS is not available"}; 135: true -> 136: create_events_collection(), 137: dynamic_modules:ensure_modules(host_type(), required_modules(CN)), 138: enable_system_metrics(mim()), 139: Config 140: end; 141: init_per_testcase(module_opts_are_reported = CN, Config) -> 142: create_events_collection(), 143: dynamic_modules:ensure_modules(host_type(), required_modules(CN)), 144: enable_system_metrics(mim()), 145: Config; 146: init_per_testcase(_TestcaseName, Config) -> 147: create_events_collection(), 148: enable_system_metrics(mim()), 149: Config. 150: 151: end_per_testcase(system_metrics_are_not_reported_when_not_allowed, Config) -> 152: clear_events_collection(), 153: delete_prev_client_id(mim()), 154: Config; 155: end_per_testcase(all_clustered_mongooses_report_the_same_client_id , Config) -> 156: clear_events_collection(), 157: delete_prev_client_id(mim()), 158: Nodes = [mim(), mim2()], 159: [ begin delete_prev_client_id(Node), disable_system_metrics(Node) end || Node <- Nodes ], 160: distributed_helper:remove_node_from_cluster(mim2(), Config), 161: Config; 162: end_per_testcase(xmpp_stanzas_counts_are_reported = CN, Config) -> 163: clear_events_collection(), 164: disable_system_metrics(mim()), 165: escalus:delete_users(Config, escalus:get_users([alice, bob])), 166: escalus:end_per_testcase(CN, Config); 167: end_per_testcase(_TestcaseName, Config) -> 168: clear_events_collection(), 169: disable_system_metrics(mim()), 170: delete_prev_client_id(mim()), 171: Config. 172: 173: 174: %%-------------------------------------------------------------------- 175: %% Tests 176: %%-------------------------------------------------------------------- 177: system_metrics_are_not_reported_when_not_allowed(_Config) -> 178: true = system_metrics_service_is_disabled(mim()). 179: 180: periodic_report_available(_Config) -> 181: ReportsNumber = get_events_collection_size(), 182: mongoose_helper:wait_until( 183: fun() -> 184: NewReportsNumber = get_events_collection_size(), 185: NewReportsNumber > ReportsNumber + 1 186: end, 187: true). 188: 189: all_clustered_mongooses_report_the_same_client_id(_Config) -> 190: mongoose_helper:wait_until(fun is_host_count_reported/0, true), 191: all_event_have_the_same_client_id(). 192: 193: system_metrics_are_reported_to_google_analytics_when_mim_starts(_Config) -> 194: mongoose_helper:wait_until(fun is_host_count_reported/0, true), 195: mongoose_helper:wait_until(fun are_modules_reported/0, true), 196: mongoose_helper:wait_until(fun events_are_reported_to_primary_tracking_id/0, true), 197: all_event_have_the_same_client_id(). 198: 199: system_metrics_are_reported_to_configurable_google_analytics(_Config) -> 200: mongoose_helper:wait_until(fun is_host_count_reported/0, true), 201: mongoose_helper:wait_until(fun are_modules_reported/0, true), 202: mongoose_helper:wait_until(fun events_are_reported_to_both_tracking_ids/0, true), 203: all_event_have_the_same_client_id(). 204: 205: system_metrics_are_reported_to_a_json_file(_Config) -> 206: ReportFilePath = rpc(mim(), mongoose_system_metrics_file, location, []), 207: ReportLastModified = rpc(mim(), filelib, last_modified, [ReportFilePath]), 208: Fun = fun() -> 209: ReportLastModified < rpc(mim(), filelib, last_modified, [ReportFilePath]) 210: end, 211: mongoose_helper:wait_until(Fun, true), 212: %% now we read the content of the file and check if it's a valid JSON 213: {ok, File} = rpc(mim(), file, read_file, [ReportFilePath]), 214: jiffy:decode(File). 215: 216: module_opts_are_reported(_Config) -> 217: mongoose_helper:wait_until(fun are_modules_reported/0, true), 218: Backend = mongoose_helper:mnesia_or_rdbms_backend(), 219: MemBackend = ct_helper:get_internal_database(), 220: check_module_backend(mod_bosh, MemBackend), 221: check_module_backend(mod_event_pusher, push), 222: check_module_backend(mod_event_pusher_push, Backend), 223: check_module_backend(mod_http_upload, s3), 224: check_module_backend(mod_last, Backend), 225: check_module_backend(mod_muc, Backend), 226: check_module_opt(mod_muc, <<"online_backend">>, atom_to_binary(MemBackend)), 227: check_module_backend(mod_muc_light, Backend), 228: check_module_backend(mod_offline, Backend), 229: check_module_backend(mod_privacy, Backend), 230: check_module_backend(mod_private, Backend), 231: check_module_backend(mod_pubsub, Backend), 232: check_module_opt(mod_push_service_mongoosepush, <<"api_version">>, <<"v3">>), 233: check_module_backend(mod_roster, Backend), 234: check_module_backend(mod_vcard, Backend). 235: 236: rdbms_module_opts_are_reported(_Config) -> 237: mongoose_helper:wait_until(fun are_modules_reported/0, true), 238: check_module_backend(mod_auth_token, rdbms), 239: check_module_backend(mod_inbox, rdbms), 240: check_module_backend(mod_mam, rdbms). 241: 242: check_module_backend(Module, Backend) -> 243: check_module_opt(Module, <<"backend">>, atom_to_binary(Backend)). 244: 245: mongoose_version_is_reported(_Config) -> 246: mongoose_helper:wait_until(fun is_mongoose_version_reported/0, true). 247: 248: cluster_uptime_is_reported(_Config) -> 249: mongoose_helper:wait_until(fun is_cluster_uptime_reported/0, true). 250: 251: xmpp_components_are_reported(Config) -> 252: CompOpts = ?config(component1, Config), 253: {Component, Addr, _} = connect_component(CompOpts), 254: mongoose_helper:wait_until(fun are_xmpp_components_reported/0, true), 255: mongoose_helper:wait_until(fun more_than_one_component_is_reported/0, true), 256: disconnect_component(Component, Addr). 257: 258: api_are_reported(_Config) -> 259: mongoose_helper:wait_until(fun is_api_reported/0, true). 260: 261: transport_mechanisms_are_reported(_Config) -> 262: mongoose_helper:wait_until(fun are_transport_mechanisms_reported/0, true). 263: 264: outgoing_pools_are_reported(_Config) -> 265: mongoose_helper:wait_until(fun are_outgoing_pools_reported/0, true). 266: 267: xmpp_stanzas_counts_are_reported(Config) -> 268: escalus:story(Config, [{alice,1}, {bob,1}], fun(Alice, Bob) -> 269: mongoose_helper:wait_until(fun is_message_count_reported/0, true), 270: mongoose_helper:wait_until(fun is_iq_count_reported/0, true), 271: Sent = get_metric_value(<<"xmppMessageSent">>), 272: Received = get_metric_value(<<"xmppMessageReceived">>), 273: escalus:send(Alice, escalus_stanza:chat_to(Bob, <<"Hi">>)), 274: escalus:assert(is_chat_message, [<<"Hi">>], escalus:wait_for_stanza(Bob)), 275: F = fun() -> assert_message_count_is_incremented(Sent, Received) end, 276: mongoose_helper:wait_until(F, ok, #{sleep_time => 500, time_left => timer:seconds(20)}) 277: end). 278: 279: config_type_is_reported(_Config) -> 280: mongoose_helper:wait_until(fun is_config_type_reported/0, true). 281: 282: just_removed_from_config_logs_question(_Config) -> 283: disable_system_metrics(mim3()), 284: %% WHEN 285: Result = distributed_helper:rpc( 286: mim3(), service_mongoose_system_metrics, verify_if_configured, []), 287: %% THEN 288: ?assertEqual(ignore, Result). 289: 290: in_config_unmodified_logs_request_for_agreement(_Config) -> 291: %% WHEN 292: disable_system_metrics(mim()), 293: logger_ct_backend:capture(warning), 294: enable_system_metrics(mim()), 295: %% THEN 296: FilterFun = fun(_, Msg) -> 297: re:run(Msg, "MongooseIM docs", [global]) /= nomatch 298: end, 299: mongoose_helper:wait_until(fun() -> length(logger_ct_backend:recv(FilterFun)) end, 1), 300: %% CLEAN 301: logger_ct_backend:stop_capture(), 302: disable_system_metrics(mim()). 303: 304: in_config_with_explicit_no_report_goes_off_silently(_Config) -> 305: %% WHEN 306: logger_ct_backend:capture(warning), 307: start_system_metrics_service(mim(), #{report => false}), 308: logger_ct_backend:stop_capture(), 309: %% THEN 310: FilterFun = fun(warning, Msg) -> 311: re:run(Msg, "MongooseIM docs", [global]) /= nomatch; 312: (_,_) -> false 313: end, 314: [] = logger_ct_backend:recv(FilterFun), 315: %% CLEAN 316: disable_system_metrics(mim()). 317: 318: in_config_with_explicit_reporting_goes_on_silently(_Config) -> 319: %% WHEN 320: logger_ct_backend:capture(warning), 321: start_system_metrics_service(mim(), #{report => true}), 322: logger_ct_backend:stop_capture(), 323: %% THEN 324: FilterFun = fun(warning, Msg) -> 325: re:run(Msg, "MongooseIM docs", [global]) /= nomatch; 326: (_,_) -> false 327: end, 328: [] = logger_ct_backend:recv(FilterFun), 329: %% CLEAN 330: disable_system_metrics(mim()). 331: 332: %%-------------------------------------------------------------------- 333: %% Helpers 334: %%-------------------------------------------------------------------- 335: 336: required_modules(CaseName) -> 337: lists:filter(fun({Module, _Opts}) -> is_module_supported(Module) end, 338: modules_to_test(CaseName)). 339: 340: modules_to_test(module_opts_are_reported) -> 341: Backend = mongoose_helper:mnesia_or_rdbms_backend(), 342: MemBackend = ct_helper:get_internal_database(), 343: [required_module(mod_bosh, #{backend => MemBackend}), 344: required_module(mod_event_pusher, 345: #{push => config([modules, mod_event_pusher, push], #{backend => Backend})}), 346: required_module(mod_http_upload, s3), 347: required_module(mod_last, Backend), 348: required_module(mod_muc, #{backend => Backend, online_backend => MemBackend}), 349: required_module(mod_muc_light, Backend), 350: required_module(mod_offline, Backend), 351: required_module(mod_privacy, Backend), 352: required_module(mod_private, Backend), 353: required_module(mod_pubsub, Backend), 354: required_module(mod_push_service_mongoosepush), 355: required_module(mod_roster, Backend), 356: required_module(mod_vcard, Backend)]; 357: modules_to_test(rdbms_module_opts_are_reported) -> 358: [required_module(mod_auth_token), 359: required_module(mod_inbox), 360: required_module(mod_mam)]. 361: 362: required_module(Module) -> 363: required_module(Module, #{}). 364: 365: required_module(Module, Backend) when is_atom(Backend) -> 366: {Module, mod_config(Module, #{backend => Backend})}; 367: required_module(Module, Opts) -> 368: {Module, mod_config(Module, Opts)}. 369: 370: check_module_opt(Module, Key, Value) when is_binary(Key), is_binary(Value) -> 371: case is_module_supported(Module) of 372: true -> 373: ?assert(is_module_opt_reported(atom_to_binary(Module), Key, Value), {Module, Key, Value}); 374: false -> 375: ct:log("Skipping unsupported module ~p", [Module]) 376: end. 377: 378: is_module_supported(Module) -> 379: is_host_type_static() orelse supports_dynamic_domains(Module). 380: 381: is_host_type_static() -> 382: rpc(mim(), mongoose_domain_core, is_static, [host_type()]). 383: 384: supports_dynamic_domains(Module) -> 385: rpc(mim(), gen_mod, does_module_support, [Module, dynamic_domains]). 386: 387: all_event_have_the_same_client_id() -> 388: Tab = ets:tab2list(?ETS_TABLE), 389: UniqueSortedTab = lists:usort([Cid || #event{client_id = Cid} <- Tab]), 390: 1 = length(UniqueSortedTab). 391: 392: is_host_count_reported() -> 393: is_in_table(<<"host_count">>). 394: 395: are_modules_reported() -> 396: is_in_table(<<"module">>). 397: 398: is_in_table(EventName) -> 399: Tab = ets:tab2list(?ETS_TABLE), 400: lists:any( 401: fun(#event{name = Name, params = Params}) -> 402: verify_name(Name, EventName, Params) 403: end, Tab). 404: 405: verify_name(<<"module_with_opt">>, <<"module">>, Params) -> 406: Module = maps:get(<<"module">>, Params), 407: Result = re:run(Module, "^mod_.*"), 408: case Result of 409: {match, _Captured} -> true; 410: nomatch -> false 411: end; 412: verify_name(Name, Name, _) -> 413: true; 414: verify_name(_, _, _) -> 415: false. 416: 417: get_events_collection_size() -> 418: ets:info(?ETS_TABLE, size). 419: 420: enable_system_metrics(Node) -> 421: enable_system_metrics(Node, #{initial_report => 100, periodic_report => 100}). 422: 423: enable_system_metrics_with_configurable_tracking_id(Node) -> 424: enable_system_metrics(Node, #{initial_report => 100, periodic_report => 100, 425: tracking_id => ?TRACKING_ID_EXTRA}). 426: 427: enable_system_metrics(Node, Opts) -> 428: UrlArgs = [google_analytics_url, ?SERVER_URL], 429: ok = mongoose_helper:successful_rpc(Node, mongoose_config, set_opt, UrlArgs), 430: start_system_metrics_service(Node, Opts). 431: 432: start_system_metrics_service(Node, ExtraOpts) -> 433: Opts = config([services, service_mongoose_system_metrics], ExtraOpts), 434: dynamic_services:ensure_started(Node, service_mongoose_system_metrics, Opts). 435: 436: disable_system_metrics(Node) -> 437: dynamic_services:ensure_stopped(Node, service_mongoose_system_metrics), 438: mongoose_helper:successful_rpc(Node, mongoose_config, unset_opt, [ google_analytics_url ]). 439: 440: delete_prev_client_id(Node) -> 441: mongoose_helper:successful_rpc(Node, mnesia, delete_table, [service_mongoose_system_metrics]). 442: 443: create_events_collection() -> 444: ets:new(?ETS_TABLE, [duplicate_bag, named_table, public]). 445: 446: clear_events_collection() -> 447: ets:delete_all_objects(?ETS_TABLE). 448: 449: system_metrics_service_is_enabled(Node) -> 450: Pid = distributed_helper:rpc(Node, erlang, whereis, [service_mongoose_system_metrics]), 451: erlang:is_pid(Pid). 452: 453: system_metrics_service_is_disabled(Node) -> 454: not system_metrics_service_is_enabled(Node). 455: 456: events_are_reported_to_primary_tracking_id() -> 457: events_are_reported_to_tracking_ids([primary_tracking_id()]). 458: 459: events_are_reported_to_both_tracking_ids() -> 460: events_are_reported_to_tracking_ids([primary_tracking_id(), ?TRACKING_ID_EXTRA]). 461: 462: primary_tracking_id() -> 463: case os:getenv("CI") of 464: "true" -> ?TRACKING_ID_CI; 465: _ -> ?TRACKING_ID 466: end. 467: 468: events_are_reported_to_tracking_ids(ConfiguredTrackingIds) -> 469: Tab = ets:tab2list(?ETS_TABLE), 470: ActualTrackingIds = lists:usort([InstanceId || #event{instance_id = InstanceId} <- Tab]), 471: ExpectedTrackingIds = lists:sort([list_to_binary(Tid) || #{id := Tid} <- ConfiguredTrackingIds]), 472: ExpectedTrackingIds =:= ActualTrackingIds. 473: 474: is_feature_reported(EventName, Key) -> 475: length(match_events(EventName, Key)) > 0. 476: 477: is_feature_reported(EventName, Key, Value) -> 478: length(match_events(EventName, Key, Value)) > 0. 479: 480: is_module_opt_reported(Module, Key, Value) -> 481: length(get_matched_events_for_module(Module, Key, Value)) > 0. 482: 483: is_mongoose_version_reported() -> 484: is_feature_reported(<<"cluster">>, <<"version">>). 485: 486: is_cluster_uptime_reported() -> 487: is_feature_reported(<<"cluster">>, <<"uptime">>). 488: 489: are_xmpp_components_reported() -> 490: is_feature_reported(<<"cluster">>, <<"component">>). 491: 492: is_config_type_reported() -> 493: IsToml = is_feature_reported(<<"cluster">>, <<"config_type">>, <<"toml">>), 494: IsCfg = is_feature_reported(<<"cluster">>, <<"config_type">>, <<"cfg">>), 495: IsToml orelse IsCfg. 496: 497: is_api_reported() -> 498: is_in_table(<<"http_api">>). 499: 500: are_transport_mechanisms_reported() -> 501: is_in_table(<<"transport_mechanism">>). 502: 503: are_outgoing_pools_reported() -> 504: is_in_table(<<"outgoing_pool">>). 505: 506: is_iq_count_reported() -> 507: is_feature_reported(<<"xmpp_stanza_count">>, 508: <<"stanza_type">>, 509: <<"xmppIqSent">>). 510: 511: is_message_count_reported() -> 512: XmppMessageSent = is_feature_reported(<<"xmpp_stanza_count">>, 513: <<"stanza_type">>, 514: <<"xmppMessageSent">>), 515: XmppMessageReceived = is_feature_reported(<<"xmpp_stanza_count">>, 516: <<"stanza_type">>, 517: <<"xmppMessageReceived">>), 518: XmppMessageSent andalso XmppMessageReceived. 519: 520: assert_message_count_is_incremented(Sent, Received) -> 521: assert_increment(<<"xmppMessageSent">>, Sent), 522: assert_increment(<<"xmppMessageReceived">>, Received). 523: 524: assert_increment(EventCategory, InitialValue) -> 525: Events = match_events(<<"xmpp_stanza_count">>, <<"stanza_type">>, EventCategory), 526: % expect exactly one event with an increment of 1 527: SeekedEvent = [Event || Event = #event{params = #{<<"total">> := Total, <<"increment">> := 1}} 528: <- Events, Total == InitialValue + 1], 529: ?assertMatch([_], SeekedEvent). 530: 531: get_metric_value(EventCategory) -> 532: [#event{params = #{<<"total">> := Value}} | _] = match_events(<<"xmpp_stanza_count">>, <<"stanza_type">>, EventCategory), 533: Value. 534: 535: more_than_one_component_is_reported() -> 536: Events = match_events(<<"cluster">>), 537: lists:any(fun(#event{params = Params}) -> 538: maps:get(<<"component">>, Params) > 0 539: end, Events). 540: 541: match_events(EventName) -> 542: ets:match_object(?ETS_TABLE, #event{name = EventName, _ = '_'}). 543: 544: match_events(EventName, ParamKey) -> 545: Res = ets:match_object(?ETS_TABLE, #event{name = EventName, _ = '_'}), 546: [Event || Event = #event{params = #{ParamKey := _}} <- Res]. 547: 548: match_events(EventName, ParamKey, ParamValue) -> 549: Res = ets:match_object(?ETS_TABLE, #event{name = EventName, _ = '_'}), 550: [Event || Event = #event{params = #{ParamKey := Value}} <- Res, Value == ParamValue]. 551: 552: get_matched_events_for_module(ParamModule, Key, ParamValue) -> 553: Res = ets:match_object(?ETS_TABLE, #event{name = <<"module_with_opt">>, _ = '_'}), 554: [Event || Event = #event{params = #{<<"module">> := Module, Key := Value}} <- Res, 555: Value == ParamValue, Module == ParamModule]. 556: 557: %%-------------------------------------------------------------------- 558: %% Cowboy handlers 559: %%-------------------------------------------------------------------- 560: handler_init(Req0) -> 561: {ok, Body, Req} = cowboy_req:read_body(Req0), 562: #{measurement_id := InstanceId, api_secret := AppSecret} = cowboy_req:match_qs([measurement_id, api_secret], Req0), 563: BodyMap = jiffy:decode(Body, [return_maps]), 564: EventTab = maps:get(<<"events">>, BodyMap), 565: ClientID = maps:get(<<"client_id">>, BodyMap), 566: lists:map( 567: fun(Event) -> 568: EventRecord = #event{name = maps:get(<<"name">>, Event), 569: params = maps:get(<<"params">>, Event), 570: client_id = ClientID, 571: instance_id = InstanceId, 572: app_secret = AppSecret}, 573: %% TODO there is a race condition when table is not available 574: ets:insert(?ETS_TABLE, EventRecord) 575: end, EventTab), 576: Req1 = cowboy_req:reply(200, #{}, <<>>, Req), 577: {ok, Req1, no_state}.