Skip to content

Commit a16a7d5

Browse files
committed
feat(@angular-devkit/benchmark): add package
1 parent 39b4cc0 commit a16a7d5

20 files changed

+917
-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",
+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
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/**/*_spec.ts",
19+
"src/**/*_spec_large.ts",
20+
"src/**/*_benchmark.ts",
21+
],
22+
),
23+
module_name = "@angular-devkit/benchmark",
24+
module_root = "src/index.d.ts",
25+
deps = [
26+
"//packages/angular_devkit/core",
27+
"@rxjs",
28+
"@rxjs//operators",
29+
# @typings: node
30+
],
31+
)
32+
33+
ts_library(
34+
name = "benchmark_test_lib",
35+
srcs = glob(
36+
include = [
37+
"src/**/*_spec.ts",
38+
"src/**/*_spec_large.ts",
39+
],
40+
),
41+
data = [
42+
"src/test/test-script.js"
43+
],
44+
deps = [
45+
":benchmark",
46+
"//packages/angular_devkit/core",
47+
"@rxjs",
48+
"@rxjs//operators",
49+
# @typings: jasmine
50+
# @typings: node
51+
],
52+
)
53+
54+
jasmine_node_test(
55+
name = "benchmark_test",
56+
srcs = [":benchmark_test_lib"],
57+
)
+113
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
# Angular Devkit Benchmark
2+
3+
This tools provides benchmark information for processes.
4+
The tool will gathering metrics from a given command, then average them out over the total runs.
5+
6+
It currently shows only time, process, cpu, and memory used but might be extended in the future.
7+
8+
This tool was created to provide an objective and reproducible way of benchmarking process
9+
performance.
10+
11+
Given a process (or its source code), process inputs and environment, keeping two of these elements
12+
constant while varying the third should allow for meaningful benchmarks over time.
13+
14+
In the context of the DevKit, we publish many CLI tools and have access to their source code.
15+
By tracking tool resource usage we can catch performance regressions or improvements on our CI.
16+
17+
18+
## Installation
19+
20+
You can install the benchmark tool via `npm install -g benchmark` for a global install, or without
21+
`-g` for a local install.
22+
Installing globally gives you access to the `benchmark` binary in your `PATH`.
23+
24+
25+
## CLI Usage
26+
27+
Call the `benchmark` binary, followed by options, then double dash, then the command to benchmark:
28+
29+
```
30+
$ benchmark -- node fibonacci.js 40
31+
[benchmark] Benchmarking process over 5 iterations, with up to 5 retries.
32+
[benchmark] node fibonacci.js 40 (at D:\sandbox\latest-project)
33+
[benchmark] Process Stats
34+
[benchmark] Elapsed Time: 2365.40 ms (2449.00, 2444.00, 2357.00, 2312.00, 2265.00)
35+
[benchmark] Average Process usage: 1.00 process(es) (1.00, 1.00, 1.00, 1.00, 1.00)
36+
[benchmark] Peak Process usage: 1.00 process(es) (1.00, 1.00, 1.00, 1.00, 1.00)
37+
[benchmark] Average CPU usage: 4.72 % (5.03, 4.86, 4.50, 4.50, 4.69)
38+
[benchmark] Peak CPU usage: 23.40 % (25.00, 23.40, 21.80, 21.80, 25.00)
39+
[benchmark] Average Memory usage: 22.34 MB (22.32, 22.34, 22.34, 22.35, 22.35)
40+
[benchmark] Peak Memory usage: 22.34 MB (22.32, 22.34, 22.34, 22.35, 22.35)
41+
```
42+
43+
For more information on the available options, run `benchmark --help`:
44+
```
45+
$ benchmark --help
46+
[benchmark] benchmark [options] -- [command to benchmark]
47+
48+
Collects process stats from running the command.
49+
50+
Options:
51+
--help Show this message.
52+
--verbose Show more information while running.
53+
--exit-code Expected exit code for the command. Default is 0.
54+
--iterations Number of iterations to run the benchmark over. Default is 5.
55+
--retries Number of times to retry when process fails. Default is 5.
56+
--cwd Current working directory to run the process in.
57+
--output-file File to output benchmark log to.
58+
--overwrite-output-file If the output file should be overwritten rather than appended to.
59+
--prefix Logging prefix.
60+
61+
Example:
62+
benchmark --iterations=3 -- node my-script.js
63+
```
64+
65+
66+
NB: the `fibonacci.js` in the example is a naive implementation of a fibonacci number calculator:
67+
```
68+
// fibonacci.js
69+
const fib = (n) => n > 1 ? fib(n - 1) + fib(n - 2) : n;
70+
console.log(fib(parseInt(process.argv[2])));
71+
```
72+
73+
## API Usage
74+
75+
You can also use the benchmarking API directly:
76+
77+
```
78+
import { Command, defaultStatsCapture, runBenchmark } from '@angular-devkit/benchmark';
79+
80+
const command = new Command('node', ['fibonacci.js', '40']);
81+
const captures = [defaultStatsCapture];
82+
83+
runBenchmark({ command, command }).subscribe(results => {
84+
// results is:[{
85+
// "name": "Process Stats",
86+
// "metrics": [{
87+
// "name": "Elapsed Time", "unit": "ms", "value": 1883.6,
88+
// "componentValues": [1733, 1957, 1580, 1763, 2385]
89+
// }, {
90+
// "name": "Average Process usage", "unit": "process(es)", "value": 1,
91+
// "componentValues": [1, 1, 1, 1, 1]
92+
// }, {
93+
// "name": "Peak Process usage", "unit": "process(es)", "value": 1,
94+
// "componentValues": [1, 1, 1, 1, 1]
95+
// }, {
96+
// "name": "Average CPU usage", "unit": "%", "value": 3.0855555555555556,
97+
// "componentValues": [1.9625, 1.9500000000000002, 1.9500000000000002, 4.887499999999999, 4.677777777777778]
98+
// }, {
99+
// "name": "Peak CPU usage", "unit": "%", "value": 19.380000000000003,
100+
// "componentValues": [15.7, 15.6, 15.6, 25, 25]
101+
// }, {
102+
// "name": "Average Memory usage", "unit": "MB", "value": 22.364057600000002,
103+
// "componentValues": [22.383104, 22.332416, 22.401024, 22.355968, 22.347776]
104+
// }, {
105+
// "name": "Peak Memory usage", "unit": "MB", "value": 22.3649792,
106+
// "componentValues": [22.384639999999997, 22.335487999999998, 22.401024, 22.355968, 22.347776]
107+
// }]
108+
// }]
109+
});
110+
```
111+
112+
A good example of API usage is the `benchmark` binary itself, found in `./bin/benchmark.ts`.
113+
We recommend using TypeScript to get full access to the interfaces included.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
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, terminal } from '@angular-devkit/core';
11+
import { appendFileSync, writeFileSync } from 'fs';
12+
import * as minimist from 'minimist';
13+
import { filter, map } from 'rxjs/operators';
14+
import { Command } from '../src/command';
15+
import { defaultReporter } from '../src/default-reporter';
16+
import { defaultStatsCapture } from '../src/default-stats-capture';
17+
import { runBenchmark } from '../src/run-benchmark';
18+
19+
// Show usage of the CLI tool, and exit the process.
20+
function usage(logger: logging.Logger, exitCode = 0): never {
21+
logger.info(tags.stripIndent`
22+
benchmark [options] -- [command to benchmark]
23+
24+
Collects process stats from running the command.
25+
26+
Options:
27+
--help Show this message.
28+
--verbose Show more information while running.
29+
--exit-code Expected exit code for the command. Default is 0.
30+
--iterations Number of iterations to run the benchmark over. Default is 5.
31+
--retries Number of times to retry when process fails. Default is 5.
32+
--cwd Current working directory to run the process in.
33+
--output-file File to output benchmark log to.
34+
--overwrite-output-file If the output file should be overwritten rather than appended to.
35+
--prefix Logging prefix.
36+
37+
Example:
38+
benchmark --iterations=3 -- node my-script.js
39+
`);
40+
41+
process.exit(exitCode);
42+
// The node typing sometimes don't have a never type for process.exit().
43+
throw 0;
44+
}
45+
46+
interface BenchmarkCliArgv {
47+
help: boolean;
48+
verbose: boolean;
49+
'overwrite-output-file': boolean;
50+
'exit-code': number;
51+
iterations: number;
52+
retries: number;
53+
'output-file': string | null;
54+
cwd: string;
55+
prefix: string;
56+
'--': string[] | null;
57+
}
58+
59+
// Parse the command line.
60+
const argv = minimist(process.argv.slice(2), {
61+
boolean: ['help', 'verbose', 'overwrite-output-file'],
62+
default: {
63+
'exit-code': 0,
64+
'iterations': 5,
65+
'retries': 5,
66+
'output-file': null,
67+
'cwd': process.cwd(),
68+
'prefix': '[benchmark] ',
69+
},
70+
'--': true,
71+
}) as {} as BenchmarkCliArgv;
72+
73+
// Create the DevKit Logger used through the CLI.
74+
const logger = new logging.TransformLogger(
75+
'benchmark-prefix-logger',
76+
stream => stream.pipe(map(entry => (entry.message = argv['prefix'] + entry.message, entry))),
77+
);
78+
79+
// Log to console.
80+
logger
81+
.pipe(filter(entry => (entry.level != 'debug' || argv['verbose'])))
82+
.subscribe(entry => {
83+
let color: (s: string) => string = x => terminal.dim(terminal.white(x));
84+
let output = process.stdout;
85+
switch (entry.level) {
86+
case 'info':
87+
color = terminal.white;
88+
break;
89+
case 'warn':
90+
color = terminal.yellow;
91+
break;
92+
case 'error':
93+
color = terminal.red;
94+
output = process.stderr;
95+
break;
96+
case 'fatal':
97+
color = (x: string) => terminal.bold(terminal.red(x));
98+
output = process.stderr;
99+
break;
100+
}
101+
102+
output.write(color(entry.message) + '\n');
103+
});
104+
105+
106+
// Print help.
107+
if (argv['help']) {
108+
usage(logger);
109+
}
110+
111+
// Exit early if we can't find the command to benchmark.
112+
if (!Array.isArray(argv['--']) || (argv['--'] as Array<string>).length < 1) {
113+
logger.fatal(`Missing command, see benchmark --help for help.`);
114+
process.exit(1);
115+
throw 0;
116+
}
117+
118+
// Setup file logging.
119+
if (argv['output-file'] !== null) {
120+
if (argv['overwrite-output-file']) {
121+
writeFileSync(argv['output-file'] as string, '');
122+
}
123+
logger.pipe(filter(entry => (entry.level != 'debug' || argv['verbose'])))
124+
.subscribe(entry => appendFileSync(argv['output-file'] as string, `${entry.message}\n`));
125+
}
126+
127+
// Run benchmark on given command, capturing stats and reporting them.
128+
const exitCode = argv['exit-code'];
129+
const cmd = (argv['--'] as Array<string>)[0];
130+
const args = (argv['--'] as Array<string>).slice(1);
131+
const command = new Command(cmd, args, argv['cwd'], exitCode);
132+
const captures = [defaultStatsCapture];
133+
const reporters = [defaultReporter(logger)];
134+
const iterations = argv['iterations'];
135+
const retries = argv['retries'];
136+
137+
logger.info(`Benchmarking process over ${iterations} iterations, with up to ${retries} retries.`);
138+
logger.info(` ${command.toString()}`);
139+
140+
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.2.0",
21+
"tree-kill": "^1.2.0"
22+
}
23+
}

0 commit comments

Comments
 (0)