Skip to content

Commit ed3552c

Browse files
committed
add telemetry to DAP and LSP requests
1 parent 562d876 commit ed3552c

File tree

5 files changed

+227
-27
lines changed

5 files changed

+227
-27
lines changed

apps/elixir_ls_debugger/lib/debugger/output.ex

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,27 @@ defmodule ElixirLS.Debugger.Output do
6060
send_event(server, "output", %{"category" => "stderr", "output" => maybe_append_newline(str)})
6161
end
6262

63+
def telemetry(server \\ __MODULE__, event, properties, measurements)
64+
when is_binary(event) and is_map(properties) and is_map(measurements) do
65+
common_properties = %{
66+
"elixir_ls.elixir_version" => System.version(),
67+
"elixir_ls.otp_release" => System.otp_release(),
68+
"elixir_ls.erts_version" => Application.spec(:erts, :vsn),
69+
"elixir_ls.mix_env" => Mix.env(),
70+
"elixir_ls.mix_target" => Mix.target()
71+
}
72+
73+
send_event(server, "output", %{
74+
"category" => "telemetry",
75+
"output" => event,
76+
"data" => %{
77+
"name" => event,
78+
"properties" => Map.merge(common_properties, properties),
79+
"measurements" => measurements
80+
}
81+
})
82+
end
83+
6384
defp maybe_append_newline(message) do
6485
unless String.ends_with?(message, "\n") do
6586
message <> "\n"

apps/elixir_ls_debugger/lib/debugger/server.ex

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,11 @@ defmodule ElixirLS.Debugger.Server do
281281
{:noreply, %{state | dbg_session: from}}
282282
end
283283

