Skip to content

Commit

Permalink
Add auto-snapshotting and analytics for memory usage.
Browse files Browse the repository at this point in the history
See video linked to issue: flutter/devtools#5606

Change-Id: I9f22031871e30bc7160e2c49b0423fb64df62223
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/300862
Reviewed-by: Samuel Rawlins <[email protected]>
Reviewed-by: Jacob Richman <[email protected]>
  • Loading branch information
polina-c committed May 16, 2023
1 parent 91091f3 commit 5f1e7ad
Show file tree
Hide file tree
Showing 7 changed files with 230 additions and 1 deletion.
2 changes: 1 addition & 1 deletion DEPS
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ vars = {
"test_descriptor_rev": "23e49a21fc6b4bf3164d336c699b29d1b8bb4622",
"test_process_rev": "b6a6cd5f598250c71d8deeea3d38eb821af5e932",
"test_reflective_loader_rev": "d1b763f6281a46a48e1da6f0a6e8152e3480e8d2",
"tools_rev": "62c96040d8090bc1bb866db75d40bd1707876cf9",
"tools_rev": "49da4cabaddec3c82485b15d83ddac2278f947db",
"typed_data_rev": "921f5c0380c6c487d8a065c45618162efa4cbd92",
"usage_rev": "929a4e31f0bd4f861dd0e34d4c6f7184c751b569",
"vector_math_rev": "e3de8da3e7db3b9b4f56a16061e9e480539fb08c",
Expand Down
23 changes: 23 additions & 0 deletions pkg/analysis_server/lib/src/analytics/analytics_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import 'package:analysis_server/src/protocol_server.dart';
import 'package:analysis_server/src/status/pages.dart';
import 'package:analyzer/dart/analysis/analysis_context.dart';
import 'package:collection/collection.dart';
import 'package:leak_tracker/src/usage_tracking/model.dart';
import 'package:unified_analytics/unified_analytics.dart';

/// An interface for managing and reporting analytics.
Expand Down Expand Up @@ -190,6 +191,28 @@ class AnalyticsManager {
requestData.addValue('openWorkspacePaths', openWorkspacePaths.length);
}

Future<void> sendMemoryUsage(MemoryUsageEvent event) async {
final delta = event.delta;
var seconds = event.period?.inSeconds;

assert((event.delta == null) == (event.period == null));

if (delta == null || seconds == null) {
await analytics.sendEvent(eventName: DashEvent.memoryInfo, eventData: {
'rss': event.rss,
});
return;
}

if (seconds == 0) seconds = 1;

await analytics.sendEvent(eventName: DashEvent.memoryInfo, eventData: {
'rss': event.rss,
'periodSec': seconds,
'mbPerSec': delta / seconds,
});
}

/// Record that the given [response] was sent to the client.
void sentResponse({required Response response}) {
var sendTime = DateTime.now();
Expand Down
7 changes: 7 additions & 0 deletions pkg/analysis_server/lib/src/server/driver.dart
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ import 'package:telemetry/crash_reporting.dart';
import 'package:telemetry/telemetry.dart' as telemetry;
import 'package:unified_analytics/unified_analytics.dart';

import '../utilities/usage_tracking/usage_tracking.dart';

/// The [Driver] class represents a single running instance of the analysis
/// server application. It is responsible for parsing command line options
/// and starting the HTTP and/or stdio servers.
Expand Down Expand Up @@ -355,6 +357,11 @@ class Driver implements ServerStarter {
errorNotifier,
sendPort);
}

configureMemoryUsageTracking(
arguments,
(memoryUsageEvent) => analyticsManager.sendMemoryUsage(memoryUsageEvent),
);
}

