1
0
Fork 0
mirror of https://github.com/Luzifer/nginx-sso.git synced 2025-01-02 03:01:16 +00:00
nginx-sso/vendor/go.opencensus.io/exporter/prometheus/prometheus.go
Knut Ahlers 9b3c895c04
Update dependencies
Signed-off-by: Knut Ahlers <knut@ahlers.me>
2019-04-22 06:44:07 +02:00

295 lines
8 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 "go.opencensus.io/exporter/prometheus"
import (
"bytes"
"fmt"
"log"
"net/http"
"sync"
"go.opencensus.io/internal"
"go.opencensus.io/stats/view"
"go.opencensus.io/tag"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
// 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
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()
}
collector := newCollector(o, o.Registry)
e := &Exporter{
opts: o,
g: o.Registry,
c: collector,
handler: promhttp.HandlerFor(o.Registry, promhttp.HandlerOpts{}),
}
return e, nil
}
var _ http.Handler = (*Exporter)(nil)
var _ view.Exporter = (*Exporter)(nil)
func (c *collector) registerViews(views ...*view.View) {
count := 0
for _, view := range views {
sig := viewSignature(c.opts.Namespace, view)
c.registeredViewsMu.Lock()
_, ok := c.registeredViews[sig]
c.registeredViewsMu.Unlock()
if !ok {
desc := prometheus.NewDesc(
viewName(c.opts.Namespace, view),
view.Description,
tagKeysToLabels(view.TagKeys),
c.opts.ConstLabels,
)
c.registeredViewsMu.Lock()
c.registeredViews[sig] = desc
c.registeredViewsMu.Unlock()
count++
}
}
if count == 0 {
return
}
c.ensureRegisteredOnce()
}
// 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.
func (e *Exporter) ExportView(vd *view.Data) {
if len(vd.Rows) == 0 {
return
}
e.c.addViewData(vd)
}
// 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
mu sync.Mutex // mu guards all the fields.
registerOnce sync.Once
// reg helps collector register views dynamically.
reg *prometheus.Registry
// viewData are accumulated and atomically
// appended to on every Export invocation, from
// stats. These views are cleared out when
// Collect is invoked and the cycle is repeated.
viewData map[string]*view.Data
registeredViewsMu sync.Mutex
// registeredViews maps a view to a prometheus desc.
registeredViews map[string]*prometheus.Desc
}
func (c *collector) addViewData(vd *view.Data) {
c.registerViews(vd.View)
sig := viewSignature(c.opts.Namespace, vd.View)
c.mu.Lock()
c.viewData[sig] = vd
c.mu.Unlock()
}
func (c *collector) Describe(ch chan<- *prometheus.Desc) {
c.registeredViewsMu.Lock()
registered := make(map[string]*prometheus.Desc)
for k, desc := range c.registeredViews {
registered[k] = desc
}
c.registeredViewsMu.Unlock()
for _, desc := range registered {
ch <- desc
}
}
// Collect fetches the statistics from OpenCensus
// and delivers them as Prometheus Metrics.
// Collect is invoked everytime a prometheus.Gatherer is run
// for example when the HTTP endpoint is invoked by Prometheus.
func (c *collector) Collect(ch chan<- prometheus.Metric) {
// We need a copy of all the view data up until this point.
viewData := c.cloneViewData()
for _, vd := range viewData {
sig := viewSignature(c.opts.Namespace, vd.View)
c.registeredViewsMu.Lock()
desc := c.registeredViews[sig]
c.registeredViewsMu.Unlock()
for _, row := range vd.Rows {
metric, err := c.toMetric(desc, vd.View, row)
if err != nil {
c.opts.onError(err)
} else {
ch <- metric
}
}
}
}
func (c *collector) toMetric(desc *prometheus.Desc, v *view.View, row *view.Row) (prometheus.Metric, error) {
switch data := row.Data.(type) {
case *view.CountData:
return prometheus.NewConstMetric(desc, prometheus.CounterValue, float64(data.Value), tagValues(row.Tags, v.TagKeys)...)
case *view.DistributionData:
points := make(map[float64]uint64)
// Histograms are cumulative in Prometheus.
// Get cumulative bucket counts.
cumCount := uint64(0)
for i, b := range v.Aggregation.Buckets {
cumCount += uint64(data.CountPerBucket[i])
points[b] = cumCount
}
return prometheus.NewConstHistogram(desc, uint64(data.Count), data.Sum(), points, tagValues(row.Tags, v.TagKeys)...)
case *view.SumData:
return prometheus.NewConstMetric(desc, prometheus.UntypedValue, data.Value, tagValues(row.Tags, v.TagKeys)...)
case *view.LastValueData:
return prometheus.NewConstMetric(desc, prometheus.GaugeValue, data.Value, tagValues(row.Tags, v.TagKeys)...)
default:
return nil, fmt.Errorf("aggregation %T is not yet supported", v.Aggregation)
}
}
func tagKeysToLabels(keys []tag.Key) (labels []string) {
for _, key := range keys {
labels = append(labels, internal.Sanitize(key.Name()))
}
return labels
}
func newCollector(opts Options, registrar *prometheus.Registry) *collector {
return &collector{
reg: registrar,
opts: opts,
registeredViews: make(map[string]*prometheus.Desc),
viewData: make(map[string]*view.Data),
}
}
func tagValues(t []tag.Tag, expectedKeys []tag.Key) []string {
var values []string
// Add empty string for all missing keys in the tags map.
idx := 0
for _, t := range t {
for t.Key != expectedKeys[idx] {
idx++
values = append(values, "")
}
values = append(values, t.Value)
idx++
}
for idx < len(expectedKeys) {
idx++
values = append(values, "")
}
return values
}
func viewName(namespace string, v *view.View) string {
var name string
if namespace != "" {
name = namespace + "_"
}
return name + internal.Sanitize(v.Name)
}
func viewSignature(namespace string, v *view.View) string {
var buf bytes.Buffer
buf.WriteString(viewName(namespace, v))
for _, k := range v.TagKeys {
buf.WriteString("-" + k.Name())
}
return buf.String()
}
func (c *collector) cloneViewData() map[string]*view.Data {
c.mu.Lock()
defer c.mu.Unlock()
viewDataCopy := make(map[string]*view.Data)
for sig, viewData := range c.viewData {
viewDataCopy[sig] = viewData
}
return viewDataCopy
}