profiling_summarizer.dart 5.39 KB
Newer Older
1 2 3 4 5 6 7
// 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';

8 9 10 11 12 13 14 15 16
/// 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,
};
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35

// 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,
}

36
/// Summarizes [TimelineEvents]s corresponding to [kProfilingEvents] category.
37
///
38
/// A sample event (some fields have been omitted for brevity):
39 40
/// ```
///     {
41
///      "category": "embedder",
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
///      "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) {
61
      assert(kProfilingEvents.contains(event.name));
62 63
      final ProfileType type = _getProfileType(event.name);
      eventsByType[type] ??= <TimelineEvent>[];
64
      eventsByType[type]!.add(event);
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
    }
    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)) {
97
      return eventByType[profileType]!.isNotEmpty;
98 99 100 101 102 103 104
    } else {
      return false;
    }
  }

  /// Computes the average of the `profileType` over the recorded events.
  double computeAverage(ProfileType profileType) {
105
    final List<TimelineEvent> events = eventByType[profileType]!;
106 107 108 109 110 111 112 113 114
    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) {
115
    final List<TimelineEvent> events = eventByType[profileType]!;
116 117 118 119 120 121 122
    assert(events.isNotEmpty);
    final List<double> doubles = events
        .map((TimelineEvent e) => _getProfileValue(profileType, e))
        .toList();
    return findPercentile(doubles, percentile);
  }

123
  static ProfileType _getProfileType(String? eventName) {
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
    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) {
151 152
    assert(e.arguments!.containsKey(argKey));
    final dynamic argVal = e.arguments![argKey];
153 154 155 156
    assert(argVal is String);
    return double.parse(argVal as String);
  }
}