88import java .util .Timer ;
99import java .util .TimerTask ;
1010import java .util .concurrent .ConcurrentHashMap ;
11- import java .util .concurrent .RejectedExecutionException ;
11+ import java .util .concurrent .TimeUnit ;
1212import java .util .concurrent .atomic .AtomicBoolean ;
1313import org .jetbrains .annotations .ApiStatus ;
1414import org .jetbrains .annotations .NotNull ;
@@ -20,8 +20,7 @@ public final class DefaultCompositePerformanceCollector implements CompositePerf
2020 private static final long TRANSACTION_COLLECTION_TIMEOUT_MILLIS = 30000 ;
2121 private final @ NotNull AutoClosableReentrantLock timerLock = new AutoClosableReentrantLock ();
2222 private volatile @ Nullable Timer timer = null ;
23- private final @ NotNull Map <String , List <PerformanceCollectionData >> performanceDataMap =
24- new ConcurrentHashMap <>();
23+ private final @ NotNull Map <String , CompositeData > compositeDataMap = new ConcurrentHashMap <>();
2524 private final @ NotNull List <IPerformanceSnapshotCollector > snapshotCollectors ;
2625 private final @ NotNull List <IPerformanceContinuousCollector > continuousCollectors ;
2726 private final boolean hasNoCollectors ;
@@ -65,23 +64,11 @@ public void start(final @NotNull ITransaction transaction) {
6564 collector .onSpanStarted (transaction );
6665 }
6766
68- if (!performanceDataMap .containsKey (transaction .getEventId ().toString ())) {
69- performanceDataMap .put (transaction .getEventId ().toString (), new ArrayList <>());
70- // We schedule deletion of collected performance data after a timeout
71- try {
72- options
73- .getExecutorService ()
74- .schedule (() -> stop (transaction ), TRANSACTION_COLLECTION_TIMEOUT_MILLIS );
75- } catch (RejectedExecutionException e ) {
76- options
77- .getLogger ()
78- .log (
79- SentryLevel .ERROR ,
80- "Failed to call the executor. Performance collector will not be automatically finished. Did you call Sentry.close()?" ,
81- e );
82- }
67+ final @ NotNull String id = transaction .getEventId ().toString ();
68+ if (!compositeDataMap .containsKey (id )) {
69+ compositeDataMap .put (id , new CompositeData (transaction ));
8370 }
84- start (transaction . getEventId (). toString () );
71+ start (id );
8572 }
8673
8774 @ Override
@@ -95,8 +82,10 @@ public void start(final @NotNull String id) {
9582 return ;
9683 }
9784
98- if (!performanceDataMap .containsKey (id )) {
99- performanceDataMap .put (id , new ArrayList <>());
85+ if (!compositeDataMap .containsKey (id )) {
86+ // Transactions are added in start(ITransaction). If we are here, it means we don't come from
87+ // a transaction
88+ compositeDataMap .put (id , new CompositeData (null ));
10089 }
10190 if (!isStarted .getAndSet (true )) {
10291 try (final @ NotNull ISentryLifecycleToken ignored = timerLock .acquire ()) {
@@ -118,6 +107,7 @@ public void run() {
118107 // and collect() calls.
119108 // This way ICollectors that collect average stats based on time intervals, like
120109 // AndroidCpuCollector, can have an actual time interval to evaluate.
110+ final @ NotNull List <ITransaction > timedOutTransactions = new ArrayList <>();
121111 TimerTask timerTask =
122112 new TimerTask () {
123113 @ Override
@@ -129,16 +119,31 @@ public void run() {
129119 if (now - lastCollectionTimestamp <= 10 ) {
130120 return ;
131121 }
122+ timedOutTransactions .clear ();
123+
132124 lastCollectionTimestamp = now ;
133125 final @ NotNull PerformanceCollectionData tempData =
134- new PerformanceCollectionData (new SentryNanotimeDate ().nanoTimestamp ());
126+ new PerformanceCollectionData (options . getDateProvider (). now ().nanoTimestamp ());
135127
128+ // Enrich tempData using collectors
136129 for (IPerformanceSnapshotCollector collector : snapshotCollectors ) {
137130 collector .collect (tempData );
138131 }
139132
140- for (List <PerformanceCollectionData > data : performanceDataMap .values ()) {
141- data .add (tempData );
133+ // Add the enriched tempData to all transactions/profiles/objects that collect data.
134+ // Then Check if that object timed out.
135+ for (CompositeData data : compositeDataMap .values ()) {
136+ if (data .addDataAndCheckTimeout (tempData )) {
137+ // timed out
138+ if (data .transaction != null ) {
139+ timedOutTransactions .add (data .transaction );
140+ }
141+ }
142+ }
143+ // Stop timed out transactions outside compositeDataMap loop, as stop() modifies the
144+ // map
145+ for (final @ NotNull ITransaction t : timedOutTransactions ) {
146+ stop (t );
142147 }
143148 }
144149 };
@@ -183,13 +188,14 @@ public void onSpanFinished(@NotNull ISpan span) {
183188
184189 @ Override
185190 public @ Nullable List <PerformanceCollectionData > stop (final @ NotNull String id ) {
186- final @ Nullable List <PerformanceCollectionData > data = performanceDataMap .remove (id );
191+ final @ Nullable CompositeData data = compositeDataMap .remove (id );
192+ options .getLogger ().log (SentryLevel .DEBUG , "stop collecting performance info for " + id );
187193
188- // close if they are no more running requests
189- if (performanceDataMap .isEmpty ()) {
194+ // close if there are no more running requests
195+ if (compositeDataMap .isEmpty ()) {
190196 close ();
191197 }
192- return data ;
198+ return data != null ? data . dataList : null ;
193199 }
194200
195201 @ Override
@@ -198,7 +204,7 @@ public void close() {
198204 .getLogger ()
199205 .log (SentryLevel .DEBUG , "stop collecting all performance info for transactions" );
200206
201- performanceDataMap .clear ();
207+ compositeDataMap .clear ();
202208 for (final @ NotNull IPerformanceContinuousCollector collector : continuousCollectors ) {
203209 collector .clear ();
204210 }
@@ -211,4 +217,30 @@ public void close() {
211217 }
212218 }
213219 }
220+
221+ private class CompositeData {
222+ private final @ NotNull List <PerformanceCollectionData > dataList ;
223+ private final @ Nullable ITransaction transaction ;
224+ private final long startTimestamp ;
225+
226+ private CompositeData (final @ Nullable ITransaction transaction ) {
227+ this .dataList = new ArrayList <>();
228+ this .transaction = transaction ;
229+ this .startTimestamp = options .getDateProvider ().now ().nanoTimestamp ();
230+ }
231+
232+ /**
233+ * Adds the data to the internal list of PerformanceCollectionData. Then it checks if data
234+ * collection timed out (for transactions only).
235+ *
236+ * @return true if data collection timed out (for transactions only).
237+ */
238+ boolean addDataAndCheckTimeout (final @ NotNull PerformanceCollectionData data ) {
239+ dataList .add (data );
240+ return transaction != null
241+ && options .getDateProvider ().now ().nanoTimestamp ()
242+ > startTimestamp
243+ + TimeUnit .MILLISECONDS .toNanos (TRANSACTION_COLLECTION_TIMEOUT_MILLIS );
244+ }
245+ }
214246}
0 commit comments