Skip to content

Commit c46bc0f

Browse files
committed
WIP - rework SpanStorage to use ETS
1 parent e77a601 commit c46bc0f

File tree

4 files changed

+90
-62
lines changed

4 files changed

+90
-62
lines changed

lib/sentry/application.ex

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ defmodule Sentry.Application do
1010
config = Config.validate!()
1111
:ok = Config.persist(config)
1212

13+
# Setup ETS tables for span storage
14+
Sentry.Opentelemetry.SpanStorage.setup()
15+
1316
http_client = Keyword.fetch!(config, :client)
1417

1518
maybe_http_client_spec =
@@ -27,7 +30,6 @@ defmodule Sentry.Application do
2730
Sentry.Sources,
2831
Sentry.Dedupe,
2932
Sentry.ClientReport.Sender,
30-
Sentry.Opentelemetry.SpanStorage,
3133
{Sentry.Integrations.CheckInIDMappings,
3234
[
3335
max_expected_check_in_time:

lib/sentry/opentelemetry/span_processor.ex

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ defmodule Sentry.Opentelemetry.SpanProcessor do
6262
contexts: %{
6363
trace: build_trace_context(root_span)
6464
},
65-
spans: Enum.map([root_span | child_spans], &build_span(&1))
65+
spans: [build_span(root_span) | Enum.map(child_spans, &build_span(&1))]
6666
})
6767
end
6868

@@ -304,7 +304,9 @@ defmodule Sentry.Opentelemetry.SpanProcessor do
304304
start_timestamp: span_record.start_time,
305305
timestamp: span_record.end_time,
306306
span_id: span_record.span_id,
307-
parent_span_id: span_record.parent_span_id
307+
parent_span_id: span_record.parent_span_id,
308+
# Add origin to match other span types
309+
origin: span_record.origin
308310
}
309311
end
310312

Lines changed: 63 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,88 +1,92 @@
11
defmodule Sentry.Opentelemetry.SpanStorage do
2-
use GenServer
2+
@moduledoc false
33

4-
def start_link(_opts) do
5-
GenServer.start_link(__MODULE__, nil, name: __MODULE__)
6-
end
4+
@root_spans_table :sentry_root_spans
5+
@child_spans_table :sentry_child_spans
76

8-
def init(_) do
9-
{:ok, %{root_spans: %{}, child_spans: %{}}}
10-
end
7+
def setup do
8+
case :ets.whereis(@root_spans_table) do
9+
:undefined ->
10+
:ets.new(@root_spans_table, [:set, :public, :named_table])
1111

12-
def store_span(span_data) do
13-
GenServer.call(__MODULE__, {:store_span, span_data})
14-
end
12+
_ ->
13+
:ok
14+
end
1515

16-
def get_root_span(span_id) do
17-
GenServer.call(__MODULE__, {:get_root_span, span_id})
18-
end
16+
case :ets.whereis(@child_spans_table) do
17+
:undefined ->
18+
:ets.new(@child_spans_table, [:bag, :public, :named_table])
1919

20-
def get_child_spans(parent_span_id) do
21-
GenServer.call(__MODULE__, {:get_child_spans, parent_span_id})
22-
end
23-
24-
def update_span(span_data) do
25-
GenServer.call(__MODULE__, {:update_span, span_data})
26-
end
20+
_ ->
21+
:ok
22+
end
2723

28-
def remove_span(span_id) do
29-
GenServer.call(__MODULE__, {:remove_span, span_id})
24+
:ok
3025
end
3126

32-
def remove_child_spans(parent_span_id) do
33-
GenServer.call(__MODULE__, {:remove_child_spans, parent_span_id})
34-
end
27+
def store_span(span_data) do
28+
ensure_tables_exist()
3529

36-
def handle_call({:store_span, span_data}, _from, state) do
3730
if span_data.parent_span_id == nil do
38-
new_state = put_in(state, [:root_spans, span_data.span_id], span_data)
39-
{:reply, :ok, new_state}
31+
:ets.insert(@root_spans_table, {span_data.span_id, span_data})
4032
else
41-
new_state =
42-
update_in(state, [:child_spans, span_data.parent_span_id], fn spans ->
43-
(spans || []) ++ [span_data]
44-
end)
45-
46-
{:reply, :ok, new_state}
33+
:ets.insert(@child_spans_table, {span_data.parent_span_id, span_data})
4734
end
35+
36+
:ok
4837
end
4938

