diff --git a/apps/els_core/src/els_config.erl b/apps/els_core/src/els_config.erl index 2b9d9892b..3fca71a2b 100644 --- a/apps/els_core/src/els_config.erl +++ b/apps/els_core/src/els_config.erl @@ -54,7 +54,8 @@ | code_reload | elvis_config_path | indexing_enabled - | bsp_enabled. + | bsp_enabled + | compiler_telemetry_enabled. -type path() :: file:filename(). -type state() :: #{ apps_dirs => [path()] @@ -74,6 +75,7 @@ , code_reload => map() | 'disabled' , indexing_enabled => boolean() , bsp_enabled => boolean() | auto + , compiler_telemetry_enabled => boolean() }. %%============================================================================== @@ -119,6 +121,9 @@ do_initialize(RootUri, Capabilities, InitOptions, {ConfigPath, Config}) -> ElvisConfigPath = maps:get("elvis_config_path", Config, undefined), BSPEnabled = maps:get("bsp_enabled", Config, auto), IncrementalSync = maps:get("incremental_sync", Config, true), + CompilerTelemetryEnabled + = maps:get("compiler_telemetry_enabled", Config, false), + IndexingEnabled = maps:get(<<"indexingEnabled">>, InitOptions, true), %% Passed by the LSP client @@ -140,6 +145,7 @@ do_initialize(RootUri, Capabilities, InitOptions, {ConfigPath, Config}) -> , CtRunTest)), ok = set(elvis_config_path, ElvisConfigPath), ok = set(bsp_enabled, BSPEnabled), + ok = set(compiler_telemetry_enabled, CompilerTelemetryEnabled), ok = set(incremental_sync, IncrementalSync), %% Calculated from the above ok = set(apps_paths , project_paths(RootPath, AppsDirs, false)), diff --git a/apps/els_lsp/src/els_compiler_diagnostics.erl b/apps/els_lsp/src/els_compiler_diagnostics.erl index f16fc26d6..ed97e69a9 100644 --- a/apps/els_lsp/src/els_compiler_diagnostics.erl +++ b/apps/els_lsp/src/els_compiler_diagnostics.erl @@ -22,6 +22,7 @@ -export([ include_options/0 , macro_options/0 + , telemetry/2 ]). %%============================================================================== @@ -76,6 +77,7 @@ source() -> -spec on_complete(uri(), [els_diagnostics:diagnostic()]) -> ok. on_complete(Uri, Diagnostics) -> + ?MODULE:telemetry(Uri, Diagnostics), maybe_compile_and_load(Uri, Diagnostics). %%============================================================================== @@ -808,3 +810,19 @@ compile_options(Module) -> [] end end. + +%% @doc Send a telemetry/event LSP message, for logging in the client +-spec telemetry(uri(), [els_diagnostics:diagnostic()]) -> ok. +telemetry(Uri, Diagnostics) -> + case els_config:get(compiler_telemetry_enabled) of + true -> + Codes = [Code || #{ code := Code } <- Diagnostics ], + Method = <<"telemetry/event">>, + Params = #{ uri => Uri + , diagnostics => Codes + , type => <<"erlang-diagnostic-codes">> + }, + els_server:send_notification(Method, Params); + _ -> + ok + end. diff --git a/apps/els_lsp/test/els_diagnostics_SUITE.erl b/apps/els_lsp/test/els_diagnostics_SUITE.erl index 0cc90b70a..e84d109bc 100644 --- a/apps/els_lsp/test/els_diagnostics_SUITE.erl +++ b/apps/els_lsp/test/els_diagnostics_SUITE.erl @@ -20,6 +20,7 @@ , compiler_with_parse_transform_included/1 , compiler_with_parse_transform_broken/1 , compiler_with_parse_transform_deps/1 + , compiler_telemetry/1 , code_path_extra_dirs/1 , use_long_names/1 , epp_with_nonexistent_macro/1 @@ -113,6 +114,10 @@ init_per_testcase(exclude_unused_includes = TestCase, Config) -> NewConfig = els_test_utils:init_per_testcase(TestCase, Config), els_config:set(exclude_unused_includes, ["et/include/et.hrl"]), NewConfig; +init_per_testcase(TestCase, Config) when TestCase =:= compiler_telemetry -> + els_mock_diagnostics:setup(), + mock_compiler_telemetry_enabled(), + els_test_utils:init_per_testcase(TestCase, Config); init_per_testcase(TestCase, Config) -> els_mock_diagnostics:setup(), els_test_utils:init_per_testcase(TestCase, Config). @@ -144,6 +149,11 @@ end_per_testcase(exclude_unused_includes = TestCase, Config) -> els_test_utils:end_per_testcase(TestCase, Config), els_mock_diagnostics:teardown(), ok; +end_per_testcase(TestCase, Config) when TestCase =:= compiler_telemetry -> + unmock_compiler_telemetry_enabled(), + els_test_utils:end_per_testcase(TestCase, Config), + els_mock_diagnostics:teardown(), + ok; end_per_testcase(TestCase, Config) -> els_test_utils:end_per_testcase(TestCase, Config), els_mock_diagnostics:teardown(), @@ -412,6 +422,30 @@ compiler_with_parse_transform_deps(Config) -> ?assertEqual(ExpectedWarningsRanges, WarningsRanges), ok. +-spec compiler_telemetry(config()) -> ok. +compiler_telemetry(Config) -> + Uri = ?config(diagnostics_uri, Config), + els_mock_diagnostics:subscribe(), + ok = els_client:did_save(Uri), + Diagnostics = els_mock_diagnostics:wait_until_complete(), + ?assertEqual(5, length(Diagnostics)), + Warnings = [D || #{severity := ?DIAGNOSTIC_WARNING} = D <- Diagnostics], + Errors = [D || #{severity := ?DIAGNOSTIC_ERROR} = D <- Diagnostics], + ?assertEqual(2, length(Warnings)), + ?assertEqual(3, length(Errors)), + ?assertEqual([<<"L1230">>], [Code || #{ code := Code } <- Warnings ]), + ?assertEqual([<<"L0000">>, <<"L0000">>, <<"L1295">>] + , [Code || #{ code := Code } <- Errors ]), + Telemetry = wait_for_compiler_telemetry(), + #{ type := Type + , uri := UriT + , diagnostics := DiagnosticsCodes } = Telemetry, + ?assertEqual(<<"erlang-diagnostic-codes">>, Type), + ?assertEqual(Uri, UriT), + ?assertEqual([ <<"L1230">>, <<"L0000">>, <<"L0000">>, <<"L1295">>] + , DiagnosticsCodes), + ok. + -spec code_path_extra_dirs(config()) -> ok. code_path_extra_dirs(Config) -> RootPath = binary_to_list(?config(root_path, Config)), @@ -785,3 +819,36 @@ mock_code_reload_enabled() -> unmock_code_reload_enabled() -> meck:unload(els_config). + +mock_compiler_telemetry_enabled() -> + meck:new(els_config, [passthrough, no_link]), + meck:expect( els_config + , get + , fun(compiler_telemetry_enabled) -> + true; + (Key) -> + meck:passthrough([Key]) + end + ), + Self = self(), + meck:expect( els_server + , send_notification + , fun(<<"telemetry/event">> = Method, Params) -> + Self ! {on_complete_telemetry, Params}, + meck:passthrough([Method, Params]); + (M, P) -> + meck:passthrough([M, P]) + end + ), + ok. + +-spec wait_for_compiler_telemetry() -> {uri(), [els_diagnostics:diagnostic()]}. +wait_for_compiler_telemetry() -> + receive + {on_complete_telemetry, Params} -> + Params + end. + +unmock_compiler_telemetry_enabled() -> + meck:unload(els_config), + meck:unload(els_server).