Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 4aaf13a

Browse files
mralephcommit-bot@chromium.org
authored andcommitted
[vm/tool] Improvements to snapshot_analysis summary command.
It now supports filtering by name and displaying additional histogram bucketed by object type. Issue dart-lang/sdk#41249 Cq-Include-Trybots: luci.dart.try:pkg-linux-debug-try,pkg-linux-release-try,pkg-win-release-try,pkg-mac-release-try Change-Id: I5b0dcdbea0fc0b4af36ac5a6331057406087a409 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/151385 Commit-Queue: Vyacheslav Egorov <[email protected]> Reviewed-by: Alexander Markov <[email protected]>
1 parent 91c80bb commit 4aaf13a

File tree

5 files changed

+172
-102
lines changed

5 files changed

+172
-102
lines changed

pkg/vm/lib/snapshot/commands/compare.dart

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,9 @@ Use --narrow flag to limit column widths.''';
4141
help: 'Truncate column content to the given width'
4242
' (${AsciiTable.unlimitedWidth} means do not truncate).',
4343
defaultsTo: AsciiTable.unlimitedWidth.toString())
44-
..addOption('granularity',
45-
help: 'Choose the granularity of the output.',
44+
..addOption('by',
45+
abbr: 'b',
46+
help: 'Choose the breakdown rule for the output.',
4647
allowed: ['method', 'class', 'library', 'package'],
4748
defaultsTo: 'method')
4849
..addFlag('collapse-anonymous-closures', help: '''
@@ -75,7 +76,7 @@ precisely based on their source position (which is included in their name).
7576
final newJsonPath = _checkExists(argResults.rest[1]);
7677
printComparison(oldJsonPath, newJsonPath,
7778
maxWidth: maxWidth,
78-
granularity: _parseHistogramType(argResults['granularity']),
79+
granularity: _parseHistogramType(argResults['by']),
7980
collapseAnonymousClosures: argResults['collapse-anonymous-closures']);
8081
}
8182

@@ -121,12 +122,12 @@ precisely based on their source position (which is included in their name).
121122
final totalDiff = diff.totalSize;
122123

123124
// Compute histogram.
124-
final histogram = SizesHistogram.from(diff, granularity);
125+
final histogram = computeHistogram(diff, granularity);
125126

126127
// Now produce the report table.
127128
const numLargerSymbolsToReport = 30;
128129
const numSmallerSymbolsToReport = 10;
129-
printHistogram(histogram,
130+
printHistogram(diff, histogram,
130131
sizeHeader: 'Diff (Bytes)',
131132
prefix: histogram.bySize
132133
.where((k) => histogram.buckets[k] > 0)

pkg/vm/lib/snapshot/commands/summary.dart

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,16 @@ This tool can process snapshot size reports produced by
3535
help: 'Truncate column content to the given width'
3636
' (${AsciiTable.unlimitedWidth} means do not truncate).',
3737
defaultsTo: AsciiTable.unlimitedWidth.toString())
38-
..addOption('granularity',
39-
help: 'Choose the granularity of the output.',
38+
..addOption('by',
39+
abbr: 'b',
40+
help: 'Choose breakdown rule of the output.',
4041
allowed: ['method', 'class', 'library', 'package'],
4142
defaultsTo: 'method')
43+
..addOption(
44+
'where',
45+
abbr: 'w',
46+
help: 'Filter output using the given glob.',
47+
)
4248
..addFlag('collapse-anonymous-closures', help: '''
4349
Collapse all anonymous closures from the same scope into a single entry.
4450
When comparing size of AOT snapshots for two different versions of a
@@ -76,8 +82,9 @@ precisely based on their source position (which is included in their name).
7682

7783
await outputSummary(input,
7884
maxWidth: maxWidth,
79-
granularity: _parseHistogramType(argResults['granularity']),
80-
collapseAnonymousClosures: argResults['collapse-anonymous-closures']);
85+
granularity: _parseHistogramType(argResults['by']),
86+
collapseAnonymousClosures: argResults['collapse-anonymous-closures'],
87+
filter: argResults['where']);
8188
}
8289

8390
static HistogramType _parseHistogramType(String value) {
@@ -98,14 +105,23 @@ precisely based on their source position (which is included in their name).
98105
void outputSummary(File input,
99106
{int maxWidth = 0,
100107
bool collapseAnonymousClosures = false,
101-
HistogramType granularity = HistogramType.bySymbol}) async {
108+
HistogramType granularity = HistogramType.bySymbol,
109+
String filter}) async {
102110
final info = await loadProgramInfo(input);
103111

104112
// Compute histogram.
105-
final histogram = SizesHistogram.from(info, granularity);
113+
final histogram = computeHistogram(info, granularity, filter: filter);
106114

107115
// Now produce the report table.
108116
const topToReport = 30;
109-
printHistogram(histogram,
117+
printHistogram(info, histogram,
110118
prefix: histogram.bySize.take(topToReport), maxWidth: maxWidth);
119+
120+
if (info.snapshotInfo != null) {
121+
print('\nBreakdown by object type:');
122+
final typeHistogram =
123+
computeHistogram(info, HistogramType.byNodeType, filter: filter);
124+
printHistogram(info, typeHistogram,
125+
prefix: typeHistogram.bySize, maxWidth: maxWidth);
126+
}
111127
}

pkg/vm/lib/snapshot/program_info.dart

Lines changed: 92 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ class ProgramInfo {
5959
/// Recursively visit all function nodes, which have [FunctionInfo.info]
6060
/// populated.
6161
void visit(
62-
void Function(String pkg, String lib, String cls, String fun, int size)
62+
void Function(
63+
String pkg, String lib, String cls, String fun, ProgramInfoNode n)
6364
callback) {
6465
final context = List<String>(NodeType.values.length);
6566

@@ -71,13 +72,11 @@ class ProgramInfo {
7172
context[node._type] = node.name;
7273
}
7374

74-
if (node.size != null) {
75-
final String pkg = context[NodeType.packageNode.index];
76-
final String lib = context[NodeType.libraryNode.index];
77-
final String cls = context[NodeType.classNode.index];
78-
final String mem = context[NodeType.functionNode.index];
79-
callback(pkg, lib, cls, mem, node.size);
80-
}
75+
final String pkg = context[NodeType.packageNode.index];
76+
final String lib = context[NodeType.libraryNode.index];
77+
final String cls = context[NodeType.classNode.index];
78+
final String mem = context[NodeType.functionNode.index];
79+
callback(pkg, lib, cls, mem, node);
8180

8281
for (var child in node.children.values) {
8382
recurse(child);
@@ -91,8 +90,8 @@ class ProgramInfo {
9190

9291
int get totalSize {
9392
var result = 0;
94-
visit((pkg, lib, cls, fun, size) {
95-
result += size;
93+
visit((pkg, lib, cls, fun, node) {
94+
result += node.size ?? 0;
9695
});
9796
return result;
9897
}
@@ -181,13 +180,11 @@ Iterable<T> _allKeys<T>(Map<T, dynamic> a, Map<T, dynamic> b) {
181180
return <T>{...?a?.keys, ...?b?.keys};
182181
}
183182

184-
/// Histogram of sizes based on a [ProgramInfo] bucketed using one of the
185-
/// [HistogramType] rules.
186-
class SizesHistogram {
183+
class Histogram {
187184
/// Rule used to produce this histogram. Specifies how bucket names
188185
/// are constructed given (library-uri,class-name,function-name) tuples and
189186
/// how these bucket names can be deconstructed back into human readable form.
190-
final Bucketing bucketing;
187+
final BucketInfo bucketInfo;
191188

192189
/// Histogram buckets.
193190
final Map<String, int> buckets;
@@ -200,24 +197,77 @@ class SizesHistogram {
200197

201198
int get length => bySize.length;
202199

203-
SizesHistogram._(this.bucketing, this.buckets, this.bySize, this.totalSize);
200+
Histogram._(this.bucketInfo, this.buckets)
201+
: bySize = buckets.keys.toList(growable: false)
202+
..sort((a, b) => buckets[b] - buckets[a]),
203+
totalSize = buckets.values.fold(0, (sum, size) => sum + size);
204+
205+
static Histogram fromIterable<T>(
206+
Iterable<T> entries, {
207+
@required int Function(T) sizeOf,
208+
@required String Function(T) bucketFor,
209+
@required BucketInfo bucketInfo,
210+
}) {
211+
final buckets = <String, int>{};
212+
213+
for (var e in entries) {
214+
final bucket = bucketFor(e);
215+
final size = sizeOf(e);
216+
buckets[bucket] = (buckets[bucket] ?? 0) + size;
217+
}
218+
219+
return Histogram._(bucketInfo, buckets);
220+
}
221+
}
222+
223+
/// Construct the histogram of specific [type] given a [ProgramInfo].
224+
Histogram computeHistogram(ProgramInfo info, HistogramType type,
225+
{String filter}) {
226+
bool Function(String, String, String) matchesFilter;
227+
228+
if (filter != null) {
229+
final re = RegExp(filter.replaceAll('*', '.*'), caseSensitive: false);
230+
matchesFilter = (lib, cls, fun) => re.hasMatch("${lib}::${cls}.${fun}");
231+
} else {
232+
matchesFilter = (_, __, ___) => true;
233+
}
234+
235+
if (type == HistogramType.byNodeType) {
236+
final Set<int> filteredNodes = {};
237+
if (filter != null) {
238+
info.visit((pkg, lib, cls, fun, node) {
239+
if (matchesFilter(lib, cls, fun)) {
240+
filteredNodes.add(node.id);
241+
}
242+
});
243+
}
204244

205-
/// Construct the histogram of specific [type] given a [ProgramInfo].
206-
static SizesHistogram from(ProgramInfo info, HistogramType type) {
245+
return Histogram.fromIterable<Node>(
246+
info.snapshotInfo.snapshot.nodes.where((n) =>
247+
filter == null ||
248+
filteredNodes.contains(info.snapshotInfo.ownerOf(n).id)),
249+
sizeOf: (n) {
250+
return n.selfSize;
251+
},
252+
bucketFor: (n) => n.type,
253+
bucketInfo: const BucketInfo(nameComponents: ['Type']));
254+
} else {
207255
final buckets = <String, int>{};
208256
final bucketing = Bucketing._forType[type];
209257

210-
var totalSize = 0;
211-
info.visit((pkg, lib, cls, fun, size) {
258+
info.visit((pkg, lib, cls, fun, node) {
259+
if (node.size == null || node.size == 0) {
260+
return;
261+
}
262+
212263
final bucket = bucketing.bucketFor(pkg, lib, cls, fun);
213-
buckets[bucket] = (buckets[bucket] ?? 0) + size;
214-
totalSize += size;
264+
if (!matchesFilter(lib, cls, fun)) {
265+
return;
266+
}
267+
buckets[bucket] = (buckets[bucket] ?? 0) + node.size;
215268
});
216269

217-
final bySize = buckets.keys.toList(growable: false);
218-
bySize.sort((a, b) => buckets[b] - buckets[a]);
219-
220-
return SizesHistogram._(bucketing, buckets, bySize, totalSize);
270+
return Histogram._(bucketing, buckets);
221271
}
222272
}
223273

@@ -226,22 +276,28 @@ enum HistogramType {
226276
byClass,
227277
byLibrary,
228278
byPackage,
279+
byNodeType,
229280
}
230281

231-
abstract class Bucketing {
282+
class BucketInfo {
232283
/// Specifies which human readable name components can be extracted from
233284
/// the bucket name.
234-
List<String> get nameComponents;
285+
final List<String> nameComponents;
235286

287+
/// Deconstructs bucket name into human readable components (the order matches
288+
/// one returned by [nameComponents]).
289+
List<String> namesFromBucket(String bucket) => [bucket];
290+
291+
const BucketInfo({@required this.nameComponents});
292+
}
293+
294+
abstract class Bucketing extends BucketInfo {
236295
/// Constructs the bucket name from the given library name [lib], class name
237296
/// [cls] and function name [fun].
238297
String bucketFor(String pkg, String lib, String cls, String fun);
239298

240-
/// Deconstructs bucket name into human readable components (the order matches
241-
/// one returned by [nameComponents]).
242-
List<String> namesFromBucket(String bucket);
243-
244-
const Bucketing();
299+
const Bucketing({@required List<String> nameComponents})
300+
: super(nameComponents: nameComponents);
245301

246302
static const _forType = {
247303
HistogramType.bySymbol: _BucketBySymbol(),
@@ -255,9 +311,6 @@ abstract class Bucketing {
255311
const String _nameSeparator = ';;;';
256312

257313
class _BucketBySymbol extends Bucketing {
258-
@override
259-
List<String> get nameComponents => const ['Library', 'Symbol'];
260-
261314
@override
262315
String bucketFor(String pkg, String lib, String cls, String fun) {
263316
if (fun == null) {
@@ -269,13 +322,10 @@ class _BucketBySymbol extends Bucketing {
269322
@override
270323
List<String> namesFromBucket(String bucket) => bucket.split(_nameSeparator);
271324

272-
const _BucketBySymbol();
325+
const _BucketBySymbol() : super(nameComponents: const ['Library', 'Symbol']);
273326
}
274327

275328
class _BucketByClass extends Bucketing {
276-
@override
277-
List<String> get nameComponents => ['Library', 'Class'];
278-
279329
@override
280330
String bucketFor(String pkg, String lib, String cls, String fun) {
281331
if (cls == null) {
@@ -287,34 +337,22 @@ class _BucketByClass extends Bucketing {
287337
@override
288338
List<String> namesFromBucket(String bucket) => bucket.split(_nameSeparator);
289339

290-
const _BucketByClass();
340+
const _BucketByClass() : super(nameComponents: const ['Library', 'Class']);
291341
}
292342

293343
class _BucketByLibrary extends Bucketing {
294-
@override
295-
List<String> get nameComponents => ['Library'];
296-
297344
@override
298345
String bucketFor(String pkg, String lib, String cls, String fun) => '$lib';
299346

300-
@override
301-
List<String> namesFromBucket(String bucket) => [bucket];
302-
303-
const _BucketByLibrary();
347+
const _BucketByLibrary() : super(nameComponents: const ['Library']);
304348
}
305349

306350
class _BucketByPackage extends Bucketing {
307-
@override
308-
List<String> get nameComponents => ['Package'];
309-
310351
@override
311352
String bucketFor(String pkg, String lib, String cls, String fun) =>
312353
pkg ?? lib;
313354

314-
@override
315-
List<String> namesFromBucket(String bucket) => [bucket];
316-
317-
const _BucketByPackage();
355+
const _BucketByPackage() : super(nameComponents: const ['Package']);
318356
}
319357

320358
String packageOf(String lib) {

0 commit comments

Comments
 (0)