Unverified Commit 5142238c authored by Zachary Anderson's avatar Zachary Anderson Committed by GitHub

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

parent 9c6f11d5
...@@ -26,7 +26,7 @@ ...@@ -26,7 +26,7 @@
/// increase the API surface that we have to test in Flutter tools, and the APIs /// 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. /// in `dart:io` can sometimes be hard to use in tests.
import 'dart:async'; 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'; import 'package:meta/meta.dart';
...@@ -62,6 +62,7 @@ export 'dart:io' ...@@ -62,6 +62,7 @@ export 'dart:io'
// Platform NO! use `platform.dart` // Platform NO! use `platform.dart`
Process, Process,
ProcessException, ProcessException,
// ProcessInfo, NO! use `io.dart`
ProcessResult, ProcessResult,
// ProcessSignal NO! Use [ProcessSignal] below. // ProcessSignal NO! Use [ProcessSignal] below.
ProcessStartMode, ProcessStartMode,
...@@ -188,3 +189,25 @@ Stdio get stdio => context.get<Stdio>() ?? const Stdio(); ...@@ -188,3 +189,25 @@ Stdio get stdio => context.get<Stdio>() ?? const Stdio();
io.Stdout get stdout => stdio.stdout; io.Stdout get stdout => stdio.stdout;
Stream<List<int>> get stdin => stdio.stdin; Stream<List<int>> get stdin => stdio.stdin;
io.IOSink get stderr => stdio.stderr; 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;
}
...@@ -104,6 +104,7 @@ Future<T> runInContext<T>( ...@@ -104,6 +104,7 @@ Future<T> runInContext<T>(
MacOSWorkflow: () => const MacOSWorkflow(), MacOSWorkflow: () => const MacOSWorkflow(),
MDnsObservatoryDiscovery: () => MDnsObservatoryDiscovery(), MDnsObservatoryDiscovery: () => MDnsObservatoryDiscovery(),
OperatingSystemUtils: () => OperatingSystemUtils(), OperatingSystemUtils: () => OperatingSystemUtils(),
ProcessInfo: () => ProcessInfo(),
ProcessUtils: () => ProcessUtils(), ProcessUtils: () => ProcessUtils(),
SimControl: () => SimControl(), SimControl: () => SimControl(),
Stdio: () => const Stdio(), Stdio: () => const Stdio(),
......
...@@ -153,4 +153,20 @@ class BuildEvent extends UsageEvent { ...@@ -153,4 +153,20 @@ class BuildEvent extends UsageEvent {
class CommandResultEvent extends UsageEvent { class CommandResultEvent extends UsageEvent {
CommandResultEvent(String commandPath, FlutterCommandResult result) CommandResultEvent(String commandPath, FlutterCommandResult result)
: super(commandPath, result?.toString() ?? 'unspecified'); : 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);
}
} }
...@@ -54,6 +54,7 @@ enum CustomDimensions { ...@@ -54,6 +54,7 @@ enum CustomDimensions {
commandBuildAppBundleTargetPlatform, // cd41 commandBuildAppBundleTargetPlatform, // cd41
commandBuildAppBundleBuildMode, // cd42 commandBuildAppBundleBuildMode, // cd42
buildEventError, // cd43 buildEventError, // cd43
commandResultEventMaxRss, // cd44
} }
String cdKey(CustomDimensions cd) => 'cd${cd.index + 1}'; String cdKey(CustomDimensions cd) => 'cd${cd.index + 1}';
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'package:flutter_tools/src/base/common.dart'; 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/base/time.dart';
import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/reporting/reporting.dart'; import 'package:flutter_tools/src/reporting/reporting.dart';
...@@ -19,16 +20,20 @@ void main() { ...@@ -19,16 +20,20 @@ void main() {
MockitoCache cache; MockitoCache cache;
MockitoUsage usage; MockitoUsage usage;
MockClock clock; MockClock clock;
MockProcessInfo mockProcessInfo;
List<int> mockTimes; List<int> mockTimes;
setUp(() { setUp(() {
cache = MockitoCache(); cache = MockitoCache();
usage = MockitoUsage(); usage = MockitoUsage();
clock = MockClock(); clock = MockClock();
mockProcessInfo = MockProcessInfo();
when(usage.isFirstRun).thenReturn(false); when(usage.isFirstRun).thenReturn(false);
when(clock.now()).thenAnswer( when(clock.now()).thenAnswer(
(Invocation _) => DateTime.fromMillisecondsSinceEpoch(mockTimes.removeAt(0)) (Invocation _) => DateTime.fromMillisecondsSinceEpoch(mockTimes.removeAt(0))
); );
when(mockProcessInfo.maxRss).thenReturn(10);
}); });
testUsingContext('honors shouldUpdateCache false', () async { testUsingContext('honors shouldUpdateCache false', () async {
...@@ -49,7 +54,15 @@ void main() { ...@@ -49,7 +54,15 @@ void main() {
Cache: () => cache, 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. // Crash if called a third time which is unexpected.
mockTimes = <int>[1000, 2000]; mockTimes = <int>[1000, 2000];
...@@ -61,14 +74,10 @@ void main() { ...@@ -61,14 +74,10 @@ void main() {
await flutterCommand.run(); await flutterCommand.run();
verify(usage.sendCommand(captureAny, parameters: captureAnyNamed('parameters'))); verify(usage.sendCommand(captureAny, parameters: captureAnyNamed('parameters')));
verify(usage.sendEvent(captureAny, 'success')); verify(usage.sendEvent(captureAny, 'success', parameters: captureAnyNamed('parameters')));
},
overrides: <Type, Generator>{
SystemClock: () => clock,
Usage: () => usage,
}); });
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. // Crash if called a third time which is unexpected.
mockTimes = <int>[1000, 2000]; mockTimes = <int>[1000, 2000];
...@@ -80,14 +89,10 @@ void main() { ...@@ -80,14 +89,10 @@ void main() {
await flutterCommand.run(); await flutterCommand.run();
verify(usage.sendCommand(captureAny, parameters: captureAnyNamed('parameters'))); verify(usage.sendCommand(captureAny, parameters: captureAnyNamed('parameters')));
verify(usage.sendEvent(captureAny, 'warning')); verify(usage.sendEvent(captureAny, 'warning', parameters: captureAnyNamed('parameters')));
},
overrides: <Type, Generator>{
SystemClock: () => clock,
Usage: () => usage,
}); });
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. // Crash if called a third time which is unexpected.
mockTimes = <int>[1000, 2000]; mockTimes = <int>[1000, 2000];
...@@ -101,15 +106,11 @@ void main() { ...@@ -101,15 +106,11 @@ void main() {
await flutterCommand.run(); await flutterCommand.run();
} on ToolExit { } on ToolExit {
verify(usage.sendCommand(captureAny, parameters: captureAnyNamed('parameters'))); 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. // Crash if called a third time which is unexpected.
mockTimes = <int>[1000, 2000]; mockTimes = <int>[1000, 2000];
...@@ -125,15 +126,32 @@ void main() { ...@@ -125,15 +126,32 @@ void main() {
fail('Mock should make this fail'); fail('Mock should make this fail');
} on ToolExit { } on ToolExit {
verify(usage.sendCommand(captureAny, parameters: captureAnyNamed('parameters'))); 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. // Crash if called a third time which is unexpected.
mockTimes = <int>[1000, 2000]; mockTimes = <int>[1000, 2000];
...@@ -152,13 +170,9 @@ void main() { ...@@ -152,13 +170,9 @@ void main() {
null, 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. // Crash if called a third time which is unexpected.
mockTimes = <int>[1000, 2000]; mockTimes = <int>[1000, 2000];
...@@ -169,13 +183,9 @@ void main() { ...@@ -169,13 +183,9 @@ void main() {
verifyNever(usage.sendTiming( verifyNever(usage.sendTiming(
any, any, any, any, any, any,
label: anyNamed('label'))); 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. // Crash if called a third time which is unexpected.
mockTimes = <int>[1000, 2000]; mockTimes = <int>[1000, 2000];
...@@ -202,13 +212,9 @@ void main() { ...@@ -202,13 +212,9 @@ void main() {
'success-blah1-blah2-blah3', '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. // Crash if called a third time which is unexpected.
mockTimes = <int>[1000, 2000]; mockTimes = <int>[1000, 2000];
...@@ -238,10 +244,6 @@ void main() { ...@@ -238,10 +244,6 @@ void main() {
], ],
); );
} }
},
overrides: <Type, Generator>{
SystemClock: () => clock,
Usage: () => usage,
}); });
}); });
} }
...@@ -261,3 +263,4 @@ class FakeCommand extends FlutterCommand { ...@@ -261,3 +263,4 @@ class FakeCommand extends FlutterCommand {
} }
class MockVersion extends Mock implements FlutterVersion {} class MockVersion extends Mock implements FlutterVersion {}
class MockProcessInfo extends Mock implements ProcessInfo {}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment