Skip to content

Commit c177b2b

Browse files
committed
rename invalid metrics by partial metrics
Signed-off-by: Augustin Husson <[email protected]>
1 parent a6b7c3d commit c177b2b

File tree

15 files changed

+126
-125
lines changed

15 files changed

+126
-125
lines changed

README.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -80,16 +80,16 @@ The tool provides an API endpoint, `/api/v1/metrics`, which returns the usage da
8080
You can used the following query parameter to filter the list returned:
8181

8282
* **metric_name**: when used, it will trigger a fuzzy search on the metric_name based on the pattern provided.
83-
* **used**: when used, will return only the metric used or not (depending if you set this boolean to true or to false). Leave it empty if you want both.
84-
* **merge_invalid_metrics**: when used, it will use the data from /api/v1/invalid_metrics and merge them here.
83+
* **used**: when used, will return only the metric used or not (depending on if you set this boolean to true or to false). Leave it empty if you want both.
84+
* **merge_invalid_metrics**: when used, it will use the data from /api/v1/partial_metrics and merge them here.
8585

86-
### Invalid Metrics
86+
### Partial Metrics
8787

88-
The API endpoint `/api/v1/invalid_metrics` is exposing the usage for metrics that contains variable or regexp.
88+
The API endpoint `/api/v1/partial_metrics` is exposing the usage for metrics that contains variable or regexp.
8989

