| // 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); |
| } |
| } |