diff --git a/web/frontend/src/Job.root.svelte b/web/frontend/src/Job.root.svelte index ca11692..895dd5a 100644 --- a/web/frontend/src/Job.root.svelte +++ b/web/frontend/src/Job.root.svelte @@ -37,9 +37,9 @@ import Metric from "./job/Metric.svelte"; import TagManagement from "./job/TagManagement.svelte"; import StatsTable from "./job/StatsTable.svelte"; - import JobFootprint from "./generic/helper/JobFootprint.svelte"; + import JobSummary from "./job/JobSummary.svelte"; + import ConcurrentJobs from "./generic/helper/ConcurrentJobs.svelte"; import PlotTable from "./generic/PlotTable.svelte"; - import Polar from "./generic/plots/Polar.svelte"; import Roofline from "./generic/plots/Roofline.svelte"; import JobInfo from "./generic/joblist/JobInfo.svelte"; import MetricSelection from "./generic/select/MetricSelection.svelte"; @@ -59,7 +59,9 @@ selectedScopes = []; let plots = {}, - jobTags + jobTags, + statsTable, + roofWidth let missingMetrics = [], missingHosts = [], @@ -231,95 +233,95 @@ })); - - + + + {#if $initq.error} {$initq.error.message} {:else if $initq.data} - + + + + + + + + {#if $initq.data.job.concurrentJobs != null && $initq.data.job.concurrentJobs.items.length != 0} + + + {$initq.data.job.concurrentJobs.items.length} Concurrent Jobs + + + roles.manager)}/> + + + {/if} + {#if $initq.data?.job?.metaData?.message} + + +

This note was added by administrators:

+
+

{@html $initq.data.job.metaData.message}

+
+
+ {/if} +
+
{:else} {/if} - {#if $initq.data && showFootprint} - - - - {/if} - {#if $initq?.data && $jobMetrics?.data?.jobMetrics} - {#if $initq.data.job.concurrentJobs != null && $initq.data.job.concurrentJobs.items.length != 0} - {#if authlevel > roles.manager} - -
- Concurrent Jobs -
-
    -
  • - See All -
  • - {#each $initq.data.job.concurrentJobs.items as pjob, index} -
  • - {pjob.jobId} -
  • - {/each} -
- + + + {#if showFootprint} + + {#if $initq.error} + {$initq.error.message} + {:else if $initq?.data && $jobMetrics?.data} + {:else} - -
- {$initq.data.job.concurrentJobs.items.length} Concurrent Jobs -
-

- Number of shared jobs on the same node with overlapping runtimes. -

- + {/if} - {/if} - - - - c.name == $initq.data.job.cluster) - .subClusters.find((sc) => sc.name == $initq.data.job.subCluster)} - data={transformDataForRoofline( - $jobMetrics.data.jobMetrics.find( - (m) => m.name == "flops_any" && m.scope == "node", - )?.metric, - $jobMetrics.data.jobMetrics.find( - (m) => m.name == "mem_bw" && m.scope == "node", - )?.metric, - )} - /> - - {:else} - - - {/if} + + + + {#if $initq.error || $jobMetrics.error} + +

Initq Error: {$initq.error?.message}

+

jobMetrics Error: {$jobMetrics.error?.message}

+
+ {:else if $initq?.data && $jobMetrics?.data} + +
+ c.name == $initq.data.job.cluster) + .subClusters.find((sc) => sc.name == $initq.data.job.subCluster)} + data={transformDataForRoofline( + $jobMetrics.data.jobMetrics.find( + (m) => m.name == "flops_any" && m.scope == "node", + )?.metric, + $jobMetrics.data.jobMetrics.find( + (m) => m.name == "mem_bw" && m.scope == "node", + )?.metric, + )} + /> +
+
+ {:else} + + {/if} +
- + +
+ + {#if $initq.data} @@ -376,75 +378,81 @@ {/if} - + +
+ + {#if $initq.data} - - {#if somethingMissing} - -
- - - Missing Metrics/Resources - - - {#if missingMetrics.length > 0} -

- No data at all is available for the metrics: {missingMetrics.join( - ", ", - )} -

- {/if} - {#if missingHosts.length > 0} -

Some metrics are missing for the following hosts:

-
    - {#each missingHosts as missing} -
  • - {missing.hostname}: {missing.metrics.join(", ")} -
  • - {/each} -
- {/if} -
-
+ + + {#if somethingMissing} + +
+ + + Missing Metrics/Resources + + + {#if missingMetrics.length > 0} +

+ No data at all is available for the metrics: {missingMetrics.join( + ", ", + )} +

+ {/if} + {#if missingHosts.length > 0} +

Some metrics are missing for the following hosts:

+
    + {#each missingHosts as missing} +
  • + {missing.hostname}: {missing.metrics.join(", ")} +
  • + {/each} +
+ {/if} +
+
+
+
+ {/if} + + {#if $jobMetrics?.data?.jobMetrics} + {#key $jobMetrics.data.jobMetrics} + + {/key} + {/if} + + +
+ {#if $initq.data.job.metaData?.jobScript} +
{$initq.data.job.metaData?.jobScript}
+ {:else} + No job script available + {/if}
- {/if} - - {#if $jobMetrics?.data?.jobMetrics} - {#key $jobMetrics.data.jobMetrics} - - {/key} - {/if} - - -
- {#if $initq.data.job.metaData?.jobScript} -
{$initq.data.job.metaData?.jobScript}
- {:else} - No job script available - {/if} -
-
- -
- {#if $initq.data.job.metaData?.slurmInfo} -
{$initq.data.job.metaData?.slurmInfo}
- {:else} - No additional slurm information available - {/if} -
-
-
+ +
+ {#if $initq.data.job.metaData?.slurmInfo} +
{$initq.data.job.metaData?.slurmInfo}
+ {:else} + No additional slurm information available + {/if} +
+
+ +
{/if} diff --git a/web/frontend/src/generic/helper/ConcurrentJobs.svelte b/web/frontend/src/generic/helper/ConcurrentJobs.svelte new file mode 100644 index 0000000..85bac83 --- /dev/null +++ b/web/frontend/src/generic/helper/ConcurrentJobs.svelte @@ -0,0 +1,101 @@ + + + + +{#if renderCard} + + + {cJobs.items.length} Concurrent Jobs + + + + {#if showLinks} + + {:else} +
    + {#each cJobs.items as cJob} +
  • + {cJob.jobId} +
  • + {/each} +
+ {/if} +
+
+{:else} +

+ {cJobs.items.length} Jobs running on the same node with overlapping runtimes using shared resources. + ( See All ) +

+
+ {#if showLinks} +
    + {#each cJobs.items as cJob} +
  • + {cJob.jobId} +
  • + {/each} +
+ {:else} +
    + {#each cJobs.items as cJob} +
  • + {cJob.jobId} +
  • + {/each} +
+ {/if} +{/if} + + diff --git a/web/frontend/src/generic/joblist/JobInfo.svelte b/web/frontend/src/generic/joblist/JobInfo.svelte index 1e32afc..55b8ebf 100644 --- a/web/frontend/src/generic/joblist/JobInfo.svelte +++ b/web/frontend/src/generic/joblist/JobInfo.svelte @@ -117,7 +117,7 @@ {/if}

-

+

{#each jobTags as tag} {/each} diff --git a/web/frontend/src/generic/plots/Polar.svelte b/web/frontend/src/generic/plots/Polar.svelte index cbc4ce1..b55e4f0 100644 --- a/web/frontend/src/generic/plots/Polar.svelte +++ b/web/frontend/src/generic/plots/Polar.svelte @@ -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: { diff --git a/web/frontend/src/generic/plots/Roofline.svelte b/web/frontend/src/generic/plots/Roofline.svelte index fbe9aae..c9a0bbe 100644 --- a/web/frontend/src/generic/plots/Roofline.svelte +++ b/web/frontend/src/generic/plots/Roofline.svelte @@ -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; @@ -41,8 +41,6 @@ const lineWidth = clusterCockpitConfig.plot_general_lineWidth; - - // Helpers function getGradientR(x) { if (x < 0.5) return 0; @@ -317,7 +315,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 +362,7 @@ {#if data != null} -

+
{:else} Cannot render roofline: No data! diff --git a/web/frontend/src/job/JobSummary.svelte b/web/frontend/src/job/JobSummary.svelte new file mode 100644 index 0000000..48f6e99 --- /dev/null +++ b/web/frontend/src/job/JobSummary.svelte @@ -0,0 +1,340 @@ + + + + + + + + + + + {#each footprintData as fpd, index} + {#if fpd.impact !== 4} +
+
 {fpd.name} ({fpd.stat})
+
+
+ {#if fpd.impact === 3 || fpd.impact === -1} + + {:else if fpd.impact === 2} + + {/if} + {#if fpd.impact === 3} + + {:else if fpd.impact === 2} + + {:else if fpd.impact === 1} + + {:else if fpd.impact === 0} + + {:else if fpd.impact === -1} + + {/if} +
+
+ {fpd.value} / {fpd.peak} + {fpd.unit}   +
+
+ {fpd.message} +
+ + {#if fpd.dir} + + + + {/if} + + + + {#if !fpd.dir} + + + + {/if} + + {:else} +
+
+  {fpd.name} ({fpd.stat}) +
+
+
+ +
+
+ {fpd.value}  +
+
+
+ {fpd.message} + {/if} + {/each} +
+
+ + + + + + + +

Based on footprint data, this job performs as follows:

+
+
    + {#each summaryMessages as sm} +
  • + {@html sm} +
  • + {/each} +
+
+
+
+
+ + diff --git a/web/frontend/src/job/StatsTable.svelte b/web/frontend/src/job/StatsTable.svelte index 88777f6..606d05b 100644 --- a/web/frontend/src/job/StatsTable.svelte +++ b/web/frontend/src/job/StatsTable.svelte @@ -84,7 +84,7 @@ } - +
@@ -146,8 +146,6 @@
-
-