Skip to content

Commit 279ab4a

Browse files
authored
Tools for inspecting timing and performance during production
1 parent 4c8f2ce commit 279ab4a

File tree

1 file changed

+126
-0
lines changed

1 file changed

+126
-0
lines changed

lib/nerves_hub/debug.ex

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
defmodule NervesHub.Debug do
2+
@moduledoc """
3+
Tools for debugging performance in production.
4+
"""
5+
6+
def mark(label) do
7+
t = Process.get(:t)
8+
s = Process.get(:s)
9+
10+
new_t =
11+
if t do
12+
new = System.monotonic_time(:millisecond)
13+
IO.puts("#{label} [#{inspect(self())}]: #{new - t} | #{new - s} ms")
14+
new
15+
else
16+
IO.puts("#{label} new process")
17+
v = System.monotonic_time(:millisecond)
18+
Process.put(:s, v)
19+
v
20+
end
21+
22+
Process.put(:t, new_t)
23+
end
24+
25+
def mark(piped, label) do
26+
mark(label)
27+
piped
28+
end
29+
30+
def log_slow_queries(threshold_ms \\ 100) do
31+
pid = self()
32+
_ = :telemetry.detach("ecto-debug-handler")
33+
34+
:ok =
35+
:telemetry.attach(
36+
"ecto-debug-handler",
37+
[:nerves_hub, :repo, :query],
38+
fn event, measurements, metadata, config ->
39+
send(pid, {:event, event, measurements, metadata, config})
40+
end,
41+
%{}
42+
)
43+
44+
spawn(fn ->
45+
:timer.sleep(30_000)
46+
_ = :telemetry.detach("ecto-debug-handler")
47+
send(pid, :done)
48+
end)
49+
50+
wait_for_query(threshold_ms)
51+
52+
:ok
53+
end
54+
55+
defp wait_for_query(threshold_ms) do
56+
threshold = System.convert_time_unit(threshold_ms, :millisecond, :native)
57+
58+
receive do
59+
{:event, event, measurements, metadata, _config} ->
60+
measurements = Map.delete(measurements, :idle_time)
61+
62+
if Enum.any?(measurements, fn {_k, v} -> v > threshold end) do
63+
IO.inspect(metadata)
64+
65+
Enum.each(measurements, fn {k, v} ->
66+
if v > threshold do
67+
ms = System.convert_time_unit(v, :native, :millisecond)
68+
IO.inspect("#{inspect(event)} #{k}: #{ms} milliseconds")
69+
end
70+
end)
71+
end
72+
73+
wait_for_query(threshold_ms)
74+
75+
:done ->
76+
:ok
77+
after
78+
30_000 ->
79+
:timeout
80+
end
81+
end
82+
83+
def time_function({_, _, _} = recon_pattern, samples, sample_timeout \\ 10_000) do
84+
pid = self()
85+
86+
:recon_trace.calls(recon_pattern, samples,
87+
timestamp: :trace,
88+
formatter: fn term ->
89+
send(pid, {:sample, term})
90+
"Collected trace.\n"
91+
end
92+
)
93+
94+
wait_samples(samples, samples, sample_timeout)
95+
end
96+
97+
defp wait_samples(0, _limit, _timeout) do
98+
:ok
99+
end
100+
101+
defp wait_samples(count, limit, timeout) do
102+
receive do
103+
{:sample, {:trace_ts, _pid, :call, {_m, _f, _args}, erlang_ts}} ->
104+
microseconds = ts_to_micro(erlang_ts)
105+
IO.puts("Sample call start, #{microseconds}uS")
106+
wait_samples(count - 1, limit, timeout)
107+
108+
{:sample, term} ->
109+
IO.inspect(term, label: "sample", limit: :infinity)
110+
wait_samples(count - 1, limit, timeout)
111+
after
112+
timeout ->
113+
{:error, :timeout}
114+
end
115+
end
116+
117+
defp ts_to_micro({megaseconds, seconds, microseconds}) do
118+
microseconds
119+
|> add(seconds_to_micro(seconds))
120+
|> add(megaseconds_to_micro(megaseconds))
121+
end
122+
123+
defp add(a, b), do: a + b
124+
defp seconds_to_micro(seconds), do: seconds * 1_000_000
125+
defp megaseconds_to_micro(megaseconds), do: megaseconds * 1_000_000_000_000
126+
end

0 commit comments

Comments
 (0)