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