istio
298 строк · 9.5 Кб
1// Copyright 2019 Istio 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
15package monitoring16
17import (18"net/http"19"sync"20
21"github.com/prometheus/client_golang/prometheus"22"github.com/prometheus/client_golang/prometheus/promhttp"23"go.opentelemetry.io/otel"24"go.opentelemetry.io/otel/attribute"25otelprom "go.opentelemetry.io/otel/exporters/prometheus"26api "go.opentelemetry.io/otel/metric"27"go.opentelemetry.io/otel/sdk/metric"28
29"istio.io/istio/pkg/log"30"istio.io/istio/pkg/maps"31"istio.io/istio/pkg/slices"32)
33
34var (35meter = func() api.Meter {36return otel.GetMeterProvider().Meter("istio")37}38
39monitoringLogger = log.RegisterScope("monitoring", "metrics monitoring")40)
41
42func init() {43otel.SetLogger(log.NewLogrAdapter(monitoringLogger))44}
45
46// RegisterPrometheusExporter sets the global metrics handler to the provided Prometheus registerer and gatherer.
47// Returned is an HTTP handler that can be used to read metrics from.
48func RegisterPrometheusExporter(reg prometheus.Registerer, gatherer prometheus.Gatherer) (http.Handler, error) {49if reg == nil {50reg = prometheus.DefaultRegisterer51}52if gatherer == nil {53gatherer = prometheus.DefaultGatherer54}55promOpts := []otelprom.Option{56otelprom.WithoutScopeInfo(),57otelprom.WithoutTargetInfo(),58otelprom.WithoutUnits(),59otelprom.WithRegisterer(reg),60otelprom.WithoutCounterSuffixes(),61}62
63prom, err := otelprom.New(promOpts...)64if err != nil {65return nil, err66}67
68opts := []metric.Option{metric.WithReader(prom)}69opts = append(opts, knownMetrics.toHistogramViews()...)70mp := metric.NewMeterProvider(opts...)71otel.SetMeterProvider(mp)72handler := promhttp.HandlerFor(gatherer, promhttp.HandlerOpts{})73return handler, nil74}
75
76// A Metric collects numerical observations.
77type Metric interface {78// Increment records a value of 1 for the current measure. For Sums,79// this is equivalent to adding 1 to the current value. For Gauges,80// this is equivalent to setting the value to 1. For Distributions,81// this is equivalent to making an observation of value 1.82Increment()83
84// Decrement records a value of -1 for the current measure. For Sums,85// this is equivalent to subtracting -1 to the current value. For Gauges,86// this is equivalent to setting the value to -1. For Distributions,87// this is equivalent to making an observation of value -1.88Decrement()89
90// Name returns the name value of a Metric.91Name() string92
93// Record makes an observation of the provided value for the given measure.94Record(value float64)95
96// RecordInt makes an observation of the provided value for the measure.97RecordInt(value int64)98
99// With creates a new Metric, with the LabelValues provided. This allows creating100// a set of pre-dimensioned data for recording purposes. This is primarily used101// for documentation and convenience. Metrics created with this method do not need102// to be registered (they share the registration of their parent Metric).103With(labelValues ...LabelValue) Metric104
105// Register configures the Metric for export. It MUST be called before collection106// of values for the Metric. An error will be returned if registration fails.107Register() error108}
109
110// DerivedMetric can be used to supply values that dynamically derive from internal
111// state, but are not updated based on any specific event. Their value will be calculated
112// based on a value func that executes when the metrics are exported.
113//
114// At the moment, only a Gauge type is supported.
115type DerivedMetric interface {116// Name returns the name value of a DerivedMetric.117Name() string118
119// Register handles any required setup to ensure metric export.120Register() error121
122// ValueFrom is used to update the derived value with the provided123// function and the associated label values. If the metric is unlabeled,124// ValueFrom may be called without any labelValues. Otherwise, the labelValues125// supplied MUST match the label keys supplied at creation time both in number126// and in order.127ValueFrom(valueFn func() float64, labelValues ...LabelValue) DerivedMetric128}
129
130// CreateLabel will attempt to create a new Label.
131func CreateLabel(key string) Label {132return Label{attribute.Key(key)}133}
134
135// A Label provides a named dimension for a Metric.
136type Label struct {137key attribute.Key138}
139
140// Value creates a new LabelValue for the Label.
141func (l Label) Value(value string) LabelValue {142return LabelValue{l.key.String(value)}143}
144
145// A LabelValue represents a Label with a specific value. It is used to record
146// values for a Metric.
147type LabelValue struct {148keyValue attribute.KeyValue149}
150
151func (l LabelValue) Key() Label {152return Label{l.keyValue.Key}153}
154
155func (l LabelValue) Value() string {156return l.keyValue.Value.AsString()157}
158
159// RecordHook has a callback function which a measure is recorded.
160type RecordHook interface {161OnRecord(name string, tags []LabelValue, value float64)162}
163
164var (165recordHooks = map[string]RecordHook{}166recordHookMutex sync.RWMutex167)
168
169// RegisterRecordHook adds a RecordHook for a given measure.
170func RegisterRecordHook(name string, h RecordHook) {171recordHookMutex.Lock()172defer recordHookMutex.Unlock()173recordHooks[name] = h174}
175
176// NewSum creates a new Sum Metric (the values will be cumulative).
177// That means that data collected by the new Metric will be summed before export.
178func NewSum(name, description string, opts ...Options) Metric {179knownMetrics.register(MetricDefinition{180Name: name,181Type: "Sum",182Description: description,183})184o, dm := createOptions(name, description, opts...)185if dm != nil {186return dm187}188return newCounter(o)189}
190
191// NewGauge creates a new Gauge Metric. That means that data collected by the new
192// Metric will export only the last recorded value.
193func NewGauge(name, description string, opts ...Options) Metric {194knownMetrics.register(MetricDefinition{195Name: name,196Type: "LastValue",197Description: description,198})199o, dm := createOptions(name, description, opts...)200if dm != nil {201return dm202}203return newGauge(o)204}
205
206// NewDerivedGauge creates a new Gauge Metric. That means that data collected by the new
207// Metric will export only the last recorded value.
208// Unlike NewGauge, the DerivedGauge accepts functions which are called to get the current value.
209func NewDerivedGauge(name, description string) DerivedMetric {210knownMetrics.register(MetricDefinition{211Name: name,212Type: "LastValue",213Description: description,214})215return newDerivedGauge(name, description)216}
217
218// NewDistribution creates a new Metric with an aggregation type of Distribution. This means that the
219// data collected by the Metric will be collected and exported as a histogram, with the specified bounds.
220func NewDistribution(name, description string, bounds []float64, opts ...Options) Metric {221knownMetrics.register(MetricDefinition{222Name: name,223Type: "Distribution",224Description: description,225Bounds: bounds,226})227o, dm := createOptions(name, description, opts...)228if dm != nil {229return dm230}231return newDistribution(o)232}
233
234// MetricDefinition records a metric's metadata.
235// This is used to work around two limitations of OpenTelemetry:
236// - (https://github.com/open-telemetry/opentelemetry-go/issues/4003) Histogram buckets cannot be defined per instrument.
237// instead, we record all metric definitions and add them as Views at registration time.
238// - Support pkg/collateral, which wants to query all metrics. This cannot use a simple Collect() call, as this ignores any unused metrics.
239type MetricDefinition struct {240Name string241Type string242Description string243Bounds []float64244}
245
246// metrics stores known metrics
247type metrics struct {248started bool249mu sync.Mutex250known map[string]MetricDefinition251}
252
253// knownMetrics is a global that stores all registered metrics
254var knownMetrics = metrics{255known: map[string]MetricDefinition{},256}
257
258// ExportMetricDefinitions reports all currently registered metric definitions.
259func ExportMetricDefinitions() []MetricDefinition {260knownMetrics.mu.Lock()261defer knownMetrics.mu.Unlock()262return slices.SortBy(maps.Values(knownMetrics.known), func(a MetricDefinition) string {263return a.Name264})265}
266
267// register records a newly defined metric. Only valid before an exporter is set.
268func (d *metrics) register(def MetricDefinition) {269d.mu.Lock()270defer d.mu.Unlock()271if d.started {272log.Fatalf("Attempting to initialize metric %q after metrics have started", def.Name)273}274d.known[def.Name] = def275}
276
277// toHistogramViews works around https://github.com/open-telemetry/opentelemetry-go/issues/4003; in the future we can define
278// this when we create the histogram.
279func (d *metrics) toHistogramViews() []metric.Option {280d.mu.Lock()281defer d.mu.Unlock()282d.started = true283opts := []metric.Option{}284for name, def := range d.known {285if def.Bounds == nil {286continue287}288// for each histogram metric (i.e. those with bounds), set up a view explicitly defining those buckets.289v := metric.WithView(metric.NewView(290metric.Instrument{Name: name},291metric.Stream{Aggregation: metric.AggregationExplicitBucketHistogram{292Boundaries: def.Bounds,293}},294))295opts = append(opts, v)296}297return opts298}
299