Skip to content

Commit 6a971dd

Browse files
authored
Move source code maps to ETS (#777)
1 parent e40e9a8 commit 6a971dd

File tree

5 files changed

+69
-46
lines changed

5 files changed

+69
-46
lines changed

lib/sentry/application.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ defmodule Sentry.Application do
33

44
use Application
55

6-
alias Sentry.{Config, Sources}
6+
alias Sentry.Config
77

88
@impl true
99
def start(_type, _opts) do
@@ -24,6 +24,7 @@ defmodule Sentry.Application do
2424
children =
2525
[
2626
{Registry, keys: :unique, name: Sentry.Transport.SenderRegistry},
27+
Sentry.Sources,
2728
Sentry.Dedupe,
2829
{Sentry.Integrations.CheckInIDMappings,
2930
[
@@ -35,7 +36,6 @@ defmodule Sentry.Application do
3536
[Sentry.Transport.SenderPool]
3637

3738
cache_loaded_applications()
38-
_ = Sources.load_source_code_map_if_present()
3939

4040
with {:ok, pid} <-
4141
Supervisor.start_link(children, strategy: :one_for_one, name: Sentry.Supervisor) do

lib/sentry/event.ex

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -551,8 +551,9 @@ defmodule Sentry.Event do
551551
not Config.enable_source_code_context?() ->
552552
frame
553553

554-
source_map = Sources.get_source_code_map_from_persistent_term() ->
555-
{pre_context, context, post_context} = Sources.get_source_context(source_map, file, line)
554+
source_map_for_file = Sources.get_lines_for_file(file) ->
555+
{pre_context, context, post_context} =
556+
Sources.get_source_context(source_map_for_file, line)
556557

557558
%Interfaces.Stacktrace.Frame{
558559
frame

lib/sentry/sources.ex

Lines changed: 50 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,49 @@
11
defmodule Sentry.Sources do
22
@moduledoc false
33

4+
use GenServer
5+
46
alias Sentry.Config
57

8+
@type source_map_for_file :: %{
9+
optional(line_no :: pos_integer()) => line_contents :: String.t()
10+
}
11+
612
@type source_map :: %{
7-
optional(String.t()) => %{
8-
(line_no :: pos_integer()) => line_contents :: String.t()
9-
}
13+
optional(String.t()) => source_map_for_file()
1014
}
1115

12-
@source_code_map_key {:sentry, :source_code_map}
16+
## GenServer
17+
18+
@table __MODULE__
19+
20+
@spec start_link(keyword()) :: GenServer.on_start()
21+
def start_link([] = _) do
22+
GenServer.start_link(__MODULE__, nil, name: __MODULE__)
23+
end
24+
25+
@impl true
26+
def init(nil) do
27+
_ = :ets.new(@table, [:public, :named_table, read_concurrency: true])
28+
{:ok, :no_state, {:continue, :load_source_code_map}}
29+
end
30+
31+
@impl true
32+
def handle_continue(:load_source_code_map, state) do
33+
:ok =
34+
with {:loaded, source_map} <- load_source_code_map_if_present() do
35+
Enum.each(source_map, fn {path, lines_map} ->
36+
:ets.insert(@table, {path, lines_map})
37+
end)
38+
else
39+
_error -> :ok
40+
end
41+
42+
{:noreply, state}
43+
end
44+
45+
## Other functions
46+
1347
@compression_level if Mix.env() == :test, do: 0, else: 9
1448

1549
# Default argument is here for testing.
@@ -21,7 +55,6 @@ defmodule Sentry.Sources do
2155

2256
with {:ok, contents} <- File.read(path),
2357
{:ok, source_map} <- decode_source_code_map(contents) do
24-
:persistent_term.put(@source_code_map_key, source_map)
2558
{:loaded, source_map}
2659
else
2760
{:error, :binary_to_term} ->
@@ -67,11 +100,6 @@ defmodule Sentry.Sources do
67100
end
68101
end
69102

70-
@spec get_source_code_map_from_persistent_term() :: source_map() | nil
71-
def get_source_code_map_from_persistent_term do
72-
:persistent_term.get(@source_code_map_key, nil)
73-
end
74-
75103
@spec load_files(keyword()) :: {:ok, source_map()} | {:error, message :: String.t()}
76104
def load_files(config \\ []) when is_list(config) do
77105
config = Sentry.Config.validate!(config)
@@ -106,23 +134,25 @@ defmodule Sentry.Sources do
106134
source_map -> {:ok, source_map}
107135
end
108136

109-
@spec get_source_context(source_map(), String.t() | nil, pos_integer() | nil) ::
110-
{[String.t()], String.t() | nil, [String.t()]}
111-
def get_source_context(%{} = files, file_name, line_number) do
112-
context_lines = Config.context_lines()
113-
114-
case Map.fetch(files, file_name) do
115-
:error -> {[], nil, []}
116-
{:ok, file} -> get_source_context_for_file(file, line_number, context_lines)
137+
@spec get_lines_for_file(Path.t()) :: map() | nil
138+
def get_lines_for_file(file) do
139+
case :ets.lookup(@table, file) do
140+
[{^file, lines}] -> lines
141+
[] -> nil
117142
end
118143
end
119144

120-
defp get_source_context_for_file(file, line_number, context_lines) do
145+
@spec get_source_context(source_map_for_file(), pos_integer() | nil) ::
146+
{[String.t()], String.t() | nil, [String.t()]}
147+
def get_source_context(source_map_for_file, line_number)
148+
when is_map(source_map_for_file) and (is_integer(line_number) or is_nil(line_number)) do
149+
context_lines = Config.context_lines()
150+
121151
context_line_indices = 0..(2 * context_lines)
122152

123153
Enum.reduce(context_line_indices, {[], nil, []}, fn i, {pre_context, context, post_context} ->
124154
context_line_number = line_number - context_lines + i
125-
source = Map.get(file, context_line_number)
155+
source = Map.get(source_map_for_file, context_line_number)
126156

127157
cond do
128158
context_line_number == line_number && source ->

lib/sentry/transport/sender.ex

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ defmodule Sentry.Transport.Sender do
3232

3333
@impl GenServer
3434
def init([]) do
35+
if function_exported?(Process, :set_label, 1) do
36+
apply(Process, :set_label, [__MODULE__])
37+
end
38+
3539
{:ok, %__MODULE__{}}
3640
end
3741

test/sources_test.exs

Lines changed: 10 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -82,45 +82,33 @@ defmodule Sentry.SourcesTest do
8282
end
8383

8484
@tag :tmp_dir
85-
test "stores the source code map in :persistent_term if valid", %{tmp_dir: tmp_dir} do
85+
test "reads the source code map from the file", %{tmp_dir: tmp_dir} do
8686
encoded_map = Sources.encode_source_code_map(%{"foo.ex" => %{}})
8787
path = Path.join(tmp_dir, "valid.map")
8888
File.write!(path, encoded_map)
8989
assert {:loaded, map} = Sources.load_source_code_map_if_present(path)
9090
assert map == %{"foo.ex" => %{}}
91-
assert :persistent_term.get({:sentry, :source_code_map}) == %{"foo.ex" => %{}}
92-
after
93-
:persistent_term.erase({:sentry, :source_code_map})
9491
end
9592
end
9693

97-
describe "get_source_context/3" do
94+
describe "get_source_context/2" do
9895
test "returns the correct context" do
9996
map = %{
100-
"foo.ex" => %{
101-
1 => "defmodule Foo do",
102-
2 => " def bar do",
103-
3 => " \"bar\"",
104-
4 => " end",
105-
5 => "end"
106-
},
107-
"bar.ex" => %{
108-
1 => "defmodule Bar do",
109-
2 => " def baz do",
110-
3 => " \"baz\"",
111-
4 => " end",
112-
5 => "end"
113-
}
97+
1 => "defmodule Foo do",
98+
2 => " def bar do",
99+
3 => " \"bar\"",
100+
4 => " end",
101+
5 => "end"
114102
}
115103

116-
assert {pre, context, post} = Sources.get_source_context(map, "foo.ex", 4)
104+
assert {pre, context, post} = Sources.get_source_context(map, 4)
117105
assert pre == ["defmodule Foo do", " def bar do", " \"bar\""]
118106
assert context == " end"
119107
assert post == ["end"]
120108
end
121109

122-
test "works if the file doesn't exist" do
123-
assert {[], nil, []} = Sources.get_source_context(%{}, "foo.ex", 4)
110+
test "works if the line number doesn't exist" do
111+
assert {[], nil, []} = Sources.get_source_context(%{}, 4)
124112
end
125113
end
126114
end

0 commit comments

Comments
 (0)