mirror of https://github.com/knative/pkg.git
				
				
				
			
		
			
				
	
	
		
			304 lines
		
	
	
		
			8.5 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			304 lines
		
	
	
		
			8.5 KiB
		
	
	
	
		
			Go
		
	
	
	
// Copyright 2017, OpenCensus Authors
 | 
						|
//
 | 
						|
// Licensed under the Apache License, Version 2.0 (the "License");
 | 
						|
// you may not use this file except in compliance with the License.
 | 
						|
// You may obtain a copy of the License at
 | 
						|
//
 | 
						|
//     http://www.apache.org/licenses/LICENSE-2.0
 | 
						|
//
 | 
						|
// Unless required by applicable law or agreed to in writing, software
 | 
						|
// distributed under the License is distributed on an "AS IS" BASIS,
 | 
						|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
						|
// See the License for the specific language governing permissions and
 | 
						|
// limitations under the License.
 | 
						|
 | 
						|
// Package prometheus contains a Prometheus exporter that supports exporting
 | 
						|
// OpenCensus views as Prometheus metrics.
 | 
						|
package prometheus // import "contrib.go.opencensus.io/exporter/prometheus"
 | 
						|
 | 
						|
import (
 | 
						|
	"context"
 | 
						|
	"fmt"
 | 
						|
	"log"
 | 
						|
	"net/http"
 | 
						|
	"sync"
 | 
						|
 | 
						|
	"github.com/prometheus/client_golang/prometheus"
 | 
						|
	"github.com/prometheus/client_golang/prometheus/promhttp"
 | 
						|
	"go.opencensus.io/metric/metricdata"
 | 
						|
	"go.opencensus.io/metric/metricexport"
 | 
						|
	"go.opencensus.io/stats/view"
 | 
						|
)
 | 
						|
 | 
						|
// Exporter exports stats to Prometheus, users need
 | 
						|
// to register the exporter as an http.Handler to be
 | 
						|
// able to export.
 | 
						|
type Exporter struct {
 | 
						|
	opts    Options
 | 
						|
	g       prometheus.Gatherer
 | 
						|
	c       *collector
 | 
						|
	handler http.Handler
 | 
						|
}
 | 
						|
 | 
						|
// Options contains options for configuring the exporter.
 | 
						|
type Options struct {
 | 
						|
	Namespace   string
 | 
						|
	Registry    *prometheus.Registry
 | 
						|
	Registerer  prometheus.Registerer
 | 
						|
	Gatherer    prometheus.Gatherer
 | 
						|
	OnError     func(err error)
 | 
						|
	ConstLabels prometheus.Labels // ConstLabels will be set as labels on all views.
 | 
						|
}
 | 
						|
 | 
						|
// NewExporter returns an exporter that exports stats to Prometheus.
 | 
						|
func NewExporter(o Options) (*Exporter, error) {
 | 
						|
	if o.Registry == nil {
 | 
						|
		o.Registry = prometheus.NewRegistry()
 | 
						|
	}
 | 
						|
	if o.Registerer == nil {
 | 
						|
		o.Registerer = o.Registry
 | 
						|
	}
 | 
						|
	if o.Gatherer == nil {
 | 
						|
		o.Gatherer = o.Registry
 | 
						|
	}
 | 
						|
 | 
						|
	collector := newCollector(o, o.Registerer)
 | 
						|
	e := &Exporter{
 | 
						|
		opts:    o,
 | 
						|
		g:       o.Gatherer,
 | 
						|
		c:       collector,
 | 
						|
		handler: promhttp.HandlerFor(o.Gatherer, promhttp.HandlerOpts{}),
 | 
						|
	}
 | 
						|
	collector.ensureRegisteredOnce()
 | 
						|
 | 
						|
	return e, nil
 | 
						|
}
 | 
						|
 | 
						|
var _ http.Handler = (*Exporter)(nil)
 | 
						|
 | 
						|
// ensureRegisteredOnce invokes reg.Register on the collector itself
 | 
						|
// exactly once to ensure that we don't get errors such as
 | 
						|
//  cannot register the collector: descriptor Desc{fqName: *}
 | 
						|
//  already exists with the same fully-qualified name and const label values
 | 
						|
// which is documented by Prometheus at
 | 
						|
//  https://github.com/prometheus/client_golang/blob/fcc130e101e76c5d303513d0e28f4b6d732845c7/prometheus/registry.go#L89-L101
 | 
						|