284-
def handle_call({:request_finished, packet, result}, _from, state = %__MODULE__{}) do
284+
def handle_call(
285+
{:request_finished, request(_, command) = packet, start_time, result},
286+
_from,
287+
state = %__MODULE__{}
288+
) do
285289
if MapSet.member?(state.progresses, packet["seq"]) do
286290
Output.send_event("progressEnd", %{
287291
"progressId" => packet["seq"]
@@ -300,7 +304,12 @@ defmodule ElixirLS.Debugger.Server do
300304
)
301305

302306
{:ok, response_body} ->
307+
elapsed = System.monotonic_time(:millisecond) - start_time
303308
Output.send_response(packet, response_body)
309+
310+
Output.telemetry("elixir_ls.dap_request", %{"elixir_ls.dap_command" => command}, %{
311+
"elixir_ls.dap_request_time" => elapsed
312+
})
304313
end
305314

306315
state = %{
@@ -325,16 +334,33 @@ defmodule ElixirLS.Debugger.Server do
325334
@impl GenServer
326335
def handle_cast({:receive_packet, request(_, "disconnect") = packet}, state = %__MODULE__{}) do
327336
Output.send_response(packet, %{})
337+
338+
Output.telemetry("elixir_ls.dap_request", %{"elixir_ls.dap_command" => "disconnect"}, %{
339+
"elixir_ls.dap_request_time" => 0
340+
})
341+
328342
{:noreply, state, {:continue, :disconnect}}
329343
end
330344

331-
def handle_cast({:receive_packet, request(seq, _) = packet}, state = %__MODULE__{}) do
345+
def handle_cast({:receive_packet, request(seq, command) = packet}, state = %__MODULE__{}) do
346+
start_time = System.monotonic_time(:millisecond)
347+
332348
try do
333349
if state.client_info == nil do
334350
case packet do
335351
request(_, "initialize") ->
336352
{response_body, state} = handle_request(packet, state)
353+
elapsed = System.monotonic_time(:millisecond) - start_time
337354
Output.send_response(packet, response_body)
355+
356+
Output.telemetry(
357+
"elixir_ls.dap_request",
358+
%{"elixir_ls.dap_command" => "initialize"},
359+
%{
360+
"elixir_ls.dap_request_time" => elapsed
361+
}
362+
)
363+
338364
{:noreply, state}
339365

340366
request(_, command) ->
@@ -349,11 +375,17 @@ defmodule ElixirLS.Debugger.Server do
349375
state =
350376
case handle_request(packet, state) do
351377
{response_body, state} ->
378+
elapsed = System.monotonic_time(:millisecond) - start_time
352379
Output.send_response(packet, response_body)
380+
381+
Output.telemetry("elixir_ls.dap_request", %{"elixir_ls.dap_command" => command}, %{
382+
"elixir_ls.dap_request_time" => elapsed
383+
})
384+
353385
state
354386

355387
{:async, fun, state} ->
356-
{pid, _ref} = handle_request_async(packet, fun)
388+
{pid, _ref} = handle_request_async(packet, start_time, fun)
357389
%{state | requests: Map.put(state.requests, seq, {pid, packet})}
358390
end
359391

@@ -2096,7 +2128,7 @@ defmodule ElixirLS.Debugger.Server do
20962128
end
20972129
end
20982130

2099-
defp handle_request_async(packet, func) do
2131+
defp handle_request_async(packet, start_time, func) do
21002132
parent = self()
21012133

21022134
spawn_monitor(fn ->
@@ -2116,7 +2148,7 @@ defmodule ElixirLS.Debugger.Server do
21162148
%ServerError{message: "internalServerError", format: message, variables: %{}}}
21172149
end
21182150

2119-
GenServer.call(parent, {:request_finished, packet, result}, :infinity)
2151+
GenServer.call(parent, {:request_finished, packet, start_time, result}, :infinity)
21202152
end)
21212153
end
21222154
end

apps/language_server/lib/language_server/json_rpc.ex

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,22 @@ defmodule ElixirLS.LanguageServer.JsonRpc do
9090
notify("window/logMessage", %{type: message_type_code(type), message: to_string(message)})
9191
end
9292

93+
def telemetry(name, properties, measurements) do
94+
common_properties = %{
95+
"elixir_ls.elixir_version" => System.version(),
96+
"elixir_ls.otp_release" => System.otp_release(),
97+
"elixir_ls.erts_version" => Application.spec(:erts, :vsn),
98+
"elixir_ls.mix_env" => Mix.env(),
99+
"elixir_ls.mix_target" => Mix.target()
100+
}
101+
102+
notify("telemetry/event", %{
103+
name: name,
104+
properties: Map.merge(common_properties, properties),
105+
measurements: measurements
106+
})
107+
end
108+
93109
def register_capability_request(server \\ __MODULE__, server_instance_id, method, options) do
94110
id = server_instance_id <> method <> JasonV.encode!(options)
95111

apps/language_server/lib/language_server/providers/execute_command.ex

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ defmodule ElixirLS.LanguageServer.Providers.ExecuteCommand do
1818
{:ok, any} | {:error, atom, String.t()}
1919

2020
def execute(command_with_server_id, args, state) do
21-
with [command, _server_id] <- String.split(command_with_server_id, ":"),
21+
with command when not is_nil(command) <- get_command(command_with_server_id),
2222
handler when not is_nil(handler) <- Map.get(@handlers, command) do
2323
handler.execute(args, state)
2424
else
@@ -32,4 +32,11 @@ defmodule ElixirLS.LanguageServer.Providers.ExecuteCommand do
3232
"#{k}:#{server_instance_id}"
3333
end
3434
end
35+
36+
def get_command(command_with_server_id) do
37+
case String.split(command_with_server_id, ":") do
38+
[command, _server_id] -> command
39+
_ -> nil
40+
end
41+
end
3542
end

apps/language_server/lib/language_server/server.ex

Lines changed: 145 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -130,12 +130,32 @@ defmodule ElixirLS.LanguageServer.Server do
130130

131131
@impl GenServer
132132
def handle_call({:request_finished, id, result}, _from, state = %__MODULE__{}) do
133+
{{_pid, command, start_time}, requests} = Map.pop!(state.requests, id)
134+
133135
case result do
134-
{:error, type, msg} -> JsonRpc.respond_with_error(id, type, msg)
135-
{:ok, result} -> JsonRpc.respond(id, result)
136+
{:error, type, msg} ->
137+
JsonRpc.respond_with_error(id, type, msg)
138+
139+
JsonRpc.telemetry(
140+
"elixir_ls.lsp_request_error",
141+
%{
142+
"elixir_ls.lsp_command" => command,
143+
"elixir_ls.lsp_error" => type,
144+
"elixir_ls.lsp_error_message" => msg
145+
},
146+
%{}
147+
)
148+
149+
{:ok, result} ->
150+
elapsed = System.monotonic_time(:millisecond) - start_time
151+
JsonRpc.respond(id, result)
152+
153+
JsonRpc.telemetry("elixir_ls.lsp_request", %{"elixir_ls.lsp_command" => command}, %{
154+
"elixir_ls.lsp_request_time" => elapsed
155+
})
136156
end
137157

138-
state = %{state | requests: Map.delete(state.requests, id)}
158+
state = %{state | requests: requests}
139159
{:reply, :ok, state}
140160
end
141161

@@ -259,10 +279,21 @@ defmodule ElixirLS.LanguageServer.Server do
259279
@impl GenServer
260280
def handle_info({:DOWN, _ref, :process, pid, reason}, %__MODULE__{requests: requests} = state) do
261281
state =
262-
case Enum.find(requests, &match?({_, ^pid}, &1)) do
263-
{id, _} ->
282+
case Enum.find(requests, &match?({_, {^pid, _, _}}, &1)) do
283+
{id, {^pid, command, _start_time}} ->
264284
error_msg = Exception.format_exit(reason)
265285
JsonRpc.respond_with_error(id, :server_error, error_msg)
286+
287+
JsonRpc.telemetry(
288+
"elixir_ls.lsp_request_error",
289+
%{
290+
"elixir_ls.lsp_command" => command,
291+
"elixir_ls.lsp_error" => :server_error,
292+
"elixir_ls.lsp_error_message" => error_msg
293+
},
294+
%{}
295+
)
296+
266297
%{state | requests: Map.delete(requests, id)}
267298

268299
nil ->
@@ -360,9 +391,10 @@ defmodule ElixirLS.LanguageServer.Server do
360391

361392
defp handle_notification(cancel_request(id), %__MODULE__{requests: requests} = state) do
362393
case requests do
363-
%{^id => pid} ->
394+
%{^id => {pid, _command, _start_time}} ->
364395
Process.exit(pid, :cancelled)
365396
JsonRpc.respond_with_error(id, :request_cancelled, "Request cancelled")
397+
366398
%{state | requests: Map.delete(requests, id)}
367399

368400
_ ->
@@ -579,40 +611,132 @@ defmodule ElixirLS.LanguageServer.Server do
579611
state = %__MODULE__{server_instance_id: server_instance_id}
580612
)
581613
when not is_initialized(server_instance_id) do
614+
start_time = System.monotonic_time(:millisecond)
615+
582616
case packet do
583617
initialize_req(_id, _root_uri, _client_capabilities) ->
584618
{:ok, result, state} = handle_request(packet, state)
619+
elapsed = System.monotonic_time(:millisecond) - start_time
585620
JsonRpc.respond(id, result)
621+
622+
JsonRpc.telemetry("elixir_ls.lsp_request", %{"elixir_ls.lsp_command" => "initialize"}, %{
623+
"elixir_ls.lsp_request_time" => elapsed
624+
})
625+
586626
state
587627

588628
_ ->
589629
JsonRpc.respond_with_error(id, :server_not_initialized)
630+
631+
JsonRpc.telemetry(
632+
"elixir_ls.lsp_request_error",
633+
%{
634+
"elixir_ls.lsp_command" => "initialize",
635+
"elixir_ls.lsp_error" => :server_not_initialized,
636+
"elixir_ls.lsp_error_message" => "Server not initialized"
637+
},
638+
%{}
639+
)
640+
590641
state
591642
end
592643
end
593644

594-
defp handle_request_packet(id, packet, state = %__MODULE__{received_shutdown?: false}) do
595-
case handle_request(packet, state) do
596-
{:ok, result, state} ->
597-
JsonRpc.respond(id, result)
598-
state
645+
defp handle_request_packet(
646+
id,
647+
packet = %{"method" => command},
648+
state = %__MODULE__{received_shutdown?: false}
649+
) do
650+
command =
651+
case packet do
652+
execute_command_req(_id, custom_command_with_server_id, _args) ->
653+
command <> ":" <> (ExecuteCommand.get_command(custom_command_with_server_id) || "")
654+
655+
_ ->
656+
command
657+
end
658+
659+
start_time = System.monotonic_time(:millisecond)
660+
661+
try do
662+
case handle_request(packet, state) do
663+
{:ok, result, state} ->
664+
elapsed = System.monotonic_time(:millisecond) - start_time
665+
JsonRpc.respond(id, result)
666+
667+
JsonRpc.telemetry("elixir_ls.lsp_request", %{"elixir_ls.lsp_command" => command}, %{
668+
"elixir_ls.lsp_request_time" => elapsed
669+
})
670+
671+
state
672+
673+
{:error, type, msg, state} ->
674+
JsonRpc.respond_with_error(id, type, msg)
675+
676+
JsonRpc.telemetry(
677+
"elixir_ls.lsp_request_error",
678+
%{
679+
"elixir_ls.lsp_command" => command,
680+
"elixir_ls.lsp_error" => type,
681+
"elixir_ls.lsp_error_message" => msg
682+
},
683+
%{}
684+
)
685+
686+
state
687+
688+
{:async, fun, state} ->
689+
{pid, _ref} = handle_request_async(id, fun)
690+
%{state | requests: Map.put(state.requests, id, {pid, command, start_time})}
691+
end
692+
rescue
693+
e in InvalidParamError ->
694+
JsonRpc.respond_with_error(id, :invalid_params, e.message)
695+
696+
JsonRpc.telemetry(
697+
"elixir_ls.lsp_request_error",
698+
%{
699+
"elixir_ls.lsp_command" => command,
700+
"elixir_ls.lsp_error" => :invalid_params,
701+
"elixir_ls.lsp_error_message" => e.message
702+
},
703+
%{}
704+
)
599705

