Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit a402f67

Browse files
authored
Merge pull request gskinnerTeam#58 from filiph/perf_optimization
Add benchmarking
2 parents 8f42684 + c2cec05 commit a402f67

17 files changed

+551
-65
lines changed

benchmark/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
*.benchmark
2+
*.json

benchmark/README.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Performance profiling
2+
3+
Run the following to gather new benchmark data:
4+
5+
```text
6+
$ flutter drive --target=benchmark/app.dart --driver=benchmark/app_benchmark.dart --profile --endless-trace-buffer --purge-persistent-cache
7+
```
8+
9+
You can do many runs one after another to minimize the effect of random noise
10+
in measurements.
11+
12+
```text
13+
for n in {1..10}; do echo "=== Run number ${n}/10 ==="; \
14+
flutter drive \
15+
--target=benchmark/app.dart \
16+
--driver=benchmark/app_benchmark.dart \
17+
--profile \
18+
--endless-trace-buffer \
19+
--purge-persistent-cache; \
20+
done
21+
```
22+
23+
(You can then combine the separate runs into one file using `benchmerge`.)
24+
25+
Install Benchmarkhor (`pub global activate benchmarkhor`), then run:
26+
27+
```text
28+
$ benchextract benchmark/*.json
29+
```
30+
31+
Finally, compare different benchmark runs with
32+
33+
```text
34+
$ benchcompare benchmark/some-baseline.benchmark benchmark/new.benchmark
35+
```

