mirror of
https://github.com/ClusterCockpit/cc-backend
synced 2024-12-27 05:49:04 +01:00
Change to prod data, allow and handle null data
- fix errors regarding render timing - always collect time info in transFormData function - remove size from polar plot
This commit is contained in:
parent
b449b77b95
commit
1b8c4e293c
@ -4,6 +4,7 @@
|
|||||||
groupByScope,
|
groupByScope,
|
||||||
fetchMetricsStore,
|
fetchMetricsStore,
|
||||||
checkMetricDisabled,
|
checkMetricDisabled,
|
||||||
|
transformDataForRoofline
|
||||||
} from "./utils.js";
|
} from "./utils.js";
|
||||||
import {
|
import {
|
||||||
Row,
|
Row,
|
||||||
@ -130,8 +131,8 @@
|
|||||||
lazyFetchMoreMetrics();
|
lazyFetchMoreMetrics();
|
||||||
|
|
||||||
let plots = {},
|
let plots = {},
|
||||||
|
roofWidth,
|
||||||
jobTags,
|
jobTags,
|
||||||
fullWidth,
|
|
||||||
statsTable;
|
statsTable;
|
||||||
$: document.title = $initq.fetching
|
$: document.title = $initq.fetching
|
||||||
? "Loading..."
|
? "Loading..."
|
||||||
@ -190,7 +191,6 @@
|
|||||||
}));
|
}));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="row" bind:clientWidth={fullWidth} />
|
|
||||||
<Row>
|
<Row>
|
||||||
<Col>
|
<Col>
|
||||||
{#if $initq.error}
|
{#if $initq.error}
|
||||||
@ -245,7 +245,6 @@
|
|||||||
{/if}
|
{/if}
|
||||||
<Col>
|
<Col>
|
||||||
<Polar
|
<Polar
|
||||||
size={fullWidth / 4.1}
|
|
||||||
metrics={ccconfig[
|
metrics={ccconfig[
|
||||||
`job_view_polarPlotMetrics:${$initq.data.job.cluster}`
|
`job_view_polarPlotMetrics:${$initq.data.job.cluster}`
|
||||||
] || ccconfig[`job_view_polarPlotMetrics`]}
|
] || ccconfig[`job_view_polarPlotMetrics`]}
|
||||||
@ -254,21 +253,24 @@
|
|||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
<Col>
|
<Col>
|
||||||
|
<div bind:clientWidth={roofWidth}>
|
||||||
<Roofline
|
<Roofline
|
||||||
width={fullWidth / 3 - 10}
|
width={roofWidth - 10}
|
||||||
height={fullWidth / 5}
|
height={(roofWidth / 2) - 5}
|
||||||
|
renderTime={true}
|
||||||
cluster={clusters
|
cluster={clusters
|
||||||
.find((c) => c.name == $initq.data.job.cluster)
|
.find((c) => c.name == $initq.data.job.cluster)
|
||||||
.subClusters.find(
|
.subClusters.find(
|
||||||
(sc) => sc.name == $initq.data.job.subCluster
|
(sc) => sc.name == $initq.data.job.subCluster
|
||||||
)}
|
)}
|
||||||
flopsAny={$jobMetrics.data.jobMetrics.find(
|
data={
|
||||||
(m) => m.name == "flops_any" && m.scope == "node"
|
transformDataForRoofline (
|
||||||
)}
|
$jobMetrics.data.jobMetrics.find((m) => m.name == "flops_any" && m.scope == "node").metric,
|
||||||
memBw={$jobMetrics.data.jobMetrics.find(
|
$jobMetrics.data.jobMetrics.find((m) => m.name == "mem_bw" && m.scope == "node").metric
|
||||||
(m) => m.name == "mem_bw" && m.scope == "node"
|
)
|
||||||
)}
|
}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
{:else}
|
{:else}
|
||||||
<Col />
|
<Col />
|
||||||
|
@ -31,8 +31,8 @@
|
|||||||
export let cluster;
|
export let cluster;
|
||||||
|
|
||||||
let plotWidths = [],
|
let plotWidths = [],
|
||||||
colWidth1 = 0,
|
colWidth1,
|
||||||
colWidth2 = 0
|
colWidth2
|
||||||
let from = new Date(Date.now() - 5 * 60 * 1000),
|
let from = new Date(Date.now() - 5 * 60 * 1000),
|
||||||
to = new Date(Date.now());
|
to = new Date(Date.now());
|
||||||
const topOptions = [
|
const topOptions = [
|
||||||
@ -429,14 +429,14 @@
|
|||||||
<Roofline
|
<Roofline
|
||||||
width={plotWidths[i] - 10}
|
width={plotWidths[i] - 10}
|
||||||
height={300}
|
height={300}
|
||||||
colorDots={true}
|
|
||||||
showTime={false}
|
|
||||||
cluster={subCluster}
|
cluster={subCluster}
|
||||||
data={transformPerNodeDataForRoofline(
|
data={
|
||||||
|
transformPerNodeDataForRoofline(
|
||||||
$mainQuery.data.nodeMetrics.filter(
|
$mainQuery.data.nodeMetrics.filter(
|
||||||
(data) => data.subCluster == subCluster.name
|
(data) => data.subCluster == subCluster.name
|
||||||
)
|
)
|
||||||
)}
|
)
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
{/key}
|
{/key}
|
||||||
</div>
|
</div>
|
||||||
@ -444,7 +444,7 @@
|
|||||||
</Row>
|
</Row>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
<hr style="margin-top: -1em;" />
|
<hr/>
|
||||||
|
|
||||||
<!-- Usage Stats as Histograms -->
|
<!-- Usage Stats as Histograms -->
|
||||||
|
|
||||||
|
@ -22,7 +22,6 @@
|
|||||||
LineElement
|
LineElement
|
||||||
);
|
);
|
||||||
|
|
||||||
export let size
|
|
||||||
export let metrics
|
export let metrics
|
||||||
export let cluster
|
export let cluster
|
||||||
export let jobMetrics
|
export let jobMetrics
|
||||||
@ -95,7 +94,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="chart-container">
|
<div class="chart-container">
|
||||||
<Radar {data} {options} width={size} height={size}/>
|
<Radar {data} {options}/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
@ -4,99 +4,52 @@
|
|||||||
import { onMount, onDestroy } from 'svelte'
|
import { onMount, onDestroy } from 'svelte'
|
||||||
import { Card } from 'sveltestrap'
|
import { Card } from 'sveltestrap'
|
||||||
|
|
||||||
export let flopsAny = null
|
export let data = null
|
||||||
export let memBw = null
|
export let renderTime = false
|
||||||
export let maxY = null
|
export let maxY = null // Optional
|
||||||
export let cluster = null
|
export let cluster = null
|
||||||
export let width = 500
|
export let width = 500
|
||||||
export let height = 300
|
export let height = 300
|
||||||
export let renderTime = false
|
|
||||||
export let data = null
|
|
||||||
|
|
||||||
let plotWrapper = null
|
let plotWrapper = null
|
||||||
let uplot = null
|
let uplot = null
|
||||||
let timeoutId = null
|
let timeoutId = null
|
||||||
|
|
||||||
// Three Render-Cases:
|
/* Data Format
|
||||||
// #1 Single-Job Roofline -> Has Time-Information: Use data, allow renderTime
|
* data = [null, [], []] // 0: null-axis required for scatter, 1: Array of XY-Array for Scatter, 2: Optional Time Info
|
||||||
// #2 MultiNode Roofline - > Has No Time-Information: Transform from nodeData, only "IST"-state of nodes, no timeInfo
|
* data[1][0] = [100, 200, 500, ...] // X Axis -> Intensity (Vals up to clusters' flopRateScalar value)
|
||||||
// #3 Multi-Job Roofline as Heatmap -> Keep Original
|
* data[1][1] = [1000, 2000, 1500, ...] // Y Axis -> Performance (Vals up to clusters' flopRateSimd value)
|
||||||
|
* data[2] = [0.1, 0.15, 0.2, ...] // Color Code -> Time Information (Floats from 0 to 1) (Optional)
|
||||||
|
*/
|
||||||
|
|
||||||
// Start Demo Data
|
// Check
|
||||||
|
// console.assert(data , "you must provide data")
|
||||||
function randInt(min, max) {
|
|
||||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
||||||
}
|
|
||||||
|
|
||||||
function randFloat(min, max) {
|
|
||||||
return roundTwo(((Math.random() * (max - min + 1)) + min) / randInt(1, 500));
|
|
||||||
}
|
|
||||||
|
|
||||||
function roundTwo(num) {
|
|
||||||
return Math.round((num + Number.EPSILON) * 100) / 100
|
|
||||||
}
|
|
||||||
|
|
||||||
function filledArr(len, val, time) {
|
|
||||||
let arr = Array(len);
|
|
||||||
|
|
||||||
if (typeof val == "function") {
|
|
||||||
for (let i = 0; i < len; ++i)
|
|
||||||
arr[i] = val(i);
|
|
||||||
}
|
|
||||||
else if (time) {
|
|
||||||
for (let i = 0; i < len; ++i)
|
|
||||||
arr[i] = i / 1000;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
for (let i = 0; i < len; ++i)
|
|
||||||
arr[i] = i;
|
|
||||||
}
|
|
||||||
|
|
||||||
return arr;
|
|
||||||
}
|
|
||||||
|
|
||||||
let points = 1000;
|
|
||||||
|
|
||||||
data = [null, []] // Null-Axis required for scatter
|
|
||||||
data[1][0] = filledArr(points, i => randFloat(1,5000), false) // Intensity
|
|
||||||
data[1][1] = filledArr(points, i => randFloat(1,5000), false) // Performance
|
|
||||||
// data[1][0] = filledArr(points, 0, false) // Intensity
|
|
||||||
// data[1][1] = filledArr(points, 0, false) // Performance
|
|
||||||
data[2] = filledArr(points, 0, true) // Time Information (Optional)
|
|
||||||
|
|
||||||
// End Demo Data
|
|
||||||
|
|
||||||
// Helpers
|
// Helpers
|
||||||
|
|
||||||
function getGradientR(x) {
|
function getGradientR(x) {
|
||||||
if (x < 0.5) return 0
|
if (x < 0.5) return 0
|
||||||
if (x > 0.75) return 255
|
if (x > 0.75) return 255
|
||||||
x = (x - 0.5) * 4.0
|
x = (x - 0.5) * 4.0
|
||||||
return Math.floor(x * 255.0)
|
return Math.floor(x * 255.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
function getGradientG(x) {
|
function getGradientG(x) {
|
||||||
if (x > 0.25 && x < 0.75) return 255
|
if (x > 0.25 && x < 0.75) return 255
|
||||||
if (x < 0.25) x = x * 4.0
|
if (x < 0.25) x = x * 4.0
|
||||||
else x = 1.0 - (x - 0.75) * 4.0
|
else x = 1.0 - (x - 0.75) * 4.0
|
||||||
return Math.floor(x * 255.0)
|
return Math.floor(x * 255.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
function getGradientB(x) {
|
function getGradientB(x) {
|
||||||
if (x < 0.25) return 255
|
if (x < 0.25) return 255
|
||||||
if (x > 0.5) return 0
|
if (x > 0.5) return 0
|
||||||
x = 1.0 - (x - 0.25) * 4.0
|
x = 1.0 - (x - 0.25) * 4.0
|
||||||
return Math.floor(x * 255.0)
|
return Math.floor(x * 255.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
function getRGB(c) {
|
function getRGB(c) {
|
||||||
return `rgb(${getGradientR(c)}, ${getGradientG(c)}, ${getGradientB(c)})`
|
return `rgb(${getGradientR(c)}, ${getGradientG(c)}, ${getGradientB(c)})`
|
||||||
}
|
}
|
||||||
|
|
||||||
function nearestThousand (num) {
|
function nearestThousand (num) {
|
||||||
return Math.ceil(num/1000) * 1000
|
return Math.ceil(num/1000) * 1000
|
||||||
}
|
}
|
||||||
|
|
||||||
function lineIntersect(x1, y1, x2, y2, x3, y3, x4, y4) {
|
function lineIntersect(x1, y1, x2, y2, x3, y3, x4, y4) {
|
||||||
let l = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1)
|
let l = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1)
|
||||||
let a = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / l
|
let a = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / l
|
||||||
@ -105,12 +58,11 @@
|
|||||||
y: y1 + a * (y2 - y1)
|
y: y1 + a * (y2 - y1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// End Helpers
|
// End Helpers
|
||||||
|
|
||||||
|
// Dot Renderers
|
||||||
const drawColorPoints = (u, seriesIdx, idx0, idx1) => {
|
const drawColorPoints = (u, seriesIdx, idx0, idx1) => {
|
||||||
const size = 5 * devicePixelRatio;
|
const size = 5 * devicePixelRatio;
|
||||||
|
|
||||||
uPlot.orient(u, seriesIdx, (series, dataX, dataY, scaleX, scaleY, valToPosX, valToPosY, xOff, yOff, xDim, yDim, moveTo, lineTo, rect, arc) => {
|
uPlot.orient(u, seriesIdx, (series, dataX, dataY, scaleX, scaleY, valToPosX, valToPosY, xOff, yOff, xDim, yDim, moveTo, lineTo, rect, arc) => {
|
||||||
let d = u.data[seriesIdx];
|
let d = u.data[seriesIdx];
|
||||||
let deg360 = 2 * Math.PI;
|
let deg360 = 2 * Math.PI;
|
||||||
@ -136,7 +88,6 @@
|
|||||||
|
|
||||||
const drawPoints = (u, seriesIdx, idx0, idx1) => {
|
const drawPoints = (u, seriesIdx, idx0, idx1) => {
|
||||||
const size = 5 * devicePixelRatio;
|
const size = 5 * devicePixelRatio;
|
||||||
|
|
||||||
uPlot.orient(u, seriesIdx, (series, dataX, dataY, scaleX, scaleY, valToPosX, valToPosY, xOff, yOff, xDim, yDim, moveTo, lineTo, rect, arc) => {
|
uPlot.orient(u, seriesIdx, (series, dataX, dataY, scaleX, scaleY, valToPosX, valToPosY, xOff, yOff, xDim, yDim, moveTo, lineTo, rect, arc) => {
|
||||||
let d = u.data[seriesIdx];
|
let d = u.data[seriesIdx];
|
||||||
u.ctx.strokeStyle = getRGB(0);
|
u.ctx.strokeStyle = getRGB(0);
|
||||||
@ -158,7 +109,9 @@
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
function render() {
|
// Main Function
|
||||||
|
function render(plotData) {
|
||||||
|
if (plotData) {
|
||||||
const opts = {
|
const opts = {
|
||||||
title: "",
|
title: "",
|
||||||
mode: 2,
|
mode: 2,
|
||||||
@ -169,8 +122,14 @@
|
|||||||
},
|
},
|
||||||
cursor: { drag: { x: false, y: false } },
|
cursor: { drag: { x: false, y: false } },
|
||||||
axes: [
|
axes: [
|
||||||
{ label: 'Intensity [FLOPS/Byte]' },
|
{
|
||||||
{ label: 'Performace [GFLOPS]' }
|
label: 'Intensity [FLOPS/Byte]',
|
||||||
|
values: (u, vals) => vals.map(v => formatNumber(v))
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Performace [GFLOPS]',
|
||||||
|
values: (u, vals) => vals.map(v => formatNumber(v))
|
||||||
|
}
|
||||||
],
|
],
|
||||||
scales: {
|
scales: {
|
||||||
x: {
|
x: {
|
||||||
@ -255,16 +214,16 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
uplot = new uPlot(opts, plotData, plotWrapper);
|
||||||
uplot = new uPlot(opts, data, plotWrapper);
|
} else {
|
||||||
|
console.log('No data for roofline!')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copied from Histogram
|
// Svelte and Sizechange
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
render()
|
render(data)
|
||||||
})
|
})
|
||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
if (uplot)
|
if (uplot)
|
||||||
uplot.destroy()
|
uplot.destroy()
|
||||||
@ -272,7 +231,6 @@
|
|||||||
if (timeoutId != null)
|
if (timeoutId != null)
|
||||||
clearTimeout(timeoutId)
|
clearTimeout(timeoutId)
|
||||||
})
|
})
|
||||||
|
|
||||||
function sizeChanged() {
|
function sizeChanged() {
|
||||||
if (timeoutId != null)
|
if (timeoutId != null)
|
||||||
clearTimeout(timeoutId)
|
clearTimeout(timeoutId)
|
||||||
@ -281,13 +239,10 @@
|
|||||||
timeoutId = null
|
timeoutId = null
|
||||||
if (uplot)
|
if (uplot)
|
||||||
uplot.destroy()
|
uplot.destroy()
|
||||||
|
render(data)
|
||||||
render()
|
|
||||||
}, 200)
|
}, 200)
|
||||||
}
|
}
|
||||||
|
|
||||||
$: sizeChanged(width, height)
|
$: sizeChanged(width, height)
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if data != null}
|
{#if data != null}
|
||||||
|
@ -6,8 +6,8 @@ const power = [1, 1e3, 1e6, 1e9, 1e12, 1e15, 1e18, 1e21]
|
|||||||
const prefix = ['', 'K', 'M', 'G', 'T', 'P', 'E']
|
const prefix = ['', 'K', 'M', 'G', 'T', 'P', 'E']
|
||||||
|
|
||||||
export function formatNumber(x) {
|
export function formatNumber(x) {
|
||||||
if ( isNaN(x) ) {
|
if ( isNaN(x) || x == null) {
|
||||||
return x // Return if String , used in Histograms
|
return x // Return if String or Null
|
||||||
} else {
|
} else {
|
||||||
for (let i = 0; i < prefix.length; i++)
|
for (let i = 0; i < prefix.length; i++)
|
||||||
if (power[i] <= x && x < power[i+1])
|
if (power[i] <= x && x < power[i+1])
|
||||||
|
@ -6,7 +6,7 @@ import {
|
|||||||
} from "@urql/svelte";
|
} from "@urql/svelte";
|
||||||
import { setContext, getContext, hasContext, onDestroy, tick } from "svelte";
|
import { setContext, getContext, hasContext, onDestroy, tick } from "svelte";
|
||||||
import { readable } from "svelte/store";
|
import { readable } from "svelte/store";
|
||||||
import { formatNumber } from './units.js'
|
// import { formatNumber } from './units.js'
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Call this function only at component initialization time!
|
* Call this function only at component initialization time!
|
||||||
@ -326,8 +326,11 @@ export function convert2uplot(canvasData) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function binsFromFootprint(weights, scope, values, numBins) {
|
export function binsFromFootprint(weights, scope, values, numBins) {
|
||||||
let min = 0, max = 0
|
let min = 0, max = 0 //, median = 0
|
||||||
if (values.length != 0) {
|
if (values.length != 0) {
|
||||||
|
// Extreme, wrong peak vlaues: Filter here or backend?
|
||||||
|
// median = median(values)
|
||||||
|
|
||||||
for (let x of values) {
|
for (let x of values) {
|
||||||
min = Math.min(min, x)
|
min = Math.min(min, x)
|
||||||
max = Math.max(max, x)
|
max = Math.max(max, x)
|
||||||
@ -364,11 +367,12 @@ export function binsFromFootprint(weights, scope, values, numBins) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function transformDataForRoofline(flopsAny, memBw, renderTime) { // Uses Metric Object
|
export function transformDataForRoofline(flopsAny, memBw) { // Uses Metric Objects: {series:[{},{},...], timestep:60, name:$NAME}
|
||||||
const nodes = flopsAny.series.length
|
const nodes = flopsAny.series.length
|
||||||
const timesteps = flopsAny.series[0].data.length
|
const timesteps = flopsAny.series[0].data.length
|
||||||
|
|
||||||
/* c will contain values from 0 to 1 representing the time */
|
/* c will contain values from 0 to 1 representing the time */
|
||||||
|
let data = null
|
||||||
const x = [], y = [], c = []
|
const x = [], y = [], c = []
|
||||||
|
|
||||||
if (flopsAny && memBw) {
|
if (flopsAny && memBw) {
|
||||||
@ -383,24 +387,23 @@ export function transformDataForRoofline(flopsAny, memBw, renderTime) { // Uses
|
|||||||
|
|
||||||
x.push(intensity)
|
x.push(intensity)
|
||||||
y.push(f)
|
y.push(f)
|
||||||
c.push(renderTime ? j / timesteps : 0)
|
c.push(j / timesteps)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.warn("transformData: metrics for 'mem_bw' and/or 'flops_any' missing!")
|
console.warn("transformData: metrics for 'mem_bw' and/or 'flops_any' missing!")
|
||||||
}
|
}
|
||||||
|
if (x.length > 0 && y.length > 0 && c.length > 0) {
|
||||||
return {
|
data = [null, [x, y], c] // for dataformat see roofline.svelte
|
||||||
x, y, c,
|
|
||||||
xLabel: 'Intensity [FLOPS/byte]',
|
|
||||||
yLabel: 'Performance [GFLOPS]'
|
|
||||||
}
|
}
|
||||||
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return something to be plotted. The argument shall be the result of the
|
// Return something to be plotted. The argument shall be the result of the
|
||||||
// `nodeMetrics` GraphQL query.
|
// `nodeMetrics` GraphQL query.
|
||||||
export function transformPerNodeDataForRoofline(nodes) {
|
export function transformPerNodeDataForRoofline(nodes) {
|
||||||
const x = [], y = [], c = []
|
let data = null
|
||||||
|
const x = [], y = []
|
||||||
for (let node of nodes) {
|
for (let node of nodes) {
|
||||||
let flopsAny = node.metrics.find(m => m.name == 'flops_any' && m.scope == 'node')?.metric
|
let flopsAny = node.metrics.find(m => m.name == 'flops_any' && m.scope == 'node')?.metric
|
||||||
let memBw = node.metrics.find(m => m.name == 'mem_bw' && m.scope == 'node')?.metric
|
let memBw = node.metrics.find(m => m.name == 'mem_bw' && m.scope == 'node')?.metric
|
||||||
@ -417,12 +420,21 @@ export function transformPerNodeDataForRoofline(nodes) {
|
|||||||
|
|
||||||
x.push(intensity)
|
x.push(intensity)
|
||||||
y.push(f)
|
y.push(f)
|
||||||
c.push(0)
|
|
||||||
}
|
}
|
||||||
|
if (x.length > 0 && y.length > 0) {
|
||||||
return {
|
data = [null, [x, y], []] // for dataformat see roofline.svelte
|
||||||
x, y, c,
|
|
||||||
xLabel: 'Intensity [FLOPS/byte]',
|
|
||||||
yLabel: 'Performance [GFLOPS]'
|
|
||||||
}
|
}
|
||||||
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://stackoverflow.com/questions/45309447/calculating-median-javascript
|
||||||
|
// function median(numbers) {
|
||||||
|
// const sorted = Array.from(numbers).sort((a, b) => a - b);
|
||||||
|
// const middle = Math.floor(sorted.length / 2);
|
||||||
|
|
||||||
|
// if (sorted.length % 2 === 0) {
|
||||||
|
// return (sorted[middle - 1] + sorted[middle]) / 2;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return sorted[middle];
|
||||||
|
// }
|
||||||
|
Loading…
Reference in New Issue
Block a user