summaryrefslogtreecommitdiffstats
path: root/vendor/github.com/prometheus
diff options
context:
space:
mode:
authorChristopher Speller <crspeller@gmail.com>2018-02-16 06:47:51 -0800
committerJoram Wilander <jwawilander@gmail.com>2018-02-16 09:47:51 -0500
commit6d8f122a5160f6d9e4c51579f2429dfaa62c7271 (patch)
tree6e0242cd6709260abd74060a7ec7dc1381efa36e /vendor/github.com/prometheus
parentb112747de76f9c11c4d8083207049fac6e435019 (diff)
downloadchat-6d8f122a5160f6d9e4c51579f2429dfaa62c7271.tar.gz
chat-6d8f122a5160f6d9e4c51579f2429dfaa62c7271.tar.bz2
chat-6d8f122a5160f6d9e4c51579f2429dfaa62c7271.zip
Upgrading server dependancies (#8308)
Diffstat (limited to 'vendor/github.com/prometheus')
-rw-r--r--vendor/github.com/prometheus/client_golang/ISSUE_TEMPLATE.md8
-rw-r--r--vendor/github.com/prometheus/client_golang/README.md47
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/doc.go8
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/http.go5
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/process_collector.go14
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/promhttp/delegator.go6
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/promhttp/delegator_1_8.go2
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/promhttp/http.go129
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/promhttp/http_test.go119
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_server_test.go26
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/push/deprecated.go172
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/push/example_add_from_gatherer_test.go32
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/push/examples_test.go11
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/push/push.go244
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/push/push_test.go78
-rw-r--r--vendor/github.com/prometheus/client_golang/prometheus/registry.go291
-rw-r--r--vendor/github.com/prometheus/procfs/fs.go4
-rw-r--r--vendor/github.com/prometheus/procfs/nfs/parse.go13
-rw-r--r--vendor/github.com/prometheus/procfs/nfs/parse_nfs.go10
-rw-r--r--vendor/github.com/prometheus/procfs/nfs/parse_nfs_test.go127
20 files changed, 1039 insertions, 307 deletions
diff --git a/vendor/github.com/prometheus/client_golang/ISSUE_TEMPLATE.md b/vendor/github.com/prometheus/client_golang/ISSUE_TEMPLATE.md
new file mode 100644
index 000000000..a456acf5b
--- /dev/null
+++ b/vendor/github.com/prometheus/client_golang/ISSUE_TEMPLATE.md
@@ -0,0 +1,8 @@
+<!--
+
+ If you are unhappy how your favorite Go dependency management tool deals
+ with this repository, please do not file an issue but read
+ https://github.com/prometheus/client_golang#important-note-about-releases-versioning-tagging-stability-and-your-favorite-go-dependency-management-tool
+ instead. Thank you very much.
+
+-->
diff --git a/vendor/github.com/prometheus/client_golang/README.md b/vendor/github.com/prometheus/client_golang/README.md
index 0eb0df1df..17d46ecaf 100644
--- a/vendor/github.com/prometheus/client_golang/README.md
+++ b/vendor/github.com/prometheus/client_golang/README.md
@@ -2,6 +2,7 @@
[![Build Status](https://travis-ci.org/prometheus/client_golang.svg?branch=master)](https://travis-ci.org/prometheus/client_golang)
[![Go Report Card](https://goreportcard.com/badge/github.com/prometheus/client_golang)](https://goreportcard.com/report/github.com/prometheus/client_golang)
+[![go-doc](https://godoc.org/github.com/prometheus/client_golang?status.svg)](https://godoc.org/github.com/prometheus/client_golang)
This is the [Go](http://golang.org) client library for
[Prometheus](http://prometheus.io). It has two separate parts, one for
@@ -10,6 +11,50 @@ Prometheus HTTP API.
__This library requires Go1.7 or later.__
+## Important note about releases, versioning, tagging, stability, and your favorite Go dependency management tool
+
+While our goal is to follow [Semantic Versioning](https://semver.org/), this
+repository is still pre-1.0.0. To quote the
+[Semantic Versioning spec](https://semver.org/#spec-item-4): “Anything may
+change at any time. The public API should not be considered stable.” We know
+that this is at odds with the widespread use of this library. However, just
+declaring something 1.0.0 doesn't make it 1.0.0. Instead, we are working
+towards a 1.0.0 release that actually deserves its major version number.
+
+Having said that, we aim for always keeping the tip of master in a workable
+state and for only introducing ”mildly” breaking changes up to and including
+[v0.9.0](https://github.com/prometheus/client_golang/milestone/1). After that,
+a number of ”hard” breaking changes are planned, see the
+[v0.10.0 milestone](https://github.com/prometheus/client_golang/milestone/2),
+which should get the library much closer to 1.0.0 state.
+
+Dependency management in Go projects is still in flux, and there are many tools
+floating around. While [dep](https://golang.github.io/dep/) might develop into
+the de-facto standard tool, it is still officially experimental. The roadmap
+for this library has been laid out with a lot of sometimes painful experience
+in mind. We really cannot adjust it every other month to the needs of the
+currently most popular or most promising Go dependency management tool. The
+recommended course of action with dependency management tools is the following:
+
+- Do not expect strict post-1.0.0 semver semantics prior to the 1.0.0
+ release. If your dependency management tool expects strict post-1.0.0 semver
+ semantics, you have to wait. Sorry.
+- If you want absolute certainty, please lock to a specific commit. You can
+ also lock to tags, but please don't ask for more tagging. This would suggest
+ some release or stability testing procedure that simply is not in place. As
+ said above, we are aiming for stability of the tip of master, but if we
+ tagged every single commit, locking to tags would be the same as locking to
+ commits.
+- If you want to get the newer features and improvements and are willing to
+ take the minor risk of newly introduced bugs and “mild” breakage, just always
+ update to the tip of master (which is essentially the original idea of Go
+ dependency management). We recommend to not use features marked as
+ _deprecated_ in this case.
+- Once [v0.9.0](https://github.com/prometheus/client_golang/milestone/1) is
+ out, you could lock to v0.9.x to get bugfixes (and perhaps minor new
+ features) while avoiding the “hard” breakage that will come with post-0.9
+ features.
+
## Instrumenting applications
[![code-coverage](http://gocover.io/_badge/github.com/prometheus/client_golang/prometheus)](http://gocover.io/github.com/prometheus/client_golang/prometheus) [![go-doc](https://godoc.org/github.com/prometheus/client_golang/prometheus?status.svg)](https://godoc.org/github.com/prometheus/client_golang/prometheus)
@@ -26,7 +71,7 @@ contains simple examples of instrumented code.
## Client for the Prometheus HTTP API
-[![code-coverage](http://gocover.io/_badge/github.com/prometheus/client_golang/api/prometheus)](http://gocover.io/github.com/prometheus/client_golang/api/prometheus) [![go-doc](https://godoc.org/github.com/prometheus/client_golang/api/prometheus?status.svg)](https://godoc.org/github.com/prometheus/client_golang/api/prometheus)
+[![code-coverage](http://gocover.io/_badge/github.com/prometheus/client_golang/api/prometheus/v1)](http://gocover.io/github.com/prometheus/client_golang/api/prometheus/v1) [![go-doc](https://godoc.org/github.com/prometheus/client_golang/api/prometheus?status.svg)](https://godoc.org/github.com/prometheus/client_golang/api)
The
[`api/prometheus` directory](https://github.com/prometheus/client_golang/tree/master/api/prometheus)
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/doc.go b/vendor/github.com/prometheus/client_golang/prometheus/doc.go
index 36ef15567..f727c991d 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/doc.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/doc.go
@@ -11,10 +11,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-// Package prometheus provides metrics primitives to instrument code for
-// monitoring. It also offers a registry for metrics. Sub-packages allow to
-// expose the registered metrics via HTTP (package promhttp) or push them to a
-// Pushgateway (package push).
+// Package prometheus is the core instrumentation package. It provides metrics
+// primitives to instrument code for monitoring. It also offers a registry for
+// metrics. Sub-packages allow to expose the registered metrics via HTTP
+// (package promhttp) or push them to a Pushgateway (package push).
//
// All exported functions and methods are safe to be used concurrently unless
// specified otherwise.
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/http.go b/vendor/github.com/prometheus/client_golang/prometheus/http.go
index bfee5c6eb..dd0f8197f 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/http.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/http.go
@@ -61,9 +61,8 @@ func giveBuf(buf *bytes.Buffer) {
// name).
//
// Deprecated: Please note the issues described in the doc comment of
-// InstrumentHandler. You might want to consider using promhttp.Handler instead
-// (which is not instrumented, but can be instrumented with the tooling provided
-// in package promhttp).
+// InstrumentHandler. You might want to consider using
+// promhttp.InstrumentedHandler instead.
func Handler() http.Handler {
return InstrumentHandler("prometheus", UninstrumentedHandler())
}
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 94b2553e1..32ac74a7f 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/process_collector.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/process_collector.go
@@ -26,8 +26,11 @@ type processCollector struct {
}
// NewProcessCollector returns a collector which exports the current state of
-// process metrics including cpu, memory and file descriptor usage as well as
-// the process start time for the given process id under the given namespace.
+// process metrics including CPU, memory and file descriptor usage as well as
+// the process start time for the given process ID under the given namespace.
+//
+// Currently, the collector depends on a Linux-style proc filesystem and
+// therefore only exports metrics for Linux.
func NewProcessCollector(pid int, namespace string) Collector {
return NewProcessCollectorPIDFn(
func() (int, error) { return pid, nil },
@@ -35,11 +38,8 @@ func NewProcessCollector(pid int, namespace string) Collector {
)
}
-// NewProcessCollectorPIDFn returns a collector which exports the current state
-// of process metrics including cpu, memory and file descriptor usage as well
-// as the process start time under the given namespace. The given pidFn is
-// called on each collect and is used to determine the process to export
-// metrics for.
+// NewProcessCollectorPIDFn works like NewProcessCollector but the process ID is
+// determined on each collect anew by calling the given pidFn function.
func NewProcessCollectorPIDFn(
pidFn func() (int, error),
namespace 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
index 5ee095b09..9c1c66dcc 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/promhttp/delegator.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/promhttp/delegator.go
@@ -102,10 +102,10 @@ func init() {
return d
}
pickDelegator[closeNotifier] = func(d *responseWriterDelegator) delegator { // 1
- return closeNotifierDelegator{d}
+ return &closeNotifierDelegator{d}
}
pickDelegator[flusher] = func(d *responseWriterDelegator) delegator { // 2
- return flusherDelegator{d}
+ return &flusherDelegator{d}
}
pickDelegator[flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 3
return struct {
@@ -115,7 +115,7 @@ func init() {
}{d, &flusherDelegator{d}, &closeNotifierDelegator{d}}
}
pickDelegator[hijacker] = func(d *responseWriterDelegator) delegator { // 4
- return hijackerDelegator{d}
+ return &hijackerDelegator{d}
}
pickDelegator[hijacker+closeNotifier] = func(d *responseWriterDelegator) delegator { // 5
return struct {
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
index f4d386f7a..75a905e2f 100644
--- 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
@@ -28,7 +28,7 @@ func (d *pusherDelegator) Push(target string, opts *http.PushOptions) error {
func init() {
pickDelegator[pusher] = func(d *responseWriterDelegator) delegator { // 16
- return pusherDelegator{d}
+ return &pusherDelegator{d}
}
pickDelegator[pusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 17
return struct {
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 2d67f2496..8dc260355 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/promhttp/http.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/promhttp/http.go
@@ -39,6 +39,7 @@ import (
"net/http"
"strings"
"sync"
+ "time"
"github.com/prometheus/common/expfmt"
@@ -67,21 +68,51 @@ func giveBuf(buf *bytes.Buffer) {
bufPool.Put(buf)
}
-// Handler returns an HTTP handler for the prometheus.DefaultGatherer. The
-// Handler uses the default HandlerOpts, i.e. report the first error as an HTTP
-// error, no error logging, and compression if requested by the client.
+// Handler returns an http.Handler for the prometheus.DefaultGatherer, using
+// default HandlerOpts, i.e. it reports the first error as an HTTP error, it has
+// no error logging, and it applies compression if requested by the client.
//
-// If you want to create a Handler for the DefaultGatherer with different
-// HandlerOpts, create it with HandlerFor with prometheus.DefaultGatherer and
-// your desired HandlerOpts.
+// The returned http.Handler is already instrumented using the
+// InstrumentMetricHandler function and the prometheus.DefaultRegisterer. If you
+// create multiple http.Handlers by separate calls of the Handler function, the
+// metrics used for instrumentation will be shared between them, providing
+// global scrape counts.
+//
+// This function is meant to cover the bulk of basic use cases. If you are doing
+// anything that requires more customization (including using a non-default
+// Gatherer, different instrumentation, and non-default HandlerOpts), use the
+// HandlerFor function. See there for details.
func Handler() http.Handler {
- return HandlerFor(prometheus.DefaultGatherer, HandlerOpts{})
+ return InstrumentMetricHandler(
+ prometheus.DefaultRegisterer, HandlerFor(prometheus.DefaultGatherer, HandlerOpts{}),
+ )
}
-// HandlerFor returns an http.Handler for the provided Gatherer. The behavior
-// of the Handler is defined by the provided HandlerOpts.
+// HandlerFor returns an uninstrumented http.Handler for the provided
+// Gatherer. The behavior of the Handler is defined by the provided
+// HandlerOpts. Thus, HandlerFor is useful to create http.Handlers for custom
+// Gatherers, with non-default HandlerOpts, and/or with custom (or no)
+// instrumentation. Use the InstrumentMetricHandler function to apply the same
+// kind of instrumentation as it is used by the Handler function.
func HandlerFor(reg prometheus.Gatherer, opts HandlerOpts) http.Handler {
- return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+ var inFlightSem chan struct{}
+ if opts.MaxRequestsInFlight > 0 {
+ inFlightSem = make(chan struct{}, opts.MaxRequestsInFlight)
+ }
+
+ h := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+ if inFlightSem != nil {
+ select {
+ case inFlightSem <- struct{}{}: // All good, carry on.
+ defer func() { <-inFlightSem }()
+ default:
+ http.Error(w, fmt.Sprintf(
+ "Limit of concurrent requests reached (%d), try again later.", opts.MaxRequestsInFlight,
+ ), http.StatusServiceUnavailable)
+ return
+ }
+ }
+
mfs, err := reg.Gather()
if err != nil {
if opts.ErrorLog != nil {
@@ -137,9 +168,70 @@ func HandlerFor(reg prometheus.Gatherer, opts HandlerOpts) http.Handler {
if encoding != "" {
header.Set(contentEncodingHeader, encoding)
}
- w.Write(buf.Bytes())
+ if _, err := w.Write(buf.Bytes()); err != nil && opts.ErrorLog != nil {
+ opts.ErrorLog.Println("error while sending encoded metrics:", err)
+ }
// TODO(beorn7): Consider streaming serving of metrics.
})
+
+ if opts.Timeout <= 0 {
+ return h
+ }
+ return http.TimeoutHandler(h, opts.Timeout, fmt.Sprintf(
+ "Exceeded configured timeout of %v.\n",
+ opts.Timeout,
+ ))
+}
+
+// InstrumentMetricHandler is usually used with an http.Handler returned by the
+// HandlerFor function. It instruments the provided http.Handler with two
+// metrics: A counter vector "promhttp_metric_handler_requests_total" to count
+// scrapes partitioned by HTTP status code, and a gauge
+// "promhttp_metric_handler_requests_in_flight" to track the number of
+// simultaneous scrapes. This function idempotently registers collectors for
+// both metrics with the provided Registerer. It panics if the registration
+// fails. The provided metrics are useful to see how many scrapes hit the
+// monitored target (which could be from different Prometheus servers or other
+// scrapers), and how often they overlap (which would result in more than one
+// scrape in flight at the same time). Note that the scrapes-in-flight gauge
+// will contain the scrape by which it is exposed, while the scrape counter will
+// only get incremented after the scrape is complete (as only then the status
+// code is known). For tracking scrape durations, use the
+// "scrape_duration_seconds" gauge created by the Prometheus server upon each
+// scrape.
+func InstrumentMetricHandler(reg prometheus.Registerer, handler http.Handler) http.Handler {
+ cnt := prometheus.NewCounterVec(
+ prometheus.CounterOpts{
+ Name: "promhttp_metric_handler_requests_total",
+ Help: "Total number of scrapes by HTTP status code.",
+ },
+ []string{"code"},
+ )
+ // Initialize the most likely HTTP status codes.
+ cnt.WithLabelValues("200")
+ cnt.WithLabelValues("500")
+ cnt.WithLabelValues("503")
+ if err := reg.Register(cnt); err != nil {
+ if are, ok := err.(prometheus.AlreadyRegisteredError); ok {
+ cnt = are.ExistingCollector.(*prometheus.CounterVec)
+ } else {
+ panic(err)
+ }
+ }
+
+ gge := prometheus.NewGauge(prometheus.GaugeOpts{
+ Name: "promhttp_metric_handler_requests_in_flight",
+ Help: "Current number of scrapes being served.",
+ })
+ if err := reg.Register(gge); err != nil {
+ if are, ok := err.(prometheus.AlreadyRegisteredError); ok {
+ gge = are.ExistingCollector.(prometheus.Gauge)
+ } else {
+ panic(err)
+ }
+ }
+
+ return InstrumentHandlerCounter(cnt, InstrumentHandlerInFlight(gge, handler))
}
// HandlerErrorHandling defines how a Handler serving metrics will handle
@@ -183,6 +275,21 @@ type HandlerOpts struct {
// If DisableCompression is true, the handler will never compress the
// response, even if requested by the client.
DisableCompression bool
+ // The number of concurrent HTTP requests is limited to
+ // MaxRequestsInFlight. Additional requests are responded to with 503
+ // Service Unavailable and a suitable message in the body. If
+ // MaxRequestsInFlight is 0 or negative, no limit is applied.
+ MaxRequestsInFlight int
+ // If handling a request takes longer than Timeout, it is responded to
+ // with 503 ServiceUnavailable and a suitable Message. No timeout is
+ // applied if Timeout is 0 or negative. Note that with the current
+ // implementation, reaching the timeout simply ends the HTTP requests as
+ // described above (and even that only if sending of the body hasn't
+ // started yet), while the bulk work of gathering all the metrics keeps
+ // running in the background (with the eventual result to be thrown
+ // away). Until the implementation is improved, it is recommended to
+ // implement a separate timeout in potentially slow Collectors.
+ Timeout time.Duration
}
// decorateWriter wraps a writer to handle gzip compression if requested. It
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 413ff7baa..aeaa0b4d7 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
@@ -19,7 +19,9 @@ import (
"log"
"net/http"
"net/http/httptest"
+ "strings"
"testing"
+ "time"
"github.com/prometheus/client_golang/prometheus"
)
@@ -37,6 +39,23 @@ func (e errorCollector) Collect(ch chan<- prometheus.Metric) {
)
}
+type blockingCollector struct {
+ CollectStarted, Block chan struct{}
+}
+
+func (b blockingCollector) Describe(ch chan<- *prometheus.Desc) {
+ ch <- prometheus.NewDesc("dummy_desc", "not helpful", nil, nil)
+}
+
+func (b blockingCollector) Collect(ch chan<- prometheus.Metric) {
+ select {
+ case b.CollectStarted <- struct{}{}:
+ default:
+ }
+ // Collects nothing, just waits for a channel receive.
+ <-b.Block
+}
+
func TestHandlerErrorHandling(t *testing.T) {
// Create a registry that collects a MetricFamily with two elements,
@@ -129,3 +148,103 @@ the_count 0
}()
panicHandler.ServeHTTP(writer, request)
}
+
+func TestInstrumentMetricHandler(t *testing.T) {
+ reg := prometheus.NewRegistry()
+ handler := InstrumentMetricHandler(reg, HandlerFor(reg, HandlerOpts{}))
+ // Do it again to test idempotency.
+ InstrumentMetricHandler(reg, HandlerFor(reg, HandlerOpts{}))
+ writer := httptest.NewRecorder()
+ request, _ := http.NewRequest("GET", "/", nil)
+ request.Header.Add("Accept", "test/plain")
+
+ handler.ServeHTTP(writer, request)
+ if got, want := writer.Code, http.StatusOK; got != want {
+ t.Errorf("got HTTP status code %d, want %d", got, want)
+ }
+
+ want := "promhttp_metric_handler_requests_in_flight 1\n"
+ if got := writer.Body.String(); !strings.Contains(got, want) {
+ t.Errorf("got body %q, does not contain %q", got, want)
+ }
+ want = "promhttp_metric_handler_requests_total{code=\"200\"} 0\n"
+ if got := writer.Body.String(); !strings.Contains(got, want) {
+ t.Errorf("got body %q, does not contain %q", got, want)
+ }
+
+ writer.Body.Reset()
+ handler.ServeHTTP(writer, request)
+ if got, want := writer.Code, http.StatusOK; got != want {
+ t.Errorf("got HTTP status code %d, want %d", got, want)
+ }
+
+ want = "promhttp_metric_handler_requests_in_flight 1\n"
+ if got := writer.Body.String(); !strings.Contains(got, want) {
+ t.Errorf("got body %q, does not contain %q", got, want)
+ }
+ want = "promhttp_metric_handler_requests_total{code=\"200\"} 1\n"
+ if got := writer.Body.String(); !strings.Contains(got, want) {
+ t.Errorf("got body %q, does not contain %q", got, want)
+ }
+}
+
+func TestHandlerMaxRequestsInFlight(t *testing.T) {
+ reg := prometheus.NewRegistry()
+ handler := HandlerFor(reg, HandlerOpts{MaxRequestsInFlight: 1})
+ w1 := httptest.NewRecorder()
+ w2 := httptest.NewRecorder()
+ w3 := httptest.NewRecorder()
+ request, _ := http.NewRequest("GET", "/", nil)
+ request.Header.Add("Accept", "test/plain")
+
+ c := blockingCollector{Block: make(chan struct{}), CollectStarted: make(chan struct{}, 1)}
+ reg.MustRegister(c)
+
+ rq1Done := make(chan struct{})
+ go func() {
+ handler.ServeHTTP(w1, request)
+ close(rq1Done)
+ }()
+ <-c.CollectStarted
+
+ handler.ServeHTTP(w2, request)
+
+ if got, want := w2.Code, http.StatusServiceUnavailable; got != want {
+ t.Errorf("got HTTP status code %d, want %d", got, want)
+ }
+ if got, want := w2.Body.String(), "Limit of concurrent requests reached (1), try again later.\n"; got != want {
+ t.Errorf("got body %q, want %q", got, want)
+ }
+
+ close(c.Block)
+ <-rq1Done
+
+ handler.ServeHTTP(w3, request)
+
+ if got, want := w3.Code, http.StatusOK; got != want {
+ t.Errorf("got HTTP status code %d, want %d", got, want)
+ }
+}
+
+func TestHandlerTimeout(t *testing.T) {
+ reg := prometheus.NewRegistry()
+ handler := HandlerFor(reg, HandlerOpts{Timeout: time.Millisecond})
+ w := httptest.NewRecorder()
+
+ request, _ := http.NewRequest("GET", "/", nil)
+ request.Header.Add("Accept", "test/plain")
+
+ c := blockingCollector{Block: make(chan struct{}), CollectStarted: make(chan struct{}, 1)}
+ reg.MustRegister(c)
+
+ handler.ServeHTTP(w, request)
+
+ if got, want := w.Code, http.StatusServiceUnavailable; got != want {
+ t.Errorf("got HTTP status code %d, want %d", got, want)
+ }
+ if got, want := w.Body.String(), "Exceeded configured timeout of 1ms.\n"; got != want {
+ t.Errorf("got body %q, want %q", got, want)
+ }
+
+ close(c.Block) // To not leak a goroutine.
+}
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
index e9af63e04..716c6f45e 100644
--- 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
@@ -281,6 +281,16 @@ func (t *testResponseWriter) ReadFrom(io.Reader) (int64, error) {
return 0, nil
}
+// testFlusher is an http.ResponseWriter that also implements http.Flusher.
+type testFlusher struct {
+ flushCalled bool
+}
+
+func (t *testFlusher) Header() http.Header { return nil }
+func (t *testFlusher) Write([]byte) (int, error) { return 0, nil }
+func (t *testFlusher) WriteHeader(int) {}
+func (t *testFlusher) Flush() { t.flushCalled = true }
+
func TestInterfaceUpgrade(t *testing.T) {
w := &testResponseWriter{}
d := newDelegator(w, nil)
@@ -299,6 +309,22 @@ func TestInterfaceUpgrade(t *testing.T) {
if _, ok := d.(http.Hijacker); ok {
t.Error("delegator unexpectedly implements http.Hijacker")
}
+
+ f := &testFlusher{}
+ d = newDelegator(f, nil)
+ if _, ok := d.(http.CloseNotifier); ok {
+ t.Error("delegator unexpectedly implements http.CloseNotifier")
+ }
+ d.(http.Flusher).Flush()
+ if !w.flushCalled {
+ t.Error("Flush not called")
+ }
+ if _, ok := d.(io.ReaderFrom); ok {
+ t.Error("delegator unexpectedly implements io.ReaderFrom")
+ }
+ if _, ok := d.(http.Hijacker); ok {
+ t.Error("delegator unexpectedly implements http.Hijacker")
+ }
}
func ExampleInstrumentHandlerDuration() {
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/push/deprecated.go b/vendor/github.com/prometheus/client_golang/prometheus/push/deprecated.go
new file mode 100644
index 000000000..3d62b5725
--- /dev/null
+++ b/vendor/github.com/prometheus/client_golang/prometheus/push/deprecated.go
@@ -0,0 +1,172 @@
+// Copyright 2018 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 push
+
+// This file contains only deprecated code. Remove after v0.9 is released.
+
+import (
+ "bytes"
+ "fmt"
+ "io/ioutil"
+ "net/http"
+ "net/url"
+ "os"
+ "strings"
+
+ "github.com/prometheus/common/expfmt"
+ "github.com/prometheus/common/model"
+
+ "github.com/prometheus/client_golang/prometheus"
+)
+
+// FromGatherer triggers a metric collection by the provided Gatherer (which is
+// usually implemented by a prometheus.Registry) and pushes all gathered metrics
+// to the Pushgateway specified by url, using the provided job name and the
+// (optional) further grouping labels (the grouping map may be nil). See the
+// Pushgateway documentation for detailed implications of the job and other
+// grouping labels. Neither the job name nor any grouping label value may
+// contain a "/". The metrics pushed must not contain a job label of their own
+// nor any of the grouping labels.
+//
+// You can use just host:port or ip:port as url, in which case 'http://' is
+// added automatically. You can also include the schema in the URL. However, do
+// not include the '/metrics/jobs/...' part.
+//
+// Note that all previously pushed metrics with the same job and other grouping
+// labels will be replaced with the metrics pushed by this call. (It uses HTTP
+// method 'PUT' to push to the Pushgateway.)
+//
+// Deprecated: Please use a Pusher created with New instead.
+func FromGatherer(job string, grouping map[string]string, url string, g prometheus.Gatherer) error {
+ return push(job, grouping, url, g, "PUT")
+}
+
+// AddFromGatherer works like FromGatherer, but only previously pushed metrics
+// with the same name (and the same job and other grouping labels) will be
+// replaced. (It uses HTTP method 'POST' to push to the Pushgateway.)
+//
+// Deprecated: Please use a Pusher created with New instead.
+func AddFromGatherer(job string, grouping map[string]string, url string, g prometheus.Gatherer) error {
+ return push(job, grouping, url, g, "POST")
+}
+
+func push(job string, grouping map[string]string, pushURL string, g prometheus.Gatherer, method string) error {
+ if !strings.Contains(pushURL, "://") {
+ pushURL = "http://" + pushURL
+ }
+ if strings.HasSuffix(pushURL, "/") {
+ pushURL = pushURL[:len(pushURL)-1]
+ }
+
+ if strings.Contains(job, "/") {
+ return fmt.Errorf("job contains '/': %s", job)
+ }
+ urlComponents := []string{url.QueryEscape(job)}
+ for ln, lv := range grouping {
+ if !model.LabelName(ln).IsValid() {
+ return fmt.Errorf("grouping label has invalid name: %s", ln)
+ }
+ if strings.Contains(lv, "/") {
+ return fmt.Errorf("value of grouping label %s contains '/': %s", ln, lv)
+ }
+ urlComponents = append(urlComponents, ln, lv)
+ }
+ pushURL = fmt.Sprintf("%s/metrics/job/%s", pushURL, strings.Join(urlComponents, "/"))
+
+ mfs, err := g.Gather()
+ if err != nil {
+ return err
+ }
+ buf := &bytes.Buffer{}
+ enc := expfmt.NewEncoder(buf, expfmt.FmtProtoDelim)
+ // Check for pre-existing grouping labels:
+ for _, mf := range mfs {
+ for _, m := range mf.GetMetric() {
+ for _, l := range m.GetLabel() {
+ if l.GetName() == "job" {
+ return fmt.Errorf("pushed metric %s (%s) already contains a job label", mf.GetName(), m)
+ }
+ if _, ok := grouping[l.GetName()]; ok {
+ return fmt.Errorf(
+ "pushed metric %s (%s) already contains grouping label %s",
+ mf.GetName(), m, l.GetName(),
+ )
+ }
+ }
+ }
+ enc.Encode(mf)
+ }
+ req, err := http.NewRequest(method, pushURL, buf)
+ if err != nil {
+ return err
+ }
+ req.Header.Set(contentTypeHeader, string(expfmt.FmtProtoDelim))
+ resp, err := http.DefaultClient.Do(req)
+ if err != nil {
+ return err
+ }
+ defer resp.Body.Close()
+ if resp.StatusCode != 202 {
+ body, _ := ioutil.ReadAll(resp.Body) // Ignore any further error as this is for an error message only.
+ return fmt.Errorf("unexpected status code %d while pushing to %s: %s", resp.StatusCode, pushURL, body)
+ }
+ return nil
+}
+
+// Collectors works like FromGatherer, but it does not use a Gatherer. Instead,
+// it collects from the provided collectors directly. It is a convenient way to
+// push only a few metrics.
+//
+// Deprecated: Please use a Pusher created with New instead.
+func Collectors(job string, grouping map[string]string, url string, collectors ...prometheus.Collector) error {
+ return pushCollectors(job, grouping, url, "PUT", collectors...)
+}
+
+// AddCollectors works like AddFromGatherer, but it does not use a Gatherer.
+// Instead, it collects from the provided collectors directly. It is a
+// convenient way to push only a few metrics.
+//
+// Deprecated: Please use a Pusher created with New instead.
+func AddCollectors(job string, grouping map[string]string, url string, collectors ...prometheus.Collector) error {
+ return pushCollectors(job, grouping, url, "POST", collectors...)
+}
+
+func pushCollectors(job string, grouping map[string]string, url, method string, collectors ...prometheus.Collector) error {
+ r := prometheus.NewRegistry()
+ for _, collector := range collectors {
+ if err := r.Register(collector); err != nil {
+ return err
+ }
+ }
+ return push(job, grouping, url, r, method)
+}
+
+// HostnameGroupingKey returns a label map with the only entry
+// {instance="<hostname>"}. This can be conveniently used as the grouping
+// parameter if metrics should be pushed with the hostname as label. The
+// returned map is created upon each call so that the caller is free to add more
+// labels to the map.
+//
+// Deprecated: Usually, metrics pushed to the Pushgateway should not be
+// host-centric. (You would use https://github.com/prometheus/node_exporter in
+// that case.) If you have the need to add the hostname to the grouping key, you
+// are probably doing something wrong. See
+// https://prometheus.io/docs/practices/pushing/ for details.
+func HostnameGroupingKey() map[string]string {
+ hostname, err := os.Hostname()
+ if err != nil {
+ return map[string]string{"instance": "unknown"}
+ }
+ return map[string]string{"instance": hostname}
+}
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
index 5180c0745..dd22b526a 100644
--- 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
@@ -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 push_test
import (
@@ -53,10 +47,14 @@ func performBackup() (int, error) {
return 42, nil
}
-func ExampleAddFromGatherer() {
+func ExamplePusher_Add() {
+ // We use a registry here to benefit from the consistency checks that
+ // happen during registration.
registry := prometheus.NewRegistry()
registry.MustRegister(completionTime, duration, records)
- // Note that successTime is not registered at this time.
+ // Note that successTime is not registered.
+
+ pusher := push.New("http://pushgateway:9091", "db_backup").Gatherer(registry)
start := time.Now()
n, err := performBackup()
@@ -67,18 +65,16 @@ func ExampleAddFromGatherer() {
if err != nil {
fmt.Println("DB backup failed:", err)
} else {
- // Only now register successTime.
- registry.MustRegister(successTime)
+ // Add successTime to pusher only in case of success.
+ // We could as well register it with the registry.
+ // This example, however, demonstrates that you can
+ // mix Gatherers and Collectors when handling a Pusher.
+ pusher.Collector(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 {
+ // Add is used here rather than Push to not delete a previously pushed
+ // success timestamp in case of a failure of this backup.
+ if err := pusher.Add(); 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 7e0ac66a5..fa5549a9e 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
@@ -20,17 +20,16 @@ import (
"github.com/prometheus/client_golang/prometheus/push"
)
-func ExampleCollectors() {
+func ExamplePusher_Push() {
completionTime := prometheus.NewGauge(prometheus.GaugeOpts{
Name: "db_backup_last_completion_timestamp_seconds",
Help: "The timestamp of the last successful completion of a DB backup.",
})
completionTime.SetToCurrentTime()
- if err := push.Collectors(
- "db_backup", push.HostnameGroupingKey(),
- "http://pushgateway:9091",
- completionTime,
- ); err != nil {
+ if err := push.New("http://pushgateway:9091", "db_backup").
+ Collector(completionTime).
+ Grouping("db", "customers").
+ Push(); 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 8fb6f5f17..02be52c38 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/push/push.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/push/push.go
@@ -11,20 +11,27 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-// Copyright (c) 2013, The Prometheus Authors
-// All rights reserved.
+// Package push provides functions to push metrics to a Pushgateway. It uses a
+// builder approach. Create a Pusher with New and then add the various options
+// by using its methods, finally calling Add or Push, like this:
//
-// Use of this source code is governed by a BSD-style license that can be found
-// in the LICENSE file.
-
-// Package push provides functions to push metrics to a Pushgateway. The metrics
-// to push are either collected from a provided registry, or from explicitly
-// listed collectors.
+// // Easy case:
+// push.New("http://example.org/metrics", "my_job").Gatherer(myRegistry).Push()
+//
+// // Complex case:
+// push.New("http://example.org/metrics", "my_job").
+// Collector(myCollector1).
+// Collector(myCollector2).
+// Grouping("zone", "xy").
+// Client(&myHTTPClient).
+// BasicAuth("top", "secret").
+// Add()
+//
+// See the examples section for more detailed examples.
//
-// See the documentation of the Pushgateway to understand the meaning of the
-// grouping parameters and the differences between push.Registry and
-// push.Collectors on the one hand and push.AddRegistry and push.AddCollectors
-// on the other hand: https://github.com/prometheus/pushgateway
+// See the documentation of the Pushgateway to understand the meaning of
+// the grouping key and the differences between Push and Add:
+// https://github.com/prometheus/pushgateway
package push
import (
@@ -33,7 +40,6 @@ import (
"io/ioutil"
"net/http"
"net/url"
- "os"
"strings"
"github.com/prometheus/common/expfmt"
@@ -44,57 +50,149 @@ import (
const contentTypeHeader = "Content-Type"
-// FromGatherer triggers a metric collection by the provided Gatherer (which is
-// usually implemented by a prometheus.Registry) and pushes all gathered metrics
-// to the Pushgateway specified by url, using the provided job name and the
-// (optional) further grouping labels (the grouping map may be nil). See the
-// Pushgateway documentation for detailed implications of the job and other
-// grouping labels. Neither the job name nor any grouping label value may
-// contain a "/". The metrics pushed must not contain a job label of their own
-// nor any of the grouping labels.
+// Pusher manages a push to the Pushgateway. Use New to create one, configure it
+// with its methods, and finally use the Add or Push method to push.
+type Pusher struct {
+ error error
+
+ url, job string
+ grouping map[string]string
+
+ gatherers prometheus.Gatherers
+ registerer prometheus.Registerer
+
+ client *http.Client
+ useBasicAuth bool
+ username, password string
+}
+
+// New creates a new Pusher to push to the provided URL withe the provided job
+// name. You can use just host:port or ip:port as url, in which case “http://”
+// is added automatically. Alternatively, include the schema in the
+// URL. However, do not include the “/metrics/jobs/…” part.
//
-// You can use just host:port or ip:port as url, in which case 'http://' is
-// added automatically. You can also include the schema in the URL. However, do
-// not include the '/metrics/jobs/...' part.
+// Note that until https://github.com/prometheus/pushgateway/issues/97 is
+// resolved, a “/” character in the job name is prohibited.
+func New(url, job string) *Pusher {
+ var (
+ reg = prometheus.NewRegistry()
+ err error
+ )
+ if !strings.Contains(url, "://") {
+ url = "http://" + url
+ }
+ if strings.HasSuffix(url, "/") {
+ url = url[:len(url)-1]
+ }
+ if strings.Contains(job, "/") {
+ err = fmt.Errorf("job contains '/': %s", job)
+ }
+
+ return &Pusher{
+ error: err,
+ url: url,
+ job: job,
+ grouping: map[string]string{},
+ gatherers: prometheus.Gatherers{reg},
+ registerer: reg,
+ client: &http.Client{},
+ }
+}
+
+// Push collects/gathers all metrics from all Collectors and Gatherers added to
+// this Pusher. Then, it pushes them to the Pushgateway configured while
+// creating this Pusher, using the configured job name and any added grouping
+// labels as grouping key. All previously pushed metrics with the same job and
+// other grouping labels will be replaced with the metrics pushed by this
+// call. (It uses HTTP method “PUT” to push to the Pushgateway.)
//
-// Note that all previously pushed metrics with the same job and other grouping
-// labels will be replaced with the metrics pushed by this call. (It uses HTTP
-// method 'PUT' to push to the Pushgateway.)
-func FromGatherer(job string, grouping map[string]string, url string, g prometheus.Gatherer) error {
- return push(job, grouping, url, g, "PUT")
+// Push returns the first error encountered by any method call (including this
+// one) in the lifetime of the Pusher.
+func (p *Pusher) Push() error {
+ return p.push("PUT")
}
-// AddFromGatherer works like FromGatherer, but only previously pushed metrics
-// with the same name (and the same job and other grouping labels) will be
-// replaced. (It uses HTTP method 'POST' to push to the Pushgateway.)
-func AddFromGatherer(job string, grouping map[string]string, url string, g prometheus.Gatherer) error {
- return push(job, grouping, url, g, "POST")
+// Add works like push, but only previously pushed metrics with the same name
+// (and the same job and other grouping labels) will be replaced. (It uses HTTP
+// method “POST” to push to the Pushgateway.)
+func (p *Pusher) Add() error {
+ return p.push("POST")
}
-func push(job string, grouping map[string]string, pushURL string, g prometheus.Gatherer, method string) error {
- if !strings.Contains(pushURL, "://") {
- pushURL = "http://" + pushURL
- }
- if strings.HasSuffix(pushURL, "/") {
- pushURL = pushURL[:len(pushURL)-1]
- }
+// Gatherer adds a Gatherer to the Pusher, from which metrics will be gathered
+// to push them to the Pushgateway. The gathered metrics must not contain a job
+// label of their own.
+//
+// For convenience, this method returns a pointer to the Pusher itself.
+func (p *Pusher) Gatherer(g prometheus.Gatherer) *Pusher {
+ p.gatherers = append(p.gatherers, g)
+ return p
+}
- if strings.Contains(job, "/") {
- return fmt.Errorf("job contains '/': %s", job)
+// Collector adds a Collector to the Pusher, from which metrics will be
+// collected to push them to the Pushgateway. The collected metrics must not
+// contain a job label of their own.
+//
+// For convenience, this method returns a pointer to the Pusher itself.
+func (p *Pusher) Collector(c prometheus.Collector) *Pusher {
+ if p.error == nil {
+ p.error = p.registerer.Register(c)
}
- urlComponents := []string{url.QueryEscape(job)}
- for ln, lv := range grouping {
- if !model.LabelName(ln).IsValid() {
- return fmt.Errorf("grouping label has invalid name: %s", ln)
+ return p
+}
+
+// Grouping adds a label pair to the grouping key of the Pusher, replacing any
+// previously added label pair with the same label name. Note that setting any
+// labels in the grouping key that are already contained in the metrics to push
+// will lead to an error.
+//
+// For convenience, this method returns a pointer to the Pusher itself.
+//
+// Note that until https://github.com/prometheus/pushgateway/issues/97 is
+// resolved, this method does not allow a “/” character in the label value.
+func (p *Pusher) Grouping(name, value string) *Pusher {
+ if p.error == nil {
+ if !model.LabelName(name).IsValid() {
+ p.error = fmt.Errorf("grouping label has invalid name: %s", name)
+ return p
}
- if strings.Contains(lv, "/") {
- return fmt.Errorf("value of grouping label %s contains '/': %s", ln, lv)
+ if strings.Contains(value, "/") {
+ p.error = fmt.Errorf("value of grouping label %s contains '/': %s", name, value)
+ return p
}
+ p.grouping[name] = value
+ }
+ return p
+}
+
+// Client sets a custom HTTP client for the Pusher. For convenience, this method
+// returns a pointer to the Pusher itself.
+func (p *Pusher) Client(c *http.Client) *Pusher {
+ p.client = c
+ return p
+}
+
+// BasicAuth configures the Pusher to use HTTP Basic Authentication with the
+// provided username and password. For convenience, this method returns a
+// pointer to the Pusher itself.
+func (p *Pusher) BasicAuth(username, password string) *Pusher {
+ p.useBasicAuth = true
+ p.username = username
+ p.password = password
+ return p
+}
+
+func (p *Pusher) push(method string) error {
+ if p.error != nil {
+ return p.error
+ }
+ urlComponents := []string{url.QueryEscape(p.job)}
+ for ln, lv := range p.grouping {
urlComponents = append(urlComponents, ln, lv)
}
- pushURL = fmt.Sprintf("%s/metrics/job/%s", pushURL, strings.Join(urlComponents, "/"))
+ pushURL := fmt.Sprintf("%s/metrics/job/%s", p.url, strings.Join(urlComponents, "/"))
- mfs, err := g.Gather()
+ mfs, err := p.gatherers.Gather()
if err != nil {
return err
}
@@ -107,7 +205,7 @@ func push(job string, grouping map[string]string, pushURL string, g prometheus.G
if l.GetName() == "job" {
return fmt.Errorf("pushed metric %s (%s) already contains a job label", mf.GetName(), m)
}
- if _, ok := grouping[l.GetName()]; ok {
+ if _, ok := p.grouping[l.GetName()]; ok {
return fmt.Errorf(
"pushed metric %s (%s) already contains grouping label %s",
mf.GetName(), m, l.GetName(),
@@ -121,8 +219,11 @@ func push(job string, grouping map[string]string, pushURL string, g prometheus.G
if err != nil {
return err
}
+ if p.useBasicAuth {
+ req.SetBasicAuth(p.username, p.password)
+ }
req.Header.Set(contentTypeHeader, string(expfmt.FmtProtoDelim))
- resp, err := http.DefaultClient.Do(req)
+ resp, err := p.client.Do(req)
if err != nil {
return err
}
@@ -133,40 +234,3 @@ func push(job string, grouping map[string]string, pushURL string, g prometheus.G
}
return nil
}
-
-// Collectors works like FromGatherer, but it does not use a Gatherer. Instead,
-// it collects from the provided collectors directly. It is a convenient way to
-// push only a few metrics.
-func Collectors(job string, grouping map[string]string, url string, collectors ...prometheus.Collector) error {
- return pushCollectors(job, grouping, url, "PUT", collectors...)
-}
-
-// AddCollectors works like AddFromGatherer, but it does not use a Gatherer.
-// Instead, it collects from the provided collectors directly. It is a
-// convenient way to push only a few metrics.
-func AddCollectors(job string, grouping map[string]string, url string, collectors ...prometheus.Collector) error {
- return pushCollectors(job, grouping, url, "POST", collectors...)
-}
-
-func pushCollectors(job string, grouping map[string]string, url, method string, collectors ...prometheus.Collector) error {
- r := prometheus.NewRegistry()
- for _, collector := range collectors {
- if err := r.Register(collector); err != nil {
- return err
- }
- }
- return push(job, grouping, url, r, method)
-}
-
-// HostnameGroupingKey returns a label map with the only entry
-// {instance="<hostname>"}. This can be conveniently used as the grouping
-// parameter if metrics should be pushed with the hostname as label. The
-// returned map is created upon each call so that the caller is free to add more
-// labels to the map.
-func HostnameGroupingKey() map[string]string {
- hostname, err := os.Hostname()
- if err != nil {
- return map[string]string{"instance": "unknown"}
- }
- return map[string]string{"instance": hostname}
-}
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/push/push_test.go b/vendor/github.com/prometheus/client_golang/prometheus/push/push_test.go
index 28ed9b74b..34ec334bb 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/push/push_test.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/push/push_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 push
import (
@@ -24,7 +18,6 @@ import (
"io/ioutil"
"net/http"
"net/http/httptest"
- "os"
"testing"
"github.com/prometheus/common/expfmt"
@@ -40,11 +33,6 @@ func TestPush(t *testing.T) {
lastPath string
)
- host, err := os.Hostname()
- if err != nil {
- t.Error(err)
- }
-
// Fake a Pushgateway that always responds with 202.
pgwOK := httptest.NewServer(
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@@ -98,26 +86,32 @@ func TestPush(t *testing.T) {
}
wantBody := buf.Bytes()
- // PushCollectors, all good.
- if err := Collectors("testjob", HostnameGroupingKey(), pgwOK.URL, metric1, metric2); err != nil {
+ // Push some Collectors, all good.
+ if err := New(pgwOK.URL, "testjob").
+ Collector(metric1).
+ Collector(metric2).
+ Push(); err != nil {
t.Fatal(err)
}
if lastMethod != "PUT" {
- t.Error("want method PUT for PushCollectors, got", lastMethod)
+ t.Error("want method PUT for Push, got", lastMethod)
}
if bytes.Compare(lastBody, wantBody) != 0 {
t.Errorf("got body %v, want %v", lastBody, wantBody)
}
- if lastPath != "/metrics/job/testjob/instance/"+host {
+ if lastPath != "/metrics/job/testjob" {
t.Error("unexpected path:", lastPath)
}
- // PushAddCollectors, with nil grouping, all good.
- if err := AddCollectors("testjob", nil, pgwOK.URL, metric1, metric2); err != nil {
+ // Add some Collectors, with nil grouping, all good.
+ if err := New(pgwOK.URL, "testjob").
+ Collector(metric1).
+ Collector(metric2).
+ Add(); err != nil {
t.Fatal(err)
}
if lastMethod != "POST" {
- t.Error("want method POST for PushAddCollectors, got", lastMethod)
+ t.Error("want method POST for Add, got", lastMethod)
}
if bytes.Compare(lastBody, wantBody) != 0 {
t.Errorf("got body %v, want %v", lastBody, wantBody)
@@ -126,8 +120,11 @@ func TestPush(t *testing.T) {
t.Error("unexpected path:", lastPath)
}
- // PushCollectors with a broken PGW.
- if err := Collectors("testjob", nil, pgwErr.URL, metric1, metric2); err == nil {
+ // Push some Collectors with a broken PGW.
+ if err := New(pgwErr.URL, "testjob").
+ Collector(metric1).
+ Collector(metric2).
+ Push(); err == nil {
t.Error("push to broken Pushgateway succeeded")
} else {
if got, want := err.Error(), "unexpected status code 500 while pushing to "+pgwErr.URL+"/metrics/job/testjob: fake error\n"; got != want {
@@ -135,22 +132,39 @@ func TestPush(t *testing.T) {
}
}
- // PushCollectors with invalid grouping or job.
- if err := Collectors("testjob", map[string]string{"foo": "bums"}, pgwErr.URL, metric1, metric2); err == nil {
+ // Push some Collectors with invalid grouping or job.
+ if err := New(pgwOK.URL, "testjob").
+ Grouping("foo", "bums").
+ Collector(metric1).
+ Collector(metric2).
+ Push(); err == nil {
t.Error("push with grouping contained in metrics succeeded")
}
- if err := Collectors("test/job", nil, pgwErr.URL, metric1, metric2); err == nil {
+ if err := New(pgwOK.URL, "test/job").
+ Collector(metric1).
+ Collector(metric2).
+ Push(); err == nil {
t.Error("push with invalid job value succeeded")
}
- if err := Collectors("testjob", map[string]string{"foo/bar": "bums"}, pgwErr.URL, metric1, metric2); err == nil {
+ if err := New(pgwOK.URL, "testjob").
+ Grouping("foobar", "bu/ms").
+ Collector(metric1).
+ Collector(metric2).
+ Push(); err == nil {
t.Error("push with invalid grouping succeeded")
}
- if err := Collectors("testjob", map[string]string{"foo-bar": "bums"}, pgwErr.URL, metric1, metric2); err == nil {
+ if err := New(pgwOK.URL, "testjob").
+ Grouping("foo-bar", "bums").
+ Collector(metric1).
+ Collector(metric2).
+ Push(); err == nil {
t.Error("push with invalid grouping succeeded")
}
// Push registry, all good.
- if err := FromGatherer("testjob", HostnameGroupingKey(), pgwOK.URL, reg); err != nil {
+ if err := New(pgwOK.URL, "testjob").
+ Gatherer(reg).
+ Push(); err != nil {
t.Fatal(err)
}
if lastMethod != "PUT" {
@@ -160,12 +174,16 @@ func TestPush(t *testing.T) {
t.Errorf("got body %v, want %v", lastBody, wantBody)
}
- // PushAdd registry, all good.
- if err := AddFromGatherer("testjob", map[string]string{"a": "x", "b": "y"}, pgwOK.URL, reg); err != nil {
+ // Add registry, all good.
+ if err := New(pgwOK.URL, "testjob").
+ Grouping("a", "x").
+ Grouping("b", "y").
+ Gatherer(reg).
+ Add(); err != nil {
t.Fatal(err)
}
if lastMethod != "POST" {
- t.Error("want method POSTT for PushAdd, got", lastMethod)
+ t.Error("want method POST for Add, got", lastMethod)
}
if bytes.Compare(lastBody, wantBody) != 0 {
t.Errorf("got body %v, want %v", lastBody, wantBody)
diff --git a/vendor/github.com/prometheus/client_golang/prometheus/registry.go b/vendor/github.com/prometheus/client_golang/prometheus/registry.go
index c84a4420e..bee370364 100644
--- a/vendor/github.com/prometheus/client_golang/prometheus/registry.go
+++ b/vendor/github.com/prometheus/client_golang/prometheus/registry.go
@@ -18,6 +18,7 @@ import (
"errors"
"fmt"
"os"
+ "runtime"
"sort"
"sync"
"unicode/utf8"
@@ -36,13 +37,13 @@ const (
// DefaultRegisterer and DefaultGatherer are the implementations of the
// Registerer and Gatherer interface a number of convenience functions in this
// package act on. Initially, both variables point to the same Registry, which
-// has a process collector (see NewProcessCollector) and a Go collector (see
-// NewGoCollector) already registered. This approach to keep default instances
-// as global state mirrors the approach of other packages in the Go standard
-// library. Note that there are caveats. Change the variables with caution and
-// only if you understand the consequences. Users who want to avoid global state
-// altogether should not use the convenience function and act on custom
-// instances instead.
+// has a process collector (currently on Linux only, see NewProcessCollector)
+// and a Go collector (see NewGoCollector) already registered. This approach to
+// keep default instances as global state mirrors the approach of other packages
+// in the Go standard library. Note that there are caveats. Change the variables
+// with caution and only if you understand the consequences. Users who want to
+// avoid global state altogether should not use the convenience functions and
+// act on custom instances instead.
var (
defaultRegistry = NewRegistry()
DefaultRegisterer Registerer = defaultRegistry
@@ -202,6 +203,13 @@ func (errs MultiError) Error() string {
return buf.String()
}
+// Append appends the provided error if it is not nil.
+func (errs *MultiError) Append(err error) {
+ if err != nil {
+ *errs = append(*errs, err)
+ }
+}
+
// MaybeUnwrap returns nil if len(errs) is 0. It returns the first and only
// contained error as error if len(errs is 1). In all other cases, it returns
// the MultiError directly. This is helpful for returning a MultiError in a way
@@ -368,22 +376,12 @@ func (r *Registry) Gather() ([]*dto.MetricFamily, error) {
)
r.mtx.RLock()
+ goroutineBudget := len(r.collectorsByID)
metricFamiliesByName := make(map[string]*dto.MetricFamily, len(r.dimHashesByName))
-
- // Scatter.
- // (Collectors could be complex and slow, so we call them all at once.)
- wg.Add(len(r.collectorsByID))
- go func() {
- wg.Wait()
- close(metricChan)
- }()
+ collectors := make(chan Collector, len(r.collectorsByID))
for _, collector := range r.collectorsByID {
- go func(collector Collector) {
- defer wg.Done()
- collector.Collect(metricChan)
- }(collector)
+ collectors <- collector
}
-
// In case pedantic checks are enabled, we have to copy the map before
// giving up the RLock.
if r.pedanticChecksEnabled {
@@ -392,127 +390,174 @@ func (r *Registry) Gather() ([]*dto.MetricFamily, error) {
registeredDescIDs[id] = struct{}{}
}
}
-
r.mtx.RUnlock()
+ wg.Add(goroutineBudget)
+
+ collectWorker := func() {
+ for {
+ select {
+ case collector := <-collectors:
+ collector.Collect(metricChan)
+ wg.Done()
+ default:
+ return
+ }
+ }
+ }
+
+ // Start the first worker now to make sure at least one is running.
+ go collectWorker()
+ goroutineBudget--
+
+ // Close the metricChan once all collectors are collected.
+ go func() {
+ wg.Wait()
+ close(metricChan)
+ }()
+
// Drain metricChan in case of premature return.
defer func() {
for range metricChan {
}
}()
- // Gather.
- for metric := range metricChan {
- // This could be done concurrently, too, but it required locking
- // of metricFamiliesByName (and of metricHashes if checks are
- // enabled). Most likely not worth it.
- desc := metric.Desc()
- dtoMetric := &dto.Metric{}
- if err := metric.Write(dtoMetric); err != nil {
- errs = append(errs, fmt.Errorf(
- "error collecting metric %v: %s", desc, err,
- ))
- continue
- }
- metricFamily, ok := metricFamiliesByName[desc.fqName]
- if ok {
- if metricFamily.GetHelp() != desc.help {
- errs = append(errs, fmt.Errorf(
- "collected metric %s %s has help %q but should have %q",
- desc.fqName, dtoMetric, desc.help, metricFamily.GetHelp(),
- ))
- continue
+collectLoop:
+ for {
+ select {
+ case metric, ok := <-metricChan:
+ if !ok {
+ // metricChan is closed, we are done.
+ break collectLoop
}
- // TODO(beorn7): Simplify switch once Desc has type.
- switch metricFamily.GetType() {
- case dto.MetricType_COUNTER:
- if dtoMetric.Counter == nil {
- errs = append(errs, fmt.Errorf(
- "collected metric %s %s should be a Counter",
- desc.fqName, dtoMetric,
- ))
- continue
- }
- case dto.MetricType_GAUGE:
- if dtoMetric.Gauge == nil {
- errs = append(errs, fmt.Errorf(
- "collected metric %s %s should be a Gauge",
- desc.fqName, dtoMetric,
- ))
- continue
- }
- case dto.MetricType_SUMMARY:
- if dtoMetric.Summary == nil {
- errs = append(errs, fmt.Errorf(
- "collected metric %s %s should be a Summary",
- desc.fqName, dtoMetric,
- ))
- continue
- }
- case dto.MetricType_UNTYPED:
- if dtoMetric.Untyped == nil {
- errs = append(errs, fmt.Errorf(
- "collected metric %s %s should be Untyped",
- desc.fqName, dtoMetric,
- ))
- continue
- }
- case dto.MetricType_HISTOGRAM:
- if dtoMetric.Histogram == nil {
- errs = append(errs, fmt.Errorf(
- "collected metric %s %s should be a Histogram",
- desc.fqName, dtoMetric,
+ errs.Append(processMetric(
+ metric, metricFamiliesByName,
+ metricHashes, dimHashes,
+ registeredDescIDs,
+ ))
+ default:
+ if goroutineBudget <= 0 || len(collectors) == 0 {
+ // All collectors are aleady being worked on or
+ // we have already as many goroutines started as
+ // there are collectors. Just process metrics
+ // from now on.
+ for metric := range metricChan {
+ errs.Append(processMetric(
+ metric, metricFamiliesByName,
+ metricHashes, dimHashes,
+ registeredDescIDs,
))
- continue
}
- default:
- panic("encountered MetricFamily with invalid type")
+ break collectLoop
}
- } else {
- metricFamily = &dto.MetricFamily{}
- metricFamily.Name = proto.String(desc.fqName)
- metricFamily.Help = proto.String(desc.help)
- // TODO(beorn7): Simplify switch once Desc has type.
- switch {
- case dtoMetric.Gauge != nil:
- metricFamily.Type = dto.MetricType_GAUGE.Enum()
- case dtoMetric.Counter != nil:
- metricFamily.Type = dto.MetricType_COUNTER.Enum()
- case dtoMetric.Summary != nil:
- 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:
- errs = append(errs, fmt.Errorf(
- "empty metric collected: %s", dtoMetric,
- ))
- continue
- }
- metricFamiliesByName[desc.fqName] = metricFamily
+ // Start more workers.
+ go collectWorker()
+ goroutineBudget--
+ runtime.Gosched()
}
- if err := checkMetricConsistency(metricFamily, dtoMetric, metricHashes, dimHashes); err != nil {
- errs = append(errs, err)
- continue
+ }
+ return normalizeMetricFamilies(metricFamiliesByName), errs.MaybeUnwrap()
+}
+
+// processMetric is an internal helper method only used by the Gather method.
+func processMetric(
+ metric Metric,
+ metricFamiliesByName map[string]*dto.MetricFamily,
+ metricHashes map[uint64]struct{},
+ dimHashes map[string]uint64,
+ registeredDescIDs map[uint64]struct{},
+) error {
+ desc := metric.Desc()
+ dtoMetric := &dto.Metric{}
+ if err := metric.Write(dtoMetric); err != nil {
+ return fmt.Errorf("error collecting metric %v: %s", desc, err)
+ }
+ metricFamily, ok := metricFamiliesByName[desc.fqName]
+ if ok {
+ if metricFamily.GetHelp() != desc.help {
+ return fmt.Errorf(
+ "collected metric %s %s has help %q but should have %q",
+ desc.fqName, dtoMetric, desc.help, metricFamily.GetHelp(),
+ )
}
- if r.pedanticChecksEnabled {
- // Is the desc registered at all?
- if _, exist := registeredDescIDs[desc.id]; !exist {
- errs = append(errs, fmt.Errorf(
- "collected metric %s %s with unregistered descriptor %s",
- metricFamily.GetName(), dtoMetric, desc,
- ))
- continue
+ // TODO(beorn7): Simplify switch once Desc has type.
+ switch metricFamily.GetType() {
+ case dto.MetricType_COUNTER:
+ if dtoMetric.Counter == nil {
+ return fmt.Errorf(
+ "collected metric %s %s should be a Counter",
+ desc.fqName, dtoMetric,
+ )
+ }
+ case dto.MetricType_GAUGE:
+ if dtoMetric.Gauge == nil {
+ return fmt.Errorf(
+ "collected metric %s %s should be a Gauge",
+ desc.fqName, dtoMetric,
+ )
+ }
+ case dto.MetricType_SUMMARY:
+ if dtoMetric.Summary == nil {
+ return fmt.Errorf(
+ "collected metric %s %s should be a Summary",
+ desc.fqName, dtoMetric,
+ )
}
- if err := checkDescConsistency(metricFamily, dtoMetric, desc); err != nil {
- errs = append(errs, err)
- continue
+ case dto.MetricType_UNTYPED:
+ if dtoMetric.Untyped == nil {
+ return fmt.Errorf(
+ "collected metric %s %s should be Untyped",
+ desc.fqName, dtoMetric,
+ )
}
+ case dto.MetricType_HISTOGRAM:
+ if dtoMetric.Histogram == nil {
+ return fmt.Errorf(
+ "collected metric %s %s should be a Histogram",
+ desc.fqName, dtoMetric,
+ )
+ }
+ default:
+ panic("encountered MetricFamily with invalid type")
+ }
+ } else {
+ metricFamily = &dto.MetricFamily{}
+ metricFamily.Name = proto.String(desc.fqName)
+ metricFamily.Help = proto.String(desc.help)
+ // TODO(beorn7): Simplify switch once Desc has type.
+ switch {
+ case dtoMetric.Gauge != nil:
+ metricFamily.Type = dto.MetricType_GAUGE.Enum()
+ case dtoMetric.Counter != nil:
+ metricFamily.Type = dto.MetricType_COUNTER.Enum()
+ case dtoMetric.Summary != nil:
+ 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 fmt.Errorf("empty metric collected: %s", dtoMetric)
}
- metricFamily.Metric = append(metricFamily.Metric, dtoMetric)
+ metricFamiliesByName[desc.fqName] = metricFamily
}
- return normalizeMetricFamilies(metricFamiliesByName), errs.MaybeUnwrap()
+ if err := checkMetricConsistency(metricFamily, dtoMetric, metricHashes, dimHashes); err != nil {
+ return err
+ }
+ if registeredDescIDs != nil {
+ // Is the desc registered at all?
+ if _, exist := registeredDescIDs[desc.id]; !exist {
+ return fmt.Errorf(
+ "collected metric %s %s with unregistered descriptor %s",
+ metricFamily.GetName(), dtoMetric, desc,
+ )
+ }
+ if err := checkDescConsistency(metricFamily, dtoMetric, desc); err != nil {
+ return err
+ }
+ }
+ metricFamily.Metric = append(metricFamily.Metric, dtoMetric)
+ return nil
}
// Gatherers is a slice of Gatherer instances that implements the Gatherer
diff --git a/vendor/github.com/prometheus/procfs/fs.go b/vendor/github.com/prometheus/procfs/fs.go
index 36c1586a1..65f09223f 100644
--- a/vendor/github.com/prometheus/procfs/fs.go
+++ b/vendor/github.com/prometheus/procfs/fs.go
@@ -46,8 +46,8 @@ func (fs FS) XFSStats() (*xfs.Stats, error) {
return xfs.ParseStats(f)
}
-// NFSdClientRPCStats retrieves NFS daemon RPC statistics.
-func (fs FS) NFSdClientRPCStats() (*nfs.ClientRPCStats, error) {
+// NFSClientRPCStats retrieves NFS client RPC statistics.
+func (fs FS) NFSClientRPCStats() (*nfs.ClientRPCStats, error) {
f, err := os.Open(fs.Path("net/rpc/nfs"))
if err != nil {
return nil, err
diff --git a/vendor/github.com/prometheus/procfs/nfs/parse.go b/vendor/github.com/prometheus/procfs/nfs/parse.go
index 3aa32563a..8f568f011 100644
--- a/vendor/github.com/prometheus/procfs/nfs/parse.go
+++ b/vendor/github.com/prometheus/procfs/nfs/parse.go
@@ -178,8 +178,17 @@ func parseV3Stats(v []uint64) (V3Stats, error) {
func parseClientV4Stats(v []uint64) (ClientV4Stats, error) {
values := int(v[0])
- if len(v[1:]) != values || values < 59 {
- return ClientV4Stats{}, fmt.Errorf("invalid V4Stats line %q", v)
+ if len(v[1:]) != values {
+ return ClientV4Stats{}, fmt.Errorf("invalid ClientV4Stats line %q", v)
+ }
+
+ // This function currently supports mapping 59 NFS v4 client stats. Older
+ // kernels may emit fewer stats, so we must detect this and pad out the
+ // values to match the expected slice size.
+ if values < 59 {
+ newValues := make([]uint64, 60)
+ copy(newValues, v)
+ v = newValues
}
return ClientV4Stats{
diff --git a/vendor/github.com/prometheus/procfs/nfs/parse_nfs.go b/vendor/github.com/prometheus/procfs/nfs/parse_nfs.go
index b5c0b15f3..c0d3a5ad9 100644
--- a/vendor/github.com/prometheus/procfs/nfs/parse_nfs.go
+++ b/vendor/github.com/prometheus/procfs/nfs/parse_nfs.go
@@ -32,12 +32,12 @@ func ParseClientRPCStats(r io.Reader) (*ClientRPCStats, error) {
parts := strings.Fields(scanner.Text())
// require at least <key> <value>
if len(parts) < 2 {
- return nil, fmt.Errorf("invalid NFSd metric line %q", line)
+ return nil, fmt.Errorf("invalid NFS metric line %q", line)
}
values, err := util.ParseUint64s(parts[1:])
if err != nil {
- return nil, fmt.Errorf("error parsing NFSd metric line: %s", err)
+ return nil, fmt.Errorf("error parsing NFS metric line: %s", err)
}
switch metricLine := parts[0]; metricLine {
@@ -52,15 +52,15 @@ func ParseClientRPCStats(r io.Reader) (*ClientRPCStats, error) {
case "proc4":
stats.ClientV4Stats, err = parseClientV4Stats(values)
default:
- return nil, fmt.Errorf("unknown NFSd metric line %q", metricLine)
+ return nil, fmt.Errorf("unknown NFS metric line %q", metricLine)
}
if err != nil {
- return nil, fmt.Errorf("errors parsing NFSd metric line: %s", err)
+ return nil, fmt.Errorf("errors parsing NFS metric line: %s", err)
}
}
if err := scanner.Err(); err != nil {
- return nil, fmt.Errorf("error scanning NFSd file: %s", err)
+ return nil, fmt.Errorf("error scanning NFS file: %s", err)
}
return stats, nil
diff --git a/vendor/github.com/prometheus/procfs/nfs/parse_nfs_test.go b/vendor/github.com/prometheus/procfs/nfs/parse_nfs_test.go
index d821f1b4c..afe3db47e 100644
--- a/vendor/github.com/prometheus/procfs/nfs/parse_nfs_test.go
+++ b/vendor/github.com/prometheus/procfs/nfs/parse_nfs_test.go
@@ -21,7 +21,7 @@ import (
"github.com/prometheus/procfs/nfs"
)
-func TestNewNFSdClientRPCStats(t *testing.T) {
+func TestNewNFSClientRPCStats(t *testing.T) {
tests := []struct {
name string
content string
@@ -33,6 +33,131 @@ func TestNewNFSdClientRPCStats(t *testing.T) {
content: "invalid",
invalid: true,
}, {
+ name: "good old kernel version file",
+ content: `net 70 70 69 45
+rpc 1218785755 374636 1218815394
+proc2 18 16 57 74 52 71 73 45 86 0 52 83 61 17 53 50 23 70 82
+proc3 22 0 1061909262 48906 4077635 117661341 5 29391916 2570425 2993289 590 0 0 7815 15 1130 0 3983 92385 13332 2 1 23729
+proc4 48 98 51 54 83 85 23 24 1 28 73 68 83 12 84 39 68 59 58 88 29 74 69 96 21 84 15 53 86 54 66 56 97 36 49 32 85 81 11 58 32 67 13 28 35 90 1 26 1337
+`,
+ stats: &nfs.ClientRPCStats{
+ Network: nfs.Network{
+ NetCount: 70,
+ UDPCount: 70,
+ TCPCount: 69,
+ TCPConnect: 45,
+ },
+ ClientRPC: nfs.ClientRPC{
+ RPCCount: 1218785755,
+ Retransmissions: 374636,
+ AuthRefreshes: 1218815394,
+ },
+ V2Stats: nfs.V2Stats{
+ Null: 16,
+ GetAttr: 57,
+ SetAttr: 74,
+ Root: 52,
+ Lookup: 71,
+ ReadLink: 73,
+ Read: 45,
+ WrCache: 86,
+ Write: 0,
+ Create: 52,
+ Remove: 83,
+ Rename: 61,
+ Link: 17,
+ SymLink: 53,
+ MkDir: 50,
+ RmDir: 23,
+ ReadDir: 70,
+ FsStat: 82,
+ },
+ V3Stats: nfs.V3Stats{
+ Null: 0,
+ GetAttr: 1061909262,
+ SetAttr: 48906,
+ Lookup: 4077635,
+ Access: 117661341,
+ ReadLink: 5,
+ Read: 29391916,
+ Write: 2570425,
+ Create: 2993289,
+ MkDir: 590,
+ SymLink: 0,
+ MkNod: 0,
+ Remove: 7815,
+ RmDir: 15,
+ Rename: 1130,
+ Link: 0,
+ ReadDir: 3983,
+ ReadDirPlus: 92385,
+ FsStat: 13332,
+ FsInfo: 2,
+ PathConf: 1,
+ Commit: 23729, },
+ ClientV4Stats: nfs.ClientV4Stats{
+ Null: 98,
+ Read: 51,
+ Write: 54,
+ Commit: 83,
+ Open: 85,
+ OpenConfirm: 23,
+ OpenNoattr: 24,
+ OpenDowngrade: 1,
+ Close: 28,
+ Setattr: 73,
+ FsInfo: 68,
+ Renew: 83,
+ SetClientId: 12,
+ SetClientIdConfirm: 84,
+ Lock: 39,
+ Lockt: 68,
+ Locku: 59,
+ Access: 58,
+ Getattr: 88,
+ Lookup: 29,
+ LookupRoot: 74,
+ Remove: 69,
+ Rename: 96,
+ Link: 21,
+ Symlink: 84,
+ Create: 15,
+ Pathconf: 53,
+ StatFs: 86,
+ ReadLink: 54,
+ ReadDir: 66,
+ ServerCaps: 56,
+ DelegReturn: 97,
+ GetAcl: 36,
+ SetAcl: 49,
+ FsLocations: 32,
+ ReleaseLockowner: 85,
+ Secinfo: 81,
+ FsidPresent: 11,
+ ExchangeId: 58,
+ CreateSession: 32,
+ DestroySession: 67,
+ Sequence: 13,
+ GetLeaseTime: 28,
+ ReclaimComplete: 35,
+ LayoutGet: 90,
+ GetDeviceInfo: 1,
+ LayoutCommit: 26,
+ LayoutReturn: 1337,
+ SecinfoNoName: 0,
+ TestStateId: 0,
+ FreeStateId: 0,
+ GetDeviceList: 0,
+ BindConnToSession: 0,
+ DestroyClientId: 0,
+ Seek: 0,
+ Allocate: 0,
+ DeAllocate: 0,
+ LayoutStats: 0,
+ Clone: 0,
+ },
+ },
+ }, {
name: "good file",
content: `net 18628 0 18628 6
rpc 4329785 0 4338291