@@ -41,12 +41,23 @@ type resourceKey struct {
41
41
job string
42
42
instance string
43
43
}
44
+
45
+ // The name of the metric family doesn't include magic suffixes (e.g. _bucket),
46
+ // so for a classic histgram and a native histogram of the same family, the
47
+ // metric family will be the same. To be able to tell them apart, we need to
48
+ // store whether the metric is a native histogram or not.
49
+ type metricFamilyKey struct {
50
+ isExponentialHistogram bool
51
+ name string
52
+ }
53
+
44
54
type transaction struct {
45
55
isNew bool
46
56
trimSuffixes bool
47
57
enableNativeHistograms bool
58
+ addingNativeHistogram bool // true if the last sample was a native histogram.
48
59
ctx context.Context
49
- families map [resourceKey ]map [scopeID ]map [string ]* metricFamily
60
+ families map [resourceKey ]map [scopeID ]map [metricFamilyKey ]* metricFamily
50
61
mc scrape.MetricMetadataStore
51
62
sink consumer.Metrics
52
63
externalLabels labels.Labels
@@ -79,7 +90,7 @@ func newTransaction(
79
90
) * transaction {
80
91
return & transaction {
81
92
ctx : ctx ,
82
- families : make (map [resourceKey ]map [scopeID ]map [string ]* metricFamily ),
93
+ families : make (map [resourceKey ]map [scopeID ]map [metricFamilyKey ]* metricFamily ),
83
94
isNew : true ,
84
95
trimSuffixes : trimSuffixes ,
85
96
enableNativeHistograms : enableNativeHistograms ,
@@ -97,6 +108,8 @@ func newTransaction(
97
108
98
109
// Append always returns 0 to disable label caching.
99
110
func (t * transaction ) Append (_ storage.SeriesRef , ls labels.Labels , atMs int64 , val float64 ) (storage.SeriesRef , error ) {
111
+ t .addingNativeHistogram = false
112
+
100
113
select {
101
114
case <- t .ctx .Done ():
102
115
return 0 , errTransactionAborted
@@ -157,15 +170,16 @@ func (t *transaction) Append(_ storage.SeriesRef, ls labels.Labels, atMs int64,
157
170
return 0 , nil
158
171
}
159
172
160
- curMF , existing := t . getOrCreateMetricFamily ( * rKey , getScopeID (ls ), metricName )
173
+ scope := getScopeID (ls )
161
174
162
- if t .enableNativeHistograms && curMF .mtype == pmetric .MetricTypeExponentialHistogram {
163
- // If a histogram has both classic and native version, the native histogram is scraped
164
- // first. Getting a float sample for the same series means that `scrape_classic_histogram`
165
- // is set to true in the scrape config. In this case, we should ignore the native histogram.
166
- curMF .mtype = pmetric .MetricTypeHistogram
175
+ if t .enableNativeHistograms && value .IsStaleNaN (val ) {
176
+ if t .detectAndStoreNativeHistogramStaleness (atMs , rKey , scope , metricName , ls ) {
177
+ return 0 , nil
178
+ }
167
179
}
168
180
181
+ curMF , existing := t .getOrCreateMetricFamily (* rKey , scope , metricName )
182
+
169
183
seriesRef := t .getSeriesRef (ls , curMF .mtype )
170
184
err = curMF .addSeries (seriesRef , metricName , ls , atMs , val )
171
185
if err != nil {
@@ -190,26 +204,64 @@ func (t *transaction) Append(_ storage.SeriesRef, ls labels.Labels, atMs int64,
190
204
return 0 , nil // never return errors, as that fails the whole scrape
191
205
}
192
206
207
+ // detectAndStoreNativeHistogramStaleness returns true if it detects
208
+ // and stores a native histogram staleness marker.
209
+ func (t * transaction ) detectAndStoreNativeHistogramStaleness (atMs int64 , key * resourceKey , scope scopeID , metricName string , ls labels.Labels ) bool {
210
+ // Detect the special case of stale native histogram series.
211
+ // Currently Prometheus does not store the histogram type in
212
+ // its staleness tracker.
213
+ md , ok := t .mc .GetMetadata (metricName )
214
+ if ! ok {
215
+ // Native histograms always have metadata.
216
+ return false
217
+ }
218
+ if md .Type != model .MetricTypeHistogram {
219
+ // Not a histogram.
220
+ return false
221
+ }
222
+ if md .Metric != metricName {
223
+ // Not a native histogram because it has magic suffixes (e.g. _bucket).
224
+ return false
225
+ }
226
+ // Store the staleness marker as a native histogram.
227
+ t .addingNativeHistogram = true
228
+
229
+ curMF , _ := t .getOrCreateMetricFamily (* key , scope , metricName )
230
+ seriesRef := t .getSeriesRef (ls , curMF .mtype )
231
+
232
+ _ = curMF .addExponentialHistogramSeries (seriesRef , metricName , ls , atMs , & histogram.Histogram {Sum : math .Float64frombits (value .StaleNaN )}, nil )
233
+ // ignore errors here, this is best effort.
234
+
235
+ return true
236
+ }
237
+
193
238
// getOrCreateMetricFamily returns the metric family for the given metric name and scope,
194
239
// and true if an existing family was found.
195
240
func (t * transaction ) getOrCreateMetricFamily (key resourceKey , scope scopeID , mn string ) (* metricFamily , bool ) {
196
241
if _ , ok := t .families [key ]; ! ok {
197
- t .families [key ] = make (map [scopeID ]map [string ]* metricFamily )
242
+ t .families [key ] = make (map [scopeID ]map [metricFamilyKey ]* metricFamily )
198
243
}
199
244
if _ , ok := t.families [key ][scope ]; ! ok {
200
- t.families [key ][scope ] = make (map [string ]* metricFamily )
245
+ t.families [key ][scope ] = make (map [metricFamilyKey ]* metricFamily )
201
246
}
202
247
203
- curMf , ok := t.families [key ][scope ][mn ]
248
+ mfKey := metricFamilyKey {isExponentialHistogram : t .addingNativeHistogram , name : mn }
249
+
250
+ curMf , ok := t.families [key ][scope ][mfKey ]
251
+
204
252
if ! ok {
205
253
fn := mn
206
254
if _ , ok := t .mc .GetMetadata (mn ); ! ok {
207
255
fn = normalizeMetricName (mn )
208
256
}
209
- mf , ok := t.families [key ][scope ][fn ]
257
+ fnKey := metricFamilyKey {isExponentialHistogram : mfKey .isExponentialHistogram , name : fn }
258
+ mf , ok := t.families [key ][scope ][fnKey ]
210
259
if ! ok || ! mf .includesMetric (mn ) {
211
260
curMf = newMetricFamily (mn , t .mc , t .logger )
212
- t.families [key ][scope ][curMf .name ] = curMf
261
+ if curMf .mtype == pmetric .MetricTypeHistogram && mfKey .isExponentialHistogram {
262
+ curMf .mtype = pmetric .MetricTypeExponentialHistogram
263
+ }
264
+ t.families [key ][scope ][metricFamilyKey {isExponentialHistogram : mfKey .isExponentialHistogram , name : curMf .name }] = curMf
213
265
return curMf , false
214
266
}
215
267
curMf = mf
@@ -257,6 +309,8 @@ func (t *transaction) AppendHistogram(_ storage.SeriesRef, ls labels.Labels, atM
257
309
default :
258
310
}
259
311
312
+ t .addingNativeHistogram = true
313
+
260
314
if t .externalLabels .Len () != 0 {
261
315
b := labels .NewBuilder (ls )
262
316
t .externalLabels .Range (func (l labels.Label ) {
@@ -286,13 +340,7 @@ func (t *transaction) AppendHistogram(_ storage.SeriesRef, ls labels.Labels, atM
286
340
// The `up`, `target_info`, `otel_scope_info` metrics should never generate native histograms,
287
341
// thus we don't check for them here as opposed to the Append function.
288
342
289
- curMF , existing := t .getOrCreateMetricFamily (* rKey , getScopeID (ls ), metricName )
290
- if ! existing {
291
- curMF .mtype = pmetric .MetricTypeExponentialHistogram
292
- } else if curMF .mtype != pmetric .MetricTypeExponentialHistogram {
293
- // Already scraped as classic histogram.
294
- return 0 , nil
295
- }
343
+ curMF , _ := t .getOrCreateMetricFamily (* rKey , getScopeID (ls ), metricName )
296
344
297
345
if h != nil && h .CounterResetHint == histogram .GaugeType || fh != nil && fh .CounterResetHint == histogram .GaugeType {
298
346
t .logger .Warn ("dropping unsupported gauge histogram datapoint" , zap .String ("metric_name" , metricName ), zap .Any ("labels" , ls ))
@@ -307,14 +355,14 @@ func (t *transaction) AppendHistogram(_ storage.SeriesRef, ls labels.Labels, atM
307
355
}
308
356
309
357
func (t * transaction ) AppendCTZeroSample (_ storage.SeriesRef , ls labels.Labels , atMs , ctMs int64 ) (storage.SeriesRef , error ) {
310
- return t .setCreationTimestamp (ls , atMs , ctMs , false )
358
+ return t .setCreationTimestamp (ls , atMs , ctMs )
311
359
}
312
360
313
361
func (t * transaction ) AppendHistogramCTZeroSample (_ storage.SeriesRef , ls labels.Labels , atMs , ctMs int64 , _ * histogram.Histogram , _ * histogram.FloatHistogram ) (storage.SeriesRef , error ) {
314
- return t .setCreationTimestamp (ls , atMs , ctMs , true )
362
+ return t .setCreationTimestamp (ls , atMs , ctMs )
315
363
}
316
364
317
- func (t * transaction ) setCreationTimestamp (ls labels.Labels , atMs , ctMs int64 , histogram bool ) (storage.SeriesRef , error ) {
365
+ func (t * transaction ) setCreationTimestamp (ls labels.Labels , atMs , ctMs int64 ) (storage.SeriesRef , error ) {
318
366
select {
319
367
case <- t .ctx .Done ():
320
368
return 0 , errTransactionAborted
@@ -347,16 +395,7 @@ func (t *transaction) setCreationTimestamp(ls labels.Labels, atMs, ctMs int64, h
347
395
return 0 , errMetricNameNotFound
348
396
}
349
397
350
- curMF , existing := t .getOrCreateMetricFamily (* rKey , getScopeID (ls ), metricName )
351
-
352
- if histogram {
353
- if ! existing {
354
- curMF .mtype = pmetric .MetricTypeExponentialHistogram
355
- } else if curMF .mtype != pmetric .MetricTypeExponentialHistogram {
356
- // Already scraped as classic histogram.
357
- return 0 , nil
358
- }
359
- }
398
+ curMF , _ := t .getOrCreateMetricFamily (* rKey , getScopeID (ls ), metricName )
360
399
361
400
seriesRef := t .getSeriesRef (ls , curMF .mtype )
362
401
curMF .addCreationTimestamp (seriesRef , ls , atMs , ctMs )
@@ -543,6 +582,7 @@ func (t *transaction) UpdateMetadata(_ storage.SeriesRef, _ labels.Labels, _ met
543
582
}
544
583
545
584
func (t * transaction ) AddTargetInfo (key resourceKey , ls labels.Labels ) {
585
+ t .addingNativeHistogram = false
546
586
if resource , ok := t .nodeResources [key ]; ok {
547
587
attrs := resource .Attributes ()
548
588
ls .Range (func (lbl labels.Label ) {
@@ -555,6 +595,7 @@ func (t *transaction) AddTargetInfo(key resourceKey, ls labels.Labels) {
555
595
}
556
596
557
597
func (t * transaction ) addScopeInfo (key resourceKey , ls labels.Labels ) {
598
+ t .addingNativeHistogram = false
558
599
attrs := pcommon .NewMap ()
559
600
scope := scopeID {}
560
601
ls .Range (func (lbl labels.Label ) {
0 commit comments