mirror of
https://github.com/ClusterCockpit/cc-backend
synced 2025-07-26 22:26:08 +02:00
Merge branch 'hotfix' of https://github.com/ClusterCockpit/cc-backend into hotfix
This commit is contained in:
@@ -5,221 +5,222 @@
|
||||
-->
|
||||
|
||||
<script>
|
||||
import uPlot from 'uplot'
|
||||
import { formatNumber } from '../units.js'
|
||||
import { onMount, onDestroy } from 'svelte'
|
||||
import { Card } from 'sveltestrap'
|
||||
import uPlot from "uplot";
|
||||
import { formatNumber } from "../units.js";
|
||||
import { onMount, onDestroy } from "svelte";
|
||||
import { Card } from "@sveltestrap/sveltestrap";
|
||||
|
||||
export let data
|
||||
export let usesBins = false
|
||||
export let width = 500
|
||||
export let height = 300
|
||||
export let title = ''
|
||||
export let xlabel = ''
|
||||
export let xunit = 'X'
|
||||
export let ylabel = ''
|
||||
export let yunit = 'Y'
|
||||
export let data;
|
||||
export let usesBins = false;
|
||||
export let width = 500;
|
||||
export let height = 300;
|
||||
export let title = "";
|
||||
export let xlabel = "";
|
||||
export let xunit = "X";
|
||||
export let ylabel = "";
|
||||
export let yunit = "Y";
|
||||
|
||||
const { bars } = uPlot.paths
|
||||
const { bars } = uPlot.paths;
|
||||
|
||||
const drawStyles = {
|
||||
bars: 1,
|
||||
points: 2,
|
||||
const drawStyles = {
|
||||
bars: 1,
|
||||
points: 2,
|
||||
};
|
||||
|
||||
function paths(u, seriesIdx, idx0, idx1, extendGap, buildClip) {
|
||||
let s = u.series[seriesIdx];
|
||||
let style = s.drawStyle;
|
||||
|
||||
let renderer = // If bars to wide, change here
|
||||
style == drawStyles.bars ? bars({ size: [0.75, 100] }) : () => null;
|
||||
|
||||
return renderer(u, seriesIdx, idx0, idx1, extendGap, buildClip);
|
||||
}
|
||||
|
||||
// converts the legend into a simple tooltip
|
||||
function legendAsTooltipPlugin({
|
||||
className,
|
||||
style = { backgroundColor: "rgba(255, 249, 196, 0.92)", color: "black" },
|
||||
} = {}) {
|
||||
let legendEl;
|
||||
|
||||
function init(u, opts) {
|
||||
legendEl = u.root.querySelector(".u-legend");
|
||||
|
||||
legendEl.classList.remove("u-inline");
|
||||
className && legendEl.classList.add(className);
|
||||
|
||||
uPlot.assign(legendEl.style, {
|
||||
textAlign: "left",
|
||||
pointerEvents: "none",
|
||||
display: "none",
|
||||
position: "absolute",
|
||||
left: 0,
|
||||
top: 0,
|
||||
zIndex: 100,
|
||||
boxShadow: "2px 2px 10px rgba(0,0,0,0.5)",
|
||||
...style,
|
||||
});
|
||||
|
||||
// hide series color markers
|
||||
const idents = legendEl.querySelectorAll(".u-marker");
|
||||
|
||||
for (let i = 0; i < idents.length; i++) idents[i].style.display = "none";
|
||||
|
||||
const overEl = u.over;
|
||||
overEl.style.overflow = "visible";
|
||||
|
||||
// move legend into plot bounds
|
||||
overEl.appendChild(legendEl);
|
||||
|
||||
// show/hide tooltip on enter/exit
|
||||
overEl.addEventListener("mouseenter", () => {
|
||||
legendEl.style.display = null;
|
||||
});
|
||||
overEl.addEventListener("mouseleave", () => {
|
||||
legendEl.style.display = "none";
|
||||
});
|
||||
|
||||
// let tooltip exit plot
|
||||
// overEl.style.overflow = "visible";
|
||||
}
|
||||
|
||||
function update(u) {
|
||||
const { left, top } = u.cursor;
|
||||
legendEl.style.transform =
|
||||
"translate(" + (left + 15) + "px, " + (top + 15) + "px)";
|
||||
}
|
||||
|
||||
return {
|
||||
hooks: {
|
||||
init: init,
|
||||
setCursor: update,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
let plotWrapper = null;
|
||||
let uplot = null;
|
||||
let timeoutId = null;
|
||||
|
||||
function render() {
|
||||
let opts = {
|
||||
width: width,
|
||||
height: height,
|
||||
title: title,
|
||||
plugins: [legendAsTooltipPlugin()],
|
||||
cursor: {
|
||||
points: {
|
||||
size: (u, seriesIdx) => u.series[seriesIdx].points.size * 2.5,
|
||||
width: (u, seriesIdx, size) => size / 4,
|
||||
stroke: (u, seriesIdx) =>
|
||||
u.series[seriesIdx].points.stroke(u, seriesIdx) + "90",
|
||||
fill: (u, seriesIdx) => "#fff",
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
time: false,
|
||||
},
|
||||
},
|
||||
axes: [
|
||||
{
|
||||
stroke: "#000000",
|
||||
// scale: 'x',
|
||||
label: xlabel,
|
||||
labelGap: 10,
|
||||
size: 25,
|
||||
incrs: [1, 2, 5, 6, 10, 12, 50, 100, 500, 1000, 5000, 10000],
|
||||
border: {
|
||||
show: true,
|
||||
stroke: "#000000",
|
||||
},
|
||||
ticks: {
|
||||
width: 1 / devicePixelRatio,
|
||||
size: 5 / devicePixelRatio,
|
||||
stroke: "#000000",
|
||||
},
|
||||
values: (_, t) => t.map((v) => formatNumber(v)),
|
||||
},
|
||||
{
|
||||
stroke: "#000000",
|
||||
// scale: 'y',
|
||||
label: ylabel,
|
||||
labelGap: 10,
|
||||
size: 35,
|
||||
border: {
|
||||
show: true,
|
||||
stroke: "#000000",
|
||||
},
|
||||
ticks: {
|
||||
width: 1 / devicePixelRatio,
|
||||
size: 5 / devicePixelRatio,
|
||||
stroke: "#000000",
|
||||
},
|
||||
values: (_, t) => t.map((v) => formatNumber(v)),
|
||||
},
|
||||
],
|
||||
series: [
|
||||
{
|
||||
label: xunit !== "" ? xunit : null,
|
||||
value: (u, ts, sidx, didx) => {
|
||||
if (usesBins) {
|
||||
const min = u.data[sidx][didx - 1] ? u.data[sidx][didx - 1] : 0;
|
||||
const max = u.data[sidx][didx];
|
||||
ts = min + " - " + max; // narrow spaces
|
||||
}
|
||||
return ts;
|
||||
},
|
||||
},
|
||||
Object.assign(
|
||||
{
|
||||
label: yunit !== "" ? yunit : null,
|
||||
width: 1 / devicePixelRatio,
|
||||
drawStyle: drawStyles.points,
|
||||
lineInterpolation: null,
|
||||
paths,
|
||||
},
|
||||
{
|
||||
drawStyle: drawStyles.bars,
|
||||
lineInterpolation: null,
|
||||
stroke: "#85abce",
|
||||
fill: "#85abce", // + "1A", // Transparent Fill
|
||||
},
|
||||
),
|
||||
],
|
||||
};
|
||||
|
||||
function paths(u, seriesIdx, idx0, idx1, extendGap, buildClip) {
|
||||
let s = u.series[seriesIdx];
|
||||
let style = s.drawStyle;
|
||||
uplot = new uPlot(opts, data, plotWrapper);
|
||||
}
|
||||
|
||||
let renderer = ( // If bars to wide, change here
|
||||
style == drawStyles.bars ? (
|
||||
bars({size: [0.75, 100]})
|
||||
) :
|
||||
() => null
|
||||
)
|
||||
onMount(() => {
|
||||
render();
|
||||
});
|
||||
|
||||
return renderer(u, seriesIdx, idx0, idx1, extendGap, buildClip);
|
||||
}
|
||||
onDestroy(() => {
|
||||
if (uplot) uplot.destroy();
|
||||
|
||||
// converts the legend into a simple tooltip
|
||||
function legendAsTooltipPlugin({ className, style = { backgroundColor:"rgba(255, 249, 196, 0.92)", color: "black" } } = {}) {
|
||||
let legendEl;
|
||||
if (timeoutId != null) clearTimeout(timeoutId);
|
||||
});
|
||||
|
||||
function init(u, opts) {
|
||||
legendEl = u.root.querySelector(".u-legend");
|
||||
function sizeChanged() {
|
||||
if (timeoutId != null) clearTimeout(timeoutId);
|
||||
|
||||
legendEl.classList.remove("u-inline");
|
||||
className && legendEl.classList.add(className);
|
||||
timeoutId = setTimeout(() => {
|
||||
timeoutId = null;
|
||||
if (uplot) uplot.destroy();
|
||||
|
||||
uPlot.assign(legendEl.style, {
|
||||
textAlign: "left",
|
||||
pointerEvents: "none",
|
||||
display: "none",
|
||||
position: "absolute",
|
||||
left: 0,
|
||||
top: 0,
|
||||
zIndex: 100,
|
||||
boxShadow: "2px 2px 10px rgba(0,0,0,0.5)",
|
||||
...style
|
||||
});
|
||||
render();
|
||||
}, 200);
|
||||
}
|
||||
|
||||
// hide series color markers
|
||||
const idents = legendEl.querySelectorAll(".u-marker");
|
||||
|
||||
for (let i = 0; i < idents.length; i++)
|
||||
idents[i].style.display = "none";
|
||||
|
||||
const overEl = u.over;
|
||||
overEl.style.overflow = "visible";
|
||||
|
||||
// move legend into plot bounds
|
||||
overEl.appendChild(legendEl);
|
||||
|
||||
// show/hide tooltip on enter/exit
|
||||
overEl.addEventListener("mouseenter", () => {legendEl.style.display = null;});
|
||||
overEl.addEventListener("mouseleave", () => {legendEl.style.display = "none";});
|
||||
|
||||
// let tooltip exit plot
|
||||
// overEl.style.overflow = "visible";
|
||||
}
|
||||
|
||||
function update(u) {
|
||||
const { left, top } = u.cursor;
|
||||
legendEl.style.transform = "translate(" + (left + 15) + "px, " + (top + 15) + "px)";
|
||||
}
|
||||
|
||||
return {
|
||||
hooks: {
|
||||
init: init,
|
||||
setCursor: update,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let plotWrapper = null
|
||||
let uplot = null
|
||||
let timeoutId = null
|
||||
|
||||
function render() {
|
||||
let opts = {
|
||||
width: width,
|
||||
height: height,
|
||||
title: title,
|
||||
plugins: [
|
||||
legendAsTooltipPlugin()
|
||||
],
|
||||
cursor: {
|
||||
points: {
|
||||
size: (u, seriesIdx) => u.series[seriesIdx].points.size * 2.5,
|
||||
width: (u, seriesIdx, size) => size / 4,
|
||||
stroke: (u, seriesIdx) => u.series[seriesIdx].points.stroke(u, seriesIdx) + '90',
|
||||
fill: (u, seriesIdx) => "#fff",
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
time: false
|
||||
},
|
||||
},
|
||||
axes: [
|
||||
{
|
||||
stroke: "#000000",
|
||||
// scale: 'x',
|
||||
label: xlabel,
|
||||
labelGap: 10,
|
||||
size: 25,
|
||||
incrs: [1, 2, 5, 6, 10, 12, 50, 100, 500, 1000, 5000, 10000],
|
||||
border: {
|
||||
show: true,
|
||||
stroke: "#000000",
|
||||
},
|
||||
ticks: {
|
||||
width: 1 / devicePixelRatio,
|
||||
size: 5 / devicePixelRatio,
|
||||
stroke: "#000000",
|
||||
},
|
||||
values: (_, t) => t.map(v => formatNumber(v)),
|
||||
},
|
||||
{
|
||||
stroke: "#000000",
|
||||
// scale: 'y',
|
||||
label: ylabel,
|
||||
labelGap: 10,
|
||||
size: 35,
|
||||
border: {
|
||||
show: true,
|
||||
stroke: "#000000",
|
||||
},
|
||||
ticks: {
|
||||
width: 1 / devicePixelRatio,
|
||||
size: 5 / devicePixelRatio,
|
||||
stroke: "#000000",
|
||||
},
|
||||
values: (_, t) => t.map(v => formatNumber(v)),
|
||||
},
|
||||
],
|
||||
series: [
|
||||
{
|
||||
label: xunit !== '' ? xunit : null,
|
||||
value: (u, ts, sidx, didx) => {
|
||||
if (usesBins) {
|
||||
const min = u.data[sidx][didx - 1] ? u.data[sidx][didx - 1] : 0
|
||||
const max = u.data[sidx][didx]
|
||||
ts = min + ' - ' + max // narrow spaces
|
||||
}
|
||||
return ts
|
||||
}
|
||||
},
|
||||
Object.assign({
|
||||
label: yunit !== '' ? yunit : null,
|
||||
width: 1 / devicePixelRatio,
|
||||
drawStyle: drawStyles.points,
|
||||
lineInterpolation: null,
|
||||
paths,
|
||||
}, {
|
||||
drawStyle: drawStyles.bars,
|
||||
lineInterpolation: null,
|
||||
stroke: "#85abce",
|
||||
fill: "#85abce", // + "1A", // Transparent Fill
|
||||
}),
|
||||
]
|
||||
};
|
||||
|
||||
uplot = new uPlot(opts, data, plotWrapper)
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
render()
|
||||
})
|
||||
|
||||
onDestroy(() => {
|
||||
if (uplot)
|
||||
uplot.destroy()
|
||||
|
||||
if (timeoutId != null)
|
||||
clearTimeout(timeoutId)
|
||||
})
|
||||
|
||||
function sizeChanged() {
|
||||
if (timeoutId != null)
|
||||
clearTimeout(timeoutId)
|
||||
|
||||
timeoutId = setTimeout(() => {
|
||||
timeoutId = null
|
||||
if (uplot)
|
||||
uplot.destroy()
|
||||
|
||||
render()
|
||||
}, 200)
|
||||
}
|
||||
|
||||
$: sizeChanged(width, height)
|
||||
$: sizeChanged(width, height);
|
||||
</script>
|
||||
|
||||
{#if data.length > 0}
|
||||
<div bind:this={plotWrapper}/>
|
||||
<div bind:this={plotWrapper} />
|
||||
{:else}
|
||||
<Card class="mx-4" body color="warning">Cannot render histogram: No data!</Card>
|
||||
<Card class="mx-4" body color="warning"
|
||||
>Cannot render histogram: No data!</Card
|
||||
>
|
||||
{/if}
|
||||
|
||||
|
||||
|
@@ -1,3 +1,121 @@
|
||||
<script context="module">
|
||||
export function formatTime(t, forNode = false) {
|
||||
if (t !== null) {
|
||||
if (isNaN(t)) {
|
||||
return t;
|
||||
} else {
|
||||
const tAbs = Math.abs(t);
|
||||
const h = Math.floor(tAbs / 3600);
|
||||
const m = Math.floor((tAbs % 3600) / 60);
|
||||
// Re-Add "negativity" to time ticks only as string, so that if-cases work as intended
|
||||
if (h == 0) return `${forNode && m != 0 ? "-" : ""}${m}m`;
|
||||
else if (m == 0) return `${forNode ? "-" : ""}${h}h`;
|
||||
else return `${forNode ? "-" : ""}${h}:${m}h`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function timeIncrs(timestep, maxX, forNode) {
|
||||
if (forNode === true) {
|
||||
return [60, 300, 900, 1800, 3600, 7200, 14400, 21600]; // forNode fixed increments
|
||||
} else {
|
||||
let incrs = [];
|
||||
for (let t = timestep; t < maxX; t *= 10)
|
||||
incrs.push(t, t * 2, t * 3, t * 5);
|
||||
|
||||
return incrs;
|
||||
}
|
||||
}
|
||||
|
||||
export function findThresholds(
|
||||
metricConfig,
|
||||
scope,
|
||||
subCluster,
|
||||
isShared,
|
||||
hwthreads,
|
||||
) {
|
||||
// console.log('NAME ' + metricConfig.name + ' / SCOPE ' + scope + ' / SUBCLUSTER ' + subCluster.name)
|
||||
if (!metricConfig || !scope || !subCluster) {
|
||||
console.warn("Argument missing for findThresholds!");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (
|
||||
(scope == "node" && isShared == false) ||
|
||||
metricConfig.aggregation == "avg"
|
||||
) {
|
||||
if (metricConfig.subClusters && metricConfig.subClusters.length === 0) {
|
||||
// console.log('subClusterConfigs array empty, use metricConfig defaults')
|
||||
return {
|
||||
normal: metricConfig.normal,
|
||||
caution: metricConfig.caution,
|
||||
alert: metricConfig.alert,
|
||||
peak: metricConfig.peak,
|
||||
};
|
||||
} else if (
|
||||
metricConfig.subClusters &&
|
||||
metricConfig.subClusters.length > 0
|
||||
) {
|
||||
// console.log('subClusterConfigs found, use subCluster Settings if matching jobs subcluster:')
|
||||
let forSubCluster = metricConfig.subClusters.find(
|
||||
(sc) => sc.name == subCluster.name,
|
||||
);
|
||||
if (
|
||||
forSubCluster &&
|
||||
forSubCluster.normal &&
|
||||
forSubCluster.caution &&
|
||||
forSubCluster.alert &&
|
||||
forSubCluster.peak
|
||||
)
|
||||
return forSubCluster;
|
||||
else
|
||||
return {
|
||||
normal: metricConfig.normal,
|
||||
caution: metricConfig.caution,
|
||||
alert: metricConfig.alert,
|
||||
peak: metricConfig.peak,
|
||||
};
|
||||
} else {
|
||||
console.warn("metricConfig.subClusters not found!");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (metricConfig.aggregation != "sum") {
|
||||
console.warn(
|
||||
"Missing or unkown aggregation mode (sum/avg) for metric:",
|
||||
metricConfig,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
let divisor = 1
|
||||
if (isShared == true) { // Shared
|
||||
if (numaccs > 0) divisor = subCluster.topology.accelerators.length / numaccs;
|
||||
else if (numhwthreads > 0) divisor = subCluster.topology.node.length / numhwthreads;
|
||||
}
|
||||
else if (scope == 'socket') divisor = subCluster.topology.socket.length;
|
||||
else if (scope == "core") divisor = subCluster.topology.core.length;
|
||||
else if (scope == "accelerator")
|
||||
divisor = subCluster.topology.accelerators.length;
|
||||
else if (scope == "hwthread") divisor = subCluster.topology.node.length;
|
||||
else {
|
||||
// console.log('TODO: how to calc thresholds for ', scope)
|
||||
return null;
|
||||
}
|
||||
|
||||
let mc =
|
||||
metricConfig?.subClusters?.find((sc) => sc.name == subCluster.name) ||
|
||||
metricConfig;
|
||||
return {
|
||||
peak: mc.peak / divisor,
|
||||
normal: mc.normal / divisor,
|
||||
caution: mc.caution / divisor,
|
||||
alert: mc.alert / divisor,
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<!--
|
||||
@component
|
||||
|
||||
@@ -21,433 +139,407 @@
|
||||
// TODO: Move helper functions to module context?
|
||||
-->
|
||||
<script>
|
||||
import uPlot from 'uplot'
|
||||
import { formatNumber } from '../units.js'
|
||||
import { getContext, onMount, onDestroy } from 'svelte'
|
||||
import { Card } from 'sveltestrap'
|
||||
import uPlot from "uplot";
|
||||
import { formatNumber } from "../units.js";
|
||||
import { getContext, onMount, onDestroy } from "svelte";
|
||||
import { Card } from "@sveltestrap/sveltestrap";
|
||||
|
||||
export let metric
|
||||
export let scope = 'node'
|
||||
export let resources = []
|
||||
export let width
|
||||
export let height
|
||||
export let timestep
|
||||
export let series
|
||||
export let useStatsSeries = null
|
||||
export let statisticsSeries = null
|
||||
export let cluster
|
||||
export let subCluster
|
||||
export let isShared = false
|
||||
export let forNode = false
|
||||
export let numhwthreads = 0
|
||||
export let numaccs = 0
|
||||
export let metric;
|
||||
export let scope = "node";
|
||||
export let resources = [];
|
||||
export let width;
|
||||
export let height;
|
||||
export let timestep;
|
||||
export let series;
|
||||
export let useStatsSeries = null;
|
||||
export let statisticsSeries = null;
|
||||
export let cluster;
|
||||
export let subCluster;
|
||||
export let isShared = false;
|
||||
export let forNode = false;
|
||||
export let hwthreads = 0;
|
||||
|
||||
if (useStatsSeries == null)
|
||||
useStatsSeries = statisticsSeries != null
|
||||
if (useStatsSeries == null) useStatsSeries = statisticsSeries != null;
|
||||
|
||||
if (useStatsSeries == false && series == null)
|
||||
useStatsSeries = true
|
||||
if (useStatsSeries == false && series == null) useStatsSeries = true;
|
||||
|
||||
const metricConfig = getContext('metrics')(cluster, metric)
|
||||
const clusterCockpitConfig = getContext('cc-config')
|
||||
const resizeSleepTime = 250
|
||||
const normalLineColor = '#000000'
|
||||
const lineWidth = clusterCockpitConfig.plot_general_lineWidth / window.devicePixelRatio
|
||||
const lineColors = clusterCockpitConfig.plot_general_colorscheme
|
||||
const backgroundColors = { normal: 'rgba(255, 255, 255, 1.0)', caution: 'rgba(255, 128, 0, 0.3)', alert: 'rgba(255, 0, 0, 0.3)' }
|
||||
const thresholds = findThresholds(metricConfig, scope, typeof subCluster == 'string' ? cluster.subClusters.find(sc => sc.name == subCluster) : subCluster, isShared, numhwthreads, numaccs)
|
||||
const metricConfig = getContext("metrics")(cluster, metric);
|
||||
const clusterCockpitConfig = getContext("cc-config");
|
||||
const resizeSleepTime = 250;
|
||||
const normalLineColor = "#000000";
|
||||
const lineWidth =
|
||||
clusterCockpitConfig.plot_general_lineWidth / window.devicePixelRatio;
|
||||
const lineColors = clusterCockpitConfig.plot_general_colorscheme;
|
||||
const backgroundColors = {
|
||||
normal: "rgba(255, 255, 255, 1.0)",
|
||||
caution: "rgba(255, 128, 0, 0.3)",
|
||||
alert: "rgba(255, 0, 0, 0.3)",
|
||||
};
|
||||
const thresholds = findThresholds(
|
||||
metricConfig,
|
||||
scope,
|
||||
typeof subCluster == "string"
|
||||
? cluster.subClusters.find((sc) => sc.name == subCluster)
|
||||
: subCluster,
|
||||
isShared,
|
||||
hwthreads,
|
||||
);
|
||||
|
||||
// converts the legend into a simple tooltip
|
||||
function legendAsTooltipPlugin({ className, style = { backgroundColor:"rgba(255, 249, 196, 0.92)", color: "black" } } = {}) {
|
||||
let legendEl;
|
||||
const dataSize = series.length
|
||||
// converts the legend into a simple tooltip
|
||||
function legendAsTooltipPlugin({
|
||||
className,
|
||||
style = { backgroundColor: "rgba(255, 249, 196, 0.92)", color: "black" },
|
||||
} = {}) {
|
||||
let legendEl;
|
||||
const dataSize = series.length;
|
||||
|
||||
function init(u, opts) {
|
||||
legendEl = u.root.querySelector(".u-legend");
|
||||
function init(u, opts) {
|
||||
legendEl = u.root.querySelector(".u-legend");
|
||||
|
||||
legendEl.classList.remove("u-inline");
|
||||
className && legendEl.classList.add(className);
|
||||
legendEl.classList.remove("u-inline");
|
||||
className && legendEl.classList.add(className);
|
||||
|
||||
uPlot.assign(legendEl.style, {
|
||||
textAlign: "left",
|
||||
pointerEvents: "none",
|
||||
display: "none",
|
||||
position: "absolute",
|
||||
left: 0,
|
||||
top: 0,
|
||||
zIndex: 100,
|
||||
boxShadow: "2px 2px 10px rgba(0,0,0,0.5)",
|
||||
...style
|
||||
});
|
||||
uPlot.assign(legendEl.style, {
|
||||
textAlign: "left",
|
||||
pointerEvents: "none",
|
||||
display: "none",
|
||||
position: "absolute",
|
||||
left: 0,
|
||||
top: 0,
|
||||
zIndex: 100,
|
||||
boxShadow: "2px 2px 10px rgba(0,0,0,0.5)",
|
||||
...style,
|
||||
});
|
||||
|
||||
// conditional hide series color markers:
|
||||
if (useStatsSeries === true || // Min/Max/Avg Self-Explanatory
|
||||
dataSize === 1 || // Only one Y-Dataseries
|
||||
dataSize > 6 ){ // More than 6 Y-Dataseries
|
||||
const idents = legendEl.querySelectorAll(".u-marker");
|
||||
for (let i = 0; i < idents.length; i++)
|
||||
idents[i].style.display = "none";
|
||||
}
|
||||
// conditional hide series color markers:
|
||||
if (
|
||||
useStatsSeries === true || // Min/Max/Avg Self-Explanatory
|
||||
dataSize === 1 || // Only one Y-Dataseries
|
||||
dataSize > 6
|
||||
) {
|
||||
// More than 6 Y-Dataseries
|
||||
const idents = legendEl.querySelectorAll(".u-marker");
|
||||
for (let i = 0; i < idents.length; i++)
|
||||
idents[i].style.display = "none";
|
||||
}
|
||||
|
||||
const overEl = u.over;
|
||||
overEl.style.overflow = "visible";
|
||||
const overEl = u.over;
|
||||
overEl.style.overflow = "visible";
|
||||
|
||||
// move legend into plot bounds
|
||||
overEl.appendChild(legendEl);
|
||||
// move legend into plot bounds
|
||||
overEl.appendChild(legendEl);
|
||||
|
||||
// show/hide tooltip on enter/exit
|
||||
overEl.addEventListener("mouseenter", () => {legendEl.style.display = null;});
|
||||
overEl.addEventListener("mouseleave", () => {legendEl.style.display = "none";});
|
||||
// show/hide tooltip on enter/exit
|
||||
overEl.addEventListener("mouseenter", () => {
|
||||
legendEl.style.display = null;
|
||||
});
|
||||
overEl.addEventListener("mouseleave", () => {
|
||||
legendEl.style.display = "none";
|
||||
});
|
||||
|
||||
// let tooltip exit plot
|
||||
// overEl.style.overflow = "visible";
|
||||
}
|
||||
|
||||
function update(u) {
|
||||
const { left, top } = u.cursor;
|
||||
const width = u.over.querySelector(".u-legend").offsetWidth;
|
||||
legendEl.style.transform = "translate(" + (left - width - 15) + "px, " + (top + 15) + "px)";
|
||||
}
|
||||
|
||||
if (dataSize <= 12 || useStatsSeries === true) {
|
||||
return {
|
||||
hooks: {
|
||||
init: init,
|
||||
setCursor: update,
|
||||
}
|
||||
}
|
||||
} else { // Setting legend-opts show/live as object with false here will not work ...
|
||||
return {}
|
||||
}
|
||||
// let tooltip exit plot
|
||||
// overEl.style.overflow = "visible";
|
||||
}
|
||||
|
||||
function backgroundColor() {
|
||||
if (clusterCockpitConfig.plot_general_colorBackground == false
|
||||
|| !thresholds
|
||||
|| !(series && series.every(s => s.statistics != null)))
|
||||
return backgroundColors.normal
|
||||
|
||||
let cond = thresholds.alert < thresholds.caution
|
||||
? (a, b) => a <= b
|
||||
: (a, b) => a >= b
|
||||
|
||||
let avg = series.reduce((sum, series) => sum + series.statistics.avg, 0) / series.length
|
||||
|
||||
if (Number.isNaN(avg))
|
||||
return backgroundColors.normal
|
||||
|
||||
if (cond(avg, thresholds.alert))
|
||||
return backgroundColors.alert
|
||||
|
||||
if (cond(avg, thresholds.caution))
|
||||
return backgroundColors.caution
|
||||
|
||||
return backgroundColors.normal
|
||||
function update(u) {
|
||||
const { left, top } = u.cursor;
|
||||
const width = u.over.querySelector(".u-legend").offsetWidth;
|
||||
legendEl.style.transform =
|
||||
"translate(" + (left - width - 15) + "px, " + (top + 15) + "px)";
|
||||
}
|
||||
|
||||
function lineColor(i, n) {
|
||||
if (n >= lineColors.length)
|
||||
return lineColors[i % lineColors.length];
|
||||
else
|
||||
return lineColors[Math.floor((i / n) * lineColors.length)];
|
||||
if (dataSize <= 12 || useStatsSeries === true) {
|
||||
return {
|
||||
hooks: {
|
||||
init: init,
|
||||
setCursor: update,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
// Setting legend-opts show/live as object with false here will not work ...
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
const longestSeries = useStatsSeries
|
||||
? statisticsSeries.mean.length
|
||||
: series.reduce((n, series) => Math.max(n, series.data.length), 0)
|
||||
const maxX = longestSeries * timestep
|
||||
let maxY = null
|
||||
|
||||
if (thresholds !== null) {
|
||||
maxY = useStatsSeries
|
||||
? (statisticsSeries.max.reduce((max, x) => Math.max(max, x), thresholds.normal) || thresholds.normal)
|
||||
: (series.reduce((max, series) => Math.max(max, series.statistics?.max), thresholds.normal) || thresholds.normal)
|
||||
function backgroundColor() {
|
||||
if (
|
||||
clusterCockpitConfig.plot_general_colorBackground == false ||
|
||||
!thresholds ||
|
||||
!(series && series.every((s) => s.statistics != null))
|
||||
)
|
||||
return backgroundColors.normal;
|
||||
|
||||
if (maxY >= (10 * thresholds.peak)) { // Hard y-range render limit if outliers in series data
|
||||
maxY = (10 * thresholds.peak)
|
||||
}
|
||||
let cond =
|
||||
thresholds.alert < thresholds.caution
|
||||
? (a, b) => a <= b
|
||||
: (a, b) => a >= b;
|
||||
|
||||
let avg =
|
||||
series.reduce((sum, series) => sum + series.statistics.avg, 0) /
|
||||
series.length;
|
||||
|
||||
if (Number.isNaN(avg)) return backgroundColors.normal;
|
||||
|
||||
if (cond(avg, thresholds.alert)) return backgroundColors.alert;
|
||||
|
||||
if (cond(avg, thresholds.caution)) return backgroundColors.caution;
|
||||
|
||||
return backgroundColors.normal;
|
||||
}
|
||||
|
||||
function lineColor(i, n) {
|
||||
if (n >= lineColors.length) return lineColors[i % lineColors.length];
|
||||
else return lineColors[Math.floor((i / n) * lineColors.length)];
|
||||
}
|
||||
|
||||
const longestSeries = useStatsSeries
|
||||
? statisticsSeries.mean.length
|
||||
: series.reduce((n, series) => Math.max(n, series.data.length), 0);
|
||||
const maxX = longestSeries * timestep;
|
||||
let maxY = null;
|
||||
|
||||
if (thresholds !== null) {
|
||||
maxY = useStatsSeries
|
||||
? statisticsSeries.max.reduce(
|
||||
(max, x) => Math.max(max, x),
|
||||
thresholds.normal,
|
||||
) || thresholds.normal
|
||||
: series.reduce(
|
||||
(max, series) => Math.max(max, series.statistics?.max),
|
||||
thresholds.normal,
|
||||
) || thresholds.normal;
|
||||
|
||||
if (maxY >= 10 * thresholds.peak) {
|
||||
// Hard y-range render limit if outliers in series data
|
||||
maxY = 10 * thresholds.peak;
|
||||
}
|
||||
}
|
||||
|
||||
const plotSeries = [{label: 'Runtime', value: (u, ts, sidx, didx) => didx == null ? null : formatTime(ts, forNode)}]
|
||||
const plotData = [new Array(longestSeries)]
|
||||
const plotSeries = [
|
||||
{
|
||||
label: "Runtime",
|
||||
value: (u, ts, sidx, didx) =>
|
||||
didx == null ? null : formatTime(ts, forNode),
|
||||
},
|
||||
];
|
||||
const plotData = [new Array(longestSeries)];
|
||||
|
||||
if (forNode === true) {
|
||||
// Negative Timestamp Buildup
|
||||
for (let i = 0; i <= longestSeries; i++) {
|
||||
plotData[0][i] = (longestSeries - i) * timestep * -1;
|
||||
}
|
||||
} else {
|
||||
// Positive Timestamp Buildup
|
||||
for (
|
||||
let j = 0;
|
||||
j < longestSeries;
|
||||
j++ // TODO: Cache/Reuse this array?
|
||||
)
|
||||
plotData[0][j] = j * timestep;
|
||||
}
|
||||
|
||||
let plotBands = undefined;
|
||||
if (useStatsSeries) {
|
||||
plotData.push(statisticsSeries.min);
|
||||
plotData.push(statisticsSeries.max);
|
||||
plotData.push(statisticsSeries.mean);
|
||||
|
||||
if (forNode === true) {
|
||||
// Negative Timestamp Buildup
|
||||
for (let i = 0; i <= longestSeries; i++) {
|
||||
plotData[0][i] = (longestSeries - i) * timestep * -1
|
||||
}
|
||||
// timestamp 0 with null value for reversed time axis
|
||||
if (plotData[1].length != 0) plotData[1].push(null);
|
||||
if (plotData[2].length != 0) plotData[2].push(null);
|
||||
if (plotData[3].length != 0) plotData[3].push(null);
|
||||
}
|
||||
|
||||
plotSeries.push({
|
||||
label: "min",
|
||||
scale: "y",
|
||||
width: lineWidth,
|
||||
stroke: "red",
|
||||
});
|
||||
plotSeries.push({
|
||||
label: "max",
|
||||
scale: "y",
|
||||
width: lineWidth,
|
||||
stroke: "green",
|
||||
});
|
||||
plotSeries.push({
|
||||
label: "mean",
|
||||
scale: "y",
|
||||
width: lineWidth,
|
||||
stroke: "black",
|
||||
});
|
||||
|
||||
plotBands = [
|
||||
{ series: [2, 3], fill: "rgba(0,255,0,0.1)" },
|
||||
{ series: [3, 1], fill: "rgba(255,0,0,0.1)" },
|
||||
];
|
||||
} else {
|
||||
for (let i = 0; i < series.length; i++) {
|
||||
plotData.push(series[i].data);
|
||||
if (forNode === true && plotData[1].length != 0) plotData[1].push(null); // timestamp 0 with null value for reversed time axis
|
||||
plotSeries.push({
|
||||
label:
|
||||
scope === "node"
|
||||
? resources[i].hostname
|
||||
: // scope === 'accelerator' ? resources[0].accelerators[i] :
|
||||
scope + " #" + (i + 1),
|
||||
scale: "y",
|
||||
width: lineWidth,
|
||||
stroke: lineColor(i, series.length),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const opts = {
|
||||
width,
|
||||
height,
|
||||
plugins: [legendAsTooltipPlugin()],
|
||||
series: plotSeries,
|
||||
axes: [
|
||||
{
|
||||
scale: "x",
|
||||
space: 35,
|
||||
incrs: timeIncrs(timestep, maxX, forNode),
|
||||
values: (_, vals) => vals.map((v) => formatTime(v, forNode)),
|
||||
},
|
||||
{
|
||||
scale: "y",
|
||||
grid: { show: true },
|
||||
labelFont: "sans-serif",
|
||||
values: (u, vals) => vals.map((v) => formatNumber(v)),
|
||||
},
|
||||
],
|
||||
bands: plotBands,
|
||||
padding: [5, 10, -20, 0],
|
||||
hooks: {
|
||||
draw: [
|
||||
(u) => {
|
||||
// Draw plot type label:
|
||||
let textl = `${scope}${plotSeries.length > 2 ? "s" : ""}${
|
||||
useStatsSeries
|
||||
? ": min/avg/max"
|
||||
: metricConfig != null && scope != metricConfig.scope
|
||||
? ` (${metricConfig.aggregation})`
|
||||
: ""
|
||||
}`;
|
||||
let textr = `${isShared && scope != "core" && scope != "accelerator" ? "[Shared]" : ""}`;
|
||||
u.ctx.save();
|
||||
u.ctx.textAlign = "start"; // 'end'
|
||||
u.ctx.fillStyle = "black";
|
||||
u.ctx.fillText(textl, u.bbox.left + 10, u.bbox.top + 10);
|
||||
u.ctx.textAlign = "end";
|
||||
u.ctx.fillStyle = "black";
|
||||
u.ctx.fillText(
|
||||
textr,
|
||||
u.bbox.left + u.bbox.width - 10,
|
||||
u.bbox.top + 10,
|
||||
);
|
||||
// u.ctx.fillText(text, u.bbox.left + u.bbox.width - 10, u.bbox.top + u.bbox.height - 10) // Recipe for bottom right
|
||||
|
||||
if (!thresholds) {
|
||||
u.ctx.restore();
|
||||
return;
|
||||
}
|
||||
|
||||
let y = u.valToPos(thresholds.normal, "y", true);
|
||||
u.ctx.save();
|
||||
u.ctx.lineWidth = lineWidth;
|
||||
u.ctx.strokeStyle = normalLineColor;
|
||||
u.ctx.setLineDash([5, 5]);
|
||||
u.ctx.beginPath();
|
||||
u.ctx.moveTo(u.bbox.left, y);
|
||||
u.ctx.lineTo(u.bbox.left + u.bbox.width, y);
|
||||
u.ctx.stroke();
|
||||
u.ctx.restore();
|
||||
},
|
||||
],
|
||||
},
|
||||
scales: {
|
||||
x: { time: false },
|
||||
y: maxY ? { range: [0, maxY * 1.1] } : {},
|
||||
},
|
||||
legend: {
|
||||
// Display legend until max 12 Y-dataseries
|
||||
show: series.length <= 12 || useStatsSeries === true ? true : false,
|
||||
live: series.length <= 12 || useStatsSeries === true ? true : false,
|
||||
},
|
||||
cursor: { drag: { x: true, y: true } },
|
||||
};
|
||||
|
||||
// console.log(opts)
|
||||
|
||||
let plotWrapper = null;
|
||||
let uplot = null;
|
||||
let timeoutId = null;
|
||||
let prevWidth = null,
|
||||
prevHeight = null;
|
||||
|
||||
function render() {
|
||||
if (!width || Number.isNaN(width) || width < 0) return;
|
||||
|
||||
if (prevWidth != null && Math.abs(prevWidth - width) < 10) return;
|
||||
|
||||
prevWidth = width;
|
||||
prevHeight = height;
|
||||
|
||||
if (!uplot) {
|
||||
opts.width = width;
|
||||
opts.height = height;
|
||||
uplot = new uPlot(opts, plotData, plotWrapper);
|
||||
} else {
|
||||
// Positive Timestamp Buildup
|
||||
for (let j = 0; j < longestSeries; j++) // TODO: Cache/Reuse this array?
|
||||
plotData[0][j] = j * timestep
|
||||
uplot.setSize({ width, height });
|
||||
}
|
||||
}
|
||||
|
||||
let plotBands = undefined
|
||||
if (useStatsSeries) {
|
||||
plotData.push(statisticsSeries.min)
|
||||
plotData.push(statisticsSeries.max)
|
||||
plotData.push(statisticsSeries.mean)
|
||||
function onSizeChange() {
|
||||
if (!uplot) return;
|
||||
|
||||
if (forNode === true) { // timestamp 0 with null value for reversed time axis
|
||||
if (plotData[1].length != 0) plotData[1].push(null)
|
||||
if (plotData[2].length != 0) plotData[2].push(null)
|
||||
if (plotData[3].length != 0) plotData[3].push(null)
|
||||
}
|
||||
if (timeoutId != null) clearTimeout(timeoutId);
|
||||
|
||||
plotSeries.push({ label: 'min', scale: 'y', width: lineWidth, stroke: 'red' })
|
||||
plotSeries.push({ label: 'max', scale: 'y', width: lineWidth, stroke: 'green' })
|
||||
plotSeries.push({ label: 'mean', scale: 'y', width: lineWidth, stroke: 'black' })
|
||||
|
||||
plotBands = [
|
||||
{ series: [2,3], fill: 'rgba(0,255,0,0.1)' },
|
||||
{ series: [3,1], fill: 'rgba(255,0,0,0.1)' }
|
||||
];
|
||||
} else {
|
||||
for (let i = 0; i < series.length; i++) {
|
||||
plotData.push(series[i].data)
|
||||
if (forNode === true && plotData[1].length != 0) plotData[1].push(null) // timestamp 0 with null value for reversed time axis
|
||||
plotSeries.push({
|
||||
label: scope === 'node' ? resources[i].hostname :
|
||||
// scope === 'accelerator' ? resources[0].accelerators[i] :
|
||||
scope + ' #' + (i+1),
|
||||
scale: 'y',
|
||||
width: lineWidth,
|
||||
stroke: lineColor(i, series.length)
|
||||
})
|
||||
}
|
||||
timeoutId = setTimeout(() => {
|
||||
timeoutId = null;
|
||||
render();
|
||||
}, resizeSleepTime);
|
||||
}
|
||||
|
||||
$: if (series[0].data.length > 0) {
|
||||
onSizeChange(width, height);
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
if (series[0].data.length > 0) {
|
||||
plotWrapper.style.backgroundColor = backgroundColor();
|
||||
render();
|
||||
}
|
||||
});
|
||||
|
||||
const opts = {
|
||||
width,
|
||||
height,
|
||||
plugins: [
|
||||
legendAsTooltipPlugin()
|
||||
],
|
||||
series: plotSeries,
|
||||
axes: [
|
||||
{
|
||||
scale: 'x',
|
||||
space: 35,
|
||||
incrs: timeIncrs(timestep, maxX, forNode),
|
||||
values: (_, vals) => vals.map(v => formatTime(v, forNode))
|
||||
},
|
||||
{
|
||||
scale: 'y',
|
||||
grid: { show: true },
|
||||
labelFont: 'sans-serif',
|
||||
values: (u, vals) => vals.map(v => formatNumber(v))
|
||||
}
|
||||
],
|
||||
bands: plotBands,
|
||||
padding: [5, 10, -20, 0],
|
||||
hooks: {
|
||||
draw: [(u) => {
|
||||
// Draw plot type label:
|
||||
let textl = `${scope}${plotSeries.length > 2 ? 's' : ''}${
|
||||
useStatsSeries ? ': min/avg/max' : (metricConfig != null && scope != metricConfig.scope ? ` (${metricConfig.aggregation})` : '')}`
|
||||
let textr = `${(isShared && (scope != 'core' && scope != 'accelerator')) ? '[Shared]' : '' }`
|
||||
u.ctx.save()
|
||||
u.ctx.textAlign = 'start' // 'end'
|
||||
u.ctx.fillStyle = 'black'
|
||||
u.ctx.fillText(textl, u.bbox.left + 10, u.bbox.top + 10)
|
||||
u.ctx.textAlign = 'end'
|
||||
u.ctx.fillStyle = 'black'
|
||||
u.ctx.fillText(textr, u.bbox.left + u.bbox.width - 10, u.bbox.top + 10)
|
||||
// u.ctx.fillText(text, u.bbox.left + u.bbox.width - 10, u.bbox.top + u.bbox.height - 10) // Recipe for bottom right
|
||||
onDestroy(() => {
|
||||
if (uplot) uplot.destroy();
|
||||
|
||||
if (!thresholds) {
|
||||
u.ctx.restore()
|
||||
return
|
||||
}
|
||||
if (timeoutId != null) clearTimeout(timeoutId);
|
||||
});
|
||||
|
||||
let y = u.valToPos(thresholds.normal, 'y', true)
|
||||
u.ctx.save()
|
||||
u.ctx.lineWidth = lineWidth
|
||||
u.ctx.strokeStyle = normalLineColor
|
||||
u.ctx.setLineDash([5, 5])
|
||||
u.ctx.beginPath()
|
||||
u.ctx.moveTo(u.bbox.left, y)
|
||||
u.ctx.lineTo(u.bbox.left + u.bbox.width, y)
|
||||
u.ctx.stroke()
|
||||
u.ctx.restore()
|
||||
}]
|
||||
},
|
||||
scales: {
|
||||
x: { time: false },
|
||||
y: maxY ? { range: [0., maxY * 1.1] } : {}
|
||||
},
|
||||
legend : { // Display legend until max 12 Y-dataseries
|
||||
show: (series.length <= 12 || useStatsSeries === true) ? true : false,
|
||||
live: (series.length <= 12 || useStatsSeries === true) ? true : false
|
||||
},
|
||||
cursor: { drag: { x: true, y: true } }
|
||||
}
|
||||
|
||||
// console.log(opts)
|
||||
|
||||
let plotWrapper = null
|
||||
let uplot = null
|
||||
let timeoutId = null
|
||||
let prevWidth = null, prevHeight = null
|
||||
|
||||
function render() {
|
||||
if (!width || Number.isNaN(width) || width < 0)
|
||||
return
|
||||
|
||||
if (prevWidth != null && Math.abs(prevWidth - width) < 10)
|
||||
return
|
||||
|
||||
prevWidth = width
|
||||
prevHeight = height
|
||||
|
||||
if (!uplot) {
|
||||
opts.width = width
|
||||
opts.height = height
|
||||
uplot = new uPlot(opts, plotData, plotWrapper)
|
||||
} else {
|
||||
uplot.setSize({ width, height })
|
||||
}
|
||||
}
|
||||
|
||||
function onSizeChange() {
|
||||
if (!uplot)
|
||||
return
|
||||
|
||||
if (timeoutId != null)
|
||||
clearTimeout(timeoutId)
|
||||
|
||||
timeoutId = setTimeout(() => {
|
||||
timeoutId = null
|
||||
render()
|
||||
}, resizeSleepTime)
|
||||
}
|
||||
|
||||
$: if (series[0].data.length > 0) {
|
||||
onSizeChange(width, height)
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
if (series[0].data.length > 0) {
|
||||
plotWrapper.style.backgroundColor = backgroundColor()
|
||||
render()
|
||||
}
|
||||
})
|
||||
|
||||
onDestroy(() => {
|
||||
if (uplot)
|
||||
uplot.destroy()
|
||||
|
||||
if (timeoutId != null)
|
||||
clearTimeout(timeoutId)
|
||||
})
|
||||
|
||||
// `from` and `to` must be numbers between 0 and 1.
|
||||
export function setTimeRange(from, to) {
|
||||
if (!uplot || from > to)
|
||||
return false
|
||||
|
||||
uplot.setScale('x', { min: from * maxX, max: to * maxX })
|
||||
return true
|
||||
}
|
||||
</script>
|
||||
<script context="module">
|
||||
export function formatTime(t, forNode = false) {
|
||||
if (t !== null) {
|
||||
if (isNaN(t)) {
|
||||
return t
|
||||
} else {
|
||||
const tAbs = Math.abs(t)
|
||||
const h = Math.floor(tAbs / 3600)
|
||||
const m = Math.floor((tAbs % 3600) / 60)
|
||||
// Re-Add "negativity" to time ticks only as string, so that if-cases work as intended
|
||||
if (h == 0)
|
||||
return `${forNode && m != 0 ? '-' : ''}${m}m`
|
||||
else if (m == 0)
|
||||
return `${forNode?'-':''}${h}h`
|
||||
else
|
||||
return `${forNode?'-':''}${h}:${m}h`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function timeIncrs(timestep, maxX, forNode) {
|
||||
if (forNode === true) {
|
||||
return [60, 300, 900, 1800, 3600, 7200, 14400, 21600] // forNode fixed increments
|
||||
} else {
|
||||
let incrs = []
|
||||
for (let t = timestep; t < maxX; t *= 10)
|
||||
incrs.push(t, t * 2, t * 3, t * 5)
|
||||
|
||||
return incrs
|
||||
}
|
||||
}
|
||||
|
||||
export function findThresholds(metricConfig, scope, subCluster, isShared, numhwthreads, numaccs) {
|
||||
// console.log('NAME ' + metricConfig.name + ' / SCOPE ' + scope + ' / SUBCLUSTER ' + subCluster.name)
|
||||
if (!metricConfig || !scope || !subCluster) {
|
||||
console.warn('Argument missing for findThresholds!')
|
||||
return null
|
||||
}
|
||||
|
||||
if ((scope == 'node' && isShared == false) || metricConfig.aggregation == 'avg') {
|
||||
if (metricConfig.subClusters && metricConfig.subClusters.length === 0) {
|
||||
// console.log('subClusterConfigs array empty, use metricConfig defaults')
|
||||
return { normal: metricConfig.normal, caution: metricConfig.caution, alert: metricConfig.alert, peak: metricConfig.peak }
|
||||
} else if (metricConfig.subClusters && metricConfig.subClusters.length > 0) {
|
||||
// console.log('subClusterConfigs found, use subCluster Settings if matching jobs subcluster:')
|
||||
let forSubCluster = metricConfig.subClusters.find(sc => sc.name == subCluster.name)
|
||||
if (forSubCluster && forSubCluster.normal && forSubCluster.caution && forSubCluster.alert && forSubCluster.peak) return forSubCluster
|
||||
else return { normal: metricConfig.normal, caution: metricConfig.caution, alert: metricConfig.alert, peak: metricConfig.peak}
|
||||
} else {
|
||||
console.warn('metricConfig.subClusters not found!')
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
if (metricConfig.aggregation != 'sum') {
|
||||
console.warn('Missing or unkown aggregation mode (sum/avg) for metric:', metricConfig)
|
||||
return null
|
||||
}
|
||||
|
||||
let divisor = 1
|
||||
if (isShared == true) { // Shared
|
||||
if (numaccs > 0) {
|
||||
divisor = subCluster.topology.accelerators.length / numaccs
|
||||
} else if (numhwthreads > 0) {
|
||||
divisor = subCluster.topology.node.length / numhwthreads
|
||||
}
|
||||
else if (scope == 'socket')
|
||||
divisor = subCluster.topology.socket.length
|
||||
else if (scope == 'core')
|
||||
divisor = subCluster.topology.core.length
|
||||
else if (scope == 'accelerator')
|
||||
divisor = subCluster.topology.accelerators.length
|
||||
else if (scope == 'hwthread')
|
||||
divisor = subCluster.topology.node.length
|
||||
else
|
||||
// console.log('TODO: how to calc thresholds for ', scope)
|
||||
return null
|
||||
}
|
||||
|
||||
let mc = metricConfig?.subClusters?.find(sc => sc.name == subCluster.name) || metricConfig
|
||||
return {
|
||||
peak: mc.peak / divisor,
|
||||
normal: mc.normal / divisor,
|
||||
caution: mc.caution / divisor,
|
||||
alert: mc.alert / divisor
|
||||
}
|
||||
}
|
||||
// `from` and `to` must be numbers between 0 and 1.
|
||||
export function setTimeRange(from, to) {
|
||||
if (!uplot || from > to) return false;
|
||||
|
||||
uplot.setScale("x", { min: from * maxX, max: to * maxX });
|
||||
return true;
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if series[0].data.length > 0}
|
||||
<div bind:this={plotWrapper} class="cc-plot"></div>
|
||||
<div bind:this={plotWrapper} class="cc-plot"></div>
|
||||
{:else}
|
||||
<Card class="mx-4" body color="warning">Cannot render plot: No series data returned for <code>{metric}</code></Card>
|
||||
<Card class="mx-4" body color="warning"
|
||||
>Cannot render plot: No series data returned for <code>{metric}</code></Card
|
||||
>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.cc-plot {
|
||||
border-radius: 5px;
|
||||
}
|
||||
.cc-plot {
|
||||
border-radius: 5px;
|
||||
}
|
||||
</style>
|
||||
|
@@ -1,254 +1,339 @@
|
||||
<script>
|
||||
import uPlot from 'uplot'
|
||||
import { formatNumber } from '../units.js'
|
||||
import { onMount, onDestroy } from 'svelte'
|
||||
import { Card } from 'sveltestrap'
|
||||
import uPlot from "uplot";
|
||||
import { formatNumber } from "../units.js";
|
||||
import { onMount, onDestroy } from "svelte";
|
||||
import { Card } from "@sveltestrap/sveltestrap";
|
||||
|
||||
export let data = null
|
||||
export let renderTime = false
|
||||
export let allowSizeChange = false
|
||||
export let cluster = null
|
||||
export let width = 600
|
||||
export let height = 350
|
||||
export let data = null;
|
||||
export let renderTime = false;
|
||||
export let allowSizeChange = false;
|
||||
export let cluster = null;
|
||||
export let width = 600;
|
||||
export let height = 350;
|
||||
|
||||
let plotWrapper = null
|
||||
let uplot = null
|
||||
let timeoutId = null
|
||||
let plotWrapper = null;
|
||||
let uplot = null;
|
||||
let timeoutId = null;
|
||||
|
||||
const lineWidth = clusterCockpitConfig.plot_general_lineWidth
|
||||
const lineWidth = clusterCockpitConfig.plot_general_lineWidth;
|
||||
|
||||
/* Data Format
|
||||
* data = [null, [], []] // 0: null-axis required for scatter, 1: Array of XY-Array for Scatter, 2: Optional Time Info
|
||||
* data[1][0] = [100, 200, 500, ...] // X Axis -> Intensity (Vals up to clusters' flopRateScalar value)
|
||||
* 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)
|
||||
*/
|
||||
/* Data Format
|
||||
* data = [null, [], []] // 0: null-axis required for scatter, 1: Array of XY-Array for Scatter, 2: Optional Time Info
|
||||
* data[1][0] = [100, 200, 500, ...] // X Axis -> Intensity (Vals up to clusters' flopRateScalar value)
|
||||
* 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)
|
||||
*/
|
||||
|
||||
// Helpers
|
||||
function getGradientR(x) {
|
||||
if (x < 0.5) return 0
|
||||
if (x > 0.75) return 255
|
||||
x = (x - 0.5) * 4.0
|
||||
return Math.floor(x * 255.0)
|
||||
}
|
||||
function getGradientG(x) {
|
||||
if (x > 0.25 && x < 0.75) return 255
|
||||
if (x < 0.25) x = x * 4.0
|
||||
else x = 1.0 - (x - 0.75) * 4.0
|
||||
return Math.floor(x * 255.0)
|
||||
}
|
||||
function getGradientB(x) {
|
||||
if (x < 0.25) return 255
|
||||
if (x > 0.5) return 0
|
||||
x = 1.0 - (x - 0.25) * 4.0
|
||||
return Math.floor(x * 255.0)
|
||||
}
|
||||
function getRGB(c) {
|
||||
return `rgb(${getGradientR(c)}, ${getGradientG(c)}, ${getGradientB(c)})`
|
||||
}
|
||||
function nearestThousand (num) {
|
||||
return Math.ceil(num/1000) * 1000
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
// End Helpers
|
||||
|
||||
// Dot Renderers
|
||||
const drawColorPoints = (u, seriesIdx, idx0, idx1) => {
|
||||
const size = 5 * devicePixelRatio;
|
||||
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 deg360 = 2 * Math.PI;
|
||||
for (let i = 0; i < d[0].length; i++) {
|
||||
let p = new Path2D();
|
||||
let xVal = d[0][i];
|
||||
let yVal = d[1][i];
|
||||
u.ctx.strokeStyle = getRGB(u.data[2][i])
|
||||
u.ctx.fillStyle = getRGB(u.data[2][i])
|
||||
if (xVal >= scaleX.min && xVal <= scaleX.max && yVal >= scaleY.min && yVal <= scaleY.max) {
|
||||
let cx = valToPosX(xVal, scaleX, xDim, xOff);
|
||||
let cy = valToPosY(yVal, scaleY, yDim, yOff);
|
||||
|
||||
p.moveTo(cx + size/2, cy);
|
||||
arc(p, cx, cy, size/2, 0, deg360);
|
||||
}
|
||||
u.ctx.fill(p);
|
||||
}
|
||||
});
|
||||
return null;
|
||||
// Helpers
|
||||
function getGradientR(x) {
|
||||
if (x < 0.5) return 0;
|
||||
if (x > 0.75) return 255;
|
||||
x = (x - 0.5) * 4.0;
|
||||
return Math.floor(x * 255.0);
|
||||
}
|
||||
function getGradientG(x) {
|
||||
if (x > 0.25 && x < 0.75) return 255;
|
||||
if (x < 0.25) x = x * 4.0;
|
||||
else x = 1.0 - (x - 0.75) * 4.0;
|
||||
return Math.floor(x * 255.0);
|
||||
}
|
||||
function getGradientB(x) {
|
||||
if (x < 0.25) return 255;
|
||||
if (x > 0.5) return 0;
|
||||
x = 1.0 - (x - 0.25) * 4.0;
|
||||
return Math.floor(x * 255.0);
|
||||
}
|
||||
function getRGB(c) {
|
||||
return `rgb(${getGradientR(c)}, ${getGradientG(c)}, ${getGradientB(c)})`;
|
||||
}
|
||||
function nearestThousand(num) {
|
||||
return Math.ceil(num / 1000) * 1000;
|
||||
}
|
||||
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),
|
||||
};
|
||||
}
|
||||
// End Helpers
|
||||
|
||||
const drawPoints = (u, seriesIdx, idx0, idx1) => {
|
||||
const size = 5 * devicePixelRatio;
|
||||
uPlot.orient(u, seriesIdx, (series, dataX, dataY, scaleX, scaleY, valToPosX, valToPosY, xOff, yOff, xDim, yDim, moveTo, lineTo, rect, arc) => {
|
||||
let d = u.data[seriesIdx];
|
||||
u.ctx.strokeStyle = getRGB(0);
|
||||
u.ctx.fillStyle = getRGB(0);
|
||||
let deg360 = 2 * Math.PI;
|
||||
let p = new Path2D();
|
||||
for (let i = 0; i < d[0].length; i++) {
|
||||
let xVal = d[0][i];
|
||||
let yVal = d[1][i];
|
||||
if (xVal >= scaleX.min && xVal <= scaleX.max && yVal >= scaleY.min && yVal <= scaleY.max) {
|
||||
let cx = valToPosX(xVal, scaleX, xDim, xOff);
|
||||
let cy = valToPosY(yVal, scaleY, yDim, yOff);
|
||||
p.moveTo(cx + size/2, cy);
|
||||
arc(p, cx, cy, size/2, 0, deg360);
|
||||
}
|
||||
}
|
||||
u.ctx.fill(p);
|
||||
});
|
||||
return null;
|
||||
};
|
||||
// Dot Renderers
|
||||
const drawColorPoints = (u, seriesIdx, idx0, idx1) => {
|
||||
const size = 5 * devicePixelRatio;
|
||||
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 deg360 = 2 * Math.PI;
|
||||
for (let i = 0; i < d[0].length; i++) {
|
||||
let p = new Path2D();
|
||||
let xVal = d[0][i];
|
||||
let yVal = d[1][i];
|
||||
u.ctx.strokeStyle = getRGB(u.data[2][i]);
|
||||
u.ctx.fillStyle = getRGB(u.data[2][i]);
|
||||
if (
|
||||
xVal >= scaleX.min &&
|
||||
xVal <= scaleX.max &&
|
||||
yVal >= scaleY.min &&
|
||||
yVal <= scaleY.max
|
||||
) {
|
||||
let cx = valToPosX(xVal, scaleX, xDim, xOff);
|
||||
let cy = valToPosY(yVal, scaleY, yDim, yOff);
|
||||
|
||||
// Main Function
|
||||
function render(plotData) {
|
||||
if (plotData) {
|
||||
const opts = {
|
||||
title: "",
|
||||
mode: 2,
|
||||
width: width,
|
||||
height: height,
|
||||
legend: {
|
||||
show: false
|
||||
},
|
||||
cursor: { drag: { x: false, y: false } },
|
||||
axes: [
|
||||
{
|
||||
label: 'Intensity [FLOPS/Byte]',
|
||||
values: (u, vals) => vals.map(v => formatNumber(v))
|
||||
},
|
||||
{
|
||||
label: 'Performace [GFLOPS]',
|
||||
values: (u, vals) => vals.map(v => formatNumber(v))
|
||||
}
|
||||
],
|
||||
scales: {
|
||||
x: {
|
||||
time: false,
|
||||
range: [0.01, 1000],
|
||||
distr: 3, // Render as log
|
||||
log: 10, // log exp
|
||||
},
|
||||
y: {
|
||||
range: [1.0, cluster?.flopRateSimd?.value ? nearestThousand(cluster.flopRateSimd.value) : 10000],
|
||||
distr: 3, // Render as log
|
||||
log: 10, // log exp
|
||||
},
|
||||
},
|
||||
series: [
|
||||
{},
|
||||
{ paths: renderTime ? drawColorPoints : drawPoints }
|
||||
],
|
||||
hooks: {
|
||||
drawClear: [
|
||||
u => {
|
||||
u.series.forEach((s, i) => {
|
||||
if (i > 0)
|
||||
s._paths = null;
|
||||
});
|
||||
},
|
||||
],
|
||||
draw: [
|
||||
u => { // draw roofs when cluster set
|
||||
// console.log(u)
|
||||
if (cluster != null) {
|
||||
const padding = u._padding // [top, right, bottom, left]
|
||||
|
||||
u.ctx.strokeStyle = 'black'
|
||||
u.ctx.lineWidth = lineWidth
|
||||
u.ctx.beginPath()
|
||||
|
||||
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 = u.valToPos(scalarKnee, 'x', true), // Value, axis, toCanvasPixels
|
||||
simdKneeX = u.valToPos(simdKnee, 'x', true),
|
||||
flopRateScalarY = u.valToPos(cluster.flopRateScalar.value, 'y', true),
|
||||
flopRateSimdY = u.valToPos(cluster.flopRateSimd.value, 'y', true)
|
||||
|
||||
// Debug get zoomLevel from browser
|
||||
// console.log("Zoom", Math.round(window.devicePixelRatio * 100))
|
||||
|
||||
if (scalarKneeX < (width * window.devicePixelRatio) - (padding[1] * window.devicePixelRatio)) { // Lower horizontal roofline
|
||||
u.ctx.moveTo(scalarKneeX, flopRateScalarY)
|
||||
u.ctx.lineTo((width * window.devicePixelRatio) - (padding[1] * window.devicePixelRatio), flopRateScalarY)
|
||||
}
|
||||
|
||||
if (simdKneeX < (width * window.devicePixelRatio) - (padding[1] * window.devicePixelRatio)) { // Top horitontal roofline
|
||||
u.ctx.moveTo(simdKneeX, flopRateSimdY)
|
||||
u.ctx.lineTo((width * window.devicePixelRatio) - (padding[1] * window.devicePixelRatio), flopRateSimdY)
|
||||
}
|
||||
|
||||
let x1 = u.valToPos(0.01, 'x', true),
|
||||
y1 = u.valToPos(ycut, 'y', true)
|
||||
|
||||
let x2 = u.valToPos(simdKnee, 'x', true),
|
||||
y2 = flopRateSimdY
|
||||
|
||||
let xAxisIntersect = lineIntersect(
|
||||
x1, y1, x2, y2,
|
||||
u.valToPos(0.01, 'x', true), u.valToPos(1.0, 'y', true), // X-Axis Start Coords
|
||||
u.valToPos(1000, 'x', true), u.valToPos(1.0, 'y', true) // X-Axis End Coords
|
||||
)
|
||||
|
||||
if (xAxisIntersect.x > x1) {
|
||||
x1 = xAxisIntersect.x
|
||||
y1 = xAxisIntersect.y
|
||||
}
|
||||
|
||||
// Diagonal
|
||||
u.ctx.moveTo(x1, y1)
|
||||
u.ctx.lineTo(x2, y2)
|
||||
|
||||
u.ctx.stroke()
|
||||
// Reset grid lineWidth
|
||||
u.ctx.lineWidth = 0.15
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
// cursor: { drag: { x: true, y: true } } // Activate zoom
|
||||
};
|
||||
uplot = new uPlot(opts, plotData, plotWrapper);
|
||||
} else {
|
||||
console.log('No data for roofline!')
|
||||
p.moveTo(cx + size / 2, cy);
|
||||
arc(p, cx, cy, size / 2, 0, deg360);
|
||||
}
|
||||
u.ctx.fill(p);
|
||||
}
|
||||
},
|
||||
);
|
||||
return null;
|
||||
};
|
||||
|
||||
const drawPoints = (u, seriesIdx, idx0, idx1) => {
|
||||
const size = 5 * devicePixelRatio;
|
||||
uPlot.orient(
|
||||
u,
|
||||
seriesIdx,
|
||||
(
|
||||
series,
|
||||
dataX,
|
||||
dataY,
|
||||
scaleX,
|
||||
scaleY,
|
||||
valToPosX,
|
||||
valToPosY,
|
||||
xOff,
|
||||
yOff,
|
||||
xDim,
|
||||
yDim,
|
||||
moveTo,
|
||||
lineTo,
|
||||
rect,
|
||||
arc,
|
||||
) => {
|
||||
let d = u.data[seriesIdx];
|
||||
u.ctx.strokeStyle = getRGB(0);
|
||||
u.ctx.fillStyle = getRGB(0);
|
||||
let deg360 = 2 * Math.PI;
|
||||
let p = new Path2D();
|
||||
for (let i = 0; i < d[0].length; i++) {
|
||||
let xVal = d[0][i];
|
||||
let yVal = d[1][i];
|
||||
if (
|
||||
xVal >= scaleX.min &&
|
||||
xVal <= scaleX.max &&
|
||||
yVal >= scaleY.min &&
|
||||
yVal <= scaleY.max
|
||||
) {
|
||||
let cx = valToPosX(xVal, scaleX, xDim, xOff);
|
||||
let cy = valToPosY(yVal, scaleY, yDim, yOff);
|
||||
p.moveTo(cx + size / 2, cy);
|
||||
arc(p, cx, cy, size / 2, 0, deg360);
|
||||
}
|
||||
}
|
||||
u.ctx.fill(p);
|
||||
},
|
||||
);
|
||||
return null;
|
||||
};
|
||||
|
||||
// Main Function
|
||||
function render(plotData) {
|
||||
if (plotData) {
|
||||
const opts = {
|
||||
title: "",
|
||||
mode: 2,
|
||||
width: width,
|
||||
height: height,
|
||||
legend: {
|
||||
show: false,
|
||||
},
|
||||
cursor: { drag: { x: false, y: false } },
|
||||
axes: [
|
||||
{
|
||||
label: "Intensity [FLOPS/Byte]",
|
||||
values: (u, vals) => vals.map((v) => formatNumber(v)),
|
||||
},
|
||||
{
|
||||
label: "Performace [GFLOPS]",
|
||||
values: (u, vals) => vals.map((v) => formatNumber(v)),
|
||||
},
|
||||
],
|
||||
scales: {
|
||||
x: {
|
||||
time: false,
|
||||
range: [0.01, 1000],
|
||||
distr: 3, // Render as log
|
||||
log: 10, // log exp
|
||||
},
|
||||
y: {
|
||||
range: [
|
||||
1.0,
|
||||
cluster?.flopRateSimd?.value
|
||||
? nearestThousand(cluster.flopRateSimd.value)
|
||||
: 10000,
|
||||
],
|
||||
distr: 3, // Render as log
|
||||
log: 10, // log exp
|
||||
},
|
||||
},
|
||||
series: [{}, { paths: renderTime ? drawColorPoints : drawPoints }],
|
||||
hooks: {
|
||||
drawClear: [
|
||||
(u) => {
|
||||
u.series.forEach((s, i) => {
|
||||
if (i > 0) s._paths = null;
|
||||
});
|
||||
},
|
||||
],
|
||||
draw: [
|
||||
(u) => {
|
||||
// draw roofs when cluster set
|
||||
// console.log(u)
|
||||
if (cluster != null) {
|
||||
const padding = u._padding; // [top, right, bottom, left]
|
||||
|
||||
u.ctx.strokeStyle = "black";
|
||||
u.ctx.lineWidth = lineWidth;
|
||||
u.ctx.beginPath();
|
||||
|
||||
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 = u.valToPos(scalarKnee, "x", true), // Value, axis, toCanvasPixels
|
||||
simdKneeX = u.valToPos(simdKnee, "x", true),
|
||||
flopRateScalarY = u.valToPos(
|
||||
cluster.flopRateScalar.value,
|
||||
"y",
|
||||
true,
|
||||
),
|
||||
flopRateSimdY = u.valToPos(
|
||||
cluster.flopRateSimd.value,
|
||||
"y",
|
||||
true,
|
||||
);
|
||||
|
||||
// Debug get zoomLevel from browser
|
||||
// console.log("Zoom", Math.round(window.devicePixelRatio * 100))
|
||||
|
||||
if (
|
||||
scalarKneeX <
|
||||
width * window.devicePixelRatio -
|
||||
padding[1] * window.devicePixelRatio
|
||||
) {
|
||||
// Lower horizontal roofline
|
||||
u.ctx.moveTo(scalarKneeX, flopRateScalarY);
|
||||
u.ctx.lineTo(
|
||||
width * window.devicePixelRatio -
|
||||
padding[1] * window.devicePixelRatio,
|
||||
flopRateScalarY,
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
simdKneeX <
|
||||
width * window.devicePixelRatio -
|
||||
padding[1] * window.devicePixelRatio
|
||||
) {
|
||||
// Top horitontal roofline
|
||||
u.ctx.moveTo(simdKneeX, flopRateSimdY);
|
||||
u.ctx.lineTo(
|
||||
width * window.devicePixelRatio -
|
||||
padding[1] * window.devicePixelRatio,
|
||||
flopRateSimdY,
|
||||
);
|
||||
}
|
||||
|
||||
let x1 = u.valToPos(0.01, "x", true),
|
||||
y1 = u.valToPos(ycut, "y", true);
|
||||
|
||||
let x2 = u.valToPos(simdKnee, "x", true),
|
||||
y2 = flopRateSimdY;
|
||||
|
||||
let xAxisIntersect = lineIntersect(
|
||||
x1,
|
||||
y1,
|
||||
x2,
|
||||
y2,
|
||||
u.valToPos(0.01, "x", true),
|
||||
u.valToPos(1.0, "y", true), // X-Axis Start Coords
|
||||
u.valToPos(1000, "x", true),
|
||||
u.valToPos(1.0, "y", true), // X-Axis End Coords
|
||||
);
|
||||
|
||||
if (xAxisIntersect.x > x1) {
|
||||
x1 = xAxisIntersect.x;
|
||||
y1 = xAxisIntersect.y;
|
||||
}
|
||||
|
||||
// Diagonal
|
||||
u.ctx.moveTo(x1, y1);
|
||||
u.ctx.lineTo(x2, y2);
|
||||
|
||||
u.ctx.stroke();
|
||||
// Reset grid lineWidth
|
||||
u.ctx.lineWidth = 0.15;
|
||||
}
|
||||
},
|
||||
],
|
||||
},
|
||||
// cursor: { drag: { x: true, y: true } } // Activate zoom
|
||||
};
|
||||
uplot = new uPlot(opts, plotData, plotWrapper);
|
||||
} else {
|
||||
console.log("No data for roofline!");
|
||||
}
|
||||
}
|
||||
|
||||
// Svelte and Sizechange
|
||||
onMount(() => {
|
||||
render(data)
|
||||
})
|
||||
onDestroy(() => {
|
||||
if (uplot)
|
||||
uplot.destroy()
|
||||
// Svelte and Sizechange
|
||||
onMount(() => {
|
||||
render(data);
|
||||
});
|
||||
onDestroy(() => {
|
||||
if (uplot) uplot.destroy();
|
||||
|
||||
if (timeoutId != null)
|
||||
clearTimeout(timeoutId)
|
||||
})
|
||||
function sizeChanged() {
|
||||
if (timeoutId != null)
|
||||
clearTimeout(timeoutId)
|
||||
if (timeoutId != null) clearTimeout(timeoutId);
|
||||
});
|
||||
function sizeChanged() {
|
||||
if (timeoutId != null) clearTimeout(timeoutId);
|
||||
|
||||
timeoutId = setTimeout(() => {
|
||||
timeoutId = null
|
||||
if (uplot)
|
||||
uplot.destroy()
|
||||
render(data)
|
||||
}, 200)
|
||||
}
|
||||
$: if (allowSizeChange) sizeChanged(width, height)
|
||||
timeoutId = setTimeout(() => {
|
||||
timeoutId = null;
|
||||
if (uplot) uplot.destroy();
|
||||
render(data);
|
||||
}, 200);
|
||||
}
|
||||
$: if (allowSizeChange) sizeChanged(width, height);
|
||||
</script>
|
||||
|
||||
{#if data != null}
|
||||
<div bind:this={plotWrapper}/>
|
||||
<div bind:this={plotWrapper} />
|
||||
{:else}
|
||||
<Card class="mx-4" body color="warning">Cannot render roofline: No data!</Card>
|
||||
{/if}
|
||||
<Card class="mx-4" body color="warning">Cannot render roofline: No data!</Card
|
||||
>
|
||||
{/if}
|
||||
|
||||
|
Reference in New Issue
Block a user