Finish prototype implementation of nodelist view

This commit is contained in:
Christoph Kluge
2024-10-14 18:37:48 +02:00
parent 2f6e5a7648
commit 673fdc443c
7 changed files with 230 additions and 429 deletions

View File

@@ -5,203 +5,52 @@
- `job Object`: The job object (GraphQL.Job)
- `metrics [String]`: Currently selected metrics
- `plotWidth Number`: Width of the sub-components
- `plotHeight Number?`: Height of the sub-components [Default: 275]
- `showFootprint Bool`: Display of footprint component for job
- `triggerMetricRefresh Bool?`: If changed to true from upstream, will trigger metric query
-->
<script>
import { queryStore, gql, getContextClient } from "@urql/svelte";
import { getContext } from "svelte";
import { Card, Spinner } from "@sveltestrap/sveltestrap";
import { maxScope, checkMetricDisabled } from "../../generic/utils.js";
import { Card } from "@sveltestrap/sveltestrap";
import MetricPlot from "../../generic/plots/MetricPlot.svelte";
import NodeInfo from "./NodeInfo.svelte";
export let job;
export let metrics;
export let plotWidth;
export let plotHeight = 275;
export let showFootprint;
export let triggerMetricRefresh = false;
const resampleConfig = getContext("resampling") || null;
const resampleDefault = resampleConfig ? Math.max(...resampleConfig.resolutions) : 0;
let { id } = job;
let scopes = job.numNodes == 1
? job.numAcc >= 1
? ["core", "accelerator"]
: ["core"]
: ["node"];
let selectedResolution = resampleDefault;
let zoomStates = {};
const cluster = getContext("clusters").find((c) => c.name == job.cluster);
const client = getContextClient();
const query = gql`
query ($id: ID!, $metrics: [String!]!, $scopes: [MetricScope!]!, $selectedResolution: Int) {
jobMetrics(id: $id, metrics: $metrics, scopes: $scopes, resolution: $selectedResolution) {
name
scope
metric {
unit {
prefix
base
}
timestep
statisticsSeries {
min
mean
median
max
}
series {
hostname
id
data
statistics {
min
avg
max
}
}
}
}
}
`;
function handleZoom(detail, metric) {
if ( // States have to differ, causes deathloop if just set
(zoomStates[metric]?.x?.min !== detail?.lastZoomState?.x?.min) &&
(zoomStates[metric]?.y?.max !== detail?.lastZoomState?.y?.max)
) {
zoomStates[metric] = {...detail.lastZoomState}
}
if (detail?.newRes) { // Triggers GQL
selectedResolution = detail.newRes
}
}
$: metricsQuery = queryStore({
client: client,
query: query,
variables: { id, metrics, scopes, selectedResolution },
});
function refreshMetrics() {
metricsQuery = queryStore({
client: client,
query: query,
variables: { id, metrics, scopes, selectedResolution },
// requestPolicy: 'network-only' // use default cache-first for refresh
});
}
$: if (job.state === 'running' && triggerMetricRefresh === true) {
refreshMetrics();
}
// Helper
const selectScope = (jobMetrics) =>
jobMetrics.reduce(
(a, b) =>
maxScope([a.scope, b.scope]) == a.scope
? job.numNodes > 1
? a
: b
: job.numNodes > 1
? b
: a,
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 {
disabled: checkMetricDisabled(
jobMetric.data.name,
job.cluster,
job.subCluster,
),
data: jobMetric.data,
};
} else {
return jobMetric;
}
});
export let cluster;
export let nodeData;
export let selectedMetrics;
const sortOrder = (nodeMetrics) =>
selectedMetrics.map((name) => nodeMetrics.find((nodeMetric) => nodeMetric.name == name));
</script>
<tr>
<td>
<JobInfo {job} />
<NodeInfo {cluster} subCluster={nodeData.subCluster} hostname={nodeData.host} />
</td>
{#if job.monitoringStatus == 0 || job.monitoringStatus == 2}
<td colspan={metrics.length}>
<Card body color="warning">Not monitored or archiving failed</Card>
</td>
{:else if $metricsQuery.fetching}
<td colspan={metrics.length} style="text-align: center;">
<Spinner secondary />
</td>
{:else if $metricsQuery.error}
<td colspan={metrics.length}>
<Card body color="danger" class="mb-3">
{$metricsQuery.error.message.length > 500
? $metricsQuery.error.message.substring(0, 499) + "..."
: $metricsQuery.error.message}
</Card>
</td>
{:else}
{#if showFootprint}
<td>
<JobFootprint
{job}
width={plotWidth}
height="{plotHeight}px"
displayTitle={false}
/>
</td>
{/if}
{#each sortAndSelectScope($metricsQuery.data.jobMetrics) as metric, i (metric || i)}
<td>
<!-- Subluster Metricconfig remove keyword for jobtables (joblist main, user joblist, project joblist) to be used here as toplevel case-->
{#if metric.disabled == false && metric.data}
<MetricPlot
on:zoom={({detail}) => { handleZoom(detail, metric.data.name) }}
height={plotHeight}
timestep={metric.data.metric.timestep}
scope={metric.data.scope}
series={metric.data.metric.series}
statisticsSeries={metric.data.metric.statisticsSeries}
metric={metric.data.name}
{cluster}
subCluster={job.subCluster}
isShared={job.exclusive != 1}
numhwthreads={job.numHWThreads}
numaccs={job.numAcc}
zoomState={zoomStates[metric.data.name] || null}
/>
{:else if metric.disabled == true && metric.data}
<Card body color="info"
{#each sortOrder(nodeData?.data) as metricData}
<td>
{#if metricData}
{#if nodeData?.disabled[metricData.name]}
<Card style="margin-left: 2rem;margin-right: 2rem;" body color="info"
>Metric disabled for subcluster <code
>{metric.data.name}:{job.subCluster}</code
>{metricData.name}:{nodeData.subCluster}</code
></Card
>
{:else}
<Card body color="warning">No dataset returned</Card>
<MetricPlot
timestep={metricData.metric.timestep}
series={metricData.metric.series}
metric={metricData.name}
{cluster}
subCluster={nodeData.subCluster}
forNode={true}
/>
{/if}
</td>
{/each}
{/if}
{:else}
<Card
style="margin-left: 2rem;margin-right: 2rem;"
body
color="warning"
>No dataset returned for <code>{metricData.name}</code></Card
>
{/if}
</td>
{/each}
</tr>