subcluster in metricSelect, add infobox to systems

- Default: Show Clusters next to Metrics
- New: If Cluster filter activem show subclusters instead (reactive)
- add note to analysis view
- systems view now with info boxes if metric is removed for subcluster
This commit is contained in:
Christoph Kluge 2023-03-31 17:18:16 +02:00
parent 68a839bf1c
commit adc1d94e3f
6 changed files with 77 additions and 22 deletions

View File

@ -269,7 +269,7 @@ func (r *queryResolver) NodeMetrics(ctx context.Context, cluster string, nodes [
for _, scopedMetric := range scopedMetrics { for _, scopedMetric := range scopedMetrics {
host.Metrics = append(host.Metrics, &model.JobMetricWithName{ host.Metrics = append(host.Metrics, &model.JobMetricWithName{
Name: metric, Name: metric,
Scope: schema.MetricScopeNode, // NodeMetrics allow fixed scope? Scope: schema.MetricScopeNode,
Metric: scopedMetric, Metric: scopedMetric,
}) })
} }

View File

@ -216,6 +216,7 @@
<Col> <Col>
<Card body> <Card body>
These histograms show the distribution of the averages of all jobs matching the filters. Each job/average is weighted by its node hours. These histograms show the distribution of the averages of all jobs matching the filters. Each job/average is weighted by its node hours.
Note that some metrics could be disabled for specific subclusters as per metriConfig and thus could affect shown average values.
</Card> </Card>
<br/> <br/>
</Col> </Col>
@ -247,6 +248,7 @@
<Col> <Col>
<Card body> <Card body>
Each circle represents one job. The size of a circle is proportional to its node hours. Darker circles mean multiple jobs have the same averages for the respective metrics. Each circle represents one job. The size of a circle is proportional to its node hours. Darker circles mean multiple jobs have the same averages for the respective metrics.
Note that some metrics could be disabled for specific subclusters as per metriConfig and thus could affect shown average values.
</Card> </Card>
<br/> <br/>
</Col> </Col>

View File

@ -15,11 +15,15 @@
export let filterPresets = {} export let filterPresets = {}
let filters, jobList, matchedJobs = null let filters = []
let jobList, matchedJobs = null
let sorting = { field: 'startTime', order: 'DESC' }, isSortingOpen = false, isMetricsSelectionOpen = false let sorting = { field: 'startTime', order: 'DESC' }, isSortingOpen = false, isMetricsSelectionOpen = false
let metrics = filterPresets.cluster let metrics = filterPresets.cluster
? ccconfig[`plot_list_selectedMetrics:${filterPresets.cluster}`] || ccconfig.plot_list_selectedMetrics ? ccconfig[`plot_list_selectedMetrics:${filterPresets.cluster}`] || ccconfig.plot_list_selectedMetrics
: ccconfig.plot_list_selectedMetrics : ccconfig.plot_list_selectedMetrics
let selectedCluster = filterPresets?.cluster ? filterPresets.cluster : null
$: selectedCluster = filters[0]?.cluster ? filters[0].cluster.eq : null
// The filterPresets are handled by the Filters component, // The filterPresets are handled by the Filters component,
// so we need to wait for it to be ready before we can start a query. // so we need to wait for it to be ready before we can start a query.
@ -56,7 +60,10 @@
<Filters <Filters
filterPresets={filterPresets} filterPresets={filterPresets}
bind:this={filters} bind:this={filters}
on:update={({ detail }) => jobList.update(detail.filters)} /> on:update={({ detail }) => {
filters = detail.filters
jobList.update(detail.filters)}
} />
</Col> </Col>
<Col xs="3" style="margin-left: auto;"> <Col xs="3" style="margin-left: auto;">
@ -82,7 +89,7 @@
bind:isOpen={isSortingOpen} /> bind:isOpen={isSortingOpen} />
<MetricSelection <MetricSelection
cluster={filterPresets.cluster} bind:cluster={selectedCluster}
configName="plot_list_selectedMetrics" configName="plot_list_selectedMetrics"
bind:metrics={metrics} bind:metrics={metrics}
bind:isOpen={isMetricsSelectionOpen} /> bind:isOpen={isMetricsSelectionOpen} />

View File

@ -95,7 +95,7 @@
<Modal isOpen={isOpen} toggle={() => (isOpen = !isOpen)}> <Modal isOpen={isOpen} toggle={() => (isOpen = !isOpen)}>
<ModalHeader> <ModalHeader>
Configure columns Configure columns (Metric availability shown)
</ModalHeader> </ModalHeader>
<ModalBody> <ModalBody>
<ListGroup> <ListGroup>
@ -113,9 +113,26 @@
{/if} {/if}
{metric} {metric}
<span style="float: right;"> <span style="float: right;">
{cluster == null ? clusters {cluster == null ?
clusters // No single cluster specified: List Clusters with Metric
.filter(cluster => cluster.metricConfig.find(m => m.name == metric) != null) .filter(cluster => cluster.metricConfig.find(m => m.name == metric) != null)
.map(cluster => cluster.name).join(', ') : ''} .map(cluster => cluster.name).join(', ') :
clusters // Single cluster requested: List Subclusters with do not have metric remove flag
.filter(cluster => cluster.metricConfig.find(m => m.name == metric) != null)
.map(function(cluster) {
let scNames = cluster.subClusters.map(sc => sc.name)
scNames.forEach(function(scName){
let met = cluster.metricConfig.find(m => m.name == metric)
let msc = met.subClusters.find(msc => msc.name == scName)
if (msc != null) {
if (msc.remove == true) {
scNames = scNames.filter(scn => scn != msc.name)
}
}
})
return scNames
})
.join(', ')}
</span> </span>
</li> </li>
{/each} {/each}

View File

@ -21,6 +21,7 @@
const clusters = getContext('clusters') const clusters = getContext('clusters')
const ccconfig = getContext('cc-config') const ccconfig = getContext('cc-config')
const metricConfig = getContext('metrics')
let plotHeight = 300 let plotHeight = 300
let hostnameFilter = '' let hostnameFilter = ''
@ -112,10 +113,22 @@
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(h => ({ host: h.host, subCluster: h.subCluster, data: h.metrics.find(m => m.name == selectedMetric && m.scope == 'node') })) .map(function (h) {
let thisConfig = metricConfig(cluster, selectedMetric)
let thisSCIndex = thisConfig.subClusters.findIndex(sc => sc.name == h.subCluster)
// Metric remove == true
if (thisSCIndex >= 0) {
if (thisConfig.subClusters[thisSCIndex].remove == true) {
return { host: h.host, subCluster: h.subCluster, data: null, removed: true }
}
}
// 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))}> .sort((a, b) => a.host.localeCompare(b.host))}>
<h4 style="width: 100%; text-align: center;"><a href="/monitoring/node/{cluster}/{item.host}">{item.host} ({item.subCluster})</a></h4> <h4 style="width: 100%; text-align: center;"><a href="/monitoring/node/{cluster}/{item.host}">{item.host} ({item.subCluster})</a></h4>
{#if item.removed == false && item.data != null}
<MetricPlot <MetricPlot
width={width} width={width}
height={plotHeight} height={plotHeight}
@ -124,6 +137,11 @@
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}
<Card body color="info">Metric '{ selectedMetric }' disabled for subcluster '{ item.subCluster }'</Card>
{:else}
<Card body color="warning">Missing Data</Card>
{/if}
</PlotTable> </PlotTable>
{/if} {/if}
</Col> </Col>

View File

@ -18,10 +18,12 @@
export let user export let user
export let filterPresets export let filterPresets
let filters, jobList let filters = []
let jobList
let sorting = { field: 'startTime', order: 'DESC' }, isSortingOpen = false let sorting = { field: 'startTime', order: 'DESC' }, isSortingOpen = false
let metrics = ccconfig.plot_list_selectedMetrics, isMetricsSelectionOpen = false let metrics = ccconfig.plot_list_selectedMetrics, isMetricsSelectionOpen = false
let w1, w2, histogramHeight = 250 let w1, w2, histogramHeight = 250
let selectedCluster = filterPresets?.cluster ? filterPresets.cluster : null
const stats = operationStore(` const stats = operationStore(`
query($filter: [JobFilter!]!) { query($filter: [JobFilter!]!) {
@ -40,6 +42,12 @@
pause: true pause: true
}) })
// filters[filters.findIndex(filter => filter.cluster != null)] ?
// filters[filters.findIndex(filter => filter.cluster != null)].cluster.eq :
// null
// Cluster filter has to be alwas @ first index, above will throw error
$: selectedCluster = filters[0]?.cluster ? filters[0].cluster.eq : null
query(stats) query(stats)
onMount(() => filters.update()) onMount(() => filters.update())
@ -75,11 +83,12 @@
startTimeQuickSelect={true} startTimeQuickSelect={true}
bind:this={filters} bind:this={filters}
on:update={({ detail }) => { on:update={({ detail }) => {
let filters = [...detail.filters, { user: { eq: user.username } }] let jobFilters = [...detail.filters, { user: { eq: user.username } }]
$stats.variables = { filter: filters } $stats.variables = { filter: jobFilters }
$stats.context.pause = false $stats.context.pause = false
$stats.reexecute() $stats.reexecute()
jobList.update(filters) filters = jobFilters
jobList.update(jobFilters)
}} /> }} />
</Col> </Col>
<Col xs="auto" style="margin-left: auto;"> <Col xs="auto" style="margin-left: auto;">
@ -171,6 +180,8 @@
bind:sorting={sorting} bind:sorting={sorting}
bind:isOpen={isSortingOpen} /> bind:isOpen={isSortingOpen} />
<MetricSelection configName="plot_list_selectedMetrics" <MetricSelection
bind:cluster={selectedCluster}
configName="plot_list_selectedMetrics"
bind:metrics={metrics} bind:metrics={metrics}
bind:isOpen={isMetricsSelectionOpen} /> bind:isOpen={isMetricsSelectionOpen} />