1 | // JSLitmus.js |
---|
2 | // |
---|
3 | // History: |
---|
4 | // 2008-10-27: Initial release |
---|
5 | // 2008-11-09: Account for iteration loop overhead |
---|
6 | // 2008-11-13: Added OS detection |
---|
7 | // |
---|
8 | // Copyright (c) 2008, Robert Kieffer |
---|
9 | // All Rights Reserved |
---|
10 | // |
---|
11 | // Permission is hereby granted, free of charge, to any person obtaining a copy |
---|
12 | // of this software and associated documentation files (the |
---|
13 | // Software), to deal in the Software without restriction, including |
---|
14 | // without limitation the rights to use, copy, modify, merge, publish, |
---|
15 | // distribute, sublicense, and/or sell copies of the Software, and to permit |
---|
16 | // persons to whom the Software is furnished to do so, subject to the |
---|
17 | // following conditions: |
---|
18 | // |
---|
19 | // THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, |
---|
20 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
---|
21 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN |
---|
22 | // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, |
---|
23 | // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR |
---|
24 | // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE |
---|
25 | // USE OR OTHER DEALINGS IN THE SOFTWARE. |
---|
26 | |
---|
27 | (function() { |
---|
28 | // Private methods and state |
---|
29 | |
---|
30 | // Get platform info but don't go crazy trying to everything that's out |
---|
31 | // there. This is just for the major platforms and OSes. |
---|
32 | var platform = 'unknown platform', ua = navigator.userAgent; |
---|
33 | |
---|
34 | // Detect OS |
---|
35 | var oses = ['Windows','iPhone OS','(Intel |PPC )?Mac OS X','Linux'].join('|'); |
---|
36 | var pOS = new RegExp('((' + oses + ') [^ \);]*)').test(ua) ? RegExp.$1 : null; |
---|
37 | if (!pOS) pOS = new RegExp('((' + oses + ')[^ \);]*)').test(ua) ? RegExp.$1 : null; |
---|
38 | |
---|
39 | // Detect browser |
---|
40 | var pName = /(Chrome|MSIE|Safari|Opera|Firefox)/.test(ua) ? RegExp.$1 : null; |
---|
41 | |
---|
42 | // Detect version |
---|
43 | var vre = new RegExp('(Version|' + pName + ')[ \/]([^ ;]*)'); |
---|
44 | var pVersion = (pName && vre.test(ua)) ? RegExp.$2 : null; |
---|
45 | var platform = (pOS && pName && pVersion) ? pName + ' ' + pVersion + ' on ' + pOS : 'unknown platform'; |
---|
46 | |
---|
47 | /* Enhanced version of escape() */ |
---|
48 | var urlEscape = function(s) { |
---|
49 | return escape(s).replace(/\+/g, '%2b'); |
---|
50 | }; |
---|
51 | |
---|
52 | /* Find an element by id */ |
---|
53 | var $ = function(id) {return document.getElementById(id)}; |
---|
54 | |
---|
55 | /* Show a status message */ |
---|
56 | var status = function(msg) { |
---|
57 | var el = $('jsl_status'); |
---|
58 | if (el) el.innerHTML = msg || ''; |
---|
59 | } |
---|
60 | |
---|
61 | /* Convert a number to an abbreviated string like, "15K" or "10M" */ |
---|
62 | var toLabel = function(n) { |
---|
63 | if (n == Infinity) { |
---|
64 | return 'Infinity'; |
---|
65 | } else if (n > 1e9) { |
---|
66 | n = Math.round(n/1e8); |
---|
67 | return n/10 + 'B'; |
---|
68 | } else if (n > 1e6) { |
---|
69 | n = Math.round(n/1e5); |
---|
70 | return n/10 + 'M'; |
---|
71 | } else if (n > 1e3) { |
---|
72 | n = Math.round(n/1e2); |
---|
73 | return n/10 + 'K'; |
---|
74 | } |
---|
75 | return n; |
---|
76 | }; |
---|
77 | |
---|
78 | /* Copy properties from src to dst */ |
---|
79 | var objectExtend = function(dst, src) { |
---|
80 | for (var k in src) dst[k] = src[k]; return dst; |
---|
81 | }; |
---|
82 | |
---|
83 | /* Like Array.join(), but for the key-value pairs in an object */ |
---|
84 | var objectJoin = function(o, delimit1, delimit2) { |
---|
85 | var pairs = []; |
---|
86 | for (var k in o) pairs.push(k + delimit1 + o[k]); |
---|
87 | return pairs.join(delimit2); |
---|
88 | }; |
---|
89 | |
---|
90 | // IE workaround - monkey patch Array.indexOf() if it's not defined |
---|
91 | if (!Array.prototype.indexOf) { |
---|
92 | Array.prototype.indexOf = function(o) { |
---|
93 | for (var i = 0; i < this.length; i++) if (this[i] === o) return i; |
---|
94 | return -1; |
---|
95 | } |
---|
96 | } |
---|
97 | |
---|
98 | /** |
---|
99 | * (private) Test manages a single test (created with |
---|
100 | * JSLitmus.test()) |
---|
101 | */ |
---|
102 | var Test = function (name, f) { |
---|
103 | if (!f || !/function[^\(]*\(([^,\)]*)/.test(f.toString())) { |
---|
104 | throw new Error('"' + name + '" test: Test is not a valid Function object'); |
---|
105 | } |
---|
106 | this.loopArg = RegExp.$1; |
---|
107 | this.name = name; |
---|
108 | this.f = f; |
---|
109 | } |
---|
110 | |
---|
111 | // |
---|
112 | // Test - static members |
---|
113 | // |
---|
114 | objectExtend(Test, { |
---|
115 | // Calibration tests for establishing iteration loop overhead |
---|
116 | CALIBRATIONS: [ |
---|
117 | new Test('empty (looping)', function(count) {while (count--);}), |
---|
118 | new Test('empty (non-looping)', function() {}) |
---|
119 | ], |
---|
120 | |
---|
121 | /* |
---|
122 | * Run calibration tests. Returns true if calibrations are not yet |
---|
123 | * complete (in which calling code should run the tests yet). |
---|
124 | * onCalibrated - Callback to invoke when calibrations have finished |
---|
125 | */ |
---|
126 | calibrate: function(onCalibrated) { |
---|
127 | for (var i = 0; i < Test.CALIBRATIONS.length; i++) { |
---|
128 | var cal = Test.CALIBRATIONS[i]; |
---|
129 | if (cal.running) return true; |
---|
130 | if (!cal.count) { |
---|
131 | cal.isCalibration = true; |
---|
132 | cal.onStop = onCalibrated; |
---|
133 | //cal.MIN_TIME = .1; // Do calibrations quickly |
---|
134 | cal.run(2e4); |
---|
135 | return true; |
---|
136 | } |
---|
137 | } |
---|
138 | return false; |
---|
139 | } |
---|
140 | }); |
---|
141 | |
---|
142 | // Test instance members |
---|
143 | objectExtend(Test.prototype, { |
---|
144 | INIT_COUNT: 10, // Initial number of iterations |
---|
145 | MAX_COUNT: 1e9, // Max iterations allowed (i.e. used to detect bad looping functions) |
---|
146 | MIN_TIME: .5, // Minimum time a test should take to get valid results (secs). |
---|
147 | |
---|
148 | /** Called when the test state changes */ |
---|
149 | onChange: function() {}, |
---|
150 | |
---|
151 | /** Called when the test is finished */ |
---|
152 | onStop: function() {}, |
---|
153 | |
---|
154 | /** |
---|
155 | * Reset test state |
---|
156 | */ |
---|
157 | reset: function() { |
---|
158 | delete this.count; |
---|
159 | delete this.time; |
---|
160 | delete this.running; |
---|
161 | delete this.error; |
---|
162 | }, |
---|
163 | |
---|
164 | /** |
---|
165 | * Public(ish) method for running the test. We call the actual run method, |
---|
166 | * _run(), in a timeout to make sure the browser has a chance to finish |
---|
167 | * rendering any UI changes we've made, like updating the status message. |
---|
168 | */ |
---|
169 | run: function(count) { |
---|
170 | count = count || this.INIT_COUNT |
---|
171 | status('Testing ' + this.name + ' x ' + count); |
---|
172 | this.running = true; |
---|
173 | var me = this; |
---|
174 | setTimeout(function() {me._run(count);}, 200); |
---|
175 | }, |
---|
176 | |
---|
177 | /** |
---|
178 | * Run the test |
---|
179 | */ |
---|
180 | _run: function(count) { |
---|
181 | var me = this; |
---|
182 | |
---|
183 | // Make sure calibration tests have run |
---|
184 | if (!me.isCalibration && Test.calibrate(function() {me.run(count);})) return; |
---|
185 | this.error = null; |
---|
186 | |
---|
187 | try { |
---|
188 | var start, f = this.f, now, i = count; |
---|
189 | |
---|
190 | // Start the timer |
---|
191 | start = new Date(); |
---|
192 | |
---|
193 | // Now for the money shot. If this is a looping function ... |
---|
194 | if (this.loopArg) { |
---|
195 | // ... let it do the iteration itself |
---|
196 | f(count); |
---|
197 | } else { |
---|
198 | // ... otherwise do the iteration for it |
---|
199 | while (i--) f(); |
---|
200 | } |
---|
201 | |
---|
202 | // Get time test took (in secs) |
---|
203 | this.time = Math.max(1,new Date() - start)/1000; |
---|
204 | |
---|
205 | // Store iteration count and per-operation time taken |
---|
206 | this.count = count; |
---|
207 | this.period = this.time/count; |
---|
208 | |
---|
209 | // Do we need to do another run? |
---|
210 | this.running = this.time <= this.MIN_TIME; |
---|
211 | |
---|
212 | // ... if so, compute how many times we should iterate |
---|
213 | if (this.running) { |
---|
214 | // Bump the count to the nearest power of 2 |
---|
215 | var x = this.MIN_TIME/this.time; |
---|
216 | var pow = Math.pow(2, Math.max(1, Math.ceil(Math.log(x)/Math.log(2)))); |
---|
217 | count *= pow; |
---|
218 | if (count > this.MAX_COUNT) { |
---|
219 | throw new Error('Max count exceeded. If this test uses a looping function, make sure the iteration loop is working properly.') |
---|
220 | } |
---|
221 | } |
---|
222 | } catch (e) { |
---|
223 | // Exceptions are caught and displayed in the test UI |
---|
224 | this.reset(); |
---|
225 | this.error = e; |
---|
226 | } |
---|
227 | |
---|
228 | // Figure out what to do next |
---|
229 | if (this.running) { |
---|
230 | me.run(count); |
---|
231 | } else { |
---|
232 | status(''); |
---|
233 | me.onStop(me); |
---|
234 | } |
---|
235 | |
---|
236 | // Finish up |
---|
237 | this.onChange(this); |
---|
238 | }, |
---|
239 | |
---|
240 | /** |
---|
241 | * Get the number of operations per second for this test. |
---|
242 | * normalize - if true, iteration loop overhead taken into account |
---|
243 | */ |
---|
244 | getHz: function(normalize) { |
---|
245 | var p = this.period; |
---|
246 | |
---|
247 | // Adjust period based on the calibration test time |
---|
248 | if (normalize && !this.isCalibration) { |
---|
249 | var cal = Test.CALIBRATIONS[this.loopArg ? 0 : 1]; |
---|
250 | |
---|
251 | // If the period is within 20% of the calibration time, then zero the |
---|
252 | // it out |
---|
253 | p = p < cal.period*1.2 ? 0 : p - cal.period; |
---|
254 | } |
---|
255 | |
---|
256 | return Math.round(1/p); |
---|
257 | }, |
---|
258 | |
---|
259 | toString: function() { |
---|
260 | return this.name + ' - ' + this.time/this.count + ' secs'; |
---|
261 | } |
---|
262 | }); |
---|
263 | |
---|
264 | // CSS we need for the UI |
---|
265 | var STYLESHEET = '<style> \ |
---|
266 | #jslitmus {font-family:sans-serif; font-size: 12px;} \ |
---|
267 | #jslitmus a {text-decoration: none;} \ |
---|
268 | #jslitmus a:hover {text-decoration: underline;} \ |
---|
269 | #jsl_status { \ |
---|
270 | margin-top: 10px; \ |
---|
271 | font-size: 10px; \ |
---|
272 | color: #888; \ |
---|
273 | } \ |
---|
274 | A IMG {border:none} \ |
---|
275 | #test_results { \ |
---|
276 | margin-top: 10px; \ |
---|
277 | font-size: 12px; \ |
---|
278 | font-family: sans-serif; \ |
---|
279 | border-collapse: collapse; \ |
---|
280 | border-spacing: 0px; \ |
---|
281 | } \ |
---|
282 | #test_results th, #test_results td { \ |
---|
283 | border: solid 1px #ccc; \ |
---|
284 | vertical-align: top; \ |
---|
285 | padding: 3px; \ |
---|
286 | } \ |
---|
287 | #test_results th { \ |
---|
288 | vertical-align: bottom; \ |
---|
289 | background-color: #ccc; \ |
---|
290 | padding: 1px; \ |
---|
291 | font-size: 10px; \ |
---|
292 | } \ |
---|
293 | #test_results #test_platform { \ |
---|
294 | color: #444; \ |
---|
295 | text-align:center; \ |
---|
296 | } \ |
---|
297 | #test_results .test_row { \ |
---|
298 | color: #006; \ |
---|
299 | cursor: pointer; \ |
---|
300 | } \ |
---|
301 | #test_results .test_nonlooping { \ |
---|
302 | border-left-style: dotted; \ |
---|
303 | border-left-width: 2px; \ |
---|
304 | } \ |
---|
305 | #test_results .test_looping { \ |
---|
306 | border-left-style: solid; \ |
---|
307 | border-left-width: 2px; \ |
---|
308 | } \ |
---|
309 | #test_results .test_name {white-space: nowrap;} \ |
---|
310 | #test_results .test_pending { \ |
---|
311 | } \ |
---|
312 | #test_results .test_running { \ |
---|
313 | font-style: italic; \ |
---|
314 | } \ |
---|
315 | #test_results .test_done {} \ |
---|
316 | #test_results .test_done { \ |
---|
317 | text-align: right; \ |
---|
318 | font-family: monospace; \ |
---|
319 | } \ |
---|
320 | #test_results .test_error {color: #600;} \ |
---|
321 | #test_results .test_error .error_head {font-weight:bold;} \ |
---|
322 | #test_results .test_error .error_body {font-size:85%;} \ |
---|
323 | #test_results .test_row:hover td { \ |
---|
324 | background-color: #ffc; \ |
---|
325 | text-decoration: underline; \ |
---|
326 | } \ |
---|
327 | #chart { \ |
---|
328 | margin-top: 10px; \ |
---|
329 | } \ |
---|
330 | #chart img { \ |
---|
331 | border: solid 1px #ccc; \ |
---|
332 | margin-bottom: 5px; \ |
---|
333 | } \ |
---|
334 | #chart #tiny_url { \ |
---|
335 | float: left; \ |
---|
336 | font-size: 10px; \ |
---|
337 | } \ |
---|
338 | #jslitmus_credit { \ |
---|
339 | font-size: 10px; \ |
---|
340 | color: #888; \ |
---|
341 | display:block; \ |
---|
342 | text-align:center; \ |
---|
343 | float: right; \ |
---|
344 | } \ |
---|
345 | </style>'; |
---|
346 | |
---|
347 | // HTML markup for the UI |
---|
348 | var MARKUP = '<div id="jslitmus"> \ |
---|
349 | <button onclick="JSLitmus.runAll()">Run Tests</button> \ |
---|
350 | <button id="stop_button" disabled="disabled" onclick="JSLitmus.stop()">Stop Tests</button> \ |
---|
351 | <br \> \ |
---|
352 | <br \> \ |
---|
353 | <input type="checkbox" style="vertical-align: middle" id="test_normalize" checked="checked" onchange="JSLitmus.renderAll()""> Normalize results \ |
---|
354 | <table id="test_results"> \ |
---|
355 | <colgroup> \ |
---|
356 | <col /> \ |
---|
357 | <col width="100" /> \ |
---|
358 | </colgroup> \ |
---|
359 | <tr><th id="test_platform" colspan="2">' + platform + '</th></tr> \ |
---|
360 | <tr><th>Test</th><th>Ops/sec</th></tr> \ |
---|
361 | <tr id="test_row_template" class="test_row" style="display:none"> \ |
---|
362 | <td class="test_name"></td> \ |
---|
363 | <td class="test_result">Ready</td> \ |
---|
364 | </tr> \ |
---|
365 | </table> \ |
---|
366 | <div id="jsl_status"></div> \ |
---|
367 | <div id="chart" style="display:none"> \ |
---|
368 | <a id="chart_link" target="_blank"><img id="chart_image"></a> \ |
---|
369 | <a id="jslitmus_credit" title="Check out the JSLitmus home page" href="http://broofa.com/Tools/JSLitmus" target="_blank">powered by JSLitmus</a> \ |
---|
370 | <a id="tiny_url" href="" title="Get a compact url for this chart" target="_blank">chart\'s tinyurl</a> \ |
---|
371 | </div> \ |
---|
372 | </div>'; |
---|
373 | |
---|
374 | /** |
---|
375 | * JSLitmus API |
---|
376 | */ |
---|
377 | window.JSLitmus = { |
---|
378 | _tests: [], |
---|
379 | _queue: [], |
---|
380 | |
---|
381 | params: {}, |
---|
382 | |
---|
383 | /** |
---|
384 | * Initialize |
---|
385 | */ |
---|
386 | _init: function() { |
---|
387 | // Parse query params into JSLitmus.params[] hash |
---|
388 | var match = (location + '').match(/([^?#]*)(#.*)?$/); |
---|
389 | if (match) { |
---|
390 | var pairs = match[1].split('&'); |
---|
391 | for (var i = 0; i < pairs.length; i++) { |
---|
392 | var pair = pairs[i].split('='); |
---|
393 | if (pair.length > 1) { |
---|
394 | var key = pair.shift(); |
---|
395 | var value = pair.length > 1 ? pair.join('=') : pair[0]; |
---|
396 | this.params[key] = value; |
---|
397 | } |
---|
398 | } |
---|
399 | } |
---|
400 | |
---|
401 | // Write out the stylesheet. We have to do this here because IE |
---|
402 | // doesn't honor sheets written after the document has loaded. |
---|
403 | document.write(STYLESHEET); |
---|
404 | |
---|
405 | // Setup the rest of the UI once the document is loaded |
---|
406 | if (window.addEventListener) { |
---|
407 | window.addEventListener('load', this._setup, false); |
---|
408 | } else if (document.addEventListener) { |
---|
409 | document.addEventListener('load', this._setup, false); |
---|
410 | } else if (window.attachEvent) { |
---|
411 | window.attachEvent('onload', this._setup); |
---|
412 | } |
---|
413 | |
---|
414 | |
---|
415 | return this; |
---|
416 | }, |
---|
417 | |
---|
418 | /** |
---|
419 | * Set up the UI |
---|
420 | */ |
---|
421 | _setup: function() { |
---|
422 | var el = $('jslitmus_container'); |
---|
423 | if (!el) document.body.appendChild(el = document.createElement('div')); |
---|
424 | |
---|
425 | el.innerHTML = MARKUP; |
---|
426 | |
---|
427 | // Render the UI for all our tests |
---|
428 | for (var i=0; i < JSLitmus._tests.length; i++) |
---|
429 | JSLitmus.renderTest(JSLitmus._tests[i]); |
---|
430 | }, |
---|
431 | |
---|
432 | /** |
---|
433 | * (Re)render all the test results |
---|
434 | */ |
---|
435 | renderAll: function() { |
---|
436 | for (var i = 0; i < JSLitmus._tests.length; i++) |
---|
437 | JSLitmus.renderTest(JSLitmus._tests[i]); |
---|
438 | JSLitmus.renderChart(); |
---|
439 | }, |
---|
440 | |
---|
441 | /** |
---|
442 | * (Re)render the chart graphics |
---|
443 | */ |
---|
444 | renderChart: function() { |
---|
445 | var url = JSLitmus.chartUrl(); |
---|
446 | $('chart_link').href = url; |
---|
447 | $('chart_image').src = url; |
---|
448 | $('tiny_url').href = 'http://tinyurl.com/create.php?url='+encodeURIComponent(url) |
---|
449 | $('chart').style.display = ''; |
---|
450 | }, |
---|
451 | |
---|
452 | /** |
---|
453 | * (Re)redner the results for a specific test |
---|
454 | */ |
---|
455 | renderTest: function(test) { |
---|
456 | // Make a new row if needed |
---|
457 | if (!test._row) { |
---|
458 | var trow = $('test_row_template'); |
---|
459 | if (!trow) return; |
---|
460 | |
---|
461 | test._row = trow.cloneNode(true); |
---|
462 | test._row.style.display = ''; |
---|
463 | test._row.id = ''; |
---|
464 | test._row.onclick = function() {JSLitmus._queueTest(test);}; |
---|
465 | test._row.title = 'Run ' + test.name + ' test'; |
---|
466 | trow.parentNode.appendChild(test._row); |
---|
467 | test._row.cells[0].innerHTML = test.name; |
---|
468 | } |
---|
469 | |
---|
470 | var cell = test._row.cells[1]; |
---|
471 | var cns = [test.loopArg ? 'test_looping' : 'test_nonlooping']; |
---|
472 | |
---|
473 | if (test.error) { |
---|
474 | cns.push('test_error'); |
---|
475 | cell.innerHTML = |
---|
476 | '<div class="error_head">' + test.error + '</div>' + |
---|
477 | '<ul class="error_body"><li>' + |
---|
478 | objectJoin(test.error, ': ', '</li><li>') + |
---|
479 | '</li></ul>'; |
---|
480 | } else { |
---|
481 | if (test.running) { |
---|
482 | cns.push('test_running'); |
---|
483 | cell.innerHTML = 'running'; |
---|
484 | } else if (JSLitmus._queue.indexOf(test) >= 0) { |
---|
485 | cns.push('test_pending'); |
---|
486 | cell.innerHTML = 'pending'; |
---|
487 | } else if (test.count) { |
---|
488 | cns.push('test_done'); |
---|
489 | var hz = test.getHz($('test_normalize').checked); |
---|
490 | cell.innerHTML = hz != Infinity ? hz : '∞'; |
---|
491 | } else { |
---|
492 | cell.innerHTML = 'ready'; |
---|
493 | } |
---|
494 | } |
---|
495 | cell.className = cns.join(' '); |
---|
496 | }, |
---|
497 | |
---|
498 | /** |
---|
499 | * Create a new test |
---|
500 | */ |
---|
501 | test: function(name, f) { |
---|
502 | // Create the Test object |
---|
503 | var test = new Test(name, f); |
---|
504 | JSLitmus._tests.push(test); |
---|
505 | |
---|
506 | // Re-render if the test state changes |
---|
507 | test.onChange = JSLitmus.renderTest; |
---|
508 | |
---|
509 | // Run the next test if this one finished |
---|
510 | test.onStop = function(test) { |
---|
511 | if (JSLitmus.onTestFinish) JSLitmus.onTestFinish(test); |
---|
512 | JSLitmus.currentTest = null; |
---|
513 | JSLitmus._nextTest(); |
---|
514 | } |
---|
515 | |
---|
516 | // Render the new test |
---|
517 | this.renderTest(test); |
---|
518 | }, |
---|
519 | |
---|
520 | /** |
---|
521 | * Add all tests to the run queue |
---|
522 | */ |
---|
523 | runAll: function() { |
---|
524 | for (var i =0; i < JSLitmus._tests.length; i++) |
---|
525 | JSLitmus._queueTest(JSLitmus._tests[i]); |
---|
526 | }, |
---|
527 | |
---|
528 | /** |
---|
529 | * Remove all tests from the run queue. The current test has to finish on |
---|
530 | * it's own though |
---|
531 | */ |
---|
532 | stop: function() { |
---|
533 | while (JSLitmus._queue.length) { |
---|
534 | var test = JSLitmus._queue.shift(); |
---|
535 | JSLitmus.renderTest(test); |
---|
536 | } |
---|
537 | }, |
---|
538 | |
---|
539 | /** |
---|
540 | * Run the next test in the run queue |
---|
541 | */ |
---|
542 | _nextTest: function() { |
---|
543 | if (!JSLitmus.currentTest) { |
---|
544 | var test = JSLitmus._queue.shift(); |
---|
545 | if (test) { |
---|
546 | $('stop_button').disabled = false; |
---|
547 | JSLitmus.currentTest = test; |
---|
548 | test.run(); |
---|
549 | JSLitmus.renderTest(test); |
---|
550 | if (JSLitmus.onTestStart) JSLitmus.onTestStart(test); |
---|
551 | } else { |
---|
552 | $('stop_button').disabled = true; |
---|
553 | JSLitmus.renderChart(); |
---|
554 | } |
---|
555 | } |
---|
556 | }, |
---|
557 | |
---|
558 | /** |
---|
559 | * Add a test to the run queue |
---|
560 | */ |
---|
561 | _queueTest: function(test) { |
---|
562 | if (JSLitmus._queue.indexOf(test) >= 0) return; |
---|
563 | JSLitmus._queue.push(test); |
---|
564 | JSLitmus.renderTest(test); |
---|
565 | JSLitmus._nextTest(); |
---|
566 | }, |
---|
567 | |
---|
568 | /** |
---|
569 | * Generate a Google Chart URL that shows the data for all tests |
---|
570 | */ |
---|
571 | chartUrl: function() { |
---|
572 | var n = JSLitmus._tests.length, markers = [], data = []; |
---|
573 | var d, min = 0, max = -1e10; |
---|
574 | var normalize = $('test_normalize').checked; |
---|
575 | |
---|
576 | // Gather test data |
---|
577 | for (var i=0; i < JSLitmus._tests.length; i++) { |
---|
578 | var test = JSLitmus._tests[i]; |
---|
579 | if (test.count) { |
---|
580 | var hz = test.getHz(normalize); |
---|
581 | var v = hz != Infinity ? hz : 0; |
---|
582 | data.push(v); |
---|
583 | markers.push('t' + urlEscape(test.name + '(' + toLabel(hz)+ ')') + ',000000,0,' + |
---|
584 | markers.length + ',10'); |
---|
585 | max = Math.max(v, max); |
---|
586 | } |
---|
587 | } |
---|
588 | if (markers.length <= 0) return null; |
---|
589 | |
---|
590 | // Build chart title |
---|
591 | var title = document.getElementsByTagName('title'); |
---|
592 | title = (title && title.length) ? title[0].innerHTML : null; |
---|
593 | var chart_title = []; |
---|
594 | if (title) chart_title.push(title); |
---|
595 | chart_title.push('Ops/sec (' + platform + ')'); |
---|
596 | |
---|
597 | // Build labels |
---|
598 | var labels = [toLabel(min), toLabel(max)]; |
---|
599 | |
---|
600 | var w = 250, bw = 15; |
---|
601 | var bs = 5; |
---|
602 | var h = markers.length*(bw + bs) + 30 + chart_title.length*20; |
---|
603 | |
---|
604 | var params = { |
---|
605 | chtt: escape(chart_title.join('|')), |
---|
606 | chts: '000000,10', |
---|
607 | cht: 'bhg', // chart type |
---|
608 | chd: 't:' + data.join(','), // data set |
---|
609 | chds: min + ',' + max, // max/min of data |
---|
610 | chxt: 'x', // label axes |
---|
611 | chxl: '0:|' + labels.join('|'), // labels |
---|
612 | chsp: '0,1', |
---|
613 | chm: markers.join('|'), // test names |
---|
614 | chbh: [bw, 0, bs].join(','), // bar widths |
---|
615 | // chf: 'bg,lg,0,eeeeee,0,eeeeee,.5,ffffff,1', // gradient |
---|
616 | chs: w + 'x' + h |
---|
617 | } |
---|
618 | return 'http://chart.apis.google.com/chart?' + objectJoin(params, '=', '&'); |
---|
619 | } |
---|
620 | }; |
---|
621 | |
---|
622 | JSLitmus._init(); |
---|
623 | })(); |
---|