|
3 | 3 | // found in the LICENSE file. |
4 | 4 |
|
5 | 5 | import 'dart:convert' show json; |
| 6 | +import 'dart:math'; |
6 | 7 |
|
7 | 8 | import 'package:file/file.dart'; |
8 | 9 | import 'package:flutter_driver/flutter_driver.dart'; |
9 | 10 | import 'package:flutter_driver/src/driver/profiling_summarizer.dart'; |
| 11 | +import 'package:flutter_driver/src/driver/refresh_rate_summarizer.dart'; |
10 | 12 | import 'package:flutter_driver/src/driver/scene_display_lag_summarizer.dart'; |
11 | 13 | import 'package:flutter_driver/src/driver/vsync_frame_lag_summarizer.dart'; |
12 | 14 | import 'package:path/path.dart' as path; |
@@ -89,10 +91,14 @@ void main() { |
89 | 91 | 'ts': timeStamp, |
90 | 92 | }; |
91 | 93 |
|
92 | | - Map<String, dynamic> vsyncCallback(int timeStamp) => <String, dynamic>{ |
| 94 | + Map<String, dynamic> vsyncCallback(int timeStamp, {String phase = 'B', String startTime = '2750850055428', String endTime = '2750866722095'}) => <String, dynamic>{ |
93 | 95 | 'name': 'VsyncProcessCallback', |
94 | | - 'ph': 'B', |
| 96 | + 'ph': phase, |
95 | 97 | 'ts': timeStamp, |
| 98 | + 'args': <String, dynamic>{ |
| 99 | + 'StartTime': startTime, |
| 100 | + 'TargetTime': endTime, |
| 101 | + } |
96 | 102 | }; |
97 | 103 |
|
98 | 104 | List<Map<String, dynamic>> _genGC(String name, int count, int startTime, int timeDiff) { |
@@ -467,6 +473,11 @@ void main() { |
467 | 473 | '99th_percentile_picture_cache_memory': 0.0, |
468 | 474 | 'worst_picture_cache_memory': 0.0, |
469 | 475 | 'total_ui_gc_time': 0.4, |
| 476 | + '30hz_frame_percentage': 0, |
| 477 | + '60hz_frame_percentage': 0, |
| 478 | + '90hz_frame_percentage': 0, |
| 479 | + '120hz_frame_percentage': 0, |
| 480 | + 'illegal_refresh_rate_frame_count': 0, |
470 | 481 | }, |
471 | 482 | ); |
472 | 483 | }); |
@@ -582,6 +593,11 @@ void main() { |
582 | 593 | '99th_percentile_picture_cache_memory': 0.0, |
583 | 594 | 'worst_picture_cache_memory': 0.0, |
584 | 595 | 'total_ui_gc_time': 0.4, |
| 596 | + '30hz_frame_percentage': 0, |
| 597 | + '60hz_frame_percentage': 100, |
| 598 | + '90hz_frame_percentage': 0, |
| 599 | + '120hz_frame_percentage': 0, |
| 600 | + 'illegal_refresh_rate_frame_count': 0, |
585 | 601 | }); |
586 | 602 | }); |
587 | 603 | }); |
@@ -734,5 +750,173 @@ void main() { |
734 | 750 | expect(summarizer.computePercentileVsyncFrameLag(99), 990); |
735 | 751 | }); |
736 | 752 | }); |
| 753 | + |
| 754 | + group('RefreshRateSummarizer tests', () { |
| 755 | + |
| 756 | + const double kCompareDelta = 0.01; |
| 757 | + RefreshRateSummary _summarize(List<Map<String, dynamic>> traceEvents) { |
| 758 | + final Timeline timeline = Timeline.fromJson(<String, dynamic>{ |
| 759 | + 'traceEvents': traceEvents, |
| 760 | + }); |
| 761 | + return RefreshRateSummary(vsyncEvents: timeline.events!); |
| 762 | + } |
| 763 | + |
| 764 | + List<Map<String, dynamic>> _populateEvents({required int numberOfEvents, required int startTime, required int interval, required int margin}) { |
| 765 | + final List<Map<String, dynamic>> events = <Map<String, dynamic>>[]; |
| 766 | + int startTimeInNanoseconds = startTime; |
| 767 | + for (int i = 0; i < numberOfEvents; i ++) { |
| 768 | + final int randomMargin = margin >= 1 ? (-margin + Random().nextInt(margin*2)) : 0; |
| 769 | + final int endTime = startTimeInNanoseconds + interval + randomMargin; |
| 770 | + events.add(vsyncCallback(0, startTime: startTimeInNanoseconds.toString(), endTime: endTime.toString())); |
| 771 | + startTimeInNanoseconds = endTime; |
| 772 | + } |
| 773 | + return events; |
| 774 | + } |
| 775 | + |
| 776 | + test('Recognize 30 hz frames.', () async { |
| 777 | + const int startTimeInNanoseconds = 2750850055430; |
| 778 | + const int intervalInNanoseconds = 33333333; |
| 779 | + // allow some margins |
| 780 | + const int margin = 3000000; |
| 781 | + final List<Map<String, dynamic>> events = _populateEvents(numberOfEvents: 100, |
| 782 | + startTime: startTimeInNanoseconds, |
| 783 | + interval: intervalInNanoseconds, |
| 784 | + margin: margin, |
| 785 | + ); |
| 786 | + final RefreshRateSummary summary = _summarize(events); |
| 787 | + expect(summary.percentageOf30HzFrames, closeTo(100, kCompareDelta)); |
| 788 | + expect(summary.percentageOf60HzFrames, 0); |
| 789 | + expect(summary.percentageOf90HzFrames, 0); |
| 790 | + expect(summary.percentageOf120HzFrames, 0); |
| 791 | + expect(summary.framesWithIllegalRefreshRate, isEmpty); |
| 792 | + }); |
| 793 | + |
| 794 | + test('Recognize 60 hz frames.', () async { |
| 795 | + const int startTimeInNanoseconds = 2750850055430; |
| 796 | + const int intervalInNanoseconds = 16666666; |
| 797 | + // allow some margins |
| 798 | + const int margin = 1200000; |
| 799 | + final List<Map<String, dynamic>> events = _populateEvents(numberOfEvents: 100, |
| 800 | + startTime: startTimeInNanoseconds, |
| 801 | + interval: intervalInNanoseconds, |
| 802 | + margin: margin, |
| 803 | + ); |
| 804 | + |
| 805 | + final RefreshRateSummary summary = _summarize(events); |
| 806 | + expect(summary.percentageOf30HzFrames, 0); |
| 807 | + expect(summary.percentageOf60HzFrames, closeTo(100, kCompareDelta)); |
| 808 | + expect(summary.percentageOf90HzFrames, 0); |
| 809 | + expect(summary.percentageOf120HzFrames, 0); |
| 810 | + expect(summary.framesWithIllegalRefreshRate, isEmpty); |
| 811 | + }); |
| 812 | + |
| 813 | + test('Recognize 90 hz frames.', () async { |
| 814 | + const int startTimeInNanoseconds = 2750850055430; |
| 815 | + const int intervalInNanoseconds = 11111111; |
| 816 | + // allow some margins |
| 817 | + const int margin = 500000; |
| 818 | + final List<Map<String, dynamic>> events = _populateEvents(numberOfEvents: 100, |
| 819 | + startTime: startTimeInNanoseconds, |
| 820 | + interval: intervalInNanoseconds, |
| 821 | + margin: margin, |
| 822 | + ); |
| 823 | + |
| 824 | + final RefreshRateSummary summary = _summarize(events); |
| 825 | + expect(summary.percentageOf30HzFrames, 0); |
| 826 | + expect(summary.percentageOf60HzFrames, 0); |
| 827 | + expect(summary.percentageOf90HzFrames, closeTo(100, kCompareDelta)); |
| 828 | + expect(summary.percentageOf120HzFrames, 0); |
| 829 | + expect(summary.framesWithIllegalRefreshRate, isEmpty); |
| 830 | + }); |
| 831 | + |
| 832 | + test('Recognize 120 hz frames.', () async { |
| 833 | + const int startTimeInNanoseconds = 2750850055430; |
| 834 | + const int intervalInNanoseconds = 8333333; |
| 835 | + // allow some margins |
| 836 | + const int margin = 300000; |
| 837 | + final List<Map<String, dynamic>> events = _populateEvents(numberOfEvents: 100, |
| 838 | + startTime: startTimeInNanoseconds, |
| 839 | + interval: intervalInNanoseconds, |
| 840 | + margin: margin, |
| 841 | + ); |
| 842 | + final RefreshRateSummary summary = _summarize(events); |
| 843 | + expect(summary.percentageOf30HzFrames, 0); |
| 844 | + expect(summary.percentageOf60HzFrames, 0); |
| 845 | + expect(summary.percentageOf90HzFrames, 0); |
| 846 | + expect(summary.percentageOf120HzFrames, closeTo(100, kCompareDelta)); |
| 847 | + expect(summary.framesWithIllegalRefreshRate, isEmpty); |
| 848 | + }); |
| 849 | + |
| 850 | + test('Identify illegal refresh rates.', () async { |
| 851 | + const int startTimeInNanoseconds = 2750850055430; |
| 852 | + const int intervalInNanoseconds = 10000000; |
| 853 | + final List<Map<String, dynamic>> events = _populateEvents(numberOfEvents: 1, |
| 854 | + startTime: startTimeInNanoseconds, |
| 855 | + interval: intervalInNanoseconds, |
| 856 | + margin: 0, |
| 857 | + ); |
| 858 | + final RefreshRateSummary summary = _summarize(events); |
| 859 | + expect(summary.percentageOf30HzFrames, 0); |
| 860 | + expect(summary.percentageOf60HzFrames, 0); |
| 861 | + expect(summary.percentageOf90HzFrames, 0); |
| 862 | + expect(summary.percentageOf120HzFrames, 0); |
| 863 | + expect(summary.framesWithIllegalRefreshRate, isNotEmpty); |
| 864 | + expect(summary.framesWithIllegalRefreshRate.first, closeTo(100, kCompareDelta)); |
| 865 | + }); |
| 866 | + |
| 867 | + test('Mixed refresh rates.', () async { |
| 868 | + |
| 869 | + final List<Map<String, dynamic>> events = <Map<String, dynamic>>[]; |
| 870 | + const int num30Hz = 10; |
| 871 | + const int num60Hz = 20; |
| 872 | + const int num90Hz = 20; |
| 873 | + const int num120Hz = 40; |
| 874 | + const int numIllegal = 10; |
| 875 | + |
| 876 | + // Add 30hz frames |
| 877 | + events.addAll(_populateEvents(numberOfEvents: num30Hz, |
| 878 | + startTime: 0, |
| 879 | + interval: 32000000, |
| 880 | + margin: 0, |
| 881 | + )); |
| 882 | + |
| 883 | + // Add 60hz frames |
| 884 | + events.addAll(_populateEvents(numberOfEvents: num60Hz, |
| 885 | + startTime: 0, |
| 886 | + interval: 16000000, |
| 887 | + margin: 0, |
| 888 | + )); |
| 889 | + |
| 890 | + |
| 891 | + // Add 90hz frames |
| 892 | + events.addAll(_populateEvents(numberOfEvents: num90Hz, |
| 893 | + startTime: 0, |
| 894 | + interval: 11000000, |
| 895 | + margin: 0, |
| 896 | + )); |
| 897 | + |
| 898 | + // Add 120hz frames |
| 899 | + events.addAll(_populateEvents(numberOfEvents: num120Hz, |
| 900 | + startTime: 0, |
| 901 | + interval: 8000000, |
| 902 | + margin: 0, |
| 903 | + )); |
| 904 | + |
| 905 | + // Add illegal refresh rate frames |
| 906 | + events.addAll(_populateEvents(numberOfEvents: numIllegal, |
| 907 | + startTime: 0, |
| 908 | + interval: 60000, |
| 909 | + margin: 0, |
| 910 | + )); |
| 911 | + |
| 912 | + final RefreshRateSummary summary = _summarize(events); |
| 913 | + expect(summary.percentageOf30HzFrames, closeTo(num30Hz, kCompareDelta)); |
| 914 | + expect(summary.percentageOf60HzFrames, closeTo(num60Hz, kCompareDelta)); |
| 915 | + expect(summary.percentageOf90HzFrames, closeTo(num90Hz, kCompareDelta)); |
| 916 | + expect(summary.percentageOf120HzFrames, closeTo(num120Hz, kCompareDelta)); |
| 917 | + expect(summary.framesWithIllegalRefreshRate, isNotEmpty); |
| 918 | + expect(summary.framesWithIllegalRefreshRate.length, 10); |
| 919 | + }); |
| 920 | + }); |
737 | 921 | }); |
738 | 922 | } |
0 commit comments