From f9dcfa3ba5a966dc521bf53d92e27a7609aaebf3 Mon Sep 17 00:00:00 2001 From: Tobias Bosch Date: Fri, 6 Mar 2015 11:46:33 -0800 Subject: [PATCH] feat(benchpress): add a file reporter --- modules/angular2/src/facade/lang.dart | 6 ++ modules/angular2/src/facade/lang.es6 | 6 ++ modules/benchpress/benchpress.es6 | 22 +++++ modules/benchpress/common.js | 1 + modules/benchpress/src/measure_values.js | 10 +- .../src/reporter/json_file_reporter.js | 58 +++++++++++ modules/benchpress/src/sample_description.js | 8 ++ .../test/reporter/json_file_reporter_spec.js | 96 +++++++++++++++++++ package.json | 1 + protractor-shared.js | 11 ++- 10 files changed, 217 insertions(+), 2 deletions(-) create mode 100644 modules/benchpress/src/reporter/json_file_reporter.js create mode 100644 modules/benchpress/test/reporter/json_file_reporter_spec.js diff --git a/modules/angular2/src/facade/lang.dart b/modules/angular2/src/facade/lang.dart index e18b481057..e37ed59c62 100644 --- a/modules/angular2/src/facade/lang.dart +++ b/modules/angular2/src/facade/lang.dart @@ -210,7 +210,13 @@ class DateWrapper { static DateTime fromMillis(int ms) { return new DateTime.fromMillisecondsSinceEpoch(ms); } + static int toMillis(DateTime date) { + return date.millisecondsSinceEpoch; + } static DateTime now() { return new DateTime.now(); } + static toJson(DateTime date) { + return date.toUtc().toIso8601String(); + } } diff --git a/modules/angular2/src/facade/lang.es6 b/modules/angular2/src/facade/lang.es6 index 613e0d51b7..76107378f2 100644 --- a/modules/angular2/src/facade/lang.es6 +++ b/modules/angular2/src/facade/lang.es6 @@ -275,7 +275,13 @@ export class DateWrapper { static fromMillis(ms) { return new Date(ms); } + static toMillis(date:Date) { + return date.getTime(); + } static now() { return new Date(); } + static toJson(date) { + return date.toJSON(); + } } diff --git a/modules/benchpress/benchpress.es6 b/modules/benchpress/benchpress.es6 index 7b9e8b8681..6db01a4c9f 100644 --- a/modules/benchpress/benchpress.es6 +++ b/modules/benchpress/benchpress.es6 @@ -1,2 +1,24 @@ +import { bind } from 'angular2/di'; +import { JsonFileReporter } from './common'; + export * from './common'; export { SeleniumWebDriverAdapter } from './src/webdriver/selenium_webdriver_adapter'; + +var fs = require('fs'); + +// Note: Can't do the `require` call in a facade as it can't be loaded into the browser! +JsonFileReporter.BINDINGS.push( + bind(JsonFileReporter.WRITE_FILE).toValue(writeFile) +); + +function writeFile(filename, content) { + return new Promise(function(resolve, reject) { + fs.writeFile(filename, content, (error) => { + if (error) { + reject(error); + } else { + resolve(); + } + }); + }) +} diff --git a/modules/benchpress/common.js b/modules/benchpress/common.js index c967724f8c..a4df855c08 100644 --- a/modules/benchpress/common.js +++ b/modules/benchpress/common.js @@ -7,6 +7,7 @@ export { WebDriverAdapter } from './src/web_driver_adapter'; export { SizeValidator } from './src/validator/size_validator'; export { RegressionSlopeValidator } from './src/validator/regression_slope_validator'; export { ConsoleReporter } from './src/reporter/console_reporter'; +export { JsonFileReporter } from './src/reporter/json_file_reporter'; export { SampleDescription } from './src/sample_description'; export { PerflogMetric } from './src/metric/perflog_metric'; export { ChromeDriverExtension } from './src/webdriver/chrome_driver_extension'; diff --git a/modules/benchpress/src/measure_values.js b/modules/benchpress/src/measure_values.js index cafae766c2..89ed03c91a 100644 --- a/modules/benchpress/src/measure_values.js +++ b/modules/benchpress/src/measure_values.js @@ -1,4 +1,4 @@ -import { Date } from 'angular2/src/facade/lang'; +import { Date, DateWrapper } from 'angular2/src/facade/lang'; import { StringMap } from 'angular2/src/facade/collection'; export class MeasureValues { @@ -11,4 +11,12 @@ export class MeasureValues { this.runIndex = runIndex; this.values = values; } + + toJson() { + return { + 'timeStamp': DateWrapper.toJson(this.timeStamp), + 'runIndex': this.runIndex, + 'values': this.values + }; + } } diff --git a/modules/benchpress/src/reporter/json_file_reporter.js b/modules/benchpress/src/reporter/json_file_reporter.js new file mode 100644 index 0000000000..f431dea2a9 --- /dev/null +++ b/modules/benchpress/src/reporter/json_file_reporter.js @@ -0,0 +1,58 @@ +import { DateWrapper, isPresent, isBlank, Json } from 'angular2/src/facade/lang'; +import { List } from 'angular2/src/facade/collection'; +import { Promise, PromiseWrapper } from 'angular2/src/facade/async'; + +import { bind, OpaqueToken } from 'angular2/di'; + +import { Reporter } from '../reporter'; +import { SampleDescription } from '../sample_description'; +import { MeasureValues } from '../measure_values'; + +/** + * A reporter that writes results into a json file. + * TODO(tbosch): right now we bind the `writeFile` method + * in benchpres/benchpress.es6. This does not work for Dart, + * find another way... + */ +export class JsonFileReporter extends Reporter { + // TODO(tbosch): use static values when our transpiler supports them + static get WRITE_FILE() { return _WRITE_FILE; } + // TODO(tbosch): use static values when our transpiler supports them + static get PATH() { return _PATH; } + static get BINDINGS() { return _BINDINGS; } + + _writeFile:Function; + _path:string; + _description:SampleDescription; + + constructor(sampleDescription, path, writeFile) { + super(); + this._description = sampleDescription; + this._path = path; + this._writeFile = writeFile; + } + + reportMeasureValues(measureValues:MeasureValues):Promise { + return PromiseWrapper.resolve(null); + } + + reportSample(completeSample:List, validSample:List):Promise { + var content = Json.stringify({ + 'description': this._description, + 'completeSample': completeSample, + 'validSample': validSample + }); + var filePath = `${this._path}/${this._description.id}_${DateWrapper.toMillis(DateWrapper.now())}.json`; + return this._writeFile(filePath, content); + } +} + +var _WRITE_FILE = new OpaqueToken('JsonFileReporter.writeFile'); +var _PATH = new OpaqueToken('JsonFileReporter.path'); +var _BINDINGS = [ + bind(JsonFileReporter).toFactory( + (sampleDescription, path, writeFile) => new JsonFileReporter(sampleDescription, path, writeFile), + [SampleDescription, _PATH, _WRITE_FILE] + ), + bind(_PATH).toValue('.') +]; diff --git a/modules/benchpress/src/sample_description.js b/modules/benchpress/src/sample_description.js index 7054741f4e..773ecda8ae 100644 --- a/modules/benchpress/src/sample_description.js +++ b/modules/benchpress/src/sample_description.js @@ -23,6 +23,14 @@ export class SampleDescription { StringMapWrapper.forEach(description, (value, prop) => this.description[prop] = value ); }); } + + toJson() { + return { + 'id': this.id, + 'description': this.description, + 'metrics': this.metrics + }; + } } var _BINDINGS = [ diff --git a/modules/benchpress/test/reporter/json_file_reporter_spec.js b/modules/benchpress/test/reporter/json_file_reporter_spec.js new file mode 100644 index 0000000000..fb769551ac --- /dev/null +++ b/modules/benchpress/test/reporter/json_file_reporter_spec.js @@ -0,0 +1,96 @@ +import {describe, ddescribe, it, iit, xit, expect, beforeEach, afterEach} from 'angular2/test_lib'; + +import { DateWrapper, Json, RegExpWrapper, isPresent } from 'angular2/src/facade/lang'; +import { PromiseWrapper } from 'angular2/src/facade/async'; + +import { + bind, Injector, + SampleDescription, + MeasureValues +} from 'benchpress/common'; + + +import { JsonFileReporter } from 'benchpress/src/reporter/json_file_reporter'; + +export function main() { + describe('file reporter', () => { + var loggedFile; + + function createReporter({sampleId, descriptions, metrics, path}) { + var bindings = [ + JsonFileReporter.BINDINGS, + bind(SampleDescription).toValue(new SampleDescription(sampleId, descriptions, metrics)), + bind(JsonFileReporter.PATH).toValue(path), + bind(JsonFileReporter.WRITE_FILE).toValue((filename, content) => { + loggedFile = { + 'filename': filename, + 'content': content + }; + return PromiseWrapper.resolve(null); + }) + ]; + return new Injector(bindings).get(JsonFileReporter); + } + + it('should write all data into a file', (done) => { + createReporter({ + sampleId: 'someId', + descriptions: [{ 'a': 2 }], + path: 'somePath', + metrics: { + 'script': 'script time' + } + }).reportSample([ + mv(0, 0, { 'a': 3, 'b': 6}) + ], [mv(0, 0, { + 'a': 3, 'b': 6 + }), mv(1, 1, { + 'a': 5, 'b': 9 + })]); + var regExp = RegExpWrapper.create('somePath/someId_\\d+\\.json'); + expect(isPresent(RegExpWrapper.firstMatch(regExp, loggedFile['filename']))).toBe(true); + var parsedContent = Json.parse(loggedFile['content']); + expect(parsedContent).toEqual({ + "description": { + "id": "someId", + "description": { + "a": 2 + }, + "metrics": {"script": "script time"} + }, + "completeSample": [{ + "timeStamp": "1970-01-01T00:00:00.000Z", + "runIndex": 0, + "values": { + "a": 3, + "b": 6 + } + }], + "validSample": [ + { + "timeStamp": "1970-01-01T00:00:00.000Z", + "runIndex": 0, + "values": { + "a": 3, + "b": 6 + } + }, + { + "timeStamp": "1970-01-01T00:00:00.001Z", + "runIndex": 1, + "values": { + "a": 5, + "b": 9 + } + } + ] + }); + done(); + }); + + }); +} + +function mv(runIndex, time, values) { + return new MeasureValues(runIndex, DateWrapper.fromMillis(time), values); +} diff --git a/package.json b/package.json index 1b62ce1c22..13c0d9759c 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "dgeni": "^0.4.1", "dgeni-packages": "^0.10.10", "event-stream": "^3.1.5", + "fs-extra": "^0.16.4", "glob": "^4.0.6", "gulp": "^3.8.8", "gulp-changed": "^1.0.0", diff --git a/protractor-shared.js b/protractor-shared.js index d78d3890e1..ecfbdbe77d 100644 --- a/protractor-shared.js +++ b/protractor-shared.js @@ -1,5 +1,6 @@ // load traceur runtime as our tests are written in es6 require('traceur/bin/traceur-runtime.js'); +var fs = require('fs-extra'); var argv = require('yargs') .usage('Angular e2e/perf test options.') @@ -208,13 +209,21 @@ exports.createBenchpressRunner = function(options) { if (process.env.GIT_SHA) { runId = process.env.GIT_SHA + ' ' + runId; } + var resultsFolder = './dist/benchmark_results'; + fs.ensureDirSync(resultsFolder); var bindings = [ benchpress.SeleniumWebDriverAdapter.PROTRACTOR_BINDINGS, benchpress.bind(benchpress.Options.FORCE_GC).toValue(argv['force-gc']), benchpress.bind(benchpress.Options.DEFAULT_DESCRIPTION).toValue({ 'lang': options.lang, 'runId': runId - }) + }), + benchpress.MultiReporter.createBindings([ + benchpress.ConsoleReporter, + benchpress.JsonFileReporter + ]), + benchpress.JsonFileReporter.BINDINGS, + benchpress.bind(benchpress.JsonFileReporter.PATH).toValue(resultsFolder) ]; if (argv['benchmark']) { bindings.push(benchpress.Validator.bindTo(benchpress.RegressionSlopeValidator));