-
Notifications
You must be signed in to change notification settings - Fork 24
Expand file tree
/
Copy pathtrace_service.ex
More file actions
154 lines (127 loc) · 4.23 KB
/
trace_service.ex
File metadata and controls
154 lines (127 loc) · 4.23 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
defmodule LiveDebugger.Services.TraceService do
@moduledoc """
This module is responsible for accessing traces from ETS.
It uses calls to `EtsTableServer` to get proper table reference.
"""
alias LiveDebugger.Structs.Trace
alias LiveDebugger.CommonTypes
alias LiveDebugger.GenServers.EtsTableServer
alias Phoenix.LiveComponent.CID
@default_limit 100
@type ets_elem() :: {integer(), Trace.t()}
@type ets_continuation :: term()
@typedoc """
Pid is used to store mapping to table references.
It identifies ETS tables managed by EtsTableServer.
"""
@type ets_table_id() :: pid()
@doc """
Inserts a new trace into the ETS table.
"""
@spec insert(Trace.t()) :: true
def insert(%Trace{pid: pid, id: id} = trace) do
pid
|> ets_table!()
|> :ets.insert({id, trace})
end
@doc """
Gets a trace of process from the ETS table by `id`.
"""
@spec get(pid :: ets_table_id(), id :: integer()) :: Trace.t() | nil
def get(pid, id) when is_pid(pid) and is_integer(id) do
pid
|> ets_table!()
|> :ets.lookup(id)
|> case do
[] -> nil
[{_id, trace}] -> trace
end
end
@doc """
Returns existing traces of a process for the table with optional filters.
## Options
* `:node_id` - PID or CID to filter traces by
* `:limit` - Maximum number of traces to return (default: 100)
* `:cont` - Used to get next page of items in the following queries
* `:functions` - List of function names to filter traces by
"""
@spec existing_traces(pid :: ets_table_id(), opts :: keyword()) ::
{[Trace.t()], ets_continuation()} | :end_of_table
def existing_traces(pid, opts \\ []) when is_pid(pid) do
opts
|> Keyword.get(:cont, nil)
|> case do
:end_of_table -> :end_of_table
nil -> existing_traces_start(pid, opts)
_cont -> existing_traces_continuation(opts)
end
|> case do
{entries, :"$end_of_table"} ->
{Enum.map(entries, &elem(&1, 1)), :end_of_table}
{entries, new_cont} ->
{Enum.map(entries, &elem(&1, 1)), new_cont}
_ ->
:end_of_table
end
end
@doc """
Deletes traces for LiveView or LiveComponent for given pid.
* `node_id` - PID or CID which identifies node
"""
@spec clear_traces(pid :: ets_table_id(), node_id :: pid() | CommonTypes.cid()) :: true
def clear_traces(pid, %CID{} = node_id) when is_pid(pid) do
pid
|> ets_table!()
|> :ets.match_delete({:_, %{cid: node_id}})
end
def clear_traces(pid, node_id) when is_pid(pid) and is_pid(node_id) do
pid
|> ets_table!()
|> :ets.match_delete({:_, %{pid: node_id, cid: nil}})
end
@spec existing_traces_start(ets_table_id(), Keyword.t()) ::
{[ets_elem()], ets_continuation()} | :"$end_of_table"
defp existing_traces_start(table_id, opts) do
limit = Keyword.get(opts, :limit, @default_limit)
functions = Keyword.get(opts, :functions, [])
node_id = Keyword.get(opts, :node_id)
if limit < 1 do
raise ArgumentError, "limit must be >= 1"
end
match_spec = match_spec(node_id, functions)
table_id
|> ets_table!()
|> :ets.select(match_spec, limit)
end
@spec existing_traces_continuation(Keyword.t()) ::
{[ets_elem()], ets_continuation()} | :"$end_of_table"
defp existing_traces_continuation(opts) do
cont = Keyword.get(opts, :cont, nil)
:ets.select(cont)
end
defp match_spec(node_id, functions) when is_pid(node_id) do
[
{{:_, %{function: :"$1", pid: node_id, cid: nil}}, to_spec(functions), [:"$_"]}
]
end
defp match_spec(%CID{} = node_id, functions) do
[{{:_, %{function: :"$1", cid: node_id}}, to_spec(functions), [:"$_"]}]
end
defp match_spec(nil, functions) do
[{{:_, %{function: :"$1"}}, to_spec(functions), [:"$_"]}]
end
def to_spec([]), do: []
def to_spec([single]), do: [{:"=:=", :"$1", single}]
def to_spec([first, second | rest]) do
initial_orelse = {:orelse, List.first(to_spec([first])), List.first(to_spec([second]))}
result =
Enum.reduce(rest, initial_orelse, fn x, acc ->
{:orelse, acc, List.first(to_spec([x]))}
end)
[result]
end
@spec ets_table!(pid :: ets_table_id()) :: :ets.table()
defp ets_table!(pid) when is_pid(pid) do
EtsTableServer.table!(pid)
end
end