|  | // 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(); | 
|  | } |