600-
{:error, type, msg, state} ->
601-
JsonRpc.respond_with_error(id, type, msg)
602706
state
707+
catch
708+
kind, payload ->
709+
{payload, stacktrace} = Exception.blame(kind, payload, __STACKTRACE__)
710+
error_msg = Exception.format(kind, payload, stacktrace)
711+
JsonRpc.respond_with_error(id, :server_error, error_msg)
712+
713+
JsonRpc.telemetry(
714+
"elixir_ls.lsp_request_error",
715+
%{
716+
"elixir_ls.lsp_command" => command,
717+
"elixir_ls.lsp_error" => :server_error,
718+
"elixir_ls.lsp_error_message" => error_msg
719+
},
720+
%{}
721+
)
603722

604-
{:async, fun, state} ->
605-
{pid, _ref} = handle_request_async(id, fun)
606-
%{state | requests: Map.put(state.requests, id, pid)}
723+
state
607724
end
608-
rescue
609-
e in InvalidParamError ->
610-
JsonRpc.respond_with_error(id, :invalid_params, e.message)
611-
state
612725
end
613726

614-
defp handle_request_packet(id, _packet, state = %__MODULE__{}) do
727+
defp handle_request_packet(id, _packet = %{"method" => command}, state = %__MODULE__{}) do
615728
JsonRpc.respond_with_error(id, :invalid_request)
729+
730+
JsonRpc.telemetry(
731+
"elixir_ls.lsp_request_error",
732+
%{
733+
"elixir_ls.lsp_command" => command,
734+
"elixir_ls.lsp_error" => :invalid_request,
735+
"elixir_ls.lsp_error_message" => "Invalid Request"
736+
},
737+
%{}
738+
)
739+
616740
state
617741
end
618742

0 commit comments

Comments
 (0)