...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package prommatch
17
18 import (
19 "bytes"
20 "fmt"
21 "net"
22 "net/netip"
23 "regexp"
24
25 dto "github.com/prometheus/client_model/go"
26 "github.com/prometheus/common/expfmt"
27 "github.com/prometheus/common/model"
28 )
29
30
31 type Expression interface {
32 matches(sp *model.Sample) bool
33 }
34
35 type funcMatcher func(sp *model.Sample) bool
36
37 func (fc funcMatcher) matches(sp *model.Sample) bool {
38 return fc(sp)
39 }
40
41
42 func NewMatcher(name string, ms ...Expression) *Matcher {
43 return &Matcher{
44 expressions: append([]Expression{hasName(name)}, ms...),
45 }
46 }
47
48
49 type Matcher struct {
50 expressions []Expression
51 }
52
53
54
55
56
57 func (e *Matcher) HasMatchInString(s string) (bool, error) {
58 v, err := extractSamplesVectorFromString(s)
59 if err != nil {
60 return false, fmt.Errorf("failed to parse input string as vector of samples: %w", err)
61 }
62 return e.hasMatchInVector(v), nil
63 }
64
65 func (e *Matcher) hasMatchInVector(v model.Vector) bool {
66 for _, s := range v {
67 if e.sampleMatches(s) {
68 return true
69 }
70 }
71 return false
72 }
73
74 func (e *Matcher) sampleMatches(s *model.Sample) bool {
75 for _, m := range e.expressions {
76 if !m.matches(s) {
77 return false
78 }
79 }
80 return true
81 }
82
83
84 type LabelMatcher func(string) bool
85
86
87 type Labels map[string]LabelMatcher
88
89
90 var _ Expression = Labels{}
91
92 func (l Labels) matches(s *model.Sample) bool {
93 for k, m := range l {
94 labelValue := s.Metric[model.LabelName(k)]
95 if !m(string(labelValue)) {
96 return false
97 }
98 }
99 return true
100 }
101
102
103 func Equals(expected string) LabelMatcher {
104 return func(s string) bool {
105 return expected == s
106 }
107 }
108
109
110 func Like(re *regexp.Regexp) LabelMatcher {
111 return func(s string) bool {
112 return re.MatchString(s)
113 }
114 }
115
116
117 func Absent() LabelMatcher {
118 return func(s string) bool {
119 return s == ""
120 }
121 }
122
123
124 func Any() LabelMatcher {
125 return func(s string) bool {
126 return s != ""
127 }
128 }
129
130
131 func HasValueLike(f func(float64) bool) Expression {
132 return funcMatcher(func(sp *model.Sample) bool {
133 return f(float64(sp.Value))
134 })
135 }
136
137
138 func HasValueOf(f float64) Expression {
139 return funcMatcher(func(sp *model.Sample) bool {
140 return f == float64(sp.Value)
141 })
142 }
143
144
145 func HasPositiveValue() Expression {
146 return HasValueLike(func(f float64) bool {
147 return f > 0
148 })
149 }
150
151
152
153 func IsAddr() LabelMatcher {
154 return func(s string) bool {
155 if _, err := netip.ParseAddrPort(s); err != nil {
156 return false
157 }
158 return true
159 }
160 }
161
162
163 func IsIP() LabelMatcher {
164 return func(s string) bool {
165 return net.ParseIP(s) != nil
166 }
167 }
168
169 func hasName(metricName string) Expression {
170 return funcMatcher(func(sp *model.Sample) bool {
171 return sp.Metric[model.MetricNameLabel] == model.LabelValue(metricName)
172 })
173 }
174
175 func extractSamplesVectorFromString(s string) (model.Vector, error) {
176 bb := bytes.NewBufferString(s)
177
178 p := &expfmt.TextParser{}
179 metricFamilies, err := p.TextToMetricFamilies(bb)
180 if err != nil {
181 return nil, fmt.Errorf("failed to parse input as metrics: %w", err)
182 }
183 var mfs []*dto.MetricFamily
184 for _, m := range metricFamilies {
185 mfs = append(mfs, m)
186 }
187 v, err := expfmt.ExtractSamples(&expfmt.DecodeOptions{}, mfs...)
188 if err != nil {
189 return nil, fmt.Errorf("failed to extract samples from input: %w", err)
190 }
191 return v, nil
192 }
193
View as plain text