diff --git a/web/frontend/src/generic/JobCompare.svelte b/web/frontend/src/generic/JobCompare.svelte index 9ae8ea7..592be55 100644 --- a/web/frontend/src/generic/JobCompare.svelte +++ b/web/frontend/src/generic/JobCompare.svelte @@ -2,7 +2,6 @@ @component jobCompare component; compares jobs according to set filters or job selection 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 @@ -21,7 +20,7 @@ // mutationStore, } from "@urql/svelte"; import { Row, Col, Card, Spinner, Table, Input, InputGroup, InputGroupText, Icon } from "@sveltestrap/sveltestrap"; - import { formatTime } from "./units.js"; + import { formatTime, roundTwoDigits } from "./units.js"; import Comparogram from "./plots/Comparogram.svelte"; const ccconfig = getContext("cc-config"), @@ -34,6 +33,8 @@ let filter = [...filterBuffer] || []; let comparePlotData = {}; + let compareTableData = []; + let compareTableSorting = {}; let jobIds = []; let jobClusters = []; let tableJobIDFilter = ""; @@ -82,9 +83,33 @@ jobIds = []; jobClusters = []; comparePlotData = {}; + compareTableData = [...$compareData.data.jobsMetricStats]; jobs2uplot($compareData.data.jobsMetricStats, metrics); } + $: if ((!$compareData.fetching && !$compareData.error) && metrics) { + // Meta + compareTableSorting['meta'] = { + startTime: { dir: "down", active: true }, + duration: { dir: "up", active: false }, + cluster: { dir: "up", active: false }, + }; + // Resources + compareTableSorting['resources'] = { + Nodes: { dir: "up", active: false }, + Threads: { dir: "up", active: false }, + Accs: { dir: "up", active: false }, + }; + // Metrics + for (let metric of metrics) { + compareTableSorting[metric] = { + min: { dir: "up", active: false }, + avg: { dir: "up", active: false }, + max: { dir: "up", active: false }, + }; + } + } + /* FUNCTIONS */ // (Re-)query and optionally set new filters; Query will be started reactively. export function queryJobs(filters) { @@ -97,6 +122,57 @@ } } + function sortBy(key, field) { + let s = compareTableSorting[key][field]; + if (s.active) { + s.dir = s.dir == "up" ? "down" : "up"; + } else { + for (let key in compareTableSorting) + for (let field in compareTableSorting[key]) compareTableSorting[key][field].active = false; + s.active = true; + } + compareTableSorting = { ...compareTableSorting }; + + if (key == 'resources') { + let longField = ""; + switch (field) { + case "Nodes": + longField = "numNodes" + break + case "Threads": + longField = "numHWThreads" + break + case "Accs": + longField = "numAccelerators" + break + default: + console.log("Unknown Res Field", field) + } + compareTableData = compareTableData.sort((j1, j2) => { + if (j1[longField] == null || j2[longField] == null) return -1; + return s.dir != "up" ? j1[longField] - j2[longField] : j2[longField] - j1[longField]; + }); + } else if (key == 'meta') { + compareTableData = compareTableData.sort((j1, j2) => { + if (j1[field] == null || j2[field] == null) return -1; + if (field == 'cluster') { + let c1 = `${j1.cluster} (${j1.subCluster})` + let c2 = `${j2.cluster} (${j2.subCluster})` + return s.dir != "up" ? c1.localeCompare(c2) : c2.localeCompare(c1) + } else { + return s.dir != "up" ? j1[field] - j2[field] : j2[field] - j1[field]; + } + }); + } else { + compareTableData = compareTableData.sort((j1, j2) => { + let s1 = j1.stats.find((m) => m.name == key)?.data; + let s2 = j2.stats.find((m) => m.name == key)?.data; + if (s1 == null || s2 == null) return -1; + return s.dir != "up" ? s1[field] - s2[field] : s2[field] - s1[field]; + }); + } + } + function jobs2uplot(jobs, metrics) { // Resources Init comparePlotData['resources'] = {unit:'', data: [[],[],[],[],[],[]]} // data: [X, XST, XRT, YNODES, YTHREADS, YACCS] @@ -219,11 +295,10 @@ - Index - JobID - Cluster + JobID StartTime Duration + Cluster Resources {#each metrics as metric} {metric} @@ -231,47 +306,87 @@ - - - + + + - - - - + sortBy('meta', 'startTime')}> + Sort + + + sortBy('meta', 'duration')}> + Sort + + + sortBy('meta', 'cluster')}> + Sort + + {#each ["Nodes", "Threads", "Accs"] as res} - {res} + sortBy('resources', res)}> + {res} + + {/each} {#each metrics as metric} {#each ["min", "avg", "max"] as stat} - {stat} + sortBy(metric, stat)}> + {stat} + + {/each} {/each} - {#each $compareData.data.jobsMetricStats.filter((j) => j.jobId.includes(tableJobIDFilter)) as job, jindex (job.jobId)} + {#each compareTableData.filter((j) => j.jobId.includes(tableJobIDFilter)) as job (job.id)} - {jindex} - {job.jobId} - {job.cluster} ({job.subCluster}) - {new Date(job.startTime * 1000).toISOString()} + {job.jobId} + {new Date(job.startTime * 1000).toLocaleString()} {formatTime(job.duration)} + {job.cluster} ({job.subCluster}) {job.numNodes} {job.numHWThreads} {job.numAccelerators} {#each metrics as metric} - {job.stats.find((s) => s.name == metric).data.min} - {job.stats.find((s) => s.name == metric).data.avg} - {job.stats.find((s) => s.name == metric).data.max} + {roundTwoDigits(job.stats.find((s) => s.name == metric).data.min)} + {roundTwoDigits(job.stats.find((s) => s.name == metric).data.avg)} + {roundTwoDigits(job.stats.find((s) => s.name == metric).data.max)} {/each} {:else} - No jobs found + + No jobs found. + {/each}