blob: e76ccbd4e960f012323fa5b6a710b2d8f8774d25 [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
import 'dart:io';
import 'dart:typed_data';
import 'package:protobuf/protobuf.dart' as pb;
import 'common_util.dart';
import 'queries/index.dart';
import 'report.pb.dart' as bloaty_report;
/// A list of bloaty reports to analyze.
/// Generated from https://javiercbk.github.io/json_to_dart/.
class AnalysisRequest {
List<AnalysisItem> items;
/// The SHA-256 hash of the access heatmap file used to generate the reports.
/// If a heatmap file was not used, this field may be null.
String heatmapContentSha;
AnalysisRequest({this.items, this.heatmapContentSha});
AnalysisRequest.fromJson(Map<String, dynamic> json) {
if (json['items'] != null) {
items = <AnalysisItem>[];
json['items'].forEach((v) {
items.add(AnalysisItem.fromJson(v));
});
}
if (json['heatmap_content_sha'] != null) {
heatmapContentSha = json['heatmap_content_sha'];
}
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
if (items != null) {
data['items'] = items.map((v) => v.toJson()).toList();
}
if (heatmapContentSha != null) {
data['heatmap_content_sha'] = heatmapContentSha;
}
return data;
}
}
/// Generated from https://javiercbk.github.io/json_to_dart/.
class AnalysisItem {
String path;
/// If the access heatmap is not used, this field may be null.
String filteredCounterpart;
String name;
AnalysisItem({this.path, this.filteredCounterpart, this.name});
AnalysisItem.fromJson(Map<String, dynamic> json) {
path = json['path'];
filteredCounterpart = json['filtered_counterpart'];
name = json['name'];
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
data['path'] = path;
data['filtered_counterpart'] = filteredCounterpart;
data['name'] = name;
return data;
}
AnalysisItem absolute(String basePath) {
String abs(String p) => File(p).isAbsolute ? p : _pathJoin(basePath, p);
final newPath = abs(path);
final newFilteredCounterpart = flatMap(filteredCounterpart, abs);
return AnalysisItem(
path: newPath, filteredCounterpart: newFilteredCounterpart, name: name);
}
}
String _pathJoin(String part1, String part2) {
final buffer = StringBuffer()..write(part1);
if (!part1.endsWith('/')) buffer.write('/');
buffer.write(part2);
return buffer.toString();
}
// Add our custom functionality on top of the protobuf generated types.
// See https://fuchsia.googlesource.com/third_party/bloaty/+/refs/heads/fuchsia/src/report.proto
// for the proto message definitions.
/// A tuple representing the file size and memory size of some entity.
class SizeInfo {
SizeInfo(this.fileActual, this.vmActual);
SizeInfo.fromBloaty(bloaty_report.SizeInfo sizeInfo)
: fileActual = sizeInfo.fileActual.toInt(),
vmActual = sizeInfo.vmActual.toInt();
bloaty_report.SizeInfo toBloaty() => bloaty_report.SizeInfo()
..fileActual = fileActual
..vmActual = vmActual
..freeze();
final int fileActual;
final int vmActual;
@override
String toString() =>
'SizeInfo { fileActual: $fileActual, vmActual: $vmActual }';
}
/// An ELF symbol, containing name, size, and optional associated rust crate
/// information.
class Symbol {
Symbol(this.sizes, this.name, this.maybeRustCrate);
Symbol.fromBloaty(bloaty_report.Symbol symbol)
: sizes = SizeInfo.fromBloaty(symbol.sizes),
name = normalizeSymbolNames(symbol.name),
maybeRustCrate = symbol.maybeRustCrate;
bloaty_report.Symbol toBloaty() => bloaty_report.Symbol()
..sizes = sizes.toBloaty()
..name = name
..maybeRustCrate = maybeRustCrate
..freeze();
static String normalizeSymbolNames(String name) {
// Outlined functions have names like OUTLINED_FUNCTION_0, which can
// appear 1000+ time, and can cause false aliasing. We treat these as
// special cases by designating them as a placeholder symbols and
// renaming them to '** outlined function'.
if (name.startsWith('OUTLINED_FUNCTION_')) {
return '** outlined function';
}
return name;
}
/// The file and memory size of this symbol.
final SizeInfo sizes;
/// The demangled name of this symbol.
/// Demangling is currently performed by bloaty.
final String name;
/// If not empty, indicates that this symbol was instantiated when compiling
/// the specified rust crate. Rust performs crate-local monomorphization,
/// so we might observe for instance an `std::foo` symbol in some user crate.
/// We would attribute the size to that user crate, since it is the specific
/// usage from that user crate that caused this symbol to stay in the binary.
final String maybeRustCrate;
@override
String toString() => 'Symbol{ sizes: $sizes, name: $name }';
}
/// A `CompileUnit` is the unit of compilation, typically one or a group of
/// source files, that is part of a bigger crate/library.
///
/// Our most common C++ setup compiles every file individually, so one
/// compile unit correspond to one `.cc` file in a C++ program.
///
/// Our most common Rust setup compiles a group of Rust files together via
/// ThinLTO, so one compile unit roughly correspond to some subset of a
/// Rust crate, bloaty sometimes is able to pin-point the exact
/// `.rs` file that a symbol lives in.
///
/// It is due to these reasons that the `name` in a `CompileUnit` should only
/// be deemed as a best-effort correspondence to actual file names
/// in the source repository.
class CompileUnit {
CompileUnit(this.sizes, this.symbols, this.name)
: context = CompileUnitContext(name);
CompileUnit.fromBloaty(bloaty_report.CompileUnit compileUnit)
: sizes = SizeInfo.fromBloaty(compileUnit.sizes),
symbols =
compileUnit.symbols?.map((s) => Symbol.fromBloaty(s))?.toList(),
name = compileUnit.name {
context = CompileUnitContext(name);
}
bloaty_report.CompileUnit toBloaty() => bloaty_report.CompileUnit()
..sizes = sizes.toBloaty()
..symbols.addAll(symbols?.map((e) => e.toBloaty())?.toList())
..name = name
..freeze();
/// The file and memory size of this compile unit.
final SizeInfo sizes;
/// The list of symbols contained in this compile unit.
final List<Symbol> symbols;
/// The name of the compile unit. It may be in one of the following format,
/// in decreasing order of descriptiveness:
///
/// ### Source files
/// - foo/bar/baz.c
/// - foo/bar/baz.cc
/// - foo/bar/baz.rs
/// - ...
///
/// ### Rust crates
/// - [crate: foobar]
///
/// ### Fallback to section names
/// - [section: .rodata]
///
/// ### Fallback to segment names
/// - [LOAD #2 [R]]
/// - [LOAD #4 [RW]]
final String name;
/// See `CompileUnitContext`. This object allows queries to attach
/// domain-specific information to the compile unit as they are run, achieving
/// information sharing with separation of concerns.
CompileUnitContext context;
}
/// A hierarchical size breakdown of an ELF binary.
///
/// A report for a binary contains some compile units, which themselves contain
/// some symbols.
class Report {
Report(this.compileUnits, this.fileTotal, this.vmTotal);
/// Create a report from its corresponding protobuf representation.
Report.fromBloaty(String name, bloaty_report.Report report,
{ProgramContext reuseContext})
: compileUnits =
report.compileUnits.map((c) => CompileUnit.fromBloaty(c)).toList(),
fileTotal = report.fileTotal.toInt(),
vmTotal = report.vmTotal.toInt() {
if (reuseContext == null)
context = ProgramContext(name, this);
else
context = reuseContext;
}
/// Deserialize a report in protobuf format from `bytes`.
Report.fromBytes(String name, Uint8List bytes, {ProgramContext reuseContext})
: this.fromBloaty(
name,
bloaty_report.Report.create()
..mergeFromCodedBufferReader(
pb.CodedBufferReader(bytes, sizeLimit: bytes.length)),
reuseContext: reuseContext);
/// Convert the report into its protobuf representation.
bloaty_report.Report toBloaty() => bloaty_report.Report()
..compileUnits.addAll(compileUnits.map((e) => e.toBloaty()).toList())
..fileTotal = fileTotal
..vmTotal = vmTotal
..freeze();
final List<CompileUnit> compileUnits;
/// How many bytes is this binary on disk.
final int fileTotal;
/// How many bytes does this binary take up when loaded into memory.
final int vmTotal;
/// See `ProgramContext`. This object allows queries to attach domain-specific
/// information to the report as they are run, achieving information sharing
/// with separation of concerns.
ProgramContext context;
}