| // 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/flutter/blob/main/engine/src/flutter/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 [TimelineEvent]s corresponding to [kProfilingEvents] category. | |
| /// | |
| /// A sample event (some fields have been omitted for brevity): | |
| /// ```json | |
| /// { | |
| /// "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 eventsByType = <ProfileType, List<TimelineEvent>>{}; | |
| for (final 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. 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 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 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) { | |
| return switch (eventName) { | |
| _kCpuProfile => ProfileType.CPU, | |
| _kGpuProfile => ProfileType.GPU, | |
| _kMemoryProfile => ProfileType.Memory, | |
| _ => 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); | |
| } | |
| } |