go.temporal.io/server@v1.23.0/common/metrics/opentelemetry_provider.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  	"context"
    29  	"net/http"
    30  	"time"
    31  
    32  	"github.com/prometheus/client_golang/prometheus"
    33  	"github.com/prometheus/client_golang/prometheus/promhttp"
    34  	exporters "go.opentelemetry.io/otel/exporters/prometheus"
    35  	"go.opentelemetry.io/otel/metric"
    36  	sdkmetrics "go.opentelemetry.io/otel/sdk/metric"
    37  
    38  	"go.temporal.io/server/common/log"
    39  	"go.temporal.io/server/common/log/tag"
    40  )
    41  
    42  var _ OpenTelemetryProvider = (*openTelemetryProviderImpl)(nil)
    43  
    44  type (
    45  	OpenTelemetryProvider interface {
    46  		Stop(logger log.Logger)
    47  		GetMeter() metric.Meter
    48  	}
    49  
    50  	openTelemetryProviderImpl struct {
    51  		meter  metric.Meter
    52  		config *PrometheusConfig
    53  		server *http.Server
    54  	}
    55  )
    56  
    57  func NewOpenTelemetryProvider(
    58  	logger log.Logger,
    59  	prometheusConfig *PrometheusConfig,
    60  	clientConfig *ClientConfig,
    61  ) (*openTelemetryProviderImpl, error) {
    62  	reg := prometheus.NewRegistry()
    63  	exporter, err := exporters.New(exporters.WithRegisterer(reg))
    64  	if err != nil {
    65  		logger.Error("Failed to initialize prometheus exporter.", tag.Error(err))
    66  		return nil, err
    67  	}
    68  
    69  	var views []sdkmetrics.View
    70  	for _, u := range []string{Dimensionless, Bytes, Milliseconds} {
    71  		views = append(views, sdkmetrics.NewView(
    72  			sdkmetrics.Instrument{
    73  				Kind: sdkmetrics.InstrumentKindHistogram,
    74  				Unit: u,
    75  			},
    76  			sdkmetrics.Stream{
    77  				Aggregation: sdkmetrics.AggregationExplicitBucketHistogram{
    78  					Boundaries: clientConfig.PerUnitHistogramBoundaries[u],
    79  				},
    80  			},
    81  		))
    82  	}
    83  	provider := sdkmetrics.NewMeterProvider(
    84  		sdkmetrics.WithReader(exporter),
    85  		sdkmetrics.WithView(views...),
    86  	)
    87  	metricServer := initPrometheusListener(prometheusConfig, reg, logger)
    88  	meter := provider.Meter("temporal")
    89  	reporter := &openTelemetryProviderImpl{
    90  		meter:  meter,
    91  		config: prometheusConfig,
    92  		server: metricServer,
    93  	}
    94  
    95  	return reporter, nil
    96  }
    97  
    98  func initPrometheusListener(config *PrometheusConfig, reg *prometheus.Registry, logger log.Logger) *http.Server {
    99  	handlerPath := config.HandlerPath
   100  	if handlerPath == "" {
   101  		handlerPath = "/metrics"
   102  	}
   103  
   104  	handler := http.NewServeMux()
   105  	handler.HandleFunc(handlerPath, promhttp.HandlerFor(reg, promhttp.HandlerOpts{Registry: reg}).ServeHTTP)
   106  
   107  	if config.ListenAddress == "" {
   108  		logger.Fatal("Listen address must be specified.", tag.Address(config.ListenAddress))
   109  	}
   110  	server := &http.Server{Addr: config.ListenAddress, Handler: handler}
   111  
   112  	go func() {
   113  		err := server.ListenAndServe()
   114  		if err != http.ErrServerClosed {
   115  			logger.Fatal("Failed to initialize prometheus listener.", tag.Address(config.ListenAddress))
   116  		}
   117  	}()
   118  
   119  	return server
   120  }
   121  
   122  func (r *openTelemetryProviderImpl) GetMeter() metric.Meter {
   123  	return r.meter
   124  }
   125  
   126  func (r *openTelemetryProviderImpl) Stop(logger log.Logger) {
   127  	ctx, closeCtx := context.WithTimeout(context.Background(), time.Second)
   128  	defer closeCtx()
   129  	if err := r.server.Shutdown(ctx); !(err == nil || err == http.ErrServerClosed) {
   130  		logger.Error("Prometheus metrics server shutdown failure.", tag.Address(r.config.ListenAddress), tag.Error(err))
   131  	}
   132  }