|
| 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