blob: 0ed7dfd29d8c55a9dfc97b9419ce7f1d5030152d [file] [log] [blame]
// 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';
/// Profiling related timeline events.
///
/// We do not use a profiling category for these as all the dart timeline events
/// have the same profiling category "embedder".
const Set<String> kProfilingEvents = <String>{
_kCpuProfile,
_kGpuProfile,
_kMemoryProfile,
};
// 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 [kProfilingEvents] category.
///
/// A sample event (some fields have been omitted for brevity):
/// ```
/// {
/// "category": "embedder",
/// "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(kProfilingEvents.contains(event.name));
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;
}
}
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);
}
}