9090
```json
9191
{
92-
"node_cpu_utilization_${instance}": {
92+
"node_disk_discard_time_.+": {
9393
"usage": {
9494
"alertRules": [
9595
{
@@ -101,7 +101,7 @@ The API endpoint `/api/v1/invalid_metrics` is exposing the usage for metrics tha
101101
]
102102
}
103103
},
104-
"node_disk_discard_time_.+": {
104+
"node_cpu_utilization_${instance}": {
105105
"usage": {
106106
"dashboards": [
107107
"https://demo.perses.dev/api/v1/projects/perses/dashboards/nodeexporterfull"

database/database.go

Lines changed: 39 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -34,29 +34,29 @@ var replaceVariableRegexp = regexp.MustCompile(`\$\{[a-zA-Z0-9_:]+}`)
3434
type Database interface {
3535
GetMetric(name string) *v1.Metric
3636
ListMetrics() (map[string]*v1.Metric, error)
37-
ListInvalidMetrics() (map[string]*v1.InvalidMetric, error)
37+
ListPartialMetrics() (map[string]*v1.PartialMetric, error)
3838
ListPendingUsage() map[string]*v1.MetricUsage
3939
EnqueueMetricList(metrics []string)
40-
EnqueueInvalidMetricsUsage(usages map[string]*v1.MetricUsage)
40+
EnqueuePartialMetricsUsage(usages map[string]*v1.MetricUsage)
4141
EnqueueUsage(usages map[string]*v1.MetricUsage)
4242
EnqueueLabels(labels map[string][]string)
4343
}
4444

4545
func New(cfg config.Database) Database {
4646
d := &db{
4747
metrics: make(map[string]*v1.Metric),
48-
invalidMetrics: make(map[string]*v1.InvalidMetric),
48+
partialMetrics: make(map[string]*v1.PartialMetric),
4949
usage: make(map[string]*v1.MetricUsage),
5050
usageQueue: make(chan map[string]*v1.MetricUsage, 250),
51-
invalidMetricsUsageQueue: make(chan map[string]*v1.MetricUsage, 250),
51+
partialMetricsUsageQueue: make(chan map[string]*v1.MetricUsage, 250),
5252
labelsQueue: make(chan map[string][]string, 250),
5353
metricsQueue: make(chan []string, 10),
5454
path: cfg.Path,
5555
}
5656

5757
go d.watchUsageQueue()
5858
go d.watchMetricsQueue()
59-
go d.watchInvalidMetricsUsageQueue()
59+
go d.watchPartialMetricsUsageQueue()
6060
go d.watchLabelsQueue()
6161
if !*cfg.InMemory {
6262
if err := d.readMetricsInJSONFile(); err != nil {
@@ -72,8 +72,8 @@ type db struct {
7272
// metrics is the list of metric name (as a key) associated to their usage based on the different collector activated.
7373
// This struct is our "database".
7474
metrics map[string]*v1.Metric
75-
// invalidMetrics is the list of metric name that likely contains a variable or a regexp and as such cannot be a valid metric name.
76-
invalidMetrics map[string]*v1.InvalidMetric
75+
// partialMetrics is the list of metric name that likely contains a variable or a regexp and as such cannot be a valid metric name.
76+
partialMetrics map[string]*v1.PartialMetric
7777
// usage is a buffer in case the metric name has not yet been collected
7878
usage map[string]*v1.MetricUsage
7979
// metricsQueue is the channel that should be used to send and receive the list of metric name to keep in memory.
@@ -87,10 +87,10 @@ type db struct {
8787
// There will be no other way to write in it.
8888
// Doing that allows us to accept more HTTP requests to write data and to delay the actual writing.
8989
usageQueue chan map[string]*v1.MetricUsage
90-
// invalidMetricsUsageQueue is the way to send the usage per metric that is not valid to write in the database.
90+
// partialMetricsUsageQueue is the way to send the usage per metric that is not valid to write in the database.
9191
// There will be no other way to write in it.
9292
// Doing that allows us to accept more HTTP requests to write data and to delay the actual writing.
93-
invalidMetricsUsageQueue chan map[string]*v1.MetricUsage
93+
partialMetricsUsageQueue chan map[string]*v1.MetricUsage
9494
// path is the path to the JSON file where metrics is flushed periodically
9595
// It is empty if the database is purely in memory.
9696
path string
@@ -102,7 +102,7 @@ type db struct {
102102
// 2. Read the file directly when a read query is coming
103103
// Like that we have two different ways to read and write the data.
104104
metricsMutex sync.Mutex
105-
invalidMetricsUsageMutex sync.Mutex
105+
partialMetricsUsageMutex sync.Mutex
106106
}
107107

108108
func (d *db) GetMetric(name string) *v1.Metric {
@@ -117,10 +117,10 @@ func (d *db) ListMetrics() (map[string]*v1.Metric, error) {
117117
return deep.Copy(d.metrics)
118118
}
119119

120-
func (d *db) ListInvalidMetrics() (map[string]*v1.InvalidMetric, error) {
121-
d.invalidMetricsUsageMutex.Lock()
122-
defer d.invalidMetricsUsageMutex.Unlock()
123-
return deep.Copy(d.invalidMetrics)
120+
func (d *db) ListPartialMetrics() (map[string]*v1.PartialMetric, error) {
121+
d.partialMetricsUsageMutex.Lock()
122+
defer d.partialMetricsUsageMutex.Unlock()
123+
return deep.Copy(d.partialMetrics)
124124
}
125125

126126
func (d *db) EnqueueMetricList(metrics []string) {
@@ -137,8 +137,8 @@ func (d *db) EnqueueUsage(usages map[string]*v1.MetricUsage) {
137137
d.usageQueue <- usages
138138
}
139139

140-
func (d *db) EnqueueInvalidMetricsUsage(usages map[string]*v1.MetricUsage) {
141-
d.invalidMetricsUsageQueue <- usages
140+
func (d *db) EnqueuePartialMetricsUsage(usages map[string]*v1.MetricUsage) {
141+
d.partialMetricsUsageQueue <- usages
142142
}
143143

144144
func (d *db) EnqueueLabels(labels map[string][]string) {
@@ -167,22 +167,22 @@ func (d *db) watchMetricsQueue() {
167167
}
168168
}
169169

170-
func (d *db) watchInvalidMetricsUsageQueue() {
171-
for data := range d.invalidMetricsUsageQueue {
172-
d.invalidMetricsUsageMutex.Lock()
170+
func (d *db) watchPartialMetricsUsageQueue() {
171+
for data := range d.partialMetricsUsageQueue {
172+
d.partialMetricsUsageMutex.Lock()
173173
for metricName, usage := range data {
174-
if _, ok := d.invalidMetrics[metricName]; !ok {
175-
re, matchingMetrics := d.matchInvalidMetric(metricName)
176-
d.invalidMetrics[metricName] = &v1.InvalidMetric{
174+
if _, ok := d.partialMetrics[metricName]; !ok {
175+
re, matchingMetrics := d.matchPartialMetric(metricName)
176+
d.partialMetrics[metricName] = &v1.PartialMetric{
177177
Usage: usage,
178178
MatchingMetrics: matchingMetrics,
179179
MatchingRegexp: re,
180180
}
181181
} else {
182-
d.invalidMetrics[metricName].Usage = v1.MergeUsage(d.invalidMetrics[metricName].Usage, usage)
182+
d.partialMetrics[metricName].Usage = v1.MergeUsage(d.partialMetrics[metricName].Usage, usage)
183183
}
184184
}
185-
d.invalidMetricsUsageMutex.Unlock()
185+
d.partialMetricsUsageMutex.Unlock()
186186
}
187187
}
188188

@@ -253,10 +253,10 @@ func (d *db) readMetricsInJSONFile() error {
253253
return json.Unmarshal(data, &d.metrics)
254254
}
255255

256-
func (d *db) matchInvalidMetric(invalidMetric string) (*common.Regexp, v1.Set[string]) {
257-
re, err := generateRegexp(invalidMetric)
256+
func (d *db) matchPartialMetric(partialMetric string) (*common.Regexp, v1.Set[string]) {
257+
re, err := generateRegexp(partialMetric)
258258
if err != nil {
259-
logrus.WithError(err).Errorf("unable to compile the invalid metric name %q into a regexp", invalidMetric)
259+
logrus.WithError(err).Errorf("unable to compile the partial metric name %q into a regexp", partialMetric)
260260
return nil, nil
261261
}
262262
if re == nil {
@@ -274,45 +274,45 @@ func (d *db) matchInvalidMetric(invalidMetric string) (*common.Regexp, v1.Set[st
274274
}
275275

276276
func (d *db) matchValidMetric(validMetric string) {
277-
d.invalidMetricsUsageMutex.Lock()
278-
defer d.invalidMetricsUsageMutex.Unlock()
279-
for metricName, invalidMetric := range d.invalidMetrics {
280-
re := invalidMetric.MatchingRegexp
277+
d.partialMetricsUsageMutex.Lock()
278+
defer d.partialMetricsUsageMutex.Unlock()
279+
for metricName, partialMetric := range d.partialMetrics {
280+
re := partialMetric.MatchingRegexp
281281
if re == nil {
282282
var err error
283283
re, err = generateRegexp(metricName)
284284
if err != nil {
285-
logrus.WithError(err).Errorf("unable to compile the invalid metric name %q into a regexp", metricName)
285+
logrus.WithError(err).Errorf("unable to compile the partial metric name %q into a regexp", metricName)
286286
continue
287287
}
288-
invalidMetric.MatchingRegexp = re
288+
partialMetric.MatchingRegexp = re
289289
if re == nil {
290290
continue
291291
}
292292
}
293293
if re.MatchString(validMetric) {
294-
matchingMetrics := invalidMetric.MatchingMetrics
294+
matchingMetrics := partialMetric.MatchingMetrics
295295
if matchingMetrics == nil {
296296
matchingMetrics = v1.NewSet[string]()
297-
invalidMetric.MatchingMetrics = matchingMetrics
297+
partialMetric.MatchingMetrics = matchingMetrics
298298
}
299299
matchingMetrics.Add(validMetric)
300300
}
301301
}
302302
}
303303

304-
// GenerateRegexp is taking an invalid metric name,
304+
// GenerateRegexp is taking an partial metric name,
305305
// will replace every variable by a pattern and then returning a regepx if the final string is not just equal to .*.
306-
func generateRegexp(invalidMetricName string) (*common.Regexp, error) {
306+
func generateRegexp(partialMetricName string) (*common.Regexp, error) {
307307
// The first step is to replace every variable by a single special char.
308308
// We are using a special single char because it will be easier to find if these chars are continuous
309309
// or if there are other characters in between.
310-
s := replaceVariableRegexp.ReplaceAllString(invalidMetricName, "#")
310+
s := replaceVariableRegexp.ReplaceAllString(partialMetricName, "#")
311311
s = strings.ReplaceAll(s, ".+", "#")
312312
s = strings.ReplaceAll(s, ".*", "#")
313313
if s == "#" || len(s) == 0 {
314314
// This means the metric name is just a variable and as such can match all metric.
315-
// So it's basically impossible to know what this invalid metric name is covering/matching.
315+
// So it's basically impossible to know what this partial metric name is covering/matching.
316316
return nil, nil
317317
}
318318
// The next step is to contact every continuous special char '#' to a single one.

database/database_test.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,39 +28,39 @@ func newRegexp(re string) *common.Regexp {
2828
func TestGenerateRegexp(t *testing.T) {
2929
tests := []struct {
3030
title string
31-
invalidMetric string
31+
partialMetric string
3232
result *common.Regexp
3333
}{
3434
{
3535
title: "metric equal to a variable",
36-
invalidMetric: "${metric}",
36+
partialMetric: "${metric}",
3737
result: nil,
3838
},
3939
{
4040
title: "metric with variable a suffix",
41-
invalidMetric: "otelcol_exporter_enqueue_failed_log_records${suffix}",
41+
partialMetric: "otelcol_exporter_enqueue_failed_log_records${suffix}",
4242
result: newRegexp(`^otelcol_exporter_enqueue_failed_log_records.+$`),
4343
},
4444
{
4545
title: "metric with multiple variable 1",
46-
invalidMetric: "${foo}${bar}${john}${doe}",
46+
partialMetric: "${foo}${bar}${john}${doe}",
4747
result: nil,
4848
},
4949
{
5050
title: "metric with multiple variable 2",
51-
invalidMetric: "prefix_${foo}${bar}:collection_${collection}_suffix:${john}${doe}",
51+
partialMetric: "prefix_${foo}${bar}:collection_${collection}_suffix:${john}${doe}",
5252
result: newRegexp(`^prefix_.+:collection_.+_suffix:.+$`),
5353
},
5454
{
5555
title: "metric no variable",
56-
invalidMetric: "otelcol_receiver_.+",
56+
partialMetric: "otelcol_receiver_.+",
5757
result: newRegexp(`^otelcol_receiver_.+$`),
5858
},
5959
}
6060

6161
for _, test := range tests {
6262
t.Run(test.title, func(t *testing.T) {
63-
re, err := generateRegexp(test.invalidMetric)
63+
re, err := generateRegexp(test.partialMetric)
6464
assert.NoError(t, err)
6565
assert.Equal(t, test.result, re)
6666
})

pkg/analyze/grafana/grafana.go

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -165,22 +165,22 @@ func Analyze(dashboard *SimplifiedDashboard) (modelAPIV1.Set[string], modelAPIV1
165165
func extractMetricsFromPanels(panels []Panel, staticVariables *strings.Replacer, allVariableNames modelAPIV1.Set[string], dashboard *SimplifiedDashboard) (modelAPIV1.Set[string], modelAPIV1.Set[string], []*modelAPIV1.LogError) {
166166
var errs []*modelAPIV1.LogError
167167
result := modelAPIV1.Set[string]{}
168-
invalidMetricsResult := modelAPIV1.Set[string]{}
168+
partialMetricsResult := modelAPIV1.Set[string]{}
169169
for _, p := range panels {
170170
for _, t := range extractTarget(p) {
171171
if len(t.Expr) == 0 {
172172
continue
173173
}
174174
exprWithVariableReplaced := replaceVariables(t.Expr, staticVariables)
175-
metrics, invalidMetrics, err := prometheus.AnalyzePromQLExpression(exprWithVariableReplaced)
175+
metrics, partialMetrics, err := prometheus.AnalyzePromQLExpression(exprWithVariableReplaced)
176176
if err != nil {
177177
otherMetrics := parser.ExtractMetricNameWithVariable(exprWithVariableReplaced)
178178
if len(otherMetrics) > 0 {
179179
for m := range otherMetrics {
180180
if prometheus.IsValidMetricName(m) {
181181
result.Add(m)
182182
} else {
183-
invalidMetricsResult.Add(formatVariableInMetricName(m, allVariableNames))
183+
partialMetricsResult.Add(formatVariableInMetricName(m, allVariableNames))
184184
}
185185
}
186186
} else {
@@ -191,24 +191,25 @@ func extractMetricsFromPanels(panels []Panel, staticVariables *strings.Replacer,
191191
}
192192
} else {
193193
result.Merge(metrics)
194-
invalidMetricsResult.Merge(invalidMetrics)
194+
partialMetricsResult.Merge(partialMetrics)
195195
}
196196
}
197197
}
198-
return result, invalidMetricsResult, errs
198+
return result, partialMetricsResult, errs
199199
}
200200

201201
func extractMetricsFromVariables(variables []templateVar, staticVariables *strings.Replacer, allVariableNames modelAPIV1.Set[string], dashboard *SimplifiedDashboard) (modelAPIV1.Set[string], modelAPIV1.Set[string], []*modelAPIV1.LogError) {
202202
var errs []*modelAPIV1.LogError
203203
result := modelAPIV1.Set[string]{}
204-
invalidMetricsResult := modelAPIV1.Set[string]{}
204+
partialMetricsResult := modelAPIV1.Set[string]{}
205205
for _, v := range variables {
206206
if v.Type != "query" {
207207
continue
208208
}
209209
query, err := v.extractQueryFromVariableTemplating()
210210
if err != nil {
211-
// It appears when there is an issue, we cannot do anything about it actually and usually the variable is not the one we are looking for.
211+
// It appears when there is an issue, we cannot do anything about it,
212+
// and usually the variable is not the one we are looking for.
212213
// So we just log it as a warning
213214
errs = append(errs, &modelAPIV1.LogError{
214215
Warning: err,
@@ -232,21 +233,21 @@ func extractMetricsFromVariables(variables []templateVar, staticVariables *strin
232233
query = queryResultRegexp.FindStringSubmatch(query)[1]
233234
// metrics(.*partial_metric_name)
234235
} else if metricsRegexp.MatchString(query) {
235-
// for this particular use case, the query is an invalid metric names so there is no need to use the PromQL parser.
236+
// for this particular use case, the query is a partial metric names so there is no need to use the PromQL parser.
236237
query = metricsRegexp.FindStringSubmatch(query)[1]
237-
invalidMetricsResult.Add(formatVariableInMetricName(query, allVariableNames))
238+
partialMetricsResult.Add(formatVariableInMetricName(query, allVariableNames))
238239
continue
239240
}
240241
exprWithVariableReplaced := replaceVariables(query, staticVariables)
241-
metrics, invalidMetrics, err := prometheus.AnalyzePromQLExpression(exprWithVariableReplaced)
242+
metrics, partialMetrics, err := prometheus.AnalyzePromQLExpression(exprWithVariableReplaced)
242243
if err != nil {
243244
otherMetrics := parser.ExtractMetricNameWithVariable(exprWithVariableReplaced)
244245
if len(otherMetrics) > 0 {
245246
for m := range otherMetrics {
246247
if prometheus.IsValidMetricName(m) {
247248
result.Add(m)
248249
} else {
249-
invalidMetricsResult.Add(formatVariableInMetricName(m, allVariableNames))
250+
partialMetricsResult.Add(formatVariableInMetricName(m, allVariableNames))
250251
}
251252
}
252253
} else {
@@ -257,10 +258,10 @@ func extractMetricsFromVariables(variables []templateVar, staticVariables *strin
257258
}
258259
} else {
259260
result.Merge(metrics)
260-
invalidMetricsResult.Merge(invalidMetrics)
261+
partialMetricsResult.Merge(partialMetrics)
261262
}
262263
}
263-
return result, invalidMetricsResult, errs
264+
return result, partialMetricsResult, errs
264265
}
265266

266267
func extractStaticVariables(variables []templateVar) map[string]string {

pkg/analyze/grafana/grafana_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,9 +121,9 @@ func TestAnalyze(t *testing.T) {
121121
if err != nil {
122122
t.Fatal(err)
123123
}
124-
metrics, invalidMetrics, errs := Analyze(dashboard)
124+
metrics, partialMetrics, errs := Analyze(dashboard)
125125
metricsAsSlice := metrics.TransformAsSlice()
126-
invalidMetricsAsSlice := invalidMetrics.TransformAsSlice()
126+
invalidMetricsAsSlice := partialMetrics.TransformAsSlice()
127127
slices.Sort(metricsAsSlice)
128128
slices.Sort(invalidMetricsAsSlice)
129129
assert.Equal(t, tt.resultMetrics, metricsAsSlice)

0 commit comments

Comments
 (0)