mirror of
https://github.com/ClusterCockpit/cc-backend
synced 2025-07-27 06:36:07 +02:00
rework job view header, change footprint to summary component
This commit is contained in:
@@ -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 {
|
||||
|
@@ -117,7 +117,7 @@
|
||||
{/if}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<p class="mb-2">
|
||||
{#each jobTags as tag}
|
||||
<Tag {tag} />
|
||||
{/each}
|
||||
|
@@ -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: {
|
||||
|
@@ -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
|
||||
>
|
||||
|
Reference in New Issue
Block a user