review handling of disabled metrics in frontend

This commit is contained in:
Christoph Kluge
2026-02-09 15:33:59 +01:00
parent 7dd3ee3084
commit d1e7ea09bc
5 changed files with 189 additions and 196 deletions

View File

@@ -333,7 +333,18 @@
{:else if thisJob && $jobMetrics?.data?.scopedJobStats} {:else if thisJob && $jobMetrics?.data?.scopedJobStats}
<!-- Note: Ignore '#snippet' Error in IDE --> <!-- Note: Ignore '#snippet' Error in IDE -->
{#snippet gridContent(item)} {#snippet gridContent(item)}
{#if item.data} {#if item?.disabled}
<Card color="info" class="mt-2">
<CardHeader class="mb-0">
<b>Disabled Metric</b>
</CardHeader>
<CardBody>
<p>No dataset(s) returned for <b>{item.metric}</b></p>
<p class="mb-1">Metric has been disabled for subcluster <b>{thisJob.subCluster}</b>.</p>
<p class="mb-1">To remove this card, open metric selection, de-select the metric, and press "Close and Apply".</p>
</CardBody>
</Card>
{:else if item?.data}
<Metric <Metric
bind:this={plots[item.metric]} bind:this={plots[item.metric]}
job={thisJob} job={thisJob}
@@ -343,16 +354,6 @@
presetScopes={item.data.map((x) => x.scope)} presetScopes={item.data.map((x) => x.scope)}
isShared={thisJob.shared != "none"} isShared={thisJob.shared != "none"}
/> />
{:else if item.disabled == true}
<Card color="info">
<CardHeader class="mb-0">
<b>Disabled Metric</b>
</CardHeader>
<CardBody>
<p>Metric <b>{item.metric}</b> is disabled for cluster <b>{thisJob.cluster}:{thisJob.subCluster}</b>.</p>
<p class="mb-1">To remove this card, open metric selection and press "Close and Apply".</p>
</CardBody>
</Card>
{:else} {:else}
<Card color="warning" class="mt-2"> <Card color="warning" class="mt-2">
<CardHeader class="mb-0"> <CardHeader class="mb-0">

View File

@@ -253,12 +253,15 @@
forNode forNode
/> />
{:else if item.disabled === true && item.metric} {:else if item.disabled === true && item.metric}
<Card style="margin-left: 2rem;margin-right: 2rem;" body color="info" <Card color="info" class="mx-2">
>Metric disabled for subcluster <code <CardHeader class="mb-0">
>{item.name}:{$nodeMetricsData.data.nodeMetrics[0] <b>Disabled Metric</b>
.subCluster}</code </CardHeader>
></Card <CardBody>
> <p>No dataset(s) returned for <b>{item.name}</b></p>
<p class="mb-1">Metric has been disabled for subcluster <b>{$nodeMetricsData.data.nodeMetrics[0].subCluster}</b>.</p>
</CardBody>
</Card>
{:else} {:else}
<Card color="warning" class="mx-2"> <Card color="warning" class="mx-2">
<CardHeader class="mb-0"> <CardHeader class="mb-0">

View File

@@ -99,7 +99,7 @@
}) })
); );
const refinedData = $derived($metricsQuery?.data?.jobMetrics ? sortAndSelectScope($metricsQuery.data.jobMetrics) : []); const refinedData = $derived($metricsQuery?.data?.jobMetrics ? sortAndSelectScope(metrics, $metricsQuery.data.jobMetrics) : []);
/* Effects */ /* Effects */
$effect(() => { $effect(() => {
@@ -140,6 +140,26 @@
}); });
} }
function sortAndSelectScope(metricList = [], jobMetrics = []) {
const pendingData = [];
metricList.forEach((metricName) => {
const pendingMetric = {
name: metricName,
disabled: checkMetricDisabled(
globalMetrics,
metricName,
job.cluster,
job.subCluster,
),
data: null
};
const scopesData = jobMetrics.filter((jobMetric) => jobMetric.name == metricName)
if (scopesData.length > 0) pendingMetric.data = selectScope(scopesData)
pendingData.push(pendingMetric)
});
return pendingData;
};
const selectScope = (jobMetrics) => const selectScope = (jobMetrics) =>
jobMetrics.reduce( jobMetrics.reduce(
(a, b) => (a, b) =>
@@ -152,30 +172,6 @@
: a, : a,
jobMetrics[0], jobMetrics[0],
); );
const sortAndSelectScope = (jobMetrics) =>
metrics
.map((name) => jobMetrics.filter((jobMetric) => jobMetric.name == name))
.map((jobMetrics) => ({
disabled: false,
data: jobMetrics.length > 0 ? selectScope(jobMetrics) : null,
}))
.map((jobMetric) => {
if (jobMetric.data) {
return {
name: jobMetric.data.name,
disabled: checkMetricDisabled(
globalMetrics,
jobMetric.data.name,
job.cluster,
job.subCluster,
),
data: jobMetric.data,
};
} else {
return jobMetric;
}
});
</script> </script>
<tr> <tr>
@@ -211,39 +207,36 @@
{/if} {/if}
{#each refinedData as metric, i (metric?.name || i)} {#each refinedData as metric, i (metric?.name || i)}
<td> <td>
{#key metric} {#if metric?.disabled}
{#if metric?.data} <Card body class="mx-2" color="info">
{#if metric?.disabled} <p>No dataset(s) returned for <b>{metrics[i]}</b></p>
<Card body class="mx-2" color="info"> <p class="mb-1">Metric has been disabled for subcluster <b>{job.subCluster}</b>.</p>
Metric <b>{metric.data.name}</b>: Disabled for subcluster <code>{job.subCluster}</code> </Card>
</Card> {:else if metric?.data}
{:else} <MetricPlot
<MetricPlot onZoom={(detail) => handleZoom(detail, metric.data.name)}
onZoom={(detail) => handleZoom(detail, metric.data.name)} height={plotHeight}
height={plotHeight} timestep={metric.data.metric.timestep}
timestep={metric.data.metric.timestep} scope={metric.data.scope}
scope={metric.data.scope} series={metric.data.metric.series}
series={metric.data.metric.series} statisticsSeries={metric.data.metric.statisticsSeries}
statisticsSeries={metric.data.metric.statisticsSeries} metric={metric.data.name}
metric={metric.data.name} cluster={clusterInfos.find((c) => c.name == job.cluster)}
cluster={clusterInfos.find((c) => c.name == job.cluster)} subCluster={job.subCluster}
subCluster={job.subCluster} isShared={job.shared != "none"}
isShared={job.shared != "none"} numhwthreads={job.numHWThreads}
numhwthreads={job.numHWThreads} numaccs={job.numAcc}
numaccs={job.numAcc} zoomState={zoomStates[metric.data.name] || null}
zoomState={zoomStates[metric.data.name] || null} thresholdState={thresholdStates[metric.data.name] || null}
thresholdState={thresholdStates[metric.data.name] || null} />
/> {:else}
{/if} <Card body class="mx-2" color="warning">
{:else} <p>No dataset(s) returned for <b>{metrics[i]}</b></p>
<Card body class="mx-2" color="warning"> <p class="mb-1">Metric or host was not found in metric store for cluster <b>{job.cluster}</b>:</p>
<p>No dataset(s) returned for <b>{metrics[i]}</b></p> <p class="mb-1">Identical messages in <i>{metrics[i]} column</i>: Metric not found.</p>
<p class="mb-1">Metric or host was not found in metric store for cluster <b>{job.cluster}</b>:</p> <p class="mb-1">Identical messages in <i>job {job.jobId} row</i>: Host not found.</p>
<p class="mb-1">Identical messages in <i>{metrics[i]} column</i>: Metric not found.</p> </Card>
<p class="mb-1">Identical messages in <i>job {job.jobId} row</i>: Host not found.</p> {/if}
</Card>
{/if}
{/key}
</td> </td>
{:else} {:else}
<td> <td>

View File

@@ -110,7 +110,7 @@
}; };
}); });
}; };
let pendingMapped = []; let pendingMapped = [];
if (rawData.length > 0) { if (rawData.length > 0) {
pendingMapped = rawData.map((h) => ({ pendingMapped = rawData.map((h) => ({
@@ -120,12 +120,11 @@
data: h.metrics.filter( data: h.metrics.filter(
(m) => m?.name == selectedMetric && m.scope == "node", (m) => m?.name == selectedMetric && m.scope == "node",
), ),
// TODO: Move To New Func Variant With Disabled Check on WHole Cluster Level: This never Triggers!
disabled: checkMetricDisabled(globalMetrics, selectedMetric, cluster, h.subCluster), disabled: checkMetricDisabled(globalMetrics, selectedMetric, cluster, h.subCluster),
})) }))
.sort((a, b) => a.host.localeCompare(b.host)) .sort((a, b) => a.host.localeCompare(b.host))
} }
return pendingMapped; return pendingMapped;
} }
</script> </script>
@@ -162,35 +161,32 @@
</Badge> </Badge>
</span> </span>
</div> </div>
{#if item?.data} {#if item?.disabled}
{#if item.disabled === true} <Card color="info">
<!-- TODO: Will never be Shown: Overview Single Metric Return Will be Null, see Else Case--> <CardHeader class="mb-0">
<Card body class="mx-3" color="info" <b>Disabled Metric</b>
>Metric disabled for subcluster <code </CardHeader>
>{selectedMetric}:{item.subCluster}</code <CardBody>
></Card <p>No dataset(s) returned for <b>{selectedMetric}</b></p>
> <p class="mb-1">Metric has been disabled for subcluster <b>{item.subCluster}</b>.</p>
{:else if item.disabled === false} </CardBody>
<!-- "No Data"-Warning included in MetricPlot-Component --> </Card>
<!-- #key: X-axis keeps last selected timerange otherwise --> {:else if item?.data}
{#key item.data[0].metric.series[0].data.length} <!-- "Empty Series"-Warning included in MetricPlot-Component -->
<MetricPlot <!-- #key: X-axis keeps last selected timerange otherwise -->
timestep={item.data[0].metric.timestep} {#key item.data[0].metric.series[0].data.length}
series={item.data[0].metric.series} <MetricPlot
metric={item.data[0].name} timestep={item.data[0].metric.timestep}
{cluster} series={item.data[0].metric.series}
subCluster={item.subCluster} metric={item.data[0].name}
forNode {cluster}
enableFlip subCluster={item.subCluster}
/> forNode
{/key} enableFlip
{:else} />
<Card body class="mx-3" color="info"> {/key}
Global Metric List Not Initialized
Can not determine {selectedMetric} availability: Please Reload Page
</Card>
{/if}
{:else} {:else}
<!-- Should Not Appear -->
<Card color="warning"> <Card color="warning">
<CardHeader class="mb-0"> <CardHeader class="mb-0">
<b>Missing Metric</b> <b>Missing Metric</b>
@@ -205,10 +201,22 @@
{/each} {/each}
{/key} {/key}
</Row> </Row>
{:else if hostnameFilter || hoststateFilter != 'all'}
<Row class="mx-1">
<Card class="px-0">
<CardHeader>
<b>Empty Filter Return</b>
</CardHeader>
<CardBody>
<p>No datasets returned for <b>{selectedMetric}</b>.</p>
<p class="mb-1">Hostname filter and/or host state filter returned no matches.</p>
</CardBody>
</Card>
</Row>
{:else} {:else}
<Row> <Row class="mx-1">
<Card color="warning"> <Card class="px-0" color="warning">
<CardHeader class="mb-0"> <CardHeader>
<b>Missing Metric</b> <b>Missing Metric</b>
</CardHeader> </CardHeader>
<CardBody> <CardBody>

View File

@@ -72,10 +72,30 @@
); );
const extendedLegendData = $derived($nodeJobsData?.data ? buildExtendedLegend() : null); const extendedLegendData = $derived($nodeJobsData?.data ? buildExtendedLegend() : null);
const refinedData = $derived(nodeData?.metrics ? sortAndSelectScope(nodeData.metrics) : []); const refinedData = $derived(nodeData?.metrics ? sortAndSelectScope(selectedMetrics, nodeData.metrics) : []);
const dataHealth = $derived(refinedData.filter((rd) => rd.disabled === false).map((enabled) => (enabled?.data?.metric?.series?.length > 0))); const dataHealth = $derived(refinedData.filter((rd) => rd.disabled === false).map((enabled) => (enabled?.data?.metric?.series?.length > 0)));
/* Functions */ /* Functions */
function sortAndSelectScope(metricList = [], nodeMetrics = []) {
const pendingData = [];
metricList.forEach((metricName) => {
const pendingMetric = {
name: metricName,
disabled: checkMetricDisabled(
globalMetrics,
metricName,
cluster,
nodeData.subCluster,
),
data: null
};
const scopesData = nodeMetrics.filter((nodeMetric) => nodeMetric.name == metricName)
if (scopesData.length > 0) pendingMetric.data = selectScope(scopesData)
pendingData.push(pendingMetric)
});
return pendingData;
};
const selectScope = (nodeMetrics) => const selectScope = (nodeMetrics) =>
nodeMetrics.reduce( nodeMetrics.reduce(
(a, b) => (a, b) =>
@@ -83,29 +103,6 @@
nodeMetrics[0], nodeMetrics[0],
); );
const sortAndSelectScope = (allNodeMetrics) =>
selectedMetrics
.map((selectedName) => allNodeMetrics.filter((nodeMetric) => nodeMetric.name == selectedName))
.map((matchedNodeMetrics) => ({
disabled: false,
data: matchedNodeMetrics.length > 0 ? selectScope(matchedNodeMetrics) : null,
}))
.map((scopedNodeMetric) => {
if (scopedNodeMetric?.data) {
return {
disabled: checkMetricDisabled(
globalMetrics,
scopedNodeMetric.data.name,
cluster,
nodeData.subCluster,
),
data: scopedNodeMetric.data,
};
} else {
return scopedNodeMetric;
}
});
function buildExtendedLegend() { function buildExtendedLegend() {
let pendingExtendedLegendData = null let pendingExtendedLegendData = null
// Build Extended for allocated nodes [Commented: Only Build extended Legend For Shared Nodes] // Build Extended for allocated nodes [Commented: Only Build extended Legend For Shared Nodes]
@@ -171,68 +168,59 @@
{/if} {/if}
</td> </td>
{#each refinedData as metricData, i (metricData?.data?.name || i)} {#each refinedData as metricData, i (metricData?.data?.name || i)}
{#key metricData} <td>
<td> {#if metricData?.disabled}
{#if metricData?.disabled} <Card body class="mx-2" color="info">
<Card body class="mx-2" color="info" <p>No dataset(s) returned for <b>{selectedMetrics[i]}</b></p>
>Metric <b>{selectedMetrics[i]}</b> disabled for subcluster <code <p class="mb-1">Metric has been disabled for subcluster <b>{nodeData.subCluster}</b>.</p>
>{nodeData.subCluster}</code </Card>
></Card {:else if !metricData?.data}
> <Card body class="mx-2" color="warning">
{:else if !metricData?.data} <p>No dataset(s) returned for <b>{selectedMetrics[i]}</b></p>
<Card body class="mx-2" color="warning"> <p class="mb-1">Metric was not found in metric store for cluster <b>{cluster}</b>.</p>
<p>No dataset(s) returned for <b>{selectedMetrics[i]}</b></p> </Card>
<p class="mb-1">Metric was not found in metric store for cluster <b>{cluster}</b>.</p> {:else if !!metricData.data?.metric.statisticsSeries}
</Card> <!-- "No Data"-Warning included in MetricPlot-Component -->
{:else if !metricData?.data?.name} <MetricPlot
<Card body class="mx-2" color="warning" {cluster}
>Metric without name for subcluster <code subCluster={nodeData.subCluster}
>{`Metric Index ${i}`}:{nodeData.subCluster}</code metric={metricData.data.name}
></Card scope={metricData.data.scope}
> timestep={metricData.data.metric.timestep}
{:else if !!metricData.data?.metric.statisticsSeries} series={metricData.data.metric.series}
<!-- "No Data"-Warning included in MetricPlot-Component --> statisticsSeries={metricData.data?.metric.statisticsSeries}
<MetricPlot useStatsSeries={!!metricData.data?.metric.statisticsSeries}
{cluster} height={175}
subCluster={nodeData.subCluster} {plotSync}
metric={metricData.data.name} forNode
scope={metricData.data.scope} />
timestep={metricData.data.metric.timestep} <div class="my-2"></div>
series={metricData.data.metric.series} {#key extendedLegendData}
statisticsSeries={metricData.data?.metric.statisticsSeries} <MetricPlot
useStatsSeries={!!metricData.data?.metric.statisticsSeries} {cluster}
height={175} subCluster={nodeData.subCluster}
{plotSync} metric={metricData.data.name}
forNode scope={metricData.data.scope}
/> timestep={metricData.data.metric.timestep}
<div class="my-2"></div> series={metricData.data.metric.series}
{#key extendedLegendData} height={175}
<MetricPlot {extendedLegendData}
{cluster} {plotSync}
subCluster={nodeData.subCluster} forNode
metric={metricData.data.name} />
scope={metricData.data.scope} {/key}
timestep={metricData.data.metric.timestep} {:else}
series={metricData.data.metric.series} <MetricPlot
height={175} {cluster}
{extendedLegendData} subCluster={nodeData.subCluster}
{plotSync} metric={metricData.data.name}
forNode scope={metricData.data.scope}
/> timestep={metricData.data.metric.timestep}
{/key} series={metricData.data.metric.series}
{:else} height={375}
<MetricPlot forNode
{cluster} />
subCluster={nodeData.subCluster} {/if}
metric={metricData.data.name} </td>
scope={metricData.data.scope}
timestep={metricData.data.metric.timestep}
series={metricData.data.metric.series}
height={375}
forNode
/>
{/if}
</td>
{/key}
{/each} {/each}
</tr> </tr>