diff options
Diffstat (limited to 'js/src/devtools/gc-ubench')
19 files changed, 1124 insertions, 0 deletions
diff --git a/js/src/devtools/gc-ubench/benchmarks/bigTextNodes.js b/js/src/devtools/gc-ubench/benchmarks/bigTextNodes.js new file mode 100644 index 000000000..1a6119f56 --- /dev/null +++ b/js/src/devtools/gc-ubench/benchmarks/bigTextNodes.js @@ -0,0 +1,25 @@ +window.tests.set('bigTextNodes', (function() { +var garbage = []; +var garbageIndex = 0; +return { + description: "var foo = [ textNode, textNode, ... ]", + + load: (N) => { garbage = new Array(N); }, + unload: () => { garbage = []; garbageIndex = 0; }, + + defaultGarbagePerFrame: "8", + defaultGarbageTotal: "8", + + makeGarbage: (N) => { + var a = []; + var s = "x"; + for (var i = 0; i < 16; i++) + s = s + s; + for (var i = 0; i < N; i++) + a.push(document.createTextNode(s)); + garbage[garbageIndex++] = a; + if (garbageIndex == garbage.length) + garbageIndex = 0; + } +}; +})()); diff --git a/js/src/devtools/gc-ubench/benchmarks/events.js b/js/src/devtools/gc-ubench/benchmarks/events.js new file mode 100644 index 000000000..37f91f754 --- /dev/null +++ b/js/src/devtools/gc-ubench/benchmarks/events.js @@ -0,0 +1,25 @@ +window.tests.set('events', (function() { +var garbage = []; +var garbageIndex = 0; +return { + description: "var foo = [ textNode, textNode, ... ]", + + load: (N) => { garbage = new Array(N); }, + unload: () => { garbage = []; garbageIndex = 0; }, + + defaultGarbagePerFrame: "100K", + defaultGarbageTotal: "8", + + makeGarbage: (N) => { + var a = []; + for (var i = 0; i < N; i++) { + var e = document.createEvent("Events"); + e.initEvent("TestEvent", true, true); + a.push(e); + } + garbage[garbageIndex++] = a; + if (garbageIndex == garbage.length) + garbageIndex = 0; + } +}; +})()); diff --git a/js/src/devtools/gc-ubench/benchmarks/expandoEvents.js b/js/src/devtools/gc-ubench/benchmarks/expandoEvents.js new file mode 100644 index 000000000..70ff780c4 --- /dev/null +++ b/js/src/devtools/gc-ubench/benchmarks/expandoEvents.js @@ -0,0 +1,26 @@ +window.tests.set('expandoEvents', (function() { +var garbage = []; +var garbageIndex = 0; +return { + description: "var foo = [ textNode, textNode, ... ]", + + load: (N) => { garbage = new Array(N); }, + unload: () => { garbage = []; garbageIndex = 0; }, + + defaultGarbagePerFrame: "100K", + defaultGarbageTotal: "8", + + makeGarbage: (N) => { + var a = []; + for (var i = 0; i < N; i++) { + var e = document.createEvent("Events"); + e.initEvent("TestEvent", true, true); + e.color = ["tuna"]; + a.push(e); + } + garbage[garbageIndex++] = a; + if (garbageIndex == garbage.length) + garbageIndex = 0; + } +}; +})()); diff --git a/js/src/devtools/gc-ubench/benchmarks/globalArrayArrayLiteral.js b/js/src/devtools/gc-ubench/benchmarks/globalArrayArrayLiteral.js new file mode 100644 index 000000000..9982090d8 --- /dev/null +++ b/js/src/devtools/gc-ubench/benchmarks/globalArrayArrayLiteral.js @@ -0,0 +1,16 @@ +window.tests.set('globalArrayArrayLiteral', (function() { +var garbage = []; +var garbageIndex = 0; +return { + description: "var foo = [[], ....]", + load: (N) => { garbage = new Array(N); }, + unload: () => { garbage = []; garbageIndex = 0; }, + makeGarbage: (N) => { + for (var i = 0; i < N; i++) { + garbage[garbageIndex++] = ['foo', 'bar', 'baz', 'baa']; + if (garbageIndex == garbage.length) + garbageIndex = 0; + } + } +}; +})()); diff --git a/js/src/devtools/gc-ubench/benchmarks/globalArrayBuffer.js b/js/src/devtools/gc-ubench/benchmarks/globalArrayBuffer.js new file mode 100644 index 000000000..4aee22ee7 --- /dev/null +++ b/js/src/devtools/gc-ubench/benchmarks/globalArrayBuffer.js @@ -0,0 +1,23 @@ +window.tests.set('globalArrayBuffer', (function() { +var garbage = []; +var garbageIndex = 0; +return { + description: "var foo = ArrayBuffer(N); # (large malloc data)", + + load: (N) => { garbage = new Array(N); }, + unload: () => { garbage = []; garbageIndex = 0; }, + + defaultGarbageTotal: "8K", + defaultGarbagePerFrame: "4M", + + makeGarbage: (N) => { + var ab = new ArrayBuffer(N); + var view = new Uint8Array(ab); + view[0] = 1; + view[N - 1] = 2; + garbage[garbageIndex++] = ab; + if (garbageIndex == garbage.length) + garbageIndex = 0; + } +}; +})()); diff --git a/js/src/devtools/gc-ubench/benchmarks/globalArrayFgFinalized.js b/js/src/devtools/gc-ubench/benchmarks/globalArrayFgFinalized.js new file mode 100644 index 000000000..f2f914376 --- /dev/null +++ b/js/src/devtools/gc-ubench/benchmarks/globalArrayFgFinalized.js @@ -0,0 +1,23 @@ +window.tests.set('globalArrayFgFinalized', (function() { +var garbage = []; +var garbageIndex = 0; +return { + description: "var foo = [ new Map, new Map, ... ]; # (foreground finalized)", + + load: (N) => { garbage = new Array(N); }, + unload: () => { garbage = []; garbageIndex = 0; }, + + defaultGarbageTotal: "8K", + defaultGarbagePerFrame: "1M", + + makeGarbage: (N) => { + var arr = []; + for (var i = 0; i < N; i++) { + arr.push(new Map); + } + garbage[garbageIndex++] = arr; + if (garbageIndex == garbage.length) + garbageIndex = 0; + } +}; +})()); diff --git a/js/src/devtools/gc-ubench/benchmarks/globalArrayLargeArray.js b/js/src/devtools/gc-ubench/benchmarks/globalArrayLargeArray.js new file mode 100644 index 000000000..2c0ff3564 --- /dev/null +++ b/js/src/devtools/gc-ubench/benchmarks/globalArrayLargeArray.js @@ -0,0 +1,18 @@ +window.tests.set('globalArrayLargeArray', (function() { +var garbage = []; +var garbageIndex = 0; +return { + description: "var foo = [[...], ....]", + load: (N) => { garbage = new Array(N); }, + unload: () => { garbage = []; garbageIndex = 0; }, + makeGarbage: (N) => { + var a = new Array(N); + for (var i = 0; i < N; i++) { + a[i] = N - i; + } + garbage[garbageIndex++] = a; + if (garbageIndex == garbage.length) + garbageIndex = 0; + } +}; +})()); diff --git a/js/src/devtools/gc-ubench/benchmarks/globalArrayLargeObject.js b/js/src/devtools/gc-ubench/benchmarks/globalArrayLargeObject.js new file mode 100644 index 000000000..d676c255e --- /dev/null +++ b/js/src/devtools/gc-ubench/benchmarks/globalArrayLargeObject.js @@ -0,0 +1,23 @@ +window.tests.set('globalArrayLargeObject', (function() { +var garbage = []; +var garbageIndex = 0; +return { + description: "var foo = { LARGE }; # (large slots)", + + load: (N) => { garbage = new Array(N); }, + unload: () => { garbage = []; garbageIndex = 0; }, + + defaultGarbageTotal: "8K", + defaultGarbagePerFrame: "200K", + + makeGarbage: (N) => { + var obj = {}; + for (var i = 0; i < N; i++) { + obj["key" + i] = i; + } + garbage[garbageIndex++] = obj; + if (garbageIndex == garbage.length) + garbageIndex = 0; + } +}; +})()); diff --git a/js/src/devtools/gc-ubench/benchmarks/globalArrayNewObject.js b/js/src/devtools/gc-ubench/benchmarks/globalArrayNewObject.js new file mode 100644 index 000000000..2ab04dff0 --- /dev/null +++ b/js/src/devtools/gc-ubench/benchmarks/globalArrayNewObject.js @@ -0,0 +1,16 @@ +window.tests.set('globalArrayNewObject', (function() { +var garbage = []; +var garbageIndex = 0; +return { + description: "var foo = [new Object(), ....]", + load: (N) => { garbage = new Array(N); }, + unload: () => { garbage = []; garbageIndex = 0; }, + makeGarbage: (N) => { + for (var i = 0; i < N; i++) { + garbage[garbageIndex++] = new Object(); + if (garbageIndex == garbage.length) + garbageIndex = 0; + } + } +}; +})()); diff --git a/js/src/devtools/gc-ubench/benchmarks/globalArrayObjectLiteral.js b/js/src/devtools/gc-ubench/benchmarks/globalArrayObjectLiteral.js new file mode 100644 index 000000000..7a8c1b2d6 --- /dev/null +++ b/js/src/devtools/gc-ubench/benchmarks/globalArrayObjectLiteral.js @@ -0,0 +1,16 @@ +window.tests.set('globalArrayObjectLiteral', (function() { +var garbage = []; +var garbageIndex = 0; +return { + description: "var foo = [{}, ....]", + load: (N) => { garbage = new Array(N); }, + unload: () => { garbage = []; garbageIndex = 0; }, + makeGarbage: (N) => { + for (var i = 0; i < N; i++) { + garbage[garbageIndex++] = {a: 'foo', b: 'bar', 0: 'foo', 1: 'bar'}; + if (garbageIndex == garbage.length) + garbageIndex = 0; + } + } +}; +})()); diff --git a/js/src/devtools/gc-ubench/benchmarks/globalArrayReallocArray.js b/js/src/devtools/gc-ubench/benchmarks/globalArrayReallocArray.js new file mode 100644 index 000000000..6a7b16011 --- /dev/null +++ b/js/src/devtools/gc-ubench/benchmarks/globalArrayReallocArray.js @@ -0,0 +1,18 @@ +window.tests.set('globalArrayReallocArray', (function() { +var garbage = []; +var garbageIndex = 0; +return { + description: "var foo = [[,,,], ....]", + load: (N) => { garbage = new Array(N); }, + unload: () => { garbage = []; garbageIndex = 0; }, + makeGarbage: (N) => { + var a = []; + for (var i = 0; i < N; i++) { + a[i] = N - i; + } + garbage[garbageIndex++] = a; + if (garbageIndex == garbage.length) + garbageIndex = 0; + } +}; +})()); diff --git a/js/src/devtools/gc-ubench/benchmarks/largeArrayPropertyAndElements.js b/js/src/devtools/gc-ubench/benchmarks/largeArrayPropertyAndElements.js new file mode 100644 index 000000000..36ae04971 --- /dev/null +++ b/js/src/devtools/gc-ubench/benchmarks/largeArrayPropertyAndElements.js @@ -0,0 +1,34 @@ +window.tests.set('largeArrayPropertyAndElements', (function() { + var garbage; + var index; + + return { + description: "Large array with both properties and elements", + + load: n => { + garbage = new Array(n); + garbage.fill(null); + index = 0; + }, + + unload: () => { + garbage = null; + index = 0; + }, + + defaultGarbageTotal: "100K", + defaultGarbagePerFrame: "30K", + + makeGarbage: n => { + for (var i = 0; i < n; i++) { + index++; + index %= garbage.length; + + var obj = {}; + garbage[index] = obj; + garbage["key-" + index] = obj; + } + } + }; + +}())); diff --git a/js/src/devtools/gc-ubench/benchmarks/noAllocation.js b/js/src/devtools/gc-ubench/benchmarks/noAllocation.js new file mode 100644 index 000000000..5e01c309b --- /dev/null +++ b/js/src/devtools/gc-ubench/benchmarks/noAllocation.js @@ -0,0 +1,6 @@ +window.tests.set('noAllocation', { + description: "Do not generate any garbage.", + load: (N) => {}, + unload: () => {}, + makeGarbage: (N) => {} +}); diff --git a/js/src/devtools/gc-ubench/benchmarks/pairCyclicWeakMap.js b/js/src/devtools/gc-ubench/benchmarks/pairCyclicWeakMap.js new file mode 100644 index 000000000..4dcb4d57e --- /dev/null +++ b/js/src/devtools/gc-ubench/benchmarks/pairCyclicWeakMap.js @@ -0,0 +1,33 @@ +window.tests.set('pairCyclicWeakMap', (function() { +var garbage = []; +var garbageIndex = 0; +return { + description: "wm1[k1] = k2; wm2[k2] = k3; wm1[k3] = k4; wm2[k4] = ...", + + defaultGarbagePerFrame: "1K", + defaultGarbageTotal: "1K", + + load: (N) => { garbage = new Array(N); }, + + unload: () => { garbage = []; garbageIndex = 0; }, + + makeGarbage: (M) => { + var wm1 = new WeakMap(); + var wm2 = new WeakMap(); + var initialKey = {}; + var key = initialKey; + var value = {}; + for (var i = 0; i < M/2; i++) { + wm1.set(key, value); + key = value; + value = {}; + wm2.set(key, value); + key = value; + value = {}; + } + garbage[garbageIndex++] = [ initialKey, wm1, wm2 ]; + if (garbageIndex == garbage.length) + garbageIndex = 0; + } +}; +})()); diff --git a/js/src/devtools/gc-ubench/benchmarks/propertyTreeSplitting.js b/js/src/devtools/gc-ubench/benchmarks/propertyTreeSplitting.js new file mode 100644 index 000000000..47001030f --- /dev/null +++ b/js/src/devtools/gc-ubench/benchmarks/propertyTreeSplitting.js @@ -0,0 +1,25 @@ +window.tests.set('propertyTreeSplitting', (function() { +var garbage = []; +var garbageIndex = 0; +return { + description: "use delete to generate Shape garbage", + load: (N) => { garbage = new Array(N); }, + unload: () => { garbage = []; garbageIndex = 0; }, + makeGarbage: (N) => { + function f() + { + var a1 = eval; + delete eval; + eval = a1; + var a3 = toString; + delete toString; + toString = a3; + } + for (var a = 0; a < N; ++a) { + garbage[garbageIndex++] = new f(); + if (garbageIndex == garbage.length) + garbageIndex = 0; + } + } +}; +})()); diff --git a/js/src/devtools/gc-ubench/benchmarks/selfCyclicWeakMap.js b/js/src/devtools/gc-ubench/benchmarks/selfCyclicWeakMap.js new file mode 100644 index 000000000..0a5ffb084 --- /dev/null +++ b/js/src/devtools/gc-ubench/benchmarks/selfCyclicWeakMap.js @@ -0,0 +1,29 @@ +window.tests.set('selfCyclicWeakMap', (function() { +var garbage = []; +var garbageIndex = 0; +return { + description: "var wm = new WeakMap(); wm[k1] = k2; wm[k2] = k3; ...", + + defaultGarbagePerFrame: "1K", + defaultGarbageTotal: "1K", + + load: (N) => { garbage = new Array(N); }, + + unload: () => { garbage = []; garbageIndex = 0; }, + + makeGarbage: (M) => { + var wm = new WeakMap(); + var initialKey = {}; + var key = initialKey; + var value = {}; + for (var i = 0; i < M; i++) { + wm.set(key, value); + key = value; + value = {}; + } + garbage[garbageIndex++] = [ initialKey, wm ]; + if (garbageIndex == garbage.length) + garbageIndex = 0; + } +}; +})()); diff --git a/js/src/devtools/gc-ubench/benchmarks/textNodes.js b/js/src/devtools/gc-ubench/benchmarks/textNodes.js new file mode 100644 index 000000000..257e5f726 --- /dev/null +++ b/js/src/devtools/gc-ubench/benchmarks/textNodes.js @@ -0,0 +1,23 @@ +window.tests.set('textNodes', (function() { +var garbage = []; +var garbageIndex = 0; +return { + description: "var foo = [ textNode, textNode, ... ]", + + load: (N) => { garbage = new Array(N); }, + unload: () => { garbage = []; garbageIndex = 0; }, + + defaultGarbagePerFrame: "100K", + defaultGarbageTotal: "8", + + makeGarbage: (N) => { + var a = []; + for (var i = 0; i < N; i++) { + a.push(document.createTextNode("t" + i)); + } + garbage[garbageIndex++] = a; + if (garbageIndex == garbage.length) + garbageIndex = 0; + } +}; +})()); diff --git a/js/src/devtools/gc-ubench/harness.js b/js/src/devtools/gc-ubench/harness.js new file mode 100644 index 000000000..f217ff1cb --- /dev/null +++ b/js/src/devtools/gc-ubench/harness.js @@ -0,0 +1,661 @@ +// Per-frame time sampling infra. Also GC'd: hopefully will not perturb things too badly. +var numSamples = 500; +var delays = new Array(numSamples); +var gcs = new Array(numSamples); +var minorGCs = new Array(numSamples); +var gcBytes = new Array(numSamples); +var mallocBytes = new Array(numSamples); +var sampleIndex = 0; +var sampleTime = 16; // ms +var gHistogram = new Map(); // {ms: count} + +var features = { + trackingSizes: ('mozMemory' in performance), + showingGCs: ('mozMemory' in performance), +}; + +// Draw state. +var stopped = 0; +var start; +var prev; +var latencyGraph; +var memoryGraph; +var ctx; +var memoryCtx; + +// Current test state. +var activeTest = undefined; +var testDuration = undefined; // ms +var testState = 'idle'; // One of 'idle' or 'running'. +var testStart = undefined; // ms +var testQueue = []; + +// Global defaults +var globalDefaultGarbageTotal = "8M"; +var globalDefaultGarbagePerFrame = "8K"; + +function Graph(ctx) { + this.ctx = ctx; + + var { width, height } = ctx.canvas; + this.layout = { + xAxisLabel_Y: height - 20, + }; +} + +Graph.prototype.xpos = index => index * 2; + +Graph.prototype.clear = function () { + var { width, height } = this.ctx.canvas; + this.ctx.clearRect(0, 0, width, height); +}; + +Graph.prototype.drawScale = function (delay) +{ + this.drawHBar(delay, `${delay}ms`, 'rgb(150,150,150)'); +} + +Graph.prototype.draw60fps = function () { + this.drawHBar(1000/60, '60fps', '#00cf61', 25); +} + +Graph.prototype.draw30fps = function () { + this.drawHBar(1000/30, '30fps', '#cf0061', 25); +} + +Graph.prototype.drawAxisLabels = function (x_label, y_label) +{ + var ctx = this.ctx; + var { width, height } = ctx.canvas; + + ctx.fillText(x_label, width / 2, this.layout.xAxisLabel_Y); + + ctx.save(); + ctx.rotate(Math.PI/2); + var start = height / 2 - ctx.measureText(y_label).width / 2; + ctx.fillText(y_label, start, -width+20); + ctx.restore(); +} + +Graph.prototype.drawFrame = function () { + var ctx = this.ctx; + var { width, height } = ctx.canvas; + + // Draw frame to show size + ctx.strokeStyle = 'rgb(0,0,0)'; + ctx.fillStyle = 'rgb(0,0,0)'; + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(width, 0); + ctx.lineTo(width, height); + ctx.lineTo(0, height); + ctx.closePath(); + ctx.stroke(); +} + +function LatencyGraph(ctx) { + Graph.call(this, ctx); + console.log(this.ctx); +} + +LatencyGraph.prototype = Object.create(Graph.prototype); + +Object.defineProperty(LatencyGraph.prototype, 'constructor', { + enumerable: false, + value: LatencyGraph }); + +LatencyGraph.prototype.ypos = function (delay) { + var { height } = this.ctx.canvas; + + var r = height + 100 - Math.log(delay) * 64; + if (r < 5) return 5; + return r; +} + +LatencyGraph.prototype.drawHBar = function (delay, label, color='rgb(0,0,0)', label_offset=0) +{ + var ctx = this.ctx; + + ctx.fillStyle = color; + ctx.strokeStyle = color; + ctx.fillText(label, this.xpos(numSamples) + 4 + label_offset, this.ypos(delay) + 3); + + ctx.beginPath(); + ctx.moveTo(this.xpos(0), this.ypos(delay)); + ctx.lineTo(this.xpos(numSamples) + label_offset, this.ypos(delay)); + ctx.stroke(); + ctx.strokeStyle = 'rgb(0,0,0)'; + ctx.fillStyle = 'rgb(0,0,0)'; +} + +LatencyGraph.prototype.draw = function () { + var ctx = this.ctx; + + this.clear(); + this.drawFrame(); + + for (var delay of [ 10, 20, 30, 50, 100, 200, 400, 800 ]) + this.drawScale(delay); + this.draw60fps(); + this.draw30fps(); + + var worst = 0, worstpos = 0; + ctx.beginPath(); + for (var i = 0; i < numSamples; i++) { + ctx.lineTo(this.xpos(i), this.ypos(delays[i])); + if (delays[i] >= worst) { + worst = delays[i]; + worstpos = i; + } + } + ctx.stroke(); + + // Draw vertical lines marking minor and major GCs + if (features.showingGCs) { + var { width, height } = ctx.canvas; + + ctx.strokeStyle = 'rgb(255,100,0)'; + var idx = sampleIndex % numSamples; + var gcCount = gcs[idx]; + for (var i = 0; i < numSamples; i++) { + idx = (sampleIndex + i) % numSamples; + if (gcCount < gcs[idx]) { + ctx.beginPath(); + ctx.moveTo(this.xpos(idx), 0); + ctx.lineTo(this.xpos(idx), this.layout.xAxisLabel_Y); + ctx.stroke(); + } + gcCount = gcs[idx]; + } + + ctx.strokeStyle = 'rgb(0,255,100)'; + idx = sampleIndex % numSamples; + gcCount = gcs[idx]; + for (var i = 0; i < numSamples; i++) { + idx = (sampleIndex + i) % numSamples; + if (gcCount < minorGCs[idx]) { + ctx.beginPath(); + ctx.moveTo(this.xpos(idx), 0); + ctx.lineTo(this.xpos(idx), 20); + ctx.stroke(); + } + gcCount = minorGCs[idx]; + } + } + + ctx.fillStyle = 'rgb(255,0,0)'; + if (worst) + ctx.fillText(`${worst.toFixed(2)}ms`, this.xpos(worstpos) - 10, this.ypos(worst) - 14); + + // Mark and label the slowest frame + ctx.beginPath(); + var where = sampleIndex % numSamples; + ctx.arc(this.xpos(where), this.ypos(delays[where]), 5, 0, Math.PI*2, true); + ctx.fill(); + ctx.fillStyle = 'rgb(0,0,0)'; + + this.drawAxisLabels('Time', 'Pause between frames (log scale)'); +} + +function MemoryGraph(ctx) { + Graph.call(this, ctx); + this.worstEver = this.bestEver = performance.mozMemory.zone.gcBytes; + this.limit = Math.max(this.worstEver, performance.mozMemory.zone.gcAllocTrigger); +} + +MemoryGraph.prototype = Object.create(Graph.prototype); + +Object.defineProperty(MemoryGraph.prototype, 'constructor', { + enumerable: false, + value: MemoryGraph }); + +MemoryGraph.prototype.ypos = function (size) { + var { height } = this.ctx.canvas; + + var range = this.limit - this.bestEver; + var percent = (size - this.bestEver) / range; + + return (1 - percent) * height * 0.9 + 20; +} + +MemoryGraph.prototype.drawHBar = function (size, label, color='rgb(150,150,150)') +{ + var ctx = this.ctx; + + var y = this.ypos(size); + + ctx.fillStyle = color; + ctx.strokeStyle = color; + ctx.fillText(label, this.xpos(numSamples) + 4, y + 3); + + ctx.beginPath(); + ctx.moveTo(this.xpos(0), y); + ctx.lineTo(this.xpos(numSamples), y); + ctx.stroke(); + ctx.strokeStyle = 'rgb(0,0,0)'; + ctx.fillStyle = 'rgb(0,0,0)'; +} + +function format_gcBytes(bytes) { + if (bytes < 4000) + return `${bytes} bytes`; + else if (bytes < 4e6) + return `${(bytes / 1024).toFixed(2)} KB`; + else if (bytes < 4e9) + return `${(bytes / 1024 / 1024).toFixed(2)} MB`; + else + return `${(bytes / 1024 / 1024 / 1024).toFixed(2)} GB`; +}; + +MemoryGraph.prototype.draw = function () { + var ctx = this.ctx; + + this.clear(); + this.drawFrame(); + + var worst = 0, worstpos = 0; + for (var i = 0; i < numSamples; i++) { + if (gcBytes[i] >= worst) { + worst = gcBytes[i]; + worstpos = i; + } + if (gcBytes[i] < this.bestEver) { + this.bestEver = gcBytes[i]; + } + } + + if (this.worstEver < worst) { + this.worstEver = worst; + this.limit = Math.max(this.worstEver, performance.mozMemory.zone.gcAllocTrigger); + } + + this.drawHBar(this.bestEver, `${format_gcBytes(this.bestEver)} min`, '#00cf61'); + this.drawHBar(this.worstEver, `${format_gcBytes(this.worstEver)} max`, '#cc1111'); + this.drawHBar(performance.mozMemory.zone.gcAllocTrigger, `${format_gcBytes(performance.mozMemory.zone.gcAllocTrigger)} trigger`, '#cc11cc'); + + ctx.fillStyle = 'rgb(255,0,0)'; + if (worst) + ctx.fillText(format_gcBytes(worst), this.xpos(worstpos) - 10, this.ypos(worst) - 14); + + ctx.beginPath(); + var where = sampleIndex % numSamples; + ctx.arc(this.xpos(where), this.ypos(gcBytes[where]), 5, 0, Math.PI*2, true); + ctx.fill(); + + ctx.beginPath(); + for (var i = 0; i < numSamples; i++) { + if (i == (sampleIndex + 1) % numSamples) + ctx.moveTo(this.xpos(i), this.ypos(gcBytes[i])); + else + ctx.lineTo(this.xpos(i), this.ypos(gcBytes[i])); + if (i == where) + ctx.stroke(); + } + ctx.stroke(); + + this.drawAxisLabels('Time', 'Heap Memory Usage'); +} + +function stopstart() +{ + if (stopped) { + window.requestAnimationFrame(handler); + prev = performance.now(); + start += prev - stopped; + document.getElementById('stop').value = 'Pause'; + stopped = 0; + } else { + document.getElementById('stop').value = 'Resume'; + stopped = performance.now(); + } +} + +var previous = 0; +function handler(timestamp) +{ + if (stopped) + return; + + if (testState === 'running' && (timestamp - testStart) > testDuration) + end_test(timestamp); + + if (testState == 'running') + document.getElementById("test-progress").textContent = ((testDuration - (timestamp - testStart))/1000).toFixed(1) + " sec"; + + activeTest.makeGarbage(activeTest.garbagePerFrame); + + var elt = document.getElementById('data'); + var delay = timestamp - prev; + prev = timestamp; + + // Take the histogram at 10us intervals so that we have enough resolution to capture. + // a 16.66[666] target with adequate accuracy. + update_histogram(gHistogram, Math.round(delay * 100)); + + var t = timestamp - start; + var newIndex = Math.round(t / sampleTime); + while (sampleIndex < newIndex) { + sampleIndex++; + var idx = sampleIndex % numSamples; + delays[idx] = delay; + if (features.trackingSizes) + gcBytes[idx] = performance.mozMemory.gcBytes; + if (features.showingGCs) { + gcs[idx] = performance.mozMemory.gcNumber; + minorGCs[idx] = performance.mozMemory.minorGCCount; + } + } + + latencyGraph.draw(); + if (memoryGraph) + memoryGraph.draw(); + window.requestAnimationFrame(handler); +} + +function summarize(arr) { + if (arr.length == 0) + return []; + + var result = []; + var run_start = 0; + var prev = arr[0]; + for (var i = 1; i <= arr.length; i++) { + if (i == arr.length || arr[i] != prev) { + if (i == run_start + 1) { + result.push(arr[i]); + } else { + result.push(prev + " x " + (i - run_start)); + } + run_start = i; + } + if (i != arr.length) + prev = arr[i]; + } + + return result; +} + +function update_histogram(histogram, delay) +{ + var current = histogram.has(delay) ? histogram.get(delay) : 0; + histogram.set(delay, ++current); +} + +function reset_draw_state() +{ + for (var i = 0; i < numSamples; i++) + delays[i] = 0; + start = prev = performance.now(); + sampleIndex = 0; +} + +function onunload() +{ + if (activeTest) + activeTest.unload(); + activeTest = undefined; +} + +function onload() +{ + // Load initial test duration. + duration_changed(); + + // Load initial garbage size. + garbage_total_changed(); + garbage_per_frame_changed(); + + // Populate the test selection dropdown. + var select = document.getElementById("test-selection"); + for (var [name, test] of tests) { + test.name = name; + var option = document.createElement("option"); + option.id = name; + option.text = name; + option.title = test.description; + select.add(option); + } + + // Load the initial test. + change_active_test('noAllocation'); + + // Polyfill rAF. + var requestAnimationFrame = + window.requestAnimationFrame || window.mozRequestAnimationFrame || + window.webkitRequestAnimationFrame || window.msRequestAnimationFrame; + window.requestAnimationFrame = requestAnimationFrame; + + // Acquire our canvas. + var canvas = document.getElementById('graph'); + latencyGraph = new LatencyGraph(canvas.getContext('2d')); + + if (!performance.mozMemory) { + document.getElementById('memgraph-disabled').style.display = 'block'; + document.getElementById('track-sizes-div').style.display = 'none'; + } + + trackHeapSizes(document.getElementById('track-sizes').checked); + + // Start drawing. + reset_draw_state(); + window.requestAnimationFrame(handler); +} + +function run_one_test() +{ + start_test_cycle([activeTest.name]); +} + +function run_all_tests() +{ + start_test_cycle(tests.keys()); +} + +function start_test_cycle(tests_to_run) +{ + // Convert from an iterable to an array for pop. + testQueue = []; + for (var key of tests_to_run) + testQueue.push(key); + testState = 'running'; + testStart = performance.now(); + gHistogram.clear(); + + start_test(testQueue.shift()); + reset_draw_state(); +} + +function start_test(testName) +{ + change_active_test(testName); + console.log(`Running test: ${testName}`); + document.getElementById("test-selection").value = testName; +} + +function end_test(timestamp) +{ + document.getElementById("test-progress").textContent = "(not running)"; + report_test_result(activeTest, gHistogram); + gHistogram.clear(); + console.log(`Ending test ${activeTest.name}`); + if (testQueue.length) { + start_test(testQueue.shift()); + testStart = timestamp; + } else { + testState = 'idle'; + testStart = 0; + } + reset_draw_state(); +} + +function report_test_result(test, histogram) +{ + var resultList = document.getElementById('results-display'); + var resultElem = document.createElement("div"); + var score = compute_test_score(histogram); + var sparks = compute_test_spark_histogram(histogram); + var params = `(${format_units(test.garbagePerFrame)},${format_units(test.garbageTotal)})`; + resultElem.innerHTML = `${score.toFixed(3)} ms/s : ${sparks} : ${test.name}${params} - ${test.description}`; + resultList.appendChild(resultElem); +} + +// Compute a score based on the total ms we missed frames by per second. +function compute_test_score(histogram) +{ + var score = 0; + for (var [delay, count] of histogram) { + delay = delay / 100; + score += Math.abs((delay - 16.66) * count); + } + score = score / (testDuration / 1000); + return Math.round(score * 1000) / 1000; +} + +// Build a spark-lines histogram for the test results to show with the aggregate score. +function compute_test_spark_histogram(histogram) +{ + var ranges = [ + [-99999999, 16.6], + [16.6, 16.8], + [16.8, 25], + [25, 33.4], + [33.4, 60], + [60, 100], + [100, 300], + [300, 99999999], + ]; + var rescaled = new Map(); + for (var [delay, count] of histogram) { + delay = delay / 100; + for (var i = 0; i < ranges.length; ++i) { + var low = ranges[i][0]; + var high = ranges[i][1]; + if (low <= delay && delay < high) { + update_histogram(rescaled, i); + break; + } + } + } + var total = 0; + for (var [i, count] of rescaled) + total += count; + var sparks = "▁▂▃▄▅▆▇█"; + var colors = ['#aaaa00', '#007700', '#dd0000', '#ff0000', + '#ff0000', '#ff0000', '#ff0000', '#ff0000']; + var line = ""; + for (var i = 0; i < ranges.length; ++i) { + var amt = rescaled.has(i) ? rescaled.get(i) : 0; + var spark = sparks.charAt(parseInt(amt/total*8)); + line += `<span style="color:${colors[i]}">${spark}</span>`; + } + return line; +} + +function reload_active_test() +{ + activeTest.unload(); + activeTest.load(activeTest.garbageTotal); +} + +function change_active_test(new_test_name) +{ + if (activeTest) + activeTest.unload(); + activeTest = tests.get(new_test_name); + + if (!activeTest.garbagePerFrame) + activeTest.garbagePerFrame = parse_units(activeTest.defaultGarbagePerFrame || globalDefaultGarbagePerFrame); + if (!activeTest.garbageTotal) + activeTest.garbageTotal = parse_units(activeTest.defaultGarbageTotal || globalDefaultGarbageTotal); + + document.getElementById("garbage-per-frame").value = format_units(activeTest.garbagePerFrame); + document.getElementById("garbage-total").value = format_units(activeTest.garbageTotal); + + activeTest.load(activeTest.garbageTotal); +} + +function duration_changed() +{ + var durationInput = document.getElementById('test-duration'); + testDuration = parseInt(durationInput.value) * 1000; + console.log(`Updated test duration to: ${testDuration / 1000} seconds`); +} + +function test_changed() +{ + var select = document.getElementById("test-selection"); + console.log(`Switching to test: ${select.value}`); + change_active_test(select.value); + gHistogram.clear(); + reset_draw_state(); +} + +function parse_units(v) +{ + if (v.length == 0) + return NaN; + var lastChar = v[v.length - 1].toLowerCase(); + if (!isNaN(parseFloat(lastChar))) + return parseFloat(v); + var units = parseFloat(v.substr(0, v.length - 1)); + if (lastChar == "k") + return units * 1e3; + if (lastChar == "m") + return units * 1e6; + if (lastChar == "g") + return units * 1e9; + return NaN; +} + +function format_units(n) +{ + n = String(n); + if (n.length > 9 && n.substr(-9) == "000000000") + return n.substr(0, n.length - 9) + "G"; + else if (n.length > 9 && n.substr(-6) == "000000") + return n.substr(0, n.length - 6) + "M"; + else if (n.length > 3 && n.substr(-3) == "000") + return n.substr(0, n.length - 3) + "K"; + else + return String(n); +} + +function garbage_total_changed() +{ + var value = parse_units(document.getElementById('garbage-total').value); + if (isNaN(value)) + return; + if (activeTest) { + activeTest.garbageTotal = value; + console.log(`Updated garbage-total to ${activeTest.garbageTotal} items`); + reload_active_test(); + } + gHistogram.clear(); + reset_draw_state(); +} + +function garbage_per_frame_changed() +{ + var value = parse_units(document.getElementById('garbage-per-frame').value); + if (isNaN(value)) + return; + if (activeTest) { + activeTest.garbagePerFrame = value; + console.log(`Updated garbage-per-frame to ${activeTest.garbagePerFrame} items`); + } +} + +function trackHeapSizes(track) +{ + features.trackingSizes = track; + + var canvas = document.getElementById('memgraph'); + + if (features.trackingSizes) { + canvas.style.display = 'block'; + memoryGraph = new MemoryGraph(canvas.getContext('2d')); + } else { + canvas.style.display = 'none'; + memoryGraph = null; + } +} diff --git a/js/src/devtools/gc-ubench/index.html b/js/src/devtools/gc-ubench/index.html new file mode 100644 index 000000000..d5ab2a083 --- /dev/null +++ b/js/src/devtools/gc-ubench/index.html @@ -0,0 +1,84 @@ +<html> +<head> + <title>GC uBench</title> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> + + <!-- Include benchmark modules. --> + <script>var tests = new Map();</script> + <script src="benchmarks/noAllocation.js"></script> + <script src="benchmarks/globalArrayNewObject.js"></script> + <script src="benchmarks/globalArrayArrayLiteral.js"></script> + <script src="benchmarks/globalArrayLargeArray.js"></script> + <script src="benchmarks/globalArrayLargeObject.js"></script> + <script src="benchmarks/globalArrayObjectLiteral.js"></script> + <script src="benchmarks/globalArrayReallocArray.js"></script> + <script src="benchmarks/globalArrayBuffer.js"></script> + <script src="benchmarks/globalArrayFgFinalized.js"></script> + <script src="benchmarks/largeArrayPropertyAndElements.js"></script> + <script src="benchmarks/selfCyclicWeakMap.js"></script> + <script src="benchmarks/pairCyclicWeakMap.js"></script> + <script src="benchmarks/textNodes.js"></script> + <script src="benchmarks/bigTextNodes.js"></script> + <script src="benchmarks/events.js"></script> + <script src="benchmarks/expandoEvents.js"></script> + <script src="benchmarks/propertyTreeSplitting.js"></script> + + <script src="harness.js"></script> + +</head> + +<body onload="onload()" onunload="onunload()"> + +<canvas id="graph" width="1080" height="400" style="padding-left:10px"></canvas> +<canvas id="memgraph" width="1080" height="400" style="padding-left:10px"></canvas> +<div id="memgraph-disabled" style="display: none"><i>No performance.mozMemory object available. Set dom.enable_memory_stats to True to see heap size info.</i></div> + +<hr> + +<div id='track-sizes-div'> + Show heap size graph: <input id='track-sizes' type='checkbox' onclick="trackHeapSizes(this.checked)"> +</div> + +<div> + <input type="button" id="stop" value="Pause" onclick="stopstart()"></input> +</div> + +<div> + Duration: <input type="text" id="test-duration" size="3" value="8" onchange="duration_changed()"></input>s + <input type="button" id="test-one" value="Run Test" onclick="run_one_test()"></input> + <input type="button" id="test-all" value="Run All Tests" onclick="run_all_tests()"></input> +</div> + +<div> + Currently running test load: + <select id="test-selection" required onchange="test_changed()"></select> +</div> + +<div> + Time remaining: <span id="test-progress">(not running)</span> +</div + +<div> + 60 fps: <span id="pct60">n/a</span> + 45 fps: <span id="pct45">n/a</span> + 30 fps: <span id="pct30">n/a</span> +</div + +<div> + Garbage items per frame: + <input type="text" id="garbage-per-frame" size="5" value="8K" + onchange="garbage_per_frame_changed()"></input> +</div> +<div> + Garbage piles: + <input type="text" id="garbage-total" size="5" value="8M" + onchange="garbage_total_changed()"></input> +</div> + +<div id="results-Area"> + Test Results: + <div id="results-display" style="padding-left: 10px; border: 1px solid black;"></div> +</div> + +</body> +</html> |