Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
9e57c01
-
polina-c Nov 26, 2022
cc52b56
-
polina-c Nov 26, 2022
f131082
Update leak_analyzer_test.dart
polina-c Nov 26, 2022
fec4a09
Merge branch 'leaks-reorg-1' into leaks-2
polina-c Nov 26, 2022
77cb822
-
polina-c Nov 26, 2022
bd86c44
Merge branch 'leaks-reorg-1' into leaks-2
polina-c Nov 26, 2022
6922842
-
polina-c Nov 26, 2022
f11ff90
-
polina-c Nov 26, 2022
90ddb7e
-
polina-c Nov 26, 2022
7d731cf
-
polina-c Nov 26, 2022
aca149e
Update controller.dart
polina-c Nov 27, 2022
3127a77
Update controller.dart
polina-c Nov 27, 2022
0101caf
Update controller.dart
polina-c Nov 28, 2022
190d3af
-
polina-c Nov 28, 2022
3cece55
Update controller.dart
polina-c Nov 30, 2022
fdcd7f7
-
polina-c Dec 1, 2022
160c646
-
polina-c Dec 2, 2022
e0a7c50
Merge branch 'master' of github.com:flutter/devtools into leaks-2
polina-c Dec 2, 2022
99db8f4
-
polina-c Dec 2, 2022
db2c2dd
Update controller.dart
polina-c Dec 4, 2022
d3be583
-
polina-c Dec 4, 2022
13ca364
Merge branch 'master' of github.com:flutter/devtools into leaks-2
polina-c Dec 6, 2022
9912fd5
Merge branch 'master' of github.com:flutter/devtools into leaks-2
polina-c Dec 6, 2022
80f3afc
Update pubspec.yaml
polina-c Dec 6, 2022
b55ab75
Update pubspec.yaml
polina-c Dec 6, 2022
4afa44b
Update pubspec.yaml
polina-c Dec 7, 2022
2923a62
Update controller.dart
polina-c Dec 7, 2022
9b304d8
-
polina-c Dec 7, 2022
2ffdcb6
Merge branch 'master' of github.com:flutter/devtools into leaks-2
polina-c Dec 7, 2022
53ab0d5
Merge branch 'master' of github.com:flutter/devtools into leaks-2
polina-c Dec 7, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ import 'dart:async';
import 'package:devtools_shared/devtools_shared.dart';
import 'package:flutter/foundation.dart';
import 'package:intl/intl.dart';
import 'package:leak_tracker/devtools_integration.dart';
import 'package:vm_service/vm_service.dart';

import '../../analytics/analytics.dart' as ga;
import '../../analytics/constants.dart' as analytics_constants;
import '../../config_specific/file/file.dart';
import '../../config_specific/logger/logger.dart';
import '../../primitives/auto_dispose.dart';
import '../../service/service_extensions.dart';
import '../../service/service_manager.dart';
import '../../shared/globals.dart';
import '../../shared/utils.dart';
Expand Down Expand Up @@ -202,7 +202,7 @@ class MemoryController extends DisposableController

