From cc1cf3a647862d100685907c71f3ac16802d3708 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Rainone?= <476650+arl@users.noreply.github.com> Date: Sun, 20 Feb 2022 13:58:26 +0100 Subject: [PATCH] Switch to es6 (#67) * internal/static: js cosmetics * internal/static: convert to ES6 modules * _example: ignore dev directory * _example: minor test log improvements * internal/static: in ui.js switch to const where possible * Update CHANGELOG.md --- CHANGELOG.md | 21 +- _example/examples_test.go | 6 +- internal/static/app.js | 105 ++++--- internal/static/buffer.js | 83 +++-- internal/static/index.html | 24 +- internal/static/stats.js | 286 +++++++++-------- internal/static/ui.js | 627 +++++++++++++++++-------------------- 7 files changed, 547 insertions(+), 605 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 833c5e99..b45a5669 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,21 +1,22 @@ Unreleased yet ============== - * Build and test all examples - * Polishing (README, small UI improvements) - * Assets are `go:embed`ed, so the minimum go version is now go1.16 + * Switch javascript code to ES6 (#65) + * Build and test all examples (#63) + * Assets are `go:embed`ed, so the minimum go version is now go1.16 (#55) + * Polishing (README, small UI improvements) (#54) * Small ui improvements: link to go.dev rather than golang.org v0.4.0 / 2021-05-08 ================== * Auto-reconnect to new server from GUI after closed websocket connection (#49) - * Reorganize examples - * Make `IndexAtRoot` returns an `http.HandlerFunc` instead of `http.Handler` + * Reorganize examples (#51) + * Make `IndexAtRoot` returns an `http.HandlerFunc` instead of `http.Handler` (#52) v0.3.0 / 2021-02-14 ================== - * Enable 'save as png' button on plots + * Enable 'save as png' button on plots (#44) v0.2.2 / 2020-12-13 ================== @@ -23,14 +24,14 @@ v0.2.2 / 2020-12-13 * Use Go Modules for 'github.com/gorilla/websocket' (#39) * Support custom frequency (#37) * Added fixed go-chi example (#38) - * _example: add echo (#22) - * _example: add example gin (#34) + * `_example`: add echo (#22) + * `_example`: add gin example (#34) * ci: track coverage * RegisterDefault returns an error now * Ensure send frequency is a strictly positive integer * Don't log if we can't upgrade to websocket - * _example: add chi router (#38) - * _example: change structure to have one example per directory + * `_example`_example: add chi router (#38) + * `_example`_example: change structure to have one example per directory v0.2.1 / 2020-10-29 =================== diff --git a/_example/examples_test.go b/_example/examples_test.go index 6ea62131..02082b21 100644 --- a/_example/examples_test.go +++ b/_example/examples_test.go @@ -51,7 +51,7 @@ func TestExamples(t *testing.T) { }, } for _, ent := range ents { - if !ent.IsDir() { + if !ent.IsDir() || ent.Name() == "dev" { continue } t.Run(ent.Name(), func(t *testing.T) { @@ -150,12 +150,12 @@ func gorun() (stop func() error, err error) { if outb.Len() > 0 { out = outb.String() } - fmt.Printf("go run, output:\n%s\n", out) + fmt.Printf("command output:\n%s\n", out) } }() if err := <-errc; err != nil { - return nil, fmt.Errorf("go run: %v", err) + return nil, fmt.Errorf("command error: %v", err) } return cmd.Process.Kill, nil diff --git a/internal/static/app.js b/internal/static/app.js index 4cebcf60..9ecb55e2 100644 --- a/internal/static/app.js +++ b/internal/static/app.js @@ -1,66 +1,67 @@ -(function() { - function $(id) { - return document.getElementById(id); - } - - function buildWebsocketURI() { - var loc = window.location, - ws_prot = "ws:"; - if (loc.protocol === "https:") { - ws_prot = "wss:"; - } - return ws_prot + "//" + loc.host + loc.pathname + "ws" - } +import * as stats from './stats.js'; +import * as ui from './ui.js'; - const dataRetentionSeconds = 60; - var timeout = 250; +const $ = id => { + return document.getElementById(id); +} - function clamp(val, min, max) { - if (val < min) return min; - if (val > max) return max; - return val; +const buildWebsocketURI = () => { + var loc = window.location, + ws_prot = "ws:"; + if (loc.protocol === "https:") { + ws_prot = "wss:"; } + return ws_prot + "//" + loc.host + loc.pathname + "ws" +} - /* WebSocket connection handling */ +const dataRetentionSeconds = 60; +var timeout = 250; - function connect() { - let ws = new WebSocket(buildWebsocketURI()); - console.log("Attempting websocket connection to statsviz server..."); +const clamp = (val, min, max) => { + if (val < min) return min; + if (val > max) return max; + return val; +} - ws.onopen = () => { - console.log("Successfully connected"); - timeout = 250; // reset connection timeout for next time - }; +/* WebSocket connection handling */ - ws.onclose = event => { - console.log("Closed websocket connection: ", event); - setTimeout(connect, clamp(timeout += timeout, 250, 5000)); - }; +const connect = () => { + let ws = new WebSocket(buildWebsocketURI()); + console.log("Attempting websocket connection to statsviz server..."); - ws.onerror = error => { - console.log("Websocket error: ", error); - ws.close(); - }; + ws.onopen = () => { + console.log("Successfully connected"); + timeout = 250; // reset connection timeout for next time + }; - var initDone = false; - ws.onmessage = event => { - let allStats = JSON.parse(event.data) - if (!initDone) { - stats.init(dataRetentionSeconds, allStats); - stats.pushData(new Date(), allStats); - initDone = true; - let data = stats.slice(dataRetentionSeconds); - ui.createPlots(data); - return; - } + ws.onclose = event => { + console.log("Closed websocket connection: ", event); + setTimeout(connect, clamp(timeout += timeout, 250, 5000)); + }; + ws.onerror = error => { + console.log("Websocket error: ", error); + ws.close(); + }; + + var initDone = false; + ws.onmessage = event => { + let allStats = JSON.parse(event.data) + if (!initDone) { + stats.init(dataRetentionSeconds, allStats); stats.pushData(new Date(), allStats); - if (ui.isPaused()) { - return - } + initDone = true; let data = stats.slice(dataRetentionSeconds); - ui.updatePlots(data); + ui.createPlots(data); + return; + } + + stats.pushData(new Date(), allStats); + if (ui.isPaused()) { + return } + let data = stats.slice(dataRetentionSeconds); + ui.updatePlots(data); } - connect(); -}()); \ No newline at end of file +} +connect(); \ No newline at end of file diff --git a/internal/static/buffer.js b/internal/static/buffer.js index c4fe7ec7..90c5b788 100644 --- a/internal/static/buffer.js +++ b/internal/static/buffer.js @@ -1,53 +1,46 @@ // Buffer declares the Buffer class. -var Buffer = (function() { - class Buffer { - constructor(len, cap) { - if (cap - len < 0) { - console.Error("cap - len must be positive"); - } - - // TODO(arl): value using TypedArray rather than Array here - this._buf = new Array(cap); - this._pos = 0; - this._len = len; - this._cap = cap; +export default class Buffer { + constructor(len, cap) { + if (cap - len < 0) { + console.Error("cap - len must be positive"); } - last() { - if (this.length() == 0) { - throw 'Cannot call last() on an empty Buffer'; - } - return this._buf[this._pos]; + // TODO(arl): value using TypedArray rather than Array here + this._buf = new Array(cap); + this._pos = 0; + this._len = len; + this._cap = cap; + } + last() { + if (this.length() == 0) { + throw 'Cannot call last() on an empty Buffer'; } - push(pt) { - if (this._pos >= this._cap) { - // move data to the beggining of the buffer, effectively discarding - // the cap-len oldest elements - this._buf.copyWithin(0, this._cap - this._len); - this._pos = this._len; - } - - this._buf[this._pos] = pt; - this._pos++; + return this._buf[this._pos]; + } + push(pt) { + if (this._pos >= this._cap) { + // move data to the beggining of the buffer, effectively discarding + // the cap-len oldest elements + this._buf.copyWithin(0, this._cap - this._len); + this._pos = this._len; } - length() { - if (this._pos > this._len) { - return this._len; - } - - return this._pos; - } - // slice returns a slice of the len latest datapoints present in the buffer. - slice(len) { - // Cap the dimension of the returned slice to the data available - if (len > this.length()) { - len = this.length(); - } - - let start = this._pos - len; - return this._buf.slice(start, start + len); + this._buf[this._pos] = pt; + this._pos++; + } + length() { + if (this._pos > this._len) { + return this._len; } + return this._pos; } + // slice returns a slice of the len latest datapoints present in the buffer. + slice(len) { + // Cap the dimension of the returned slice to the data available + if (len > this.length()) { + len = this.length(); + } - return Buffer; -}()); \ No newline at end of file + let start = this._pos - len; + return this._buf.slice(start, start + len); + } +}; \ No newline at end of file diff --git a/internal/static/index.html b/internal/static/index.html index 6172b517..a520c8ef 100644 --- a/internal/static/index.html +++ b/internal/static/index.html @@ -33,22 +33,19 @@
- - Heap + Heap
- - MSpan / MCache + MSpan / MCache
- - Size Classes + Size Classes
@@ -59,22 +56,19 @@
- - Objects + Objects
- - Goroutines + Goroutines
- - GC / CPU fraction + GC / CPU fraction
@@ -82,11 +76,7 @@
- - - - - + \ No newline at end of file diff --git a/internal/static/stats.js b/internal/static/stats.js index 04692096..45057b79 100644 --- a/internal/static/stats.js +++ b/internal/static/stats.js @@ -1,170 +1,168 @@ // stats holds the data and function to modify it. -var stats = (function() { - var m = {}; - - const idxHeapAlloc = 0; - const idxHeapSys = 1; - const idxHeapIdle = 2; - const idxHeapInuse = 3; - const idxHeapNextGC = 4; - const numSeriesHeap = 5; - - const idxMSpanMCacheMSpanInUse = 0; - const idxMSpanMCacheMSpanSys = 1; - const idxMSpanMSpanMSCacheInUse = 2; - const idxMSpanMSpanMSCacheSys = 3; - const numSeriesMSpanMCache = 4; - - const idxObjectsLive = 0; - const idxObjectsLookups = 1; - const idxObjectsHeap = 2; - const numSeriesObjects = 3; - - var data = { - times: null, - heap: new Array(numSeriesHeap), - mspanMCache: new Array(numSeriesMSpanMCache), - objects: new Array(numSeriesObjects), - goroutines: null, - gcfraction: null, - lastGCs: new Array(), - bySize: null, - }; - - m.init = function(buflen, allStats) { - const extraBufferCapacity = 20; // 20% of extra (preallocated) buffer datapoints - const bufcap = buflen + (buflen * extraBufferCapacity) / 100; // number of actual datapoints - - const memStats = allStats.Mem; - - data.times = new Buffer(buflen, bufcap); - data.goroutines = new Buffer(buflen, bufcap); - data.gcfraction = new Buffer(buflen, bufcap); - - for (let i = 0; i < numSeriesHeap; i++) { - data.heap[i] = new Buffer(buflen, bufcap); - } +import Buffer from "./buffer.js"; + +const idxHeapAlloc = 0; +const idxHeapSys = 1; +const idxHeapIdle = 2; +const idxHeapInuse = 3; +const idxHeapNextGC = 4; +const numSeriesHeap = 5; + +const idxMSpanMCacheMSpanInUse = 0; +const idxMSpanMCacheMSpanSys = 1; +const idxMSpanMSpanMSCacheInUse = 2; +const idxMSpanMSpanMSCacheSys = 3; +const numSeriesMSpanMCache = 4; + +const idxObjectsLive = 0; +const idxObjectsLookups = 1; +const idxObjectsHeap = 2; +const numSeriesObjects = 3; + +var data = { + times: null, + heap: new Array(numSeriesHeap), + mspanMCache: new Array(numSeriesMSpanMCache), + objects: new Array(numSeriesObjects), + goroutines: null, + gcfraction: null, + // Array of the last relevant GC times + lastGCs: new Array(), + bySize: null, +}; - for (let i = 0; i < numSeriesMSpanMCache; i++) { - data.mspanMCache[i] = new Buffer(buflen, bufcap); - } +// Contain indexed class sizes, this is initialized after reception of the first message. +var classSizes = new Array(); +const lastGCs = data.lastGCs; - for (let i = 0; i < numSeriesObjects; i++) { - data.objects[i] = new Buffer(buflen, bufcap); - } - // size classes heatmap - for (let i = 0; i < memStats.BySize.length; i++) { - m.classSizes.push(memStats.BySize[i].Size); - } +const init = (buflen, allStats) => { + const extraBufferCapacity = 20; // 20% of extra (preallocated) buffer datapoints + const bufcap = buflen + (buflen * extraBufferCapacity) / 100; // number of actual datapoints - data.bySize = new Array(m.classSizes.length); - for (let i = 0; i < data.bySize.length; i++) { - data.bySize[i] = new Buffer(buflen, bufcap); - } - }; + const memStats = allStats.Mem; - // Array of the last relevant GC times - m.lastGCs = data.lastGCs; - - function updateLastGC(memStats) { - const nanoToSeconds = 1000 * 1000 * 1000; - let t = Math.floor(memStats.LastGC / nanoToSeconds); - let lastGC = new Date(t * 1000); - if (data.lastGCs.length == 0) { - data.lastGCs.push(lastGC); - return; - } - if (lastGC.getTime() != data.lastGCs[data.lastGCs.length - 1].getTime()) { - data.lastGCs.push(lastGC); - // We've added a GC timestamp, check if we can cut the front. We - // don't need to keep track data.lastGCs[0] if it happened before - // the oldest timestamp we're showing. - let mints = data.times._buf[0]; - if (data.lastGCs[0] < mints) { - data.lastGCs.splice(0, 1); - } - } + data.times = new Buffer(buflen, bufcap); + data.goroutines = new Buffer(buflen, bufcap); + data.gcfraction = new Buffer(buflen, bufcap); + + for (let i = 0; i < numSeriesHeap; i++) { + data.heap[i] = new Buffer(buflen, bufcap); } - // Contain indexed class sizes, this is initialized after reception of the first message. - m.classSizes = new Array(); + for (let i = 0; i < numSeriesMSpanMCache; i++) { + data.mspanMCache[i] = new Buffer(buflen, bufcap); + } - m.pushData = function(ts, allStats) { - data.times.push(ts); // timestamp + for (let i = 0; i < numSeriesObjects; i++) { + data.objects[i] = new Buffer(buflen, bufcap); + } - const memStats = allStats.Mem; + // size classes heatmap + for (let i = 0; i < memStats.BySize.length; i++) { + classSizes.push(memStats.BySize[i].Size); + } - data.gcfraction.push(memStats.GCCPUFraction); - data.goroutines.push(allStats.NumGoroutine); + data.bySize = new Array(classSizes.length); + for (let i = 0; i < data.bySize.length; i++) { + data.bySize[i] = new Buffer(buflen, bufcap); + } +}; + +const updateLastGC = memStats => { + const nanoToSeconds = 1000 * 1000 * 1000; + let t = Math.floor(memStats.LastGC / nanoToSeconds); + let lastGC = new Date(t * 1000); + if (data.lastGCs.length == 0) { + data.lastGCs.push(lastGC); + return; + } + if (lastGC.getTime() != data.lastGCs[data.lastGCs.length - 1].getTime()) { + data.lastGCs.push(lastGC); + // We've added a GC timestamp, check if we can cut the front. We + // don't need to keep track data.lastGCs[0] if it happened before + // the oldest timestamp we're showing. + let mints = data.times._buf[0]; + if (data.lastGCs[0] < mints) { + data.lastGCs.splice(0, 1); + } + } +} - data.heap[idxHeapAlloc].push(memStats.HeapAlloc); - data.heap[idxHeapSys].push(memStats.HeapSys); - data.heap[idxHeapIdle].push(memStats.HeapIdle); - data.heap[idxHeapInuse].push(memStats.HeapInuse); - data.heap[idxHeapNextGC].push(memStats.NextGC); +const pushData = (ts, allStats) => { + data.times.push(ts); // timestamp - data.mspanMCache[idxMSpanMCacheMSpanInUse].push(memStats.MSpanInuse); - data.mspanMCache[idxMSpanMCacheMSpanSys].push(memStats.MSpanSys); - data.mspanMCache[idxMSpanMSpanMSCacheInUse].push(memStats.MCacheInuse); - data.mspanMCache[idxMSpanMSpanMSCacheSys].push(memStats.MCacheSys); + const memStats = allStats.Mem; - data.objects[idxObjectsLive].push(memStats.Mallocs - memStats.Frees); - data.objects[idxObjectsLookups].push(memStats.Lookups); - data.objects[idxObjectsHeap].push(memStats.HeapObjects); + data.gcfraction.push(memStats.GCCPUFraction); + data.goroutines.push(allStats.NumGoroutine); - for (let i = 0; i < memStats.BySize.length; i++) { - const size = memStats.BySize[i]; - data.bySize[i].push(size.Mallocs - size.Frees); - } + data.heap[idxHeapAlloc].push(memStats.HeapAlloc); + data.heap[idxHeapSys].push(memStats.HeapSys); + data.heap[idxHeapIdle].push(memStats.HeapIdle); + data.heap[idxHeapInuse].push(memStats.HeapInuse); + data.heap[idxHeapNextGC].push(memStats.NextGC); - updateLastGC(memStats); - } + data.mspanMCache[idxMSpanMCacheMSpanInUse].push(memStats.MSpanInuse); + data.mspanMCache[idxMSpanMCacheMSpanSys].push(memStats.MSpanSys); + data.mspanMCache[idxMSpanMSpanMSCacheInUse].push(memStats.MCacheInuse); + data.mspanMCache[idxMSpanMSpanMSCacheSys].push(memStats.MCacheSys); + + data.objects[idxObjectsLive].push(memStats.Mallocs - memStats.Frees); + data.objects[idxObjectsLookups].push(memStats.Lookups); + data.objects[idxObjectsHeap].push(memStats.HeapObjects); - m.length = function() { - return data.times.length(); + for (let i = 0; i < memStats.BySize.length; i++) { + const size = memStats.BySize[i]; + data.bySize[i].push(size.Mallocs - size.Frees); } - m.slice = function(nitems) { - const times = data.times.slice(nitems); - const gcfraction = data.gcfraction.slice(nitems); - const goroutines = data.goroutines.slice(nitems); + updateLastGC(memStats); +} - // Heap plot data - let heap = new Array(numSeriesHeap); - for (let i = 0; i < numSeriesHeap; i++) { - heap[i] = data.heap[i].slice(nitems); - } +const length = () => { + return data.times.length(); +} - // MSpan/MCache plot data - let mspanMCache = new Array(numSeriesMSpanMCache); - for (let i = 0; i < numSeriesMSpanMCache; i++) { - mspanMCache[i] = data.mspanMCache[i].slice(nitems); - } +const slice = nitems => { + const times = data.times.slice(nitems); + const gcfraction = data.gcfraction.slice(nitems); + const goroutines = data.goroutines.slice(nitems); - // Objects plot data - let objects = new Array(numSeriesObjects); - for (let i = 0; i < numSeriesObjects; i++) { - objects[i] = data.objects[i].slice(nitems); - } + // Heap plot data + let heap = new Array(numSeriesHeap); + for (let i = 0; i < numSeriesHeap; i++) { + heap[i] = data.heap[i].slice(nitems); + } - // BySizes heatmap data - let bySizes = new Array(data.bySize.length); - for (let i = 0; i < data.bySize.length; i++) { - const size = data.bySize[i]; - bySizes[i] = data.bySize[i].slice(nitems); - } + // MSpan/MCache plot data + let mspanMCache = new Array(numSeriesMSpanMCache); + for (let i = 0; i < numSeriesMSpanMCache; i++) { + mspanMCache[i] = data.mspanMCache[i].slice(nitems); + } - return { - times: times, - gcfraction: gcfraction, - goroutines: goroutines, - heap: heap, - mspanMCache: mspanMCache, - objects: objects, - bySizes: bySizes, - } + // Objects plot data + let objects = new Array(numSeriesObjects); + for (let i = 0; i < numSeriesObjects; i++) { + objects[i] = data.objects[i].slice(nitems); + } + + // BySizes heatmap data + let bySizes = new Array(data.bySize.length); + for (let i = 0; i < data.bySize.length; i++) { + const size = data.bySize[i]; + bySizes[i] = data.bySize[i].slice(nitems); + } + + return { + times: times, + gcfraction: gcfraction, + goroutines: goroutines, + heap: heap, + mspanMCache: mspanMCache, + objects: objects, + bySizes: bySizes, } +} - return m; -}()); \ No newline at end of file +export { init, lastGCs, classSizes, pushData, length, slice }; \ No newline at end of file diff --git a/internal/static/ui.js b/internal/static/ui.js index 5c8d6eff..86bb99e4 100644 --- a/internal/static/ui.js +++ b/internal/static/ui.js @@ -1,377 +1,336 @@ // ui holds the user interface state -var ui = (function() { - var m = {}; +import { classSizes, lastGCs } from './stats.js'; - let paused = false; +const GCLines = data => { + const gcs = lastGCs; + const mints = data.times[0]; + const maxts = data.times[data.times.length - 1]; - m.isPaused = function() { return paused; } - m.togglePause = function() { paused = !paused; } - m.plots = null; + const shapes = []; - function GCLines(data) { - const gcs = stats.lastGCs; - const mints = data.times[0]; - const maxts = data.times[data.times.length - 1]; - - let shapes = []; - - for (let i = 0, n = gcs.length; i < n; i++) { - let d = gcs[i]; - // Clamp GC times which are out of bounds - if (d < mints || d > maxts) { - continue; - } - - shapes.push({ - type: 'line', - x0: d, - x1: d, - yref: 'paper', - y0: 0, - y1: 1, - line: { - color: 'rgb(55, 128, 191)', - width: 1, - dash: 'longdashdot', - } - }) + for (let i = 0, n = gcs.length; i < n; i++) { + let d = gcs[i]; + // Clamp GC times which are out of bounds + if (d < mints || d > maxts) { + continue; } - return shapes; - } - function heapData(data) { - return [{ - x: data.times, - y: data.heap[0], - type: 'scatter', - name: 'heap alloc', - hovertemplate: 'heap alloc: %{y:.4s}B', - }, - { - x: data.times, - y: data.heap[1], - type: 'scatter', - name: 'heap sys', - hovertemplate: 'heap sys: %{y:.4s}B', - }, - { - x: data.times, - y: data.heap[2], - type: 'scatter', - name: 'heap idle', - hovertemplate: 'heap idle: %{y:.4s}B', - }, - { - x: data.times, - y: data.heap[3], - type: 'scatter', - name: 'heap in-use', - hovertemplate: 'heap in-use: %{y:.4s}B', - }, - { - x: data.times, - y: data.heap[4], - type: 'scatter', - name: 'next gc', - hovertemplate: 'next gc: %{y:.4s}B', - }, - ] + shapes.push({ + type: 'line', + x0: d, + x1: d, + yref: 'paper', + y0: 0, + y1: 1, + line: { + color: 'rgb(55, 128, 191)', + width: 1, + dash: 'longdashdot', + } + }) } + return shapes; +} - // https://plotly.com/javascript/reference/layout - let heapLayout = { - title: 'Heap', - xaxis: { - title: 'time', - tickformat: '%H:%M:%S', +const heapData = data => { + return [{ + x: data.times, + y: data.heap[0], + type: 'scatter', + name: 'heap alloc', + hovertemplate: 'heap alloc: %{y:.4s}B', }, - yaxis: { - title: 'bytes', - ticksuffix: 'B', - // tickformat: ' ', - exponentformat: 'SI', - } - }; - - function mspanMCacheData(data) { - return [{ - x: data.times, - y: data.mspanMCache[0], - type: 'scatter', - name: 'mspan in-use', - hovertemplate: 'mspan in-use: %{y:.4s}B', - }, - { - x: data.times, - y: data.mspanMCache[1], - type: 'scatter', - name: 'mspan sys', - hovertemplate: 'mspan sys: %{y:.4s}B', - }, - { - x: data.times, - y: data.mspanMCache[2], - type: 'scatter', - name: 'mcache in-use', - hovertemplate: 'mcache in-use: %{y:.4s}B', - }, - { - x: data.times, - y: data.mspanMCache[3], - type: 'scatter', - name: 'mcache sys', - hovertemplate: 'mcache sys: %{y:.4s}B', - }, - ] - } - - let mspanMCacheLayout = { - title: 'MSpan/MCache', - xaxis: { - title: 'time', - tickformat: '%H:%M:%S', + { + x: data.times, + y: data.heap[1], + type: 'scatter', + name: 'heap sys', + hovertemplate: 'heap sys: %{y:.4s}B', }, - yaxis: { - title: 'bytes', - ticksuffix: 'B', - // tickformat: ' ', - exponentformat: 'SI', - } - }; - - const colorscale = [ - [0, 'rgb(166,206,227, 0.5)'], - [0.05, 'rgb(31,120,180,0.5)'], - [0.2, 'rgb(178,223,138,0.5)'], - [0.5, 'rgb(51,160,44,0.5)'], - [1, 'rgb(227,26,28,0.5)'] - ]; - - function sizeClassesData(data) { - var ret = [{ + { x: data.times, - y: stats.classSizes, - z: data.bySizes, - type: 'heatmap', - hovertemplate: '
size class: %{y:} B' + - '
objects: %{z}
', - showlegend: false, - colorscale: colorscale, - }]; - return ret; - } - - let sizeClassesLayout = { - title: 'Size Classes', - xaxis: { - title: 'time', - tickformat: '%H:%M:%S', + y: data.heap[2], + type: 'scatter', + name: 'heap idle', + hovertemplate: 'heap idle: %{y:.4s}B', }, - yaxis: { - title: 'size classes', - exponentformat: 'SI', - } - }; - - function objectsData(data) { - return [{ - x: data.times, - y: data.objects[0], - type: 'scatter', - name: 'live', - hovertemplate: 'live objects: %{y}', - }, - { - x: data.times, - y: data.objects[1], - type: 'scatter', - name: 'lookups', - hovertemplate: 'pointer lookups: %{y}', - }, - { - x: data.times, - y: data.objects[2], - type: 'scatter', - name: 'heap', - hovertemplate: 'heap objects: %{y}', - }, - ] - } - - let objectsLayout = { - title: 'Objects', - xaxis: { - title: 'time', - tickformat: '%H:%M:%S', + { + x: data.times, + y: data.heap[3], + type: 'scatter', + name: 'heap in-use', + hovertemplate: 'heap in-use: %{y:.4s}B', }, - yaxis: { - title: 'objects' - } - }; - - function goroutinesData(data) { - return [{ + { x: data.times, - y: data.goroutines, + y: data.heap[4], type: 'scatter', - name: 'goroutines', - hovertemplate: 'goroutines: %{y}', - }, ] + name: 'next gc', + hovertemplate: 'next gc: %{y:.4s}B', + }, + ] +} + +// https://plotly.com/javascript/reference/layout +const heapLayout = { + title: 'Heap', + xaxis: { + title: 'time', + tickformat: '%H:%M:%S', + }, + yaxis: { + title: 'bytes', + ticksuffix: 'B', + // tickformat: ' ', + exponentformat: 'SI', } +}; - let goroutinesLayout = { - title: 'Goroutines', - xaxis: { - title: 'time', - tickformat: '%H:%M:%S', +const mspanMCacheData = data => { + return [{ + x: data.times, + y: data.mspanMCache[0], + type: 'scatter', + name: 'mspan in-use', + hovertemplate: 'mspan in-use: %{y:.4s}B', }, - yaxis: { - title: 'goroutines', - } - }; - - function gcFractionData(data) { - return [{ + { + x: data.times, + y: data.mspanMCache[1], + type: 'scatter', + name: 'mspan sys', + hovertemplate: 'mspan sys: %{y:.4s}B', + }, + { + x: data.times, + y: data.mspanMCache[2], + type: 'scatter', + name: 'mcache in-use', + hovertemplate: 'mcache in-use: %{y:.4s}B', + }, + { x: data.times, - y: data.gcfraction, + y: data.mspanMCache[3], type: 'scatter', - name: 'gc/cpu', - hovertemplate: 'gcc/CPU fraction: %{y:,.4%}', - }, ] + name: 'mcache sys', + hovertemplate: 'mcache sys: %{y:.4s}B', + }, + ] +} + +const mspanMCacheLayout = { + title: 'MSpan/MCache', + xaxis: { + title: 'time', + tickformat: '%H:%M:%S', + }, + yaxis: { + title: 'bytes', + ticksuffix: 'B', + // tickformat: ' ', + exponentformat: 'SI', + } +}; + +const colorscale = [ + [0, 'rgb(166,206,227, 0.5)'], + [0.05, 'rgb(31,120,180,0.5)'], + [0.2, 'rgb(178,223,138,0.5)'], + [0.5, 'rgb(51,160,44,0.5)'], + [1, 'rgb(227,26,28,0.5)'] +]; + +const sizeClassesData = data => { + var ret = [{ + x: data.times, + y: classSizes, + z: data.bySizes, + type: 'heatmap', + hovertemplate: '
size class: %{y:} B' + + '
objects: %{z}
', + showlegend: false, + colorscale: colorscale, + }]; + return ret; +} + +const sizeClassesLayout = { + title: 'Size Classes', + xaxis: { + title: 'time', + tickformat: '%H:%M:%S', + }, + yaxis: { + title: 'size classes', + exponentformat: 'SI', } +}; - let gcFractionLayout = { - title: 'GC CPU fraction', - xaxis: { - title: 'time', - tickformat: '%H:%M:%S', +const objectsData = data => { + return [{ + x: data.times, + y: data.objects[0], + type: 'scatter', + name: 'live', + hovertemplate: 'live objects: %{y}', }, - yaxis: { - title: 'gc/cpu (%)', - tickformat: ',.5%', - } - }; - - - let configs = function() { - let plots = ['heap', 'mspan-mcache', 'size-classes', 'objects', 'gcfraction', 'goroutines']; - let cfgs = {}; - - plots.forEach(plotName => { - // Create plot config where only 'save image' and 'show on hover' toggles are enabled. - const config = { - displaylogo: false, - modeBarButtonsToRemove: ['2D', 'zoom2d', 'pan2d', 'select2d', 'lasso2d', 'zoomIn2d', 'zoomOut2d', 'autoScale2d', 'resetScale2d', 'toggleSpikelines'], - toImageButtonOptions: { - format: 'png', - filename: plotName - } - } + { + x: data.times, + y: data.objects[1], + type: 'scatter', + name: 'lookups', + hovertemplate: 'pointer lookups: %{y}', + }, + { + x: data.times, + y: data.objects[2], + type: 'scatter', + name: 'heap', + hovertemplate: 'heap objects: %{y}', + }, + ] +} + +const objectsLayout = { + title: 'Objects', + xaxis: { + title: 'time', + tickformat: '%H:%M:%S', + }, + yaxis: { + title: 'objects' + } +}; + +const goroutinesData = data => { + return [{ + x: data.times, + y: data.goroutines, + type: 'scatter', + name: 'goroutines', + hovertemplate: 'goroutines: %{y}', + }, ] +} + +const goroutinesLayout = { + title: 'Goroutines', + xaxis: { + title: 'time', + tickformat: '%H:%M:%S', + }, + yaxis: { + title: 'goroutines', + } +}; + +const gcFractionData = data => { + return [{ + x: data.times, + y: data.gcfraction, + type: 'scatter', + name: 'gc/cpu', + hovertemplate: 'gcc/CPU fraction: %{y:,.4%}', + }, ] +} + +const gcFractionLayout = { + title: 'GC CPU fraction', + xaxis: { + title: 'time', + tickformat: '%H:%M:%S', + }, + yaxis: { + title: 'gc/cpu (%)', + tickformat: ',.5%', + } +}; - cfgs[plotName] = config; - }); - return cfgs; - }(); +const configs = () => { + const plots = ['heap', 'mspan-mcache', 'size-classes', 'objects', 'gcfraction', 'goroutines']; + const cfgs = {}; - m.createPlots = function(data) { - $('.ui.accordion').accordion({ - exclusive: false, - onOpen: function() { - this.firstElementChild.hidden = false; - }, - onClose: function() { - this.firstElementChild.hidden = true; + plots.forEach(plotName => { + // Create plot config where only 'save image' and 'show on hover' toggles are enabled. + const config = { + displaylogo: false, + modeBarButtonsToRemove: ['2D', 'zoom2d', 'pan2d', 'select2d', 'lasso2d', 'zoomIn2d', 'zoomOut2d', 'autoScale2d', 'resetScale2d', 'toggleSpikelines'], + toImageButtonOptions: { + format: 'png', + filename: plotName } - }); - - - heapElt = $('#heap')[0]; - mspanMCacheElt = $('#mspan-mcache')[0]; - sizeClassesElt = $('#size-classes')[0]; - objectsElt = $('#objects')[0]; - gcfractionElt = $('#gcfraction')[0]; - goroutinesElt = $('#goroutines')[0]; - - Plotly.newPlot(heapElt, heapData(data), heapLayout, configs['heap']); - Plotly.newPlot(mspanMCacheElt, mspanMCacheData(data), mspanMCacheLayout, configs['mspan-mcache']); - Plotly.newPlot(sizeClassesElt, sizeClassesData(data), sizeClassesLayout, configs['size-classes']); - Plotly.newPlot(objectsElt, objectsData(data), objectsLayout, configs['objects']); - Plotly.newPlot(gcfractionElt, gcFractionData(data), gcFractionLayout, configs['gcfraction']); - Plotly.newPlot(goroutinesElt, goroutinesData(data), goroutinesLayout, configs['goroutines']); - } + } - var updateIdx = 0; - m.updatePlots = function(data) { - let gcLines = GCLines(data); + cfgs[plotName] = config; + }); - heapLayout.shapes = gcLines; - if (!heapElt.hidden) { - Plotly.react(heapElt, heapData(data), heapLayout, configs['heap']); - } + return cfgs; +}; - mspanMCacheLayout.shapes = gcLines; - if (!mspanMCacheElt.hidden) { - Plotly.react(mspanMCacheElt, mspanMCacheData(data), mspanMCacheLayout, configs['mspan-mcache']); - } +const heapElt = $('#heap')[0]; +const mspanMCacheElt = $('#mspan-mcache')[0]; +const sizeClassesElt = $('#size-classes')[0]; +const objectsElt = $('#objects')[0]; +const gcfractionElt = $('#gcfraction')[0]; +const goroutinesElt = $('#goroutines')[0]; - objectsLayout.shapes = gcLines; - if (!objectsElt.hidden) { - Plotly.react(objectsElt, objectsData(data), objectsLayout, configs['objects']); +const createPlots = (data) => { + $('.ui.accordion').accordion({ + exclusive: false, + onOpen: function() { + this.firstElementChild.hidden = false; + }, + onClose: function() { + this.firstElementChild.hidden = true; } + }); - if (!gcfractionElt.hidden) { - Plotly.react(gcfractionElt, gcFractionData(data), gcFractionLayout, configs['gcfraction']); - } + Plotly.newPlot(heapElt, heapData(data), heapLayout, configs['heap']); + Plotly.newPlot(mspanMCacheElt, mspanMCacheData(data), mspanMCacheLayout, configs['mspan-mcache']); + Plotly.newPlot(sizeClassesElt, sizeClassesData(data), sizeClassesLayout, configs['size-classes']); + Plotly.newPlot(objectsElt, objectsData(data), objectsLayout, configs['objects']); + Plotly.newPlot(gcfractionElt, gcFractionData(data), gcFractionLayout, configs['gcfraction']); + Plotly.newPlot(goroutinesElt, goroutinesData(data), goroutinesLayout, configs['goroutines']); +} - if (!goroutinesElt.hidden) { - Plotly.react(goroutinesElt, goroutinesData(data), goroutinesLayout, configs['goroutines']); - } +var updateIdx = 0; - if (!sizeClassesElt.hidden && updateIdx % 5 == 0) { - // Update the size class heatmap 5 times less often since it's expensive. - Plotly.react(sizeClassesElt, sizeClassesData(data), sizeClassesLayout, configs['size-classes']); - } +const updatePlots = data => { + let gcLines = GCLines(data); - updateIdx++; + heapLayout.shapes = gcLines; + if (!heapElt.hidden) { + Plotly.react(heapElt, heapData(data), heapLayout, configs['heap']); } - function traceInfo(traceName) { - let traces = { - 'heap alloc': 'HeapAlloc', - 'heap sys': 'HeapSys', - 'heap idle': 'HeapIdle', - 'heap in-use': 'HeapInuse', - 'next gc': 'NextGC', + mspanMCacheLayout.shapes = gcLines; + if (!mspanMCacheElt.hidden) { + Plotly.react(mspanMCacheElt, mspanMCacheData(data), mspanMCacheLayout, configs['mspan-mcache']); + } - 'mspan in-use': 'MSpanInuse', - 'mspan sys': 'MSpanSys', - 'mcache in-use': 'MCacheInuse', - 'mcache sys': 'MCacheSys', + objectsLayout.shapes = gcLines; + if (!objectsElt.hidden) { + Plotly.react(objectsElt, objectsData(data), objectsLayout, configs['objects']); + } - 'gcfraction': 'GCCPUFraction', + if (!gcfractionElt.hidden) { + Plotly.react(gcfractionElt, gcFractionData(data), gcFractionLayout, configs['gcfraction']); + } - 'lookups': 'Lookups', - 'heap objects': 'HeapObjects', - }; + if (!goroutinesElt.hidden) { + Plotly.react(goroutinesElt, goroutinesData(data), goroutinesLayout, configs['goroutines']); + } - let fieldName = traces[traceName]; - if (fieldName !== undefined) { - return memStatsDoc(fieldName); - } - if (traceName == 'goroutines') { - return "The number of goroutines" - } - if (traceName == 'live') { - return "The number of live objects" - } - if (traceName == 'goroutines') { - return "Number of the goroutines" - } - if (traceName == 'size classes') { - return "Reports per-size class allocation statistics" - } - }; + if (!sizeClassesElt.hidden && updateIdx % 5 == 0) { + // Update the size class heatmap 5 times less often since it's expensive. + Plotly.react(sizeClassesElt, sizeClassesData(data), sizeClassesLayout, configs['size-classes']); + } + + updateIdx++; +} + +let paused = false; +const isPaused = () => { return paused; } +const togglePause = () => { paused = !paused; } - return m; -}()); \ No newline at end of file +export { isPaused, togglePause, createPlots, updatePlots }; \ No newline at end of file