Skip to content

Commit c5ae566

Browse files
committed
feat(@angular-devkit/benchmark): add package
1 parent 761b066 commit c5ae566

19 files changed

+732
-0
lines changed

.monorepo.json

+5
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,11 @@
8787
"hash": "9d2161b7ca9044c2286c7d66e10ead12",
8888
"snapshotRepo": "angular/angular-devkit-architect-cli-builds"
8989
},
90+
"@angular-devkit/benchmark": {
91+
"name": "Benchmark",
92+
"section": "Tooling",
93+
"version": "0.0.0"
94+
},
9095
"@angular-devkit/build-optimizer": {
9196
"name": "Build Optimizer",
9297
"links": [

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ will also be available to third party tools and IDEs.
5151
| Project | Package | Version | Links |
5252
|---|---|---|---|
5353
**Angular CLI** | [`@angular/cli`](https://npmjs.com/package/@angular/cli) | [![latest](https://img.shields.io/npm/v/%40angular%2Fcli/latest.svg)](https://npmjs.com/package/@angular/cli) | [![README](https://img.shields.io/badge/README--green.svg)](https://github.com/angular/angular-cli/blob/master/packages/angular/cli/README.md) [![snapshot](https://img.shields.io/badge/snapshot--blue.svg)](https://github.com/angular/cli-builds)
54+
**Benchmark** | [`@angular-devkit/benchmark`](https://npmjs.com/package/@angular-devkit/benchmark) | [![latest](https://img.shields.io/npm/v/%40angular-devkit%2Fbenchmark/latest.svg)](https://npmjs.com/package/@angular-devkit/benchmark) |
5455
**Schematics CLI** | [`@angular-devkit/schematics-cli`](https://npmjs.com/package/@angular-devkit/schematics-cli) | [![latest](https://img.shields.io/npm/v/%40angular-devkit%2Fschematics-cli/latest.svg)](https://npmjs.com/package/@angular-devkit/schematics-cli) | [![snapshot](https://img.shields.io/badge/snapshot--blue.svg)](https://github.com/angular/angular-devkit-schematics-cli-builds)
5556

5657

bin/benchmark

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#!/usr/bin/env node
2+
/**
3+
* @license
4+
* Copyright Google Inc. All Rights Reserved.
5+
*
6+
* Use of this source code is governed by an MIT-style license that can be
7+
* found in the LICENSE file at https://angular.io/license
8+
*/
9+
'use strict';
10+
11+
12+
require('../lib/bootstrap-local');
13+
const packages = require('../lib/packages').packages;
14+
require(packages['@angular-devkit/benchmark'].bin['benchmark']);

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"description": "Software Development Kit for Angular",
66
"bin": {
77
"architect": "./bin/architect",
8+
"benchmark": "./bin/benchmark",
89
"build-optimizer": "./bin/build-optimizer",
910
"devkit-admin": "./bin/devkit-admin",
1011
"ng": "./bin/ng",
+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# Copyright Google Inc. All Rights Reserved.
2+
#
3+
# Use of this source code is governed by an MIT-style license that can be
4+
# found in the LICENSE file at https://angular.io/license
5+
6+
licenses(["notice"]) # MIT
7+
8+
load("@build_bazel_rules_typescript//:defs.bzl", "ts_library")
9+
load("@build_bazel_rules_nodejs//:defs.bzl", "jasmine_node_test")
10+
11+
package(default_visibility = ["//visibility:public"])
12+
13+
ts_library(
14+
name = "benchmark",
15+
srcs = glob(
16+
include = ["src/**/*.ts"],
17+
exclude = [
18+
"src/**/cli.ts",
19+
"src/**/*_spec.ts",
20+
"src/**/*_spec_large.ts",
21+
"src/**/*_benchmark.ts",
22+
],
23+
),
24+
deps = [
25+
"//packages/angular_devkit/core",
26+
"@rxjs",
27+
"@rxjs//operators",
28+
# @typings: node
29+
],
30+
)
31+
32+
ts_library(
33+
name = "benchmark_test_lib",
34+
srcs = glob(
35+
include = [
36+
"src/**/*_spec.ts",
37+
"src/**/*_spec_large.ts",
38+
],
39+
),
40+
data = [
41+
"src/test/test-script.js"
42+
],
43+
deps = [
44+
":benchmark",
45+
"//packages/angular_devkit/core",
46+
"@rxjs",
47+
"@rxjs//operators",
48+
# @typings: jasmine
49+
# @typings: node
50+
],
51+
)
52+
53+
jasmine_node_test(
54+
name = "benchmark_test",
55+
srcs = [":benchmark_test_lib"],
56+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# TODO
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
#!/usr/bin/env node
2+
/**
3+
* @license
4+
* Copyright Google Inc. All Rights Reserved.
5+
*
6+
* Use of this source code is governed by an MIT-style license that can be
7+
* found in the LICENSE file at https://angular.io/license
8+
*/
9+
10+
import { logging, tags } from '@angular-devkit/core';
11+
import { createConsoleLogger } from '@angular-devkit/core/node';
12+
import { appendFileSync, writeFileSync } from 'fs';
13+
import * as minimist from 'minimist';
14+
import { filter, map } from 'rxjs/operators';
15+
import { Command } from '../src/command';
16+
import { defaultReporter } from '../src/default-reporter';
17+
import { defaultStatsCapture } from '../src/default-stats-capture';
18+
import { runBenchmark } from '../src/run-benchmark';
19+
20+
// Show usage of the CLI tool, and exit the process.
21+
function usage(exitCode = 0): never {
22+
logger.info(tags.stripIndent`
23+
benchmark [options] -- [command to benchmark]
24+
25+
Collects process stats from running the command.
26+
27+
Options:
28+
--help Show this message.
29+
--verbose Show more information.
30+
--exit-code Expected exit code. Default is 0.
31+
--iterations Number of iterations to run the benchmark over. Default is 5.
32+
--retries Number of times to retry when process fails. Default is 5.
33+
--cwd Current working directory to run the process in.
34+
--output-file File to output benchmark log.
35+
--overwrite-output-file If the output file should be overwritten rather than appended to.
36+
--prefix Logging prefix.
37+
38+
Example:
39+
benchmark --iterations=3 -- node my-script.js
40+
`);
41+
42+
process.exit(exitCode);
43+
// The node typing sometimes don't have a never type for process.exit().
44+
throw 0;
45+
}
46+
47+
// Parse the command line.
48+
const argv = minimist(process.argv.slice(2), {
49+
boolean: ['help', 'verbose', 'overwrite-output-file'],
50+
default: {
51+
'exit-code': 0,
52+
'iterations': 5,
53+
'retries': 5,
54+
// TODO: add output file reporter
55+
'output-file': null,
56+
'cwd': process.cwd(),
57+
'prefix': '[benchmark] ',
58+
},
59+
'--': true,
60+
});
61+
62+
// Create the DevKit Logger used through the CLI.
63+
const consoleLogger = createConsoleLogger(argv['verbose']);
64+
if (argv['output-file'] !== null) {
65+
if (argv['overwrite-output-file']) {
66+
writeFileSync(argv['output-file'], '');
67+
}
68+
consoleLogger.pipe(filter(entry => (entry.level != 'debug' || argv['verbose'])))
69+
.subscribe(entry => appendFileSync(argv['output-file'], `${entry.message}\n`));
70+
}
71+
const logger = new logging.TransformLogger(
72+
'benchmark-prefix-logger',
73+
stream => stream.pipe(map(entry =>
74+
(entry.message = argv['prefix'] + entry.message, entry)),
75+
),
76+
consoleLogger,
77+
);
78+
79+
if (argv['help']) {
80+
usage();
81+
}
82+
83+
if (!Array.isArray(argv['--']) || (argv['--'] as Array<string>).length < 1) {
84+
logger.fatal(`Missing command, see benchmark --help for help.`);
85+
process.exit(1);
86+
throw 0;
87+
}
88+
89+
// Run benchmark on given command, capturing stats and reporting them.
90+
const exitCode = argv['exit-code'];
91+
const cmd = (argv['--'] as Array<string>)[0];
92+
const args = (argv['--'] as Array<string>).slice(1);
93+
const command = new Command(cmd, args, argv['cwd'], exitCode);
94+
const captures = [defaultStatsCapture];
95+
const reporters = [defaultReporter(logger)];
96+
const iterations = argv['iterations'];
97+
const retries = argv['retries'];
98+
99+
logger.info(`Benchmarking process over ${iterations} iterations, with up to ${retries} retries.`);
100+
logger.info(` ${command.toString()}`);
101+
102+
runBenchmark({ command, captures, reporters, iterations, retries, logger }).subscribe();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"name": "@angular-devkit/benchmark",
3+
"version": "0.0.0",
4+
"description": "Angular Benchmark",
5+
"bin": {
6+
"benchmark": "./bin/benchmark.js"
7+
},
8+
"keywords": [
9+
"benchmark"
10+
],
11+
"engines": {
12+
"node": ">= 8.9.0",
13+
"npm": ">= 5.5.1"
14+
},
15+
"dependencies": {
16+
"@angular-devkit/core": "0.0.0",
17+
"minimist": "^1.2.0",
18+
"pidusage": "^2.0.16",
19+
"pidtree": "^0.3.0",
20+
"rxjs": "^6.0.0",
21+
"tree-kill": "^1.2.0"
22+
}
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
export class Command {
10+
constructor(
11+
private _cmd: string,
12+
private _args: string[] = [],
13+
private _cwd: string = process.cwd(),
14+
private _expectedExitCode = 0,
15+
) { }
16+
17+
get cmd() {
18+
return this._cmd;
19+
}
20+
21+
get args() {
22+
return this._args;
23+
}
24+
25+
get cwd() {
26+
return this._cwd;
27+
}
28+
29+
get expectedExitCode() {
30+
return this._expectedExitCode;
31+
}
32+
33+
toString() {
34+
const { cmd, args, cwd } = this;
35+
const argsStr = args.length > 0 ? ' ' + args.join(' ') : '';
36+
37+
return `${cmd}${argsStr} (at ${cwd})`;
38+
}
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
import { logging, tags } from '@angular-devkit/core';
9+
import { AggregatedMetric, BenchmarkReporter, Metric } from './interfaces';
10+
11+
export const defaultReporter = (logger: logging.Logger): BenchmarkReporter => (process, groups) => {
12+
const toplevelLogger = logger;
13+
const indentLogger = new logging.IndentLogger('benchmark-indent-logger', toplevelLogger);
14+
15+
const formatMetric = (metric: Metric | AggregatedMetric) => tags.oneLine`
16+
${metric.name}: ${metric.value.toFixed(2)} ${metric.unit}
17+
${metric.componentValues ? `(${metric.componentValues.map(v => v.toFixed(2)).join(', ')})` : ''}
18+
`;
19+
20+
groups.forEach(group => {
21+
toplevelLogger.info(`${group.name}`);
22+
group.metrics.forEach(metric => indentLogger.info(formatMetric(metric)));
23+
});
24+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import { Observable } from 'rxjs';
10+
import { map, reduce } from 'rxjs/operators';
11+
import { AggregatedProcessStats, Capture, MetricGroup, MonitoredProcess } from './interfaces';
12+
import { comulativeMovingAverage, max } from './utils';
13+
14+
15+
export const defaultStatsCapture: Capture = (
16+
process: MonitoredProcess,
17+
): Observable<MetricGroup> => {
18+
type Accumulator = {
19+
elapsed: number,
20+
avgProcesses: number,
21+
peakProcesses: number,
22+
avgCpu: number,
23+
peakCpu: number,
24+
avgMemory: number,
25+
peakMemory: number,
26+
};
27+
const seed: Accumulator = {
28+
elapsed: 0,
29+
avgProcesses: 0,
30+
peakProcesses: 0,
31+
avgCpu: 0,
32+
peakCpu: 0,
33+
avgMemory: 0,
34+
peakMemory: 0,
35+
};
36+
37+
return process.stats$.pipe(
38+
reduce<AggregatedProcessStats, Accumulator>((acc, val, idx) => ({
39+
elapsed: val.elapsed,
40+
avgProcesses: comulativeMovingAverage(acc.avgProcesses, val.processes, idx),
41+
peakProcesses: max(acc.peakProcesses, val.processes),
42+
avgCpu: comulativeMovingAverage(acc.avgCpu, val.cpu, idx),
43+
peakCpu: max(acc.peakCpu, val.cpu),
44+
avgMemory: comulativeMovingAverage(acc.avgMemory, val.memory, idx),
45+
peakMemory: max(acc.peakMemory, val.memory),
46+
}), seed),
47+
map(metrics => ({
48+
name: 'Process Stats',
49+
metrics: [
50+
{ name: 'Elapsed Time', unit: 'ms', value: metrics.elapsed },
51+
{ name: 'Average Process usage', unit: 'process(es)', value: metrics.avgProcesses },
52+
{ name: 'Peak Process usage', unit: 'process(es)', value: metrics.peakProcesses },
53+
{ name: 'Average CPU usage', unit: '%', value: metrics.avgCpu },
54+
{ name: 'Peak CPU usage', unit: '%', value: metrics.peakCpu },
55+
{ name: 'Average Memory usage', unit: 'MB', value: metrics.avgMemory * 1e-6 },
56+
{ name: 'Peak Memory usage', unit: 'MB', value: metrics.peakMemory * 1e-6 },
57+
],
58+
})),
59+
);
60+
};

0 commit comments

Comments
 (0)