Consolidate UsageDash into single GraphQL query

Merge three separate queries (topJobsQuery, topNodesQuery, topAccsQuery)
into one topStatsQuery with 6 aliased jobsStatistics fields, reducing
3 HTTP round trips to 1 on the status dashboard.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Entire-Checkpoint: 40d806a3240c
This commit is contained in:
2026-03-13 13:14:29 +01:00
parent dd3e5427f4
commit 0037d969b2

View File

@@ -8,7 +8,7 @@
- `useAltColors Bool?`: Use alternative color set [Default: false] - `useAltColors Bool?`: Use alternative color set [Default: false]
--> -->
<script> <script>
import { import {
Row, Row,
Col, Col,
@@ -19,13 +19,9 @@
Tooltip, Tooltip,
Input, Input,
InputGroup, InputGroup,
InputGroupText InputGroupText,
} from "@sveltestrap/sveltestrap"; } from "@sveltestrap/sveltestrap";
import { import { queryStore, gql, getContextClient } from "@urql/svelte";
queryStore,
gql,
getContextClient,
} from "@urql/svelte";
import { import {
scramble, scramble,
scrambleNames, scrambleNames,
@@ -46,186 +42,168 @@
/* Const Init */ /* Const Init */
const client = getContextClient(); const client = getContextClient();
const durationBinOptions = ["1m","10m","1h","6h","12h"]; const durationBinOptions = ["1m", "10m", "1h", "6h", "12h"];
/* State Init */ /* State Init */
let pagingState = $state({page: 1, itemsPerPage: 10}) // Top 10 let pagingState = $state({ page: 1, itemsPerPage: 10 }); // Top 10
let selectedHistograms = $state([]) // Dummy For Refresh let selectedHistograms = $state([]); // Dummy For Refresh
let colWidthJobs = $state(0); let colWidthJobs = $state(0);
let colWidthNodes = $state(0); let colWidthNodes = $state(0);
let colWidthAccs = $state(0); let colWidthAccs = $state(0);
let numDurationBins = $state("1h"); let numDurationBins = $state("1h");
/* Derived */ /* Derived */
const canvasPrefix = $derived(`${presetCluster}-${presetSubCluster ? presetSubCluster : ''}`) const canvasPrefix = $derived(
`${presetCluster}-${presetSubCluster ? presetSubCluster : ""}`,
const statusFilter = $derived(presetSubCluster
? [{ state: ["running"] }, { cluster: { eq: presetCluster} }, { subCluster: { eq: presetSubCluster } }]
: [{ state: ["running"] }, { cluster: { eq: presetCluster} }]
); );
const topJobsQuery = $derived(loadMe ? queryStore({
client: client,
query: gql`
query (
$filter: [JobFilter!]!
$paging: PageRequest!
) {
topUser: jobsStatistics(
filter: $filter
page: $paging
sortBy: TOTALJOBS
groupBy: USER
) {
id
name
totalJobs
}
topProjects: jobsStatistics(
filter: $filter
page: $paging
sortBy: TOTALJOBS
groupBy: PROJECT
) {
id
totalJobs
}
}
`,
variables: {
filter: statusFilter,
paging: pagingState // Top 10
},
requestPolicy: "network-only"
}) : null);
const topNodesQuery = $derived(loadMe ? queryStore({ const statusFilter = $derived(
client: client, presetSubCluster
query: gql` ? [
query ( { state: ["running"] },
$filter: [JobFilter!]! { cluster: { eq: presetCluster } },
$paging: PageRequest! { subCluster: { eq: presetSubCluster } },
) { ]
topUser: jobsStatistics( : [{ state: ["running"] }, { cluster: { eq: presetCluster } }],
filter: $filter );
page: $paging const topStatsQuery = $derived(
sortBy: TOTALNODES loadMe
groupBy: USER ? queryStore({
) { client: client,
id query: gql`
name query ($filter: [JobFilter!]!, $paging: PageRequest!) {
totalNodes topUserJobs: jobsStatistics(
} filter: $filter
topProjects: jobsStatistics( page: $paging
filter: $filter sortBy: TOTALJOBS
page: $paging groupBy: USER
sortBy: TOTALNODES ) {
groupBy: PROJECT id
) { name
id totalJobs
totalNodes }
} topProjectJobs: jobsStatistics(
} filter: $filter
`, page: $paging
variables: { sortBy: TOTALJOBS
filter: statusFilter, groupBy: PROJECT
paging: pagingState ) {
}, id
requestPolicy: "network-only" totalJobs
}) : null); }
topUserNodes: jobsStatistics(
const topAccsQuery = $derived(loadMe ? queryStore({ filter: $filter
client: client, page: $paging
query: gql` sortBy: TOTALNODES
query ( groupBy: USER
$filter: [JobFilter!]! ) {
$paging: PageRequest! id
) { name
topUser: jobsStatistics( totalNodes
filter: $filter }
page: $paging topProjectNodes: jobsStatistics(
sortBy: TOTALACCS filter: $filter
groupBy: USER page: $paging
) { sortBy: TOTALNODES
id groupBy: PROJECT
name ) {
totalAccs id
} totalNodes
topProjects: jobsStatistics( }
filter: $filter topUserAccs: jobsStatistics(
page: $paging filter: $filter
sortBy: TOTALACCS page: $paging
groupBy: PROJECT sortBy: TOTALACCS
) { groupBy: USER
id ) {
totalAccs id
} name
} totalAccs
`, }
variables: { topProjectAccs: jobsStatistics(
filter: statusFilter, filter: $filter
paging: pagingState page: $paging
}, sortBy: TOTALACCS
requestPolicy: "network-only" groupBy: PROJECT
}): null); ) {
id
totalAccs
}
}
`,
variables: {
filter: statusFilter,
paging: pagingState, // Top 10
},
requestPolicy: "network-only",
})
: null,
);
// Note: nodeMetrics are requested on configured $timestep resolution // Note: nodeMetrics are requested on configured $timestep resolution
const nodeStatusQuery = $derived(loadMe ? queryStore({ const nodeStatusQuery = $derived(
client: client, loadMe
query: gql` ? queryStore({
query ( client: client,
$filter: [JobFilter!]! query: gql`
$selectedHistograms: [String!] query (
$numDurationBins: String $filter: [JobFilter!]!
) { $selectedHistograms: [String!]
jobsStatistics(filter: $filter, metrics: $selectedHistograms, numDurationBins: $numDurationBins) { $numDurationBins: String
histDuration { ) {
count jobsStatistics(
value filter: $filter
} metrics: $selectedHistograms
histNumNodes { numDurationBins: $numDurationBins
count ) {
value histDuration {
} count
histNumAccs { value
count }
value histNumNodes {
} count
} value
} }
`, histNumAccs {
variables: { count
filter: statusFilter, value
selectedHistograms: selectedHistograms, // No Metrics requested for node hardware stats }
numDurationBins: numDurationBins, }
}, }
requestPolicy: "network-only" `,
}) : null); variables: {
filter: statusFilter,
selectedHistograms: selectedHistograms, // No Metrics requested for node hardware stats
numDurationBins: numDurationBins,
},
requestPolicy: "network-only",
})
: null,
);
/* Functions */ /* Functions */
function legendColors(targetIdx) { function legendColors(targetIdx) {
// Reuses first color if targetIdx overflows // Reuses first color if targetIdx overflows
let c; let c;
if (useCbColors) { if (useCbColors) {
c = [...colors['colorblind']]; c = [...colors["colorblind"]];
} else if (useAltColors) { } else if (useAltColors) {
c = [...colors['alternative']]; c = [...colors["alternative"]];
} else { } else {
c = [...colors['default']]; c = [...colors["default"]];
} }
return c[(c.length + targetIdx) % c.length]; return c[(c.length + targetIdx) % c.length];
} }
</script> </script>
<!-- Refresher and space for other options --> <!-- Refresher and space for other options -->
<Row class="justify-content-between"> <Row class="justify-content-between">
<Col class="mb-2 mb-md-0" xs="12" md="5" lg="4" xl="3"> <Col class="mb-2 mb-md-0" xs="12" md="5" lg="4" xl="3">
<InputGroup> <InputGroup>
<InputGroupText> <InputGroupText>
<Icon name="bar-chart-line-fill" /> <Icon name="bar-chart-line-fill" />
</InputGroupText> </InputGroupText>
<InputGroupText> <InputGroupText>Duration Bin Size</InputGroupText>
Duration Bin Size
</InputGroupText>
<Input type="select" bind:value={numDurationBins}> <Input type="select" bind:value={numDurationBins}>
{#each durationBinOptions as dbin} {#each durationBinOptions as dbin}
<option value={dbin}>{dbin}</option> <option value={dbin}>{dbin}</option>
@@ -237,24 +215,26 @@
<Refresher <Refresher
initially={120} initially={120}
onRefresh={() => { onRefresh={() => {
pagingState = { page:1, itemsPerPage: 10 }; pagingState = { page: 1, itemsPerPage: 10 };
selectedHistograms = [...$state.snapshot(selectedHistograms)]; selectedHistograms = [...$state.snapshot(selectedHistograms)];
}} }}
/> />
</Col> </Col>
</Row> </Row>
<hr/> <hr />
<!-- Job Duration, Top Users and Projects--> <!-- Job Duration, Top Users and Projects-->
{#if $topJobsQuery?.fetching || $nodeStatusQuery?.fetching} {#if $topStatsQuery?.fetching || $nodeStatusQuery?.fetching}
<Spinner /> <Spinner />
{:else if $topJobsQuery?.data && $nodeStatusQuery?.data} {:else if $topStatsQuery?.data && $nodeStatusQuery?.data}
<Row> <Row>
<Col xs="12" lg="4" class="p-2"> <Col xs="12" lg="4" class="p-2">
{#key $nodeStatusQuery.data.jobsStatistics[0].histDuration} {#key $nodeStatusQuery.data.jobsStatistics[0].histDuration}
<Histogram <Histogram
data={convert2uplot($nodeStatusQuery.data.jobsStatistics[0].histDuration)} data={convert2uplot(
$nodeStatusQuery.data.jobsStatistics[0].histDuration,
)}
title="Duration Distribution" title="Duration Distribution"
xlabel="Current Job Runtimes" xlabel="Current Job Runtimes"
xunit="Runtime" xunit="Runtime"
@@ -269,18 +249,18 @@
</Col> </Col>
<Col xs="6" md="3" lg="2" class="p-2"> <Col xs="6" md="3" lg="2" class="p-2">
<div bind:clientWidth={colWidthJobs}> <div bind:clientWidth={colWidthJobs}>
<h4 class="text-center"> <h4 class="text-center">Top Users: Jobs</h4>
Top Users: Jobs
</h4>
<Pie <Pie
{useAltColors} {useAltColors}
canvasId="{canvasPrefix}-hpcpie-jobs-users" canvasId="{canvasPrefix}-hpcpie-jobs-users"
size={colWidthJobs * 0.75} size={colWidthJobs * 0.75}
sliceLabel="Jobs" sliceLabel="Jobs"
quantities={$topJobsQuery.data.topUser.map( quantities={$topStatsQuery.data.topUserJobs.map(
(tu) => tu['totalJobs'], (tu) => tu["totalJobs"],
)}
entities={$topStatsQuery.data.topUserJobs.map((tu) =>
scrambleNames ? scramble(tu.id) : tu.id,
)} )}
entities={$topJobsQuery.data.topUser.map((tu) => scrambleNames ? scramble(tu.id) : tu.id)}
/> />
</div> </div>
</Col> </Col>
@@ -291,11 +271,17 @@
<th style="padding-left: 0.5rem;">User</th> <th style="padding-left: 0.5rem;">User</th>
<th>Jobs</th> <th>Jobs</th>
</tr> </tr>
{#each $topJobsQuery.data.topUser as tu, i} {#each $topStatsQuery.data.topUserJobs as tu, i}
<tr> <tr>
<td><Icon name="circle-fill" style="color: {legendColors(i)};" /></td> <td
><Icon name="circle-fill" style="color: {legendColors(i)};" /></td
>
<td id="{canvasPrefix}-topName-jobs-{tu.id}"> <td id="{canvasPrefix}-topName-jobs-{tu.id}">
<a target="_blank" href="/monitoring/user/{tu.id}?cluster={presetCluster}{presetSubCluster ? '&partition='+presetSubCluster : ''}&state=running" <a
target="_blank"
href="/monitoring/user/{tu.id}?cluster={presetCluster}{presetSubCluster
? '&partition=' + presetSubCluster
: ''}&state=running"
>{scrambleNames ? scramble(tu.id) : tu.id} >{scrambleNames ? scramble(tu.id) : tu.id}
</a> </a>
</td> </td>
@@ -306,25 +292,25 @@
>{scrambleNames ? scramble(tu.name) : tu.name}</Tooltip >{scrambleNames ? scramble(tu.name) : tu.name}</Tooltip
> >
{/if} {/if}
<td>{tu['totalJobs']}</td> <td>{tu["totalJobs"]}</td>
</tr> </tr>
{/each} {/each}
</Table> </Table>
</Col> </Col>
<Col xs="6" md="3" lg="2" class="p-2"> <Col xs="6" md="3" lg="2" class="p-2">
<h4 class="text-center"> <h4 class="text-center">Top Projects: Jobs</h4>
Top Projects: Jobs
</h4>
<Pie <Pie
{useAltColors} {useAltColors}
canvasId="{canvasPrefix}-hpcpie-jobs-projects" canvasId="{canvasPrefix}-hpcpie-jobs-projects"
size={colWidthJobs * 0.75} size={colWidthJobs * 0.75}
sliceLabel={'Jobs'} sliceLabel={"Jobs"}
quantities={$topJobsQuery.data.topProjects.map( quantities={$topStatsQuery.data.topProjectJobs.map(
(tp) => tp['totalJobs'], (tp) => tp["totalJobs"],
)}
entities={$topStatsQuery.data.topProjectJobs.map((tp) =>
scrambleNames ? scramble(tp.id) : tp.id,
)} )}
entities={$topJobsQuery.data.topProjects.map((tp) => scrambleNames ? scramble(tp.id) : tp.id)}
/> />
</Col> </Col>
<Col xs="6" md="3" lg="2" class="p-2"> <Col xs="6" md="3" lg="2" class="p-2">
@@ -334,34 +320,44 @@
<th style="padding-left: 0.5rem;">Project</th> <th style="padding-left: 0.5rem;">Project</th>
<th>Jobs</th> <th>Jobs</th>
</tr> </tr>
{#each $topJobsQuery.data.topProjects as tp, i} {#each $topStatsQuery.data.topProjectJobs as tp, i}
<tr> <tr>
<td><Icon name="circle-fill" style="color: {legendColors(i)};" /></td> <td
><Icon name="circle-fill" style="color: {legendColors(i)};" /></td
>
<td> <td>
<a target="_blank" href="/monitoring/jobs/?cluster={presetCluster}{presetSubCluster ? '&partition='+presetSubCluster : ''}&state=running&project={tp.id}&projectMatch=eq" <a
target="_blank"
href="/monitoring/jobs/?cluster={presetCluster}{presetSubCluster
? '&partition=' + presetSubCluster
: ''}&state=running&project={tp.id}&projectMatch=eq"
>{scrambleNames ? scramble(tp.id) : tp.id} >{scrambleNames ? scramble(tp.id) : tp.id}
</a> </a>
</td> </td>
<td>{tp['totalJobs']}</td> <td>{tp["totalJobs"]}</td>
</tr> </tr>
{/each} {/each}
</Table> </Table>
</Col> </Col>
</Row> </Row>
{:else} {:else}
<Card class="mx-4" body color="warning">Cannot render job status charts: No data!</Card> <Card class="mx-4" body color="warning"
>Cannot render job status charts: No data!</Card
>
{/if} {/if}
<hr/> <hr />
<!-- Node Distribution, Top Users and Projects--> <!-- Node Distribution, Top Users and Projects-->
{#if $topNodesQuery?.fetching || $nodeStatusQuery?.fetching} {#if $topStatsQuery?.fetching || $nodeStatusQuery?.fetching}
<Spinner /> <Spinner />
{:else if $topNodesQuery?.data && $nodeStatusQuery?.data} {:else if $topStatsQuery?.data && $nodeStatusQuery?.data}
<Row> <Row>
<Col xs="12" lg="4" class="p-2"> <Col xs="12" lg="4" class="p-2">
<Histogram <Histogram
data={convert2uplot($nodeStatusQuery.data.jobsStatistics[0].histNumNodes)} data={convert2uplot(
$nodeStatusQuery.data.jobsStatistics[0].histNumNodes,
)}
title="Number of Nodes Distribution" title="Number of Nodes Distribution"
xlabel="Allocated Nodes" xlabel="Allocated Nodes"
xunit="Nodes" xunit="Nodes"
@@ -373,18 +369,18 @@
</Col> </Col>
<Col xs="6" md="3" lg="2" class="p-2"> <Col xs="6" md="3" lg="2" class="p-2">
<div bind:clientWidth={colWidthNodes}> <div bind:clientWidth={colWidthNodes}>
<h4 class="text-center"> <h4 class="text-center">Top Users: Nodes</h4>
Top Users: Nodes
</h4>
<Pie <Pie
{useAltColors} {useAltColors}
canvasId="{canvasPrefix}-hpcpie-nodes-users" canvasId="{canvasPrefix}-hpcpie-nodes-users"
size={colWidthNodes * 0.75} size={colWidthNodes * 0.75}
sliceLabel="Nodes" sliceLabel="Nodes"
quantities={$topNodesQuery.data.topUser.map( quantities={$topStatsQuery.data.topUserNodes.map(
(tu) => tu['totalNodes'], (tu) => tu["totalNodes"],
)}
entities={$topStatsQuery.data.topUserNodes.map((tu) =>
scrambleNames ? scramble(tu.id) : tu.id,
)} )}
entities={$topNodesQuery.data.topUser.map((tu) => scrambleNames ? scramble(tu.id) : tu.id)}
/> />
</div> </div>
</Col> </Col>
@@ -395,11 +391,17 @@
<th style="padding-left: 0.5rem;">User</th> <th style="padding-left: 0.5rem;">User</th>
<th>Nodes</th> <th>Nodes</th>
</tr> </tr>
{#each $topNodesQuery.data.topUser as tu, i} {#each $topStatsQuery.data.topUserNodes as tu, i}
<tr> <tr>
<td><Icon name="circle-fill" style="color: {legendColors(i)};" /></td> <td
><Icon name="circle-fill" style="color: {legendColors(i)};" /></td
>
<td id="{canvasPrefix}-topName-nodes-{tu.id}"> <td id="{canvasPrefix}-topName-nodes-{tu.id}">
<a target="_blank" href="/monitoring/user/{tu.id}?cluster={presetCluster}{presetSubCluster ? '&partition='+presetSubCluster : ''}&state=running" <a
target="_blank"
href="/monitoring/user/{tu.id}?cluster={presetCluster}{presetSubCluster
? '&partition=' + presetSubCluster
: ''}&state=running"
>{scrambleNames ? scramble(tu.id) : tu.id} >{scrambleNames ? scramble(tu.id) : tu.id}
</a> </a>
</td> </td>
@@ -410,25 +412,25 @@
>{scrambleNames ? scramble(tu.name) : tu.name}</Tooltip >{scrambleNames ? scramble(tu.name) : tu.name}</Tooltip
> >
{/if} {/if}
<td>{tu['totalNodes']}</td> <td>{tu["totalNodes"]}</td>
</tr> </tr>
{/each} {/each}
</Table> </Table>
</Col> </Col>
<Col xs="6" md="3" lg="2" class="p-2"> <Col xs="6" md="3" lg="2" class="p-2">
<h4 class="text-center"> <h4 class="text-center">Top Projects: Nodes</h4>
Top Projects: Nodes
</h4>
<Pie <Pie
{useAltColors} {useAltColors}
canvasId="{canvasPrefix}-hpcpie-nodes-projects" canvasId="{canvasPrefix}-hpcpie-nodes-projects"
size={colWidthNodes * 0.75} size={colWidthNodes * 0.75}
sliceLabel={'Nodes'} sliceLabel={"Nodes"}
quantities={$topNodesQuery.data.topProjects.map( quantities={$topStatsQuery.data.topProjectNodes.map(
(tp) => tp['totalNodes'], (tp) => tp["totalNodes"],
)}
entities={$topStatsQuery.data.topProjectNodes.map((tp) =>
scrambleNames ? scramble(tp.id) : tp.id,
)} )}
entities={$topNodesQuery.data.topProjects.map((tp) => scrambleNames ? scramble(tp.id) : tp.id)}
/> />
</Col> </Col>
<Col xs="6" md="3" lg="2" class="p-2"> <Col xs="6" md="3" lg="2" class="p-2">
@@ -438,34 +440,44 @@
<th style="padding-left: 0.5rem;">Project</th> <th style="padding-left: 0.5rem;">Project</th>
<th>Nodes</th> <th>Nodes</th>
</tr> </tr>
{#each $topNodesQuery.data.topProjects as tp, i} {#each $topStatsQuery.data.topProjectNodes as tp, i}
<tr> <tr>
<td><Icon name="circle-fill" style="color: {legendColors(i)};" /></td> <td
><Icon name="circle-fill" style="color: {legendColors(i)};" /></td
>
<td> <td>
<a target="_blank" href="/monitoring/jobs/?cluster={presetCluster}{presetSubCluster ? '&partition='+presetSubCluster : ''}&state=running&project={tp.id}&projectMatch=eq" <a
target="_blank"
href="/monitoring/jobs/?cluster={presetCluster}{presetSubCluster
? '&partition=' + presetSubCluster
: ''}&state=running&project={tp.id}&projectMatch=eq"
>{scrambleNames ? scramble(tp.id) : tp.id} >{scrambleNames ? scramble(tp.id) : tp.id}
</a> </a>
</td> </td>
<td>{tp['totalNodes']}</td> <td>{tp["totalNodes"]}</td>
</tr> </tr>
{/each} {/each}
</Table> </Table>
</Col> </Col>
</Row> </Row>
{:else} {:else}
<Card class="mx-4" body color="warning">Cannot render node status charts: No data!</Card> <Card class="mx-4" body color="warning"
>Cannot render node status charts: No data!</Card
>
{/if} {/if}
<hr/> <hr />
<!-- Acc Distribution, Top Users and Projects--> <!-- Acc Distribution, Top Users and Projects-->
{#if $topAccsQuery?.fetching || $nodeStatusQuery?.fetching} {#if $topStatsQuery?.fetching || $nodeStatusQuery?.fetching}
<Spinner /> <Spinner />
{:else if $topAccsQuery?.data && $nodeStatusQuery?.data} {:else if $topStatsQuery?.data && $nodeStatusQuery?.data}
<Row> <Row>
<Col xs="12" lg="4" class="p-2"> <Col xs="12" lg="4" class="p-2">
<Histogram <Histogram
data={convert2uplot($nodeStatusQuery.data.jobsStatistics[0].histNumAccs)} data={convert2uplot(
$nodeStatusQuery.data.jobsStatistics[0].histNumAccs,
)}
title="Number of Accelerators Distribution" title="Number of Accelerators Distribution"
xlabel="Allocated Accs" xlabel="Allocated Accs"
xunit="Accs" xunit="Accs"
@@ -477,18 +489,18 @@
</Col> </Col>
<Col xs="6" md="3" lg="2" class="p-2"> <Col xs="6" md="3" lg="2" class="p-2">
<div bind:clientWidth={colWidthAccs}> <div bind:clientWidth={colWidthAccs}>
<h4 class="text-center"> <h4 class="text-center">Top Users: GPUs</h4>
Top Users: GPUs
</h4>
<Pie <Pie
{useAltColors} {useAltColors}
canvasId="{canvasPrefix}-hpcpie-accs-users" canvasId="{canvasPrefix}-hpcpie-accs-users"
size={colWidthAccs * 0.75} size={colWidthAccs * 0.75}
sliceLabel="GPUs" sliceLabel="GPUs"
quantities={$topAccsQuery.data.topUser.map( quantities={$topStatsQuery.data.topUserAccs.map(
(tu) => tu['totalAccs'], (tu) => tu["totalAccs"],
)}
entities={$topStatsQuery.data.topUserAccs.map((tu) =>
scrambleNames ? scramble(tu.id) : tu.id,
)} )}
entities={$topAccsQuery.data.topUser.map((tu) => scrambleNames ? scramble(tu.id) : tu.id)}
/> />
</div> </div>
</Col> </Col>
@@ -499,11 +511,17 @@
<th style="padding-left: 0.5rem;">User</th> <th style="padding-left: 0.5rem;">User</th>
<th>GPUs</th> <th>GPUs</th>
</tr> </tr>
{#each $topAccsQuery.data.topUser as tu, i} {#each $topStatsQuery.data.topUserAccs as tu, i}
<tr> <tr>
<td><Icon name="circle-fill" style="color: {legendColors(i)};" /></td> <td
><Icon name="circle-fill" style="color: {legendColors(i)};" /></td
>
<td id="{canvasPrefix}-topName-accs-{tu.id}"> <td id="{canvasPrefix}-topName-accs-{tu.id}">
<a target="_blank" href="/monitoring/user/{tu.id}?cluster={presetCluster}{presetSubCluster ? '&partition='+presetSubCluster : ''}&state=running" <a
target="_blank"
href="/monitoring/user/{tu.id}?cluster={presetCluster}{presetSubCluster
? '&partition=' + presetSubCluster
: ''}&state=running"
>{scrambleNames ? scramble(tu.id) : tu.id} >{scrambleNames ? scramble(tu.id) : tu.id}
</a> </a>
</td> </td>
@@ -514,25 +532,25 @@
>{scrambleNames ? scramble(tu.name) : tu.name}</Tooltip >{scrambleNames ? scramble(tu.name) : tu.name}</Tooltip
> >
{/if} {/if}
<td>{tu['totalAccs']}</td> <td>{tu["totalAccs"]}</td>
</tr> </tr>
{/each} {/each}
</Table> </Table>
</Col> </Col>
<Col xs="6" md="3" lg="2" class="p-2"> <Col xs="6" md="3" lg="2" class="p-2">
<h4 class="text-center"> <h4 class="text-center">Top Projects: GPUs</h4>
Top Projects: GPUs
</h4>
<Pie <Pie
{useAltColors} {useAltColors}
canvasId="{canvasPrefix}-hpcpie-accs-projects" canvasId="{canvasPrefix}-hpcpie-accs-projects"
size={colWidthAccs * 0.75} size={colWidthAccs * 0.75}
sliceLabel={'GPUs'} sliceLabel={"GPUs"}
quantities={$topAccsQuery.data.topProjects.map( quantities={$topStatsQuery.data.topProjectAccs.map(
(tp) => tp['totalAccs'], (tp) => tp["totalAccs"],
)}
entities={$topStatsQuery.data.topProjectAccs.map((tp) =>
scrambleNames ? scramble(tp.id) : tp.id,
)} )}
entities={$topAccsQuery.data.topProjects.map((tp) => scrambleNames ? scramble(tp.id) : tp.id)}
/> />
</Col> </Col>
<Col xs="6" md="3" lg="2" class="p-2"> <Col xs="6" md="3" lg="2" class="p-2">
@@ -542,20 +560,29 @@
<th style="padding-left: 0.5rem;">Project</th> <th style="padding-left: 0.5rem;">Project</th>
<th>GPUs</th> <th>GPUs</th>
</tr> </tr>
{#each $topAccsQuery.data.topProjects as tp, i} {#each $topStatsQuery.data.topProjectAccs as tp, i}
<tr> <tr>
<td><Icon name="circle-fill" style="color: {legendColors(i)};" /></td> <td
><Icon name="circle-fill" style="color: {legendColors(i)};" /></td
>
<td> <td>
<a target="_blank" href="/monitoring/jobs/?cluster={presetCluster}{presetSubCluster ? '&partition='+presetSubCluster : ''}&state=running&project={tp.id}&projectMatch=eq" <a
target="_blank"
href="/monitoring/jobs/?cluster={presetCluster}{presetSubCluster
? '&partition=' + presetSubCluster
: ''}&state=running&project={tp.id}&projectMatch=eq"
>{scrambleNames ? scramble(tp.id) : tp.id} >{scrambleNames ? scramble(tp.id) : tp.id}
</a> </a>
</td> </td>
<td>{tp['totalAccs']}</td> <td>{tp["totalAccs"]}</td>
</tr> </tr>
{/each} {/each}
</Table> </Table>
</Col> </Col>
</Row> </Row>
{:else} {:else}
<Card class="mx-4" body color="warning">Cannot render accelerator status charts: No data!</Card> <Card class="mx-4" body color="warning"
>Cannot render accelerator status charts: No data!</Card
>
{/if} {/if}