var __extends = (this && this.__extends) || (function () {
    var extendStatics = Object.setPrototypeOf ||
        ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
        function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
    return function (d, b) {
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
(function (factory) {
    if (typeof module === "object" && typeof module.exports === "object") {
        var v = factory(require, exports);
        if (v !== undefined) module.exports = v;
    }
    else if (typeof define === "function" && define.amd) {
        define(["require", "exports", "fs", "./Reporter"], factory);
    }
})(function (require, exports) {
    "use strict";
    Object.defineProperty(exports, "__esModule", { value: true });
    var fs_1 = require("fs");
    var Reporter_1 = require("./Reporter");
    /**
     * Benchmark is a reporter that can generate a baseline report and do runtime comparisons against an existing baseline.
     *
     * Configuration
     * -------------
     * Along with the default reporter options, Benchmark also supports a `mode` option. This can have two
     * values:
     *
     *     'baseline': Benchmark data will be written to a baseline file when testing is finished
     *     'test': Benchmark is compared to a baseline read from a file when testing starts
     *
     * Baseline data is stored hierarchically by environment and then by test.
     *
     * Notation
     * --------
     * rme: relative margin of error -- margin of error as a percentage of the mean margin of error
     * mean: mean execution time per function run
     * hz: Hertz (number of executions of a function per second). 1/Hz is the mean execution time of function.
     */
    var BenchmarkReporter = (function (_super) {
        __extends(BenchmarkReporter, _super);
        function BenchmarkReporter(executor, config) {
            var _this = _super.call(this, executor, config) || this;
            // In test mode, try to load benchmark data for comparison
            if (_this.mode === 'test') {
                try {
                    _this.baseline = JSON.parse(fs_1.readFileSync(_this.filename, { encoding: 'utf8' }));
                }
                catch (error) {
                    _this.console.warn('Unable to load benchmark baseline data from ' + _this.filename);
                    _this.console.warn('Switching to "baseline" mode');
                    _this.mode = 'baseline';
                }
            }
            if (!_this.baseline) {
                _this.baseline = {};
            }
            // Cache environments by session ID so we can look them up again when serialized tests come back from remote
            // browsers
            _this.sessions = {};
            return _this;
        }
        BenchmarkReporter.prototype._getSession = function (testOrSuite) {
            var sessionId = testOrSuite.sessionId || 'local';
            var session = this.sessions[sessionId];
            if (!session) {
                var client = void 0;
                var version = void 0;
                var platform_1;
                if (testOrSuite.sessionId) {
                    var environmentType = testOrSuite.remote.environmentType;
                    client = environmentType.browserName;
                    version = environmentType.version;
                    platform_1 = environmentType.platform;
                }
                else {
                    client = process.title;
                    version = process.version;
                    platform_1 = process.platform;
                }
                session = this.sessions[sessionId] = {
                    suites: {},
                    environment: {
                        client: client,
                        version: version,
                        platform: platform_1,
                        id: client + ':' + version + ':' + platform_1
                    }
                };
            }
            return session;
        };
        BenchmarkReporter.prototype.runEnd = function () {
            if (this.mode === 'baseline') {
                var existingBaseline_1;
                try {
                    existingBaseline_1 = JSON.parse(fs_1.readFileSync(this.filename, { encoding: 'utf8' }));
                }
                catch (error) {
                    existingBaseline_1 = {};
                }
                // Merge the newly recorded baseline data into the existing baseline data and write it back out to output
                // file.
                var baseline_1 = this.baseline;
                Object.keys(baseline_1).forEach(function (environmentId) {
                    existingBaseline_1[environmentId] = baseline_1[environmentId];
                });
                fs_1.writeFileSync(this.filename, JSON.stringify(existingBaseline_1, null, '    '));
            }
        };
        BenchmarkReporter.prototype.suiteEnd = function (suite) {
            var session = this._getSession(suite);
            if (!suite.hasParent) {
                var environment = session.environment;
                this.console.log('Finished benchmarking ' + environment.client + ' ' + environment.version + ' on ' + environment.platform);
            }
            else if (this.mode === 'test') {
                var suiteInfo = session.suites[suite.id];
                var numTests = suiteInfo.numBenchmarks;
                if (numTests > 0) {
                    var numFailedTests = suiteInfo.numFailedBenchmarks;
                    var message = numFailedTests + '/' + numTests + ' benchmarks failed in ' + suite.id;
                    if (numFailedTests > 0) {
                        this.console.warn(message);
                    }
                    else {
                        this.console.log(message);
                    }
                }
            }
        };
        BenchmarkReporter.prototype.suiteStart = function (suite) {
            var session = this._getSession(suite);
            // This is a session root suite
            if (!suite.hasParent) {
                var environment = session.environment;
                var environmentName = environment.client + ' ' + environment.version + ' on ' + environment.platform;
                var baselineEnvironments = this.baseline;
                this.console.log((this.mode === 'baseline' ? 'Baselining' : 'Benchmarking') + ' ' + environmentName);
                if (this.mode === 'baseline') {
                    baselineEnvironments[environment.id] = {
                        client: environment.client,
                        version: environment.version,
                        platform: environment.platform,
                        tests: {}
                    };
                }
                else if (!baselineEnvironments[environment.id]) {
                    this.console.warn('No baseline data for ' + environmentName + '!');
                }
            }
            else {
                session.suites[suite.id] = {
                    numBenchmarks: 0,
                    numFailedBenchmarks: 0
                };
            }
        };
        BenchmarkReporter.prototype.testEnd = function (test) {
            var _this = this;
            // Just check for the benchmark property because the test may be a deserialized object rather than an actual
            // BenchmarkTest instance.
            if (test.benchmark == null) {
                return;
            }
            if (test.error) {
                var session = this._getSession(test);
                var suiteInfo = session.suites[test.parentId];
                suiteInfo.numBenchmarks++;
                suiteInfo.numFailedBenchmarks++;
                this.console.error('FAIL: ' + test.id);
                this.console.error(this.executor.formatError(test.error, { space: '  ' }));
            }
            else {
                var checkTest = function (baseline, benchmark) {
                    var warn = [];
                    var fail = [];
                    var list;
                    var baselineMean = baseline.stats.mean;
                    var thresholds = _this.thresholds || {};
                    var percentDifference = 100 * (benchmark.stats.mean - baselineMean) / baselineMean;
                    if (thresholds.warn && thresholds.warn.mean && Math.abs(percentDifference) > thresholds.warn.mean) {
                        list = warn;
                        if (thresholds.fail && thresholds.fail.mean && Math.abs(percentDifference) > thresholds.fail.mean) {
                            list = fail;
                        }
                        list.push('Execution time is ' + percentDifference.toFixed(1) + '% off');
                    }
                    var baselineRme = baseline.stats.rme;
                    // RME is already a percent
                    percentDifference = benchmark.stats.rme - baselineRme;
                    if (thresholds.warn && thresholds.warn.rme && Math.abs(percentDifference) > thresholds.warn.rme) {
                        list = warn;
                        if (thresholds.fail && thresholds.fail.rme && Math.abs(percentDifference) > thresholds.fail.rme) {
                            list = fail;
                        }
                        list.push('RME is ' + percentDifference.toFixed(1) + '% off');
                    }
                    if (fail.length) {
                        _this.console.error('FAIL ' + test.id + ' (' + fail.join(', ') + ')');
                        return false;
                    }
                    else if (warn.length) {
                        _this.console.warn('WARN ' + test.id + ' (' + warn.join(', ') + ')');
                    }
                    else {
                        _this.console.log('PASS ' + test.id);
                    }
                    return true;
                };
                var benchmark = test.benchmark;
                var session = this._getSession(test);
                var environment = session.environment;
                var suiteInfo = session.suites[test.parentId];
                suiteInfo.numBenchmarks++;
                var baseline = this.baseline[environment.id];
                if (this.mode === 'baseline') {
                    baseline.tests[test.id] = {
                        hz: benchmark.hz,
                        times: benchmark.times,
                        stats: {
                            rme: benchmark.stats.rme,
                            moe: benchmark.stats.moe,
                            mean: benchmark.stats.mean
                        }
                    };
                    this.console.log('Baselined ' + test.name);
                    this.executor.log('Time per run:', formatSeconds(benchmark.stats.mean), '\xb1', benchmark.stats.rme.toFixed(2), '%');
                }
                else {
                    if (baseline) {
                        var testData = baseline.tests[test.id];
                        var result = checkTest(testData, benchmark);
                        var baselineStats = testData.stats;
                        var benchmarkStats = benchmark.stats;
                        this.executor.log('Expected time per run:', formatSeconds(baselineStats.mean), '\xb1', baselineStats.rme.toFixed(2), '%');
                        this.executor.log('Actual time per run:', formatSeconds(benchmarkStats.mean), '\xb1', benchmarkStats.rme.toFixed(2), '%');
                        if (!result) {
                            suiteInfo.numFailedBenchmarks++;
                        }
                    }
                }
            }
        };
        __decorate([
            Reporter_1.eventHandler()
        ], BenchmarkReporter.prototype, "runEnd", null);
        __decorate([
            Reporter_1.eventHandler()
        ], BenchmarkReporter.prototype, "suiteEnd", null);
        __decorate([
            Reporter_1.eventHandler()
        ], BenchmarkReporter.prototype, "suiteStart", null);
        __decorate([
            Reporter_1.eventHandler()
        ], BenchmarkReporter.prototype, "testEnd", null);
        return BenchmarkReporter;
    }(Reporter_1.default));
    exports.default = BenchmarkReporter;
    // jshint node:true
    function formatSeconds(value) {
        if (value == null) {
            return null;
        }
        var units = 's';
        if (value < 1) {
            var places = Math.ceil(Math.log(value) / Math.log(10)) - 1;
            if (places < -9) {
                value *= Math.pow(10, 12);
                units = 'ps';
            }
            else if (places < -6) {
                value *= Math.pow(10, 9);
                units = 'ns';
            }
            else if (places < -3) {
                value *= Math.pow(10, 6);
                units = 'µs';
            }
            else if (places < 0) {
                value *= Math.pow(10, 3);
                units = 'ms';
            }
        }
        return value.toFixed(3) + units;
    }
});
//# sourceMappingURL=Benchmark.js.map