mirror of
				https://github.com/ClusterCockpit/cc-backend
				synced 2025-11-04 01:25:06 +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:
		internal
pkg/archive
web/frontend/src
@@ -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,10 +324,13 @@ 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{
 | 
				
			||||||
			Avg:  avg / float64(job.NumNodes),
 | 
									Prefix: archive.GetMetricConfig(job.Cluster, metric).Unit.Prefix,
 | 
				
			||||||
			Min:  min,
 | 
									Base:   archive.GetMetricConfig(job.Cluster, metric).Unit.Base,
 | 
				
			||||||
			Max:  max,
 | 
								},
 | 
				
			||||||
 | 
								Avg: avg / float64(job.NumNodes),
 | 
				
			||||||
 | 
								Min: min,
 | 
				
			||||||
 | 
								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 selectedHost = null, plot, fetching = false, error = null
 | 
				
			||||||
 | 
					    let selectedScope = minScope(scopes)
 | 
				
			||||||
 | 
					    let selectedScopeIndex = scopes.findIndex(s => s == selectedScope)
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // console.log('- Inputs -')
 | 
				
			||||||
 | 
					    // console.log(metricName)
 | 
				
			||||||
 | 
					    // console.log(scopes)
 | 
				
			||||||
 | 
					    // console.log(rawData)
 | 
				
			||||||
 | 
					    // console.log('- Prep Scopes -')
 | 
				
			||||||
 | 
					    // console.log(selectedScope)
 | 
				
			||||||
 | 
					    // console.log(selectedScopeIndex)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let selectedScope = minScope(scopes.map(s => s.scope)), selectedHost = null, plot, fetching = false, error = null
 | 
					    $: avaliableScopes = scopes
 | 
				
			||||||
 | 
					    $: data = rawData[selectedScopeIndex]
 | 
				
			||||||
    $: avaliableScopes = scopes.map(metric => metric.scope)
 | 
					 | 
				
			||||||
    $: data = scopes.find(metric => metric.scope == selectedScope)
 | 
					 | 
				
			||||||
    $: 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
 | 
				
			||||||
 | 
					                scope
 | 
				
			||||||
                metric {
 | 
					                metric {
 | 
				
			||||||
                    scope
 | 
					                    timestep
 | 
				
			||||||
                    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
 | 
				
			||||||
 | 
					                scope
 | 
				
			||||||
                metric {
 | 
					                metric {
 | 
				
			||||||
                    scope
 | 
					                    timestep
 | 
				
			||||||
                    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
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -37,11 +37,11 @@ export function init(extraInitQuery = '') {
 | 
				
			|||||||
        clusters {
 | 
					        clusters {
 | 
				
			||||||
            name,
 | 
					            name,
 | 
				
			||||||
            metricConfig {
 | 
					            metricConfig {
 | 
				
			||||||
                name, unit {base, prefix}, peak,
 | 
					                name, unit { base, prefix }, peak,
 | 
				
			||||||
                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
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user