1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
// 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.
// @dart = 2.8
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 brewity):
/// ```
/// {
/// "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;
}
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);
}
}