Files
moebiusbandandClaude Opus 5 93eac532c4 feat: default to average downsampling
Average consolidation makes every plotted point the true arithmetic mean of
its interval, which is what the Ganglia-style plot appearance depends on.
LTTB deliberately keeps extremes instead, so a plot downsampled with it
cannot be read as interval means.

resolveResampleAlgo used to return an empty string when neither the user nor
the config had chosen an algorithm, and cc-lib's GetResampler maps that to
LTTB. Route every fallback through config.ResampleAlgo() and ship "average"
as the generated and example default.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 12:17:57 +02:00

142 lines
3.6 KiB
Go

// Copyright (C) NHR@FAU, University Erlangen-Nuremberg.
// All rights reserved. This file is part of cc-backend.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package graph
import (
"context"
"strings"
"github.com/ClusterCockpit/cc-backend/internal/config"
"github.com/ClusterCockpit/cc-backend/internal/graph/model"
"github.com/ClusterCockpit/cc-backend/internal/metricdispatch"
"github.com/ClusterCockpit/cc-backend/internal/repository"
"github.com/ClusterCockpit/cc-backend/pkg/archive"
)
// resolveResolutionFromPolicy reads the user's resample policy preference and
// computes a resolution based on job duration and metric frequency. Returns nil
// if the user has no policy set.
func resolveResolutionFromPolicy(ctx context.Context, duration int64, cluster string, metrics []string) *int {
user := repository.GetUserFromContext(ctx)
if user == nil {
return nil
}
conf, err := repository.GetUserCfgRepo().GetUIConfig(user)
if err != nil {
return nil
}
policyVal, ok := conf["plotConfiguration_resamplePolicy"]
if !ok {
return nil
}
policyStr, ok := policyVal.(string)
if !ok || policyStr == "" {
return nil
}
policy := metricdispatch.ResamplePolicy(policyStr)
targetPoints := metricdispatch.TargetPointsForPolicy(policy)
if targetPoints == 0 {
return nil
}
// Find the smallest metric frequency across the requested metrics
frequency := smallestFrequency(cluster, metrics)
if frequency <= 0 {
return nil
}
res := metricdispatch.ComputeResolution(duration, int64(frequency), targetPoints)
return &res
}
// resolveResampleAlgo returns the resampling algorithm name to use, checking
// the explicit GraphQL parameter first, then the user's preference.
func resolveResampleAlgo(ctx context.Context, resampleAlgo *model.ResampleAlgo) string {
if resampleAlgo != nil {
return strings.ToLower(resampleAlgo.String())
}
user := repository.GetUserFromContext(ctx)
if user == nil {
return config.ResampleAlgo()
}
conf, err := repository.GetUserCfgRepo().GetUIConfig(user)
if err != nil {
return config.ResampleAlgo()
}
algoVal, ok := conf["plotConfiguration_resampleAlgo"]
if ok {
if algoStr, ok := algoVal.(string); ok && algoStr != "" {
return algoStr
}
}
// Fall back to the global default algo
return config.ResampleAlgo()
}
// resolveResolutionFromDefaultPolicy computes a resolution using the global
// default policy from config. Returns nil if no policy is configured.
func resolveResolutionFromDefaultPolicy(duration int64, cluster string, metrics []string) *int {
cfg := config.Keys.EnableResampling
if cfg == nil {
return nil
}
policyStr := cfg.DefaultPolicy
if policyStr == "" {
policyStr = "medium"
}
policy := metricdispatch.ResamplePolicy(policyStr)
targetPoints := metricdispatch.TargetPointsForPolicy(policy)
if targetPoints == 0 {
return nil
}
frequency := smallestFrequency(cluster, metrics)
if frequency <= 0 {
return nil
}
res := metricdispatch.ComputeResolution(duration, int64(frequency), targetPoints)
return &res
}
// smallestFrequency returns the smallest metric timestep (in seconds) among the
// requested metrics for the given cluster. Falls back to 0 if nothing is found.
func smallestFrequency(cluster string, metrics []string) int {
cl := archive.GetCluster(cluster)
if cl == nil {
return 0
}
minFreq := 0
for _, mc := range cl.MetricConfig {
if len(metrics) > 0 {
found := false
for _, m := range metrics {
if mc.Name == m {
found = true
break
}
}
if !found {
continue
}
}
if minFreq == 0 || mc.Timestep < minFreq {
minFreq = mc.Timestep
}
}
return minFreq
}