Implement size analyzer to unzip & parse APK and AOT size snapshot to generate analysis json #62495
Conversation
| json.decode(aotSizeJson.readAsStringSync()) as List<dynamic>, | ||
| ); | ||
|
|
||
| // Rearrange children to ensure the type key shows at the top. |
why does the order of the keys in the output map matter?
It's to make sure that the type field isn't just hidden at the bottom of the json because of the giant AOT data. I'm not sure when the user would need to check the type manually by opening up the json, but I was thinking it'd be better to make the type field more visible.
|
|
||
| static const int tableWidth = 80; | ||
|
|
||
| Future<Map<String, dynamic>> analyzeApkSize({ |
add dart doc describing what this method does. Add a note saying that aotSizeJson can be from instruction sizes or v8 snapshot (if that is correct)
From other PR: if this function now does 2 things, reflect it in the name.
Done
packages/flutter_tools/pubspec.yaml
Outdated
| @@ -34,6 +34,7 @@ dependencies: | |||
| native_stack_traces: 0.3.7 | |||
| build_daemon: 2.1.4 | |||
| shelf: 0.7.5 | |||
| vm_snapshot_analysis: ^0.5.0+1 | |||
this should be an exact version, then run flutter update-packages --force-upgrade to update the checksums
| BufferLogger logger; | ||
| FakeProcessManager processManager; | ||
|
|
||
| const FakeCommand unzipCommmand = FakeCommand( |
for the consts here, I would hoist them out of main at put them at the top level
Done
| currentParent = currentParent.childByName(pathPart) ?? | ||
| currentParent.addChild(SymbolNode(pathPart)); | ||
| } | ||
| // TODO: this shouldn't be necessary, https://github.com/dart-lang/sdk/issues/41137 |
this issue is marked as fixed, is this still necessary
Removed
| return child; | ||
| } | ||
|
|
||
| List<SymbolNode> get ancestors { |
Neither ancestors nor siblings are used in the test, so they currently have 0 coverage
Removed since those are not used (the entire SymbolNode code was copied from here: #51594 (comment))
| return rootNode; | ||
| } | ||
|
|
||
| /// Prints the paths from currentNode all leaf nodes. |
make this documentation more specific. This method seems like it is specific to the lib/ directory in an APK. note that here
Done
|
|
||
| if (currentNode.children.isNotEmpty && !currentNode.name.contains('libapp.so')) { | ||
| for (final SymbolNode child in currentNode.children) { | ||
| _printLibDetails(child, totalPath + '/', aotSizeJson); |
use string concatenation '$totalPath/'
Done
| int numBytes, | ||
| int level, { |
make these two named and required parameters
Done
| _printLibDetails(child, totalPath + '/', aotSizeJson); | ||
| } | ||
| } else { | ||
| // Print total path and size if currentPath does not have any chilren. |
nit: if currentNode does not have any children
Done
| if (libraryUri != null) ...libraryUri.split('/') else '@stubs', | ||
| if (className?.isNotEmpty ?? false) className, |
i'm surprised that we are checking that className.isNotEmpty and checking that libraryUri != null. I would expect these two checks to be the same. Why are we checking for null on one and empty on the other?
Removed
| } else if (path.endsWith('libflutter.so')) { | ||
| childWithPathAsName.name += ' (Flutter Engine)'; | ||
| } | ||
| currentNode.addChild(childWithPathAsName); |
does childWithPathAsName.addValue(pathsToSize[paths]); need to be called before currentNode.addChild(childWithPathAsName); so that the childWithPathAsName's value is summed into currentNode's value when calling addChild?
childWithPathAsName's value should already have been added to currentNode on the previous iteration of the loop.
|
|
||
| final List<SymbolNode> sortedSymbols = root.children.toList() | ||
| ..sort((SymbolNode a, SymbolNode b) => b.value.compareTo(a.value)); | ||
| for (final SymbolNode node in sortedSymbols.take(10)) { |
why 10? put in a const with a descriptive name
Done
| final List<SymbolNode> sortedSymbols = root.children.toList() | ||
| ..sort((SymbolNode a, SymbolNode b) => b.value.compareTo(a.value)); | ||
| for (final SymbolNode node in sortedSymbols.take(10)) { | ||
| _printEntitySize(node.name, node.value, 4); |
same comment here. why 4?
| Map<String, dynamic> apkAnalysisJson, | ||
| List<String> path, | ||
| List<dynamic> aotSizeJson, |
make these required named parameters
Done
| } | ||
|
|
||
| /// Adds breakdown of aot size data as the children of the node at the given path. | ||
| Map<String, dynamic> addAotSizeDataToJson( |
rename to addAotSizeDataToApkAnalysis
Done
|
Note the analysis test https://github.com/flutter/flutter/pull/62495/checks?check_run_id=923824683 Also, high level UX suggestion: I think we should list all the files in devtools, but for CLI where space is limited, the various small files aren't that useful to breakdown. I suggest bundling every single file at the root that's <1kB into just one line that says |
Great start!
| import 'logger.dart'; | ||
|
|
||
| class SizeAnalyzer { | ||
| SizeAnalyzer(this._fileSystem, this._logger, this._processUtils); |
Using required named parameters will be clearer than positional parameters here.
Done
|
|
||
| static const int tableWidth = 80; | ||
|
|
||
| Future<Map<String, dynamic>> analyzeApkSize({ |
From other PR: if this function now does 2 things, reflect it in the name.
| // TODO: implement Windows. | ||
| String unzipOut; | ||
| try { | ||
| unzipOut = (await _processUtils.run(<String>[ |
From other PR: using zipinfo rather than unzip could be an optimization (feel free to do it in a separate PR)
Added a TODO
| // TODO(peterdjlee): Add aot size for all platforms. | ||
| apkAnalysisJson = addAotSizeDataToJson( | ||
| apkAnalysisJson, | ||
| 'lib/arm64-v8a/libapp.so (Dart AOT)'.split('/'), |
add code comments here for what this is doing
Done
| } | ||
| } | ||
|
|
||
| class SymbolNode { |
This can probably be private. Also add comments
Done
| '''; | ||
|
|
||
| setUp(() { | ||
| fileSystem = MemoryFileSystem.test(); |
ah is this why those parameters were in the constructor? There is already a dependency injection mechanism. See other tests using testUsingContext. Then you can remove those construction params.
@jonahwilliams helped me put in those constructors explaining we might not want to use the global keyword. Would removing the construction params force us to use the global keyword?
Yes, new code should not use the global zone injection - it makes testing the code very brittle as we've found that new contributors frequently do not understand which interfaces need to be overriden.
So the new consensus is to do constructor injection through the entire graph? Is there a tracking bug to migrate existing code?
#47161 , but I haven't done a good job of keeping it updated
|
Analysis test is failing with |
| @@ -0,0 +1,166 @@ | |||
| // Copyright 2014 The Flutter Authors. All rights reserved. | |||
2020
confusingly it should be 2014
| } | ||
| return json; | ||
| } | ||
| } |
new line at end of file
| ); | ||
| logger.printStatus('━' * tableWidth); | ||
| final Directory tempApkContent = fileSystem.systemTempDirectory.createTempSync('flutter_tools.'); | ||
| // TODO: implement Windows. |
This needs a bug assigned to it
| tempApkContent.path | ||
| ])).stdout; | ||
| } on Exception catch (e) { | ||
| print(e); |
this should be a printError and not a regular print
Done
| // 'path/to/file' where file = 1500 => pathsToSize[['path', 'to', 'file']] = 1500 | ||
| for (final String line in const LineSplitter().convert(unzipOut)) { | ||
| // Expression to match 'Size' column to group 1 and 'Name' column to group 2. | ||
| final RegExp parseUnzipOutput = RegExp(r'^\s*\d+\s+[\w|:]+\s+(\d+)\s+.* (.+)$'); |
Prefer hoisting this into a private static, to avoid recompiling the regexp every time it is created
Done
| /// A pretty printer for an entity with a size. | ||
| void _printEntitySize( | ||
| String entityName, { | ||
| @required int byteSize, |
formatting is off a bit here, indent the required params by 2 spaces
Done
| // 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 'package:file/memory.dart'; |
you need a newline here to make analysis pass
|
You should be able to sync to the latest master to get a correctly formatted pubspec |
| import 'package:flutter_tools/src/base/process.dart'; | ||
| import 'package:flutter_tools/src/base/terminal.dart'; |
Do these need to be relative imports to fix the analyze bot?
Looks like other files have relative imports! Changed to relative imports.
| // TODO(peterdjlee): Implement a way to unzip the APK for Windows. See issue #62603. | ||
| String unzipOut; | ||
| try { | ||
| // TODO(peterdjlee): Use zipinfo instead of unzip. |
FYI, as far as I know we do not document requiring zipinfo to be installed, so switching may not be straightforward
both zipinfo and unzip are built-in on macOS and neither are built-in on Ubuntu so we're more or less in the same position either way.
No, we're not in the same state - we require unzip and warn if it is not installed and do no such thing for zipinfo
ah I see, SG, let's leave it then. This makes it simpler.
LGTM
|
As soon as https://cirrus-ci.com/task/5692958575951872 finishes this should be good to land. The g3 breakage is expected because we've added a new dependency (not in this PR but a prior one) |
… generate analysis json (flutter#62495) * Implement size analyzer to unzip & parse APK and AOT size snapshot to generate analysis json
… generate analysis json (flutter#62495) * Implement size analyzer to unzip & parse APK and AOT size snapshot to generate analysis json
Description
Implemented

SizeAnalyzerwhere an APK file and an AOT size snapshot can be passed in throughanalyzeApkSize(apk, aotSizeJson)which returns a Map that contains the breakdown of APK data with AOT snapshot data included under libapp.so. `analyzeApkSize(apk, aotSizeJson) also prints out a top level summary to the terminal as such:Related Issues
#51594
Tests
I added a test file
analyze_size_test.dartto test that this toolChecklist
Before you create this PR, confirm that it meets all requirements listed below by checking the relevant checkboxes (
[x]). This will ensure a smooth and quick review process.///).flutter analyze --flutter-repo) does not report any problems on my PR.Breaking Change
Did any tests fail when you ran them? Please read Handling breaking changes.
The text was updated successfully, but these errors were encountered: