@@ -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
0 commit comments