Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions lib/sentry/application.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,6 @@ defmodule Sentry.Application do
config = Config.validate!()
:ok = Config.persist(config)

# Setup ETS tables for span storage
Sentry.Opentelemetry.SpanStorage.setup()

http_client = Keyword.fetch!(config, :client)

maybe_http_client_spec =
Expand All @@ -30,6 +27,7 @@ defmodule Sentry.Application do
Sentry.Sources,
Sentry.Dedupe,
Sentry.ClientReport.Sender,
Sentry.Opentelemetry.SpanStorage,
{Sentry.Integrations.CheckInIDMappings,
[
max_expected_check_in_time:
Expand Down
75 changes: 39 additions & 36 deletions lib/sentry/opentelemetry/span_storage.ex
Original file line number Diff line number Diff line change
@@ -1,78 +1,81 @@
defmodule Sentry.Opentelemetry.SpanStorage do
@moduledoc false
use GenServer

@root_spans_table :sentry_root_spans
@child_spans_table :sentry_child_spans
@table :span_storage
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Scope this to Sentry, as the ETS namespace is global. My suggestion is

Suggested change
@table :span_storage
@table __MODULE__

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In one of our pairing sessions for check_in_id_mappings.ex, we discussed this being bad practice to have both the GenServer and the table name the same. Thoughts?


def setup do
case :ets.whereis(@root_spans_table) do
:undefined ->
:ets.new(@root_spans_table, [:set, :public, :named_table])
@spec start_link(keyword()) :: GenServer.on_start()
def start_link(opts \\ []) do
name = Keyword.get(opts, :name, __MODULE__)
GenServer.start_link(__MODULE__, nil, name: name)
end

_ ->
:ok
end
@impl true
def init(nil) do
_table =
if :ets.whereis(@table) == :undefined do
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the table is tied to this GenServer, where would we ever have the table running without it that we need this check?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When testing, an error is thrown because the table is already created. We do this in check_in_id_mappings.ex as well.

:ets.new(@table, [:named_table, :public, :bag])
end

case :ets.whereis(@child_spans_table) do
:undefined ->
:ets.new(@child_spans_table, [:bag, :public, :named_table])
{:ok, :no_state}
end

_ ->
:ok
def store_span(span_data) when span_data.parent_span_id == nil do
case :ets.lookup(@table, {:root_span, span_data.span_id}) do
[] -> :ets.insert(@table, {{:root_span, span_data.span_id}, span_data})
_ -> :ok
end

:ok
end

def store_span(span_data) do
if span_data.parent_span_id == nil do
:ets.insert(@root_spans_table, {span_data.span_id, span_data})
else
:ets.insert(@child_spans_table, {span_data.parent_span_id, span_data})
end

:ok
_ = :ets.insert(@table, {span_data.parent_span_id, span_data})
end

def get_root_span(span_id) do
case :ets.lookup(@root_spans_table, span_id) do
[{^span_id, span}] -> span
case :ets.lookup(@table, {:root_span, span_id}) do
[{{:root_span, ^span_id}, span}] -> span
[] -> nil
end
end

def get_child_spans(parent_span_id) do
:ets.lookup(@child_spans_table, parent_span_id)
:ets.lookup(@table, parent_span_id)
|> Enum.map(fn {_parent_id, span} -> span end)
end

def update_span(span_data) do
if span_data.parent_span_id == nil do
:ets.insert(@root_spans_table, {span_data.span_id, span_data})
case :ets.lookup(@table, {:root_span, span_data.parent_span_id}) do
[] ->
:ets.insert(@table, {{:root_span, span_data.parent_span_id}, span_data})

_ ->
:ets.delete(@table, {:root_span, span_data.parent_span_id})
:ets.insert(@table, {{:root_span, span_data.parent_span_id}, span_data})
end
else
existing_spans = :ets.lookup(@child_spans_table, span_data.parent_span_id)

:ets.delete(@child_spans_table, span_data.parent_span_id)
existing_spans = :ets.lookup(@table, span_data.parent_span_id)

Enum.each(existing_spans, fn {parent_id, span} ->
if span.span_id != span_data.span_id do
:ets.insert(@child_spans_table, {parent_id, span})
if span.span_id == span_data.span_id do
:ets.delete_object(@table, {parent_id, span})
:ets.insert(@table, {span_data.parent_span_id, span_data})
end
end)

:ets.insert(@child_spans_table, {span_data.parent_span_id, span_data})
end

:ok
end

def remove_span(span_id) do
:ets.delete(@root_spans_table, span_id)
:ets.delete(@table, {:root_span, span_id})
:ok
end

def remove_child_spans(parent_span_id) do
:ets.delete(@child_spans_table, parent_span_id)
:ets.delete(@table, parent_span_id)
:ok
end

# need to ad in sweep for any leftover spans. Should we initiate a sweep on_end or add a ttl or both?
end
12 changes: 2 additions & 10 deletions test/sentry/opentelemetry/span_processor_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,13 @@

import Sentry.TestHelpers

alias Sentry.Opentelemetry.SpanStorage

Check warning on line 6 in test/sentry/opentelemetry/span_processor_test.exs

View workflow job for this annotation

GitHub Actions / Test (Elixir 1.13.4-otp-22, OTP 22.3.4)

unused alias SpanStorage

setup do
# Create tables
SpanStorage.setup()

on_exit(fn ->
# Only try to clean up tables if they exist
if :ets.whereis(:sentry_root_spans) != :undefined do
:ets.delete_all_objects(:sentry_root_spans)
end

if :ets.whereis(:sentry_child_spans) != :undefined do
:ets.delete_all_objects(:sentry_child_spans)
if :ets.whereis(:span_storage) != :undefined do
:ets.delete_all_objects(:span_storage)
end
end)

Expand All @@ -42,7 +35,7 @@
end
end

test "sends captured root spans as transactions" do

Check failure on line 38 in test/sentry/opentelemetry/span_processor_test.exs

View workflow job for this annotation

GitHub Actions / Test (Elixir 1.17, OTP 27.1)

test sends captured root spans as transactions (Sentry.Opentelemetry.SpanProcessorTest)

Check failure on line 38 in test/sentry/opentelemetry/span_processor_test.exs

View workflow job for this annotation

GitHub Actions / Test (Elixir 1.16, OTP 26.2)

test sends captured root spans as transactions (Sentry.Opentelemetry.SpanProcessorTest)

Check failure on line 38 in test/sentry/opentelemetry/span_processor_test.exs

View workflow job for this annotation

GitHub Actions / Test (Elixir 1.13.4-otp-22, OTP 22.3.4)

test sends captured root spans as transactions (Sentry.Opentelemetry.SpanProcessorTest)
put_test_config(environment_name: "test")

Sentry.Test.start_collecting_sentry_reports()
Expand All @@ -63,7 +56,7 @@
assert span.op == "child_instrumented_function_one"
end

test "sends captured spans as transactions with child spans" do

Check failure on line 59 in test/sentry/opentelemetry/span_processor_test.exs

View workflow job for this annotation

GitHub Actions / Test (Elixir 1.17, OTP 27.1)

test sends captured spans as transactions with child spans (Sentry.Opentelemetry.SpanProcessorTest)

Check failure on line 59 in test/sentry/opentelemetry/span_processor_test.exs

View workflow job for this annotation

GitHub Actions / Test (Elixir 1.16, OTP 26.2)

test sends captured spans as transactions with child spans (Sentry.Opentelemetry.SpanProcessorTest)

Check failure on line 59 in test/sentry/opentelemetry/span_processor_test.exs

View workflow job for this annotation

GitHub Actions / Test (Elixir 1.13.4-otp-22, OTP 22.3.4)

test sends captured spans as transactions with child spans (Sentry.Opentelemetry.SpanProcessorTest)
put_test_config(environment_name: "test")

Sentry.Test.start_collecting_sentry_reports()
Expand All @@ -71,7 +64,6 @@
TestEndpoint.instrumented_function()

assert [%Sentry.Transaction{} = transaction] = Sentry.Test.pop_sentry_transactions()

assert_valid_iso8601(transaction.timestamp)
assert_valid_iso8601(transaction.start_timestamp)
assert transaction.timestamp > transaction.start_timestamp
Expand Down
Loading