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