mirror of
https://github.com/ClusterCockpit/cc-backend
synced 2024-12-26 13:29:05 +01:00
Render loglog scatter, fix data format, start draw
This commit is contained in:
parent
c1b5134627
commit
8d7f942de4
@ -672,6 +672,7 @@
|
|||||||
<div bind:clientWidth={colWidth3}>
|
<div bind:clientWidth={colWidth3}>
|
||||||
<Rooflineuplot
|
<Rooflineuplot
|
||||||
width={colWidth3 - 25}
|
width={colWidth3 - 25}
|
||||||
|
cluster={$initq.data.clusters.find((c) => c.name == cluster).subClusters[0]}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
export let flopsAny = null
|
export let flopsAny = null
|
||||||
export let memBw = null
|
export let memBw = null
|
||||||
export let cluster = null
|
export let cluster = null
|
||||||
export let maxY = null
|
export let defaultMaxY = null
|
||||||
export let width = 500
|
export let width = 500
|
||||||
export let height = 300
|
export let height = 300
|
||||||
export let tiles = null
|
export let tiles = null
|
||||||
@ -19,6 +19,11 @@
|
|||||||
let uplot = null
|
let uplot = null
|
||||||
let timeoutId = null
|
let timeoutId = null
|
||||||
|
|
||||||
|
const paddingLeft = 40,
|
||||||
|
paddingRight = 10,
|
||||||
|
paddingTop = 10,
|
||||||
|
paddingBottom = 50
|
||||||
|
|
||||||
// Three Render-Cases:
|
// Three Render-Cases:
|
||||||
// #1 Single-Job Roofline -> Has Time-Information: Use data, allow colorDots && showTime
|
// #1 Single-Job Roofline -> Has Time-Information: Use data, allow colorDots && showTime
|
||||||
// #2 MultiNode Roofline - > Has No Time-Information: Transform from nodeData, only "IST"-state of nodes, no timeInfo
|
// #2 MultiNode Roofline - > Has No Time-Information: Transform from nodeData, only "IST"-state of nodes, no timeInfo
|
||||||
@ -30,6 +35,14 @@
|
|||||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
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) {
|
function filledArr(len, val) {
|
||||||
let arr = Array(len);
|
let arr = Array(len);
|
||||||
|
|
||||||
@ -45,26 +58,42 @@
|
|||||||
return arr;
|
return arr;
|
||||||
}
|
}
|
||||||
|
|
||||||
let points = 100;
|
let points = 1000;
|
||||||
let series = 2;
|
|
||||||
let time = []
|
|
||||||
|
|
||||||
for (let i = 0; i < points; ++i)
|
data = [null, [], []] // Null-Axis required for scatter
|
||||||
time[i] = i;
|
data[1][0] = filledArr(points, i => randFloat(1,5000)) // Intensity
|
||||||
|
data[1][1] = filledArr(points, i => randFloat(1,5000)) // Performance
|
||||||
data = filledArr(series, v => [
|
data[2] = filledArr(points, i => 0) // Time Information (Optional)
|
||||||
filledArr(points, i => randInt(0,200)),
|
|
||||||
filledArr(points, i => randInt(0,200)),
|
|
||||||
]);
|
|
||||||
|
|
||||||
data[0] = null;
|
|
||||||
|
|
||||||
|
console.log("Subcluster: ", cluster);
|
||||||
console.log("Data: ", data);
|
console.log("Data: ", data);
|
||||||
|
|
||||||
// End Demo Data
|
// End Demo Data
|
||||||
|
|
||||||
// Helpers
|
// Helpers
|
||||||
|
|
||||||
|
const [minX, maxX, minY, maxY] = [0.01, 1000, 1., cluster?.flopRateSimd?.value || defaultMaxY]
|
||||||
|
|
||||||
|
const w = width - paddingLeft - paddingRight
|
||||||
|
|
||||||
|
const h = height - paddingTop - paddingBottom
|
||||||
|
|
||||||
|
const [log10minX, log10maxX, log10minY, log10maxY] =
|
||||||
|
[Math.log10(minX), Math.log10(maxX), Math.log10(minY), Math.log10(maxY)]
|
||||||
|
|
||||||
|
const getCanvasX = (x) => {
|
||||||
|
x = Math.log10(x)
|
||||||
|
x -= log10minX; x /= (log10maxX - log10minX)
|
||||||
|
return Math.round((x * w) + paddingLeft)
|
||||||
|
}
|
||||||
|
|
||||||
|
const getCanvasY = (y) => {
|
||||||
|
y = Math.log10(y)
|
||||||
|
y -= log10minY
|
||||||
|
y /= (log10maxY - log10minY)
|
||||||
|
return Math.round((h - y * h) + paddingTop)
|
||||||
|
}
|
||||||
|
|
||||||
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
|
||||||
@ -90,6 +119,78 @@
|
|||||||
return `rgb(${getGradientR(c)}, ${getGradientG(c)}, ${getGradientB(c)})`
|
return `rgb(${getGradientR(c)}, ${getGradientG(c)}, ${getGradientB(c)})`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function lineIntersect(x1, y1, x2, y2, x3, y3, x4, y4) {
|
||||||
|
let l = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1)
|
||||||
|
let a = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / l
|
||||||
|
return {
|
||||||
|
x: x1 + a * (x2 - x1),
|
||||||
|
y: y1 + a * (y2 - y1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function transformData(flopsAny, memBw, colorDots) { // Uses Metric Object
|
||||||
|
const nodes = flopsAny.series.length
|
||||||
|
const timesteps = flopsAny.series[0].data.length
|
||||||
|
|
||||||
|
/* c will contain values from 0 to 1 representing the time */
|
||||||
|
const x = [], y = [], c = []
|
||||||
|
|
||||||
|
if (flopsAny && memBw) {
|
||||||
|
for (let i = 0; i < nodes; i++) {
|
||||||
|
const flopsData = flopsAny.series[i].data
|
||||||
|
const memBwData = memBw.series[i].data
|
||||||
|
for (let j = 0; j < timesteps; j++) {
|
||||||
|
const f = flopsData[j], m = memBwData[j]
|
||||||
|
const intensity = f / m
|
||||||
|
if (Number.isNaN(intensity) || !Number.isFinite(intensity))
|
||||||
|
continue
|
||||||
|
|
||||||
|
x.push(intensity)
|
||||||
|
y.push(f)
|
||||||
|
c.push(colorDots ? j / timesteps : 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.warn("transformData: metrics for 'mem_bw' and/or 'flops_any' missing!")
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
x, y, c,
|
||||||
|
xLabel: 'Intensity [FLOPS/byte]',
|
||||||
|
yLabel: 'Performance [GFLOPS]'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return something to be plotted. The argument shall be the result of the
|
||||||
|
// `nodeMetrics` GraphQL query.
|
||||||
|
export function transformPerNodeData(nodes) {
|
||||||
|
const x = [], y = [], c = []
|
||||||
|
for (let node of nodes) {
|
||||||
|
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
|
||||||
|
if (!flopsAny || !memBw) {
|
||||||
|
console.warn("transformPerNodeData: metrics for 'mem_bw' and/or 'flops_any' missing!")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
let flopsData = flopsAny.series[0].data, memBwData = memBw.series[0].data
|
||||||
|
const f = flopsData[flopsData.length - 1], m = memBwData[flopsData.length - 1]
|
||||||
|
const intensity = f / m
|
||||||
|
if (Number.isNaN(intensity) || !Number.isFinite(intensity))
|
||||||
|
continue
|
||||||
|
|
||||||
|
x.push(intensity)
|
||||||
|
y.push(f)
|
||||||
|
c.push(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
x, y, c,
|
||||||
|
xLabel: 'Intensity [FLOPS/byte]',
|
||||||
|
yLabel: 'Performance [GFLOPS]'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// End Helpers
|
// End Helpers
|
||||||
|
|
||||||
const drawPoints = (u, seriesIdx, idx0, idx1) => {
|
const drawPoints = (u, seriesIdx, idx0, idx1) => {
|
||||||
@ -123,22 +224,6 @@
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
function guardedRange(u, min, max) {
|
|
||||||
if (max == min) {
|
|
||||||
if (min == null) {
|
|
||||||
min = 0;
|
|
||||||
max = 100;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
let delta = Math.abs(max) || 100;
|
|
||||||
max += delta;
|
|
||||||
min -= delta;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return [min, max];
|
|
||||||
}
|
|
||||||
|
|
||||||
function render() {
|
function render() {
|
||||||
const opts = {
|
const opts = {
|
||||||
title: "",
|
title: "",
|
||||||
@ -146,8 +231,39 @@
|
|||||||
width: width,
|
width: width,
|
||||||
height: height,
|
height: height,
|
||||||
legend: {
|
legend: {
|
||||||
live: false,
|
show: false
|
||||||
},
|
},
|
||||||
|
axes: [
|
||||||
|
{
|
||||||
|
label: 'Intensity [FLOPS/Byte]'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Performace [GFLOPS]'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
scales: {
|
||||||
|
x: {
|
||||||
|
time: false,
|
||||||
|
distr: 3,
|
||||||
|
log: 10,
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
distr: 3,
|
||||||
|
log: 10,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
stroke: (u, seriesIdx) => {
|
||||||
|
for (let i = 0; i < points; ++i) { return getRGB(data[2][i]) }
|
||||||
|
},
|
||||||
|
fill: (u, seriesIdx) => {
|
||||||
|
for (let i = 0; i < points; ++i) { return getRGB(data[2][i]) }
|
||||||
|
},
|
||||||
|
paths: drawPoints,
|
||||||
|
}
|
||||||
|
],
|
||||||
hooks: {
|
hooks: {
|
||||||
drawClear: [
|
drawClear: [
|
||||||
u => {
|
u => {
|
||||||
@ -157,48 +273,51 @@
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
draw: [
|
||||||
scales: {
|
u => { // draw roofs
|
||||||
x: {
|
u.ctx.strokeStyle = 'black'
|
||||||
time: false,
|
u.ctx.lineWidth = 2
|
||||||
// auto: false,
|
u.ctx.beginPath()
|
||||||
// range: [0, 500],
|
if (cluster != null) {
|
||||||
// remove any scale padding, use raw data limits
|
const ycut = 0.01 * cluster.memoryBandwidth.value
|
||||||
range: guardedRange,
|
const scalarKnee = (cluster.flopRateScalar.value - ycut) / cluster.memoryBandwidth.value
|
||||||
},
|
const simdKnee = (cluster.flopRateSimd.value - ycut) / cluster.memoryBandwidth.value
|
||||||
y: {
|
const scalarKneeX = getCanvasX(scalarKnee),
|
||||||
// auto: false,
|
simdKneeX = getCanvasX(simdKnee),
|
||||||
// range: [0, 500],
|
flopRateScalarY = getCanvasY(cluster.flopRateScalar.value),
|
||||||
// remove any scale padding, use raw data limits
|
flopRateSimdY = getCanvasY(cluster.flopRateSimd.value)
|
||||||
range: guardedRange,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
series: [
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
stroke: "red",
|
|
||||||
fill: "rgba(255,0,0,0.1)",
|
|
||||||
paths: (u, seriesIdx, idx0, idx1) => {
|
|
||||||
uPlot.orient(u, seriesIdx, (series, dataX, dataY, scaleX, scaleY, valToPosX, valToPosY, xOff, yOff, xDim, yDim) => {
|
|
||||||
let d = u.data[seriesIdx];
|
|
||||||
|
|
||||||
console.log(d);
|
if (scalarKneeX < width - paddingRight) {
|
||||||
});
|
u.ctx.moveTo(scalarKneeX, flopRateScalarY)
|
||||||
|
u.ctx.lineTo(width - paddingRight, flopRateScalarY)
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
if (simdKneeX < width - paddingRight) {
|
||||||
},
|
u.ctx.moveTo(simdKneeX, flopRateSimdY)
|
||||||
*/
|
u.ctx.lineTo(width - paddingRight, flopRateSimdY)
|
||||||
},
|
}
|
||||||
{
|
|
||||||
stroke: (u, seriesIdx) => {
|
let x1 = getCanvasX(0.01),
|
||||||
for (let i = 0; i < points; ++i) { return getRGB(time[i]) }
|
y1 = getCanvasY(ycut),
|
||||||
},
|
x2 = getCanvasX(simdKnee),
|
||||||
fill: (u, seriesIdx) => {
|
y2 = flopRateSimdY
|
||||||
for (let i = 0; i < points; ++i) { return getRGB(time[i]) }
|
|
||||||
},
|
let xAxisIntersect = lineIntersect(
|
||||||
paths: drawPoints,
|
x1, y1, x2, y2,
|
||||||
}
|
0, height - paddingBottom, width, height - paddingBottom)
|
||||||
],
|
|
||||||
|
if (xAxisIntersect.x > x1) {
|
||||||
|
x1 = xAxisIntersect.x
|
||||||
|
y1 = xAxisIntersect.y
|
||||||
|
}
|
||||||
|
|
||||||
|
u.ctx.moveTo(x1, y1)
|
||||||
|
u.ctx.lineTo(x2, y2)
|
||||||
|
}
|
||||||
|
u.ctx.stroke()
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
uplot = new uPlot(opts, data, plotWrapper);
|
uplot = new uPlot(opts, data, plotWrapper);
|
||||||
|
Loading…
Reference in New Issue
Block a user