benchmark/app.dart

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
import 'package:flutter/cupertino.dart';
2+
import 'package:flutter/material.dart';
3+
import 'package:flutter_driver/driver_extension.dart';
4+
import 'package:flutter_folio/data/app_user.dart';
5+
import 'package:flutter_folio/data/book_data.dart';
6+
import 'package:flutter_folio/main.dart';
7+
import 'package:flutter_folio/models/app_model.dart';
8+
import 'package:flutter_folio/models/books_model.dart';
9+
import 'package:flutter_folio/services/cloudinary/cloud_storage_service.dart';
10+
import 'package:flutter_folio/services/firebase/firebase_service.dart';
11+
import 'package:provider/provider.dart';
12+
13+
void main() {
14+
// This line enables the extension.
15+
enableFlutterDriverExtension();
16+
17+
// Create core models & services
18+
FirebaseService firebase = _MockFirebaseService();
19+
BooksModel booksModel = BooksModel();
20+
AppModel appModel = AppModel(booksModel, firebase);
21+
22+
// /// Run
23+
runApp(MultiProvider(
24+
providers: [
25+
// Firebase
26+
Provider.value(value: firebase),
27+
// Cloudinary
28+
Provider(create: (_) => CloudStorageService()),
29+
// App Model - Stores data related to global settings or app modes
30+
ChangeNotifierProvider.value(value: appModel),
31+
// BooksModel - Stores data about the content in the app
32+
ChangeNotifierProvider.value(value: booksModel),
33+
],
34+
child: const AppBootstrapper(),
35+
));
36+
}
37+
38+
class _MockFirebaseService implements FirebaseService {
39+
final _mockUser = AppUser(
40+
documentId: 'exampleDocId',
41+
42+
fireId: 'exampleUser',
43+
);
44+
45+
@override
46+
String? userId = 'exampleUser';
47+
48+
AppUser? _currentUser;
49+
50+
@override
51+
bool get isSignedIn => _currentUser != null;
52+
53+
@override
54+
List<String> get userPath => [FireIds.users, userId ?? ''];
55+
56+
@override
57+
Future<String> addBook(ScrapBookData value) {
58+
throw UnimplementedError();
59+
}
60+
61+
@override
62+
Future<String> addBookScrap(ScrapItem value) {
63+
throw UnimplementedError();
64+
}
65+
66+
@override
67+
Future<String> addDoc(List<String> keys, Map<String, dynamic> json, {String? documentId, bool addUserPath = true}) {
68+
throw UnimplementedError();
69+
}
70+
71+
@override
72+
Future<String> addPage(ScrapPageData value) {
73+
throw UnimplementedError();
74+
}
75+
76+
@override
77+
Future<String> addPlacedScrap(PlacedScrapItem value) {
78+
throw UnimplementedError();
79+
}
80+
81+
@override
82+
Future<String> addUser(AppUser value) {
83+
throw UnimplementedError();
84+
}
85+
86+
@override
87+
Future<void> deleteBook(ScrapBookData value) {
88+
throw UnimplementedError();
89+
}
90+
91+
@override
92+
Future<void> deleteBookScrap({required String bookId, required String scrapId}) {
93+
throw UnimplementedError();
94+
}
95+
96+
@override
97+
Future<void> deleteDoc(List<String> keys) {
98+
throw UnimplementedError();
99+
}
100+
101+
@override
102+
Future<void> deletePage({required String bookId, required String pageId}) {
103+
throw UnimplementedError();
104+
}
105+
106+
@override
107+
Future<void> deletePlacedScrap(PlacedScrapItem value) {
108+
throw UnimplementedError();
109+
}
110+
111+
@override
112+
Future<List<ScrapBookData>?> getAllBooks() async {
113+
return <ScrapBookData>[
114+
for (var i = 0; i < 50; i++) ScrapBookData.random(),
115+
];
116+
}
117+
118+
@override
119+
Future<List<ScrapItem>?> getAllBookScraps({required String bookId}) {
120+
throw UnimplementedError();
121+
}
122+
123+
@override
124+
Future<List<ScrapPageData>?> getAllPages({required String bookId}) {
125+
throw UnimplementedError();
126+
}
127+
128+
@override
129+
Future<List<PlacedScrapItem>?> getAllPlacedScraps({required String bookId, required String pageId}) {
130+
throw UnimplementedError();
131+
}
132+
133+
@override
134+
Future<ScrapBookData?> getBook({required String bookId}) {
135+
throw UnimplementedError();
136+
}
137+
138+
@override
139+
Future<List<Map<String, dynamic>>?> getCollection(List<String> keys) {
140+
throw UnimplementedError();
141+
}
142+
143+
@override
144+
Future<Map<String, dynamic>?> getDoc(List<String> keys) {
145+
throw UnimplementedError();
146+
}
147+
148+
@override
149+
Stream<Map<String, dynamic>>? getDocStream(List<String> keys) {
150+
throw UnimplementedError();
151+
}
152+
153+
@override
154+
Stream<List<Map<String, dynamic>>>? getListStream(List<String> keys) {
155+
throw UnimplementedError();
156+
}
157+
158+
@override
159+
Future<ScrapPageData?> getPage({required String bookId, required String pageId}) {
160+
throw UnimplementedError();
161+
}
162+
163+
@override
164+
Future<int> getPageCount(String bookId) {
165+
throw UnimplementedError();
166+
}
167+
168+
@override
169+
String getPathFromKeys(List<String> keys, {bool addUserPath = true}) {
170+
throw UnimplementedError();
171+
}
172+
173+
@override
174+
Future<AppUser?> getUser() async => _mockUser;
175+
176+
@override
177+
void init() {
178+
// Nothing to init in the mock.
179+
}
180+
181+
@override
182+
Future<void> setBook(ScrapBookData value) async {
183+
// Pass.
184+
}
185+
186+
@override
187+
Future<void> setBookScrap(ScrapItem value) {
188+
throw UnimplementedError();
189+
}
190+
191+
@override
192+
Future<void> setPage(ScrapPageData value) {
193+
// TODO: implement setPage
194+
throw UnimplementedError();
195+
}
196+
197+
@override
198+
Future<void> setPlacedScrap(PlacedScrapItem value) {
199+
throw UnimplementedError();
200+
}
201+
202+
@override
203+
Future<void> setUserData(AppUser value) {
204+
throw UnimplementedError();
205+
}
206+
207+
@override
208+
Future<AppUser?> signIn({required String email, required String password, bool createAccount = false}) async {
209+
_currentUser = _mockUser;
210+
return _mockUser;
211+
}
212+
213+
@override
214+
Future<void> signOut() async {
215+
_currentUser = null;
216+
}
217+
218+
@override
219+
Future<void> updateDoc(List<String> keys, Map<String, dynamic> json) {
220+
throw UnimplementedError();
221+
}
222+
}

