go.temporal.io/server@v1.23.0/common/metrics/registry.go (about) 1 // The MIT License 2 // 3 // Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. 4 // 5 // Copyright (c) 2020 Uber Technologies, Inc. 6 // 7 // Permission is hereby granted, free of charge, to any person obtaining a copy 8 // of this software and associated documentation files (the "Software"), to deal 9 // in the Software without restriction, including without limitation the rights 10 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 // copies of the Software, and to permit persons to whom the Software is 12 // furnished to do so, subject to the following conditions: 13 // 14 // The above copyright notice and this permission notice shall be included in 15 // all copies or substantial portions of the Software. 16 // 17 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 // THE SOFTWARE. 24 25 package metrics 26 27 import ( 28 "errors" 29 "fmt" 30 "sync" 31 ) 32 33 type ( 34 // registry tracks a list of metricDefinition objects added with register and then builds a catalog 35 // of them using the buildCatalog method. See globalRegistry for more. 36 registry struct { 37 sync.Mutex 38 definitions []metricDefinition 39 } 40 // catalog is a map of metric name to definition. It should not be modified after it is built. 41 catalog map[string]metricDefinition 42 ) 43 44 var ( 45 // globalRegistry tracks metrics defined via the New*Def methods. We use a global variable here so that clients may 46 // continue to refer to package-level metrics like metrics.ServiceRequests, while still allowing us to iterate over 47 // all metrics defined in the package to register them with the metrics system. The sequence through which metrics 48 // are registered, sampled and scraped is as follows: 49 // 50 // 1. When the metrics package is initialized, statements calling New*Def are executed to define metrics, 51 // which adds them metric to the global registry. 52 // 2. Before a Handler object is constructed, one this package's fx provider functions will call registry.buildCatalog to 53 // buildCatalog the catalog for these metrics. 54 // 3. The constructed catalog is passed to the Handler so that it knows the metadata for all defined metrics. 55 // 4. Clients call methods on the Handler to obtain metric objects like Handler.Counter and Handler.Timer. 56 // 5. Those methods retrieve the metadata from the catalog and use it to construct the metric object using a 57 // third-party metrics library, e.g. OpenTelemetry. This is where most of the work happens. 58 // 6. Clients record a metric using that metrics object, e.g. by calling CounterFunc, and the sample is recorded. 59 // 7. At some point, the /metrics endpoint is scraped, and the Prometheus handler we register will iterate over all 60 // the aggregated samples and metrics and write them to the response. The metric metadata we passed to the 61 // third-party metrics library in step 5 is used here and rendered in the response as comments like: 62 // # HELP <metric name> <metric description>. 63 globalRegistry registry 64 // errMetricAlreadyExists is returned by registry.buildCatalog when it finds two metrics with the same name. 65 errMetricAlreadyExists = errors.New("metric already exists") 66 ) 67 68 // register adds a metric definition to the list of pending metric definitions. This method is thread-safe. 69 func (c *registry) register(name string, opts ...Option) metricDefinition { 70 c.Lock() 71 defer c.Unlock() 72 73 d := metricDefinition{ 74 name: name, 75 description: "", 76 unit: "", 77 } 78 for _, opt := range opts { 79 opt.apply(&d) 80 } 81 82 c.definitions = append(c.definitions, d) 83 84 return d 85 } 86 87 // buildCatalog builds a catalog from the list of pending metric definitions. It is safe to call this method multiple 88 // times. This method is thread-safe. 89 func (c *registry) buildCatalog() (catalog, error) { 90 c.Lock() 91 defer c.Unlock() 92 93 r := make(catalog, len(c.definitions)) 94 for _, d := range c.definitions { 95 if original, ok := r[d.name]; ok { 96 return nil, fmt.Errorf( 97 "%w: metric %q already defined with %+v. Cannot redefine with %+v", 98 errMetricAlreadyExists, d.name, original, d, 99 ) 100 } 101 102 r[d.name] = d 103 } 104 105 return r, nil 106 } 107 108 func (c catalog) getMetric(name string) (metricDefinition, bool) { 109 def, ok := c[name] 110 return def, ok 111 }