@@ -66,12 +66,18 @@ func init() {
66
66
// pre-registered.
67
67
func NewRegistry () * Registry {
68
68
return & Registry {
69
- collectorsByID : map [uint64 ]Collector {},
70
- descIDs : map [uint64 ]struct {}{},
71
- dimHashesByName : map [string ]uint64 {},
69
+ collectorsByID : map [uint64 ]Collector {},
70
+ collectorsByEscapedID : map [uint64 ]Collector {},
71
+ descIDs : map [uint64 ]struct {}{},
72
+ escapedDescIDs : map [uint64 ]struct {}{},
73
+ dimHashesByName : map [string ]uint64 {},
72
74
}
73
75
}
74
76
77
+ func (r * Registry ) HasEscapedCollision () bool {
78
+ return r .hasEscapedCollision
79
+ }
80
+
75
81
// NewPedanticRegistry returns a registry that checks during collection if each
76
82
// collected Metric is consistent with its reported Desc, and if the Desc has
77
83
// actually been registered with the registry. Unchecked Collectors (those whose
@@ -131,6 +137,11 @@ type Registerer interface {
131
137
// instance must only collect consistent metrics throughout its
132
138
// lifetime.
133
139
Unregister (Collector ) bool
140
+
141
+ // HasEscapedCollision returns true if any two of the registered metrics would
142
+ // be the same when escaped to underscores. This is needed to prevent
143
+ // duplicate metric issues when being scraped by a legacy system.
144
+ HasEscapedCollision () bool
134
145
}
135
146
136
147
// Gatherer is the interface for the part of a registry in charge of gathering
@@ -258,22 +269,36 @@ func (errs MultiError) MaybeUnwrap() error {
258
269
// Registry implements Collector to allow it to be used for creating groups of
259
270
// metrics. See the Grouping example for how this can be done.
260
271
type Registry struct {
261
- mtx sync.RWMutex
262
- collectorsByID map [uint64 ]Collector // ID is a hash of the descIDs.
272
+ mtx sync.RWMutex
273
+ collectorsByID map [uint64 ]Collector // ID is a hash of the descIDs.
274
+ // collectorsByEscapedID stores colletors by escapedID, only if escaped id is
275
+ // different (otherwise we can just do the lookup in the regular map).
276
+ collectorsByEscapedID map [uint64 ]Collector
263
277
descIDs map [uint64 ]struct {}
278
+ // escapedDescIDs records desc ids of the escaped version of the metric, only
279
+ // if different from the regular name.
280
+ escapedDescIDs map [uint64 ]struct {}
264
281
dimHashesByName map [string ]uint64
265
282
uncheckedCollectors []Collector
266
283
pedanticChecksEnabled bool
284
+
285
+ // hasEscapedCollision is set to true if any two metrics that were not
286
+ // identical under UTF-8 would collide if scraped by a system that requires
287
+ // names to be escaped to legacy underscore replacement.
288
+ hasEscapedCollision bool
267
289
}
268
290
269
291
// Register implements Registerer.
270
292
func (r * Registry ) Register (c Collector ) error {
271
293
var (
272
- descChan = make (chan * Desc , capDescChan )
273
- newDescIDs = map [uint64 ]struct {}{}
274
- newDimHashesByName = map [string ]uint64 {}
275
- collectorID uint64 // All desc IDs XOR'd together.
276
- duplicateDescErr error
294
+ descChan = make (chan * Desc , capDescChan )
295
+ newDescIDs = map [uint64 ]struct {}{}
296
+ newEscapedIDs = map [uint64 ]struct {}{}
297
+ newDimHashesByName = map [string ]uint64 {}
298
+ collectorID uint64 // All desc IDs XOR'd together.
299
+ escapedID uint64
300
+ duplicateDescErr error
301
+ duplicateEscapedDesc bool
277
302
)
278
303
go func () {
279
304
c .Describe (descChan )
@@ -307,6 +332,22 @@ func (r *Registry) Register(c Collector) error {
307
332
collectorID ^= desc .id
308
333
}
309
334
335
+ // Also check to see if the descID is unique when all the names are escaped
336
+ // to underscores. First check the primary map, then check the secondary
337
+ // map. We only officially log a collision later.
338
+ if _ , exists := r .descIDs [desc .escapedID ]; exists {
339
+ duplicateEscapedDesc = true
340
+ }
341
+ if _ , exists := r .escapedDescIDs [desc .escapedID ]; exists {
342
+ duplicateEscapedDesc = true
343
+ }
344
+ if _ , exists := newEscapedIDs [desc .escapedID ]; ! exists {
345
+ if desc .escapedID != desc .id {
346
+ newEscapedIDs [desc .escapedID ] = struct {}{}
347
+ }
348
+ escapedID ^= desc .escapedID
349
+ }
350
+
310
351
// Are all the label names and the help string consistent with
311
352
// previous descriptors of the same name?
312
353
// First check existing descriptors...
@@ -331,7 +372,17 @@ func (r *Registry) Register(c Collector) error {
331
372
r .uncheckedCollectors = append (r .uncheckedCollectors , c )
332
373
return nil
333
374
}
334
- if existing , exists := r .collectorsByID [collectorID ]; exists {
375
+
376
+ existing , collision := r .collectorsByID [collectorID ]
377
+ // Also check whether the underscore-escaped versions of the IDs match.
378
+ if ! collision {
379
+ _ , escapedCollision := r .collectorsByID [escapedID ]
380
+ r .hasEscapedCollision = r .hasEscapedCollision || escapedCollision
381
+ _ , escapedCollision = r .collectorsByEscapedID [escapedID ]
382
+ r .hasEscapedCollision = r .hasEscapedCollision || escapedCollision
383
+ }
384
+
385
+ if collision {
335
386
switch e := existing .(type ) {
336
387
case * wrappingCollector :
337
388
return AlreadyRegisteredError {
@@ -351,23 +402,36 @@ func (r *Registry) Register(c Collector) error {
351
402
return duplicateDescErr
352
403
}
353
404
405
+ if duplicateEscapedDesc {
406
+ r .hasEscapedCollision = true
407
+ }
408
+
354
409
// Only after all tests have passed, actually register.
355
410
r .collectorsByID [collectorID ] = c
411
+ // We only need to store the escapedID if it doesn't match the unescaped one.
412
+ if escapedID != collectorID {
413
+ r .collectorsByEscapedID [escapedID ] = c
414
+ }
356
415
for hash := range newDescIDs {
357
416
r .descIDs [hash ] = struct {}{}
358
417
}
359
418
for name , dimHash := range newDimHashesByName {
360
419
r .dimHashesByName [name ] = dimHash
361
420
}
421
+ for hash := range newEscapedIDs {
422
+ r .escapedDescIDs [hash ] = struct {}{}
423
+ }
362
424
return nil
363
425
}
364
426
365
427
// Unregister implements Registerer.
366
428
func (r * Registry ) Unregister (c Collector ) bool {
367
429
var (
368
- descChan = make (chan * Desc , capDescChan )
369
- descIDs = map [uint64 ]struct {}{}
370
- collectorID uint64 // All desc IDs XOR'd together.
430
+ descChan = make (chan * Desc , capDescChan )
431
+ descIDs = map [uint64 ]struct {}{}
432
+ escpaedDescIDs = map [uint64 ]struct {}{}
433
+ collectorID uint64 // All desc IDs XOR'd together.
434
+ collectorEscapedID uint64
371
435
)
372
436
go func () {
373
437
c .Describe (descChan )
@@ -377,6 +441,8 @@ func (r *Registry) Unregister(c Collector) bool {
377
441
if _ , exists := descIDs [desc .id ]; ! exists {
378
442
collectorID ^= desc .id
379
443
descIDs [desc .id ] = struct {}{}
444
+ collectorEscapedID ^= desc .escapedID
445
+ escpaedDescIDs [desc .escapedID ] = struct {}{}
380
446
}
381
447
}
382
448
@@ -391,9 +457,13 @@ func (r *Registry) Unregister(c Collector) bool {
391
457
defer r .mtx .Unlock ()
392
458
393
459
delete (r .collectorsByID , collectorID )
460
+ delete (r .collectorsByEscapedID , collectorEscapedID )
394
461
for id := range descIDs {
395
462
delete (r .descIDs , id )
396
463
}
464
+ for id := range escpaedDescIDs {
465
+ delete (r .escapedDescIDs , id )
466
+ }
397
467
// dimHashesByName is left untouched as those must be consistent
398
468
// throughout the lifetime of a program.
399
469
return true
0 commit comments