Use html tag for metadata message

- remove old footprint version based on chartjs pie
This commit is contained in:
Christoph Kluge 2023-11-22 10:53:18 +01:00
parent 6b78b4e12b
commit 709880ff5a
4 changed files with 223 additions and 523 deletions

View File

@ -27,7 +27,7 @@
import TagManagement from "./TagManagement.svelte"; import TagManagement from "./TagManagement.svelte";
import MetricSelection from "./MetricSelection.svelte"; import MetricSelection from "./MetricSelection.svelte";
import StatsTable from "./StatsTable.svelte"; import StatsTable from "./StatsTable.svelte";
import JobFootprintBars from "./JobFootprintBars.svelte"; import JobFootprint from "./JobFootprint.svelte";
import { getContext } from "svelte"; import { getContext } from "svelte";
export let dbid; export let dbid;
@ -206,12 +206,7 @@
{#if $jobMetrics.data} {#if $jobMetrics.data}
{#key $jobMetrics.data} {#key $jobMetrics.data}
<Col> <Col>
<!-- <JobFootprint <JobFootprint
bind:this={jobFootprint}
job={$initq.data.job}
jobMetrics={$jobMetrics.data.jobMetrics}
/> -->
<JobFootprintBars
bind:this={jobFootprint} bind:this={jobFootprint}
job={$initq.data.job} job={$initq.data.job}
jobMetrics={$jobMetrics.data.jobMetrics} jobMetrics={$jobMetrics.data.jobMetrics}

View File

@ -1,45 +1,33 @@
<script> <script>
import { getContext } from 'svelte' import { getContext } from 'svelte'
// import { Button, Table, InputGroup, InputGroupText, Icon } from 'sveltestrap' import {
Card,
CardHeader,
CardTitle,
CardBody,
Progress,
Icon,
} from "sveltestrap";
import { mean, round } from 'mathjs' import { mean, round } from 'mathjs'
// import { findThresholds } from './plots/MetricPlot.svelte' // import { findThresholds } from './plots/MetricPlot.svelte'
// import { formatNumber } from './units.js' // import { formatNumber, scaleNumbers } from './units.js'
import { Pie } from 'svelte-chartjs';
import {
Chart as ChartJS,
Title,
Tooltip,
Legend,
Filler,
ArcElement,
CategoryScale
} from 'chart.js';
ChartJS.register(
Title,
Tooltip,
Legend,
Filler,
ArcElement,
CategoryScale
);
export let job export let job
export let jobMetrics export let jobMetrics
export let view = 'job'
export let width = 'auto'
export let size = 200 // console.log('CLUSTER', job.cluster)
export let displayLegend = false
const footprintMetrics = ['mem_used', 'mem_bw','flops_any', 'cpu_load'] // 'acc_utilization' / missing: energy , move to central config before deployment 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))) // console.log('JMs', jobMetrics.filter((jm) => footprintMetrics.includes(jm.name)))
const footprintMetricConfigs = footprintMetrics.map((fm) => { const footprintMetricConfigs = footprintMetrics.map((fm) => {
return getContext('metrics')(job.cluster, fm) return getContext('metrics')(job.cluster, fm)
}).filter( Boolean ) // Filter only "truthy" vals, see: https://stackoverflow.com/questions/28607451/removing-undefined-values-from-array }).filter( Boolean ) // Filter only "truthy" vals, see: https://stackoverflow.com/questions/28607451/removing-undefined-values-from-array
console.log("FMCs", footprintMetricConfigs) // console.log("FMCs", footprintMetricConfigs)
// const footprintMetricThresholds = footprintMetricConfigs.map((fmc) => { // Only required if scopes smaller than node required // 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 // return {name: fmc.name, ...findThresholds(fmc, 'node', job?.subCluster ? job.subCluster : '')} // Merge 2 objects
@ -47,239 +35,244 @@
// console.log("FMTs", footprintMetricThresholds) // console.log("FMTs", footprintMetricThresholds)
const meanVals = footprintMetrics.map((fm) => { const footprintData = footprintMetrics.map((fm) => {
let jm = jobMetrics.find((jm) => jm.name === fm && jm.scope === 'node') // Only Node Scope const jm = jobMetrics.find((jm) => jm.name === fm && jm.scope === 'node')
// ... get Mean
let mv = null let mv = null
if (jm?.metric?.statisticsSeries) { if (jm?.metric?.statisticsSeries) {
mv = {name: jm.name, avg: round(mean(jm.metric.statisticsSeries.mean), 2)} mv = round(mean(jm.metric.statisticsSeries.mean), 2)
} else if (jm?.metric?.series[0]) { } else if (jm?.metric?.series[0]) {
mv = {name: jm.name, avg: jm.metric.series[0].statistics.avg} mv = jm.metric.series[0].statistics.avg
} }
// ... get Unit
let unit = null
if (jm?.metric?.unit?.base) { if (jm?.metric?.unit?.base) {
return {...mv, unit: jm.metric.unit.prefix + jm.metric.unit.base} unit = jm.metric.unit.prefix + jm.metric.unit.base
} else { } else {
return {...mv, unit: ''} unit = ''
} }
// From MetricConfig: Scope only for scaling -> Not of interest here
}).filter( Boolean ) const metricConfig = footprintMetricConfigs.find((fmc) => fmc.name === fm)
// ... get Thresholds
console.log("MVs", meanVals) 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 footprintData = meanVals.map((mv) => { const levelCaution = metricConfig.caution - mv
const metricConfig = footprintMetricConfigs.find((fmc) => fmc.name === mv.name) const levelAlert = metricConfig.alert - mv
const levelPeak = metricConfig.peak - mv.avg // Collect
const levelNormal = metricConfig.normal - mv.avg if (fm !== 'mem_used') { // Alert if usage is low, peak as maxmimum possible (scaled down for flops_any)
const levelCaution = metricConfig.caution - mv.avg
const levelAlert = metricConfig.alert - mv.avg
if (mv.name !== 'mem_used') { // Alert if usage is low, peak is high good usage
if (levelAlert > 0) { if (levelAlert > 0) {
return { return {
data: [mv.avg, levelAlert], name: fm,
color: ['hsl(0, 100%, 60%)', '#AAA'], unit: unit,
messages: ['Metric strongly below recommended levels!', 'Difference towards acceptable performace'], avg: mv,
impact: 2 max: fm === 'flops_any' ? round((metricConfig.peak * 0.85), 0) : metricConfig.peak,
} // 'hsl(0, 100%, 35%)' color: 'danger',
message: 'Metric strongly below common levels!',
impact: 3
}
} else if (levelCaution > 0) { } else if (levelCaution > 0) {
return { return {
data: [mv.avg, levelCaution], name: fm,
color: ['hsl(56, 100%, 50%)', '#AAA'], unit: unit,
messages: ['Metric below recommended levels', 'Difference towards normal performance'], avg: mv,
impact: 1 max: fm === 'flops_any' ? round((metricConfig.peak * 0.85), 0) : metricConfig.peak,
} // '#d5b60a' color: 'warning',
message: 'Metric below common levels',
impact: 2
}
} else if (levelNormal > 0) { } else if (levelNormal > 0) {
return { return {
data: [mv.avg, levelNormal], name: fm,
color: ['hsl(100, 100%, 60%)', '#AAA'], unit: unit,
messages: ['Metric within recommended levels', 'Difference towards optimal performance'], avg: mv,
impact: 0 max: fm === 'flops_any' ? round((metricConfig.peak * 0.85), 0) : metricConfig.peak,
} // 'hsl(100, 100%, 35%)' color: 'success',
message: 'Metric within common levels',
impact: 1
}
} else if (levelPeak > 0) { } else if (levelPeak > 0) {
return { return {
data: [mv.avg, levelPeak], name: fm,
color: ['hsl(180, 100%, 60%)', '#AAA'], unit: unit,
messages: ['Metric performs better than recommended levels', 'Difference towards maximum capacity'], // "Perfomrs optimal"? 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 impact: 0
} // 'hsl(180, 100%, 35%)' }
} else { // If avg greater than configured peak: render negative diff as zero } 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 { return {
data: [mv.avg, 0], ...checkData,
color: ['hsl(180, 100%, 60%)', '#AAA'], color: 'danger',
messages: ['Metric performs at maximum capacity', 'Maximum reached'], 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 impact: 0
} // 'hsl(180, 100%, 35%)' }
}
} }
} else { // Inverse Logic: Alert if usage is high, Peak is bad and limits execution } else { // Inverse Logic: Alert if usage is high, Peak is bad and limits execution
if (levelPeak <= 0 && levelAlert <= 0 && levelCaution <= 0 && levelNormal <= 0) { // If avg greater than configured peak: render negative diff as zero 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 { return {
data: [mv.avg, 0], ...checkData,
color: ['#7F00FF', '#AAA'], color: 'danger',
messages: ['Memory usage at maximum capacity!', 'Maximum reached'], 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 impact: 4
} // '#5D3FD3' }
} else if (levelPeak > 0 && (levelAlert <= 0 && levelCaution <= 0 && levelNormal <= 0)) { }
} else if (levelAlert <= 0 && levelCaution <= 0 && levelNormal <= 0) {
return { return {
data: [mv.avg, levelPeak], name: fm,
color: ['#7F00FF', '#AAA'], unit: unit,
messages: ['Memory usage extremely above recommended levels!', 'Difference towards maximum memory capacity'], avg: mv,
impact: 2 max: metricConfig.peak,
} // '#5D3FD3' color: 'danger',
message: 'Memory usage extremely above common levels!',
impact: 4
}
} else if (levelAlert > 0 && (levelCaution <= 0 && levelNormal <= 0)) { } else if (levelAlert > 0 && (levelCaution <= 0 && levelNormal <= 0)) {
return { return {
data: [mv.avg, levelAlert], name: fm,
color: ['hsl(0, 100%, 60%)', '#AAA'], unit: unit,
messages: ['Memory usage strongly above recommended levels!', 'Difference towards highly alerting memory usage'], avg: mv,
impact: 2 max: metricConfig.peak,
} // 'hsl(0, 100%, 35%)' color: 'danger',
message: 'Memory usage strongly above common levels!',
impact: 3
}
} else if (levelCaution > 0 && levelNormal <= 0) { } else if (levelCaution > 0 && levelNormal <= 0) {
return { return {
data: [mv.avg, levelCaution], name: fm,
color: ['hsl(56, 100%, 50%)', '#AAA'], unit: unit,
messages: ['Memory usage above recommended levels', 'Difference towards alerting memory usage'], avg: mv,
impact: 1 max: metricConfig.peak,
} // '#d5b60a' color: 'warning',
message: 'Memory usage above common levels',
impact: 2
}
} else { } else {
return { return {
data: [mv.avg, levelNormal], name: fm,
color: ['hsl(100, 100%, 60%)', '#AAA'], unit: unit,
messages: ['Memory usage within recommended levels', 'Difference towards increased memory usage'], avg: mv,
impact: 0 max: metricConfig.peak,
} // 'hsl(100, 100%, 35%)' color: 'success',
message: 'Memory usage within common levels',
impact: 1
} }
} }
}) }
}).filter( Boolean )
console.log("FPD", footprintData) // console.log("FPD", footprintData)
// Collect data for chartjs
const footprintLabels = meanVals.map((mv) => [mv.name, 'Threshold']).flat()
const footprintUnits = meanVals.map((mv) => [mv.unit, mv.unit]).flat()
const footprintMessages = footprintData.map((fpd) => fpd.messages).flat()
const footprintResultSum = footprintData.map((fpd) => fpd.impact).reduce((accumulator, currentValue) => { return accumulator + currentValue }, 0)
let footprintResult = ''
if (footprintResultSum <= 1) {
footprintResult = 'good'
} else if (footprintResultSum > 1 && footprintResultSum <= 3) {
footprintResult = 'well'
} else if (footprintResultSum > 3 && footprintResultSum <= 5) {
footprintResult = 'acceptable'
} else {
footprintResult = 'badly'
}
$: data = {
labels: footprintLabels,
datasets: [
{
backgroundColor: footprintData[0].color,
data: footprintData[0].data
},
{
backgroundColor: footprintData[1].color,
data: footprintData[1].data
},
{
backgroundColor: footprintData[2].color,
data: footprintData[2].data
},
{
backgroundColor: footprintData[3].color,
data: footprintData[3].data
}
]
}
const options = {
maintainAspectRatio: false,
animation: false,
plugins: {
legend: {
display: displayLegend,
labels: { // see: https://www.chartjs.org/docs/latest/samples/other-charts/multi-series-pie.html
generateLabels: function(chart) {
// Get the default label list
const original = ChartJS.overrides.pie.plugins.legend.labels.generateLabels;
const labelsOriginal = original.call(this, chart);
// Build an array of colors used in the datasets of the chart
let datasetColors = chart.data.datasets.map(function(e) {
return e.backgroundColor;
});
datasetColors = datasetColors.flat();
// Modify the color and hide state of each label
labelsOriginal.forEach(label => {
// There are twice as many labels as there are datasets. This converts the label index into the corresponding dataset index
label.datasetIndex = (label.index - label.index % 2) / 2;
// The hidden state must match the dataset's hidden state
label.hidden = !chart.isDatasetVisible(label.datasetIndex);
// Change the color to match the dataset
label.fillStyle = datasetColors[label.index];
});
return labelsOriginal;
}
},
onClick: function(mouseEvent, legendItem, legend) {
// toggle the visibility of the dataset from what it currently is
legend.chart.getDatasetMeta(
legendItem.datasetIndex
).hidden = legend.chart.isDatasetVisible(legendItem.datasetIndex);
legend.chart.update();
}
},
tooltip: {
callbacks: {
label: function(context) {
const labelIndex = (context.datasetIndex * 2) + context.dataIndex;
if (context.chart.data.labels[labelIndex] === 'Threshold') {
return ' -' + context.formattedValue + ' ' + footprintUnits[labelIndex]
} else {
return ' ' + context.formattedValue + ' ' + footprintUnits[labelIndex]
}
},
title: function(context) {
const labelIndex = (context[0].datasetIndex * 2) + context[0].dataIndex;
if (context[0].chart.data.labels[labelIndex] === 'Threshold') {
return 'Until ' + context[0].chart.data.labels[labelIndex]
} else {
return 'Average ' + context[0].chart.data.labels[labelIndex]
}
},
footer: function(context) {
const labelIndex = (context[0].datasetIndex * 2) + context[0].dataIndex;
if (context[0].chart.data.labels[labelIndex] === 'Threshold') {
return footprintMessages[labelIndex]
} else {
return footprintMessages[labelIndex]
}
}
}
}
}
}
</script> </script>
<div class="chart-container" style="--container-width: {size}; --container-height: {size}"> <Card class="h-auto mt-1" style="width: {width}px;">
<Pie {data} {options}/> {#if view === 'job'}
</div> <CardHeader>
<div class="mt-3 d-flex justify-content-center"> <CardTitle class="mb-0 d-flex justify-content-center">
<b>Overall Job Performance:&nbsp;</b> Your job {job.state === 'running' ? 'performs' : 'performed'} {footprintResult}. Core Metrics Footprint
</div> </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}
{#if job?.metaData?.message}
<hr class="mt-1 mb-2"/>
{@html job.metaData.message}
{/if}
</CardBody>
</Card>
<style> <style>
.chart-container { .cursor-help {
position: relative; cursor: help;
margin: auto;
height: var(--container-height);
width: var(--container-width);
} }
</style> </style>

View File

@ -1,281 +0,0 @@
<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}
{#if job?.metaData?.message}
<hr class="mt-1 mb-2"/>
<div class="rounded bg-warning">
<div class="mx-1">
<b>Note:</b> {job.metaData.message}
</div>
</div>
{/if}
</CardBody>
</Card>
<style>
.cursor-help {
cursor: help;
}
</style>

View File

@ -15,7 +15,6 @@
import MetricPlot from "../plots/MetricPlot.svelte"; import MetricPlot from "../plots/MetricPlot.svelte";
import JobInfo from "./JobInfo.svelte"; import JobInfo from "./JobInfo.svelte";
import JobFootprint from "../JobFootprint.svelte"; import JobFootprint from "../JobFootprint.svelte";
import JobFootprintBars from "../JobFootprintBars.svelte";
import { maxScope, checkMetricDisabled } from "../utils.js"; import { maxScope, checkMetricDisabled } from "../utils.js";
export let job; export let job;
@ -139,14 +138,8 @@
</td> </td>
{:else} {:else}
{#if showFootprint} {#if showFootprint}
<!-- <td>
<JobFootprint
job={job}
jobMetrics={$metricsQuery.data.jobMetrics}
/>
</td> -->
<td> <td>
<JobFootprintBars <JobFootprint
job={job} job={job}
jobMetrics={$metricsQuery.data.jobMetrics} jobMetrics={$metricsQuery.data.jobMetrics}
width={plotWidth} width={plotWidth}