diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 078e4b4b17..dfc8c581e2 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -20,33 +20,33 @@ }, { "ImportPath": "github.com/prometheus/client_golang/_vendor/goautoneg", - "Comment": "0.1.0-24-g4627d59", - "Rev": "4627d59e8a09c330c5ccfe7414baca28d8df847d" + "Comment": "0.1.0-37-g54238de", + "Rev": "54238dea8fa4dfaa411b8c9ef0680ba441fb7b1c" }, { "ImportPath": "github.com/prometheus/client_golang/_vendor/perks/quantile", - "Comment": "0.1.0-24-g4627d59", - "Rev": "4627d59e8a09c330c5ccfe7414baca28d8df847d" + "Comment": "0.1.0-37-g54238de", + "Rev": "54238dea8fa4dfaa411b8c9ef0680ba441fb7b1c" }, { "ImportPath": "github.com/prometheus/client_golang/extraction", - "Comment": "0.1.0-24-g4627d59", - "Rev": "4627d59e8a09c330c5ccfe7414baca28d8df847d" + "Comment": "0.1.0-37-g54238de", + "Rev": "54238dea8fa4dfaa411b8c9ef0680ba441fb7b1c" }, { "ImportPath": "github.com/prometheus/client_golang/model", - "Comment": "0.1.0-24-g4627d59", - "Rev": "4627d59e8a09c330c5ccfe7414baca28d8df847d" + "Comment": "0.1.0-37-g54238de", + "Rev": "54238dea8fa4dfaa411b8c9ef0680ba441fb7b1c" }, { "ImportPath": "github.com/prometheus/client_golang/prometheus", - "Comment": "0.1.0-24-g4627d59", - "Rev": "4627d59e8a09c330c5ccfe7414baca28d8df847d" + "Comment": "0.1.0-37-g54238de", + "Rev": "54238dea8fa4dfaa411b8c9ef0680ba441fb7b1c" }, { "ImportPath": "github.com/prometheus/client_golang/text", - "Comment": "0.1.0-24-g4627d59", - "Rev": "4627d59e8a09c330c5ccfe7414baca28d8df847d" + "Comment": "0.1.0-37-g54238de", + "Rev": "54238dea8fa4dfaa411b8c9ef0680ba441fb7b1c" }, { "ImportPath": "github.com/prometheus/client_model/go", diff --git a/Godeps/_workspace/src/github.com/prometheus/client_golang/extraction/metricfamilyprocessor.go b/Godeps/_workspace/src/github.com/prometheus/client_golang/extraction/metricfamilyprocessor.go index 5ed5343484..5edb49c242 100644 --- a/Godeps/_workspace/src/github.com/prometheus/client_golang/extraction/metricfamilyprocessor.go +++ b/Godeps/_workspace/src/github.com/prometheus/client_golang/extraction/metricfamilyprocessor.go @@ -16,6 +16,7 @@ package extraction import ( "fmt" "io" + "math" dto "github.com/prometheus/client_model/go" @@ -85,7 +86,10 @@ func extractCounter(out Ingester, o *ProcessOptions, f *dto.MetricFamily) error continue } - sample := new(model.Sample) + sample := &model.Sample{ + Metric: model.Metric{}, + Value: model.SampleValue(m.Counter.GetValue()), + } samples = append(samples, sample) if m.TimestampMs != nil { @@ -93,16 +97,12 @@ func extractCounter(out Ingester, o *ProcessOptions, f *dto.MetricFamily) error } else { sample.Timestamp = o.Timestamp } - sample.Metric = model.Metric{} - metric := sample.Metric + metric := sample.Metric for _, p := range m.Label { metric[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue()) } - metric[model.MetricNameLabel] = model.LabelValue(f.GetName()) - - sample.Value = model.SampleValue(m.Counter.GetValue()) } return out.Ingest(samples) @@ -116,7 +116,10 @@ func extractGauge(out Ingester, o *ProcessOptions, f *dto.MetricFamily) error { continue } - sample := new(model.Sample) + sample := &model.Sample{ + Metric: model.Metric{}, + Value: model.SampleValue(m.Gauge.GetValue()), + } samples = append(samples, sample) if m.TimestampMs != nil { @@ -124,16 +127,12 @@ func extractGauge(out Ingester, o *ProcessOptions, f *dto.MetricFamily) error { } else { sample.Timestamp = o.Timestamp } - sample.Metric = model.Metric{} - metric := sample.Metric + metric := sample.Metric for _, p := range m.Label { metric[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue()) } - metric[model.MetricNameLabel] = model.LabelValue(f.GetName()) - - sample.Value = model.SampleValue(m.Gauge.GetValue()) } return out.Ingest(samples) @@ -153,48 +152,50 @@ func extractSummary(out Ingester, o *ProcessOptions, f *dto.MetricFamily) error } for _, q := range m.Summary.Quantile { - sample := new(model.Sample) + sample := &model.Sample{ + Metric: model.Metric{}, + Value: model.SampleValue(q.GetValue()), + Timestamp: timestamp, + } samples = append(samples, sample) - sample.Timestamp = timestamp - sample.Metric = model.Metric{} metric := sample.Metric - for _, p := range m.Label { metric[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue()) } // BUG(matt): Update other names to "quantile". - metric[model.LabelName("quantile")] = model.LabelValue(fmt.Sprint(q.GetQuantile())) - + metric[model.LabelName(model.QuantileLabel)] = model.LabelValue(fmt.Sprint(q.GetQuantile())) metric[model.MetricNameLabel] = model.LabelValue(f.GetName()) - - sample.Value = model.SampleValue(q.GetValue()) } if m.Summary.SampleSum != nil { - sum := new(model.Sample) - sum.Timestamp = timestamp - metric := model.Metric{} + sum := &model.Sample{ + Metric: model.Metric{}, + Value: model.SampleValue(m.Summary.GetSampleSum()), + Timestamp: timestamp, + } + samples = append(samples, sum) + + metric := sum.Metric for _, p := range m.Label { metric[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue()) } metric[model.MetricNameLabel] = model.LabelValue(f.GetName() + "_sum") - sum.Metric = metric - sum.Value = model.SampleValue(m.Summary.GetSampleSum()) - samples = append(samples, sum) } if m.Summary.SampleCount != nil { - count := new(model.Sample) - count.Timestamp = timestamp - metric := model.Metric{} + count := &model.Sample{ + Metric: model.Metric{}, + Value: model.SampleValue(m.Summary.GetSampleCount()), + Timestamp: timestamp, + } + samples = append(samples, count) + + metric := count.Metric for _, p := range m.Label { metric[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue()) } metric[model.MetricNameLabel] = model.LabelValue(f.GetName() + "_count") - count.Metric = metric - count.Value = model.SampleValue(m.Summary.GetSampleCount()) - samples = append(samples, count) } } @@ -209,7 +210,10 @@ func extractUntyped(out Ingester, o *ProcessOptions, f *dto.MetricFamily) error continue } - sample := new(model.Sample) + sample := &model.Sample{ + Metric: model.Metric{}, + Value: model.SampleValue(m.Untyped.GetValue()), + } samples = append(samples, sample) if m.TimestampMs != nil { @@ -217,16 +221,12 @@ func extractUntyped(out Ingester, o *ProcessOptions, f *dto.MetricFamily) error } else { sample.Timestamp = o.Timestamp } - sample.Metric = model.Metric{} - metric := sample.Metric + metric := sample.Metric for _, p := range m.Label { metric[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue()) } - metric[model.MetricNameLabel] = model.LabelValue(f.GetName()) - - sample.Value = model.SampleValue(m.Untyped.GetValue()) } return out.Ingest(samples) @@ -245,49 +245,72 @@ func extractHistogram(out Ingester, o *ProcessOptions, f *dto.MetricFamily) erro timestamp = model.TimestampFromUnixNano(*m.TimestampMs * 1000000) } + infSeen := false + for _, q := range m.Histogram.Bucket { - sample := new(model.Sample) + sample := &model.Sample{ + Metric: model.Metric{}, + Value: model.SampleValue(q.GetCumulativeCount()), + Timestamp: timestamp, + } samples = append(samples, sample) - sample.Timestamp = timestamp - sample.Metric = model.Metric{} metric := sample.Metric - for _, p := range m.Label { metric[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue()) } - metric[model.LabelName("le")] = model.LabelValue(fmt.Sprint(q.GetUpperBound())) - + metric[model.LabelName(model.BucketLabel)] = model.LabelValue(fmt.Sprint(q.GetUpperBound())) metric[model.MetricNameLabel] = model.LabelValue(f.GetName() + "_bucket") - sample.Value = model.SampleValue(q.GetCumulativeCount()) + if math.IsInf(q.GetUpperBound(), +1) { + infSeen = true + } } - // TODO: If +Inf bucket is missing, add it. if m.Histogram.SampleSum != nil { - sum := new(model.Sample) - sum.Timestamp = timestamp - metric := model.Metric{} + sum := &model.Sample{ + Metric: model.Metric{}, + Value: model.SampleValue(m.Histogram.GetSampleSum()), + Timestamp: timestamp, + } + samples = append(samples, sum) + + metric := sum.Metric for _, p := range m.Label { metric[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue()) } metric[model.MetricNameLabel] = model.LabelValue(f.GetName() + "_sum") - sum.Metric = metric - sum.Value = model.SampleValue(m.Histogram.GetSampleSum()) - samples = append(samples, sum) } if m.Histogram.SampleCount != nil { - count := new(model.Sample) - count.Timestamp = timestamp - metric := model.Metric{} + count := &model.Sample{ + Metric: model.Metric{}, + Value: model.SampleValue(m.Histogram.GetSampleCount()), + Timestamp: timestamp, + } + samples = append(samples, count) + + metric := count.Metric for _, p := range m.Label { metric[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue()) } metric[model.MetricNameLabel] = model.LabelValue(f.GetName() + "_count") - count.Metric = metric - count.Value = model.SampleValue(m.Histogram.GetSampleCount()) - samples = append(samples, count) + + if !infSeen { + infBucket := &model.Sample{ + Metric: model.Metric{}, + Value: count.Value, + Timestamp: timestamp, + } + samples = append(samples, infBucket) + + metric := infBucket.Metric + for _, p := range m.Label { + metric[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue()) + } + metric[model.LabelName(model.BucketLabel)] = model.LabelValue("+Inf") + metric[model.MetricNameLabel] = model.LabelValue(f.GetName() + "_bucket") + } } } diff --git a/Godeps/_workspace/src/github.com/prometheus/client_golang/model/labelname.go b/Godeps/_workspace/src/github.com/prometheus/client_golang/model/labelname.go index 047e756555..75b2e79dae 100644 --- a/Godeps/_workspace/src/github.com/prometheus/client_golang/model/labelname.go +++ b/Godeps/_workspace/src/github.com/prometheus/client_golang/model/labelname.go @@ -33,6 +33,14 @@ const ( // JobLabel is the label name indicating the job from which a timeseries // was scraped. JobLabel LabelName = "job" + + // BucketLabel is used for the label that defines the upper bound of a + // bucket of a histogram ("le" -> "less or equal"). + BucketLabel = "le" + + // QuantileLabel is used for the label that defines the quantile in a + // summary. + QuantileLabel = "quantile" ) // A LabelName is a key for a LabelSet or Metric. It has a value associated diff --git a/Godeps/_workspace/src/github.com/prometheus/client_golang/prometheus/benchmark_test.go b/Godeps/_workspace/src/github.com/prometheus/client_golang/prometheus/benchmark_test.go index d43a857e63..6ae7333fcc 100644 --- a/Godeps/_workspace/src/github.com/prometheus/client_golang/prometheus/benchmark_test.go +++ b/Godeps/_workspace/src/github.com/prometheus/client_golang/prometheus/benchmark_test.go @@ -129,3 +129,31 @@ func BenchmarkSummaryNoLabels(b *testing.B) { m.Observe(3.1415) } } + +func BenchmarkHistogramWithLabelValues(b *testing.B) { + m := NewHistogramVec( + HistogramOpts{ + Name: "benchmark_histogram", + Help: "A histogram to benchmark it.", + }, + []string{"one", "two", "three"}, + ) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + m.WithLabelValues("eins", "zwei", "drei").Observe(3.1415) + } +} + +func BenchmarkHistogramNoLabels(b *testing.B) { + m := NewHistogram(HistogramOpts{ + Name: "benchmark_histogram", + Help: "A histogram to benchmark it.", + }, + ) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + m.Observe(3.1415) + } +} diff --git a/Godeps/_workspace/src/github.com/prometheus/client_golang/prometheus/counter.go b/Godeps/_workspace/src/github.com/prometheus/client_golang/prometheus/counter.go index d715ee0bb5..f8d633fbd5 100644 --- a/Godeps/_workspace/src/github.com/prometheus/client_golang/prometheus/counter.go +++ b/Godeps/_workspace/src/github.com/prometheus/client_golang/prometheus/counter.go @@ -74,7 +74,7 @@ func (c *counter) Add(v float64) { // CounterVec is a Collector that bundles a set of Counters 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 -// (e.g. number of http requests, partitioned by response code and +// (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 diff --git a/Godeps/_workspace/src/github.com/prometheus/client_golang/prometheus/examples_test.go b/Godeps/_workspace/src/github.com/prometheus/client_golang/prometheus/examples_test.go index 78b1b81f29..5e62967b0b 100644 --- a/Godeps/_workspace/src/github.com/prometheus/client_golang/prometheus/examples_test.go +++ b/Godeps/_workspace/src/github.com/prometheus/client_golang/prometheus/examples_test.go @@ -129,7 +129,7 @@ func ExampleCounterVec() { httpReqs := prometheus.NewCounterVec( prometheus.CounterOpts{ Name: "http_requests_total", - Help: "How many HTTP requests processed, partitioned by status code and http method.", + Help: "How many HTTP requests processed, partitioned by status code and HTTP method.", ConstLabels: prometheus.Labels{"env": *binaryVersion}, }, []string{"code", "method"}, @@ -200,7 +200,7 @@ func ExampleRegister() { fmt.Println("taskCounter registered.") } // Don't forget to tell the HTTP server about the Prometheus handler. - // (In a real program, you still need to start the http server...) + // (In a real program, you still need to start the HTTP server...) http.Handle("/metrics", prometheus.Handler()) // Now you can start workers and give every one of them a pointer to @@ -240,7 +240,7 @@ func ExampleRegister() { // Prometheus will not allow you to ever export metrics with // inconsistent help strings or label names. After unregistering, the - // unregistered metrics will cease to show up in the /metrics http + // unregistered metrics will cease to show up in the /metrics HTTP // response, but the registry still remembers that those metrics had // been exported before. For this example, we will now choose a // different name. (In a real program, you would obviously not export @@ -452,3 +452,49 @@ func ExampleSummaryVec() { // > // ] } + +func ExampleHistogram() { + temps := prometheus.NewHistogram(prometheus.HistogramOpts{ + Name: "pond_temperature_celsius", + Help: "The temperature of the frog pond.", // Sorry, we can't measure how badly it smells. + Buckets: prometheus.LinearBuckets(20, 5, 5), // 5 buckets, each 5 centigrade wide. + }) + + // Simulate some observations. + for i := 0; i < 1000; i++ { + temps.Observe(30 + math.Floor(120*math.Sin(float64(i)*0.1))/10) + } + + // Just for demonstration, let's check the state of the histogram by + // (ab)using its Write method (which is usually only used by Prometheus + // internally). + metric := &dto.Metric{} + temps.Write(metric) + fmt.Println(proto.MarshalTextString(metric)) + + // Output: + // histogram: < + // sample_count: 1000 + // sample_sum: 29969.50000000001 + // bucket: < + // cumulative_count: 192 + // upper_bound: 20 + // > + // bucket: < + // cumulative_count: 366 + // upper_bound: 25 + // > + // bucket: < + // cumulative_count: 501 + // upper_bound: 30 + // > + // bucket: < + // cumulative_count: 638 + // upper_bound: 35 + // > + // bucket: < + // cumulative_count: 816 + // upper_bound: 40 + // > + // > +} diff --git a/Godeps/_workspace/src/github.com/prometheus/client_golang/prometheus/http.go b/Godeps/_workspace/src/github.com/prometheus/client_golang/prometheus/http.go index 818c90fb65..dac92fd907 100644 --- a/Godeps/_workspace/src/github.com/prometheus/client_golang/prometheus/http.go +++ b/Godeps/_workspace/src/github.com/prometheus/client_golang/prometheus/http.go @@ -47,7 +47,7 @@ func nowSeries(t ...time.Time) nower { } // InstrumentHandler wraps the given HTTP handler for instrumentation. It -// registers four metric collectors (if not already done) and reports http +// registers four metric collectors (if not already done) and reports HTTP // metrics to the (newly or already) registered collectors: http_requests_total // (CounterVec), http_request_duration_microseconds (Summary), // http_request_size_bytes (Summary), http_response_size_bytes (Summary). Each diff --git a/Godeps/_workspace/src/github.com/prometheus/client_golang/prometheus/registry.go b/Godeps/_workspace/src/github.com/prometheus/client_golang/prometheus/registry.go index 505a4aa810..6709f25785 100644 --- a/Godeps/_workspace/src/github.com/prometheus/client_golang/prometheus/registry.go +++ b/Godeps/_workspace/src/github.com/prometheus/client_golang/prometheus/registry.go @@ -171,7 +171,7 @@ func SetMetricFamilyInjectionHook(hook func() []*dto.MetricFamily) { } // PanicOnCollectError sets the behavior whether a panic is caused upon an error -// while metrics are collected and served to the http endpoint. By default, an +// while metrics are collected and served to the HTTP endpoint. By default, an // internal server error (status code 500) is served with an error message. func PanicOnCollectError(b bool) { defRegistry.panicOnCollectError = b @@ -464,6 +464,8 @@ func (r *registry) writePB(w io.Writer, writeEncoded encoder) (int, error) { metricFamily.Type = dto.MetricType_SUMMARY.Enum() case dtoMetric.Untyped != nil: metricFamily.Type = dto.MetricType_UNTYPED.Enum() + case dtoMetric.Histogram != nil: + metricFamily.Type = dto.MetricType_HISTOGRAM.Enum() default: return 0, fmt.Errorf("empty metric collected: %s", dtoMetric) } diff --git a/Godeps/_workspace/src/github.com/prometheus/client_golang/prometheus/summary.go b/Godeps/_workspace/src/github.com/prometheus/client_golang/prometheus/summary.go index 5d8b82f595..93e7b6bb1b 100644 --- a/Godeps/_workspace/src/github.com/prometheus/client_golang/prometheus/summary.go +++ b/Godeps/_workspace/src/github.com/prometheus/client_golang/prometheus/summary.go @@ -25,6 +25,7 @@ import ( dto "github.com/prometheus/client_model/go" "github.com/prometheus/client_golang/_vendor/perks/quantile" + "github.com/prometheus/client_golang/model" ) // A Summary captures individual observations from an event or sample stream and @@ -35,6 +36,12 @@ import ( // Summary provides the median, the 90th and the 99th percentile of the latency // as rank estimations. // +// 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 +// need aggregatable quantiles (e.g. you want the 99th percentile latency of all +// queries served across all instances of a service), consider the Histogram +// metric type. See the Prometheus documentation for more details. +// // To create Summary instances, use NewSummary. type Summary interface { Metric @@ -44,9 +51,13 @@ type Summary interface { Observe(float64) } -// DefObjectives are the default Summary quantile values. var ( + // DefObjectives are the default Summary quantile values. DefObjectives = map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001} + + errQuantileLabelNotAllowed = fmt.Errorf( + "%q is not allowed as label name in summaries", model.QuantileLabel, + ) ) // Default values for SummaryOpts. @@ -110,7 +121,10 @@ type SummaryOpts struct { // AgeBuckets is the number of buckets used to exclude observations that // are older than MaxAge from the summary. A higher number has a // resource penalty, so only increase it if the higher resolution is - // really required. The default value is DefAgeBuckets. + // really required. For very high observation rates, you might want to + // reduce the number of age buckets. With only one age bucket, you will + // effectively see a complete reset of the summary each time MaxAge has + // passed. The default value is DefAgeBuckets. AgeBuckets uint32 // BufCap defines the default sample stream buffer size. The default @@ -119,10 +133,6 @@ type SummaryOpts struct { // is the internal buffer size of the underlying package // "github.com/bmizerany/perks/quantile"). BufCap uint32 - - // Epsilon is the error epsilon for the quantile rank estimate. Must be - // positive. The default is DefEpsilon. - Epsilon float64 } // TODO: Great fuck-up with the sliding-window decay algorithm... The Merge @@ -158,6 +168,17 @@ func newSummary(desc *Desc, opts SummaryOpts, labelValues ...string) Summary { panic(errInconsistentCardinality) } + for _, n := range desc.variableLabels { + if n == model.QuantileLabel { + panic(errQuantileLabelNotAllowed) + } + } + for _, lp := range desc.constLabelPairs { + if lp.GetName() == model.QuantileLabel { + panic(errQuantileLabelNotAllowed) + } + } + if len(opts.Objectives) == 0 { opts.Objectives = DefObjectives } @@ -358,7 +379,7 @@ func (s quantSort) Less(i, j int) bool { // SummaryVec is a Collector that bundles a set of Summaries 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 -// (e.g. http request latencies, partitioned by status code and method). Create +// (e.g. HTTP request latencies, partitioned by status code and method). Create // instances with NewSummaryVec. type SummaryVec struct { MetricVec @@ -411,14 +432,14 @@ func (m *SummaryVec) GetMetricWith(labels Labels) (Summary, error) { // 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) +// myVec.WithLabelValues("404", "GET").Observe(42.21) func (m *SummaryVec) WithLabelValues(lvs ...string) Summary { return m.MetricVec.WithLabelValues(lvs...).(Summary) } // 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) +// myVec.With(Labels{"code": "404", "method": "GET"}).Observe(42.21) func (m *SummaryVec) With(labels Labels) Summary { return m.MetricVec.With(labels).(Summary) } diff --git a/Godeps/_workspace/src/github.com/prometheus/client_golang/prometheus/summary_test.go b/Godeps/_workspace/src/github.com/prometheus/client_golang/prometheus/summary_test.go index 3f83796ea6..40d05fae52 100644 --- a/Godeps/_workspace/src/github.com/prometheus/client_golang/prometheus/summary_test.go +++ b/Godeps/_workspace/src/github.com/prometheus/client_golang/prometheus/summary_test.go @@ -120,6 +120,10 @@ func BenchmarkSummaryWrite8(b *testing.B) { } func TestSummaryConcurrency(t *testing.T) { + if testing.Short() { + t.Skip("Skipping test in short mode.") + } + rand.Seed(42) it := func(n uint32) bool { @@ -195,6 +199,10 @@ func TestSummaryConcurrency(t *testing.T) { } func TestSummaryVecConcurrency(t *testing.T) { + if testing.Short() { + t.Skip("Skipping test in short mode.") + } + rand.Seed(42) objectives := make([]float64, 0, len(DefObjectives)) diff --git a/Godeps/_workspace/src/github.com/prometheus/client_golang/text/create.go b/Godeps/_workspace/src/github.com/prometheus/client_golang/text/create.go index 64e372c0cd..4430459946 100644 --- a/Godeps/_workspace/src/github.com/prometheus/client_golang/text/create.go +++ b/Godeps/_workspace/src/github.com/prometheus/client_golang/text/create.go @@ -24,8 +24,10 @@ import ( "bytes" "fmt" "io" + "math" "strings" + "github.com/prometheus/client_golang/model" dto "github.com/prometheus/client_model/go" ) @@ -116,7 +118,7 @@ func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (int, error) { for _, q := range metric.Summary.Quantile { n, err = writeSample( name, metric, - "quantile", fmt.Sprint(q.GetQuantile()), + model.QuantileLabel, fmt.Sprint(q.GetQuantile()), q.GetValue(), out, ) @@ -145,10 +147,11 @@ func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (int, error) { "expected summary in metric %s", metric, ) } + infSeen := false for _, q := range metric.Histogram.Bucket { n, err = writeSample( name+"_bucket", metric, - "le", fmt.Sprint(q.GetUpperBound()), + model.BucketLabel, fmt.Sprint(q.GetUpperBound()), float64(q.GetCumulativeCount()), out, ) @@ -156,7 +159,21 @@ func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (int, error) { if err != nil { return written, err } - // TODO: Add +inf bucket if it's missing. + if math.IsInf(q.GetUpperBound(), +1) { + infSeen = true + } + } + if !infSeen { + n, err = writeSample( + name+"_bucket", metric, + model.BucketLabel, "+Inf", + float64(metric.Histogram.GetSampleCount()), + out, + ) + if err != nil { + return written, err + } + written += n } n, err = writeSample( name+"_sum", metric, "", "", diff --git a/Godeps/_workspace/src/github.com/prometheus/client_golang/text/create_test.go b/Godeps/_workspace/src/github.com/prometheus/client_golang/text/create_test.go index 9326e6e5b5..fe938de80c 100644 --- a/Godeps/_workspace/src/github.com/prometheus/client_golang/text/create_test.go +++ b/Godeps/_workspace/src/github.com/prometheus/client_golang/text/create_test.go @@ -267,6 +267,50 @@ request_duration_microseconds_bucket{le="172.8"} 1524 request_duration_microseconds_bucket{le="+Inf"} 2693 request_duration_microseconds_sum 1.7560473e+06 request_duration_microseconds_count 2693 +`, + }, + // 5: Histogram with missing +Inf bucket. + { + in: &dto.MetricFamily{ + Name: proto.String("request_duration_microseconds"), + Help: proto.String("The response latency."), + Type: dto.MetricType_HISTOGRAM.Enum(), + Metric: []*dto.Metric{ + &dto.Metric{ + Histogram: &dto.Histogram{ + SampleCount: proto.Uint64(2693), + SampleSum: proto.Float64(1756047.3), + Bucket: []*dto.Bucket{ + &dto.Bucket{ + UpperBound: proto.Float64(100), + CumulativeCount: proto.Uint64(123), + }, + &dto.Bucket{ + UpperBound: proto.Float64(120), + CumulativeCount: proto.Uint64(412), + }, + &dto.Bucket{ + UpperBound: proto.Float64(144), + CumulativeCount: proto.Uint64(592), + }, + &dto.Bucket{ + UpperBound: proto.Float64(172.8), + CumulativeCount: proto.Uint64(1524), + }, + }, + }, + }, + }, + }, + out: `# HELP request_duration_microseconds The response latency. +# TYPE request_duration_microseconds histogram +request_duration_microseconds_bucket{le="100"} 123 +request_duration_microseconds_bucket{le="120"} 412 +request_duration_microseconds_bucket{le="144"} 592 +request_duration_microseconds_bucket{le="172.8"} 1524 +request_duration_microseconds_bucket{le="+Inf"} 2693 +request_duration_microseconds_sum 1.7560473e+06 +request_duration_microseconds_count 2693 `, }, } diff --git a/Godeps/_workspace/src/github.com/prometheus/client_golang/text/parse.go b/Godeps/_workspace/src/github.com/prometheus/client_golang/text/parse.go index eaff592740..e317d6850b 100644 --- a/Godeps/_workspace/src/github.com/prometheus/client_golang/text/parse.go +++ b/Godeps/_workspace/src/github.com/prometheus/client_golang/text/parse.go @@ -274,8 +274,8 @@ func (p *Parser) startLabelName() stateFn { } // Special summary/histogram treatment. Don't add 'quantile' and 'le' // labels to 'real' labels. - if !(p.currentMF.GetType() == dto.MetricType_SUMMARY && p.currentLabelPair.GetName() == "quantile") && - !(p.currentMF.GetType() == dto.MetricType_HISTOGRAM && p.currentLabelPair.GetName() == "le") { + if !(p.currentMF.GetType() == dto.MetricType_SUMMARY && p.currentLabelPair.GetName() == model.QuantileLabel) && + !(p.currentMF.GetType() == dto.MetricType_HISTOGRAM && p.currentLabelPair.GetName() == model.BucketLabel) { p.currentMetric.Label = append(p.currentMetric.Label, p.currentLabelPair) } if p.skipBlankTabIfCurrentBlankTab(); p.err != nil { @@ -306,7 +306,7 @@ func (p *Parser) startLabelValue() stateFn { // - Quantile labels are special, will result in dto.Quantile later. // - Other labels have to be added to currentLabels for signature calculation. if p.currentMF.GetType() == dto.MetricType_SUMMARY { - if p.currentLabelPair.GetName() == "quantile" { + if p.currentLabelPair.GetName() == model.QuantileLabel { if p.currentQuantile, p.err = strconv.ParseFloat(p.currentLabelPair.GetValue(), 64); p.err != nil { // Create a more helpful error message. p.parseError(fmt.Sprintf("expected float as value for 'quantile' label, got %q", p.currentLabelPair.GetValue())) @@ -318,7 +318,7 @@ func (p *Parser) startLabelValue() stateFn { } // Similar special treatment of histograms. if p.currentMF.GetType() == dto.MetricType_HISTOGRAM { - if p.currentLabelPair.GetName() == "le" { + if p.currentLabelPair.GetName() == model.BucketLabel { if p.currentBucket, p.err = strconv.ParseFloat(p.currentLabelPair.GetValue(), 64); p.err != nil { // Create a more helpful error message. p.parseError(fmt.Sprintf("expected float as value for 'le' label, got %q", p.currentLabelPair.GetValue()))