@@ -6,78 +6,74 @@ import 'dart:async';
66import 'dart:convert' ;
77
88import 'package:flutter/material.dart' ;
9+ import 'package:leak_tracker/devtools_integration.dart' ;
910import 'package:vm_service/vm_service.dart' ;
1011
1112import '../../../../config_specific/import_export/import_export.dart' ;
12- import '../../../../config_specific/logger/logger.dart' as logger;
1313import '../../../../primitives/utils.dart' ;
14- import '../../../../service/service_extensions.dart' ;
1514import '../../../../shared/globals.dart' ;
1615import '../../primitives/memory_utils.dart' ;
1716import 'diagnostics/formatter.dart' ;
1817import 'diagnostics/leak_analyzer.dart' ;
1918import 'diagnostics/model.dart' ;
20- import 'instrumentation/model.dart' ;
2119import 'primitives/analysis_status.dart' ;
22-
23- // TODO(polina-c): reference these constants in dart SDK, when it gets submitted
24- // there.
25- // https://github.com/flutter/devtools/issues/3951
26- const _extensionKindToReceiveLeaksSummary = 'memory_leaks_summary' ;
27- const _extensionKindToReceiveLeaksDetails = 'memory_leaks_details' ;
20+ import 'primitives/simple_items.dart' ;
2821
2922const yamlFilePrefix = 'memory_leaks' ;
3023
3124class LeaksPaneController {
32- LeaksPaneController () {
33- _subscribeForMemoryLeaksMessages ();
25+ LeaksPaneController ()
26+ : assert (
27+ supportedLeakTrackingProtocols
28+ .contains (appLeakTrackerProtocolVersion),
29+ ) {
30+ subscriptionWithHistory = serviceManager
31+ .service! .onExtensionEventWithHistory
32+ .listen (_onAppMessageWithHistory);
3433 }
3534
36- final status = AnalysisStatusController ();
35+ final analysisStatus = AnalysisStatusController ();
3736
3837 final leakSummaryHistory = ValueNotifier <String >('' );
39- final leakSummaryReceived = ValueNotifier <bool >(false );
38+ late String appProtocolVersion;
39+ final appStatus =
40+ ValueNotifier <AppStatus >(AppStatus .noCommunicationsRecieved);
41+
4042 LeakSummary ? _lastLeakSummary;
4143
4244 final _exportController = ExportController ();
4345
44- late StreamSubscription summarySubscription;
45- late StreamSubscription detailsSubscription;
46-
47- /// Subscribes for summary with history and for details without history.
48- void _subscribeForMemoryLeaksMessages () {
49- detailsSubscription =
50- serviceManager.service! .onExtensionEvent.listen (_receivedLeaksDetails);
51-
52- summarySubscription = serviceManager.service! .onExtensionEventWithHistory
53- .listen (_receivedLeaksSummary);
54- }
46+ late StreamSubscription subscriptionWithHistory;
5547
5648 void dispose () {
57- unawaited (summarySubscription.cancel ());
58- unawaited (detailsSubscription.cancel ());
59- status.dispose ();
49+ unawaited (subscriptionWithHistory.cancel ());
50+ analysisStatus.dispose ();
6051 }
6152
62- void _receivedLeaksSummary (Event event) {
63- if (event.extensionKind != _extensionKindToReceiveLeaksSummary) return ;
64- leakSummaryReceived.value = true ;
65- try {
66- final newSummary = LeakSummary .fromJson (event.json! ['extensionData' ]! );
67- final time = event.timestamp != null
68- ? DateTime .fromMicrosecondsSinceEpoch (event.timestamp! )
69- : DateTime .now ();
53+ void _onAppMessageWithHistory (Event vmServiceEvent) {
54+ if (appStatus.value == AppStatus .unsupportedProtocolVersion) return ;
55+
56+ final message = EventFromApp .fromVmServiceEvent (vmServiceEvent)? .message;
57+ if (message == null ) return ;
58+
59+ if (message is LeakTrackingStarted ) {
60+ appStatus.value = AppStatus .leakTrackingStarted;
61+ appProtocolVersion = message.protocolVersion;
62+ return ;
63+ }
64+
65+ if (message is LeakSummary ) {
66+ appStatus.value = AppStatus .leaksFound;
67+ if (message.matches (_lastLeakSummary)) return ;
68+ _lastLeakSummary = message;
7069
71- if (newSummary.matches (_lastLeakSummary)) return ;
72- _lastLeakSummary = newSummary;
7370 leakSummaryHistory.value =
74- '${formatDateTime (time )}: ${newSummary .toMessage ()}\n '
71+ '${formatDateTime (message . time )}: ${message .toMessage ()}\n '
7572 '${leakSummaryHistory .value }' ;
76- } catch (error, trace) {
77- leakSummaryHistory.value = 'error: $error \n ${leakSummaryHistory .value }' ;
78- logger.log (error);
79- logger.log (trace);
73+ return ;
8074 }
75+
76+ throw StateError ('Unsupported event type: ${message .runtimeType }' );
8177 }
8278
8379 Future <NotGCedAnalyzerTask > _createAnalysisTask (
@@ -87,18 +83,21 @@ class LeaksPaneController {
8783 return NotGCedAnalyzerTask .fromSnapshot (graph, reports);
8884 }
8985
90- Future <void > _receivedLeaksDetails (Event event) async {
91- if (event.extensionKind != _extensionKindToReceiveLeaksDetails) return ;
92- if (status.status.value != AnalysisStatus .Ongoing ) return ;
93- NotGCedAnalyzerTask ? task;
94-
86+ Future <void > requestLeaksAndSaveToYaml () async {
9587 try {
96- await _setMessageWithDelay ('Received details. Parsing...' );
97- final leakDetails = Leaks .fromJson (event.json! ['extensionData' ]! );
88+ analysisStatus.status.value = AnalysisStatus .Ongoing ;
89+ await _setMessageWithDelay ('Requested details from the application.' );
90+
91+ final leakDetails =
92+ await _invokeLeakExtension <RequestForLeakDetails , Leaks >(
93+ RequestForLeakDetails (),
94+ );
9895
9996 final notGCed = leakDetails.byType[LeakType .notGCed] ?? [];
10097
98+ NotGCedAnalyzerTask ? task;
10199 NotGCedAnalyzed ? notGCedAnalyzed;
100+
102101 if (notGCed.isNotEmpty) {
103102 await _setMessageWithDelay ('Taking heap snapshot...' );
104103 task = await _createAnalysisTask (notGCed);
@@ -114,21 +113,17 @@ class LeaksPaneController {
114113 notGCed: notGCedAnalyzed,
115114 );
116115
117- _saveResultAndSetStatus (yaml, task);
118- } catch (error, trace) {
119- var message = '${status .message .value }\n Error: $error ' ;
120- if (task != null ) {
121- final fileName = _saveTask (task, DateTime .now ());
122- message += '\n Downloaded raw data to $fileName .' ;
123- await _setMessageWithDelay (message);
124- status.status.value = AnalysisStatus .ShowingError ;
125- }
126- logger.log (error);
127- logger.log (trace);
116+ _saveResultAndSetAnalysisStatus (yaml, task);
117+ } catch (error) {
118+ analysisStatus.message.value = 'Error: $error ' ;
119+ analysisStatus.status.value = AnalysisStatus .ShowingError ;
128120 }
129121 }
130122
131- void _saveResultAndSetStatus (String yaml, NotGCedAnalyzerTask ? task) async {
123+ void _saveResultAndSetAnalysisStatus (
124+ String yaml,
125+ NotGCedAnalyzerTask ? task,
126+ ) async {
132127 final now = DateTime .now ();
133128 final yamlFile = ExportController .generateFileName (
134129 time: now,
@@ -142,7 +137,7 @@ class LeaksPaneController {
142137 await _setMessageWithDelay (
143138 'Downloaded the leak analysis to $yamlFile $taskFileMessage .' ,
144139 );
145- status .status.value = AnalysisStatus .ShowingResult ;
140+ analysisStatus .status.value = AnalysisStatus .ShowingResult ;
146141 }
147142
148143 /// Saves raw analysis task for troubleshooting and deeper analysis.
@@ -160,44 +155,35 @@ class LeaksPaneController {
160155 }
161156
162157 Future <void > _setMessageWithDelay (String message) async {
163- status .message.value = message;
158+ analysisStatus .message.value = message;
164159 await delayForBatchProcessing (micros: 5000 );
165160 }
166161
167- Future <void > forceGC () async {
168- status.status.value = AnalysisStatus .Ongoing ;
169- await _setMessageWithDelay ('Forcing full garbage collection...' );
170- await _invokeMemoryLeakTrackingExtension (
171- < String , dynamic > {
172- // TODO(polina-c): reference the constant in Flutter
173- // https://github.com/flutter/devtools/issues/3951
174- 'forceGC' : 'true' ,
175- },
162+ Future <R > _invokeLeakExtension <M extends Object , R extends Object >(
163+ M message,
164+ ) async {
165+ final response = await serviceManager.service! .callServiceExtension (
166+ memoryLeakTrackingExtensionName,
167+ isolateId: serviceManager.isolateManager.mainIsolate.value! .id! ,
168+ args: RequestToApp (message).toRequestParameters (),
176169 );
177- status.status.value = AnalysisStatus .ShowingResult ;
178- await _setMessageWithDelay ('Full garbage collection initiated.' );
179- }
180170
181- Future <void > requestLeaks () async {
182- status.status.value = AnalysisStatus .Ongoing ;
183- await _setMessageWithDelay ('Requested details from the application.' );
184-
185- await _invokeMemoryLeakTrackingExtension (
186- < String , dynamic > {
187- // TODO(polina-c): reference the constant in Flutter
188- // https://github.com/flutter/devtools/issues/3951
189- 'requestDetails' : 'true' ,
190- },
191- );
171+ return ResponseFromApp <R >.fromServiceResponse (response).message;
192172 }
193173
194- Future <void > _invokeMemoryLeakTrackingExtension (
195- Map <String , dynamic > args,
196- ) async {
197- await serviceManager.service! .callServiceExtension (
198- memoryLeakTracking,
199- isolateId: serviceManager.isolateManager.mainIsolate.value! .id! ,
200- args: args,
201- );
174+ String appStatusMessage () {
175+ switch (appStatus.value) {
176+ case AppStatus .leakTrackingNotSupported:
177+ return 'The application does not support leak tracking.' ;
178+ case AppStatus .noCommunicationsRecieved:
179+ return 'Waiting for leak tracking messages from the application...' ;
180+ case AppStatus .unsupportedProtocolVersion:
181+ return 'The application uses unsupported leak tracking protocol $appProtocolVersion . '
182+ 'Upgrade to a newer version of leak_tracker to switch to one of supported protocols: $supportedLeakTrackingProtocols .' ;
183+ case AppStatus .leakTrackingStarted:
184+ return 'Leak tracking started. No leaks communicated so far.' ;
185+ case AppStatus .leaksFound:
186+ throw StateError ('There is no UI message for ${AppStatus .leaksFound }.' );
187+ }
202188 }
203189}
0 commit comments