void startAnalysisServer(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Auto-snapshotting

IMPORTANT: memory snapshots should not be requested from external users because they may contain PII.

If a user reports that the process `dart:analysis_server.dart.snapshot` takes too much memory,
and the issue is hard to reproduce, you may want to request memory snapshots from the user.

## Request numbers

Ask user to provide memory footprint for the process `dart:analysis_server.dart.snapshot`.
If there are many instances of the process, ask for the biggest memory footprint among
the instances.

- **Mac**: column 'Real Mem' in 'Activity Monitor'
- **Windows**: TODO: add content
- **Linux**: TODO: add content

## Create auto-snapshotting argument

Based on the reported and expected values, construct auto-snapshotting argument. See example in
the [test file](../../../../test/utilities/autosnapshotting/autosnapshotting_test.dart), the
constant `_autosnapshottingArg`.

See explanation of parameters in
[documentation for AutoSnapshottingConfig](https://github.com/dart-lang/leak_tracker/blob/main/lib/src/autosnapshotting/model.dart).

## Instruct user to configure analyzer

Pass the created argument to the user and instruct them to configure
analyzer.

### For VSCode

1. Open Settings > Extensions > Dart > Analyser
2. Add the argument to `Dart: Analyzer Additional Args`

### For Android Studio

1. Double-press Shift
2. Type 'Registry' into search field
3. Click 'Registry...'
4. Add the argument to the value of the key 'dart.server.additional.arguments'

## Analyze snapshots

Ask user to provide the collected snapshots and analyze them.

TODO (polina-c): link DevTools documentation


Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// Copyright (c) 2023, 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 'package:args/args.dart';
import 'package:collection/collection.dart';
import 'package:leak_tracker/src/usage_tracking/model.dart';
import 'package:leak_tracker/src/usage_tracking/usage_tracking.dart';

void configureMemoryUsageTracking(
List<String> arguments,
UsageCallback callback,
) {
final config = UsageTrackingConfig(
interval: const Duration(seconds: 1),
usageEventsConfig: UsageEventsConfig(
callback,
deltaMb: 512,
),
autoSnapshottingConfig: parseAutoSnapshottingConfig(arguments),
);

trackMemoryUsage(config);
}

/// Parses the config for autosnapshotting from CLI [args].
///
/// See example of config in tests for this function.
///
/// If there is no argument that starts with '--autosnapshotting=', returns null.
///
/// In case of error throws exception.
AutoSnapshottingConfig? parseAutoSnapshottingConfig(List<String> args) {
const argName = 'autosnapshotting';
final arg = args.firstWhereOrNull((a) => a.contains('--$argName'));
if (arg == null) return null;

var parser = ArgParser()..addMultiOption(argName, splitCommas: true);

final parsedArgs = parser.parse([arg]);
assert(parsedArgs.options.contains(argName));

final values = parsedArgs[argName] as List<String>;
if (values.isEmpty) return null;

final items = Map.fromEntries(values.map((e) {
final keyValue = e.split('=');
if (keyValue.length != 2) {
throw ArgumentError(
'Invalid auto-snapshotting config: $values.\n'
'Expected "key-value", got "$e".',
);
}

final keyString = keyValue[0];
try {
final key = _Keys.values.byName(keyString);
return MapEntry(key, keyValue[1]);
} on ArgumentError {
throw ArgumentError('Invalid auto-snapshotting key: $keyString".');
}
}));

if (!items.containsKey(_Keys.dir)) {
throw ArgumentError(
'${_Keys.dir.name} should be provided for auto-snapshotting.');
}

return AutoSnapshottingConfig(
thresholdMb: _parseKey(_Keys.thresholdMb, items, 7000),
increaseMb: _parseKey(_Keys.increaseMb, items, 500),
directory: items[_Keys.dir]!,
directorySizeLimitMb: _parseKey(_Keys.dirLimitMb, items, 30000),
minDelayBetweenSnapshots: Duration(
seconds: _parseKey(_Keys.delaySec, items, 20),
),
);
}

int _parseKey(_Keys key, Map<_Keys, String> items, int defaultValue) {
final value = items[key];
if (value == null || value.trim().isEmpty) return defaultValue;
final result = int.tryParse(value);
if (result == null) {
throw ArgumentError(
'Invalid auto-snapshotting value for ${key.name}: $value.');
}
return result;
}

enum _Keys {
thresholdMb,
increaseMb,
dir,
dirLimitMb,
delaySec,
}
1 change: 1 addition & 0 deletions pkg/analysis_server/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ dependencies:
crypto: any
dart_style: any
http: any
leak_tracker: any
linter: any
meta: any
path: any
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright (c) 2023, 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 'package:analysis_server/src/utilities/usage_tracking/usage_tracking.dart';
import 'package:test/test.dart';

void main() {
group('parseAutoSnapshottingConfig', () {
test('parses correct config', () {
final config = parseAutoSnapshottingConfig(_argsWithSnapshotting)!;

expect(config.thresholdMb, 200);
expect(config.increaseMb, 100);
expect(config.directory, '/Users/polinach/Downloads/analyzer_snapshots');
expect(config.directorySizeLimitMb, 10000);
expect(config.minDelayBetweenSnapshots, Duration(seconds: 20));
});

test('returns null for no config', () {
final config = parseAutoSnapshottingConfig(_argsNoSnapshotting);
expect(config, null);
});

test('throws for wrong config', () {
final wrongAutosnapshottingArg =
'--autosnapshotting--wrong-configuration';

expect(
() => parseAutoSnapshottingConfig(
[wrongAutosnapshottingArg, 'some other arg']),
throwsA(isA<Object>()),
);
});
});
}

const _argsNoSnapshotting = [
'--sdk=C:/b/s/w/ir/x/w/sdk/sdk/',
'--train-using=C:/b/s/w/ir/x/w/sdk/pkg/compiler/lib'
];

const _argsWithSnapshotting = [
_autosnapshottingArg,
'--sdk=C:/b/s/w/ir/x/w/sdk/sdk/',
'--train-using=C:/b/s/w/ir/x/w/sdk/pkg/compiler/lib'
];

// This constant is referenced in README.md for auto-snapshotting.
const _autosnapshottingArg =
'--autosnapshotting=thresholdMb=200,increaseMb=100,dir=/Users/polinach/Downloads/analyzer_snapshots,dirLimitMb=10000,delaySec=20';

0 comments on commit 5f1e7ad

Please sign in to comment.