master
go 174 lines 4.66 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package azure_monitor
4
5 import (
6 "fmt"
7 "sort"
8 "strings"
9
10 "github.com/netdata/netdata/go/plugins/plugin/framework/charttpl"
11 "github.com/netdata/netdata/go/plugins/plugin/go.d/collector/azure_monitor/azureprofiles"
12 "gopkg.in/yaml.v3"
13 )
14
15 func buildCollectorRuntimeFromConfig(profileNames []string, profileEntries map[string]ProfileEntryConfig, catalog azureprofiles.Catalog, workloadResourceTagKey string) (*collectorRuntime, error) {
16 profiles, err := catalog.Resolve(profileNames)
17 if err != nil {
18 return nil, err
19 }
20
21 runtime := &collectorRuntime{
22 Profiles: make([]*profileRuntime, 0, len(profiles)),
23 WorkloadResourceTagKey: stringsLowerTrim(workloadResourceTagKey),
24 }
25
26 seenProfileNames := make(map[string]struct{}, len(profiles))
27 seenChartIDs := make(map[string]struct{})
28
29 for _, src := range profiles {
30 p, err := buildProfileRuntime(src, profileEntries[src.Name])
31 if err != nil {
32 return nil, err
33 }
34 if _, ok := seenProfileNames[p.Name]; ok {
35 return nil, fmt.Errorf("profile name collision for profile %q", src.Name)
36 }
37 seenProfileNames[p.Name] = struct{}{}
38
39 if err := walkCharts(p.Template, func(chart charttpl.Chart) error {
40 if _, ok := seenChartIDs[chart.ID]; ok {
41 return fmt.Errorf("chart id collision: %s", chart.ID)
42 }
43 seenChartIDs[chart.ID] = struct{}{}
44 return nil
45 }); err != nil {
46 return nil, err
47 }
48
49 runtime.Profiles = append(runtime.Profiles, p)
50 }
51
52 tpl, err := buildChartTemplate(runtime)
53 if err != nil {
54 return nil, err
55 }
56 runtime.ChartTemplateYAML = tpl
57
58 return runtime, nil
59 }
60
61 func buildProfileRuntime(resolved azureprofiles.ResolvedProfile, entry ProfileEntryConfig) (*profileRuntime, error) {
62 profileName := stringsTrim(resolved.Name)
63 if profileName == "" {
64 return nil, fmt.Errorf("profile has empty name")
65 }
66
67 displayName := stringsTrim(resolved.Config.DisplayName)
68 if displayName == "" {
69 return nil, fmt.Errorf("profile %q has empty display_name", profileName)
70 }
71
72 resourceType := stringsTrim(resolved.Config.ResourceType)
73 metricNamespace := stringsTrim(resolved.Config.MetricNamespace)
74 if metricNamespace == "" {
75 metricNamespace = resourceType
76 }
77
78 out := &profileRuntime{
79 Name: profileName,
80 DisplayName: displayName,
81 ResourceType: resourceType,
82 MetricNamespace: metricNamespace,
83 Filters: cloneResourceFilters(entry.Filters),
84 Metrics: make([]*metricRuntime, 0, len(resolved.Config.Metrics)),
85 }
86
87 for _, m := range resolved.Config.Metrics {
88 grain := strings.ToUpper(stringsTrim(m.TimeGrain))
89 if grain == "" {
90 grain = "PT1M"
91 }
92 grainEvery, ok := azureprofiles.SupportedTimeGrains[grain]
93 if !ok {
94 return nil, fmt.Errorf("profile %q metric %q has unsupported time grain %q", profileName, m.ID, grain)
95 }
96
97 mr := &metricRuntime{
98 ID: stringsTrim(m.ID),
99 AzureName: stringsTrim(m.AzureName),
100 TimeGrain: grain,
101 TimeGrainEvery: grainEvery,
102 Series: make([]*seriesRuntime, 0, len(m.Series)),
103 }
104
105 for _, s := range m.Series {
106 aggregation := azureprofiles.NormalizeAggregation(s.Aggregation)
107 mr.Series = append(mr.Series, &seriesRuntime{
108 Aggregation: aggregation,
109 Kind: azureprofiles.NormalizeSeriesKind(s.Kind),
110 Instrument: azureprofiles.ExportedSeriesName(profileName, m.ID, aggregation),
111 })
112 }
113
114 sort.Slice(mr.Series, func(i, j int) bool {
115 return mr.Series[i].Aggregation < mr.Series[j].Aggregation
116 })
117 out.Metrics = append(out.Metrics, mr)
118 }
119
120 sort.Slice(out.Metrics, func(i, j int) bool {
121 return out.Metrics[i].ID < out.Metrics[j].ID
122 })
123
124 out.Template = resolved.Config.Template
125 out.Template.Metrics = profileMetricsList(out)
126 return out, nil
127 }
128
129 func buildChartTemplate(runtime *collectorRuntime) (string, error) {
130 spec := charttpl.Spec{
131 Version: charttpl.VersionV1,
132 ContextNamespace: "azure_monitor",
133 Groups: make([]charttpl.Group, 0, len(runtime.Profiles)),
134 }
135
136 for _, p := range runtime.Profiles {
137 spec.Groups = append(spec.Groups, p.Template)
138 }
139
140 if err := spec.Validate(); err != nil {
141 return "", err
142 }
143
144 raw, err := yaml.Marshal(spec)
145 if err != nil {
146 return "", err
147 }
148 return string(raw), nil
149 }
150
151 func profileMetricsList(p *profileRuntime) []string {
152 list := make([]string, 0)
153 for _, metric := range p.Metrics {
154 for _, series := range metric.Series {
155 list = append(list, series.Instrument)
156 }
157 }
158 sort.Strings(list)
159 return list
160 }
161
162 func walkCharts(group charttpl.Group, fn func(charttpl.Chart) error) error {
163 for _, chart := range group.Charts {
164 if err := fn(chart); err != nil {
165 return err
166 }
167 }
168 for _, child := range group.Groups {
169 if err := walkCharts(child, fn); err != nil {
170 return err
171 }
172 }
173 return nil
174 }