// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. import 'dart:async'; import 'package:vm_service/vm_service.dart'; class DwdsStats { /// The time when the user starts the debugger. DateTime? _debuggerStart; DateTime? get debuggerStart => _debuggerStart; /// The time when dwds launches DevTools. DateTime? _devToolsStart; DateTime? get devToolsStart => _devToolsStart; /// Records and returns whether the debugger is ready. bool _isFirstDebuggerReady = true; bool get isFirstDebuggerReady { final wasReady = _isFirstDebuggerReady; _isFirstDebuggerReady = false; return wasReady; } void updateLoadTime({ required DateTime debuggerStart, required DateTime devToolsStart, }) { _debuggerStart = debuggerStart; _devToolsStart = devToolsStart; } DwdsStats(); } class DwdsEventKind { static const String compilerUpdateDependencies = 'COMPILER_UPDATE_DEPENDENCIES'; static const String devtoolsLaunch = 'DEVTOOLS_LAUNCH'; static const String devToolsLoad = 'DEVTOOLS_LOAD'; static const String debuggerReady = 'DEBUGGER_READY'; static const String dwdsAttach = 'DWDS_ATTACH'; static const String dwdsLaunch = 'DWDS_LAUNCH'; static const String evaluate = 'EVALUATE'; static const String evaluateInFrame = 'EVALUATE_IN_FRAME'; static const String fullReload = 'FULL_RELOAD'; static const String getIsolate = 'GET_ISOLATE'; static const String getScripts = 'GET_SCRIPTS'; static const String getSourceReport = 'GET_SOURCE_REPORT'; static const String getVM = 'GET_VM'; static const String hotRestart = 'HOT_RESTART'; static const String httpRequestException = 'HTTP_REQUEST_EXCEPTION'; static const String resume = 'RESUME'; DwdsEventKind._(); } class DwdsEvent { final String type; final Map payload; DwdsEvent(this.type, this.payload); DwdsEvent.dwdsLaunch({required String codeRunner, bool? isFlutterApp}) : this(DwdsEventKind.dwdsLaunch, { 'codeRunner': codeRunner, 'isFlutterApp': isFlutterApp ?? false, }); DwdsEvent.dwdsAttach({required String client, bool? isFlutterApp}) : this(DwdsEventKind.dwdsAttach, { 'client': client, 'isFlutterApp': isFlutterApp ?? false, }); DwdsEvent.compilerUpdateDependencies(String entrypoint) : this(DwdsEventKind.compilerUpdateDependencies, { 'entrypoint': entrypoint, }); DwdsEvent.devtoolsLaunch() : this(DwdsEventKind.devtoolsLaunch, {}); DwdsEvent.evaluate(String expression, Response? result) : this(DwdsEventKind.evaluate, { 'expression': expression, 'success': result != null && result is InstanceRef, if (result != null && result is ErrorRef) 'error': result, }); DwdsEvent.evaluateInFrame(String expression, Response? result) : this(DwdsEventKind.evaluateInFrame, { 'expression': expression, 'success': result != null && result is InstanceRef, if (result != null && result is ErrorRef) 'error': result, }); DwdsEvent.getIsolate() : this(DwdsEventKind.getIsolate, {}); DwdsEvent.getScripts() : this(DwdsEventKind.getScripts, {}); DwdsEvent.getVM() : this(DwdsEventKind.getVM, {}); DwdsEvent.resume(String? step) : this(DwdsEventKind.resume, {if (step != null) 'step': step}); DwdsEvent.getSourceReport() : this(DwdsEventKind.getSourceReport, {}); DwdsEvent.hotRestart() : this(DwdsEventKind.hotRestart, {}); DwdsEvent.fullReload() : this(DwdsEventKind.fullReload, {}); DwdsEvent.debuggerReady(int elapsedMilliseconds, String screen) : this(DwdsEventKind.debuggerReady, { 'elapsedMilliseconds': elapsedMilliseconds, 'screen': screen, }); DwdsEvent.devToolsLoad(int elapsedMilliseconds, String screen) : this(DwdsEventKind.devToolsLoad, { 'elapsedMilliseconds': elapsedMilliseconds, 'screen': screen, }); DwdsEvent.httpRequestException(String server, String exception) : this(DwdsEventKind.httpRequestException, { 'server': server, 'exception': exception, }); void addException(dynamic exception) { payload['exception'] = exception; } void addElapsedTime(int elapsedMilliseconds) { payload['elapsedMilliseconds'] = elapsedMilliseconds; } @override String toString() { return 'TYPE: $type Payload: $payload'; } } final _eventController = StreamController.broadcast(); /// Adds an event to the global [eventStream]; void emitEvent(DwdsEvent event) => _eventController.sink.add(event); /// A global stream of [DwdsEvent]s. Stream get eventStream => _eventController.stream; /// Call [function] and record its execution time. /// /// Calls [event] to create the event to be recorded, /// and appends time and exception details to it if /// available. Future captureElapsedTime( Future Function() function, DwdsEvent Function(T? result) event, ) async { final stopwatch = Stopwatch()..start(); T? result; try { return result = await function(); } catch (e) { emitEvent( event(null) ..addException(e) ..addElapsedTime(stopwatch.elapsedMilliseconds), ); rethrow; } finally { emitEvent(event(result)..addElapsedTime(stopwatch.elapsedMilliseconds)); } }