Skip to content

Commit 619c9b9

Browse files
authored
Fix faulty span.frame_delay calculation for early app start spans (#3427)
* Fix large frame delay values * Update changelog
1 parent 0a2d0b6 commit 619c9b9

File tree

3 files changed

+42
-34
lines changed

3 files changed

+42
-34
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
11
# Changelog
22

3+
4+
## Unreleased
5+
36
### Features
47

58
- Publish Gradle module metadata ([#3422](https://github.com/getsentry/sentry-java/pull/3422))
69

10+
### Fixes
11+
12+
- Fix faulty `span.frame_delay` calculation for early app start spans ([#3427](https://github.com/getsentry/sentry-java/pull/3427))
13+
714
## 7.9.0
815

916
### Features

sentry-android-core/src/main/java/io/sentry/android/core/SpanFrameMetricsCollector.java

Lines changed: 28 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,7 @@
77
import io.sentry.NoOpSpan;
88
import io.sentry.NoOpTransaction;
99
import io.sentry.SentryDate;
10-
import io.sentry.SentryLongDate;
1110
import io.sentry.SentryNanotimeDate;
12-
import io.sentry.SentryTracer;
1311
import io.sentry.SpanDataConvention;
1412
import io.sentry.android.core.internal.util.SentryFrameMetricsCollector;
1513
import io.sentry.protocol.MeasurementValue;
@@ -33,7 +31,7 @@ public class SpanFrameMetricsCollector
3331
// grow indefinitely in case of a long running span
3432
private static final int MAX_FRAMES_COUNT = 3600;
3533
private static final long ONE_SECOND_NANOS = TimeUnit.SECONDS.toNanos(1);
36-
private static final SentryNanotimeDate UNIX_START_DATE = new SentryNanotimeDate(new Date(0), 0);
34+
private static final SentryNanotimeDate EMPTY_NANO_TIME = new SentryNanotimeDate(new Date(0), 0);
3735

3836
private final boolean enabled;
3937
private final @NotNull Object lock = new Object();
@@ -125,7 +123,7 @@ public void onSpanFinished(final @NotNull ISpan span) {
125123
} else {
126124
// otherwise only remove old/irrelevant frames
127125
final @NotNull ISpan oldestSpan = runningSpans.first();
128-
frames.headSet(new Frame(realNanos(oldestSpan.getStartDate()))).clear();
126+
frames.headSet(new Frame(toNanoTime(oldestSpan.getStartDate()))).clear();
129127
}
130128
}
131129
}
@@ -138,22 +136,20 @@ private void captureFrameMetrics(@NotNull final ISpan span) {
138136
return;
139137
}
140138

141-
// Ignore spans with no finish date, but SentryTracer is not finished when executing this
142-
// callback, yet, so in that case we use the current timestamp.
143-
final @Nullable SentryDate spanFinishDate =
144-
span instanceof SentryTracer ? new SentryNanotimeDate() : span.getFinishDate();
139+
final @Nullable SentryDate spanFinishDate = span.getFinishDate();
145140
if (spanFinishDate == null) {
146141
return;
147142
}
148-
final long spanEndNanos = realNanos(spanFinishDate);
149143

150-
final @NotNull SentryFrameMetrics frameMetrics = new SentryFrameMetrics();
151-
final long spanStartNanos = realNanos(span.getStartDate());
152-
if (spanStartNanos >= spanEndNanos) {
144+
final long spanStartNanos = toNanoTime(span.getStartDate());
145+
final long spanEndNanos = toNanoTime(spanFinishDate);
146+
final long spanDurationNanos = spanEndNanos - spanStartNanos;
147+
if (spanDurationNanos <= 0) {
153148
return;
154149
}
155150

156-
final long spanDurationNanos = spanEndNanos - spanStartNanos;
151+
final @NotNull SentryFrameMetrics frameMetrics = new SentryFrameMetrics();
152+
157153
long frameDurationNanos = lastKnownFrameDurationNanos;
158154

159155
if (!frames.isEmpty()) {
@@ -199,11 +195,15 @@ private void captureFrameMetrics(@NotNull final ISpan span) {
199195
int totalFrameCount = frameMetrics.getTotalFrameCount();
200196

201197
final long nextScheduledFrameNanos = frameMetricsCollector.getLastKnownFrameStartTimeNanos();
202-
totalFrameCount +=
203-
addPendingFrameDelay(
204-
frameMetrics, frameDurationNanos, spanEndNanos, nextScheduledFrameNanos);
205-
totalFrameCount += interpolateFrameCount(frameMetrics, frameDurationNanos, spanDurationNanos);
206-
198+
// nextScheduledFrameNanos might be -1 if no frames have been scheduled for drawing yet
199+
// e.g. can happen during early app start
200+
if (nextScheduledFrameNanos != -1) {
201+
totalFrameCount +=
202+
addPendingFrameDelay(
203+
frameMetrics, frameDurationNanos, spanEndNanos, nextScheduledFrameNanos);
204+
totalFrameCount +=
205+
interpolateFrameCount(frameMetrics, frameDurationNanos, spanDurationNanos);
206+
}
207207
final long frameDelayNanos =
208208
frameMetrics.getSlowFrameDelayNanos() + frameMetrics.getFrozenFrameDelayNanos();
209209
final double frameDelayInSeconds = frameDelayNanos / 1e9d;
@@ -305,19 +305,20 @@ private static int addPendingFrameDelay(
305305
* diff does ¯\_(ツ)_/¯
306306
*
307307
* @param date the input date
308-
* @return a timestamp in nano precision
308+
* @return a non-unix timestamp in nano precision, similar to {@link System#nanoTime()}.
309309
*/
310-
private static long realNanos(final @NotNull SentryDate date) {
311-
// SentryNanotimeDate nanotime is based on System.nanotime(), like UNIX_START_DATE
310+
private static long toNanoTime(final @NotNull SentryDate date) {
311+
// SentryNanotimeDate nanotime is based on System.nanotime(), like EMPTY_NANO_TIME,
312+
// thus diff will simply return the System.nanotime() value of date
312313
if (date instanceof SentryNanotimeDate) {
313-
return date.diff(UNIX_START_DATE);
314+
return date.diff(EMPTY_NANO_TIME);
314315
}
315316

316-
// SentryLongDate nanotime is based on current date converted to nanoseconds, which is a
317-
// different order than frames based System.nanotime(). So we have to convert the nanotime of
318-
// the SentryLongDate to a System.nanotime() compatible one.
319-
return date.diff(new SentryLongDate(DateUtils.millisToNanos(System.currentTimeMillis())))
320-
+ System.nanoTime();
317+
// e.g. SentryLongDate is unix time based - upscaled to nanos,
318+
// we need to project it back to System.nanotime() format
319+
long nowUnixInNanos = DateUtils.millisToNanos(System.currentTimeMillis());
320+
long shiftInNanos = nowUnixInNanos - date.nanoTimestamp();
321+
return System.nanoTime() - shiftInNanos;
321322
}
322323

323324
private static class Frame implements Comparable<Frame> {

sentry/src/main/java/io/sentry/SentryTracer.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,13 @@ public void finish(
199199
this.finishStatus = FinishStatus.finishing(status);
200200
if (!root.isFinished()
201201
&& (!transactionOptions.isWaitForChildren() || hasAllChildrenFinished())) {
202+
203+
// any un-finished childs will remain unfinished
204+
// as relay takes care of setting the end-timestamp + deadline_exceeded
205+
// see
206+
// https://github.com/getsentry/relay/blob/40697d0a1c54e5e7ad8d183fc7f9543b94fe3839/relay-general/src/store/transactions/processor.rs#L374-L378
207+
root.finish(finishStatus.spanStatus, finishTimestamp);
208+
202209
List<PerformanceCollectionData> performanceCollectionData = null;
203210
if (transactionPerformanceCollector != null) {
204211
performanceCollectionData = transactionPerformanceCollector.stop(this);
@@ -215,13 +222,6 @@ public void finish(
215222
performanceCollectionData.clear();
216223
}
217224

218-
// any un-finished childs will remain unfinished
219-
// as relay takes care of setting the end-timestamp + deadline_exceeded
220-
// see
221-
// https://github.com/getsentry/relay/blob/40697d0a1c54e5e7ad8d183fc7f9543b94fe3839/relay-general/src/store/transactions/processor.rs#L374-L378
222-
223-
root.finish(finishStatus.spanStatus, finishTimestamp);
224-
225225
hub.configureScope(
226226
scope -> {
227227
scope.withTransaction(

0 commit comments

Comments
 (0)