mirror of
https://github.com/ClusterCockpit/cc-backend
synced 2026-01-28 06:51:45 +01:00
review plot rendering and doublemetric opts
This commit is contained in:
@@ -6,9 +6,6 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// import {
|
|
||||||
// getContext
|
|
||||||
// } from "svelte"
|
|
||||||
import {
|
import {
|
||||||
queryStore,
|
queryStore,
|
||||||
gql,
|
gql,
|
||||||
@@ -55,9 +52,6 @@
|
|||||||
let to = $state(new Date(Date.now()));
|
let to = $state(new Date(Date.now()));
|
||||||
let stackedFrom = $state(Math.floor(Date.now() / 1000) - 14400);
|
let stackedFrom = $state(Math.floor(Date.now() / 1000) - 14400);
|
||||||
let colWidthStates = $state(0);
|
let colWidthStates = $state(0);
|
||||||
let colWidthRoof = $state(0);
|
|
||||||
let colWidthTotals = $state(0);
|
|
||||||
let colWidthStacked = $state(0);
|
|
||||||
|
|
||||||
/* Derived */
|
/* Derived */
|
||||||
// States for Stacked charts
|
// States for Stacked charts
|
||||||
@@ -522,29 +516,38 @@
|
|||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
|
|
||||||
<Col> <!-- Total Cluster Metric in Time SUMS-->
|
<!-- Total Cluster Metric in Time SUMS-->
|
||||||
<div bind:clientWidth={colWidthTotals}>
|
<Col class="text-center">
|
||||||
|
<h5 class="mt-2 mb-0">
|
||||||
|
Cluster Utilization (
|
||||||
|
<span style="color: #0000ff;">
|
||||||
|
{`${$statusQuery?.data?.clusterMetrics?.metrics[0]?.name} (${$statusQuery?.data?.clusterMetrics?.metrics[0]?.unit?.prefix}${$statusQuery?.data?.clusterMetrics?.metrics[0]?.unit?.base})`}
|
||||||
|
</span>,
|
||||||
|
<span style="color: #ff0000;">
|
||||||
|
{`${$statusQuery?.data?.clusterMetrics?.metrics[1]?.name} (${$statusQuery?.data?.clusterMetrics?.metrics[1]?.unit?.prefix}${$statusQuery?.data?.clusterMetrics?.metrics[1]?.unit?.base})`}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
</h5>
|
||||||
|
<div>
|
||||||
|
{#key $statusQuery?.data?.clusterMetrics}
|
||||||
<DoubleMetric
|
<DoubleMetric
|
||||||
width={colWidthTotals}
|
|
||||||
timestep={$statusQuery?.data?.clusterMetrics[0]?.timestep || 60}
|
timestep={$statusQuery?.data?.clusterMetrics[0]?.timestep || 60}
|
||||||
numNodes={$statusQuery?.data?.clusterMetrics?.nodeCount || 0}
|
numNodes={$statusQuery?.data?.clusterMetrics?.nodeCount || 0}
|
||||||
metricData={$statusQuery?.data?.clusterMetrics?.metrics || []}
|
metricData={$statusQuery?.data?.clusterMetrics?.metrics || []}
|
||||||
cluster={presetCluster}
|
publicMode
|
||||||
fixLinewidth={2}
|
|
||||||
/>
|
/>
|
||||||
|
{/key}
|
||||||
</div>
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
|
|
||||||
<Col> <!-- Nodes Roofline -->
|
<Col> <!-- Nodes Roofline -->
|
||||||
<div bind:clientWidth={colWidthRoof}>
|
<div>
|
||||||
{#key $statusQuery?.data?.nodeMetrics}
|
{#key $statusQuery?.data?.nodeMetrics}
|
||||||
<Roofline
|
<Roofline
|
||||||
colorBackground
|
colorBackground
|
||||||
useColors={false}
|
useColors={false}
|
||||||
useLegend={false}
|
useLegend={false}
|
||||||
allowSizeChange
|
allowSizeChange
|
||||||
width={colWidthRoof}
|
|
||||||
height={300}
|
|
||||||
cluster={presetCluster}
|
cluster={presetCluster}
|
||||||
subCluster={clusterInfo?.roofData ? clusterInfo.roofData : null}
|
subCluster={clusterInfo?.roofData ? clusterInfo.roofData : null}
|
||||||
roofData={transformNodesStatsToData($statusQuery?.data?.nodeMetrics)}
|
roofData={transformNodesStatsToData($statusQuery?.data?.nodeMetrics)}
|
||||||
@@ -607,11 +610,10 @@
|
|||||||
</Col>
|
</Col>
|
||||||
|
|
||||||
<Col> <!-- Stacked SchedState -->
|
<Col> <!-- Stacked SchedState -->
|
||||||
<div bind:clientWidth={colWidthStacked}>
|
<div>
|
||||||
{#key $statesTimed?.data?.nodeStatesTimed}
|
{#key $statesTimed?.data?.nodeStatesTimed}
|
||||||
<Stacked
|
<Stacked
|
||||||
data={$statesTimed?.data?.nodeStatesTimed}
|
data={$statesTimed?.data?.nodeStatesTimed}
|
||||||
width={colWidthStacked}
|
|
||||||
height={260}
|
height={260}
|
||||||
ylabel="Nodes"
|
ylabel="Nodes"
|
||||||
yunit = "#Count"
|
yunit = "#Count"
|
||||||
|
|||||||
@@ -229,7 +229,7 @@
|
|||||||
</DropdownToggle>
|
</DropdownToggle>
|
||||||
<DropdownMenu class="dropdown-menu-lg-end">
|
<DropdownMenu class="dropdown-menu-lg-end">
|
||||||
<NavbarLinks
|
<NavbarLinks
|
||||||
{clustersNames}
|
{clusterNames}
|
||||||
{subclusterMap}
|
{subclusterMap}
|
||||||
direction="right"
|
direction="right"
|
||||||
links={views.filter(
|
links={views.filter(
|
||||||
|
|||||||
@@ -4,123 +4,56 @@
|
|||||||
Only width/height should change reactively.
|
Only width/height should change reactively.
|
||||||
|
|
||||||
Properties:
|
Properties:
|
||||||
- `metric String`: The metric name
|
- `metricData [Data]`: Two series of metric data including unit info
|
||||||
- `scope String?`: Scope of the displayed data [Default: node]
|
- `timestep Number`: Data timestep
|
||||||
|
- `numNodes Number`: Number of nodes from which metric data is aggregated
|
||||||
|
- `cluster String`: Cluster name of the parent job / data [Default: ""]
|
||||||
|
- `forNode Bool?`: If this plot is used for node data display; will render x-axis as negative time with $now as maximum [Default: true]
|
||||||
|
- `enableFlip Bool?`: Whether to use legend tooltip flipping based on canvas size [Default: false]
|
||||||
|
- `publicMode Bool?`: Disables tooltip legend and enables larger colored axis labels [Default: false]
|
||||||
- `height Number?`: The plot height [Default: 300]
|
- `height Number?`: The plot height [Default: 300]
|
||||||
- `timestep Number`: The timestep used for X-axis rendering
|
|
||||||
- `series [GraphQL.Series]`: The metric data object
|
|
||||||
- `statisticsSeries [GraphQL.StatisticsSeries]?`: Min/Max/Median representation of metric data [Default: null]
|
|
||||||
- `cluster String?`: Cluster name of the parent job / data [Default: ""]
|
|
||||||
- `subCluster String`: Name of the subCluster of the parent job
|
|
||||||
- `isShared Bool?`: If this job used shared resources; for additional legend display [Default: false]
|
|
||||||
- `forNode Bool?`: If this plot is used for node data display; will render x-axis as negative time with $now as maximum [Default: false]
|
|
||||||
- `numhwthreads Number?`: Number of job HWThreads [Default: 0]
|
|
||||||
- `numaccs Number?`: Number of job Accelerators [Default: 0]
|
|
||||||
- `zoomState Object?`: The last zoom state to preserve on user zoom [Default: null]
|
|
||||||
- `thersholdState Object?`: The last threshold state to preserve on user zoom [Default: null]
|
|
||||||
- `extendedLegendData Object?`: Additional information to be rendered in an extended legend [Default: null]
|
|
||||||
- `onZoom Func`: Callback function to handle zoom-in event
|
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import uPlot from "uplot";
|
import uPlot from "uplot";
|
||||||
import { formatNumber, formatDurationTime } from "../units.js";
|
import { formatNumber, formatDurationTime } from "../units.js";
|
||||||
import { getContext, onMount, onDestroy } from "svelte";
|
import { getContext, onDestroy } from "svelte";
|
||||||
import { Card } from "@sveltestrap/sveltestrap";
|
import { Card } from "@sveltestrap/sveltestrap";
|
||||||
|
|
||||||
/* Svelte 5 Props */
|
/* Svelte 5 Props */
|
||||||
let {
|
let {
|
||||||
// metric,
|
metricData,
|
||||||
width = 0,
|
|
||||||
height = 300,
|
|
||||||
fixLinewidth = null,
|
|
||||||
timestep,
|
timestep,
|
||||||
numNodes,
|
numNodes,
|
||||||
metricData,
|
cluster,
|
||||||
// useStatsSeries = false,
|
|
||||||
// statisticsSeries = null,
|
|
||||||
cluster = "",
|
|
||||||
forNode = true,
|
forNode = true,
|
||||||
// zoomState = null,
|
|
||||||
// thresholdState = null,
|
|
||||||
enableFlip = false,
|
enableFlip = false,
|
||||||
publicMode = false,
|
publicMode = false,
|
||||||
// onZoom
|
height = 300,
|
||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
/* Const Init */
|
/* Const Init */
|
||||||
const clusterCockpitConfig = getContext("cc-config");
|
const clusterCockpitConfig = getContext("cc-config");
|
||||||
const fixedLineColors = ["#0000ff", "#ff0000"]; // Plot only uses 2 Datasets: High Contrast
|
const fixedLineColors = ["#0000ff", "#ff0000"]; // Plot only uses 2 Datasets: High Contrast
|
||||||
// const resampleConfig = getContext("resampling");
|
|
||||||
// const subClusterTopology = getContext("getHardwareTopology")(cluster, subCluster);
|
|
||||||
// const metricConfig = getContext("getMetricConfig")(cluster, subCluster, metric);
|
|
||||||
// const cbmode = clusterCockpitConfig?.plotConfiguration_colorblindMode || false;
|
|
||||||
const renderSleepTime = 200;
|
const renderSleepTime = 200;
|
||||||
// const backgroundColors = {
|
|
||||||
// normal: "rgba(255, 255, 255, 1.0)",
|
|
||||||
// caution: cbmode ? "rgba(239, 230, 69, 0.3)" : "rgba(255, 128, 0, 0.3)",
|
|
||||||
// alert: cbmode ? "rgba(225, 86, 44, 0.3)" : "rgba(255, 0, 0, 0.3)",
|
|
||||||
// };
|
|
||||||
|
|
||||||
/* Var Init */
|
/* Var Init */
|
||||||
let timeoutId = null;
|
let timeoutId = null;
|
||||||
|
|
||||||
/* State Init */
|
/* State Init */
|
||||||
let plotWrapper = $state(null);
|
let plotWrapper = $state(null);
|
||||||
|
let width = $state(0); // Wrapper Width
|
||||||
let uplot = $state(null);
|
let uplot = $state(null);
|
||||||
|
|
||||||
/* Derived */
|
/* Derived */
|
||||||
const lineWidth = $derived(fixLinewidth ? fixLinewidth : clusterCockpitConfig.plotConfiguration_lineWidth / window.devicePixelRatio);
|
|
||||||
// const usesMeanStatsSeries = $derived((statisticsSeries?.mean && statisticsSeries.mean.length != 0));
|
|
||||||
// const resampleTrigger = $derived(resampleConfig?.trigger ? Number(resampleConfig.trigger) : null);
|
|
||||||
// const resampleResolutions = $derived(resampleConfig?.resolutions ? [...resampleConfig.resolutions] : null);
|
|
||||||
// const resampleMinimum = $derived(resampleConfig?.resolutions ? Math.min(...resampleConfig.resolutions) : null);
|
|
||||||
// const thresholds = $derived(findJobAggregationThresholds(
|
|
||||||
// subClusterTopology,
|
|
||||||
// metricConfig,
|
|
||||||
// scope,
|
|
||||||
// numhwthreads,
|
|
||||||
// numaccs
|
|
||||||
// ));
|
|
||||||
const longestSeries = $derived.by(() => {
|
|
||||||
// if (useStatsSeries) {
|
|
||||||
// return usesMeanStatsSeries ? statisticsSeries?.mean?.length : statisticsSeries?.median?.length;
|
|
||||||
// } else {
|
|
||||||
return metricData.reduce((n, m) => Math.max(n, m.data.length), 0);
|
|
||||||
// }
|
|
||||||
});
|
|
||||||
const maxX = $derived(longestSeries * timestep);
|
const maxX = $derived(longestSeries * timestep);
|
||||||
// const maxY = $derived.by(() => {
|
const lineWidth = $derived(publicMode ? 2 : clusterCockpitConfig.plotConfiguration_lineWidth / window.devicePixelRatio);
|
||||||
// let pendingY = 0;
|
const longestSeries = $derived.by(() => {
|
||||||
// // if (useStatsSeries) {
|
return metricData.reduce((n, m) => Math.max(n, m.data.length), 0);
|
||||||
// // pendingY = statisticsSeries.max.reduce(
|
});
|
||||||
// // (max, x) => Math.max(max, x),
|
|
||||||
// // thresholds?.normal,
|
|
||||||
// // ) || thresholds?.normal
|
|
||||||
// // } else {
|
|
||||||
// pendingY = series.reduce(
|
|
||||||
// (max, series) => Math.max(max, series?.statistics?.max),
|
|
||||||
// thresholds?.normal,
|
|
||||||
// ) || thresholds?.normal;
|
|
||||||
// // }
|
|
||||||
|
|
||||||
// if (pendingY >= 10 * thresholds.peak) {
|
// Derive Plot Params
|
||||||
// // Hard y-range render limit if outliers in series data
|
let plotData = $derived.by(() => {
|
||||||
// return (10 * thresholds.peak);
|
|
||||||
// } else {
|
|
||||||
// return pendingY;
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// const plotBands = $derived.by(() => {
|
|
||||||
// if (useStatsSeries) {
|
|
||||||
// return [
|
|
||||||
// { series: [2, 3], fill: cbmode ? "rgba(0,0,255,0.1)" : "rgba(0,255,0,0.1)" },
|
|
||||||
// { series: [3, 1], fill: cbmode ? "rgba(0,255,0,0.1)" : "rgba(255,0,0,0.1)" },
|
|
||||||
// ];
|
|
||||||
// };
|
|
||||||
// return null;
|
|
||||||
// })
|
|
||||||
const plotData = $derived.by(() => {
|
|
||||||
let pendingData = [new Array(longestSeries)];
|
let pendingData = [new Array(longestSeries)];
|
||||||
// X
|
// X
|
||||||
if (forNode === true) {
|
if (forNode === true) {
|
||||||
@@ -135,25 +68,15 @@
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
// Y
|
// Y
|
||||||
// if (useStatsSeries) {
|
|
||||||
// pendingData.push(statisticsSeries.min);
|
|
||||||
// pendingData.push(statisticsSeries.max);
|
|
||||||
// if (usesMeanStatsSeries) {
|
|
||||||
// pendingData.push(statisticsSeries.mean);
|
|
||||||
// } else {
|
|
||||||
// pendingData.push(statisticsSeries.median);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// } else {
|
|
||||||
for (let i = 0; i < metricData.length; i++) {
|
for (let i = 0; i < metricData.length; i++) {
|
||||||
pendingData.push(metricData[i]?.data);
|
pendingData.push(metricData[i]?.data);
|
||||||
};
|
};
|
||||||
// };
|
|
||||||
return pendingData;
|
return pendingData;
|
||||||
})
|
})
|
||||||
const plotSeries = $derived.by(() => {
|
|
||||||
|
let plotSeries = $derived.by(() => {
|
||||||
|
// X
|
||||||
let pendingSeries = [
|
let pendingSeries = [
|
||||||
// Note: X-Legend Will not be shown as soon as Y-Axis are in extendedMode
|
|
||||||
{
|
{
|
||||||
label: "Runtime",
|
label: "Runtime",
|
||||||
value: (u, ts, sidx, didx) =>
|
value: (u, ts, sidx, didx) =>
|
||||||
@@ -161,87 +84,108 @@
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
// Y
|
// Y
|
||||||
// if (useStatsSeries) {
|
|
||||||
// pendingSeries.push({
|
|
||||||
// label: "min",
|
|
||||||
// scale: "y",
|
|
||||||
// width: lineWidth,
|
|
||||||
// stroke: cbmode ? "rgb(0,255,0)" : "red",
|
|
||||||
// });
|
|
||||||
// pendingSeries.push({
|
|
||||||
// label: "max",
|
|
||||||
// scale: "y",
|
|
||||||
// width: lineWidth,
|
|
||||||
// stroke: cbmode ? "rgb(0,0,255)" : "green",
|
|
||||||
// });
|
|
||||||
// pendingSeries.push({
|
|
||||||
// label: usesMeanStatsSeries ? "mean" : "median",
|
|
||||||
// scale: "y",
|
|
||||||
// width: lineWidth,
|
|
||||||
// stroke: "black",
|
|
||||||
// });
|
|
||||||
|
|
||||||
// } else {
|
|
||||||
for (let i = 0; i < metricData.length; i++) {
|
for (let i = 0; i < metricData.length; i++) {
|
||||||
// Default
|
|
||||||
// if (!extendedLegendData) {
|
|
||||||
pendingSeries.push({
|
pendingSeries.push({
|
||||||
label: publicMode ? null : `${metricData[i]?.name} (${metricData[i]?.unit?.prefix}${metricData[i]?.unit?.base})`,
|
label: publicMode ? null : `${metricData[i]?.name} (${metricData[i]?.unit?.prefix}${metricData[i]?.unit?.base})`,
|
||||||
scale: `y${i+1}`,
|
scale: `y${i+1}`,
|
||||||
width: lineWidth,
|
width: lineWidth,
|
||||||
stroke: fixedLineColors[i],
|
stroke: fixedLineColors[i],
|
||||||
});
|
});
|
||||||
// }
|
|
||||||
// Extended Legend For NodeList
|
|
||||||
// else {
|
|
||||||
// pendingSeries.push({
|
|
||||||
// label:
|
|
||||||
// scope === "node"
|
|
||||||
// ? series[i].hostname
|
|
||||||
// : scope === "accelerator"
|
|
||||||
// ? 'Acc #' + (i + 1) // series[i].id.slice(9, 14) | Too Hardware Specific
|
|
||||||
// : scope + " #" + (i + 1),
|
|
||||||
// scale: "y",
|
|
||||||
// width: lineWidth,
|
|
||||||
// stroke: lineColor(i, series?.length),
|
|
||||||
// values: (u, sidx, idx) => {
|
|
||||||
// // "i" = "sidx - 1" : sidx contains x-axis-data
|
|
||||||
// if (idx == null)
|
|
||||||
// return {
|
|
||||||
// time: '-',
|
|
||||||
// value: '-',
|
|
||||||
// user: '-',
|
|
||||||
// job: '-'
|
|
||||||
// };
|
|
||||||
|
|
||||||
// if (series[i].id in extendedLegendData) {
|
|
||||||
// return {
|
|
||||||
// time: formatDurationTime(plotData[0][idx], forNode),
|
|
||||||
// value: plotData[sidx][idx],
|
|
||||||
// user: extendedLegendData[series[i].id].user,
|
|
||||||
// job: extendedLegendData[series[i].id].job,
|
|
||||||
// };
|
|
||||||
// } else {
|
|
||||||
// return {
|
|
||||||
// time: formatDurationTime(plotData[0][idx], forNode),
|
|
||||||
// value: plotData[sidx][idx],
|
|
||||||
// user: '-',
|
|
||||||
// job: '-',
|
|
||||||
// };
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
};
|
};
|
||||||
return pendingSeries;
|
return pendingSeries;
|
||||||
})
|
})
|
||||||
|
|
||||||
/* Effects */
|
// Set Options
|
||||||
// $effect(() => {
|
function getOpts(optWidth, optHeight) {
|
||||||
// if (!useStatsSeries && statisticsSeries != null) useStatsSeries = true;
|
let baseOpts = {
|
||||||
// })
|
width: optWidth,
|
||||||
|
height: optHeight,
|
||||||
|
series: plotSeries,
|
||||||
|
axes: [
|
||||||
|
{
|
||||||
|
scale: "x",
|
||||||
|
incrs: timeIncrs(timestep, maxX, forNode),
|
||||||
|
values: (_, vals) => vals.map((v) => formatDurationTime(v, forNode)),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scale: "y1",
|
||||||
|
grid: { show: true },
|
||||||
|
values: (u, vals) => vals.map((v) => formatNumber(v)),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
side: 1,
|
||||||
|
scale: "y2",
|
||||||
|
grid: { show: false },
|
||||||
|
values: (u, vals) => vals.map((v) => formatNumber(v)),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
// bands: plotBands,
|
||||||
|
padding: [5, 10, -20, 0],
|
||||||
|
hooks: {},
|
||||||
|
scales: {
|
||||||
|
x: { time: false },
|
||||||
|
y1: { auto: true },
|
||||||
|
y2: { auto: true },
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
show: !publicMode,
|
||||||
|
live: !publicMode
|
||||||
|
},
|
||||||
|
cursor: {
|
||||||
|
drag: { x: true, y: true },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (publicMode) {
|
||||||
|
// X
|
||||||
|
baseOpts.axes[0].space = 60;
|
||||||
|
baseOpts.axes[0].font = '16px Arial';
|
||||||
|
// Y1
|
||||||
|
baseOpts.axes[1].space = 50;
|
||||||
|
baseOpts.axes[1].size = 60;
|
||||||
|
baseOpts.axes[1].font = '16px Arial';
|
||||||
|
baseOpts.axes[1].stroke = fixedLineColors[0];
|
||||||
|
// Y2
|
||||||
|
baseOpts.axes[2].space = 40;
|
||||||
|
baseOpts.axes[2].size = 60;
|
||||||
|
baseOpts.axes[2].font = '16px Arial';
|
||||||
|
baseOpts.axes[2].stroke = fixedLineColors[1];
|
||||||
|
} else {
|
||||||
|
baseOpts.title = 'Cluster Utilization';
|
||||||
|
baseOpts.plugins = [legendAsTooltipPlugin()];
|
||||||
|
// X
|
||||||
|
baseOpts.axes[0].label = 'Time';
|
||||||
|
// Y1
|
||||||
|
baseOpts.axes[1].label = `${metricData[0]?.name} (${metricData[0]?.unit?.prefix}${metricData[0]?.unit?.base})`;
|
||||||
|
// Y2
|
||||||
|
baseOpts.axes[2].label = `${metricData[1]?.name} (${metricData[1]?.unit?.prefix}${metricData[1]?.unit?.base})`;
|
||||||
|
baseOpts.hooks.draw = [
|
||||||
|
(u) => {
|
||||||
|
// Draw plot type label:
|
||||||
|
let textl = `Cluster ${cluster}`
|
||||||
|
let textr = `Sums of ${numNodes} nodes`
|
||||||
|
u.ctx.save();
|
||||||
|
u.ctx.textAlign = "start"; // 'end'
|
||||||
|
u.ctx.fillStyle = "black";
|
||||||
|
u.ctx.fillText(textl, u.bbox.left + 10, u.bbox.top + (forNode ? 0 : 10));
|
||||||
|
u.ctx.textAlign = "end";
|
||||||
|
u.ctx.fillStyle = "black";
|
||||||
|
u.ctx.fillText(
|
||||||
|
textr,
|
||||||
|
u.bbox.left + u.bbox.width - 10,
|
||||||
|
u.bbox.top + (forNode ? 0 : 10),
|
||||||
|
);
|
||||||
|
|
||||||
|
u.ctx.restore();
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
return baseOpts;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Effects */
|
||||||
// This updates plot on all size changes if wrapper (== data) exists
|
// This updates plot on all size changes if wrapper (== data) exists
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (plotWrapper) {
|
if (plotWrapper) {
|
||||||
@@ -262,73 +206,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// removed arg "subcluster": input metricconfig and topology now directly derived from subcluster
|
|
||||||
// function findJobAggregationThresholds(
|
|
||||||
// subClusterTopology,
|
|
||||||
// metricConfig,
|
|
||||||
// scope,
|
|
||||||
// numhwthreads,
|
|
||||||
// numaccs
|
|
||||||
// ) {
|
|
||||||
|
|
||||||
// if (!subClusterTopology || !metricConfig || !scope) {
|
|
||||||
// console.warn("Argument missing for findJobAggregationThresholds!");
|
|
||||||
// return null;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // handle special *-stat scopes
|
|
||||||
// if (scope.match(/(.*)-stat$/)) {
|
|
||||||
// const statParts = scope.split('-');
|
|
||||||
// scope = statParts[0]
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (metricConfig?.aggregation == "avg") {
|
|
||||||
// // Return as Configured
|
|
||||||
// return {
|
|
||||||
// normal: metricConfig.normal,
|
|
||||||
// caution: metricConfig.caution,
|
|
||||||
// alert: metricConfig.alert,
|
|
||||||
// peak: metricConfig.peak,
|
|
||||||
// };
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (metricConfig?.aggregation == "sum") {
|
|
||||||
// // Scale Thresholds
|
|
||||||
// let fraction;
|
|
||||||
// if (numaccs > 0) fraction = subClusterTopology.accelerators.length / numaccs;
|
|
||||||
// else if (numhwthreads > 0) fraction = subClusterTopology.core.length / numhwthreads;
|
|
||||||
// else fraction = 1; // Fallback
|
|
||||||
|
|
||||||
// let divisor;
|
|
||||||
// // Exclusive: Fraction = 1; Shared: Fraction > 1
|
|
||||||
// if (scope == 'node') divisor = fraction;
|
|
||||||
// // Cap divisor at number of available sockets or domains
|
|
||||||
// else if (scope == 'socket') divisor = (fraction < subClusterTopology.socket.length) ? subClusterTopology.socket.length : fraction;
|
|
||||||
// else if (scope == "memoryDomain") divisor = (fraction < subClusterTopology.memoryDomain.length) ? subClusterTopology.socket.length : fraction;
|
|
||||||
// // Use Maximum Division for Smallest Scopes
|
|
||||||
// else if (scope == "core") divisor = subClusterTopology.core.length;
|
|
||||||
// else if (scope == "hwthread") divisor = subClusterTopology.core.length; // alt. name for core
|
|
||||||
// else if (scope == "accelerator") divisor = subClusterTopology.accelerators.length;
|
|
||||||
// else {
|
|
||||||
// console.log('Unknown scope, return default aggregation thresholds for sum', scope)
|
|
||||||
// divisor = 1;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return {
|
|
||||||
// peak: metricConfig.peak / divisor,
|
|
||||||
// normal: metricConfig.normal / divisor,
|
|
||||||
// caution: metricConfig.caution / divisor,
|
|
||||||
// alert: metricConfig.alert / divisor,
|
|
||||||
// };
|
|
||||||
// }
|
|
||||||
|
|
||||||
// console.warn(
|
|
||||||
// "Missing or unkown aggregation mode (sum/avg) for metric:",
|
|
||||||
// metricConfig,
|
|
||||||
// );
|
|
||||||
// return null;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// UPLOT PLUGIN // converts the legend into a simple tooltip
|
// UPLOT PLUGIN // converts the legend into a simple tooltip
|
||||||
function legendAsTooltipPlugin({
|
function legendAsTooltipPlugin({
|
||||||
className,
|
className,
|
||||||
@@ -408,220 +285,22 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// RETURN BG COLOR FROM THRESHOLD
|
function onSizeChange(chgWidth, chgHeight) {
|
||||||
// function backgroundColor() {
|
|
||||||
// if (
|
|
||||||
// clusterCockpitConfig.plotConfiguration_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 render(ren_width, ren_height) {
|
|
||||||
// Set Options
|
|
||||||
const opts = {
|
|
||||||
width: ren_width,
|
|
||||||
height: ren_height,
|
|
||||||
title: publicMode ? null : 'Cluster Utilization',
|
|
||||||
plugins: [legendAsTooltipPlugin()],
|
|
||||||
series: plotSeries,
|
|
||||||
axes: [
|
|
||||||
{
|
|
||||||
scale: "x",
|
|
||||||
space: publicMode ? 60 : null,
|
|
||||||
incrs: timeIncrs(timestep, maxX, forNode),
|
|
||||||
font: publicMode ? '16px Arial' : CanvasRenderingContext2D['font'],
|
|
||||||
label: publicMode ? null : "Time",
|
|
||||||
values: (_, vals) => vals.map((v) => formatDurationTime(v, forNode)),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
scale: "y1",
|
|
||||||
grid: { show: true },
|
|
||||||
stroke: publicMode ? fixedLineColors[0] : "#000000",
|
|
||||||
size: publicMode ? 60 : null,
|
|
||||||
space: publicMode ? 50 : null,
|
|
||||||
font: publicMode ? '16px Arial' : CanvasRenderingContext2D['font'],
|
|
||||||
label: publicMode ? null : `${metricData[0]?.name} (${metricData[0]?.unit?.prefix}${metricData[0]?.unit?.base})`,
|
|
||||||
values: (u, vals) => vals.map((v) => formatNumber(v)),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
side: 1,
|
|
||||||
scale: "y2",
|
|
||||||
grid: { show: false },
|
|
||||||
stroke: publicMode ? fixedLineColors[1] : "#000000",
|
|
||||||
size: publicMode ? 60 : null,
|
|
||||||
space: publicMode ? 40 : null,
|
|
||||||
font: publicMode ? '16px Arial' : CanvasRenderingContext2D['font'],
|
|
||||||
label: publicMode ? null : `${metricData[1]?.name} (${metricData[1]?.unit?.prefix}${metricData[1]?.unit?.base})`,
|
|
||||||
values: (u, vals) => vals.map((v) => formatNumber(v)),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
// bands: plotBands,
|
|
||||||
padding: [5, 10, -20, 0],
|
|
||||||
hooks: {
|
|
||||||
// init: [
|
|
||||||
// (u) => {
|
|
||||||
// /* IF Zoom Enabled */
|
|
||||||
// if (resampleConfig && !forNode) {
|
|
||||||
// u.over.addEventListener("dblclick", (e) => {
|
|
||||||
// // console.log('Dispatch: Zoom Reset')
|
|
||||||
// onZoom({
|
|
||||||
// lastZoomState: {
|
|
||||||
// x: { time: false },
|
|
||||||
// y: { auto: true }
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
// };
|
|
||||||
// },
|
|
||||||
// ],
|
|
||||||
draw: publicMode ? null : [
|
|
||||||
(u) => {
|
|
||||||
// Draw plot type label:
|
|
||||||
let textl = `Cluster ${cluster}`
|
|
||||||
// let textl = `${scope}${plotSeries.length > 2 ? "s" : ""}${
|
|
||||||
// useStatsSeries
|
|
||||||
// ? (usesMeanStatsSeries ? ": min/mean/max" : ": min/median/max")
|
|
||||||
// : metricConfig != null && scope != metricConfig.scope
|
|
||||||
// ? ` (${metricConfig.aggregation})`
|
|
||||||
// : ""
|
|
||||||
// }`;
|
|
||||||
let textr = `Sums of ${numNodes} nodes`
|
|
||||||
//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 + (forNode ? 0 : 10));
|
|
||||||
u.ctx.textAlign = "end";
|
|
||||||
u.ctx.fillStyle = "black";
|
|
||||||
u.ctx.fillText(
|
|
||||||
textr,
|
|
||||||
u.bbox.left + u.bbox.width - 10,
|
|
||||||
u.bbox.top + (forNode ? 0 : 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 = "#000000";
|
|
||||||
// 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();
|
|
||||||
},
|
|
||||||
],
|
|
||||||
// setScale: [
|
|
||||||
// (u, key) => { // If ZoomResample is Configured && Not System/Node View
|
|
||||||
// if (resampleConfig && !forNode && key === 'x') {
|
|
||||||
// const numX = (u.series[0].idxs[1] - u.series[0].idxs[0])
|
|
||||||
// if (numX <= resampleTrigger && timestep !== resampleMinimum) {
|
|
||||||
// /* Get closest zoom level; prevents multiple iterative zoom requests for big zoom-steps (e.g. 600 -> 300 -> 120 -> 60) */
|
|
||||||
// // Which resolution to theoretically request to achieve 30 or more visible data points:
|
|
||||||
// const target = (numX * timestep) / resampleTrigger
|
|
||||||
// // Which configured resolution actually matches the closest to theoretical target:
|
|
||||||
// const closest = resampleResolutions.reduce(function(prev, curr) {
|
|
||||||
// return (Math.abs(curr - target) < Math.abs(prev - target) ? curr : prev);
|
|
||||||
// });
|
|
||||||
// // Prevents non-required dispatches
|
|
||||||
// if (timestep !== closest) {
|
|
||||||
// // console.log('Dispatch: Zoom with Res from / to', timestep, closest)
|
|
||||||
// onZoom({
|
|
||||||
// newRes: closest,
|
|
||||||
// lastZoomState: u?.scales,
|
|
||||||
// lastThreshold: thresholds?.normal
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// // console.log('Dispatch: Zoom Update States')
|
|
||||||
// onZoom({
|
|
||||||
// lastZoomState: u?.scales,
|
|
||||||
// lastThreshold: thresholds?.normal
|
|
||||||
// });
|
|
||||||
// };
|
|
||||||
// };
|
|
||||||
// },
|
|
||||||
// ]
|
|
||||||
},
|
|
||||||
scales: {
|
|
||||||
x: { time: false },
|
|
||||||
y1: { auto: true },
|
|
||||||
y2: { auto: true },
|
|
||||||
},
|
|
||||||
legend: {
|
|
||||||
// Display legend until max 12 Y-dataseries
|
|
||||||
show: true, // metricData.length <= 12 || useStatsSeries,
|
|
||||||
live: true // But This Plot always for 2 Data-Series
|
|
||||||
},
|
|
||||||
cursor: {
|
|
||||||
drag: { x: true, y: true },
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Handle Render
|
|
||||||
if (!uplot) {
|
|
||||||
// if (plotSync) {
|
|
||||||
// opts.cursor.sync = {
|
|
||||||
// key: plotSync.key,
|
|
||||||
// scales: ["x", null],
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (zoomState && metricConfig?.aggregation == "avg") {
|
|
||||||
// opts.scales = {...zoomState}
|
|
||||||
// } else if (zoomState && metricConfig?.aggregation == "sum") {
|
|
||||||
// // Allow Zoom In === Ymin changed
|
|
||||||
// if (zoomState.y.min !== 0) { // scope change?: only use zoomState if thresholds match
|
|
||||||
// if ((thresholdState === thresholds?.normal)) { opts.scales = {...zoomState} };
|
|
||||||
// } // else: reset scaling to default
|
|
||||||
// }
|
|
||||||
|
|
||||||
uplot = new uPlot(opts, plotData, plotWrapper);
|
|
||||||
} else {
|
|
||||||
uplot.setSize({ width: ren_width, height: ren_height });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onSizeChange(chg_width, chg_height) {
|
|
||||||
if (!uplot) return;
|
|
||||||
if (timeoutId != null) clearTimeout(timeoutId);
|
if (timeoutId != null) clearTimeout(timeoutId);
|
||||||
timeoutId = setTimeout(() => {
|
timeoutId = setTimeout(() => {
|
||||||
timeoutId = null;
|
timeoutId = null;
|
||||||
render(chg_width, chg_height);
|
render(chgWidth, chgHeight);
|
||||||
}, renderSleepTime);
|
}, renderSleepTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* On Mount */
|
function render(renWidth, renHeight) {
|
||||||
onMount(() => {
|
if (!uplot) {
|
||||||
if (plotWrapper) {
|
let opts = getOpts(renWidth, renHeight);
|
||||||
render(width, height);
|
uplot = new uPlot(opts, plotData, plotWrapper);
|
||||||
|
} else {
|
||||||
|
uplot.setSize({ width: renWidth, height: renHeight });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
/* On Destroy */
|
/* On Destroy */
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
@@ -636,8 +315,12 @@
|
|||||||
<div bind:this={plotWrapper} bind:clientWidth={width}
|
<div bind:this={plotWrapper} bind:clientWidth={width}
|
||||||
class={forNode ? 'py-2 rounded' : 'rounded'}
|
class={forNode ? 'py-2 rounded' : 'rounded'}
|
||||||
></div>
|
></div>
|
||||||
|
{:else if cluster}
|
||||||
|
<Card body color="warning" class="mx-4"
|
||||||
|
>Cannot render plot: No series data returned for <code>{cluster}</code>.</Card
|
||||||
|
>
|
||||||
{:else}
|
{:else}
|
||||||
<Card body color="warning" class="mx-4"
|
<Card body color="warning" class="mx-4"
|
||||||
>Cannot render plot: No series data returned for <code>{cluster}</code></Card
|
>Cannot render plot: No series data returned.</Card
|
||||||
>
|
>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -40,8 +40,7 @@
|
|||||||
useColors = true,
|
useColors = true,
|
||||||
useLegend = true,
|
useLegend = true,
|
||||||
colorBackground = false,
|
colorBackground = false,
|
||||||
width = 600,
|
height = 300,
|
||||||
height = 380,
|
|
||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
/* Const Init */
|
/* Const Init */
|
||||||
@@ -53,11 +52,12 @@
|
|||||||
|
|
||||||
/* State Init */
|
/* State Init */
|
||||||
let plotWrapper = $state(null);
|
let plotWrapper = $state(null);
|
||||||
|
let width = $state(0); // Wrapper Width
|
||||||
let uplot = $state(null);
|
let uplot = $state(null);
|
||||||
|
|
||||||
/* Effect */
|
/* Effect */
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (allowSizeChange) sizeChanged(width, height);
|
if (allowSizeChange) onSizeChange(width, height);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Copied Example Vars for Uplot Bubble
|
// Copied Example Vars for Uplot Bubble
|
||||||
@@ -517,11 +517,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Main Functions
|
// Main Functions
|
||||||
function sizeChanged() {
|
function onSizeChange() {
|
||||||
if (timeoutId != null) clearTimeout(timeoutId);
|
if (timeoutId != null) clearTimeout(timeoutId);
|
||||||
timeoutId = setTimeout(() => {
|
timeoutId = setTimeout(() => {
|
||||||
timeoutId = null;
|
timeoutId = null;
|
||||||
if (uplot) uplot.destroy();
|
if (uplot) uplot.destroy(); // Prevents Multi-Render
|
||||||
render(roofData, jobsData, nodesData);
|
render(roofData, jobsData, nodesData);
|
||||||
}, 200);
|
}, 200);
|
||||||
}
|
}
|
||||||
@@ -995,7 +995,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if roofData != null}
|
{#if roofData != null}
|
||||||
<div bind:this={plotWrapper} class="p-2"></div>
|
<div bind:this={plotWrapper} bind:clientWidth={width} class="p-2"></div>
|
||||||
{:else}
|
{:else}
|
||||||
<Card class="mx-4 my-2" body color="warning">Cannot render roofline: No data!</Card>
|
<Card class="mx-4 my-2" body color="warning">Cannot render roofline: No data!</Card>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
@component Node State/Health Data Stacked Plot Component, based on uPlot; states by timestamp
|
@component Node State/Health Data Stacked Plot Component, based on uPlot; states by timestamp
|
||||||
|
|
||||||
Properties:
|
Properties:
|
||||||
- `width Number?`: The plot width [Default: 0]
|
|
||||||
- `height Number?`: The plot height [Default: 300]
|
- `height Number?`: The plot height [Default: 300]
|
||||||
- `data [Array]`: The data object [Default: null]
|
- `data [Array]`: The data object [Default: null]
|
||||||
- `xlabel String?`: Plot X axis label [Default: ""]
|
- `xlabel String?`: Plot X axis label [Default: ""]
|
||||||
@@ -15,12 +14,11 @@
|
|||||||
<script>
|
<script>
|
||||||
import uPlot from "uplot";
|
import uPlot from "uplot";
|
||||||
import { formatUnixTime } from "../units.js";
|
import { formatUnixTime } from "../units.js";
|
||||||
import { getContext, onMount, onDestroy } from "svelte";
|
import { getContext, onDestroy } from "svelte";
|
||||||
import { Card } from "@sveltestrap/sveltestrap";
|
import { Card } from "@sveltestrap/sveltestrap";
|
||||||
|
|
||||||
/* Svelte 5 Props */
|
/* Svelte 5 Props */
|
||||||
let {
|
let {
|
||||||
width = 0,
|
|
||||||
height = 300,
|
height = 300,
|
||||||
data = null,
|
data = null,
|
||||||
xlabel = null,
|
xlabel = null,
|
||||||
@@ -129,17 +127,17 @@
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function getStackedOpts(title, width, height, series, data) {
|
function getStackedOpts(optTitle, optWidth, optHeight, optSeries, optData) {
|
||||||
let opts = {
|
let opts = {
|
||||||
width,
|
width: optWidth,
|
||||||
height,
|
height: optHeight,
|
||||||
title,
|
title: optTitle,
|
||||||
plugins: [legendAsTooltipPlugin()],
|
plugins: [legendAsTooltipPlugin()],
|
||||||
series,
|
series: optSeries,
|
||||||
axes: [
|
axes: [
|
||||||
{
|
{
|
||||||
scale: "x",
|
scale: "x",
|
||||||
space: 25, // Tick Spacing
|
// space: 25, // Tick Spacing
|
||||||
rotate: 30,
|
rotate: 30,
|
||||||
show: true,
|
show: true,
|
||||||
label: xlabel,
|
label: xlabel,
|
||||||
@@ -168,25 +166,25 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let stacked = stack(data, i => false);
|
let stacked = stack(optData, i => false);
|
||||||
opts.bands = stacked.bands;
|
opts.bands = stacked.bands;
|
||||||
|
|
||||||
opts.cursor = opts.cursor || {};
|
opts.cursor = opts.cursor || {};
|
||||||
opts.cursor.dataIdx = (u, seriesIdx, closestIdx, xValue) => {
|
opts.cursor.dataIdx = (u, seriesIdx, closestIdx, xValue) => {
|
||||||
return data[seriesIdx][closestIdx] == null ? null : closestIdx;
|
return optData[seriesIdx][closestIdx] == null ? null : closestIdx;
|
||||||
};
|
};
|
||||||
|
|
||||||
opts.series.forEach(s => {
|
opts.series.forEach(s => {
|
||||||
// Format Time Info from Unix TS to LocalTimeString
|
// Format Time Info from Unix TS to LocalTimeString
|
||||||
s.value = (u, v, si, i) => (si === 0) ? formatUnixTime(data[si][i]) : data[si][i];
|
s.value = (u, v, si, i) => (si === 0) ? formatUnixTime(optData[si][i]) : optData[si][i];
|
||||||
|
|
||||||
s.points = s.points || {};
|
s.points = s.points || {};
|
||||||
|
|
||||||
// scan raw unstacked data to return only real points
|
// scan raw unstacked optData to return only real points
|
||||||
s.points.filter = (u, seriesIdx, show, gaps) => {
|
s.points.filter = (u, seriesIdx, show, gaps) => {
|
||||||
if (show) {
|
if (show) {
|
||||||
let pts = [];
|
let pts = [];
|
||||||
data[seriesIdx].forEach((v, i) => {
|
optData[seriesIdx].forEach((v, i) => {
|
||||||
v != null && pts.push(i);
|
v != null && pts.push(i);
|
||||||
});
|
});
|
||||||
return pts;
|
return pts;
|
||||||
@@ -206,7 +204,7 @@
|
|||||||
opts.hooks = {
|
opts.hooks = {
|
||||||
setSeries: [
|
setSeries: [
|
||||||
(u, i) => {
|
(u, i) => {
|
||||||
let stacked = stack(data, i => !u.series[i].show);
|
let stacked = stack(optData, i => !u.series[i].show);
|
||||||
u.delBand(null);
|
u.delBand(null);
|
||||||
stacked.bands.forEach(b => u.addBand(b));
|
stacked.bands.forEach(b => u.addBand(b));
|
||||||
u.setData(stacked.data);
|
u.setData(stacked.data);
|
||||||
@@ -298,10 +296,11 @@
|
|||||||
|
|
||||||
/* Var Init */
|
/* Var Init */
|
||||||
let timeoutId = null;
|
let timeoutId = null;
|
||||||
let uplot = null;
|
|
||||||
|
|
||||||
/* State Init */
|
/* State Init */
|
||||||
let plotWrapper = $state(null);
|
let plotWrapper = $state(null);
|
||||||
|
let width = $state(0); // Wrapper Width
|
||||||
|
let uplot = $state(null);
|
||||||
|
|
||||||
/* Effects */
|
/* Effects */
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
@@ -311,6 +310,14 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
/* Functions */
|
/* Functions */
|
||||||
|
function onSizeChange(chg_width, chg_height) {
|
||||||
|
if (timeoutId != null) clearTimeout(timeoutId);
|
||||||
|
timeoutId = setTimeout(() => {
|
||||||
|
timeoutId = null;
|
||||||
|
render(chg_width, chg_height);
|
||||||
|
}, 200);
|
||||||
|
}
|
||||||
|
|
||||||
function render(ren_width, ren_height) {
|
function render(ren_width, ren_height) {
|
||||||
if (!uplot) {
|
if (!uplot) {
|
||||||
let { opts, data } = getStackedOpts(title, ren_width, ren_height, plotSeries, collectData);
|
let { opts, data } = getStackedOpts(title, ren_width, ren_height, plotSeries, collectData);
|
||||||
@@ -320,22 +327,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onSizeChange(chg_width, chg_height) {
|
|
||||||
if (!uplot) return;
|
|
||||||
if (timeoutId != null) clearTimeout(timeoutId);
|
|
||||||
timeoutId = setTimeout(() => {
|
|
||||||
timeoutId = null;
|
|
||||||
render(chg_width, chg_height);
|
|
||||||
}, 200);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* On Mount */
|
|
||||||
onMount(() => {
|
|
||||||
if (plotWrapper) {
|
|
||||||
render(width, height);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/* On Destroy */
|
/* On Destroy */
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
if (timeoutId != null) clearTimeout(timeoutId);
|
if (timeoutId != null) clearTimeout(timeoutId);
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ export function formatUnixTime(t, withDate = false) {
|
|||||||
return t;
|
return t;
|
||||||
} else {
|
} else {
|
||||||
if (withDate) return new Date(t * 1000).toLocaleString();
|
if (withDate) return new Date(t * 1000).toLocaleString();
|
||||||
else return new Date(t * 1000).toLocaleTimeString();
|
else return new Date(t * 1000).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,10 +58,6 @@
|
|||||||
let to = $state(new Date(Date.now()));
|
let to = $state(new Date(Date.now()));
|
||||||
let stackedFrom = $state(Math.floor(Date.now() / 1000) - 14400);
|
let stackedFrom = $state(Math.floor(Date.now() / 1000) - 14400);
|
||||||
let colWidthJobs = $state(0);
|
let colWidthJobs = $state(0);
|
||||||
let colWidthRoof = $state(0);
|
|
||||||
let colWidthTotals =$state(0);
|
|
||||||
let colWidthStacked1 = $state(0);
|
|
||||||
let colWidthStacked2 = $state(0);
|
|
||||||
|
|
||||||
/* Derived */
|
/* Derived */
|
||||||
// States for Stacked charts
|
// States for Stacked charts
|
||||||
@@ -531,13 +527,11 @@
|
|||||||
</Col>
|
</Col>
|
||||||
|
|
||||||
<Col> <!-- Job Roofline -->
|
<Col> <!-- Job Roofline -->
|
||||||
<div bind:clientWidth={colWidthRoof}>
|
<div>
|
||||||
{#key $statusQuery?.data?.jobsMetricStats}
|
{#key $statusQuery?.data?.jobsMetricStats}
|
||||||
<Roofline
|
<Roofline
|
||||||
useColors={true}
|
useColors={true}
|
||||||
allowSizeChange
|
allowSizeChange
|
||||||
width={colWidthRoof}
|
|
||||||
height={300}
|
|
||||||
subCluster={clusterInfo?.roofData ? clusterInfo.roofData : null}
|
subCluster={clusterInfo?.roofData ? clusterInfo.roofData : null}
|
||||||
roofData={transformJobsStatsToData($statusQuery?.data?.jobsMetricStats)}
|
roofData={transformJobsStatsToData($statusQuery?.data?.jobsMetricStats)}
|
||||||
jobsData={transformJobsStatsToInfo($statusQuery?.data?.jobsMetricStats)}
|
jobsData={transformJobsStatsToInfo($statusQuery?.data?.jobsMetricStats)}
|
||||||
@@ -547,24 +541,23 @@
|
|||||||
</Col>
|
</Col>
|
||||||
|
|
||||||
<Col> <!-- Total Cluster Metric in Time SUMS-->
|
<Col> <!-- Total Cluster Metric in Time SUMS-->
|
||||||
<div bind:clientWidth={colWidthTotals}>
|
<div>
|
||||||
|
{#key $statusQuery?.data?.clusterMetrics}
|
||||||
<DoubleMetric
|
<DoubleMetric
|
||||||
width={colWidthTotals}
|
|
||||||
timestep={$statusQuery?.data?.clusterMetrics[0]?.timestep || 60}
|
timestep={$statusQuery?.data?.clusterMetrics[0]?.timestep || 60}
|
||||||
numNodes={$statusQuery?.data?.clusterMetrics?.nodeCount || 0}
|
numNodes={$statusQuery?.data?.clusterMetrics?.nodeCount || 0}
|
||||||
metricData={$statusQuery?.data?.clusterMetrics?.metrics || []}
|
metricData={$statusQuery?.data?.clusterMetrics?.metrics || []}
|
||||||
cluster={presetCluster}
|
cluster={presetCluster}
|
||||||
fixLinewidth={2}
|
|
||||||
/>
|
/>
|
||||||
|
{/key}
|
||||||
</div>
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
|
|
||||||
<Col> <!-- Stacked SchedState -->
|
<Col> <!-- Stacked SchedState -->
|
||||||
<div bind:clientWidth={colWidthStacked1}>
|
<div>
|
||||||
{#key $statesTimed?.data?.nodeStates}
|
{#key $statesTimed?.data?.nodeStates}
|
||||||
<Stacked
|
<Stacked
|
||||||
data={$statesTimed?.data?.nodeStates}
|
data={$statesTimed?.data?.nodeStates}
|
||||||
width={colWidthStacked1}
|
|
||||||
height={330}
|
height={330}
|
||||||
xlabel="Time"
|
xlabel="Time"
|
||||||
ylabel="Nodes"
|
ylabel="Nodes"
|
||||||
@@ -577,11 +570,10 @@
|
|||||||
</Col>
|
</Col>
|
||||||
|
|
||||||
<Col> <!-- Stacked Healthstate -->
|
<Col> <!-- Stacked Healthstate -->
|
||||||
<div bind:clientWidth={colWidthStacked2}>
|
<div>
|
||||||
{#key $statesTimed?.data?.healthStates}
|
{#key $statesTimed?.data?.healthStates}
|
||||||
<Stacked
|
<Stacked
|
||||||
data={$statesTimed?.data?.healthStates}
|
data={$statesTimed?.data?.healthStates}
|
||||||
width={colWidthStacked2}
|
|
||||||
height={330}
|
height={330}
|
||||||
xlabel="Time"
|
xlabel="Time"
|
||||||
ylabel="Nodes"
|
ylabel="Nodes"
|
||||||
|
|||||||
@@ -42,9 +42,6 @@
|
|||||||
|
|
||||||
/* State Init */
|
/* State Init */
|
||||||
let pieWidth = $state(0);
|
let pieWidth = $state(0);
|
||||||
let stackedWidth1 = $state(0);
|
|
||||||
let stackedWidth2 = $state(0);
|
|
||||||
let plotWidths = $state([]);
|
|
||||||
let from = $state(new Date(Date.now() - 5 * 60 * 1000));
|
let from = $state(new Date(Date.now() - 5 * 60 * 1000));
|
||||||
let to = $state(new Date(Date.now()));
|
let to = $state(new Date(Date.now()));
|
||||||
let stackedFrom = $state(Math.floor(Date.now() / 1000) - 14400);
|
let stackedFrom = $state(Math.floor(Date.now() / 1000) - 14400);
|
||||||
@@ -414,14 +411,13 @@
|
|||||||
{#if $statesTimed.data}
|
{#if $statesTimed.data}
|
||||||
<Row cols={{ md: 2 , sm: 1}} class="mb-3 justify-content-center">
|
<Row cols={{ md: 2 , sm: 1}} class="mb-3 justify-content-center">
|
||||||
<Col class="px-3 mt-2 mt-lg-0">
|
<Col class="px-3 mt-2 mt-lg-0">
|
||||||
<div bind:clientWidth={stackedWidth1}>
|
<div>
|
||||||
{#key $statesTimed?.data?.nodeStates}
|
{#key $statesTimed?.data?.nodeStates}
|
||||||
<h4 class="text-center">
|
<h4 class="text-center">
|
||||||
{cluster.charAt(0).toUpperCase() + cluster.slice(1)} Node States Over Time
|
{cluster.charAt(0).toUpperCase() + cluster.slice(1)} Node States Over Time
|
||||||
</h4>
|
</h4>
|
||||||
<Stacked
|
<Stacked
|
||||||
data={$statesTimed?.data?.nodeStates}
|
data={$statesTimed?.data?.nodeStates}
|
||||||
width={stackedWidth1 * 0.95}
|
|
||||||
xlabel="Time"
|
xlabel="Time"
|
||||||
ylabel="Nodes"
|
ylabel="Nodes"
|
||||||
yunit = "#Count"
|
yunit = "#Count"
|
||||||
@@ -432,14 +428,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
<Col class="px-3 mt-2 mt-lg-0">
|
<Col class="px-3 mt-2 mt-lg-0">
|
||||||
<div bind:clientWidth={stackedWidth2}>
|
<div>
|
||||||
{#key $statesTimed?.data?.healthStates}
|
{#key $statesTimed?.data?.healthStates}
|
||||||
<h4 class="text-center">
|
<h4 class="text-center">
|
||||||
{cluster.charAt(0).toUpperCase() + cluster.slice(1)} Health States Over Time
|
{cluster.charAt(0).toUpperCase() + cluster.slice(1)} Health States Over Time
|
||||||
</h4>
|
</h4>
|
||||||
<Stacked
|
<Stacked
|
||||||
data={$statesTimed?.data?.healthStates}
|
data={$statesTimed?.data?.healthStates}
|
||||||
width={stackedWidth2 * 0.95}
|
|
||||||
xlabel="Time"
|
xlabel="Time"
|
||||||
ylabel="Nodes"
|
ylabel="Nodes"
|
||||||
yunit = "#Count"
|
yunit = "#Count"
|
||||||
@@ -628,13 +623,11 @@
|
|||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
<Col class="px-3 mt-2 mt-lg-0">
|
<Col class="px-3 mt-2 mt-lg-0">
|
||||||
<div bind:clientWidth={plotWidths[i]}>
|
<div>
|
||||||
{#key $statusQuery?.data?.nodeMetrics}
|
{#key $statusQuery?.data?.nodeMetrics}
|
||||||
<Roofline
|
<Roofline
|
||||||
useColors={true}
|
useColors={true}
|
||||||
allowSizeChange
|
allowSizeChange
|
||||||
width={plotWidths[i] - 10}
|
|
||||||
height={300}
|
|
||||||
cluster={cluster}
|
cluster={cluster}
|
||||||
subCluster={subCluster}
|
subCluster={subCluster}
|
||||||
roofData={transformNodesStatsToData($statusQuery?.data?.nodeMetrics.filter(
|
roofData={transformNodesStatsToData($statusQuery?.data?.nodeMetrics.filter(
|
||||||
@@ -650,13 +643,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
<Col class="px-3 mt-2 mt-lg-0">
|
<Col class="px-3 mt-2 mt-lg-0">
|
||||||
<div bind:clientWidth={plotWidths[i]}>
|
<div>
|
||||||
{#key $statusQuery?.data?.jobsMetricStats}
|
{#key $statusQuery?.data?.jobsMetricStats}
|
||||||
<Roofline
|
<Roofline
|
||||||
useColors={true}
|
useColors={true}
|
||||||
allowSizeChange
|
allowSizeChange
|
||||||
width={plotWidths[i] - 10}
|
|
||||||
height={300}
|
|
||||||
subCluster={subCluster}
|
subCluster={subCluster}
|
||||||
roofData={transformJobsStatsToData($statusQuery?.data?.jobsMetricStats.filter(
|
roofData={transformJobsStatsToData($statusQuery?.data?.jobsMetricStats.filter(
|
||||||
(data) => data.subCluster == subCluster.name,
|
(data) => data.subCluster == subCluster.name,
|
||||||
|
|||||||
Reference in New Issue
Block a user