Skip to content

Commit eaede0a

Browse files
author
HP Dietz
committed
Add JSON reporter. Major refactoring of project file structure.
1 parent fe90cd5 commit eaede0a

23 files changed

+164
-66
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
### 1.9.0
2+
* [#32: Add JSON reporter.](https://github.com/haensl/js-performance/issues/32)
3+
* Major refactoring of project file structure
4+
15
### 1.8.0
26
* [#10: Add profile for guards.](https://github.com/haensl/js-performance/issues/10)
37
* Add command line option to specify result precision.

README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,19 @@ To run specific profiles simply supply their names separated by spaces when star
1919

2020
## CLI
2121

22+
```bash
23+
Usage: node js-performance.js
24+
25+
-h, --help Display this helptext.
26+
-i, --iterations= Specify the number of iterations per profiled function. Default: 1000.
27+
-j, --json Output results in JSON format.
28+
-l, --list List available profiles.
29+
-m, --magnitude= Specify the magnitude of testdata. Default: 1000.
30+
-p, --precision= Specify the precision in terms of decimal places of results. Default: 4 decimals.
31+
-q, --quiet Print results only.
32+
-v, --verbose Print verbose information.
33+
```
34+
2235
### Getting help
2336

2437
```bash
@@ -67,6 +80,13 @@ To print results only:
6780
./js-performance --quiet
6881
```
6982

83+
### Report in JSON format
84+
85+
```bash
86+
./js-performance -j
87+
./js-performance --json
88+
```
89+
7090
## [Changelog](CHANGELOG.md)
7191

7292
## [License](LICENSE)

js-performance.js

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,16 @@ const GetOpt = require('node-getopt');
88
const chalk = require('chalk');
99
const appRoot = __dirname;
1010
global.requireModule = (module) => require(join(appRoot, module));
11-
const DEFAULTS = requireModule('src/support/defaults');
12-
const VERBOSITY = requireModule('src/support/verbosity');
13-
const ProfileRunner = requireModule('src/profile-runner');
14-
const Reporter = requireModule('src/reporter/reporter');
11+
const DEFAULTS = requireModule('lib/support/defaults');
12+
const VERBOSITY = requireModule('lib/support/verbosity');
13+
const ProfileRunner = requireModule('lib/profile-runner');
14+
const ConsoleReporter = requireModule('lib/reporter/console');
15+
const JSONReporter = requireModule('lib/reporter/json');
1516

1617
const opts = new GetOpt([
1718
['h', 'help', 'Display this helptext.'],
1819
['i', 'iterations=', `Specify the number of iterations per profiled function. Default: ${DEFAULTS.iterations}.`],
20+
['j', 'json', `Output results in JSON format.`],
1921
['l', 'list', 'List available profiles.'],
2022
['m', 'magnitude=', `Specify the magnitude of testdata. Default: ${DEFAULTS.testdataMagnitude}.`],
2123
['p', 'precision=', `Specify the precision in terms of decimal places of results. Default: ${DEFAULTS.precision} decimals.`],
@@ -25,7 +27,7 @@ const opts = new GetOpt([
2527
.parseSystem();
2628

2729
if ('list' in opts.options) {
28-
requireModule('src/profiles')
30+
requireModule('lib/profiles')
2931
.forEach((profile) => {
3032
console.info(`${chalk.bold.underline(profile.name)}\n${profile.description(VERBOSITY.VERBOSE)}\n`);
3133
});
@@ -59,18 +61,9 @@ if ('precision' in opts.options
5961
precision = parseInt(opts.options.precision, 10);
6062
}
6163

62-
let profiles = [];
64+
let profiles = requireModule('lib/profiles');
6365
if (opts.argv.length > 0) {
64-
opts.argv.forEach((profileName) => {
65-
const discoveredProfiles = glob.sync(`src/profiles/**/@(${profileName}.profile|${profileName}.profile.js|${profileName}.js)`);
66-
if (discoveredProfiles.length === 1) {
67-
profiles.push(requireModule(discoveredProfiles.pop()));
68-
} else if (verbosity >= VERBOSITY.NORMAL) {
69-
console.info(`Skipping unknown profile "${profileName}".`);
70-
}
71-
});
72-
} else {
73-
profiles = profiles.concat(requireModule('src/profiles'));
66+
profiles = profiles.filter((profile) => opts.argv.includes(profile.name));
7467
}
7568

7669
const profileRunner = new ProfileRunner({
@@ -79,5 +72,8 @@ const profileRunner = new ProfileRunner({
7972
magnitude
8073
});
8174

82-
new Reporter(profileRunner, verbosity, precision);
75+
const reporter = 'json' in opts.options
76+
? new JSONReporter(verbosity, precision)
77+
: new ConsoleReporter(verbosity, precision);
78+
reporter.reportOn(profileRunner);
8379
profileRunner.run();

src/profile-runner.js renamed to lib/profile-runner/index.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
'use strict';
22

33
const EventEmitter = require('events');
4-
const clock = requireModule('src/support/clock/clock');
5-
const events = requireModule('src/support/events');
6-
const testdata = requireModule('src/support/testdata/testdata');
4+
const clock = requireModule('lib/support/clock');
5+
const events = requireModule('lib/support/events');
6+
const testdata = requireModule('lib/support/testdata');
77

88
class ProfileRunner extends EventEmitter {
99

src/profile-runner.spec.js renamed to lib/profile-runner/profile-runner.spec.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ const sinonChai = require('sinon-chai');
66

77
chai.use(sinonChai);
88
const expect = chai.expect;
9-
const ProfileRunner = require('./profile-runner');
10-
const events = require('./support/events');
9+
const ProfileRunner = requireModule('lib/profile-runner');
10+
const events = requireModule('lib/support/events');
1111

1212
describe('Profile Runner', () => {
1313
let profileRunner;

src/profiles/guards/guards.profile.spec.js renamed to lib/profiles/guards/guards.profile.spec.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
const expect = require('chai').expect;
2-
const guards = requireModule('src/profiles/guards/guards.profile');
2+
const guards = requireModule('lib/profiles/guards');
33

44
describe('Guards', () => {
55
let data;

src/profiles/guards/guards.profile.js renamed to lib/profiles/guards/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const VERBOSITY = requireModule('src/support/verbosity');
1+
const VERBOSITY = requireModule('lib/support/verbosity');
22

33
const guardTypeofNotUndefined = {
44
description: () => 'typeof !== undefined',
@@ -41,6 +41,7 @@ const guardHasOwnProperty = {
4141
};
4242

4343
module.exports = {
44+
name: 'guards',
4445
description: (verbosity) => {
4546
switch (verbosity) {
4647
case VERBOSITY.QUIET:
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
'use strict';
22

33
const glob = require('glob');
4-
module.exports = glob.sync('**/*.profile.js')
4+
module.exports = glob.sync('lib/profiles/*/index.js')
55
.reduce((allProfiles, profile) => {
6-
allProfiles.push(requireModule(profile)); // eslint-disable-line
6+
allProfiles.push(requireModule(profile));
77
return allProfiles;
88
}, []);

src/profiles/loops/loops.profile.js renamed to lib/profiles/loops/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use strict';
22

3-
const VERBOSITY = requireModule('src/support/verbosity');
3+
const VERBOSITY = requireModule('lib/support/verbosity');
44

55
const loopForEach = {
66
description: () => '[].forEach() => []',

src/profiles/loops/loops.profile.spec.js renamed to lib/profiles/loops/loops.profile.spec.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
'use strict';
22

33
const expect = require('chai').expect;
4-
const loops = requireModule('src/profiles/loops/loops.profile');
4+
const loops = requireModule('lib/profiles/loops');
55

66
describe('Loops', () => {
77
let result;

src/profiles/recursion/recursion.profile.js renamed to lib/profiles/recursion/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use strict';
22

3-
const VERBOSITY = requireModule('src/support/verbosity');
3+
const VERBOSITY = requireModule('lib/support/verbosity');
44

55
const recursiveSum = {
66
description: () => 'recursive sum',

src/profiles/recursion/recursion.profile.spec.js renamed to lib/profiles/recursion/recursion.profile.spec.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
'use strict';
22

33
const expect = require('chai').expect;
4-
const recursions = requireModule('src/profiles/recursion/recursion.profile');
4+
const recursions = requireModule('lib/profiles/recursion');
55

66
describe('Recursion', () => {
77
let result;
Lines changed: 9 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,13 @@
11
'use strict';
22

33
const chalk = require('chalk');
4-
const VERBOSITY = requireModule('src/support/verbosity');
5-
const events = requireModule('src/support/events');
6-
7-
class Reporter {
8-
/**
9-
* @param {ProfileRunner} profileRunner The test runner
10-
* @param {number} verbosity The verbosity level
11-
* @param {number} precision The number of decimal places to print for results
12-
*/
13-
constructor(profileRunner, verbosity, precision) {
14-
this.verbosity = verbosity;
15-
this.precision = precision;
4+
const VERBOSITY = requireModule('lib/support/verbosity');
5+
const events = requireModule('lib/support/events');
6+
const Reporter = requireModule('lib/reporter/reporter');
167

8+
class ConsoleReporter extends Reporter{
9+
reportOn(profileRunner) {
10+
super.reportOn(profileRunner);
1711
profileRunner.on(events.START, (profiles) => {
1812
if (this.verbosity === VERBOSITY.VERBOSE) {
1913
console.info(`Executing ${profiles.length} profile${profiles.length === 1 ? '' : 's'}.\n`);
@@ -22,7 +16,7 @@ class Reporter {
2216

2317
profileRunner.on(events.END, (profiles) => {
2418
if (this.verbosity >= VERBOSITY.VERBOSE) {
25-
console.log(`Finished ${profiles.length} profiles.`);
19+
console.info(`Finished ${profiles.length} profiles.`);
2620
}
2721
});
2822

@@ -35,9 +29,7 @@ class Reporter {
3529
profileRunner.on(events.PROFILE_END, (profile, result) => {
3630
if (this.verbosity >= VERBOSITY.NORMAL) {
3731
console.log(' ==============');
38-
const best = result.testResults
39-
.sort((a, b) => a.averageTime - b.averageTime)
40-
.filter((a, i, arr) => a.averageTime === arr[0].averageTime);
32+
const best = this.bestResults(result);
4133
if (best.length === 1) {
4234
console.info(chalk.green(` Winner: "${best[0].func.description()}" (${this.formatTime(best[0].averageTime)})\n`));
4335
} else {
@@ -57,19 +49,7 @@ class Reporter {
5749
console.info(` ${this.formatTime(result.averageTime)}\n`);
5850
}
5951
});
60-
61-
profileRunner.on(events.ERROR, (err) => {
62-
console.error(err.stack);
63-
});
64-
}
65-
66-
formatTime(t) {
67-
if (t * 1000 < 10) {
68-
return `${(t * 1000).toFixed(this.precision)}μs`;
69-
}
70-
71-
return `${t.toFixed(this.precision)}ms`;
7252
}
7353
}
7454

75-
module.exports = Reporter;
55+
module.exports = ConsoleReporter;

lib/reporter/json/index.js

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
const VERBOSITY = requireModule('lib/support/verbosity');
2+
const events = requireModule('lib/support/events');
3+
const Reporter = requireModule('lib/reporter/reporter');
4+
5+
class JSONReporter extends Reporter {
6+
constructor(verbosity, precision) {
7+
super(verbosity, precision);
8+
this.report = [];
9+
this.profiles = {};
10+
}
11+
12+
reportOn(profileRunner) {
13+
super.reportOn(profileRunner);
14+
15+
profileRunner.on(events.END, (profiles) => {
16+
console.info(JSON.stringify(this.report, null, 2));
17+
});
18+
19+
profileRunner.on(events.PROFILE_START, (profile) => {
20+
this.profiles[profile.name] = [];
21+
});
22+
23+
profileRunner.on(events.PROFILE_END, (profile, result) => {
24+
const best = this.bestResults(result);
25+
const p = {
26+
name: profile.name,
27+
description: profile.description(this.verbosity)
28+
};
29+
30+
if (this.verbosity >= VERBOSITY.NORMAL) {
31+
p.tests = this.profiles[profile.name];
32+
p.fastest = best.map((b) => ({
33+
description: `${b.func.description()}`,
34+
average: this.formatTime(b.averageTime, true)
35+
}));
36+
}
37+
38+
this.report.push(p);
39+
});
40+
41+
profileRunner.on(events.TEST_END, (profile, func, result) => {
42+
if (this.verbosity >= VERBOSITY.NORMAL) {
43+
this.profiles[profile.name].push({
44+
description: func.description(),
45+
average: this.formatTime(result.averageTime, true)
46+
});
47+
}
48+
});
49+
}
50+
}
51+
52+
module.exports = JSONReporter;

lib/reporter/reporter/index.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
const events = requireModule('lib/support/events');
2+
3+
class Reporter {
4+
/**
5+
* @param {number} verbosity The verbosity level
6+
* @param {number} precision The number of decimal places to print for results
7+
*/
8+
constructor(verbosity, precision) {
9+
this.verbosity = verbosity;
10+
this.precision = precision;
11+
}
12+
13+
/**
14+
* @param {ProfileRunner} profileRunner The test runner
15+
*/
16+
reportOn(profileRunner) {
17+
profileRunner.on(events.ERROR, (err) => {
18+
console.error(err.stack);
19+
});
20+
}
21+
22+
/**
23+
* @param {object} results of profile run
24+
* @returns {Array} best results
25+
*/
26+
bestResults(results) {
27+
return results.testResults
28+
.sort((a, b) => a.averageTime - b.averageTime)
29+
.filter((a, i, arr) => a.averageTime === arr[0].averageTime);
30+
}
31+
32+
formatTime(t, preserveUnits = false) {
33+
if (preserveUnits) {
34+
return t.toFixed(this.precision);
35+
}
36+
37+
if (t * 1000 < 10) {
38+
return `${(t * 1000).toFixed(this.precision)}μs`;
39+
}
40+
41+
return `${t.toFixed(this.precision)}ms`;
42+
}
43+
}
44+
45+
module.exports = Reporter;

src/support/clock/clock.spec.js renamed to lib/support/clock/clock.spec.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
const chai = require('chai');
44
const expect = chai.expect;
55
const sinon = require('sinon');
6-
const clock = require('./clock');
6+
const clock = requireModule('lib/support/clock');
77

88
chai.use(require('sinon-chai'));
99

src/support/clock/clock.js renamed to lib/support/clock/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use strict';
22

3-
const testdata = requireModule('src/support/testdata/testdata')();
3+
const testdata = requireModule('lib/support/testdata')();
44

55
const delta = (start) => {
66
if (!Array.isArray(start)) {

src/support/defaults.js renamed to lib/support/defaults/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use strict';
22

3-
const VERBOSITY = requireModule('src/support/verbosity');
3+
const VERBOSITY = requireModule('lib/support/verbosity');
44

55
module.exports = {
66
iterations: 1000,
File renamed without changes.

src/support/testdata/testdata.js renamed to lib/support/testdata/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use strict';
22

3-
const DEFAULTS = requireModule('src/support/defaults');
3+
const DEFAULTS = requireModule('lib/support/defaults');
44

55
const testArray = (len) => {
66
let i = len;

0 commit comments

Comments
 (0)