rework job view header, change footprint to summary component

This commit is contained in:
Christoph Kluge
2024-09-05 16:44:03 +02:00
parent 398e3c1b91
commit df484dc816
7 changed files with 626 additions and 177 deletions

View File

@@ -13,62 +13,86 @@
import {
Card,
CardHeader,
CardTitle,
CardBody,
Icon
} from "@sveltestrap/sveltestrap";
export let cJobs;
export let showLinks = false;
export let displayTitle = true;
export let renderCard = false;
export let width = "auto";
export let height = "310px";
export let height = "400px";
</script>
<Card class="mt-1 overflow-auto" style="width: {width}; height: {height}">
{#if displayTitle}
<CardHeader>
<CardTitle class="mb-0 d-flex justify-content-center">
{#if renderCard}
<Card class="overflow-auto" style="width: {width}; height: {height}">
<CardHeader class="mb-0 d-flex justify-content-center">
{cJobs.items.length} Concurrent Jobs
<Icon
style="cursor:help; margin-left:0.5rem;"
name="info-circle"
title="Jobs running on the same node with overlapping runtimes using shared resources"
/>
</CardTitle>
</CardHeader>
{/if}
<CardBody>
{#if showLinks}
<ul>
<li>
<a
href="/monitoring/jobs/?{cJobs.listQuery}"
target="_blank">See All</a
>
</li>
{#each cJobs.items as cJob}
<CardBody>
{#if showLinks}
<ul>
<li>
<a href="/monitoring/job/{cJob.id}" target="_blank"
>{cJob.jobId}</a
<a
href="/monitoring/jobs/?{cJobs.listQuery}"
target="_blank">See All</a
>
</li>
{/each}
</ul>
{:else}
{#if displayTitle}
<p>
Jobs running on the same node with overlapping runtimes using shared resources.
</p>
{#each cJobs.items as cJob}
<li>
<a href="/monitoring/job/{cJob.id}" target="_blank"
>{cJob.jobId}</a
>
</li>
{/each}
</ul>
{:else}
<p>
<b>{cJobs.items.length} </b>
Jobs running on the same node with overlapping runtimes using shared resources.
</p>
<ul>
{#each cJobs.items as cJob}
<li>
{cJob.jobId}
</li>
{/each}
</ul>
{/if}
{/if}
</CardBody>
</Card>
</CardBody>
</Card>
{:else}
<p>
Jobs running on the same node with overlapping runtimes using shared resources.
</p>
<hr/>
{#if showLinks}
<ul>
<li>
<a
href="/monitoring/jobs/?{cJobs.listQuery}"
target="_blank">See All</a
>
</li>
{#each cJobs.items as cJob}
<li>
<a href="/monitoring/job/{cJob.id}" target="_blank"
>{cJob.jobId}</a
>
</li>
{/each}
</ul>
{:else}
<ul>
{#each cJobs.items as cJob}
<li>
{cJob.jobId}
</li>
{/each}
</ul>
{/if}
{/if}
<style>
ul {

View File

@@ -117,7 +117,7 @@
{/if}
</p>
<p>
<p class="mb-2">
{#each jobTags as tag}
<Tag {tag} />
{/each}

View File

@@ -2,10 +2,11 @@
@component Polar Plot based on chartJS Radar
Properties:
- `metrics [String]`: Metric names to display as polar plot
- `cluster GraphQL.Cluster`: Cluster Object of the parent job
- `subCluster GraphQL.SubCluster`: SubCluster Object of the parent job
- `jobMetrics [GraphQL.JobMetricWithName]`: Metric data
- `footprintData [Object]?`: job.footprint content, evaluated in regards to peak config in jobSummary.svelte [Default: null]
- `metrics [String]?`: Metric names to display as polar plot [Default: null]
- `cluster GraphQL.Cluster?`: Cluster Object of the parent job [Default: null]
- `subCluster GraphQL.SubCluster?`: SubCluster Object of the parent job [Default: null]
- `jobMetrics [GraphQL.JobMetricWithName]?`: Metric data [Default: null]
- `height Number?`: Plot height [Default: 365]
-->
@@ -33,24 +34,52 @@
LineElement
);
export let metrics
export let cluster
export let subCluster
export let jobMetrics
export let height = 365
export let footprintData = null;
export let metrics = null;
export let cluster = null;
export let subCluster = null;
export let jobMetrics = null;
export let height = 350;
const getMetricConfig = getContext("getMetricConfig")
const labels = metrics.filter(name => {
if (!jobMetrics.find(m => m.name == name && m.scope == "node")) {
console.warn(`PolarPlot: No metric data for '${name}'`)
return false
function getLabels() {
if (footprintData) {
return footprintData.filter(fpd => {
if (!jobMetrics.find(m => m.name == fpd.name && m.scope == "node" || fpd.impact == 4)) {
console.warn(`PolarPlot: No metric data (or config) for '${fpd.name}'`)
return false
}
return true
})
.map(filtered => filtered.name)
.sort(function (a, b) {
return ((a > b) ? 1 : ((b > a) ? -1 : 0));
});
} else {
return metrics.filter(name => {
if (!jobMetrics.find(m => m.name == name && m.scope == "node")) {
console.warn(`PolarPlot: No metric data for '${name}'`)
return false
}
return true
})
.sort(function (a, b) {
return ((a > b) ? 1 : ((b > a) ? -1 : 0));
});
}
return true
}
const labels = getLabels();
const getMetricConfig = getContext("getMetricConfig");
const getValuesForStatGeneric = (getStat) => labels.map(name => {
const peak = getMetricConfig(cluster, subCluster, name).peak
const metric = jobMetrics.find(m => m.name == name && m.scope == "node")
const value = getStat(metric.metric) / peak
return value <= 1. ? value : 1.
})
const getValuesForStat = (getStat) => labels.map(name => {
const peak = getMetricConfig(cluster, subCluster, name).peak
const getValuesForStatFootprint = (getStat) => labels.map(name => {
const peak = footprintData.find(fpd => fpd.name === name).peak
const metric = jobMetrics.find(m => m.name == name && m.scope == "node")
const value = getStat(metric.metric) / peak
return value <= 1. ? value : 1.
@@ -70,12 +99,32 @@
return avg / metric.series.length
}
function loadDataGeneric(type) {
if (type === 'avg') {
return getValuesForStatGeneric(getAvg)
} else if (type === 'max') {
return getValuesForStatGeneric(getMax)
}
console.log('Unknown Type For Polar Data')
return []
}
function loadDataForFootprint(type) {
if (type === 'avg') {
return getValuesForStatFootprint(getAvg)
} else if (type === 'max') {
return getValuesForStatFootprint(getMax)
}
console.log('Unknown Type For Polar Data')
return []
}
const data = {
labels: labels,
datasets: [
{
label: 'Max',
data: getValuesForStat(getMax),
data: footprintData ? loadDataForFootprint('max') : loadDataGeneric('max'), //
fill: 1,
backgroundColor: 'rgba(0, 102, 255, 0.25)',
borderColor: 'rgb(0, 102, 255)',
@@ -86,7 +135,7 @@
},
{
label: 'Avg',
data: getValuesForStat(getAvg),
data: footprintData ? loadDataForFootprint('avg') : loadDataGeneric('avg'), // getValuesForStat(getAvg)
fill: true,
backgroundColor: 'rgba(255, 153, 0, 0.25)',
borderColor: 'rgb(255, 153, 0)',
@@ -100,7 +149,7 @@
// No custom defined options but keep for clarity
const options = {
maintainAspectRatio: false,
maintainAspectRatio: true,
animation: false,
scales: { // fix scale
r: {

View File

@@ -7,7 +7,7 @@
- `allowSizeChange Bool?`: If dimensions of rendered plot can change [Default: false]
- `subCluster GraphQL.SubCluster?`: SubCluster Object; contains required topology information [Default: null]
- `width Number?`: Plot width (reactively adaptive) [Default: 600]
- `height Number?`: Plot height (reactively adaptive) [Default: 350]
- `height Number?`: Plot height (reactively adaptive) [Default: 380]
Data Format:
- `data = [null, [], []]`
@@ -33,7 +33,7 @@
export let allowSizeChange = false;
export let subCluster = null;
export let width = 600;
export let height = 350;
export let height = 380;
let plotWrapper = null;
let uplot = null;
@@ -317,7 +317,7 @@
// The Color Scale For Time Information
const posX = u.valToPos(0.1, "x", true)
const posXLimit = u.valToPos(100, "x", true)
const posY = u.valToPos(15000.0, "y", true)
const posY = u.valToPos(14000.0, "y", true)
u.ctx.fillStyle = 'black'
u.ctx.fillText('Start', posX, posY)
const start = posX + 10
@@ -364,7 +364,7 @@
</script>
{#if data != null}
<div bind:this={plotWrapper} />
<div bind:this={plotWrapper} class="p-2"/>
{:else}
<Card class="mx-4" body color="warning">Cannot render roofline: No data!</Card
>