Skip to content

Commit 126af88

Browse files
polina-cCoderDake
authored andcommitted
Integrate leak_tracker. (#4891)
1 parent eb74cf6 commit 126af88

File tree

15 files changed

+147
-326
lines changed

15 files changed

+147
-326
lines changed

packages/devtools_app/lib/src/screens/memory/memory_controller.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@ import 'dart:async';
77
import 'package:devtools_shared/devtools_shared.dart';
88
import 'package:flutter/foundation.dart';
99
import 'package:intl/intl.dart';
10+
import 'package:leak_tracker/devtools_integration.dart';
1011
import 'package:vm_service/vm_service.dart';
1112

1213
import '../../analytics/analytics.dart' as ga;
1314
import '../../analytics/constants.dart' as analytics_constants;
1415
import '../../config_specific/file/file.dart';
1516
import '../../config_specific/logger/logger.dart';
1617
import '../../primitives/auto_dispose.dart';
17-
import '../../service/service_extensions.dart';
1818
import '../../service/service_manager.dart';
1919
import '../../shared/globals.dart';
2020
import '../../shared/utils.dart';
@@ -202,7 +202,7 @@ class MemoryController extends DisposableController
202202

203203
void _refreshShouldShowLeaksTab() {
204204
_shouldShowLeaksTab.value = serviceManager.serviceExtensionManager
205-
.hasServiceExtension(memoryLeakTracking)
205+
.hasServiceExtension(memoryLeakTrackingExtensionName)
206206
.value;
207207
}
208208

packages/devtools_app/lib/src/screens/memory/panes/leaks/controller.dart

Lines changed: 80 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -6,78 +6,74 @@ import 'dart:async';
66
import 'dart:convert';
77

88
import 'package:flutter/material.dart';
9+
import 'package:leak_tracker/devtools_integration.dart';
910
import 'package:vm_service/vm_service.dart';
1011

1112
import '../../../../config_specific/import_export/import_export.dart';
12-
import '../../../../config_specific/logger/logger.dart' as logger;
1313
import '../../../../primitives/utils.dart';
14-
import '../../../../service/service_extensions.dart';
1514
import '../../../../shared/globals.dart';
1615
import '../../primitives/memory_utils.dart';
1716
import 'diagnostics/formatter.dart';
1817
import 'diagnostics/leak_analyzer.dart';
1918
import 'diagnostics/model.dart';
20-
import 'instrumentation/model.dart';
2119
import '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

2922
const yamlFilePrefix = 'memory_leaks';
3023

3124
class 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}\nError: $error';
120-
if (task != null) {
121-
final fileName = _saveTask(task, DateTime.now());
122-
message += '\nDownloaded 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
}

packages/devtools_app/lib/src/screens/memory/panes/leaks/diagnostics/formatter.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
// found in the LICENSE file.
44

55
import 'package:collection/collection.dart';
6+
import 'package:leak_tracker/devtools_integration.dart';
67

7-
import '../instrumentation/model.dart';
88
import 'model.dart';
99

1010
const linkToGuidance =

packages/devtools_app/lib/src/screens/memory/panes/leaks/diagnostics/leak_analyzer.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
// found in the LICENSE file.
44

55
import 'package:flutter/material.dart';
6+
import 'package:leak_tracker/devtools_integration.dart';
67

78
import '../../../shared/heap/model.dart';
89
import '../../../shared/heap/spanning_tree.dart';
9-
import '../instrumentation/model.dart';
1010
import 'model.dart';
1111

1212
/// Analyzes notGCed leaks and returns result of the analysis.

packages/devtools_app/lib/src/screens/memory/panes/leaks/diagnostics/model.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5+
import 'package:leak_tracker/devtools_integration.dart';
56
import 'package:vm_service/vm_service.dart';
67

78
import '../../../shared/heap/model.dart';
8-
import '../instrumentation/model.dart';
99

1010
/// Names for json fields.
1111
class _JsonFields {

packages/devtools_app/lib/src/screens/memory/panes/leaks/instrumentation/README.md

Lines changed: 0 additions & 2 deletions
This file was deleted.

0 commit comments

Comments
 (0)