50-
def handle_call({:get_root_span, span_id}, _from, state) do
51-
{:reply, state.root_spans[span_id], state}
39+
def get_root_span(span_id) do
40+
ensure_tables_exist()
41+
42+
case :ets.lookup(@root_spans_table, span_id) do
43+
[{^span_id, span}] -> span
44+
[] -> nil
45+
end
5246
end
5347

54-
def handle_call({:get_child_spans, parent_span_id}, _from, state) do
55-
{:reply, state.child_spans[parent_span_id] || [], state}
48+
def get_child_spans(parent_span_id) do
49+
ensure_tables_exist()
50+
51+
:ets.lookup(@child_spans_table, parent_span_id)
52+
|> Enum.map(fn {_parent_id, span} -> span end)
5653
end
5754

58-
def handle_call({:update_span, span_data}, _from, state) do
55+
def update_span(span_data) do
56+
ensure_tables_exist()
57+
5958
if span_data.parent_span_id == nil do
60-
new_state = put_in(state, [:root_spans, span_data.span_id], span_data)
61-
{:reply, :ok, new_state}
59+
:ets.insert(@root_spans_table, {span_data.span_id, span_data})
6260
else
63-
new_state =
64-
update_in(state, [:child_spans, span_data.parent_span_id], fn spans ->
65-
Enum.map(spans || [], fn span ->
66-
if span.span_id == span_data.span_id, do: span_data, else: span
67-
end)
68-
end)
69-
70-
{:reply, :ok, new_state}
61+
existing_spans = :ets.lookup(@child_spans_table, span_data.parent_span_id)
62+
63+
:ets.delete(@child_spans_table, span_data.parent_span_id)
64+
65+
Enum.each(existing_spans, fn {parent_id, span} ->
66+
if span.span_id != span_data.span_id do
67+
:ets.insert(@child_spans_table, {parent_id, span})
68+
end
69+
end)
70+
71+
:ets.insert(@child_spans_table, {span_data.parent_span_id, span_data})
7172
end
73+
74+
:ok
7275
end
7376

74-
def handle_call({:remove_span, span_id}, _from, state) do
75-
new_state = %{
76-
state
77-
| root_spans: Map.delete(state.root_spans, span_id),
78-
child_spans: Map.delete(state.child_spans, span_id)
79-
}
77+
def remove_span(span_id) do
78+
ensure_tables_exist()
79+
:ets.delete(@root_spans_table, span_id)
80+
:ok
81+
end
8082

81-
{:reply, :ok, new_state}
83+
def remove_child_spans(parent_span_id) do
84+
ensure_tables_exist()
85+
:ets.delete(@child_spans_table, parent_span_id)
86+
:ok
8287
end
8388

84-
def handle_call({:remove_child_spans, parent_span_id}, _from, state) do
85-
new_state = %{state | child_spans: Map.delete(state.child_spans, parent_span_id)}
86-
{:reply, :ok, new_state}
89+
defp ensure_tables_exist do
90+
setup()
8791
end
8892
end

test/sentry/opentelemetry/span_processor_test.exs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,26 @@ defmodule Sentry.Opentelemetry.SpanProcessorTest do
33

44
import Sentry.TestHelpers
55

6+
alias Sentry.Opentelemetry.SpanStorage
7+
8+
setup do
9+
# Create tables
10+
SpanStorage.setup()
11+
12+
on_exit(fn ->
13+
# Only try to clean up tables if they exist
14+
if :ets.whereis(:sentry_root_spans) != :undefined do
15+
:ets.delete_all_objects(:sentry_root_spans)
16+
end
17+
18+
if :ets.whereis(:sentry_child_spans) != :undefined do
19+
:ets.delete_all_objects(:sentry_child_spans)
20+
end
21+
end)
22+
23+
:ok
24+
end
25+
626
defmodule TestEndpoint do
727
require OpenTelemetry.Tracer, as: Tracer
828

0 commit comments

Comments
 (0)