From 719aaaff4bb213f99dbc76cde45512c4b217eb49 Mon Sep 17 00:00:00 2001 From: Christoph Kluge Date: Tue, 27 Jan 2026 17:39:16 +0100 Subject: [PATCH 1/2] fix data flipping on doublemetric render --- web/frontend/src/DashPublic.root.svelte | 8 ++++--- .../src/generic/plots/DoubleMetricPlot.svelte | 21 ++++++++++--------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/web/frontend/src/DashPublic.root.svelte b/web/frontend/src/DashPublic.root.svelte index 91f4664c..9c17e7d8 100644 --- a/web/frontend/src/DashPublic.root.svelte +++ b/web/frontend/src/DashPublic.root.svelte @@ -286,6 +286,8 @@ sort((a, b) => b.count - a.count) }); + const sortedClusterMetrics = $derived($statusQuery?.data?.clusterMetrics?.metrics.sort((a, b) => b.name.localeCompare(a.name))); + /* Functions */ function transformNodesStatsToData(subclusterData) { let data = null @@ -516,10 +518,10 @@
Cluster Utilization ( - {`${$statusQuery?.data?.clusterMetrics?.metrics[0]?.name} (${$statusQuery?.data?.clusterMetrics?.metrics[0]?.unit?.prefix}${$statusQuery?.data?.clusterMetrics?.metrics[0]?.unit?.base})`} + {`${sortedClusterMetrics[0]?.name} (${sortedClusterMetrics[0]?.unit?.prefix}${sortedClusterMetrics[0]?.unit?.base})`} , - {`${$statusQuery?.data?.clusterMetrics?.metrics[1]?.name} (${$statusQuery?.data?.clusterMetrics?.metrics[1]?.unit?.prefix}${$statusQuery?.data?.clusterMetrics?.metrics[1]?.unit?.base})`} + {`${sortedClusterMetrics[1]?.name} (${sortedClusterMetrics[1]?.unit?.prefix}${sortedClusterMetrics[1]?.unit?.base})`} )
@@ -528,7 +530,7 @@ diff --git a/web/frontend/src/generic/plots/DoubleMetricPlot.svelte b/web/frontend/src/generic/plots/DoubleMetricPlot.svelte index c865dd68..e94e269d 100644 --- a/web/frontend/src/generic/plots/DoubleMetricPlot.svelte +++ b/web/frontend/src/generic/plots/DoubleMetricPlot.svelte @@ -4,7 +4,7 @@ Only width/height should change reactively. Properties: - - `metricData [Data]`: Two series of metric data including unit info + - `metricData [Data]`: Two series of metric data including unit info, unsorted - `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: ""] @@ -46,10 +46,11 @@ let uplot = $state(null); /* Derived */ + const sortedMetricData = $derived(publicMode ? [...metricData] : metricData.sort((a, b) => b.name.localeCompare(a.name))); // PublicMode: Presorted const maxX = $derived(longestSeries * timestep); const lineWidth = $derived(publicMode ? 2 : clusterCockpitConfig.plotConfiguration_lineWidth / window.devicePixelRatio); const longestSeries = $derived.by(() => { - return metricData.reduce((n, m) => Math.max(n, m.data.length), 0); + return sortedMetricData.reduce((n, m) => Math.max(n, m.data.length), 0); }); // Derive Plot Params @@ -68,8 +69,8 @@ }; }; // Y - for (let i = 0; i < metricData.length; i++) { - pendingData.push(metricData[i]?.data); + for (let i = 0; i < sortedMetricData.length; i++) { + pendingData.push(sortedMetricData[i]?.data); }; return pendingData; }) @@ -84,9 +85,9 @@ } ]; // Y - for (let i = 0; i < metricData.length; i++) { + for (let i = 0; i < sortedMetricData.length; i++) { pendingSeries.push({ - label: publicMode ? null : `${metricData[i]?.name} (${metricData[i]?.unit?.prefix}${metricData[i]?.unit?.base})`, + label: publicMode ? null : `${sortedMetricData[i]?.name} (${sortedMetricData[i]?.unit?.prefix}${sortedMetricData[i]?.unit?.base})`, scale: `y${i+1}`, width: lineWidth, stroke: fixedLineColors[i], @@ -156,9 +157,9 @@ // X baseOpts.axes[0].label = 'Time'; // Y1 - baseOpts.axes[1].label = `${metricData[0]?.name} (${metricData[0]?.unit?.prefix}${metricData[0]?.unit?.base})`; + baseOpts.axes[1].label = `${sortedMetricData[0]?.name} (${sortedMetricData[0]?.unit?.prefix}${sortedMetricData[0]?.unit?.base})`; // Y2 - baseOpts.axes[2].label = `${metricData[1]?.name} (${metricData[1]?.unit?.prefix}${metricData[1]?.unit?.base})`; + baseOpts.axes[2].label = `${sortedMetricData[1]?.name} (${sortedMetricData[1]?.unit?.prefix}${sortedMetricData[1]?.unit?.base})`; baseOpts.hooks.draw = [ (u) => { // Draw plot type label: @@ -212,7 +213,7 @@ style = { backgroundColor: "rgba(255, 249, 196, 0.92)", color: "black" }, } = {}) { let legendEl; - const dataSize = metricData.length; + const dataSize = sortedMetricData.length; function init(u, opts) { legendEl = u.root.querySelector(".u-legend"); @@ -311,7 +312,7 @@ -{#if metricData[0]?.data && metricData[0]?.data?.length > 0} +{#if sortedMetricData[0]?.data && sortedMetricData[0]?.data?.length > 0}
From 9d9babe94ddd3f4aa793b1b9d2c5e5bd1706bac5 Mon Sep 17 00:00:00 2001 From: Christoph Kluge Date: Tue, 27 Jan 2026 19:04:29 +0100 Subject: [PATCH 2/2] review clusterMetrics aggregation handling, fixes index error --- internal/graph/schema.resolvers.go | 36 +++++++++++++++++------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/internal/graph/schema.resolvers.go b/internal/graph/schema.resolvers.go index 3e142f9a..9bc8811d 100644 --- a/internal/graph/schema.resolvers.go +++ b/internal/graph/schema.resolvers.go @@ -905,26 +905,32 @@ func (r *queryResolver) ClusterMetrics(ctx context.Context, cluster string, metr for _, metrics := range data { clusterMetrics.NodeCount += 1 for metric, scopedMetrics := range metrics { - _, ok := collectorData[metric] - if !ok { - collectorData[metric] = make([]schema.Float, 0) - for _, scopedMetric := range scopedMetrics { - // Collect Info + for _, scopedMetric := range scopedMetrics { + // Collect Info Once + _, okTimestep := collectorTimestep[metric] + if !okTimestep { collectorTimestep[metric] = scopedMetric.Timestep - collectorUnit[metric] = scopedMetric.Unit - // Collect Initial Data - for _, ser := range scopedMetric.Series { - collectorData[metric] = append(collectorData[metric], ser.Data...) - } } - } else { - // Sum up values by index - for _, scopedMetric := range scopedMetrics { - // For This Purpose (Cluster_Wide-Sum of Node Metrics) OK - for _, ser := range scopedMetric.Series { + _, okUnit := collectorUnit[metric] + if !okUnit { + collectorUnit[metric] = scopedMetric.Unit + } + // Collect Data + for _, ser := range scopedMetric.Series { + _, okData := collectorData[metric] + // Init With Datasize > 0 + if !okData && len(ser.Data) != 0 { + collectorData[metric] = make([]schema.Float, len(ser.Data)) + } else if !okData { + cclog.Debugf("ClusterMetrics Skip Init: No Data -> %s at %s; Size %d", metric, ser.Hostname, len(ser.Data)) + } + // Sum if init'd and matching size + if okData && len(ser.Data) == len(collectorData[metric]) { for i, val := range ser.Data { collectorData[metric][i] += val } + } else if okData { + cclog.Debugf("ClusterMetrics Skip Sum: Data Diff -> %s at %s; Want Size %d, Have Size %d", metric, ser.Hostname, len(collectorData[metric]), len(ser.Data)) } } }