-
Notifications
You must be signed in to change notification settings - Fork 9
/
metric.go
165 lines (135 loc) · 4.87 KB
/
metric.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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
package metrics
import (
"fmt"
"io"
"regexp"
"sort"
"strings"
vm "github.com/VictoriaMetrics/metrics"
"github.com/safing/portbase/api"
"github.com/safing/portbase/config"
)
// PrometheusFormatRequirement is required format defined by prometheus for
// metric and label names.
const (
prometheusBaseFormt = "[a-zA-Z_][a-zA-Z0-9_]*"
PrometheusFormatRequirement = "^" + prometheusBaseFormt + "$"
)
var prometheusFormat = regexp.MustCompile(PrometheusFormatRequirement)
// Metric represents one or more metrics.
type Metric interface {
ID() string
LabeledID() string
Opts() *Options
WritePrometheus(w io.Writer)
}
type metricBase struct {
Identifier string
Labels map[string]string
LabeledIdentifier string
Options *Options
set *vm.Set
}
// Options can be used to set advanced metric settings.
type Options struct {
// Name defines an optional human readable name for the metric.
Name string
// InternalID specifies an alternative internal ID that will be used when
// exposing the metric via the API in a structured format.
InternalID string
// AlertLimit defines an upper limit that triggers an alert.
AlertLimit float64
// AlertTimeframe defines an optional timeframe in seconds for which the
// AlertLimit should be interpreted in.
AlertTimeframe float64
// Permission defines the permission that is required to read the metric.
Permission api.Permission
// ExpertiseLevel defines the expertise level that the metric is meant for.
ExpertiseLevel config.ExpertiseLevel
// Persist enabled persisting the metric on shutdown and loading the previous
// value at start. This is only supported for counters.
Persist bool
}
func newMetricBase(id string, labels map[string]string, opts Options) (*metricBase, error) {
// Check formats.
if !prometheusFormat.MatchString(strings.ReplaceAll(id, "/", "_")) {
return nil, fmt.Errorf("metric name %q must match %s", id, PrometheusFormatRequirement)
}
for labelName := range labels {
if !prometheusFormat.MatchString(labelName) {
return nil, fmt.Errorf("metric label name %q must match %s", labelName, PrometheusFormatRequirement)
}
}
// Check permission.
if opts.Permission < api.PermitAnyone {
// Default to PermitUser.
opts.Permission = api.PermitUser
}
// Ensure that labels is a map.
if labels == nil {
labels = make(map[string]string)
}
// Create metric base.
base := &metricBase{
Identifier: id,
Labels: labels,
Options: &opts,
set: vm.NewSet(),
}
base.LabeledIdentifier = base.buildLabeledID()
return base, nil
}
// ID returns the given ID of the metric.
func (m *metricBase) ID() string {
return m.Identifier
}
// LabeledID returns the Prometheus-compatible labeled ID of the metric.
func (m *metricBase) LabeledID() string {
return m.LabeledIdentifier
}
// Opts returns the metric options. They may not be modified.
func (m *metricBase) Opts() *Options {
return m.Options
}
// WritePrometheus writes the metric in the prometheus format to the given writer.
func (m *metricBase) WritePrometheus(w io.Writer) {
m.set.WritePrometheus(w)
}
func (m *metricBase) buildLabeledID() string {
// Because we use the namespace and the global flags here, we need to flag
// them as immutable.
registryLock.Lock()
defer registryLock.Unlock()
firstMetricRegistered = true
// Build ID from Identifier.
metricID := strings.TrimSpace(strings.ReplaceAll(m.Identifier, "/", "_"))
// Add namespace to ID.
if metricNamespace != "" {
metricID = metricNamespace + "_" + metricID
}
// Return now if no labels are defined.
if len(globalLabels) == 0 && len(m.Labels) == 0 {
return metricID
}
// Add global labels to the custom ones, if they don't exist yet.
for labelName, labelValue := range globalLabels {
if _, ok := m.Labels[labelName]; !ok {
m.Labels[labelName] = labelValue
}
}
// Render labels into a slice and sort them in order to make the labeled ID
// reproducible.
labels := make([]string, 0, len(m.Labels))
for labelName, labelValue := range m.Labels {
labels = append(labels, fmt.Sprintf("%s=%q", labelName, labelValue))
}
sort.Strings(labels)
// Return fully labaled ID.
return fmt.Sprintf("%s{%s}", metricID, strings.Join(labels, ","))
}
// Split metrics into sets, according to the API Auth Levels, which will also correspond to the UI Mode levels. SPN // nodes will also allow public access to metrics with the permission "PermitAnyone".
// Save "life-long" metrics on shutdown and load them at start.
// Generate the correct metric name and labels.
// Expose metrics via http, but also via the runtime DB in order to push metrics to the UI.
// The UI will have to parse the prometheus metrics format and will not be able to immediately present historical data, // but data will have to be built.
// Provide the option to push metrics to a prometheus push gateway, this is especially helpful when gathering data from // loads of SPN nodes.