diff options
Diffstat (limited to 'website/app.ts')
| -rw-r--r-- | website/app.ts | 299 |
1 files changed, 299 insertions, 0 deletions
diff --git a/website/app.ts b/website/app.ts new file mode 100644 index 000000000..dfc299d2b --- /dev/null +++ b/website/app.ts @@ -0,0 +1,299 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. + +// How much to multiply time values in order to process log graphs properly. +const TimeScaleFactor = 10000; + +export async function getJson(path) { + return (await fetch(path)).json(); +} + +function getBenchmarkVarieties(data, benchmarkName) { + // Look at last sha hash. + const last = data[data.length - 1]; + return Object.keys(last[benchmarkName]); +} + +export function createColumns(data, benchmarkName) { + const varieties = getBenchmarkVarieties(data, benchmarkName); + return varieties.map(variety => [ + variety, + ...data.map(d => { + if (d[benchmarkName] != null) { + if (d[benchmarkName][variety] != null) { + const v = d[benchmarkName][variety]; + if (benchmarkName == "benchmark") { + const meanValue = v ? v.mean : 0; + return meanValue || null; + } else { + return v; + } + } + } + return null; + }) + ]); +} + +export function createExecTimeColumns(data) { + return createColumns(data, "benchmark"); +} + +export function createThroughputColumns(data) { + return createColumns(data, "throughput"); +} + +export function createProxyColumns(data) { + return createColumns(data, "req_per_sec_proxy"); +} + +export function createReqPerSecColumns(data) { + return createColumns(data, "req_per_sec"); +} + +export function createMaxLatencyColumns(data) { + return createColumns(data, "max_latency"); +} + +export function createMaxMemoryColumns(data) { + return createColumns(data, "max_memory"); +} + +export function createBinarySizeColumns(data) { + const propName = "binary_size"; + const binarySizeNames = Object.keys(data[data.length - 1][propName]); + return binarySizeNames.map(name => [ + name, + ...data.map(d => { + const binarySizeData = d["binary_size"]; + switch (typeof binarySizeData) { + case "number": // legacy implementation + return name === "deno" ? binarySizeData : 0; + default: + if (!binarySizeData) { + return null; + } + return binarySizeData[name] || null; + } + }) + ]); +} + +export function createThreadCountColumns(data) { + const propName = "thread_count"; + const threadCountNames = Object.keys(data[data.length - 1][propName]); + return threadCountNames.map(name => [ + name, + ...data.map(d => { + const threadCountData = d[propName]; + if (!threadCountData) { + return null; + } + return threadCountData[name] || null; + }) + ]); +} + +export function createSyscallCountColumns(data) { + const propName = "syscall_count"; + const syscallCountNames = Object.keys(data[data.length - 1][propName]); + return syscallCountNames.map(name => [ + name, + ...data.map(d => { + const syscallCountData = d[propName]; + if (!syscallCountData) { + return null; + } + return syscallCountData[name] || null; + }) + ]); +} + +export function createSha1List(data) { + return data.map(d => d.sha1); +} + +export function formatMB(bytes) { + return (bytes / (1024 * 1024)).toFixed(2); +} + +export function formatReqSec(reqPerSec) { + return reqPerSec / 1000; +} + +/** + * @param {string} id The id of dom element + * @param {string[]} categories categories for x-axis values + * @param {any[][]} columns The columns data + * @param {function} onclick action on clicking nodes of chart + * @param {string} yLabel label of y axis + * @param {function} yTickFormat formatter of y axis ticks + */ +function generate( + id, + categories, + columns, + onclick, + yLabel = "", + yTickFormat = null +) { + const yAxis = { + padding: { bottom: 0 }, + min: 0, + label: yLabel, + tick: null + }; + if (yTickFormat) { + yAxis.tick = { + format: yTickFormat + }; + if (yTickFormat == logScale) { + delete yAxis.min; + for (let col of columns) { + for (let i = 1; i < col.length; i++) { + if (col[i] == null || col[i] === 0) { + continue; + } + col[i] = Math.log10(col[i] * TimeScaleFactor); + } + } + } + } + + // @ts-ignore + c3.generate({ + bindto: id, + data: { + columns, + onclick + }, + axis: { + x: { + type: "category", + show: false, + categories + }, + y: yAxis + } + }); +} + +function logScale(t) { + return (Math.pow(10, t) / TimeScaleFactor).toFixed(4); +} + +function formatSecsAsMins(t) { + // TODO use d3.round() + const a = t % 60; + const min = Math.floor(t / 60); + return a < 30 ? min : min + 1; +} + +/** + * @param dataUrl The url of benchramk data json. + */ +export function drawCharts(dataUrl) { + // TODO Using window["location"]["hostname"] instead of + // window.location.hostname because when deno runs app_test.js it gets a type + // error here, not knowing about window.location. Ideally Deno would skip + // type check entirely on JS files. + if (window["location"]["hostname"] != "deno.github.io") { + dataUrl = "https://denoland.github.io/deno/" + dataUrl; + } + return drawChartsFromBenchmarkData(dataUrl); +} + +const proxyFields = [ + "req_per_sec" + //"max_latency" +]; +function extractProxyFields(data) { + for (const row of data) { + for (const field of proxyFields) { + const d = row[field]; + if (!d) continue; + const name = field + "_proxy"; + const newField = {}; + row[name] = newField; + for (const k of Object.getOwnPropertyNames(d)) { + if (k.includes("_proxy")) { + const v = d[k]; + delete d[k]; + newField[k] = v; + } + } + } + } +} +/** + * Draws the charts from the benchmark data stored in gh-pages branch. + */ +export async function drawChartsFromBenchmarkData(dataUrl) { + const data = await getJson(dataUrl); + + // hack to extract proxy fields from req/s fields + extractProxyFields(data); + + const execTimeColumns = createExecTimeColumns(data); + const throughputColumns = createThroughputColumns(data); + const reqPerSecColumns = createReqPerSecColumns(data); + const proxyColumns = createProxyColumns(data); + const maxLatencyColumns = createMaxLatencyColumns(data); + const maxMemoryColumns = createMaxMemoryColumns(data); + const binarySizeColumns = createBinarySizeColumns(data); + const threadCountColumns = createThreadCountColumns(data); + const syscallCountColumns = createSyscallCountColumns(data); + const sha1List = createSha1List(data); + const sha1ShortList = sha1List.map(sha1 => sha1.substring(0, 6)); + + const viewCommitOnClick = _sha1List => d => { + // @ts-ignore + window.open( + `https://github.com/denoland/deno/commit/${_sha1List[d["index"]]}` + ); + }; + + function gen(id, columns, yLabel = "", yTickFormat = null) { + generate( + id, + sha1ShortList, + columns, + viewCommitOnClick(sha1List), + yLabel, + yTickFormat + ); + } + + gen("#exec-time-chart", execTimeColumns, "seconds", logScale); + gen("#throughput-chart", throughputColumns, "seconds", logScale); + gen("#req-per-sec-chart", reqPerSecColumns, "1000 req/sec", formatReqSec); + gen("#proxy-req-per-sec-chart", proxyColumns, "req/sec"); + gen("#max-latency-chart", maxLatencyColumns, "milliseconds", logScale); + gen("#max-memory-chart", maxMemoryColumns, "megabytes", formatMB); + gen("#binary-size-chart", binarySizeColumns, "megabytes", formatMB); + gen("#thread-count-chart", threadCountColumns, "threads"); + gen("#syscall-count-chart", syscallCountColumns, "syscalls"); +} + +export function main(): void { + window["chartWidth"] = 800; + const overlay = window["document"].getElementById("spinner-overlay"); + + function showSpinner() { + overlay.style.display = "block"; + } + + function hideSpinner() { + overlay.style.display = "none"; + } + + function updateCharts() { + const u = window.location.hash.match("all") ? "./data.json" : "recent.json"; + + showSpinner(); + + drawCharts(u).finally(hideSpinner); + } + updateCharts(); + + window["onhashchange"] = updateCharts; +} |
