Skip to content

Commit 553adcc

Browse files
jiekunhagen1778
andauthored
fix metadata display for summary and empty record metrics (#99)
fix #98 1. hide metadata if the metrics itself is not printed. --------- Co-authored-by: hagen1778 <[email protected]>
1 parent 242cb37 commit 553adcc

File tree

4 files changed

+77
-8
lines changed

4 files changed

+77
-8
lines changed

metrics.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,10 @@ func WriteMetadataIfNeeded(w io.Writer, metricName, metricType string) {
338338
return
339339
}
340340
metricFamily := getMetricFamily(metricName)
341+
writeMetadata(w, metricFamily, metricType)
342+
}
343+
344+
func writeMetadata(w io.Writer, metricFamily, metricType string) {
341345
fmt.Fprintf(w, "# HELP %s\n", metricFamily)
342346
fmt.Fprintf(w, "# TYPE %s %s\n", metricFamily, metricType)
343347
}

set.go

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,22 @@ func (s *Set) WritePrometheus(w io.Writer) {
3737
// Collect all the metrics in in-memory buffer in order to prevent from long locking due to slow w.
3838
var bb bytes.Buffer
3939
lessFunc := func(i, j int) bool {
40+
// the sorting must be stable.
41+
// see edge cases why we can't simply do `s.a[i].name < s.a[j].name` here:
42+
// https://github.com/VictoriaMetrics/metrics/pull/99#issuecomment-3277072175
43+
44+
// sort by metric family name first, to group the same metric family in one place.
45+
fName1, fName2 := getMetricFamily(s.a[i].name), getMetricFamily(s.a[j].name)
46+
if fName1 != fName2 {
47+
return fName1 < fName2
48+
}
49+
50+
// stabilize the order for summary and quantiles.
51+
if s.a[i].metric.metricType() != s.a[j].metric.metricType() {
52+
return s.a[i].metric.metricType() < s.a[j].metric.metricType()
53+
}
54+
55+
// lastly by metric names, which is for quantiles and histogram buckets.
4056
return s.a[i].name < s.a[j].name
4157
}
4258
s.mu.Lock()
@@ -50,18 +66,34 @@ func (s *Set) WritePrometheus(w io.Writer) {
5066
metricsWriters := s.metricsWriters
5167
s.mu.Unlock()
5268

53-
prevMetricFamily := ""
69+
// metricsWithMetadataBuf is used to hold marshalTo temporary, and decide whether metadata is needed.
70+
// it will be written to `bb` at the end and then reset for next *namedMetric in for-loop.
71+
var metricsWithMetadataBuf bytes.Buffer
72+
var prevMetricFamily string
5473
for _, nm := range sa {
74+
if !isMetadataEnabled() {
75+
// Call marshalTo without the global lock, since certain metric types such as Gauge
76+
// can call a callback, which, in turn, can try calling s.mu.Lock again.
77+
nm.metric.marshalTo(nm.name, &bb)
78+
continue
79+
}
80+
81+
metricsWithMetadataBuf.Reset()
82+
// Call marshalTo without the global lock, since certain metric types such as Gauge
83+
// can call a callback, which, in turn, can try calling s.mu.Lock again.
84+
nm.metric.marshalTo(nm.name, &metricsWithMetadataBuf)
85+
if metricsWithMetadataBuf.Len() == 0 {
86+
continue
87+
}
88+
5589
metricFamily := getMetricFamily(nm.name)
5690
if metricFamily != prevMetricFamily {
57-
// write meta info only once per metric family
91+
// write metadata only once per metric family
5892
metricType := nm.metric.metricType()
59-
WriteMetadataIfNeeded(&bb, nm.name, metricType)
93+
writeMetadata(&bb, metricFamily, metricType)
6094
prevMetricFamily = metricFamily
6195
}
62-
// Call marshalTo without the global lock, since certain metric types such as Gauge
63-
// can call a callback, which, in turn, can try calling s.mu.Lock again.
64-
nm.metric.marshalTo(nm.name, &bb)
96+
bb.Write(metricsWithMetadataBuf.Bytes())
6597
}
6698
w.Write(bb.Bytes())
6799

set_example_test.go

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,13 @@ package metrics_test
33
import (
44
"bytes"
55
"fmt"
6+
67
"github.com/VictoriaMetrics/metrics"
78
)
89

910
func ExampleSet() {
11+
metrics.ExposeMetadata(false)
12+
1013
// Create a set with a counter
1114
s := metrics.NewSet()
1215
sc := s.NewCounter("set_counter")
@@ -30,8 +33,7 @@ func ExampleExposeMetadata() {
3033

3134
s := metrics.NewSet()
3235

33-
sc := s.NewCounter("set_counter")
34-
sc.Inc()
36+
s.NewCounter("set_counter").Inc()
3537

3638
s.NewGauge(`unused_bytes{foo="bar"}`, func() float64 { return 58 })
3739
s.NewGauge(`used_bytes{foo="bar"}`, func() float64 { return 42 })
@@ -43,6 +45,15 @@ func ExampleExposeMetadata() {
4345

4446
s.NewSummary("response_size_bytes").Update(1)
4547

48+
// response_size_bytes_extra_suffix name should verify that there are no collisions with response_size_bytes
49+
s.NewSummary("response_size_bytes_extra_suffix").Update(1)
50+
51+
// This summary should not exist in the output, same goes to its metadata.
52+
s.NewSummary(`test_summary_without_sample`)
53+
54+
// This summary will have quantiles printed before _sum/_count metrics
55+
s.NewSummary(`vm_request_duration_seconds{path="/api/v1/query_range"}`).Update(1)
56+
4657
// Dump metrics from s.
4758
var bb bytes.Buffer
4859
s.WritePrometheus(&bb)
@@ -65,6 +76,15 @@ func ExampleExposeMetadata() {
6576
// response_size_bytes{quantile="0.97"} 1
6677
// response_size_bytes{quantile="0.99"} 1
6778
// response_size_bytes{quantile="1"} 1
79+
// # HELP response_size_bytes_extra_suffix
80+
// # TYPE response_size_bytes_extra_suffix summary
81+
// response_size_bytes_extra_suffix_sum 1
82+
// response_size_bytes_extra_suffix_count 1
83+
// response_size_bytes_extra_suffix{quantile="0.5"} 1
84+
// response_size_bytes_extra_suffix{quantile="0.9"} 1
85+
// response_size_bytes_extra_suffix{quantile="0.97"} 1
86+
// response_size_bytes_extra_suffix{quantile="0.99"} 1
87+
// response_size_bytes_extra_suffix{quantile="1"} 1
6888
// # HELP set_counter
6989
// # TYPE set_counter counter
7090
// set_counter 1
@@ -75,4 +95,13 @@ func ExampleExposeMetadata() {
7595
// # TYPE used_bytes gauge
7696
// used_bytes{foo="bar"} 42
7797
// used_bytes{foo="baz"} 43
98+
// # HELP vm_request_duration_seconds
99+
// # TYPE vm_request_duration_seconds summary
100+
// vm_request_duration_seconds_sum{path="/api/v1/query_range"} 1
101+
// vm_request_duration_seconds_count{path="/api/v1/query_range"} 1
102+
// vm_request_duration_seconds{path="/api/v1/query_range",quantile="0.5"} 1
103+
// vm_request_duration_seconds{path="/api/v1/query_range",quantile="0.9"} 1
104+
// vm_request_duration_seconds{path="/api/v1/query_range",quantile="0.97"} 1
105+
// vm_request_duration_seconds{path="/api/v1/query_range",quantile="0.99"} 1
106+
// vm_request_duration_seconds{path="/api/v1/query_range",quantile="1"} 1
78107
}

summary.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,10 @@ func (qv *quantileValue) marshalTo(prefix string, w io.Writer) {
201201
}
202202

203203
func (qv *quantileValue) metricType() string {
204+
// this metricsType should not be printed, because summary (sum and count) of the same metric family will be printed first,
205+
// and if metadata is needed, the metadata from summary should be used.
206+
// quantile will be printed later, so its metrics type won't be printed as metadata.
207+
// See: https://github.com/VictoriaMetrics/metrics/pull/99
204208
return "unsupported"
205209
}
206210

0 commit comments

Comments
 (0)