@@ -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 .disableLegacyCollision = 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
+ // stores colletors by escapedID, only if escaped id is different (otherwise
275
+ // we can just do the lookup in the regular map).
276
+ collectorsByEscapedID map [uint64 ]Collector
263
277
descIDs map [uint64 ]struct {}
278
+ // desc ids, only if different
279
+ escapedDescIDs map [uint64 ]struct {}
264
280
dimHashesByName map [string ]uint64
265
281
uncheckedCollectors []Collector
266
282
pedanticChecksEnabled bool
283
+ // This flag is inverted so that the default can be the false value.
284
+ disableLegacyCollision 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,23 @@ 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 .disableLegacyCollision {
333
+ if _ , exists := r .escapedDescIDs [desc .escapedID ]; exists {
334
+ duplicateDescErr = fmt .Errorf ("descriptor %s will collide with an existing descriptor when escaped for compatibility with non-UTF8 systems" , desc )
335
+ }
336
+ if _ , exists := r .descIDs [desc .escapedID ]; exists {
337
+ duplicateDescErr = fmt .Errorf ("descriptor %s will collide with an existing descriptor when escaped for compatibility with non-UTF8 systems" , desc )
338
+ }
339
+ }
340
+ if _ , exists := newEscapedIDs [desc .escapedID ]; ! exists {
341
+ if desc .escapedID != desc .id {
342
+ newEscapedIDs [desc .escapedID ] = struct {}{}
343
+ }
344
+ escapedID ^= desc .escapedID
345
+ }
346
+
310
347
// Are all the label names and the help string consistent with
311
348
// previous descriptors of the same name?
312
349
// First check existing descriptors...
@@ -331,7 +368,18 @@ func (r *Registry) Register(c Collector) error {
331
368
r .uncheckedCollectors = append (r .uncheckedCollectors , c )
332
369
return nil
333
370
}
334
- if existing , exists := r .collectorsByID [collectorID ]; exists {
371
+
372
+ existing , collision := r .collectorsByID [collectorID ]
373
+ // Unless we are in pure UTF-8 mode, we also need to check that the
374
+ // underscore-escaped versions of the IDs don't match.
375
+ if ! collision && ! r .disableLegacyCollision {
376
+ existing , collision = r .collectorsByID [escapedID ]
377
+ if ! collision {
378
+ existing , collision = r .collectorsByEscapedID [escapedID ]
379
+ }
380
+ }
381
+
382
+ if collision {
335
383
switch e := existing .(type ) {
336
384
case * wrappingCollector :
337
385
return AlreadyRegisteredError {
@@ -353,21 +401,30 @@ func (r *Registry) Register(c Collector) error {
353
401
354
402
// Only after all tests have passed, actually register.
355
403
r .collectorsByID [collectorID ] = c
404
+ // We only need to store the escapedID if it doesn't match the unescaped one.
405
+ if escapedID != collectorID {
406
+ r .collectorsByEscapedID [escapedID ] = c
407
+ }
356
408
for hash := range newDescIDs {
357
409
r .descIDs [hash ] = struct {}{}
358
410
}
359
411
for name , dimHash := range newDimHashesByName {
360
412
r .dimHashesByName [name ] = dimHash
361
413
}
414
+ for hash := range newEscapedIDs {
415
+ r .escapedDescIDs [hash ] = struct {}{}
416
+ }
362
417
return nil
363
418
}
364
419
365
420
// Unregister implements Registerer.
366
421
func (r * Registry ) Unregister (c Collector ) bool {
367
422
var (
368
- descChan = make (chan * Desc , capDescChan )
369
- descIDs = map [uint64 ]struct {}{}
370
- collectorID uint64 // All desc IDs XOR'd together.
423
+ descChan = make (chan * Desc , capDescChan )
424
+ descIDs = map [uint64 ]struct {}{}
425
+ escpaedDescIDs = map [uint64 ]struct {}{}
426
+ collectorID uint64 // All desc IDs XOR'd together.
427
+ collectorEscapedID uint64
371
428
)
372
429
go func () {
373
430
c .Describe (descChan )
@@ -377,6 +434,8 @@ func (r *Registry) Unregister(c Collector) bool {
377
434
if _ , exists := descIDs [desc .id ]; ! exists {
378
435
collectorID ^= desc .id
379
436
descIDs [desc .id ] = struct {}{}
437
+ collectorEscapedID ^= desc .escapedID
438
+ escpaedDescIDs [desc .escapedID ] = struct {}{}
380
439
}
381
440
}
382
441
@@ -391,9 +450,13 @@ func (r *Registry) Unregister(c Collector) bool {
391
450
defer r .mtx .Unlock ()
392
451
393
452
delete (r .collectorsByID , collectorID )
453
+ delete (r .collectorsByEscapedID , collectorEscapedID )
394
454
for id := range descIDs {
395
455
delete (r .descIDs , id )
396
456
}
457
+ for id := range escpaedDescIDs {
458
+ delete (r .escapedDescIDs , id )
459
+ }
397
460
// dimHashesByName is left untouched as those must be consistent
398
461
// throughout the lifetime of a program.
399
462
return true
0 commit comments