diff --git a/packages/devtools_app/lib/src/analytics/_analytics_stub.dart b/packages/devtools_app/lib/src/analytics/_analytics_stub.dart index 6237258de2f..1b66ef962c9 100644 --- a/packages/devtools_app/lib/src/analytics/_analytics_stub.dart +++ b/packages/devtools_app/lib/src/analytics/_analytics_stub.dart @@ -26,6 +26,14 @@ void screen( int value = 0, ]) {} +void timeStart(String screenName, String timedOperation) {} + +void timeEnd( + String screenName, + String timedOperation, { + ScreenAnalyticsMetrics Function() screenMetricsProvider, +}) {} + void timeSync( String screenName, String timedOperation, { diff --git a/packages/devtools_app/lib/src/analytics/_analytics_web.dart b/packages/devtools_app/lib/src/analytics/_analytics_web.dart index 6d6eb221ad3..c9e0ace7d8a 100644 --- a/packages/devtools_app/lib/src/analytics/_analytics_web.dart +++ b/packages/devtools_app/lib/src/analytics/_analytics_web.dart @@ -275,6 +275,60 @@ void screen( ); } +String _operationKey(String screenName, String timedOperation) { + return '$screenName-$timedOperation'; +} + +final _timedOperationsInProgress = {}; + +// Use this method coupled with `timeEnd` when an operation cannot be timed in +// a callback, but rather needs to be timed instead at two disjoint start and +// end marks. +void timeStart(String screenName, String timedOperation) { + final startTime = DateTime.now(); + final operationKey = _operationKey( + screenName, + timedOperation, + ); + _timedOperationsInProgress[operationKey] = startTime; +} + +// Use this method coupled with `timeStart` when an operation cannot be timed in +// a callback, but rather needs to be timed instead at two disjoint start and +// end marks. +void timeEnd( + String screenName, + String timedOperation, { + ScreenAnalyticsMetrics Function() screenMetricsProvider, +}) { + final endTime = DateTime.now(); + final operationKey = _operationKey( + screenName, + timedOperation, + ); + final startTime = _timedOperationsInProgress.remove(operationKey); + assert(startTime != null); + if (startTime == null) { + log( + 'Could not time operation "$timedOperation" because a) `timeEnd` was ' + 'called before `timeStart` or b) the `screenName` and `timedOperation`' + 'parameters for the `timeStart` and `timeEnd` calls do not match.', + LogLevel.warning, + ); + return; + } + final durationMicros = + endTime.microsecondsSinceEpoch - startTime.microsecondsSinceEpoch; + _timing( + screenName, + timedOperation, + durationMicros: durationMicros, + screenMetrics: + screenMetricsProvider != null ? screenMetricsProvider() : null, + ); +} + +// Use this when a synchronous operation can be timed in a callback. void timeSync( String screenName, String timedOperation, { @@ -305,6 +359,7 @@ void timeSync( ); } +// Use this when an asynchronous operation can be timed in a callback. Future timeAsync( String screenName, String timedOperation, { diff --git a/packages/devtools_app/lib/src/analytics/constants.dart b/packages/devtools_app/lib/src/analytics/constants.dart index 4d56b60ab55..d571ce88bfe 100644 --- a/packages/devtools_app/lib/src/analytics/constants.dart +++ b/packages/devtools_app/lib/src/analytics/constants.dart @@ -105,3 +105,6 @@ const String export = 'export'; const String expandAll = 'expandAll'; const String collapseAll = 'collapseAll'; const String documentationLink = 'documentationLink'; +// This should track the time from `initState` for a screen to the time when +// the page data has loaded and is ready to interact with. +const String pageReady = 'pageReady'; diff --git a/packages/devtools_app/lib/src/debugger/debugger_screen.dart b/packages/devtools_app/lib/src/debugger/debugger_screen.dart index 0bcde4584a9..9231ed72a79 100644 --- a/packages/devtools_app/lib/src/debugger/debugger_screen.dart +++ b/packages/devtools_app/lib/src/debugger/debugger_screen.dart @@ -9,6 +9,7 @@ import 'package:provider/provider.dart'; import 'package:vm_service/vm_service.dart'; import '../analytics/analytics.dart' as ga; +import '../analytics/constants.dart' as analytics_constants; import '../auto_dispose_mixin.dart'; import '../common_widgets.dart'; import '../dialogs.dart'; @@ -80,10 +81,14 @@ class DebuggerScreenBodyState extends State DebuggerController controller; + bool _shownFirstScript; + @override void initState() { super.initState(); ga.screen(DebuggerScreen.id); + ga.timeStart(DebuggerScreen.id, analytics_constants.pageReady); + _shownFirstScript = false; } @override @@ -95,11 +100,6 @@ class DebuggerScreenBodyState extends State controller = newController; } - @override - void dispose() { - super.dispose(); - } - void _onLocationSelected(ScriptLocation location) { if (location != null) { controller.showScriptLocation(location); @@ -114,6 +114,13 @@ class DebuggerScreenBodyState extends State return ValueListenableBuilder( valueListenable: controller.currentParsedScript, builder: (context, parsedScript, _) { + if (scriptRef != null && + parsedScript != null && + !_shownFirstScript) { + ga.timeEnd(DebuggerScreen.id, analytics_constants.pageReady); + // TODO(annagrin): mark end of IPL timing for debugger page here. + _shownFirstScript = true; + } return CodeView( key: DebuggerScreenBody.codeViewKey, controller: controller,