mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
505 lines
25 KiB
Dart
505 lines
25 KiB
Dart
// 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 'dart:convert' show JsonEncoder, json;
|
|
import 'dart:math' as math;
|
|
|
|
import 'package:file/file.dart';
|
|
import 'package:path/path.dart' as path;
|
|
|
|
import 'common.dart';
|
|
import 'frame_request_pending_latency_summarizer.dart';
|
|
import 'gc_summarizer.dart';
|
|
import 'gpu_sumarizer.dart';
|
|
import 'percentile_utils.dart';
|
|
import 'profiling_summarizer.dart';
|
|
import 'raster_cache_summarizer.dart';
|
|
import 'refresh_rate_summarizer.dart';
|
|
import 'scene_display_lag_summarizer.dart';
|
|
import 'timeline.dart';
|
|
import 'vsync_frame_lag_summarizer.dart';
|
|
|
|
const JsonEncoder _prettyEncoder = JsonEncoder.withIndent(' ');
|
|
|
|
const String _kEmptyDurationMessage = r'''
|
|
The TimelineSummary had no events to summarize.
|
|
|
|
This can happen if the timeline summarization covered too short of a period
|
|
or if the driver script failed to interact with the application to generate
|
|
events. For example, if your driver script contained only a "driver.scroll()"
|
|
command but the app under test was not scrollable then no events would be
|
|
generated by the interaction.
|
|
''';
|
|
|
|
/// The maximum amount of time considered safe to spend for a frame's build
|
|
/// phase. Anything past that is in the danger of missing the frame at 60FPS.
|
|
///
|
|
/// This is a hard-coded number and does not take into account the real device
|
|
/// frame rate. Prefer using percentiles on the total build or raster time
|
|
/// than metrics based on this value.
|
|
const Duration kBuildBudget = Duration(milliseconds: 16);
|
|
|
|
/// The name of the framework frame build events we need to filter or extract.
|
|
const String kBuildFrameEventName = 'Frame';
|
|
|
|
/// The name of the engine frame rasterization events we need to filter or extract.
|
|
const String kRasterizeFrameEventName = 'GPURasterizer::Draw';
|
|
|
|
/// Extracts statistics from a [Timeline].
|
|
class TimelineSummary {
|
|
/// Creates a timeline summary given a full timeline object.
|
|
TimelineSummary.summarize(this._timeline);
|
|
|
|
final Timeline _timeline;
|
|
|
|
/// Average amount of time spent per frame in the framework building widgets,
|
|
/// updating layout, painting and compositing.
|
|
///
|
|
/// Throws a [StateError] if this summary contains no timeline events.
|
|
double computeAverageFrameBuildTimeMillis() {
|
|
return _averageInMillis(_extractFrameDurations());
|
|
}
|
|
|
|
/// The [p]-th percentile frame rasterization time in milliseconds.
|
|
///
|
|
/// Throws a [StateError] if this summary contains no timeline events.
|
|
double computePercentileFrameBuildTimeMillis(double p) {
|
|
return _percentileInMillis(_extractFrameDurations(), p);
|
|
}
|
|
|
|
/// The longest frame build time in milliseconds.
|
|
///
|
|
/// Throws a [StateError] if this summary contains no timeline events.
|
|
double computeWorstFrameBuildTimeMillis() {
|
|
return _maxInMillis(_extractFrameDurations());
|
|
}
|
|
|
|
/// The number of frames that missed the [kBuildBudget] and therefore are
|
|
/// in the danger of missing frames.
|
|
///
|
|
/// This does not take into account the real device frame rate. Prefer using
|
|
/// [computePercentileFrameBuildTimeMillis] for evaluating performance.
|
|
int computeMissedFrameBuildBudgetCount() {
|
|
return _extractFrameDurations()
|
|
.where((Duration duration) => duration > kBuildBudget)
|
|
.length;
|
|
}
|
|
|
|
/// Average amount of time spent per frame in the engine rasterizer.
|
|
///
|
|
/// Throws a [StateError] if this summary contains no timeline events.
|
|
double computeAverageFrameRasterizerTimeMillis() {
|
|
return _averageInMillis(_extractGpuRasterizerDrawDurations());
|
|
}
|
|
|
|
/// Standard deviation amount of time spent per frame in the engine rasterizer.
|
|
///
|
|
/// Throws a [StateError] if this summary contains no timeline events.
|
|
double computeStandardDeviationFrameRasterizerTimeMillis() {
|
|
final List<Duration> durations = _extractGpuRasterizerDrawDurations();
|
|
final double average = _averageInMillis(durations);
|
|
double tally = 0.0;
|
|
for (final Duration duration in durations) {
|
|
final double time = duration.inMicroseconds.toDouble() / 1000.0;
|
|
tally += (average - time).abs();
|
|
}
|
|
return tally / durations.length;
|
|
}
|
|
|
|
/// The longest frame rasterization time in milliseconds.
|
|
///
|
|
/// Throws a [StateError] if this summary contains no timeline events.
|
|
double computeWorstFrameRasterizerTimeMillis() {
|
|
return _maxInMillis(_extractGpuRasterizerDrawDurations());
|
|
}
|
|
|
|
/// The [p]-th percentile frame rasterization time in milliseconds.
|
|
///
|
|
/// Throws a [StateError] if this summary contains no timeline events.
|
|
double computePercentileFrameRasterizerTimeMillis(double p) {
|
|
return _percentileInMillis(_extractGpuRasterizerDrawDurations(), p);
|
|
}
|
|
|
|
/// The number of frames that missed the [kBuildBudget] on the raster thread
|
|
/// and therefore are in the danger of missing frames.
|
|
///
|
|
/// This does not take into account the real device frame rate. Prefer using
|
|
/// [computePercentileFrameRasterizerTimeMillis] for evaluating performance.
|
|
int computeMissedFrameRasterizerBudgetCount() {
|
|
return _extractGpuRasterizerDrawDurations()
|
|
.where((Duration duration) => duration > kBuildBudget)
|
|
.length;
|
|
}
|
|
|
|
/// The total number of frames recorded in the timeline.
|
|
int countFrames() => _extractFrameDurations().length;
|
|
|
|
/// The total number of rasterizer cycles recorded in the timeline.
|
|
int countRasterizations() => _extractGpuRasterizerDrawDurations().length;
|
|
|
|
/// The total number of old generation garbage collections recorded in the timeline.
|
|
int oldGenerationGarbageCollections() {
|
|
return _timeline.events!.where((TimelineEvent event) {
|
|
return event.category == 'GC' && event.name == 'CollectOldGeneration';
|
|
}).length;
|
|
}
|
|
|
|
/// The total number of new generation garbage collections recorded in the timeline.
|
|
int newGenerationGarbageCollections() {
|
|
return _timeline.events!.where((TimelineEvent event) {
|
|
return event.category == 'GC' && event.name == 'CollectNewGeneration';
|
|
}).length;
|
|
}
|
|
|
|
/// Encodes this summary as JSON.
|
|
///
|
|
/// Data ends with "_time_millis" means time in milliseconds and numbers in
|
|
/// the "frame_build_times", "frame_rasterizer_times", "frame_begin_times" and
|
|
/// "frame_rasterizer_begin_times" lists are in microseconds.
|
|
///
|
|
/// * "average_frame_build_time_millis": Average amount of time spent per
|
|
/// frame in the framework building widgets, updating layout, painting and
|
|
/// compositing.
|
|
/// See [computeAverageFrameBuildTimeMillis].
|
|
/// * "90th_percentile_frame_build_time_millis" and
|
|
/// "99th_percentile_frame_build_time_millis": The p-th percentile frame
|
|
/// rasterization time in milliseconds. 90 and 99-th percentile number is
|
|
/// usually a better metric to estimate worse cases. See discussion in
|
|
/// https://github.com/flutter/flutter/pull/19121#issuecomment-419520765
|
|
/// See [computePercentileFrameBuildTimeMillis].
|
|
/// * "worst_frame_build_time_millis": The longest frame build time.
|
|
/// See [computeWorstFrameBuildTimeMillis].
|
|
/// * "missed_frame_build_budget_count': The number of frames that missed
|
|
/// the [kBuildBudget] and therefore are in the danger of missing frames.
|
|
/// See [computeMissedFrameBuildBudgetCount]. Because [kBuildBudget] is a
|
|
/// constant, this does not represent a real missed frame count.
|
|
/// * "average_frame_rasterizer_time_millis": Average amount of time spent
|
|
/// per frame in the engine rasterizer.
|
|
/// See [computeAverageFrameRasterizerTimeMillis].
|
|
/// * "stddev_frame_rasterizer_time_millis": Standard deviation of the amount
|
|
/// of time spent per frame in the engine rasterizer.
|
|
/// See [computeStandardDeviationFrameRasterizerTimeMillis].
|
|
/// * "90th_percentile_frame_rasterizer_time_millis" and
|
|
/// "99th_percentile_frame_rasterizer_time_millis": The 90/99-th percentile
|
|
/// frame rasterization time in milliseconds.
|
|
/// See [computePercentileFrameRasterizerTimeMillis].
|
|
/// * "worst_frame_rasterizer_time_millis": The longest frame rasterization
|
|
/// time.
|
|
/// See [computeWorstFrameRasterizerTimeMillis].
|
|
/// * "missed_frame_rasterizer_budget_count": The number of frames that missed
|
|
/// the [kBuildBudget] on the raster thread and therefore are in the danger
|
|
/// of missing frames. See [computeMissedFrameRasterizerBudgetCount].
|
|
/// Because [kBuildBudget] is a constant, this does not represent a real
|
|
/// missed frame count.
|
|
/// * "frame_count": The total number of frames recorded in the timeline. This
|
|
/// is also the length of the "frame_build_times" and the "frame_begin_times"
|
|
/// lists.
|
|
/// See [countFrames].
|
|
/// * "frame_rasterizer_count": The total number of rasterizer cycles recorded
|
|
/// in the timeline. This is also the length of the "frame_rasterizer_times"
|
|
/// and the "frame_rasterizer_begin_times" lists.
|
|
/// See [countRasterizations].
|
|
/// * "frame_build_times": The build time of each frame, by tracking the
|
|
/// [TimelineEvent] with name [kBuildFrameEventName].
|
|
/// * "frame_rasterizer_times": The rasterize time of each frame, by tracking
|
|
/// the [TimelineEvent] with name [kRasterizeFrameEventName]
|
|
/// * "frame_begin_times": The build begin timestamp of each frame.
|
|
/// * "frame_rasterizer_begin_times": The rasterize begin time of each frame.
|
|
/// * "average_vsync_transitions_missed": Computes the average of the
|
|
/// `vsync_transitions_missed` over the lag events.
|
|
/// See [SceneDisplayLagSummarizer.computeAverageVsyncTransitionsMissed].
|
|
/// * "90th_percentile_vsync_transitions_missed" and
|
|
/// "99th_percentile_vsync_transitions_missed": The 90/99-th percentile
|
|
/// `vsync_transitions_missed` over the lag events.
|
|
/// See [SceneDisplayLagSummarizer.computePercentileVsyncTransitionsMissed].
|
|
/// * "average_vsync_frame_lag": Computes the average of the time between
|
|
/// platform vsync signal and the engine frame process start time.
|
|
/// See [VsyncFrameLagSummarizer.computeAverageVsyncFrameLag].
|
|
/// * "90th_percentile_vsync_frame_lag" and "99th_percentile_vsync_frame_lag":
|
|
/// The 90/99-th percentile delay between platform vsync signal and engine
|
|
/// frame process start time.
|
|
/// See [VsyncFrameLagSummarizer.computePercentileVsyncFrameLag].
|
|
/// * "average_layer_cache_count": The average of the values seen for the
|
|
/// count of the engine layer cache entries.
|
|
/// See [RasterCacheSummarizer.computeAverageLayerCount].
|
|
/// * "90th_percentile_layer_cache_count" and
|
|
/// "99th_percentile_layer_cache_count": The 90/99-th percentile values seen
|
|
/// for the count of the engine layer cache entries.
|
|
/// See [RasterCacheSummarizer.computePercentileLayerCount].
|
|
/// * "worst_layer_cache_count": The worst (highest) value seen for the
|
|
/// count of the engine layer cache entries.
|
|
/// See [RasterCacheSummarizer.computeWorstLayerCount].
|
|
/// * "average_layer_cache_memory": The average of the values seen for the
|
|
/// memory used for the engine layer cache entries, in megabytes.
|
|
/// See [RasterCacheSummarizer.computeAverageLayerMemory].
|
|
/// * "90th_percentile_layer_cache_memory" and
|
|
/// "99th_percentile_layer_cache_memory": The 90/99-th percentile values seen
|
|
/// for the memory used for the engine layer cache entries.
|
|
/// See [RasterCacheSummarizer.computePercentileLayerMemory].
|
|
/// * "worst_layer_cache_memory": The worst (highest) value seen for the
|
|
/// memory used for the engine layer cache entries.
|
|
/// See [RasterCacheSummarizer.computeWorstLayerMemory].
|
|
/// * "average_picture_cache_count": The average of the values seen for the
|
|
/// count of the engine picture cache entries.
|
|
/// See [RasterCacheSummarizer.computeAveragePictureCount].
|
|
/// * "90th_percentile_picture_cache_count" and
|
|
/// "99th_percentile_picture_cache_count": The 90/99-th percentile values seen
|
|
/// for the count of the engine picture cache entries.
|
|
/// See [RasterCacheSummarizer.computePercentilePictureCount].
|
|
/// * "worst_picture_cache_count": The worst (highest) value seen for the
|
|
/// count of the engine picture cache entries.
|
|
/// See [RasterCacheSummarizer.computeWorstPictureCount].
|
|
/// * "average_picture_cache_memory": The average of the values seen for the
|
|
/// memory used for the engine picture cache entries, in megabytes.
|
|
/// See [RasterCacheSummarizer.computeAveragePictureMemory].
|
|
/// * "90th_percentile_picture_cache_memory" and
|
|
/// "99th_percentile_picture_cache_memory": The 90/99-th percentile values seen
|
|
/// for the memory used for the engine picture cache entries.
|
|
/// See [RasterCacheSummarizer.computePercentilePictureMemory].
|
|
/// * "worst_picture_cache_memory": The worst (highest) value seen for the
|
|
/// memory used for the engine picture cache entries.
|
|
/// See [RasterCacheSummarizer.computeWorstPictureMemory].
|
|
/// * "average_frame_request_pending_latency": Computes the average of the delay
|
|
/// between `Animator::RequestFrame` and `Animator::BeginFrame` in the engine.
|
|
/// See [FrameRequestPendingLatencySummarizer.computeAverageFrameRequestPendingLatency].
|
|
/// * "90th_percentile_frame_request_pending_latency" and
|
|
/// "99th_percentile_frame_request_pending_latency": The 90/99-th percentile
|
|
/// delay between `Animator::RequestFrame` and `Animator::BeginFrame` in the
|
|
/// engine.
|
|
/// See [FrameRequestPendingLatencySummarizer.computePercentileFrameRequestPendingLatency].
|
|
Map<String, dynamic> get summaryJson {
|
|
final SceneDisplayLagSummarizer sceneDisplayLagSummarizer = _sceneDisplayLagSummarizer();
|
|
final VsyncFrameLagSummarizer vsyncFrameLagSummarizer = _vsyncFrameLagSummarizer();
|
|
final Map<String, dynamic> profilingSummary = _profilingSummarizer().summarize();
|
|
final RasterCacheSummarizer rasterCacheSummarizer = _rasterCacheSummarizer();
|
|
final GCSummarizer gcSummarizer = _gcSummarizer();
|
|
final RefreshRateSummary refreshRateSummary = RefreshRateSummary(vsyncEvents: _extractNamedEvents(kUIThreadVsyncProcessEvent));
|
|
final FrameRequestPendingLatencySummarizer frameRequestPendingLatencySummarizer = _frameRequestPendingLatencySummarizer();
|
|
final GpuSumarizer gpuSummarizer = _gpuSumarizer();
|
|
|
|
final Map<String, dynamic> timelineSummary = <String, dynamic>{
|
|
'average_frame_build_time_millis': computeAverageFrameBuildTimeMillis(),
|
|
'90th_percentile_frame_build_time_millis': computePercentileFrameBuildTimeMillis(90.0),
|
|
'99th_percentile_frame_build_time_millis': computePercentileFrameBuildTimeMillis(99.0),
|
|
'worst_frame_build_time_millis': computeWorstFrameBuildTimeMillis(),
|
|
'missed_frame_build_budget_count': computeMissedFrameBuildBudgetCount(),
|
|
'average_frame_rasterizer_time_millis': computeAverageFrameRasterizerTimeMillis(),
|
|
'stddev_frame_rasterizer_time_millis': computeStandardDeviationFrameRasterizerTimeMillis(),
|
|
'90th_percentile_frame_rasterizer_time_millis': computePercentileFrameRasterizerTimeMillis(90.0),
|
|
'99th_percentile_frame_rasterizer_time_millis': computePercentileFrameRasterizerTimeMillis(99.0),
|
|
'worst_frame_rasterizer_time_millis': computeWorstFrameRasterizerTimeMillis(),
|
|
'missed_frame_rasterizer_budget_count': computeMissedFrameRasterizerBudgetCount(),
|
|
'frame_count': countFrames(),
|
|
'frame_rasterizer_count': countRasterizations(),
|
|
'new_gen_gc_count': newGenerationGarbageCollections(),
|
|
'old_gen_gc_count': oldGenerationGarbageCollections(),
|
|
'frame_build_times': _extractFrameDurations()
|
|
.map<int>((Duration duration) => duration.inMicroseconds)
|
|
.toList(),
|
|
'frame_rasterizer_times': _extractGpuRasterizerDrawDurations()
|
|
.map<int>((Duration duration) => duration.inMicroseconds)
|
|
.toList(),
|
|
'frame_begin_times': _extractBeginTimestamps(kBuildFrameEventName)
|
|
.map<int>((Duration duration) => duration.inMicroseconds)
|
|
.toList(),
|
|
'frame_rasterizer_begin_times': _extractBeginTimestamps(kRasterizeFrameEventName)
|
|
.map<int>((Duration duration) => duration.inMicroseconds)
|
|
.toList(),
|
|
'average_vsync_transitions_missed': sceneDisplayLagSummarizer.computeAverageVsyncTransitionsMissed(),
|
|
'90th_percentile_vsync_transitions_missed': sceneDisplayLagSummarizer.computePercentileVsyncTransitionsMissed(90.0),
|
|
'99th_percentile_vsync_transitions_missed': sceneDisplayLagSummarizer.computePercentileVsyncTransitionsMissed(99.0),
|
|
'average_vsync_frame_lag': vsyncFrameLagSummarizer.computeAverageVsyncFrameLag(),
|
|
'90th_percentile_vsync_frame_lag': vsyncFrameLagSummarizer.computePercentileVsyncFrameLag(90.0),
|
|
'99th_percentile_vsync_frame_lag': vsyncFrameLagSummarizer.computePercentileVsyncFrameLag(99.0),
|
|
'average_layer_cache_count': rasterCacheSummarizer.computeAverageLayerCount(),
|
|
'90th_percentile_layer_cache_count': rasterCacheSummarizer.computePercentileLayerCount(90.0),
|
|
'99th_percentile_layer_cache_count': rasterCacheSummarizer.computePercentileLayerCount(99.0),
|
|
'average_frame_request_pending_latency': frameRequestPendingLatencySummarizer.computeAverageFrameRequestPendingLatency(),
|
|
'90th_percentile_frame_request_pending_latency': frameRequestPendingLatencySummarizer.computePercentileFrameRequestPendingLatency(90.0),
|
|
'99th_percentile_frame_request_pending_latency': frameRequestPendingLatencySummarizer.computePercentileFrameRequestPendingLatency(99.0),
|
|
'worst_layer_cache_count': rasterCacheSummarizer.computeWorstLayerCount(),
|
|
'average_layer_cache_memory': rasterCacheSummarizer.computeAverageLayerMemory(),
|
|
'90th_percentile_layer_cache_memory': rasterCacheSummarizer.computePercentileLayerMemory(90.0),
|
|
'99th_percentile_layer_cache_memory': rasterCacheSummarizer.computePercentileLayerMemory(99.0),
|
|
'worst_layer_cache_memory': rasterCacheSummarizer.computeWorstLayerMemory(),
|
|
'average_picture_cache_count': rasterCacheSummarizer.computeAveragePictureCount(),
|
|
'90th_percentile_picture_cache_count': rasterCacheSummarizer.computePercentilePictureCount(90.0),
|
|
'99th_percentile_picture_cache_count': rasterCacheSummarizer.computePercentilePictureCount(99.0),
|
|
'worst_picture_cache_count': rasterCacheSummarizer.computeWorstPictureCount(),
|
|
'average_picture_cache_memory': rasterCacheSummarizer.computeAveragePictureMemory(),
|
|
'90th_percentile_picture_cache_memory': rasterCacheSummarizer.computePercentilePictureMemory(90.0),
|
|
'99th_percentile_picture_cache_memory': rasterCacheSummarizer.computePercentilePictureMemory(99.0),
|
|
'worst_picture_cache_memory': rasterCacheSummarizer.computeWorstPictureMemory(),
|
|
'total_ui_gc_time': gcSummarizer.totalGCTimeMillis,
|
|
'30hz_frame_percentage': refreshRateSummary.percentageOf30HzFrames,
|
|
'60hz_frame_percentage': refreshRateSummary.percentageOf60HzFrames,
|
|
'80hz_frame_percentage': refreshRateSummary.percentageOf80HzFrames,
|
|
'90hz_frame_percentage': refreshRateSummary.percentageOf90HzFrames,
|
|
'120hz_frame_percentage': refreshRateSummary.percentageOf120HzFrames,
|
|
'illegal_refresh_rate_frame_count': refreshRateSummary.framesWithIllegalRefreshRate.length,
|
|
'average_gpu_frame_time': gpuSummarizer.computeAverageGPUTime(),
|
|
'90th_percentile_gpu_frame_time': gpuSummarizer.computePercentileGPUTime(90.0),
|
|
'99th_percentile_gpu_frame_time': gpuSummarizer.computePercentileGPUTime(99.0),
|
|
'worst_gpu_frame_time': gpuSummarizer.computeWorstGPUTime(),
|
|
};
|
|
|
|
timelineSummary.addAll(profilingSummary);
|
|
return timelineSummary;
|
|
}
|
|
|
|
/// Writes all of the recorded timeline data to a file.
|
|
///
|
|
/// By default, this will dump [summaryJson] to a companion file named
|
|
/// `$traceName.timeline_summary.json`. If you want to skip the summary, set
|
|
/// the `includeSummary` parameter to false.
|
|
///
|
|
/// See also:
|
|
///
|
|
/// * [Timeline.fromJson], which explains detail about the timeline data.
|
|
Future<void> writeTimelineToFile(
|
|
String traceName, {
|
|
String? destinationDirectory,
|
|
bool pretty = false,
|
|
bool includeSummary = true,
|
|
}) async {
|
|
destinationDirectory ??= testOutputsDirectory;
|
|
await fs.directory(destinationDirectory).create(recursive: true);
|
|
final File file = fs.file(path.join(destinationDirectory, '$traceName.timeline.json'));
|
|
await file.writeAsString(_encodeJson(_timeline.json, pretty));
|
|
|
|
if (includeSummary) {
|
|
await _writeSummaryToFile(traceName, destinationDirectory: destinationDirectory, pretty: pretty);
|
|
}
|
|
}
|
|
|
|
Future<void> _writeSummaryToFile(
|
|
String traceName, {
|
|
required String destinationDirectory,
|
|
bool pretty = false,
|
|
}) async {
|
|
await fs.directory(destinationDirectory).create(recursive: true);
|
|
final File file = fs.file(path.join(destinationDirectory, '$traceName.timeline_summary.json'));
|
|
await file.writeAsString(_encodeJson(summaryJson, pretty));
|
|
}
|
|
|
|
String _encodeJson(Map<String, dynamic> jsonObject, bool pretty) {
|
|
return pretty
|
|
? _prettyEncoder.convert(jsonObject)
|
|
: json.encode(jsonObject);
|
|
}
|
|
|
|
List<TimelineEvent> _extractNamedEvents(String name) {
|
|
return _timeline.events!
|
|
.where((TimelineEvent event) => event.name == name)
|
|
.toList();
|
|
}
|
|
|
|
List<TimelineEvent> _extractEventsWithNames(Set<String> names) {
|
|
return _timeline.events!
|
|
.where((TimelineEvent event) => names.contains(event.name))
|
|
.toList();
|
|
}
|
|
|
|
List<Duration> _extractDurations(
|
|
String name,
|
|
Duration Function(TimelineEvent beginEvent, TimelineEvent endEvent) extractor,
|
|
) {
|
|
final List<Duration> result = <Duration>[];
|
|
final List<TimelineEvent> events = _extractNamedEvents(name);
|
|
|
|
// Timeline does not guarantee that the first event is the "begin" event.
|
|
TimelineEvent? begin;
|
|
for (final TimelineEvent event in events) {
|
|
if (event.phase == 'B' || event.phase == 'b') {
|
|
begin = event;
|
|
} else {
|
|
if (begin != null) {
|
|
result.add(extractor(begin, event));
|
|
// each begin only gets used once.
|
|
begin = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/// Extracts Duration list that are reported as a pair of begin/end events.
|
|
///
|
|
/// Extracts Duration of events by looking for events with the name and phase
|
|
/// begin ("ph": "B"). This routine assumes that the next event with the same
|
|
/// name is phase end ("ph": "E"), but it's not examined.
|
|
/// "SceneDisplayLag" event is an exception, with phase ("ph") labeled
|
|
/// 'b' and 'e', meaning begin and end phase for an async event.
|
|
/// See [SceneDisplayLagSummarizer].
|
|
/// See: https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU
|
|
List<Duration> _extractBeginEndEvents(String name) {
|
|
return _extractDurations(
|
|
name,
|
|
(TimelineEvent beginEvent, TimelineEvent endEvent) {
|
|
return Duration(microseconds: endEvent.timestampMicros! - beginEvent.timestampMicros!);
|
|
},
|
|
);
|
|
}
|
|
|
|
List<Duration> _extractBeginTimestamps(String name) {
|
|
final List<Duration> result = _extractDurations(
|
|
name,
|
|
(TimelineEvent beginEvent, TimelineEvent endEvent) {
|
|
return Duration(microseconds: beginEvent.timestampMicros!);
|
|
},
|
|
);
|
|
|
|
// Align timestamps so the first event is at 0.
|
|
for (int i = result.length - 1; i >= 0; i -= 1) {
|
|
result[i] = result[i] - result[0];
|
|
}
|
|
return result;
|
|
}
|
|
|
|
double _averageInMillis(List<Duration> durations) {
|
|
if (durations.isEmpty) {
|
|
throw StateError(_kEmptyDurationMessage);
|
|
}
|
|
final double total = durations.fold<double>(0.0, (double t, Duration duration) => t + duration.inMicroseconds.toDouble() / 1000.0);
|
|
return total / durations.length;
|
|
}
|
|
|
|
double _percentileInMillis(List<Duration> durations, double percentile) {
|
|
if (durations.isEmpty) {
|
|
throw StateError(_kEmptyDurationMessage);
|
|
}
|
|
assert(percentile >= 0.0 && percentile <= 100.0);
|
|
final List<double> doubles = durations.map<double>((Duration duration) => duration.inMicroseconds.toDouble() / 1000.0).toList();
|
|
return findPercentile(doubles, percentile);
|
|
}
|
|
|
|
double _maxInMillis(List<Duration> durations) {
|
|
if (durations.isEmpty) {
|
|
throw StateError(_kEmptyDurationMessage);
|
|
}
|
|
return durations
|
|
.map<double>((Duration duration) => duration.inMicroseconds.toDouble() / 1000.0)
|
|
.reduce(math.max);
|
|
}
|
|
|
|
SceneDisplayLagSummarizer _sceneDisplayLagSummarizer() => SceneDisplayLagSummarizer(_extractNamedEvents(kSceneDisplayLagEvent));
|
|
|
|
List<Duration> _extractGpuRasterizerDrawDurations() => _extractBeginEndEvents(kRasterizeFrameEventName);
|
|
|
|
ProfilingSummarizer _profilingSummarizer() => ProfilingSummarizer.fromEvents(_extractEventsWithNames(kProfilingEvents));
|
|
|
|
List<Duration> _extractFrameDurations() => _extractBeginEndEvents(kBuildFrameEventName);
|
|
|
|
VsyncFrameLagSummarizer _vsyncFrameLagSummarizer() => VsyncFrameLagSummarizer(_extractEventsWithNames(kVsyncTimelineEventNames));
|
|
|
|
RasterCacheSummarizer _rasterCacheSummarizer() => RasterCacheSummarizer(_extractNamedEvents(kRasterCacheEvent));
|
|
|
|
FrameRequestPendingLatencySummarizer _frameRequestPendingLatencySummarizer() => FrameRequestPendingLatencySummarizer(_extractNamedEvents(kFrameRequestPendingEvent));
|
|
|
|
GCSummarizer _gcSummarizer() => GCSummarizer.fromEvents(_extractEventsWithNames(kGCRootEvents));
|
|
|
|
GpuSumarizer _gpuSumarizer() => GpuSumarizer(_extractEventsWithNames(GpuSumarizer.kGpuEvents));
|
|
}
|