diff --git a/web/frontend/src/Analysis.root.svelte b/web/frontend/src/Analysis.root.svelte index 689b7a2..122a67b 100644 --- a/web/frontend/src/Analysis.root.svelte +++ b/web/frontend/src/Analysis.root.svelte @@ -459,7 +459,7 @@ {#each $topQuery.data.topList as te, i} - + {#if groupSelection.key == "user"} -
+
diff --git a/web/frontend/src/status/DevelDash.svelte b/web/frontend/src/status/DevelDash.svelte index e0f4960..17426fc 100644 --- a/web/frontend/src/status/DevelDash.svelte +++ b/web/frontend/src/status/DevelDash.svelte @@ -22,12 +22,13 @@ } from "../generic/utils.js"; //import Roofline from "../generic/plots/Roofline.svelte"; import Roofline from "../generic/plots/Roofline.svelte"; - import Pie, { cbColors, colors } from "../generic/plots/Pie.svelte"; + import Pie, { colors } from "../generic/plots/Pie.svelte"; import { formatTime } from "../generic/units.js"; /* Svelte 5 Props */ let { - cluster + cluster, + useCbColors = false } = $props(); /* Const Init */ @@ -40,7 +41,6 @@ let plotWidths = $state([]); let statesWidth = $state(0); let healthWidth = $state(0); - let cbmode = $state(false); // let nodesCounts = $state({}); // let jobsJounts = $state({}); @@ -313,6 +313,12 @@ return result } + function legendColors(targetIdx) { + // Reuses first color if targetIdx overflows + let c = [...colors['default']]; + return c[(c.length + targetIdx) % c.length]; + } + @@ -386,7 +392,7 @@ }, 0)} Nodes {#each refinedStateData as sd, i} - + {sd.state} {sd.count} @@ -427,7 +433,7 @@ }, 0)} Nodes {#each refinedHealthData as hd, i} - + {hd.state} {hd.count} diff --git a/web/frontend/src/status/StatusDash.svelte b/web/frontend/src/status/StatusDash.svelte index f3fdd9b..44a0ab4 100644 --- a/web/frontend/src/status/StatusDash.svelte +++ b/web/frontend/src/status/StatusDash.svelte @@ -15,7 +15,7 @@ CardBody, Table, Progress, - // Icon, + Icon, } from "@sveltestrap/sveltestrap"; import { queryStore, @@ -24,15 +24,16 @@ } from "@urql/svelte"; import { init, - // transformPerNodeDataForRoofline, - } from "../generic/utils.js"; import { scaleNumbers, formatTime } from "../generic/units.js"; import Roofline from "../generic/plots/Roofline.svelte"; + import Pie, { colors } from "../generic/plots/Pie.svelte"; /* Svelte 5 Props */ let { - cluster + cluster, + useCbColors = false, + useAltColors = false, } = $props(); /* Const Init */ @@ -42,6 +43,7 @@ /* State Init */ let from = $state(new Date(Date.now() - 5 * 60 * 1000)); let to = $state(new Date(Date.now())); + let pieWidth = $state(0); let plotWidths = $state([]); // Bar Gauges let allocatedNodes = $state({}); @@ -58,6 +60,30 @@ let totalAccs = $state({}); /* Derived */ + // Accumulated NodeStates for Piecharts + const nodesStateCounts = $derived(queryStore({ + client: client, + query: gql` + query ($filter: [NodeFilter!]) { + nodeStates(filter: $filter) { + state + count + } + } + `, + variables: { + filter: { cluster: { eq: cluster }} + }, + })); + + const refinedStateData = $derived.by(() => { + return $nodesStateCounts?.data?.nodeStates.filter((e) => ['allocated', 'reserved', 'idle', 'mixed','down', 'unknown'].includes(e.state)) + }); + + const refinedHealthData = $derived.by(() => { + return $nodesStateCounts?.data?.nodeStates.filter((e) => ['full', 'partial', 'failed'].includes(e.state)) + }); + // Note: nodeMetrics are requested on configured $timestep resolution // Result: The latest 5 minutes (datapoints) for each node independent of job const statusQuery = $derived(queryStore({ @@ -334,8 +360,107 @@ return result } + function legendColors(targetIdx) { + // Reuses first color if targetIdx overflows + let c; + if (useCbColors) { + c = [...colors['colorblind']]; + } else if (useAltColors) { + c = [...colors['alternative']]; + } else { + c = [...colors['default']]; + } + return c[(c.length + targetIdx) % c.length]; + } + + +{#if $initq.data && $nodesStateCounts.data} + + +
+ {#key refinedStateData} +

+ {cluster.charAt(0).toUpperCase() + cluster.slice(1)} Node States +

+ sd.count, + )} + entities={refinedStateData.map( + (sd) => sd.state, + )} + /> + {/key} +
+ + + {#key refinedStateData} + + + + + + + {#each refinedStateData as sd, i} + + + + + + {/each} +
Current StateNodes
{sd.state}{sd.count}
+ {/key} + + + +
+ {#key refinedHealthData} +

+ {cluster.charAt(0).toUpperCase() + cluster.slice(1)} Node Health +

+ sd.count, + )} + entities={refinedHealthData.map( + (sd) => sd.state, + )} + /> + {/key} +
+ + + {#key refinedHealthData} + + + + + + + {#each refinedHealthData as hd, i} + + + + + + {/each} +
Current HealthNodes
{hd.state}{hd.count}
+ {/key} + +
+{/if} + +
{#if $initq.data && $statusQuery.data} {#each $initq.data.clusters.find((c) => c.name == cluster).subClusters as subCluster, i} @@ -454,5 +579,5 @@ {/each} {:else} - Cannot render status tab: No data! + Cannot render status rooflines: No data! {/if} diff --git a/web/frontend/src/status/UsageDash.svelte b/web/frontend/src/status/UsageDash.svelte index 93604ea..0de7da2 100644 --- a/web/frontend/src/status/UsageDash.svelte +++ b/web/frontend/src/status/UsageDash.svelte @@ -6,7 +6,6 @@ --> -{#if $initq.data} - - - -
+ +{#if $topJobsQuery.fetching || $nodeStatusQuery.fetching} + +{:else if $topJobsQuery.data && $nodeStatusQuery.data} + + + + + +

- Top Users on {cluster.charAt(0).toUpperCase() + cluster.slice(1)} + Top Users: Jobs

- {#key $topUserQuery.data} - {#if $topUserQuery.fetching} - - {:else if $topUserQuery.error} - {$topUserQuery.error.message} - {:else} - tu[topUserSelection.key], - )} - entities={$topUserQuery.data.topUser.map((tu) => scrambleNames ? scramble(tu.id) : tu.id)} - /> - {/if} - {/key} + tu['totalJobs'], + )} + entities={$topJobsQuery.data.topUser.map((tu) => scrambleNames ? scramble(tu.id) : tu.id)} + />
- - {#key $topUserQuery.data} - {#if $topUserQuery.fetching} - - {:else if $topUserQuery.error} - {$topUserQuery.error.message} - {:else} - - - - - - - {#each $topUserQuery.data.topUser as tu, i} - - - - {#if tu?.name} - {scrambleNames ? scramble(tu.name) : tu.name} - {/if} - - - {/each} -
LegendUser NameNumber of - -
{scrambleNames ? scramble(tu.id) : tu.id}{tu[topUserSelection.key]}
- {/if} - {/key} + + + + + + + + {#each $topJobsQuery.data.topUser as tu, i} + + + + {#if tu?.name} + {scrambleNames ? scramble(tu.name) : tu.name} + {/if} + + + {/each} +
UserActive Jobs
+ {scrambleNames ? scramble(tu.id) : tu.id} + + {tu['totalJobs']}
- + +

- Top Projects on {cluster.charAt(0).toUpperCase() + cluster.slice(1)} + Top Projects: Jobs

- {#key $topProjectQuery.data} - {#if $topProjectQuery.fetching} - - {:else if $topProjectQuery.error} - {$topProjectQuery.error.message} - {:else} - tp[topProjectSelection.key], - )} - entities={$topProjectQuery.data.topProjects.map((tp) => scrambleNames ? scramble(tp.id) : tp.id)} - /> - {/if} - {/key} + tp['totalJobs'], + )} + entities={$topJobsQuery.data.topProjects.map((tp) => scrambleNames ? scramble(tp.id) : tp.id)} + /> - - {#key $topProjectQuery.data} - {#if $topProjectQuery.fetching} - - {:else if $topProjectQuery.error} - {$topProjectQuery.error.message} - {:else} - - - - - - - {#each $topProjectQuery.data.topProjects as tp, i} - - - - - - {/each} -
LegendProject CodeNumber of - -
{scrambleNames ? scramble(tp.id) : tp.id}{tp[topProjectSelection.key]}
- {/if} - {/key} + + + + + + + + {#each $topJobsQuery.data.topProjects as tp, i} + + + + + + {/each} +
ProjectActive Jobs
+ {scrambleNames ? scramble(tp.id) : tp.id} + + {tp['totalJobs']}
+{:else} + Cannot render job status charts: No data! {/if} + +
+ + +{#if $topNodesQuery.fetching || $nodeStatusQuery.fetching} + +{:else if $topNodesQuery.data && $nodeStatusQuery.data} + + + + + +
+

+ Top Users: Nodes +

+ tu['totalNodes'], + )} + entities={$topNodesQuery.data.topUser.map((tu) => scrambleNames ? scramble(tu.id) : tu.id)} + /> +
+ + + + + + + + + {#each $topNodesQuery.data.topUser as tu, i} + + + + {#if tu?.name} + {scrambleNames ? scramble(tu.name) : tu.name} + {/if} + + + {/each} +
UserNodes
+ {scrambleNames ? scramble(tu.id) : tu.id} + + {tu['totalNodes']}
+ + + +

+ Top Projects: Nodes +

+ tp['totalNodes'], + )} + entities={$topNodesQuery.data.topProjects.map((tp) => scrambleNames ? scramble(tp.id) : tp.id)} + /> + + + + + + + + + {#each $topNodesQuery.data.topProjects as tp, i} + + + + + + {/each} +
ProjectNodes
+ {scrambleNames ? scramble(tp.id) : tp.id} + + {tp['totalNodes']}
+ +
+{:else} + Cannot render node status charts: No data! +{/if} + +
+ + +{#if $topAccsQuery.fetching || $nodeStatusQuery.fetching} + +{:else if $topAccsQuery.data && $nodeStatusQuery.data} + + + + + +
+

+ Top Users: GPUs +

+ tu['totalAccs'], + )} + entities={$topAccsQuery.data.topUser.map((tu) => scrambleNames ? scramble(tu.id) : tu.id)} + /> +
+ + + + + + + + + {#each $topAccsQuery.data.topUser as tu, i} + + + + {#if tu?.name} + {scrambleNames ? scramble(tu.name) : tu.name} + {/if} + + + {/each} +
UserGPUs
+ {scrambleNames ? scramble(tu.id) : tu.id} + + {tu['totalAccs']}
+ + + +

+ Top Projects: GPUs +

+ tp['totalAccs'], + )} + entities={$topAccsQuery.data.topProjects.map((tp) => scrambleNames ? scramble(tp.id) : tp.id)} + /> + + + + + + + + + {#each $topAccsQuery.data.topProjects as tp, i} + + + + + + {/each} +
ProjectGPUs
+ {scrambleNames ? scramble(tp.id) : tp.id} + + {tp['totalAccs']}
+ +
+{:else} + Cannot render accelerator status charts: No data! +{/if} \ No newline at end of file