change: remove heuristic metricHealth, replace with DB metricHealth

- add metricHealth to single Node view
This commit is contained in:
Christoph Kluge
2026-03-19 15:55:58 +01:00
parent 886791cf8a
commit 10b4fa5a06
12 changed files with 171 additions and 104 deletions

View File

@@ -6,8 +6,8 @@
- `subCluster String`: The nodes' subCluster [Default: ""]
- `pendingSelectedMetrics [String]`: The array of selected metrics [Default []]
- `selectedResolution Number?`: The selected data resolution [Default: 0]
- `hostnameFilter String?`: The active hostnamefilter [Default: ""]
- `hoststateFilter String?`: The active hoststatefilter [Default: ""]
- `hostnameFilter String?`: The active hostname filter [Default: ""]
- `nodeStateFilter String?`: The active nodeState filter [Default: ""]
- `systemUnits Object`: The object of metric units [Default: null]
- `from Date?`: The selected "from" date [Default: null]
- `to Date?`: The selected "to" date [Default: null]
@@ -28,7 +28,7 @@
pendingSelectedMetrics = [],
selectedResolution = 0,
hostnameFilter = "",
hoststateFilter = "",
nodeStateFilter = "",
systemUnits = null,
from = null,
to = null
@@ -54,7 +54,8 @@
) {
items {
host
state
nodeState
metricHealth
subCluster
metrics {
name
@@ -110,7 +111,7 @@
variables: {
cluster: cluster,
subCluster: subCluster,
stateFilter: hoststateFilter,
stateFilter: nodeStateFilter,
nodeFilter: hostnameFilter,
scopes: ["core", "socket", "accelerator"],
metrics: pendingSelectedMetrics,
@@ -164,7 +165,7 @@
$effect(() => {
// Update NodeListRows metrics only: Keep ordered nodes on page 1
hostnameFilter, hoststateFilter
hostnameFilter, nodeStateFilter
// Continous Scroll: Paging if parameters change: Existing entries will not match new selections
nodes = [];
if (!usePaging) {

View File

@@ -5,8 +5,8 @@
- `ccconfig Object?`: The ClusterCockpit Config Context [Default: null]
- `cluster String`: The cluster to show status information for
- `selectedMetric String?`: The selectedMetric input [Default: ""]
- `hostnameFilter String?`: The active hostnamefilter [Default: ""]
- `hostnameFilter String?`: The active hoststatefilter [Default: ""]
- `hostnameFilter String?`: The active hostname filter [Default: ""]
- `nodeStateFilter String?`: The active nodeState filter [Default: ""]
- `from Date?`: The selected "from" date [Default: null]
- `to Date?`: The selected "to" date [Default: null]
- `globalMetrics [Obj]`: Includes the backend supplied availabilities for cluster and subCluster
@@ -24,7 +24,7 @@
cluster = "",
selectedMetric = "",
hostnameFilter = "",
hoststateFilter = "",
nodeStateFilter = "",
from = null,
to = null,
globalMetrics
@@ -55,7 +55,7 @@
to: $to
) {
host
state
nodeState
subCluster
metrics {
name
@@ -91,11 +91,11 @@
const mappedData = $derived(handleQueryData($nodesQuery?.data));
const filteredData = $derived(mappedData.filter((h) => {
if (hostnameFilter) {
if (hoststateFilter == 'all') return h.host.includes(hostnameFilter)
else return (h.host.includes(hostnameFilter) && h.state == hoststateFilter)
if (nodeStateFilter == 'all') return h.host.includes(hostnameFilter)
else return (h.host.includes(hostnameFilter) && h.nodeState == nodeStateFilter)
} else {
if (hoststateFilter == 'all') return true
else return h.state == hoststateFilter
if (nodeStateFilter == 'all') return true
else return h.nodeState == nodeStateFilter
}
}));
@@ -116,7 +116,7 @@
if (rawData.length > 0) {
pendingMapped = rawData.map((h) => ({
host: h.host,
state: h?.state? h.state : 'notindb',
nodeState: h?.nodeState || 'notindb',
subCluster: h.subCluster,
data: h.metrics.filter(
(m) => m?.name == selectedMetric && m.scope == "node",
@@ -157,8 +157,8 @@
>
</h4>
<span style="margin-right: 0.5rem;">
<Badge color={stateColors[item?.state? item.state : 'notindb']}>
State: {item?.state? item.state.charAt(0).toUpperCase() + item.state.slice(1) : 'Not in DB'}
<Badge color={stateColors[item?.nodeState || 'notindb']}>
State: {item?.nodeState ? item.nodeState.charAt(0).toUpperCase() + item.nodeState.slice(1) : 'Not in DB'}
</Badge>
</span>
</div>
@@ -202,7 +202,7 @@
{/each}
{/key}
</Row>
{:else if hostnameFilter || hoststateFilter != 'all'}
{:else if hostnameFilter || nodeStateFilter != 'all'}
<Row class="mx-1">
<Card class="px-0">
<CardHeader>

View File

@@ -5,7 +5,8 @@
- `cluster String`: The nodes' cluster
- `subCluster String`: The nodes' subCluster
- `hostname String`: The nodes' hostname
- `dataHealth [Bool]`: Array of Booleans depicting state of returned data per metric
- `nodeState String`: The nodes current state as reported by the scheduler
- `metricHealth String`: The nodes current metric health as reported by the metricstore
- `nodeJobsData [Object]`: Data returned by GQL for jobs runninig on this node [Default: null]
-->
@@ -32,8 +33,8 @@
cluster,
subCluster,
hostname,
hoststate,
dataHealth,
nodeState,
metricHealth,
nodeJobsData = null,
} = $props();
@@ -50,12 +51,6 @@
}
/* Derived */
// Not at least one returned, selected metric: NodeHealth warning
const fetchInfo = $derived(dataHealth.includes('fetching'));
// Not at least one returned, selected metric: NodeHealth warning
const healthWarn = $derived(!dataHealth.includes(true));
// At least one non-returned selected metric: Metric config error?
const metricWarn = $derived(dataHealth.includes(false));
const userList = $derived(nodeJobsData
? Array.from(new Set(nodeJobsData.jobs.items.map((j) => scrambleNames ? scramble(j.user) : j.user))).sort((a, b) => a.localeCompare(b))
: []
@@ -86,14 +81,7 @@
<Row cols={{xs: 1, lg: 2}}>
<Col class="mb-2 mb-lg-0">
<InputGroup size="sm">
{#if fetchInfo}
<InputGroupText class="flex-grow-1 flex-lg-grow-0">
<Icon name="arrow-clockwise" style="padding-right: 0.5rem;"/>
</InputGroupText>
<Button class="flex-grow-1" color="dark" outline disabled>
Fetching
</Button>
{:else if healthWarn}
{#if metricHealth == "failed"}
<InputGroupText class="flex-grow-1 flex-lg-grow-0">
<Icon name="exclamation-circle" style="padding-right: 0.5rem;"/>
<span>Info</span>
@@ -101,13 +89,17 @@
<Button class="flex-grow-1" color="danger" disabled>
No Metrics
</Button>
{:else if metricWarn}
{:else if metricHealth == "partial" || metricHealth == "unknown"}
<InputGroupText class="flex-grow-1 flex-lg-grow-0">
<Icon name="info-circle" style="padding-right: 0.5rem;"/>
<span>Info</span>
</InputGroupText>
<Button class="flex-grow-1" color="warning" disabled>
Missing Metric
{#if metricHealth == "partial"}
Missing Metric(s)
{:else if metricHealth == "unknown"}
Metric Health Unknown
{/if}
</Button>
{:else if nodeJobsData.jobs.count == 1 && nodeJobsData?.jobs?.items[0]?.shared == "none"}
<InputGroupText class="flex-grow-1 flex-lg-grow-0">
@@ -150,8 +142,8 @@
<InputGroupText class="flex-grow-1 flex-lg-grow-0">
State
</InputGroupText>
<Button class="flex-grow-1" color={stateColors[hoststate]} disabled>
{hoststate.charAt(0).toUpperCase() + hoststate.slice(1)}
<Button class="flex-grow-1" color={stateColors[nodeState]} disabled>
{nodeState.charAt(0).toUpperCase() + nodeState.slice(1)}
</Button>
</InputGroup>
</Col>

View File

@@ -75,7 +75,6 @@
const extendedLegendData = $derived($nodeJobsData?.data ? buildExtendedLegend() : null);
const refinedData = $derived(nodeData?.metrics ? sortAndSelectScope(selectedMetrics, nodeData.metrics) : []);
const dataHealth = $derived(refinedData.filter((rd) => rd.availability == "configured").map((enabled) => (nodeDataFetching ? 'fetching' : enabled?.data?.metric?.series?.length > 0)));
/* Functions */
function sortAndSelectScope(metricList = [], nodeMetrics = []) {
@@ -145,11 +144,12 @@
{:else}
<NodeInfo
{cluster}
{dataHealth}
nodeJobsData={$nodeJobsData.data}
subCluster={nodeData.subCluster}
hostname={nodeData.host}
hoststate={nodeData?.state? nodeData.state: 'notindb'}/>
nodeState={nodeData?.nodeState || 'notindb'}
metricHealth={nodeData?.metricHealth || 'unknown'}
/>
{/if}
</td>
{#each refinedData as metricData, i (metricData?.data?.name || i)}