mirror of
https://github.com/ClusterCockpit/cc-backend
synced 2024-12-26 05:19:05 +01:00
feat: add selectable histograms to status view
This commit is contained in:
parent
ee6d286cd7
commit
07073e290a
@ -8,10 +8,12 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ClusterCockpit/cc-backend/internal/config"
|
"github.com/ClusterCockpit/cc-backend/internal/config"
|
||||||
"github.com/ClusterCockpit/cc-backend/internal/graph/model"
|
"github.com/ClusterCockpit/cc-backend/internal/graph/model"
|
||||||
|
"github.com/ClusterCockpit/cc-backend/internal/metricdata"
|
||||||
"github.com/ClusterCockpit/cc-backend/pkg/archive"
|
"github.com/ClusterCockpit/cc-backend/pkg/archive"
|
||||||
"github.com/ClusterCockpit/cc-backend/pkg/log"
|
"github.com/ClusterCockpit/cc-backend/pkg/log"
|
||||||
"github.com/ClusterCockpit/cc-backend/pkg/schema"
|
"github.com/ClusterCockpit/cc-backend/pkg/schema"
|
||||||
@ -460,6 +462,18 @@ func (r *JobRepository) AddMetricHistograms(
|
|||||||
stat *model.JobsStatistics) (*model.JobsStatistics, error) {
|
stat *model.JobsStatistics) (*model.JobsStatistics, error) {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
|
// Running Jobs Only: First query jobdata from sqlite, then query data and make bins
|
||||||
|
for _, f := range filter {
|
||||||
|
if f.State != nil {
|
||||||
|
if len(f.State) == 1 && f.State[0] == "running" {
|
||||||
|
stat.HistMetrics = r.runningJobsMetricStatisticsHistogram(ctx, metrics, filter)
|
||||||
|
log.Debugf("Timer AddMetricHistograms %s", time.Since(start))
|
||||||
|
return stat, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// All other cases: Query and make bins in sqlite directly
|
||||||
for _, m := range metrics {
|
for _, m := range metrics {
|
||||||
metricHisto, err := r.jobsMetricStatisticsHistogram(ctx, m, filter)
|
metricHisto, err := r.jobsMetricStatisticsHistogram(ctx, m, filter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -639,3 +653,102 @@ func (r *JobRepository) jobsMetricStatisticsHistogram(
|
|||||||
log.Debugf("Timer jobsStatisticsHistogram %s", time.Since(start))
|
log.Debugf("Timer jobsStatisticsHistogram %s", time.Since(start))
|
||||||
return &result, nil
|
return &result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *JobRepository) runningJobsMetricStatisticsHistogram(
|
||||||
|
ctx context.Context,
|
||||||
|
metrics []string,
|
||||||
|
filters []*model.JobFilter) []*model.MetricHistoPoints {
|
||||||
|
|
||||||
|
// Get Jobs
|
||||||
|
jobs, err := r.QueryJobs(ctx, filters, &model.PageRequest{Page: 1, ItemsPerPage: 500 + 1}, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Error while querying jobs for footprint: %s", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if len(jobs) > 500 {
|
||||||
|
log.Errorf("too many jobs matched (max: %d)", 500)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get AVGs from metric repo
|
||||||
|
avgs := make([][]schema.Float, len(metrics))
|
||||||
|
for i := range avgs {
|
||||||
|
avgs[i] = make([]schema.Float, 0, len(jobs))
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, job := range jobs {
|
||||||
|
if job.MonitoringStatus == schema.MonitoringStatusDisabled || job.MonitoringStatus == schema.MonitoringStatusArchivingFailed {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := metricdata.LoadAverages(job, metrics, avgs, ctx); err != nil {
|
||||||
|
log.Errorf("Error while loading averages for histogram: %s", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate metrics to fill endresult
|
||||||
|
data := make([]*model.MetricHistoPoints, 0)
|
||||||
|
for idx, metric := range metrics {
|
||||||
|
// Get specific Peak or largest Peak
|
||||||
|
var metricConfig *schema.MetricConfig
|
||||||
|
var peak float64 = 0.0
|
||||||
|
var unit string = ""
|
||||||
|
|
||||||
|
for _, f := range filters {
|
||||||
|
if f.Cluster != nil {
|
||||||
|
metricConfig = archive.GetMetricConfig(*f.Cluster.Eq, metric)
|
||||||
|
peak = metricConfig.Peak
|
||||||
|
unit = metricConfig.Unit.Prefix + metricConfig.Unit.Base
|
||||||
|
log.Debugf("Cluster %s filter found with peak %f for %s", *f.Cluster.Eq, peak, metric)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if peak == 0.0 {
|
||||||
|
for _, c := range archive.Clusters {
|
||||||
|
for _, m := range c.MetricConfig {
|
||||||
|
if m.Name == metric {
|
||||||
|
if m.Peak > peak {
|
||||||
|
peak = m.Peak
|
||||||
|
}
|
||||||
|
if unit == "" {
|
||||||
|
unit = m.Unit.Prefix + m.Unit.Base
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make and fill bins
|
||||||
|
bins := 10.0
|
||||||
|
peakBin := peak / bins
|
||||||
|
|
||||||
|
points := make([]*model.MetricHistoPoint, 0)
|
||||||
|
for b := 0; b < 10; b++ {
|
||||||
|
count := 0
|
||||||
|
bindex := b + 1
|
||||||
|
bmin := math.Round(peakBin * float64(b))
|
||||||
|
bmax := math.Round(peakBin * (float64(b) + 1.0))
|
||||||
|
|
||||||
|
// Iterate AVG values for indexed metric and count for bins
|
||||||
|
for _, val := range avgs[idx] {
|
||||||
|
if float64(val) >= bmin && float64(val) < bmax {
|
||||||
|
count += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bminint := int(bmin)
|
||||||
|
bmaxint := int(bmax)
|
||||||
|
|
||||||
|
// Append Bin to Metric Result Array
|
||||||
|
point := model.MetricHistoPoint{Bin: &bindex, Count: count, Min: &bminint, Max: &bmaxint}
|
||||||
|
points = append(points, &point)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append Metric Result Array to final results array
|
||||||
|
result := model.MetricHistoPoints{Metric: metric, Unit: unit, Data: points}
|
||||||
|
data = append(data, &result)
|
||||||
|
}
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
Table,
|
Table,
|
||||||
Progress,
|
Progress,
|
||||||
Icon,
|
Icon,
|
||||||
|
Button
|
||||||
} from "sveltestrap";
|
} from "sveltestrap";
|
||||||
import { init, convert2uplot, transformPerNodeDataForRoofline } from "./utils.js";
|
import { init, convert2uplot, transformPerNodeDataForRoofline } from "./utils.js";
|
||||||
import { scaleNumbers } from "./units.js";
|
import { scaleNumbers } from "./units.js";
|
||||||
@ -24,6 +25,8 @@
|
|||||||
getContextClient,
|
getContextClient,
|
||||||
mutationStore,
|
mutationStore,
|
||||||
} from "@urql/svelte";
|
} from "@urql/svelte";
|
||||||
|
import PlotTable from './PlotTable.svelte'
|
||||||
|
import HistogramSelection from './HistogramSelection.svelte'
|
||||||
|
|
||||||
const { query: initq } = init();
|
const { query: initq } = init();
|
||||||
const ccconfig = getContext("cc-config");
|
const ccconfig = getContext("cc-config");
|
||||||
@ -63,7 +66,8 @@
|
|||||||
option.key == ccconfig.status_view_selectedTopUserCategory
|
option.key == ccconfig.status_view_selectedTopUserCategory
|
||||||
);
|
);
|
||||||
|
|
||||||
let metricsInHistograms = ccconfig[`status_view_histogramMetrics:${cluster}`] || ccconfig.status_view_histogramMetrics
|
let isHistogramSelectionOpen = false
|
||||||
|
$: metricsInHistograms = cluster ? ccconfig[`user_view_histogramMetrics:${cluster}`] : (ccconfig.user_view_histogramMetrics || [])
|
||||||
|
|
||||||
const client = getContextClient();
|
const client = getContextClient();
|
||||||
$: mainQuery = queryStore({
|
$: mainQuery = queryStore({
|
||||||
@ -75,6 +79,7 @@
|
|||||||
$metrics: [String!]
|
$metrics: [String!]
|
||||||
$from: Time!
|
$from: Time!
|
||||||
$to: Time!
|
$to: Time!
|
||||||
|
$metricsInHistograms: [String!]
|
||||||
) {
|
) {
|
||||||
nodeMetrics(
|
nodeMetrics(
|
||||||
cluster: $cluster
|
cluster: $cluster
|
||||||
@ -100,7 +105,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stats: jobsStatistics(filter: $filter) {
|
stats: jobsStatistics(filter: $filter, metrics: $metricsInHistograms) {
|
||||||
histDuration {
|
histDuration {
|
||||||
count
|
count
|
||||||
value
|
value
|
||||||
@ -117,6 +122,16 @@
|
|||||||
count
|
count
|
||||||
value
|
value
|
||||||
}
|
}
|
||||||
|
histMetrics {
|
||||||
|
metric
|
||||||
|
unit
|
||||||
|
data {
|
||||||
|
min
|
||||||
|
max
|
||||||
|
count
|
||||||
|
bin
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
allocatedNodes(cluster: $cluster) {
|
allocatedNodes(cluster: $cluster) {
|
||||||
@ -131,6 +146,7 @@
|
|||||||
from: from.toISOString(),
|
from: from.toISOString(),
|
||||||
to: to.toISOString(),
|
to: to.toISOString(),
|
||||||
filter: [{ state: ["running"] }, { cluster: { eq: cluster } }],
|
filter: [{ state: ["running"] }, { cluster: { eq: cluster } }],
|
||||||
|
metricsInHistograms: metricsInHistograms
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -313,7 +329,7 @@
|
|||||||
<Col xs="auto" style="align-self: flex-end;">
|
<Col xs="auto" style="align-self: flex-end;">
|
||||||
<h4 class="mb-0">Current utilization of cluster "{cluster}"</h4>
|
<h4 class="mb-0">Current utilization of cluster "{cluster}"</h4>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs="auto">
|
<Col xs="auto" style="margin-left: 0.25rem;">
|
||||||
{#if $initq.fetching || $mainQuery.fetching}
|
{#if $initq.fetching || $mainQuery.fetching}
|
||||||
<Spinner />
|
<Spinner />
|
||||||
{:else if $initq.error}
|
{:else if $initq.error}
|
||||||
@ -323,6 +339,13 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs="auto" style="margin-left: auto;">
|
<Col xs="auto" style="margin-left: auto;">
|
||||||
|
<Button
|
||||||
|
outline color="secondary"
|
||||||
|
on:click={() => (isHistogramSelectionOpen = true)}>
|
||||||
|
<Icon name="bar-chart-line"/> Select Histograms
|
||||||
|
</Button>
|
||||||
|
</Col>
|
||||||
|
<Col xs="auto" style="margin-left: 0.25rem;">
|
||||||
<Refresher
|
<Refresher
|
||||||
initially={120}
|
initially={120}
|
||||||
on:reload={() => {
|
on:reload={() => {
|
||||||
@ -668,4 +691,34 @@
|
|||||||
{/key}
|
{/key}
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
<hr class="my-2" />
|
||||||
|
{#if metricsInHistograms}
|
||||||
|
<Row>
|
||||||
|
<Col>
|
||||||
|
{#key $mainQuery.data.stats[0].histMetrics}
|
||||||
|
<PlotTable
|
||||||
|
let:item
|
||||||
|
let:width
|
||||||
|
renderFor="user"
|
||||||
|
items={$mainQuery.data.stats[0].histMetrics}
|
||||||
|
itemsPerRow={3}>
|
||||||
|
|
||||||
|
<Histogram
|
||||||
|
data={convert2uplot(item.data)}
|
||||||
|
width={width} height={250}
|
||||||
|
title="Distribution of '{item.metric}'"
|
||||||
|
xlabel={`${item.metric} bin maximum [${item.unit}]`}
|
||||||
|
xunit={item.unit}
|
||||||
|
ylabel="Number of Jobs"
|
||||||
|
yunit="Jobs"/>
|
||||||
|
</PlotTable>
|
||||||
|
{/key}
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
<HistogramSelection
|
||||||
|
bind:cluster={cluster}
|
||||||
|
bind:metricsInHistograms={metricsInHistograms}
|
||||||
|
bind:isOpen={isHistogramSelectionOpen} />
|
||||||
|
Loading…
Reference in New Issue
Block a user