blob: c60572658bbfe1036b12fdd325035ecaca7d6ccb [file] [log] [blame]
// 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();
}