[flutter_tool] Report rss high watermark in command analytics events (#40988)

This commit is contained in:
Zachary Anderson 2019-09-25 11:26:06 -07:00 committed by GitHub
parent 9c6f11d5d1
commit 5142238c85
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 89 additions and 45 deletions

View file

@ -26,7 +26,7 @@
/// increase the API surface that we have to test in Flutter tools, and the APIs
/// in `dart:io` can sometimes be hard to use in tests.
import 'dart:async';
import 'dart:io' as io show exit, IOSink, Process, ProcessSignal, stderr, stdin, Stdout, stdout;
import 'dart:io' as io show exit, IOSink, Process, ProcessInfo, ProcessSignal, stderr, stdin, Stdout, stdout;
import 'package:meta/meta.dart';
@ -62,6 +62,7 @@ export 'dart:io'
// Platform NO! use `platform.dart`
Process,
ProcessException,
// ProcessInfo, NO! use `io.dart`
ProcessResult,
// ProcessSignal NO! Use [ProcessSignal] below.
ProcessStartMode,
@ -188,3 +189,25 @@ Stdio get stdio => context.get<Stdio>() ?? const Stdio();
io.Stdout get stdout => stdio.stdout;
Stream<List<int>> get stdin => stdio.stdin;
io.IOSink get stderr => stdio.stderr;
/// An overridable version of io.ProcessInfo.
abstract class ProcessInfo {
factory ProcessInfo() => _DefaultProcessInfo();
static ProcessInfo get instance => context.get<ProcessInfo>();
int get currentRss;
int get maxRss;
}
ProcessInfo get processInfo => ProcessInfo.instance;
/// The default implementation of [ProcessInfo], which uses [io.ProcessInfo].
class _DefaultProcessInfo implements ProcessInfo {
@override
int get currentRss => io.ProcessInfo.currentRss;
@override
int get maxRss => io.ProcessInfo.maxRss;
}

View file

@ -104,6 +104,7 @@ Future<T> runInContext<T>(
MacOSWorkflow: () => const MacOSWorkflow(),
MDnsObservatoryDiscovery: () => MDnsObservatoryDiscovery(),
OperatingSystemUtils: () => OperatingSystemUtils(),
ProcessInfo: () => ProcessInfo(),
ProcessUtils: () => ProcessUtils(),
SimControl: () => SimControl(),
Stdio: () => const Stdio(),

View file

@ -153,4 +153,20 @@ class BuildEvent extends UsageEvent {
class CommandResultEvent extends UsageEvent {
CommandResultEvent(String commandPath, FlutterCommandResult result)
: super(commandPath, result?.toString() ?? 'unspecified');
@override
void send() {
int maxRss;
try {
maxRss = processInfo.maxRss;
} catch (e) {
// If grabbing the maxRss fails for some reason, just leave it off the
// event.
}
final Map<String, String> parameters = _useCdKeys(<CustomDimensions, String>{
if (maxRss != null)
CustomDimensions.commandResultEventMaxRss: maxRss.toString(),
});
flutterUsage.sendEvent(category, parameter, parameters: parameters);
}
}

View file

@ -54,6 +54,7 @@ enum CustomDimensions {
commandBuildAppBundleTargetPlatform, // cd41
commandBuildAppBundleBuildMode, // cd42
buildEventError, // cd43
commandResultEventMaxRss, // cd44
}
String cdKey(CustomDimensions cd) => 'cd${cd.index + 1}';

View file

@ -3,6 +3,7 @@
// found in the LICENSE file.
import 'package:flutter_tools/src/base/common.dart';
import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/time.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/reporting/reporting.dart';
@ -19,16 +20,20 @@ void main() {
MockitoCache cache;
MockitoUsage usage;
MockClock clock;
MockProcessInfo mockProcessInfo;
List<int> mockTimes;
setUp(() {
cache = MockitoCache();
usage = MockitoUsage();
clock = MockClock();
mockProcessInfo = MockProcessInfo();
when(usage.isFirstRun).thenReturn(false);
when(clock.now()).thenAnswer(
(Invocation _) => DateTime.fromMillisecondsSinceEpoch(mockTimes.removeAt(0))
);
when(mockProcessInfo.maxRss).thenReturn(10);
});
testUsingContext('honors shouldUpdateCache false', () async {
@ -49,7 +54,15 @@ void main() {
Cache: () => cache,
});
testUsingContext('reports command that results in success', () async {
void testUsingCommandContext(String testName, Function testBody) {
testUsingContext(testName, testBody, overrides: <Type, Generator>{
ProcessInfo: () => mockProcessInfo,
SystemClock: () => clock,
Usage: () => usage,
});
}
testUsingCommandContext('reports command that results in success', () async {
// Crash if called a third time which is unexpected.
mockTimes = <int>[1000, 2000];
@ -61,14 +74,10 @@ void main() {
await flutterCommand.run();
verify(usage.sendCommand(captureAny, parameters: captureAnyNamed('parameters')));
verify(usage.sendEvent(captureAny, 'success'));
},
overrides: <Type, Generator>{
SystemClock: () => clock,
Usage: () => usage,
verify(usage.sendEvent(captureAny, 'success', parameters: captureAnyNamed('parameters')));
});
testUsingContext('reports command that results in warning', () async {
testUsingCommandContext('reports command that results in warning', () async {
// Crash if called a third time which is unexpected.
mockTimes = <int>[1000, 2000];
@ -80,14 +89,10 @@ void main() {
await flutterCommand.run();
verify(usage.sendCommand(captureAny, parameters: captureAnyNamed('parameters')));
verify(usage.sendEvent(captureAny, 'warning'));
},
overrides: <Type, Generator>{
SystemClock: () => clock,
Usage: () => usage,
verify(usage.sendEvent(captureAny, 'warning', parameters: captureAnyNamed('parameters')));
});
testUsingContext('reports command that results in failure', () async {
testUsingCommandContext('reports command that results in failure', () async {
// Crash if called a third time which is unexpected.
mockTimes = <int>[1000, 2000];
@ -101,15 +106,11 @@ void main() {
await flutterCommand.run();
} on ToolExit {
verify(usage.sendCommand(captureAny, parameters: captureAnyNamed('parameters')));
verify(usage.sendEvent(captureAny, 'fail'));
verify(usage.sendEvent(captureAny, 'fail', parameters: captureAnyNamed('parameters')));
}
},
overrides: <Type, Generator>{
SystemClock: () => clock,
Usage: () => usage,
});
testUsingContext('reports command that results in error', () async {
testUsingCommandContext('reports command that results in error', () async {
// Crash if called a third time which is unexpected.
mockTimes = <int>[1000, 2000];
@ -125,15 +126,32 @@ void main() {
fail('Mock should make this fail');
} on ToolExit {
verify(usage.sendCommand(captureAny, parameters: captureAnyNamed('parameters')));
verify(usage.sendEvent(captureAny, 'fail'));
verify(usage.sendEvent(captureAny, 'fail', parameters: captureAnyNamed('parameters')));
}
},
overrides: <Type, Generator>{
SystemClock: () => clock,
Usage: () => usage,
});
testUsingContext('report execution timing by default', () async {
testUsingCommandContext('reports maxRss', () async {
// Crash if called a third time which is unexpected.
mockTimes = <int>[1000, 2000];
final DummyFlutterCommand flutterCommand = DummyFlutterCommand(
commandFunction: () async {
return const FlutterCommandResult(ExitStatus.success);
}
);
await flutterCommand.run();
verify(usage.sendCommand(captureAny, parameters: captureAnyNamed('parameters')));
expect(verify(usage.sendEvent(
any,
'success',
parameters: captureAnyNamed('parameters'),
)).captured[0],
containsPair(cdKey(CustomDimensions.commandResultEventMaxRss),
mockProcessInfo.maxRss.toString()));
});
testUsingCommandContext('report execution timing by default', () async {
// Crash if called a third time which is unexpected.
mockTimes = <int>[1000, 2000];
@ -152,13 +170,9 @@ void main() {
null,
],
);
},
overrides: <Type, Generator>{
SystemClock: () => clock,
Usage: () => usage,
});
testUsingContext('no timing report without usagePath', () async {
testUsingCommandContext('no timing report without usagePath', () async {
// Crash if called a third time which is unexpected.
mockTimes = <int>[1000, 2000];
@ -169,13 +183,9 @@ void main() {
verifyNever(usage.sendTiming(
any, any, any,
label: anyNamed('label')));
},
overrides: <Type, Generator>{
SystemClock: () => clock,
Usage: () => usage,
});
testUsingContext('report additional FlutterCommandResult data', () async {
testUsingCommandContext('report additional FlutterCommandResult data', () async {
// Crash if called a third time which is unexpected.
mockTimes = <int>[1000, 2000];
@ -202,13 +212,9 @@ void main() {
'success-blah1-blah2-blah3',
],
);
},
overrides: <Type, Generator>{
SystemClock: () => clock,
Usage: () => usage,
});
testUsingContext('report failed execution timing too', () async {
testUsingCommandContext('report failed execution timing too', () async {
// Crash if called a third time which is unexpected.
mockTimes = <int>[1000, 2000];
@ -238,10 +244,6 @@ void main() {
],
);
}
},
overrides: <Type, Generator>{
SystemClock: () => clock,
Usage: () => usage,
});
});
}
@ -261,3 +263,4 @@ class FakeCommand extends FlutterCommand {
}
class MockVersion extends Mock implements FlutterVersion {}
class MockProcessInfo extends Mock implements ProcessInfo {}