diff --git a/web/frontend/src/Status.root.svelte b/web/frontend/src/Status.root.svelte
index 1559121..df5eb71 100644
--- a/web/frontend/src/Status.root.svelte
+++ b/web/frontend/src/Status.root.svelte
@@ -672,6 +672,7 @@
c.name == cluster).subClusters[0]}
/>
diff --git a/web/frontend/src/plots/Rooflineuplot.svelte b/web/frontend/src/plots/Rooflineuplot.svelte
index e1f9c3c..4defe68 100644
--- a/web/frontend/src/plots/Rooflineuplot.svelte
+++ b/web/frontend/src/plots/Rooflineuplot.svelte
@@ -7,7 +7,7 @@
export let flopsAny = null
export let memBw = null
export let cluster = null
- export let maxY = null
+ export let defaultMaxY = null
export let width = 500
export let height = 300
export let tiles = null
@@ -19,6 +19,11 @@
let uplot = null
let timeoutId = null
+ const paddingLeft = 40,
+ paddingRight = 10,
+ paddingTop = 10,
+ paddingBottom = 50
+
// Three Render-Cases:
// #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
@@ -30,6 +35,14 @@
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) {
let arr = Array(len);
@@ -45,26 +58,42 @@
return arr;
}
- let points = 100;
- let series = 2;
- let time = []
+ let points = 1000;
- for (let i = 0; i < points; ++i)
- time[i] = i;
-
- data = filledArr(series, v => [
- filledArr(points, i => randInt(0,200)),
- filledArr(points, i => randInt(0,200)),
- ]);
-
- data[0] = null;
+ data = [null, [], []] // Null-Axis required for scatter
+ data[1][0] = filledArr(points, i => randFloat(1,5000)) // Intensity
+ data[1][1] = filledArr(points, i => randFloat(1,5000)) // Performance
+ data[2] = filledArr(points, i => 0) // Time Information (Optional)
+ console.log("Subcluster: ", cluster);
console.log("Data: ", data);
// End Demo Data
// 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) {
if (x < 0.5) return 0
if (x > 0.75) return 255
@@ -90,6 +119,78 @@
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
const drawPoints = (u, seriesIdx, idx0, idx1) => {
@@ -123,22 +224,6 @@
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() {
const opts = {
title: "",
@@ -146,8 +231,39 @@
width: width,
height: height,
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: {
drawClear: [
u => {
@@ -157,48 +273,51 @@
});
},
],
- },
- scales: {
- x: {
- time: false,
- // auto: false,
- // range: [0, 500],
- // remove any scale padding, use raw data limits
- range: guardedRange,
- },
- y: {
- // auto: false,
- // range: [0, 500],
- // remove any scale padding, use raw data limits
- 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];
+ draw: [
+ u => { // draw roofs
+ u.ctx.strokeStyle = 'black'
+ u.ctx.lineWidth = 2
+ u.ctx.beginPath()
+ if (cluster != null) {
+ const ycut = 0.01 * cluster.memoryBandwidth.value
+ const scalarKnee = (cluster.flopRateScalar.value - ycut) / cluster.memoryBandwidth.value
+ const simdKnee = (cluster.flopRateSimd.value - ycut) / cluster.memoryBandwidth.value
+ const scalarKneeX = getCanvasX(scalarKnee),
+ simdKneeX = getCanvasX(simdKnee),
+ flopRateScalarY = getCanvasY(cluster.flopRateScalar.value),
+ flopRateSimdY = getCanvasY(cluster.flopRateSimd.value)
- console.log(d);
- });
+ if (scalarKneeX < width - paddingRight) {
+ u.ctx.moveTo(scalarKneeX, flopRateScalarY)
+ u.ctx.lineTo(width - paddingRight, flopRateScalarY)
+ }
- return null;
- },
- */
- },
- {
- stroke: (u, seriesIdx) => {
- for (let i = 0; i < points; ++i) { return getRGB(time[i]) }
- },
- fill: (u, seriesIdx) => {
- for (let i = 0; i < points; ++i) { return getRGB(time[i]) }
- },
- paths: drawPoints,
- }
- ],
+ if (simdKneeX < width - paddingRight) {
+ u.ctx.moveTo(simdKneeX, flopRateSimdY)
+ u.ctx.lineTo(width - paddingRight, flopRateSimdY)
+ }
+
+ let x1 = getCanvasX(0.01),
+ y1 = getCanvasY(ycut),
+ x2 = getCanvasX(simdKnee),
+ y2 = flopRateSimdY
+
+ let xAxisIntersect = lineIntersect(
+ 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);