-
Notifications
You must be signed in to change notification settings - Fork 10
/
plan.go
131 lines (113 loc) · 3.9 KB
/
plan.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
// Copyright 2024 Block, Inc.
package blip
import (
"fmt"
"regexp"
"time"
)
// Plan represents different levels of metrics collection.
type Plan struct {
// Name is the name of the plan (required).
//
// When loaded from config.plans.files, Name is the exact name of the config.
// The first file is the default plan if config.plan.default is not specified.
//
// When loaded from a config.plans.table, Name is the name column. The name
// column cannot be NULL. The plan table is ordered by name (ascending) and
// the first plan is the default if config.plan.default is not specified.
//
// config.plan.adjust.readonly and .active refer to Name.
Name string
// Levels are the collection frequencies that constitue the plan (required).
Levels map[string]Level
// MonitorId is the optional monitorId column from a plan table.
//
// When default plans are loaded from a table (config.plans.table),
// the talbe is not filtered; all plans in the table are loaded.
//
// When a monitor (M) loads plans from a table (config.monitors.plans.table),
// the table is filtered: WHERE monitorId = config.monitors.M.id.
MonitorId string `yaml:"-"`
// Source of plan: file name, table name, "plugin", or "blip" (internal plans).
Source string `yaml:"-"`
}
// Level is one collection frequency in a plan.
type Level struct {
Name string `yaml:"-"`
Freq string `yaml:"freq"`
Collect map[string]Domain `yaml:"collect"`
}
// Domain is one metric domain for collecting related metrics.
type Domain struct {
Name string `yaml:"-"`
Metrics []string `yaml:"metrics,omitempty"`
Options map[string]string `yaml:"options,omitempty"`
Errors map[string]string `yaml:"errors,omitempty"`
}
const metricPattern = `^[a-zA-Z0-9_-]*$`
var validMetricRegex = regexp.MustCompile(metricPattern)
func (p Plan) Validate() error {
freqs := map[time.Duration]string{}
for levelName := range p.Levels {
// Validate freq: set, valid, and no duplicates
freq := p.Levels[levelName].Freq
if freq == "" {
return fmt.Errorf("at %s: freq not set (Go time duration string required)", levelName)
}
d, err := time.ParseDuration(freq)
if err != nil {
return fmt.Errorf("at %s: invalid freq: %s: %s", levelName, freq, err)
}
if firstLevelName, ok := freqs[d]; ok {
return fmt.Errorf("at %s: duplicate freq: %s (%s): first seen at %s", levelName, freq, d, firstLevelName)
}
freqs[d] = levelName
// Validate that every metric matches metricPattern (help prevent SQL injection)
for domainName := range p.Levels[levelName].Collect {
for _, metricName := range p.Levels[levelName].Collect[domainName].Metrics {
if !validMetricRegex.MatchString(metricName) {
return fmt.Errorf("at %s/%s: invalid metric: %s (does not match /%s/)",
levelName, domainName, metricName, metricPattern)
}
}
}
}
return nil
}
func (p Plan) Freq() (time.Duration, map[string]time.Duration) {
var min time.Duration
domain := map[string]time.Duration{}
for _, level := range p.Levels {
freqL, _ := time.ParseDuration(level.Freq) // already validated
if freqL < min || min == 0 {
min = freqL
}
for name := range level.Collect {
freqD, ok := domain[name]
if !ok {
domain[name] = freqL
} else if freqL < freqD || freqD == 0 {
domain[name] = freqL
}
}
}
return min, domain
}
func (p *Plan) InterpolateEnvVars() {
for levelName := range p.Levels {
for domainName := range p.Levels[levelName].Collect {
for k, v := range p.Levels[levelName].Collect[domainName].Options {
p.Levels[levelName].Collect[domainName].Options[k] = interpolateEnv(v)
}
}
}
}
func (p *Plan) InterpolateMonitor(mon *ConfigMonitor) {
for levelName := range p.Levels {
for domainName := range p.Levels[levelName].Collect {
for k, v := range p.Levels[levelName].Collect[domainName].Options {
p.Levels[levelName].Collect[domainName].Options[k] = mon.interpolateMon(v)
}
}
}
}