Skip to content

Commit bb17c57

Browse files
buenaflorcodex
andauthored
fix(flutter): Send frame delay in seconds (#3677)
* fix(flutter): Send frame delay in seconds Convert accumulated frame delay from milliseconds to seconds before writing frames.delay and frames_delay. This matches the backend convention and the Android SDK behavior. Co-Authored-By: OpenAI Codex <noreply@openai.com> * fix(flutter): Set frame delay measurement unit Mark frames_delay measurements as seconds when using the public measurement constructor and when applying Flutter frame metrics to root spans. Co-Authored-By: OpenAI Codex <noreply@openai.com> --------- Co-authored-by: OpenAI Codex <noreply@openai.com>
1 parent 2269f05 commit bb17c57

7 files changed

Lines changed: 54 additions & 36 deletions

File tree

packages/dart/lib/src/sentry_measurement.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,10 @@ class SentryMeasurement {
3030
: name = frozenFramesName,
3131
unit = SentryMeasurementUnit.none;
3232

33-
/// Total duration of frames delayed.
33+
/// Total duration of frames delayed in seconds.
3434
SentryMeasurement.framesDelay(this.value)
3535
: name = framesDelayName,
36-
unit = SentryMeasurementUnit.none;
36+
unit = DurationSentryMeasurementUnit.second;
3737

3838
/// Duration of the Cold App start in milliseconds
3939
SentryMeasurement.coldAppStart(Duration duration)

packages/dart/test/sentry_measurement_test.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ void main() {
1818
SentryMeasurement.frozenFrames(10).unit, SentryMeasurementUnit.none);
1919
});
2020

21+
test('frames delay has seconds unit', () {
22+
expect(SentryMeasurement.framesDelay(1.5).unit,
23+
DurationSentryMeasurementUnit.second);
24+
});
25+
2126
test('warm start has milliseconds unit', () {
2227
expect(SentryMeasurement.warmAppStart(Duration(seconds: 1)).unit,
2328
DurationSentryMeasurementUnit.milliSecond);

packages/flutter/lib/src/frames_tracking/sentry_delayed_frames_tracker.dart

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ class SentryDelayedFramesTracker {
129129
(spanDuration / _expectedFrameDuration.inMilliseconds).ceil(),
130130
slowFrameCount: 0,
131131
frozenFrameCount: 0,
132-
framesDelay: 0);
132+
framesDelay: 0.0);
133133
}
134134

135135
final spanStartMs = spanStartTimestamp.millisecondsSinceEpoch;
@@ -141,7 +141,7 @@ class SentryDelayedFramesTracker {
141141
int frozenFrameCount = 0;
142142
int slowFramesDuration = 0;
143143
int frozenFramesDuration = 0;
144-
int framesDelay = 0;
144+
int framesDelayMs = 0;
145145

146146
for (final timing in relevantFrames) {
147147
final frameStartMs = timing.startTimestamp.millisecondsSinceEpoch;
@@ -188,7 +188,7 @@ class SentryDelayedFramesTracker {
188188
slowFramesDuration += effectiveDuration;
189189
}
190190

191-
framesDelay += effectiveDelay;
191+
framesDelayMs += effectiveDelay;
192192
}
193193

194194
final normalFramesCount =
@@ -200,7 +200,7 @@ class SentryDelayedFramesTracker {
200200
if (totalFrameCount < 0 ||
201201
slowFrameCount < 0 ||
202202
frozenFrameCount < 0 ||
203-
framesDelay < 0) {
203+
framesDelayMs < 0) {
204204
return null;
205205
}
206206

@@ -213,7 +213,7 @@ class SentryDelayedFramesTracker {
213213
totalFrameCount: totalFrameCount,
214214
slowFrameCount: slowFrameCount,
215215
frozenFrameCount: frozenFrameCount,
216-
framesDelay: framesDelay);
216+
framesDelay: framesDelayMs / Duration.millisecondsPerSecond);
217217
}
218218

219219
/// Clears the state of the tracker.
@@ -242,7 +242,9 @@ class SpanFrameMetrics {
242242
final int totalFrameCount;
243243
final int slowFrameCount;
244244
final int frozenFrameCount;
245-
final int framesDelay;
245+
246+
/// Total delayed frame duration in seconds.
247+
final double framesDelay;
246248

247249
SpanFrameMetrics({
248250
required this.totalFrameCount,
@@ -261,7 +263,11 @@ class SpanFrameMetrics {
261263
span.setMeasurement(SentryMeasurement.totalFramesName, totalFrameCount);
262264
span.setMeasurement(SentryMeasurement.slowFramesName, slowFrameCount);
263265
span.setMeasurement(SentryMeasurement.frozenFramesName, frozenFrameCount);
264-
span.setMeasurement(SentryMeasurement.framesDelayName, framesDelay);
266+
span.setMeasurement(
267+
SentryMeasurement.framesDelayName,
268+
framesDelay,
269+
unit: DurationSentryMeasurementUnit.second,
270+
);
265271
} else {
266272
_setData(span);
267273
}

packages/flutter/lib/src/frames_tracking/span_frame_metrics_collector.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ extension _InstrumentationSpanFrameMetrics on InstrumentationSpan {
105105
attributes[SemanticAttributesConstants.framesFrozen] =
106106
SentryAttribute.int(metrics.frozenFrameCount);
107107
attributes[SemanticAttributesConstants.framesDelay] =
108-
SentryAttribute.int(metrics.framesDelay);
108+
SentryAttribute.double(metrics.framesDelay);
109109
spanRef.setAttributesIfAbsent(attributes);
110110
}
111111
} else {

packages/flutter/test/frame_tracking/sentry_delayed_frames_tracker_test.dart

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ void main() {
109109
expect(metrics!.totalFrameCount, 63); // 1000ms / 16ms ≈ 63 frames
110110
expect(metrics.slowFrameCount, 0);
111111
expect(metrics.frozenFrameCount, 0);
112-
expect(metrics.framesDelay, 0);
112+
expect(metrics.framesDelay, 0.0);
113113
});
114114

115115
test('calculates metrics for frames fully contained within span', () {
@@ -132,7 +132,7 @@ void main() {
132132
expect(metrics!.totalFrameCount, 63);
133133
expect(metrics.slowFrameCount, 1);
134134
expect(metrics.frozenFrameCount, 0);
135-
expect(metrics.framesDelay, 4); // 20ms - 16ms = 4ms delay
135+
expect(metrics.framesDelay, 0.004); // 20ms - 16ms = 4ms delay
136136
});
137137

138138
test('calculates metrics for frames partially contained within span', () {
@@ -156,7 +156,7 @@ void main() {
156156
expect(metrics!.totalFrameCount, 24); // ~500ms / 16ms = 31 frames
157157
expect(metrics.slowFrameCount, 2);
158158
expect(metrics.frozenFrameCount, 0);
159-
expect(metrics.framesDelay, 134);
159+
expect(metrics.framesDelay, 0.134);
160160
});
161161

162162
test('calculates metrics for frozen frames', () {
@@ -175,7 +175,7 @@ void main() {
175175
expect(metrics, isNotNull);
176176
expect(metrics!.frozenFrameCount, 1);
177177
expect(metrics.slowFrameCount, 0);
178-
expect(metrics.framesDelay, 784); // 800ms - 16ms = 784ms delay
178+
expect(metrics.framesDelay, 0.784); // 800ms - 16ms = 784ms delay
179179
});
180180

181181
test('removeIrrelevantFrames removes the correct frames', () {

packages/flutter/test/frame_tracking/span_frame_metrics_collector_test.dart

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ void main() {
4545
totalFrameCount: 10,
4646
slowFrameCount: 2,
4747
frozenFrameCount: 1,
48-
framesDelay: 500,
48+
framesDelay: 0.5,
4949
);
5050
when(fixture.mockFrameTracker.getFrameMetrics(
5151
spanStartTimestamp: startTime,
@@ -61,12 +61,14 @@ void main() {
6161
expect(tracer.data[SpanDataConvention.totalFrames], 10);
6262
expect(tracer.data[SpanDataConvention.slowFrames], 2);
6363
expect(tracer.data[SpanDataConvention.frozenFrames], 1);
64-
expect(tracer.data[SpanDataConvention.framesDelay], 500);
64+
expect(tracer.data[SpanDataConvention.framesDelay], 0.5);
6565
expect(tracer.measurements[SentryMeasurement.totalFramesName]?.value, 10);
6666
expect(tracer.measurements[SentryMeasurement.slowFramesName]?.value, 2);
6767
expect(tracer.measurements[SentryMeasurement.frozenFramesName]?.value, 1);
6868
expect(
69-
tracer.measurements[SentryMeasurement.framesDelayName]?.value, 500);
69+
tracer.measurements[SentryMeasurement.framesDelayName]?.value, 0.5);
70+
expect(tracer.measurements[SentryMeasurement.framesDelayName]?.unit,
71+
DurationSentryMeasurementUnit.second);
7072
expect(sut.activeSpans, isEmpty);
7173
});
7274

@@ -94,7 +96,7 @@ void main() {
9496
totalFrameCount: 10,
9597
slowFrameCount: 2,
9698
frozenFrameCount: 1,
97-
framesDelay: 500,
99+
framesDelay: 0.5,
98100
);
99101
when(fixture.mockFrameTracker.getFrameMetrics(
100102
spanStartTimestamp: startTimeForSpan1,
@@ -105,7 +107,7 @@ void main() {
105107
totalFrameCount: 5,
106108
slowFrameCount: 1,
107109
frozenFrameCount: 3,
108-
framesDelay: 400,
110+
framesDelay: 0.4,
109111
);
110112
when(fixture.mockFrameTracker.getFrameMetrics(
111113
spanStartTimestamp: startTimeForSpan2,
@@ -122,17 +124,19 @@ void main() {
122124
expect(tracer.data[SpanDataConvention.totalFrames], 10);
123125
expect(tracer.data[SpanDataConvention.slowFrames], 2);
124126
expect(tracer.data[SpanDataConvention.frozenFrames], 1);
125-
expect(tracer.data[SpanDataConvention.framesDelay], 500);
127+
expect(tracer.data[SpanDataConvention.framesDelay], 0.5);
126128
expect(tracer.measurements[SentryMeasurement.totalFramesName]?.value, 10);
127129
expect(tracer.measurements[SentryMeasurement.slowFramesName]?.value, 2);
128130
expect(tracer.measurements[SentryMeasurement.frozenFramesName]?.value, 1);
129131
expect(
130-
tracer.measurements[SentryMeasurement.framesDelayName]?.value, 500);
132+
tracer.measurements[SentryMeasurement.framesDelayName]?.value, 0.5);
133+
expect(tracer.measurements[SentryMeasurement.framesDelayName]?.unit,
134+
DurationSentryMeasurementUnit.second);
131135

132136
expect(span2.data[SpanDataConvention.totalFrames], 5);
133137
expect(span2.data[SpanDataConvention.slowFrames], 1);
134138
expect(span2.data[SpanDataConvention.frozenFrames], 3);
135-
expect(span2.data[SpanDataConvention.framesDelay], 400);
139+
expect(span2.data[SpanDataConvention.framesDelay], 0.4);
136140

137141
expect(sut.activeSpans, isEmpty);
138142
});
@@ -168,7 +172,7 @@ void main() {
168172
totalFrameCount: 15,
169173
slowFrameCount: 3,
170174
frozenFrameCount: 2,
171-
framesDelay: 600,
175+
framesDelay: 0.6,
172176
);
173177
when(fixture.mockFrameTracker.getFrameMetrics(
174178
spanStartTimestamp: startTime,
@@ -187,7 +191,7 @@ void main() {
187191
expect(
188192
span.attributes[SemanticAttributesConstants.framesFrozen]?.value, 2);
189193
expect(
190-
span.attributes[SemanticAttributesConstants.framesDelay]?.value, 600);
194+
span.attributes[SemanticAttributesConstants.framesDelay]?.value, 0.6);
191195
expect(sut.activeSpans, isEmpty);
192196
});
193197

@@ -222,7 +226,7 @@ void main() {
222226
totalFrameCount: 10,
223227
slowFrameCount: 1,
224228
frozenFrameCount: 0,
225-
framesDelay: 100,
229+
framesDelay: 0.1,
226230
);
227231
when(fixture.mockFrameTracker.getFrameMetrics(
228232
spanStartTimestamp: startTime1,
@@ -233,7 +237,7 @@ void main() {
233237
totalFrameCount: 20,
234238
slowFrameCount: 4,
235239
frozenFrameCount: 2,
236-
framesDelay: 800,
240+
framesDelay: 0.8,
237241
);
238242
when(fixture.mockFrameTracker.getFrameMetrics(
239243
spanStartTimestamp: startTime2,
@@ -254,7 +258,7 @@ void main() {
254258
expect(
255259
span1.attributes[SemanticAttributesConstants.framesFrozen]?.value, 0);
256260
expect(span1.attributes[SemanticAttributesConstants.framesDelay]?.value,
257-
100);
261+
0.1);
258262

259263
expect(
260264
span2.attributes[SemanticAttributesConstants.framesTotal]?.value, 20);
@@ -263,7 +267,7 @@ void main() {
263267
expect(
264268
span2.attributes[SemanticAttributesConstants.framesFrozen]?.value, 2);
265269
expect(span2.attributes[SemanticAttributesConstants.framesDelay]?.value,
266-
800);
270+
0.8);
267271

268272
expect(sut.activeSpans, isEmpty);
269273
});
@@ -348,7 +352,7 @@ void main() {
348352
totalFrameCount: 10,
349353
slowFrameCount: 2,
350354
frozenFrameCount: 1,
351-
framesDelay: 500,
355+
framesDelay: 0.5,
352356
);
353357
when(fixture.mockFrameTracker.getFrameMetrics(
354358
spanStartTimestamp: startTime,
@@ -384,7 +388,7 @@ void main() {
384388
totalFrameCount: 15,
385389
slowFrameCount: 3,
386390
frozenFrameCount: 2,
387-
framesDelay: 600,
391+
framesDelay: 0.6,
388392
);
389393
when(fixture.mockFrameTracker.getFrameMetrics(
390394
spanStartTimestamp: startTime,
@@ -412,7 +416,7 @@ void main() {
412416
totalFrameCount: 10,
413417
slowFrameCount: 2,
414418
frozenFrameCount: 1,
415-
framesDelay: 500,
419+
framesDelay: 0.5,
416420
);
417421
when(fixture.mockFrameTracker.getFrameMetrics(
418422
spanStartTimestamp: startTime,

packages/flutter/test/frame_tracking/span_frame_metrics_test.dart

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ void main() {
2222
verify(span.setData(SpanDataConvention.totalFrames, 10)).called(1);
2323
verify(span.setData(SpanDataConvention.slowFrames, 2)).called(1);
2424
verify(span.setData(SpanDataConvention.frozenFrames, 1)).called(1);
25-
verify(span.setData(SpanDataConvention.framesDelay, 30)).called(1);
25+
verify(span.setData(SpanDataConvention.framesDelay, 0.03)).called(1);
2626
});
2727

2828
test('applyTo sets data and measurements on root spans', () {
@@ -36,15 +36,18 @@ void main() {
3636
verify(tracer.setData(SpanDataConvention.totalFrames, 10)).called(1);
3737
verify(tracer.setData(SpanDataConvention.slowFrames, 2)).called(1);
3838
verify(tracer.setData(SpanDataConvention.frozenFrames, 1)).called(1);
39-
verify(tracer.setData(SpanDataConvention.framesDelay, 30)).called(1);
39+
verify(tracer.setData(SpanDataConvention.framesDelay, 0.03)).called(1);
4040

4141
verify(span.setMeasurement(SentryMeasurement.totalFramesName, 10))
4242
.called(1);
4343
verify(span.setMeasurement(SentryMeasurement.slowFramesName, 2)).called(1);
4444
verify(span.setMeasurement(SentryMeasurement.frozenFramesName, 1))
4545
.called(1);
46-
verify(span.setMeasurement(SentryMeasurement.framesDelayName, 30))
47-
.called(1);
46+
verify(span.setMeasurement(
47+
SentryMeasurement.framesDelayName,
48+
0.03,
49+
unit: DurationSentryMeasurementUnit.second,
50+
)).called(1);
4851
});
4952
}
5053

@@ -54,7 +57,7 @@ class _Fixture {
5457
totalFrameCount: 10,
5558
slowFrameCount: 2,
5659
frozenFrameCount: 1,
57-
framesDelay: 30,
60+
framesDelay: 0.03,
5861
);
5962
}
6063
}

0 commit comments

Comments
 (0)