mirror of
https://github.com/ClusterCockpit/cc-backend
synced 2025-05-15 05:21:43 +02:00
254 lines
7.5 KiB
Svelte
254 lines
7.5 KiB
Svelte
<!--
|
|
@component jobCompare component; compares jobs according to set filters
|
|
|
|
Properties:
|
|
- `sorting Object?`: Currently active sorting [Default: {field: "startTime", type: "col", order: "DESC"}]
|
|
- `matchedJobs Number?`: Number of matched jobs for selected filters [Default: 0]
|
|
- `metrics [String]?`: The currently selected metrics [Default: User-Configured Selection]
|
|
- `showFootprint Bool`: If to display the jobFootprint component
|
|
|
|
Functions:
|
|
- `refreshJobs()`: Load jobs data with unchanged parameters and 'network-only' keyword
|
|
- `refreshAllMetrics()`: Trigger downstream refresh of all running jobs' metric data
|
|
- `queryJobs(filters?: [JobFilter])`: Load jobs data with new filters, starts from page 1
|
|
-->
|
|
|
|
<script>
|
|
import { getContext } from "svelte";
|
|
import uPlot from "uplot";
|
|
import {
|
|
queryStore,
|
|
gql,
|
|
getContextClient,
|
|
// mutationStore,
|
|
} from "@urql/svelte";
|
|
import { Row, Col, Card, Spinner } from "@sveltestrap/sveltestrap";
|
|
import { formatTime } from "./units.js";
|
|
import Comparogram from "./plots/Comparogram.svelte";
|
|
|
|
const ccconfig = getContext("cc-config"),
|
|
// initialized = getContext("initialized"),
|
|
globalMetrics = getContext("globalMetrics");
|
|
|
|
const equalsCheck = (a, b) => {
|
|
return JSON.stringify(a) === JSON.stringify(b);
|
|
}
|
|
|
|
export let matchedCompareJobs = 0;
|
|
export let filterBuffer = [];
|
|
export let metrics = ccconfig.plot_list_selectedMetrics;
|
|
|
|
let filter = [...filterBuffer];
|
|
let comparePlotData = {};
|
|
let jobIds = [];
|
|
let jobClusters = [];
|
|
|
|
/*uPlot*/
|
|
let plotSync = uPlot.sync("compareJobsView");
|
|
|
|
/* GQL */
|
|
const client = getContextClient();
|
|
// Pull All Series For Metrics Statistics Only On Node Scope
|
|
const compareQuery = gql`
|
|
query ($filter: [JobFilter!]!, $metrics: [String!]!) {
|
|
jobsMetricStats(filter: $filter, metrics: $metrics) {
|
|
jobId
|
|
startTime
|
|
duration
|
|
cluster
|
|
subCluster
|
|
numNodes
|
|
numHWThreads
|
|
numAccelerators
|
|
stats {
|
|
name
|
|
data {
|
|
min
|
|
avg
|
|
max
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`;
|
|
|
|
/* REACTIVES */
|
|
|
|
$: compareData = queryStore({
|
|
client: client,
|
|
query: compareQuery,
|
|
variables:{ filter, metrics },
|
|
});
|
|
|
|
$: matchedCompareJobs = $compareData.data != null ? $compareData.data.jobsMetricStats.length : -1;
|
|
$: if ($compareData.data != null) {
|
|
jobIds = [];
|
|
jobClusters = [];
|
|
comparePlotData = {};
|
|
jobs2uplot($compareData.data.jobsMetricStats, metrics);
|
|
}
|
|
|
|
/* FUNCTIONS */
|
|
// Force refresh list with existing unchanged variables (== usually would not trigger reactivity)
|
|
export function refreshJobs() {
|
|
compareData = queryStore({
|
|
client: client,
|
|
query: compareQuery,
|
|
variables: { filter, metrics },
|
|
requestPolicy: "network-only",
|
|
});
|
|
}
|
|
|
|
export function refreshAllMetrics() {
|
|
// Refresh Job Metrics (Downstream will only query for running jobs)
|
|
triggerMetricRefresh = true
|
|
setTimeout(function () {
|
|
triggerMetricRefresh = false;
|
|
}, 100);
|
|
}
|
|
|
|
// (Re-)query and optionally set new filters; Query will be started reactively.
|
|
export function queryJobs(filters) {
|
|
if (filters != null) {
|
|
let minRunningFor = ccconfig.plot_list_hideShortRunningJobs;
|
|
if (minRunningFor && minRunningFor > 0) {
|
|
filters.push({ minRunningFor });
|
|
}
|
|
filter = filters;
|
|
}
|
|
}
|
|
|
|
function jobs2uplot(jobs, metrics) {
|
|
// Resources Init
|
|
comparePlotData['resources'] = {unit:'', data: [[],[],[],[],[],[]]} // data: [X, XST, XRT, YNODES, YTHREADS, YACCS]
|
|
// Metric Init
|
|
for (let m of metrics) {
|
|
// Get Unit
|
|
const rawUnit = globalMetrics.find((gm) => gm.name == m)?.unit
|
|
const metricUnit = (rawUnit?.prefix ? rawUnit.prefix : "") + (rawUnit?.base ? rawUnit.base : "")
|
|
comparePlotData[m] = {unit: metricUnit, data: [[],[],[],[],[],[]]} // data: [X, XST, XRT, YMIN, YAVG, YMAX]
|
|
}
|
|
|
|
// Iterate jobs if exists
|
|
if (jobs) {
|
|
let plotIndex = 0
|
|
jobs.forEach((j) => {
|
|
// Collect JobIDs & Clusters for X-Ticks and Legend
|
|
jobIds.push(j.jobId)
|
|
jobClusters.push(`${j.cluster} ${j.subCluster}`)
|
|
// Resources
|
|
comparePlotData['resources'].data[0].push(plotIndex)
|
|
comparePlotData['resources'].data[1].push(j.startTime)
|
|
comparePlotData['resources'].data[2].push(j.duration)
|
|
comparePlotData['resources'].data[3].push(j.numNodes)
|
|
comparePlotData['resources'].data[4].push(j?.numHWThreads?j.numHWThreads:0)
|
|
comparePlotData['resources'].data[5].push(j?.numAccelerators?j.numAccelerators:0)
|
|
// Metrics
|
|
for (let s of j.stats) {
|
|
comparePlotData[s.name].data[0].push(plotIndex)
|
|
comparePlotData[s.name].data[1].push(j.startTime)
|
|
comparePlotData[s.name].data[2].push(j.duration)
|
|
comparePlotData[s.name].data[3].push(s.data.min)
|
|
comparePlotData[s.name].data[4].push(s.data.avg)
|
|
comparePlotData[s.name].data[5].push(s.data.max)
|
|
}
|
|
plotIndex++
|
|
})
|
|
}
|
|
}
|
|
|
|
// Adapt for Persisting Job Selections in DB later down the line
|
|
// const updateConfigurationMutation = ({ name, value }) => {
|
|
// return mutationStore({
|
|
// client: client,
|
|
// query: gql`
|
|
// mutation ($name: String!, $value: String!) {
|
|
// updateConfiguration(name: $name, value: $value)
|
|
// }
|
|
// `,
|
|
// variables: { name, value },
|
|
// });
|
|
// };
|
|
|
|
// function updateConfiguration(value, page) {
|
|
// updateConfigurationMutation({
|
|
// name: "plot_list_jobsPerPage",
|
|
// value: value,
|
|
// }).subscribe((res) => {
|
|
// if (res.fetching === false && !res.error) {
|
|
// jobs = [] // Empty List
|
|
// paging = { itemsPerPage: value, page: page }; // Trigger reload of jobList
|
|
// } else if (res.fetching === false && res.error) {
|
|
// throw res.error;
|
|
// }
|
|
// });
|
|
// }
|
|
|
|
</script>
|
|
|
|
{#if $compareData.fetching}
|
|
<Row>
|
|
<Col>
|
|
<Spinner secondary />
|
|
</Col>
|
|
</Row>
|
|
{:else if $compareData.error}
|
|
<Row>
|
|
<Col>
|
|
<Card body color="danger" class="mb-3"
|
|
><h2>{$compareData.error.message}</h2></Card
|
|
>
|
|
</Col>
|
|
</Row>
|
|
{:else}
|
|
{#key comparePlotData}
|
|
<Row>
|
|
<Col>
|
|
<Comparogram
|
|
title={'Compare Resources'}
|
|
xlabel="JobIDs"
|
|
xticks={jobIds}
|
|
xinfo={jobClusters}
|
|
ylabel={'Resource Counts'}
|
|
data={comparePlotData['resources'].data}
|
|
{plotSync}
|
|
forResources
|
|
/>
|
|
</Col>
|
|
</Row>
|
|
{#each metrics as m}
|
|
<Row>
|
|
<Col>
|
|
<Comparogram
|
|
title={`Compare Metric '${m}'`}
|
|
xlabel="JobIDs"
|
|
xticks={jobIds}
|
|
xinfo={jobClusters}
|
|
ylabel={m}
|
|
metric={m}
|
|
yunit={comparePlotData[m].unit}
|
|
data={comparePlotData[m].data}
|
|
{plotSync}
|
|
/>
|
|
</Col>
|
|
</Row>
|
|
{/each}
|
|
{/key}
|
|
<hr/><hr/>
|
|
{#each $compareData.data.jobsMetricStats as job, jindex (job.jobId)}
|
|
<Row>
|
|
<Col><b>{jindex}: <i>{job.jobId}</i></b></Col>
|
|
<Col><i>{new Date(job.startTime * 1000).toISOString()}</i></Col>
|
|
<Col><i>{formatTime(job.duration)}</i></Col>
|
|
{#each job.stats as stat (stat.name)}
|
|
<Col><b>{stat.name}</b></Col>
|
|
<Col>Min {stat.data.min}</Col>
|
|
<Col>Avg {stat.data.avg}</Col>
|
|
<Col>Max {stat.data.max}</Col>
|
|
{/each}
|
|
</Row>
|
|
<hr/>
|
|
{:else}
|
|
<div> No jobs found </div>
|
|
{/each}
|
|
{/if} |