Skip to content

Commit

Permalink
Send telemetry notification with compiler diagnostic codes
Browse files Browse the repository at this point in the history
When enabled, default off, send a `telemetry/event` notification containing the
URI and error codes for compiler diagnostics in the given file.

The allows a client to log the telemetry, and perform later analysis on the
occurrence of diagnostics types.

Note: any logging takes place at the client level, if enabled, to whatever
location makes sense for the specific client.
  • Loading branch information
alanz committed Sep 6, 2021
1 parent d50da89 commit 3dee606
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 1 deletion.
8 changes: 7 additions & 1 deletion apps/els_core/src/els_config.erl
Original file line number Diff line number Diff line change
Expand Up @@ -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()]
Expand All @@ -74,6 +75,7 @@
, code_reload => map() | 'disabled'
, indexing_enabled => boolean()
, bsp_enabled => boolean() | auto
, compiler_telemetry_enabled => boolean()
}.

%%==============================================================================
Expand Down Expand Up @@ -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
Expand All @@ -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)),
Expand Down
18 changes: 18 additions & 0 deletions apps/els_lsp/src/els_compiler_diagnostics.erl
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

-export([ include_options/0
, macro_options/0
, telemetry/2
]).

%%==============================================================================
Expand Down Expand Up @@ -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).

%%==============================================================================
Expand Down Expand Up @@ -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.
67 changes: 67 additions & 0 deletions apps/els_lsp/test/els_diagnostics_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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).
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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)),
Expand Down Expand Up @@ -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).

0 comments on commit 3dee606

Please sign in to comment.