go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/common/tsmon/registry/registry.go (about)

     1  // Copyright 2017 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package registry holds a map of all metrics registered by the process.
    16  //
    17  // This map is global and it is populated during init() time, when individual
    18  // metrics are defined.
    19  package registry
    20  
    21  import (
    22  	"fmt"
    23  	"regexp"
    24  	"sync"
    25  
    26  	"go.chromium.org/luci/common/tsmon/monitor"
    27  	"go.chromium.org/luci/common/tsmon/types"
    28  )
    29  
    30  var (
    31  	registry          = map[metricRegistryKey]types.Metric{}
    32  	lock              = sync.RWMutex{}
    33  	metricNameRe      = regexp.MustCompile("^(/[a-zA-Z0-9_-]+)+$")
    34  	metricFieldNameRe = regexp.MustCompile("^[A-Za-z_][A-Za-z0-9_]*$")
    35  )
    36  
    37  type metricRegistryKey struct {
    38  	MetricName string
    39  	TargetType types.TargetType
    40  }
    41  
    42  // Add adds a metric to the metric registry.
    43  //
    44  // Panics if
    45  // - the metric name is invalid.
    46  // - a metric with the same name and target type is defined already.
    47  // - a field name is invalid.
    48  func Add(m types.Metric) {
    49  	key := metricRegistryKey{
    50  		MetricName: m.Info().Name,
    51  		TargetType: m.Info().TargetType,
    52  	}
    53  	fields := m.Info().Fields
    54  
    55  	lock.Lock()
    56  	defer lock.Unlock()
    57  
    58  	switch _, exist := registry[key]; {
    59  	case key.MetricName == "":
    60  		panic(fmt.Errorf("empty metric name"))
    61  	case !metricNameRe.MatchString(monitor.MetricNamePrefix + key.MetricName):
    62  		panic(fmt.Errorf("invalid metric name %q: doesn't match %s", key.MetricName, metricNameRe))
    63  	case exist:
    64  		panic(fmt.Errorf("duplicate metric name: metric %q with target %q is registered already",
    65  			key.MetricName, key.TargetType.Name))
    66  	default:
    67  		for _, f := range fields {
    68  			if !metricFieldNameRe.MatchString(f.Name) {
    69  				panic(fmt.Errorf("invalid field name %q: doesn't match %s",
    70  					f.Name, metricFieldNameRe))
    71  			}
    72  		}
    73  	}
    74  	registry[key] = m
    75  }
    76  
    77  // Iter calls a callback for each registered metric.
    78  //
    79  // Metrics are visited in no particular order. The callback must not modify
    80  // the registry.
    81  func Iter(cb func(m types.Metric)) {
    82  	lock.RLock()
    83  	defer lock.RUnlock()
    84  	for _, v := range registry {
    85  		cb(v)
    86  	}
    87  }