blob: d93bd3ec8e7f27f51ca1428173bd65458ee4cd48 [file] [log] [blame]
// 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 json;
import 'dart:math';
import 'package:file/file.dart';
import 'package:flutter_driver/flutter_driver.dart';
import 'package:flutter_driver/src/driver/profiling_summarizer.dart';
import 'package:flutter_driver/src/driver/refresh_rate_summarizer.dart';
import 'package:flutter_driver/src/driver/scene_display_lag_summarizer.dart';
import 'package:flutter_driver/src/driver/vsync_frame_lag_summarizer.dart';
import 'package:path/path.dart' as path;
import '../../common.dart';
void main() {
group('TimelineSummary', () {
TimelineSummary summarize(List<Map<String, dynamic>> testEvents) {
return TimelineSummary.summarize(Timeline.fromJson(<String, dynamic>{
'traceEvents': testEvents,
}));
}
Map<String, dynamic> frameBegin(int timeStamp) => <String, dynamic>{
'name': 'Frame',
'ph': 'B',
'ts': timeStamp,
};
Map<String, dynamic> frameEnd(int timeStamp) => <String, dynamic>{
'name': 'Frame',
'ph': 'E',
'ts': timeStamp,
};
Map<String, dynamic> begin(int timeStamp) => <String, dynamic>{
'name': 'GPURasterizer::Draw',
'ph': 'B',
'ts': timeStamp,
};
Map<String, dynamic> end(int timeStamp) => <String, dynamic>{
'name': 'GPURasterizer::Draw',
'ph': 'E',
'ts': timeStamp,
};
Map<String, dynamic> lagBegin(int timeStamp, int vsyncsMissed) => <String, dynamic>{
'name': 'SceneDisplayLag',
'ph': 'b',
'ts': timeStamp,
'args': <String, String>{
'vsync_transitions_missed': vsyncsMissed.toString()
}
};
Map<String, dynamic> lagEnd(int timeStamp, int vsyncsMissed) => <String, dynamic>{
'name': 'SceneDisplayLag',
'ph': 'e',
'ts': timeStamp,
'args': <String, String>{
'vsync_transitions_missed': vsyncsMissed.toString()
}
};
Map<String, dynamic> cpuUsage(int timeStamp, double cpuUsage) => <String, dynamic>{
'cat': 'embedder',
'name': 'CpuUsage',
'ts': timeStamp,
'args': <String, String>{
'total_cpu_usage': cpuUsage.toString()
}
};
Map<String, dynamic> memoryUsage(int timeStamp, double dirty, double shared) => <String, dynamic>{
'cat': 'embedder',
'name': 'MemoryUsage',
'ts': timeStamp,
'args': <String, String>{
'owned_shared_memory_usage': shared.toString(),
'dirty_memory_usage': dirty.toString(),
}
};
Map<String, dynamic> platformVsync(int timeStamp) => <String, dynamic>{
'name': 'VSYNC',
'ph': 'B',
'ts': timeStamp,
};
Map<String, dynamic> vsyncCallback(int timeStamp, {String phase = 'B', String startTime = '2750850055428', String endTime = '2750866722095'}) => <String, dynamic>{
'name': 'VsyncProcessCallback',
'ph': phase,
'ts': timeStamp,
'args': <String, dynamic>{
'StartTime': startTime,
'TargetTime': endTime,
}
};
List<Map<String, dynamic>> _genGC(String name, int count, int startTime, int timeDiff) {
int ts = startTime;
bool begin = true;
final List<Map<String, dynamic>> ret = <Map<String, dynamic>>[];
for (int i = 0; i < count; i++) {
ret.add(<String, dynamic>{
'name': name,
'cat': 'GC',
'tid': 19695,
'pid': 19650,
'ts': ts,
'tts': ts,
'ph': begin ? 'B' : 'E',
'args': <String, dynamic>{
'isolateGroupId': 'isolateGroups/10824099774666259225',
},
});
ts = ts + timeDiff;
begin = !begin;
}
return ret;
}
List<Map<String, dynamic>> newGenGC(int count, int startTime, int timeDiff) {
return _genGC('CollectNewGeneration', count, startTime, timeDiff);
}
List<Map<String, dynamic>> oldGenGC(int count, int startTime, int timeDiff) {
return _genGC('CollectOldGeneration', count, startTime, timeDiff);
}
List<Map<String, dynamic>> rasterizeTimeSequenceInMillis(List<int> sequence) {
final List<Map<String, dynamic>> result = <Map<String, dynamic>>[];
int t = 0;
for (final int duration in sequence) {
result.add(begin(t));
t += duration * 1000;
result.add(end(t));
}
return result;
}
group('frame_count', () {
test('counts frames', () {
expect(
summarize(<Map<String, dynamic>>[
frameBegin(1000), frameEnd(2000),
frameBegin(3000), frameEnd(5000),
]).countFrames(),
2,
);
});
});
group('average_frame_build_time_millis', () {
test('throws when there is no data', () {
expect(
() => summarize(<Map<String, dynamic>>[]).computeAverageFrameBuildTimeMillis(),
throwsA(predicate<ArgumentError>((ArgumentError e) => e.message == 'durations is empty!')),
);
});
test('computes average frame build time in milliseconds', () {
expect(
summarize(<Map<String, dynamic>>[
frameBegin(1000), frameEnd(2000),
frameBegin(3000), frameEnd(5000),
]).computeAverageFrameBuildTimeMillis(),
1.5,
);
});
test('skips leading "end" events', () {
expect(
summarize(<Map<String, dynamic>>[
frameEnd(1000),
frameBegin(2000), frameEnd(4000),
]).computeAverageFrameBuildTimeMillis(),
2.0,
);
});
test('skips trailing "begin" events', () {
expect(
summarize(<Map<String, dynamic>>[
frameBegin(2000), frameEnd(4000),
frameBegin(5000),
]).computeAverageFrameBuildTimeMillis(),
2.0,
);
});
// see https://github.com/flutter/flutter/issues/54095.
test('ignore multiple "end" events', () {
expect(
summarize(<Map<String, dynamic>>[
frameBegin(2000), frameEnd(4000),
frameEnd(4300), // rogue frame end.
frameBegin(5000), frameEnd(6000),
]).computeAverageFrameBuildTimeMillis(),
1.5,
);
});
test('pick latest when there are multiple "begin" events', () {
expect(
summarize(<Map<String, dynamic>>[
frameBegin(1000), // rogue frame begin.
frameBegin(2000), frameEnd(4000),
frameEnd(4300), // rogue frame end.
frameBegin(4400), // rogue frame begin.
frameBegin(5000), frameEnd(6000),
]).computeAverageFrameBuildTimeMillis(),
1.5,
);
});
});
group('worst_frame_build_time_millis', () {
test('throws when there is no data', () {
expect(
() => summarize(<Map<String, dynamic>>[]).computeWorstFrameBuildTimeMillis(),
throwsA(predicate<ArgumentError>((ArgumentError e) => e.message == 'durations is empty!')),
);
});
test('computes worst frame build time in milliseconds', () {
expect(
summarize(<Map<String, dynamic>>[
frameBegin(1000), frameEnd(2000),
frameBegin(3000), frameEnd(5000),
]).computeWorstFrameBuildTimeMillis(),
2.0,
);
expect(
summarize(<Map<String, dynamic>>[
frameBegin(3000), frameEnd(5000),
frameBegin(1000), frameEnd(2000),
]).computeWorstFrameBuildTimeMillis(),
2.0,
);
});
test('skips leading "end" events', () {
expect(
summarize(<Map<String, dynamic>>[
frameEnd(1000),
frameBegin(2000), frameEnd(4000),
]).computeWorstFrameBuildTimeMillis(),
2.0,
);
});
test('skips trailing "begin" events', () {
expect(
summarize(<Map<String, dynamic>>[
frameBegin(2000), frameEnd(4000),
frameBegin(5000),
]).computeWorstFrameBuildTimeMillis(),
2.0,
);
});
});
group('computeMissedFrameBuildBudgetCount', () {
test('computes the number of missed build budgets', () {
final TimelineSummary summary = summarize(<Map<String, dynamic>>[
frameBegin(1000), frameEnd(18000),
frameBegin(19000), frameEnd(28000),
frameBegin(29000), frameEnd(47000),
]);
expect(summary.countFrames(), 3);
expect(summary.computeMissedFrameBuildBudgetCount(), 2);
});
});
group('average_frame_rasterizer_time_millis', () {
test('throws when there is no data', () {
expect(
() => summarize(<Map<String, dynamic>>[]).computeAverageFrameRasterizerTimeMillis(),
throwsA(predicate<ArgumentError>((ArgumentError e) => e.message == 'durations is empty!')),
);
});
test('computes average frame rasterizer time in milliseconds', () {
expect(
summarize(<Map<String, dynamic>>[
begin(1000), end(2000),
begin(3000), end(5000),
]).computeAverageFrameRasterizerTimeMillis(),
1.5,
);
});
test('skips leading "end" events', () {
expect(
summarize(<Map<String, dynamic>>[
end(1000),
begin(2000), end(4000),
]).computeAverageFrameRasterizerTimeMillis(),
2.0,
);
});
test('skips trailing "begin" events', () {
expect(
summarize(<Map<String, dynamic>>[
begin(2000), end(4000),
begin(5000),
]).computeAverageFrameRasterizerTimeMillis(),
2.0,
);
});
});
group('worst_frame_rasterizer_time_millis', () {
test('throws when there is no data', () {
expect(
() => summarize(<Map<String, dynamic>>[]).computeWorstFrameRasterizerTimeMillis(),
throwsA(predicate<ArgumentError>((ArgumentError e) => e.message == 'durations is empty!')),
);
});
test('computes worst frame rasterizer time in milliseconds', () {
expect(
summarize(<Map<String, dynamic>>[
begin(1000), end(2000),
begin(3000), end(5000),
]).computeWorstFrameRasterizerTimeMillis(),
2.0,
);
expect(
summarize(<Map<String, dynamic>>[
begin(3000), end(5000),
begin(1000), end(2000),
]).computeWorstFrameRasterizerTimeMillis(),
2.0,
);
});
test('skips leading "end" events', () {
expect(
summarize(<Map<String, dynamic>>[
end(1000),
begin(2000), end(4000),
]).computeWorstFrameRasterizerTimeMillis(),
2.0,
);
});
test('skips trailing "begin" events', () {
expect(
summarize(<Map<String, dynamic>>[
begin(2000), end(4000),
begin(5000),
]).computeWorstFrameRasterizerTimeMillis(),
2.0,
);
});
});
group('percentile_frame_rasterizer_time_millis', () {
test('throws when there is no data', () {
expect(
() => summarize(<Map<String, dynamic>>[]).computePercentileFrameRasterizerTimeMillis(90.0),
throwsA(predicate<ArgumentError>((ArgumentError e) => e.message == 'durations is empty!')),
);
});
const List<List<int>> sequences = <List<int>>[
<int>[1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
<int>[1, 2, 3, 4, 5],
<int>[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
];
const List<int> p90s = <int>[
9,
5,
18,
];
test('computes 90th frame rasterizer time in milliseconds', () {
for (int i = 0; i < sequences.length; ++i) {
expect(
summarize(rasterizeTimeSequenceInMillis(sequences[i])).computePercentileFrameRasterizerTimeMillis(90.0),
p90s[i],
);
}
});
test('compute 99th frame rasterizer time in milliseconds', () {
final List<int> sequence = <int>[];
for (int i = 1; i <= 100; ++i) {
sequence.add(i);
}
expect(
summarize(rasterizeTimeSequenceInMillis(sequence)).computePercentileFrameRasterizerTimeMillis(99.0),
99,
);
});
});
group('computeMissedFrameRasterizerBudgetCount', () {
test('computes the number of missed rasterizer budgets', () {
final TimelineSummary summary = summarize(<Map<String, dynamic>>[
begin(1000), end(18000),
begin(19000), end(28000),
begin(29000), end(47000),
]);
expect(summary.computeMissedFrameRasterizerBudgetCount(), 2);
});
});
group('summaryJson', () {
test('computes and returns summary as JSON', () {
expect(
summarize(<Map<String, dynamic>>[
begin(1000), end(19000),
begin(19000), end(29000),
begin(29000), end(49000),
...newGenGC(4, 10, 100),
...oldGenGC(5, 10000, 100),
frameBegin(1000), frameEnd(18000),
frameBegin(19000), frameEnd(28000),
frameBegin(29000), frameEnd(48000),
]).summaryJson,
<String, dynamic>{
'average_frame_build_time_millis': 15.0,
'90th_percentile_frame_build_time_millis': 19.0,
'99th_percentile_frame_build_time_millis': 19.0,
'worst_frame_build_time_millis': 19.0,
'missed_frame_build_budget_count': 2,
'average_frame_rasterizer_time_millis': 16.0,
'90th_percentile_frame_rasterizer_time_millis': 20.0,
'99th_percentile_frame_rasterizer_time_millis': 20.0,
'worst_frame_rasterizer_time_millis': 20.0,
'missed_frame_rasterizer_budget_count': 2,
'frame_count': 3,
'frame_rasterizer_count': 3,
'new_gen_gc_count': 4,
'old_gen_gc_count': 5,
'frame_build_times': <int>[17000, 9000, 19000],
'frame_rasterizer_times': <int>[18000, 10000, 20000],
'frame_begin_times': <int>[0, 18000, 28000],
'frame_rasterizer_begin_times': <int>[0, 18000, 28000],
'average_vsync_transitions_missed': 0.0,
'90th_percentile_vsync_transitions_missed': 0.0,
'99th_percentile_vsync_transitions_missed': 0.0,
'average_vsync_frame_lag': 0.0,
'90th_percentile_vsync_frame_lag': 0.0,
'99th_percentile_vsync_frame_lag': 0.0,
'average_layer_cache_count': 0.0,
'90th_percentile_layer_cache_count': 0.0,
'99th_percentile_layer_cache_count': 0.0,
'worst_layer_cache_count': 0.0,
'average_layer_cache_memory': 0.0,
'90th_percentile_layer_cache_memory': 0.0,
'99th_percentile_layer_cache_memory': 0.0,
'worst_layer_cache_memory': 0.0,
'average_picture_cache_count': 0.0,
'90th_percentile_picture_cache_count': 0.0,
'99th_percentile_picture_cache_count': 0.0,
'worst_picture_cache_count': 0.0,
'average_picture_cache_memory': 0.0,
'90th_percentile_picture_cache_memory': 0.0,
'99th_percentile_picture_cache_memory': 0.0,
'worst_picture_cache_memory': 0.0,
'total_ui_gc_time': 0.4,
'30hz_frame_percentage': 0,
'60hz_frame_percentage': 0,
'90hz_frame_percentage': 0,
'120hz_frame_percentage': 0,
'illegal_refresh_rate_frame_count': 0,
},
);
});
});
group('writeTimelineToFile', () {
late Directory tempDir;
setUp(() {
useMemoryFileSystemForTesting();
tempDir = fs.systemTempDirectory.createTempSync('flutter_driver_test.');
});
tearDown(() {
tryToDelete(tempDir);
restoreFileSystem();
});
test('writes timeline to JSON file without summary', () async {
await summarize(<Map<String, String>>[<String, String>{'foo': 'bar'}])
.writeTimelineToFile('test', destinationDirectory: tempDir.path, includeSummary: false);
final String written =
await fs.file(path.join(tempDir.path, 'test.timeline.json')).readAsString();
expect(written, '{"traceEvents":[{"foo":"bar"}]}');
});
test('writes timeline to JSON file with summary', () async {
await summarize(<Map<String, dynamic>>[
<String, String>{'foo': 'bar'},
begin(1000), end(19000),
frameBegin(1000), frameEnd(18000),
]).writeTimelineToFile(
'test',
destinationDirectory: tempDir.path,
);
final String written =
await fs.file(path.join(tempDir.path, 'test.timeline.json')).readAsString();
expect(
written,
'{"traceEvents":[{"foo":"bar"},'
'{"name":"GPURasterizer::Draw","ph":"B","ts":1000},'
'{"name":"GPURasterizer::Draw","ph":"E","ts":19000},'
'{"name":"Frame","ph":"B","ts":1000},'
'{"name":"Frame","ph":"E","ts":18000}]}',
);
});
test('writes summary to JSON file', () async {
await summarize(<Map<String, dynamic>>[
begin(1000), end(19000),
begin(19000), end(29000),
begin(29000), end(49000),
frameBegin(1000), frameEnd(18000),
frameBegin(19000), frameEnd(28000),
frameBegin(29000), frameEnd(48000),
lagBegin(1000, 4), lagEnd(2000, 4),
lagBegin(1200, 12), lagEnd(2400, 12),
lagBegin(4200, 8), lagEnd(9400, 8),
...newGenGC(4, 10, 100),
...oldGenGC(5, 10000, 100),
cpuUsage(5000, 20), cpuUsage(5010, 60),
memoryUsage(6000, 20, 40), memoryUsage(6100, 30, 45),
platformVsync(7000), vsyncCallback(7500),
]).writeTimelineToFile('test', destinationDirectory: tempDir.path);
final String written =
await fs.file(path.join(tempDir.path, 'test.timeline_summary.json')).readAsString();
expect(json.decode(written), <String, dynamic>{
'average_frame_build_time_millis': 15.0,
'worst_frame_build_time_millis': 19.0,
'90th_percentile_frame_build_time_millis': 19.0,
'99th_percentile_frame_build_time_millis': 19.0,
'missed_frame_build_budget_count': 2,
'average_frame_rasterizer_time_millis': 16.0,
'90th_percentile_frame_rasterizer_time_millis': 20.0,
'99th_percentile_frame_rasterizer_time_millis': 20.0,
'worst_frame_rasterizer_time_millis': 20.0,
'missed_frame_rasterizer_budget_count': 2,
'frame_count': 3,
'frame_rasterizer_count': 3,
'new_gen_gc_count': 4,
'old_gen_gc_count': 5,
'frame_build_times': <int>[17000, 9000, 19000],
'frame_rasterizer_times': <int>[18000, 10000, 20000],
'frame_begin_times': <int>[0, 18000, 28000],
'frame_rasterizer_begin_times': <int>[0, 18000, 28000],
'average_vsync_transitions_missed': 8.0,
'90th_percentile_vsync_transitions_missed': 12.0,
'99th_percentile_vsync_transitions_missed': 12.0,
'average_vsync_frame_lag': 500.0,
'90th_percentile_vsync_frame_lag': 500.0,
'99th_percentile_vsync_frame_lag': 500.0,
'average_cpu_usage': 40.0,
'90th_percentile_cpu_usage': 60.0,
'99th_percentile_cpu_usage': 60.0,
'average_memory_usage': 67.5,
'90th_percentile_memory_usage': 75.0,
'99th_percentile_memory_usage': 75.0,
'average_layer_cache_count': 0.0,
'90th_percentile_layer_cache_count': 0.0,
'99th_percentile_layer_cache_count': 0.0,
'worst_layer_cache_count': 0.0,
'average_layer_cache_memory': 0.0,
'90th_percentile_layer_cache_memory': 0.0,
'99th_percentile_layer_cache_memory': 0.0,
'worst_layer_cache_memory': 0.0,
'average_picture_cache_count': 0.0,
'90th_percentile_picture_cache_count': 0.0,
'99th_percentile_picture_cache_count': 0.0,
'worst_picture_cache_count': 0.0,
'average_picture_cache_memory': 0.0,
'90th_percentile_picture_cache_memory': 0.0,
'99th_percentile_picture_cache_memory': 0.0,
'worst_picture_cache_memory': 0.0,
'total_ui_gc_time': 0.4,
'30hz_frame_percentage': 0,
'60hz_frame_percentage': 100,
'90hz_frame_percentage': 0,
'120hz_frame_percentage': 0,
'illegal_refresh_rate_frame_count': 0,
});
});
});
group('SceneDisplayLagSummarizer tests', () {
SceneDisplayLagSummarizer summarize(List<Map<String, dynamic>> traceEvents) {
final Timeline timeline = Timeline.fromJson(<String, dynamic>{
'traceEvents': traceEvents,
});
return SceneDisplayLagSummarizer(timeline.events!);
}
test('average_vsyncs_missed', () async {
final SceneDisplayLagSummarizer summarizer = summarize(<Map<String, dynamic>>[
lagBegin(1000, 4), lagEnd(2000, 4),
lagBegin(1200, 12), lagEnd(2400, 12),
lagBegin(4200, 8), lagEnd(9400, 8),
]);
expect(summarizer.computeAverageVsyncTransitionsMissed(), 8.0);
});
test('all stats are 0 for 0 missed transitions', () async {
final SceneDisplayLagSummarizer summarizer = summarize(<Map<String, dynamic>>[]);
expect(summarizer.computeAverageVsyncTransitionsMissed(), 0.0);
expect(summarizer.computePercentileVsyncTransitionsMissed(90.0), 0.0);
expect(summarizer.computePercentileVsyncTransitionsMissed(99.0), 0.0);
});
test('90th_percentile_vsyncs_missed', () async {
final SceneDisplayLagSummarizer summarizer = summarize(<Map<String, dynamic>>[
lagBegin(1000, 4), lagEnd(2000, 4),
lagBegin(1200, 12), lagEnd(2400, 12),
lagBegin(4200, 8), lagEnd(9400, 8),
lagBegin(6100, 14), lagEnd(11000, 14),
lagBegin(7100, 16), lagEnd(11500, 16),
lagBegin(7400, 11), lagEnd(13000, 11),
lagBegin(8200, 27), lagEnd(14100, 27),
lagBegin(8700, 7), lagEnd(14300, 7),
lagBegin(24200, 4187), lagEnd(39400, 4187),
]);
expect(summarizer.computePercentileVsyncTransitionsMissed(90), 27.0);
});
test('99th_percentile_vsyncs_missed', () async {
final SceneDisplayLagSummarizer summarizer = summarize(<Map<String, dynamic>>[
lagBegin(1000, 4), lagEnd(2000, 4),
lagBegin(1200, 12), lagEnd(2400, 12),
lagBegin(4200, 8), lagEnd(9400, 8),
lagBegin(6100, 14), lagEnd(11000, 14),
lagBegin(24200, 4187), lagEnd(39400, 4187),
]);
expect(summarizer.computePercentileVsyncTransitionsMissed(99), 4187.0);
});
});
group('ProfilingSummarizer tests', () {
ProfilingSummarizer summarize(List<Map<String, dynamic>> traceEvents) {
final Timeline timeline = Timeline.fromJson(<String, dynamic>{
'traceEvents': traceEvents,
});
return ProfilingSummarizer.fromEvents(timeline.events!);
}
test('has_both_cpu_and_memory_usage', () async {
final ProfilingSummarizer summarizer = summarize(<Map<String, dynamic>>[
cpuUsage(0, 10),
memoryUsage(0, 6, 10),
cpuUsage(0, 12),
memoryUsage(0, 8, 40),
]);
expect(summarizer.computeAverage(ProfileType.CPU), 11.0);
expect(summarizer.computeAverage(ProfileType.Memory), 32.0);
});
test('has_only_memory_usage', () async {
final ProfilingSummarizer summarizer = summarize(<Map<String, dynamic>>[
memoryUsage(0, 6, 10),
memoryUsage(0, 8, 40),
]);
expect(summarizer.computeAverage(ProfileType.Memory), 32.0);
expect(summarizer.summarize().containsKey('average_cpu_usage'), false);
});
test('90th_percentile_cpu_usage', () async {
final ProfilingSummarizer summarizer = summarize(<Map<String, dynamic>>[
cpuUsage(0, 10), cpuUsage(1, 20),
cpuUsage(2, 20), cpuUsage(3, 80),
cpuUsage(4, 70), cpuUsage(4, 72),
cpuUsage(4, 85), cpuUsage(4, 100),
]);
expect(summarizer.computePercentile(ProfileType.CPU, 90), 85.0);
});
});
group('VsyncFrameLagSummarizer tests', () {
VsyncFrameLagSummarizer summarize(List<Map<String, dynamic>> traceEvents) {
final Timeline timeline = Timeline.fromJson(<String, dynamic>{
'traceEvents': traceEvents,
});
return VsyncFrameLagSummarizer(timeline.events!);
}
test('average_vsync_frame_lag', () async {
final VsyncFrameLagSummarizer summarizer = summarize(<Map<String, dynamic>>[
platformVsync(10),
vsyncCallback(12),
platformVsync(16),
vsyncCallback(29),
]);
expect(summarizer.computeAverageVsyncFrameLag(), 7.5);
});
test('malformed_event_ordering', () async {
final VsyncFrameLagSummarizer summarizer = summarize(<Map<String, dynamic>>[
vsyncCallback(10),
platformVsync(10),
]);
expect(summarizer.computeAverageVsyncFrameLag(), 0);
expect(summarizer.computePercentileVsyncFrameLag(80), 0);
});
test('penalize_consecutive_vsyncs', () async {
final VsyncFrameLagSummarizer summarizer = summarize(<Map<String, dynamic>>[
platformVsync(10),
platformVsync(12),
]);
expect(summarizer.computeAverageVsyncFrameLag(), 2);
});
test('pick_nearest_platform_vsync', () async {
final VsyncFrameLagSummarizer summarizer = summarize(<Map<String, dynamic>>[
platformVsync(10),
platformVsync(12),
vsyncCallback(18),
]);
expect(summarizer.computeAverageVsyncFrameLag(), 4);
});
test('percentile_vsync_frame_lag', () async {
final List<Map<String, dynamic>> events = <Map<String, dynamic>>[];
int ts = 100;
for (int i = 0; i < 100; i++) {
events.add(platformVsync(ts));
ts = ts + 10 * (i + 1);
events.add(vsyncCallback(ts));
}
final VsyncFrameLagSummarizer summarizer = summarize(events);
expect(summarizer.computePercentileVsyncFrameLag(90), 890);
expect(summarizer.computePercentileVsyncFrameLag(99), 990);
});
});
group('RefreshRateSummarizer tests', () {
const double kCompareDelta = 0.01;
RefreshRateSummary _summarize(List<Map<String, dynamic>> traceEvents) {
final Timeline timeline = Timeline.fromJson(<String, dynamic>{
'traceEvents': traceEvents,
});
return RefreshRateSummary(vsyncEvents: timeline.events!);
}
List<Map<String, dynamic>> _populateEvents({required int numberOfEvents, required int startTime, required int interval, required int margin}) {
final List<Map<String, dynamic>> events = <Map<String, dynamic>>[];
int startTimeInNanoseconds = startTime;
for (int i = 0; i < numberOfEvents; i ++) {
final int randomMargin = margin >= 1 ? (-margin + Random().nextInt(margin*2)) : 0;
final int endTime = startTimeInNanoseconds + interval + randomMargin;
events.add(vsyncCallback(0, startTime: startTimeInNanoseconds.toString(), endTime: endTime.toString()));
startTimeInNanoseconds = endTime;
}
return events;
}
test('Recognize 30 hz frames.', () async {
const int startTimeInNanoseconds = 2750850055430;
const int intervalInNanoseconds = 33333333;
// allow some margins
const int margin = 3000000;
final List<Map<String, dynamic>> events = _populateEvents(numberOfEvents: 100,
startTime: startTimeInNanoseconds,
interval: intervalInNanoseconds,
margin: margin,
);
final RefreshRateSummary summary = _summarize(events);
expect(summary.percentageOf30HzFrames, closeTo(100, kCompareDelta));
expect(summary.percentageOf60HzFrames, 0);
expect(summary.percentageOf90HzFrames, 0);
expect(summary.percentageOf120HzFrames, 0);
expect(summary.framesWithIllegalRefreshRate, isEmpty);
});
test('Recognize 60 hz frames.', () async {
const int startTimeInNanoseconds = 2750850055430;
const int intervalInNanoseconds = 16666666;
// allow some margins
const int margin = 1200000;
final List<Map<String, dynamic>> events = _populateEvents(numberOfEvents: 100,
startTime: startTimeInNanoseconds,
interval: intervalInNanoseconds,
margin: margin,
);
final RefreshRateSummary summary = _summarize(events);
expect(summary.percentageOf30HzFrames, 0);
expect(summary.percentageOf60HzFrames, closeTo(100, kCompareDelta));
expect(summary.percentageOf90HzFrames, 0);
expect(summary.percentageOf120HzFrames, 0);
expect(summary.framesWithIllegalRefreshRate, isEmpty);
});
test('Recognize 90 hz frames.', () async {
const int startTimeInNanoseconds = 2750850055430;
const int intervalInNanoseconds = 11111111;
// allow some margins
const int margin = 500000;
final List<Map<String, dynamic>> events = _populateEvents(numberOfEvents: 100,
startTime: startTimeInNanoseconds,
interval: intervalInNanoseconds,
margin: margin,
);
final RefreshRateSummary summary = _summarize(events);
expect(summary.percentageOf30HzFrames, 0);
expect(summary.percentageOf60HzFrames, 0);
expect(summary.percentageOf90HzFrames, closeTo(100, kCompareDelta));
expect(summary.percentageOf120HzFrames, 0);
expect(summary.framesWithIllegalRefreshRate, isEmpty);
});
test('Recognize 120 hz frames.', () async {
const int startTimeInNanoseconds = 2750850055430;
const int intervalInNanoseconds = 8333333;
// allow some margins
const int margin = 300000;
final List<Map<String, dynamic>> events = _populateEvents(numberOfEvents: 100,
startTime: startTimeInNanoseconds,
interval: intervalInNanoseconds,
margin: margin,
);
final RefreshRateSummary summary = _summarize(events);
expect(summary.percentageOf30HzFrames, 0);
expect(summary.percentageOf60HzFrames, 0);
expect(summary.percentageOf90HzFrames, 0);
expect(summary.percentageOf120HzFrames, closeTo(100, kCompareDelta));
expect(summary.framesWithIllegalRefreshRate, isEmpty);
});
test('Identify illegal refresh rates.', () async {
const int startTimeInNanoseconds = 2750850055430;
const int intervalInNanoseconds = 10000000;
final List<Map<String, dynamic>> events = _populateEvents(numberOfEvents: 1,
startTime: startTimeInNanoseconds,
interval: intervalInNanoseconds,
margin: 0,
);
final RefreshRateSummary summary = _summarize(events);
expect(summary.percentageOf30HzFrames, 0);
expect(summary.percentageOf60HzFrames, 0);
expect(summary.percentageOf90HzFrames, 0);
expect(summary.percentageOf120HzFrames, 0);
expect(summary.framesWithIllegalRefreshRate, isNotEmpty);
expect(summary.framesWithIllegalRefreshRate.first, closeTo(100, kCompareDelta));
});
test('Mixed refresh rates.', () async {
final List<Map<String, dynamic>> events = <Map<String, dynamic>>[];
const int num30Hz = 10;
const int num60Hz = 20;
const int num90Hz = 20;
const int num120Hz = 40;
const int numIllegal = 10;
// Add 30hz frames
events.addAll(_populateEvents(numberOfEvents: num30Hz,
startTime: 0,
interval: 32000000,
margin: 0,
));
// Add 60hz frames
events.addAll(_populateEvents(numberOfEvents: num60Hz,
startTime: 0,
interval: 16000000,
margin: 0,
));
// Add 90hz frames
events.addAll(_populateEvents(numberOfEvents: num90Hz,
startTime: 0,
interval: 11000000,
margin: 0,
));
// Add 120hz frames
events.addAll(_populateEvents(numberOfEvents: num120Hz,
startTime: 0,
interval: 8000000,
margin: 0,
));
// Add illegal refresh rate frames
events.addAll(_populateEvents(numberOfEvents: numIllegal,
startTime: 0,
interval: 60000,
margin: 0,
));
final RefreshRateSummary summary = _summarize(events);
expect(summary.percentageOf30HzFrames, closeTo(num30Hz, kCompareDelta));
expect(summary.percentageOf60HzFrames, closeTo(num60Hz, kCompareDelta));
expect(summary.percentageOf90HzFrames, closeTo(num90Hz, kCompareDelta));
expect(summary.percentageOf120HzFrames, closeTo(num120Hz, kCompareDelta));
expect(summary.framesWithIllegalRefreshRate, isNotEmpty);
expect(summary.framesWithIllegalRefreshRate.length, 10);
});
});
});
}