@@ -66,12 +66,23 @@ 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
+ // AllowEscapedCollisions determines whether the Registry should reject
78
+ // Collectors that would collide when escaped to underscores for compatibility
79
+ // with older systems. You may set this option to Allow if you know your metrics
80
+ // will never be scraped by an older system.
81
+ func (r * Registry ) AllowEscapedCollisions (allow bool ) * Registry {
82
+ r .allowEscapedCollision = allow
83
+ return r
84
+ }
85
+
75
86
// NewPedanticRegistry returns a registry that checks during collection if each
76
87
// collected Metric is consistent with its reported Desc, and if the Desc has
77
88
// actually been registered with the registry. Unchecked Collectors (those whose
@@ -258,21 +269,30 @@ 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
+ allowEscapedCollision bool
267
285
}
268
286
269
287
// Register implements Registerer.
270
288
func (r * Registry ) Register (c Collector ) error {
271
289
var (
272
290
descChan = make (chan * Desc , capDescChan )
273
291
newDescIDs = map [uint64 ]struct {}{}
292
+ newEscapedIDs = map [uint64 ]struct {}{}
274
293
newDimHashesByName = map [string ]uint64 {}
275
294
collectorID uint64 // All desc IDs XOR'd together.
295
+ escapedID uint64
276
296
duplicateDescErr error
277
297
)
278
298
go func () {
@@ -307,6 +327,24 @@ func (r *Registry) Register(c Collector) error {
307
327
collectorID ^= desc .id
308
328
}
309
329
330
+ // Unless we are in pure UTF-8 mode, also check to see if the descID is
331
+ // unique when all the names are escaped to underscores.
332
+ if ! r .allowEscapedCollision {
333
+ // First check the primary map, then check the secondary map.
334
+ if _ , exists := r .descIDs [desc .escapedID ]; exists {
335
+ duplicateDescErr = fmt .Errorf ("descriptor %s will collide with an existing descriptor when escaped for compatibility with non-UTF8 systems" , desc )
336
+ }
337
+ if _ , exists := r .escapedDescIDs [desc .escapedID ]; exists {
338
+ duplicateDescErr = fmt .Errorf ("descriptor %s will collide with an existing descriptor when escaped for compatibility with non-UTF8 systems" , desc )
339
+ }
340
+ }
341
+ if _ , exists := newEscapedIDs [desc .escapedID ]; ! exists {
342
+ if desc .escapedID != desc .id {
343
+ newEscapedIDs [desc .escapedID ] = struct {}{}
344
+ }
345
+ escapedID ^= desc .escapedID
346
+ }
347
+
310
348
// Are all the label names and the help string consistent with
311
349
// previous descriptors of the same name?
312
350
// First check existing descriptors...
@@ -331,7 +369,18 @@ func (r *Registry) Register(c Collector) error {
331
369
r .uncheckedCollectors = append (r .uncheckedCollectors , c )
332
370
return nil
333
371
}
334
- if existing , exists := r .collectorsByID [collectorID ]; exists {
372
+
373
+ existing , collision := r .collectorsByID [collectorID ]
374
+ // Unless we are in pure UTF-8 mode, we also need to check that the
375
+ // underscore-escaped versions of the IDs don't match.
376
+ if ! collision && ! r .allowEscapedCollision {
377
+ existing , collision = r .collectorsByID [escapedID ]
378
+ if ! collision {
379
+ existing , collision = r .collectorsByEscapedID [escapedID ]
380
+ }
381
+ }
382
+
383
+ if collision {
335
384
switch e := existing .(type ) {
336
385
case * wrappingCollector :
337
386
return AlreadyRegisteredError {
@@ -353,21 +402,30 @@ func (r *Registry) Register(c Collector) error {
353
402
354
403
// Only after all tests have passed, actually register.
355
404
r .collectorsByID [collectorID ] = c
405
+ // We only need to store the escapedID if it doesn't match the unescaped one.
406
+ if escapedID != collectorID {
407
+ r .collectorsByEscapedID [escapedID ] = c
408
+ }
356
409
for hash := range newDescIDs {
357
410
r .descIDs [hash ] = struct {}{}
358
411
}
359
412
for name , dimHash := range newDimHashesByName {
360
413
r .dimHashesByName [name ] = dimHash
361
414
}
415
+ for hash := range newEscapedIDs {
416
+ r .escapedDescIDs [hash ] = struct {}{}
417
+ }
362
418
return nil
363
419
}
364
420
365
421
// Unregister implements Registerer.
366
422
func (r * Registry ) Unregister (c Collector ) bool {
367
423
var (
368
- descChan = make (chan * Desc , capDescChan )
369
- descIDs = map [uint64 ]struct {}{}
370
- collectorID uint64 // All desc IDs XOR'd together.
424
+ descChan = make (chan * Desc , capDescChan )
425
+ descIDs = map [uint64 ]struct {}{}
426
+ escpaedDescIDs = map [uint64 ]struct {}{}
427
+ collectorID uint64 // All desc IDs XOR'd together.
428
+ collectorEscapedID uint64
371
429
)
372
430
go func () {
373
431
c .Describe (descChan )
@@ -377,6 +435,8 @@ func (r *Registry) Unregister(c Collector) bool {
377
435
if _ , exists := descIDs [desc .id ]; ! exists {
378
436
collectorID ^= desc .id
379
437
descIDs [desc .id ] = struct {}{}
438
+ collectorEscapedID ^= desc .escapedID
439
+ escpaedDescIDs [desc .escapedID ] = struct {}{}
380
440
}
381
441
}
382
442
@@ -391,9 +451,13 @@ func (r *Registry) Unregister(c Collector) bool {
391
451
defer r .mtx .Unlock ()
392
452
393
453
delete (r .collectorsByID , collectorID )
454
+ delete (r .collectorsByEscapedID , collectorEscapedID )
394
455
for id := range descIDs {
395
456
delete (r .descIDs , id )
396
457
}
458
+ for id := range escpaedDescIDs {
459
+ delete (r .escapedDescIDs , id )
460
+ }
397
461
// dimHashesByName is left untouched as those must be consistent
398
462
// throughout the lifetime of a program.
399
463
return true
0 commit comments