| // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| import 'package:args/command_runner.dart'; |
| |
| import 'package:dart2js_info/info.dart'; |
| import 'package:dart2js_info/src/diff.dart'; |
| import 'package:dart2js_info/src/io.dart'; |
| import 'package:dart2js_info/src/util.dart'; |
| |
| import 'usage_exception.dart'; |
| |
| /// A command that computes the diff between two info files. |
| class DiffCommand extends Command<void> with PrintUsageException { |
| @override |
| final String name = "diff"; |
| @override |
| final String description = |
| "See code size differences between two dump-info files."; |
| |
| DiffCommand() { |
| argParser.addFlag('summary-only', |
| defaultsTo: false, |
| help: "Show only a summary and hide details of each library"); |
| } |
| |
| @override |
| void run() async { |
| final argRes = argResults!; |
| var args = argRes.rest; |
| if (args.length < 2) { |
| usageException( |
| 'Missing arguments, expected two dump-info files to compare'); |
| } |
| |
| final oldInfo = await infoFromFile(args[0]); |
| final newInfo = await infoFromFile(args[1]); |
| final summaryOnly = argRes['summary-only']; |
| |
| final diffs = diff(oldInfo, newInfo); |
| |
| // Categorize the diffs |
| final adds = <AddDiff>[]; |
| final removals = <RemoveDiff>[]; |
| final sizeChanges = <SizeDiff>[]; |
| final becameDeferred = <DeferredStatusDiff>[]; |
| final becameUndeferred = <DeferredStatusDiff>[]; |
| |
| for (var diff in diffs) { |
| switch (diff.kind) { |
| case DiffKind.add: |
| adds.add(diff as AddDiff); |
| break; |
| case DiffKind.remove: |
| removals.add(diff as RemoveDiff); |
| break; |
| case DiffKind.size: |
| sizeChanges.add(diff as SizeDiff); |
| break; |
| case DiffKind.deferred: |
| var deferredDiff = diff as DeferredStatusDiff; |
| if (deferredDiff.wasDeferredBefore) { |
| becameUndeferred.add(deferredDiff); |
| } else { |
| becameDeferred.add(deferredDiff); |
| } |
| break; |
| } |
| } |
| |
| // Sort the changes by the size of the element that changed. |
| for (var diffs in [adds, removals, becameDeferred, becameUndeferred]) { |
| diffs.sort((a, b) => b.info.size - a.info.size); |
| } |
| |
| // Sort changes in size by size difference. |
| sizeChanges.sort((a, b) => b.sizeDifference - a.sizeDifference); |
| |
| var totalSizes = <List<Diff>, int>{}; |
| for (var diffs in [adds, removals, becameDeferred, becameUndeferred]) { |
| var totalSize = 0; |
| for (var diff in diffs) { |
| // Only count diffs from leaf elements so we don't double count |
| // them when we account for class size diff or library size diff. |
| if (diff.info.kind == InfoKind.field || |
| diff.info.kind == InfoKind.function || |
| diff.info.kind == InfoKind.closure || |
| diff.info.kind == InfoKind.typedef) { |
| totalSize += diff.info.size; |
| } |
| } |
| totalSizes[diffs] = totalSize; |
| } |
| var totalSizeChange = 0; |
| for (var sizeChange in sizeChanges) { |
| // Only count diffs from leaf elements so we don't double count |
| // them when we account for class size diff or library size diff. |
| if (sizeChange.info.kind == InfoKind.field || |
| sizeChange.info.kind == InfoKind.function || |
| sizeChange.info.kind == InfoKind.closure || |
| sizeChange.info.kind == InfoKind.typedef) { |
| totalSizeChange += sizeChange.sizeDifference; |
| } |
| } |
| totalSizes[sizeChanges] = totalSizeChange; |
| |
| reportSummary(oldInfo, newInfo, adds, removals, sizeChanges, becameDeferred, |
| becameUndeferred, totalSizes); |
| if (!summaryOnly) { |
| print(''); |
| reportFull(oldInfo, newInfo, adds, removals, sizeChanges, becameDeferred, |
| becameUndeferred, totalSizes); |
| } |
| } |
| } |
| |
| void reportSummary( |
| AllInfo oldInfo, |
| AllInfo newInfo, |
| List<AddDiff> adds, |
| List<RemoveDiff> removals, |
| List<SizeDiff> sizeChanges, |
| List<DeferredStatusDiff> becameDeferred, |
| List<DeferredStatusDiff> becameUndeferred, |
| Map<List<Diff>, int> totalSizes) { |
| var overallSizeDiff = newInfo.program!.size - oldInfo.program!.size; |
| print('total_size_difference $overallSizeDiff'); |
| |
| print('total_added ${totalSizes[adds]}'); |
| print('total_removed ${totalSizes[removals]}'); |
| print('total_size_changed ${totalSizes[sizeChanges]}'); |
| print('total_became_deferred ${totalSizes[becameDeferred]}'); |
| print('total_no_longer_deferred ${totalSizes[becameUndeferred]}'); |
| } |
| |
| void reportFull( |
| AllInfo oldInfo, |
| AllInfo newInfo, |
| List<AddDiff> adds, |
| List<RemoveDiff> removals, |
| List<SizeDiff> sizeChanges, |
| List<DeferredStatusDiff> becameDeferred, |
| List<DeferredStatusDiff> becameUndeferred, |
| Map<List<Diff>, int> totalSizes) { |
| // TODO(het): Improve this output. Siggi has good suggestions in |
| // https://github.com/dart-lang/dart2js_info/pull/19 |
| |
| _section('ADDED', size: totalSizes[adds]); |
| for (var add in adds) { |
| print('${longName(add.info, useLibraryUri: true)}: ${add.info.size} bytes'); |
| } |
| print(''); |
| |
| _section('REMOVED', size: totalSizes[removals]); |
| for (var removal in removals) { |
| print('${longName(removal.info, useLibraryUri: true)}: ' |
| '${removal.info.size} bytes'); |
| } |
| print(''); |
| |
| _section('CHANGED SIZE', size: totalSizes[sizeChanges]); |
| for (var sizeChange in sizeChanges) { |
| print('${longName(sizeChange.info, useLibraryUri: true)}: ' |
| '${sizeChange.sizeDifference} bytes'); |
| } |
| print(''); |
| |
| _section('BECAME DEFERRED', size: totalSizes[becameDeferred]); |
| for (var diff in becameDeferred) { |
| print('${longName(diff.info, useLibraryUri: true)}: ' |
| '${diff.info.size} bytes'); |
| } |
| print(''); |
| |
| _section('NO LONGER DEFERRED', size: totalSizes[becameUndeferred]); |
| for (var diff in becameUndeferred) { |
| print('${longName(diff.info, useLibraryUri: true)}: ' |
| '${diff.info.size} bytes'); |
| } |
| } |
| |
| void _section(String title, {int? size}) { |
| print('$title ($size bytes)'); |
| print('=' * 72); |
| } |