Unverified Commit 8d3bc184 authored by Jenn Magder's avatar Jenn Magder Committed by GitHub

Migrate analyze_size to null safety (#81002)

parent b8833afc
......@@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// @dart = 2.8
import 'package:archive/archive.dart';
import 'package:archive/archive_io.dart';
import 'package:meta/meta.dart';
......@@ -11,6 +9,7 @@ import 'package:vm_snapshot_analysis/treemap.dart';
import '../convert.dart';
import '../reporting/reporting.dart';
import 'common.dart';
import 'file_system.dart';
import 'logger.dart';
import 'terminal.dart';
......@@ -18,10 +17,9 @@ import 'terminal.dart';
/// A class to analyze APK and AOT snapshot and generate a breakdown of the data.
class SizeAnalyzer {
SizeAnalyzer({
@required FileSystem fileSystem,
@required Logger logger,
// TODO(jonahwilliams): migrate to required once this has rolled into google3.
Usage flutterUsage,
required FileSystem fileSystem,
required Logger logger,
required Usage flutterUsage,
Pattern appFilenamePattern = 'libapp.so',
}) : _flutterUsage = flutterUsage,
_fileSystem = fileSystem,
......@@ -32,7 +30,7 @@ class SizeAnalyzer {
final Logger _logger;
final Pattern _appFilenamePattern;
final Usage _flutterUsage;
String _appFilename;
String? _appFilename;
static const String aotSnapshotFileName = 'aot-snapshot.json';
static const int tableWidth = 80;
......@@ -40,12 +38,12 @@ class SizeAnalyzer {
static const int _kZipSizeMaxDepth = 1;
/// Analyze the [aotSnapshot] in an uncompressed output directory.
Future<Map<String, dynamic>> analyzeAotSnapshot({
@required Directory outputDirectory,
@required File aotSnapshot,
@required File precompilerTrace,
@required String type,
String excludePath,
Future<Map<String, Object?>> analyzeAotSnapshot({
required Directory outputDirectory,
required File aotSnapshot,
required File precompilerTrace,
required String type,
String? excludePath,
}) async {
_logger.printStatus('▒' * tableWidth);
_logger.printStatus('━' * tableWidth);
......@@ -56,10 +54,12 @@ class SizeAnalyzer {
);
// Convert an AOT snapshot file into a map.
final Map<String, dynamic> processedAotSnapshotJson = treemapFromJson(
json.decode(aotSnapshot.readAsStringSync()),
);
final _SymbolNode aotSnapshotJsonRoot = _parseAotSnapshot(processedAotSnapshotJson);
final Object? decodedAotSnapshot = json.decode(aotSnapshot.readAsStringSync());
if (decodedAotSnapshot == null) {
throwToolExit('AOT snapshot is invalid for analysis');
}
final Map<String, Object?> processedAotSnapshotJson = treemapFromJson(decodedAotSnapshot);
final _SymbolNode? aotSnapshotJsonRoot = _parseAotSnapshot(processedAotSnapshotJson);
for (final _SymbolNode firstLevelPath in aotAnalysisJson.children) {
_printEntitySize(
......@@ -68,14 +68,14 @@ class SizeAnalyzer {
level: 1,
);
// Print the expansion of lib directory to show more info for `appFilename`.
if (firstLevelPath.name == _fileSystem.path.basename(outputDirectory.path)) {
if (firstLevelPath.name == _fileSystem.path.basename(outputDirectory.path) && aotSnapshotJsonRoot != null) {
_printLibChildrenPaths(firstLevelPath, '', aotSnapshotJsonRoot, _kAotSizeMaxDepth, 0);
}
}
_logger.printStatus('▒' * tableWidth);
Map<String, dynamic> apkAnalysisJson = aotAnalysisJson.toJson();
Map<String, Object?> apkAnalysisJson = aotAnalysisJson.toJson();
apkAnalysisJson['type'] = type; // one of apk, aab, ios, macos, windows, or linux.
......@@ -83,7 +83,7 @@ class SizeAnalyzer {
apkAnalysisJson: apkAnalysisJson,
path: _locatedAotFilePath,
aotSnapshotJson: processedAotSnapshotJson,
precompilerTrace: json.decode(precompilerTrace.readAsStringSync()) as Map<String, Object>,
precompilerTrace: json.decode(precompilerTrace.readAsStringSync()) as Map<String, Object?>? ?? <String, Object?>{},
);
assert(_appFilename != null);
......@@ -97,11 +97,11 @@ class SizeAnalyzer {
///
/// [kind] must be one of 'apk' or 'aab'.
/// The [aotSnapshot] can be either instruction sizes snapshot or a v8 snapshot.
Future<Map<String, dynamic>> analyzeZipSizeAndAotSnapshot({
@required File zipFile,
@required File aotSnapshot,
@required File precompilerTrace,
@required String kind,
Future<Map<String, Object?>> analyzeZipSizeAndAotSnapshot({
required File zipFile,
required File aotSnapshot,
required File precompilerTrace,
required String kind,
}) async {
assert(kind == 'apk' || kind == 'aab');
_logger.printStatus('▒' * tableWidth);
......@@ -116,16 +116,20 @@ class SizeAnalyzer {
final _SymbolNode apkAnalysisRoot = _parseUnzipFile(zipFile);
// Convert an AOT snapshot file into a map.
final Map<String, dynamic> processedAotSnapshotJson = treemapFromJson(
json.decode(aotSnapshot.readAsStringSync()),
);
final _SymbolNode aotSnapshotJsonRoot = _parseAotSnapshot(processedAotSnapshotJson);
for (final _SymbolNode firstLevelPath in apkAnalysisRoot.children) {
_printLibChildrenPaths(firstLevelPath, '', aotSnapshotJsonRoot, _kZipSizeMaxDepth, 0);
final Object? decodedAotSnapshot = json.decode(aotSnapshot.readAsStringSync());
if (decodedAotSnapshot == null) {
throwToolExit('AOT snapshot is invalid for analysis');
}
final Map<String, Object?> processedAotSnapshotJson = treemapFromJson(decodedAotSnapshot);
final _SymbolNode? aotSnapshotJsonRoot = _parseAotSnapshot(processedAotSnapshotJson);
if (aotSnapshotJsonRoot != null) {
for (final _SymbolNode firstLevelPath in apkAnalysisRoot.children) {
_printLibChildrenPaths(firstLevelPath, '', aotSnapshotJsonRoot, _kZipSizeMaxDepth, 0);
}
}
_logger.printStatus('▒' * tableWidth);
Map<String, dynamic> apkAnalysisJson = apkAnalysisRoot.toJson();
Map<String, Object?> apkAnalysisJson = apkAnalysisRoot.toJson();
apkAnalysisJson['type'] = kind;
......@@ -134,7 +138,7 @@ class SizeAnalyzer {
apkAnalysisJson: apkAnalysisJson,
path: _locatedAotFilePath,
aotSnapshotJson: processedAotSnapshotJson,
precompilerTrace: json.decode(precompilerTrace.readAsStringSync()) as Map<String, Object>,
precompilerTrace: json.decode(precompilerTrace.readAsStringSync()) as Map<String, Object?>? ?? <String, Object?>{},
);
CodeSizeEvent(kind, flutterUsage: _flutterUsage).send();
return apkAnalysisJson;
......@@ -145,12 +149,15 @@ class SizeAnalyzer {
final Map<List<String>, int> pathsToSize = <List<String>, int>{};
for (final ArchiveFile archiveFile in archive.files) {
pathsToSize[_fileSystem.path.split(archiveFile.name)] = archiveFile.rawContent.length;
final InputStream? rawContent = archiveFile.rawContent;
if (rawContent != null) {
pathsToSize[_fileSystem.path.split(archiveFile.name)] = rawContent.length;
}
}
return _buildSymbolTree(pathsToSize);
}
_SymbolNode _parseDirectory(Directory directory, String relativeTo, String excludePath) {
_SymbolNode _parseDirectory(Directory directory, String relativeTo, String? excludePath) {
final Map<List<String>, int> pathsToSize = <List<String>, int>{};
for (final File file in directory.listSync(recursive: true).whereType<File>()) {
if (excludePath != null && file.uri.pathSegments.contains(excludePath)) {
......@@ -163,9 +170,9 @@ class SizeAnalyzer {
return _buildSymbolTree(pathsToSize);
}
List<String> _locatedAotFilePath;
List<String> _locatedAotFilePath = <String>[];
List<String> _buildNodeName(_SymbolNode start, _SymbolNode parent) {
List<String> _buildNodeName(_SymbolNode start, _SymbolNode? parent) {
final List<String> results = <String>[start.name];
while (parent != null && parent.name != 'Root') {
results.insert(0, parent.name);
......@@ -180,7 +187,7 @@ class SizeAnalyzer {
for (final List<String> paths in pathsToSize.keys) {
for (final String path in paths) {
_SymbolNode childWithPathAsName = currentNode.childByName(path);
_SymbolNode? childWithPathAsName = currentNode.childByName(path);
if (childWithPathAsName == null) {
childWithPathAsName = _SymbolNode(path);
......@@ -193,7 +200,7 @@ class SizeAnalyzer {
}
currentNode.addChild(childWithPathAsName);
}
childWithPathAsName.addSize(pathsToSize[paths]);
childWithPathAsName.addSize(pathsToSize[paths] ?? 0);
currentNode = childWithPathAsName;
}
currentNode = rootNode;
......@@ -240,7 +247,7 @@ class SizeAnalyzer {
/// Go through the AOT gen snapshot size JSON and print out a collapsed summary
/// for the first package level.
void _printAotSnapshotSummary(_SymbolNode aotSnapshotRoot, {int maxDirectoriesShown = 20, @required int level}) {
void _printAotSnapshotSummary(_SymbolNode aotSnapshotRoot, {int maxDirectoriesShown = 20, required int level}) {
_printEntitySize(
'Dart AOT symbols accounted decompressed size',
byteSize: aotSnapshotRoot.byteSize,
......@@ -273,19 +280,19 @@ class SizeAnalyzer {
}
/// Adds breakdown of aot snapshot data as the children of the node at the given path.
Map<String, dynamic> _addAotSnapshotDataToAnalysis({
@required Map<String, dynamic> apkAnalysisJson,
@required List<String> path,
@required Map<String, dynamic> aotSnapshotJson,
@required Map<String, dynamic> precompilerTrace,
Map<String, Object?> _addAotSnapshotDataToAnalysis({
required Map<String, Object?> apkAnalysisJson,
required List<String> path,
required Map<String, Object?> aotSnapshotJson,
required Map<String, Object?> precompilerTrace,
}) {
Map<String, dynamic> currentLevel = apkAnalysisJson;
Map<String, Object?> currentLevel = apkAnalysisJson;
currentLevel['precompiler-trace'] = precompilerTrace;
while (path.isNotEmpty) {
final List<Map<String, dynamic>> children = currentLevel['children'] as List<Map<String, dynamic>>;
final Map<String, dynamic> childWithPathAsName = children.firstWhere(
(Map<String, dynamic> child) => child['n'] as String == path.first,
);
final List<Map<String, Object?>>? children = currentLevel['children'] as List<Map<String, Object?>>?;
final Map<String, Object?> childWithPathAsName = children?.firstWhere(
(Map<String, Object?> child) => (child['n'] as String?) == path.first,
) ?? <String, Object?>{};
path.removeAt(0);
currentLevel = childWithPathAsName;
}
......@@ -298,8 +305,8 @@ class SizeAnalyzer {
/// Print an entity's name with its size on the same line.
void _printEntitySize(
String entityName, {
@required int byteSize,
@required int level,
required int byteSize,
required int level,
bool showColor = true,
bool emphasis = false,
}) {
......@@ -354,13 +361,13 @@ class SizeAnalyzer {
}
}
_SymbolNode _parseAotSnapshot(Map<String, dynamic> aotSnapshotJson) {
_SymbolNode? _parseAotSnapshot(Map<String, Object?> aotSnapshotJson) {
final bool isLeafNode = aotSnapshotJson['children'] == null;
if (!isLeafNode) {
return _buildNodeWithChildren(aotSnapshotJson);
} else {
// TODO(peterdjlee): Investigate why there are leaf nodes with size of null.
final int byteSize = aotSnapshotJson['value'] as int;
final int? byteSize = aotSnapshotJson['value'] as int?;
if (byteSize == null) {
return null;
}
......@@ -369,11 +376,11 @@ class SizeAnalyzer {
}
_SymbolNode _buildNode(
Map<String, dynamic> aotSnapshotJson,
Map<String, Object?> aotSnapshotJson,
int byteSize, {
List<_SymbolNode> children = const <_SymbolNode>[],
}) {
final String name = aotSnapshotJson['n'] as String;
final String name = aotSnapshotJson['n']! as String;
final Map<String, _SymbolNode> childrenMap = <String, _SymbolNode>{};
for (final _SymbolNode child in children) {
......@@ -388,16 +395,21 @@ class SizeAnalyzer {
/// Builds a node by recursively building all of its children first
/// in order to calculate the sum of its children's sizes.
_SymbolNode _buildNodeWithChildren(Map<String, dynamic> aotSnapshotJson) {
final List<dynamic> rawChildren = aotSnapshotJson['children'] as List<dynamic>;
_SymbolNode? _buildNodeWithChildren(Map<String, Object?> aotSnapshotJson) {
final List<Object?> rawChildren = aotSnapshotJson['children'] as List<Object?>? ?? <Object?>[];
final List<_SymbolNode> symbolNodeChildren = <_SymbolNode>[];
int totalByteSize = 0;
// Given a child, build its subtree.
for (final dynamic child in rawChildren) {
final _SymbolNode childTreemapNode = _parseAotSnapshot(child as Map<String, dynamic>);
symbolNodeChildren.add(childTreemapNode);
totalByteSize += childTreemapNode.byteSize;
for (final Object? child in rawChildren) {
if (child == null) {
continue;
}
final _SymbolNode? childTreemapNode = _parseAotSnapshot(child as Map<String, Object?>);
if (childTreemapNode != null) {
symbolNodeChildren.add(childTreemapNode);
totalByteSize += childTreemapNode.byteSize;
}
}
// If none of the children matched the diff tree type
......@@ -430,13 +442,13 @@ class _SymbolNode {
byteSize += sizeToBeAdded;
}
_SymbolNode get parent => _parent;
_SymbolNode _parent;
_SymbolNode? get parent => _parent;
_SymbolNode? _parent;
Iterable<_SymbolNode> get children => _children.values;
final Map<String, _SymbolNode> _children;
_SymbolNode childByName(String name) => _children[name];
_SymbolNode? childByName(String name) => _children[name];
_SymbolNode addChild(_SymbolNode child) {
assert(child.parent == null);
......@@ -452,12 +464,12 @@ class _SymbolNode {
children.forEach(addChild);
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> json = <String, dynamic>{
Map<String, Object?> toJson() {
final Map<String, Object?> json = <String, Object?>{
'n': name,
'value': byteSize
};
final List<Map<String, dynamic>> childrenAsJson = <Map<String, dynamic>>[];
final List<Map<String, Object?>> childrenAsJson = <Map<String, Object?>>[];
for (final _SymbolNode child in children) {
childrenAsJson.add(child.toJson());
}
......@@ -470,7 +482,7 @@ class _SymbolNode {
/// Matches `pattern` against the entirety of `string`.
@visibleForTesting
Match matchesPattern(String string, {@required Pattern pattern}) {
final Match match = pattern.matchAsPrefix(string);
Match? matchesPattern(String string, {required Pattern pattern}) {
final Match? match = pattern.matchAsPrefix(string);
return (match != null && match.end == string.length) ? match : null;
}
......@@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// @dart = 2.8
import 'package:archive/archive.dart';
import 'package:file/memory.dart';
import 'package:flutter_tools/src/base/analyze_size.dart';
......@@ -43,15 +41,15 @@ const String aotSizeOutput = '''
''';
void main() {
MemoryFileSystem fileSystem;
BufferLogger logger;
late MemoryFileSystem fileSystem;
late BufferLogger logger;
setUp(() {
fileSystem = MemoryFileSystem.test();
logger = BufferLogger.test();
});
test('matchesPattern matches only entire strings', () {
testWithoutContext('matchesPattern matches only entire strings', () {
expect(matchesPattern('', pattern: ''), isNotNull);
expect(matchesPattern('', pattern: 'foo'), null);
expect(matchesPattern('foo', pattern: ''), null);
......@@ -62,7 +60,7 @@ void main() {
expect(matchesPattern('foobar', pattern: RegExp(r'.*b')), null);
});
test('builds APK analysis correctly', () async {
testWithoutContext('builds APK analysis correctly', () async {
final SizeAnalyzer sizeAnalyzer = SizeAnalyzer(
fileSystem: fileSystem,
logger: logger,
......@@ -78,7 +76,7 @@ void main() {
..addFile(ArchiveFile('lib/arm64-v8a/libflutter.so', 50, List<int>.filled(50, 0)));
final File apk = fileSystem.file('test.apk')
..writeAsBytesSync(ZipEncoder().encode(archive));
..writeAsBytesSync(ZipEncoder().encode(archive)!);
final File aotSizeJson = fileSystem.file('test.json')
..createSync()
..writeAsStringSync(aotSizeOutput);
......@@ -139,7 +137,7 @@ void main() {
expect(result['precompiler-trace'], <String, Object>{});
});
test('outputs summary to command line correctly', () async {
testWithoutContext('outputs summary to command line correctly', () async {
final SizeAnalyzer sizeAnalyzer = SizeAnalyzer(
fileSystem: fileSystem,
logger: logger,
......@@ -155,7 +153,7 @@ void main() {
..addFile(ArchiveFile('lib/arm64-v8a/libflutter.so', 50, List<int>.filled(50, 0)));
final File apk = fileSystem.file('test.apk')
..writeAsBytesSync(ZipEncoder().encode(archive));
..writeAsBytesSync(ZipEncoder().encode(archive)!);
final File aotSizeJson = fileSystem.file('test.json')
..writeAsStringSync(aotSizeOutput);
final File precompilerTrace = fileSystem.file('trace.json')
......@@ -181,7 +179,7 @@ void main() {
);
});
test('can analyze contents of output directory', () async {
testWithoutContext('can analyze contents of output directory', () async {
final SizeAnalyzer sizeAnalyzer = SizeAnalyzer(
fileSystem: fileSystem,
logger: logger,
......@@ -202,7 +200,7 @@ void main() {
final File precompilerTrace = fileSystem.file('trace.json')
..writeAsStringSync('{}');
final Map<String, Object> result = await sizeAnalyzer.analyzeAotSnapshot(
final Map<String, Object?> result = await sizeAnalyzer.analyzeAotSnapshot(
outputDirectory: outputDirectory,
aotSnapshot: aotSizeJson,
precompilerTrace: precompilerTrace,
......@@ -223,4 +221,36 @@ void main() {
expect(result['type'], 'linux');
expect(result['precompiler-trace'], <String, Object>{});
});
testWithoutContext('handles null AOT snapshot json', () async {
final SizeAnalyzer sizeAnalyzer = SizeAnalyzer(
fileSystem: fileSystem,
logger: logger,
appFilenamePattern: RegExp(r'lib.*app\.so'),
flutterUsage: TestUsage(),
);
final Directory outputDirectory = fileSystem.directory('example/out/foo.app')..createSync(recursive: true);
final File invalidAotSizeJson = fileSystem.file('test.json')..writeAsStringSync('null');
final File precompilerTrace = fileSystem.file('trace.json');
await expectLater(
() => sizeAnalyzer.analyzeAotSnapshot(
outputDirectory: outputDirectory,
aotSnapshot: invalidAotSizeJson,
precompilerTrace: precompilerTrace,
type: 'linux',
),
throwsToolExit());
final File apk = fileSystem.file('test.apk')..writeAsBytesSync(ZipEncoder().encode(Archive())!);
await expectLater(
() => sizeAnalyzer.analyzeZipSizeAndAotSnapshot(
zipFile: apk,
aotSnapshot: invalidAotSizeJson,
precompilerTrace: precompilerTrace,
kind: 'apk',
),
throwsToolExit());
});
}
......@@ -9,6 +9,7 @@ import 'package:file_testing/file_testing.dart';
import 'package:flutter_tools/src/base/io.dart';
import '../src/common.dart';
import '../src/context.dart';
import 'test_utils.dart';
const String apkDebugMessage = 'A summary of your APK analysis can be found at: ';
......@@ -16,7 +17,7 @@ const String iosDebugMessage = 'A summary of your iOS bundle analysis can be fou
const String runDevToolsMessage = 'flutter pub global activate devtools; flutter pub global run devtools ';
void main() {
testWithoutContext('--analyze-size flag produces expected output on hello_world for Android', () async {
testUsingContext('--analyze-size flag produces expected output on hello_world for Android', () async {
final String workingDirectory = fileSystem.path.join(getFlutterRoot(), 'examples', 'hello_world');
final String flutterBin = fileSystem.path.join(getFlutterRoot(), 'bin', 'flutter');
final ProcessResult result = await processManager.run(<String>[
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment