mirror of
https://github.com/ClusterCockpit/cc-backend
synced 2025-08-23 18:43:00 +02:00
remove outdated components
This commit is contained in:
@@ -1,468 +0,0 @@
|
|||||||
<!--
|
|
||||||
@component Main cluster status view component; renders current system-usage information
|
|
||||||
|
|
||||||
Properties:
|
|
||||||
- `cluster String`: The cluster to show status information for
|
|
||||||
-->
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import {
|
|
||||||
Row,
|
|
||||||
Col,
|
|
||||||
Table,
|
|
||||||
Icon
|
|
||||||
} from "@sveltestrap/sveltestrap";
|
|
||||||
import {
|
|
||||||
queryStore,
|
|
||||||
gql,
|
|
||||||
getContextClient,
|
|
||||||
} from "@urql/svelte";
|
|
||||||
import {
|
|
||||||
init,
|
|
||||||
} from "../generic/utils.js";
|
|
||||||
//import Roofline from "../generic/plots/Roofline.svelte";
|
|
||||||
import Roofline from "../generic/plots/Roofline.svelte";
|
|
||||||
import Pie, { colors } from "../generic/plots/Pie.svelte";
|
|
||||||
import { formatTime } from "../generic/units.js";
|
|
||||||
|
|
||||||
/* Svelte 5 Props */
|
|
||||||
let {
|
|
||||||
cluster,
|
|
||||||
useCbColors = false
|
|
||||||
} = $props();
|
|
||||||
|
|
||||||
/* Const Init */
|
|
||||||
const { query: initq } = init();
|
|
||||||
const client = getContextClient();
|
|
||||||
|
|
||||||
/* State Init */
|
|
||||||
let from = $state(new Date(Date.now() - 5 * 60 * 1000));
|
|
||||||
let to = $state(new Date(Date.now()));
|
|
||||||
let plotWidths = $state([]);
|
|
||||||
let statesWidth = $state(0);
|
|
||||||
let healthWidth = $state(0);
|
|
||||||
// let nodesCounts = $state({});
|
|
||||||
// let jobsJounts = $state({});
|
|
||||||
|
|
||||||
/* Derived */
|
|
||||||
// Note: nodeMetrics are requested on configured $timestep resolution
|
|
||||||
// Result: The latest 5 minutes (datapoints) for each node independent of job
|
|
||||||
const jobRoofQuery = $derived(queryStore({
|
|
||||||
client: client,
|
|
||||||
query: gql`
|
|
||||||
query ($filter: [JobFilter!]!, $metrics: [String!]!) {
|
|
||||||
jobsMetricStats(filter: $filter, metrics: $metrics) {
|
|
||||||
id
|
|
||||||
jobId
|
|
||||||
duration
|
|
||||||
numNodes
|
|
||||||
numAccelerators
|
|
||||||
subCluster
|
|
||||||
stats {
|
|
||||||
name
|
|
||||||
data {
|
|
||||||
avg
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
variables: {
|
|
||||||
filter: [{ state: ["running"] }, { cluster: { eq: cluster } }],
|
|
||||||
metrics: ["flops_any", "mem_bw"], // Fixed names for job roofline
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Optimal new query, does not exist
|
|
||||||
// const nodeRoofQuery = $derived(queryStore({
|
|
||||||
// client: client,
|
|
||||||
// query: gql`
|
|
||||||
// query ($filter: [JobFilter!]!, $metrics: [String!]!) {
|
|
||||||
// nodeRoofline(filter: $filter, metrics: $metrics) {
|
|
||||||
// nodeName
|
|
||||||
// nodeState
|
|
||||||
// numJobs
|
|
||||||
// stats {
|
|
||||||
// name
|
|
||||||
// data {
|
|
||||||
// avg
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// `,
|
|
||||||
// variables: {
|
|
||||||
// filter: [{ state: ["running"] }, { cluster: { eq: cluster } }],
|
|
||||||
// metrics: ["flops_any", "mem_bw"], // Fixed names for job roofline
|
|
||||||
// },
|
|
||||||
// }));
|
|
||||||
|
|
||||||
// Load Required Roofline Data Averages for all nodes of cluster: use for node avg data and name, use secondary (new?) querie(s) for slurmstate and numjobs
|
|
||||||
const nodesData = $derived(queryStore({
|
|
||||||
client: client,
|
|
||||||
query: gql`
|
|
||||||
query ($cluster: String!, $metrics: [String!], $from: Time!, $to: Time!) {
|
|
||||||
nodeMetrics(
|
|
||||||
cluster: $cluster
|
|
||||||
metrics: $metrics
|
|
||||||
from: $from
|
|
||||||
to: $to
|
|
||||||
) {
|
|
||||||
host
|
|
||||||
subCluster
|
|
||||||
metrics {
|
|
||||||
name
|
|
||||||
metric {
|
|
||||||
series {
|
|
||||||
statistics {
|
|
||||||
avg
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
variables: {
|
|
||||||
cluster: cluster,
|
|
||||||
metrics: ["flops_any", "mem_bw"],
|
|
||||||
from: from,
|
|
||||||
to: to,
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Load for jobcount per node only -- might me required for total running jobs anyways in parent component!
|
|
||||||
// Also, think about extra query with only TotalJobCount and Items [Resources, ...some meta infos], not including metric data
|
|
||||||
const paging = { itemsPerPage: -1, page: 1 };
|
|
||||||
const sorting = { field: "startTime", type: "col", order: "DESC" };
|
|
||||||
const filter = [
|
|
||||||
{ cluster: { eq: cluster } },
|
|
||||||
{ state: ["running"] },
|
|
||||||
];
|
|
||||||
const nodeJobsQuery = gql`
|
|
||||||
query (
|
|
||||||
$filter: [JobFilter!]!
|
|
||||||
$sorting: OrderByInput!
|
|
||||||
$paging: PageRequest!
|
|
||||||
) {
|
|
||||||
jobs(filter: $filter, order: $sorting, page: $paging) {
|
|
||||||
items {
|
|
||||||
jobId
|
|
||||||
resources {
|
|
||||||
hostname
|
|
||||||
}
|
|
||||||
}
|
|
||||||
count
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const nodesJobs = $derived(queryStore({
|
|
||||||
client: client,
|
|
||||||
query: nodeJobsQuery,
|
|
||||||
variables: { paging, sorting, filter },
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
// Last required query: Node State
|
|
||||||
const nodesState = $derived(queryStore({
|
|
||||||
client: client,
|
|
||||||
query: gql`
|
|
||||||
query (
|
|
||||||
$filter: [NodeFilter!]
|
|
||||||
$sorting: OrderByInput
|
|
||||||
) {
|
|
||||||
nodes(filter: $filter, order: $sorting) {
|
|
||||||
count
|
|
||||||
items {
|
|
||||||
hostname
|
|
||||||
cluster
|
|
||||||
subCluster
|
|
||||||
nodeState
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
variables: {
|
|
||||||
filter: { cluster: { eq: cluster }},
|
|
||||||
sorting: sorting // Unused in Backend: Use Placeholder
|
|
||||||
// Subcluster filter?
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
// 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))
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
/* Function */
|
|
||||||
function transformJobsStatsToData(subclusterData) {
|
|
||||||
/* c will contain values from 0 to 1 representing the duration */
|
|
||||||
let data = null
|
|
||||||
const x = [], y = [], c = [], day = 86400.0
|
|
||||||
|
|
||||||
if (subclusterData) {
|
|
||||||
for (let i = 0; i < subclusterData.length; i++) {
|
|
||||||
const flopsData = subclusterData[i].stats.find((s) => s.name == "flops_any")
|
|
||||||
const memBwData = subclusterData[i].stats.find((s) => s.name == "mem_bw")
|
|
||||||
|
|
||||||
const f = flopsData.data.avg
|
|
||||||
const m = memBwData.data.avg
|
|
||||||
const d = subclusterData[i].duration / day
|
|
||||||
|
|
||||||
const intensity = f / m
|
|
||||||
if (Number.isNaN(intensity) || !Number.isFinite(intensity))
|
|
||||||
continue
|
|
||||||
|
|
||||||
x.push(intensity)
|
|
||||||
y.push(f)
|
|
||||||
// Long Jobs > 1 Day: Use max Color
|
|
||||||
if (d > 1.0) c.push(1.0)
|
|
||||||
else c.push(d)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.warn("transformJobsStatsToData: metrics for 'mem_bw' and/or 'flops_any' missing!")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (x.length > 0 && y.length > 0 && c.length > 0) {
|
|
||||||
data = [null, [x, y], c] // for dataformat see roofline.svelte
|
|
||||||
}
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
function transformNodesStatsToData(subclusterData) {
|
|
||||||
let data = null
|
|
||||||
const x = [], y = []
|
|
||||||
|
|
||||||
if (subclusterData) {
|
|
||||||
for (let i = 0; i < subclusterData.length; i++) {
|
|
||||||
const flopsData = subclusterData[i].metrics.find((s) => s.name == "flops_any")
|
|
||||||
const memBwData = subclusterData[i].metrics.find((s) => s.name == "mem_bw")
|
|
||||||
|
|
||||||
const f = flopsData.metric.series[0].statistics.avg
|
|
||||||
const m = memBwData.metric.series[0].statistics.avg
|
|
||||||
|
|
||||||
let intensity = f / m
|
|
||||||
if (Number.isNaN(intensity) || !Number.isFinite(intensity)) {
|
|
||||||
// continue // Old: Introduces mismatch between Data and Info Arrays
|
|
||||||
intensity = 0.0 // New: Set to Float Zero: Will not show in Log-Plot (Always below render limit)
|
|
||||||
}
|
|
||||||
|
|
||||||
x.push(intensity)
|
|
||||||
y.push(f)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// console.warn("transformNodesStatsToData: metrics for 'mem_bw' and/or 'flops_any' missing!")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (x.length > 0 && y.length > 0) {
|
|
||||||
data = [null, [x, y]] // for dataformat see roofline.svelte
|
|
||||||
}
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
function transformJobsStatsToInfo(subclusterData) {
|
|
||||||
if (subclusterData) {
|
|
||||||
return subclusterData.map((sc) => { return {id: sc.id, jobId: sc.jobId, numNodes: sc.numNodes, numAcc: sc?.numAccelerators? sc.numAccelerators : 0, duration: formatTime(sc.duration)} })
|
|
||||||
} else {
|
|
||||||
console.warn("transformJobsStatsToInfo: jobInfo missing!")
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function transformNodesStatsToInfo(subClusterData) {
|
|
||||||
let result = [];
|
|
||||||
if (subClusterData && $nodesState?.data) {
|
|
||||||
// Use Nodes as Returned from CCMS, *NOT* as saved in DB via SlurmState-API!
|
|
||||||
for (let j = 0; j < subClusterData.length; j++) {
|
|
||||||
// nodesCounts[subClusterData[i].subCluster] = $nodesState.data.nodes.count; // Probably better as own derived!
|
|
||||||
|
|
||||||
const nodeName = subClusterData[j]?.host ? subClusterData[j].host : "unknown"
|
|
||||||
const nodeMatch = $nodesState.data.nodes.items.find((n) => n.hostname == nodeName && n.subCluster == subClusterData[j].subCluster);
|
|
||||||
const nodeState = nodeMatch?.nodeState ? nodeMatch.nodeState : "notindb"
|
|
||||||
let numJobs = 0
|
|
||||||
|
|
||||||
if ($nodesJobs?.data) {
|
|
||||||
const nodeJobs = $nodesJobs.data.jobs.items.filter((job) => job.resources.find((res) => res.hostname == nodeName))
|
|
||||||
numJobs = nodeJobs?.length ? nodeJobs.length : 0
|
|
||||||
}
|
|
||||||
|
|
||||||
result.push({nodeName: nodeName, nodeState: nodeState, numJobs: numJobs})
|
|
||||||
};
|
|
||||||
};
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
function legendColors(targetIdx) {
|
|
||||||
// Reuses first color if targetIdx overflows
|
|
||||||
let c = [...colors['default']];
|
|
||||||
return c[(c.length + targetIdx) % c.length];
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<!-- Gauges & Roofline per Subcluster-->
|
|
||||||
{#if $initq.data && $jobRoofQuery.data}
|
|
||||||
{#each $initq.data.clusters.find((c) => c.name == cluster).subClusters as subCluster, i}
|
|
||||||
<Row cols={{ lg: 2, md: 2 , sm: 1}} class="mb-3 justify-content-center">
|
|
||||||
<Col class="px-3 mt-2 mt-lg-0">
|
|
||||||
<b>Bubble Node</b>
|
|
||||||
<div bind:clientWidth={plotWidths[i]}>
|
|
||||||
{#key $nodesData?.data?.nodeMetrics || $nodesJobs?.data?.jobs}
|
|
||||||
<b>{subCluster.name} Total: {$jobRoofQuery.data.jobsMetricStats.filter(
|
|
||||||
(data) => data.subCluster == subCluster.name,
|
|
||||||
).length} Jobs</b>
|
|
||||||
<Roofline
|
|
||||||
allowSizeChange
|
|
||||||
width={plotWidths[i] - 10}
|
|
||||||
height={300}
|
|
||||||
cluster={cluster}
|
|
||||||
subCluster={subCluster}
|
|
||||||
roofData={transformNodesStatsToData($nodesData?.data?.nodeMetrics.filter(
|
|
||||||
(data) => data.subCluster == subCluster.name,
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
nodesData={transformNodesStatsToInfo($nodesData?.data?.nodeMetrics.filter(
|
|
||||||
(data) => data.subCluster == subCluster.name,
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
{/key}
|
|
||||||
</div>
|
|
||||||
</Col>
|
|
||||||
<Col class="px-3 mt-2 mt-lg-0">
|
|
||||||
<b>Bubble Jobs</b>
|
|
||||||
<div bind:clientWidth={plotWidths[i]}>
|
|
||||||
{#key $jobRoofQuery.data.jobsMetricStats}
|
|
||||||
<b>{subCluster.name} Total: {$jobRoofQuery.data.jobsMetricStats.filter(
|
|
||||||
(data) => data.subCluster == subCluster.name,
|
|
||||||
).length} Jobs</b>
|
|
||||||
<Roofline
|
|
||||||
allowSizeChange
|
|
||||||
width={plotWidths[i] - 10}
|
|
||||||
height={300}
|
|
||||||
subCluster={subCluster}
|
|
||||||
roofData={transformJobsStatsToData($jobRoofQuery?.data?.jobsMetricStats.filter(
|
|
||||||
(data) => data.subCluster == subCluster.name,
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
jobsData={transformJobsStatsToInfo($jobRoofQuery?.data?.jobsMetricStats.filter(
|
|
||||||
(data) => data.subCluster == subCluster.name,
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
{/key}
|
|
||||||
</div>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
{/each}
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<hr/>
|
|
||||||
<hr/>
|
|
||||||
|
|
||||||
{#if $initq.data && $nodesStateCounts.data}
|
|
||||||
<Row cols={{ lg: 4, md: 2 , sm: 1}} class="mb-3 justify-content-center">
|
|
||||||
<Col class="px-3 mt-2 mt-lg-0">
|
|
||||||
<b>Node State</b>
|
|
||||||
<div bind:clientWidth={statesWidth}>
|
|
||||||
{#key refinedStateData}
|
|
||||||
<b>Total: {refinedStateData.reduce((sum, item) => {
|
|
||||||
return sum + item.count;
|
|
||||||
}, 0)} Nodes
|
|
||||||
</b>
|
|
||||||
<Pie
|
|
||||||
canvasId="hpcpie-slurm-old"
|
|
||||||
size={statesWidth}
|
|
||||||
sliceLabel="Nodes"
|
|
||||||
quantities={refinedStateData.map(
|
|
||||||
(sd) => sd.count,
|
|
||||||
)}
|
|
||||||
entities={refinedStateData.map(
|
|
||||||
(sd) => sd.state,
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
{/key}
|
|
||||||
</div>
|
|
||||||
</Col>
|
|
||||||
<Col class="px-4 py-2">
|
|
||||||
{#key refinedStateData}
|
|
||||||
<Table>
|
|
||||||
<tr class="mb-2">
|
|
||||||
<th>Legend</th>
|
|
||||||
<th>Current State</th>
|
|
||||||
<th>#Nodes</th>
|
|
||||||
</tr>
|
|
||||||
{#each refinedStateData as sd, i}
|
|
||||||
<tr>
|
|
||||||
<td><Icon name="circle-fill" style="color: {legendColors(i)};" /></td>
|
|
||||||
<td>{sd.state}</td>
|
|
||||||
<td>{sd.count}</td>
|
|
||||||
</tr>
|
|
||||||
{/each}
|
|
||||||
</Table>
|
|
||||||
{/key}
|
|
||||||
</Col>
|
|
||||||
|
|
||||||
<Col class="px-3 mt-2 mt-lg-0">
|
|
||||||
<b>Node Health</b>
|
|
||||||
<div bind:clientWidth={healthWidth}>
|
|
||||||
{#key refinedHealthData}
|
|
||||||
<b>Total: {refinedStateData.reduce((sum, item) => {
|
|
||||||
return sum + item.count;
|
|
||||||
}, 0)} Nodes
|
|
||||||
</b>
|
|
||||||
<Pie
|
|
||||||
canvasId="hpcpie-health-old"
|
|
||||||
size={healthWidth}
|
|
||||||
sliceLabel="Nodes"
|
|
||||||
quantities={refinedHealthData.map(
|
|
||||||
(sd) => sd.count,
|
|
||||||
)}
|
|
||||||
entities={refinedHealthData.map(
|
|
||||||
(sd) => sd.state,
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
{/key}
|
|
||||||
</div>
|
|
||||||
</Col>
|
|
||||||
<Col class="px-4 py-2">
|
|
||||||
{#key refinedHealthData}
|
|
||||||
<Table>
|
|
||||||
<tr class="mb-2">
|
|
||||||
<th>Legend</th>
|
|
||||||
<th>Current Health</th>
|
|
||||||
<th>#Nodes</th>
|
|
||||||
</tr>
|
|
||||||
{#each refinedHealthData as hd, i}
|
|
||||||
<tr>
|
|
||||||
<td><Icon name="circle-fill" style="color: {legendColors(i)};" /></td>
|
|
||||||
<td>{hd.state}</td>
|
|
||||||
<td>{hd.count}</td>
|
|
||||||
</tr>
|
|
||||||
{/each}
|
|
||||||
</Table>
|
|
||||||
{/key}
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
{/if}
|
|
@@ -1,127 +0,0 @@
|
|||||||
<!--
|
|
||||||
@component Main cluster status view component; renders current system-usage information
|
|
||||||
|
|
||||||
Properties:
|
|
||||||
- `cluster String`: The cluster to show status information for
|
|
||||||
-->
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import {
|
|
||||||
Row,
|
|
||||||
Col,
|
|
||||||
} from "@sveltestrap/sveltestrap";
|
|
||||||
import {
|
|
||||||
queryStore,
|
|
||||||
gql,
|
|
||||||
getContextClient,
|
|
||||||
} from "@urql/svelte";
|
|
||||||
import {
|
|
||||||
init,
|
|
||||||
convert2uplot,
|
|
||||||
} from "../generic/utils.js";
|
|
||||||
import Histogram from "../generic/plots/Histogram.svelte";
|
|
||||||
|
|
||||||
/* Svelte 5 Props */
|
|
||||||
let {
|
|
||||||
cluster
|
|
||||||
} = $props();
|
|
||||||
|
|
||||||
/* Const Init */
|
|
||||||
const { query: initq } = init();
|
|
||||||
const client = getContextClient();
|
|
||||||
|
|
||||||
/* Derived */
|
|
||||||
// Note: nodeMetrics are requested on configured $timestep resolution
|
|
||||||
const nodeStatusQuery = $derived(queryStore({
|
|
||||||
client: client,
|
|
||||||
query: gql`
|
|
||||||
query (
|
|
||||||
$filter: [JobFilter!]!
|
|
||||||
$selectedHistograms: [String!]
|
|
||||||
) {
|
|
||||||
jobsStatistics(filter: $filter, metrics: $selectedHistograms) {
|
|
||||||
histDuration {
|
|
||||||
count
|
|
||||||
value
|
|
||||||
}
|
|
||||||
histNumNodes {
|
|
||||||
count
|
|
||||||
value
|
|
||||||
}
|
|
||||||
histNumCores {
|
|
||||||
count
|
|
||||||
value
|
|
||||||
}
|
|
||||||
histNumAccs {
|
|
||||||
count
|
|
||||||
value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
variables: {
|
|
||||||
filter: [{ state: ["running"] }, { cluster: { eq: cluster } }],
|
|
||||||
selectedHistograms: [], // No Metrics requested for node hardware stats
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{#if $initq.data && $nodeStatusQuery.data}
|
|
||||||
<!-- Static Stats as Histograms : Running Duration && Allocated Hardware Counts-->
|
|
||||||
<Row cols={{ lg: 2, md: 1 }}>
|
|
||||||
<Col class="p-2">
|
|
||||||
{#key $nodeStatusQuery.data.jobsStatistics}
|
|
||||||
<Histogram
|
|
||||||
data={convert2uplot($nodeStatusQuery.data.jobsStatistics[0].histDuration)}
|
|
||||||
title="Duration Distribution"
|
|
||||||
xlabel="Current Job Runtimes"
|
|
||||||
xunit="Runtime"
|
|
||||||
ylabel="Number of Jobs"
|
|
||||||
yunit="Jobs"
|
|
||||||
usesBins
|
|
||||||
xtime
|
|
||||||
/>
|
|
||||||
{/key}
|
|
||||||
</Col>
|
|
||||||
<Col class="p-2">
|
|
||||||
{#key $nodeStatusQuery.data.jobsStatistics}
|
|
||||||
<Histogram
|
|
||||||
data={convert2uplot($nodeStatusQuery.data.jobsStatistics[0].histNumNodes)}
|
|
||||||
title="Number of Nodes Distribution"
|
|
||||||
xlabel="Allocated Nodes"
|
|
||||||
xunit="Nodes"
|
|
||||||
ylabel="Number of Jobs"
|
|
||||||
yunit="Jobs"
|
|
||||||
/>
|
|
||||||
{/key}
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
<Row cols={{ lg: 2, md: 1 }}>
|
|
||||||
<Col class="p-2">
|
|
||||||
{#key $nodeStatusQuery.data.jobsStatistics}
|
|
||||||
<Histogram
|
|
||||||
data={convert2uplot($nodeStatusQuery.data.jobsStatistics[0].histNumCores)}
|
|
||||||
title="Number of Cores Distribution"
|
|
||||||
xlabel="Allocated Cores"
|
|
||||||
xunit="Cores"
|
|
||||||
ylabel="Number of Jobs"
|
|
||||||
yunit="Jobs"
|
|
||||||
/>
|
|
||||||
{/key}
|
|
||||||
</Col>
|
|
||||||
<Col class="p-2">
|
|
||||||
{#key $nodeStatusQuery.data.jobsStatistics}
|
|
||||||
<Histogram
|
|
||||||
data={convert2uplot($nodeStatusQuery.data.jobsStatistics[0].histNumAccs)}
|
|
||||||
title="Number of Accelerators Distribution"
|
|
||||||
xlabel="Allocated Accs"
|
|
||||||
xunit="Accs"
|
|
||||||
ylabel="Number of Jobs"
|
|
||||||
yunit="Jobs"
|
|
||||||
/>
|
|
||||||
{/key}
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
|
|
Reference in New Issue
Block a user