summaryrefslogtreecommitdiffstats
path: root/vendor/github.com/prometheus/client_golang/prometheus
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/prometheus/client_golang/prometheus')
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/benchmark_test.go24
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/counter.go191
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/counter_test.go160
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/desc.go39
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/desc_test.go17
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/doc.go69
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/example_timer_complex_test.go71
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/example_timer_gauge_test.go48
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/example_timer_test.go40
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/examples_test.go17
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/expvar_collector_test.go2
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/gauge.go204
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/gauge_test.go24
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/go_collector.go51
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/go_collector_test.go106
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/graphite/bridge.go280
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/graphite/bridge_test.go309
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/histogram.go147
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/histogram_test.go24
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/http.go110
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/http_test.go45
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/labels.go57
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/metric.go20
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/observer.go52
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/process_collector.go104
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/process_collector_test.go24
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/promhttp/delegator.go199
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/promhttp/delegator_1_8.go181
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/promhttp/delegator_pre_1_8.go44
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/promhttp/http.go31
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/promhttp/http_test.go6
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_client.go97
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_client_1_8.go144
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_client_1_8_test.go195
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_server.go447
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_server_test.go375
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/push/example_add_from_gatherer_test.go84
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/push/examples_test.go24
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/push/push.go2
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/registry.go68
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/registry_test.go67
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/summary.go173
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/summary_test.go53
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/timer.go51
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/timer_test.go152
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/untyped.go102
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/value.go78
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/value_test.go43
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/vec.go491
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/vec_test.go281
50 files changed, 4657 insertions, 966 deletions
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/benchmark_test.go b/vendor/github.com/prometheus/client_golang/prometheus/benchmark_test.go
index a3d86698b..4a05721dc 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/benchmark_test.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/benchmark_test.go
@@ -129,8 +129,9 @@ func BenchmarkGaugeNoLabels(b *testing.B) {
func BenchmarkSummaryWithLabelValues(b *testing.B) {
m := NewSummaryVec(
SummaryOpts{
- Name: "benchmark_summary",
- Help: "A summary to benchmark it.",
+ Name: "benchmark_summary",
+ Help: "A summary to benchmark it.",
+ Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001},
},
[]string{"one", "two", "three"},
)
@@ -143,8 +144,9 @@ func BenchmarkSummaryWithLabelValues(b *testing.B) {
func BenchmarkSummaryNoLabels(b *testing.B) {
m := NewSummary(SummaryOpts{
- Name: "benchmark_summary",
- Help: "A summary to benchmark it.",
+ Name: "benchmark_summary",
+ Help: "A summary to benchmark it.",
+ Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001},
},
)
b.ReportAllocs()
@@ -181,3 +183,17 @@ func BenchmarkHistogramNoLabels(b *testing.B) {
m.Observe(3.1415)
}
}
+
+func BenchmarkParallelCounter(b *testing.B) {
+ c := NewCounter(CounterOpts{
+ Name: "benchmark_counter",
+ Help: "A Counter to benchmark it.",
+ })
+ b.ReportAllocs()
+ b.ResetTimer()
+ b.RunParallel(func(pb *testing.PB) {
+ for pb.Next() {
+ c.Inc()
+ }
+ })
+}
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/counter.go b/vendor/github.com/prometheus/client_golang/prometheus/counter.go
index ee37949ad..765e4550c 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/counter.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/counter.go
@@ -15,6 +15,10 @@ package prometheus
import (
"errors"
+ "math"
+ "sync/atomic"
+
+ dto "github.com/prometheus/client_model/go"
)
// Counter is a Metric that represents a single numerical value that only ever
@@ -30,16 +34,8 @@ type Counter interface {
Metric
Collector
- // Set is used to set the Counter to an arbitrary value. It is only used
- // if you have to transfer a value from an external counter into this
- // Prometheus metric. Do not use it for regular handling of a
- // Prometheus counter (as it can be used to break the contract of
- // monotonically increasing values).
- //
- // Deprecated: Use NewConstMetric to create a counter for an external
- // value. A Counter should never be set.
- Set(float64)
- // Inc increments the counter by 1.
+ // Inc increments the counter by 1. Use Add to increment it by arbitrary
+ // non-negative values.
Inc()
// Add adds the given value to the counter. It panics if the value is <
// 0.
@@ -50,6 +46,14 @@ type Counter interface {
type CounterOpts Opts
// NewCounter creates a new Counter based on the provided CounterOpts.
+//
+// The returned implementation tracks the counter value in two separate
+// variables, a float64 and a uint64. The latter is used to track calls of the
+// Inc method and calls of the Add method with a value that can be represented
+// as a uint64. This allows atomic increments of the counter with optimal
+// performance. (It is common to have an Inc call in very hot execution paths.)
+// Both internal tracking values are added up in the Write method. This has to
+// be taken into account when it comes to precision and overflow behavior.
func NewCounter(opts CounterOpts) Counter {
desc := NewDesc(
BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
@@ -57,20 +61,58 @@ func NewCounter(opts CounterOpts) Counter {
nil,
opts.ConstLabels,
)
- result := &counter{value: value{desc: desc, valType: CounterValue, labelPairs: desc.constLabelPairs}}
+ result := &counter{desc: desc, labelPairs: desc.constLabelPairs}
result.init(result) // Init self-collection.
return result
}
type counter struct {
- value
+ // valBits contains the bits of the represented float64 value, while
+ // valInt stores values that are exact integers. Both have to go first
+ // in the struct to guarantee alignment for atomic operations.
+ // http://golang.org/pkg/sync/atomic/#pkg-note-BUG
+ valBits uint64
+ valInt uint64
+
+ selfCollector
+ desc *Desc
+
+ labelPairs []*dto.LabelPair
+}
+
+func (c *counter) Desc() *Desc {
+ return c.desc
}
func (c *counter) Add(v float64) {
if v < 0 {
panic(errors.New("counter cannot decrease in value"))
}
- c.value.Add(v)
+ ival := uint64(v)
+ if float64(ival) == v {
+ atomic.AddUint64(&c.valInt, ival)
+ return
+ }
+
+ for {
+ oldBits := atomic.LoadUint64(&c.valBits)
+ newBits := math.Float64bits(math.Float64frombits(oldBits) + v)
+ if atomic.CompareAndSwapUint64(&c.valBits, oldBits, newBits) {
+ return
+ }
+ }
+}
+
+func (c *counter) Inc() {
+ atomic.AddUint64(&c.valInt, 1)
+}
+
+func (c *counter) Write(out *dto.Metric) error {
+ fval := math.Float64frombits(atomic.LoadUint64(&c.valBits))
+ ival := atomic.LoadUint64(&c.valInt)
+ val := fval + float64(ival)
+
+ return populateMetric(CounterValue, val, c.labelPairs, out)
}
// CounterVec is a Collector that bundles a set of Counters that all share the
@@ -78,16 +120,12 @@ func (c *counter) Add(v float64) {
// if you want to count the same thing partitioned by various dimensions
// (e.g. number of HTTP requests, partitioned by response code and
// method). Create instances with NewCounterVec.
-//
-// CounterVec embeds MetricVec. See there for a full list of methods with
-// detailed documentation.
type CounterVec struct {
- *MetricVec
+ *metricVec
}
// NewCounterVec creates a new CounterVec based on the provided CounterOpts and
-// partitioned by the given label names. At least one label name must be
-// provided.
+// partitioned by the given label names.
func NewCounterVec(opts CounterOpts, labelNames []string) *CounterVec {
desc := NewDesc(
BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
@@ -96,34 +134,62 @@ func NewCounterVec(opts CounterOpts, labelNames []string) *CounterVec {
opts.ConstLabels,
)
return &CounterVec{
- MetricVec: newMetricVec(desc, func(lvs ...string) Metric {
- result := &counter{value: value{
- desc: desc,
- valType: CounterValue,
- labelPairs: makeLabelPairs(desc, lvs),
- }}
+ metricVec: newMetricVec(desc, func(lvs ...string) Metric {
+ if len(lvs) != len(desc.variableLabels) {
+ panic(errInconsistentCardinality)
+ }
+ result := &counter{desc: desc, labelPairs: makeLabelPairs(desc, lvs)}
result.init(result) // Init self-collection.
return result
}),
}
}
-// GetMetricWithLabelValues replaces the method of the same name in
-// MetricVec. The difference is that this method returns a Counter and not a
-// Metric so that no type conversion is required.
-func (m *CounterVec) GetMetricWithLabelValues(lvs ...string) (Counter, error) {
- metric, err := m.MetricVec.GetMetricWithLabelValues(lvs...)
+// GetMetricWithLabelValues returns the Counter for the given slice of label
+// values (same order as the VariableLabels in Desc). If that combination of
+// label values is accessed for the first time, a new Counter is created.
+//
+// It is possible to call this method without using the returned Counter to only
+// create the new Counter but leave it at its starting value 0. See also the
+// SummaryVec example.
+//
+// Keeping the Counter for later use is possible (and should be considered if
+// performance is critical), but keep in mind that Reset, DeleteLabelValues and
+// Delete can be used to delete the Counter from the CounterVec. In that case,
+// the Counter will still exist, but it will not be exported anymore, even if a
+// Counter with the same label values is created later.
+//
+// An error is returned if the number of label values is not the same as the
+// number of VariableLabels in Desc (minus any curried labels).
+//
+// Note that for more than one label value, this method is prone to mistakes
+// caused by an incorrect order of arguments. Consider GetMetricWith(Labels) as
+// an alternative to avoid that type of mistake. For higher label numbers, the
+// latter has a much more readable (albeit more verbose) syntax, but it comes
+// with a performance overhead (for creating and processing the Labels map).
+// See also the GaugeVec example.
+func (v *CounterVec) GetMetricWithLabelValues(lvs ...string) (Counter, error) {
+ metric, err := v.metricVec.getMetricWithLabelValues(lvs...)
if metric != nil {
return metric.(Counter), err
}
return nil, err
}
-// GetMetricWith replaces the method of the same name in MetricVec. The
-// difference is that this method returns a Counter and not a Metric so that no
-// type conversion is required.
-func (m *CounterVec) GetMetricWith(labels Labels) (Counter, error) {
- metric, err := m.MetricVec.GetMetricWith(labels)
+// GetMetricWith returns the Counter for the given Labels map (the label names
+// must match those of the VariableLabels in Desc). If that label map is
+// accessed for the first time, a new Counter is created. Implications of
+// creating a Counter without using it and keeping the Counter for later use are
+// the same as for GetMetricWithLabelValues.
+//
+// An error is returned if the number and names of the Labels are inconsistent
+// with those of the VariableLabels in Desc (minus any curried labels).
+//
+// This method is used for the same purpose as
+// GetMetricWithLabelValues(...string). See there for pros and cons of the two
+// methods.
+func (v *CounterVec) GetMetricWith(labels Labels) (Counter, error) {
+ metric, err := v.metricVec.getMetricWith(labels)
if metric != nil {
return metric.(Counter), err
}
@@ -131,18 +197,57 @@ func (m *CounterVec) GetMetricWith(labels Labels) (Counter, error) {
}
// WithLabelValues works as GetMetricWithLabelValues, but panics where
-// GetMetricWithLabelValues would have returned an error. By not returning an
-// error, WithLabelValues allows shortcuts like
+// GetMetricWithLabelValues would have returned an error. Not returning an
+// error allows shortcuts like
// myVec.WithLabelValues("404", "GET").Add(42)
-func (m *CounterVec) WithLabelValues(lvs ...string) Counter {
- return m.MetricVec.WithLabelValues(lvs...).(Counter)
+func (v *CounterVec) WithLabelValues(lvs ...string) Counter {
+ c, err := v.GetMetricWithLabelValues(lvs...)
+ if err != nil {
+ panic(err)
+ }
+ return c
}
// With works as GetMetricWith, but panics where GetMetricWithLabels would have
-// returned an error. By not returning an error, With allows shortcuts like
-// myVec.With(Labels{"code": "404", "method": "GET"}).Add(42)
-func (m *CounterVec) With(labels Labels) Counter {
- return m.MetricVec.With(labels).(Counter)
+// returned an error. Not returning an error allows shortcuts like
+// myVec.With(prometheus.Labels{"code": "404", "method": "GET"}).Add(42)
+func (v *CounterVec) With(labels Labels) Counter {
+ c, err := v.GetMetricWith(labels)
+ if err != nil {
+ panic(err)
+ }
+ return c
+}
+
+// CurryWith returns a vector curried with the provided labels, i.e. the
+// returned vector has those labels pre-set for all labeled operations performed
+// on it. The cardinality of the curried vector is reduced accordingly. The
+// order of the remaining labels stays the same (just with the curried labels
+// taken out of the sequence – which is relevant for the
+// (GetMetric)WithLabelValues methods). It is possible to curry a curried
+// vector, but only with labels not yet used for currying before.
+//
+// The metrics contained in the CounterVec are shared between the curried and
+// uncurried vectors. They are just accessed differently. Curried and uncurried
+// vectors behave identically in terms of collection. Only one must be
+// registered with a given registry (usually the uncurried version). The Reset
+// method deletes all metrics, even if called on a curried vector.
+func (v *CounterVec) CurryWith(labels Labels) (*CounterVec, error) {
+ vec, err := v.curryWith(labels)
+ if vec != nil {
+ return &CounterVec{vec}, err
+ }
+ return nil, err
+}
+
+// MustCurryWith works as CurryWith but panics where CurryWith would have
+// returned an error.
+func (v *CounterVec) MustCurryWith(labels Labels) *CounterVec {
+ vec, err := v.CurryWith(labels)
+ if err != nil {
+ panic(err)
+ }
+ return vec
}
// CounterFunc is a Counter whose value is determined at collect time by calling a
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/counter_test.go b/vendor/github.com/prometheus/client_golang/prometheus/counter_test.go
index 67391a23a..5062f51af 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/counter_test.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/counter_test.go
@@ -14,6 +14,7 @@
package prometheus
import (
+ "fmt"
"math"
"testing"
@@ -27,13 +28,27 @@ func TestCounterAdd(t *testing.T) {
ConstLabels: Labels{"a": "1", "b": "2"},
}).(*counter)
counter.Inc()
- if expected, got := 1., math.Float64frombits(counter.valBits); expected != got {
+ if expected, got := 0.0, math.Float64frombits(counter.valBits); expected != got {
t.Errorf("Expected %f, got %f.", expected, got)
}
+ if expected, got := uint64(1), counter.valInt; expected != got {
+ t.Errorf("Expected %d, got %d.", expected, got)
+ }
counter.Add(42)
- if expected, got := 43., math.Float64frombits(counter.valBits); expected != got {
+ if expected, got := 0.0, math.Float64frombits(counter.valBits); expected != got {
t.Errorf("Expected %f, got %f.", expected, got)
}
+ if expected, got := uint64(43), counter.valInt; expected != got {
+ t.Errorf("Expected %d, got %d.", expected, got)
+ }
+
+ counter.Add(24.42)
+ if expected, got := 24.42, math.Float64frombits(counter.valBits); expected != got {
+ t.Errorf("Expected %f, got %f.", expected, got)
+ }
+ if expected, got := uint64(43), counter.valInt; expected != got {
+ t.Errorf("Expected %d, got %d.", expected, got)
+ }
if expected, got := "counter cannot decrease in value", decreaseCounter(counter).Error(); expected != got {
t.Errorf("Expected error %q, got %q.", expected, got)
@@ -42,7 +57,7 @@ func TestCounterAdd(t *testing.T) {
m := &dto.Metric{}
counter.Write(m)
- if expected, got := `label:<name:"a" value:"1" > label:<name:"b" value:"2" > counter:<value:43 > `, m.String(); expected != got {
+ if expected, got := `label:<name:"a" value:"1" > label:<name:"b" value:"2" > counter:<value:67.42 > `, m.String(); expected != got {
t.Errorf("expected %q, got %q", expected, got)
}
}
@@ -56,3 +71,142 @@ func decreaseCounter(c *counter) (err error) {
c.Add(-1)
return nil
}
+
+func TestCounterVecGetMetricWithInvalidLabelValues(t *testing.T) {
+ testCases := []struct {
+ desc string
+ labels Labels
+ }{
+ {
+ desc: "non utf8 label value",
+ labels: Labels{"a": "\xFF"},
+ },
+ {
+ desc: "not enough label values",
+ labels: Labels{},
+ },
+ {
+ desc: "too many label values",
+ labels: Labels{"a": "1", "b": "2"},
+ },
+ }
+
+ for _, test := range testCases {
+ counterVec := NewCounterVec(CounterOpts{
+ Name: "test",
+ }, []string{"a"})
+
+ labelValues := make([]string, len(test.labels))
+ for _, val := range test.labels {
+ labelValues = append(labelValues, val)
+ }
+
+ expectPanic(t, func() {
+ counterVec.WithLabelValues(labelValues...)
+ }, fmt.Sprintf("WithLabelValues: expected panic because: %s", test.desc))
+ expectPanic(t, func() {
+ counterVec.With(test.labels)
+ }, fmt.Sprintf("WithLabelValues: expected panic because: %s", test.desc))
+
+ if _, err := counterVec.GetMetricWithLabelValues(labelValues...); err == nil {
+ t.Errorf("GetMetricWithLabelValues: expected error because: %s", test.desc)
+ }
+ if _, err := counterVec.GetMetricWith(test.labels); err == nil {
+ t.Errorf("GetMetricWith: expected error because: %s", test.desc)
+ }
+ }
+}
+
+func expectPanic(t *testing.T, op func(), errorMsg string) {
+ defer func() {
+ if err := recover(); err == nil {
+ t.Error(errorMsg)
+ }
+ }()
+
+ op()
+}
+
+func TestCounterAddInf(t *testing.T) {
+ counter := NewCounter(CounterOpts{
+ Name: "test",
+ Help: "test help",
+ }).(*counter)
+
+ counter.Inc()
+ if expected, got := 0.0, math.Float64frombits(counter.valBits); expected != got {
+ t.Errorf("Expected %f, got %f.", expected, got)
+ }
+ if expected, got := uint64(1), counter.valInt; expected != got {
+ t.Errorf("Expected %d, got %d.", expected, got)
+ }
+
+ counter.Add(math.Inf(1))
+ if expected, got := math.Inf(1), math.Float64frombits(counter.valBits); expected != got {
+ t.Errorf("valBits expected %f, got %f.", expected, got)
+ }
+ if expected, got := uint64(1), counter.valInt; expected != got {
+ t.Errorf("valInts expected %d, got %d.", expected, got)
+ }
+
+ counter.Inc()
+ if expected, got := math.Inf(1), math.Float64frombits(counter.valBits); expected != got {
+ t.Errorf("Expected %f, got %f.", expected, got)
+ }
+ if expected, got := uint64(2), counter.valInt; expected != got {
+ t.Errorf("Expected %d, got %d.", expected, got)
+ }
+
+ m := &dto.Metric{}
+ counter.Write(m)
+
+ if expected, got := `counter:<value:inf > `, m.String(); expected != got {
+ t.Errorf("expected %q, got %q", expected, got)
+ }
+}
+
+func TestCounterAddLarge(t *testing.T) {
+ counter := NewCounter(CounterOpts{
+ Name: "test",
+ Help: "test help",
+ }).(*counter)
+
+ // large overflows the underlying type and should therefore be stored in valBits.
+ large := float64(math.MaxUint64 + 1)
+ counter.Add(large)
+ if expected, got := large, math.Float64frombits(counter.valBits); expected != got {
+ t.Errorf("valBits expected %f, got %f.", expected, got)
+ }
+ if expected, got := uint64(0), counter.valInt; expected != got {
+ t.Errorf("valInts expected %d, got %d.", expected, got)
+ }
+
+ m := &dto.Metric{}
+ counter.Write(m)
+
+ if expected, got := fmt.Sprintf("counter:<value:%0.16e > ", large), m.String(); expected != got {
+ t.Errorf("expected %q, got %q", expected, got)
+ }
+}
+
+func TestCounterAddSmall(t *testing.T) {
+ counter := NewCounter(CounterOpts{
+ Name: "test",
+ Help: "test help",
+ }).(*counter)
+ small := 0.000000000001
+ counter.Add(small)
+ if expected, got := small, math.Float64frombits(counter.valBits); expected != got {
+ t.Errorf("valBits expected %f, got %f.", expected, got)
+ }
+ if expected, got := uint64(0), counter.valInt; expected != got {
+ t.Errorf("valInts expected %d, got %d.", expected, got)
+ }
+
+ m := &dto.Metric{}
+ counter.Write(m)
+
+ if expected, got := fmt.Sprintf("counter:<value:%0.0e > ", small), m.String(); expected != got {
+ t.Errorf("expected %q, got %q", expected, got)
+ }
+}
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/desc.go b/vendor/github.com/prometheus/client_golang/prometheus/desc.go
index 77f4b30e8..4a755b0fa 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/desc.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/desc.go
@@ -16,33 +16,15 @@ package prometheus
import (
"errors"
"fmt"
- "regexp"
"sort"
"strings"
"github.com/golang/protobuf/proto"
+ "github.com/prometheus/common/model"
dto "github.com/prometheus/client_model/go"
)
-var (
- metricNameRE = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_:]*$`)
- labelNameRE = regexp.MustCompile("^[a-zA-Z_][a-zA-Z0-9_]*$")
-)
-
-// reservedLabelPrefix is a prefix which is not legal in user-supplied
-// label names.
-const reservedLabelPrefix = "__"
-
-// Labels represents a collection of label name -> value mappings. This type is
-// commonly used with the With(Labels) and GetMetricWith(Labels) methods of
-// metric vector Collectors, e.g.:
-// myVec.With(Labels{"code": "404", "method": "GET"}).Add(42)
-//
-// The other use-case is the specification of constant label pairs in Opts or to
-// create a Desc.
-type Labels map[string]string
-
// Desc is the descriptor used by every Prometheus Metric. It is essentially
// the immutable meta-data of a Metric. The normal Metric implementations
// included in this package manage their Desc under the hood. Users only have to
@@ -78,7 +60,7 @@ type Desc struct {
// Help string. Each Desc with the same fqName must have the same
// dimHash.
dimHash uint64
- // err is an error that occured during construction. It is reported on
+ // err is an error that occurred during construction. It is reported on
// registration time.
err error
}
@@ -91,8 +73,7 @@ type Desc struct {
// and therefore not part of the Desc. (They are managed within the Metric.)
//
// For constLabels, the label values are constant. Therefore, they are fully
-// specified in the Desc. See the Opts documentation for the implications of
-// constant labels.
+// specified in the Desc. See the Collector example for a usage pattern.
func NewDesc(fqName, help string, variableLabels []string, constLabels Labels) *Desc {
d := &Desc{
fqName: fqName,
@@ -103,7 +84,7 @@ func NewDesc(fqName, help string, variableLabels []string, constLabels Labels) *
d.err = errors.New("empty help string")
return d
}
- if !metricNameRE.MatchString(fqName) {
+ if !model.IsValidMetricName(model.LabelValue(fqName)) {
d.err = fmt.Errorf("%q is not a valid metric name", fqName)
return d
}
@@ -127,6 +108,12 @@ func NewDesc(fqName, help string, variableLabels []string, constLabels Labels) *
for _, labelName := range labelNames {
labelValues = append(labelValues, constLabels[labelName])
}
+ // Validate the const label values. They can't have a wrong cardinality, so
+ // use in len(labelValues) as expectedNumberOfValues.
+ if err := validateLabelValues(labelValues, len(labelValues)); err != nil {
+ d.err = err
+ return d
+ }
// Now add the variable label names, but prefix them with something that
// cannot be in a regular label name. That prevents matching the label
// dimension with a different mix between preset and variable labels.
@@ -142,6 +129,7 @@ func NewDesc(fqName, help string, variableLabels []string, constLabels Labels) *
d.err = errors.New("duplicate label names")
return d
}
+
vh := hashNew()
for _, val := range labelValues {
vh = hashAdd(vh, val)
@@ -198,8 +186,3 @@ func (d *Desc) String() string {
d.variableLabels,
)
}
-
-func checkLabelName(l string) bool {
- return labelNameRE.MatchString(l) &&
- !strings.HasPrefix(l, reservedLabelPrefix)
-}
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/desc_test.go b/vendor/github.com/prometheus/client_golang/prometheus/desc_test.go
new file mode 100644
index 000000000..2f962652c
--- /dev/null
+++ b/vendor/github.com/prometheus/client_golang/prometheus/desc_test.go
@@ -0,0 +1,17 @@
+package prometheus
+
+import (
+ "testing"
+)
+
+func TestNewDescInvalidLabelValues(t *testing.T) {
+ desc := NewDesc(
+ "sample_label",
+ "sample label",
+ nil,
+ Labels{"a": "\xFF"},
+ )
+ if desc.err == nil {
+ t.Errorf("NewDesc: expected error because: %s", desc.err)
+ }
+}
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/doc.go b/vendor/github.com/prometheus/client_golang/prometheus/doc.go
index b15a2d3b9..36ef15567 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/doc.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/doc.go
@@ -17,7 +17,7 @@
// Pushgateway (package push).
//
// All exported functions and methods are safe to be used concurrently unless
-//specified otherwise.
+// specified otherwise.
//
// A Basic Example
//
@@ -26,6 +26,7 @@
// package main
//
// import (
+// "log"
// "net/http"
//
// "github.com/prometheus/client_golang/prometheus"
@@ -59,7 +60,7 @@
// // The Handler function provides a default handler to expose metrics
// // via an HTTP server. "/metrics" is the usual endpoint for that.
// http.Handle("/metrics", promhttp.Handler())
-// http.ListenAndServe(":8080", nil)
+// log.Fatal(http.ListenAndServe(":8080", nil))
// }
//
//
@@ -69,7 +70,7 @@
// Metrics
//
// The number of exported identifiers in this package might appear a bit
-// overwhelming. Hovever, in addition to the basic plumbing shown in the example
+// overwhelming. However, in addition to the basic plumbing shown in the example
// above, you only need to understand the different metric types and their
// vector versions for basic usage.
//
@@ -95,8 +96,8 @@
// SummaryVec, HistogramVec, and UntypedVec are not.
//
// To create instances of Metrics and their vector versions, you need a suitable
-// …Opts struct, i.e. GaugeOpts, CounterOpts, SummaryOpts,
-// HistogramOpts, or UntypedOpts.
+// …Opts struct, i.e. GaugeOpts, CounterOpts, SummaryOpts, HistogramOpts, or
+// UntypedOpts.
//
// Custom Collectors and constant Metrics
//
@@ -114,8 +115,8 @@
// Metric instances “on the fly” using NewConstMetric, NewConstHistogram, and
// NewConstSummary (and their respective Must… versions). That will happen in
// the Collect method. The Describe method has to return separate Desc
-// instances, representative of the “throw-away” metrics to be created
-// later. NewDesc comes in handy to create those Desc instances.
+// instances, representative of the “throw-away” metrics to be created later.
+// NewDesc comes in handy to create those Desc instances.
//
// The Collector example illustrates the use case. You can also look at the
// source code of the processCollector (mirroring process metrics), the
@@ -129,34 +130,34 @@
// Advanced Uses of the Registry
//
// While MustRegister is the by far most common way of registering a Collector,
-// sometimes you might want to handle the errors the registration might
-// cause. As suggested by the name, MustRegister panics if an error occurs. With
-// the Register function, the error is returned and can be handled.
+// sometimes you might want to handle the errors the registration might cause.
+// As suggested by the name, MustRegister panics if an error occurs. With the
+// Register function, the error is returned and can be handled.
//
// An error is returned if the registered Collector is incompatible or
// inconsistent with already registered metrics. The registry aims for
-// consistency of the collected metrics according to the Prometheus data
-// model. Inconsistencies are ideally detected at registration time, not at
-// collect time. The former will usually be detected at start-up time of a
-// program, while the latter will only happen at scrape time, possibly not even
-// on the first scrape if the inconsistency only becomes relevant later. That is
-// the main reason why a Collector and a Metric have to describe themselves to
-// the registry.
+// consistency of the collected metrics according to the Prometheus data model.
+// Inconsistencies are ideally detected at registration time, not at collect
+// time. The former will usually be detected at start-up time of a program,
+// while the latter will only happen at scrape time, possibly not even on the
+// first scrape if the inconsistency only becomes relevant later. That is the
+// main reason why a Collector and a Metric have to describe themselves to the
+// registry.
//
// So far, everything we did operated on the so-called default registry, as it
-// can be found in the global DefaultRegistry variable. With NewRegistry, you
+// can be found in the global DefaultRegisterer variable. With NewRegistry, you
// can create a custom registry, or you can even implement the Registerer or
-// Gatherer interfaces yourself. The methods Register and Unregister work in
-// the same way on a custom registry as the global functions Register and
-// Unregister on the default registry.
-//
-// There are a number of uses for custom registries: You can use registries
-// with special properties, see NewPedanticRegistry. You can avoid global state,
-// as it is imposed by the DefaultRegistry. You can use multiple registries at
-// the same time to expose different metrics in different ways. You can use
+// Gatherer interfaces yourself. The methods Register and Unregister work in the
+// same way on a custom registry as the global functions Register and Unregister
+// on the default registry.
+//
+// There are a number of uses for custom registries: You can use registries with
+// special properties, see NewPedanticRegistry. You can avoid global state, as
+// it is imposed by the DefaultRegisterer. You can use multiple registries at
+// the same time to expose different metrics in different ways. You can use
// separate registries for testing purposes.
//
-// Also note that the DefaultRegistry comes registered with a Collector for Go
+// Also note that the DefaultRegisterer comes registered with a Collector for Go
// runtime metrics (via NewGoCollector) and a Collector for process metrics (via
// NewProcessCollector). With a custom registry, you are in control and decide
// yourself about the Collectors to register.
@@ -166,16 +167,20 @@
// The Registry implements the Gatherer interface. The caller of the Gather
// method can then expose the gathered metrics in some way. Usually, the metrics
// are served via HTTP on the /metrics endpoint. That's happening in the example
-// above. The tools to expose metrics via HTTP are in the promhttp
-// sub-package. (The top-level functions in the prometheus package are
-// deprecated.)
+// above. The tools to expose metrics via HTTP are in the promhttp sub-package.
+// (The top-level functions in the prometheus package are deprecated.)
//
// Pushing to the Pushgateway
//
// Function for pushing to the Pushgateway can be found in the push sub-package.
//
+// Graphite Bridge
+//
+// Functions and examples to push metrics from a Gatherer to Graphite can be
+// found in the graphite sub-package.
+//
// Other Means of Exposition
//
-// More ways of exposing metrics can easily be added. Sending metrics to
-// Graphite would be an example that will soon be implemented.
+// More ways of exposing metrics can easily be added by following the approaches
+// of the existing implementations.
package prometheus
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/example_timer_complex_test.go b/vendor/github.com/prometheus/client_golang/prometheus/example_timer_complex_test.go
new file mode 100644
index 000000000..c5e7de5e5
--- /dev/null
+++ b/vendor/github.com/prometheus/client_golang/prometheus/example_timer_complex_test.go
@@ -0,0 +1,71 @@
+// Copyright 2014 The Prometheus 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_test
+
+import (
+ "net/http"
+
+ "github.com/prometheus/client_golang/prometheus"
+)
+
+var (
+ // apiRequestDuration tracks the duration separate for each HTTP status
+ // class (1xx, 2xx, ...). This creates a fair amount of time series on
+ // the Prometheus server. Usually, you would track the duration of
+ // serving HTTP request without partitioning by outcome. Do something
+ // like this only if needed. Also note how only status classes are
+ // tracked, not every single status code. The latter would create an
+ // even larger amount of time series. Request counters partitioned by
+ // status code are usually OK as each counter only creates one time
+ // series. Histograms are way more expensive, so partition with care and
+ // only where you really need separate latency tracking. Partitioning by
+ // status class is only an example. In concrete cases, other partitions
+ // might make more sense.
+ apiRequestDuration = prometheus.NewHistogramVec(
+ prometheus.HistogramOpts{
+ Name: "api_request_duration_seconds",
+ Help: "Histogram for the request duration of the public API, partitioned by status class.",
+ Buckets: prometheus.ExponentialBuckets(0.1, 1.5, 5),
+ },
+ []string{"status_class"},
+ )
+)
+
+func handler(w http.ResponseWriter, r *http.Request) {
+ status := http.StatusOK
+ // The ObserverFunc gets called by the deferred ObserveDuration and
+ // decides which Histogram's Observe method is called.
+ timer := prometheus.NewTimer(prometheus.ObserverFunc(func(v float64) {
+ switch {
+ case status >= 500: // Server error.
+ apiRequestDuration.WithLabelValues("5xx").Observe(v)
+ case status >= 400: // Client error.
+ apiRequestDuration.WithLabelValues("4xx").Observe(v)
+ case status >= 300: // Redirection.
+ apiRequestDuration.WithLabelValues("3xx").Observe(v)
+ case status >= 200: // Success.
+ apiRequestDuration.WithLabelValues("2xx").Observe(v)
+ default: // Informational.
+ apiRequestDuration.WithLabelValues("1xx").Observe(v)
+ }
+ }))
+ defer timer.ObserveDuration()
+
+ // Handle the request. Set status accordingly.
+ // ...
+}
+
+func ExampleTimer_complex() {
+ http.HandleFunc("/api", handler)
+}
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/example_timer_gauge_test.go b/vendor/github.com/prometheus/client_golang/prometheus/example_timer_gauge_test.go
new file mode 100644
index 000000000..7184a0d1d
--- /dev/null
+++ b/vendor/github.com/prometheus/client_golang/prometheus/example_timer_gauge_test.go
@@ -0,0 +1,48 @@
+// Copyright 2014 The Prometheus 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_test
+
+import (
+ "os"
+
+ "github.com/prometheus/client_golang/prometheus"
+)
+
+var (
+ // If a function is called rarely (i.e. not more often than scrapes
+ // happen) or ideally only once (like in a batch job), it can make sense
+ // to use a Gauge for timing the function call. For timing a batch job
+ // and pushing the result to a Pushgateway, see also the comprehensive
+ // example in the push package.
+ funcDuration = prometheus.NewGauge(prometheus.GaugeOpts{
+ Name: "example_function_duration_seconds",
+ Help: "Duration of the last call of an example function.",
+ })
+)
+
+func run() error {
+ // The Set method of the Gauge is used to observe the duration.
+ timer := prometheus.NewTimer(prometheus.ObserverFunc(funcDuration.Set))
+ defer timer.ObserveDuration()
+
+ // Do something. Return errors as encountered. The use of 'defer' above
+ // makes sure the function is still timed properly.
+ return nil
+}
+
+func ExampleTimer_gauge() {
+ if err := run(); err != nil {
+ os.Exit(1)
+ }
+}
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/example_timer_test.go b/vendor/github.com/prometheus/client_golang/prometheus/example_timer_test.go
new file mode 100644
index 000000000..bd86bb472
--- /dev/null
+++ b/vendor/github.com/prometheus/client_golang/prometheus/example_timer_test.go
@@ -0,0 +1,40 @@
+// Copyright 2014 The Prometheus 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_test
+
+import (
+ "math/rand"
+ "time"
+
+ "github.com/prometheus/client_golang/prometheus"
+)
+
+var (
+ requestDuration = prometheus.NewHistogram(prometheus.HistogramOpts{
+ Name: "example_request_duration_seconds",
+ Help: "Histogram for the runtime of a simple example function.",
+ Buckets: prometheus.LinearBuckets(0.01, 0.01, 10),
+ })
+)
+
+func ExampleTimer() {
+ // timer times this example function. It uses a Histogram, but a Summary
+ // would also work, as both implement Observer. Check out
+ // https://prometheus.io/docs/practices/histograms/ for differences.
+ timer := prometheus.NewTimer(requestDuration)
+ defer timer.ObserveDuration()
+
+ // Do something here that takes time.
+ time.Sleep(time.Duration(rand.NormFloat64()*10000+50000) * time.Microsecond)
+}
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/examples_test.go b/vendor/github.com/prometheus/client_golang/prometheus/examples_test.go
index f87f21a8f..45f60650f 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/examples_test.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/examples_test.go
@@ -113,7 +113,7 @@ func ExampleCounter() {
pushComplete := make(chan struct{})
// TODO: Start a goroutine that performs repository pushes and reports
// each completion via the channel.
- for _ = range pushComplete {
+ for range pushComplete {
pushCounter.Inc()
}
// Output:
@@ -169,8 +169,8 @@ func ExampleInstrumentHandler() {
func ExampleLabelPairSorter() {
labelPairs := []*dto.LabelPair{
- &dto.LabelPair{Name: proto.String("status"), Value: proto.String("404")},
- &dto.LabelPair{Name: proto.String("method"), Value: proto.String("get")},
+ {Name: proto.String("status"), Value: proto.String("404")},
+ {Name: proto.String("method"), Value: proto.String("get")},
}
sort.Sort(prometheus.LabelPairSorter(labelPairs))
@@ -334,8 +334,9 @@ func ExampleRegister() {
func ExampleSummary() {
temps := prometheus.NewSummary(prometheus.SummaryOpts{
- Name: "pond_temperature_celsius",
- Help: "The temperature of the frog pond.", // Sorry, we can't measure how badly it smells.
+ Name: "pond_temperature_celsius",
+ Help: "The temperature of the frog pond.",
+ Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001},
})
// Simulate some observations.
@@ -372,8 +373,9 @@ func ExampleSummary() {
func ExampleSummaryVec() {
temps := prometheus.NewSummaryVec(
prometheus.SummaryOpts{
- Name: "pond_temperature_celsius",
- Help: "The temperature of the frog pond.", // Sorry, we can't measure how badly it smells.
+ Name: "pond_temperature_celsius",
+ Help: "The temperature of the frog pond.",
+ Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001},
},
[]string{"species"},
)
@@ -640,6 +642,7 @@ func ExampleAlreadyRegisteredError() {
panic(err)
}
}
+ reqCounter.Inc()
}
func ExampleGatherers() {
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/expvar_collector_test.go b/vendor/github.com/prometheus/client_golang/prometheus/expvar_collector_test.go
index 5d3128fae..910dac325 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/expvar_collector_test.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/expvar_collector_test.go
@@ -24,7 +24,7 @@ import (
"github.com/prometheus/client_golang/prometheus"
)
-func ExampleExpvarCollector() {
+func ExampleNewExpvarCollector() {
expvarCollector := prometheus.NewExpvarCollector(map[string]*prometheus.Desc{
"memstats": prometheus.NewDesc(
"expvar_memstats",
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/gauge.go b/vendor/github.com/prometheus/client_golang/prometheus/gauge.go
index 8b70e5141..17c72d7eb 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/gauge.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/gauge.go
@@ -13,6 +13,14 @@
package prometheus
+import (
+ "math"
+ "sync/atomic"
+ "time"
+
+ dto "github.com/prometheus/client_model/go"
+)
+
// Gauge is a Metric that represents a single numerical value that can
// arbitrarily go up and down.
//
@@ -27,29 +35,95 @@ type Gauge interface {
// Set sets the Gauge to an arbitrary value.
Set(float64)
- // Inc increments the Gauge by 1.
+ // Inc increments the Gauge by 1. Use Add to increment it by arbitrary
+ // values.
Inc()
- // Dec decrements the Gauge by 1.
+ // Dec decrements the Gauge by 1. Use Sub to decrement it by arbitrary
+ // values.
Dec()
- // Add adds the given value to the Gauge. (The value can be
- // negative, resulting in a decrease of the Gauge.)
+ // Add adds the given value to the Gauge. (The value can be negative,
+ // resulting in a decrease of the Gauge.)
Add(float64)
// Sub subtracts the given value from the Gauge. (The value can be
// negative, resulting in an increase of the Gauge.)
Sub(float64)
+
+ // SetToCurrentTime sets the Gauge to the current Unix time in seconds.
+ SetToCurrentTime()
}
// GaugeOpts is an alias for Opts. See there for doc comments.
type GaugeOpts Opts
// NewGauge creates a new Gauge based on the provided GaugeOpts.
+//
+// The returned implementation is optimized for a fast Set method. If you have a
+// choice for managing the value of a Gauge via Set vs. Inc/Dec/Add/Sub, pick
+// the former. For example, the Inc method of the returned Gauge is slower than
+// the Inc method of a Counter returned by NewCounter. This matches the typical
+// scenarios for Gauges and Counters, where the former tends to be Set-heavy and
+// the latter Inc-heavy.
func NewGauge(opts GaugeOpts) Gauge {
- return newValue(NewDesc(
+ desc := NewDesc(
BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
opts.Help,
nil,
opts.ConstLabels,
- ), GaugeValue, 0)
+ )
+ result := &gauge{desc: desc, labelPairs: desc.constLabelPairs}
+ result.init(result) // Init self-collection.
+ return result
+}
+
+type gauge struct {
+ // valBits contains the bits of the represented float64 value. It has
+ // to go first in the struct to guarantee alignment for atomic
+ // operations. http://golang.org/pkg/sync/atomic/#pkg-note-BUG
+ valBits uint64
+
+ selfCollector
+
+ desc *Desc
+ labelPairs []*dto.LabelPair
+}
+
+func (g *gauge) Desc() *Desc {
+ return g.desc
+}
+
+func (g *gauge) Set(val float64) {
+ atomic.StoreUint64(&g.valBits, math.Float64bits(val))
+}
+
+func (g *gauge) SetToCurrentTime() {
+ g.Set(float64(time.Now().UnixNano()) / 1e9)
+}
+
+func (g *gauge) Inc() {
+ g.Add(1)
+}
+
+func (g *gauge) Dec() {
+ g.Add(-1)
+}
+
+func (g *gauge) Add(val float64) {
+ for {
+ oldBits := atomic.LoadUint64(&g.valBits)
+ newBits := math.Float64bits(math.Float64frombits(oldBits) + val)
+ if atomic.CompareAndSwapUint64(&g.valBits, oldBits, newBits) {
+ return
+ }
+ }
+}
+
+func (g *gauge) Sub(val float64) {
+ g.Add(val * -1)
+}
+
+func (g *gauge) Write(out *dto.Metric) error {
+ val := math.Float64frombits(atomic.LoadUint64(&g.valBits))
+ return populateMetric(GaugeValue, val, g.labelPairs, out)
}
// GaugeVec is a Collector that bundles a set of Gauges that all share the same
@@ -58,12 +132,11 @@ func NewGauge(opts GaugeOpts) Gauge {
// (e.g. number of operations queued, partitioned by user and operation
// type). Create instances with NewGaugeVec.
type GaugeVec struct {
- *MetricVec
+ *metricVec
}
// NewGaugeVec creates a new GaugeVec based on the provided GaugeOpts and
-// partitioned by the given label names. At least one label name must be
-// provided.
+// partitioned by the given label names.
func NewGaugeVec(opts GaugeOpts, labelNames []string) *GaugeVec {
desc := NewDesc(
BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
@@ -72,28 +145,62 @@ func NewGaugeVec(opts GaugeOpts, labelNames []string) *GaugeVec {
opts.ConstLabels,
)
return &GaugeVec{
- MetricVec: newMetricVec(desc, func(lvs ...string) Metric {
- return newValue(desc, GaugeValue, 0, lvs...)
+ metricVec: newMetricVec(desc, func(lvs ...string) Metric {
+ if len(lvs) != len(desc.variableLabels) {
+ panic(errInconsistentCardinality)
+ }
+ result := &gauge{desc: desc, labelPairs: makeLabelPairs(desc, lvs)}
+ result.init(result) // Init self-collection.
+ return result
}),
}
}
-// GetMetricWithLabelValues replaces the method of the same name in
-// MetricVec. The difference is that this method returns a Gauge and not a
-// Metric so that no type conversion is required.
-func (m *GaugeVec) GetMetricWithLabelValues(lvs ...string) (Gauge, error) {
- metric, err := m.MetricVec.GetMetricWithLabelValues(lvs...)
+// GetMetricWithLabelValues returns the Gauge for the given slice of label
+// values (same order as the VariableLabels in Desc). If that combination of
+// label values is accessed for the first time, a new Gauge is created.
+//
+// It is possible to call this method without using the returned Gauge to only
+// create the new Gauge but leave it at its starting value 0. See also the
+// SummaryVec example.
+//
+// Keeping the Gauge for later use is possible (and should be considered if
+// performance is critical), but keep in mind that Reset, DeleteLabelValues and
+// Delete can be used to delete the Gauge from the GaugeVec. In that case, the
+// Gauge will still exist, but it will not be exported anymore, even if a
+// Gauge with the same label values is created later. See also the CounterVec
+// example.
+//
+// An error is returned if the number of label values is not the same as the
+// number of VariableLabels in Desc (minus any curried labels).
+//
+// Note that for more than one label value, this method is prone to mistakes
+// caused by an incorrect order of arguments. Consider GetMetricWith(Labels) as
+// an alternative to avoid that type of mistake. For higher label numbers, the
+// latter has a much more readable (albeit more verbose) syntax, but it comes
+// with a performance overhead (for creating and processing the Labels map).
+func (v *GaugeVec) GetMetricWithLabelValues(lvs ...string) (Gauge, error) {
+ metric, err := v.metricVec.getMetricWithLabelValues(lvs...)
if metric != nil {
return metric.(Gauge), err
}
return nil, err
}
-// GetMetricWith replaces the method of the same name in MetricVec. The
-// difference is that this method returns a Gauge and not a Metric so that no
-// type conversion is required.
-func (m *GaugeVec) GetMetricWith(labels Labels) (Gauge, error) {
- metric, err := m.MetricVec.GetMetricWith(labels)
+// GetMetricWith returns the Gauge for the given Labels map (the label names
+// must match those of the VariableLabels in Desc). If that label map is
+// accessed for the first time, a new Gauge is created. Implications of
+// creating a Gauge without using it and keeping the Gauge for later use are
+// the same as for GetMetricWithLabelValues.
+//
+// An error is returned if the number and names of the Labels are inconsistent
+// with those of the VariableLabels in Desc (minus any curried labels).
+//
+// This method is used for the same purpose as
+// GetMetricWithLabelValues(...string). See there for pros and cons of the two
+// methods.
+func (v *GaugeVec) GetMetricWith(labels Labels) (Gauge, error) {
+ metric, err := v.metricVec.getMetricWith(labels)
if metric != nil {
return metric.(Gauge), err
}
@@ -101,18 +208,57 @@ func (m *GaugeVec) GetMetricWith(labels Labels) (Gauge, error) {
}
// WithLabelValues works as GetMetricWithLabelValues, but panics where
-// GetMetricWithLabelValues would have returned an error. By not returning an
-// error, WithLabelValues allows shortcuts like
+// GetMetricWithLabelValues would have returned an error. Not returning an
+// error allows shortcuts like
// myVec.WithLabelValues("404", "GET").Add(42)
-func (m *GaugeVec) WithLabelValues(lvs ...string) Gauge {
- return m.MetricVec.WithLabelValues(lvs...).(Gauge)
+func (v *GaugeVec) WithLabelValues(lvs ...string) Gauge {
+ g, err := v.GetMetricWithLabelValues(lvs...)
+ if err != nil {
+ panic(err)
+ }
+ return g
}
// With works as GetMetricWith, but panics where GetMetricWithLabels would have
-// returned an error. By not returning an error, With allows shortcuts like
-// myVec.With(Labels{"code": "404", "method": "GET"}).Add(42)
-func (m *GaugeVec) With(labels Labels) Gauge {
- return m.MetricVec.With(labels).(Gauge)
+// returned an error. Not returning an error allows shortcuts like
+// myVec.With(prometheus.Labels{"code": "404", "method": "GET"}).Add(42)
+func (v *GaugeVec) With(labels Labels) Gauge {
+ g, err := v.GetMetricWith(labels)
+ if err != nil {
+ panic(err)
+ }
+ return g
+}
+
+// CurryWith returns a vector curried with the provided labels, i.e. the
+// returned vector has those labels pre-set for all labeled operations performed
+// on it. The cardinality of the curried vector is reduced accordingly. The
+// order of the remaining labels stays the same (just with the curried labels
+// taken out of the sequence – which is relevant for the
+// (GetMetric)WithLabelValues methods). It is possible to curry a curried
+// vector, but only with labels not yet used for currying before.
+//
+// The metrics contained in the GaugeVec are shared between the curried and
+// uncurried vectors. They are just accessed differently. Curried and uncurried
+// vectors behave identically in terms of collection. Only one must be
+// registered with a given registry (usually the uncurried version). The Reset
+// method deletes all metrics, even if called on a curried vector.
+func (v *GaugeVec) CurryWith(labels Labels) (*GaugeVec, error) {
+ vec, err := v.curryWith(labels)
+ if vec != nil {
+ return &GaugeVec{vec}, err
+ }
+ return nil, err
+}
+
+// MustCurryWith works as CurryWith but panics where CurryWith would have
+// returned an error.
+func (v *GaugeVec) MustCurryWith(labels Labels) *GaugeVec {
+ vec, err := v.CurryWith(labels)
+ if err != nil {
+ panic(err)
+ }
+ return vec
}
// GaugeFunc is a Gauge whose value is determined at collect time by calling a
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/gauge_test.go b/vendor/github.com/prometheus/client_golang/prometheus/gauge_test.go
index 48cab4636..a2e3c1416 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/gauge_test.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/gauge_test.go
@@ -19,6 +19,7 @@ import (
"sync"
"testing"
"testing/quick"
+ "time"
dto "github.com/prometheus/client_model/go"
)
@@ -82,7 +83,7 @@ func TestGaugeConcurrency(t *testing.T) {
}
start.Done()
- if expected, got := <-result, math.Float64frombits(gge.(*value).valBits); math.Abs(expected-got) > 0.000001 {
+ if expected, got := <-result, math.Float64frombits(gge.(*gauge).valBits); math.Abs(expected-got) > 0.000001 {
t.Fatalf("expected approx. %f, got %f", expected, got)
return false
}
@@ -146,7 +147,7 @@ func TestGaugeVecConcurrency(t *testing.T) {
start.Done()
for i := range sStreams {
- if expected, got := <-results[i], math.Float64frombits(gge.WithLabelValues(string('A'+i)).(*value).valBits); math.Abs(expected-got) > 0.000001 {
+ if expected, got := <-results[i], math.Float64frombits(gge.WithLabelValues(string('A'+i)).(*gauge).valBits); math.Abs(expected-got) > 0.000001 {
t.Fatalf("expected approx. %f, got %f", expected, got)
return false
}
@@ -180,3 +181,22 @@ func TestGaugeFunc(t *testing.T) {
t.Errorf("expected %q, got %q", expected, got)
}
}
+
+func TestGaugeSetCurrentTime(t *testing.T) {
+ g := NewGauge(GaugeOpts{
+ Name: "test_name",
+ Help: "test help",
+ })
+ g.SetToCurrentTime()
+ unixTime := float64(time.Now().Unix())
+
+ m := &dto.Metric{}
+ g.Write(m)
+
+ delta := unixTime - m.GetGauge().GetValue()
+ // This is just a smoke test to make sure SetToCurrentTime is not
+ // totally off. Tests with current time involved are hard...
+ if math.Abs(delta) > 5 {
+ t.Errorf("Gauge set to current time deviates from current time by more than 5s, delta is %f seconds", delta)
+ }
+}
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/go_collector.go b/vendor/github.com/prometheus/client_golang/prometheus/go_collector.go
index abc9d4ec4..096454af9 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/go_collector.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/go_collector.go
@@ -8,8 +8,10 @@ import (
)
type goCollector struct {
- goroutines Gauge
- gcDesc *Desc
+ goroutinesDesc *Desc
+ threadsDesc *Desc
+ gcDesc *Desc
+ goInfoDesc *Desc
// metrics to describe and collect
metrics memStatsMetrics
@@ -19,15 +21,22 @@ type goCollector struct {
// go process.
func NewGoCollector() Collector {
return &goCollector{
- goroutines: NewGauge(GaugeOpts{
- Namespace: "go",
- Name: "goroutines",
- Help: "Number of goroutines that currently exist.",
- }),
+ goroutinesDesc: NewDesc(
+ "go_goroutines",
+ "Number of goroutines that currently exist.",
+ nil, nil),
+ threadsDesc: NewDesc(
+ "go_threads",
+ "Number of OS threads created.",
+ nil, nil),
gcDesc: NewDesc(
"go_gc_duration_seconds",
"A summary of the GC invocation durations.",
nil, nil),
+ goInfoDesc: NewDesc(
+ "go_info",
+ "Information about the Go environment.",
+ nil, Labels{"version": runtime.Version()}),
metrics: memStatsMetrics{
{
desc: NewDesc(
@@ -48,7 +57,7 @@ func NewGoCollector() Collector {
}, {
desc: NewDesc(
memstatNamespace("sys_bytes"),
- "Number of bytes obtained by system. Sum of all system allocations.",
+ "Number of bytes obtained from system.",
nil, nil,
),
eval: func(ms *runtime.MemStats) float64 { return float64(ms.Sys) },
@@ -111,12 +120,12 @@ func NewGoCollector() Collector {
valType: GaugeValue,
}, {
desc: NewDesc(
- memstatNamespace("heap_released_bytes_total"),
- "Total number of heap bytes released to OS.",
+ memstatNamespace("heap_released_bytes"),
+ "Number of heap bytes released to OS.",
nil, nil,
),
eval: func(ms *runtime.MemStats) float64 { return float64(ms.HeapReleased) },
- valType: CounterValue,
+ valType: GaugeValue,
}, {
desc: NewDesc(
memstatNamespace("heap_objects"),
@@ -213,6 +222,14 @@ func NewGoCollector() Collector {
),
eval: func(ms *runtime.MemStats) float64 { return float64(ms.LastGC) / 1e9 },
valType: GaugeValue,
+ }, {
+ desc: NewDesc(
+ memstatNamespace("gc_cpu_fraction"),
+ "The fraction of this program's available CPU time used by the GC since the program started.",
+ nil, nil,
+ ),
+ eval: func(ms *runtime.MemStats) float64 { return ms.GCCPUFraction },
+ valType: GaugeValue,
},
},
}
@@ -224,9 +241,10 @@ func memstatNamespace(s string) string {
// Describe returns all descriptions of the collector.
func (c *goCollector) Describe(ch chan<- *Desc) {
- ch <- c.goroutines.Desc()
+ ch <- c.goroutinesDesc
+ ch <- c.threadsDesc
ch <- c.gcDesc
-
+ ch <- c.goInfoDesc
for _, i := range c.metrics {
ch <- i.desc
}
@@ -234,8 +252,9 @@ func (c *goCollector) Describe(ch chan<- *Desc) {
// Collect returns the current state of all metrics of the collector.
func (c *goCollector) Collect(ch chan<- Metric) {
- c.goroutines.Set(float64(runtime.NumGoroutine()))
- ch <- c.goroutines
+ ch <- MustNewConstMetric(c.goroutinesDesc, GaugeValue, float64(runtime.NumGoroutine()))
+ n, _ := runtime.ThreadCreateProfile(nil)
+ ch <- MustNewConstMetric(c.threadsDesc, GaugeValue, float64(n))
var stats debug.GCStats
stats.PauseQuantiles = make([]time.Duration, 5)
@@ -248,6 +267,8 @@ func (c *goCollector) Collect(ch chan<- Metric) {
quantiles[0.0] = stats.PauseQuantiles[0].Seconds()
ch <- MustNewConstSummary(c.gcDesc, uint64(stats.NumGC), float64(stats.PauseTotal.Seconds()), quantiles)
+ ch <- MustNewConstMetric(c.goInfoDesc, GaugeValue, 1)
+
ms := &runtime.MemStats{}
runtime.ReadMemStats(ms)
for _, i := range c.metrics {
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/go_collector_test.go b/vendor/github.com/prometheus/client_golang/prometheus/go_collector_test.go
index 9a8858cbd..72264da9a 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/go_collector_test.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/go_collector_test.go
@@ -29,33 +29,37 @@ func TestGoCollector(t *testing.T) {
for {
select {
- case metric := <-ch:
- switch m := metric.(type) {
- // Attention, this also catches Counter...
- case Gauge:
- pb := &dto.Metric{}
- m.Write(pb)
- if pb.GetGauge() == nil {
- continue
- }
-
- if old == -1 {
- old = int(pb.GetGauge().GetValue())
- close(waitc)
- continue
- }
+ case m := <-ch:
+ // m can be Gauge or Counter,
+ // currently just test the go_goroutines Gauge
+ // and ignore others.
+ if m.Desc().fqName != "go_goroutines" {
+ continue
+ }
+ pb := &dto.Metric{}
+ m.Write(pb)
+ if pb.GetGauge() == nil {
+ continue
+ }
- if diff := int(pb.GetGauge().GetValue()) - old; diff != 1 {
- // TODO: This is flaky in highly concurrent situations.
- t.Errorf("want 1 new goroutine, got %d", diff)
- }
+ if old == -1 {
+ old = int(pb.GetGauge().GetValue())
+ close(waitc)
+ continue
+ }
- // GoCollector performs two sends per call.
- // On line 27 we need to receive the second send
- // to shut down cleanly.
- <-ch
- return
+ if diff := int(pb.GetGauge().GetValue()) - old; diff != 1 {
+ // TODO: This is flaky in highly concurrent situations.
+ t.Errorf("want 1 new goroutine, got %d", diff)
}
+
+ // GoCollector performs three sends per call.
+ // On line 27 we need to receive three more sends
+ // to shut down cleanly.
+ <-ch
+ <-ch
+ <-ch
+ return
case <-time.After(1 * time.Second):
t.Fatalf("expected collect timed out")
}
@@ -85,37 +89,33 @@ func TestGCCollector(t *testing.T) {
for {
select {
case metric := <-ch:
- switch m := metric.(type) {
- case *constSummary, *value:
- pb := &dto.Metric{}
- m.Write(pb)
- if pb.GetSummary() == nil {
- continue
- }
-
- if len(pb.GetSummary().Quantile) != 5 {
- t.Errorf("expected 4 buckets, got %d", len(pb.GetSummary().Quantile))
- }
- for idx, want := range []float64{0.0, 0.25, 0.5, 0.75, 1.0} {
- if *pb.GetSummary().Quantile[idx].Quantile != want {
- t.Errorf("bucket #%d is off, got %f, want %f", idx, *pb.GetSummary().Quantile[idx].Quantile, want)
- }
- }
- if first {
- first = false
- oldGC = *pb.GetSummary().SampleCount
- oldPause = *pb.GetSummary().SampleSum
- close(waitc)
- continue
- }
- if diff := *pb.GetSummary().SampleCount - oldGC; diff != 1 {
- t.Errorf("want 1 new garbage collection run, got %d", diff)
- }
- if diff := *pb.GetSummary().SampleSum - oldPause; diff <= 0 {
- t.Errorf("want moar pause, got %f", diff)
+ pb := &dto.Metric{}
+ metric.Write(pb)
+ if pb.GetSummary() == nil {
+ continue
+ }
+ if len(pb.GetSummary().Quantile) != 5 {
+ t.Errorf("expected 4 buckets, got %d", len(pb.GetSummary().Quantile))
+ }
+ for idx, want := range []float64{0.0, 0.25, 0.5, 0.75, 1.0} {
+ if *pb.GetSummary().Quantile[idx].Quantile != want {
+ t.Errorf("bucket #%d is off, got %f, want %f", idx, *pb.GetSummary().Quantile[idx].Quantile, want)
}
- return
}
+ if first {
+ first = false
+ oldGC = *pb.GetSummary().SampleCount
+ oldPause = *pb.GetSummary().SampleSum
+ close(waitc)
+ continue
+ }
+ if diff := *pb.GetSummary().SampleCount - oldGC; diff != 1 {
+ t.Errorf("want 1 new garbage collection run, got %d", diff)
+ }
+ if diff := *pb.GetSummary().SampleSum - oldPause; diff <= 0 {
+ t.Errorf("want moar pause, got %f", diff)
+ }
+ return
case <-time.After(1 * time.Second):
t.Fatalf("expected collect timed out")
}
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/graphite/bridge.go b/vendor/github.com/prometheus/client_golang/prometheus/graphite/bridge.go
new file mode 100644
index 000000000..11533374b
--- /dev/null
+++ b/vendor/github.com/prometheus/client_golang/prometheus/graphite/bridge.go
@@ -0,0 +1,280 @@
+// Copyright 2016 The Prometheus 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 graphite provides a bridge to push Prometheus metrics to a Graphite
+// server.
+package graphite
+
+import (
+ "bufio"
+ "errors"
+ "fmt"
+ "io"
+ "net"
+ "sort"
+ "time"
+
+ "github.com/prometheus/common/expfmt"
+ "github.com/prometheus/common/model"
+ "golang.org/x/net/context"
+
+ dto "github.com/prometheus/client_model/go"
+
+ "github.com/prometheus/client_golang/prometheus"
+)
+
+const (
+ defaultInterval = 15 * time.Second
+ millisecondsPerSecond = 1000
+)
+
+// HandlerErrorHandling defines how a Handler serving metrics will handle
+// errors.
+type HandlerErrorHandling int
+
+// These constants cause handlers serving metrics to behave as described if
+// errors are encountered.
+const (
+ // Ignore errors and try to push as many metrics to Graphite as possible.
+ ContinueOnError HandlerErrorHandling = iota
+
+ // Abort the push to Graphite upon the first error encountered.
+ AbortOnError
+)
+
+// Config defines the Graphite bridge config.
+type Config struct {
+ // The url to push data to. Required.
+ URL string
+
+ // The prefix for the pushed Graphite metrics. Defaults to empty string.
+ Prefix string
+
+ // The interval to use for pushing data to Graphite. Defaults to 15 seconds.
+ Interval time.Duration
+
+ // The timeout for pushing metrics to Graphite. Defaults to 15 seconds.
+ Timeout time.Duration
+
+ // The Gatherer to use for metrics. Defaults to prometheus.DefaultGatherer.
+ Gatherer prometheus.Gatherer
+
+ // The logger that messages are written to. Defaults to no logging.
+ Logger Logger
+
+ // ErrorHandling defines how errors are handled. Note that errors are
+ // logged regardless of the configured ErrorHandling provided Logger
+ // is not nil.
+ ErrorHandling HandlerErrorHandling
+}
+
+// Bridge pushes metrics to the configured Graphite server.
+type Bridge struct {
+ url string
+ prefix string
+ interval time.Duration
+ timeout time.Duration
+
+ errorHandling HandlerErrorHandling
+ logger Logger
+
+ g prometheus.Gatherer
+}
+
+// Logger is the minimal interface Bridge needs for logging. Note that
+// log.Logger from the standard library implements this interface, and it is
+// easy to implement by custom loggers, if they don't do so already anyway.
+type Logger interface {
+ Println(v ...interface{})
+}
+
+// NewBridge returns a pointer to a new Bridge struct.
+func NewBridge(c *Config) (*Bridge, error) {
+ b := &Bridge{}
+
+ if c.URL == "" {
+ return nil, errors.New("missing URL")
+ }
+ b.url = c.URL
+
+ if c.Gatherer == nil {
+ b.g = prometheus.DefaultGatherer
+ } else {
+ b.g = c.Gatherer
+ }
+
+ if c.Logger != nil {
+ b.logger = c.Logger
+ }
+
+ if c.Prefix != "" {
+ b.prefix = c.Prefix
+ }
+
+ var z time.Duration
+ if c.Interval == z {
+ b.interval = defaultInterval
+ } else {
+ b.interval = c.Interval
+ }
+
+ if c.Timeout == z {
+ b.timeout = defaultInterval
+ } else {
+ b.timeout = c.Timeout
+ }
+
+ b.errorHandling = c.ErrorHandling
+
+ return b, nil
+}
+
+// Run starts the event loop that pushes Prometheus metrics to Graphite at the
+// configured interval.
+func (b *Bridge) Run(ctx context.Context) {
+ ticker := time.NewTicker(b.interval)
+ defer ticker.Stop()
+ for {
+ select {
+ case <-ticker.C:
+ if err := b.Push(); err != nil && b.logger != nil {
+ b.logger.Println("error pushing to Graphite:", err)
+ }
+ case <-ctx.Done():
+ return
+ }
+ }
+}
+
+// Push pushes Prometheus metrics to the configured Graphite server.
+func (b *Bridge) Push() error {
+ mfs, err := b.g.Gather()
+ if err != nil || len(mfs) == 0 {
+ switch b.errorHandling {
+ case AbortOnError:
+ return err
+ case ContinueOnError:
+ if b.logger != nil {
+ b.logger.Println("continue on error:", err)
+ }
+ default:
+ panic("unrecognized error handling value")
+ }
+ }
+
+ conn, err := net.DialTimeout("tcp", b.url, b.timeout)
+ if err != nil {
+ return err
+ }
+ defer conn.Close()
+
+ return writeMetrics(conn, mfs, b.prefix, model.Now())
+}
+
+func writeMetrics(w io.Writer, mfs []*dto.MetricFamily, prefix string, now model.Time) error {
+ vec, err := expfmt.ExtractSamples(&expfmt.DecodeOptions{
+ Timestamp: now,
+ }, mfs...)
+ if err != nil {
+ return err
+ }
+
+ buf := bufio.NewWriter(w)
+ for _, s := range vec {
+ if err := writeSanitized(buf, prefix); err != nil {
+ return err
+ }
+ if err := buf.WriteByte('.'); err != nil {
+ return err
+ }
+ if err := writeMetric(buf, s.Metric); err != nil {
+ return err
+ }
+ if _, err := fmt.Fprintf(buf, " %g %d\n", s.Value, int64(s.Timestamp)/millisecondsPerSecond); err != nil {
+ return err
+ }
+ if err := buf.Flush(); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func writeMetric(buf *bufio.Writer, m model.Metric) error {
+ metricName, hasName := m[model.MetricNameLabel]
+ numLabels := len(m) - 1
+ if !hasName {
+ numLabels = len(m)
+ }
+
+ labelStrings := make([]string, 0, numLabels)
+ for label, value := range m {
+ if label != model.MetricNameLabel {
+ labelStrings = append(labelStrings, fmt.Sprintf("%s %s", string(label), string(value)))
+ }
+ }
+
+ var err error
+ switch numLabels {
+ case 0:
+ if hasName {
+ return writeSanitized(buf, string(metricName))
+ }
+ default:
+ sort.Strings(labelStrings)
+ if err = writeSanitized(buf, string(metricName)); err != nil {
+ return err
+ }
+ for _, s := range labelStrings {
+ if err = buf.WriteByte('.'); err != nil {
+ return err
+ }
+ if err = writeSanitized(buf, s); err != nil {
+ return err
+ }
+ }
+ }
+ return nil
+}
+
+func writeSanitized(buf *bufio.Writer, s string) error {
+ prevUnderscore := false
+
+ for _, c := range s {
+ c = replaceInvalidRune(c)
+ if c == '_' {
+ if prevUnderscore {
+ continue
+ }
+ prevUnderscore = true
+ } else {
+ prevUnderscore = false
+ }
+ if _, err := buf.WriteRune(c); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func replaceInvalidRune(c rune) rune {
+ if c == ' ' {
+ return '.'
+ }
+ if !((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || c == ':' || (c >= '0' && c <= '9')) {
+ return '_'
+ }
+ return c
+}
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/graphite/bridge_test.go b/vendor/github.com/prometheus/client_golang/prometheus/graphite/bridge_test.go
new file mode 100644
index 000000000..c2b274c6a
--- /dev/null
+++ b/vendor/github.com/prometheus/client_golang/prometheus/graphite/bridge_test.go
@@ -0,0 +1,309 @@
+package graphite
+
+import (
+ "bufio"
+ "bytes"
+ "io"
+ "log"
+ "net"
+ "os"
+ "regexp"
+ "testing"
+ "time"
+
+ "github.com/prometheus/common/model"
+ "golang.org/x/net/context"
+
+ "github.com/prometheus/client_golang/prometheus"
+)
+
+func TestSanitize(t *testing.T) {
+ testCases := []struct {
+ in, out string
+ }{
+ {in: "hello", out: "hello"},
+ {in: "hE/l1o", out: "hE_l1o"},
+ {in: "he,*ll(.o", out: "he_ll_o"},
+ {in: "hello_there%^&", out: "hello_there_"},
+ }
+
+ var buf bytes.Buffer
+ w := bufio.NewWriter(&buf)
+
+ for i, tc := range testCases {
+ if err := writeSanitized(w, tc.in); err != nil {
+ t.Fatalf("write failed: %v", err)
+ }
+ if err := w.Flush(); err != nil {
+ t.Fatalf("flush failed: %v", err)
+ }
+
+ if want, got := tc.out, buf.String(); want != got {
+ t.Fatalf("test case index %d: got sanitized string %s, want %s", i, got, want)
+ }
+
+ buf.Reset()
+ }
+}
+
+func TestWriteSummary(t *testing.T) {
+ sumVec := prometheus.NewSummaryVec(
+ prometheus.SummaryOpts{
+ Name: "name",
+ Help: "docstring",
+ ConstLabels: prometheus.Labels{"constname": "constvalue"},
+ Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001},
+ },
+ []string{"labelname"},
+ )
+
+ sumVec.WithLabelValues("val1").Observe(float64(10))
+ sumVec.WithLabelValues("val1").Observe(float64(20))
+ sumVec.WithLabelValues("val1").Observe(float64(30))
+ sumVec.WithLabelValues("val2").Observe(float64(20))
+ sumVec.WithLabelValues("val2").Observe(float64(30))
+ sumVec.WithLabelValues("val2").Observe(float64(40))
+
+ reg := prometheus.NewRegistry()
+ reg.MustRegister(sumVec)
+
+ mfs, err := reg.Gather()
+ if err != nil {
+ t.Fatalf("error: %v", err)
+ }
+
+ now := model.Time(1477043083)
+ var buf bytes.Buffer
+ err = writeMetrics(&buf, mfs, "prefix", now)
+ if err != nil {
+ t.Fatalf("error: %v", err)
+ }
+
+ want := `prefix.name.constname.constvalue.labelname.val1.quantile.0_5 20 1477043
+prefix.name.constname.constvalue.labelname.val1.quantile.0_9 30 1477043
+prefix.name.constname.constvalue.labelname.val1.quantile.0_99 30 1477043
+prefix.name_sum.constname.constvalue.labelname.val1 60 1477043
+prefix.name_count.constname.constvalue.labelname.val1 3 1477043
+prefix.name.constname.constvalue.labelname.val2.quantile.0_5 30 1477043
+prefix.name.constname.constvalue.labelname.val2.quantile.0_9 40 1477043
+prefix.name.constname.constvalue.labelname.val2.quantile.0_99 40 1477043
+prefix.name_sum.constname.constvalue.labelname.val2 90 1477043
+prefix.name_count.constname.constvalue.labelname.val2 3 1477043
+`
+
+ if got := buf.String(); want != got {
+ t.Fatalf("wanted \n%s\n, got \n%s\n", want, got)
+ }
+}
+
+func TestWriteHistogram(t *testing.T) {
+ histVec := prometheus.NewHistogramVec(
+ prometheus.HistogramOpts{
+ Name: "name",
+ Help: "docstring",
+ ConstLabels: prometheus.Labels{"constname": "constvalue"},
+ Buckets: []float64{0.01, 0.02, 0.05, 0.1},
+ },
+ []string{"labelname"},
+ )
+
+ histVec.WithLabelValues("val1").Observe(float64(10))
+ histVec.WithLabelValues("val1").Observe(float64(20))
+ histVec.WithLabelValues("val1").Observe(float64(30))
+ histVec.WithLabelValues("val2").Observe(float64(20))
+ histVec.WithLabelValues("val2").Observe(float64(30))
+ histVec.WithLabelValues("val2").Observe(float64(40))
+
+ reg := prometheus.NewRegistry()
+ reg.MustRegister(histVec)
+
+ mfs, err := reg.Gather()
+ if err != nil {
+ t.Fatalf("error: %v", err)
+ }
+
+ now := model.Time(1477043083)
+ var buf bytes.Buffer
+ err = writeMetrics(&buf, mfs, "prefix", now)
+ if err != nil {
+ t.Fatalf("error: %v", err)
+ }
+
+ want := `prefix.name_bucket.constname.constvalue.labelname.val1.le.0_01 0 1477043
+prefix.name_bucket.constname.constvalue.labelname.val1.le.0_02 0 1477043
+prefix.name_bucket.constname.constvalue.labelname.val1.le.0_05 0 1477043
+prefix.name_bucket.constname.constvalue.labelname.val1.le.0_1 0 1477043
+prefix.name_sum.constname.constvalue.labelname.val1 60 1477043
+prefix.name_count.constname.constvalue.labelname.val1 3 1477043
+prefix.name_bucket.constname.constvalue.labelname.val1.le._Inf 3 1477043
+prefix.name_bucket.constname.constvalue.labelname.val2.le.0_01 0 1477043
+prefix.name_bucket.constname.constvalue.labelname.val2.le.0_02 0 1477043
+prefix.name_bucket.constname.constvalue.labelname.val2.le.0_05 0 1477043
+prefix.name_bucket.constname.constvalue.labelname.val2.le.0_1 0 1477043
+prefix.name_sum.constname.constvalue.labelname.val2 90 1477043
+prefix.name_count.constname.constvalue.labelname.val2 3 1477043
+prefix.name_bucket.constname.constvalue.labelname.val2.le._Inf 3 1477043
+`
+ if got := buf.String(); want != got {
+ t.Fatalf("wanted \n%s\n, got \n%s\n", want, got)
+ }
+}
+
+func TestToReader(t *testing.T) {
+ cntVec := prometheus.NewCounterVec(
+ prometheus.CounterOpts{
+ Name: "name",
+ Help: "docstring",
+ ConstLabels: prometheus.Labels{"constname": "constvalue"},
+ },
+ []string{"labelname"},
+ )
+ cntVec.WithLabelValues("val1").Inc()
+ cntVec.WithLabelValues("val2").Inc()
+
+ reg := prometheus.NewRegistry()
+ reg.MustRegister(cntVec)
+
+ want := `prefix.name.constname.constvalue.labelname.val1 1 1477043
+prefix.name.constname.constvalue.labelname.val2 1 1477043
+`
+ mfs, err := reg.Gather()
+ if err != nil {
+ t.Fatalf("error: %v", err)
+ }
+
+ now := model.Time(1477043083)
+ var buf bytes.Buffer
+ err = writeMetrics(&buf, mfs, "prefix", now)
+ if err != nil {
+ t.Fatalf("error: %v", err)
+ }
+
+ if got := buf.String(); want != got {
+ t.Fatalf("wanted \n%s\n, got \n%s\n", want, got)
+ }
+}
+
+func TestPush(t *testing.T) {
+ reg := prometheus.NewRegistry()
+ cntVec := prometheus.NewCounterVec(
+ prometheus.CounterOpts{
+ Name: "name",
+ Help: "docstring",
+ ConstLabels: prometheus.Labels{"constname": "constvalue"},
+ },
+ []string{"labelname"},
+ )
+ cntVec.WithLabelValues("val1").Inc()
+ cntVec.WithLabelValues("val2").Inc()
+ reg.MustRegister(cntVec)
+
+ host := "localhost"
+ port := ":56789"
+ b, err := NewBridge(&Config{
+ URL: host + port,
+ Gatherer: reg,
+ Prefix: "prefix",
+ })
+ if err != nil {
+ t.Fatalf("error creating bridge: %v", err)
+ }
+
+ nmg, err := newMockGraphite(port)
+ if err != nil {
+ t.Fatalf("error creating mock graphite: %v", err)
+ }
+ defer nmg.Close()
+
+ err = b.Push()
+ if err != nil {
+ t.Fatalf("error pushing: %v", err)
+ }
+
+ wants := []string{
+ "prefix.name.constname.constvalue.labelname.val1 1",
+ "prefix.name.constname.constvalue.labelname.val2 1",
+ }
+
+ select {
+ case got := <-nmg.readc:
+ for _, want := range wants {
+ matched, err := regexp.MatchString(want, got)
+ if err != nil {
+ t.Fatalf("error pushing: %v", err)
+ }
+ if !matched {
+ t.Fatalf("missing metric:\nno match for %s received by server:\n%s", want, got)
+ }
+ }
+ return
+ case err := <-nmg.errc:
+ t.Fatalf("error reading push: %v", err)
+ case <-time.After(50 * time.Millisecond):
+ t.Fatalf("no result from graphite server")
+ }
+}
+
+func newMockGraphite(port string) (*mockGraphite, error) {
+ readc := make(chan string)
+ errc := make(chan error)
+ ln, err := net.Listen("tcp", port)
+ if err != nil {
+ return nil, err
+ }
+
+ go func() {
+ conn, err := ln.Accept()
+ if err != nil {
+ errc <- err
+ }
+ var b bytes.Buffer
+ io.Copy(&b, conn)
+ readc <- b.String()
+ }()
+
+ return &mockGraphite{
+ readc: readc,
+ errc: errc,
+ Listener: ln,
+ }, nil
+}
+
+type mockGraphite struct {
+ readc chan string
+ errc chan error
+
+ net.Listener
+}
+
+func ExampleBridge() {
+ b, err := NewBridge(&Config{
+ URL: "graphite.example.org:3099",
+ Gatherer: prometheus.DefaultGatherer,
+ Prefix: "prefix",
+ Interval: 15 * time.Second,
+ Timeout: 10 * time.Second,
+ ErrorHandling: AbortOnError,
+ Logger: log.New(os.Stdout, "graphite bridge: ", log.Lshortfile),
+ })
+ if err != nil {
+ panic(err)
+ }
+
+ go func() {
+ // Start something in a goroutine that uses metrics.
+ }()
+
+ // Push initial metrics to Graphite. Fail fast if the push fails.
+ if err := b.Push(); err != nil {
+ panic(err)
+ }
+
+ // Create a Context to control stopping the Run() loop that pushes
+ // metrics to Graphite.
+ ctx, cancel := context.WithCancel(context.Background())
+ defer cancel()
+
+ // Start pushing metrics to Graphite in the Run() loop.
+ b.Run(ctx)
+}
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/histogram.go b/vendor/github.com/prometheus/client_golang/prometheus/histogram.go
index 9719e8fac..331783a75 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/histogram.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/histogram.go
@@ -126,23 +126,16 @@ type HistogramOpts struct {
// string.
Help string
- // ConstLabels are used to attach fixed labels to this
- // Histogram. Histograms with the same fully-qualified name must have the
- // same label names in their ConstLabels.
+ // ConstLabels are used to attach fixed labels to this metric. Metrics
+ // with the same fully-qualified name must have the same label names in
+ // their ConstLabels.
//
- // Note that in most cases, labels have a value that varies during the
- // lifetime of a process. Those labels are usually managed with a
- // HistogramVec. ConstLabels serve only special purposes. One is for the
- // special case where the value of a label does not change during the
- // lifetime of a process, e.g. if the revision of the running binary is
- // put into a label. Another, more advanced purpose is if more than one
- // Collector needs to collect Histograms with the same fully-qualified
- // name. In that case, those Summaries must differ in the values of
- // their ConstLabels. See the Collector examples.
- //
- // If the value of a label never changes (not even between binaries),
- // that label most likely should not be a label at all (but part of the
- // metric name).
+ // ConstLabels are only used rarely. In particular, do not use them to
+ // attach the same labels to all your metrics. Those use cases are
+ // better covered by target labels set by the scraping Prometheus
+ // server, or by one specific metric (e.g. a build_info or a
+ // machine_role metric). See also
+ // https://prometheus.io/docs/instrumenting/writing_exporters/#target-labels,-not-static-scraped-labels
ConstLabels Labels
// Buckets defines the buckets into which observations are counted. Each
@@ -287,12 +280,11 @@ func (h *histogram) Write(out *dto.Metric) error {
// (e.g. HTTP request latencies, partitioned by status code and method). Create
// instances with NewHistogramVec.
type HistogramVec struct {
- *MetricVec
+ *metricVec
}
// NewHistogramVec creates a new HistogramVec based on the provided HistogramOpts and
-// partitioned by the given label names. At least one label name must be
-// provided.
+// partitioned by the given label names.
func NewHistogramVec(opts HistogramOpts, labelNames []string) *HistogramVec {
desc := NewDesc(
BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
@@ -301,47 +293,116 @@ func NewHistogramVec(opts HistogramOpts, labelNames []string) *HistogramVec {
opts.ConstLabels,
)
return &HistogramVec{
- MetricVec: newMetricVec(desc, func(lvs ...string) Metric {
+ metricVec: newMetricVec(desc, func(lvs ...string) Metric {
return newHistogram(desc, opts, lvs...)
}),
}
}
-// GetMetricWithLabelValues replaces the method of the same name in
-// MetricVec. The difference is that this method returns a Histogram and not a
-// Metric so that no type conversion is required.
-func (m *HistogramVec) GetMetricWithLabelValues(lvs ...string) (Histogram, error) {
- metric, err := m.MetricVec.GetMetricWithLabelValues(lvs...)
+// GetMetricWithLabelValues returns the Histogram for the given slice of label
+// values (same order as the VariableLabels in Desc). If that combination of
+// label values is accessed for the first time, a new Histogram is created.
+//
+// It is possible to call this method without using the returned Histogram to only
+// create the new Histogram but leave it at its starting value, a Histogram without
+// any observations.
+//
+// Keeping the Histogram for later use is possible (and should be considered if
+// performance is critical), but keep in mind that Reset, DeleteLabelValues and
+// Delete can be used to delete the Histogram from the HistogramVec. In that case, the
+// Histogram will still exist, but it will not be exported anymore, even if a
+// Histogram with the same label values is created later. See also the CounterVec
+// example.
+//
+// An error is returned if the number of label values is not the same as the
+// number of VariableLabels in Desc (minus any curried labels).
+//
+// Note that for more than one label value, this method is prone to mistakes
+// caused by an incorrect order of arguments. Consider GetMetricWith(Labels) as
+// an alternative to avoid that type of mistake. For higher label numbers, the
+// latter has a much more readable (albeit more verbose) syntax, but it comes
+// with a performance overhead (for creating and processing the Labels map).
+// See also the GaugeVec example.
+func (v *HistogramVec) GetMetricWithLabelValues(lvs ...string) (Observer, error) {
+ metric, err := v.metricVec.getMetricWithLabelValues(lvs...)
if metric != nil {
- return metric.(Histogram), err
+ return metric.(Observer), err
}
return nil, err
}
-// GetMetricWith replaces the method of the same name in MetricVec. The
-// difference is that this method returns a Histogram and not a Metric so that no
-// type conversion is required.
-func (m *HistogramVec) GetMetricWith(labels Labels) (Histogram, error) {
- metric, err := m.MetricVec.GetMetricWith(labels)
+// GetMetricWith returns the Histogram for the given Labels map (the label names
+// must match those of the VariableLabels in Desc). If that label map is
+// accessed for the first time, a new Histogram is created. Implications of
+// creating a Histogram without using it and keeping the Histogram for later use
+// are the same as for GetMetricWithLabelValues.
+//
+// An error is returned if the number and names of the Labels are inconsistent
+// with those of the VariableLabels in Desc (minus any curried labels).
+//
+// This method is used for the same purpose as
+// GetMetricWithLabelValues(...string). See there for pros and cons of the two
+// methods.
+func (v *HistogramVec) GetMetricWith(labels Labels) (Observer, error) {
+ metric, err := v.metricVec.getMetricWith(labels)
if metric != nil {
- return metric.(Histogram), err
+ return metric.(Observer), err
}
return nil, err
}
// WithLabelValues works as GetMetricWithLabelValues, but panics where
-// GetMetricWithLabelValues would have returned an error. By not returning an
-// error, WithLabelValues allows shortcuts like
+// GetMetricWithLabelValues would have returned an error. Not returning an
+// error allows shortcuts like
// myVec.WithLabelValues("404", "GET").Observe(42.21)
-func (m *HistogramVec) WithLabelValues(lvs ...string) Histogram {
- return m.MetricVec.WithLabelValues(lvs...).(Histogram)
+func (v *HistogramVec) WithLabelValues(lvs ...string) Observer {
+ h, err := v.GetMetricWithLabelValues(lvs...)
+ if err != nil {
+ panic(err)
+ }
+ return h
+}
+
+// With works as GetMetricWith but panics where GetMetricWithLabels would have
+// returned an error. Not returning an error allows shortcuts like
+// myVec.With(prometheus.Labels{"code": "404", "method": "GET"}).Observe(42.21)
+func (v *HistogramVec) With(labels Labels) Observer {
+ h, err := v.GetMetricWith(labels)
+ if err != nil {
+ panic(err)
+ }
+ return h
}
-// With works as GetMetricWith, but panics where GetMetricWithLabels would have
-// returned an error. By not returning an error, With allows shortcuts like
-// myVec.With(Labels{"code": "404", "method": "GET"}).Observe(42.21)
-func (m *HistogramVec) With(labels Labels) Histogram {
- return m.MetricVec.With(labels).(Histogram)
+// CurryWith returns a vector curried with the provided labels, i.e. the
+// returned vector has those labels pre-set for all labeled operations performed
+// on it. The cardinality of the curried vector is reduced accordingly. The
+// order of the remaining labels stays the same (just with the curried labels
+// taken out of the sequence – which is relevant for the
+// (GetMetric)WithLabelValues methods). It is possible to curry a curried
+// vector, but only with labels not yet used for currying before.
+//
+// The metrics contained in the HistogramVec are shared between the curried and
+// uncurried vectors. They are just accessed differently. Curried and uncurried
+// vectors behave identically in terms of collection. Only one must be
+// registered with a given registry (usually the uncurried version). The Reset
+// method deletes all metrics, even if called on a curried vector.
+func (v *HistogramVec) CurryWith(labels Labels) (ObserverVec, error) {
+ vec, err := v.curryWith(labels)
+ if vec != nil {
+ return &HistogramVec{vec}, err
+ }
+ return nil, err
+}
+
+// MustCurryWith works as CurryWith but panics where CurryWith would have
+// returned an error.
+func (v *HistogramVec) MustCurryWith(labels Labels) ObserverVec {
+ vec, err := v.CurryWith(labels)
+ if err != nil {
+ panic(err)
+ }
+ return vec
}
type constHistogram struct {
@@ -401,8 +462,8 @@ func NewConstHistogram(
buckets map[float64]uint64,
labelValues ...string,
) (Metric, error) {
- if len(desc.variableLabels) != len(labelValues) {
- return nil, errInconsistentCardinality
+ if err := validateLabelValues(labelValues, len(desc.variableLabels)); err != nil {
+ return nil, err
}
return &constHistogram{
desc: desc,
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/histogram_test.go b/vendor/github.com/prometheus/client_golang/prometheus/histogram_test.go
index d1242e08d..5a20f4b6b 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/histogram_test.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/histogram_test.go
@@ -119,6 +119,28 @@ func BenchmarkHistogramWrite8(b *testing.B) {
benchmarkHistogramWrite(8, b)
}
+func TestHistogramNonMonotonicBuckets(t *testing.T) {
+ testCases := map[string][]float64{
+ "not strictly monotonic": {1, 2, 2, 3},
+ "not monotonic at all": {1, 2, 4, 3, 5},
+ "have +Inf in the middle": {1, 2, math.Inf(+1), 3},
+ }
+ for name, buckets := range testCases {
+ func() {
+ defer func() {
+ if r := recover(); r == nil {
+ t.Errorf("Buckets %v are %s but NewHistogram did not panic.", buckets, name)
+ }
+ }()
+ _ = NewHistogram(HistogramOpts{
+ Name: "test_histogram",
+ Help: "helpless",
+ Buckets: buckets,
+ })
+ }()
+ }
+}
+
// Intentionally adding +Inf here to test if that case is handled correctly.
// Also, getCumulativeCounts depends on it.
var testBuckets = []float64{-2, -1, -0.5, 0, 0.5, 1, 2, math.Inf(+1)}
@@ -264,7 +286,7 @@ func TestHistogramVecConcurrency(t *testing.T) {
for i := 0; i < vecLength; i++ {
m := &dto.Metric{}
s := his.WithLabelValues(string('A' + i))
- s.Write(m)
+ s.(Histogram).Write(m)
if got, want := len(m.Histogram.Bucket), len(testBuckets)-1; got != want {
t.Errorf("got %d buckets in protobuf, want %d", got, want)
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/http.go b/vendor/github.com/prometheus/client_golang/prometheus/http.go
index 67ee5ac79..bfee5c6eb 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/http.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/http.go
@@ -62,7 +62,8 @@ func giveBuf(buf *bytes.Buffer) {
//
// Deprecated: Please note the issues described in the doc comment of
// InstrumentHandler. You might want to consider using promhttp.Handler instead
-// (which is non instrumented).
+// (which is not instrumented, but can be instrumented with the tooling provided
+// in package promhttp).
func Handler() http.Handler {
return InstrumentHandler("prometheus", UninstrumentedHandler())
}
@@ -95,7 +96,7 @@ func UninstrumentedHandler() http.Handler {
closer.Close()
}
if lastErr != nil && buf.Len() == 0 {
- http.Error(w, "No metrics encoded, last error:\n\n"+err.Error(), http.StatusInternalServerError)
+ http.Error(w, "No metrics encoded, last error:\n\n"+lastErr.Error(), http.StatusInternalServerError)
return
}
header := w.Header()
@@ -158,7 +159,8 @@ func nowSeries(t ...time.Time) nower {
// value. http_requests_total is a metric vector partitioned by HTTP method
// (label name "method") and HTTP status code (label name "code").
//
-// Deprecated: InstrumentHandler has several issues:
+// Deprecated: InstrumentHandler has several issues. Use the tooling provided in
+// package promhttp instead. The issues are the following:
//
// - It uses Summaries rather than Histograms. Summaries are not useful if
// aggregation across multiple instances is required.
@@ -172,9 +174,8 @@ func nowSeries(t ...time.Time) nower {
// httputil.ReverseProxy is a prominent example for a handler
// performing such writes.
//
-// Upcoming versions of this package will provide ways of instrumenting HTTP
-// handlers that are more flexible and have fewer issues. Please prefer direct
-// instrumentation in the meantime.
+// - It has additional issues with HTTP/2, cf.
+// https://github.com/prometheus/client_golang/issues/272.
func InstrumentHandler(handlerName string, handler http.Handler) http.HandlerFunc {
return InstrumentHandlerFunc(handlerName, handler.ServeHTTP)
}
@@ -184,12 +185,13 @@ func InstrumentHandler(handlerName string, handler http.Handler) http.HandlerFun
// issues).
//
// Deprecated: InstrumentHandlerFunc is deprecated for the same reasons as
-// InstrumentHandler is.
+// InstrumentHandler is. Use the tooling provided in package promhttp instead.
func InstrumentHandlerFunc(handlerName string, handlerFunc func(http.ResponseWriter, *http.Request)) http.HandlerFunc {
return InstrumentHandlerFuncWithOpts(
SummaryOpts{
Subsystem: "http",
ConstLabels: Labels{"handler": handlerName},
+ Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001},
},
handlerFunc,
)
@@ -222,7 +224,7 @@ func InstrumentHandlerFunc(handlerName string, handlerFunc func(http.ResponseWri
// SummaryOpts.
//
// Deprecated: InstrumentHandlerWithOpts is deprecated for the same reasons as
-// InstrumentHandler is.
+// InstrumentHandler is. Use the tooling provided in package promhttp instead.
func InstrumentHandlerWithOpts(opts SummaryOpts, handler http.Handler) http.HandlerFunc {
return InstrumentHandlerFuncWithOpts(opts, handler.ServeHTTP)
}
@@ -233,7 +235,7 @@ func InstrumentHandlerWithOpts(opts SummaryOpts, handler http.Handler) http.Hand
// SummaryOpts are used.
//
// Deprecated: InstrumentHandlerFuncWithOpts is deprecated for the same reasons
-// as InstrumentHandler is.
+// as InstrumentHandler is. Use the tooling provided in package promhttp instead.
func InstrumentHandlerFuncWithOpts(opts SummaryOpts, handlerFunc func(http.ResponseWriter, *http.Request)) http.HandlerFunc {
reqCnt := NewCounterVec(
CounterOpts{
@@ -245,34 +247,52 @@ func InstrumentHandlerFuncWithOpts(opts SummaryOpts, handlerFunc func(http.Respo
},
instLabels,
)
+ if err := Register(reqCnt); err != nil {
+ if are, ok := err.(AlreadyRegisteredError); ok {
+ reqCnt = are.ExistingCollector.(*CounterVec)
+ } else {
+ panic(err)
+ }
+ }
opts.Name = "request_duration_microseconds"
opts.Help = "The HTTP request latencies in microseconds."
reqDur := NewSummary(opts)
+ if err := Register(reqDur); err != nil {
+ if are, ok := err.(AlreadyRegisteredError); ok {
+ reqDur = are.ExistingCollector.(Summary)
+ } else {
+ panic(err)
+ }
+ }
opts.Name = "request_size_bytes"
opts.Help = "The HTTP request sizes in bytes."
reqSz := NewSummary(opts)
+ if err := Register(reqSz); err != nil {
+ if are, ok := err.(AlreadyRegisteredError); ok {
+ reqSz = are.ExistingCollector.(Summary)
+ } else {
+ panic(err)
+ }
+ }
opts.Name = "response_size_bytes"
opts.Help = "The HTTP response sizes in bytes."
resSz := NewSummary(opts)
-
- regReqCnt := MustRegisterOrGet(reqCnt).(*CounterVec)
- regReqDur := MustRegisterOrGet(reqDur).(Summary)
- regReqSz := MustRegisterOrGet(reqSz).(Summary)
- regResSz := MustRegisterOrGet(resSz).(Summary)
+ if err := Register(resSz); err != nil {
+ if are, ok := err.(AlreadyRegisteredError); ok {
+ resSz = are.ExistingCollector.(Summary)
+ } else {
+ panic(err)
+ }
+ }
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
now := time.Now()
delegate := &responseWriterDelegator{ResponseWriter: w}
- out := make(chan int)
- urlLen := 0
- if r.URL != nil {
- urlLen = len(r.URL.String())
- }
- go computeApproximateRequestSize(r, out, urlLen)
+ out := computeApproximateRequestSize(r)
_, cn := w.(http.CloseNotifier)
_, fl := w.(http.Flusher)
@@ -290,30 +310,44 @@ func InstrumentHandlerFuncWithOpts(opts SummaryOpts, handlerFunc func(http.Respo
method := sanitizeMethod(r.Method)
code := sanitizeCode(delegate.status)
- regReqCnt.WithLabelValues(method, code).Inc()
- regReqDur.Observe(elapsed)
- regResSz.Observe(float64(delegate.written))
- regReqSz.Observe(float64(<-out))
+ reqCnt.WithLabelValues(method, code).Inc()
+ reqDur.Observe(elapsed)
+ resSz.Observe(float64(delegate.written))
+ reqSz.Observe(float64(<-out))
})
}
-func computeApproximateRequestSize(r *http.Request, out chan int, s int) {
- s += len(r.Method)
- s += len(r.Proto)
- for name, values := range r.Header {
- s += len(name)
- for _, value := range values {
- s += len(value)
- }
+func computeApproximateRequestSize(r *http.Request) <-chan int {
+ // Get URL length in current go routine for avoiding a race condition.
+ // HandlerFunc that runs in parallel may modify the URL.
+ s := 0
+ if r.URL != nil {
+ s += len(r.URL.String())
}
- s += len(r.Host)
- // N.B. r.Form and r.MultipartForm are assumed to be included in r.URL.
+ out := make(chan int, 1)
- if r.ContentLength != -1 {
- s += int(r.ContentLength)
- }
- out <- s
+ go func() {
+ s += len(r.Method)
+ s += len(r.Proto)
+ for name, values := range r.Header {
+ s += len(name)
+ for _, value := range values {
+ s += len(value)
+ }
+ }
+ s += len(r.Host)
+
+ // N.B. r.Form and r.MultipartForm are assumed to be included in r.URL.
+
+ if r.ContentLength != -1 {
+ s += int(r.ContentLength)
+ }
+ out <- s
+ close(out)
+ }()
+
+ return out
}
type responseWriterDelegator struct {
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/http_test.go b/vendor/github.com/prometheus/client_golang/prometheus/http_test.go
index ffe0418cf..0c7fa2347 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/http_test.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/http_test.go
@@ -44,9 +44,10 @@ func TestInstrumentHandler(t *testing.T) {
opts := SummaryOpts{
Subsystem: "http",
ConstLabels: Labels{"handler": "test-handler"},
+ Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001},
}
- reqCnt := MustRegisterOrGet(NewCounterVec(
+ reqCnt := NewCounterVec(
CounterOpts{
Namespace: opts.Namespace,
Subsystem: opts.Subsystem,
@@ -55,19 +56,51 @@ func TestInstrumentHandler(t *testing.T) {
ConstLabels: opts.ConstLabels,
},
instLabels,
- )).(*CounterVec)
+ )
+ err := Register(reqCnt)
+ if err == nil {
+ t.Fatal("expected reqCnt to be registered already")
+ }
+ if are, ok := err.(AlreadyRegisteredError); ok {
+ reqCnt = are.ExistingCollector.(*CounterVec)
+ } else {
+ t.Fatal("unexpected registration error:", err)
+ }
opts.Name = "request_duration_microseconds"
opts.Help = "The HTTP request latencies in microseconds."
- reqDur := MustRegisterOrGet(NewSummary(opts)).(Summary)
+ reqDur := NewSummary(opts)
+ err = Register(reqDur)
+ if err == nil {
+ t.Fatal("expected reqDur to be registered already")
+ }
+ if are, ok := err.(AlreadyRegisteredError); ok {
+ reqDur = are.ExistingCollector.(Summary)
+ } else {
+ t.Fatal("unexpected registration error:", err)
+ }
opts.Name = "request_size_bytes"
opts.Help = "The HTTP request sizes in bytes."
- MustRegisterOrGet(NewSummary(opts))
+ reqSz := NewSummary(opts)
+ err = Register(reqSz)
+ if err == nil {
+ t.Fatal("expected reqSz to be registered already")
+ }
+ if _, ok := err.(AlreadyRegisteredError); !ok {
+ t.Fatal("unexpected registration error:", err)
+ }
opts.Name = "response_size_bytes"
opts.Help = "The HTTP response sizes in bytes."
- MustRegisterOrGet(NewSummary(opts))
+ resSz := NewSummary(opts)
+ err = Register(resSz)
+ if err == nil {
+ t.Fatal("expected resSz to be registered already")
+ }
+ if _, ok := err.(AlreadyRegisteredError); !ok {
+ t.Fatal("unexpected registration error:", err)
+ }
reqCnt.Reset()
@@ -95,7 +128,7 @@ func TestInstrumentHandler(t *testing.T) {
}
out.Reset()
- if want, got := 1, len(reqCnt.children); want != got {
+ if want, got := 1, len(reqCnt.metricMap.metrics); want != got {
t.Errorf("want %d children in reqCnt, got %d", want, got)
}
cnt, err := reqCnt.GetMetricWithLabelValues("get", "418")
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/labels.go b/vendor/github.com/prometheus/client_golang/prometheus/labels.go
new file mode 100644
index 000000000..2502e3734
--- /dev/null
+++ b/vendor/github.com/prometheus/client_golang/prometheus/labels.go
@@ -0,0 +1,57 @@
+package prometheus
+
+import (
+ "errors"
+ "fmt"
+ "strings"
+ "unicode/utf8"
+
+ "github.com/prometheus/common/model"
+)
+
+// Labels represents a collection of label name -> value mappings. This type is
+// commonly used with the With(Labels) and GetMetricWith(Labels) methods of
+// metric vector Collectors, e.g.:
+// myVec.With(Labels{"code": "404", "method": "GET"}).Add(42)
+//
+// The other use-case is the specification of constant label pairs in Opts or to
+// create a Desc.
+type Labels map[string]string
+
+// reservedLabelPrefix is a prefix which is not legal in user-supplied
+// label names.
+const reservedLabelPrefix = "__"
+
+var errInconsistentCardinality = errors.New("inconsistent label cardinality")
+
+func validateValuesInLabels(labels Labels, expectedNumberOfValues int) error {
+ if len(labels) != expectedNumberOfValues {
+ return errInconsistentCardinality
+ }
+
+ for name, val := range labels {
+ if !utf8.ValidString(val) {
+ return fmt.Errorf("label %s: value %q is not valid UTF-8", name, val)
+ }
+ }
+
+ return nil
+}
+
+func validateLabelValues(vals []string, expectedNumberOfValues int) error {
+ if len(vals) != expectedNumberOfValues {
+ return errInconsistentCardinality
+ }
+
+ for _, val := range vals {
+ if !utf8.ValidString(val) {
+ return fmt.Errorf("label value %q is not valid UTF-8", val)
+ }
+ }
+
+ return nil
+}
+
+func checkLabelName(l string) bool {
+ return model.LabelName(l).IsValid() && !strings.HasPrefix(l, reservedLabelPrefix)
+}
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/metric.go b/vendor/github.com/prometheus/client_golang/prometheus/metric.go
index d4063d98f..6213ee812 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/metric.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/metric.go
@@ -79,20 +79,12 @@ type Opts struct {
// with the same fully-qualified name must have the same label names in
// their ConstLabels.
//
- // Note that in most cases, labels have a value that varies during the
- // lifetime of a process. Those labels are usually managed with a metric
- // vector collector (like CounterVec, GaugeVec, UntypedVec). ConstLabels
- // serve only special purposes. One is for the special case where the
- // value of a label does not change during the lifetime of a process,
- // e.g. if the revision of the running binary is put into a
- // label. Another, more advanced purpose is if more than one Collector
- // needs to collect Metrics with the same fully-qualified name. In that
- // case, those Metrics must differ in the values of their
- // ConstLabels. See the Collector examples.
- //
- // If the value of a label never changes (not even between binaries),
- // that label most likely should not be a label at all (but part of the
- // metric name).
+ // ConstLabels are only used rarely. In particular, do not use them to
+ // attach the same labels to all your metrics. Those use cases are
+ // better covered by target labels set by the scraping Prometheus
+ // server, or by one specific metric (e.g. a build_info or a
+ // machine_role metric). See also
+ // https://prometheus.io/docs/instrumenting/writing_exporters/#target-labels,-not-static-scraped-labels
ConstLabels Labels
}
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/observer.go b/vendor/github.com/prometheus/client_golang/prometheus/observer.go
new file mode 100644
index 000000000..5806cd09e
--- /dev/null
+++ b/vendor/github.com/prometheus/client_golang/prometheus/observer.go
@@ -0,0 +1,52 @@
+// Copyright 2017 The Prometheus 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
+
+// Observer is the interface that wraps the Observe method, which is used by
+// Histogram and Summary to add observations.
+type Observer interface {
+ Observe(float64)
+}
+
+// The ObserverFunc type is an adapter to allow the use of ordinary
+// functions as Observers. If f is a function with the appropriate
+// signature, ObserverFunc(f) is an Observer that calls f.
+//
+// This adapter is usually used in connection with the Timer type, and there are
+// two general use cases:
+//
+// The most common one is to use a Gauge as the Observer for a Timer.
+// See the "Gauge" Timer example.
+//
+// The more advanced use case is to create a function that dynamically decides
+// which Observer to use for observing the duration. See the "Complex" Timer
+// example.
+type ObserverFunc func(float64)
+
+// Observe calls f(value). It implements Observer.
+func (f ObserverFunc) Observe(value float64) {
+ f(value)
+}
+
+// ObserverVec is an interface implemented by `HistogramVec` and `SummaryVec`.
+type ObserverVec interface {
+ GetMetricWith(Labels) (Observer, error)
+ GetMetricWithLabelValues(lvs ...string) (Observer, error)
+ With(Labels) Observer
+ WithLabelValues(...string) Observer
+ CurryWith(Labels) (ObserverVec, error)
+ MustCurryWith(Labels) ObserverVec
+
+ Collector
+}
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/process_collector.go b/vendor/github.com/prometheus/client_golang/prometheus/process_collector.go
index e31e62e78..94b2553e1 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/process_collector.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/process_collector.go
@@ -19,10 +19,10 @@ type processCollector struct {
pid int
collectFn func(chan<- Metric)
pidFn func() (int, error)
- cpuTotal Counter
- openFDs, maxFDs Gauge
- vsize, rss Gauge
- startTime Gauge
+ cpuTotal *Desc
+ openFDs, maxFDs *Desc
+ vsize, rss *Desc
+ startTime *Desc
}
// NewProcessCollector returns a collector which exports the current state of
@@ -44,40 +44,45 @@ func NewProcessCollectorPIDFn(
pidFn func() (int, error),
namespace string,
) Collector {
+ ns := ""
+ if len(namespace) > 0 {
+ ns = namespace + "_"
+ }
+
c := processCollector{
pidFn: pidFn,
collectFn: func(chan<- Metric) {},
- cpuTotal: NewCounter(CounterOpts{
- Namespace: namespace,
- Name: "process_cpu_seconds_total",
- Help: "Total user and system CPU time spent in seconds.",
- }),
- openFDs: NewGauge(GaugeOpts{
- Namespace: namespace,
- Name: "process_open_fds",
- Help: "Number of open file descriptors.",
- }),
- maxFDs: NewGauge(GaugeOpts{
- Namespace: namespace,
- Name: "process_max_fds",
- Help: "Maximum number of open file descriptors.",
- }),
- vsize: NewGauge(GaugeOpts{
- Namespace: namespace,
- Name: "process_virtual_memory_bytes",
- Help: "Virtual memory size in bytes.",
- }),
- rss: NewGauge(GaugeOpts{
- Namespace: namespace,
- Name: "process_resident_memory_bytes",
- Help: "Resident memory size in bytes.",
- }),
- startTime: NewGauge(GaugeOpts{
- Namespace: namespace,
- Name: "process_start_time_seconds",
- Help: "Start time of the process since unix epoch in seconds.",
- }),
+ cpuTotal: NewDesc(
+ ns+"process_cpu_seconds_total",
+ "Total user and system CPU time spent in seconds.",
+ nil, nil,
+ ),
+ openFDs: NewDesc(
+ ns+"process_open_fds",
+ "Number of open file descriptors.",
+ nil, nil,
+ ),
+ maxFDs: NewDesc(
+ ns+"process_max_fds",
+ "Maximum number of open file descriptors.",
+ nil, nil,
+ ),
+ vsize: NewDesc(
+ ns+"process_virtual_memory_bytes",
+ "Virtual memory size in bytes.",
+ nil, nil,
+ ),
+ rss: NewDesc(
+ ns+"process_resident_memory_bytes",
+ "Resident memory size in bytes.",
+ nil, nil,
+ ),
+ startTime: NewDesc(
+ ns+"process_start_time_seconds",
+ "Start time of the process since unix epoch in seconds.",
+ nil, nil,
+ ),
}
// Set up process metric collection if supported by the runtime.
@@ -90,12 +95,12 @@ func NewProcessCollectorPIDFn(
// Describe returns all descriptions of the collector.
func (c *processCollector) Describe(ch chan<- *Desc) {
- ch <- c.cpuTotal.Desc()
- ch <- c.openFDs.Desc()
- ch <- c.maxFDs.Desc()
- ch <- c.vsize.Desc()
- ch <- c.rss.Desc()
- ch <- c.startTime.Desc()
+ ch <- c.cpuTotal
+ ch <- c.openFDs
+ ch <- c.maxFDs
+ ch <- c.vsize
+ ch <- c.rss
+ ch <- c.startTime
}
// Collect returns the current state of all metrics of the collector.
@@ -117,26 +122,19 @@ func (c *processCollector) processCollect(ch chan<- Metric) {
}
if stat, err := p.NewStat(); err == nil {
- c.cpuTotal.Set(stat.CPUTime())
- ch <- c.cpuTotal
- c.vsize.Set(float64(stat.VirtualMemory()))
- ch <- c.vsize
- c.rss.Set(float64(stat.ResidentMemory()))
- ch <- c.rss
-
+ ch <- MustNewConstMetric(c.cpuTotal, CounterValue, stat.CPUTime())
+ ch <- MustNewConstMetric(c.vsize, GaugeValue, float64(stat.VirtualMemory()))
+ ch <- MustNewConstMetric(c.rss, GaugeValue, float64(stat.ResidentMemory()))
if startTime, err := stat.StartTime(); err == nil {
- c.startTime.Set(startTime)
- ch <- c.startTime
+ ch <- MustNewConstMetric(c.startTime, GaugeValue, startTime)
}
}
if fds, err := p.FileDescriptorsLen(); err == nil {
- c.openFDs.Set(float64(fds))
- ch <- c.openFDs
+ ch <- MustNewConstMetric(c.openFDs, GaugeValue, float64(fds))
}
if limits, err := p.NewLimits(); err == nil {
- c.maxFDs.Set(float64(limits.OpenFiles))
- ch <- c.maxFDs
+ ch <- MustNewConstMetric(c.maxFDs, GaugeValue, float64(limits.OpenFiles))
}
}
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/process_collector_test.go b/vendor/github.com/prometheus/client_golang/prometheus/process_collector_test.go
index d3362dae7..c7acb47fe 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/process_collector_test.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/process_collector_test.go
@@ -38,18 +38,18 @@ func TestProcessCollector(t *testing.T) {
}
for _, re := range []*regexp.Regexp{
- regexp.MustCompile("process_cpu_seconds_total [0-9]"),
- regexp.MustCompile("process_max_fds [1-9]"),
- regexp.MustCompile("process_open_fds [1-9]"),
- regexp.MustCompile("process_virtual_memory_bytes [1-9]"),
- regexp.MustCompile("process_resident_memory_bytes [1-9]"),
- regexp.MustCompile("process_start_time_seconds [0-9.]{10,}"),
- regexp.MustCompile("foobar_process_cpu_seconds_total [0-9]"),
- regexp.MustCompile("foobar_process_max_fds [1-9]"),
- regexp.MustCompile("foobar_process_open_fds [1-9]"),
- regexp.MustCompile("foobar_process_virtual_memory_bytes [1-9]"),
- regexp.MustCompile("foobar_process_resident_memory_bytes [1-9]"),
- regexp.MustCompile("foobar_process_start_time_seconds [0-9.]{10,}"),
+ regexp.MustCompile("\nprocess_cpu_seconds_total [0-9]"),
+ regexp.MustCompile("\nprocess_max_fds [1-9]"),
+ regexp.MustCompile("\nprocess_open_fds [1-9]"),
+ regexp.MustCompile("\nprocess_virtual_memory_bytes [1-9]"),
+ regexp.MustCompile("\nprocess_resident_memory_bytes [1-9]"),
+ regexp.MustCompile("\nprocess_start_time_seconds [0-9.]{10,}"),
+ regexp.MustCompile("\nfoobar_process_cpu_seconds_total [0-9]"),
+ regexp.MustCompile("\nfoobar_process_max_fds [1-9]"),
+ regexp.MustCompile("\nfoobar_process_open_fds [1-9]"),
+ regexp.MustCompile("\nfoobar_process_virtual_memory_bytes [1-9]"),
+ regexp.MustCompile("\nfoobar_process_resident_memory_bytes [1-9]"),
+ regexp.MustCompile("\nfoobar_process_start_time_seconds [0-9.]{10,}"),
} {
if !re.Match(buf.Bytes()) {
t.Errorf("want body to match %s\n%s", re, buf.String())
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/promhttp/delegator.go b/vendor/github.com/prometheus/client_golang/prometheus/promhttp/delegator.go
new file mode 100644
index 000000000..5ee095b09
--- /dev/null
+++ b/vendor/github.com/prometheus/client_golang/prometheus/promhttp/delegator.go
@@ -0,0 +1,199 @@
+// Copyright 2017 The Prometheus 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 promhttp
+
+import (
+ "bufio"
+ "io"
+ "net"
+ "net/http"
+)
+
+const (
+ closeNotifier = 1 << iota
+ flusher
+ hijacker
+ readerFrom
+ pusher
+)
+
+type delegator interface {
+ http.ResponseWriter
+
+ Status() int
+ Written() int64
+}
+
+type responseWriterDelegator struct {
+ http.ResponseWriter
+
+ handler, method string
+ status int
+ written int64
+ wroteHeader bool
+ observeWriteHeader func(int)
+}
+
+func (r *responseWriterDelegator) Status() int {
+ return r.status
+}
+
+func (r *responseWriterDelegator) Written() int64 {
+ return r.written
+}
+
+func (r *responseWriterDelegator) WriteHeader(code int) {
+ r.status = code
+ r.wroteHeader = true
+ r.ResponseWriter.WriteHeader(code)
+ if r.observeWriteHeader != nil {
+ r.observeWriteHeader(code)
+ }
+}
+
+func (r *responseWriterDelegator) Write(b []byte) (int, error) {
+ if !r.wroteHeader {
+ r.WriteHeader(http.StatusOK)
+ }
+ n, err := r.ResponseWriter.Write(b)
+ r.written += int64(n)
+ return n, err
+}
+
+type closeNotifierDelegator struct{ *responseWriterDelegator }
+type flusherDelegator struct{ *responseWriterDelegator }
+type hijackerDelegator struct{ *responseWriterDelegator }
+type readerFromDelegator struct{ *responseWriterDelegator }
+
+func (d *closeNotifierDelegator) CloseNotify() <-chan bool {
+ return d.ResponseWriter.(http.CloseNotifier).CloseNotify()
+}
+func (d *flusherDelegator) Flush() {
+ d.ResponseWriter.(http.Flusher).Flush()
+}
+func (d *hijackerDelegator) Hijack() (net.Conn, *bufio.ReadWriter, error) {
+ return d.ResponseWriter.(http.Hijacker).Hijack()
+}
+func (d *readerFromDelegator) ReadFrom(re io.Reader) (int64, error) {
+ if !d.wroteHeader {
+ d.WriteHeader(http.StatusOK)
+ }
+ n, err := d.ResponseWriter.(io.ReaderFrom).ReadFrom(re)
+ d.written += n
+ return n, err
+}
+
+var pickDelegator = make([]func(*responseWriterDelegator) delegator, 32)
+
+func init() {
+ // TODO(beorn7): Code generation would help here.
+ pickDelegator[0] = func(d *responseWriterDelegator) delegator { // 0
+ return d
+ }
+ pickDelegator[closeNotifier] = func(d *responseWriterDelegator) delegator { // 1
+ return closeNotifierDelegator{d}
+ }
+ pickDelegator[flusher] = func(d *responseWriterDelegator) delegator { // 2
+ return flusherDelegator{d}
+ }
+ pickDelegator[flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 3
+ return struct {
+ *responseWriterDelegator
+ http.Flusher
+ http.CloseNotifier
+ }{d, &flusherDelegator{d}, &closeNotifierDelegator{d}}
+ }
+ pickDelegator[hijacker] = func(d *responseWriterDelegator) delegator { // 4
+ return hijackerDelegator{d}
+ }
+ pickDelegator[hijacker+closeNotifier] = func(d *responseWriterDelegator) delegator { // 5
+ return struct {
+ *responseWriterDelegator
+ http.Hijacker
+ http.CloseNotifier
+ }{d, &hijackerDelegator{d}, &closeNotifierDelegator{d}}
+ }
+ pickDelegator[hijacker+flusher] = func(d *responseWriterDelegator) delegator { // 6
+ return struct {
+ *responseWriterDelegator
+ http.Hijacker
+ http.Flusher
+ }{d, &hijackerDelegator{d}, &flusherDelegator{d}}
+ }
+ pickDelegator[hijacker+flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 7
+ return struct {
+ *responseWriterDelegator
+ http.Hijacker
+ http.Flusher
+ http.CloseNotifier
+ }{d, &hijackerDelegator{d}, &flusherDelegator{d}, &closeNotifierDelegator{d}}
+ }
+ pickDelegator[readerFrom] = func(d *responseWriterDelegator) delegator { // 8
+ return readerFromDelegator{d}
+ }
+ pickDelegator[readerFrom+closeNotifier] = func(d *responseWriterDelegator) delegator { // 9
+ return struct {
+ *responseWriterDelegator
+ io.ReaderFrom
+ http.CloseNotifier
+ }{d, &readerFromDelegator{d}, &closeNotifierDelegator{d}}
+ }
+ pickDelegator[readerFrom+flusher] = func(d *responseWriterDelegator) delegator { // 10
+ return struct {
+ *responseWriterDelegator
+ io.ReaderFrom
+ http.Flusher
+ }{d, &readerFromDelegator{d}, &flusherDelegator{d}}
+ }
+ pickDelegator[readerFrom+flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 11
+ return struct {
+ *responseWriterDelegator
+ io.ReaderFrom
+ http.Flusher
+ http.CloseNotifier
+ }{d, &readerFromDelegator{d}, &flusherDelegator{d}, &closeNotifierDelegator{d}}
+ }
+ pickDelegator[readerFrom+hijacker] = func(d *responseWriterDelegator) delegator { // 12
+ return struct {
+ *responseWriterDelegator
+ io.ReaderFrom
+ http.Hijacker
+ }{d, &readerFromDelegator{d}, &hijackerDelegator{d}}
+ }
+ pickDelegator[readerFrom+hijacker+closeNotifier] = func(d *responseWriterDelegator) delegator { // 13
+ return struct {
+ *responseWriterDelegator
+ io.ReaderFrom
+ http.Hijacker
+ http.CloseNotifier
+ }{d, &readerFromDelegator{d}, &hijackerDelegator{d}, &closeNotifierDelegator{d}}
+ }
+ pickDelegator[readerFrom+hijacker+flusher] = func(d *responseWriterDelegator) delegator { // 14
+ return struct {
+ *responseWriterDelegator
+ io.ReaderFrom
+ http.Hijacker
+ http.Flusher
+ }{d, &readerFromDelegator{d}, &hijackerDelegator{d}, &flusherDelegator{d}}
+ }
+ pickDelegator[readerFrom+hijacker+flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 15
+ return struct {
+ *responseWriterDelegator
+ io.ReaderFrom
+ http.Hijacker
+ http.Flusher
+ http.CloseNotifier
+ }{d, &readerFromDelegator{d}, &hijackerDelegator{d}, &flusherDelegator{d}, &closeNotifierDelegator{d}}
+ }
+}
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/promhttp/delegator_1_8.go b/vendor/github.com/prometheus/client_golang/prometheus/promhttp/delegator_1_8.go
new file mode 100644
index 000000000..f4d386f7a
--- /dev/null
+++ b/vendor/github.com/prometheus/client_golang/prometheus/promhttp/delegator_1_8.go
@@ -0,0 +1,181 @@
+// Copyright 2017 The Prometheus 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.
+
+// +build go1.8
+
+package promhttp
+
+import (
+ "io"
+ "net/http"
+)
+
+type pusherDelegator struct{ *responseWriterDelegator }
+
+func (d *pusherDelegator) Push(target string, opts *http.PushOptions) error {
+ return d.ResponseWriter.(http.Pusher).Push(target, opts)
+}
+
+func init() {
+ pickDelegator[pusher] = func(d *responseWriterDelegator) delegator { // 16
+ return pusherDelegator{d}
+ }
+ pickDelegator[pusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 17
+ return struct {
+ *responseWriterDelegator
+ http.Pusher
+ http.CloseNotifier
+ }{d, &pusherDelegator{d}, &closeNotifierDelegator{d}}
+ }
+ pickDelegator[pusher+flusher] = func(d *responseWriterDelegator) delegator { // 18
+ return struct {
+ *responseWriterDelegator
+ http.Pusher
+ http.Flusher
+ }{d, &pusherDelegator{d}, &flusherDelegator{d}}
+ }
+ pickDelegator[pusher+flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 19
+ return struct {
+ *responseWriterDelegator
+ http.Pusher
+ http.Flusher
+ http.CloseNotifier
+ }{d, &pusherDelegator{d}, &flusherDelegator{d}, &closeNotifierDelegator{d}}
+ }
+ pickDelegator[pusher+hijacker] = func(d *responseWriterDelegator) delegator { // 20
+ return struct {
+ *responseWriterDelegator
+ http.Pusher
+ http.Hijacker
+ }{d, &pusherDelegator{d}, &hijackerDelegator{d}}
+ }
+ pickDelegator[pusher+hijacker+closeNotifier] = func(d *responseWriterDelegator) delegator { // 21
+ return struct {
+ *responseWriterDelegator
+ http.Pusher
+ http.Hijacker
+ http.CloseNotifier
+ }{d, &pusherDelegator{d}, &hijackerDelegator{d}, &closeNotifierDelegator{d}}
+ }
+ pickDelegator[pusher+hijacker+flusher] = func(d *responseWriterDelegator) delegator { // 22
+ return struct {
+ *responseWriterDelegator
+ http.Pusher
+ http.Hijacker
+ http.Flusher
+ }{d, &pusherDelegator{d}, &hijackerDelegator{d}, &flusherDelegator{d}}
+ }
+ pickDelegator[pusher+hijacker+flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { //23
+ return struct {
+ *responseWriterDelegator
+ http.Pusher
+ http.Hijacker
+ http.Flusher
+ http.CloseNotifier
+ }{d, &pusherDelegator{d}, &hijackerDelegator{d}, &flusherDelegator{d}, &closeNotifierDelegator{d}}
+ }
+ pickDelegator[pusher+readerFrom] = func(d *responseWriterDelegator) delegator { // 24
+ return struct {
+ *responseWriterDelegator
+ http.Pusher
+ io.ReaderFrom
+ }{d, &pusherDelegator{d}, &readerFromDelegator{d}}
+ }
+ pickDelegator[pusher+readerFrom+closeNotifier] = func(d *responseWriterDelegator) delegator { // 25
+ return struct {
+ *responseWriterDelegator
+ http.Pusher
+ io.ReaderFrom
+ http.CloseNotifier
+ }{d, &pusherDelegator{d}, &readerFromDelegator{d}, &closeNotifierDelegator{d}}
+ }
+ pickDelegator[pusher+readerFrom+flusher] = func(d *responseWriterDelegator) delegator { // 26
+ return struct {
+ *responseWriterDelegator
+ http.Pusher
+ io.ReaderFrom
+ http.Flusher
+ }{d, &pusherDelegator{d}, &readerFromDelegator{d}, &flusherDelegator{d}}
+ }
+ pickDelegator[pusher+readerFrom+flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 27
+ return struct {
+ *responseWriterDelegator
+ http.Pusher
+ io.ReaderFrom
+ http.Flusher
+ http.CloseNotifier
+ }{d, &pusherDelegator{d}, &readerFromDelegator{d}, &flusherDelegator{d}, &closeNotifierDelegator{d}}
+ }
+ pickDelegator[pusher+readerFrom+hijacker] = func(d *responseWriterDelegator) delegator { // 28
+ return struct {
+ *responseWriterDelegator
+ http.Pusher
+ io.ReaderFrom
+ http.Hijacker
+ }{d, &pusherDelegator{d}, &readerFromDelegator{d}, &hijackerDelegator{d}}
+ }
+ pickDelegator[pusher+readerFrom+hijacker+closeNotifier] = func(d *responseWriterDelegator) delegator { // 29
+ return struct {
+ *responseWriterDelegator
+ http.Pusher
+ io.ReaderFrom
+ http.Hijacker
+ http.CloseNotifier
+ }{d, &pusherDelegator{d}, &readerFromDelegator{d}, &hijackerDelegator{d}, &closeNotifierDelegator{d}}
+ }
+ pickDelegator[pusher+readerFrom+hijacker+flusher] = func(d *responseWriterDelegator) delegator { // 30
+ return struct {
+ *responseWriterDelegator
+ http.Pusher
+ io.ReaderFrom
+ http.Hijacker
+ http.Flusher
+ }{d, &pusherDelegator{d}, &readerFromDelegator{d}, &hijackerDelegator{d}, &flusherDelegator{d}}
+ }
+ pickDelegator[pusher+readerFrom+hijacker+flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 31
+ return struct {
+ *responseWriterDelegator
+ http.Pusher
+ io.ReaderFrom
+ http.Hijacker
+ http.Flusher
+ http.CloseNotifier
+ }{d, &pusherDelegator{d}, &readerFromDelegator{d}, &hijackerDelegator{d}, &flusherDelegator{d}, &closeNotifierDelegator{d}}
+ }
+}
+
+func newDelegator(w http.ResponseWriter, observeWriteHeaderFunc func(int)) delegator {
+ d := &responseWriterDelegator{
+ ResponseWriter: w,
+ observeWriteHeader: observeWriteHeaderFunc,
+ }
+
+ id := 0
+ if _, ok := w.(http.CloseNotifier); ok {
+ id += closeNotifier
+ }
+ if _, ok := w.(http.Flusher); ok {
+ id += flusher
+ }
+ if _, ok := w.(http.Hijacker); ok {
+ id += hijacker
+ }
+ if _, ok := w.(io.ReaderFrom); ok {
+ id += readerFrom
+ }
+ if _, ok := w.(http.Pusher); ok {
+ id += pusher
+ }
+
+ return pickDelegator[id](d)
+}
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/promhttp/delegator_pre_1_8.go b/vendor/github.com/prometheus/client_golang/prometheus/promhttp/delegator_pre_1_8.go
new file mode 100644
index 000000000..8bb9b8b68
--- /dev/null
+++ b/vendor/github.com/prometheus/client_golang/prometheus/promhttp/delegator_pre_1_8.go
@@ -0,0 +1,44 @@
+// Copyright 2017 The Prometheus 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.
+
+// +build !go1.8
+
+package promhttp
+
+import (
+ "io"
+ "net/http"
+)
+
+func newDelegator(w http.ResponseWriter, observeWriteHeaderFunc func(int)) delegator {
+ d := &responseWriterDelegator{
+ ResponseWriter: w,
+ observeWriteHeader: observeWriteHeaderFunc,
+ }
+
+ id := 0
+ if _, ok := w.(http.CloseNotifier); ok {
+ id += closeNotifier
+ }
+ if _, ok := w.(http.Flusher); ok {
+ id += flusher
+ }
+ if _, ok := w.(http.Hijacker); ok {
+ id += hijacker
+ }
+ if _, ok := w.(io.ReaderFrom); ok {
+ id += readerFrom
+ }
+
+ return pickDelegator[id](d)
+}
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/promhttp/http.go b/vendor/github.com/prometheus/client_golang/prometheus/promhttp/http.go
index b6dd5a266..2d67f2496 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/promhttp/http.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/promhttp/http.go
@@ -11,21 +11,24 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-// Copyright (c) 2013, The Prometheus Authors
-// All rights reserved.
+// Package promhttp provides tooling around HTTP servers and clients.
//
-// Use of this source code is governed by a BSD-style license that can be found
-// in the LICENSE file.
-
-// Package promhttp contains functions to create http.Handler instances to
-// expose Prometheus metrics via HTTP. In later versions of this package, it
-// will also contain tooling to instrument instances of http.Handler and
-// http.RoundTripper.
+// First, the package allows the creation of http.Handler instances to expose
+// Prometheus metrics via HTTP. promhttp.Handler acts on the
+// prometheus.DefaultGatherer. With HandlerFor, you can create a handler for a
+// custom registry or anything that implements the Gatherer interface. It also
+// allows the creation of handlers that act differently on errors or allow to
+// log errors.
+//
+// Second, the package provides tooling to instrument instances of http.Handler
+// via middleware. Middleware wrappers follow the naming scheme
+// InstrumentHandlerX, where X describes the intended use of the middleware.
+// See each function's doc comment for specific details.
//
-// promhttp.Handler acts on the prometheus.DefaultGatherer. With HandlerFor,
-// you can create a handler for a custom registry or anything that implements
-// the Gatherer interface. It also allows to create handlers that act
-// differently on errors or allow to log errors.
+// Finally, the package allows for an http.RoundTripper to be instrumented via
+// middleware. Middleware wrappers follow the naming scheme
+// InstrumentRoundTripperX, where X describes the intended use of the
+// middleware. See each function's doc comment for specific details.
package promhttp
import (
@@ -125,7 +128,7 @@ func HandlerFor(reg prometheus.Gatherer, opts HandlerOpts) http.Handler {
closer.Close()
}
if lastErr != nil && buf.Len() == 0 {
- http.Error(w, "No metrics encoded, last error:\n\n"+err.Error(), http.StatusInternalServerError)
+ http.Error(w, "No metrics encoded, last error:\n\n"+lastErr.Error(), http.StatusInternalServerError)
return
}
header := w.Header()
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/promhttp/http_test.go b/vendor/github.com/prometheus/client_golang/prometheus/promhttp/http_test.go
index d4a7d4a7b..413ff7baa 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/promhttp/http_test.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/promhttp/http_test.go
@@ -11,12 +11,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-// Copyright (c) 2013, The Prometheus Authors
-// All rights reserved.
-//
-// Use of this source code is governed by a BSD-style license that can be found
-// in the LICENSE file.
-
package promhttp
import (
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_client.go b/vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_client.go
new file mode 100644
index 000000000..86fd56447
--- /dev/null
+++ b/vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_client.go
@@ -0,0 +1,97 @@
+// Copyright 2017 The Prometheus 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 promhttp
+
+import (
+ "net/http"
+ "time"
+
+ "github.com/prometheus/client_golang/prometheus"
+)
+
+// The RoundTripperFunc type is an adapter to allow the use of ordinary
+// functions as RoundTrippers. If f is a function with the appropriate
+// signature, RountTripperFunc(f) is a RoundTripper that calls f.
+type RoundTripperFunc func(req *http.Request) (*http.Response, error)
+
+// RoundTrip implements the RoundTripper interface.
+func (rt RoundTripperFunc) RoundTrip(r *http.Request) (*http.Response, error) {
+ return rt(r)
+}
+
+// InstrumentRoundTripperInFlight is a middleware that wraps the provided
+// http.RoundTripper. It sets the provided prometheus.Gauge to the number of
+// requests currently handled by the wrapped http.RoundTripper.
+//
+// See the example for ExampleInstrumentRoundTripperDuration for example usage.
+func InstrumentRoundTripperInFlight(gauge prometheus.Gauge, next http.RoundTripper) RoundTripperFunc {
+ return RoundTripperFunc(func(r *http.Request) (*http.Response, error) {
+ gauge.Inc()
+ defer gauge.Dec()
+ return next.RoundTrip(r)
+ })
+}
+
+// InstrumentRoundTripperCounter is a middleware that wraps the provided
+// http.RoundTripper to observe the request result with the provided CounterVec.
+// The CounterVec must have zero, one, or two non-const non-curried labels. For
+// those, the only allowed label names are "code" and "method". The function
+// panics otherwise. Partitioning of the CounterVec happens by HTTP status code
+// and/or HTTP method if the respective instance label names are present in the
+// CounterVec. For unpartitioned counting, use a CounterVec with zero labels.
+//
+// If the wrapped RoundTripper panics or returns a non-nil error, the Counter
+// is not incremented.
+//
+// See the example for ExampleInstrumentRoundTripperDuration for example usage.
+func InstrumentRoundTripperCounter(counter *prometheus.CounterVec, next http.RoundTripper) RoundTripperFunc {
+ code, method := checkLabels(counter)
+
+ return RoundTripperFunc(func(r *http.Request) (*http.Response, error) {
+ resp, err := next.RoundTrip(r)
+ if err == nil {
+ counter.With(labels(code, method, r.Method, resp.StatusCode)).Inc()
+ }
+ return resp, err
+ })
+}
+
+// InstrumentRoundTripperDuration is a middleware that wraps the provided
+// http.RoundTripper to observe the request duration with the provided
+// ObserverVec. The ObserverVec must have zero, one, or two non-const
+// non-curried labels. For those, the only allowed label names are "code" and
+// "method". The function panics otherwise. The Observe method of the Observer
+// in the ObserverVec is called with the request duration in
+// seconds. Partitioning happens by HTTP status code and/or HTTP method if the
+// respective instance label names are present in the ObserverVec. For
+// unpartitioned observations, use an ObserverVec with zero labels. Note that
+// partitioning of Histograms is expensive and should be used judiciously.
+//
+// If the wrapped RoundTripper panics or returns a non-nil error, no values are
+// reported.
+//
+// Note that this method is only guaranteed to never observe negative durations
+// if used with Go1.9+.
+func InstrumentRoundTripperDuration(obs prometheus.ObserverVec, next http.RoundTripper) RoundTripperFunc {
+ code, method := checkLabels(obs)
+
+ return RoundTripperFunc(func(r *http.Request) (*http.Response, error) {
+ start := time.Now()
+ resp, err := next.RoundTrip(r)
+ if err == nil {
+ obs.With(labels(code, method, r.Method, resp.StatusCode)).Observe(time.Since(start).Seconds())
+ }
+ return resp, err
+ })
+}
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_client_1_8.go b/vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_client_1_8.go
new file mode 100644
index 000000000..0bd80c355
--- /dev/null
+++ b/vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_client_1_8.go
@@ -0,0 +1,144 @@
+// Copyright 2017 The Prometheus 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.
+
+// +build go1.8
+
+package promhttp
+
+import (
+ "context"
+ "crypto/tls"
+ "net/http"
+ "net/http/httptrace"
+ "time"
+)
+
+// InstrumentTrace is used to offer flexibility in instrumenting the available
+// httptrace.ClientTrace hook functions. Each function is passed a float64
+// representing the time in seconds since the start of the http request. A user
+// may choose to use separately buckets Histograms, or implement custom
+// instance labels on a per function basis.
+type InstrumentTrace struct {
+ GotConn func(float64)
+ PutIdleConn func(float64)
+ GotFirstResponseByte func(float64)
+ Got100Continue func(float64)
+ DNSStart func(float64)
+ DNSDone func(float64)
+ ConnectStart func(float64)
+ ConnectDone func(float64)
+ TLSHandshakeStart func(float64)
+ TLSHandshakeDone func(float64)
+ WroteHeaders func(float64)
+ Wait100Continue func(float64)
+ WroteRequest func(float64)
+}
+
+// InstrumentRoundTripperTrace is a middleware that wraps the provided
+// RoundTripper and reports times to hook functions provided in the
+// InstrumentTrace struct. Hook functions that are not present in the provided
+// InstrumentTrace struct are ignored. Times reported to the hook functions are
+// time since the start of the request. Only with Go1.9+, those times are
+// guaranteed to never be negative. (Earlier Go versions are not using a
+// monotonic clock.) Note that partitioning of Histograms is expensive and
+// should be used judiciously.
+//
+// For hook functions that receive an error as an argument, no observations are
+// made in the event of a non-nil error value.
+//
+// See the example for ExampleInstrumentRoundTripperDuration for example usage.
+func InstrumentRoundTripperTrace(it *InstrumentTrace, next http.RoundTripper) RoundTripperFunc {
+ return RoundTripperFunc(func(r *http.Request) (*http.Response, error) {
+ start := time.Now()
+
+ trace := &httptrace.ClientTrace{
+ GotConn: func(_ httptrace.GotConnInfo) {
+ if it.GotConn != nil {
+ it.GotConn(time.Since(start).Seconds())
+ }
+ },
+ PutIdleConn: func(err error) {
+ if err != nil {
+ return
+ }
+ if it.PutIdleConn != nil {
+ it.PutIdleConn(time.Since(start).Seconds())
+ }
+ },
+ DNSStart: func(_ httptrace.DNSStartInfo) {
+ if it.DNSStart != nil {
+ it.DNSStart(time.Since(start).Seconds())
+ }
+ },
+ DNSDone: func(_ httptrace.DNSDoneInfo) {
+ if it.DNSStart != nil {
+ it.DNSStart(time.Since(start).Seconds())
+ }
+ },
+ ConnectStart: func(_, _ string) {
+ if it.ConnectStart != nil {
+ it.ConnectStart(time.Since(start).Seconds())
+ }
+ },
+ ConnectDone: func(_, _ string, err error) {
+ if err != nil {
+ return
+ }
+ if it.ConnectDone != nil {
+ it.ConnectDone(time.Since(start).Seconds())
+ }
+ },
+ GotFirstResponseByte: func() {
+ if it.GotFirstResponseByte != nil {
+ it.GotFirstResponseByte(time.Since(start).Seconds())
+ }
+ },
+ Got100Continue: func() {
+ if it.Got100Continue != nil {
+ it.Got100Continue(time.Since(start).Seconds())
+ }
+ },
+ TLSHandshakeStart: func() {
+ if it.TLSHandshakeStart != nil {
+ it.TLSHandshakeStart(time.Since(start).Seconds())
+ }
+ },
+ TLSHandshakeDone: func(_ tls.ConnectionState, err error) {
+ if err != nil {
+ return
+ }
+ if it.TLSHandshakeDone != nil {
+ it.TLSHandshakeDone(time.Since(start).Seconds())
+ }
+ },
+ WroteHeaders: func() {
+ if it.WroteHeaders != nil {
+ it.WroteHeaders(time.Since(start).Seconds())
+ }
+ },
+ Wait100Continue: func() {
+ if it.Wait100Continue != nil {
+ it.Wait100Continue(time.Since(start).Seconds())
+ }
+ },
+ WroteRequest: func(_ httptrace.WroteRequestInfo) {
+ if it.WroteRequest != nil {
+ it.WroteRequest(time.Since(start).Seconds())
+ }
+ },
+ }
+ r = r.WithContext(httptrace.WithClientTrace(context.Background(), trace))
+
+ return next.RoundTrip(r)
+ })
+}
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_client_1_8_test.go b/vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_client_1_8_test.go
new file mode 100644
index 000000000..7e3f5229f
--- /dev/null
+++ b/vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_client_1_8_test.go
@@ -0,0 +1,195 @@
+// Copyright 2017 The Prometheus 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.
+
+// +build go1.8
+
+package promhttp
+
+import (
+ "log"
+ "net/http"
+ "testing"
+ "time"
+
+ "github.com/prometheus/client_golang/prometheus"
+)
+
+func TestClientMiddlewareAPI(t *testing.T) {
+ client := http.DefaultClient
+ client.Timeout = 1 * time.Second
+
+ reg := prometheus.NewRegistry()
+
+ inFlightGauge := prometheus.NewGauge(prometheus.GaugeOpts{
+ Name: "client_in_flight_requests",
+ Help: "A gauge of in-flight requests for the wrapped client.",
+ })
+
+ counter := prometheus.NewCounterVec(
+ prometheus.CounterOpts{
+ Name: "client_api_requests_total",
+ Help: "A counter for requests from the wrapped client.",
+ },
+ []string{"code", "method"},
+ )
+
+ dnsLatencyVec := prometheus.NewHistogramVec(
+ prometheus.HistogramOpts{
+ Name: "dns_duration_seconds",
+ Help: "Trace dns latency histogram.",
+ Buckets: []float64{.005, .01, .025, .05},
+ },
+ []string{"event"},
+ )
+
+ tlsLatencyVec := prometheus.NewHistogramVec(
+ prometheus.HistogramOpts{
+ Name: "tls_duration_seconds",
+ Help: "Trace tls latency histogram.",
+ Buckets: []float64{.05, .1, .25, .5},
+ },
+ []string{"event"},
+ )
+
+ histVec := prometheus.NewHistogramVec(
+ prometheus.HistogramOpts{
+ Name: "request_duration_seconds",
+ Help: "A histogram of request latencies.",
+ Buckets: prometheus.DefBuckets,
+ },
+ []string{"method"},
+ )
+
+ reg.MustRegister(counter, tlsLatencyVec, dnsLatencyVec, histVec, inFlightGauge)
+
+ trace := &InstrumentTrace{
+ DNSStart: func(t float64) {
+ dnsLatencyVec.WithLabelValues("dns_start")
+ },
+ DNSDone: func(t float64) {
+ dnsLatencyVec.WithLabelValues("dns_done")
+ },
+ TLSHandshakeStart: func(t float64) {
+ tlsLatencyVec.WithLabelValues("tls_handshake_start")
+ },
+ TLSHandshakeDone: func(t float64) {
+ tlsLatencyVec.WithLabelValues("tls_handshake_done")
+ },
+ }
+
+ client.Transport = InstrumentRoundTripperInFlight(inFlightGauge,
+ InstrumentRoundTripperCounter(counter,
+ InstrumentRoundTripperTrace(trace,
+ InstrumentRoundTripperDuration(histVec, http.DefaultTransport),
+ ),
+ ),
+ )
+
+ resp, err := client.Get("http://google.com")
+ if err != nil {
+ t.Fatalf("%v", err)
+ }
+ defer resp.Body.Close()
+}
+
+func ExampleInstrumentRoundTripperDuration() {
+ client := http.DefaultClient
+ client.Timeout = 1 * time.Second
+
+ inFlightGauge := prometheus.NewGauge(prometheus.GaugeOpts{
+ Name: "client_in_flight_requests",
+ Help: "A gauge of in-flight requests for the wrapped client.",
+ })
+
+ counter := prometheus.NewCounterVec(
+ prometheus.CounterOpts{
+ Name: "client_api_requests_total",
+ Help: "A counter for requests from the wrapped client.",
+ },
+ []string{"code", "method"},
+ )
+
+ // dnsLatencyVec uses custom buckets based on expected dns durations.
+ // It has an instance label "event", which is set in the
+ // DNSStart and DNSDonehook functions defined in the
+ // InstrumentTrace struct below.
+ dnsLatencyVec := prometheus.NewHistogramVec(
+ prometheus.HistogramOpts{
+ Name: "dns_duration_seconds",
+ Help: "Trace dns latency histogram.",
+ Buckets: []float64{.005, .01, .025, .05},
+ },
+ []string{"event"},
+ )
+
+ // tlsLatencyVec uses custom buckets based on expected tls durations.
+ // It has an instance label "event", which is set in the
+ // TLSHandshakeStart and TLSHandshakeDone hook functions defined in the
+ // InstrumentTrace struct below.
+ tlsLatencyVec := prometheus.NewHistogramVec(
+ prometheus.HistogramOpts{
+ Name: "tls_duration_seconds",
+ Help: "Trace tls latency histogram.",
+ Buckets: []float64{.05, .1, .25, .5},
+ },
+ []string{"event"},
+ )
+
+ // histVec has no labels, making it a zero-dimensional ObserverVec.
+ histVec := prometheus.NewHistogramVec(
+ prometheus.HistogramOpts{
+ Name: "request_duration_seconds",
+ Help: "A histogram of request latencies.",
+ Buckets: prometheus.DefBuckets,
+ },
+ []string{},
+ )
+
+ // Register all of the metrics in the standard registry.
+ prometheus.MustRegister(counter, tlsLatencyVec, dnsLatencyVec, histVec, inFlightGauge)
+
+ // Define functions for the available httptrace.ClientTrace hook
+ // functions that we want to instrument.
+ trace := &InstrumentTrace{
+ DNSStart: func(t float64) {
+ dnsLatencyVec.WithLabelValues("dns_start")
+ },
+ DNSDone: func(t float64) {
+ dnsLatencyVec.WithLabelValues("dns_done")
+ },
+ TLSHandshakeStart: func(t float64) {
+ tlsLatencyVec.WithLabelValues("tls_handshake_start")
+ },
+ TLSHandshakeDone: func(t float64) {
+ tlsLatencyVec.WithLabelValues("tls_handshake_done")
+ },
+ }
+
+ // Wrap the default RoundTripper with middleware.
+ roundTripper := InstrumentRoundTripperInFlight(inFlightGauge,
+ InstrumentRoundTripperCounter(counter,
+ InstrumentRoundTripperTrace(trace,
+ InstrumentRoundTripperDuration(histVec, http.DefaultTransport),
+ ),
+ ),
+ )
+
+ // Set the RoundTripper on our client.
+ client.Transport = roundTripper
+
+ resp, err := client.Get("http://google.com")
+ if err != nil {
+ log.Printf("error: %v", err)
+ }
+ defer resp.Body.Close()
+}
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_server.go b/vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_server.go
new file mode 100644
index 000000000..9db243805
--- /dev/null
+++ b/vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_server.go
@@ -0,0 +1,447 @@
+// Copyright 2017 The Prometheus 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 promhttp
+
+import (
+ "errors"
+ "net/http"
+ "strconv"
+ "strings"
+ "time"
+
+ dto "github.com/prometheus/client_model/go"
+
+ "github.com/prometheus/client_golang/prometheus"
+)
+
+// magicString is used for the hacky label test in checkLabels. Remove once fixed.
+const magicString = "zZgWfBxLqvG8kc8IMv3POi2Bb0tZI3vAnBx+gBaFi9FyPzB/CzKUer1yufDa"
+
+// InstrumentHandlerInFlight is a middleware that wraps the provided
+// http.Handler. It sets the provided prometheus.Gauge to the number of
+// requests currently handled by the wrapped http.Handler.
+//
+// See the example for InstrumentHandlerDuration for example usage.
+func InstrumentHandlerInFlight(g prometheus.Gauge, next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ g.Inc()
+ defer g.Dec()
+ next.ServeHTTP(w, r)
+ })
+}
+
+// InstrumentHandlerDuration is a middleware that wraps the provided
+// http.Handler to observe the request duration with the provided ObserverVec.
+// The ObserverVec must have zero, one, or two non-const non-curried labels. For
+// those, the only allowed label names are "code" and "method". The function
+// panics otherwise. The Observe method of the Observer in the ObserverVec is
+// called with the request duration in seconds. Partitioning happens by HTTP
+// status code and/or HTTP method if the respective instance label names are
+// present in the ObserverVec. For unpartitioned observations, use an
+// ObserverVec with zero labels. Note that partitioning of Histograms is
+// expensive and should be used judiciously.
+//
+// If the wrapped Handler does not set a status code, a status code of 200 is assumed.
+//
+// If the wrapped Handler panics, no values are reported.
+//
+// Note that this method is only guaranteed to never observe negative durations
+// if used with Go1.9+.
+func InstrumentHandlerDuration(obs prometheus.ObserverVec, next http.Handler) http.HandlerFunc {
+ code, method := checkLabels(obs)
+
+ if code {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ now := time.Now()
+ d := newDelegator(w, nil)
+ next.ServeHTTP(d, r)
+
+ obs.With(labels(code, method, r.Method, d.Status())).Observe(time.Since(now).Seconds())
+ })
+ }
+
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ now := time.Now()
+ next.ServeHTTP(w, r)
+ obs.With(labels(code, method, r.Method, 0)).Observe(time.Since(now).Seconds())
+ })
+}
+
+// InstrumentHandlerCounter is a middleware that wraps the provided http.Handler
+// to observe the request result with the provided CounterVec. The CounterVec
+// must have zero, one, or two non-const non-curried labels. For those, the only
+// allowed label names are "code" and "method". The function panics
+// otherwise. Partitioning of the CounterVec happens by HTTP status code and/or
+// HTTP method if the respective instance label names are present in the
+// CounterVec. For unpartitioned counting, use a CounterVec with zero labels.
+//
+// If the wrapped Handler does not set a status code, a status code of 200 is assumed.
+//
+// If the wrapped Handler panics, the Counter is not incremented.
+//
+// See the example for InstrumentHandlerDuration for example usage.
+func InstrumentHandlerCounter(counter *prometheus.CounterVec, next http.Handler) http.HandlerFunc {
+ code, method := checkLabels(counter)
+
+ if code {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ d := newDelegator(w, nil)
+ next.ServeHTTP(d, r)
+ counter.With(labels(code, method, r.Method, d.Status())).Inc()
+ })
+ }
+
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ next.ServeHTTP(w, r)
+ counter.With(labels(code, method, r.Method, 0)).Inc()
+ })
+}
+
+// InstrumentHandlerTimeToWriteHeader is a middleware that wraps the provided
+// http.Handler to observe with the provided ObserverVec the request duration
+// until the response headers are written. The ObserverVec must have zero, one,
+// or two non-const non-curried labels. For those, the only allowed label names
+// are "code" and "method". The function panics otherwise. The Observe method of
+// the Observer in the ObserverVec is called with the request duration in
+// seconds. Partitioning happens by HTTP status code and/or HTTP method if the
+// respective instance label names are present in the ObserverVec. For
+// unpartitioned observations, use an ObserverVec with zero labels. Note that
+// partitioning of Histograms is expensive and should be used judiciously.
+//
+// If the wrapped Handler panics before calling WriteHeader, no value is
+// reported.
+//
+// Note that this method is only guaranteed to never observe negative durations
+// if used with Go1.9+.
+//
+// See the example for InstrumentHandlerDuration for example usage.
+func InstrumentHandlerTimeToWriteHeader(obs prometheus.ObserverVec, next http.Handler) http.HandlerFunc {
+ code, method := checkLabels(obs)
+
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ now := time.Now()
+ d := newDelegator(w, func(status int) {
+ obs.With(labels(code, method, r.Method, status)).Observe(time.Since(now).Seconds())
+ })
+ next.ServeHTTP(d, r)
+ })
+}
+
+// InstrumentHandlerRequestSize is a middleware that wraps the provided
+// http.Handler to observe the request size with the provided ObserverVec. The
+// ObserverVec must have zero, one, or two non-const non-curried labels. For
+// those, the only allowed label names are "code" and "method". The function
+// panics otherwise. The Observe method of the Observer in the ObserverVec is
+// called with the request size in bytes. Partitioning happens by HTTP status
+// code and/or HTTP method if the respective instance label names are present in
+// the ObserverVec. For unpartitioned observations, use an ObserverVec with zero
+// labels. Note that partitioning of Histograms is expensive and should be used
+// judiciously.
+//
+// If the wrapped Handler does not set a status code, a status code of 200 is assumed.
+//
+// If the wrapped Handler panics, no values are reported.
+//
+// See the example for InstrumentHandlerDuration for example usage.
+func InstrumentHandlerRequestSize(obs prometheus.ObserverVec, next http.Handler) http.HandlerFunc {
+ code, method := checkLabels(obs)
+
+ if code {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ d := newDelegator(w, nil)
+ next.ServeHTTP(d, r)
+ size := computeApproximateRequestSize(r)
+ obs.With(labels(code, method, r.Method, d.Status())).Observe(float64(size))
+ })
+ }
+
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ next.ServeHTTP(w, r)
+ size := computeApproximateRequestSize(r)
+ obs.With(labels(code, method, r.Method, 0)).Observe(float64(size))
+ })
+}
+
+// InstrumentHandlerResponseSize is a middleware that wraps the provided
+// http.Handler to observe the response size with the provided ObserverVec. The
+// ObserverVec must have zero, one, or two non-const non-curried labels. For
+// those, the only allowed label names are "code" and "method". The function
+// panics otherwise. The Observe method of the Observer in the ObserverVec is
+// called with the response size in bytes. Partitioning happens by HTTP status
+// code and/or HTTP method if the respective instance label names are present in
+// the ObserverVec. For unpartitioned observations, use an ObserverVec with zero
+// labels. Note that partitioning of Histograms is expensive and should be used
+// judiciously.
+//
+// If the wrapped Handler does not set a status code, a status code of 200 is assumed.
+//
+// If the wrapped Handler panics, no values are reported.
+//
+// See the example for InstrumentHandlerDuration for example usage.
+func InstrumentHandlerResponseSize(obs prometheus.ObserverVec, next http.Handler) http.Handler {
+ code, method := checkLabels(obs)
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ d := newDelegator(w, nil)
+ next.ServeHTTP(d, r)
+ obs.With(labels(code, method, r.Method, d.Status())).Observe(float64(d.Written()))
+ })
+}
+
+func checkLabels(c prometheus.Collector) (code bool, method bool) {
+ // TODO(beorn7): Remove this hacky way to check for instance labels
+ // once Descriptors can have their dimensionality queried.
+ var (
+ desc *prometheus.Desc
+ m prometheus.Metric
+ pm dto.Metric
+ lvs []string
+ )
+
+ // Get the Desc from the Collector.
+ descc := make(chan *prometheus.Desc, 1)
+ c.Describe(descc)
+
+ select {
+ case desc = <-descc:
+ default:
+ panic("no description provided by collector")
+ }
+ select {
+ case <-descc:
+ panic("more than one description provided by collector")
+ default:
+ }
+
+ close(descc)
+
+ // Create a ConstMetric with the Desc. Since we don't know how many
+ // variable labels there are, try for as long as it needs.
+ for err := errors.New("dummy"); err != nil; lvs = append(lvs, magicString) {
+ m, err = prometheus.NewConstMetric(desc, prometheus.UntypedValue, 0, lvs...)
+ }
+
+ // Write out the metric into a proto message and look at the labels.
+ // If the value is not the magicString, it is a constLabel, which doesn't interest us.
+ // If the label is curried, it doesn't interest us.
+ // In all other cases, only "code" or "method" is allowed.
+ if err := m.Write(&pm); err != nil {
+ panic("error checking metric for labels")
+ }
+ for _, label := range pm.Label {
+ name, value := label.GetName(), label.GetValue()
+ if value != magicString || isLabelCurried(c, name) {
+ continue
+ }
+ switch name {
+ case "code":
+ code = true
+ case "method":
+ method = true
+ default:
+ panic("metric partitioned with non-supported labels")
+ }
+ }
+ return
+}
+
+func isLabelCurried(c prometheus.Collector, label string) bool {
+ // This is even hackier than the label test above.
+ // We essentially try to curry again and see if it works.
+ // But for that, we need to type-convert to the two
+ // types we use here, ObserverVec or *CounterVec.
+ switch v := c.(type) {
+ case *prometheus.CounterVec:
+ if _, err := v.CurryWith(prometheus.Labels{label: "dummy"}); err == nil {
+ return false
+ }
+ case prometheus.ObserverVec:
+ if _, err := v.CurryWith(prometheus.Labels{label: "dummy"}); err == nil {
+ return false
+ }
+ default:
+ panic("unsupported metric vec type")
+ }
+ return true
+}
+
+// emptyLabels is a one-time allocation for non-partitioned metrics to avoid
+// unnecessary allocations on each request.
+var emptyLabels = prometheus.Labels{}
+
+func labels(code, method bool, reqMethod string, status int) prometheus.Labels {
+ if !(code || method) {
+ return emptyLabels
+ }
+ labels := prometheus.Labels{}
+
+ if code {
+ labels["code"] = sanitizeCode(status)
+ }
+ if method {
+ labels["method"] = sanitizeMethod(reqMethod)
+ }
+
+ return labels
+}
+
+func computeApproximateRequestSize(r *http.Request) int {
+ s := 0
+ if r.URL != nil {
+ s += len(r.URL.String())
+ }
+
+ s += len(r.Method)
+ s += len(r.Proto)
+ for name, values := range r.Header {
+ s += len(name)
+ for _, value := range values {
+ s += len(value)
+ }
+ }
+ s += len(r.Host)
+
+ // N.B. r.Form and r.MultipartForm are assumed to be included in r.URL.
+
+ if r.ContentLength != -1 {
+ s += int(r.ContentLength)
+ }
+ return s
+}
+
+func sanitizeMethod(m string) string {
+ switch m {
+ case "GET", "get":
+ return "get"
+ case "PUT", "put":
+ return "put"
+ case "HEAD", "head":
+ return "head"
+ case "POST", "post":
+ return "post"
+ case "DELETE", "delete":
+ return "delete"
+ case "CONNECT", "connect":
+ return "connect"
+ case "OPTIONS", "options":
+ return "options"
+ case "NOTIFY", "notify":
+ return "notify"
+ default:
+ return strings.ToLower(m)
+ }
+}
+
+// If the wrapped http.Handler has not set a status code, i.e. the value is
+// currently 0, santizeCode will return 200, for consistency with behavior in
+// the stdlib.
+func sanitizeCode(s int) string {
+ switch s {
+ case 100:
+ return "100"
+ case 101:
+ return "101"
+
+ case 200, 0:
+ return "200"
+ case 201:
+ return "201"
+ case 202:
+ return "202"
+ case 203:
+ return "203"
+ case 204:
+ return "204"
+ case 205:
+ return "205"
+ case 206:
+ return "206"
+
+ case 300:
+ return "300"
+ case 301:
+ return "301"
+ case 302:
+ return "302"
+ case 304:
+ return "304"
+ case 305:
+ return "305"
+ case 307:
+ return "307"
+
+ case 400:
+ return "400"
+ case 401:
+ return "401"
+ case 402:
+ return "402"
+ case 403:
+ return "403"
+ case 404:
+ return "404"
+ case 405:
+ return "405"
+ case 406:
+ return "406"
+ case 407:
+ return "407"
+ case 408:
+ return "408"
+ case 409:
+ return "409"
+ case 410:
+ return "410"
+ case 411:
+ return "411"
+ case 412:
+ return "412"
+ case 413:
+ return "413"
+ case 414:
+ return "414"
+ case 415:
+ return "415"
+ case 416:
+ return "416"
+ case 417:
+ return "417"
+ case 418:
+ return "418"
+
+ case 500:
+ return "500"
+ case 501:
+ return "501"
+ case 502:
+ return "502"
+ case 503:
+ return "503"
+ case 504:
+ return "504"
+ case 505:
+ return "505"
+
+ case 428:
+ return "428"
+ case 429:
+ return "429"
+ case 431:
+ return "431"
+ case 511:
+ return "511"
+
+ default:
+ return strconv.Itoa(s)
+ }
+}
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_server_test.go b/vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_server_test.go
new file mode 100644
index 000000000..e9af63e04
--- /dev/null
+++ b/vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_server_test.go
@@ -0,0 +1,375 @@
+// Copyright 2017 The Prometheus 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 promhttp
+
+import (
+ "io"
+ "log"
+ "net/http"
+ "net/http/httptest"
+ "testing"
+
+ "github.com/prometheus/client_golang/prometheus"
+)
+
+func TestLabelCheck(t *testing.T) {
+ scenarios := map[string]struct {
+ varLabels []string
+ constLabels []string
+ curriedLabels []string
+ ok bool
+ }{
+ "empty": {
+ varLabels: []string{},
+ constLabels: []string{},
+ curriedLabels: []string{},
+ ok: true,
+ },
+ "code as single var label": {
+ varLabels: []string{"code"},
+ constLabels: []string{},
+ curriedLabels: []string{},
+ ok: true,
+ },
+ "method as single var label": {
+ varLabels: []string{"method"},
+ constLabels: []string{},
+ curriedLabels: []string{},
+ ok: true,
+ },
+ "cade and method as var labels": {
+ varLabels: []string{"method", "code"},
+ constLabels: []string{},
+ curriedLabels: []string{},
+ ok: true,
+ },
+ "valid case with all labels used": {
+ varLabels: []string{"code", "method"},
+ constLabels: []string{"foo", "bar"},
+ curriedLabels: []string{"dings", "bums"},
+ ok: true,
+ },
+ "unsupported var label": {
+ varLabels: []string{"foo"},
+ constLabels: []string{},
+ curriedLabels: []string{},
+ ok: false,
+ },
+ "mixed var labels": {
+ varLabels: []string{"method", "foo", "code"},
+ constLabels: []string{},
+ curriedLabels: []string{},
+ ok: false,
+ },
+ "unsupported var label but curried": {
+ varLabels: []string{},
+ constLabels: []string{},
+ curriedLabels: []string{"foo"},
+ ok: true,
+ },
+ "mixed var labels but unsupported curried": {
+ varLabels: []string{"code", "method"},
+ constLabels: []string{},
+ curriedLabels: []string{"foo"},
+ ok: true,
+ },
+ "supported label as const and curry": {
+ varLabels: []string{},
+ constLabels: []string{"code"},
+ curriedLabels: []string{"method"},
+ ok: true,
+ },
+ "supported label as const and curry with unsupported as var": {
+ varLabels: []string{"foo"},
+ constLabels: []string{"code"},
+ curriedLabels: []string{"method"},
+ ok: false,
+ },
+ }
+
+ for name, sc := range scenarios {
+ t.Run(name, func(t *testing.T) {
+ constLabels := prometheus.Labels{}
+ for _, l := range sc.constLabels {
+ constLabels[l] = "dummy"
+ }
+ c := prometheus.NewCounterVec(
+ prometheus.CounterOpts{
+ Name: "c",
+ Help: "c help",
+ ConstLabels: constLabels,
+ },
+ append(sc.varLabels, sc.curriedLabels...),
+ )
+ o := prometheus.ObserverVec(prometheus.NewHistogramVec(
+ prometheus.HistogramOpts{
+ Name: "c",
+ Help: "c help",
+ ConstLabels: constLabels,
+ },
+ append(sc.varLabels, sc.curriedLabels...),
+ ))
+ for _, l := range sc.curriedLabels {
+ c = c.MustCurryWith(prometheus.Labels{l: "dummy"})
+ o = o.MustCurryWith(prometheus.Labels{l: "dummy"})
+ }
+
+ func() {
+ defer func() {
+ if err := recover(); err != nil {
+ if sc.ok {
+ t.Error("unexpected panic:", err)
+ }
+ } else if !sc.ok {
+ t.Error("expected panic")
+ }
+ }()
+ InstrumentHandlerCounter(c, nil)
+ }()
+ func() {
+ defer func() {
+ if err := recover(); err != nil {
+ if sc.ok {
+ t.Error("unexpected panic:", err)
+ }
+ } else if !sc.ok {
+ t.Error("expected panic")
+ }
+ }()
+ InstrumentHandlerDuration(o, nil)
+ }()
+ if sc.ok {
+ // Test if wantCode and wantMethod were detected correctly.
+ var wantCode, wantMethod bool
+ for _, l := range sc.varLabels {
+ if l == "code" {
+ wantCode = true
+ }
+ if l == "method" {
+ wantMethod = true
+ }
+ }
+ gotCode, gotMethod := checkLabels(c)
+ if gotCode != wantCode {
+ t.Errorf("wanted code=%t for counter, got code=%t", wantCode, gotCode)
+ }
+ if gotMethod != wantMethod {
+ t.Errorf("wanted method=%t for counter, got method=%t", wantMethod, gotMethod)
+ }
+ gotCode, gotMethod = checkLabels(o)
+ if gotCode != wantCode {
+ t.Errorf("wanted code=%t for observer, got code=%t", wantCode, gotCode)
+ }
+ if gotMethod != wantMethod {
+ t.Errorf("wanted method=%t for observer, got method=%t", wantMethod, gotMethod)
+ }
+ }
+ })
+ }
+}
+
+func TestMiddlewareAPI(t *testing.T) {
+ reg := prometheus.NewRegistry()
+
+ inFlightGauge := prometheus.NewGauge(prometheus.GaugeOpts{
+ Name: "in_flight_requests",
+ Help: "A gauge of requests currently being served by the wrapped handler.",
+ })
+
+ counter := prometheus.NewCounterVec(
+ prometheus.CounterOpts{
+ Name: "api_requests_total",
+ Help: "A counter for requests to the wrapped handler.",
+ },
+ []string{"code", "method"},
+ )
+
+ histVec := prometheus.NewHistogramVec(
+ prometheus.HistogramOpts{
+ Name: "response_duration_seconds",
+ Help: "A histogram of request latencies.",
+ Buckets: prometheus.DefBuckets,
+ ConstLabels: prometheus.Labels{"handler": "api"},
+ },
+ []string{"method"},
+ )
+
+ writeHeaderVec := prometheus.NewHistogramVec(
+ prometheus.HistogramOpts{
+ Name: "write_header_duration_seconds",
+ Help: "A histogram of time to first write latencies.",
+ Buckets: prometheus.DefBuckets,
+ ConstLabels: prometheus.Labels{"handler": "api"},
+ },
+ []string{},
+ )
+
+ responseSize := prometheus.NewHistogramVec(
+ prometheus.HistogramOpts{
+ Name: "push_request_size_bytes",
+ Help: "A histogram of request sizes for requests.",
+ Buckets: []float64{200, 500, 900, 1500},
+ },
+ []string{},
+ )
+
+ handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.Write([]byte("OK"))
+ })
+
+ reg.MustRegister(inFlightGauge, counter, histVec, responseSize, writeHeaderVec)
+
+ chain := InstrumentHandlerInFlight(inFlightGauge,
+ InstrumentHandlerCounter(counter,
+ InstrumentHandlerDuration(histVec,
+ InstrumentHandlerTimeToWriteHeader(writeHeaderVec,
+ InstrumentHandlerResponseSize(responseSize, handler),
+ ),
+ ),
+ ),
+ )
+
+ r, _ := http.NewRequest("GET", "www.example.com", nil)
+ w := httptest.NewRecorder()
+ chain.ServeHTTP(w, r)
+}
+
+func TestInstrumentTimeToFirstWrite(t *testing.T) {
+ var i int
+ dobs := &responseWriterDelegator{
+ ResponseWriter: httptest.NewRecorder(),
+ observeWriteHeader: func(status int) {
+ i = status
+ },
+ }
+ d := newDelegator(dobs, nil)
+
+ d.WriteHeader(http.StatusOK)
+
+ if i != http.StatusOK {
+ t.Fatalf("failed to execute observeWriteHeader")
+ }
+}
+
+// testResponseWriter is an http.ResponseWriter that also implements
+// http.CloseNotifier, http.Flusher, and io.ReaderFrom.
+type testResponseWriter struct {
+ closeNotifyCalled, flushCalled, readFromCalled bool
+}
+
+func (t *testResponseWriter) Header() http.Header { return nil }
+func (t *testResponseWriter) Write([]byte) (int, error) { return 0, nil }
+func (t *testResponseWriter) WriteHeader(int) {}
+func (t *testResponseWriter) CloseNotify() <-chan bool {
+ t.closeNotifyCalled = true
+ return nil
+}
+func (t *testResponseWriter) Flush() { t.flushCalled = true }
+func (t *testResponseWriter) ReadFrom(io.Reader) (int64, error) {
+ t.readFromCalled = true
+ return 0, nil
+}
+
+func TestInterfaceUpgrade(t *testing.T) {
+ w := &testResponseWriter{}
+ d := newDelegator(w, nil)
+ d.(http.CloseNotifier).CloseNotify()
+ if !w.closeNotifyCalled {
+ t.Error("CloseNotify not called")
+ }
+ d.(http.Flusher).Flush()
+ if !w.flushCalled {
+ t.Error("Flush not called")
+ }
+ d.(io.ReaderFrom).ReadFrom(nil)
+ if !w.readFromCalled {
+ t.Error("ReadFrom not called")
+ }
+ if _, ok := d.(http.Hijacker); ok {
+ t.Error("delegator unexpectedly implements http.Hijacker")
+ }
+}
+
+func ExampleInstrumentHandlerDuration() {
+ inFlightGauge := prometheus.NewGauge(prometheus.GaugeOpts{
+ Name: "in_flight_requests",
+ Help: "A gauge of requests currently being served by the wrapped handler.",
+ })
+
+ counter := prometheus.NewCounterVec(
+ prometheus.CounterOpts{
+ Name: "api_requests_total",
+ Help: "A counter for requests to the wrapped handler.",
+ },
+ []string{"code", "method"},
+ )
+
+ // duration is partitioned by the HTTP method and handler. It uses custom
+ // buckets based on the expected request duration.
+ duration := prometheus.NewHistogramVec(
+ prometheus.HistogramOpts{
+ Name: "request_duration_seconds",
+ Help: "A histogram of latencies for requests.",
+ Buckets: []float64{.25, .5, 1, 2.5, 5, 10},
+ },
+ []string{"handler", "method"},
+ )
+
+ // responseSize has no labels, making it a zero-dimensional
+ // ObserverVec.
+ responseSize := prometheus.NewHistogramVec(
+ prometheus.HistogramOpts{
+ Name: "response_size_bytes",
+ Help: "A histogram of response sizes for requests.",
+ Buckets: []float64{200, 500, 900, 1500},
+ },
+ []string{},
+ )
+
+ // Create the handlers that will be wrapped by the middleware.
+ pushHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.Write([]byte("Push"))
+ })
+ pullHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.Write([]byte("Pull"))
+ })
+
+ // Register all of the metrics in the standard registry.
+ prometheus.MustRegister(inFlightGauge, counter, duration, responseSize)
+
+ // Instrument the handlers with all the metrics, injecting the "handler"
+ // label by currying.
+ pushChain := InstrumentHandlerInFlight(inFlightGauge,
+ InstrumentHandlerDuration(duration.MustCurryWith(prometheus.Labels{"handler": "push"}),
+ InstrumentHandlerCounter(counter,
+ InstrumentHandlerResponseSize(responseSize, pushHandler),
+ ),
+ ),
+ )
+ pullChain := InstrumentHandlerInFlight(inFlightGauge,
+ InstrumentHandlerDuration(duration.MustCurryWith(prometheus.Labels{"handler": "pull"}),
+ InstrumentHandlerCounter(counter,
+ InstrumentHandlerResponseSize(responseSize, pullHandler),
+ ),
+ ),
+ )
+
+ http.Handle("/metrics", Handler())
+ http.Handle("/push", pushChain)
+ http.Handle("/pull", pullChain)
+
+ if err := http.ListenAndServe(":3000", nil); err != nil {
+ log.Fatal(err)
+ }
+}
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/push/example_add_from_gatherer_test.go b/vendor/github.com/prometheus/client_golang/prometheus/push/example_add_from_gatherer_test.go
new file mode 100644
index 000000000..5180c0745
--- /dev/null
+++ b/vendor/github.com/prometheus/client_golang/prometheus/push/example_add_from_gatherer_test.go
@@ -0,0 +1,84 @@
+// Copyright 2016 The Prometheus 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.
+
+// Copyright (c) 2013, The Prometheus Authors
+// All rights reserved.
+//
+// Use of this source code is governed by a BSD-style license that can be found
+// in the LICENSE file.
+
+package push_test
+
+import (
+ "fmt"
+ "time"
+
+ "github.com/prometheus/client_golang/prometheus"
+ "github.com/prometheus/client_golang/prometheus/push"
+)
+
+var (
+ completionTime = prometheus.NewGauge(prometheus.GaugeOpts{
+ Name: "db_backup_last_completion_timestamp_seconds",
+ Help: "The timestamp of the last completion of a DB backup, successful or not.",
+ })
+ successTime = prometheus.NewGauge(prometheus.GaugeOpts{
+ Name: "db_backup_last_success_timestamp_seconds",
+ Help: "The timestamp of the last successful completion of a DB backup.",
+ })
+ duration = prometheus.NewGauge(prometheus.GaugeOpts{
+ Name: "db_backup_duration_seconds",
+ Help: "The duration of the last DB backup in seconds.",
+ })
+ records = prometheus.NewGauge(prometheus.GaugeOpts{
+ Name: "db_backup_records_processed",
+ Help: "The number of records processed in the last DB backup.",
+ })
+)
+
+func performBackup() (int, error) {
+ // Perform the backup and return the number of backed up records and any
+ // applicable error.
+ // ...
+ return 42, nil
+}
+
+func ExampleAddFromGatherer() {
+ registry := prometheus.NewRegistry()
+ registry.MustRegister(completionTime, duration, records)
+ // Note that successTime is not registered at this time.
+
+ start := time.Now()
+ n, err := performBackup()
+ records.Set(float64(n))
+ // Note that time.Since only uses a monotonic clock in Go1.9+.
+ duration.Set(time.Since(start).Seconds())
+ completionTime.SetToCurrentTime()
+ if err != nil {
+ fmt.Println("DB backup failed:", err)
+ } else {
+ // Only now register successTime.
+ registry.MustRegister(successTime)
+ successTime.SetToCurrentTime()
+ }
+ // AddFromGatherer is used here rather than FromGatherer to not delete a
+ // previously pushed success timestamp in case of a failure of this
+ // backup.
+ if err := push.AddFromGatherer(
+ "db_backup", nil,
+ "http://pushgateway:9091",
+ registry,
+ ); err != nil {
+ fmt.Println("Could not push to Pushgateway:", err)
+ }
+}
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/push/examples_test.go b/vendor/github.com/prometheus/client_golang/prometheus/push/examples_test.go
index 7f17ca291..7e0ac66a5 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/push/examples_test.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/push/examples_test.go
@@ -15,7 +15,6 @@ package push_test
import (
"fmt"
- "time"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/push"
@@ -24,9 +23,9 @@ import (
func ExampleCollectors() {
completionTime := prometheus.NewGauge(prometheus.GaugeOpts{
Name: "db_backup_last_completion_timestamp_seconds",
- Help: "The timestamp of the last succesful completion of a DB backup.",
+ Help: "The timestamp of the last successful completion of a DB backup.",
})
- completionTime.Set(float64(time.Now().Unix()))
+ completionTime.SetToCurrentTime()
if err := push.Collectors(
"db_backup", push.HostnameGroupingKey(),
"http://pushgateway:9091",
@@ -35,22 +34,3 @@ func ExampleCollectors() {
fmt.Println("Could not push completion time to Pushgateway:", err)
}
}
-
-func ExampleRegistry() {
- registry := prometheus.NewRegistry()
-
- completionTime := prometheus.NewGauge(prometheus.GaugeOpts{
- Name: "db_backup_last_completion_timestamp_seconds",
- Help: "The timestamp of the last succesful completion of a DB backup.",
- })
- registry.MustRegister(completionTime)
-
- completionTime.Set(float64(time.Now().Unix()))
- if err := push.FromGatherer(
- "db_backup", push.HostnameGroupingKey(),
- "http://pushgateway:9091",
- registry,
- ); err != nil {
- fmt.Println("Could not push completion time to Pushgateway:", err)
- }
-}
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/push/push.go b/vendor/github.com/prometheus/client_golang/prometheus/push/push.go
index ae40402f8..8fb6f5f17 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/push/push.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/push/push.go
@@ -84,7 +84,7 @@ func push(job string, grouping map[string]string, pushURL string, g prometheus.G
}
urlComponents := []string{url.QueryEscape(job)}
for ln, lv := range grouping {
- if !model.LabelNameRE.MatchString(ln) {
+ if !model.LabelName(ln).IsValid() {
return fmt.Errorf("grouping label has invalid name: %s", ln)
}
if strings.Contains(lv, "/") {
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/registry.go b/vendor/github.com/prometheus/client_golang/prometheus/registry.go
index 32a3986b0..c84a4420e 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/registry.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/registry.go
@@ -20,6 +20,7 @@ import (
"os"
"sort"
"sync"
+ "unicode/utf8"
"github.com/golang/protobuf/proto"
@@ -80,7 +81,7 @@ func NewPedanticRegistry() *Registry {
// Registerer is the interface for the part of a registry in charge of
// registering and unregistering. Users of custom registries should use
-// Registerer as type for registration purposes (rather then the Registry type
+// Registerer as type for registration purposes (rather than the Registry type
// directly). In that way, they are free to use custom Registerer implementation
// (e.g. for testing purposes).
type Registerer interface {
@@ -152,38 +153,6 @@ func MustRegister(cs ...Collector) {
DefaultRegisterer.MustRegister(cs...)
}
-// RegisterOrGet registers the provided Collector with the DefaultRegisterer and
-// returns the Collector, unless an equal Collector was registered before, in
-// which case that Collector is returned.
-//
-// Deprecated: RegisterOrGet is merely a convenience function for the
-// implementation as described in the documentation for
-// AlreadyRegisteredError. As the use case is relatively rare, this function
-// will be removed in a future version of this package to clean up the
-// namespace.
-func RegisterOrGet(c Collector) (Collector, error) {
- if err := Register(c); err != nil {
- if are, ok := err.(AlreadyRegisteredError); ok {
- return are.ExistingCollector, nil
- }
- return nil, err
- }
- return c, nil
-}
-
-// MustRegisterOrGet behaves like RegisterOrGet but panics instead of returning
-// an error.
-//
-// Deprecated: This is deprecated for the same reason RegisterOrGet is. See
-// there for details.
-func MustRegisterOrGet(c Collector) Collector {
- c, err := RegisterOrGet(c)
- if err != nil {
- panic(err)
- }
- return c
-}
-
// Unregister removes the registration of the provided Collector from the
// DefaultRegisterer.
//
@@ -201,25 +170,6 @@ func (gf GathererFunc) Gather() ([]*dto.MetricFamily, error) {
return gf()
}
-// SetMetricFamilyInjectionHook replaces the DefaultGatherer with one that
-// gathers from the previous DefaultGatherers but then merges the MetricFamily
-// protobufs returned from the provided hook function with the MetricFamily
-// protobufs returned from the original DefaultGatherer.
-//
-// Deprecated: This function manipulates the DefaultGatherer variable. Consider
-// the implications, i.e. don't do this concurrently with any uses of the
-// DefaultGatherer. In the rare cases where you need to inject MetricFamily
-// protobufs directly, it is recommended to use a custom Registry and combine it
-// with a custom Gatherer using the Gatherers type (see
-// there). SetMetricFamilyInjectionHook only exists for compatibility reasons
-// with previous versions of this package.
-func SetMetricFamilyInjectionHook(hook func() []*dto.MetricFamily) {
- DefaultGatherer = Gatherers{
- DefaultGatherer,
- GathererFunc(func() ([]*dto.MetricFamily, error) { return hook(), nil }),
- }
-}
-
// AlreadyRegisteredError is returned by the Register method if the Collector to
// be registered has already been registered before, or a different Collector
// that collects the same metrics has been registered before. Registration fails
@@ -294,7 +244,7 @@ func (r *Registry) Register(c Collector) error {
}()
r.mtx.Lock()
defer r.mtx.Unlock()
- // Coduct various tests...
+ // Conduct various tests...
for desc := range descChan {
// Is the descriptor valid at all?
@@ -447,7 +397,7 @@ func (r *Registry) Gather() ([]*dto.MetricFamily, error) {
// Drain metricChan in case of premature return.
defer func() {
- for _ = range metricChan {
+ for range metricChan {
}
}()
@@ -683,7 +633,7 @@ func (s metricSorter) Less(i, j int) bool {
return s[i].GetTimestampMs() < s[j].GetTimestampMs()
}
-// normalizeMetricFamilies returns a MetricFamily slice whith empty
+// normalizeMetricFamilies returns a MetricFamily slice with empty
// MetricFamilies pruned and the remaining MetricFamilies sorted by name within
// the slice, with the contained Metrics sorted within each MetricFamily.
func normalizeMetricFamilies(metricFamiliesByName map[string]*dto.MetricFamily) []*dto.MetricFamily {
@@ -706,7 +656,7 @@ func normalizeMetricFamilies(metricFamiliesByName map[string]*dto.MetricFamily)
// checkMetricConsistency checks if the provided Metric is consistent with the
// provided MetricFamily. It also hashed the Metric labels and the MetricFamily
-// name. If the resulting hash is alread in the provided metricHashes, an error
+// name. If the resulting hash is already in the provided metricHashes, an error
// is returned. If not, it is added to metricHashes. The provided dimHashes maps
// MetricFamily names to their dimHash (hashed sorted label names). If dimHashes
// doesn't yet contain a hash for the provided MetricFamily, it is
@@ -730,6 +680,12 @@ func checkMetricConsistency(
)
}
+ for _, labelPair := range dtoMetric.GetLabel() {
+ if !utf8.ValidString(*labelPair.Value) {
+ return fmt.Errorf("collected metric's label %s is not utf8: %#v", *labelPair.Name, *labelPair.Value)
+ }
+ }
+
// Is the metric unique (i.e. no other metric with the same name and the same label values)?
h := hashNew()
h = hashAdd(h, metricFamily.GetName())
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/registry_test.go b/vendor/github.com/prometheus/client_golang/prometheus/registry_test.go
index 9dacb6256..d136bba1e 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/registry_test.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/registry_test.go
@@ -209,6 +209,34 @@ metric: <
expectedMetricFamilyMergedWithExternalAsProtoCompactText := []byte(`name:"name" help:"docstring" type:COUNTER metric:<label:<name:"constname" value:"constvalue" > label:<name:"labelname" value:"different_val" > counter:<value:42 > > metric:<label:<name:"constname" value:"constvalue" > label:<name:"labelname" value:"val1" > counter:<value:1 > > metric:<label:<name:"constname" value:"constvalue" > label:<name:"labelname" value:"val2" > counter:<value:1 > >
`)
+ externalMetricFamilyWithInvalidLabelValue := &dto.MetricFamily{
+ Name: proto.String("name"),
+ Help: proto.String("docstring"),
+ Type: dto.MetricType_COUNTER.Enum(),
+ Metric: []*dto.Metric{
+ {
+ Label: []*dto.LabelPair{
+ {
+ Name: proto.String("constname"),
+ Value: proto.String("\xFF"),
+ },
+ {
+ Name: proto.String("labelname"),
+ Value: proto.String("different_val"),
+ },
+ },
+ Counter: &dto.Counter{
+ Value: proto.Float64(42),
+ },
+ },
+ },
+ }
+
+ expectedMetricFamilyInvalidLabelValueAsText := []byte(`An error has occurred during metrics gathering:
+
+collected metric's label constname is not utf8: "\xff"
+`)
+
type output struct {
headers map[string]string
body []byte
@@ -452,6 +480,22 @@ metric: <
externalMetricFamilyWithSameName,
},
},
+ { // 16
+ headers: map[string]string{
+ "Accept": "application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=compact-text",
+ },
+ out: output{
+ headers: map[string]string{
+ "Content-Type": `text/plain; charset=utf-8`,
+ },
+ body: expectedMetricFamilyInvalidLabelValueAsText,
+ },
+ collector: metricVec,
+ externalMF: []*dto.MetricFamily{
+ externalMetricFamily,
+ externalMetricFamilyWithInvalidLabelValue,
+ },
+ },
}
for i, scenario := range scenarios {
registry := prometheus.NewPedanticRegistry()
@@ -526,20 +570,21 @@ func TestRegisterWithOrGet(t *testing.T) {
},
[]string{"foo", "bar"},
)
- if err := prometheus.Register(original); err != nil {
+ var err error
+ if err = prometheus.Register(original); err != nil {
t.Fatal(err)
}
- if err := prometheus.Register(equalButNotSame); err == nil {
+ if err = prometheus.Register(equalButNotSame); err == nil {
t.Fatal("expected error when registringe equal collector")
}
- existing, err := prometheus.RegisterOrGet(equalButNotSame)
- if err != nil {
- t.Fatal(err)
- }
- if existing != original {
- t.Error("expected original collector but got something else")
- }
- if existing == equalButNotSame {
- t.Error("expected original callector but got new one")
+ if are, ok := err.(prometheus.AlreadyRegisteredError); ok {
+ if are.ExistingCollector != original {
+ t.Error("expected original collector but got something else")
+ }
+ if are.ExistingCollector == equalButNotSame {
+ t.Error("expected original callector but got new one")
+ }
+ } else {
+ t.Error("unexpected error:", err)
}
}
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/summary.go b/vendor/github.com/prometheus/client_golang/prometheus/summary.go
index bce05bf9a..f7dc85b96 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/summary.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/summary.go
@@ -36,7 +36,10 @@ const quantileLabel = "quantile"
//
// A typical use-case is the observation of request latencies. By default, a
// Summary provides the median, the 90th and the 99th percentile of the latency
-// as rank estimations.
+// as rank estimations. However, the default behavior will change in the
+// upcoming v0.10 of the library. There will be no rank estiamtions at all by
+// default. For a sane transition, it is recommended to set the desired rank
+// estimations explicitly.
//
// Note that the rank estimations cannot be aggregated in a meaningful way with
// the Prometheus query language (i.e. you cannot average or add them). If you
@@ -54,6 +57,9 @@ type Summary interface {
}
// DefObjectives are the default Summary quantile values.
+//
+// Deprecated: DefObjectives will not be used as the default objectives in
+// v0.10 of the library. The default Summary will have no quantiles then.
var (
DefObjectives = map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001}
@@ -75,8 +81,10 @@ const (
)
// SummaryOpts bundles the options for creating a Summary metric. It is
-// mandatory to set Name and Help to a non-empty string. All other fields are
-// optional and can safely be left at their zero value.
+// mandatory to set Name and Help to a non-empty string. While all other fields
+// are optional and can safely be left at their zero value, it is recommended to
+// explicitly set the Objectives field to the desired value as the default value
+// will change in the upcoming v0.10 of the library.
type SummaryOpts struct {
// Namespace, Subsystem, and Name are components of the fully-qualified
// name of the Summary (created by joining these components with
@@ -93,29 +101,28 @@ type SummaryOpts struct {
// string.
Help string
- // ConstLabels are used to attach fixed labels to this
- // Summary. Summaries with the same fully-qualified name must have the
- // same label names in their ConstLabels.
- //
- // Note that in most cases, labels have a value that varies during the
- // lifetime of a process. Those labels are usually managed with a
- // SummaryVec. ConstLabels serve only special purposes. One is for the
- // special case where the value of a label does not change during the
- // lifetime of a process, e.g. if the revision of the running binary is
- // put into a label. Another, more advanced purpose is if more than one
- // Collector needs to collect Summaries with the same fully-qualified
- // name. In that case, those Summaries must differ in the values of
- // their ConstLabels. See the Collector examples.
+ // ConstLabels are used to attach fixed labels to this metric. Metrics
+ // with the same fully-qualified name must have the same label names in
+ // their ConstLabels.
//
- // If the value of a label never changes (not even between binaries),
- // that label most likely should not be a label at all (but part of the
- // metric name).
+ // ConstLabels are only used rarely. In particular, do not use them to
+ // attach the same labels to all your metrics. Those use cases are
+ // better covered by target labels set by the scraping Prometheus
+ // server, or by one specific metric (e.g. a build_info or a
+ // machine_role metric). See also
+ // https://prometheus.io/docs/instrumenting/writing_exporters/#target-labels,-not-static-scraped-labels
ConstLabels Labels
// Objectives defines the quantile rank estimates with their respective
- // absolute error. If Objectives[q] = e, then the value reported
- // for q will be the φ-quantile value for some φ between q-e and q+e.
- // The default value is DefObjectives.
+ // absolute error. If Objectives[q] = e, then the value reported for q
+ // will be the φ-quantile value for some φ between q-e and q+e. The
+ // default value is DefObjectives. It is used if Objectives is left at
+ // its zero value (i.e. nil). To create a Summary without Objectives,
+ // set it to an empty map (i.e. map[float64]float64{}).
+ //
+ // Deprecated: Note that the current value of DefObjectives is
+ // deprecated. It will be replaced by an empty map in v0.10 of the
+ // library. Please explicitly set Objectives to the desired value.
Objectives map[float64]float64
// MaxAge defines the duration for which an observation stays relevant
@@ -183,7 +190,7 @@ func newSummary(desc *Desc, opts SummaryOpts, labelValues ...string) Summary {
}
}
- if len(opts.Objectives) == 0 {
+ if opts.Objectives == nil {
opts.Objectives = DefObjectives
}
@@ -390,12 +397,11 @@ func (s quantSort) Less(i, j int) bool {
// (e.g. HTTP request latencies, partitioned by status code and method). Create
// instances with NewSummaryVec.
type SummaryVec struct {
- *MetricVec
+ *metricVec
}
// NewSummaryVec creates a new SummaryVec based on the provided SummaryOpts and
-// partitioned by the given label names. At least one label name must be
-// provided.
+// partitioned by the given label names.
func NewSummaryVec(opts SummaryOpts, labelNames []string) *SummaryVec {
desc := NewDesc(
BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
@@ -404,47 +410,116 @@ func NewSummaryVec(opts SummaryOpts, labelNames []string) *SummaryVec {
opts.ConstLabels,
)
return &SummaryVec{
- MetricVec: newMetricVec(desc, func(lvs ...string) Metric {
+ metricVec: newMetricVec(desc, func(lvs ...string) Metric {
return newSummary(desc, opts, lvs...)
}),
}
}
-// GetMetricWithLabelValues replaces the method of the same name in
-// MetricVec. The difference is that this method returns a Summary and not a
-// Metric so that no type conversion is required.
-func (m *SummaryVec) GetMetricWithLabelValues(lvs ...string) (Summary, error) {
- metric, err := m.MetricVec.GetMetricWithLabelValues(lvs...)
+// GetMetricWithLabelValues returns the Summary for the given slice of label
+// values (same order as the VariableLabels in Desc). If that combination of
+// label values is accessed for the first time, a new Summary is created.
+//
+// It is possible to call this method without using the returned Summary to only
+// create the new Summary but leave it at its starting value, a Summary without
+// any observations.
+//
+// Keeping the Summary for later use is possible (and should be considered if
+// performance is critical), but keep in mind that Reset, DeleteLabelValues and
+// Delete can be used to delete the Summary from the SummaryVec. In that case,
+// the Summary will still exist, but it will not be exported anymore, even if a
+// Summary with the same label values is created later. See also the CounterVec
+// example.
+//
+// An error is returned if the number of label values is not the same as the
+// number of VariableLabels in Desc (minus any curried labels).
+//
+// Note that for more than one label value, this method is prone to mistakes
+// caused by an incorrect order of arguments. Consider GetMetricWith(Labels) as
+// an alternative to avoid that type of mistake. For higher label numbers, the
+// latter has a much more readable (albeit more verbose) syntax, but it comes
+// with a performance overhead (for creating and processing the Labels map).
+// See also the GaugeVec example.
+func (v *SummaryVec) GetMetricWithLabelValues(lvs ...string) (Observer, error) {
+ metric, err := v.metricVec.getMetricWithLabelValues(lvs...)
if metric != nil {
- return metric.(Summary), err
+ return metric.(Observer), err
}
return nil, err
}
-// GetMetricWith replaces the method of the same name in MetricVec. The
-// difference is that this method returns a Summary and not a Metric so that no
-// type conversion is required.
-func (m *SummaryVec) GetMetricWith(labels Labels) (Summary, error) {
- metric, err := m.MetricVec.GetMetricWith(labels)
+// GetMetricWith returns the Summary for the given Labels map (the label names
+// must match those of the VariableLabels in Desc). If that label map is
+// accessed for the first time, a new Summary is created. Implications of
+// creating a Summary without using it and keeping the Summary for later use are
+// the same as for GetMetricWithLabelValues.
+//
+// An error is returned if the number and names of the Labels are inconsistent
+// with those of the VariableLabels in Desc (minus any curried labels).
+//
+// This method is used for the same purpose as
+// GetMetricWithLabelValues(...string). See there for pros and cons of the two
+// methods.
+func (v *SummaryVec) GetMetricWith(labels Labels) (Observer, error) {
+ metric, err := v.metricVec.getMetricWith(labels)
if metric != nil {
- return metric.(Summary), err
+ return metric.(Observer), err
}
return nil, err
}
// WithLabelValues works as GetMetricWithLabelValues, but panics where
-// GetMetricWithLabelValues would have returned an error. By not returning an
-// error, WithLabelValues allows shortcuts like
+// GetMetricWithLabelValues would have returned an error. Not returning an
+// error allows shortcuts like
// myVec.WithLabelValues("404", "GET").Observe(42.21)
-func (m *SummaryVec) WithLabelValues(lvs ...string) Summary {
- return m.MetricVec.WithLabelValues(lvs...).(Summary)
+func (v *SummaryVec) WithLabelValues(lvs ...string) Observer {
+ s, err := v.GetMetricWithLabelValues(lvs...)
+ if err != nil {
+ panic(err)
+ }
+ return s
}
// With works as GetMetricWith, but panics where GetMetricWithLabels would have
-// returned an error. By not returning an error, With allows shortcuts like
-// myVec.With(Labels{"code": "404", "method": "GET"}).Observe(42.21)
-func (m *SummaryVec) With(labels Labels) Summary {
- return m.MetricVec.With(labels).(Summary)
+// returned an error. Not returning an error allows shortcuts like
+// myVec.With(prometheus.Labels{"code": "404", "method": "GET"}).Observe(42.21)
+func (v *SummaryVec) With(labels Labels) Observer {
+ s, err := v.GetMetricWith(labels)
+ if err != nil {
+ panic(err)
+ }
+ return s
+}
+
+// CurryWith returns a vector curried with the provided labels, i.e. the
+// returned vector has those labels pre-set for all labeled operations performed
+// on it. The cardinality of the curried vector is reduced accordingly. The
+// order of the remaining labels stays the same (just with the curried labels
+// taken out of the sequence – which is relevant for the
+// (GetMetric)WithLabelValues methods). It is possible to curry a curried
+// vector, but only with labels not yet used for currying before.
+//
+// The metrics contained in the SummaryVec are shared between the curried and
+// uncurried vectors. They are just accessed differently. Curried and uncurried
+// vectors behave identically in terms of collection. Only one must be
+// registered with a given registry (usually the uncurried version). The Reset
+// method deletes all metrics, even if called on a curried vector.
+func (v *SummaryVec) CurryWith(labels Labels) (ObserverVec, error) {
+ vec, err := v.curryWith(labels)
+ if vec != nil {
+ return &SummaryVec{vec}, err
+ }
+ return nil, err
+}
+
+// MustCurryWith works as CurryWith but panics where CurryWith would have
+// returned an error.
+func (v *SummaryVec) MustCurryWith(labels Labels) ObserverVec {
+ vec, err := v.CurryWith(labels)
+ if err != nil {
+ panic(err)
+ }
+ return vec
}
type constSummary struct {
@@ -505,8 +580,8 @@ func NewConstSummary(
quantiles map[float64]float64,
labelValues ...string,
) (Metric, error) {
- if len(desc.variableLabels) != len(labelValues) {
- return nil, errInconsistentCardinality
+ if err := validateLabelValues(labelValues, len(desc.variableLabels)); err != nil {
+ return nil, err
}
return &constSummary{
desc: desc,
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/summary_test.go b/vendor/github.com/prometheus/client_golang/prometheus/summary_test.go
index c4575ffbd..b162ed946 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/summary_test.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/summary_test.go
@@ -25,6 +25,45 @@ import (
dto "github.com/prometheus/client_model/go"
)
+func TestSummaryWithDefaultObjectives(t *testing.T) {
+ reg := NewRegistry()
+ summaryWithDefaultObjectives := NewSummary(SummaryOpts{
+ Name: "default_objectives",
+ Help: "Test help.",
+ })
+ if err := reg.Register(summaryWithDefaultObjectives); err != nil {
+ t.Error(err)
+ }
+
+ m := &dto.Metric{}
+ if err := summaryWithDefaultObjectives.Write(m); err != nil {
+ t.Error(err)
+ }
+ if len(m.GetSummary().Quantile) != len(DefObjectives) {
+ t.Error("expected default objectives in summary")
+ }
+}
+
+func TestSummaryWithoutObjectives(t *testing.T) {
+ reg := NewRegistry()
+ summaryWithEmptyObjectives := NewSummary(SummaryOpts{
+ Name: "empty_objectives",
+ Help: "Test help.",
+ Objectives: map[float64]float64{},
+ })
+ if err := reg.Register(summaryWithEmptyObjectives); err != nil {
+ t.Error(err)
+ }
+
+ m := &dto.Metric{}
+ if err := summaryWithEmptyObjectives.Write(m); err != nil {
+ t.Error(err)
+ }
+ if len(m.GetSummary().Quantile) != 0 {
+ t.Error("expected no objectives in summary")
+ }
+}
+
func benchmarkSummaryObserve(w int, b *testing.B) {
b.StopTimer()
@@ -136,8 +175,9 @@ func TestSummaryConcurrency(t *testing.T) {
end.Add(concLevel)
sum := NewSummary(SummaryOpts{
- Name: "test_summary",
- Help: "helpless",
+ Name: "test_summary",
+ Help: "helpless",
+ Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001},
})
allVars := make([]float64, total)
@@ -223,8 +263,9 @@ func TestSummaryVecConcurrency(t *testing.T) {
sum := NewSummaryVec(
SummaryOpts{
- Name: "test_summary",
- Help: "helpless",
+ Name: "test_summary",
+ Help: "helpless",
+ Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001},
},
[]string{"label"},
)
@@ -260,7 +301,7 @@ func TestSummaryVecConcurrency(t *testing.T) {
for i := 0; i < vecLength; i++ {
m := &dto.Metric{}
s := sum.WithLabelValues(string('A' + i))
- s.Write(m)
+ s.(Summary).Write(m)
if got, want := int(*m.Summary.SampleCount), len(allVars[i]); got != want {
t.Errorf("got sample count %d for label %c, want %d", got, 'A'+i, want)
}
@@ -305,7 +346,7 @@ func TestSummaryDecay(t *testing.T) {
m := &dto.Metric{}
i := 0
tick := time.NewTicker(time.Millisecond)
- for _ = range tick.C {
+ for range tick.C {
i++
sum.Observe(float64(i))
if i%10 == 0 {
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/timer.go b/vendor/github.com/prometheus/client_golang/prometheus/timer.go
new file mode 100644
index 000000000..b8fc5f18c
--- /dev/null
+++ b/vendor/github.com/prometheus/client_golang/prometheus/timer.go
@@ -0,0 +1,51 @@
+// Copyright 2016 The Prometheus 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
+
+import "time"
+
+// Timer is a helper type to time functions. Use NewTimer to create new
+// instances.
+type Timer struct {
+ begin time.Time
+ observer Observer
+}
+
+// NewTimer creates a new Timer. The provided Observer is used to observe a
+// duration in seconds. Timer is usually used to time a function call in the
+// following way:
+// func TimeMe() {
+// timer := NewTimer(myHistogram)
+// defer timer.ObserveDuration()
+// // Do actual work.
+// }
+func NewTimer(o Observer) *Timer {
+ return &Timer{
+ begin: time.Now(),
+ observer: o,
+ }
+}
+
+// ObserveDuration records the duration passed since the Timer was created with
+// NewTimer. It calls the Observe method of the Observer provided during
+// construction with the duration in seconds as an argument. ObserveDuration is
+// usually called with a defer statement.
+//
+// Note that this method is only guaranteed to never observe negative durations
+// if used with Go1.9+.
+func (t *Timer) ObserveDuration() {
+ if t.observer != nil {
+ t.observer.Observe(time.Since(t.begin).Seconds())
+ }
+}
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/timer_test.go b/vendor/github.com/prometheus/client_golang/prometheus/timer_test.go
new file mode 100644
index 000000000..294902068
--- /dev/null
+++ b/vendor/github.com/prometheus/client_golang/prometheus/timer_test.go
@@ -0,0 +1,152 @@
+// Copyright 2016 The Prometheus 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
+
+import (
+ "testing"
+
+ dto "github.com/prometheus/client_model/go"
+)
+
+func TestTimerObserve(t *testing.T) {
+ var (
+ his = NewHistogram(HistogramOpts{Name: "test_histogram"})
+ sum = NewSummary(SummaryOpts{Name: "test_summary"})
+ gauge = NewGauge(GaugeOpts{Name: "test_gauge"})
+ )
+
+ func() {
+ hisTimer := NewTimer(his)
+ sumTimer := NewTimer(sum)
+ gaugeTimer := NewTimer(ObserverFunc(gauge.Set))
+ defer hisTimer.ObserveDuration()
+ defer sumTimer.ObserveDuration()
+ defer gaugeTimer.ObserveDuration()
+ }()
+
+ m := &dto.Metric{}
+ his.Write(m)
+ if want, got := uint64(1), m.GetHistogram().GetSampleCount(); want != got {
+ t.Errorf("want %d observations for histogram, got %d", want, got)
+ }
+ m.Reset()
+ sum.Write(m)
+ if want, got := uint64(1), m.GetSummary().GetSampleCount(); want != got {
+ t.Errorf("want %d observations for summary, got %d", want, got)
+ }
+ m.Reset()
+ gauge.Write(m)
+ if got := m.GetGauge().GetValue(); got <= 0 {
+ t.Errorf("want value > 0 for gauge, got %f", got)
+ }
+}
+
+func TestTimerEmpty(t *testing.T) {
+ emptyTimer := NewTimer(nil)
+ emptyTimer.ObserveDuration()
+ // Do nothing, just demonstrate it works without panic.
+}
+
+func TestTimerConditionalTiming(t *testing.T) {
+ var (
+ his = NewHistogram(HistogramOpts{
+ Name: "test_histogram",
+ })
+ timeMe = true
+ m = &dto.Metric{}
+ )
+
+ timedFunc := func() {
+ timer := NewTimer(ObserverFunc(func(v float64) {
+ if timeMe {
+ his.Observe(v)
+ }
+ }))
+ defer timer.ObserveDuration()
+ }
+
+ timedFunc() // This will time.
+ his.Write(m)
+ if want, got := uint64(1), m.GetHistogram().GetSampleCount(); want != got {
+ t.Errorf("want %d observations for histogram, got %d", want, got)
+ }
+
+ timeMe = false
+ timedFunc() // This will not time again.
+ m.Reset()
+ his.Write(m)
+ if want, got := uint64(1), m.GetHistogram().GetSampleCount(); want != got {
+ t.Errorf("want %d observations for histogram, got %d", want, got)
+ }
+}
+
+func TestTimerByOutcome(t *testing.T) {
+ var (
+ his = NewHistogramVec(
+ HistogramOpts{Name: "test_histogram"},
+ []string{"outcome"},
+ )
+ outcome = "foo"
+ m = &dto.Metric{}
+ )
+
+ timedFunc := func() {
+ timer := NewTimer(ObserverFunc(func(v float64) {
+ his.WithLabelValues(outcome).Observe(v)
+ }))
+ defer timer.ObserveDuration()
+
+ if outcome == "foo" {
+ outcome = "bar"
+ return
+ }
+ outcome = "foo"
+ }
+
+ timedFunc()
+ his.WithLabelValues("foo").(Histogram).Write(m)
+ if want, got := uint64(0), m.GetHistogram().GetSampleCount(); want != got {
+ t.Errorf("want %d observations for 'foo' histogram, got %d", want, got)
+ }
+ m.Reset()
+ his.WithLabelValues("bar").(Histogram).Write(m)
+ if want, got := uint64(1), m.GetHistogram().GetSampleCount(); want != got {
+ t.Errorf("want %d observations for 'bar' histogram, got %d", want, got)
+ }
+
+ timedFunc()
+ m.Reset()
+ his.WithLabelValues("foo").(Histogram).Write(m)
+ if want, got := uint64(1), m.GetHistogram().GetSampleCount(); want != got {
+ t.Errorf("want %d observations for 'foo' histogram, got %d", want, got)
+ }
+ m.Reset()
+ his.WithLabelValues("bar").(Histogram).Write(m)
+ if want, got := uint64(1), m.GetHistogram().GetSampleCount(); want != got {
+ t.Errorf("want %d observations for 'bar' histogram, got %d", want, got)
+ }
+
+ timedFunc()
+ m.Reset()
+ his.WithLabelValues("foo").(Histogram).Write(m)
+ if want, got := uint64(1), m.GetHistogram().GetSampleCount(); want != got {
+ t.Errorf("want %d observations for 'foo' histogram, got %d", want, got)
+ }
+ m.Reset()
+ his.WithLabelValues("bar").(Histogram).Write(m)
+ if want, got := uint64(2), m.GetHistogram().GetSampleCount(); want != got {
+ t.Errorf("want %d observations for 'bar' histogram, got %d", want, got)
+ }
+
+}
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/untyped.go b/vendor/github.com/prometheus/client_golang/prometheus/untyped.go
index 5faf7e6e3..0f9ce63f4 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/untyped.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/untyped.go
@@ -13,108 +13,12 @@
package prometheus
-// Untyped is a Metric that represents a single numerical value that can
-// arbitrarily go up and down.
-//
-// An Untyped metric works the same as a Gauge. The only difference is that to
-// no type information is implied.
-//
-// To create Untyped instances, use NewUntyped.
-type Untyped interface {
- Metric
- Collector
-
- // Set sets the Untyped metric to an arbitrary value.
- Set(float64)
- // Inc increments the Untyped metric by 1.
- Inc()
- // Dec decrements the Untyped metric by 1.
- Dec()
- // Add adds the given value to the Untyped metric. (The value can be
- // negative, resulting in a decrease.)
- Add(float64)
- // Sub subtracts the given value from the Untyped metric. (The value can
- // be negative, resulting in an increase.)
- Sub(float64)
-}
-
// UntypedOpts is an alias for Opts. See there for doc comments.
type UntypedOpts Opts
-// NewUntyped creates a new Untyped metric from the provided UntypedOpts.
-func NewUntyped(opts UntypedOpts) Untyped {
- return newValue(NewDesc(
- BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
- opts.Help,
- nil,
- opts.ConstLabels,
- ), UntypedValue, 0)
-}
-
-// UntypedVec is a Collector that bundles a set of Untyped metrics that all
-// share the same Desc, but have different values for their variable
-// labels. This is used if you want to count the same thing partitioned by
-// various dimensions. Create instances with NewUntypedVec.
-type UntypedVec struct {
- *MetricVec
-}
-
-// NewUntypedVec creates a new UntypedVec based on the provided UntypedOpts and
-// partitioned by the given label names. At least one label name must be
-// provided.
-func NewUntypedVec(opts UntypedOpts, labelNames []string) *UntypedVec {
- desc := NewDesc(
- BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
- opts.Help,
- labelNames,
- opts.ConstLabels,
- )
- return &UntypedVec{
- MetricVec: newMetricVec(desc, func(lvs ...string) Metric {
- return newValue(desc, UntypedValue, 0, lvs...)
- }),
- }
-}
-
-// GetMetricWithLabelValues replaces the method of the same name in
-// MetricVec. The difference is that this method returns an Untyped and not a
-// Metric so that no type conversion is required.
-func (m *UntypedVec) GetMetricWithLabelValues(lvs ...string) (Untyped, error) {
- metric, err := m.MetricVec.GetMetricWithLabelValues(lvs...)
- if metric != nil {
- return metric.(Untyped), err
- }
- return nil, err
-}
-
-// GetMetricWith replaces the method of the same name in MetricVec. The
-// difference is that this method returns an Untyped and not a Metric so that no
-// type conversion is required.
-func (m *UntypedVec) GetMetricWith(labels Labels) (Untyped, error) {
- metric, err := m.MetricVec.GetMetricWith(labels)
- if metric != nil {
- return metric.(Untyped), err
- }
- return nil, err
-}
-
-// WithLabelValues works as GetMetricWithLabelValues, but panics where
-// GetMetricWithLabelValues would have returned an error. By not returning an
-// error, WithLabelValues allows shortcuts like
-// myVec.WithLabelValues("404", "GET").Add(42)
-func (m *UntypedVec) WithLabelValues(lvs ...string) Untyped {
- return m.MetricVec.WithLabelValues(lvs...).(Untyped)
-}
-
-// With works as GetMetricWith, but panics where GetMetricWithLabels would have
-// returned an error. By not returning an error, With allows shortcuts like
-// myVec.With(Labels{"code": "404", "method": "GET"}).Add(42)
-func (m *UntypedVec) With(labels Labels) Untyped {
- return m.MetricVec.With(labels).(Untyped)
-}
-
-// UntypedFunc is an Untyped whose value is determined at collect time by
-// calling a provided function.
+// UntypedFunc works like GaugeFunc but the collected metric is of type
+// "Untyped". UntypedFunc is useful to mirror an external metric of unknown
+// type.
//
// To create UntypedFunc instances, use NewUntypedFunc.
type UntypedFunc interface {
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/value.go b/vendor/github.com/prometheus/client_golang/prometheus/value.go
index a944c3775..543b57c27 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/value.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/value.go
@@ -14,11 +14,8 @@
package prometheus
import (
- "errors"
"fmt"
- "math"
"sort"
- "sync/atomic"
dto "github.com/prometheus/client_model/go"
@@ -36,77 +33,6 @@ const (
UntypedValue
)
-var errInconsistentCardinality = errors.New("inconsistent label cardinality")
-
-// value is a generic metric for simple values. It implements Metric, Collector,
-// Counter, Gauge, and Untyped. Its effective type is determined by
-// ValueType. This is a low-level building block used by the library to back the
-// implementations of Counter, Gauge, and Untyped.
-type value struct {
- // valBits containst the bits of the represented float64 value. It has
- // to go first in the struct to guarantee alignment for atomic
- // operations. http://golang.org/pkg/sync/atomic/#pkg-note-BUG
- valBits uint64
-
- selfCollector
-
- desc *Desc
- valType ValueType
- labelPairs []*dto.LabelPair
-}
-
-// newValue returns a newly allocated value with the given Desc, ValueType,
-// sample value and label values. It panics if the number of label
-// values is different from the number of variable labels in Desc.
-func newValue(desc *Desc, valueType ValueType, val float64, labelValues ...string) *value {
- if len(labelValues) != len(desc.variableLabels) {
- panic(errInconsistentCardinality)
- }
- result := &value{
- desc: desc,
- valType: valueType,
- valBits: math.Float64bits(val),
- labelPairs: makeLabelPairs(desc, labelValues),
- }
- result.init(result)
- return result
-}
-
-func (v *value) Desc() *Desc {
- return v.desc
-}
-
-func (v *value) Set(val float64) {
- atomic.StoreUint64(&v.valBits, math.Float64bits(val))
-}
-
-func (v *value) Inc() {
- v.Add(1)
-}
-
-func (v *value) Dec() {
- v.Add(-1)
-}
-
-func (v *value) Add(val float64) {
- for {
- oldBits := atomic.LoadUint64(&v.valBits)
- newBits := math.Float64bits(math.Float64frombits(oldBits) + val)
- if atomic.CompareAndSwapUint64(&v.valBits, oldBits, newBits) {
- return
- }
- }
-}
-
-func (v *value) Sub(val float64) {
- v.Add(val * -1)
-}
-
-func (v *value) Write(out *dto.Metric) error {
- val := math.Float64frombits(atomic.LoadUint64(&v.valBits))
- return populateMetric(v.valType, val, v.labelPairs, out)
-}
-
// valueFunc is a generic metric for simple values retrieved on collect time
// from a function. It implements Metric and Collector. Its effective type is
// determined by ValueType. This is a low-level building block used by the
@@ -153,8 +79,8 @@ func (v *valueFunc) Write(out *dto.Metric) error {
// the Collect method. NewConstMetric returns an error if the length of
// labelValues is not consistent with the variable labels in Desc.
func NewConstMetric(desc *Desc, valueType ValueType, value float64, labelValues ...string) (Metric, error) {
- if len(desc.variableLabels) != len(labelValues) {
- return nil, errInconsistentCardinality
+ if err := validateLabelValues(labelValues, len(desc.variableLabels)); err != nil {
+ return nil, err
}
return &constMetric{
desc: desc,
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/value_test.go b/vendor/github.com/prometheus/client_golang/prometheus/value_test.go
new file mode 100644
index 000000000..eed517e7b
--- /dev/null
+++ b/vendor/github.com/prometheus/client_golang/prometheus/value_test.go
@@ -0,0 +1,43 @@
+package prometheus
+
+import (
+ "fmt"
+ "testing"
+)
+
+func TestNewConstMetricInvalidLabelValues(t *testing.T) {
+ testCases := []struct {
+ desc string
+ labels Labels
+ }{
+ {
+ desc: "non utf8 label value",
+ labels: Labels{"a": "\xFF"},
+ },
+ {
+ desc: "not enough label values",
+ labels: Labels{},
+ },
+ {
+ desc: "too many label values",
+ labels: Labels{"a": "1", "b": "2"},
+ },
+ }
+
+ for _, test := range testCases {
+ metricDesc := NewDesc(
+ "sample_value",
+ "sample value",
+ []string{"a"},
+ Labels{},
+ )
+
+ expectPanic(t, func() {
+ MustNewConstMetric(metricDesc, CounterValue, 0.3, "\xFF")
+ }, fmt.Sprintf("WithLabelValues: expected panic because: %s", test.desc))
+
+ if _, err := NewConstMetric(metricDesc, CounterValue, 0.3, "\xFF"); err == nil {
+ t.Errorf("NewConstMetric: expected error because: %s", test.desc)
+ }
+ }
+}
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/vec.go b/vendor/github.com/prometheus/client_golang/prometheus/vec.go
index 7f3eef9a4..cea158249 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/vec.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/vec.go
@@ -20,200 +20,253 @@ import (
"github.com/prometheus/common/model"
)
-// MetricVec is a Collector to bundle metrics of the same name that
-// differ in their label values. MetricVec is usually not used directly but as a
-// building block for implementations of vectors of a given metric
-// type. GaugeVec, CounterVec, SummaryVec, and UntypedVec are examples already
-// provided in this package.
-type MetricVec struct {
- mtx sync.RWMutex // Protects the children.
- children map[uint64][]metricWithLabelValues
- desc *Desc
-
- newMetric func(labelValues ...string) Metric
- hashAdd func(h uint64, s string) uint64 // replace hash function for testing collision handling
+// metricVec is a Collector to bundle metrics of the same name that differ in
+// their label values. metricVec is not used directly (and therefore
+// unexported). It is used as a building block for implementations of vectors of
+// a given metric type, like GaugeVec, CounterVec, SummaryVec, and HistogramVec.
+// It also handles label currying. It uses basicMetricVec internally.
+type metricVec struct {
+ *metricMap
+
+ curry []curriedLabelValue
+
+ // hashAdd and hashAddByte can be replaced for testing collision handling.
+ hashAdd func(h uint64, s string) uint64
hashAddByte func(h uint64, b byte) uint64
}
-// newMetricVec returns an initialized MetricVec. The concrete value is
-// returned for embedding into another struct.
-func newMetricVec(desc *Desc, newMetric func(lvs ...string) Metric) *MetricVec {
- return &MetricVec{
- children: map[uint64][]metricWithLabelValues{},
- desc: desc,
- newMetric: newMetric,
+// newMetricVec returns an initialized metricVec.
+func newMetricVec(desc *Desc, newMetric func(lvs ...string) Metric) *metricVec {
+ return &metricVec{
+ metricMap: &metricMap{
+ metrics: map[uint64][]metricWithLabelValues{},
+ desc: desc,
+ newMetric: newMetric,
+ },
hashAdd: hashAdd,
hashAddByte: hashAddByte,
}
}
-// metricWithLabelValues provides the metric and its label values for
-// disambiguation on hash collision.
-type metricWithLabelValues struct {
- values []string
- metric Metric
-}
+// DeleteLabelValues removes the metric where the variable labels are the same
+// as those passed in as labels (same order as the VariableLabels in Desc). It
+// returns true if a metric was deleted.
+//
+// It is not an error if the number of label values is not the same as the
+// number of VariableLabels in Desc. However, such inconsistent label count can
+// never match an actual metric, so the method will always return false in that
+// case.
+//
+// Note that for more than one label value, this method is prone to mistakes
+// caused by an incorrect order of arguments. Consider Delete(Labels) as an
+// alternative to avoid that type of mistake. For higher label numbers, the
+// latter has a much more readable (albeit more verbose) syntax, but it comes
+// with a performance overhead (for creating and processing the Labels map).
+// See also the CounterVec example.
+func (m *metricVec) DeleteLabelValues(lvs ...string) bool {
+ h, err := m.hashLabelValues(lvs)
+ if err != nil {
+ return false
+ }
-// Describe implements Collector. The length of the returned slice
-// is always one.
-func (m *MetricVec) Describe(ch chan<- *Desc) {
- ch <- m.desc
+ return m.metricMap.deleteByHashWithLabelValues(h, lvs, m.curry)
}
-// Collect implements Collector.
-func (m *MetricVec) Collect(ch chan<- Metric) {
- m.mtx.RLock()
- defer m.mtx.RUnlock()
+// Delete deletes the metric where the variable labels are the same as those
+// passed in as labels. It returns true if a metric was deleted.
+//
+// It is not an error if the number and names of the Labels are inconsistent
+// with those of the VariableLabels in Desc. However, such inconsistent Labels
+// can never match an actual metric, so the method will always return false in
+// that case.
+//
+// This method is used for the same purpose as DeleteLabelValues(...string). See
+// there for pros and cons of the two methods.
+func (m *metricVec) Delete(labels Labels) bool {
+ h, err := m.hashLabels(labels)
+ if err != nil {
+ return false
+ }
- for _, metrics := range m.children {
- for _, metric := range metrics {
- ch <- metric.metric
+ return m.metricMap.deleteByHashWithLabels(h, labels, m.curry)
+}
+
+func (m *metricVec) curryWith(labels Labels) (*metricVec, error) {
+ var (
+ newCurry []curriedLabelValue
+ oldCurry = m.curry
+ iCurry int
+ )
+ for i, label := range m.desc.variableLabels {
+ val, ok := labels[label]
+ if iCurry < len(oldCurry) && oldCurry[iCurry].index == i {
+ if ok {
+ return nil, fmt.Errorf("label name %q is already curried", label)
+ }
+ newCurry = append(newCurry, oldCurry[iCurry])
+ iCurry++
+ } else {
+ if !ok {
+ continue // Label stays uncurried.
+ }
+ newCurry = append(newCurry, curriedLabelValue{i, val})
}
}
+ if l := len(oldCurry) + len(labels) - len(newCurry); l > 0 {
+ return nil, fmt.Errorf("%d unknown label(s) found during currying", l)
+ }
+
+ return &metricVec{
+ metricMap: m.metricMap,
+ curry: newCurry,
+ hashAdd: m.hashAdd,
+ hashAddByte: m.hashAddByte,
+ }, nil
}
-// GetMetricWithLabelValues returns the Metric for the given slice of label
-// values (same order as the VariableLabels in Desc). If that combination of
-// label values is accessed for the first time, a new Metric is created.
-//
-// It is possible to call this method without using the returned Metric to only
-// create the new Metric but leave it at its start value (e.g. a Summary or
-// Histogram without any observations). See also the SummaryVec example.
-//
-// Keeping the Metric for later use is possible (and should be considered if
-// performance is critical), but keep in mind that Reset, DeleteLabelValues and
-// Delete can be used to delete the Metric from the MetricVec. In that case, the
-// Metric will still exist, but it will not be exported anymore, even if a
-// Metric with the same label values is created later. See also the CounterVec
-// example.
-//
-// An error is returned if the number of label values is not the same as the
-// number of VariableLabels in Desc.
-//
-// Note that for more than one label value, this method is prone to mistakes
-// caused by an incorrect order of arguments. Consider GetMetricWith(Labels) as
-// an alternative to avoid that type of mistake. For higher label numbers, the
-// latter has a much more readable (albeit more verbose) syntax, but it comes
-// with a performance overhead (for creating and processing the Labels map).
-// See also the GaugeVec example.
-func (m *MetricVec) GetMetricWithLabelValues(lvs ...string) (Metric, error) {
+func (m *metricVec) getMetricWithLabelValues(lvs ...string) (Metric, error) {
h, err := m.hashLabelValues(lvs)
if err != nil {
return nil, err
}
- return m.getOrCreateMetricWithLabelValues(h, lvs), nil
+ return m.metricMap.getOrCreateMetricWithLabelValues(h, lvs, m.curry), nil
}
-// GetMetricWith returns the Metric for the given Labels map (the label names
-// must match those of the VariableLabels in Desc). If that label map is
-// accessed for the first time, a new Metric is created. Implications of
-// creating a Metric without using it and keeping the Metric for later use are
-// the same as for GetMetricWithLabelValues.
-//
-// An error is returned if the number and names of the Labels are inconsistent
-// with those of the VariableLabels in Desc.
-//
-// This method is used for the same purpose as
-// GetMetricWithLabelValues(...string). See there for pros and cons of the two
-// methods.
-func (m *MetricVec) GetMetricWith(labels Labels) (Metric, error) {
+func (m *metricVec) getMetricWith(labels Labels) (Metric, error) {
h, err := m.hashLabels(labels)
if err != nil {
return nil, err
}
- return m.getOrCreateMetricWithLabels(h, labels), nil
+ return m.metricMap.getOrCreateMetricWithLabels(h, labels, m.curry), nil
}
-// WithLabelValues works as GetMetricWithLabelValues, but panics if an error
-// occurs. The method allows neat syntax like:
-// httpReqs.WithLabelValues("404", "POST").Inc()
-func (m *MetricVec) WithLabelValues(lvs ...string) Metric {
- metric, err := m.GetMetricWithLabelValues(lvs...)
- if err != nil {
- panic(err)
+func (m *metricVec) hashLabelValues(vals []string) (uint64, error) {
+ if err := validateLabelValues(vals, len(m.desc.variableLabels)-len(m.curry)); err != nil {
+ return 0, err
+ }
+
+ var (
+ h = hashNew()
+ curry = m.curry
+ iVals, iCurry int
+ )
+ for i := 0; i < len(m.desc.variableLabels); i++ {
+ if iCurry < len(curry) && curry[iCurry].index == i {
+ h = m.hashAdd(h, curry[iCurry].value)
+ iCurry++
+ } else {
+ h = m.hashAdd(h, vals[iVals])
+ iVals++
+ }
+ h = m.hashAddByte(h, model.SeparatorByte)
}
- return metric
+ return h, nil
}
-// With works as GetMetricWith, but panics if an error occurs. The method allows
-// neat syntax like:
-// httpReqs.With(Labels{"status":"404", "method":"POST"}).Inc()
-func (m *MetricVec) With(labels Labels) Metric {
- metric, err := m.GetMetricWith(labels)
- if err != nil {
- panic(err)
+func (m *metricVec) hashLabels(labels Labels) (uint64, error) {
+ if err := validateValuesInLabels(labels, len(m.desc.variableLabels)-len(m.curry)); err != nil {
+ return 0, err
}
- return metric
+
+ var (
+ h = hashNew()
+ curry = m.curry
+ iCurry int
+ )
+ for i, label := range m.desc.variableLabels {
+ val, ok := labels[label]
+ if iCurry < len(curry) && curry[iCurry].index == i {
+ if ok {
+ return 0, fmt.Errorf("label name %q is already curried", label)
+ }
+ h = m.hashAdd(h, curry[iCurry].value)
+ iCurry++
+ } else {
+ if !ok {
+ return 0, fmt.Errorf("label name %q missing in label map", label)
+ }
+ h = m.hashAdd(h, val)
+ }
+ h = m.hashAddByte(h, model.SeparatorByte)
+ }
+ return h, nil
}
-// DeleteLabelValues removes the metric where the variable labels are the same
-// as those passed in as labels (same order as the VariableLabels in Desc). It
-// returns true if a metric was deleted.
-//
-// It is not an error if the number of label values is not the same as the
-// number of VariableLabels in Desc. However, such inconsistent label count can
-// never match an actual Metric, so the method will always return false in that
-// case.
-//
-// Note that for more than one label value, this method is prone to mistakes
-// caused by an incorrect order of arguments. Consider Delete(Labels) as an
-// alternative to avoid that type of mistake. For higher label numbers, the
-// latter has a much more readable (albeit more verbose) syntax, but it comes
-// with a performance overhead (for creating and processing the Labels map).
-// See also the CounterVec example.
-func (m *MetricVec) DeleteLabelValues(lvs ...string) bool {
- m.mtx.Lock()
- defer m.mtx.Unlock()
+// metricWithLabelValues provides the metric and its label values for
+// disambiguation on hash collision.
+type metricWithLabelValues struct {
+ values []string
+ metric Metric
+}
- h, err := m.hashLabelValues(lvs)
- if err != nil {
- return false
+// curriedLabelValue sets the curried value for a label at the given index.
+type curriedLabelValue struct {
+ index int
+ value string
+}
+
+// metricMap is a helper for metricVec and shared between differently curried
+// metricVecs.
+type metricMap struct {
+ mtx sync.RWMutex // Protects metrics.
+ metrics map[uint64][]metricWithLabelValues
+ desc *Desc
+ newMetric func(labelValues ...string) Metric
+}
+
+// Describe implements Collector. It will send exactly one Desc to the provided
+// channel.
+func (m *metricMap) Describe(ch chan<- *Desc) {
+ ch <- m.desc
+}
+
+// Collect implements Collector.
+func (m *metricMap) Collect(ch chan<- Metric) {
+ m.mtx.RLock()
+ defer m.mtx.RUnlock()
+
+ for _, metrics := range m.metrics {
+ for _, metric := range metrics {
+ ch <- metric.metric
+ }
}
- return m.deleteByHashWithLabelValues(h, lvs)
}
-// Delete deletes the metric where the variable labels are the same as those
-// passed in as labels. It returns true if a metric was deleted.
-//
-// It is not an error if the number and names of the Labels are inconsistent
-// with those of the VariableLabels in the Desc of the MetricVec. However, such
-// inconsistent Labels can never match an actual Metric, so the method will
-// always return false in that case.
-//
-// This method is used for the same purpose as DeleteLabelValues(...string). See
-// there for pros and cons of the two methods.
-func (m *MetricVec) Delete(labels Labels) bool {
+// Reset deletes all metrics in this vector.
+func (m *metricMap) Reset() {
m.mtx.Lock()
defer m.mtx.Unlock()
- h, err := m.hashLabels(labels)
- if err != nil {
- return false
+ for h := range m.metrics {
+ delete(m.metrics, h)
}
-
- return m.deleteByHashWithLabels(h, labels)
}
// deleteByHashWithLabelValues removes the metric from the hash bucket h. If
// there are multiple matches in the bucket, use lvs to select a metric and
// remove only that metric.
-func (m *MetricVec) deleteByHashWithLabelValues(h uint64, lvs []string) bool {
- metrics, ok := m.children[h]
+func (m *metricMap) deleteByHashWithLabelValues(
+ h uint64, lvs []string, curry []curriedLabelValue,
+) bool {
+ m.mtx.Lock()
+ defer m.mtx.Unlock()
+
+ metrics, ok := m.metrics[h]
if !ok {
return false
}
- i := m.findMetricWithLabelValues(metrics, lvs)
+ i := findMetricWithLabelValues(metrics, lvs, curry)
if i >= len(metrics) {
return false
}
if len(metrics) > 1 {
- m.children[h] = append(metrics[:i], metrics[i+1:]...)
+ m.metrics[h] = append(metrics[:i], metrics[i+1:]...)
} else {
- delete(m.children, h)
+ delete(m.metrics, h)
}
return true
}
@@ -221,69 +274,35 @@ func (m *MetricVec) deleteByHashWithLabelValues(h uint64, lvs []string) bool {
// deleteByHashWithLabels removes the metric from the hash bucket h. If there
// are multiple matches in the bucket, use lvs to select a metric and remove
// only that metric.
-func (m *MetricVec) deleteByHashWithLabels(h uint64, labels Labels) bool {
- metrics, ok := m.children[h]
+func (m *metricMap) deleteByHashWithLabels(
+ h uint64, labels Labels, curry []curriedLabelValue,
+) bool {
+ metrics, ok := m.metrics[h]
if !ok {
return false
}
- i := m.findMetricWithLabels(metrics, labels)
+ i := findMetricWithLabels(m.desc, metrics, labels, curry)
if i >= len(metrics) {
return false
}
if len(metrics) > 1 {
- m.children[h] = append(metrics[:i], metrics[i+1:]...)
+ m.metrics[h] = append(metrics[:i], metrics[i+1:]...)
} else {
- delete(m.children, h)
+ delete(m.metrics, h)
}
return true
}
-// Reset deletes all metrics in this vector.
-func (m *MetricVec) Reset() {
- m.mtx.Lock()
- defer m.mtx.Unlock()
-
- for h := range m.children {
- delete(m.children, h)
- }
-}
-
-func (m *MetricVec) hashLabelValues(vals []string) (uint64, error) {
- if len(vals) != len(m.desc.variableLabels) {
- return 0, errInconsistentCardinality
- }
- h := hashNew()
- for _, val := range vals {
- h = m.hashAdd(h, val)
- h = m.hashAddByte(h, model.SeparatorByte)
- }
- return h, nil
-}
-
-func (m *MetricVec) hashLabels(labels Labels) (uint64, error) {
- if len(labels) != len(m.desc.variableLabels) {
- return 0, errInconsistentCardinality
- }
- h := hashNew()
- for _, label := range m.desc.variableLabels {
- val, ok := labels[label]
- if !ok {
- return 0, fmt.Errorf("label name %q missing in label map", label)
- }
- h = m.hashAdd(h, val)
- h = m.hashAddByte(h, model.SeparatorByte)
- }
- return h, nil
-}
-
// getOrCreateMetricWithLabelValues retrieves the metric by hash and label value
// or creates it and returns the new one.
//
// This function holds the mutex.
-func (m *MetricVec) getOrCreateMetricWithLabelValues(hash uint64, lvs []string) Metric {
+func (m *metricMap) getOrCreateMetricWithLabelValues(
+ hash uint64, lvs []string, curry []curriedLabelValue,
+) Metric {
m.mtx.RLock()
- metric, ok := m.getMetricWithLabelValues(hash, lvs)
+ metric, ok := m.getMetricWithHashAndLabelValues(hash, lvs, curry)
m.mtx.RUnlock()
if ok {
return metric
@@ -291,13 +310,11 @@ func (m *MetricVec) getOrCreateMetricWithLabelValues(hash uint64, lvs []string)
m.mtx.Lock()
defer m.mtx.Unlock()
- metric, ok = m.getMetricWithLabelValues(hash, lvs)
+ metric, ok = m.getMetricWithHashAndLabelValues(hash, lvs, curry)
if !ok {
- // Copy to avoid allocation in case wo don't go down this code path.
- copiedLVs := make([]string, len(lvs))
- copy(copiedLVs, lvs)
- metric = m.newMetric(copiedLVs...)
- m.children[hash] = append(m.children[hash], metricWithLabelValues{values: copiedLVs, metric: metric})
+ inlinedLVs := inlineLabelValues(lvs, curry)
+ metric = m.newMetric(inlinedLVs...)
+ m.metrics[hash] = append(m.metrics[hash], metricWithLabelValues{values: inlinedLVs, metric: metric})
}
return metric
}
@@ -306,9 +323,11 @@ func (m *MetricVec) getOrCreateMetricWithLabelValues(hash uint64, lvs []string)
// or creates it and returns the new one.
//
// This function holds the mutex.
-func (m *MetricVec) getOrCreateMetricWithLabels(hash uint64, labels Labels) Metric {
+func (m *metricMap) getOrCreateMetricWithLabels(
+ hash uint64, labels Labels, curry []curriedLabelValue,
+) Metric {
m.mtx.RLock()
- metric, ok := m.getMetricWithLabels(hash, labels)
+ metric, ok := m.getMetricWithHashAndLabels(hash, labels, curry)
m.mtx.RUnlock()
if ok {
return metric
@@ -316,33 +335,37 @@ func (m *MetricVec) getOrCreateMetricWithLabels(hash uint64, labels Labels) Metr
m.mtx.Lock()
defer m.mtx.Unlock()
- metric, ok = m.getMetricWithLabels(hash, labels)
+ metric, ok = m.getMetricWithHashAndLabels(hash, labels, curry)
if !ok {
- lvs := m.extractLabelValues(labels)
+ lvs := extractLabelValues(m.desc, labels, curry)
metric = m.newMetric(lvs...)
- m.children[hash] = append(m.children[hash], metricWithLabelValues{values: lvs, metric: metric})
+ m.metrics[hash] = append(m.metrics[hash], metricWithLabelValues{values: lvs, metric: metric})
}
return metric
}
-// getMetricWithLabelValues gets a metric while handling possible collisions in
-// the hash space. Must be called while holding read mutex.
-func (m *MetricVec) getMetricWithLabelValues(h uint64, lvs []string) (Metric, bool) {
- metrics, ok := m.children[h]
+// getMetricWithHashAndLabelValues gets a metric while handling possible
+// collisions in the hash space. Must be called while holding the read mutex.
+func (m *metricMap) getMetricWithHashAndLabelValues(
+ h uint64, lvs []string, curry []curriedLabelValue,
+) (Metric, bool) {
+ metrics, ok := m.metrics[h]
if ok {
- if i := m.findMetricWithLabelValues(metrics, lvs); i < len(metrics) {
+ if i := findMetricWithLabelValues(metrics, lvs, curry); i < len(metrics) {
return metrics[i].metric, true
}
}
return nil, false
}
-// getMetricWithLabels gets a metric while handling possible collisions in
+// getMetricWithHashAndLabels gets a metric while handling possible collisions in
// the hash space. Must be called while holding read mutex.
-func (m *MetricVec) getMetricWithLabels(h uint64, labels Labels) (Metric, bool) {
- metrics, ok := m.children[h]
+func (m *metricMap) getMetricWithHashAndLabels(
+ h uint64, labels Labels, curry []curriedLabelValue,
+) (Metric, bool) {
+ metrics, ok := m.metrics[h]
if ok {
- if i := m.findMetricWithLabels(metrics, labels); i < len(metrics) {
+ if i := findMetricWithLabels(m.desc, metrics, labels, curry); i < len(metrics) {
return metrics[i].metric, true
}
}
@@ -351,9 +374,11 @@ func (m *MetricVec) getMetricWithLabels(h uint64, labels Labels) (Metric, bool)
// findMetricWithLabelValues returns the index of the matching metric or
// len(metrics) if not found.
-func (m *MetricVec) findMetricWithLabelValues(metrics []metricWithLabelValues, lvs []string) int {
+func findMetricWithLabelValues(
+ metrics []metricWithLabelValues, lvs []string, curry []curriedLabelValue,
+) int {
for i, metric := range metrics {
- if m.matchLabelValues(metric.values, lvs) {
+ if matchLabelValues(metric.values, lvs, curry) {
return i
}
}
@@ -362,32 +387,51 @@ func (m *MetricVec) findMetricWithLabelValues(metrics []metricWithLabelValues, l
// findMetricWithLabels returns the index of the matching metric or len(metrics)
// if not found.
-func (m *MetricVec) findMetricWithLabels(metrics []metricWithLabelValues, labels Labels) int {
+func findMetricWithLabels(
+ desc *Desc, metrics []metricWithLabelValues, labels Labels, curry []curriedLabelValue,
+) int {
for i, metric := range metrics {
- if m.matchLabels(metric.values, labels) {
+ if matchLabels(desc, metric.values, labels, curry) {
return i
}
}
return len(metrics)
}
-func (m *MetricVec) matchLabelValues(values []string, lvs []string) bool {
- if len(values) != len(lvs) {
+func matchLabelValues(values []string, lvs []string, curry []curriedLabelValue) bool {
+ if len(values) != len(lvs)+len(curry) {
return false
}
+ var iLVs, iCurry int
for i, v := range values {
- if v != lvs[i] {
+ if iCurry < len(curry) && curry[iCurry].index == i {
+ if v != curry[iCurry].value {
+ return false
+ }
+ iCurry++
+ continue
+ }
+ if v != lvs[iLVs] {
return false
}
+ iLVs++
}
return true
}
-func (m *MetricVec) matchLabels(values []string, labels Labels) bool {
- if len(labels) != len(values) {
+func matchLabels(desc *Desc, values []string, labels Labels, curry []curriedLabelValue) bool {
+ if len(values) != len(labels)+len(curry) {
return false
}
- for i, k := range m.desc.variableLabels {
+ iCurry := 0
+ for i, k := range desc.variableLabels {
+ if iCurry < len(curry) && curry[iCurry].index == i {
+ if values[i] != curry[iCurry].value {
+ return false
+ }
+ iCurry++
+ continue
+ }
if values[i] != labels[k] {
return false
}
@@ -395,10 +439,31 @@ func (m *MetricVec) matchLabels(values []string, labels Labels) bool {
return true
}
-func (m *MetricVec) extractLabelValues(labels Labels) []string {
- labelValues := make([]string, len(labels))
- for i, k := range m.desc.variableLabels {
+func extractLabelValues(desc *Desc, labels Labels, curry []curriedLabelValue) []string {
+ labelValues := make([]string, len(labels)+len(curry))
+ iCurry := 0
+ for i, k := range desc.variableLabels {
+ if iCurry < len(curry) && curry[iCurry].index == i {
+ labelValues[i] = curry[iCurry].value
+ iCurry++
+ continue
+ }
labelValues[i] = labels[k]
}
return labelValues
}
+
+func inlineLabelValues(lvs []string, curry []curriedLabelValue) []string {
+ labelValues := make([]string, len(lvs)+len(curry))
+ var iCurry, iLVs int
+ for i := range labelValues {
+ if iCurry < len(curry) && curry[iCurry].index == i {
+ labelValues[i] = curry[iCurry].value
+ iCurry++
+ continue
+ }
+ labelValues[i] = lvs[iLVs]
+ iLVs++
+ }
+ return labelValues
+}
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/vec_test.go b/vendor/github.com/prometheus/client_golang/prometheus/vec_test.go
index 445a6b39f..bd18a9f4e 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/vec_test.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/vec_test.go
@@ -21,8 +21,8 @@ import (
)
func TestDelete(t *testing.T) {
- vec := NewUntypedVec(
- UntypedOpts{
+ vec := NewGaugeVec(
+ GaugeOpts{
Name: "test",
Help: "helpless",
},
@@ -32,8 +32,8 @@ func TestDelete(t *testing.T) {
}
func TestDeleteWithCollisions(t *testing.T) {
- vec := NewUntypedVec(
- UntypedOpts{
+ vec := NewGaugeVec(
+ GaugeOpts{
Name: "test",
Help: "helpless",
},
@@ -44,12 +44,12 @@ func TestDeleteWithCollisions(t *testing.T) {
testDelete(t, vec)
}
-func testDelete(t *testing.T, vec *UntypedVec) {
+func testDelete(t *testing.T, vec *GaugeVec) {
if got, want := vec.Delete(Labels{"l1": "v1", "l2": "v2"}), false; got != want {
t.Errorf("got %v, want %v", got, want)
}
- vec.With(Labels{"l1": "v1", "l2": "v2"}).(Untyped).Set(42)
+ vec.With(Labels{"l1": "v1", "l2": "v2"}).(Gauge).Set(42)
if got, want := vec.Delete(Labels{"l1": "v1", "l2": "v2"}), true; got != want {
t.Errorf("got %v, want %v", got, want)
}
@@ -57,7 +57,7 @@ func testDelete(t *testing.T, vec *UntypedVec) {
t.Errorf("got %v, want %v", got, want)
}
- vec.With(Labels{"l1": "v1", "l2": "v2"}).(Untyped).Set(42)
+ vec.With(Labels{"l1": "v1", "l2": "v2"}).(Gauge).Set(42)
if got, want := vec.Delete(Labels{"l2": "v2", "l1": "v1"}), true; got != want {
t.Errorf("got %v, want %v", got, want)
}
@@ -65,7 +65,7 @@ func testDelete(t *testing.T, vec *UntypedVec) {
t.Errorf("got %v, want %v", got, want)
}
- vec.With(Labels{"l1": "v1", "l2": "v2"}).(Untyped).Set(42)
+ vec.With(Labels{"l1": "v1", "l2": "v2"}).(Gauge).Set(42)
if got, want := vec.Delete(Labels{"l2": "v1", "l1": "v2"}), false; got != want {
t.Errorf("got %v, want %v", got, want)
}
@@ -75,8 +75,8 @@ func testDelete(t *testing.T, vec *UntypedVec) {
}
func TestDeleteLabelValues(t *testing.T) {
- vec := NewUntypedVec(
- UntypedOpts{
+ vec := NewGaugeVec(
+ GaugeOpts{
Name: "test",
Help: "helpless",
},
@@ -86,8 +86,8 @@ func TestDeleteLabelValues(t *testing.T) {
}
func TestDeleteLabelValuesWithCollisions(t *testing.T) {
- vec := NewUntypedVec(
- UntypedOpts{
+ vec := NewGaugeVec(
+ GaugeOpts{
Name: "test",
Help: "helpless",
},
@@ -98,13 +98,13 @@ func TestDeleteLabelValuesWithCollisions(t *testing.T) {
testDeleteLabelValues(t, vec)
}
-func testDeleteLabelValues(t *testing.T, vec *UntypedVec) {
+func testDeleteLabelValues(t *testing.T, vec *GaugeVec) {
if got, want := vec.DeleteLabelValues("v1", "v2"), false; got != want {
t.Errorf("got %v, want %v", got, want)
}
- vec.With(Labels{"l1": "v1", "l2": "v2"}).(Untyped).Set(42)
- vec.With(Labels{"l1": "v1", "l2": "v3"}).(Untyped).Set(42) // Add junk data for collision.
+ vec.With(Labels{"l1": "v1", "l2": "v2"}).(Gauge).Set(42)
+ vec.With(Labels{"l1": "v1", "l2": "v3"}).(Gauge).Set(42) // Add junk data for collision.
if got, want := vec.DeleteLabelValues("v1", "v2"), true; got != want {
t.Errorf("got %v, want %v", got, want)
}
@@ -115,7 +115,7 @@ func testDeleteLabelValues(t *testing.T, vec *UntypedVec) {
t.Errorf("got %v, want %v", got, want)
}
- vec.With(Labels{"l1": "v1", "l2": "v2"}).(Untyped).Set(42)
+ vec.With(Labels{"l1": "v1", "l2": "v2"}).(Gauge).Set(42)
// Delete out of order.
if got, want := vec.DeleteLabelValues("v2", "v1"), false; got != want {
t.Errorf("got %v, want %v", got, want)
@@ -126,8 +126,8 @@ func testDeleteLabelValues(t *testing.T, vec *UntypedVec) {
}
func TestMetricVec(t *testing.T) {
- vec := NewUntypedVec(
- UntypedOpts{
+ vec := NewGaugeVec(
+ GaugeOpts{
Name: "test",
Help: "helpless",
},
@@ -137,8 +137,8 @@ func TestMetricVec(t *testing.T) {
}
func TestMetricVecWithCollisions(t *testing.T) {
- vec := NewUntypedVec(
- UntypedOpts{
+ vec := NewGaugeVec(
+ GaugeOpts{
Name: "test",
Help: "helpless",
},
@@ -149,7 +149,7 @@ func TestMetricVecWithCollisions(t *testing.T) {
testMetricVec(t, vec)
}
-func testMetricVec(t *testing.T, vec *UntypedVec) {
+func testMetricVec(t *testing.T, vec *GaugeVec) {
vec.Reset() // Actually test Reset now!
var pair [2]string
@@ -162,11 +162,11 @@ func testMetricVec(t *testing.T, vec *UntypedVec) {
vec.WithLabelValues(pair[0], pair[1]).Inc()
expected[[2]string{"v1", "v2"}]++
- vec.WithLabelValues("v1", "v2").(Untyped).Inc()
+ vec.WithLabelValues("v1", "v2").(Gauge).Inc()
}
var total int
- for _, metrics := range vec.children {
+ for _, metrics := range vec.metricMap.metrics {
for _, metric := range metrics {
total++
copy(pair[:], metric.values)
@@ -175,7 +175,7 @@ func testMetricVec(t *testing.T, vec *UntypedVec) {
if err := metric.metric.Write(&metricOut); err != nil {
t.Fatal(err)
}
- actual := *metricOut.Untyped.Value
+ actual := *metricOut.Gauge.Value
var actualPair [2]string
for i, label := range metricOut.Label {
@@ -201,7 +201,7 @@ func testMetricVec(t *testing.T, vec *UntypedVec) {
vec.Reset()
- if len(vec.children) > 0 {
+ if len(vec.metricMap.metrics) > 0 {
t.Fatalf("reset failed")
}
}
@@ -239,10 +239,233 @@ func TestCounterVecEndToEndWithCollision(t *testing.T) {
}
}
+func TestCurryVec(t *testing.T) {
+ vec := NewCounterVec(
+ CounterOpts{
+ Name: "test",
+ Help: "helpless",
+ },
+ []string{"one", "two", "three"},
+ )
+ testCurryVec(t, vec)
+}
+
+func TestCurryVecWithCollisions(t *testing.T) {
+ vec := NewCounterVec(
+ CounterOpts{
+ Name: "test",
+ Help: "helpless",
+ },
+ []string{"one", "two", "three"},
+ )
+ vec.hashAdd = func(h uint64, s string) uint64 { return 1 }
+ vec.hashAddByte = func(h uint64, b byte) uint64 { return 1 }
+ testCurryVec(t, vec)
+}
+
+func testCurryVec(t *testing.T, vec *CounterVec) {
+
+ assertMetrics := func(t *testing.T) {
+ n := 0
+ for _, m := range vec.metricMap.metrics {
+ n += len(m)
+ }
+ if n != 2 {
+ t.Error("expected two metrics, got", n)
+ }
+ m := &dto.Metric{}
+ c1, err := vec.GetMetricWithLabelValues("1", "2", "3")
+ if err != nil {
+ t.Fatal("unexpected error getting metric:", err)
+ }
+ c1.Write(m)
+ if want, got := 1., m.GetCounter().GetValue(); want != got {
+ t.Errorf("want %f as counter value, got %f", want, got)
+ }
+ m.Reset()
+ c2, err := vec.GetMetricWithLabelValues("11", "22", "33")
+ if err != nil {
+ t.Fatal("unexpected error getting metric:", err)
+ }
+ c2.Write(m)
+ if want, got := 1., m.GetCounter().GetValue(); want != got {
+ t.Errorf("want %f as counter value, got %f", want, got)
+ }
+ }
+
+ assertNoMetric := func(t *testing.T) {
+ if n := len(vec.metricMap.metrics); n != 0 {
+ t.Error("expected no metrics, got", n)
+ }
+ }
+
+ t.Run("zero labels", func(t *testing.T) {
+ c1 := vec.MustCurryWith(nil)
+ c2 := vec.MustCurryWith(nil)
+ c1.WithLabelValues("1", "2", "3").Inc()
+ c2.With(Labels{"one": "11", "two": "22", "three": "33"}).Inc()
+ assertMetrics(t)
+ if !c1.Delete(Labels{"one": "1", "two": "2", "three": "3"}) {
+ t.Error("deletion failed")
+ }
+ if !c2.DeleteLabelValues("11", "22", "33") {
+ t.Error("deletion failed")
+ }
+ assertNoMetric(t)
+ })
+ t.Run("first label", func(t *testing.T) {
+ c1 := vec.MustCurryWith(Labels{"one": "1"})
+ c2 := vec.MustCurryWith(Labels{"one": "11"})
+ c1.WithLabelValues("2", "3").Inc()
+ c2.With(Labels{"two": "22", "three": "33"}).Inc()
+ assertMetrics(t)
+ if c1.Delete(Labels{"two": "22", "three": "33"}) {
+ t.Error("deletion unexpectedly succeeded")
+ }
+ if c2.DeleteLabelValues("2", "3") {
+ t.Error("deletion unexpectedly succeeded")
+ }
+ if !c1.Delete(Labels{"two": "2", "three": "3"}) {
+ t.Error("deletion failed")
+ }
+ if !c2.DeleteLabelValues("22", "33") {
+ t.Error("deletion failed")
+ }
+ assertNoMetric(t)
+ })
+ t.Run("middle label", func(t *testing.T) {
+ c1 := vec.MustCurryWith(Labels{"two": "2"})
+ c2 := vec.MustCurryWith(Labels{"two": "22"})
+ c1.WithLabelValues("1", "3").Inc()
+ c2.With(Labels{"one": "11", "three": "33"}).Inc()
+ assertMetrics(t)
+ if c1.Delete(Labels{"one": "11", "three": "33"}) {
+ t.Error("deletion unexpectedly succeeded")
+ }
+ if c2.DeleteLabelValues("1", "3") {
+ t.Error("deletion unexpectedly succeeded")
+ }
+ if !c1.Delete(Labels{"one": "1", "three": "3"}) {
+ t.Error("deletion failed")
+ }
+ if !c2.DeleteLabelValues("11", "33") {
+ t.Error("deletion failed")
+ }
+ assertNoMetric(t)
+ })
+ t.Run("last label", func(t *testing.T) {
+ c1 := vec.MustCurryWith(Labels{"three": "3"})
+ c2 := vec.MustCurryWith(Labels{"three": "33"})
+ c1.WithLabelValues("1", "2").Inc()
+ c2.With(Labels{"one": "11", "two": "22"}).Inc()
+ assertMetrics(t)
+ if c1.Delete(Labels{"two": "22", "one": "11"}) {
+ t.Error("deletion unexpectedly succeeded")
+ }
+ if c2.DeleteLabelValues("1", "2") {
+ t.Error("deletion unexpectedly succeeded")
+ }
+ if !c1.Delete(Labels{"two": "2", "one": "1"}) {
+ t.Error("deletion failed")
+ }
+ if !c2.DeleteLabelValues("11", "22") {
+ t.Error("deletion failed")
+ }
+ assertNoMetric(t)
+ })
+ t.Run("two labels", func(t *testing.T) {
+ c1 := vec.MustCurryWith(Labels{"three": "3", "one": "1"})
+ c2 := vec.MustCurryWith(Labels{"three": "33", "one": "11"})
+ c1.WithLabelValues("2").Inc()
+ c2.With(Labels{"two": "22"}).Inc()
+ assertMetrics(t)
+ if c1.Delete(Labels{"two": "22"}) {
+ t.Error("deletion unexpectedly succeeded")
+ }
+ if c2.DeleteLabelValues("2") {
+ t.Error("deletion unexpectedly succeeded")
+ }
+ if !c1.Delete(Labels{"two": "2"}) {
+ t.Error("deletion failed")
+ }
+ if !c2.DeleteLabelValues("22") {
+ t.Error("deletion failed")
+ }
+ assertNoMetric(t)
+ })
+ t.Run("all labels", func(t *testing.T) {
+ c1 := vec.MustCurryWith(Labels{"three": "3", "two": "2", "one": "1"})
+ c2 := vec.MustCurryWith(Labels{"three": "33", "one": "11", "two": "22"})
+ c1.WithLabelValues().Inc()
+ c2.With(nil).Inc()
+ assertMetrics(t)
+ if !c1.Delete(Labels{}) {
+ t.Error("deletion failed")
+ }
+ if !c2.DeleteLabelValues() {
+ t.Error("deletion failed")
+ }
+ assertNoMetric(t)
+ })
+ t.Run("double curry", func(t *testing.T) {
+ c1 := vec.MustCurryWith(Labels{"three": "3"}).MustCurryWith(Labels{"one": "1"})
+ c2 := vec.MustCurryWith(Labels{"three": "33"}).MustCurryWith(Labels{"one": "11"})
+ c1.WithLabelValues("2").Inc()
+ c2.With(Labels{"two": "22"}).Inc()
+ assertMetrics(t)
+ if c1.Delete(Labels{"two": "22"}) {
+ t.Error("deletion unexpectedly succeeded")
+ }
+ if c2.DeleteLabelValues("2") {
+ t.Error("deletion unexpectedly succeeded")
+ }
+ if !c1.Delete(Labels{"two": "2"}) {
+ t.Error("deletion failed")
+ }
+ if !c2.DeleteLabelValues("22") {
+ t.Error("deletion failed")
+ }
+ assertNoMetric(t)
+ })
+ t.Run("use already curried label", func(t *testing.T) {
+ c1 := vec.MustCurryWith(Labels{"three": "3"})
+ if _, err := c1.GetMetricWithLabelValues("1", "2", "3"); err == nil {
+ t.Error("expected error when using already curried label")
+ }
+ if _, err := c1.GetMetricWith(Labels{"one": "1", "two": "2", "three": "3"}); err == nil {
+ t.Error("expected error when using already curried label")
+ }
+ assertNoMetric(t)
+ c1.WithLabelValues("1", "2").Inc()
+ if c1.Delete(Labels{"one": "1", "two": "2", "three": "3"}) {
+ t.Error("deletion unexpectedly succeeded")
+ }
+ if !c1.Delete(Labels{"one": "1", "two": "2"}) {
+ t.Error("deletion failed")
+ }
+ assertNoMetric(t)
+ })
+ t.Run("curry already curried label", func(t *testing.T) {
+ if _, err := vec.MustCurryWith(Labels{"three": "3"}).CurryWith(Labels{"three": "33"}); err == nil {
+ t.Error("currying unexpectedly succeeded")
+ } else if err.Error() != `label name "three" is already curried` {
+ t.Error("currying returned unexpected error:", err)
+ }
+
+ })
+ t.Run("unknown label", func(t *testing.T) {
+ if _, err := vec.CurryWith(Labels{"foo": "bar"}); err == nil {
+ t.Error("currying unexpectedly succeeded")
+ } else if err.Error() != "1 unknown label(s) found during currying" {
+ t.Error("currying returned unexpected error:", err)
+ }
+ })
+}
+
func BenchmarkMetricVecWithLabelValuesBasic(b *testing.B) {
benchmarkMetricVecWithLabelValues(b, map[string][]string{
- "l1": []string{"onevalue"},
- "l2": []string{"twovalue"},
+ "l1": {"onevalue"},
+ "l2": {"twovalue"},
})
}
@@ -290,8 +513,8 @@ func benchmarkMetricVecWithLabelValues(b *testing.B, labels map[string][]string)
}
values := make([]string, len(labels)) // Value cache for permutations.
- vec := NewUntypedVec(
- UntypedOpts{
+ vec := NewGaugeVec(
+ GaugeOpts{
Name: "test",
Help: "helpless",
},