benchmark/app_benchmark.dart

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import 'dart:convert';
2+
import 'dart:io';
3+
4+
import 'package:flutter_driver/flutter_driver.dart';
5+
import 'package:test/test.dart';
6+
7+
void main() {
8+
group('Full app', () {
9+
late FlutterDriver driver;
10+
11+
// Connect to the Flutter driver before running any tests.
12+
setUpAll(() async {
13+
driver = await FlutterDriver.connect();
14+
});
15+
16+
// Close the connection to the driver after the tests have completed.
17+
tearDownAll(() async {
18+
driver.close();
19+
});
20+
21+
setUp(() async {
22+
// You must manually clear the timeline before every new measurement
23+
// when you run the benchmark with `--endless-trace-buffer`.
24+
// Otherwise, your tests will contain tracing info of everything that
25+
// happened before they started.
26+
await driver.clearTimeline();
27+
await _ensureLoggedIn(driver);
28+
});
29+
30+
tearDown(() async {
31+
await _logOut(driver);
32+
});
33+
34+
test(
35+
'homepage scrolling up and down',
36+
() async {
37+
await driver.startTracing(streams: [TimelineStream.embedder]);
38+
for (var i = 0; i < 10; i++) {
39+
// Scroll about 6 pages down ...
40+
await driver.scroll(styledPageScaffold, 0, -6000, const Duration(seconds: 1));
41+
await driver.tap(styledPageScaffold);
42+
// ... and about 5 pages up.
43+
await driver.scroll(styledPageScaffold, 0, 5000, const Duration(seconds: 2));
44+
await driver.tap(styledPageScaffold);
45+
}
46+
final timeline = await driver.stopTracingAndDownloadTimeline();
47+
await _saveTimeline('homepage-scrolling', timeline);
48+
},
49+
timeout: _increasedTimeout,
50+
);
51+
52+
test('going from login screen to homepage and back', () async {
53+
await driver.startTracing(streams: [TimelineStream.embedder]);
54+
for (var i = 0; i < 20; i++) {
55+
// Log out ...
56+
await driver.tap(roundedProfileButton);
57+
await driver.tap(logoutButton);
58+
// ... then log in again.
59+
await driver.tap(authSubmitButton);
60+
}
61+
final timeline = await driver.stopTracingAndDownloadTimeline();
62+
63+
await _saveTimeline('login-to-homepage', timeline);
64+
}, timeout: _increasedTimeout);
65+
66+
test('opening and closing the "create new" sheet', () async {
67+
await driver.startTracing(streams: [TimelineStream.embedder]);
68+
for (var i = 0; i < 50; i++) {
69+
// Open ...
70+
await driver.tap(newFolioFab);
71+
// ... and close.
72+
await driver.scroll(newFolioCard, 0, 5000, const Duration(milliseconds: 200));
73+
}
74+
final timeline = await driver.stopTracingAndDownloadTimeline();
75+
76+
await _saveTimeline('create-new-sheet', timeline);
77+
}, timeout: _increasedTimeout);
78+
});
79+
}
80+
81+
final authSubmitButton = find.byValueKey('auth_submit_button');
82+
final logoutButton = find.byValueKey('logout_button');
83+
final materialApp = find.byType('MaterialApp');
84+
final newFolioCard = find.byType('_NewFolioCard');
85+
final newFolioFab = find.byType('_NewFolioFab');
86+
final roundedProfileButton = find.byValueKey('rounded_profile_button');
87+
final styledPageScaffold = find.byValueKey('StyledPageScaffold');
88+
89+
const Timeout _increasedTimeout = Timeout(Duration(minutes: 5));
90+
91+
Future<void> _ensureLoggedIn(FlutterDriver driver) async {
92+
// XXX: The following currently always returns an empty string.
93+
// We basically have no way of programmatically learning if we're
94+
// logged in or out.
95+
// final renderTree = await driver.getRenderTree();
96+
// print('render tree:');
97+
// print(renderTree.tree);
98+
99+
await driver.tap(authSubmitButton);
100+
}
101+
102+
Future<void> _logOut(FlutterDriver driver) async {
103+
await driver.tap(roundedProfileButton);
104+
await driver.tap(logoutButton);
105+
}
106+
107+
Future<void> _saveTimeline(String label, Timeline timeline) async {
108+
final now = DateTime.now();
109+
final nowString = now.toIso8601String().replaceAll(':', '-');
110+
final file = File('benchmark/$label-$nowString.json');
111+
await file.writeAsString(json.encode(timeline.json));
112+
}

lib/main.dart

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,18 +47,20 @@ void main() async {
4747
],
4848

4949
//child: BasicRouterSpike(),
50-
child: _AppBootstrapper(),
50+
child: const AppBootstrapper(),
5151
));
5252
});
5353
}
5454

5555
// Bootstrap the app, initializing all Controllers and Services
56-
class _AppBootstrapper extends StatefulWidget {
56+
class AppBootstrapper extends StatefulWidget {
57+
const AppBootstrapper({Key? key}) : super(key: key);
58+
5759
@override
5860
_AppBootstrapperState createState() => _AppBootstrapperState();
5961
}
6062

61-
class _AppBootstrapperState extends State<_AppBootstrapper> {
63+
class _AppBootstrapperState extends State<AppBootstrapper> {
6264
AppRouteParser routeParser = AppRouteParser();
6365
late AppRouterDelegate router;
6466
@override

0 commit comments

Comments
 (0)