@@ -2,64 +2,76 @@ defmodule Sentry.OpenTelemetry.SpanStorage do
22 @ moduledoc false
33 use GenServer
44
5+ require Logger
6+
57 @ table :span_storage
8+ @ cleanup_interval :timer . minutes ( 5 )
9+ @ span_ttl :timer . minutes ( 30 )
610
711 @ spec start_link ( keyword ( ) ) :: GenServer . on_start ( )
812 def start_link ( opts \\ [ ] ) do
913 name = Keyword . get ( opts , :name , __MODULE__ )
10- GenServer . start_link ( __MODULE__ , nil , name: name )
14+ GenServer . start_link ( __MODULE__ , opts , name: name )
1115 end
1216
1317 @ impl true
14- def init ( nil ) do
18+ def init ( opts ) do
1519 _table =
1620 if :ets . whereis ( @ table ) == :undefined do
1721 :ets . new ( @ table , [ :named_table , :public , :bag ] )
1822 end
1923
20- { :ok , :no_state }
24+ cleanup_interval = Keyword . get ( opts , :cleanup_interval , @ cleanup_interval )
25+ schedule_cleanup ( cleanup_interval )
26+
27+ { :ok , % { cleanup_interval: cleanup_interval } }
2128 end
2229
2330 def store_span ( span_data ) when span_data . parent_span_id == nil do
31+ stored_at = System . system_time ( :second )
32+
2433 case :ets . lookup ( @ table , { :root_span , span_data . span_id } ) do
25- [ ] -> :ets . insert ( @ table , { { :root_span , span_data . span_id } , span_data } )
34+ [ ] -> :ets . insert ( @ table , { { :root_span , span_data . span_id } , { span_data , stored_at } } )
2635 _ -> :ok
2736 end
2837 end
2938
3039 def store_span ( span_data ) do
31- _ = :ets . insert ( @ table , { span_data . parent_span_id , span_data } )
40+ stored_at = System . system_time ( :second )
41+ _ = :ets . insert ( @ table , { span_data . parent_span_id , { span_data , stored_at } } )
3242 end
3343
3444 def get_root_span ( span_id ) do
3545 case :ets . lookup ( @ table , { :root_span , span_id } ) do
36- [ { { :root_span , ^ span_id } , span } ] -> span
46+ [ { { :root_span , ^ span_id } , { span , _stored_at } } ] -> span
3747 [ ] -> nil
3848 end
3949 end
4050
4151 def get_child_spans ( parent_span_id ) do
4252 :ets . lookup ( @ table , parent_span_id )
43- |> Enum . map ( fn { _parent_id , span } -> span end )
53+ |> Enum . map ( fn { _parent_id , { span , _stored_at } } -> span end )
4454 end
4555
4656 def update_span ( span_data ) do
57+ stored_at = System . system_time ( :second )
58+
4759 if span_data . parent_span_id == nil do
4860 case :ets . lookup ( @ table , { :root_span , span_data . span_id } ) do
4961 [ ] ->
50- :ets . insert ( @ table , { { :root_span , span_data . span_id } , span_data } )
62+ :ets . insert ( @ table , { { :root_span , span_data . span_id } , { span_data , stored_at } } )
5163
5264 _ ->
5365 :ets . delete ( @ table , { :root_span , span_data . span_id } )
54- :ets . insert ( @ table , { { :root_span , span_data . span_id } , span_data } )
66+ :ets . insert ( @ table , { { :root_span , span_data . span_id } , { span_data , stored_at } } )
5567 end
5668 else
5769 existing_spans = :ets . lookup ( @ table , span_data . parent_span_id )
5870
59- Enum . each ( existing_spans , fn { parent_id , span } ->
71+ Enum . each ( existing_spans , fn { parent_id , { span , stored_at } } ->
6072 if span . span_id == span_data . span_id do
61- :ets . delete_object ( @ table , { parent_id , span } )
62- :ets . insert ( @ table , { span_data . parent_span_id , span_data } )
73+ :ets . delete_object ( @ table , { parent_id , { span , stored_at } } )
74+ :ets . insert ( @ table , { span_data . parent_span_id , { span_data , stored_at } } )
6375 end
6476 end )
6577 end
@@ -69,7 +81,9 @@ defmodule Sentry.OpenTelemetry.SpanStorage do
6981
7082 def remove_span ( span_id ) do
7183 case get_root_span ( span_id ) do
72- nil -> :ok
84+ nil ->
85+ :ok
86+
7387 _root_span ->
7488 :ets . delete ( @ table , { :root_span , span_id } )
7589 remove_child_spans ( span_id )
@@ -80,4 +94,44 @@ defmodule Sentry.OpenTelemetry.SpanStorage do
8094 :ets . delete ( @ table , parent_span_id )
8195 :ok
8296 end
97+
98+ @ impl true
99+ def handle_info ( :cleanup_stale_spans , state ) do
100+ cleanup_stale_spans ( )
101+ schedule_cleanup ( state . cleanup_interval )
102+ { :noreply , state }
103+ end
104+
105+ defp schedule_cleanup ( interval ) do
106+ Process . send_after ( self ( ) , :cleanup_stale_spans , interval )
107+ end
108+
109+ defp cleanup_stale_spans do
110+ now = System . system_time ( :second )
111+ cutoff_time = now - @ span_ttl
112+
113+ :ets . match_object ( @ table , { { :root_span , :_ } , { :_ , :_ } } )
114+ |> Enum . each ( fn { { :root_span , span_id } , { _span , stored_at } } ->
115+ if stored_at < cutoff_time do
116+ Logger . debug ( "Cleaning up stale root span: #{ span_id } " )
117+ remove_span ( span_id )
118+ end
119+ end )
120+
121+ :ets . match_object ( @ table , { :_ , { :_ , :_ } } )
122+ |> Enum . each ( fn { parent_id , { span , stored_at } } = object ->
123+ cond do
124+ get_root_span ( parent_id ) != nil and stored_at < cutoff_time ->
125+ Logger . debug ( "Cleaning up stale child span: #{ span . span_id } " )
126+ :ets . delete_object ( @ table , object )
127+
128+ get_root_span ( parent_id ) == nil and stored_at < cutoff_time ->
129+ Logger . debug ( "Cleaning up stale orphaned child span: #{ span . span_id } " )
130+ :ets . delete_object ( @ table , object )
131+
132+ true ->
133+ :ok
134+ end
135+ end )
136+ end
83137end
0 commit comments