master
go 158 lines 4.51 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package chartengine
4
5 import (
6 "fmt"
7 "sort"
8 "strings"
9
10 "github.com/netdata/netdata/go/plugins/pkg/metrix"
11 routecache "github.com/netdata/netdata/go/plugins/plugin/framework/chartengine/internal/cache"
12 "github.com/netdata/netdata/go/plugins/plugin/framework/chartengine/internal/program"
13 )
14
15 type routeBinding struct {
16 ChartTemplateID string
17 ChartID string
18 DimensionIndex int
19 DimensionName string
20 DimensionKeyLabel string
21 Algorithm program.Algorithm
22 Hidden bool
23 Multiplier int
24 Divisor int
25 Float bool
26 Static bool
27 Inferred bool
28 Autogen bool
29 Meta program.ChartMeta
30 Lifecycle program.LifecyclePolicy
31 }
32
33 type routeCandidate struct {
34 chartTemplateID string
35 dimensionIndex int
36 dimension program.Dimension
37 }
38
39 type matchIndex struct {
40 chartsByID map[string]program.Chart
41 byMetricName map[string][]routeCandidate
42 wildcardMatchers []routeCandidate
43 }
44
45 func buildMatchIndex(charts []program.Chart) matchIndex {
46 index := matchIndex{
47 chartsByID: make(map[string]program.Chart, len(charts)),
48 byMetricName: make(map[string][]routeCandidate),
49 wildcardMatchers: make([]routeCandidate, 0),
50 }
51
52 for _, chart := range charts {
53 index.chartsByID[chart.TemplateID] = chart
54 for i, dim := range chart.Dimensions {
55 candidate := routeCandidate{
56 chartTemplateID: chart.TemplateID,
57 dimensionIndex: i,
58 dimension: dim,
59 }
60 if len(dim.Selector.MetricNames) == 0 {
61 index.wildcardMatchers = append(index.wildcardMatchers, candidate)
62 continue
63 }
64 for _, metricName := range dim.Selector.MetricNames {
65 index.byMetricName[metricName] = append(index.byMetricName[metricName], candidate)
66 }
67 }
68 }
69
70 return index
71 }
72
73 type routeCache = routecache.RouteCache[routeBinding]
74
75 func newRouteCache() *routeCache {
76 return routecache.NewRouteCache[routeBinding]()
77 }
78
79 func (e *Engine) resolveSeriesRoutes(
80 cache *routeCache,
81 identity metrix.SeriesIdentity,
82 name string,
83 labels metrix.LabelView,
84 meta metrix.SeriesMeta,
85 index matchIndex,
86 revision uint64,
87 buildSeq uint64,
88 ) ([]routeBinding, bool, error) {
89 if cache == nil {
90 return nil, false, fmt.Errorf("chartengine: route cache is not initialized")
91 }
92
93 if cached, ok := cache.Lookup(identity, revision, buildSeq); ok {
94 return cached, true, nil
95 }
96
97 candidates := make([]routeCandidate, 0, len(index.byMetricName[name])+len(index.wildcardMatchers))
98 candidates = append(candidates, index.byMetricName[name]...)
99 candidates = append(candidates, index.wildcardMatchers...)
100
101 routes := make([]routeBinding, 0)
102 for _, candidate := range candidates {
103 if !candidate.dimension.Selector.Matcher.Matches(name, labels) {
104 continue
105 }
106 chart, ok := index.chartsByID[candidate.chartTemplateID]
107 if !ok {
108 return nil, false, fmt.Errorf("chartengine: route references unknown chart template %q", candidate.chartTemplateID)
109 }
110 chartID, ok, err := renderChartInstanceIDFromView(chart.Identity, labels)
111 if err != nil {
112 return nil, false, err
113 }
114 if !ok || strings.TrimSpace(chartID) == "" {
115 continue
116 }
117 dimName, dimKeyLabel, ok, err := resolveDimensionName(candidate.dimension, name, labels, meta)
118 if err != nil {
119 return nil, false, err
120 }
121 if !ok {
122 continue
123 }
124 routes = append(routes, routeBinding{
125 ChartTemplateID: candidate.chartTemplateID,
126 ChartID: chartID,
127 DimensionIndex: candidate.dimensionIndex,
128 DimensionName: dimName,
129 DimensionKeyLabel: dimKeyLabel,
130 Algorithm: chart.Meta.Algorithm,
131 Hidden: candidate.dimension.Hidden,
132 Multiplier: candidate.dimension.Multiplier,
133 Divisor: candidate.dimension.Divisor,
134 Float: candidate.dimension.Float,
135 Static: !candidate.dimension.Dynamic,
136 Inferred: candidate.dimension.InferNameFromSeriesMeta,
137 Autogen: false,
138 Meta: chart.Meta,
139 Lifecycle: chart.Lifecycle,
140 })
141 }
142
143 sort.Slice(routes, func(i, j int) bool {
144 if routes[i].ChartID != routes[j].ChartID {
145 return routes[i].ChartID < routes[j].ChartID
146 }
147 if routes[i].ChartTemplateID != routes[j].ChartTemplateID {
148 return routes[i].ChartTemplateID < routes[j].ChartTemplateID
149 }
150 if routes[i].DimensionIndex != routes[j].DimensionIndex {
151 return routes[i].DimensionIndex < routes[j].DimensionIndex
152 }
153 return routes[i].DimensionName < routes[j].DimensionName
154 })
155
156 cache.Store(identity, revision, buildSeq, routes)
157 return routes, false, nil
158 }