| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | |
| 3 | package jobruntime |
| 4 | |
| 5 | import ( |
| 6 | "fmt" |
| 7 | "maps" |
| 8 | "sort" |
| 9 | |
| 10 | "github.com/netdata/netdata/go/plugins/pkg/metrix" |
| 11 | "github.com/netdata/netdata/go/plugins/pkg/netdataapi" |
| 12 | "github.com/netdata/netdata/go/plugins/plugin/framework/chartengine" |
| 13 | ) |
| 14 | |
| 15 | const defaultHostScopeKey = "" |
| 16 | |
| 17 | func (j *JobV2) ensureScopeState(scope metrix.HostScope) (*jobV2ScopeState, error) { |
| 18 | if j == nil { |
| 19 | return nil, fmt.Errorf("nil job") |
| 20 | } |
| 21 | scopeKey := scope.ScopeKey |
| 22 | if scope.IsDefault() { |
| 23 | scope = metrix.HostScope{} |
| 24 | scopeKey = defaultHostScopeKey |
| 25 | } |
| 26 | if j.scopeStates == nil { |
| 27 | j.scopeStates = make(map[string]*jobV2ScopeState) |
| 28 | } |
| 29 | if state := j.scopeStates[scopeKey]; state != nil { |
| 30 | state.scope = scope |
| 31 | return state, nil |
| 32 | } |
| 33 | |
| 34 | engine, err := j.newScopeEngine() |
| 35 | if err != nil { |
| 36 | return nil, err |
| 37 | } |
| 38 | state := &jobV2ScopeState{ |
| 39 | scopeKey: scopeKey, |
| 40 | scope: scope, |
| 41 | engine: engine, |
| 42 | } |
| 43 | j.scopeStates[scopeKey] = state |
| 44 | return state, nil |
| 45 | } |
| 46 | |
| 47 | func (j *JobV2) newScopeEngine() (*chartengine.Engine, error) { |
| 48 | opts := append([]chartengine.Option{}, j.engineOptions...) |
| 49 | opts = append(opts, chartengine.WithRuntimeStore(nil)) |
| 50 | if j.runtimeAggregator != nil { |
| 51 | opts = append(opts, chartengine.WithRuntimeSampleObserver(j.runtimeAggregator.Observe)) |
| 52 | } |
| 53 | engine, err := chartengine.New(opts...) |
| 54 | if err != nil { |
| 55 | return nil, err |
| 56 | } |
| 57 | if err := engine.LoadYAML(j.chartTemplateYAML, j.chartTemplateRevision); err != nil { |
| 58 | return nil, err |
| 59 | } |
| 60 | return engine, nil |
| 61 | } |
| 62 | |
| 63 | func (j *JobV2) liveScopeSet() map[string]metrix.HostScope { |
| 64 | scopes := make(map[string]metrix.HostScope) |
| 65 | reader := j.store.Read(metrix.ReadRaw(), metrix.ReadFlatten()) |
| 66 | for _, scope := range reader.HostScopes() { |
| 67 | if j.scopeHasVisibleSeries(scope.ScopeKey) { |
| 68 | scopes[scope.ScopeKey] = scope |
| 69 | } |
| 70 | } |
| 71 | return scopes |
| 72 | } |
| 73 | |
| 74 | func (j *JobV2) scopeWorkSet(liveScopes map[string]metrix.HostScope) map[string]metrix.HostScope { |
| 75 | scopes := make(map[string]metrix.HostScope, len(liveScopes)+len(j.scopeStates)) |
| 76 | maps.Copy(scopes, liveScopes) |
| 77 | // Retain previously emitted scopes until their engine emits lifecycle |
| 78 | // removals. This includes default scope when unscoped series disappear. |
| 79 | for key, state := range j.scopeStates { |
| 80 | if _, ok := scopes[key]; ok { |
| 81 | continue |
| 82 | } |
| 83 | scopes[key] = state.scope |
| 84 | } |
| 85 | return scopes |
| 86 | } |
| 87 | |
| 88 | func (j *JobV2) scopeHasVisibleSeries(scopeKey string) bool { |
| 89 | reader := j.store.Read(metrix.ReadFlatten(), metrix.ReadHostScope(scopeKey)) |
| 90 | found := false |
| 91 | reader.ForEachSeries(func(string, metrix.LabelView, metrix.SampleValue) { |
| 92 | found = true |
| 93 | }) |
| 94 | return found |
| 95 | } |
| 96 | |
| 97 | func sortedScopeKeys(scopes map[string]metrix.HostScope) []string { |
| 98 | keys := make([]string, 0, len(scopes)) |
| 99 | for key := range scopes { |
| 100 | keys = append(keys, key) |
| 101 | } |
| 102 | sortHostScopeKeys(keys) |
| 103 | return keys |
| 104 | } |
| 105 | |
| 106 | func sortedScopeStateKeys(scopes map[string]*jobV2ScopeState) []string { |
| 107 | keys := make([]string, 0, len(scopes)) |
| 108 | for key := range scopes { |
| 109 | keys = append(keys, key) |
| 110 | } |
| 111 | sortHostScopeKeys(keys) |
| 112 | return keys |
| 113 | } |
| 114 | |
| 115 | func sortHostScopeKeys(keys []string) { |
| 116 | sort.Slice(keys, func(i, j int) bool { |
| 117 | if keys[i] == defaultHostScopeKey { |
| 118 | return true |
| 119 | } |
| 120 | if keys[j] == defaultHostScopeKey { |
| 121 | return false |
| 122 | } |
| 123 | return keys[i] < keys[j] |
| 124 | }) |
| 125 | } |
| 126 | |
| 127 | func metrixHostScopeInfo(scope metrix.HostScope) netdataapi.HostInfo { |
| 128 | return netdataapi.HostInfo{ |
| 129 | GUID: scope.GUID, |
| 130 | Hostname: scope.Hostname, |
| 131 | Labels: scope.Labels, |
| 132 | } |
| 133 | } |