Merge branch 'hotfix' of github.com:ClusterCockpit/cc-backend into hotfix

This commit is contained in:
Jan Eitzinger 2023-06-20 07:55:59 +02:00
commit 25acb2eaa5
9 changed files with 108 additions and 131 deletions

View File

@ -227,12 +227,13 @@
<PlotTable <PlotTable
let:item let:item
let:width let:width
renderFor="analysis"
items={metricsInHistograms.map(metric => ({ metric, ...binsFromFootprint( items={metricsInHistograms.map(metric => ({ metric, ...binsFromFootprint(
$footprintsQuery.data.footprints.nodehours, $footprintsQuery.data.footprints.nodehours,
$footprintsQuery.data.footprints.metrics.find(f => f.metric == metric).data, numBins) }))} $footprintsQuery.data.footprints.metrics.find(f => f.metric == metric).data, numBins) }))}
itemsPerRow={ccconfig.plot_view_plotsPerRow}> itemsPerRow={ccconfig.plot_view_plotsPerRow}>
<h4>Average Distribution of '{item.metric}'</h4>
<h4>Average Distribution of '{item.metric}'</h4>
<Histogram <Histogram
width={width} height={250} width={width} height={250}
min={item.min} max={item.max} min={item.min} max={item.max}

View File

@ -102,7 +102,7 @@
data: grouped.find((group) => data: grouped.find((group) =>
group[0].name == metric group[0].name == metric
), ),
disabled: checkMetricDisabled(metric, $initq.data.job.cluster, $initq.data.job.subCluster) disabled: checkMetricDisabled(metric, $initq.data.job.cluster, $initq.data.job.subCluster)
})) }))
</script> </script>
@ -140,7 +140,7 @@
width={polarPlotSize} height={polarPlotSize} width={polarPlotSize} height={polarPlotSize}
metrics={ccconfig[`job_view_polarPlotMetrics:${$initq.data.job.cluster}`] || ccconfig[`job_view_polarPlotMetrics`]} metrics={ccconfig[`job_view_polarPlotMetrics:${$initq.data.job.cluster}`] || ccconfig[`job_view_polarPlotMetrics`]}
cluster={$initq.data.job.cluster} cluster={$initq.data.job.cluster}
jobMetrics={$jobMetrics.data.jobMetrics} /> jobMetrics={$jobMetrics.data.jobMetrics}/>
</Col> </Col>
<Col> <Col>
<Roofline <Roofline
@ -190,6 +190,7 @@
<PlotTable <PlotTable
let:item let:item
let:width let:width
renderFor="job"
items={orderAndMap(groupByScope($jobMetrics.data.jobMetrics), selectedMetrics)} items={orderAndMap(groupByScope($jobMetrics.data.jobMetrics), selectedMetrics)}
itemsPerRow={ccconfig.plot_view_plotsPerRow}> itemsPerRow={ccconfig.plot_view_plotsPerRow}>
{#if item.data} {#if item.data}
@ -203,7 +204,7 @@
width={width} width={width}
isShared={($initq.data.job.exclusive != 1)}/> isShared={($initq.data.job.exclusive != 1)}/>
{:else} {:else}
<Card body color="warning">No data for <code>{item.metric}</code></Card> <Card body color="warning">No dataset returned for <code>{item.metric}</code></Card>
{/if} {/if}
</PlotTable> </PlotTable>
{/if} {/if}

View File

@ -24,6 +24,16 @@
export let type; export let type;
export let filterPresets; export let filterPresets;
// By default, look at the jobs of the last 30 days:
if (filterPresets?.startTime == null) {
if (filterPresets == null)
filterPresets = {}
const lastMonth = (new Date(Date.now() - (30*24*60*60*1000))).toISOString()
const now = (new Date(Date.now())).toISOString()
filterPresets.startTime = { from: lastMonth, to: now, text: 'Last 30 Days', url: 'last30d' }
}
console.assert( console.assert(
type == "USER" || type == "PROJECT", type == "USER" || type == "PROJECT",
"Invalid list type provided!" "Invalid list type provided!"

View File

@ -1,5 +1,5 @@
<script> <script>
import { init } from './utils.js' import { init, checkMetricDisabled } from './utils.js'
import { Row, Col, InputGroup, InputGroupText, Icon, Spinner, Card } from 'sveltestrap' import { Row, Col, InputGroup, InputGroupText, Icon, Spinner, Card } from 'sveltestrap'
import { queryStore, gql, getContextClient } from '@urql/svelte' import { queryStore, gql, getContextClient } from '@urql/svelte'
import TimeSelection from './filters/TimeSelection.svelte' import TimeSelection from './filters/TimeSelection.svelte'
@ -100,13 +100,23 @@
<PlotTable <PlotTable
let:item let:item
let:width let:width
renderFor="node"
itemsPerRow={ccconfig.plot_view_plotsPerRow} itemsPerRow={ccconfig.plot_view_plotsPerRow}
items={$nodesQuery.data.nodeMetrics[0].metrics.sort((a, b) => a.name.localeCompare(b.name))}> items={$nodesQuery.data.nodeMetrics[0].metrics
.map(m => ({ ...m, disabled: checkMetricDisabled(m.name, cluster, $nodesQuery.data.nodeMetrics[0].subCluster)}))
.sort((a, b) => a.name.localeCompare(b.name))}>
<h4 style="text-align: center; padding-top:15px;">{item.name} {metricUnits[item.name]}</h4> <h4 style="text-align: center; padding-top:15px;">{item.name} {metricUnits[item.name]}</h4>
<MetricPlot {#if item.disabled === false && item.metric}
width={width} height={300} metric={item.name} timestep={item.metric.timestep} <MetricPlot
cluster={clusters.find(c => c.name == cluster)} subCluster={$nodesQuery.data.nodeMetrics[0].subCluster} width={width} height={300} metric={item.name} timestep={item.metric.timestep}
series={item.metric.series} /> cluster={clusters.find(c => c.name == cluster)} subCluster={$nodesQuery.data.nodeMetrics[0].subCluster}
series={item.metric.series} />
{:else if item.disabled === true && item.metric}
<Card style="margin-left: 2rem;margin-right: 2rem;" body color="info">Metric disabled for subcluster <code>{item.name}:{$nodesQuery.data.nodeMetrics[0].subCluster}</code></Card>
{:else}
<Card style="margin-left: 2rem;margin-right: 2rem;" body color="warning">No dataset returned for <code>{item.name}</code></Card>
{/if}
</PlotTable> </PlotTable>
{/if} {/if}
</Col> </Col>

View File

@ -10,7 +10,9 @@
export let itemsPerRow export let itemsPerRow
export let items export let items
export let padding = 10 export let padding = 10
export let renderFor
let rows = []
let tableWidth = 0 let tableWidth = 0
const isPlaceholder = x => x._is_placeholder === true const isPlaceholder = x => x._is_placeholder === true
@ -30,8 +32,13 @@
return rows return rows
} }
// Analysis Implements PlotTable: Disable flag can not be present, add to row if not defined explicitly (Helps with systems view also)
$: rows = tile(items.filter(item => (item.disabled !== null && item.disabled === false)), itemsPerRow) $: if (renderFor === 'job') {
rows = tile(items.filter(item => item.disabled === false), itemsPerRow)
} else {
rows = tile(items, itemsPerRow)
}
$: plotWidth = (tableWidth / itemsPerRow) - (padding * itemsPerRow) $: plotWidth = (tableWidth / itemsPerRow) - (padding * itemsPerRow)
</script> </script>

View File

@ -1,5 +1,5 @@
<script> <script>
import { init } from './utils.js' import { init, checkMetricDisabled } from './utils.js'
import { Row, Col, Input, InputGroup, InputGroupText, Icon, Spinner, Card } from 'sveltestrap' import { Row, Col, Input, InputGroup, InputGroupText, Icon, Spinner, Card } from 'sveltestrap'
import { queryStore, gql, getContextClient } from '@urql/svelte' import { queryStore, gql, getContextClient } from '@urql/svelte'
import TimeSelection from './filters/TimeSelection.svelte' import TimeSelection from './filters/TimeSelection.svelte'
@ -114,25 +114,21 @@
<PlotTable <PlotTable
let:item let:item
let:width let:width
renderFor="systems"
itemsPerRow={ccconfig.plot_view_plotsPerRow} itemsPerRow={ccconfig.plot_view_plotsPerRow}
items={$nodesQuery.data.nodeMetrics items={$nodesQuery.data.nodeMetrics
.filter(h => h.host.includes(hostnameFilter) && h.metrics.some(m => m.name == selectedMetric && m.scope == 'node')) .filter(h => h.host.includes(hostnameFilter) && h.metrics.some(m => m.name == selectedMetric && m.scope == 'node'))
.map(function (h) { .map(h => ({
let thisConfig = metricConfig(cluster, selectedMetric) host: h.host,
let thisSCIndex = thisConfig.subClusters.findIndex(sc => sc.name == h.subCluster) subCluster: h.subCluster,
// Metric remove == true data: h.metrics.find(m => m.name == selectedMetric && m.scope == 'node'),
if (thisSCIndex >= 0) { disabled: checkMetricDisabled(selectedMetric, cluster, h.subCluster)
if (thisConfig.subClusters[thisSCIndex].remove == true) { }))
return { host: h.host, subCluster: h.subCluster, data: null, removed: true } .sort((a, b) => a.host.localeCompare(b.host))
} }>
}
// Else
return { host: h.host, subCluster: h.subCluster, data: h.metrics.find(m => m.name == selectedMetric && m.scope == 'node'), removed: false }
})
.sort((a, b) => a.host.localeCompare(b.host))}>
<h4 style="width: 100%; text-align: center;"><a style="display: block;padding-top: 15px;" href="/monitoring/node/{cluster}/{item.host}">{item.host} ({item.subCluster})</a></h4> <h4 style="width: 100%; text-align: center;"><a style="display: block;padding-top: 15px;" href="/monitoring/node/{cluster}/{item.host}">{item.host} ({item.subCluster})</a></h4>
{#if item.removed == false && item.data != null} {#if item.disabled === false && item.data}
<MetricPlot <MetricPlot
width={width} width={width}
height={plotHeight} height={plotHeight}
@ -141,10 +137,10 @@
metric={item.data.name} metric={item.data.name}
cluster={clusters.find(c => c.name == cluster)} cluster={clusters.find(c => c.name == cluster)}
subCluster={item.subCluster} /> subCluster={item.subCluster} />
{:else if item.removed == true && item.data == null} {:else if item.disabled === true && item.data}
<Card body color="info">Metric '{ selectedMetric }' disabled for subcluster '{ item.subCluster }'</Card> <Card style="margin-left: 2rem;margin-right: 2rem;" body color="info">Metric disabled for subcluster <code>{selectedMetric}:{item.subCluster}</code></Card>
{:else} {:else}
<Card body color="warning">Missing Full Dataset</Card> <Card style="margin-left: 2rem;margin-right: 2rem;" body color="warning">No dataset returned for <code>{selectedMetric}</code></Card>
{/if} {/if}
</PlotTable> </PlotTable>
{/if} {/if}

View File

@ -35,17 +35,17 @@
projectMatch: filterPresets.projectMatch || 'contains', projectMatch: filterPresets.projectMatch || 'contains',
userMatch: filterPresets.userMatch || 'contains', userMatch: filterPresets.userMatch || 'contains',
cluster: filterPresets.cluster || null, cluster: filterPresets.cluster || null,
partition: filterPresets.partition || null, partition: filterPresets.partition || null,
states: filterPresets.states || filterPresets.state ? [filterPresets.state].flat() : allJobStates, states: filterPresets.states || filterPresets.state ? [filterPresets.state].flat() : allJobStates,
startTime: filterPresets.startTime || { from: null, to: null }, startTime: filterPresets.startTime || { from: null, to: null },
tags: filterPresets.tags || [], tags: filterPresets.tags || [],
duration: filterPresets.duration || { from: null, to: null }, duration: filterPresets.duration || { from: null, to: null },
jobId: filterPresets.jobId || '', jobId: filterPresets.jobId || '',
arrayJobId: filterPresets.arrayJobId || null, arrayJobId: filterPresets.arrayJobId || null,
user: filterPresets.user || '', user: filterPresets.user || '',
project: filterPresets.project || '', project: filterPresets.project || '',
jobName: filterPresets.jobName || '', jobName: filterPresets.jobName || '',
numNodes: filterPresets.numNodes || { from: null, to: null }, numNodes: filterPresets.numNodes || { from: null, to: null },
numHWThreads: filterPresets.numHWThreads || { from: null, to: null }, numHWThreads: filterPresets.numHWThreads || { from: null, to: null },
@ -120,7 +120,11 @@
for (let state of filters.states) for (let state of filters.states)
opts.push(`state=${state}`) opts.push(`state=${state}`)
if (filters.startTime.from && filters.startTime.to) if (filters.startTime.from && filters.startTime.to)
opts.push(`startTime=${dateToUnixEpoch(filters.startTime.from)}-${dateToUnixEpoch(filters.startTime.to)}`) // if (filters.startTime.url) {
// opts.push(`startTime=${filters.startTime.url}`)
// } else {
opts.push(`startTime=${dateToUnixEpoch(filters.startTime.from)}-${dateToUnixEpoch(filters.startTime.to)}`)
// }
for (let tag of filters.tags) for (let tag of filters.tags)
opts.push(`tag=${tag}`) opts.push(`tag=${tag}`)
if (filters.duration.from && filters.duration.to) if (filters.duration.from && filters.duration.to)
@ -193,16 +197,18 @@
<DropdownItem divider/> <DropdownItem divider/>
<DropdownItem disabled>Start Time Qick Selection</DropdownItem> <DropdownItem disabled>Start Time Qick Selection</DropdownItem>
{#each [ {#each [
{ text: 'Last 6hrs', seconds: 6*60*60 }, { text: 'Last 6hrs', url: 'last6h', seconds: 6*60*60 },
{ text: 'Last 12hrs', seconds: 12*60*60 }, // { text: 'Last 12hrs', seconds: 12*60*60 },
{ text: 'Last 24hrs', seconds: 24*60*60 }, { text: 'Last 24hrs', url: 'last24h', seconds: 24*60*60 },
{ text: 'Last 48hrs', seconds: 48*60*60 }, // { text: 'Last 48hrs', seconds: 48*60*60 },
{ text: 'Last 7 days', seconds: 7*24*60*60 }, { text: 'Last 7 days', url: 'last7d', seconds: 7*24*60*60 },
{ text: 'Last 30 days', seconds: 30*24*60*60 } { text: 'Last 30 days', url: 'last30d', seconds: 30*24*60*60 }
] as {text, seconds}} ] as {text, url, seconds}}
<DropdownItem on:click={() => { <DropdownItem on:click={() => {
filters.startTime.from = (new Date(Date.now() - seconds * 1000)).toISOString() filters.startTime.from = (new Date(Date.now() - seconds * 1000)).toISOString()
filters.startTime.to = (new Date(Date.now())).toISOString() filters.startTime.to = (new Date(Date.now())).toISOString()
filters.startTime.text = text,
filters.startTime.url = url
update() update()
}}> }}>
<Icon name="calendar-range"/> {text} <Icon name="calendar-range"/> {text}
@ -212,27 +218,6 @@
</DropdownMenu> </DropdownMenu>
</ButtonDropdown> </ButtonDropdown>
</Col> </Col>
<!-- {#if startTimeQuickSelect}
<Col xs="auto">
<TimeSelection customEnabled={false} anyEnabled={true}
from={filters.startTime.from ? new Date(filters.startTime.from) : null}
to={filters.startTime.to ? new Date(filters.startTime.to) : null}
options={{
'Last 6hrs': 6*60*60,
'Last 12hrs': 12*60*60,
'Last 24hrs': 24*60*60,
'Last 48hrs': 48*60*60,
'Last 7 days': 7*24*60*60,
'Last 30 days': 30*24*60*60}}
on:change={({ detail: { from, to } }) => {
filters.startTime.from = from?.toISOString()
filters.startTime.to = to?.toISOString()
// console.log(filters.startTime)
update()
}}
/>
</Col>
{/if} -->
<Col xs="auto"> <Col xs="auto">
{#if filters.cluster} {#if filters.cluster}
<Info icon="cpu" on:click={() => (isClusterOpen = true)}> <Info icon="cpu" on:click={() => (isClusterOpen = true)}>
@ -251,8 +236,12 @@
{#if filters.startTime.from || filters.startTime.to} {#if filters.startTime.from || filters.startTime.to}
<Info icon="calendar-range" on:click={() => (isStartTimeOpen = true)}> <Info icon="calendar-range" on:click={() => (isStartTimeOpen = true)}>
{new Date(filters.startTime.from).toLocaleString()} - {new Date(filters.startTime.to).toLocaleString()} {#if filters.startTime.text}
</Info> {filters.startTime.text}
{:else}
{new Date(filters.startTime.from).toLocaleString()} - {new Date(filters.startTime.to).toLocaleString()}
{/if}
</Info>
{/if} {/if}
{#if filters.duration.from || filters.duration.to} {#if filters.duration.from || filters.duration.to}
@ -307,7 +296,11 @@
bind:isOpen={isStartTimeOpen} bind:isOpen={isStartTimeOpen}
bind:from={filters.startTime.from} bind:from={filters.startTime.from}
bind:to={filters.startTime.to} bind:to={filters.startTime.to}
on:update={() => update()} /> on:update={() => {
delete filters.startTime['text']
delete filters.startTime['url']
update()
}} />
<Duration <Duration
bind:isOpen={isDurationOpen} bind:isOpen={isDurationOpen}

View File

@ -14,7 +14,7 @@
import { Card, Spinner } from "sveltestrap"; import { Card, Spinner } from "sveltestrap";
import MetricPlot from "../plots/MetricPlot.svelte"; import MetricPlot from "../plots/MetricPlot.svelte";
import JobInfo from "./JobInfo.svelte"; import JobInfo from "./JobInfo.svelte";
import { maxScope } from "../utils.js"; import { maxScope, checkMetricDisabled } from "../utils.js";
export let job; export let job;
export let metrics; export let metrics;
@ -85,56 +85,17 @@
jobMetrics[0] jobMetrics[0]
); );
const sortAndSelectScope = (jobMetrics) =>
metrics const sortAndSelectScope = (jobMetrics) => metrics
.map(function (name) { .map(name => jobMetrics.filter(jobMetric => jobMetric.name == name))
// Get MetricConf for this selected/requested metric .map(jobMetrics => ({ disabled: false, data: jobMetrics.length > 0 ? selectScope(jobMetrics) : null }))
let thisConfig = metricConfig(cluster, name); .map(jobMetric => {
let thisSCIndex = -1 if (jobMetric.data) {
if (thisConfig) { return { disabled: checkMetricDisabled(jobMetric.data.name, job.cluster, job.subCluster), data: jobMetric.data }
thisSCIndex = thisConfig.subClusters.findIndex( } else {
(sc) => sc.name == job.subCluster return jobMetric
); }
}; })
// Check if Subcluster has MetricConf: If not found (index == -1), no further remove flag check required
if (thisSCIndex >= 0) {
// SubCluster Config present: Check if remove flag is set
if (thisConfig.subClusters[thisSCIndex].remove == true) {
// Return null data and informational flag
return { removed: true, data: null };
} else {
// load and return metric, if data available
let thisMetric = jobMetrics.filter(
(jobMetric) => jobMetric.name == name
); // Returns Array
if (thisMetric.length > 0) {
return { removed: false, data: thisMetric };
} else {
return { removed: false, data: null };
}
}
} else {
// No specific subCluster config: 'remove' flag not set, deemed false -> load and return metric, if data available
let thisMetric = jobMetrics.filter(
(jobMetric) => jobMetric.name == name
); // Returns Array
if (thisMetric.length > 0) {
return { removed: false, data: thisMetric };
} else {
return { removed: false, data: null };
}
}
})
.map(function (jobMetrics) {
if (jobMetrics.data != null && jobMetrics.data.length > 0) {
return {
removed: jobMetrics.removed,
data: selectScope(jobMetrics.data),
};
} else {
return jobMetrics;
}
});
if (job.monitoringStatus) refresh(); if (job.monitoringStatus) refresh();
</script> </script>
@ -163,7 +124,7 @@
{#each sortAndSelectScope($metricsQuery.data.jobMetrics) as metric, i (metric || i)} {#each sortAndSelectScope($metricsQuery.data.jobMetrics) as metric, i (metric || i)}
<td> <td>
<!-- Subluster Metricconfig remove keyword for jobtables (joblist main, user joblist, project joblist) to be used here as toplevel case--> <!-- Subluster Metricconfig remove keyword for jobtables (joblist main, user joblist, project joblist) to be used here as toplevel case-->
{#if metric.removed == false && metric.data != null} {#if metric.disabled == false && metric.data}
<MetricPlot <MetricPlot
width={plotWidth} width={plotWidth}
height={plotHeight} height={plotHeight}
@ -176,12 +137,10 @@
subCluster={job.subCluster} subCluster={job.subCluster}
isShared={(job.exclusive != 1)} isShared={(job.exclusive != 1)}
/> />
{:else if metric.removed == true && metric.data == null} {:else if metric.disabled == true && metric.data}
<Card body color="info" <Card body color="info">Metric disabled for subcluster <code>{metric.data.name}:{job.subCluster}</code></Card>
>Metric disabled for subcluster '{job.subCluster}'</Card
>
{:else} {:else}
<Card body color="warning">Missing Full Dataset</Card> <Card body color="warning">No dataset returned</Card>
{/if} {/if}
</td> </td>
{/each} {/each}

View File

@ -320,7 +320,7 @@
{#if series[0].data.length > 0} {#if series[0].data.length > 0}
<div bind:this={plotWrapper} class="cc-plot"></div> <div bind:this={plotWrapper} class="cc-plot"></div>
{:else} {:else}
<Card style="margin-left: 2rem;margin-right: 2rem;" body color="warning">Cannot render plot: No series data found for <code>{metric}</code></Card> <Card style="margin-left: 2rem;margin-right: 2rem;" body color="warning">Cannot render plot: No series data returned for <code>{metric}</code></Card>
{/if} {/if}
<style> <style>
.cc-plot { .cc-plot {