Unverified Commit 60cfe495 authored by Kaushik Iska's avatar Kaushik Iska Committed by GitHub

Timeline summary contains CPU, GPU and Memory usage (#58820)

As of flutter.dev/go/engine-cpu-profiling, we collect the CPU and
Memory usage. With work being done to collect GPU usage on iOS as well.

This adds them to the timeline summary.

Fixes: https://github.com/flutter/flutter/issues/58803
parent 4aea793c
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/// Returns the [p]-th percentile element from the [doubles].
/// `List<doubles>` will be sorted.
double findPercentile(List<double> doubles, double p) {
assert(doubles.isNotEmpty);
doubles.sort();
return doubles[((doubles.length - 1) * (p / 100)).round()];
}
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'percentile_utils.dart';
import 'timeline.dart';
/// The catrgory shared by all profiling related timeline events.
const String kProfilingCategory = 'flutter::profiling';
// These field names need to be in-sync with:
// https://github.com/flutter/engine/blob/master/shell/profiling/sampling_profiler.cc
const String _kCpuProfile = 'CpuUsage';
const String _kGpuProfile = 'GpuUsage';
const String _kMemoryProfile = 'MemoryUsage';
/// Represents the supported profiling event types.
enum ProfileType {
/// Profiling events corresponding to CPU usage.
CPU,
/// Profiling events corresponding to GPU usage.
GPU,
/// Profiling events corresponding to memory usage.
Memory,
}
/// Summarizes [TimelineEvents]s corresponding to [kProfilingCategory] category.
///
/// A sample event (some fields have been omitted for brewity):
/// ```
/// {
/// "category": "flutter::profiling",
/// "name": "CpuUsage",
/// "ts": 121120,
/// "args": {
/// "total_cpu_usage": "20.5",
/// "num_threads": "6"
/// }
/// },
/// ```
/// This class provides methods to compute the average and percentile information
/// for supported profiles, i.e, CPU, Memory and GPU. Not all of these exist for
/// all the platforms.
class ProfilingSummarizer {
ProfilingSummarizer._(this.eventByType);
/// Creates a ProfilingSummarizer given the timeline events.
static ProfilingSummarizer fromEvents(List<TimelineEvent> profilingEvents) {
final Map<ProfileType, List<TimelineEvent>> eventsByType =
<ProfileType, List<TimelineEvent>>{};
for (final TimelineEvent event in profilingEvents) {
assert(event.category == kProfilingCategory);
final ProfileType type = _getProfileType(event.name);
eventsByType[type] ??= <TimelineEvent>[];
eventsByType[type].add(event);
}
return ProfilingSummarizer._(eventsByType);
}
/// Key is the type of profiling event, for e.g. CPU, GPU, Memory.
final Map<ProfileType, List<TimelineEvent>> eventByType;
/// Returns the average, 90th and 99th percentile summary of CPU, GPU and Memory
/// usage from the recorded events. Note: If a given profile type isn't available
/// for any reason, the map will not contain the said profile type.
Map<String, dynamic> summarize() {
final Map<String, dynamic> summary = <String, dynamic>{};
summary.addAll(_summarize(ProfileType.CPU, 'cpu_usage'));
summary.addAll(_summarize(ProfileType.GPU, 'gpu_usage'));
summary.addAll(_summarize(ProfileType.Memory, 'memory_usage'));
return summary;
}
Map<String, double> _summarize(ProfileType profileType, String name) {
final Map<String, double> summary = <String, double>{};
if (!hasProfilingInfo(profileType)) {
return summary;
}
summary['average_$name'] = computeAverage(profileType);
summary['90th_percentile_$name'] = computePercentile(profileType, 90);
summary['99th_percentile_$name'] = computePercentile(profileType, 99);
return summary;
}
/// Returns true if there are events in the timeline corresponding to [profileType].
bool hasProfilingInfo(ProfileType profileType) {
if (eventByType.containsKey(profileType)) {
return eventByType[profileType].isNotEmpty;
} else {
return false;
}
}
/// Computes the average of the `profileType` over the recorded events.
double computeAverage(ProfileType profileType) {
final List<TimelineEvent> events = eventByType[profileType];
assert(events.isNotEmpty);
final double total = events
.map((TimelineEvent e) => _getProfileValue(profileType, e))
.reduce((double a, double b) => a + b);
return total / events.length;
}
/// The [percentile]-th percentile `profileType` over the recorded events.
double computePercentile(ProfileType profileType, double percentile) {
final List<TimelineEvent> events = eventByType[profileType];
assert(events.isNotEmpty);
final List<double> doubles = events
.map((TimelineEvent e) => _getProfileValue(profileType, e))
.toList();
return findPercentile(doubles, percentile);
}
static ProfileType _getProfileType(String eventName) {
switch (eventName) {
case _kCpuProfile:
return ProfileType.CPU;
case _kGpuProfile:
return ProfileType.GPU;
case _kMemoryProfile:
return ProfileType.Memory;
default:
throw Exception('Invalid profiling event: $eventName.');
}
}
double _getProfileValue(ProfileType profileType, TimelineEvent e) {
switch (profileType) {
case ProfileType.CPU:
return _getArgValue('total_cpu_usage', e);
case ProfileType.GPU:
return _getArgValue('gpu_usage', e);
case ProfileType.Memory:
final double dirtyMem = _getArgValue('dirty_memory_usage', e);
final double ownedSharedMem =
_getArgValue('owned_shared_memory_usage', e);
return dirtyMem + ownedSharedMem;
}
throw Exception('Invalid $profileType.');
}
double _getArgValue(String argKey, TimelineEvent e) {
assert(e.arguments.containsKey(argKey));
final dynamic argVal = e.arguments[argKey];
assert(argVal is String);
return double.parse(argVal as String);
}
}
......@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'percentile_utils.dart';
import 'timeline.dart';
/// Key for SceneDisplayLag timeline events.
......@@ -9,15 +10,6 @@ const String kSceneDisplayLagEvent = 'SceneDisplayLag';
const String _kVsyncTransitionsMissed = 'vsync_transitions_missed';
/// Returns the [p]-th percentile element from the [doubles].
///
/// Note: [doubles] will be sorted.
double findPercentile(List<double> doubles, double p) {
assert(doubles.isNotEmpty);
doubles.sort();
return doubles[((doubles.length - 1) * (p / 100)).round()];
}
/// Summarizes [TimelineEvents]s corresponding to [kSceneDisplayLagEvent] events.
///
/// A sample event (some fields have been omitted for brewity):
......
......@@ -10,6 +10,8 @@ import 'package:file/file.dart';
import 'package:path/path.dart' as path;
import 'common.dart';
import 'percentile_utils.dart';
import 'profiling_summarizer.dart';
import 'scene_display_lag_summarizer.dart';
import 'timeline.dart';
......@@ -96,8 +98,9 @@ class TimelineSummary {
/// Encodes this summary as JSON.
Map<String, dynamic> get summaryJson {
final SceneDisplayLagSummarizer sceneDisplayLagSummarizer = _sceneDisplayLagSummarizer();
final Map<String, dynamic> profilingSummary = _profilingSummarizer().summarize();
return <String, dynamic>{
final Map<String, dynamic> timelineSummary = <String, dynamic>{
'average_frame_build_time_millis': computeAverageFrameBuildTimeMillis(),
'90th_percentile_frame_build_time_millis': computePercentileFrameBuildTimeMillis(90.0),
'99th_percentile_frame_build_time_millis': computePercentileFrameBuildTimeMillis(99.0),
......@@ -126,6 +129,9 @@ class TimelineSummary {
'90th_percentile_vsync_transitions_missed': sceneDisplayLagSummarizer.computePercentileVsyncTransitionsMissed(90.0),
'99th_percentile_vsync_transitions_missed': sceneDisplayLagSummarizer.computePercentileVsyncTransitionsMissed(99.0),
};
timelineSummary.addAll(profilingSummary);
return timelineSummary;
}
/// Writes all of the recorded timeline data to a file.
......@@ -164,6 +170,12 @@ class TimelineSummary {
.toList();
}
List<TimelineEvent> _extractCategorizedEvents(String category) {
return _timeline.events
.where((TimelineEvent event) => event.category == category)
.toList();
}
List<Duration> _extractDurations(
String name,
Duration extractor(TimelineEvent beginEvent, TimelineEvent endEvent),
......@@ -242,5 +254,7 @@ class TimelineSummary {
List<Duration> _extractGpuRasterizerDrawDurations() => _extractBeginEndEvents(kRasterizeFrameEventName);
ProfilingSummarizer _profilingSummarizer() => ProfilingSummarizer.fromEvents(_extractCategorizedEvents(kProfilingCategory));
List<Duration> _extractFrameDurations() => _extractBeginEndEvents(kBuildFrameEventName);
}
......@@ -7,6 +7,7 @@ import 'dart:convert' show json;
import 'package:file/file.dart';
import 'package:flutter_driver/flutter_driver.dart';
import 'package:flutter_driver/src/driver/common.dart';
import 'package:flutter_driver/src/driver/profiling_summarizer.dart';
import 'package:flutter_driver/src/driver/scene_display_lag_summarizer.dart';
import 'package:path/path.dart' as path;
......@@ -63,6 +64,25 @@ void main() {
}
};
Map<String, dynamic> cpuUsage(int timeStamp, double cpuUsage) => <String, dynamic>{
'cat': 'flutter::profiling',
'name': 'CpuUsage',
'ts': timeStamp,
'args': <String, String>{
'total_cpu_usage': cpuUsage.toString()
}
};
Map<String, dynamic> memoryUsage(int timeStamp, double dirty, double shared) => <String, dynamic>{
'cat': 'flutter::profiling',
'name': 'MemoryUsage',
'ts': timeStamp,
'args': <String, String>{
'owned_shared_memory_usage': shared.toString(),
'dirty_memory_usage': dirty.toString(),
}
};
List<Map<String, dynamic>> rasterizeTimeSequenceInMillis(List<int> sequence) {
final List<Map<String, dynamic>> result = <Map<String, dynamic>>[];
int t = 0;
......@@ -418,6 +438,8 @@ void main() {
lagBegin(1000, 4), lagEnd(2000, 4),
lagBegin(1200, 12), lagEnd(2400, 12),
lagBegin(4200, 8), lagEnd(9400, 8),
cpuUsage(5000, 20), cpuUsage(5010, 60),
memoryUsage(6000, 20, 40), memoryUsage(6100, 30, 45),
]).writeSummaryToFile('test', destinationDirectory: tempDir.path);
final String written =
await fs.file(path.join(tempDir.path, 'test.timeline_summary.json')).readAsString();
......@@ -440,7 +462,13 @@ void main() {
'frame_rasterizer_begin_times': <int>[0, 18000, 28000],
'average_vsync_transitions_missed': 8.0,
'90th_percentile_vsync_transitions_missed': 12.0,
'99th_percentile_vsync_transitions_missed': 12.0
'99th_percentile_vsync_transitions_missed': 12.0,
'average_cpu_usage': 40.0,
'90th_percentile_cpu_usage': 60.0,
'99th_percentile_cpu_usage': 60.0,
'average_memory_usage': 67.5,
'90th_percentile_memory_usage': 75.0,
'99th_percentile_memory_usage': 75.0,
});
});
});
......@@ -495,5 +523,44 @@ void main() {
expect(summarizer.computePercentileVsyncTransitionsMissed(99), 4187.0);
});
});
group('ProfilingSummarizer tests', () {
ProfilingSummarizer summarize(List<Map<String, dynamic>> traceEvents) {
final Timeline timeline = Timeline.fromJson(<String, dynamic>{
'traceEvents': traceEvents,
});
return ProfilingSummarizer.fromEvents(timeline.events);
}
test('has_both_cpu_and_memory_usage', () async {
final ProfilingSummarizer summarizer = summarize(<Map<String, dynamic>>[
cpuUsage(0, 10),
memoryUsage(0, 6, 10),
cpuUsage(0, 12),
memoryUsage(0, 8, 40),
]);
expect(summarizer.computeAverage(ProfileType.CPU), 11.0);
expect(summarizer.computeAverage(ProfileType.Memory), 32.0);
});
test('has_only_memory_usage', () async {
final ProfilingSummarizer summarizer = summarize(<Map<String, dynamic>>[
memoryUsage(0, 6, 10),
memoryUsage(0, 8, 40),
]);
expect(summarizer.computeAverage(ProfileType.Memory), 32.0);
expect(summarizer.summarize().containsKey('average_cpu_usage'), false);
});
test('90th_percentile_cpu_usage', () async {
final ProfilingSummarizer summarizer = summarize(<Map<String, dynamic>>[
cpuUsage(0, 10), cpuUsage(1, 20),
cpuUsage(2, 20), cpuUsage(3, 80),
cpuUsage(4, 70), cpuUsage(4, 72),
cpuUsage(4, 85), cpuUsage(4, 100),
]);
expect(summarizer.computePercentile(ProfileType.CPU, 90), 85.0);
});
});
});
}
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