func (c *collector) ensureRegisteredOnce() {
 | 
						|
	c.registerOnce.Do(func() {
 | 
						|
		if err := c.reg.Register(c); err != nil {
 | 
						|
			c.opts.onError(fmt.Errorf("cannot register the collector: %v", err))
 | 
						|
		}
 | 
						|
	})
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
func (o *Options) onError(err error) {
 | 
						|
	if o.OnError != nil {
 | 
						|
		o.OnError(err)
 | 
						|
	} else {
 | 
						|
		log.Printf("Failed to export to Prometheus: %v", err)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// ExportView exports to the Prometheus if view data has one or more rows.
 | 
						|
// Each OpenCensus AggregationData will be converted to
 | 
						|
// corresponding Prometheus Metric: SumData will be converted
 | 
						|
// to Untyped Metric, CountData will be a Counter Metric,
 | 
						|
// DistributionData will be a Histogram Metric.
 | 
						|
//
 | 
						|
// Deprecated: in lieu of metricexport.Reader interface.
 | 
						|
func (e *Exporter) ExportView(vd *view.Data) {
 | 
						|
}
 | 
						|
 | 
						|
// ServeHTTP serves the Prometheus endpoint.
 | 
						|
func (e *Exporter) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 | 
						|
	e.handler.ServeHTTP(w, r)
 | 
						|
}
 | 
						|
 | 
						|
// collector implements prometheus.Collector
 | 
						|
type collector struct {
 | 
						|
	opts Options
 | 
						|
 | 
						|
	registerOnce sync.Once
 | 
						|
 | 
						|
	// reg helps collector register views dynamically.
 | 
						|
	reg prometheus.Registerer
 | 
						|
 | 
						|
	// reader reads metrics from all registered producers.
 | 
						|
	reader *metricexport.Reader
 | 
						|
}
 | 
						|
 | 
						|
func (c *collector) Describe(ch chan<- *prometheus.Desc) {
 | 
						|
	de := &descExporter{c: c, descCh: ch}
 | 
						|
	c.reader.ReadAndExport(de)
 | 
						|
}
 | 
						|
 | 
						|
// Collect fetches the statistics from OpenCensus
 | 
						|
// and delivers them as Prometheus Metrics.
 | 
						|
// Collect is invoked every time a prometheus.Gatherer is run
 | 
						|
// for example when the HTTP endpoint is invoked by Prometheus.
 | 
						|
func (c *collector) Collect(ch chan<- prometheus.Metric) {
 | 
						|
	me := &metricExporter{c: c, metricCh: ch}
 | 
						|
	c.reader.ReadAndExport(me)
 | 
						|
}
 | 
						|
 | 
						|
func newCollector(opts Options, registrar prometheus.Registerer) *collector {
 | 
						|
	return &collector{
 | 
						|
		reg:    registrar,
 | 
						|
		opts:   opts,
 | 
						|
		reader: metricexport.NewReader()}
 | 
						|
}
 | 
						|
 | 
						|
func (c *collector) toDesc(metric *metricdata.Metric) *prometheus.Desc {
 | 
						|
	var labels prometheus.Labels
 | 
						|
	switch {
 | 
						|
	case metric.Resource == nil:
 | 
						|
		labels = c.opts.ConstLabels
 | 
						|
	case c.opts.ConstLabels == nil:
 | 
						|
		labels = metric.Resource.Labels
 | 
						|
	default:
 | 
						|
		labels = prometheus.Labels{}
 | 
						|
		for k, v := range c.opts.ConstLabels {
 | 
						|
			labels[k] = v
 | 
						|
		}
 | 
						|
		// Resource labels overwrite const labels.
 | 
						|
		for k, v := range metric.Resource.Labels {
 | 
						|
			labels[k] = v
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return prometheus.NewDesc(
 | 
						|
		metricName(c.opts.Namespace, metric),
 | 
						|
		metric.Descriptor.Description,
 | 
						|
		toPromLabels(metric.Descriptor.LabelKeys),
 | 
						|
		labels)
 | 
						|
}
 | 
						|
 | 
						|
type metricExporter struct {
 | 
						|
	c        *collector
 | 
						|
	metricCh chan<- prometheus.Metric
 | 
						|
}
 | 
						|
 | 
						|
// ExportMetrics exports to the Prometheus.
 | 
						|
// Each OpenCensus Metric will be converted to
 | 
						|
// corresponding Prometheus Metric:
 | 
						|
// TypeCumulativeInt64 and TypeCumulativeFloat64 will be a Counter Metric,
 | 
						|
// TypeCumulativeDistribution will be a Histogram Metric.
 | 
						|
// TypeGaugeFloat64 and TypeGaugeInt64 will be a Gauge Metric
 | 
						|
func (me *metricExporter) ExportMetrics(ctx context.Context, metrics []*metricdata.Metric) error {
 | 
						|
	for _, metric := range metrics {
 | 
						|
		desc := me.c.toDesc(metric)
 | 
						|
		for _, ts := range metric.TimeSeries {
 | 
						|
			tvs := toLabelValues(ts.LabelValues)
 | 
						|
			for _, point := range ts.Points {
 | 
						|
				metric, err := toPromMetric(desc, metric, point, tvs)
 | 
						|
				if err != nil {
 | 
						|
					me.c.opts.onError(err)
 | 
						|
				} else if metric != nil {
 | 
						|
					me.metricCh <- metric
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
type descExporter struct {
 | 
						|
	c      *collector
 | 
						|
	descCh chan<- *prometheus.Desc
 | 
						|
}
 | 
						|
 | 
						|
// ExportMetrics exports descriptor to the Prometheus.
 | 
						|
// It is invoked when request to scrape descriptors is received.
 | 
						|
func (me *descExporter) ExportMetrics(ctx context.Context, metrics []*metricdata.Metric) error {
 | 
						|
	for _, metric := range metrics {
 | 
						|
		desc := me.c.toDesc(metric)
 | 
						|
		me.descCh <- desc
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func toPromLabels(mls []metricdata.LabelKey) (labels []string) {
 | 
						|
	for _, ml := range mls {
 | 
						|
		labels = append(labels, sanitize(ml.Key))
 | 
						|
	}
 | 
						|
	return labels
 | 
						|
}
 | 
						|
 | 
						|
func metricName(namespace string, m *metricdata.Metric) string {
 | 
						|
	var name string
 | 
						|
	if namespace != "" {
 | 
						|
		name = namespace + "_"
 | 
						|
	}
 | 
						|
	return name + sanitize(m.Descriptor.Name)
 | 
						|
}
 | 
						|
 | 
						|
func toPromMetric(
 | 
						|
	desc *prometheus.Desc,
 | 
						|
	metric *metricdata.Metric,
 | 
						|
	point metricdata.Point,
 | 
						|
	labelValues []string) (prometheus.Metric, error) {
 | 
						|
	switch metric.Descriptor.Type {
 | 
						|
	case metricdata.TypeCumulativeFloat64, metricdata.TypeCumulativeInt64:
 | 
						|
		pv, err := toPromValue(point)
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
		return prometheus.NewConstMetric(desc, prometheus.CounterValue, pv, labelValues...)
 | 
						|
 | 
						|
	case metricdata.TypeGaugeFloat64, metricdata.TypeGaugeInt64:
 | 
						|
		pv, err := toPromValue(point)
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
		return prometheus.NewConstMetric(desc, prometheus.GaugeValue, pv, labelValues...)
 | 
						|
 | 
						|
	case metricdata.TypeCumulativeDistribution:
 | 
						|
		switch v := point.Value.(type) {
 | 
						|
		case *metricdata.Distribution:
 | 
						|
			points := make(map[float64]uint64)
 | 
						|
			// Histograms are cumulative in Prometheus.
 | 
						|
			// Get cumulative bucket counts.
 | 
						|
			cumCount := uint64(0)
 | 
						|
			for i, b := range v.BucketOptions.Bounds {
 | 
						|
				cumCount += uint64(v.Buckets[i].Count)
 | 
						|
				points[b] = cumCount
 | 
						|
			}
 | 
						|
			return prometheus.NewConstHistogram(desc, uint64(v.Count), v.Sum, points, labelValues...)
 | 
						|
		default:
 | 
						|
			return nil, typeMismatchError(point)
 | 
						|
		}
 | 
						|
	case metricdata.TypeSummary:
 | 
						|
		// TODO: [rghetia] add support for TypeSummary.
 | 
						|
		return nil, nil
 | 
						|
	default:
 | 
						|
		return nil, fmt.Errorf("aggregation %T is not yet supported", metric.Descriptor.Type)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func toLabelValues(labelValues []metricdata.LabelValue) (values []string) {
 | 
						|
	for _, lv := range labelValues {
 | 
						|
		if lv.Present {
 | 
						|
			values = append(values, lv.Value)
 | 
						|
		} else {
 | 
						|
			values = append(values, "")
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return values
 | 
						|
}
 | 
						|
 | 
						|
func typeMismatchError(point metricdata.Point) error {
 | 
						|
	return fmt.Errorf("point type %T does not match metric type", point)
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
func toPromValue(point metricdata.Point) (float64, error) {
 | 
						|
	switch v := point.Value.(type) {
 | 
						|
	case float64:
 | 
						|
		return v, nil
 | 
						|
	case int64:
 | 
						|
		return float64(v), nil
 | 
						|
	default:
 | 
						|
		return 0.0, typeMismatchError(point)
 | 
						|
	}
 | 
						|
}
 |