mirror of
https://github.com/ClusterCockpit/cc-backend
synced 2024-12-25 21:09:05 +01:00
Adapt svelte to new schema, add removed metric box
- Moved 'scope' field to parent jobMetric - Implemented unit { prefix, base } where necessary - SubCluster Metric Config 'remove' option implemented in Joblists
This commit is contained in:
parent
6b84e65d88
commit
0c1b66aad9
@ -269,6 +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?
|
||||||
Metric: scopedMetric,
|
Metric: scopedMetric,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -282,7 +283,16 @@ func (r *queryResolver) NodeMetrics(ctx context.Context, cluster string, nodes [
|
|||||||
|
|
||||||
// NumberOfNodes is the resolver for the numberOfNodes field.
|
// NumberOfNodes is the resolver for the numberOfNodes field.
|
||||||
func (r *subClusterResolver) NumberOfNodes(ctx context.Context, obj *schema.SubCluster) (int, error) {
|
func (r *subClusterResolver) NumberOfNodes(ctx context.Context, obj *schema.SubCluster) (int, error) {
|
||||||
panic(fmt.Errorf("not implemented: NumberOfNodes - numberOfNodes"))
|
nodeList, err := archive.ParseNodeList(obj.Nodes)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
// log.Debugf(">>>> See raw list definition here: %v", nodeList)
|
||||||
|
stringList := nodeList.PrintList()
|
||||||
|
// log.Debugf(">>>> See parsed list here: %v", stringList)
|
||||||
|
numOfNodes := len(stringList)
|
||||||
|
// log.Debugf(">>>> See numOfNodes here: %v", len(stringList))
|
||||||
|
return numOfNodes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cluster returns generated.ClusterResolver implementation.
|
// Cluster returns generated.ClusterResolver implementation.
|
||||||
|
@ -324,7 +324,10 @@ func ArchiveJob(job *schema.Job, ctx context.Context) (*schema.JobMeta, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
jobMeta.Statistics[metric] = schema.JobStatistics{
|
jobMeta.Statistics[metric] = schema.JobStatistics{
|
||||||
Unit: archive.GetMetricConfig(job.Cluster, metric).Unit,
|
Unit: schema.Unit{
|
||||||
|
Prefix: archive.GetMetricConfig(job.Cluster, metric).Unit.Prefix,
|
||||||
|
Base: archive.GetMetricConfig(job.Cluster, metric).Unit.Base,
|
||||||
|
},
|
||||||
Avg: avg / float64(job.NumNodes),
|
Avg: avg / float64(job.NumNodes),
|
||||||
Min: min,
|
Min: min,
|
||||||
Max: max,
|
Max: max,
|
||||||
|
@ -14,6 +14,8 @@ import (
|
|||||||
|
|
||||||
type NodeList [][]interface {
|
type NodeList [][]interface {
|
||||||
consume(input string) (next string, ok bool)
|
consume(input string) (next string, ok bool)
|
||||||
|
limits() []map[string]int64
|
||||||
|
prefix() string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (nl *NodeList) Contains(name string) bool {
|
func (nl *NodeList) Contains(name string) bool {
|
||||||
@ -35,6 +37,29 @@ func (nl *NodeList) Contains(name string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (nl *NodeList) PrintList() []string {
|
||||||
|
var out []string
|
||||||
|
for _, term := range *nl {
|
||||||
|
// log.Debugf("Term: %v", term)
|
||||||
|
|
||||||
|
prefix := term[0].prefix()
|
||||||
|
// log.Debugf("Prefix as String: %s", prefix)
|
||||||
|
|
||||||
|
limitArr := term[1].limits()
|
||||||
|
for _, inner := range limitArr {
|
||||||
|
for i := inner["start"]; i < inner["end"]+1; i++ {
|
||||||
|
node := fmt.Sprintf("%s%02d", prefix, i)
|
||||||
|
out = append(out, node)
|
||||||
|
}
|
||||||
|
// log.Debugf("Inner Map @ %d: %#v", indx, inner)
|
||||||
|
// log.Debugf("Start: %#v", inner["start"])
|
||||||
|
// log.Debugf("End: %#v", inner["end"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// log.Debugf("Node List as Strings: %#v", out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
type NLExprString string
|
type NLExprString string
|
||||||
|
|
||||||
func (nle NLExprString) consume(input string) (next string, ok bool) {
|
func (nle NLExprString) consume(input string) (next string, ok bool) {
|
||||||
@ -45,6 +70,16 @@ func (nle NLExprString) consume(input string) (next string, ok bool) {
|
|||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (nle NLExprString) limits() []map[string]int64 {
|
||||||
|
// Null implementation to fullfill interface requirement
|
||||||
|
l := make([]map[string]int64, 0)
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nle NLExprString) prefix() string {
|
||||||
|
return string(nle)
|
||||||
|
}
|
||||||
|
|
||||||
type NLExprIntRanges []NLExprIntRange
|
type NLExprIntRanges []NLExprIntRange
|
||||||
|
|
||||||
func (nles NLExprIntRanges) consume(input string) (next string, ok bool) {
|
func (nles NLExprIntRanges) consume(input string) (next string, ok bool) {
|
||||||
@ -56,6 +91,22 @@ func (nles NLExprIntRanges) consume(input string) (next string, ok bool) {
|
|||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (nles NLExprIntRanges) limits() []map[string]int64 {
|
||||||
|
l := make([]map[string]int64, 0)
|
||||||
|
for _, nle := range nles {
|
||||||
|
inner := nle.limits()
|
||||||
|
// log.Debugf("limits @ nles: %#v", inner)
|
||||||
|
l = append(l, inner[0])
|
||||||
|
}
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nles NLExprIntRanges) prefix() string {
|
||||||
|
// Null implementation to fullfill interface requirement
|
||||||
|
var s string
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
type NLExprIntRange struct {
|
type NLExprIntRange struct {
|
||||||
start, end int64
|
start, end int64
|
||||||
zeroPadded bool
|
zeroPadded bool
|
||||||
@ -89,6 +140,22 @@ func (nle NLExprIntRange) consume(input string) (next string, ok bool) {
|
|||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (nle NLExprIntRange) limits() []map[string]int64 {
|
||||||
|
l := make([]map[string]int64, 0)
|
||||||
|
m := make(map[string]int64)
|
||||||
|
m["start"] = nle.start
|
||||||
|
m["end"] = nle.end
|
||||||
|
l = append(l, m)
|
||||||
|
// log.Debugf("limits @ nle: %#v", l)
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nles NLExprIntRange) prefix() string {
|
||||||
|
// Null implementation to fullfill interface requirement
|
||||||
|
var s string
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
func ParseNodeList(raw string) (NodeList, error) {
|
func ParseNodeList(raw string) (NodeList, error) {
|
||||||
isLetter := func(r byte) bool { return ('a' <= r && r <= 'z') || ('A' <= r && r <= 'Z') }
|
isLetter := func(r byte) bool { return ('a' <= r && r <= 'z') || ('A' <= r && r <= 'Z') }
|
||||||
isDigit := func(r byte) bool { return '0' <= r && r <= '9' }
|
isDigit := func(r byte) bool { return '0' <= r && r <= '9' }
|
||||||
@ -116,6 +183,8 @@ func ParseNodeList(raw string) (NodeList, error) {
|
|||||||
for _, rawterm := range rawterms {
|
for _, rawterm := range rawterms {
|
||||||
exprs := []interface {
|
exprs := []interface {
|
||||||
consume(input string) (next string, ok bool)
|
consume(input string) (next string, ok bool)
|
||||||
|
limits() []map[string]int64
|
||||||
|
prefix() string
|
||||||
}{}
|
}{}
|
||||||
for i := 0; i < len(rawterm); i++ {
|
for i := 0; i < len(rawterm); i++ {
|
||||||
c := rawterm[i]
|
c := rawterm[i]
|
||||||
|
@ -30,8 +30,8 @@
|
|||||||
let rooflineMaxY
|
let rooflineMaxY
|
||||||
let colWidth
|
let colWidth
|
||||||
let numBins = 50
|
let numBins = 50
|
||||||
const ccconfig = getContext('cc-config'),
|
const ccconfig = getContext('cc-config')
|
||||||
metricConfig = getContext('metrics')
|
const metricConfig = getContext('metrics')
|
||||||
|
|
||||||
let metricsInHistograms = ccconfig.analysis_view_histogramMetrics,
|
let metricsInHistograms = ccconfig.analysis_view_histogramMetrics,
|
||||||
metricsInScatterplots = ccconfig.analysis_view_scatterPlotMetrics
|
metricsInScatterplots = ccconfig.analysis_view_scatterPlotMetrics
|
||||||
@ -161,24 +161,29 @@
|
|||||||
<Histogram
|
<Histogram
|
||||||
width={colWidth - 25} height={300 * 0.5}
|
width={colWidth - 25} height={300 * 0.5}
|
||||||
data={$statsQuery.data.topUsers.sort((a, b) => b.count - a.count).map(({ count }, idx) => ({ count, value: idx }))}
|
data={$statsQuery.data.topUsers.sort((a, b) => b.count - a.count).map(({ count }, idx) => ({ count, value: idx }))}
|
||||||
label={(x) => x < $statsQuery.data.topUsers.length ? $statsQuery.data.topUsers[Math.floor(x)].name : '0'} />
|
label={(x) => x < $statsQuery.data.topUsers.length ? $statsQuery.data.topUsers[Math.floor(x)].name : 'No Users'}
|
||||||
|
ylabel="Node Hours [h]"/>
|
||||||
{/key}
|
{/key}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-3">
|
<div class="col-3">
|
||||||
{#key $statsQuery.data.stats[0].histDuration}
|
{#key $statsQuery.data.stats[0].histDuration}
|
||||||
<h4>Walltime Distribution</h4>
|
<h4>Duration Distribution</h4>
|
||||||
<Histogram
|
<Histogram
|
||||||
width={colWidth - 25} height={300}
|
width={colWidth - 25}
|
||||||
data={$statsQuery.data.stats[0].histDuration} />
|
data={$statsQuery.data.stats[0].histDuration}
|
||||||
|
xlabel="Current Runtimes [h]"
|
||||||
|
ylabel="Number of Jobs"/>
|
||||||
{/key}
|
{/key}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-3">
|
<div class="col-3">
|
||||||
{#key $statsQuery.data.stats[0].histNumNodes}
|
{#key $statsQuery.data.stats[0].histNumNodes}
|
||||||
<h4>Number of Nodes Distribution</h4>
|
<h4>Number of Nodes Distribution</h4>
|
||||||
<Histogram
|
<Histogram
|
||||||
width={colWidth - 25} height={300}
|
width={colWidth - 25}
|
||||||
data={$statsQuery.data.stats[0].histNumNodes} />
|
data={$statsQuery.data.stats[0].histNumNodes}
|
||||||
|
xlabel="Allocated Nodes [#]"
|
||||||
|
ylabel="Number of Jobs" />
|
||||||
{/key}
|
{/key}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-3">
|
<div class="col-3">
|
||||||
@ -189,7 +194,7 @@
|
|||||||
{:else if $rooflineQuery.data && cluster}
|
{:else if $rooflineQuery.data && cluster}
|
||||||
{#key $rooflineQuery.data}
|
{#key $rooflineQuery.data}
|
||||||
<Roofline
|
<Roofline
|
||||||
width={colWidth - 25} height={300}
|
width={colWidth - 25}
|
||||||
tiles={$rooflineQuery.data.rooflineHeatmap}
|
tiles={$rooflineQuery.data.rooflineHeatmap}
|
||||||
cluster={cluster.subClusters.length == 1 ? cluster.subClusters[0] : null}
|
cluster={cluster.subClusters.length == 1 ? cluster.subClusters[0] : null}
|
||||||
maxY={rooflineMaxY} />
|
maxY={rooflineMaxY} />
|
||||||
@ -224,12 +229,16 @@
|
|||||||
$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>{item.metric} [{metricConfig(cluster.name, item.metric)?.unit}]</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}
|
||||||
data={item.bins} label={item.label} />
|
data={item.bins}
|
||||||
|
label={item.label}
|
||||||
|
xlabel={`${item.metric} Average [${(metricConfig(cluster.name, item.metric)?.unit?.prefix ? metricConfig(cluster.name, item.metric)?.unit?.prefix : '') +
|
||||||
|
(metricConfig(cluster.name, item.metric)?.unit?.base ? metricConfig(cluster.name, item.metric)?.unit?.base : '')}]`}
|
||||||
|
ylabel="Node Hours [h]" />
|
||||||
</PlotTable>
|
</PlotTable>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
@ -254,12 +263,18 @@
|
|||||||
|
|
||||||
<ScatterPlot
|
<ScatterPlot
|
||||||
width={width} height={250} color={"rgba(0, 102, 204, 0.33)"}
|
width={width} height={250} color={"rgba(0, 102, 204, 0.33)"}
|
||||||
xLabel={`${item.m1} [${metricConfig(cluster.name, item.m1)?.unit}]`}
|
xLabel={`${item.m1} [${(metricConfig(cluster.name, item.m1)?.unit?.prefix ? metricConfig(cluster.name, item.m1)?.unit?.prefix : '') +
|
||||||
yLabel={`${item.m2} [${metricConfig(cluster.name, item.m2)?.unit}]`}
|
(metricConfig(cluster.name, item.m1)?.unit?.base ? metricConfig(cluster.name, item.m1)?.unit?.base : '')}]`}
|
||||||
|
yLabel={`${item.m2} [${(metricConfig(cluster.name, item.m2)?.unit?.prefix ? metricConfig(cluster.name, item.m2)?.unit?.prefix : '') +
|
||||||
|
(metricConfig(cluster.name, item.m2)?.unit?.base ? metricConfig(cluster.name, item.m2)?.unit?.base : '')}]`}
|
||||||
X={item.f1} Y={item.f2} S={$footprintsQuery.data.footprints.nodehours} />
|
X={item.f1} Y={item.f2} S={$footprintsQuery.data.footprints.nodehours} />
|
||||||
</PlotTable>
|
</PlotTable>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
h4 {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@ -81,7 +81,7 @@
|
|||||||
missingMetrics = metricNames.filter(metric => !metrics.some(jm => jm.name == metric))
|
missingMetrics = metricNames.filter(metric => !metrics.some(jm => jm.name == metric))
|
||||||
missingHosts = job.resources.map(({ hostname }) => ({
|
missingHosts = job.resources.map(({ hostname }) => ({
|
||||||
hostname: hostname,
|
hostname: hostname,
|
||||||
metrics: metricNames.filter(metric => !metrics.some(jm => jm.metric.scope == 'node' && jm.metric.series.some(series => series.hostname == hostname)))
|
metrics: metricNames.filter(metric => !metrics.some(jm => jm.scope == 'node' && jm.metric.series.some(series => series.hostname == hostname)))
|
||||||
})).filter(({ metrics }) => metrics.length > 0)
|
})).filter(({ metrics }) => metrics.length > 0)
|
||||||
somethingMissing = missingMetrics.length > 0 || missingHosts.length > 0
|
somethingMissing = missingMetrics.length > 0 || missingHosts.length > 0
|
||||||
}
|
}
|
||||||
@ -114,8 +114,8 @@
|
|||||||
cluster={clusters
|
cluster={clusters
|
||||||
.find(c => c.name == $initq.data.job.cluster).subClusters
|
.find(c => c.name == $initq.data.job.cluster).subClusters
|
||||||
.find(sc => sc.name == $initq.data.job.subCluster)}
|
.find(sc => sc.name == $initq.data.job.subCluster)}
|
||||||
flopsAny={$jobMetrics.data.jobMetrics.find(m => m.name == 'flops_any' && m.metric.scope == 'node').metric}
|
flopsAny={$jobMetrics.data.jobMetrics.find(m => m.name == 'flops_any' && m.scope == 'node').metric}
|
||||||
memBw={$jobMetrics.data.jobMetrics.find(m => m.name == 'mem_bw' && m.metric.scope == 'node').metric} />
|
memBw={$jobMetrics.data.jobMetrics.find(m => m.name == 'mem_bw' && m.scope == 'node').metric} />
|
||||||
</Col>
|
</Col>
|
||||||
{:else}
|
{:else}
|
||||||
<Col></Col>
|
<Col></Col>
|
||||||
@ -163,8 +163,9 @@
|
|||||||
bind:this={plots[item.metric]}
|
bind:this={plots[item.metric]}
|
||||||
on:more-loaded={({ detail }) => statsTable.moreLoaded(detail)}
|
on:more-loaded={({ detail }) => statsTable.moreLoaded(detail)}
|
||||||
job={$initq.data.job}
|
job={$initq.data.job}
|
||||||
metric={item.metric}
|
metricName={item.metric}
|
||||||
scopes={item.data.map(x => x.metric)}
|
rawData={item.data.map(x => x.metric)}
|
||||||
|
scopes={item.data.map(x => x.scope)}
|
||||||
width={width}/>
|
width={width}/>
|
||||||
{:else}
|
{:else}
|
||||||
<Card body color="warning">No data for <code>{item.metric}</code></Card>
|
<Card body color="warning">No data for <code>{item.metric}</code></Card>
|
||||||
|
@ -5,21 +5,36 @@
|
|||||||
import { fetchMetrics, minScope } from './utils'
|
import { fetchMetrics, minScope } from './utils'
|
||||||
|
|
||||||
export let job
|
export let job
|
||||||
export let metric
|
export let metricName
|
||||||
export let scopes
|
export let scopes
|
||||||
export let width
|
export let width
|
||||||
|
export let rawData
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
const cluster = getContext('clusters').find(cluster => cluster.name == job.cluster)
|
const cluster = getContext('clusters').find(cluster => cluster.name == job.cluster)
|
||||||
const subCluster = cluster.subClusters.find(subCluster => subCluster.name == job.subCluster)
|
const subCluster = cluster.subClusters.find(subCluster => subCluster.name == job.subCluster)
|
||||||
const metricConfig = cluster.metricConfig.find(metricConfig => metricConfig.name == metric)
|
const metricConfig = cluster.metricConfig.find(metricConfig => metricConfig.name == metricName)
|
||||||
|
|
||||||
let selectedScope = minScope(scopes.map(s => s.scope)), selectedHost = null, plot, fetching = false, error = null
|
let selectedHost = null, plot, fetching = false, error = null
|
||||||
|
let selectedScope = minScope(scopes)
|
||||||
|
let selectedScopeIndex = scopes.findIndex(s => s == selectedScope)
|
||||||
|
|
||||||
$: avaliableScopes = scopes.map(metric => metric.scope)
|
// console.log('- Inputs -')
|
||||||
$: data = scopes.find(metric => metric.scope == selectedScope)
|
// console.log(metricName)
|
||||||
|
// console.log(scopes)
|
||||||
|
// console.log(rawData)
|
||||||
|
// console.log('- Prep Scopes -')
|
||||||
|
// console.log(selectedScope)
|
||||||
|
// console.log(selectedScopeIndex)
|
||||||
|
|
||||||
|
$: avaliableScopes = scopes
|
||||||
|
$: data = rawData[selectedScopeIndex]
|
||||||
$: series = data?.series.filter(series => selectedHost == null || series.hostname == selectedHost)
|
$: series = data?.series.filter(series => selectedHost == null || series.hostname == selectedHost)
|
||||||
|
|
||||||
|
// console.log('- Prep Data -')
|
||||||
|
// console.log(rawData[selectedScopeIndex])
|
||||||
|
// console.log(rawData[selectedScopeIndex].series.filter(series => selectedHost == null || series.hostname == selectedHost))
|
||||||
|
|
||||||
let from = null, to = null
|
let from = null, to = null
|
||||||
export function setTimeRange(f, t) {
|
export function setTimeRange(f, t) {
|
||||||
from = f, to = t
|
from = f, to = t
|
||||||
@ -29,7 +44,7 @@
|
|||||||
|
|
||||||
export async function loadMore() {
|
export async function loadMore() {
|
||||||
fetching = true
|
fetching = true
|
||||||
let response = await fetchMetrics(job, [metric], ["core"])
|
let response = await fetchMetrics(job, [metricName], ["core"])
|
||||||
fetching = false
|
fetching = false
|
||||||
|
|
||||||
if (response.error) {
|
if (response.error) {
|
||||||
@ -38,9 +53,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (let jm of response.data.jobMetrics) {
|
for (let jm of response.data.jobMetrics) {
|
||||||
if (jm.metric.scope != "node") {
|
if (jm.scope != "node") {
|
||||||
scopes.push(jm.metric)
|
scopes.push(jm.metric)
|
||||||
selectedScope = jm.metric.scope
|
selectedScope = jm.scope
|
||||||
dispatch('more-loaded', jm)
|
dispatch('more-loaded', jm)
|
||||||
if (!avaliableScopes.includes(selectedScope))
|
if (!avaliableScopes.includes(selectedScope))
|
||||||
avaliableScopes = [...avaliableScopes, selectedScope]
|
avaliableScopes = [...avaliableScopes, selectedScope]
|
||||||
@ -52,7 +67,8 @@
|
|||||||
</script>
|
</script>
|
||||||
<InputGroup>
|
<InputGroup>
|
||||||
<InputGroupText style="min-width: 150px;">
|
<InputGroupText style="min-width: 150px;">
|
||||||
{metric} ({metricConfig?.unit})
|
{metricName} ({(metricConfig?.unit?.prefix ? metricConfig.unit.prefix : '') +
|
||||||
|
(metricConfig?.unit?.base ? metricConfig.unit.base : '')})
|
||||||
</InputGroupText>
|
</InputGroupText>
|
||||||
<select class="form-select" bind:value={selectedScope}>
|
<select class="form-select" bind:value={selectedScope}>
|
||||||
{#each avaliableScopes as scope}
|
{#each avaliableScopes as scope}
|
||||||
@ -82,7 +98,7 @@
|
|||||||
width={width} height={300}
|
width={width} height={300}
|
||||||
cluster={cluster} subCluster={subCluster}
|
cluster={cluster} subCluster={subCluster}
|
||||||
timestep={data.timestep}
|
timestep={data.timestep}
|
||||||
scope={selectedScope} metric={metric}
|
scope={selectedScope} metric={metricName}
|
||||||
series={series} />
|
series={series} />
|
||||||
{/if}
|
{/if}
|
||||||
{/key}
|
{/key}
|
||||||
|
@ -20,16 +20,19 @@
|
|||||||
from.setMinutes(from.getMinutes() - 30)
|
from.setMinutes(from.getMinutes() - 30)
|
||||||
}
|
}
|
||||||
|
|
||||||
const ccconfig = getContext('cc-config'), clusters = getContext('clusters')
|
const ccconfig = getContext('cc-config')
|
||||||
|
const clusters = getContext('clusters')
|
||||||
|
|
||||||
const nodesQuery = operationStore(`query($cluster: String!, $nodes: [String!], $from: Time!, $to: Time!) {
|
const nodesQuery = operationStore(`query($cluster: String!, $nodes: [String!], $from: Time!, $to: Time!) {
|
||||||
nodeMetrics(cluster: $cluster, nodes: $nodes, from: $from, to: $to) {
|
nodeMetrics(cluster: $cluster, nodes: $nodes, from: $from, to: $to) {
|
||||||
host, subCluster
|
host
|
||||||
|
subCluster
|
||||||
metrics {
|
metrics {
|
||||||
name,
|
name
|
||||||
|
scope
|
||||||
metric {
|
metric {
|
||||||
timestep
|
timestep
|
||||||
scope
|
unit { base, prefix }
|
||||||
series {
|
series {
|
||||||
statistics { min, avg, max }
|
statistics { min, avg, max }
|
||||||
data
|
data
|
||||||
@ -46,6 +49,17 @@
|
|||||||
|
|
||||||
$: $nodesQuery.variables = { cluster, nodes: [hostname], from: from.toISOString(), to: to.toISOString() }
|
$: $nodesQuery.variables = { cluster, nodes: [hostname], from: from.toISOString(), to: to.toISOString() }
|
||||||
|
|
||||||
|
let metricUnits = {}
|
||||||
|
$: if ($nodesQuery.data) {
|
||||||
|
for (let metric of clusters.find(c => c.name == cluster).metricConfig) {
|
||||||
|
if (metric.unit.prefix || metric.unit.base) {
|
||||||
|
metricUnits[metric.name] = '(' + (metric.unit.prefix ? metric.unit.prefix : '') + (metric.unit.base ? metric.unit.base : '') + ')'
|
||||||
|
} else { // If no unit defined: Omit Unit Display
|
||||||
|
metricUnits[metric.name] = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
query(nodesQuery)
|
query(nodesQuery)
|
||||||
|
|
||||||
$: console.log($nodesQuery?.data?.nodeMetrics[0].metrics)
|
$: console.log($nodesQuery?.data?.nodeMetrics[0].metrics)
|
||||||
@ -83,7 +97,7 @@
|
|||||||
let:width
|
let:width
|
||||||
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.sort((a, b) => a.name.localeCompare(b.name))}>
|
||||||
<h4 style="text-align: center;">{item.name}</h4>
|
<h4 style="text-align: center;">{item.name} {metricUnits[item.name]}</h4>
|
||||||
<MetricPlot
|
<MetricPlot
|
||||||
width={width} height={300} metric={item.name} timestep={item.metric.timestep}
|
width={width} height={300} metric={item.name} timestep={item.metric.timestep}
|
||||||
cluster={clusters.find(c => c.name == cluster)} subCluster={$nodesQuery.data.nodeMetrics[0].subCluster}
|
cluster={clusters.find(c => c.name == cluster)} subCluster={$nodesQuery.data.nodeMetrics[0].subCluster}
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
const allMetrics = [...new Set(jobMetrics.map(m => m.name))].sort(),
|
const allMetrics = [...new Set(jobMetrics.map(m => m.name))].sort(),
|
||||||
scopesForMetric = (metric) => jobMetrics
|
scopesForMetric = (metric) => jobMetrics
|
||||||
.filter(jm => jm.name == metric)
|
.filter(jm => jm.name == metric)
|
||||||
.map(jm => jm.metric.scope)
|
.map(jm => jm.scope)
|
||||||
|
|
||||||
let hosts = job.resources.map(r => r.hostname).sort(),
|
let hosts = job.resources.map(r => r.hostname).sort(),
|
||||||
selectedScopes = {},
|
selectedScopes = {},
|
||||||
@ -40,7 +40,7 @@
|
|||||||
s.active = true
|
s.active = true
|
||||||
}
|
}
|
||||||
|
|
||||||
let series = jobMetrics.find(jm => jm.name == metric && jm.metric.scope == 'node')?.metric.series
|
let series = jobMetrics.find(jm => jm.name == metric && jm.scope == 'node')?.metric.series
|
||||||
sorting = {...sorting}
|
sorting = {...sorting}
|
||||||
hosts = hosts.sort((h1, h2) => {
|
hosts = hosts.sort((h1, h2) => {
|
||||||
let s1 = series.find(s => s.hostname == h1)?.statistics
|
let s1 = series.find(s => s.hostname == h1)?.statistics
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
export let jobMetrics
|
export let jobMetrics
|
||||||
|
|
||||||
$: series = jobMetrics
|
$: series = jobMetrics
|
||||||
.find(jm => jm.name == metric && jm.metric.scope == scope)
|
.find(jm => jm.name == metric && jm.scope == scope)
|
||||||
?.metric.series.filter(s => s.hostname == host && s.statistics != null)
|
?.metric.series.filter(s => s.hostname == host && s.statistics != null)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -15,13 +15,14 @@
|
|||||||
let from = new Date(Date.now() - 5 * 60 * 1000), to = new Date(Date.now())
|
let from = new Date(Date.now() - 5 * 60 * 1000), to = new Date(Date.now())
|
||||||
const mainQuery = operationStore(`query($cluster: String!, $filter: [JobFilter!]!, $metrics: [String!], $from: Time!, $to: Time!) {
|
const mainQuery = operationStore(`query($cluster: String!, $filter: [JobFilter!]!, $metrics: [String!], $from: Time!, $to: Time!) {
|
||||||
nodeMetrics(cluster: $cluster, metrics: $metrics, from: $from, to: $to) {
|
nodeMetrics(cluster: $cluster, metrics: $metrics, from: $from, to: $to) {
|
||||||
host,
|
host
|
||||||
subCluster,
|
subCluster
|
||||||
metrics {
|
metrics {
|
||||||
name,
|
name
|
||||||
metric {
|
|
||||||
scope
|
scope
|
||||||
timestep,
|
metric {
|
||||||
|
timestep
|
||||||
|
unit { base, prefix }
|
||||||
series { data }
|
series { data }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -47,13 +48,15 @@
|
|||||||
? sum + (node.metrics.find(m => m.name == metric)?.metric.series.reduce((sum, series) => sum + series.data[series.data.length - 1], 0) || 0)
|
? sum + (node.metrics.find(m => m.name == metric)?.metric.series.reduce((sum, series) => sum + series.data[series.data.length - 1], 0) || 0)
|
||||||
: sum, 0)
|
: sum, 0)
|
||||||
|
|
||||||
let allocatedNodes = {}, flopRate = {}, memBwRate = {}
|
let allocatedNodes = {}, flopRate = {}, flopRateUnit = {}, memBwRate = {}, memBwRateUnit = {}
|
||||||
$: if ($initq.data && $mainQuery.data) {
|
$: if ($initq.data && $mainQuery.data) {
|
||||||
let subClusters = $initq.data.clusters.find(c => c.name == cluster).subClusters
|
let subClusters = $initq.data.clusters.find(c => c.name == cluster).subClusters
|
||||||
for (let subCluster of subClusters) {
|
for (let subCluster of subClusters) {
|
||||||
allocatedNodes[subCluster.name] = $mainQuery.data.allocatedNodes.find(({ name }) => name == subCluster.name)?.count || 0
|
allocatedNodes[subCluster.name] = $mainQuery.data.allocatedNodes.find(({ name }) => name == subCluster.name)?.count || 0
|
||||||
flopRate[subCluster.name] = Math.floor(sumUp($mainQuery.data.nodeMetrics, subCluster.name, 'flops_any') * 100) / 100
|
flopRate[subCluster.name] = Math.floor(sumUp($mainQuery.data.nodeMetrics, subCluster.name, 'flops_any') * 100) / 100
|
||||||
|
flopRateUnit[subCluster.name] = subCluster.flopRateSimd.unit.prefix + subCluster.flopRateSimd.unit.base
|
||||||
memBwRate[subCluster.name] = Math.floor(sumUp($mainQuery.data.nodeMetrics, subCluster.name, 'mem_bw') * 100) / 100
|
memBwRate[subCluster.name] = Math.floor(sumUp($mainQuery.data.nodeMetrics, subCluster.name, 'mem_bw') * 100) / 100
|
||||||
|
memBwRateUnit[subCluster.name] = subCluster.memoryBandwidth.unit.prefix + subCluster.memoryBandwidth.unit.base
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,13 +119,13 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col">Flop Rate (Any) <Icon name="info-circle" class="p-1" style="cursor: help;" title="Flops[Any] = (Flops[Double] x 2) + Flops[Single]"/></th>
|
<th scope="col">Flop Rate (Any) <Icon name="info-circle" class="p-1" style="cursor: help;" title="Flops[Any] = (Flops[Double] x 2) + Flops[Single]"/></th>
|
||||||
<td style="min-width: 100px;"><div class="col"><Progress value={flopRate[subCluster.name]} max={subCluster.flopRateSimd * subCluster.numberOfNodes}/></div></td>
|
<td style="min-width: 100px;"><div class="col"><Progress value={flopRate[subCluster.name]} max={subCluster.flopRateSimd.value * subCluster.numberOfNodes}/></div></td>
|
||||||
<td>({formatNumber(flopRate[subCluster.name])}Flops/s / {formatNumber((subCluster.flopRateSimd * subCluster.numberOfNodes))}Flops/s [Max])</td>
|
<td>({flopRate[subCluster.name]} {flopRateUnit[subCluster.name]} / {(subCluster.flopRateSimd.value * subCluster.numberOfNodes)} {flopRateUnit[subCluster.name]} [Max])</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col">MemBw Rate</th>
|
<th scope="col">MemBw Rate</th>
|
||||||
<td style="min-width: 100px;"><div class="col"><Progress value={memBwRate[subCluster.name]} max={subCluster.memoryBandwidth * subCluster.numberOfNodes}/></div></td>
|
<td style="min-width: 100px;"><div class="col"><Progress value={memBwRate[subCluster.name]} max={subCluster.memoryBandwidth.value * subCluster.numberOfNodes}/></div></td>
|
||||||
<td>({formatNumber(memBwRate[subCluster.name])}Byte/s / {formatNumber((subCluster.memoryBandwidth * subCluster.numberOfNodes))}Byte/s [Max])</td>
|
<td>({memBwRate[subCluster.name]} {memBwRateUnit[subCluster.name]} / {(subCluster.memoryBandwidth.value * subCluster.numberOfNodes)} {memBwRateUnit[subCluster.name]} [Max])</td>
|
||||||
</tr>
|
</tr>
|
||||||
</Table>
|
</Table>
|
||||||
</CardBody>
|
</CardBody>
|
||||||
@ -150,7 +153,7 @@
|
|||||||
<h4 class="mb-3 text-center">Top Users</h4>
|
<h4 class="mb-3 text-center">Top Users</h4>
|
||||||
{#key $mainQuery.data}
|
{#key $mainQuery.data}
|
||||||
<Histogram
|
<Histogram
|
||||||
width={colWidth1 - 25} height={300}
|
width={colWidth1 - 25}
|
||||||
data={$mainQuery.data.topUsers.sort((a, b) => b.count - a.count).map(({ count }, idx) => ({ count, value: idx }))}
|
data={$mainQuery.data.topUsers.sort((a, b) => b.count - a.count).map(({ count }, idx) => ({ count, value: idx }))}
|
||||||
label={(x) => x < $mainQuery.data.topUsers.length ? $mainQuery.data.topUsers[Math.floor(x)].name : '0'}
|
label={(x) => x < $mainQuery.data.topUsers.length ? $mainQuery.data.topUsers[Math.floor(x)].name : '0'}
|
||||||
xlabel="User Name" ylabel="Number of Jobs" />
|
xlabel="User Name" ylabel="Number of Jobs" />
|
||||||
@ -172,7 +175,7 @@
|
|||||||
<h4 class="mb-3 text-center">Top Projects</h4>
|
<h4 class="mb-3 text-center">Top Projects</h4>
|
||||||
{#key $mainQuery.data}
|
{#key $mainQuery.data}
|
||||||
<Histogram
|
<Histogram
|
||||||
width={colWidth1 - 25} height={300}
|
width={colWidth1 - 25}
|
||||||
data={$mainQuery.data.topProjects.sort((a, b) => b.count - a.count).map(({ count }, idx) => ({ count, value: idx }))}
|
data={$mainQuery.data.topProjects.sort((a, b) => b.count - a.count).map(({ count }, idx) => ({ count, value: idx }))}
|
||||||
label={(x) => x < $mainQuery.data.topProjects.length ? $mainQuery.data.topProjects[Math.floor(x)].name : '0'}
|
label={(x) => x < $mainQuery.data.topProjects.length ? $mainQuery.data.topProjects[Math.floor(x)].name : '0'}
|
||||||
xlabel="Project Code" ylabel="Number of Jobs" />
|
xlabel="Project Code" ylabel="Number of Jobs" />
|
||||||
@ -193,9 +196,10 @@
|
|||||||
<h4 class="mb-3 text-center">Duration Distribution</h4>
|
<h4 class="mb-3 text-center">Duration Distribution</h4>
|
||||||
{#key $mainQuery.data.stats}
|
{#key $mainQuery.data.stats}
|
||||||
<Histogram
|
<Histogram
|
||||||
width={colWidth2 - 25} height={300}
|
width={colWidth2 - 25}
|
||||||
data={$mainQuery.data.stats[0].histDuration}
|
data={$mainQuery.data.stats[0].histDuration}
|
||||||
xlabel="Current Runtime in Hours [h]" ylabel="Number of Jobs" />
|
xlabel="Current Runtimes [h]"
|
||||||
|
ylabel="Number of Jobs" />
|
||||||
{/key}
|
{/key}
|
||||||
</div>
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
@ -203,9 +207,10 @@
|
|||||||
<h4 class="mb-3 text-center">Number of Nodes Distribution</h4>
|
<h4 class="mb-3 text-center">Number of Nodes Distribution</h4>
|
||||||
{#key $mainQuery.data.stats}
|
{#key $mainQuery.data.stats}
|
||||||
<Histogram
|
<Histogram
|
||||||
width={colWidth2 - 25} height={300}
|
width={colWidth2 - 25}
|
||||||
data={$mainQuery.data.stats[0].histNumNodes}
|
data={$mainQuery.data.stats[0].histNumNodes}
|
||||||
xlabel="Allocated Nodes" ylabel="Number of Jobs" />
|
xlabel="Allocated Nodes [#]"
|
||||||
|
ylabel="Number of Jobs" />
|
||||||
{/key}
|
{/key}
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
@ -28,13 +28,14 @@
|
|||||||
|
|
||||||
const nodesQuery = operationStore(`query($cluster: String!, $metrics: [String!], $from: Time!, $to: Time!) {
|
const nodesQuery = operationStore(`query($cluster: String!, $metrics: [String!], $from: Time!, $to: Time!) {
|
||||||
nodeMetrics(cluster: $cluster, metrics: $metrics, from: $from, to: $to) {
|
nodeMetrics(cluster: $cluster, metrics: $metrics, from: $from, to: $to) {
|
||||||
host,
|
host
|
||||||
subCluster
|
subCluster
|
||||||
metrics {
|
metrics {
|
||||||
name,
|
name
|
||||||
metric {
|
|
||||||
scope
|
scope
|
||||||
timestep,
|
metric {
|
||||||
|
timestep
|
||||||
|
unit { base, prefix }
|
||||||
series {
|
series {
|
||||||
statistics { min, avg, max }
|
statistics { min, avg, max }
|
||||||
data
|
data
|
||||||
@ -49,6 +50,18 @@
|
|||||||
to: to.toISOString()
|
to: to.toISOString()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
let metricUnits = {}
|
||||||
|
$: if ($nodesQuery.data) {
|
||||||
|
let thisCluster = clusters.find(c => c.name == cluster)
|
||||||
|
for (let metric of thisCluster.metricConfig) {
|
||||||
|
if (metric.unit.prefix || metric.unit.base) {
|
||||||
|
metricUnits[metric.name] = '(' + (metric.unit.prefix ? metric.unit.prefix : '') + (metric.unit.base ? metric.unit.base : '') + ')'
|
||||||
|
} else { // If no unit defined: Omit Unit Display
|
||||||
|
metricUnits[metric.name] = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$: $nodesQuery.variables = { cluster, metrics: [selectedMetric], from: from.toISOString(), to: to.toISOString() }
|
$: $nodesQuery.variables = { cluster, metrics: [selectedMetric], from: from.toISOString(), to: to.toISOString() }
|
||||||
|
|
||||||
query(nodesQuery)
|
query(nodesQuery)
|
||||||
@ -71,7 +84,7 @@
|
|||||||
<InputGroupText>Metric</InputGroupText>
|
<InputGroupText>Metric</InputGroupText>
|
||||||
<select class="form-select" bind:value={selectedMetric}>
|
<select class="form-select" bind:value={selectedMetric}>
|
||||||
{#each clusters.find(c => c.name == cluster).metricConfig as metric}
|
{#each clusters.find(c => c.name == cluster).metricConfig as metric}
|
||||||
<option value={metric.name}>{metric.name} ({metric.unit})</option>
|
<option value={metric.name}>{metric.name} {metricUnits[metric.name]}</option>
|
||||||
{/each}
|
{/each}
|
||||||
</select>
|
</select>
|
||||||
</InputGroup>
|
</InputGroup>
|
||||||
@ -98,8 +111,8 @@
|
|||||||
let:width
|
let:width
|
||||||
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.metric.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.metric.scope == 'node') }))
|
.map(h => ({ host: h.host, subCluster: h.subCluster, data: h.metrics.find(m => m.name == selectedMetric && m.scope == 'node') }))
|
||||||
.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>
|
||||||
|
@ -136,19 +136,23 @@
|
|||||||
</Table>
|
</Table>
|
||||||
</Col>
|
</Col>
|
||||||
<div class="col-4" style="text-align: center;" bind:clientWidth={w1}>
|
<div class="col-4" style="text-align: center;" bind:clientWidth={w1}>
|
||||||
<b>Walltime</b>
|
<b>Duration Distribution</b>
|
||||||
{#key $stats.data.jobsStatistics[0].histDuration}
|
{#key $stats.data.jobsStatistics[0].histDuration}
|
||||||
<Histogram
|
<Histogram
|
||||||
data={$stats.data.jobsStatistics[0].histDuration}
|
data={$stats.data.jobsStatistics[0].histDuration}
|
||||||
width={w1 - 25} height={histogramHeight} />
|
width={w1 - 25} height={histogramHeight}
|
||||||
|
xlabel="Current Runtimes [h]"
|
||||||
|
ylabel="Number of Jobs"/>
|
||||||
{/key}
|
{/key}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-4" style="text-align: center;" bind:clientWidth={w2}>
|
<div class="col-4" style="text-align: center;" bind:clientWidth={w2}>
|
||||||
<b>Number of Nodes</b>
|
<b>Number of Nodes Distribution</b>
|
||||||
{#key $stats.data.jobsStatistics[0].histNumNodes}
|
{#key $stats.data.jobsStatistics[0].histNumNodes}
|
||||||
<Histogram
|
<Histogram
|
||||||
data={$stats.data.jobsStatistics[0].histNumNodes}
|
data={$stats.data.jobsStatistics[0].histNumNodes}
|
||||||
width={w2 - 25} height={histogramHeight} />
|
width={w2 - 25} height={histogramHeight}
|
||||||
|
xlabel="Allocated Nodes [#]"
|
||||||
|
ylabel="Number of Jobs" />
|
||||||
{/key}
|
{/key}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -101,9 +101,11 @@
|
|||||||
{#if $initialized}
|
{#if $initialized}
|
||||||
({clusters
|
({clusters
|
||||||
.map(cluster => cluster.metricConfig.find(m => m.name == metric))
|
.map(cluster => cluster.metricConfig.find(m => m.name == metric))
|
||||||
.filter(m => m != null).map(m => m.unit)
|
.filter(m => m != null)
|
||||||
.reduce((arr, unit) => arr.includes(unit) ? arr : [...arr, unit], [])
|
.map(m => (m.unit?.prefix?m.unit?.prefix:'') + (m.unit?.base?m.unit?.base:'')) // Build unitStr
|
||||||
.join(', ')})
|
.reduce((arr, unitStr) => arr.includes(unitStr) ? arr : [...arr, unitStr], []) // w/o this, output would be [unitStr, unitStr]
|
||||||
|
.join(', ')
|
||||||
|
})
|
||||||
{/if}
|
{/if}
|
||||||
</th>
|
</th>
|
||||||
{/each}
|
{/each}
|
||||||
|
@ -24,12 +24,14 @@
|
|||||||
let scopes = [job.numNodes == 1 ? 'core' : 'node']
|
let scopes = [job.numNodes == 1 ? 'core' : 'node']
|
||||||
|
|
||||||
const cluster = getContext('clusters').find(c => c.name == job.cluster)
|
const cluster = getContext('clusters').find(c => c.name == job.cluster)
|
||||||
|
// Get all MetricConfs which include subCluster-specific settings for this job
|
||||||
|
const metricConfig = getContext('metrics')
|
||||||
const metricsQuery = operationStore(`query($id: ID!, $metrics: [String!]!, $scopes: [MetricScope!]!) {
|
const metricsQuery = operationStore(`query($id: ID!, $metrics: [String!]!, $scopes: [MetricScope!]!) {
|
||||||
jobMetrics(id: $id, metrics: $metrics, scopes: $scopes) {
|
jobMetrics(id: $id, metrics: $metrics, scopes: $scopes) {
|
||||||
name
|
name
|
||||||
|
scope
|
||||||
metric {
|
metric {
|
||||||
unit, scope, timestep
|
unit { prefix, base }, timestep
|
||||||
statisticsSeries { min, mean, max }
|
statisticsSeries { min, mean, max }
|
||||||
series {
|
series {
|
||||||
hostname, id, data
|
hostname, id, data
|
||||||
@ -44,13 +46,64 @@
|
|||||||
})
|
})
|
||||||
|
|
||||||
const selectScope = (jobMetrics) => jobMetrics.reduce(
|
const selectScope = (jobMetrics) => jobMetrics.reduce(
|
||||||
(a, b) => maxScope([a.metric.scope, b.metric.scope]) == a.metric.scope
|
(a, b) => maxScope([a.scope, b.scope]) == a.scope
|
||||||
? (job.numNodes > 1 ? a : b)
|
? (job.numNodes > 1 ? a : b)
|
||||||
: (job.numNodes > 1 ? b : a), jobMetrics[0])
|
: (job.numNodes > 1 ? b : a), jobMetrics[0])
|
||||||
|
|
||||||
const sortAndSelectScope = (jobMetrics) => metrics
|
const sortAndSelectScope = (jobMetrics) => metrics
|
||||||
.map(name => jobMetrics.filter(jobMetric => jobMetric.name == name))
|
.map(function(name) {
|
||||||
.map(jobMetrics => jobMetrics.length > 0 ? selectScope(jobMetrics) : null)
|
// Get MetricConf for this selected/requested metric
|
||||||
|
let thisConfig = metricConfig(cluster, name)
|
||||||
|
let thisSCIndex = thisConfig.subClusters.findIndex(sc => sc.name == job.subCluster)
|
||||||
|
// 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
|
||||||
|
// console.log('Case 1.1 -> Returned')
|
||||||
|
// console.log({removed: true, data: null})
|
||||||
|
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) {
|
||||||
|
// console.log('Case 1.2.1 -> Returned')
|
||||||
|
// console.log({removed: false, data: thisMetric})
|
||||||
|
return {removed: false, data: thisMetric}
|
||||||
|
} else {
|
||||||
|
// console.log('Case 1.2.2 -> Returned:')
|
||||||
|
// console.log({removed: false, data: null})
|
||||||
|
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) {
|
||||||
|
// console.log('Case 2.1 -> Returned')
|
||||||
|
// console.log({removed: false, data: thisMetric})
|
||||||
|
return {removed: false, data: thisMetric}
|
||||||
|
} else {
|
||||||
|
// console.log('Case 2.2 -> Returned')
|
||||||
|
// console.log({removed: false, data: null})
|
||||||
|
return {removed: false, data: null}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.map(function(jobMetrics) {
|
||||||
|
if (jobMetrics.data != null && jobMetrics.data.length > 0) {
|
||||||
|
// console.log('Before')
|
||||||
|
// console.log(jobMetrics.data)
|
||||||
|
// console.log('After')
|
||||||
|
// console.log(selectScope(jobMetrics.data))
|
||||||
|
let res = {removed: jobMetrics.removed, data: selectScope(jobMetrics.data)}
|
||||||
|
// console.log('Packed')
|
||||||
|
// console.log(res)
|
||||||
|
return res
|
||||||
|
} else {
|
||||||
|
return jobMetrics
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
$: metricsQuery.variables = { id: job.id, metrics, scopes }
|
$: metricsQuery.variables = { id: job.id, metrics, scopes }
|
||||||
|
|
||||||
@ -81,17 +134,20 @@
|
|||||||
{:else}
|
{:else}
|
||||||
{#each sortAndSelectScope($metricsQuery.data.jobMetrics) as metric, i (metric || i)}
|
{#each sortAndSelectScope($metricsQuery.data.jobMetrics) as metric, i (metric || i)}
|
||||||
<td>
|
<td>
|
||||||
{#if metric != null}
|
<!-- 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}
|
||||||
<MetricPlot
|
<MetricPlot
|
||||||
width={plotWidth}
|
width={plotWidth}
|
||||||
height={plotHeight}
|
height={plotHeight}
|
||||||
timestep={metric.metric.timestep}
|
timestep={metric.data.metric.timestep}
|
||||||
scope={metric.metric.scope}
|
scope={metric.data.scope}
|
||||||
series={metric.metric.series}
|
series={metric.data.metric.series}
|
||||||
statisticsSeries={metric.metric.statisticsSeries}
|
statisticsSeries={metric.data.metric.statisticsSeries}
|
||||||
metric={metric.name}
|
metric={metric.data.name}
|
||||||
cluster={cluster}
|
cluster={cluster}
|
||||||
subCluster={job.subCluster} />
|
subCluster={job.subCluster} />
|
||||||
|
{:else if metric.removed == true && metric.data == null}
|
||||||
|
<Card body color="info">Metric disabled for subcluster '{ job.subCluster }'</Card>
|
||||||
{:else}
|
{:else}
|
||||||
<Card body color="warning">Missing Data</Card>
|
<Card body color="warning">Missing Data</Card>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -18,10 +18,10 @@
|
|||||||
import { onMount } from 'svelte'
|
import { onMount } from 'svelte'
|
||||||
|
|
||||||
export let data
|
export let data
|
||||||
export let width
|
export let width = 500
|
||||||
export let height
|
export let height = 300
|
||||||
export let xlabel
|
export let xlabel = ''
|
||||||
export let ylabel
|
export let ylabel = ''
|
||||||
export let min = null
|
export let min = null
|
||||||
export let max = null
|
export let max = null
|
||||||
export let label = formatNumber
|
export let label = formatNumber
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
let ctx, canvasElement
|
let ctx, canvasElement
|
||||||
|
|
||||||
const labels = metrics.filter(name => {
|
const labels = metrics.filter(name => {
|
||||||
if (!jobMetrics.find(m => m.name == name && m.metric.scope == "node")) {
|
if (!jobMetrics.find(m => m.name == name && m.scope == "node")) {
|
||||||
console.warn(`PolarPlot: No metric data for '${name}'`)
|
console.warn(`PolarPlot: No metric data for '${name}'`)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -27,7 +27,7 @@
|
|||||||
|
|
||||||
const getValuesForStat = (getStat) => labels.map(name => {
|
const getValuesForStat = (getStat) => labels.map(name => {
|
||||||
const peak = metricConfig(cluster, name).peak
|
const peak = metricConfig(cluster, name).peak
|
||||||
const metric = jobMetrics.find(m => m.name == name && m.metric.scope == "node")
|
const metric = jobMetrics.find(m => m.name == name && m.scope == "node")
|
||||||
const value = getStat(metric.metric) / peak
|
const value = getStat(metric.metric) / peak
|
||||||
return value <= 1. ? value : 1.
|
return value <= 1. ? value : 1.
|
||||||
})
|
})
|
||||||
|
@ -71,7 +71,7 @@
|
|||||||
if (width <= 0)
|
if (width <= 0)
|
||||||
return
|
return
|
||||||
|
|
||||||
const [minX, maxX, minY, maxY] = [0.01, 1000, 1., cluster?.flopRateSimd || defaultMaxY]
|
const [minX, maxX, minY, maxY] = [0.01, 1000, 1., cluster?.flopRateSimd?.value || defaultMaxY]
|
||||||
const w = width - paddingLeft - paddingRight
|
const w = width - paddingLeft - paddingRight
|
||||||
const h = height - paddingTop - paddingBottom
|
const h = height - paddingTop - paddingBottom
|
||||||
|
|
||||||
@ -185,13 +185,13 @@
|
|||||||
ctx.lineWidth = 2
|
ctx.lineWidth = 2
|
||||||
ctx.beginPath()
|
ctx.beginPath()
|
||||||
if (cluster != null) {
|
if (cluster != null) {
|
||||||
const ycut = 0.01 * cluster.memoryBandwidth
|
const ycut = 0.01 * cluster.memoryBandwidth.value
|
||||||
const scalarKnee = (cluster.flopRateScalar - ycut) / cluster.memoryBandwidth
|
const scalarKnee = (cluster.flopRateScalar.value - ycut) / cluster.memoryBandwidth.value
|
||||||
const simdKnee = (cluster.flopRateSimd - ycut) / cluster.memoryBandwidth
|
const simdKnee = (cluster.flopRateSimd.value - ycut) / cluster.memoryBandwidth.value
|
||||||
const scalarKneeX = getCanvasX(scalarKnee),
|
const scalarKneeX = getCanvasX(scalarKnee),
|
||||||
simdKneeX = getCanvasX(simdKnee),
|
simdKneeX = getCanvasX(simdKnee),
|
||||||
flopRateScalarY = getCanvasY(cluster.flopRateScalar),
|
flopRateScalarY = getCanvasY(cluster.flopRateScalar.value),
|
||||||
flopRateSimdY = getCanvasY(cluster.flopRateSimd)
|
flopRateSimdY = getCanvasY(cluster.flopRateSimd.value)
|
||||||
|
|
||||||
if (scalarKneeX < width - paddingRight) {
|
if (scalarKneeX < width - paddingRight) {
|
||||||
ctx.moveTo(scalarKneeX, flopRateScalarY)
|
ctx.moveTo(scalarKneeX, flopRateScalarY)
|
||||||
@ -270,8 +270,8 @@
|
|||||||
export function transformPerNodeData(nodes) {
|
export function transformPerNodeData(nodes) {
|
||||||
const x = [], y = [], c = []
|
const x = [], y = [], c = []
|
||||||
for (let node of nodes) {
|
for (let node of nodes) {
|
||||||
let flopsAny = node.metrics.find(m => m.name == 'flops_any' && m.metric.scope == 'node')?.metric
|
let flopsAny = node.metrics.find(m => m.name == 'flops_any' && m.scope == 'node')?.metric
|
||||||
let memBw = node.metrics.find(m => m.name == 'mem_bw' && m.metric.scope == 'node')?.metric
|
let memBw = node.metrics.find(m => m.name == 'mem_bw' && m.scope == 'node')?.metric
|
||||||
if (!flopsAny || !memBw)
|
if (!flopsAny || !memBw)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@ -301,8 +301,8 @@
|
|||||||
export let memBw = null
|
export let memBw = null
|
||||||
export let cluster = null
|
export let cluster = null
|
||||||
export let maxY = null
|
export let maxY = null
|
||||||
export let width
|
export let width = 500
|
||||||
export let height
|
export let height = 300
|
||||||
export let tiles = null
|
export let tiles = null
|
||||||
export let colorDots = true
|
export let colorDots = true
|
||||||
export let showTime = true
|
export let showTime = true
|
||||||
|
@ -41,7 +41,7 @@ export function init(extraInitQuery = '') {
|
|||||||
normal, caution, alert,
|
normal, caution, alert,
|
||||||
timestep, scope,
|
timestep, scope,
|
||||||
aggregation,
|
aggregation,
|
||||||
subClusters { name, peak, normal, caution, alert }
|
subClusters { name, peak, normal, caution, alert, remove }
|
||||||
}
|
}
|
||||||
partitions
|
partitions
|
||||||
subClusters {
|
subClusters {
|
||||||
@ -49,9 +49,9 @@ export function init(extraInitQuery = '') {
|
|||||||
socketsPerNode
|
socketsPerNode
|
||||||
coresPerSocket
|
coresPerSocket
|
||||||
threadsPerCore
|
threadsPerCore
|
||||||
flopRateScalar
|
flopRateScalar { unit { base, prefix }, value }
|
||||||
flopRateSimd
|
flopRateSimd { unit { base, prefix }, value }
|
||||||
memoryBandwidth
|
memoryBandwidth { unit { base, prefix }, value }
|
||||||
numberOfNodes
|
numberOfNodes
|
||||||
topology {
|
topology {
|
||||||
node, socket, core
|
node, socket, core
|
||||||
|
Loading…
Reference in New Issue
Block a user