mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
157 lines
5.4 KiB
Dart
157 lines
5.4 KiB
Dart
// 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);
|
|
}
|
|
}
|