michael@0: /* Any copyright is dedicated to the Public Domain. michael@0: http://creativecommons.org/publicdomain/zero/1.0/ */ michael@0: michael@0: "use strict"; michael@0: michael@0: // Load common code from ../mochitest/head.js michael@0: let mochitestDir = getRootDirectory(gTestPath).replace('/mochiperf', '/mochitest'); michael@0: Services.scriptloader.loadSubScript(mochitestDir + "head.js", this); michael@0: michael@0: // Misc. constants michael@0: const kInfoHeader = "PERF-TEST | "; michael@0: const kDeclareId = "DECLARE "; michael@0: const kResultsId = "RESULTS "; michael@0: michael@0: // Mochitest log data format version michael@0: const kDataSetVersion = "1"; michael@0: michael@0: /* michael@0: * PerfTest - helper library for simple mochitest based performance tests. michael@0: */ michael@0: michael@0: var PerfTest = { michael@0: _userStartTime: 0, michael@0: _userStopTime: 0, michael@0: michael@0: /****************************************************** michael@0: * Declare and results michael@0: */ michael@0: michael@0: /* michael@0: * declareTest michael@0: * michael@0: * Declare a test which the graph server will pick up and track. michael@0: * Must be called by every test on startup. Graph server will michael@0: * search for result data between this declaration and the next. michael@0: * michael@0: * @param aUUID string for this particular test, case sensitive. michael@0: * @param aName The name of the test. michael@0: * @param aCategory Top level test calegory. For example 'General', michael@0: * 'Graphics', 'Startup', 'Jim's Tests'. michael@0: * @param aSubCategory (optional) sub category name with aCategory. michael@0: * @param aDescription A detailed description (sentence or two) of michael@0: * what the test does. michael@0: */ michael@0: declareTest: function declareTest(aUUID, aName, aCategory, aSubCategory, aDescription) { michael@0: this._uid = aUUID; michael@0: this._print(kDeclareId, this._toJsonStr({ michael@0: id: aUUID, michael@0: version: kDataSetVersion, michael@0: name: aName, michael@0: category: aCategory, michael@0: subcategory: aSubCategory, michael@0: description: aDescription, michael@0: buildid: Services.appinfo.appBuildID, michael@0: })); michael@0: }, michael@0: michael@0: /* michael@0: * declareNumericalResult michael@0: * michael@0: * Declare a simple numerical result. michael@0: * michael@0: * @param aValue numerical value to record michael@0: * @param aDescription string describing the value to display on the y axis. michael@0: */ michael@0: declareNumericalResult: function declareNumericalResult(aValue, aDescription) { michael@0: this._print(kResultsId, this._toJsonStr({ michael@0: id: this._uid, michael@0: version: kDataSetVersion, michael@0: results: { michael@0: r0: { michael@0: value: aValue, michael@0: desc: aDescription michael@0: } michael@0: }, michael@0: })); michael@0: }, michael@0: michael@0: /* michael@0: * declareFrameRateResult michael@0: * michael@0: * Declare a frame rate for a result. michael@0: * michael@0: * @param aFrameCount numerical frame count michael@0: * @param aRunMs run time in miliseconds michael@0: * @param aDescription string describing the value to display on the y axis. michael@0: */ michael@0: declareFrameRateResult: function declareFrameRateResult(aFrameCount, aRunMs, aDescription) { michael@0: this._print(kResultsId, this._toJsonStr({ michael@0: id: this._uid, michael@0: version: kDataSetVersion, michael@0: results: { michael@0: r0: { michael@0: value: (aFrameCount / (aRunMs / 1000.0)), michael@0: desc: aDescription michael@0: } michael@0: }, michael@0: })); michael@0: }, michael@0: michael@0: /* michael@0: * declareNumericalResults michael@0: * michael@0: * Declare a set of numerical results. michael@0: * michael@0: * @param aArray an array of datapoint objects of the form: michael@0: * michael@0: * [ { value: (value), desc: "description/units" }, .. ] michael@0: * michael@0: * optional values: michael@0: * shareAxis - the 0 based index of a previous data point this point michael@0: * should share a y axis with. michael@0: */ michael@0: declareNumericalResults: function declareNumericalResults(aArray) { michael@0: let collection = new Object(); michael@0: for (let idx = 0; idx < aArray.length; idx++) { michael@0: collection['r' + idx] = { value: aArray[idx].value, desc: aArray[idx].desc }; michael@0: if (aArray[idx].shareAxis != undefined) { michael@0: collection['r' + idx].shareAxis = aArray[idx].shareAxis; michael@0: } michael@0: } michael@0: let dataset = { michael@0: id: this._uid, michael@0: version: kDataSetVersion, michael@0: results: collection michael@0: }; michael@0: this._print(kResultsId, this._toJsonStr(dataset)); michael@0: }, michael@0: michael@0: /****************************************************** michael@0: * Perf tests michael@0: */ michael@0: michael@0: perfBoundsCheck: function perfBoundsCheck(aLow, aHigh, aValue, aTestMessage) { michael@0: ok(aValue < aLow || aValue > aHigh, aTestMessage); michael@0: }, michael@0: michael@0: /****************************************************** michael@0: * Math utilities michael@0: */ michael@0: michael@0: computeMedian: function computeMedian(aArray, aOptions) { michael@0: aArray.sort(function (a, b) { michael@0: return a - b; michael@0: }); michael@0: michael@0: var idx = Math.floor(aArray.length / 2); michael@0: michael@0: if(aArray.length % 2) { michael@0: return aArray[idx]; michael@0: } else { michael@0: return (aArray[idx-1] + aArray[idx]) / 2; michael@0: } michael@0: }, michael@0: michael@0: computeAverage: function computeAverage(aArray, aOptions) { michael@0: let idx; michael@0: let count = 0, total = 0; michael@0: let highIdx = -1, lowIdx = -1; michael@0: let high = 0, low = 0; michael@0: if (aOptions.stripOutliers) { michael@0: for (idx = 0; idx < aArray.length; idx++) { michael@0: if (high < aArray[idx]) { michael@0: highIdx = idx; michael@0: high = aArray[idx]; michael@0: } michael@0: if (low > aArray[idx]) { michael@0: lowIdx = idx; michael@0: low = aArray[idx]; michael@0: } michael@0: } michael@0: } michael@0: for (idx = 0; idx < aArray.length; idx++) { michael@0: if (idx != high && idx != low) { michael@0: total += aArray[idx]; michael@0: count++; michael@0: } michael@0: } michael@0: return total / count; michael@0: }, michael@0: michael@0: computeHighLowBands: function computeHighLow(aArray, aPercentage) { michael@0: let bandCount = Math.ceil(aArray.length * aPercentage); michael@0: let lowGroup = 0, highGroup = 0; michael@0: let idx; michael@0: michael@0: function compareNumbers(a, b) { michael@0: return a - b; michael@0: } michael@0: aArray.sort(compareNumbers); michael@0: for (idx = 0; idx < bandCount; idx++) { michael@0: lowGroup += aArray[idx]; michael@0: } michael@0: let top = aArray.length - 1; michael@0: for (idx = top; idx > (top - bandCount); idx--) { michael@0: highGroup += aArray[idx]; michael@0: } michael@0: return { michael@0: low: lowGroup / bandCount, michael@0: high: highGroup / bandCount, michael@0: ave: this.computeAverage(aArray, {}) michael@0: }; michael@0: }, michael@0: michael@0: /****************************************************** michael@0: * Internal michael@0: */ michael@0: michael@0: _print: function _print() { michael@0: let str = kInfoHeader; michael@0: for (let idx = 0; idx < arguments.length; idx++) { michael@0: str += arguments[idx]; michael@0: } michael@0: info(str); michael@0: }, michael@0: michael@0: _toJsonStr: function _toJsonStr(aTable) { michael@0: return window.JSON.stringify(aTable); michael@0: }, michael@0: }; michael@0: michael@0: /* michael@0: * StopWatch - timing helper michael@0: */ michael@0: michael@0: function StopWatch(aStart) { michael@0: if (aStart) { michael@0: this.start(); michael@0: } michael@0: } michael@0: michael@0: StopWatch.prototype = { michael@0: /* michael@0: * Start timing. Resets existing clock. michael@0: */ michael@0: start: function start() { michael@0: this.reset(); michael@0: this._userStartTime = window.performance.now(); michael@0: }, michael@0: michael@0: /* michael@0: * Stop timing. michael@0: */ michael@0: stop: function stop() { michael@0: this._userStopTime = window.performance.now(); michael@0: return this.time(); michael@0: }, michael@0: michael@0: /* michael@0: * Resets both start and end time. michael@0: */ michael@0: reset: function reset() { michael@0: this._userStartTime = this._userStopTime = 0; michael@0: }, michael@0: michael@0: /* michael@0: * Returns the total time ellapsed in milliseconds. Returns zero if michael@0: * no time has been accumulated. michael@0: */ michael@0: time: function time() { michael@0: if (!this._userStartTime) { michael@0: return 0; michael@0: } michael@0: if (!this._userStopTime) { michael@0: return (window.performance.now() - this._userStartTime); michael@0: } michael@0: return this._userStopTime - this._userStartTime; michael@0: }, michael@0: };