// 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:convert' show json; import 'package:file/file.dart'; import 'package:flutter_driver/flutter_driver.dart'; import 'package:flutter_driver/src/driver/common.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> build(int timeStamp, int duration) => <String, dynamic>{ 'name': 'Frame', 'ph': 'X', 'ts': timeStamp, 'dur': duration, }; 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, }; List<Map<String, dynamic>> rasterizeTimeSequenceInMillis(List<int> sequence) { final List<Map<String, dynamic>> result = <Map<String, dynamic>>[]; int t = 0; for (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>>[ build(1000, 1000), build(3000, 2000), ]).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>>[ build(1000, 1000), build(3000, 2000), ]).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>>[ build(1000, 1000), build(3000, 2000), ]).computeWorstFrameBuildTimeMillis(), 2.0, ); expect( summarize(<Map<String, dynamic>>[ build(3000, 2000), build(1000, 1000), ]).computeWorstFrameBuildTimeMillis(), 2.0, ); }); }); group('computeMissedFrameBuildBudgetCount', () { test('computes the number of missed build budgets', () { final TimelineSummary summary = summarize(<Map<String, dynamic>>[ build(1000, 17000), build(19000, 9000), build(29000, 18000), ]); 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), build(1000, 17000), build(19000, 9000), build(29000, 19000), ]).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_build_times': <int>[17000, 9000, 19000], 'frame_rasterizer_times': <int>[18000, 10000, 20000], }, ); }); }); group('writeTimelineToFile', () { Directory tempDir; setUp(() { useMemoryFileSystemForTesting(); tempDir = fs.systemTempDirectory.createTempSync('flutter_driver_test.'); }); tearDown(() { tryToDelete(tempDir); restoreFileSystem(); }); test('writes timeline to JSON file', () async { await summarize(<Map<String, String>>[<String, String>{'foo': 'bar'}]) .writeTimelineToFile('test', destinationDirectory: tempDir.path); final String written = await fs.file(path.join(tempDir.path, 'test.timeline.json')).readAsString(); expect(written, '{"traceEvents":[{"foo":"bar"}]}'); }); test('writes summary to JSON file', () async { await summarize(<Map<String, dynamic>>[ begin(1000), end(19000), begin(19000), end(29000), begin(29000), end(49000), build(1000, 17000), build(19000, 9000), build(29000, 19000), ]).writeSummaryToFile('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_build_times': <int>[17000, 9000, 19000], 'frame_rasterizer_times': <int>[18000, 10000, 20000], }); }); }); }); }