blob: 3893c45620ea91f773b878e1c83f0a4c497d6e45 [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:collection';
import 'dart:core';
import '../common_util.dart';
import '../render/ast.dart';
import '../types.dart';
import 'index.dart';
class CrateMetadata {
CrateMetadata(this.name, this.tally, this.programs);
factory CrateMetadata.initial(String name) =>
CrateMetadata(name, Tally.zero(), <String, Tally>{});
CrateMetadata mergeWith(CrateMetadata other) {
tally += other.tally;
mergeMapInto(programs, other.programs);
return this;
}
/// Name of the crate.
final String name;
/// Size information for this crate.
/// It should be equal to the sum of all tallies in `programs`.
Tally tally;
/// Size information for this crate broken down by programs that
/// use the crate. The map key is the pretty-printed path of an ELF binary.
Map<String, Tally> programs;
}
/// Custom crate size record for output rendering.
class CratesSizeRecord extends SizeRecord {
final int numPrograms;
CratesSizeRecord({StyledString name, Tally tally, this.numPrograms})
: super(name: name, tally: tally);
}
/// Aggregates Rust crates and associated metadata.
class CratesQuery extends Query {
static const String description =
'Dumps crates in Rust binaries and aggregates their size.';
@override
String getDescription() => description;
CratesQuery({this.showProgram = false});
/// When true, show the ELF binaries containing each individual crates
/// when printing outputs.
final bool showProgram;
@override
void addReport(Report report) {
if (report.compileUnits == null)
throw Exception('Error loading compile units');
for (final compileUnit in report.compileUnits) {
for (final symbol in compileUnit.symbols) {
if (symbol.maybeRustCrate != null && symbol.maybeRustCrate.isNotEmpty) {
_crates.putIfAbsent(symbol.maybeRustCrate,
() => CrateMetadata.initial(symbol.maybeRustCrate));
_crates[symbol.maybeRustCrate]
..tally += Tally(symbol.sizes.fileActual, 1)
..programs
.putIfAbsent(report.context.name, Tally.zero)
.mergeWith(Tally(symbol.sizes.fileActual, 1));
}
}
}
}
@override
void mergeWith(Iterable<Query> others) {
for (final other in others) {
if (other is CratesQuery) {
for (final entry in other._crates.entries) {
_crates
.putIfAbsent(entry.key, () => CrateMetadata.initial(entry.key))
.mergeWith(entry.value);
}
} else {
throw Exception('$other must be $runtimeType');
}
}
}
@override
QueryReport distill() => CratesReport(_crates, showProgram: showProgram);
/// Mapping from crate name to crate details.
final _crates = <String, CrateMetadata>{};
@override
String toString() {
if (_crates.entries.isEmpty) {
return 'Nothing selected';
}
return _crates.entries.map((e) => '${e.key}: ${e.value.tally}').join('\n');
}
}
class CratesReport implements QueryReport {
CratesReport(Map<String, CrateMetadata> stats, {this.showProgram}) {
int compareCrates(String k1, String k2) {
final compareTally = stats[k1].tally.compareTo(stats[k2].tally);
if (compareTally != 0) return compareTally;
return k1.compareTo(k2);
}
_stats = SplayTreeMap<String, CrateMetadata>.of(
stats, (k1, k2) => -compareCrates(k1, k2));
}
@override
Iterable<AnyNode> export() {
if (_stats.entries.isEmpty) {
return [Node.plain('Nothing selected')];
}
return _stats.entries.map((entry) {
return Node(
title: CratesSizeRecord(
name: AddColor.green(Plain(entry.key)),
tally: entry.value.tally,
numPrograms: entry.value.programs.length),
children: [
if (showProgram)
Node(
title: StyledString([
for (final statistics in [
Statistics(entry.value.programs.values)
])
Plain('Detected in ${statistics.count} binaries. '
'avg: ${formatSize(statistics.mean.round())} '
'(${formatSize(statistics.min)} to '
'${formatSize(statistics.max)}), '
'stdev: ${formatSize(statistics.stdev.round())}.'),
if (entry.value.programs.length != null)
Plain(' Top appearance:'),
]),
children: [
for (final program in sortBinaries(entry.value.programs)
.take(numProgramsToShow))
Node.plain(
'${program.key} => ${formatSize(program.value.size)}')
])
]);
});
}
/// See `CratesQuery.showProgram`.
final bool showProgram;
// TODO(yifeit): Convert this to a configurable value similar to `showProgram`
static const int numProgramsToShow = 5;
SplayTreeMap<String, CrateMetadata> _stats;
}