cc-backend/web/frontend/src/JobFootprintBars.svelte
2023-11-21 10:27:16 +01:00

274 lines
11 KiB
Svelte

<script>
import { getContext } from 'svelte'
import {
Card,
CardHeader,
CardTitle,
CardBody,
Progress,
Icon,
} from "sveltestrap";
import { mean, round } from 'mathjs'
// import { findThresholds } from './plots/MetricPlot.svelte'
// import { formatNumber, scaleNumbers } from './units.js'
export let job
export let jobMetrics
export let view = 'job'
export let width = 'auto'
const footprintMetrics = ['cpu_load', 'flops_any', 'mem_used', 'mem_bw'] // 'acc_utilization' / missing: energy , move to central config before deployment
// console.log('JMs', jobMetrics.filter((jm) => footprintMetrics.includes(jm.name)))
const footprintMetricConfigs = footprintMetrics.map((fm) => {
return getContext('metrics')(job.cluster, fm)
}).filter( Boolean ) // Filter only "truthy" vals, see: https://stackoverflow.com/questions/28607451/removing-undefined-values-from-array
// console.log("FMCs", footprintMetricConfigs)
// const footprintMetricThresholds = footprintMetricConfigs.map((fmc) => { // Only required if scopes smaller than node required
// return {name: fmc.name, ...findThresholds(fmc, 'node', job?.subCluster ? job.subCluster : '')} // Merge 2 objects
// }).filter( Boolean )
// console.log("FMTs", footprintMetricThresholds)
const footprintData = footprintMetrics.map((fm) => {
const jm = jobMetrics.find((jm) => jm.name === fm && jm.scope === 'node')
// ... get Mean
let mv = null
if (jm?.metric?.statisticsSeries) {
mv = round(mean(jm.metric.statisticsSeries.mean), 2)
} else if (jm?.metric?.series[0]) {
mv = jm.metric.series[0].statistics.avg
}
// ... get Unit
let unit = null
if (jm?.metric?.unit?.base) {
unit = jm.metric.unit.prefix + jm.metric.unit.base
} else {
unit = ''
}
// From MetricConfig: Scope only for scaling -> Not of interest here
const metricConfig = footprintMetricConfigs.find((fmc) => fmc.name === fm)
// ... get Thresholds
const levelPeak = fm === 'flops_any' ? round((metricConfig.peak * 0.85), 0) - mv : metricConfig.peak - mv // Scale flops_any down
const levelNormal = metricConfig.normal - mv
const levelCaution = metricConfig.caution - mv
const levelAlert = metricConfig.alert - mv
// Collect
if (fm !== 'mem_used') { // Alert if usage is low, peak as maxmimum possible (scaled down for flops_any)
if (levelAlert > 0) {
return {
name: fm,
unit: unit,
avg: mv,
max: fm === 'flops_any' ? round((metricConfig.peak * 0.85), 0) : metricConfig.peak,
color: 'danger',
message: 'Metric strongly below common levels!',
impact: 3
}
} else if (levelCaution > 0) {
return {
name: fm,
unit: unit,
avg: mv,
max: fm === 'flops_any' ? round((metricConfig.peak * 0.85), 0) : metricConfig.peak,
color: 'warning',
message: 'Metric below common levels',
impact: 2
}
} else if (levelNormal > 0) {
return {
name: fm,
unit: unit,
avg: mv,
max: fm === 'flops_any' ? round((metricConfig.peak * 0.85), 0) : metricConfig.peak,
color: 'success',
message: 'Metric within common levels',
impact: 1
}
} else if (levelPeak > 0) {
return {
name: fm,
unit: unit,
avg: mv,
max: fm === 'flops_any' ? round((metricConfig.peak * 0.85), 0) : metricConfig.peak,
color: 'info',
message: 'Metric performs better than common levels',
impact: 0
}
} else { // Possible artifacts - <5% Margin OK, >5% warning, > 50% danger
const checkData = {
name: fm,
unit: unit,
avg: mv,
max: fm === 'flops_any' ? round((metricConfig.peak * 0.85), 0) : metricConfig.peak
}
if (checkData.avg >= (1.5 * checkData.max)) {
return {
...checkData,
color: 'danger',
message: 'Metric average at least 50% above common peak value: Check data for artifacts!',
impact: -2
}
} else if (checkData.avg >= (1.05 * checkData.max)) {
return {
...checkData,
color: 'warning',
message: 'Metric average at least 5% above common peak value: Check data for artifacts',
impact: -1
}
} else {
return {
...checkData,
color: 'info',
message: 'Metric performs better than common levels',
impact: 0
}
}
}
} else { // Inverse Logic: Alert if usage is high, Peak is bad and limits execution
if (levelPeak <= 0 && levelAlert <= 0 && levelCaution <= 0 && levelNormal <= 0) { // Possible artifacts - <5% Margin OK, >5% warning, > 50% danger
const checkData = {
name: fm,
unit: unit,
avg: mv,
max: metricConfig.peak
}
if (checkData.avg >= (1.5 * checkData.max)) {
return {
...checkData,
color: 'danger',
message: 'Memory usage at least 50% above possible maximum value: Check data for artifacts!',
impact: -2
}
} else if (checkData.avg >= (1.05 * checkData.max)) {
return {
...checkData,
color: 'warning',
message: 'Memory usage at least 5% above possible maximum value: Check data for artifacts!',
impact: -1
}
} else {
return {
...checkData,
color: 'danger',
message: 'Memory usage extremely above common levels!',
impact: 4
}
}
} else if (levelAlert <= 0 && levelCaution <= 0 && levelNormal <= 0) {
return {
name: fm,
unit: unit,
avg: mv,
max: metricConfig.peak,
color: 'danger',
message: 'Memory usage extremely above common levels!',
impact: 4
}
} else if (levelAlert > 0 && (levelCaution <= 0 && levelNormal <= 0)) {
return {
name: fm,
unit: unit,
avg: mv,
max: metricConfig.peak,
color: 'danger',
message: 'Memory usage strongly above common levels!',
impact: 3
}
} else if (levelCaution > 0 && levelNormal <= 0) {
return {
name: fm,
unit: unit,
avg: mv,
max: metricConfig.peak,
color: 'warning',
message: 'Memory usage above common levels',
impact: 2
}
} else {
return {
name: fm,
unit: unit,
avg: mv,
max: metricConfig.peak,
color: 'success',
message: 'Memory usage within common levels',
impact: 1
}
}
}
}).filter( Boolean )
// console.log("FPD", footprintData)
</script>
<Card class="h-auto mt-1" style="width: {width}px;">
{#if view === 'job'}
<CardHeader>
<CardTitle class="mb-0 d-flex justify-content-center">
Core Metrics Footprint
</CardTitle>
</CardHeader>
{/if}
<CardBody>
{#each footprintData as fpd}
<div class="mb-1 d-flex justify-content-between">
<div><b>{fpd.name}</b></div>
<div class="cursor-help d-inline-flex" title={fpd.message}>
<div class="mx-1">
<!-- Alerts Only -->
{#if fpd.impact === 3}
<Icon name="exclamation-triangle-fill" class="text-danger"/>
{:else if fpd.impact === 2}
<Icon name="exclamation-triangle" class="text-warning"/>
{:else if fpd.impact === -1}
<Icon name="exclamation-triangle" class="text-warning"/>
{:else if fpd.impact === -2}
<Icon name="exclamation-triangle-fill" class="text-danger"/>
{/if}
<!-- Emoji for all states-->
{#if fpd.impact === 4}
<Icon name="emoji-angry" class="text-danger"/>
{:else if fpd.impact === 3}
<Icon name="emoji-frown" class="text-danger"/>
{:else if fpd.impact === 2}
<Icon name="emoji-neutral" class="text-warning"/>
{:else if fpd.impact === 1}
<Icon name="emoji-smile" class="text-success"/>
{:else if fpd.impact === 0}
<Icon name="emoji-laughing" class="text-info"/>
{:else if fpd.impact === -1}
<Icon name="emoji-dizzy" class="text-warning"/>
{:else if fpd.impact === -2}
<Icon name="emoji-dizzy" class="text-danger"/>
{/if}
</div>
<div>
<!-- Print Values -->
{fpd.avg} / {fpd.max} {fpd.unit}
</div>
</div>
</div>
<div class="mb-2"> <!-- title={fpd.message} -->
<Progress
value={fpd.avg}
max={fpd.max}
color={fpd.color}
/>
</div>
{/each}
</CardBody>
</Card>
<style>
.cursor-help {
cursor: help;
}
</style>