flutter/packages/flutter_driver/lib/src/timeline_summary.dart
Ian Hickson 4b401a3e75 Track the worst frame in perf tests (#3642)
* Track the worst frame in perf tests

* Use backticks in "/// Returns `null` if"
2016-05-03 14:00:10 -07:00

156 lines
5.3 KiB
Dart

// Copyright 2016 The Chromium 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:async';
import 'dart:convert' show JSON, JsonEncoder;
import 'dart:math' as math;
import 'package:file/file.dart';
import 'package:path/path.dart' as path;
import 'common.dart';
import 'timeline.dart';
const String _kDefaultDirectory = 'build';
final JsonEncoder _prettyEncoder = new JsonEncoder.withIndent(' ');
/// 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 as 60FPS.
const Duration kBuildBudget = const Duration(milliseconds: 8);
/// Extracts statistics from the event loop timeline.
class TimelineSummary {
TimelineSummary.summarize(this._timeline);
final Timeline _timeline;
/// Average amount of time spent per frame in the framework building widgets,
/// updating layout, painting and compositing.
///
/// Returns `null` if no frames were recorded.
double computeAverageFrameBuildTimeMillis() {
int totalBuildTimeMicros = 0;
int frameCount = 0;
for (TimedEvent event in _extractBeginFrameEvents()) {
frameCount++;
totalBuildTimeMicros += event.duration.inMicroseconds;
}
return frameCount > 0
? (totalBuildTimeMicros / frameCount) / 1000
: null;
}
/// Find amount of time spent in the framework building widgets,
/// updating layout, painting and compositing on worst frame.
///
/// Returns `null` if no frames were recorded.
double computeWorstFrameBuildTimeMillis() {
int maxBuildTimeMicros = 0;
int frameCount = 0;
for (TimedEvent event in _extractBeginFrameEvents()) {
frameCount++;
maxBuildTimeMicros = math.max(maxBuildTimeMicros, event.duration.inMicroseconds);
}
return frameCount > 0
? maxBuildTimeMicros / 1000
: null;
}
/// The total number of frames recorded in the timeline.
int countFrames() => _extractBeginFrameEvents().length;
/// The number of frames that missed the [frameBuildBudget] and therefore are
/// in the danger of missing frames.
///
/// See [kBuildBudget].
int computeMissedFrameBuildBudgetCount([Duration frameBuildBudget = kBuildBudget]) => _extractBeginFrameEvents()
.where((TimedEvent event) => event.duration > kBuildBudget)
.length;
/// Encodes this summary as JSON.
Map<String, dynamic> get summaryJson {
return <String, dynamic> {
'average_frame_build_time_millis': computeAverageFrameBuildTimeMillis(),
'worst_frame_build_time_millis': computeWorstFrameBuildTimeMillis(),
'missed_frame_build_budget_count': computeMissedFrameBuildBudgetCount(),
'frame_count': countFrames(),
'frame_build_times': _extractBeginFrameEvents()
.map((TimedEvent event) => event.duration.inMicroseconds)
.toList()
};
}
/// Writes all of the recorded timeline data to a file.
Future<Null> writeTimelineToFile(String traceName,
{String destinationDirectory: _kDefaultDirectory, bool pretty: false}) async {
await fs.directory(destinationDirectory).create(recursive: true);
File file = fs.file(path.join(destinationDirectory, '$traceName.timeline.json'));
await file.writeAsString(_encodeJson(_timeline.json, pretty));
}
/// Writes [summaryJson] to a file.
Future<Null> writeSummaryToFile(String traceName,
{String destinationDirectory: _kDefaultDirectory, bool pretty: false}) async {
await fs.directory(destinationDirectory).create(recursive: true);
File file = fs.file(path.join(destinationDirectory, '$traceName.timeline_summary.json'));
await file.writeAsString(_encodeJson(summaryJson, pretty));
}
String _encodeJson(Map<String, dynamic> json, bool pretty) {
return pretty
? _prettyEncoder.convert(json)
: JSON.encode(json);
}
List<TimelineEvent> _extractNamedEvents(String name) {
return _timeline.events
.where((TimelineEvent event) => event.name == name)
.toList();
}
/// Extracts timed events that are reported as a pair of begin/end events.
List<TimedEvent> _extractTimedBeginEndEvents(String name) {
List<TimedEvent> result = <TimedEvent>[];
// Timeline does not guarantee that the first event is the "begin" event.
Iterator<TimelineEvent> events = _extractNamedEvents(name)
.skipWhile((TimelineEvent evt) => evt.phase != 'B').iterator;
while(events.moveNext()) {
TimelineEvent beginEvent = events.current;
if (events.moveNext()) {
TimelineEvent endEvent = events.current;
result.add(new TimedEvent(
beginEvent.timestampMicros,
endEvent.timestampMicros
));
}
}
return result;
}
List<TimedEvent> _extractBeginFrameEvents() => _extractTimedBeginEndEvents('Engine::BeginFrame');
}
/// Timing information about an event that happened in the event loop.
class TimedEvent {
/// The timestamp when the event began.
final int beginTimeMicros;
/// The timestamp when the event ended.
final int endTimeMicros;
/// The duration of the event.
final Duration duration;
TimedEvent(int beginTimeMicros, int endTimeMicros)
: this.beginTimeMicros = beginTimeMicros,
this.endTimeMicros = endTimeMicros,
this.duration = new Duration(microseconds: endTimeMicros - beginTimeMicros);
}