void _refreshShouldShowLeaksTab() {
_shouldShowLeaksTab.value = serviceManager.serviceExtensionManager
.hasServiceExtension(memoryLeakTracking)
.hasServiceExtension(memoryLeakTrackingExtensionName)
.value;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,78 +6,74 @@ import 'dart:async';
import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:leak_tracker/devtools_integration.dart';
import 'package:vm_service/vm_service.dart';

import '../../../../config_specific/import_export/import_export.dart';
import '../../../../config_specific/logger/logger.dart' as logger;
import '../../../../primitives/utils.dart';
import '../../../../service/service_extensions.dart';
import '../../../../shared/globals.dart';
import '../../primitives/memory_utils.dart';
import 'diagnostics/formatter.dart';
import 'diagnostics/leak_analyzer.dart';
import 'diagnostics/model.dart';
import 'instrumentation/model.dart';
import 'primitives/analysis_status.dart';

// TODO(polina-c): reference these constants in dart SDK, when it gets submitted
// there.
// https://github.com/flutter/devtools/issues/3951
const _extensionKindToReceiveLeaksSummary = 'memory_leaks_summary';
const _extensionKindToReceiveLeaksDetails = 'memory_leaks_details';
import 'primitives/simple_items.dart';

const yamlFilePrefix = 'memory_leaks';

class LeaksPaneController {
LeaksPaneController() {
_subscribeForMemoryLeaksMessages();
LeaksPaneController()
: assert(
supportedLeakTrackingProtocols
.contains(appLeakTrackerProtocolVersion),
) {
subscriptionWithHistory = serviceManager
.service!.onExtensionEventWithHistory
.listen(_onAppMessageWithHistory);
}

final status = AnalysisStatusController();
final analysisStatus = AnalysisStatusController();

final leakSummaryHistory = ValueNotifier<String>('');
final leakSummaryReceived = ValueNotifier<bool>(false);
late String appProtocolVersion;
final appStatus =
ValueNotifier<AppStatus>(AppStatus.noCommunicationsRecieved);

LeakSummary? _lastLeakSummary;

final _exportController = ExportController();

late StreamSubscription summarySubscription;
late StreamSubscription detailsSubscription;

/// Subscribes for summary with history and for details without history.
void _subscribeForMemoryLeaksMessages() {
detailsSubscription =
serviceManager.service!.onExtensionEvent.listen(_receivedLeaksDetails);

summarySubscription = serviceManager.service!.onExtensionEventWithHistory
.listen(_receivedLeaksSummary);
}
late StreamSubscription subscriptionWithHistory;

void dispose() {
unawaited(summarySubscription.cancel());
unawaited(detailsSubscription.cancel());
status.dispose();
unawaited(subscriptionWithHistory.cancel());
analysisStatus.dispose();
}

void _receivedLeaksSummary(Event event) {
if (event.extensionKind != _extensionKindToReceiveLeaksSummary) return;
leakSummaryReceived.value = true;
try {
final newSummary = LeakSummary.fromJson(event.json!['extensionData']!);
final time = event.timestamp != null
? DateTime.fromMicrosecondsSinceEpoch(event.timestamp!)
: DateTime.now();
void _onAppMessageWithHistory(Event vmServiceEvent) {
if (appStatus.value == AppStatus.unsupportedProtocolVersion) return;

final message = EventFromApp.fromVmServiceEvent(vmServiceEvent)?.message;
if (message == null) return;

if (message is LeakTrackingStarted) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: maybe change this to use a switch statement instead?

if (message == null) return;
switch(message.runtimeType) {
  case LeakTrackingStarted:
    ...
  case LeakSummary:
    ...
  default:
    throw StateError('...');
}

Copy link
Contributor Author

@polina-c polina-c Dec 7, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do this as patterns get released. For now there is no type promotion for 'runtimeType':

Screenshot 2022-12-07 at 9 51 25 AM

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah good point. Waiting for patterns SGTM.

appStatus.value = AppStatus.leakTrackingStarted;
appProtocolVersion = message.protocolVersion;
return;
}

if (message is LeakSummary) {
appStatus.value = AppStatus.leaksFound;
if (message.matches(_lastLeakSummary)) return;
_lastLeakSummary = message;

if (newSummary.matches(_lastLeakSummary)) return;
_lastLeakSummary = newSummary;
leakSummaryHistory.value =
'${formatDateTime(time)}: ${newSummary.toMessage()}\n'
'${formatDateTime(message.time)}: ${message.toMessage()}\n'
'${leakSummaryHistory.value}';
} catch (error, trace) {
leakSummaryHistory.value = 'error: $error\n${leakSummaryHistory.value}';
logger.log(error);
logger.log(trace);
return;
}

throw StateError('Unsupported event type: ${message.runtimeType}');
}

Future<NotGCedAnalyzerTask> _createAnalysisTask(
Expand All @@ -87,18 +83,21 @@ class LeaksPaneController {
return NotGCedAnalyzerTask.fromSnapshot(graph, reports);
}

Future<void> _receivedLeaksDetails(Event event) async {
if (event.extensionKind != _extensionKindToReceiveLeaksDetails) return;
if (status.status.value != AnalysisStatus.Ongoing) return;
NotGCedAnalyzerTask? task;

Future<void> requestLeaksAndSaveToYaml() async {
try {
await _setMessageWithDelay('Received details. Parsing...');
final leakDetails = Leaks.fromJson(event.json!['extensionData']!);
analysisStatus.status.value = AnalysisStatus.Ongoing;
await _setMessageWithDelay('Requested details from the application.');

final leakDetails =
await _invokeLeakExtension<RequestForLeakDetails, Leaks>(
RequestForLeakDetails(),
);

final notGCed = leakDetails.byType[LeakType.notGCed] ?? [];

NotGCedAnalyzerTask? task;
NotGCedAnalyzed? notGCedAnalyzed;

if (notGCed.isNotEmpty) {
await _setMessageWithDelay('Taking heap snapshot...');
task = await _createAnalysisTask(notGCed);
Expand All @@ -114,21 +113,17 @@ class LeaksPaneController {
notGCed: notGCedAnalyzed,
);

_saveResultAndSetStatus(yaml, task);
} catch (error, trace) {
var message = '${status.message.value}\nError: $error';
if (task != null) {
final fileName = _saveTask(task, DateTime.now());
message += '\nDownloaded raw data to $fileName.';
await _setMessageWithDelay(message);
status.status.value = AnalysisStatus.ShowingError;
}
logger.log(error);
logger.log(trace);
_saveResultAndSetAnalysisStatus(yaml, task);
} catch (error) {
analysisStatus.message.value = 'Error: $error';
analysisStatus.status.value = AnalysisStatus.ShowingError;
}
}

void _saveResultAndSetStatus(String yaml, NotGCedAnalyzerTask? task) async {
void _saveResultAndSetAnalysisStatus(
String yaml,
NotGCedAnalyzerTask? task,
) async {
final now = DateTime.now();
final yamlFile = ExportController.generateFileName(
time: now,
Expand All @@ -142,7 +137,7 @@ class LeaksPaneController {
await _setMessageWithDelay(
'Downloaded the leak analysis to $yamlFile$taskFileMessage.',
);
status.status.value = AnalysisStatus.ShowingResult;
analysisStatus.status.value = AnalysisStatus.ShowingResult;
}

/// Saves raw analysis task for troubleshooting and deeper analysis.
Expand All @@ -160,44 +155,35 @@ class LeaksPaneController {
}

Future<void> _setMessageWithDelay(String message) async {
status.message.value = message;
analysisStatus.message.value = message;
await delayForBatchProcessing(micros: 5000);
}

Future<void> forceGC() async {
status.status.value = AnalysisStatus.Ongoing;
await _setMessageWithDelay('Forcing full garbage collection...');
await _invokeMemoryLeakTrackingExtension(
<String, dynamic>{
// TODO(polina-c): reference the constant in Flutter
// https://github.com/flutter/devtools/issues/3951
'forceGC': 'true',
},
Future<R> _invokeLeakExtension<M extends Object, R extends Object>(
M message,
) async {
final response = await serviceManager.service!.callServiceExtension(
memoryLeakTrackingExtensionName,
isolateId: serviceManager.isolateManager.mainIsolate.value!.id!,
args: RequestToApp(message).toRequestParameters(),
);
status.status.value = AnalysisStatus.ShowingResult;
await _setMessageWithDelay('Full garbage collection initiated.');
}

Future<void> requestLeaks() async {
status.status.value = AnalysisStatus.Ongoing;
await _setMessageWithDelay('Requested details from the application.');

await _invokeMemoryLeakTrackingExtension(
<String, dynamic>{
// TODO(polina-c): reference the constant in Flutter
// https://github.com/flutter/devtools/issues/3951
'requestDetails': 'true',
},
);
return ResponseFromApp<R>.fromServiceResponse(response).message;
}

Future<void> _invokeMemoryLeakTrackingExtension(
Map<String, dynamic> args,
) async {
await serviceManager.service!.callServiceExtension(
memoryLeakTracking,
isolateId: serviceManager.isolateManager.mainIsolate.value!.id!,
args: args,
);
String appStatusMessage() {
switch (appStatus.value) {
case AppStatus.leakTrackingNotSupported:
return 'The application does not support leak tracking.';
case AppStatus.noCommunicationsRecieved:
return 'Waiting for leak tracking messages from the application...';
case AppStatus.unsupportedProtocolVersion:
return 'The application uses unsupported leak tracking protocol $appProtocolVersion. '
'Upgrade to a newer version of leak_tracker to switch to one of supported protocols: $supportedLeakTrackingProtocols.';
case AppStatus.leakTrackingStarted:
return 'Leak tracking started. No leaks communicated so far.';
case AppStatus.leaksFound:
throw StateError('There is no UI message for ${AppStatus.leaksFound}.');
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
// found in the LICENSE file.

import 'package:collection/collection.dart';
import 'package:leak_tracker/devtools_integration.dart';

import '../instrumentation/model.dart';
import 'model.dart';

const linkToGuidance =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
// found in the LICENSE file.

import 'package:flutter/material.dart';
import 'package:leak_tracker/devtools_integration.dart';

import '../../../shared/heap/model.dart';
import '../../../shared/heap/spanning_tree.dart';
import '../instrumentation/model.dart';
import 'model.dart';

/// Analyzes notGCed leaks and returns result of the analysis.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:leak_tracker/devtools_integration.dart';
import 'package:vm_service/vm_service.dart';

import '../../../shared/heap/model.dart';
import '../instrumentation/model.dart';

/// Names for json fields.
class _JsonFields {
Expand Down

This file was deleted.

Loading