|
1 /* Any copyright is dedicated to the Public Domain. |
|
2 http://creativecommons.org/publicdomain/zero/1.0/ */ |
|
3 |
|
4 "use strict"; |
|
5 |
|
6 // Load common code from ../mochitest/head.js |
|
7 let mochitestDir = getRootDirectory(gTestPath).replace('/mochiperf', '/mochitest'); |
|
8 Services.scriptloader.loadSubScript(mochitestDir + "head.js", this); |
|
9 |
|
10 // Misc. constants |
|
11 const kInfoHeader = "PERF-TEST | "; |
|
12 const kDeclareId = "DECLARE "; |
|
13 const kResultsId = "RESULTS "; |
|
14 |
|
15 // Mochitest log data format version |
|
16 const kDataSetVersion = "1"; |
|
17 |
|
18 /* |
|
19 * PerfTest - helper library for simple mochitest based performance tests. |
|
20 */ |
|
21 |
|
22 var PerfTest = { |
|
23 _userStartTime: 0, |
|
24 _userStopTime: 0, |
|
25 |
|
26 /****************************************************** |
|
27 * Declare and results |
|
28 */ |
|
29 |
|
30 /* |
|
31 * declareTest |
|
32 * |
|
33 * Declare a test which the graph server will pick up and track. |
|
34 * Must be called by every test on startup. Graph server will |
|
35 * search for result data between this declaration and the next. |
|
36 * |
|
37 * @param aUUID string for this particular test, case sensitive. |
|
38 * @param aName The name of the test. |
|
39 * @param aCategory Top level test calegory. For example 'General', |
|
40 * 'Graphics', 'Startup', 'Jim's Tests'. |
|
41 * @param aSubCategory (optional) sub category name with aCategory. |
|
42 * @param aDescription A detailed description (sentence or two) of |
|
43 * what the test does. |
|
44 */ |
|
45 declareTest: function declareTest(aUUID, aName, aCategory, aSubCategory, aDescription) { |
|
46 this._uid = aUUID; |
|
47 this._print(kDeclareId, this._toJsonStr({ |
|
48 id: aUUID, |
|
49 version: kDataSetVersion, |
|
50 name: aName, |
|
51 category: aCategory, |
|
52 subcategory: aSubCategory, |
|
53 description: aDescription, |
|
54 buildid: Services.appinfo.appBuildID, |
|
55 })); |
|
56 }, |
|
57 |
|
58 /* |
|
59 * declareNumericalResult |
|
60 * |
|
61 * Declare a simple numerical result. |
|
62 * |
|
63 * @param aValue numerical value to record |
|
64 * @param aDescription string describing the value to display on the y axis. |
|
65 */ |
|
66 declareNumericalResult: function declareNumericalResult(aValue, aDescription) { |
|
67 this._print(kResultsId, this._toJsonStr({ |
|
68 id: this._uid, |
|
69 version: kDataSetVersion, |
|
70 results: { |
|
71 r0: { |
|
72 value: aValue, |
|
73 desc: aDescription |
|
74 } |
|
75 }, |
|
76 })); |
|
77 }, |
|
78 |
|
79 /* |
|
80 * declareFrameRateResult |
|
81 * |
|
82 * Declare a frame rate for a result. |
|
83 * |
|
84 * @param aFrameCount numerical frame count |
|
85 * @param aRunMs run time in miliseconds |
|
86 * @param aDescription string describing the value to display on the y axis. |
|
87 */ |
|
88 declareFrameRateResult: function declareFrameRateResult(aFrameCount, aRunMs, aDescription) { |
|
89 this._print(kResultsId, this._toJsonStr({ |
|
90 id: this._uid, |
|
91 version: kDataSetVersion, |
|
92 results: { |
|
93 r0: { |
|
94 value: (aFrameCount / (aRunMs / 1000.0)), |
|
95 desc: aDescription |
|
96 } |
|
97 }, |
|
98 })); |
|
99 }, |
|
100 |
|
101 /* |
|
102 * declareNumericalResults |
|
103 * |
|
104 * Declare a set of numerical results. |
|
105 * |
|
106 * @param aArray an array of datapoint objects of the form: |
|
107 * |
|
108 * [ { value: (value), desc: "description/units" }, .. ] |
|
109 * |
|
110 * optional values: |
|
111 * shareAxis - the 0 based index of a previous data point this point |
|
112 * should share a y axis with. |
|
113 */ |
|
114 declareNumericalResults: function declareNumericalResults(aArray) { |
|
115 let collection = new Object(); |
|
116 for (let idx = 0; idx < aArray.length; idx++) { |
|
117 collection['r' + idx] = { value: aArray[idx].value, desc: aArray[idx].desc }; |
|
118 if (aArray[idx].shareAxis != undefined) { |
|
119 collection['r' + idx].shareAxis = aArray[idx].shareAxis; |
|
120 } |
|
121 } |
|
122 let dataset = { |
|
123 id: this._uid, |
|
124 version: kDataSetVersion, |
|
125 results: collection |
|
126 }; |
|
127 this._print(kResultsId, this._toJsonStr(dataset)); |
|
128 }, |
|
129 |
|
130 /****************************************************** |
|
131 * Perf tests |
|
132 */ |
|
133 |
|
134 perfBoundsCheck: function perfBoundsCheck(aLow, aHigh, aValue, aTestMessage) { |
|
135 ok(aValue < aLow || aValue > aHigh, aTestMessage); |
|
136 }, |
|
137 |
|
138 /****************************************************** |
|
139 * Math utilities |
|
140 */ |
|
141 |
|
142 computeMedian: function computeMedian(aArray, aOptions) { |
|
143 aArray.sort(function (a, b) { |
|
144 return a - b; |
|
145 }); |
|
146 |
|
147 var idx = Math.floor(aArray.length / 2); |
|
148 |
|
149 if(aArray.length % 2) { |
|
150 return aArray[idx]; |
|
151 } else { |
|
152 return (aArray[idx-1] + aArray[idx]) / 2; |
|
153 } |
|
154 }, |
|
155 |
|
156 computeAverage: function computeAverage(aArray, aOptions) { |
|
157 let idx; |
|
158 let count = 0, total = 0; |
|
159 let highIdx = -1, lowIdx = -1; |
|
160 let high = 0, low = 0; |
|
161 if (aOptions.stripOutliers) { |
|
162 for (idx = 0; idx < aArray.length; idx++) { |
|
163 if (high < aArray[idx]) { |
|
164 highIdx = idx; |
|
165 high = aArray[idx]; |
|
166 } |
|
167 if (low > aArray[idx]) { |
|
168 lowIdx = idx; |
|
169 low = aArray[idx]; |
|
170 } |
|
171 } |
|
172 } |
|
173 for (idx = 0; idx < aArray.length; idx++) { |
|
174 if (idx != high && idx != low) { |
|
175 total += aArray[idx]; |
|
176 count++; |
|
177 } |
|
178 } |
|
179 return total / count; |
|
180 }, |
|
181 |
|
182 computeHighLowBands: function computeHighLow(aArray, aPercentage) { |
|
183 let bandCount = Math.ceil(aArray.length * aPercentage); |
|
184 let lowGroup = 0, highGroup = 0; |
|
185 let idx; |
|
186 |
|
187 function compareNumbers(a, b) { |
|
188 return a - b; |
|
189 } |
|
190 aArray.sort(compareNumbers); |
|
191 for (idx = 0; idx < bandCount; idx++) { |
|
192 lowGroup += aArray[idx]; |
|
193 } |
|
194 let top = aArray.length - 1; |
|
195 for (idx = top; idx > (top - bandCount); idx--) { |
|
196 highGroup += aArray[idx]; |
|
197 } |
|
198 return { |
|
199 low: lowGroup / bandCount, |
|
200 high: highGroup / bandCount, |
|
201 ave: this.computeAverage(aArray, {}) |
|
202 }; |
|
203 }, |
|
204 |
|
205 /****************************************************** |
|
206 * Internal |
|
207 */ |
|
208 |
|
209 _print: function _print() { |
|
210 let str = kInfoHeader; |
|
211 for (let idx = 0; idx < arguments.length; idx++) { |
|
212 str += arguments[idx]; |
|
213 } |
|
214 info(str); |
|
215 }, |
|
216 |
|
217 _toJsonStr: function _toJsonStr(aTable) { |
|
218 return window.JSON.stringify(aTable); |
|
219 }, |
|
220 }; |
|
221 |
|
222 /* |
|
223 * StopWatch - timing helper |
|
224 */ |
|
225 |
|
226 function StopWatch(aStart) { |
|
227 if (aStart) { |
|
228 this.start(); |
|
229 } |
|
230 } |
|
231 |
|
232 StopWatch.prototype = { |
|
233 /* |
|
234 * Start timing. Resets existing clock. |
|
235 */ |
|
236 start: function start() { |
|
237 this.reset(); |
|
238 this._userStartTime = window.performance.now(); |
|
239 }, |
|
240 |
|
241 /* |
|
242 * Stop timing. |
|
243 */ |
|
244 stop: function stop() { |
|
245 this._userStopTime = window.performance.now(); |
|
246 return this.time(); |
|
247 }, |
|
248 |
|
249 /* |
|
250 * Resets both start and end time. |
|
251 */ |
|
252 reset: function reset() { |
|
253 this._userStartTime = this._userStopTime = 0; |
|
254 }, |
|
255 |
|
256 /* |
|
257 * Returns the total time ellapsed in milliseconds. Returns zero if |
|
258 * no time has been accumulated. |
|
259 */ |
|
260 time: function time() { |
|
261 if (!this._userStartTime) { |
|
262 return 0; |
|
263 } |
|
264 if (!this._userStopTime) { |
|
265 return (window.performance.now() - this._userStartTime); |
|
266 } |
|
267 return this._userStopTime - this._userStartTime; |
|
268 }, |
|
269 }; |