| // Copyright 2020 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // @dart = 2.8 |
| |
| /// Functions for running bloaty over some ELF binaries. |
| library bloaty; |
| |
| import 'dart:collection'; |
| import 'dart:core'; |
| import 'dart:io'; |
| |
| import 'package:pool/pool.dart'; |
| |
| import 'build.dart'; |
| import 'io.dart'; |
| |
| class RunBloatyOptions { |
| final Build build; |
| |
| final HashMap<String, SplayTreeSet<BuildArtifact>> artifactsByBuildId; |
| final HashMap<String, SplayTreeSet<File>> debugBinaries; |
| final HashMap<String, File> buildIdToLinkMapFile; |
| final HashMap<String, String> buildIdToAccessPattern; |
| final int heatmapFrameSize; |
| |
| final void Function(int) jobInitCallback; |
| final void Function() jobIterationCallback; |
| final void Function() jobCompleteCallback; |
| |
| RunBloatyOptions( |
| {this.build, |
| this.artifactsByBuildId, |
| this.debugBinaries, |
| this.buildIdToLinkMapFile, |
| this.buildIdToAccessPattern, |
| this.heatmapFrameSize, |
| this.jobInitCallback, |
| this.jobIterationCallback, |
| this.jobCompleteCallback}); |
| |
| /// Get the CIPD-compatible name of the host operating system. |
| /// This is used to locate the bloaty prebuilt. |
| String get hostPlatform { |
| if (Platform.isLinux) return 'linux-x64'; |
| if (Platform.isMacOS) return 'mac-x64'; |
| throw Exception('Unsupported operating system'); |
| } |
| } |
| |
| /// Runs bloaty on all binaries in the set `buildIds`, saving their |
| /// results in report files next to the binaries themselves. |
| /// `buildIds` should be a set of build-ids. |
| /// |
| /// If `options.buildIdToLinkMapFile` contains a link map for the corresponding |
| /// binary, also supply that option to bloaty. Link maps will help bloaty |
| /// produce much more accurate size breakdowns. |
| Future<void> runBloatyOnMatchedBinaries(Set<String> buildIds, |
| {RunBloatyOptions options}) async { |
| final io = Io.get(); |
| final fuchsiaDir = Directory(Platform.environment['FUCHSIA_DIR']); |
| final pathToBloaty = fuchsiaDir / |
| Directory('prebuilt/third_party/bloaty/${options.hostPlatform}/bloaty'); |
| final pool = Pool(Platform.numberOfProcessors); |
| final allFutures = <Future>[]; |
| options.jobInitCallback?.call(buildIds.length); |
| for (final buildId in buildIds) { |
| final blob = options.artifactsByBuildId[buildId].first; |
| final debugElf = options.debugBinaries[buildId].first; |
| |
| Future<void> _bloaty( |
| {List<String> dataSourceArgs = const [ |
| '-d', |
| 'compileunits,symbols', |
| ], |
| String reportSuffix = '.bloaty_report_pb'}) async { |
| final result = await io.processManager.run([ |
| pathToBloaty.absolute.path, |
| '--pb', |
| ...dataSourceArgs, |
| '-n', |
| '0', |
| '-s', |
| 'file', |
| '--demangle=full', |
| '--debug-file=${debugElf.absolute.path}', |
| if (options.buildIdToLinkMapFile.containsKey(buildId)) |
| '--link-map-file=${options.buildIdToLinkMapFile[buildId].absolute.path}', |
| options.build.openFile(blob.buildPath).absolute.path |
| ], |
| // Accommodate binary protobuf data |
| stdoutEncoding: null); |
| if (result.exitCode != 0) { |
| print(result.stdout); |
| print(result.stderr); |
| throw Exception('Failed to inspect ${blob.buildPath} ' |
| '(debug file: ${debugElf.absolute.path})'); |
| } |
| // Save stdout as protobuf |
| final output = options.build.openFile('${blob.buildPath}$reportSuffix'); |
| await output.writeAsBytes(result.stdout, flush: true); |
| } |
| |
| if (options.buildIdToAccessPattern != null) { |
| // Generate two reports, one filtered and one unfiltered by access pattern |
| allFutures |
| ..add(pool |
| .withResource(_bloaty) |
| .then((x) => options.jobIterationCallback?.call())) |
| ..add(pool.withResource(() => _bloaty(dataSourceArgs: [ |
| '--cold-bytes-filter', |
| options.buildIdToAccessPattern[buildId], |
| '--access-pattern-frame-size', |
| options.heatmapFrameSize.toString(), |
| '-d', |
| 'accesspattern,compileunits,symbols', |
| ], reportSuffix: '.filtered.bloaty_report_pb'))); |
| } else { |
| // Only generate the unfiltered report |
| allFutures.add(pool |
| .withResource(_bloaty) |
| .then((x) => options.jobIterationCallback?.call())); |
| } |
| } |
| await Future.wait(allFutures); |
| options.jobCompleteCallback?.call(); |
| } |