Skip to content

Commit da7adac

Browse files
Add timing analytics for debugger page load (#3346)
1 parent e8af0f9 commit da7adac

File tree

4 files changed

+78
-5
lines changed

4 files changed

+78
-5
lines changed

packages/devtools_app/lib/src/analytics/_analytics_stub.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,14 @@ void screen(
2626
int value = 0,
2727
]) {}
2828

29+
void timeStart(String screenName, String timedOperation) {}
30+
31+
void timeEnd(
32+
String screenName,
33+
String timedOperation, {
34+
ScreenAnalyticsMetrics Function() screenMetricsProvider,
35+
}) {}
36+
2937
void timeSync(
3038
String screenName,
3139
String timedOperation, {

packages/devtools_app/lib/src/analytics/_analytics_web.dart

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,60 @@ void screen(
275275
);
276276
}
277277

278+
String _operationKey(String screenName, String timedOperation) {
279+
return '$screenName-$timedOperation';
280+
}
281+
282+
final _timedOperationsInProgress = <String, DateTime>{};
283+
284+
// Use this method coupled with `timeEnd` when an operation cannot be timed in
285+
// a callback, but rather needs to be timed instead at two disjoint start and
286+
// end marks.
287+
void timeStart(String screenName, String timedOperation) {
288+
final startTime = DateTime.now();
289+
final operationKey = _operationKey(
290+
screenName,
291+
timedOperation,
292+
);
293+
_timedOperationsInProgress[operationKey] = startTime;
294+
}
295+
296+
// Use this method coupled with `timeStart` when an operation cannot be timed in
297+
// a callback, but rather needs to be timed instead at two disjoint start and
298+
// end marks.
299+
void timeEnd(
300+
String screenName,
301+
String timedOperation, {
302+
ScreenAnalyticsMetrics Function() screenMetricsProvider,
303+
}) {
304+
final endTime = DateTime.now();
305+
final operationKey = _operationKey(
306+
screenName,
307+
timedOperation,
308+
);
309+
final startTime = _timedOperationsInProgress.remove(operationKey);
310+
assert(startTime != null);
311+
if (startTime == null) {
312+
log(
313+
'Could not time operation "$timedOperation" because a) `timeEnd` was '
314+
'called before `timeStart` or b) the `screenName` and `timedOperation`'
315+
'parameters for the `timeStart` and `timeEnd` calls do not match.',
316+
LogLevel.warning,
317+
);
318+
return;
319+
}
320+
final durationMicros =
321+
endTime.microsecondsSinceEpoch - startTime.microsecondsSinceEpoch;
322+
_timing(
323+
screenName,
324+
timedOperation,
325+
durationMicros: durationMicros,
326+
screenMetrics:
327+
screenMetricsProvider != null ? screenMetricsProvider() : null,
328+
);
329+
}
330+
331+
// Use this when a synchronous operation can be timed in a callback.
278332
void timeSync(
279333
String screenName,
280334
String timedOperation, {
@@ -305,6 +359,7 @@ void timeSync(
305359
);
306360
}
307361

362+
// Use this when an asynchronous operation can be timed in a callback.
308363
Future<void> timeAsync(
309364
String screenName,
310365
String timedOperation, {

packages/devtools_app/lib/src/analytics/constants.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,3 +105,6 @@ const String export = 'export';
105105
const String expandAll = 'expandAll';
106106
const String collapseAll = 'collapseAll';
107107
const String documentationLink = 'documentationLink';
108+
// This should track the time from `initState` for a screen to the time when
109+
// the page data has loaded and is ready to interact with.
110+
const String pageReady = 'pageReady';

packages/devtools_app/lib/src/debugger/debugger_screen.dart

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import 'package:provider/provider.dart';
99
import 'package:vm_service/vm_service.dart';
1010

1111
import '../analytics/analytics.dart' as ga;
12+
import '../analytics/constants.dart' as analytics_constants;
1213
import '../auto_dispose_mixin.dart';
1314
import '../common_widgets.dart';
1415
import '../dialogs.dart';
@@ -80,10 +81,14 @@ class DebuggerScreenBodyState extends State<DebuggerScreenBody>
8081

8182
DebuggerController controller;
8283

84+
bool _shownFirstScript;
85+
8386
@override
8487
void initState() {
8588
super.initState();
8689
ga.screen(DebuggerScreen.id);
90+
ga.timeStart(DebuggerScreen.id, analytics_constants.pageReady);
91+
_shownFirstScript = false;
8792
}
8893

8994
@override
@@ -95,11 +100,6 @@ class DebuggerScreenBodyState extends State<DebuggerScreenBody>
95100
controller = newController;
96101
}
97102

98-
@override
99-
void dispose() {
100-
super.dispose();
101-
}
102-
103103
void _onLocationSelected(ScriptLocation location) {
104104
if (location != null) {
105105
controller.showScriptLocation(location);
@@ -114,6 +114,13 @@ class DebuggerScreenBodyState extends State<DebuggerScreenBody>
114114
return ValueListenableBuilder(
115115
valueListenable: controller.currentParsedScript,
116116
builder: (context, parsedScript, _) {
117+
if (scriptRef != null &&
118+
parsedScript != null &&
119+
!_shownFirstScript) {
120+
ga.timeEnd(DebuggerScreen.id, analytics_constants.pageReady);
121+
// TODO(annagrin): mark end of IPL timing for debugger page here.
122+
_shownFirstScript = true;
123+
}
117124
return CodeView(
118125
key: DebuggerScreenBody.codeViewKey,
119126
controller: controller,

0 commit comments

Comments
 (0)