mirror of
https://github.com/ClusterCockpit/cc-backend
synced 2025-07-01 11:13:50 +02:00
Migrate Job View
This commit is contained in:
parent
e2e67e3977
commit
79a6c9e90d
@ -9,10 +9,11 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { getContext } from "svelte";
|
||||||
import {
|
import {
|
||||||
queryStore,
|
queryStore,
|
||||||
gql,
|
gql,
|
||||||
getContextClient
|
getContextClient,
|
||||||
} from "@urql/svelte";
|
} from "@urql/svelte";
|
||||||
import {
|
import {
|
||||||
Row,
|
Row,
|
||||||
@ -26,7 +27,6 @@
|
|||||||
CardTitle,
|
CardTitle,
|
||||||
Button,
|
Button,
|
||||||
} from "@sveltestrap/sveltestrap";
|
} from "@sveltestrap/sveltestrap";
|
||||||
import { getContext } from "svelte";
|
|
||||||
import {
|
import {
|
||||||
init,
|
init,
|
||||||
groupByScope,
|
groupByScope,
|
||||||
@ -42,46 +42,33 @@
|
|||||||
import PlotGrid from "./generic/PlotGrid.svelte";
|
import PlotGrid from "./generic/PlotGrid.svelte";
|
||||||
import StatsTab from "./job/StatsTab.svelte";
|
import StatsTab from "./job/StatsTab.svelte";
|
||||||
|
|
||||||
export let dbid;
|
/* Svelte 5 Props */
|
||||||
export let username;
|
let {
|
||||||
export let authlevel;
|
dbid,
|
||||||
export let roles;
|
username,
|
||||||
|
authlevel,
|
||||||
// Setup General
|
roles
|
||||||
|
} = $props();
|
||||||
const ccconfig = getContext("cc-config")
|
|
||||||
|
|
||||||
let isMetricsSelectionOpen = false,
|
|
||||||
selectedMetrics = [],
|
|
||||||
selectedScopes = [],
|
|
||||||
plots = {};
|
|
||||||
|
|
||||||
let totalMetrics = 0;
|
|
||||||
let missingMetrics = [],
|
|
||||||
missingHosts = [],
|
|
||||||
somethingMissing = false;
|
|
||||||
|
|
||||||
// Setup GQL
|
|
||||||
// First: Add Job Query to init function -> Only requires DBID as argument, received via URL-ID
|
|
||||||
// Second: Trigger jobMetrics query with now received jobInfos (scopes: from job metadata, selectedMetrics: from config or all, job: from url-id)
|
|
||||||
|
|
||||||
|
/* Const Init */
|
||||||
|
// Important: init() needs to be first const declaration or contextclient will not be initialized before "const client = ..."
|
||||||
const { query: initq } = init(`
|
const { query: initq } = init(`
|
||||||
job(id: "${dbid}") {
|
job(id: "${dbid}") {
|
||||||
id, jobId, user, project, cluster, startTime,
|
id, jobId, user, project, cluster, startTime,
|
||||||
duration, numNodes, numHWThreads, numAcc, energy,
|
duration, numNodes, numHWThreads, numAcc, energy,
|
||||||
SMT, exclusive, partition, subCluster, arrayJobId,
|
SMT, exclusive, partition, subCluster, arrayJobId,
|
||||||
monitoringStatus, state, walltime,
|
monitoringStatus, state, walltime,
|
||||||
tags { id, type, scope, name },
|
tags { id, type, scope, name },
|
||||||
resources { hostname, hwthreads, accelerators },
|
resources { hostname, hwthreads, accelerators },
|
||||||
metaData,
|
metaData,
|
||||||
userData { name, email },
|
userData { name, email },
|
||||||
concurrentJobs { items { id, jobId }, count, listQuery },
|
concurrentJobs { items { id, jobId }, count, listQuery },
|
||||||
footprint { name, stat, value },
|
footprint { name, stat, value },
|
||||||
energyFootprint { hardware, metric, value }
|
energyFootprint { hardware, metric, value }
|
||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
const client = getContextClient();
|
const client = getContextClient();
|
||||||
|
const ccconfig = getContext("cc-config");
|
||||||
const query = gql`
|
const query = gql`
|
||||||
query ($dbid: ID!, $selectedMetrics: [String!]!, $selectedScopes: [MetricScope!]!) {
|
query ($dbid: ID!, $selectedMetrics: [String!]!, $selectedScopes: [MetricScope!]!) {
|
||||||
jobMetrics(id: $dbid, metrics: $selectedMetrics, scopes: $selectedScopes) {
|
jobMetrics(id: $dbid, metrics: $selectedMetrics, scopes: $selectedScopes) {
|
||||||
@ -114,17 +101,91 @@
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
$: jobMetrics = queryStore({
|
/* State Init */
|
||||||
client: client,
|
let plots = $state({});
|
||||||
query: query,
|
let isMetricsSelectionOpen = $state(false);
|
||||||
variables: { dbid, selectedMetrics, selectedScopes },
|
let selectedMetrics = $state([]);
|
||||||
|
let selectedScopes = $state([]);
|
||||||
|
let totalMetrics = $state(0);
|
||||||
|
|
||||||
|
/* Derived */
|
||||||
|
const jobMetrics = $derived(queryStore({
|
||||||
|
client: client,
|
||||||
|
query: query,
|
||||||
|
variables: { dbid, selectedMetrics, selectedScopes },
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const missingMetrics = $derived.by(() => {
|
||||||
|
if ($initq?.data && $jobMetrics?.data) {
|
||||||
|
let job = $initq.data.job;
|
||||||
|
let metrics = $jobMetrics.data.jobMetrics;
|
||||||
|
let metricNames = $initq.data.globalMetrics.reduce((names, gm) => {
|
||||||
|
if (gm.availability.find((av) => av.cluster === job.cluster)) {
|
||||||
|
names.push(gm.name);
|
||||||
|
}
|
||||||
|
return names;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return metricNames.filter(
|
||||||
|
(metric) =>
|
||||||
|
!metrics.some((jm) => jm.name == metric) &&
|
||||||
|
selectedMetrics.includes(metric) &&
|
||||||
|
!checkMetricDisabled(
|
||||||
|
metric,
|
||||||
|
$initq.data.job.cluster,
|
||||||
|
$initq.data.job.subCluster,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return []
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle Job Query on Init -> is not executed anymore
|
const missingHosts = $derived.by(() => {
|
||||||
getContext("on-init")(() => {
|
if ($initq?.data && $jobMetrics?.data) {
|
||||||
|
let job = $initq.data.job;
|
||||||
|
let metrics = $jobMetrics.data.jobMetrics;
|
||||||
|
let metricNames = $initq.data.globalMetrics.reduce((names, gm) => {
|
||||||
|
if (gm.availability.find((av) => av.cluster === job.cluster)) {
|
||||||
|
names.push(gm.name);
|
||||||
|
}
|
||||||
|
return names;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return job.resources
|
||||||
|
.map(({ hostname }) => ({
|
||||||
|
hostname: hostname,
|
||||||
|
metrics: metricNames.filter(
|
||||||
|
(metric) =>
|
||||||
|
!metrics.some(
|
||||||
|
(jm) =>
|
||||||
|
jm.scope == "node" &&
|
||||||
|
jm.metric.series.some((series) => series.hostname == hostname),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
}))
|
||||||
|
.filter(({ metrics }) => metrics.length > 0);
|
||||||
|
} else {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const somethingMissing = $derived(missingMetrics?.length > 0 || missingHosts?.length > 0);
|
||||||
|
|
||||||
|
/* Effects */
|
||||||
|
$effect(() => {
|
||||||
|
document.title = $initq.fetching
|
||||||
|
? "Loading..."
|
||||||
|
: $initq.error
|
||||||
|
? "Error"
|
||||||
|
: `Job ${$initq.data.job.jobId} - ClusterCockpit`;
|
||||||
|
});
|
||||||
|
|
||||||
|
/* On Init */
|
||||||
|
getContext("on-init")(() => {
|
||||||
let job = $initq.data.job;
|
let job = $initq.data.job;
|
||||||
if (!job) return;
|
if (!job) return;
|
||||||
|
|
||||||
const pendingMetrics = (
|
const pendingMetrics = (
|
||||||
ccconfig[`job_view_selectedMetrics:${job.cluster}:${job.subCluster}`] ||
|
ccconfig[`job_view_selectedMetrics:${job.cluster}:${job.subCluster}`] ||
|
||||||
ccconfig[`job_view_selectedMetrics:${job.cluster}`]
|
ccconfig[`job_view_selectedMetrics:${job.cluster}`]
|
||||||
@ -154,52 +215,7 @@
|
|||||||
selectedScopes = [...new Set(pendingScopes)];
|
selectedScopes = [...new Set(pendingScopes)];
|
||||||
});
|
});
|
||||||
|
|
||||||
// Interactive Document Title
|
/* Functions */
|
||||||
$: document.title = $initq.fetching
|
|
||||||
? "Loading..."
|
|
||||||
: $initq.error
|
|
||||||
? "Error"
|
|
||||||
: `Job ${$initq.data.job.jobId} - ClusterCockpit`;
|
|
||||||
|
|
||||||
// Find out what metrics or hosts are missing:
|
|
||||||
$: if ($initq?.data && $jobMetrics?.data?.jobMetrics) {
|
|
||||||
let job = $initq.data.job,
|
|
||||||
metrics = $jobMetrics.data.jobMetrics,
|
|
||||||
metricNames = $initq.data.globalMetrics.reduce((names, gm) => {
|
|
||||||
if (gm.availability.find((av) => av.cluster === job.cluster)) {
|
|
||||||
names.push(gm.name);
|
|
||||||
}
|
|
||||||
return names;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Metric not found in JobMetrics && Metric not explicitly disabled in config or deselected: Was expected, but is Missing
|
|
||||||
missingMetrics = metricNames.filter(
|
|
||||||
(metric) =>
|
|
||||||
!metrics.some((jm) => jm.name == metric) &&
|
|
||||||
selectedMetrics.includes(metric) &&
|
|
||||||
!checkMetricDisabled(
|
|
||||||
metric,
|
|
||||||
$initq.data.job.cluster,
|
|
||||||
$initq.data.job.subCluster,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
missingHosts = job.resources
|
|
||||||
.map(({ hostname }) => ({
|
|
||||||
hostname: hostname,
|
|
||||||
metrics: metricNames.filter(
|
|
||||||
(metric) =>
|
|
||||||
!metrics.some(
|
|
||||||
(jm) =>
|
|
||||||
jm.scope == "node" &&
|
|
||||||
jm.metric.series.some((series) => series.hostname == hostname),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
}))
|
|
||||||
.filter(({ metrics }) => metrics.length > 0);
|
|
||||||
somethingMissing = missingMetrics.length > 0 || missingHosts.length > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper
|
|
||||||
const orderAndMap = (grouped, selectedMetrics) =>
|
const orderAndMap = (grouped, selectedMetrics) =>
|
||||||
selectedMetrics.map((metric) => ({
|
selectedMetrics.map((metric) => ({
|
||||||
metric: metric,
|
metric: metric,
|
||||||
@ -293,7 +309,7 @@
|
|||||||
<Row class="mb-2">
|
<Row class="mb-2">
|
||||||
{#if $initq?.data}
|
{#if $initq?.data}
|
||||||
<Col xs="auto">
|
<Col xs="auto">
|
||||||
<Button outline on:click={() => (isMetricsSelectionOpen = true)} color="primary">
|
<Button outline onclick={() => (isMetricsSelectionOpen = true)} color="primary">
|
||||||
Select Metrics (Selected {selectedMetrics.length} of {totalMetrics} available)
|
Select Metrics (Selected {selectedMetrics.length} of {totalMetrics} available)
|
||||||
</Button>
|
</Button>
|
||||||
</Col>
|
</Col>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user