Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-pages.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNick Thomas <nick@gitlab.com>2019-08-12 13:46:19 +0300
committerNick Thomas <nick@gitlab.com>2019-08-12 13:46:19 +0300
commit115ebb519c86bb5bb93dcd44254fc7d32ec212c1 (patch)
treeaa3f90dcdaf9d0caf73d74bf9b088ec35696d0b4
parent703e83e0d719b0d12592ce8e9dc61b604fafa82d (diff)
parentdf6c6ba3d878d5b0e7659d0502c37ba9bbc06284 (diff)
Merge branch 'bjk/159_metics_names' into 'master'
Update metrics See merge request gitlab-org/gitlab-pages!103
-rw-r--r--acceptance_test.go4
-rw-r--r--app.go3
-rw-r--r--metrics/metrics.go6
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/collector.go2
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/counter.go2
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/desc.go4
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/doc.go6
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/gauge.go2
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/go_collector.go89
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/histogram.go120
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/http.go108
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/labels.go21
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/registry.go50
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/summary.go134
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/timer.go11
-rw-r--r--vendor/vendor.json10
16 files changed, 397 insertions, 175 deletions
diff --git a/acceptance_test.go b/acceptance_test.go
index 28ebdeb8..8023c4ff 100644
--- a/acceptance_test.go
+++ b/acceptance_test.go
@@ -393,8 +393,8 @@ func TestPrometheusMetricsCanBeScraped(t *testing.T) {
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
- assert.Contains(t, string(body), "gitlab_pages_http_sessions_active 0")
- assert.Contains(t, string(body), "gitlab_pages_domains_served_total 16")
+ assert.Contains(t, string(body), "gitlab_pages_http_active_sessions 0")
+ assert.Contains(t, string(body), "gitlab_pages_served_domains 16")
}
}
diff --git a/app.go b/app.go
index 2cb8bbd9..da1a5315 100644
--- a/app.go
+++ b/app.go
@@ -12,7 +12,6 @@ import (
"sync"
"time"
- "github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/rs/cors"
log "github.com/sirupsen/logrus"
@@ -301,7 +300,7 @@ func (a *theApp) Run() {
go func(fd uintptr) {
defer wg.Done()
- handler := promhttp.HandlerFor(prometheus.DefaultGatherer, promhttp.HandlerOpts{}).ServeHTTP
+ handler := promhttp.Handler().ServeHTTP
err := listenAndServe(fd, handler, false, nil, nil)
if err != nil {
capturingFatal(err, errortracking.WithField("listener", "metrics"))
diff --git a/metrics/metrics.go b/metrics/metrics.go
index 44350ae5..3bf4d7a8 100644
--- a/metrics/metrics.go
+++ b/metrics/metrics.go
@@ -7,8 +7,8 @@ import (
var (
// DomainsServed counts the total number of sites served
DomainsServed = prometheus.NewGauge(prometheus.GaugeOpts{
- Name: "gitlab_pages_domains_served_total",
- Help: "The total number of sites served by this Pages app",
+ Name: "gitlab_pages_served_domains",
+ Help: "The number of sites served by this Pages app",
})
// FailedDomainUpdates counts the number of failed site updates
@@ -39,7 +39,7 @@ var (
// SessionsActive is the number of HTTP requests currently being processed
SessionsActive = prometheus.NewGauge(prometheus.GaugeOpts{
- Name: "gitlab_pages_http_sessions_active",
+ Name: "gitlab_pages_http_active_sessions",
Help: "The number of HTTP requests currently being processed",
})
)
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/collector.go b/vendor/github.com/prometheus/client_golang/prometheus/collector.go
index c0d70b2f..1e839650 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/collector.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/collector.go
@@ -79,7 +79,7 @@ type Collector interface {
// of the Describe method. If a Collector sometimes collects no metrics at all
// (for example vectors like CounterVec, GaugeVec, etc., which only collect
// metrics after a metric with a fully specified label set has been accessed),
-// it might even get registered as an unchecked Collecter (cf. the Register
+// it might even get registered as an unchecked Collector (cf. the Register
// method of the Registerer interface). Hence, only use this shortcut
// implementation of Describe if you are certain to fulfill the contract.
//
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/counter.go b/vendor/github.com/prometheus/client_golang/prometheus/counter.go
index 765e4550..d463e36d 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/counter.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/counter.go
@@ -136,7 +136,7 @@ func NewCounterVec(opts CounterOpts, labelNames []string) *CounterVec {
return &CounterVec{
metricVec: newMetricVec(desc, func(lvs ...string) Metric {
if len(lvs) != len(desc.variableLabels) {
- panic(errInconsistentCardinality)
+ panic(makeInconsistentCardinalityError(desc.fqName, desc.variableLabels, lvs))
}
result := &counter{desc: desc, labelPairs: makeLabelPairs(desc, lvs)}
result.init(result) // Init self-collection.
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/desc.go b/vendor/github.com/prometheus/client_golang/prometheus/desc.go
index 7b8827ff..1d034f87 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/desc.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/desc.go
@@ -93,7 +93,7 @@ func NewDesc(fqName, help string, variableLabels []string, constLabels Labels) *
// First add only the const label names and sort them...
for labelName := range constLabels {
if !checkLabelName(labelName) {
- d.err = fmt.Errorf("%q is not a valid label name", labelName)
+ d.err = fmt.Errorf("%q is not a valid label name for metric %q", labelName, fqName)
return d
}
labelNames = append(labelNames, labelName)
@@ -115,7 +115,7 @@ func NewDesc(fqName, help string, variableLabels []string, constLabels Labels) *
// dimension with a different mix between preset and variable labels.
for _, labelName := range variableLabels {
if !checkLabelName(labelName) {
- d.err = fmt.Errorf("%q is not a valid label name", labelName)
+ d.err = fmt.Errorf("%q is not a valid label name for metric %q", labelName, fqName)
return d
}
labelNames = append(labelNames, "$"+labelName)
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/doc.go b/vendor/github.com/prometheus/client_golang/prometheus/doc.go
index 5d9525de..1e0d578e 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/doc.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/doc.go
@@ -122,13 +122,13 @@
// 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. Alternatively, you
-// could return no Desc at all, which will marke the Collector “unchecked”. No
-// checks are porformed at registration time, but metric consistency will still
+// could return no Desc at all, which will mark the Collector “unchecked”. No
+// checks are performed at registration time, but metric consistency will still
// be ensured at scrape time, i.e. any inconsistencies will lead to scrape
// errors. Thus, with unchecked Collectors, the responsibility to not collect
// metrics that lead to inconsistencies in the total scrape result lies with the
// implementer of the Collector. While this is not a desirable state, it is
-// sometimes necessary. The typical use case is a situatios where the exact
+// sometimes necessary. The typical use case is a situation where the exact
// metrics to be returned by a Collector cannot be predicted at registration
// time, but the implementer has sufficient knowledge of the whole system to
// guarantee metric consistency.
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/gauge.go b/vendor/github.com/prometheus/client_golang/prometheus/gauge.go
index 17c72d7e..71d406bd 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/gauge.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/gauge.go
@@ -147,7 +147,7 @@ func NewGaugeVec(opts GaugeOpts, labelNames []string) *GaugeVec {
return &GaugeVec{
metricVec: newMetricVec(desc, func(lvs ...string) Metric {
if len(lvs) != len(desc.variableLabels) {
- panic(errInconsistentCardinality)
+ panic(makeInconsistentCardinalityError(desc.fqName, desc.variableLabels, lvs))
}
result := &gauge{desc: desc, labelPairs: makeLabelPairs(desc, lvs)}
result.init(result) // Init self-collection.
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 ba3b9333..b108ec51 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/go_collector.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/go_collector.go
@@ -14,9 +14,9 @@
package prometheus
import (
- "fmt"
"runtime"
"runtime/debug"
+ "sync"
"time"
)
@@ -26,16 +26,41 @@ type goCollector struct {
gcDesc *Desc
goInfoDesc *Desc
- // metrics to describe and collect
- metrics memStatsMetrics
+ // ms... are memstats related.
+ msLast *runtime.MemStats // Previously collected memstats.
+ msLastTimestamp time.Time
+ msMtx sync.Mutex // Protects msLast and msLastTimestamp.
+ msMetrics memStatsMetrics
+ msRead func(*runtime.MemStats) // For mocking in tests.
+ msMaxWait time.Duration // Wait time for fresh memstats.
+ msMaxAge time.Duration // Maximum allowed age of old memstats.
}
// NewGoCollector returns a collector which exports metrics about the current Go
// process. This includes memory stats. To collect those, runtime.ReadMemStats
-// is called. This causes a stop-the-world, which is very short with Go1.9+
-// (~25µs). However, with older Go versions, the stop-the-world duration depends
-// on the heap size and can be quite significant (~1.7 ms/GiB as per
+// is called. This requires to “stop the world”, which usually only happens for
+// garbage collection (GC). Take the following implications into account when
+// deciding whether to use the Go collector:
+//
+// 1. The performance impact of stopping the world is the more relevant the more
+// frequently metrics are collected. However, with Go1.9 or later the
+// stop-the-world time per metrics collection is very short (~25µs) so that the
+// performance impact will only matter in rare cases. However, with older Go
+// versions, the stop-the-world duration depends on the heap size and can be
+// quite significant (~1.7 ms/GiB as per
// https://go-review.googlesource.com/c/go/+/34937).
+//
+// 2. During an ongoing GC, nothing else can stop the world. Therefore, if the
+// metrics collection happens to coincide with GC, it will only complete after
+// GC has finished. Usually, GC is fast enough to not cause problems. However,
+// with a very large heap, GC might take multiple seconds, which is enough to
+// cause scrape timeouts in common setups. To avoid this problem, the Go
+// collector will use the memstats from a previous collection if
+// runtime.ReadMemStats takes more than 1s. However, if there are no previously
+// collected memstats, or their collection is more than 5m ago, the collection
+// will block until runtime.ReadMemStats succeeds. (The problem might be solved
+// in Go1.13, see https://github.com/golang/go/issues/19812 for the related Go
+// issue.)
func NewGoCollector() Collector {
return &goCollector{
goroutinesDesc: NewDesc(
@@ -54,7 +79,11 @@ func NewGoCollector() Collector {
"go_info",
"Information about the Go environment.",
nil, Labels{"version": runtime.Version()}),
- metrics: memStatsMetrics{
+ msLast: &runtime.MemStats{},
+ msRead: runtime.ReadMemStats,
+ msMaxWait: time.Second,
+ msMaxAge: 5 * time.Minute,
+ msMetrics: memStatsMetrics{
{
desc: NewDesc(
memstatNamespace("alloc_bytes"),
@@ -253,7 +282,7 @@ func NewGoCollector() Collector {
}
func memstatNamespace(s string) string {
- return fmt.Sprintf("go_memstats_%s", s)
+ return "go_memstats_" + s
}
// Describe returns all descriptions of the collector.
@@ -262,13 +291,27 @@ func (c *goCollector) Describe(ch chan<- *Desc) {
ch <- c.threadsDesc
ch <- c.gcDesc
ch <- c.goInfoDesc
- for _, i := range c.metrics {
+ for _, i := range c.msMetrics {
ch <- i.desc
}
}
// Collect returns the current state of all metrics of the collector.
func (c *goCollector) Collect(ch chan<- Metric) {
+ var (
+ ms = &runtime.MemStats{}
+ done = make(chan struct{})
+ )
+ // Start reading memstats first as it might take a while.
+ go func() {
+ c.msRead(ms)
+ c.msMtx.Lock()
+ c.msLast = ms
+ c.msLastTimestamp = time.Now()
+ c.msMtx.Unlock()
+ close(done)
+ }()
+
ch <- MustNewConstMetric(c.goroutinesDesc, GaugeValue, float64(runtime.NumGoroutine()))
n, _ := runtime.ThreadCreateProfile(nil)
ch <- MustNewConstMetric(c.threadsDesc, GaugeValue, float64(n))
@@ -286,9 +329,31 @@ func (c *goCollector) Collect(ch chan<- Metric) {
ch <- MustNewConstMetric(c.goInfoDesc, GaugeValue, 1)
- ms := &runtime.MemStats{}
- runtime.ReadMemStats(ms)
- for _, i := range c.metrics {
+ timer := time.NewTimer(c.msMaxWait)
+ select {
+ case <-done: // Our own ReadMemStats succeeded in time. Use it.
+ timer.Stop() // Important for high collection frequencies to not pile up timers.
+ c.msCollect(ch, ms)
+ return
+ case <-timer.C: // Time out, use last memstats if possible. Continue below.
+ }
+ c.msMtx.Lock()
+ if time.Since(c.msLastTimestamp) < c.msMaxAge {
+ // Last memstats are recent enough. Collect from them under the lock.
+ c.msCollect(ch, c.msLast)
+ c.msMtx.Unlock()
+ return
+ }
+ // If we are here, the last memstats are too old or don't exist. We have
+ // to wait until our own ReadMemStats finally completes. For that to
+ // happen, we have to release the lock.
+ c.msMtx.Unlock()
+ <-done
+ c.msCollect(ch, ms)
+}
+
+func (c *goCollector) msCollect(ch chan<- Metric, ms *runtime.MemStats) {
+ for _, i := range c.msMetrics {
ch <- MustNewConstMetric(i.desc, i.valType, i.eval(ms))
}
}
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/histogram.go b/vendor/github.com/prometheus/client_golang/prometheus/histogram.go
index 4d7fa976..d7ea67bd 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/histogram.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/histogram.go
@@ -165,7 +165,7 @@ func NewHistogram(opts HistogramOpts) Histogram {
func newHistogram(desc *Desc, opts HistogramOpts, labelValues ...string) Histogram {
if len(desc.variableLabels) != len(labelValues) {
- panic(errInconsistentCardinality)
+ panic(makeInconsistentCardinalityError(desc.fqName, desc.variableLabels, labelValues))
}
for _, n := range desc.variableLabels {
@@ -204,8 +204,8 @@ func newHistogram(desc *Desc, opts HistogramOpts, labelValues ...string) Histogr
}
}
}
- // Finally we know the final length of h.upperBounds and can make counts
- // for both states:
+ // Finally we know the final length of h.upperBounds and can make buckets
+ // for both counts:
h.counts[0].buckets = make([]uint64, len(h.upperBounds))
h.counts[1].buckets = make([]uint64, len(h.upperBounds))
@@ -224,18 +224,21 @@ type histogramCounts struct {
}
type histogram struct {
- // countAndHotIdx is a complicated one. For lock-free yet atomic
- // observations, we need to save the total count of observations again,
- // combined with the index of the currently-hot counts struct, so that
- // we can perform the operation on both values atomically. The least
- // significant bit defines the hot counts struct. The remaining 63 bits
- // represent the total count of observations. This happens under the
- // assumption that the 63bit count will never overflow. Rationale: An
- // observations takes about 30ns. Let's assume it could happen in
- // 10ns. Overflowing the counter will then take at least (2^63)*10ns,
- // which is about 3000 years.
+ // countAndHotIdx enables lock-free writes with use of atomic updates.
+ // The most significant bit is the hot index [0 or 1] of the count field
+ // below. Observe calls update the hot one. All remaining bits count the
+ // number of Observe calls. Observe starts by incrementing this counter,
+ // and finish by incrementing the count field in the respective
+ // histogramCounts, as a marker for completion.
//
- // This has to be first in the struct for 64bit alignment. See
+ // Calls of the Write method (which are non-mutating reads from the
+ // perspective of the histogram) swap the hot–cold under the writeMtx
+ // lock. A cooldown is awaited (while locked) by comparing the number of
+ // observations with the initiation count. Once they match, then the
+ // last observation on the now cool one has completed. All cool fields must
+ // be merged into the new hot before releasing writeMtx.
+ //
+ // Fields with atomic access first! See alignment constraint:
// http://golang.org/pkg/sync/atomic/#pkg-note-BUG
countAndHotIdx uint64
@@ -243,16 +246,14 @@ type histogram struct {
desc *Desc
writeMtx sync.Mutex // Only used in the Write method.
- upperBounds []float64
-
// Two counts, one is "hot" for lock-free observations, the other is
// "cold" for writing out a dto.Metric. It has to be an array of
// pointers to guarantee 64bit alignment of the histogramCounts, see
// http://golang.org/pkg/sync/atomic/#pkg-note-BUG.
counts [2]*histogramCounts
- hotIdx int // Index of currently-hot counts. Only used within Write.
- labelPairs []*dto.LabelPair
+ upperBounds []float64
+ labelPairs []*dto.LabelPair
}
func (h *histogram) Desc() *Desc {
@@ -271,11 +272,11 @@ func (h *histogram) Observe(v float64) {
// 300 buckets: 154 ns/op linear - binary 61.6 ns/op
i := sort.SearchFloat64s(h.upperBounds, v)
- // We increment h.countAndHotIdx by 2 so that the counter in the upper
- // 63 bits gets incremented by 1. At the same time, we get the new value
+ // We increment h.countAndHotIdx so that the counter in the lower
+ // 63 bits gets incremented. At the same time, we get the new value
// back, which we can use to find the currently-hot counts.
- n := atomic.AddUint64(&h.countAndHotIdx, 2)
- hotCounts := h.counts[n%2]
+ n := atomic.AddUint64(&h.countAndHotIdx, 1)
+ hotCounts := h.counts[n>>63]
if i < len(h.upperBounds) {
atomic.AddUint64(&hotCounts.buckets[i], 1)
@@ -293,72 +294,43 @@ func (h *histogram) Observe(v float64) {
}
func (h *histogram) Write(out *dto.Metric) error {
- var (
- his = &dto.Histogram{}
- buckets = make([]*dto.Bucket, len(h.upperBounds))
- hotCounts, coldCounts *histogramCounts
- count uint64
- )
-
- // For simplicity, we mutex the rest of this method. It is not in the
- // hot path, i.e. Observe is called much more often than Write. The
- // complication of making Write lock-free isn't worth it.
+ // For simplicity, we protect this whole method by a mutex. It is not in
+ // the hot path, i.e. Observe is called much more often than Write. The
+ // complication of making Write lock-free isn't worth it, if possible at
+ // all.
h.writeMtx.Lock()
defer h.writeMtx.Unlock()
- // This is a bit arcane, which is why the following spells out this if
- // clause in English:
- //
- // If the currently-hot counts struct is #0, we atomically increment
- // h.countAndHotIdx by 1 so that from now on Observe will use the counts
- // struct #1. Furthermore, the atomic increment gives us the new value,
- // which, in its most significant 63 bits, tells us the count of
- // observations done so far up to and including currently ongoing
- // observations still using the counts struct just changed from hot to
- // cold. To have a normal uint64 for the count, we bitshift by 1 and
- // save the result in count. We also set h.hotIdx to 1 for the next
- // Write call, and we will refer to counts #1 as hotCounts and to counts
- // #0 as coldCounts.
- //
- // If the currently-hot counts struct is #1, we do the corresponding
- // things the other way round. We have to _decrement_ h.countAndHotIdx
- // (which is a bit arcane in itself, as we have to express -1 with an
- // unsigned int...).
- if h.hotIdx == 0 {
- count = atomic.AddUint64(&h.countAndHotIdx, 1) >> 1
- h.hotIdx = 1
- hotCounts = h.counts[1]
- coldCounts = h.counts[0]
- } else {
- count = atomic.AddUint64(&h.countAndHotIdx, ^uint64(0)) >> 1 // Decrement.
- h.hotIdx = 0
- hotCounts = h.counts[0]
- coldCounts = h.counts[1]
- }
-
- // Now we have to wait for the now-declared-cold counts to actually cool
- // down, i.e. wait for all observations still using it to finish. That's
- // the case once the count in the cold counts struct is the same as the
- // one atomically retrieved from the upper 63bits of h.countAndHotIdx.
- for {
- if count == atomic.LoadUint64(&coldCounts.count) {
- break
- }
+ // Adding 1<<63 switches the hot index (from 0 to 1 or from 1 to 0)
+ // without touching the count bits. See the struct comments for a full
+ // description of the algorithm.
+ n := atomic.AddUint64(&h.countAndHotIdx, 1<<63)
+ // count is contained unchanged in the lower 63 bits.
+ count := n & ((1 << 63) - 1)
+ // The most significant bit tells us which counts is hot. The complement
+ // is thus the cold one.
+ hotCounts := h.counts[n>>63]
+ coldCounts := h.counts[(^n)>>63]
+
+ // Await cooldown.
+ for count != atomic.LoadUint64(&coldCounts.count) {
runtime.Gosched() // Let observations get work done.
}
- his.SampleCount = proto.Uint64(count)
- his.SampleSum = proto.Float64(math.Float64frombits(atomic.LoadUint64(&coldCounts.sumBits)))
+ his := &dto.Histogram{
+ Bucket: make([]*dto.Bucket, len(h.upperBounds)),
+ SampleCount: proto.Uint64(count),
+ SampleSum: proto.Float64(math.Float64frombits(atomic.LoadUint64(&coldCounts.sumBits))),
+ }
var cumCount uint64
for i, upperBound := range h.upperBounds {
cumCount += atomic.LoadUint64(&coldCounts.buckets[i])
- buckets[i] = &dto.Bucket{
+ his.Bucket[i] = &dto.Bucket{
CumulativeCount: proto.Uint64(cumCount),
UpperBound: proto.Float64(upperBound),
}
}
- his.Bucket = buckets
out.Histogram = his
out.Label = h.labelPairs
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/http.go b/vendor/github.com/prometheus/client_golang/prometheus/http.go
index 4b8e6027..19a3e8f4 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/http.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/http.go
@@ -15,9 +15,7 @@ package prometheus
import (
"bufio"
- "bytes"
"compress/gzip"
- "fmt"
"io"
"net"
"net/http"
@@ -36,24 +34,14 @@ import (
const (
contentTypeHeader = "Content-Type"
- contentLengthHeader = "Content-Length"
contentEncodingHeader = "Content-Encoding"
acceptEncodingHeader = "Accept-Encoding"
)
-var bufPool sync.Pool
-
-func getBuf() *bytes.Buffer {
- buf := bufPool.Get()
- if buf == nil {
- return &bytes.Buffer{}
- }
- return buf.(*bytes.Buffer)
-}
-
-func giveBuf(buf *bytes.Buffer) {
- buf.Reset()
- bufPool.Put(buf)
+var gzipPool = sync.Pool{
+ New: func() interface{} {
+ return gzip.NewWriter(nil)
+ },
}
// Handler returns an HTTP handler for the DefaultGatherer. It is
@@ -71,58 +59,40 @@ func Handler() http.Handler {
// Deprecated: Use promhttp.HandlerFor(DefaultGatherer, promhttp.HandlerOpts{})
// instead. See there for further documentation.
func UninstrumentedHandler() http.Handler {
- return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+ return http.HandlerFunc(func(rsp http.ResponseWriter, req *http.Request) {
mfs, err := DefaultGatherer.Gather()
if err != nil {
- http.Error(w, "An error has occurred during metrics collection:\n\n"+err.Error(), http.StatusInternalServerError)
+ httpError(rsp, err)
return
}
contentType := expfmt.Negotiate(req.Header)
- buf := getBuf()
- defer giveBuf(buf)
- writer, encoding := decorateWriter(req, buf)
- enc := expfmt.NewEncoder(writer, contentType)
- var lastErr error
+ header := rsp.Header()
+ header.Set(contentTypeHeader, string(contentType))
+
+ w := io.Writer(rsp)
+ if gzipAccepted(req.Header) {
+ header.Set(contentEncodingHeader, "gzip")
+ gz := gzipPool.Get().(*gzip.Writer)
+ defer gzipPool.Put(gz)
+
+ gz.Reset(w)
+ defer gz.Close()
+
+ w = gz
+ }
+
+ enc := expfmt.NewEncoder(w, contentType)
+
for _, mf := range mfs {
if err := enc.Encode(mf); err != nil {
- lastErr = err
- http.Error(w, "An error has occurred during metrics encoding:\n\n"+err.Error(), http.StatusInternalServerError)
+ httpError(rsp, err)
return
}
}
- if closer, ok := writer.(io.Closer); ok {
- closer.Close()
- }
- if lastErr != nil && buf.Len() == 0 {
- http.Error(w, "No metrics encoded, last error:\n\n"+lastErr.Error(), http.StatusInternalServerError)
- return
- }
- header := w.Header()
- header.Set(contentTypeHeader, string(contentType))
- header.Set(contentLengthHeader, fmt.Sprint(buf.Len()))
- if encoding != "" {
- header.Set(contentEncodingHeader, encoding)
- }
- w.Write(buf.Bytes())
})
}
-// decorateWriter wraps a writer to handle gzip compression if requested. It
-// returns the decorated writer and the appropriate "Content-Encoding" header
-// (which is empty if no compression is enabled).
-func decorateWriter(request *http.Request, writer io.Writer) (io.Writer, string) {
- header := request.Header.Get(acceptEncodingHeader)
- parts := strings.Split(header, ",")
- for _, part := range parts {
- part = strings.TrimSpace(part)
- if part == "gzip" || strings.HasPrefix(part, "gzip;") {
- return gzip.NewWriter(writer), "gzip"
- }
- }
- return writer, ""
-}
-
var instLabels = []string{"method", "code"}
type nower interface {
@@ -360,6 +330,8 @@ type fancyResponseWriterDelegator struct {
}
func (f *fancyResponseWriterDelegator) CloseNotify() <-chan bool {
+ //lint:ignore SA1019 http.CloseNotifier is deprecated but we don't want to
+ //remove support from client_golang yet.
return f.ResponseWriter.(http.CloseNotifier).CloseNotify()
}
@@ -503,3 +475,31 @@ func sanitizeCode(s int) string {
return strconv.Itoa(s)
}
}
+
+// gzipAccepted returns whether the client will accept gzip-encoded content.
+func gzipAccepted(header http.Header) bool {
+ a := header.Get(acceptEncodingHeader)
+ parts := strings.Split(a, ",")
+ for _, part := range parts {
+ part = strings.TrimSpace(part)
+ if part == "gzip" || strings.HasPrefix(part, "gzip;") {
+ return true
+ }
+ }
+ return false
+}
+
+// httpError removes any content-encoding header and then calls http.Error with
+// the provided error and http.StatusInternalServerErrer. Error contents is
+// supposed to be uncompressed plain text. However, same as with a plain
+// http.Error, any header settings will be void if the header has already been
+// sent. The error message will still be written to the writer, but it will
+// probably be of limited use.
+func httpError(rsp http.ResponseWriter, err error) {
+ rsp.Header().Del(contentEncodingHeader)
+ http.Error(
+ rsp,
+ "An error has occurred while serving metrics:\n\n"+err.Error(),
+ http.StatusInternalServerError,
+ )
+}
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/labels.go b/vendor/github.com/prometheus/client_golang/prometheus/labels.go
index e68f132e..2744443a 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/labels.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/labels.go
@@ -37,9 +37,22 @@ const reservedLabelPrefix = "__"
var errInconsistentCardinality = errors.New("inconsistent label cardinality")
+func makeInconsistentCardinalityError(fqName string, labels, labelValues []string) error {
+ return fmt.Errorf(
+ "%s: %q has %d variable labels named %q but %d values %q were provided",
+ errInconsistentCardinality, fqName,
+ len(labels), labels,
+ len(labelValues), labelValues,
+ )
+}
+
func validateValuesInLabels(labels Labels, expectedNumberOfValues int) error {
if len(labels) != expectedNumberOfValues {
- return errInconsistentCardinality
+ return fmt.Errorf(
+ "%s: expected %d label values but got %d in %#v",
+ errInconsistentCardinality, expectedNumberOfValues,
+ len(labels), labels,
+ )
}
for name, val := range labels {
@@ -53,7 +66,11 @@ func validateValuesInLabels(labels Labels, expectedNumberOfValues int) error {
func validateLabelValues(vals []string, expectedNumberOfValues int) error {
if len(vals) != expectedNumberOfValues {
- return errInconsistentCardinality
+ return fmt.Errorf(
+ "%s: expected %d label values but got %d in %#v",
+ errInconsistentCardinality, expectedNumberOfValues,
+ len(vals), vals,
+ )
}
for _, val := range vals {
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/registry.go b/vendor/github.com/prometheus/client_golang/prometheus/registry.go
index e422ef38..f2fb67ae 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/registry.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/registry.go
@@ -16,6 +16,9 @@ package prometheus
import (
"bytes"
"fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
"runtime"
"sort"
"strings"
@@ -23,6 +26,7 @@ import (
"unicode/utf8"
"github.com/golang/protobuf/proto"
+ "github.com/prometheus/common/expfmt"
dto "github.com/prometheus/client_model/go"
@@ -533,6 +537,38 @@ func (r *Registry) Gather() ([]*dto.MetricFamily, error) {
return internal.NormalizeMetricFamilies(metricFamiliesByName), errs.MaybeUnwrap()
}
+// WriteToTextfile calls Gather on the provided Gatherer, encodes the result in the
+// Prometheus text format, and writes it to a temporary file. Upon success, the
+// temporary file is renamed to the provided filename.
+//
+// This is intended for use with the textfile collector of the node exporter.
+// Note that the node exporter expects the filename to be suffixed with ".prom".
+func WriteToTextfile(filename string, g Gatherer) error {
+ tmp, err := ioutil.TempFile(filepath.Dir(filename), filepath.Base(filename))
+ if err != nil {
+ return err
+ }
+ defer os.Remove(tmp.Name())
+
+ mfs, err := g.Gather()
+ if err != nil {
+ return err
+ }
+ for _, mf := range mfs {
+ if _, err := expfmt.MetricFamilyToText(tmp, mf); err != nil {
+ return err
+ }
+ }
+ if err := tmp.Close(); err != nil {
+ return err
+ }
+
+ if err := os.Chmod(tmp.Name(), 0644); err != nil {
+ return err
+ }
+ return os.Rename(tmp.Name(), filename)
+}
+
// processMetric is an internal helper method only used by the Gather method.
func processMetric(
metric Metric,
@@ -644,7 +680,7 @@ func processMetric(
// Gatherers is a slice of Gatherer instances that implements the Gatherer
// interface itself. Its Gather method calls Gather on all Gatherers in the
// slice in order and returns the merged results. Errors returned from the
-// Gather calles are all returned in a flattened MultiError. Duplicate and
+// Gather calls are all returned in a flattened MultiError. Duplicate and
// inconsistent Metrics are skipped (first occurrence in slice order wins) and
// reported in the returned error.
//
@@ -836,7 +872,13 @@ func checkMetricConsistency(
h = hashAddByte(h, separatorByte)
// Make sure label pairs are sorted. We depend on it for the consistency
// check.
- sort.Sort(labelPairSorter(dtoMetric.Label))
+ if !sort.IsSorted(labelPairSorter(dtoMetric.Label)) {
+ // We cannot sort dtoMetric.Label in place as it is immutable by contract.
+ copiedLabels := make([]*dto.LabelPair, len(dtoMetric.Label))
+ copy(copiedLabels, dtoMetric.Label)
+ sort.Sort(labelPairSorter(copiedLabels))
+ dtoMetric.Label = copiedLabels
+ }
for _, lp := range dtoMetric.Label {
h = hashAdd(h, lp.GetName())
h = hashAddByte(h, separatorByte)
@@ -867,8 +909,8 @@ func checkDescConsistency(
}
// Is the desc consistent with the content of the metric?
- lpsFromDesc := make([]*dto.LabelPair, 0, len(dtoMetric.Label))
- lpsFromDesc = append(lpsFromDesc, desc.constLabelPairs...)
+ lpsFromDesc := make([]*dto.LabelPair, len(desc.constLabelPairs), len(dtoMetric.Label))
+ copy(lpsFromDesc, desc.constLabelPairs)
for _, l := range desc.variableLabels {
lpsFromDesc = append(lpsFromDesc, &dto.LabelPair{
Name: proto.String(l),
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/summary.go b/vendor/github.com/prometheus/client_golang/prometheus/summary.go
index f7e92d82..1574b0fe 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/summary.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/summary.go
@@ -16,8 +16,10 @@ package prometheus
import (
"fmt"
"math"
+ "runtime"
"sort"
"sync"
+ "sync/atomic"
"time"
"github.com/beorn7/perks/quantile"
@@ -125,9 +127,10 @@ type SummaryOpts struct {
// 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.
+ // 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 to avoid problems
+ // during the transition.
Objectives map[float64]float64
// MaxAge defines the duration for which an observation stays relevant
@@ -151,7 +154,7 @@ type SummaryOpts struct {
BufCap uint32
}
-// Great fuck-up with the sliding-window decay algorithm... The Merge method of
+// Problem with the sliding-window decay algorithm... The Merge method of
// perk/quantile is actually not working as advertised - and it might be
// unfixable, as the underlying algorithm is apparently not capable of merging
// summaries in the first place. To avoid using Merge, we are currently adding
@@ -181,7 +184,7 @@ func NewSummary(opts SummaryOpts) Summary {
func newSummary(desc *Desc, opts SummaryOpts, labelValues ...string) Summary {
if len(desc.variableLabels) != len(labelValues) {
- panic(errInconsistentCardinality)
+ panic(makeInconsistentCardinalityError(desc.fqName, desc.variableLabels, labelValues))
}
for _, n := range desc.variableLabels {
@@ -214,6 +217,17 @@ func newSummary(desc *Desc, opts SummaryOpts, labelValues ...string) Summary {
opts.BufCap = DefBufCap
}
+ if len(opts.Objectives) == 0 {
+ // Use the lock-free implementation of a Summary without objectives.
+ s := &noObjectivesSummary{
+ desc: desc,
+ labelPairs: makeLabelPairs(desc, labelValues),
+ counts: [2]*summaryCounts{&summaryCounts{}, &summaryCounts{}},
+ }
+ s.init(s) // Init self-collection.
+ return s
+ }
+
s := &summary{
desc: desc,
@@ -382,6 +396,116 @@ func (s *summary) swapBufs(now time.Time) {
}
}
+type summaryCounts struct {
+ // sumBits contains the bits of the float64 representing the sum of all
+ // observations. sumBits and count have to go first in the struct to
+ // guarantee alignment for atomic operations.
+ // http://golang.org/pkg/sync/atomic/#pkg-note-BUG
+ sumBits uint64
+ count uint64
+}
+
+type noObjectivesSummary struct {
+ // countAndHotIdx enables lock-free writes with use of atomic updates.
+ // The most significant bit is the hot index [0 or 1] of the count field
+ // below. Observe calls update the hot one. All remaining bits count the
+ // number of Observe calls. Observe starts by incrementing this counter,
+ // and finish by incrementing the count field in the respective
+ // summaryCounts, as a marker for completion.
+ //
+ // Calls of the Write method (which are non-mutating reads from the
+ // perspective of the summary) swap the hot–cold under the writeMtx
+ // lock. A cooldown is awaited (while locked) by comparing the number of
+ // observations with the initiation count. Once they match, then the
+ // last observation on the now cool one has completed. All cool fields must
+ // be merged into the new hot before releasing writeMtx.
+
+ // Fields with atomic access first! See alignment constraint:
+ // http://golang.org/pkg/sync/atomic/#pkg-note-BUG
+ countAndHotIdx uint64
+
+ selfCollector
+ desc *Desc
+ writeMtx sync.Mutex // Only used in the Write method.
+
+ // Two counts, one is "hot" for lock-free observations, the other is
+ // "cold" for writing out a dto.Metric. It has to be an array of
+ // pointers to guarantee 64bit alignment of the histogramCounts, see
+ // http://golang.org/pkg/sync/atomic/#pkg-note-BUG.
+ counts [2]*summaryCounts
+
+ labelPairs []*dto.LabelPair
+}
+
+func (s *noObjectivesSummary) Desc() *Desc {
+ return s.desc
+}
+
+func (s *noObjectivesSummary) Observe(v float64) {
+ // We increment h.countAndHotIdx so that the counter in the lower
+ // 63 bits gets incremented. At the same time, we get the new value
+ // back, which we can use to find the currently-hot counts.
+ n := atomic.AddUint64(&s.countAndHotIdx, 1)
+ hotCounts := s.counts[n>>63]
+
+ for {
+ oldBits := atomic.LoadUint64(&hotCounts.sumBits)
+ newBits := math.Float64bits(math.Float64frombits(oldBits) + v)
+ if atomic.CompareAndSwapUint64(&hotCounts.sumBits, oldBits, newBits) {
+ break
+ }
+ }
+ // Increment count last as we take it as a signal that the observation
+ // is complete.
+ atomic.AddUint64(&hotCounts.count, 1)
+}
+
+func (s *noObjectivesSummary) Write(out *dto.Metric) error {
+ // For simplicity, we protect this whole method by a mutex. It is not in
+ // the hot path, i.e. Observe is called much more often than Write. The
+ // complication of making Write lock-free isn't worth it, if possible at
+ // all.
+ s.writeMtx.Lock()
+ defer s.writeMtx.Unlock()
+
+ // Adding 1<<63 switches the hot index (from 0 to 1 or from 1 to 0)
+ // without touching the count bits. See the struct comments for a full
+ // description of the algorithm.
+ n := atomic.AddUint64(&s.countAndHotIdx, 1<<63)
+ // count is contained unchanged in the lower 63 bits.
+ count := n & ((1 << 63) - 1)
+ // The most significant bit tells us which counts is hot. The complement
+ // is thus the cold one.
+ hotCounts := s.counts[n>>63]
+ coldCounts := s.counts[(^n)>>63]
+
+ // Await cooldown.
+ for count != atomic.LoadUint64(&coldCounts.count) {
+ runtime.Gosched() // Let observations get work done.
+ }
+
+ sum := &dto.Summary{
+ SampleCount: proto.Uint64(count),
+ SampleSum: proto.Float64(math.Float64frombits(atomic.LoadUint64(&coldCounts.sumBits))),
+ }
+
+ out.Summary = sum
+ out.Label = s.labelPairs
+
+ // Finally add all the cold counts to the new hot counts and reset the cold counts.
+ atomic.AddUint64(&hotCounts.count, count)
+ atomic.StoreUint64(&coldCounts.count, 0)
+ for {
+ oldBits := atomic.LoadUint64(&hotCounts.sumBits)
+ newBits := math.Float64bits(math.Float64frombits(oldBits) + sum.GetSampleSum())
+ if atomic.CompareAndSwapUint64(&hotCounts.sumBits, oldBits, newBits) {
+ atomic.StoreUint64(&coldCounts.sumBits, 0)
+ break
+ }
+ }
+ return nil
+}
+
type quantSort []*dto.Quantile
func (s quantSort) Len() int {
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/timer.go b/vendor/github.com/prometheus/client_golang/prometheus/timer.go
index b8fc5f18..8d5f1052 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/timer.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/timer.go
@@ -39,13 +39,16 @@ func NewTimer(o Observer) *Timer {
// 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.
+// construction with the duration in seconds as an argument. The observed
+// duration is also returned. 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() {
+func (t *Timer) ObserveDuration() time.Duration {
+ d := time.Since(t.begin)
if t.observer != nil {
- t.observer.Observe(time.Since(t.begin).Seconds())
+ t.observer.Observe(d.Seconds())
}
+ return d
}
diff --git a/vendor/vendor.json b/vendor/vendor.json
index 2b2f1420..cfcc297f 100644
--- a/vendor/vendor.json
+++ b/vendor/vendor.json
@@ -207,12 +207,12 @@
"revisionTime": "2018-01-27T01:58:12Z"
},
{
- "checksumSHA1": "frS661rlSEZWE9CezHhnFioQK/I=",
+ "checksumSHA1": "5d1E/NDh6XDKBifjiyJt9p5W9cs=",
"path": "github.com/prometheus/client_golang/prometheus",
- "revision": "1cafe34db7fdec6022e17e00e1c1ea501022f3e4",
- "revisionTime": "2018-10-15T14:52:39Z",
- "version": "v0.9",
- "versionExact": "v0.9.0"
+ "revision": "50c4339db732beb2165735d2cde0bff78eb3c5a5",
+ "revisionTime": "2019-05-16T21:22:10Z",
+ "version": "v0.9.3",
+ "versionExact": "v0.9.3"
},
{
"checksumSHA1": "UBqhkyjCz47+S19MVTigxJ2VjVQ=",