mirror of
https://github.com/ClusterCockpit/cc-backend
synced 2026-03-21 07:17:30 +01:00
Adopt config to use policy based resampler configuration
Entire-Checkpoint: 7536f551d548
This commit is contained in:
@@ -106,12 +106,10 @@ type NodeStateRetention struct {
|
||||
}
|
||||
|
||||
type ResampleConfig struct {
|
||||
// Minimum number of points to trigger resampling of data
|
||||
MinimumPoints int `json:"minimum-points"`
|
||||
// Array of resampling target resolutions, in seconds; Example: [600,300,60]
|
||||
Resolutions []int `json:"resolutions"`
|
||||
// Trigger next zoom level at less than this many visible datapoints
|
||||
Trigger int `json:"trigger"`
|
||||
// Default resample policy when no user preference is set ("low", "medium", "high")
|
||||
DefaultPolicy string `json:"default-policy"`
|
||||
// Default resample algorithm when no user preference is set ("lttb", "average", "simple")
|
||||
DefaultAlgo string `json:"default-algo"`
|
||||
// Policy-derived target point count (set dynamically from user preference, not from config.json)
|
||||
TargetPoints int `json:"targetPoints,omitempty"`
|
||||
}
|
||||
@@ -157,7 +155,24 @@ func Init(mainConfig json.RawMessage) {
|
||||
cclog.Abortf("Config Init: Could not decode config file '%s'.\nError: %s\n", mainConfig, err.Error())
|
||||
}
|
||||
|
||||
if Keys.EnableResampling != nil && Keys.EnableResampling.MinimumPoints > 0 {
|
||||
resampler.SetMinimumRequiredPoints(Keys.EnableResampling.MinimumPoints)
|
||||
if Keys.EnableResampling != nil {
|
||||
policy := Keys.EnableResampling.DefaultPolicy
|
||||
if policy == "" {
|
||||
policy = "medium"
|
||||
}
|
||||
resampler.SetMinimumRequiredPoints(targetPointsForPolicy(policy))
|
||||
}
|
||||
}
|
||||
|
||||
func targetPointsForPolicy(policy string) int {
|
||||
switch policy {
|
||||
case "low":
|
||||
return 200
|
||||
case "medium":
|
||||
return 500
|
||||
case "high":
|
||||
return 1000
|
||||
default:
|
||||
return 500
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,23 +92,17 @@ var configSchema = `
|
||||
"description": "Enable dynamic zoom in frontend metric plots.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"minimum-points": {
|
||||
"description": "Minimum points to trigger resampling of time-series data.",
|
||||
"type": "integer"
|
||||
"default-policy": {
|
||||
"description": "Default resample policy when no user preference is set.",
|
||||
"type": "string",
|
||||
"enum": ["low", "medium", "high"]
|
||||
},
|
||||
"trigger": {
|
||||
"description": "Trigger next zoom level at less than this many visible datapoints.",
|
||||
"type": "integer"
|
||||
},
|
||||
"resolutions": {
|
||||
"description": "Array of resampling target resolutions, in seconds.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer"
|
||||
}
|
||||
"default-algo": {
|
||||
"description": "Default resample algorithm when no user preference is set.",
|
||||
"type": "string",
|
||||
"enum": ["lttb", "average", "simple"]
|
||||
}
|
||||
},
|
||||
"required": ["trigger", "resolutions"]
|
||||
}
|
||||
},
|
||||
"api-subjects": {
|
||||
"description": "NATS subjects configuration for subscribing to job and node events.",
|
||||
|
||||
@@ -8,6 +8,7 @@ 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"
|
||||
@@ -71,15 +72,46 @@ func resolveResampleAlgo(ctx context.Context, resampleAlgo *model.ResampleAlgo)
|
||||
}
|
||||
|
||||
algoVal, ok := conf["plotConfiguration_resampleAlgo"]
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
algoStr, ok := algoVal.(string)
|
||||
if !ok {
|
||||
return ""
|
||||
if ok {
|
||||
if algoStr, ok := algoVal.(string); ok && algoStr != "" {
|
||||
return algoStr
|
||||
}
|
||||
}
|
||||
|
||||
return algoStr
|
||||
// Fall back to global default algo
|
||||
if config.Keys.EnableResampling != nil && config.Keys.EnableResampling.DefaultAlgo != "" {
|
||||
return config.Keys.EnableResampling.DefaultAlgo
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
@@ -511,9 +511,9 @@ func (r *queryResolver) JobMetrics(ctx context.Context, id string, metrics []str
|
||||
}
|
||||
if resolution == nil {
|
||||
if config.Keys.EnableResampling != nil {
|
||||
defaultRes := slices.Max(config.Keys.EnableResampling.Resolutions)
|
||||
resolution = &defaultRes
|
||||
} else {
|
||||
resolution = resolveResolutionFromDefaultPolicy(int64(job.Duration), job.Cluster, metrics)
|
||||
}
|
||||
if resolution == nil {
|
||||
defaultRes := 0
|
||||
resolution = &defaultRes
|
||||
}
|
||||
@@ -886,9 +886,9 @@ func (r *queryResolver) NodeMetricsList(ctx context.Context, cluster string, sub
|
||||
}
|
||||
if resolution == nil {
|
||||
if config.Keys.EnableResampling != nil {
|
||||
defaultRes := slices.Max(config.Keys.EnableResampling.Resolutions)
|
||||
resolution = &defaultRes
|
||||
} else {
|
||||
resolution = resolveResolutionFromDefaultPolicy(duration, cluster, metrics)
|
||||
}
|
||||
if resolution == nil {
|
||||
defaultRes := 0
|
||||
resolution = &defaultRes
|
||||
}
|
||||
|
||||
@@ -597,13 +597,19 @@ func HandleSearchBar(rw http.ResponseWriter, r *http.Request, buildInfo web.Buil
|
||||
func resamplingForUser(conf map[string]any) *config.ResampleConfig {
|
||||
globalCfg := config.Keys.EnableResampling
|
||||
|
||||
policyVal, ok := conf["plotConfiguration_resamplePolicy"]
|
||||
if !ok {
|
||||
return globalCfg
|
||||
policyStr := ""
|
||||
if policyVal, ok := conf["plotConfiguration_resamplePolicy"]; ok {
|
||||
if s, ok := policyVal.(string); ok {
|
||||
policyStr = s
|
||||
}
|
||||
}
|
||||
policyStr, ok := policyVal.(string)
|
||||
if !ok || policyStr == "" {
|
||||
return globalCfg
|
||||
|
||||
// Fall back to global default policy, then to "medium"
|
||||
if policyStr == "" && globalCfg != nil {
|
||||
policyStr = globalCfg.DefaultPolicy
|
||||
}
|
||||
if policyStr == "" {
|
||||
policyStr = "medium"
|
||||
}
|
||||
|
||||
policy := metricdispatch.ResamplePolicy(policyStr)
|
||||
@@ -612,9 +618,7 @@ func resamplingForUser(conf map[string]any) *config.ResampleConfig {
|
||||
return globalCfg
|
||||
}
|
||||
|
||||
// Build a policy-derived config: targetPoints + trigger, no resolutions array
|
||||
return &config.ResampleConfig{
|
||||
TargetPoints: targetPoints,
|
||||
Trigger: targetPoints / 4,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user