Browse code

vendor: bump etcd deps

As per https://github.com/etcd-io/etcd/blob/fa57f7fbc787b4/Gopkg.lock

List of packages required by subset of etcd used is provided by:

go list -f '{{join .Deps "\n"}}' \
github.com/docker/docker/vendor/github.com/coreos/etcd/... \
| grep -F . | grep -v coreos/etcd | sort | uniq

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>

Kir Kolyshkin authored on 2018/09/11 04:22:57
Showing 63 changed files
... ...
@@ -135,16 +135,16 @@ golang.org/x/time fbb02b2291d28baffd63558aa44b4b56f178d650
135 135
 github.com/hashicorp/go-memdb cb9a474f84cc5e41b273b20c6927680b2a8776ad
136 136
 github.com/hashicorp/go-immutable-radix 826af9ccf0feeee615d546d69b11f8e98da8c8f1 git://github.com/tonistiigi/go-immutable-radix.git
137 137
 github.com/hashicorp/golang-lru 0fb14efe8c47ae851c0034ed7a448854d3d34cf3
138
-github.com/coreos/pkg fa29b1d70f0beaddd4c7021607cc3c3be8ce94b8
138
+github.com/coreos/pkg v3
139 139
 github.com/pivotal-golang/clock 3fd3c1944c59d9742e1cd333672181cd1a6f9fa0
140
-github.com/prometheus/client_golang 52437c81da6b127a9925d17eb3a382a2e5fd395e
141
-github.com/beorn7/perks 4c0e84591b9aa9e6dcfdf3e020114cd81f89d5f9
142
-github.com/prometheus/client_model fa8ad6fec33561be4280a8f0514318c79d7f6cb6
143
-github.com/prometheus/common ebdfc6da46522d58825777cf1f90490a5b1ef1d8
144
-github.com/prometheus/procfs abf152e5f3e97f2fafac028d2cc06c1feb87ffa5
140
+github.com/prometheus/client_golang v0.8.0
141
+github.com/beorn7/perks 3a771d992973f24aa725d07868b467d1ddfceaf
142
+github.com/prometheus/client_model 6f3806018612930941127f2a7c6c453ba2c527d2
143
+github.com/prometheus/common 7600349dcfe1abd18d72d3a1770870d9800a7801
144
+github.com/prometheus/procfs 7d6f385de8bea29190f15ba9931442a0eaef9af7
145 145
 github.com/matttproud/golang_protobuf_extensions v1.0.0
146 146
 github.com/pkg/errors 839d9e913e063e28dfd0e6c7b7512793e0a48be9
147
-github.com/grpc-ecosystem/go-grpc-prometheus 6b7015e65d366bf3f19b2b2a000a831940f0f7e0
147
+github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
148 148
 
149 149
 # cli
150 150
 github.com/spf13/cobra v0.0.3
... ...
@@ -77,15 +77,20 @@ func NewHighBiased(epsilon float64) *Stream {
77 77
 // is guaranteed to be within (Quantile±Epsilon).
78 78
 //
79 79
 // See http://www.cs.rutgers.edu/~muthu/bquant.pdf for time, space, and error properties.
80
-func NewTargeted(targets map[float64]float64) *Stream {
80
+func NewTargeted(targetMap map[float64]float64) *Stream {
81
+	// Convert map to slice to avoid slow iterations on a map.
82
+	// ƒ is called on the hot path, so converting the map to a slice
83
+	// beforehand results in significant CPU savings.
84
+	targets := targetMapToSlice(targetMap)
85
+
81 86
 	ƒ := func(s *stream, r float64) float64 {
82 87
 		var m = math.MaxFloat64
83 88
 		var f float64
84
-		for quantile, epsilon := range targets {
85
-			if quantile*s.n <= r {
86
-				f = (2 * epsilon * r) / quantile
89
+		for _, t := range targets {
90
+			if t.quantile*s.n <= r {
91
+				f = (2 * t.epsilon * r) / t.quantile
87 92
 			} else {
88
-				f = (2 * epsilon * (s.n - r)) / (1 - quantile)
93
+				f = (2 * t.epsilon * (s.n - r)) / (1 - t.quantile)
89 94
 			}
90 95
 			if f < m {
91 96
 				m = f
... ...
@@ -96,6 +101,25 @@ func NewTargeted(targets map[float64]float64) *Stream {
96 96
 	return newStream(ƒ)
97 97
 }
98 98
 
99
+type target struct {
100
+	quantile float64
101
+	epsilon  float64
102
+}
103
+
104
+func targetMapToSlice(targetMap map[float64]float64) []target {
105
+	targets := make([]target, 0, len(targetMap))
106
+
107
+	for quantile, epsilon := range targetMap {
108
+		t := target{
109
+			quantile: quantile,
110
+			epsilon:  epsilon,
111
+		}
112
+		targets = append(targets, t)
113
+	}
114
+
115
+	return targets
116
+}
117
+
99 118
 // Stream computes quantiles for a stream of float64s. It is not thread-safe by
100 119
 // design. Take care when using across multiple goroutines.
101 120
 type Stream struct {
... ...
@@ -3,6 +3,8 @@
3 3
 [![Travis Build](https://travis-ci.org/grpc-ecosystem/go-grpc-prometheus.svg)](https://travis-ci.org/grpc-ecosystem/go-grpc-prometheus)
4 4
 [![Go Report Card](https://goreportcard.com/badge/github.com/grpc-ecosystem/go-grpc-prometheus)](http://goreportcard.com/report/grpc-ecosystem/go-grpc-prometheus)
5 5
 [![GoDoc](http://img.shields.io/badge/GoDoc-Reference-blue.svg)](https://godoc.org/github.com/grpc-ecosystem/go-grpc-prometheus)
6
+[![SourceGraph](https://sourcegraph.com/github.com/grpc-ecosystem/go-grpc-prometheus/-/badge.svg)](https://sourcegraph.com/github.com/grpc-ecosystem/go-grpc-prometheus/?badge)
7
+[![codecov](https://codecov.io/gh/grpc-ecosystem/go-grpc-prometheus/branch/master/graph/badge.svg)](https://codecov.io/gh/grpc-ecosystem/go-grpc-prometheus)
6 8
 [![Apache 2.0 License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE)
7 9
 
8 10
 [Prometheus](https://prometheus.io/) monitoring for your [gRPC Go](https://github.com/grpc/grpc-go) servers and clients.
... ...
@@ -36,7 +38,7 @@ import "github.com/grpc-ecosystem/go-grpc-prometheus"
36 36
     // After all your registrations, make sure all of the Prometheus metrics are initialized.
37 37
     grpc_prometheus.Register(myServer)
38 38
     // Register Prometheus metrics handler.    
39
-    http.Handle("/metrics", prometheus.Handler())
39
+    http.Handle("/metrics", promhttp.Handler())
40 40
 ...
41 41
 ```
42 42
 
... ...
@@ -47,8 +49,8 @@ import "github.com/grpc-ecosystem/go-grpc-prometheus"
47 47
 ...
48 48
    clientConn, err = grpc.Dial(
49 49
        address,
50
-		   grpc.WithUnaryInterceptor(UnaryClientInterceptor),
51
-		   grpc.WithStreamInterceptor(StreamClientInterceptor)
50
+		   grpc.WithUnaryInterceptor(grpc_prometheus.UnaryClientInterceptor),
51
+		   grpc.WithStreamInterceptor(grpc_prometheus.StreamClientInterceptor)
52 52
    )
53 53
    client = pb_testproto.NewTestServiceClient(clientConn)
54 54
    resp, err := client.PingEmpty(s.ctx, &myservice.Request{Msg: "hello"})
... ...
@@ -116,7 +118,7 @@ each of the 20 messages sent back, a counter will be incremented:
116 116
 grpc_server_msg_sent_total{grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream"} 20
117 117
 ```
118 118
 
119
-After the call completes, it's status (`OK` or other [gRPC status code](https://github.com/grpc/grpc-go/blob/master/codes/codes.go)) 
119
+After the call completes, its status (`OK` or other [gRPC status code](https://github.com/grpc/grpc-go/blob/master/codes/codes.go)) 
120 120
 and the relevant call labels increment the `grpc_server_handled_total` counter.
121 121
 
122 122
 ```jsoniq
... ...
@@ -126,8 +128,8 @@ grpc_server_handled_total{grpc_code="OK",grpc_method="PingList",grpc_service="mw
126 126
 ## Histograms
127 127
 
128 128
 [Prometheus histograms](https://prometheus.io/docs/concepts/metric_types/#histogram) are a great way
129
-to measure latency distributions of your RPCs. However since it is bad practice to have metrics
130
-of [high cardinality](https://prometheus.io/docs/practices/instrumentation/#do-not-overuse-labels))
129
+to measure latency distributions of your RPCs. However, since it is bad practice to have metrics
130
+of [high cardinality](https://prometheus.io/docs/practices/instrumentation/#do-not-overuse-labels)
131 131
 the latency monitoring metrics are disabled by default. To enable them please call the following
132 132
 in your server initialization code:
133 133
 
... ...
@@ -135,8 +137,8 @@ in your server initialization code:
135 135
 grpc_prometheus.EnableHandlingTimeHistogram()
136 136
 ```
137 137
 
138
-After the call completes, it's handling time will be recorded in a [Prometheus histogram](https://prometheus.io/docs/concepts/metric_types/#histogram)
139
-variable `grpc_server_handling_seconds`. It contains three sub-metrics:
138
+After the call completes, its handling time will be recorded in a [Prometheus histogram](https://prometheus.io/docs/concepts/metric_types/#histogram)
139
+variable `grpc_server_handling_seconds`. The histogram variable contains three sub-metrics:
140 140
 
141 141
  * `grpc_server_handling_seconds_count` - the count of all completed RPCs by status and method 
142 142
  * `grpc_server_handling_seconds_sum` - cumulative time of RPCs by status and method, useful for 
... ...
@@ -166,7 +168,7 @@ grpc_server_handling_seconds_count{grpc_code="OK",grpc_method="PingList",grpc_se
166 166
 
167 167
 ## Useful query examples
168 168
 
169
-Prometheus philosophy is to provide the most detailed metrics possible to the monitoring system, and
169
+Prometheus philosophy is to provide raw metrics to the monitoring system, and
170 170
 let the aggregations be handled there. The verbosity of above metrics make it possible to have that
171 171
 flexibility. Here's a couple of useful monitoring queries:
172 172
 
... ...
@@ -6,67 +6,34 @@
6 6
 package grpc_prometheus
7 7
 
8 8
 import (
9
-	"io"
10
-
11
-	"golang.org/x/net/context"
12
-	"google.golang.org/grpc"
13
-	"google.golang.org/grpc/codes"
9
+	prom "github.com/prometheus/client_golang/prometheus"
14 10
 )
15 11
 
16
-// UnaryClientInterceptor is a gRPC client-side interceptor that provides Prometheus monitoring for Unary RPCs.
17
-func UnaryClientInterceptor(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
18
-	monitor := newClientReporter(Unary, method)
19
-	monitor.SentMessage()
20
-	err := invoker(ctx, method, req, reply, cc, opts...)
21
-	if err != nil {
22
-		monitor.ReceivedMessage()
23
-	}
24
-	monitor.Handled(grpc.Code(err))
25
-	return err
26
-}
12
+var (
13
+	// DefaultClientMetrics is the default instance of ClientMetrics. It is
14
+	// intended to be used in conjunction the default Prometheus metrics
15
+	// registry.
16
+	DefaultClientMetrics = NewClientMetrics()
27 17
 
28
-// StreamServerInterceptor is a gRPC client-side interceptor that provides Prometheus monitoring for Streaming RPCs.
29
-func StreamClientInterceptor(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) {
30
-	monitor := newClientReporter(clientStreamType(desc), method)
31
-	clientStream, err := streamer(ctx, desc, cc, method, opts...)
32
-	if err != nil {
33
-		monitor.Handled(grpc.Code(err))
34
-		return nil, err
35
-	}
36
-	return &monitoredClientStream{clientStream, monitor}, nil
37
-}
18
+	// UnaryClientInterceptor is a gRPC client-side interceptor that provides Prometheus monitoring for Unary RPCs.
19
+	UnaryClientInterceptor = DefaultClientMetrics.UnaryClientInterceptor()
38 20
 
39
-func clientStreamType(desc *grpc.StreamDesc) grpcType {
40
-	if desc.ClientStreams && !desc.ServerStreams {
41
-		return ClientStream
42
-	} else if !desc.ClientStreams && desc.ServerStreams {
43
-		return ServerStream
44
-	}
45
-	return BidiStream
46
-}
47
-
48
-// monitoredClientStream wraps grpc.ClientStream allowing each Sent/Recv of message to increment counters.
49
-type monitoredClientStream struct {
50
-	grpc.ClientStream
51
-	monitor *clientReporter
52
-}
21
+	// StreamClientInterceptor is a gRPC client-side interceptor that provides Prometheus monitoring for Streaming RPCs.
22
+	StreamClientInterceptor = DefaultClientMetrics.StreamClientInterceptor()
23
+)
53 24
 
54
-func (s *monitoredClientStream) SendMsg(m interface{}) error {
55
-	err := s.ClientStream.SendMsg(m)
56
-	if err == nil {
57
-		s.monitor.SentMessage()
58
-	}
59
-	return err
25
+func init() {
26
+	prom.MustRegister(DefaultClientMetrics.clientStartedCounter)
27
+	prom.MustRegister(DefaultClientMetrics.clientHandledCounter)
28
+	prom.MustRegister(DefaultClientMetrics.clientStreamMsgReceived)
29
+	prom.MustRegister(DefaultClientMetrics.clientStreamMsgSent)
60 30
 }
61 31
 
62
-func (s *monitoredClientStream) RecvMsg(m interface{}) error {
63
-	err := s.ClientStream.RecvMsg(m)
64
-	if err == nil {
65
-		s.monitor.ReceivedMessage()
66
-	} else if err == io.EOF {
67
-		s.monitor.Handled(codes.OK)
68
-	} else {
69
-		s.monitor.Handled(grpc.Code(err))
70
-	}
71
-	return err
32
+// EnableClientHandlingTimeHistogram turns on recording of handling time of
33
+// RPCs. Histogram metrics can be very expensive for Prometheus to retain and
34
+// query. This function acts on the DefaultClientMetrics variable and the
35
+// default Prometheus metrics registry.
36
+func EnableClientHandlingTimeHistogram(opts ...HistogramOption) {
37
+	DefaultClientMetrics.EnableClientHandlingTimeHistogram(opts...)
38
+	prom.Register(DefaultClientMetrics.clientHandledHistogram)
72 39
 }
73 40
new file mode 100644
... ...
@@ -0,0 +1,170 @@
0
+package grpc_prometheus
1
+
2
+import (
3
+	"io"
4
+
5
+	prom "github.com/prometheus/client_golang/prometheus"
6
+	"golang.org/x/net/context"
7
+	"google.golang.org/grpc"
8
+	"google.golang.org/grpc/codes"
9
+	"google.golang.org/grpc/status"
10
+)
11
+
12
+// ClientMetrics represents a collection of metrics to be registered on a
13
+// Prometheus metrics registry for a gRPC client.
14
+type ClientMetrics struct {
15
+	clientStartedCounter          *prom.CounterVec
16
+	clientHandledCounter          *prom.CounterVec
17
+	clientStreamMsgReceived       *prom.CounterVec
18
+	clientStreamMsgSent           *prom.CounterVec
19
+	clientHandledHistogramEnabled bool
20
+	clientHandledHistogramOpts    prom.HistogramOpts
21
+	clientHandledHistogram        *prom.HistogramVec
22
+}
23
+
24
+// NewClientMetrics returns a ClientMetrics object. Use a new instance of
25
+// ClientMetrics when not using the default Prometheus metrics registry, for
26
+// example when wanting to control which metrics are added to a registry as
27
+// opposed to automatically adding metrics via init functions.
28
+func NewClientMetrics(counterOpts ...CounterOption) *ClientMetrics {
29
+	opts := counterOptions(counterOpts)
30
+	return &ClientMetrics{
31
+		clientStartedCounter: prom.NewCounterVec(
32
+			opts.apply(prom.CounterOpts{
33
+				Name: "grpc_client_started_total",
34
+				Help: "Total number of RPCs started on the client.",
35
+			}), []string{"grpc_type", "grpc_service", "grpc_method"}),
36
+
37
+		clientHandledCounter: prom.NewCounterVec(
38
+			opts.apply(prom.CounterOpts{
39
+				Name: "grpc_client_handled_total",
40
+				Help: "Total number of RPCs completed by the client, regardless of success or failure.",
41
+			}), []string{"grpc_type", "grpc_service", "grpc_method", "grpc_code"}),
42
+
43
+		clientStreamMsgReceived: prom.NewCounterVec(
44
+			opts.apply(prom.CounterOpts{
45
+				Name: "grpc_client_msg_received_total",
46
+				Help: "Total number of RPC stream messages received by the client.",
47
+			}), []string{"grpc_type", "grpc_service", "grpc_method"}),
48
+
49
+		clientStreamMsgSent: prom.NewCounterVec(
50
+			opts.apply(prom.CounterOpts{
51
+				Name: "grpc_client_msg_sent_total",
52
+				Help: "Total number of gRPC stream messages sent by the client.",
53
+			}), []string{"grpc_type", "grpc_service", "grpc_method"}),
54
+
55
+		clientHandledHistogramEnabled: false,
56
+		clientHandledHistogramOpts: prom.HistogramOpts{
57
+			Name:    "grpc_client_handling_seconds",
58
+			Help:    "Histogram of response latency (seconds) of the gRPC until it is finished by the application.",
59
+			Buckets: prom.DefBuckets,
60
+		},
61
+		clientHandledHistogram: nil,
62
+	}
63
+}
64
+
65
+// Describe sends the super-set of all possible descriptors of metrics
66
+// collected by this Collector to the provided channel and returns once
67
+// the last descriptor has been sent.
68
+func (m *ClientMetrics) Describe(ch chan<- *prom.Desc) {
69
+	m.clientStartedCounter.Describe(ch)
70
+	m.clientHandledCounter.Describe(ch)
71
+	m.clientStreamMsgReceived.Describe(ch)
72
+	m.clientStreamMsgSent.Describe(ch)
73
+	if m.clientHandledHistogramEnabled {
74
+		m.clientHandledHistogram.Describe(ch)
75
+	}
76
+}
77
+
78
+// Collect is called by the Prometheus registry when collecting
79
+// metrics. The implementation sends each collected metric via the
80
+// provided channel and returns once the last metric has been sent.
81
+func (m *ClientMetrics) Collect(ch chan<- prom.Metric) {
82
+	m.clientStartedCounter.Collect(ch)
83
+	m.clientHandledCounter.Collect(ch)
84
+	m.clientStreamMsgReceived.Collect(ch)
85
+	m.clientStreamMsgSent.Collect(ch)
86
+	if m.clientHandledHistogramEnabled {
87
+		m.clientHandledHistogram.Collect(ch)
88
+	}
89
+}
90
+
91
+// EnableClientHandlingTimeHistogram turns on recording of handling time of RPCs.
92
+// Histogram metrics can be very expensive for Prometheus to retain and query.
93
+func (m *ClientMetrics) EnableClientHandlingTimeHistogram(opts ...HistogramOption) {
94
+	for _, o := range opts {
95
+		o(&m.clientHandledHistogramOpts)
96
+	}
97
+	if !m.clientHandledHistogramEnabled {
98
+		m.clientHandledHistogram = prom.NewHistogramVec(
99
+			m.clientHandledHistogramOpts,
100
+			[]string{"grpc_type", "grpc_service", "grpc_method"},
101
+		)
102
+	}
103
+	m.clientHandledHistogramEnabled = true
104
+}
105
+
106
+// UnaryClientInterceptor is a gRPC client-side interceptor that provides Prometheus monitoring for Unary RPCs.
107
+func (m *ClientMetrics) UnaryClientInterceptor() func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
108
+	return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
109
+		monitor := newClientReporter(m, Unary, method)
110
+		monitor.SentMessage()
111
+		err := invoker(ctx, method, req, reply, cc, opts...)
112
+		if err != nil {
113
+			monitor.ReceivedMessage()
114
+		}
115
+		st, _ := status.FromError(err)
116
+		monitor.Handled(st.Code())
117
+		return err
118
+	}
119
+}
120
+
121
+// StreamClientInterceptor is a gRPC client-side interceptor that provides Prometheus monitoring for Streaming RPCs.
122
+func (m *ClientMetrics) StreamClientInterceptor() func(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) {
123
+	return func(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) {
124
+		monitor := newClientReporter(m, clientStreamType(desc), method)
125
+		clientStream, err := streamer(ctx, desc, cc, method, opts...)
126
+		if err != nil {
127
+			st, _ := status.FromError(err)
128
+			monitor.Handled(st.Code())
129
+			return nil, err
130
+		}
131
+		return &monitoredClientStream{clientStream, monitor}, nil
132
+	}
133
+}
134
+
135
+func clientStreamType(desc *grpc.StreamDesc) grpcType {
136
+	if desc.ClientStreams && !desc.ServerStreams {
137
+		return ClientStream
138
+	} else if !desc.ClientStreams && desc.ServerStreams {
139
+		return ServerStream
140
+	}
141
+	return BidiStream
142
+}
143
+
144
+// monitoredClientStream wraps grpc.ClientStream allowing each Sent/Recv of message to increment counters.
145
+type monitoredClientStream struct {
146
+	grpc.ClientStream
147
+	monitor *clientReporter
148
+}
149
+
150
+func (s *monitoredClientStream) SendMsg(m interface{}) error {
151
+	err := s.ClientStream.SendMsg(m)
152
+	if err == nil {
153
+		s.monitor.SentMessage()
154
+	}
155
+	return err
156
+}
157
+
158
+func (s *monitoredClientStream) RecvMsg(m interface{}) error {
159
+	err := s.ClientStream.RecvMsg(m)
160
+	if err == nil {
161
+		s.monitor.ReceivedMessage()
162
+	} else if err == io.EOF {
163
+		s.monitor.Handled(codes.OK)
164
+	} else {
165
+		st, _ := status.FromError(err)
166
+		s.monitor.Handled(st.Code())
167
+	}
168
+	return err
169
+}
... ...
@@ -7,105 +7,40 @@ import (
7 7
 	"time"
8 8
 
9 9
 	"google.golang.org/grpc/codes"
10
-
11
-	prom "github.com/prometheus/client_golang/prometheus"
12
-)
13
-
14
-var (
15
-	clientStartedCounter = prom.NewCounterVec(
16
-		prom.CounterOpts{
17
-			Namespace: "grpc",
18
-			Subsystem: "client",
19
-			Name:      "started_total",
20
-			Help:      "Total number of RPCs started on the client.",
21
-		}, []string{"grpc_type", "grpc_service", "grpc_method"})
22
-
23
-	clientHandledCounter = prom.NewCounterVec(
24
-		prom.CounterOpts{
25
-			Namespace: "grpc",
26
-			Subsystem: "client",
27
-			Name:      "handled_total",
28
-			Help:      "Total number of RPCs completed by the client, regardless of success or failure.",
29
-		}, []string{"grpc_type", "grpc_service", "grpc_method", "grpc_code"})
30
-
31
-	clientStreamMsgReceived = prom.NewCounterVec(
32
-		prom.CounterOpts{
33
-			Namespace: "grpc",
34
-			Subsystem: "client",
35
-			Name:      "msg_received_total",
36
-			Help:      "Total number of RPC stream messages received by the client.",
37
-		}, []string{"grpc_type", "grpc_service", "grpc_method"})
38
-
39
-	clientStreamMsgSent = prom.NewCounterVec(
40
-		prom.CounterOpts{
41
-			Namespace: "grpc",
42
-			Subsystem: "client",
43
-			Name:      "msg_sent_total",
44
-			Help:      "Total number of gRPC stream messages sent by the client.",
45
-		}, []string{"grpc_type", "grpc_service", "grpc_method"})
46
-
47
-	clientHandledHistogramEnabled = false
48
-	clientHandledHistogramOpts    = prom.HistogramOpts{
49
-		Namespace: "grpc",
50
-		Subsystem: "client",
51
-		Name:      "handling_seconds",
52
-		Help:      "Histogram of response latency (seconds) of the gRPC until it is finished by the application.",
53
-		Buckets:   prom.DefBuckets,
54
-	}
55
-	clientHandledHistogram *prom.HistogramVec
56 10
 )
57 11
 
58
-func init() {
59
-	prom.MustRegister(clientStartedCounter)
60
-	prom.MustRegister(clientHandledCounter)
61
-	prom.MustRegister(clientStreamMsgReceived)
62
-	prom.MustRegister(clientStreamMsgSent)
63
-}
64
-
65
-// EnableClientHandlingTimeHistogram turns on recording of handling time of RPCs.
66
-// Histogram metrics can be very expensive for Prometheus to retain and query.
67
-func EnableClientHandlingTimeHistogram(opts ...HistogramOption) {
68
-	for _, o := range opts {
69
-		o(&clientHandledHistogramOpts)
70
-	}
71
-	if !clientHandledHistogramEnabled {
72
-		clientHandledHistogram = prom.NewHistogramVec(
73
-			clientHandledHistogramOpts,
74
-			[]string{"grpc_type", "grpc_service", "grpc_method"},
75
-		)
76
-		prom.Register(clientHandledHistogram)
77
-	}
78
-	clientHandledHistogramEnabled = true
79
-}
80
-
81 12
 type clientReporter struct {
13
+	metrics     *ClientMetrics
82 14
 	rpcType     grpcType
83 15
 	serviceName string
84 16
 	methodName  string
85 17
 	startTime   time.Time
86 18
 }
87 19
 
88
-func newClientReporter(rpcType grpcType, fullMethod string) *clientReporter {
89
-	r := &clientReporter{rpcType: rpcType}
90
-	if clientHandledHistogramEnabled {
20
+func newClientReporter(m *ClientMetrics, rpcType grpcType, fullMethod string) *clientReporter {
21
+	r := &clientReporter{
22
+		metrics: m,
23
+		rpcType: rpcType,
24
+	}
25
+	if r.metrics.clientHandledHistogramEnabled {
91 26
 		r.startTime = time.Now()
92 27
 	}
93 28
 	r.serviceName, r.methodName = splitMethodName(fullMethod)
94
-	clientStartedCounter.WithLabelValues(string(r.rpcType), r.serviceName, r.methodName).Inc()
29
+	r.metrics.clientStartedCounter.WithLabelValues(string(r.rpcType), r.serviceName, r.methodName).Inc()
95 30
 	return r
96 31
 }
97 32
 
98 33
 func (r *clientReporter) ReceivedMessage() {
99
-	clientStreamMsgReceived.WithLabelValues(string(r.rpcType), r.serviceName, r.methodName).Inc()
34
+	r.metrics.clientStreamMsgReceived.WithLabelValues(string(r.rpcType), r.serviceName, r.methodName).Inc()
100 35
 }
101 36
 
102 37
 func (r *clientReporter) SentMessage() {
103
-	clientStreamMsgSent.WithLabelValues(string(r.rpcType), r.serviceName, r.methodName).Inc()
38
+	r.metrics.clientStreamMsgSent.WithLabelValues(string(r.rpcType), r.serviceName, r.methodName).Inc()
104 39
 }
105 40
 
106 41
 func (r *clientReporter) Handled(code codes.Code) {
107
-	clientHandledCounter.WithLabelValues(string(r.rpcType), r.serviceName, r.methodName, code.String()).Inc()
108
-	if clientHandledHistogramEnabled {
109
-		clientHandledHistogram.WithLabelValues(string(r.rpcType), r.serviceName, r.methodName).Observe(time.Since(r.startTime).Seconds())
42
+	r.metrics.clientHandledCounter.WithLabelValues(string(r.rpcType), r.serviceName, r.methodName, code.String()).Inc()
43
+	if r.metrics.clientHandledHistogramEnabled {
44
+		r.metrics.clientHandledHistogram.WithLabelValues(string(r.rpcType), r.serviceName, r.methodName).Observe(time.Since(r.startTime).Seconds())
110 45
 	}
111 46
 }
112 47
new file mode 100644
... ...
@@ -0,0 +1,41 @@
0
+package grpc_prometheus
1
+
2
+import (
3
+	prom "github.com/prometheus/client_golang/prometheus"
4
+)
5
+
6
+// A CounterOption lets you add options to Counter metrics using With* funcs.
7
+type CounterOption func(*prom.CounterOpts)
8
+
9
+type counterOptions []CounterOption
10
+
11
+func (co counterOptions) apply(o prom.CounterOpts) prom.CounterOpts {
12
+	for _, f := range co {
13
+		f(&o)
14
+	}
15
+	return o
16
+}
17
+
18
+// WithConstLabels allows you to add ConstLabels to Counter metrics.
19
+func WithConstLabels(labels prom.Labels) CounterOption {
20
+	return func(o *prom.CounterOpts) {
21
+		o.ConstLabels = labels
22
+	}
23
+}
24
+
25
+// A HistogramOption lets you add options to Histogram metrics using With*
26
+// funcs.
27
+type HistogramOption func(*prom.HistogramOpts)
28
+
29
+// WithHistogramBuckets allows you to specify custom bucket ranges for histograms if EnableHandlingTimeHistogram is on.
30
+func WithHistogramBuckets(buckets []float64) HistogramOption {
31
+	return func(o *prom.HistogramOpts) { o.Buckets = buckets }
32
+}
33
+
34
+// WithHistogramConstLabels allows you to add custom ConstLabels to
35
+// histograms metrics.
36
+func WithHistogramConstLabels(labels prom.Labels) HistogramOption {
37
+	return func(o *prom.HistogramOpts) {
38
+		o.ConstLabels = labels
39
+	}
40
+}
... ...
@@ -6,69 +6,43 @@
6 6
 package grpc_prometheus
7 7
 
8 8
 import (
9
-	"golang.org/x/net/context"
9
+	prom "github.com/prometheus/client_golang/prometheus"
10 10
 	"google.golang.org/grpc"
11 11
 )
12 12
 
13
-// PreregisterServices takes a gRPC server and pre-initializes all counters to 0.
14
-// This allows for easier monitoring in Prometheus (no missing metrics), and should be called *after* all services have
15
-// been registered with the server.
16
-func Register(server *grpc.Server) {
17
-	serviceInfo := server.GetServiceInfo()
18
-	for serviceName, info := range serviceInfo {
19
-		for _, mInfo := range info.Methods {
20
-			preRegisterMethod(serviceName, &mInfo)
21
-		}
22
-	}
23
-}
24
-
25
-// UnaryServerInterceptor is a gRPC server-side interceptor that provides Prometheus monitoring for Unary RPCs.
26
-func UnaryServerInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
27
-	monitor := newServerReporter(Unary, info.FullMethod)
28
-	monitor.ReceivedMessage()
29
-	resp, err := handler(ctx, req)
30
-	monitor.Handled(grpc.Code(err))
31
-	if err == nil {
32
-		monitor.SentMessage()
33
-	}
34
-	return resp, err
35
-}
13
+var (
14
+	// DefaultServerMetrics is the default instance of ServerMetrics. It is
15
+	// intended to be used in conjunction the default Prometheus metrics
16
+	// registry.
17
+	DefaultServerMetrics = NewServerMetrics()
36 18
 
37
-// StreamServerInterceptor is a gRPC server-side interceptor that provides Prometheus monitoring for Streaming RPCs.
38
-func StreamServerInterceptor(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
39
-	monitor := newServerReporter(streamRpcType(info), info.FullMethod)
40
-	err := handler(srv, &monitoredServerStream{ss, monitor})
41
-	monitor.Handled(grpc.Code(err))
42
-	return err
43
-}
19
+	// UnaryServerInterceptor is a gRPC server-side interceptor that provides Prometheus monitoring for Unary RPCs.
20
+	UnaryServerInterceptor = DefaultServerMetrics.UnaryServerInterceptor()
44 21
 
45
-func streamRpcType(info *grpc.StreamServerInfo) grpcType {
46
-	if info.IsClientStream && !info.IsServerStream {
47
-		return ClientStream
48
-	} else if !info.IsClientStream && info.IsServerStream {
49
-		return ServerStream
50
-	}
51
-	return BidiStream
52
-}
22
+	// StreamServerInterceptor is a gRPC server-side interceptor that provides Prometheus monitoring for Streaming RPCs.
23
+	StreamServerInterceptor = DefaultServerMetrics.StreamServerInterceptor()
24
+)
53 25
 
54
-// monitoredStream wraps grpc.ServerStream allowing each Sent/Recv of message to increment counters.
55
-type monitoredServerStream struct {
56
-	grpc.ServerStream
57
-	monitor *serverReporter
26
+func init() {
27
+	prom.MustRegister(DefaultServerMetrics.serverStartedCounter)
28
+	prom.MustRegister(DefaultServerMetrics.serverHandledCounter)
29
+	prom.MustRegister(DefaultServerMetrics.serverStreamMsgReceived)
30
+	prom.MustRegister(DefaultServerMetrics.serverStreamMsgSent)
58 31
 }
59 32
 
60
-func (s *monitoredServerStream) SendMsg(m interface{}) error {
61
-	err := s.ServerStream.SendMsg(m)
62
-	if err == nil {
63
-		s.monitor.SentMessage()
64
-	}
65
-	return err
33
+// Register takes a gRPC server and pre-initializes all counters to 0. This
34
+// allows for easier monitoring in Prometheus (no missing metrics), and should
35
+// be called *after* all services have been registered with the server. This
36
+// function acts on the DefaultServerMetrics variable.
37
+func Register(server *grpc.Server) {
38
+	DefaultServerMetrics.InitializeMetrics(server)
66 39
 }
67 40
 
68
-func (s *monitoredServerStream) RecvMsg(m interface{}) error {
69
-	err := s.ServerStream.RecvMsg(m)
70
-	if err == nil {
71
-		s.monitor.ReceivedMessage()
72
-	}
73
-	return err
41
+// EnableHandlingTimeHistogram turns on recording of handling time
42
+// of RPCs. Histogram metrics can be very expensive for Prometheus
43
+// to retain and query. This function acts on the DefaultServerMetrics
44
+// variable and the default Prometheus metrics registry.
45
+func EnableHandlingTimeHistogram(opts ...HistogramOption) {
46
+	DefaultServerMetrics.EnableHandlingTimeHistogram(opts...)
47
+	prom.Register(DefaultServerMetrics.serverHandledHistogram)
74 48
 }
75 49
new file mode 100644
... ...
@@ -0,0 +1,185 @@
0
+package grpc_prometheus
1
+
2
+import (
3
+	prom "github.com/prometheus/client_golang/prometheus"
4
+	"golang.org/x/net/context"
5
+	"google.golang.org/grpc"
6
+	"google.golang.org/grpc/status"
7
+)
8
+
9
+// ServerMetrics represents a collection of metrics to be registered on a
10
+// Prometheus metrics registry for a gRPC server.
11
+type ServerMetrics struct {
12
+	serverStartedCounter          *prom.CounterVec
13
+	serverHandledCounter          *prom.CounterVec
14
+	serverStreamMsgReceived       *prom.CounterVec
15
+	serverStreamMsgSent           *prom.CounterVec
16
+	serverHandledHistogramEnabled bool
17
+	serverHandledHistogramOpts    prom.HistogramOpts
18
+	serverHandledHistogram        *prom.HistogramVec
19
+}
20
+
21
+// NewServerMetrics returns a ServerMetrics object. Use a new instance of
22
+// ServerMetrics when not using the default Prometheus metrics registry, for
23
+// example when wanting to control which metrics are added to a registry as
24
+// opposed to automatically adding metrics via init functions.
25
+func NewServerMetrics(counterOpts ...CounterOption) *ServerMetrics {
26
+	opts := counterOptions(counterOpts)
27
+	return &ServerMetrics{
28
+		serverStartedCounter: prom.NewCounterVec(
29
+			opts.apply(prom.CounterOpts{
30
+				Name: "grpc_server_started_total",
31
+				Help: "Total number of RPCs started on the server.",
32
+			}), []string{"grpc_type", "grpc_service", "grpc_method"}),
33
+		serverHandledCounter: prom.NewCounterVec(
34
+			opts.apply(prom.CounterOpts{
35
+				Name: "grpc_server_handled_total",
36
+				Help: "Total number of RPCs completed on the server, regardless of success or failure.",
37
+			}), []string{"grpc_type", "grpc_service", "grpc_method", "grpc_code"}),
38
+		serverStreamMsgReceived: prom.NewCounterVec(
39
+			opts.apply(prom.CounterOpts{
40
+				Name: "grpc_server_msg_received_total",
41
+				Help: "Total number of RPC stream messages received on the server.",
42
+			}), []string{"grpc_type", "grpc_service", "grpc_method"}),
43
+		serverStreamMsgSent: prom.NewCounterVec(
44
+			opts.apply(prom.CounterOpts{
45
+				Name: "grpc_server_msg_sent_total",
46
+				Help: "Total number of gRPC stream messages sent by the server.",
47
+			}), []string{"grpc_type", "grpc_service", "grpc_method"}),
48
+		serverHandledHistogramEnabled: false,
49
+		serverHandledHistogramOpts: prom.HistogramOpts{
50
+			Name:    "grpc_server_handling_seconds",
51
+			Help:    "Histogram of response latency (seconds) of gRPC that had been application-level handled by the server.",
52
+			Buckets: prom.DefBuckets,
53
+		},
54
+		serverHandledHistogram: nil,
55
+	}
56
+}
57
+
58
+// EnableHandlingTimeHistogram enables histograms being registered when
59
+// registering the ServerMetrics on a Prometheus registry. Histograms can be
60
+// expensive on Prometheus servers. It takes options to configure histogram
61
+// options such as the defined buckets.
62
+func (m *ServerMetrics) EnableHandlingTimeHistogram(opts ...HistogramOption) {
63
+	for _, o := range opts {
64
+		o(&m.serverHandledHistogramOpts)
65
+	}
66
+	if !m.serverHandledHistogramEnabled {
67
+		m.serverHandledHistogram = prom.NewHistogramVec(
68
+			m.serverHandledHistogramOpts,
69
+			[]string{"grpc_type", "grpc_service", "grpc_method"},
70
+		)
71
+	}
72
+	m.serverHandledHistogramEnabled = true
73
+}
74
+
75
+// Describe sends the super-set of all possible descriptors of metrics
76
+// collected by this Collector to the provided channel and returns once
77
+// the last descriptor has been sent.
78
+func (m *ServerMetrics) Describe(ch chan<- *prom.Desc) {
79
+	m.serverStartedCounter.Describe(ch)
80
+	m.serverHandledCounter.Describe(ch)
81
+	m.serverStreamMsgReceived.Describe(ch)
82
+	m.serverStreamMsgSent.Describe(ch)
83
+	if m.serverHandledHistogramEnabled {
84
+		m.serverHandledHistogram.Describe(ch)
85
+	}
86
+}
87
+
88
+// Collect is called by the Prometheus registry when collecting
89
+// metrics. The implementation sends each collected metric via the
90
+// provided channel and returns once the last metric has been sent.
91
+func (m *ServerMetrics) Collect(ch chan<- prom.Metric) {
92
+	m.serverStartedCounter.Collect(ch)
93
+	m.serverHandledCounter.Collect(ch)
94
+	m.serverStreamMsgReceived.Collect(ch)
95
+	m.serverStreamMsgSent.Collect(ch)
96
+	if m.serverHandledHistogramEnabled {
97
+		m.serverHandledHistogram.Collect(ch)
98
+	}
99
+}
100
+
101
+// UnaryServerInterceptor is a gRPC server-side interceptor that provides Prometheus monitoring for Unary RPCs.
102
+func (m *ServerMetrics) UnaryServerInterceptor() func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
103
+	return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
104
+		monitor := newServerReporter(m, Unary, info.FullMethod)
105
+		monitor.ReceivedMessage()
106
+		resp, err := handler(ctx, req)
107
+		st, _ := status.FromError(err)
108
+		monitor.Handled(st.Code())
109
+		if err == nil {
110
+			monitor.SentMessage()
111
+		}
112
+		return resp, err
113
+	}
114
+}
115
+
116
+// StreamServerInterceptor is a gRPC server-side interceptor that provides Prometheus monitoring for Streaming RPCs.
117
+func (m *ServerMetrics) StreamServerInterceptor() func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
118
+	return func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
119
+		monitor := newServerReporter(m, streamRPCType(info), info.FullMethod)
120
+		err := handler(srv, &monitoredServerStream{ss, monitor})
121
+		st, _ := status.FromError(err)
122
+		monitor.Handled(st.Code())
123
+		return err
124
+	}
125
+}
126
+
127
+// InitializeMetrics initializes all metrics, with their appropriate null
128
+// value, for all gRPC methods registered on a gRPC server. This is useful, to
129
+// ensure that all metrics exist when collecting and querying.
130
+func (m *ServerMetrics) InitializeMetrics(server *grpc.Server) {
131
+	serviceInfo := server.GetServiceInfo()
132
+	for serviceName, info := range serviceInfo {
133
+		for _, mInfo := range info.Methods {
134
+			preRegisterMethod(m, serviceName, &mInfo)
135
+		}
136
+	}
137
+}
138
+
139
+func streamRPCType(info *grpc.StreamServerInfo) grpcType {
140
+	if info.IsClientStream && !info.IsServerStream {
141
+		return ClientStream
142
+	} else if !info.IsClientStream && info.IsServerStream {
143
+		return ServerStream
144
+	}
145
+	return BidiStream
146
+}
147
+
148
+// monitoredStream wraps grpc.ServerStream allowing each Sent/Recv of message to increment counters.
149
+type monitoredServerStream struct {
150
+	grpc.ServerStream
151
+	monitor *serverReporter
152
+}
153
+
154
+func (s *monitoredServerStream) SendMsg(m interface{}) error {
155
+	err := s.ServerStream.SendMsg(m)
156
+	if err == nil {
157
+		s.monitor.SentMessage()
158
+	}
159
+	return err
160
+}
161
+
162
+func (s *monitoredServerStream) RecvMsg(m interface{}) error {
163
+	err := s.ServerStream.RecvMsg(m)
164
+	if err == nil {
165
+		s.monitor.ReceivedMessage()
166
+	}
167
+	return err
168
+}
169
+
170
+// preRegisterMethod is invoked on Register of a Server, allowing all gRPC services labels to be pre-populated.
171
+func preRegisterMethod(metrics *ServerMetrics, serviceName string, mInfo *grpc.MethodInfo) {
172
+	methodName := mInfo.Name
173
+	methodType := string(typeFromMethodInfo(mInfo))
174
+	// These are just references (no increments), as just referencing will create the labels but not set values.
175
+	metrics.serverStartedCounter.GetMetricWithLabelValues(methodType, serviceName, methodName)
176
+	metrics.serverStreamMsgReceived.GetMetricWithLabelValues(methodType, serviceName, methodName)
177
+	metrics.serverStreamMsgSent.GetMetricWithLabelValues(methodType, serviceName, methodName)
178
+	if metrics.serverHandledHistogramEnabled {
179
+		metrics.serverHandledHistogram.GetMetricWithLabelValues(methodType, serviceName, methodName)
180
+	}
181
+	for _, code := range allCodes {
182
+		metrics.serverHandledCounter.GetMetricWithLabelValues(methodType, serviceName, methodName, code.String())
183
+	}
184
+}
... ...
@@ -7,151 +7,40 @@ import (
7 7
 	"time"
8 8
 
9 9
 	"google.golang.org/grpc/codes"
10
-
11
-	prom "github.com/prometheus/client_golang/prometheus"
12
-	"google.golang.org/grpc"
13
-)
14
-
15
-type grpcType string
16
-
17
-const (
18
-	Unary        grpcType = "unary"
19
-	ClientStream grpcType = "client_stream"
20
-	ServerStream grpcType = "server_stream"
21
-	BidiStream   grpcType = "bidi_stream"
22 10
 )
23 11
 
24
-var (
25
-	serverStartedCounter = prom.NewCounterVec(
26
-		prom.CounterOpts{
27
-			Namespace: "grpc",
28
-			Subsystem: "server",
29
-			Name:      "started_total",
30
-			Help:      "Total number of RPCs started on the server.",
31
-		}, []string{"grpc_type", "grpc_service", "grpc_method"})
32
-
33
-	serverHandledCounter = prom.NewCounterVec(
34
-		prom.CounterOpts{
35
-			Namespace: "grpc",
36
-			Subsystem: "server",
37
-			Name:      "handled_total",
38
-			Help:      "Total number of RPCs completed on the server, regardless of success or failure.",
39
-		}, []string{"grpc_type", "grpc_service", "grpc_method", "grpc_code"})
40
-
41
-	serverStreamMsgReceived = prom.NewCounterVec(
42
-		prom.CounterOpts{
43
-			Namespace: "grpc",
44
-			Subsystem: "server",
45
-			Name:      "msg_received_total",
46
-			Help:      "Total number of RPC stream messages received on the server.",
47
-		}, []string{"grpc_type", "grpc_service", "grpc_method"})
48
-
49
-	serverStreamMsgSent = prom.NewCounterVec(
50
-		prom.CounterOpts{
51
-			Namespace: "grpc",
52
-			Subsystem: "server",
53
-			Name:      "msg_sent_total",
54
-			Help:      "Total number of gRPC stream messages sent by the server.",
55
-		}, []string{"grpc_type", "grpc_service", "grpc_method"})
56
-
57
-	serverHandledHistogramEnabled = false
58
-	serverHandledHistogramOpts    = prom.HistogramOpts{
59
-		Namespace: "grpc",
60
-		Subsystem: "server",
61
-		Name:      "handling_seconds",
62
-		Help:      "Histogram of response latency (seconds) of gRPC that had been application-level handled by the server.",
63
-		Buckets:   prom.DefBuckets,
64
-	}
65
-	serverHandledHistogram *prom.HistogramVec
66
-)
67
-
68
-func init() {
69
-	prom.MustRegister(serverStartedCounter)
70
-	prom.MustRegister(serverHandledCounter)
71
-	prom.MustRegister(serverStreamMsgReceived)
72
-	prom.MustRegister(serverStreamMsgSent)
73
-}
74
-
75
-type HistogramOption func(*prom.HistogramOpts)
76
-
77
-// WithHistogramBuckets allows you to specify custom bucket ranges for histograms if EnableHandlingTimeHistogram is on.
78
-func WithHistogramBuckets(buckets []float64) HistogramOption {
79
-	return func(o *prom.HistogramOpts) { o.Buckets = buckets }
80
-}
81
-
82
-// EnableHandlingTimeHistogram turns on recording of handling time of RPCs for server-side interceptors.
83
-// Histogram metrics can be very expensive for Prometheus to retain and query.
84
-func EnableHandlingTimeHistogram(opts ...HistogramOption) {
85
-	for _, o := range opts {
86
-		o(&serverHandledHistogramOpts)
87
-	}
88
-	if !serverHandledHistogramEnabled {
89
-		serverHandledHistogram = prom.NewHistogramVec(
90
-			serverHandledHistogramOpts,
91
-			[]string{"grpc_type", "grpc_service", "grpc_method"},
92
-		)
93
-		prom.Register(serverHandledHistogram)
94
-	}
95
-	serverHandledHistogramEnabled = true
96
-}
97
-
98 12
 type serverReporter struct {
13
+	metrics     *ServerMetrics
99 14
 	rpcType     grpcType
100 15
 	serviceName string
101 16
 	methodName  string
102 17
 	startTime   time.Time
103 18
 }
104 19
 
105
-func newServerReporter(rpcType grpcType, fullMethod string) *serverReporter {
106
-	r := &serverReporter{rpcType: rpcType}
107
-	if serverHandledHistogramEnabled {
20
+func newServerReporter(m *ServerMetrics, rpcType grpcType, fullMethod string) *serverReporter {
21
+	r := &serverReporter{
22
+		metrics: m,
23
+		rpcType: rpcType,
24
+	}
25
+	if r.metrics.serverHandledHistogramEnabled {
108 26
 		r.startTime = time.Now()
109 27
 	}
110 28
 	r.serviceName, r.methodName = splitMethodName(fullMethod)
111
-	serverStartedCounter.WithLabelValues(string(r.rpcType), r.serviceName, r.methodName).Inc()
29
+	r.metrics.serverStartedCounter.WithLabelValues(string(r.rpcType), r.serviceName, r.methodName).Inc()
112 30
 	return r
113 31
 }
114 32
 
115 33
 func (r *serverReporter) ReceivedMessage() {
116
-	serverStreamMsgReceived.WithLabelValues(string(r.rpcType), r.serviceName, r.methodName).Inc()
34
+	r.metrics.serverStreamMsgReceived.WithLabelValues(string(r.rpcType), r.serviceName, r.methodName).Inc()
117 35
 }
118 36
 
119 37
 func (r *serverReporter) SentMessage() {
120
-	serverStreamMsgSent.WithLabelValues(string(r.rpcType), r.serviceName, r.methodName).Inc()
38
+	r.metrics.serverStreamMsgSent.WithLabelValues(string(r.rpcType), r.serviceName, r.methodName).Inc()
121 39
 }
122 40
 
123 41
 func (r *serverReporter) Handled(code codes.Code) {
124
-	serverHandledCounter.WithLabelValues(string(r.rpcType), r.serviceName, r.methodName, code.String()).Inc()
125
-	if serverHandledHistogramEnabled {
126
-		serverHandledHistogram.WithLabelValues(string(r.rpcType), r.serviceName, r.methodName).Observe(time.Since(r.startTime).Seconds())
127
-	}
128
-}
129
-
130
-// preRegisterMethod is invoked on Register of a Server, allowing all gRPC services labels to be pre-populated.
131
-func preRegisterMethod(serviceName string, mInfo *grpc.MethodInfo) {
132
-	methodName := mInfo.Name
133
-	methodType := string(typeFromMethodInfo(mInfo))
134
-	// These are just references (no increments), as just referencing will create the labels but not set values.
135
-	serverStartedCounter.GetMetricWithLabelValues(methodType, serviceName, methodName)
136
-	serverStreamMsgReceived.GetMetricWithLabelValues(methodType, serviceName, methodName)
137
-	serverStreamMsgSent.GetMetricWithLabelValues(methodType, serviceName, methodName)
138
-	if serverHandledHistogramEnabled {
139
-		serverHandledHistogram.GetMetricWithLabelValues(methodType, serviceName, methodName)
140
-	}
141
-	for _, code := range allCodes {
142
-		serverHandledCounter.GetMetricWithLabelValues(methodType, serviceName, methodName, code.String())
143
-	}
144
-}
145
-
146
-func typeFromMethodInfo(mInfo *grpc.MethodInfo) grpcType {
147
-	if mInfo.IsClientStream == false && mInfo.IsServerStream == false {
148
-		return Unary
149
-	}
150
-	if mInfo.IsClientStream == true && mInfo.IsServerStream == false {
151
-		return ClientStream
152
-	}
153
-	if mInfo.IsClientStream == false && mInfo.IsServerStream == true {
154
-		return ServerStream
42
+	r.metrics.serverHandledCounter.WithLabelValues(string(r.rpcType), r.serviceName, r.methodName, code.String()).Inc()
43
+	if r.metrics.serverHandledHistogramEnabled {
44
+		r.metrics.serverHandledHistogram.WithLabelValues(string(r.rpcType), r.serviceName, r.methodName).Observe(time.Since(r.startTime).Seconds())
155 45
 	}
156
-	return BidiStream
157 46
 }
... ...
@@ -6,9 +6,19 @@ package grpc_prometheus
6 6
 import (
7 7
 	"strings"
8 8
 
9
+	"google.golang.org/grpc"
9 10
 	"google.golang.org/grpc/codes"
10 11
 )
11 12
 
13
+type grpcType string
14
+
15
+const (
16
+	Unary        grpcType = "unary"
17
+	ClientStream grpcType = "client_stream"
18
+	ServerStream grpcType = "server_stream"
19
+	BidiStream   grpcType = "bidi_stream"
20
+)
21
+
12 22
 var (
13 23
 	allCodes = []codes.Code{
14 24
 		codes.OK, codes.Canceled, codes.Unknown, codes.InvalidArgument, codes.DeadlineExceeded, codes.NotFound,
... ...
@@ -25,3 +35,16 @@ func splitMethodName(fullMethodName string) (string, string) {
25 25
 	}
26 26
 	return "unknown", "unknown"
27 27
 }
28
+
29
+func typeFromMethodInfo(mInfo *grpc.MethodInfo) grpcType {
30
+	if !mInfo.IsClientStream && !mInfo.IsServerStream {
31
+		return Unary
32
+	}
33
+	if mInfo.IsClientStream && !mInfo.IsServerStream {
34
+		return ClientStream
35
+	}
36
+	if !mInfo.IsClientStream && mInfo.IsServerStream {
37
+		return ServerStream
38
+	}
39
+	return BidiStream
40
+}
... ...
@@ -7,11 +7,6 @@ SoundCloud Ltd. (http://soundcloud.com/).
7 7
 
8 8
 The following components are included in this product:
9 9
 
10
-goautoneg
11
-http://bitbucket.org/ww/goautoneg
12
-Copyright 2011, Open Knowledge Foundation Ltd.
13
-See README.txt for license details.
14
-
15 10
 perks - a fork of https://github.com/bmizerany/perks
16 11
 https://github.com/beorn7/perks
17 12
 Copyright 2013-2015 Blake Mizerany, Björn Rabenstein
... ...
@@ -1,53 +1 @@
1
-# Overview
2
-This is the [Prometheus](http://www.prometheus.io) telemetric
3
-instrumentation client [Go](http://golang.org) client library.  It
4
-enable authors to define process-space metrics for their servers and
5
-expose them through a web service interface for extraction,
6
-aggregation, and a whole slew of other post processing techniques.
7
-
8
-# Installing
9
-    $ go get github.com/prometheus/client_golang/prometheus
10
-
11
-# Example
12
-```go
13
-package main
14
-
15
-import (
16
-	"net/http"
17
-
18
-	"github.com/prometheus/client_golang/prometheus"
19
-)
20
-
21
-var (
22
-	indexed = prometheus.NewCounter(prometheus.CounterOpts{
23
-		Namespace: "my_company",
24
-		Subsystem: "indexer",
25
-		Name:      "documents_indexed",
26
-		Help:      "The number of documents indexed.",
27
-	})
28
-	size = prometheus.NewGauge(prometheus.GaugeOpts{
29
-		Namespace: "my_company",
30
-		Subsystem: "storage",
31
-		Name:      "documents_total_size_bytes",
32
-		Help:      "The total size of all documents in the storage.",
33
-	})
34
-)
35
-
36
-func main() {
37
-	http.Handle("/metrics", prometheus.Handler())
38
-
39
-	indexed.Inc()
40
-	size.Set(5)
41
-
42
-	http.ListenAndServe(":8080", nil)
43
-}
44
-
45
-func init() {
46
-	prometheus.MustRegister(indexed)
47
-	prometheus.MustRegister(size)
48
-}
49
-```
50
-
51
-# Documentation
52
-
53
-[![GoDoc](https://godoc.org/github.com/prometheus/client_golang?status.png)](https://godoc.org/github.com/prometheus/client_golang)
1
+See [![go-doc](https://godoc.org/github.com/prometheus/client_golang/prometheus?status.svg)](https://godoc.org/github.com/prometheus/client_golang/prometheus).
... ...
@@ -15,15 +15,15 @@ package prometheus
15 15
 
16 16
 // Collector is the interface implemented by anything that can be used by
17 17
 // Prometheus to collect metrics. A Collector has to be registered for
18
-// collection. See Register, MustRegister, RegisterOrGet, and MustRegisterOrGet.
18
+// collection. See Registerer.Register.
19 19
 //
20
-// The stock metrics provided by this package (like Gauge, Counter, Summary) are
21
-// also Collectors (which only ever collect one metric, namely itself). An
22
-// implementer of Collector may, however, collect multiple metrics in a
23
-// coordinated fashion and/or create metrics on the fly. Examples for collectors
24
-// already implemented in this library are the metric vectors (i.e. collection
25
-// of multiple instances of the same Metric but with different label values)
26
-// like GaugeVec or SummaryVec, and the ExpvarCollector.
20
+// The stock metrics provided by this package (Gauge, Counter, Summary,
21
+// Histogram, Untyped) are also Collectors (which only ever collect one metric,
22
+// namely itself). An implementer of Collector may, however, collect multiple
23
+// metrics in a coordinated fashion and/or create metrics on the fly. Examples
24
+// for collectors already implemented in this library are the metric vectors
25
+// (i.e. collection of multiple instances of the same Metric but with different
26
+// label values) like GaugeVec or SummaryVec, and the ExpvarCollector.
27 27
 type Collector interface {
28 28
 	// Describe sends the super-set of all possible descriptors of metrics
29 29
 	// collected by this Collector to the provided channel and returns once
... ...
@@ -37,39 +37,39 @@ type Collector interface {
37 37
 	// executing this method, it must send an invalid descriptor (created
38 38
 	// with NewInvalidDesc) to signal the error to the registry.
39 39
 	Describe(chan<- *Desc)
40
-	// Collect is called by Prometheus when collecting metrics. The
41
-	// implementation sends each collected metric via the provided channel
42
-	// and returns once the last metric has been sent. The descriptor of
43
-	// each sent metric is one of those returned by Describe. Returned
44
-	// metrics that share the same descriptor must differ in their variable
45
-	// label values. This method may be called concurrently and must
46
-	// therefore be implemented in a concurrency safe way. Blocking occurs
47
-	// at the expense of total performance of rendering all registered
48
-	// metrics. Ideally, Collector implementations support concurrent
49
-	// readers.
40
+	// Collect is called by the Prometheus registry when collecting
41
+	// metrics. The implementation sends each collected metric via the
42
+	// provided channel and returns once the last metric has been sent. The
43
+	// descriptor of each sent metric is one of those returned by
44
+	// Describe. Returned metrics that share the same descriptor must differ
45
+	// in their variable label values. This method may be called
46
+	// concurrently and must therefore be implemented in a concurrency safe
47
+	// way. Blocking occurs at the expense of total performance of rendering
48
+	// all registered metrics. Ideally, Collector implementations support
49
+	// concurrent readers.
50 50
 	Collect(chan<- Metric)
51 51
 }
52 52
 
53
-// SelfCollector implements Collector for a single Metric so that that the
54
-// Metric collects itself. Add it as an anonymous field to a struct that
55
-// implements Metric, and call Init with the Metric itself as an argument.
56
-type SelfCollector struct {
53
+// selfCollector implements Collector for a single Metric so that the Metric
54
+// collects itself. Add it as an anonymous field to a struct that implements
55
+// Metric, and call init with the Metric itself as an argument.
56
+type selfCollector struct {
57 57
 	self Metric
58 58
 }
59 59
 
60
-// Init provides the SelfCollector with a reference to the metric it is supposed
60
+// init provides the selfCollector with a reference to the metric it is supposed
61 61
 // to collect. It is usually called within the factory function to create a
62 62
 // metric. See example.
63
-func (c *SelfCollector) Init(self Metric) {
63
+func (c *selfCollector) init(self Metric) {
64 64
 	c.self = self
65 65
 }
66 66
 
67 67
 // Describe implements Collector.
68
-func (c *SelfCollector) Describe(ch chan<- *Desc) {
68
+func (c *selfCollector) Describe(ch chan<- *Desc) {
69 69
 	ch <- c.self.Desc()
70 70
 }
71 71
 
72 72
 // Collect implements Collector.
73
-func (c *SelfCollector) Collect(ch chan<- Metric) {
73
+func (c *selfCollector) Collect(ch chan<- Metric) {
74 74
 	ch <- c.self
75 75
 }
... ...
@@ -35,6 +35,9 @@ type Counter interface {
35 35
 	// Prometheus metric. Do not use it for regular handling of a
36 36
 	// Prometheus counter (as it can be used to break the contract of
37 37
 	// monotonically increasing values).
38
+	//
39
+	// Deprecated: Use NewConstMetric to create a counter for an external
40
+	// value. A Counter should never be set.
38 41
 	Set(float64)
39 42
 	// Inc increments the counter by 1.
40 43
 	Inc()
... ...
@@ -55,7 +58,7 @@ func NewCounter(opts CounterOpts) Counter {
55 55
 		opts.ConstLabels,
56 56
 	)
57 57
 	result := &counter{value: value{desc: desc, valType: CounterValue, labelPairs: desc.constLabelPairs}}
58
-	result.Init(result) // Init self-collection.
58
+	result.init(result) // Init self-collection.
59 59
 	return result
60 60
 }
61 61
 
... ...
@@ -79,7 +82,7 @@ func (c *counter) Add(v float64) {
79 79
 // CounterVec embeds MetricVec. See there for a full list of methods with
80 80
 // detailed documentation.
81 81
 type CounterVec struct {
82
-	MetricVec
82
+	*MetricVec
83 83
 }
84 84
 
85 85
 // NewCounterVec creates a new CounterVec based on the provided CounterOpts and
... ...
@@ -93,19 +96,15 @@ func NewCounterVec(opts CounterOpts, labelNames []string) *CounterVec {
93 93
 		opts.ConstLabels,
94 94
 	)
95 95
 	return &CounterVec{
96
-		MetricVec: MetricVec{
97
-			children: map[uint64]Metric{},
98
-			desc:     desc,
99
-			newMetric: func(lvs ...string) Metric {
100
-				result := &counter{value: value{
101
-					desc:       desc,
102
-					valType:    CounterValue,
103
-					labelPairs: makeLabelPairs(desc, lvs),
104
-				}}
105
-				result.Init(result) // Init self-collection.
106
-				return result
107
-			},
108
-		},
96
+		MetricVec: newMetricVec(desc, func(lvs ...string) Metric {
97
+			result := &counter{value: value{
98
+				desc:       desc,
99
+				valType:    CounterValue,
100
+				labelPairs: makeLabelPairs(desc, lvs),
101
+			}}
102
+			result.init(result) // Init self-collection.
103
+			return result
104
+		}),
109 105
 	}
110 106
 }
111 107
 
... ...
@@ -1,3 +1,16 @@
1
+// Copyright 2016 The Prometheus Authors
2
+// Licensed under the Apache License, Version 2.0 (the "License");
3
+// you may not use this file except in compliance with the License.
4
+// You may obtain a copy of the License at
5
+//
6
+// http://www.apache.org/licenses/LICENSE-2.0
7
+//
8
+// Unless required by applicable law or agreed to in writing, software
9
+// distributed under the License is distributed on an "AS IS" BASIS,
10
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+// See the License for the specific language governing permissions and
12
+// limitations under the License.
13
+
1 14
 package prometheus
2 15
 
3 16
 import (
... ...
@@ -11,18 +11,17 @@
11 11
 // See the License for the specific language governing permissions and
12 12
 // limitations under the License.
13 13
 
14
-// Package prometheus provides embeddable metric primitives for servers and
15
-// standardized exposition of telemetry through a web services interface.
14
+// Package prometheus provides metrics primitives to instrument code for
15
+// monitoring. It also offers a registry for metrics. Sub-packages allow to
16
+// expose the registered metrics via HTTP (package promhttp) or push them to a
17
+// Pushgateway (package push).
16 18
 //
17 19
 // All exported functions and methods are safe to be used concurrently unless
18
-// specified otherwise.
20
+//specified otherwise.
19 21
 //
20
-// To expose metrics registered with the Prometheus registry, an HTTP server
21
-// needs to know about the Prometheus handler. The usual endpoint is "/metrics".
22
+// A Basic Example
22 23
 //
23
-//     http.Handle("/metrics", prometheus.Handler())
24
-//
25
-// As a starting point a very basic usage example:
24
+// As a starting point, a very basic usage example:
26 25
 //
27 26
 //    package main
28 27
 //
... ...
@@ -30,6 +29,7 @@
30 30
 //    	"net/http"
31 31
 //
32 32
 //    	"github.com/prometheus/client_golang/prometheus"
33
+//    	"github.com/prometheus/client_golang/prometheus/promhttp"
33 34
 //    )
34 35
 //
35 36
 //    var (
... ...
@@ -37,75 +37,145 @@
37 37
 //    		Name: "cpu_temperature_celsius",
38 38
 //    		Help: "Current temperature of the CPU.",
39 39
 //    	})
40
-//    	hdFailures = prometheus.NewCounter(prometheus.CounterOpts{
41
-//    		Name: "hd_errors_total",
42
-//    		Help: "Number of hard-disk errors.",
43
-//    	})
40
+//    	hdFailures = prometheus.NewCounterVec(
41
+//    		prometheus.CounterOpts{
42
+//    			Name: "hd_errors_total",
43
+//    			Help: "Number of hard-disk errors.",
44
+//    		},
45
+//    		[]string{"device"},
46
+//    	)
44 47
 //    )
45 48
 //
46 49
 //    func init() {
50
+//    	// Metrics have to be registered to be exposed:
47 51
 //    	prometheus.MustRegister(cpuTemp)
48 52
 //    	prometheus.MustRegister(hdFailures)
49 53
 //    }
50 54
 //
51 55
 //    func main() {
52 56
 //    	cpuTemp.Set(65.3)
53
-//    	hdFailures.Inc()
57
+//    	hdFailures.With(prometheus.Labels{"device":"/dev/sda"}).Inc()
54 58
 //
55
-//    	http.Handle("/metrics", prometheus.Handler())
59
+//    	// The Handler function provides a default handler to expose metrics
60
+//    	// via an HTTP server. "/metrics" is the usual endpoint for that.
61
+//    	http.Handle("/metrics", promhttp.Handler())
56 62
 //    	http.ListenAndServe(":8080", nil)
57 63
 //    }
58 64
 //
59 65
 //
60
-// This is a complete program that exports two metrics, a Gauge and a Counter.
61
-// It also exports some stats about the HTTP usage of the /metrics
62
-// endpoint. (See the Handler function for more detail.)
66
+// This is a complete program that exports two metrics, a Gauge and a Counter,
67
+// the latter with a label attached to turn it into a (one-dimensional) vector.
68
+//
69
+// Metrics
63 70
 //
64
-// Two more advanced metric types are the Summary and Histogram. A more
65
-// thorough description of metric types can be found in the prometheus docs:
71
+// The number of exported identifiers in this package might appear a bit
72
+// overwhelming. Hovever, in addition to the basic plumbing shown in the example
73
+// above, you only need to understand the different metric types and their
74
+// vector versions for basic usage.
75
+//
76
+// Above, you have already touched the Counter and the Gauge. There are two more
77
+// advanced metric types: the Summary and Histogram. A more thorough description
78
+// of those four metric types can be found in the Prometheus docs:
66 79
 // https://prometheus.io/docs/concepts/metric_types/
67 80
 //
68
-// In addition to the fundamental metric types Gauge, Counter, Summary, and
69
-// Histogram, a very important part of the Prometheus data model is the
70
-// partitioning of samples along dimensions called labels, which results in
81
+// A fifth "type" of metric is Untyped. It behaves like a Gauge, but signals the
82
+// Prometheus server not to assume anything about its type.
83
+//
84
+// In addition to the fundamental metric types Gauge, Counter, Summary,
85
+// Histogram, and Untyped, a very important part of the Prometheus data model is
86
+// the partitioning of samples along dimensions called labels, which results in
71 87
 // metric vectors. The fundamental types are GaugeVec, CounterVec, SummaryVec,
72
-// and HistogramVec.
73
-//
74
-// Those are all the parts needed for basic usage. Detailed documentation and
75
-// examples are provided below.
76
-//
77
-// Everything else this package offers is essentially for "power users" only. A
78
-// few pointers to "power user features":
79
-//
80
-// All the various ...Opts structs have a ConstLabels field for labels that
81
-// never change their value (which is only useful under special circumstances,
82
-// see documentation of the Opts type).
83
-//
84
-// The Untyped metric behaves like a Gauge, but signals the Prometheus server
85
-// not to assume anything about its type.
86
-//
87
-// Functions to fine-tune how the metric registry works: EnableCollectChecks,
88
-// PanicOnCollectError, Register, Unregister, SetMetricFamilyInjectionHook.
89
-//
90
-// For custom metric collection, there are two entry points: Custom Metric
91
-// implementations and custom Collector implementations. A Metric is the
92
-// fundamental unit in the Prometheus data model: a sample at a point in time
93
-// together with its meta-data (like its fully-qualified name and any number of
94
-// pairs of label name and label value) that knows how to marshal itself into a
95
-// data transfer object (aka DTO, implemented as a protocol buffer). A Collector
96
-// gets registered with the Prometheus registry and manages the collection of
97
-// one or more Metrics. Many parts of this package are building blocks for
98
-// Metrics and Collectors. Desc is the metric descriptor, actually used by all
99
-// metrics under the hood, and by Collectors to describe the Metrics to be
100
-// collected, but only to be dealt with by users if they implement their own
101
-// Metrics or Collectors. To create a Desc, the BuildFQName function will come
102
-// in handy. Other useful components for Metric and Collector implementation
103
-// include: LabelPairSorter to sort the DTO version of label pairs,
104
-// NewConstMetric and MustNewConstMetric to create "throw away" Metrics at
105
-// collection time, MetricVec to bundle custom Metrics into a metric vector
106
-// Collector, SelfCollector to make a custom Metric collect itself.
107
-//
108
-// A good example for a custom Collector is the ExpVarCollector included in this
109
-// package, which exports variables exported via the "expvar" package as
110
-// Prometheus metrics.
88
+// HistogramVec, and UntypedVec.
89
+//
90
+// While only the fundamental metric types implement the Metric interface, both
91
+// the metrics and their vector versions implement the Collector interface. A
92
+// Collector manages the collection of a number of Metrics, but for convenience,
93
+// a Metric can also “collect itself”. Note that Gauge, Counter, Summary,
94
+// Histogram, and Untyped are interfaces themselves while GaugeVec, CounterVec,
95
+// SummaryVec, HistogramVec, and UntypedVec are not.
96
+//
97
+// To create instances of Metrics and their vector versions, you need a suitable
98
+// …Opts struct, i.e. GaugeOpts, CounterOpts, SummaryOpts,
99
+// HistogramOpts, or UntypedOpts.
100
+//
101
+// Custom Collectors and constant Metrics
102
+//
103
+// While you could create your own implementations of Metric, most likely you
104
+// will only ever implement the Collector interface on your own. At a first
105
+// glance, a custom Collector seems handy to bundle Metrics for common
106
+// registration (with the prime example of the different metric vectors above,
107
+// which bundle all the metrics of the same name but with different labels).
108
+//
109
+// There is a more involved use case, too: If you already have metrics
110
+// available, created outside of the Prometheus context, you don't need the
111
+// interface of the various Metric types. You essentially want to mirror the
112
+// existing numbers into Prometheus Metrics during collection. An own
113
+// implementation of the Collector interface is perfect for that. You can create
114
+// Metric instances “on the fly” using NewConstMetric, NewConstHistogram, and
115
+// NewConstSummary (and their respective Must… versions). That will happen in
116
+// the Collect method. The Describe method has to return separate Desc
117
+// instances, representative of the “throw-away” metrics to be created
118
+// later. NewDesc comes in handy to create those Desc instances.
119
+//
120
+// The Collector example illustrates the use case. You can also look at the
121
+// source code of the processCollector (mirroring process metrics), the
122
+// goCollector (mirroring Go metrics), or the expvarCollector (mirroring expvar
123
+// metrics) as examples that are used in this package itself.
124
+//
125
+// If you just need to call a function to get a single float value to collect as
126
+// a metric, GaugeFunc, CounterFunc, or UntypedFunc might be interesting
127
+// shortcuts.
128
+//
129
+// Advanced Uses of the Registry
130
+//
131
+// While MustRegister is the by far most common way of registering a Collector,
132
+// sometimes you might want to handle the errors the registration might
133
+// cause. As suggested by the name, MustRegister panics if an error occurs. With
134
+// the Register function, the error is returned and can be handled.
135
+//
136
+// An error is returned if the registered Collector is incompatible or
137
+// inconsistent with already registered metrics. The registry aims for
138
+// consistency of the collected metrics according to the Prometheus data
139
+// model. Inconsistencies are ideally detected at registration time, not at
140
+// collect time. The former will usually be detected at start-up time of a
141
+// program, while the latter will only happen at scrape time, possibly not even
142
+// on the first scrape if the inconsistency only becomes relevant later. That is
143
+// the main reason why a Collector and a Metric have to describe themselves to
144
+// the registry.
145
+//
146
+// So far, everything we did operated on the so-called default registry, as it
147
+// can be found in the global DefaultRegistry variable. With NewRegistry, you
148
+// can create a custom registry, or you can even implement the Registerer or
149
+// Gatherer interfaces yourself. The methods Register and Unregister work in
150
+// the same way on a custom registry as the global functions Register and
151
+// Unregister on the default registry.
152
+//
153
+// There are a number of uses for custom registries: You can use registries
154
+// with special properties, see NewPedanticRegistry. You can avoid global state,
155
+// as it is imposed by the DefaultRegistry. You can use multiple registries at
156
+// the same time to expose different metrics in different ways. You can use
157
+// separate registries for testing purposes.
158
+//
159
+// Also note that the DefaultRegistry comes registered with a Collector for Go
160
+// runtime metrics (via NewGoCollector) and a Collector for process metrics (via
161
+// NewProcessCollector). With a custom registry, you are in control and decide
162
+// yourself about the Collectors to register.
163
+//
164
+// HTTP Exposition
165
+//
166
+// The Registry implements the Gatherer interface. The caller of the Gather
167
+// method can then expose the gathered metrics in some way. Usually, the metrics
168
+// are served via HTTP on the /metrics endpoint. That's happening in the example
169
+// above. The tools to expose metrics via HTTP are in the promhttp
170
+// sub-package. (The top-level functions in the prometheus package are
171
+// deprecated.)
172
+//
173
+// Pushing to the Pushgateway
174
+//
175
+// Function for pushing to the Pushgateway can be found in the push sub-package.
176
+//
177
+// Other Means of Exposition
178
+//
179
+// More ways of exposing metrics can easily be added. Sending metrics to
180
+// Graphite would be an example that will soon be implemented.
111 181
 package prometheus
112 182
deleted file mode 100644
... ...
@@ -1,119 +0,0 @@
1
-// Copyright 2014 The Prometheus Authors
2
-// Licensed under the Apache License, Version 2.0 (the "License");
3
-// you may not use this file except in compliance with the License.
4
-// You may obtain a copy of the License at
5
-//
6
-// http://www.apache.org/licenses/LICENSE-2.0
7
-//
8
-// Unless required by applicable law or agreed to in writing, software
9
-// distributed under the License is distributed on an "AS IS" BASIS,
10
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
-// See the License for the specific language governing permissions and
12
-// limitations under the License.
13
-
14
-package prometheus
15
-
16
-import (
17
-	"encoding/json"
18
-	"expvar"
19
-)
20
-
21
-// ExpvarCollector collects metrics from the expvar interface. It provides a
22
-// quick way to expose numeric values that are already exported via expvar as
23
-// Prometheus metrics. Note that the data models of expvar and Prometheus are
24
-// fundamentally different, and that the ExpvarCollector is inherently
25
-// slow. Thus, the ExpvarCollector is probably great for experiments and
26
-// prototying, but you should seriously consider a more direct implementation of
27
-// Prometheus metrics for monitoring production systems.
28
-//
29
-// Use NewExpvarCollector to create new instances.
30
-type ExpvarCollector struct {
31
-	exports map[string]*Desc
32
-}
33
-
34
-// NewExpvarCollector returns a newly allocated ExpvarCollector that still has
35
-// to be registered with the Prometheus registry.
36
-//
37
-// The exports map has the following meaning:
38
-//
39
-// The keys in the map correspond to expvar keys, i.e. for every expvar key you
40
-// want to export as Prometheus metric, you need an entry in the exports
41
-// map. The descriptor mapped to each key describes how to export the expvar
42
-// value. It defines the name and the help string of the Prometheus metric
43
-// proxying the expvar value. The type will always be Untyped.
44
-//
45
-// For descriptors without variable labels, the expvar value must be a number or
46
-// a bool. The number is then directly exported as the Prometheus sample
47
-// value. (For a bool, 'false' translates to 0 and 'true' to 1). Expvar values
48
-// that are not numbers or bools are silently ignored.
49
-//
50
-// If the descriptor has one variable label, the expvar value must be an expvar
51
-// map. The keys in the expvar map become the various values of the one
52
-// Prometheus label. The values in the expvar map must be numbers or bools again
53
-// as above.
54
-//
55
-// For descriptors with more than one variable label, the expvar must be a
56
-// nested expvar map, i.e. where the values of the topmost map are maps again
57
-// etc. until a depth is reached that corresponds to the number of labels. The
58
-// leaves of that structure must be numbers or bools as above to serve as the
59
-// sample values.
60
-//
61
-// Anything that does not fit into the scheme above is silently ignored.
62
-func NewExpvarCollector(exports map[string]*Desc) *ExpvarCollector {
63
-	return &ExpvarCollector{
64
-		exports: exports,
65
-	}
66
-}
67
-
68
-// Describe implements Collector.
69
-func (e *ExpvarCollector) Describe(ch chan<- *Desc) {
70
-	for _, desc := range e.exports {
71
-		ch <- desc
72
-	}
73
-}
74
-
75
-// Collect implements Collector.
76
-func (e *ExpvarCollector) Collect(ch chan<- Metric) {
77
-	for name, desc := range e.exports {
78
-		var m Metric
79
-		expVar := expvar.Get(name)
80
-		if expVar == nil {
81
-			continue
82
-		}
83
-		var v interface{}
84
-		labels := make([]string, len(desc.variableLabels))
85
-		if err := json.Unmarshal([]byte(expVar.String()), &v); err != nil {
86
-			ch <- NewInvalidMetric(desc, err)
87
-			continue
88
-		}
89
-		var processValue func(v interface{}, i int)
90
-		processValue = func(v interface{}, i int) {
91
-			if i >= len(labels) {
92
-				copiedLabels := append(make([]string, 0, len(labels)), labels...)
93
-				switch v := v.(type) {
94
-				case float64:
95
-					m = MustNewConstMetric(desc, UntypedValue, v, copiedLabels...)
96
-				case bool:
97
-					if v {
98
-						m = MustNewConstMetric(desc, UntypedValue, 1, copiedLabels...)
99
-					} else {
100
-						m = MustNewConstMetric(desc, UntypedValue, 0, copiedLabels...)
101
-					}
102
-				default:
103
-					return
104
-				}
105
-				ch <- m
106
-				return
107
-			}
108
-			vm, ok := v.(map[string]interface{})
109
-			if !ok {
110
-				return
111
-			}
112
-			for lv, val := range vm {
113
-				labels[i] = lv
114
-				processValue(val, i+1)
115
-			}
116
-		}
117
-		processValue(v, 0)
118
-	}
119
-}
120 1
new file mode 100644
... ...
@@ -0,0 +1,119 @@
0
+// Copyright 2014 The Prometheus Authors
1
+// Licensed under the Apache License, Version 2.0 (the "License");
2
+// you may not use this file except in compliance with the License.
3
+// You may obtain a copy of the License at
4
+//
5
+// http://www.apache.org/licenses/LICENSE-2.0
6
+//
7
+// Unless required by applicable law or agreed to in writing, software
8
+// distributed under the License is distributed on an "AS IS" BASIS,
9
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+// See the License for the specific language governing permissions and
11
+// limitations under the License.
12
+
13
+package prometheus
14
+
15
+import (
16
+	"encoding/json"
17
+	"expvar"
18
+)
19
+
20
+type expvarCollector struct {
21
+	exports map[string]*Desc
22
+}
23
+
24
+// NewExpvarCollector returns a newly allocated expvar Collector that still has
25
+// to be registered with a Prometheus registry.
26
+//
27
+// An expvar Collector collects metrics from the expvar interface. It provides a
28
+// quick way to expose numeric values that are already exported via expvar as
29
+// Prometheus metrics. Note that the data models of expvar and Prometheus are
30
+// fundamentally different, and that the expvar Collector is inherently slower
31
+// than native Prometheus metrics. Thus, the expvar Collector is probably great
32
+// for experiments and prototying, but you should seriously consider a more
33
+// direct implementation of Prometheus metrics for monitoring production
34
+// systems.
35
+//
36
+// The exports map has the following meaning:
37
+//
38
+// The keys in the map correspond to expvar keys, i.e. for every expvar key you
39
+// want to export as Prometheus metric, you need an entry in the exports
40
+// map. The descriptor mapped to each key describes how to export the expvar
41
+// value. It defines the name and the help string of the Prometheus metric
42
+// proxying the expvar value. The type will always be Untyped.
43
+//
44
+// For descriptors without variable labels, the expvar value must be a number or
45
+// a bool. The number is then directly exported as the Prometheus sample
46
+// value. (For a bool, 'false' translates to 0 and 'true' to 1). Expvar values
47
+// that are not numbers or bools are silently ignored.
48
+//
49
+// If the descriptor has one variable label, the expvar value must be an expvar
50
+// map. The keys in the expvar map become the various values of the one
51
+// Prometheus label. The values in the expvar map must be numbers or bools again
52
+// as above.
53
+//
54
+// For descriptors with more than one variable label, the expvar must be a
55
+// nested expvar map, i.e. where the values of the topmost map are maps again
56
+// etc. until a depth is reached that corresponds to the number of labels. The
57
+// leaves of that structure must be numbers or bools as above to serve as the
58
+// sample values.
59
+//
60
+// Anything that does not fit into the scheme above is silently ignored.
61
+func NewExpvarCollector(exports map[string]*Desc) Collector {
62
+	return &expvarCollector{
63
+		exports: exports,
64
+	}
65
+}
66
+
67
+// Describe implements Collector.
68
+func (e *expvarCollector) Describe(ch chan<- *Desc) {
69
+	for _, desc := range e.exports {
70
+		ch <- desc
71
+	}
72
+}
73
+
74
+// Collect implements Collector.
75
+func (e *expvarCollector) Collect(ch chan<- Metric) {
76
+	for name, desc := range e.exports {
77
+		var m Metric
78
+		expVar := expvar.Get(name)
79
+		if expVar == nil {
80
+			continue
81
+		}
82
+		var v interface{}
83
+		labels := make([]string, len(desc.variableLabels))
84
+		if err := json.Unmarshal([]byte(expVar.String()), &v); err != nil {
85
+			ch <- NewInvalidMetric(desc, err)
86
+			continue
87
+		}
88
+		var processValue func(v interface{}, i int)
89
+		processValue = func(v interface{}, i int) {
90
+			if i >= len(labels) {
91
+				copiedLabels := append(make([]string, 0, len(labels)), labels...)
92
+				switch v := v.(type) {
93
+				case float64:
94
+					m = MustNewConstMetric(desc, UntypedValue, v, copiedLabels...)
95
+				case bool:
96
+					if v {
97
+						m = MustNewConstMetric(desc, UntypedValue, 1, copiedLabels...)
98
+					} else {
99
+						m = MustNewConstMetric(desc, UntypedValue, 0, copiedLabels...)
100
+					}
101
+				default:
102
+					return
103
+				}
104
+				ch <- m
105
+				return
106
+			}
107
+			vm, ok := v.(map[string]interface{})
108
+			if !ok {
109
+				return
110
+			}
111
+			for lv, val := range vm {
112
+				labels[i] = lv
113
+				processValue(val, i+1)
114
+			}
115
+		}
116
+		processValue(v, 0)
117
+	}
118
+}
... ...
@@ -58,7 +58,7 @@ func NewGauge(opts GaugeOpts) Gauge {
58 58
 // (e.g. number of operations queued, partitioned by user and operation
59 59
 // type). Create instances with NewGaugeVec.
60 60
 type GaugeVec struct {
61
-	MetricVec
61
+	*MetricVec
62 62
 }
63 63
 
64 64
 // NewGaugeVec creates a new GaugeVec based on the provided GaugeOpts and
... ...
@@ -72,13 +72,9 @@ func NewGaugeVec(opts GaugeOpts, labelNames []string) *GaugeVec {
72 72
 		opts.ConstLabels,
73 73
 	)
74 74
 	return &GaugeVec{
75
-		MetricVec: MetricVec{
76
-			children: map[uint64]Metric{},
77
-			desc:     desc,
78
-			newMetric: func(lvs ...string) Metric {
79
-				return newValue(desc, GaugeValue, 0, lvs...)
80
-			},
81
-		},
75
+		MetricVec: newMetricVec(desc, func(lvs ...string) Metric {
76
+			return newValue(desc, GaugeValue, 0, lvs...)
77
+		}),
82 78
 	}
83 79
 }
84 80
 
... ...
@@ -17,7 +17,7 @@ type goCollector struct {
17 17
 
18 18
 // NewGoCollector returns a collector which exports metrics about the current
19 19
 // go process.
20
-func NewGoCollector() *goCollector {
20
+func NewGoCollector() Collector {
21 21
 	return &goCollector{
22 22
 		goroutines: NewGauge(GaugeOpts{
23 23
 			Namespace: "go",
... ...
@@ -51,11 +51,11 @@ type Histogram interface {
51 51
 // bucket of a histogram ("le" -> "less or equal").
52 52
 const bucketLabel = "le"
53 53
 
54
+// DefBuckets are the default Histogram buckets. The default buckets are
55
+// tailored to broadly measure the response time (in seconds) of a network
56
+// service. Most likely, however, you will be required to define buckets
57
+// customized to your use case.
54 58
 var (
55
-	// DefBuckets are the default Histogram buckets. The default buckets are
56
-	// tailored to broadly measure the response time (in seconds) of a
57
-	// network service. Most likely, however, you will be required to define
58
-	// buckets customized to your use case.
59 59
 	DefBuckets = []float64{.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10}
60 60
 
61 61
 	errBucketLabelNotAllowed = fmt.Errorf(
... ...
@@ -210,7 +210,7 @@ func newHistogram(desc *Desc, opts HistogramOpts, labelValues ...string) Histogr
210 210
 	// Finally we know the final length of h.upperBounds and can make counts.
211 211
 	h.counts = make([]uint64, len(h.upperBounds))
212 212
 
213
-	h.Init(h) // Init self-collection.
213
+	h.init(h) // Init self-collection.
214 214
 	return h
215 215
 }
216 216
 
... ...
@@ -222,7 +222,7 @@ type histogram struct {
222 222
 	sumBits uint64
223 223
 	count   uint64
224 224
 
225
-	SelfCollector
225
+	selfCollector
226 226
 	// Note that there is no mutex required.
227 227
 
228 228
 	desc *Desc
... ...
@@ -287,7 +287,7 @@ func (h *histogram) Write(out *dto.Metric) error {
287 287
 // (e.g. HTTP request latencies, partitioned by status code and method). Create
288 288
 // instances with NewHistogramVec.
289 289
 type HistogramVec struct {
290
-	MetricVec
290
+	*MetricVec
291 291
 }
292 292
 
293 293
 // NewHistogramVec creates a new HistogramVec based on the provided HistogramOpts and
... ...
@@ -301,13 +301,9 @@ func NewHistogramVec(opts HistogramOpts, labelNames []string) *HistogramVec {
301 301
 		opts.ConstLabels,
302 302
 	)
303 303
 	return &HistogramVec{
304
-		MetricVec: MetricVec{
305
-			children: map[uint64]Metric{},
306
-			desc:     desc,
307
-			newMetric: func(lvs ...string) Metric {
308
-				return newHistogram(desc, opts, lvs...)
309
-			},
310
-		},
304
+		MetricVec: newMetricVec(desc, func(lvs ...string) Metric {
305
+			return newHistogram(desc, opts, lvs...)
306
+		}),
311 307
 	}
312 308
 }
313 309
 
... ...
@@ -15,14 +15,114 @@ package prometheus
15 15
 
16 16
 import (
17 17
 	"bufio"
18
+	"bytes"
19
+	"compress/gzip"
20
+	"fmt"
18 21
 	"io"
19 22
 	"net"
20 23
 	"net/http"
21 24
 	"strconv"
22 25
 	"strings"
26
+	"sync"
23 27
 	"time"
28
+
29
+	"github.com/prometheus/common/expfmt"
30
+)
31
+
32
+// TODO(beorn7): Remove this whole file. It is a partial mirror of
33
+// promhttp/http.go (to avoid circular import chains) where everything HTTP
34
+// related should live. The functions here are just for avoiding
35
+// breakage. Everything is deprecated.
36
+
37
+const (
38
+	contentTypeHeader     = "Content-Type"
39
+	contentLengthHeader   = "Content-Length"
40
+	contentEncodingHeader = "Content-Encoding"
41
+	acceptEncodingHeader  = "Accept-Encoding"
24 42
 )
25 43
 
44
+var bufPool sync.Pool
45
+
46
+func getBuf() *bytes.Buffer {
47
+	buf := bufPool.Get()
48
+	if buf == nil {
49
+		return &bytes.Buffer{}
50
+	}
51
+	return buf.(*bytes.Buffer)
52
+}
53
+
54
+func giveBuf(buf *bytes.Buffer) {
55
+	buf.Reset()
56
+	bufPool.Put(buf)
57
+}
58
+
59
+// Handler returns an HTTP handler for the DefaultGatherer. It is
60
+// already instrumented with InstrumentHandler (using "prometheus" as handler
61
+// name).
62
+//
63
+// Deprecated: Please note the issues described in the doc comment of
64
+// InstrumentHandler. You might want to consider using promhttp.Handler instead
65
+// (which is non instrumented).
66
+func Handler() http.Handler {
67
+	return InstrumentHandler("prometheus", UninstrumentedHandler())
68
+}
69
+
70
+// UninstrumentedHandler returns an HTTP handler for the DefaultGatherer.
71
+//
72
+// Deprecated: Use promhttp.Handler instead. See there for further documentation.
73
+func UninstrumentedHandler() http.Handler {
74
+	return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
75
+		mfs, err := DefaultGatherer.Gather()
76
+		if err != nil {
77
+			http.Error(w, "An error has occurred during metrics collection:\n\n"+err.Error(), http.StatusInternalServerError)
78
+			return
79
+		}
80
+
81
+		contentType := expfmt.Negotiate(req.Header)
82
+		buf := getBuf()
83
+		defer giveBuf(buf)
84
+		writer, encoding := decorateWriter(req, buf)
85
+		enc := expfmt.NewEncoder(writer, contentType)
86
+		var lastErr error
87
+		for _, mf := range mfs {
88
+			if err := enc.Encode(mf); err != nil {
89
+				lastErr = err
90
+				http.Error(w, "An error has occurred during metrics encoding:\n\n"+err.Error(), http.StatusInternalServerError)
91
+				return
92
+			}
93
+		}
94
+		if closer, ok := writer.(io.Closer); ok {
95
+			closer.Close()
96
+		}
97
+		if lastErr != nil && buf.Len() == 0 {
98
+			http.Error(w, "No metrics encoded, last error:\n\n"+err.Error(), http.StatusInternalServerError)
99
+			return
100
+		}
101
+		header := w.Header()
102
+		header.Set(contentTypeHeader, string(contentType))
103
+		header.Set(contentLengthHeader, fmt.Sprint(buf.Len()))
104
+		if encoding != "" {
105
+			header.Set(contentEncodingHeader, encoding)
106
+		}
107
+		w.Write(buf.Bytes())
108
+	})
109
+}
110
+
111
+// decorateWriter wraps a writer to handle gzip compression if requested.  It
112
+// returns the decorated writer and the appropriate "Content-Encoding" header
113
+// (which is empty if no compression is enabled).
114
+func decorateWriter(request *http.Request, writer io.Writer) (io.Writer, string) {
115
+	header := request.Header.Get(acceptEncodingHeader)
116
+	parts := strings.Split(header, ",")
117
+	for _, part := range parts {
118
+		part := strings.TrimSpace(part)
119
+		if part == "gzip" || strings.HasPrefix(part, "gzip;") {
120
+			return gzip.NewWriter(writer), "gzip"
121
+		}
122
+	}
123
+	return writer, ""
124
+}
125
+
26 126
 var instLabels = []string{"method", "code"}
27 127
 
28 128
 type nower interface {
... ...
@@ -58,7 +158,7 @@ func nowSeries(t ...time.Time) nower {
58 58
 // value. http_requests_total is a metric vector partitioned by HTTP method
59 59
 // (label name "method") and HTTP status code (label name "code").
60 60
 //
61
-// Note that InstrumentHandler has several issues:
61
+// Deprecated: InstrumentHandler has several issues:
62 62
 //
63 63
 // - It uses Summaries rather than Histograms. Summaries are not useful if
64 64
 // aggregation across multiple instances is required.
... ...
@@ -73,8 +173,8 @@ func nowSeries(t ...time.Time) nower {
73 73
 // performing such writes.
74 74
 //
75 75
 // Upcoming versions of this package will provide ways of instrumenting HTTP
76
-// handlers that are more flexible and have fewer issues. Consider this function
77
-// DEPRECATED and prefer direct instrumentation in the meantime.
76
+// handlers that are more flexible and have fewer issues. Please prefer direct
77
+// instrumentation in the meantime.
78 78
 func InstrumentHandler(handlerName string, handler http.Handler) http.HandlerFunc {
79 79
 	return InstrumentHandlerFunc(handlerName, handler.ServeHTTP)
80 80
 }
... ...
@@ -82,6 +182,9 @@ func InstrumentHandler(handlerName string, handler http.Handler) http.HandlerFun
82 82
 // InstrumentHandlerFunc wraps the given function for instrumentation. It
83 83
 // otherwise works in the same way as InstrumentHandler (and shares the same
84 84
 // issues).
85
+//
86
+// Deprecated: InstrumentHandlerFunc is deprecated for the same reasons as
87
+// InstrumentHandler is.
85 88
 func InstrumentHandlerFunc(handlerName string, handlerFunc func(http.ResponseWriter, *http.Request)) http.HandlerFunc {
86 89
 	return InstrumentHandlerFuncWithOpts(
87 90
 		SummaryOpts{
... ...
@@ -117,6 +220,9 @@ func InstrumentHandlerFunc(handlerName string, handlerFunc func(http.ResponseWri
117 117
 // cannot use SummaryOpts. Instead, a CounterOpts struct is created internally,
118 118
 // and all its fields are set to the equally named fields in the provided
119 119
 // SummaryOpts.
120
+//
121
+// Deprecated: InstrumentHandlerWithOpts is deprecated for the same reasons as
122
+// InstrumentHandler is.
120 123
 func InstrumentHandlerWithOpts(opts SummaryOpts, handler http.Handler) http.HandlerFunc {
121 124
 	return InstrumentHandlerFuncWithOpts(opts, handler.ServeHTTP)
122 125
 }
... ...
@@ -125,6 +231,9 @@ func InstrumentHandlerWithOpts(opts SummaryOpts, handler http.Handler) http.Hand
125 125
 // the same issues) but provides more flexibility (at the cost of a more complex
126 126
 // call syntax). See InstrumentHandlerWithOpts for details how the provided
127 127
 // SummaryOpts are used.
128
+//
129
+// Deprecated: InstrumentHandlerFuncWithOpts is deprecated for the same reasons
130
+// as InstrumentHandler is.
128 131
 func InstrumentHandlerFuncWithOpts(opts SummaryOpts, handlerFunc func(http.ResponseWriter, *http.Request)) http.HandlerFunc {
129 132
 	reqCnt := NewCounterVec(
130 133
 		CounterOpts{
... ...
@@ -22,10 +22,8 @@ import (
22 22
 const separatorByte byte = 255
23 23
 
24 24
 // A Metric models a single sample value with its meta data being exported to
25
-// Prometheus. Implementers of Metric in this package inclued Gauge, Counter,
26
-// Untyped, and Summary. Users can implement their own Metric types, but that
27
-// should be rarely needed. See the example for SelfCollector, which is also an
28
-// example for a user-implemented Metric.
25
+// Prometheus. Implementations of Metric in this package are Gauge, Counter,
26
+// Histogram, Summary, and Untyped.
29 27
 type Metric interface {
30 28
 	// Desc returns the descriptor for the Metric. This method idempotently
31 29
 	// returns the same descriptor throughout the lifetime of the
... ...
@@ -36,21 +34,23 @@ type Metric interface {
36 36
 	// Write encodes the Metric into a "Metric" Protocol Buffer data
37 37
 	// transmission object.
38 38
 	//
39
-	// Implementers of custom Metric types must observe concurrency safety
40
-	// as reads of this metric may occur at any time, and any blocking
41
-	// occurs at the expense of total performance of rendering all
42
-	// registered metrics. Ideally Metric implementations should support
43
-	// concurrent readers.
39
+	// Metric implementations must observe concurrency safety as reads of
40
+	// this metric may occur at any time, and any blocking occurs at the
41
+	// expense of total performance of rendering all registered
42
+	// metrics. Ideally, Metric implementations should support concurrent
43
+	// readers.
44 44
 	//
45
-	// The Prometheus client library attempts to minimize memory allocations
46
-	// and will provide a pre-existing reset dto.Metric pointer. Prometheus
47
-	// may recycle the dto.Metric proto message, so Metric implementations
48
-	// should just populate the provided dto.Metric and then should not keep
49
-	// any reference to it.
50
-	//
51
-	// While populating dto.Metric, labels must be sorted lexicographically.
52
-	// (Implementers may find LabelPairSorter useful for that.)
45
+	// While populating dto.Metric, it is the responsibility of the
46
+	// implementation to ensure validity of the Metric protobuf (like valid
47
+	// UTF-8 strings or syntactically valid metric and label names). It is
48
+	// recommended to sort labels lexicographically. (Implementers may find
49
+	// LabelPairSorter useful for that.) Callers of Write should still make
50
+	// sure of sorting if they depend on it.
53 51
 	Write(*dto.Metric) error
52
+	// TODO(beorn7): The original rationale of passing in a pre-allocated
53
+	// dto.Metric protobuf to save allocations has disappeared. The
54
+	// signature of this method should be changed to "Write() (*dto.Metric,
55
+	// error)".
54 56
 }
55 57
 
56 58
 // Opts bundles the options for creating most Metric types. Each metric
... ...
@@ -28,7 +28,7 @@ type processCollector struct {
28 28
 // NewProcessCollector returns a collector which exports the current state of
29 29
 // process metrics including cpu, memory and file descriptor usage as well as
30 30
 // the process start time for the given process id under the given namespace.
31
-func NewProcessCollector(pid int, namespace string) *processCollector {
31
+func NewProcessCollector(pid int, namespace string) Collector {
32 32
 	return NewProcessCollectorPIDFn(
33 33
 		func() (int, error) { return pid, nil },
34 34
 		namespace,
... ...
@@ -43,7 +43,7 @@ func NewProcessCollector(pid int, namespace string) *processCollector {
43 43
 func NewProcessCollectorPIDFn(
44 44
 	pidFn func() (int, error),
45 45
 	namespace string,
46
-) *processCollector {
46
+) Collector {
47 47
 	c := processCollector{
48 48
 		pidFn:     pidFn,
49 49
 		collectFn: func(chan<- Metric) {},
50 50
deleted file mode 100644
... ...
@@ -1,65 +0,0 @@
1
-// Copyright 2015 The Prometheus Authors
2
-// Licensed under the Apache License, Version 2.0 (the "License");
3
-// you may not use this file except in compliance with the License.
4
-// You may obtain a copy of the License at
5
-//
6
-// http://www.apache.org/licenses/LICENSE-2.0
7
-//
8
-// Unless required by applicable law or agreed to in writing, software
9
-// distributed under the License is distributed on an "AS IS" BASIS,
10
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
-// See the License for the specific language governing permissions and
12
-// limitations under the License.
13
-
14
-// Copyright (c) 2013, The Prometheus Authors
15
-// All rights reserved.
16
-//
17
-// Use of this source code is governed by a BSD-style license that can be found
18
-// in the LICENSE file.
19
-
20
-package prometheus
21
-
22
-// Push triggers a metric collection by the default registry and pushes all
23
-// collected metrics to the Pushgateway specified by url. See the Pushgateway
24
-// documentation for detailed implications of the job and instance
25
-// parameter. instance can be left empty. You can use just host:port or ip:port
26
-// as url, in which case 'http://' is added automatically. You can also include
27
-// the schema in the URL. However, do not include the '/metrics/jobs/...' part.
28
-//
29
-// Note that all previously pushed metrics with the same job and instance will
30
-// be replaced with the metrics pushed by this call. (It uses HTTP method 'PUT'
31
-// to push to the Pushgateway.)
32
-func Push(job, instance, url string) error {
33
-	return defRegistry.Push(job, instance, url, "PUT")
34
-}
35
-
36
-// PushAdd works like Push, but only previously pushed metrics with the same
37
-// name (and the same job and instance) will be replaced. (It uses HTTP method
38
-// 'POST' to push to the Pushgateway.)
39
-func PushAdd(job, instance, url string) error {
40
-	return defRegistry.Push(job, instance, url, "POST")
41
-}
42
-
43
-// PushCollectors works like Push, but it does not collect from the default
44
-// registry. Instead, it collects from the provided collectors. It is a
45
-// convenient way to push only a few metrics.
46
-func PushCollectors(job, instance, url string, collectors ...Collector) error {
47
-	return pushCollectors(job, instance, url, "PUT", collectors...)
48
-}
49
-
50
-// PushAddCollectors works like PushAdd, but it does not collect from the
51
-// default registry. Instead, it collects from the provided collectors. It is a
52
-// convenient way to push only a few metrics.
53
-func PushAddCollectors(job, instance, url string, collectors ...Collector) error {
54
-	return pushCollectors(job, instance, url, "POST", collectors...)
55
-}
56
-
57
-func pushCollectors(job, instance, url, method string, collectors ...Collector) error {
58
-	r := newRegistry()
59
-	for _, collector := range collectors {
60
-		if _, err := r.Register(collector); err != nil {
61
-			return err
62
-		}
63
-	}
64
-	return r.Push(job, instance, url, method)
65
-}
... ...
@@ -11,224 +11,287 @@
11 11
 // See the License for the specific language governing permissions and
12 12
 // limitations under the License.
13 13
 
14
-// Copyright (c) 2013, The Prometheus Authors
15
-// All rights reserved.
16
-//
17
-// Use of this source code is governed by a BSD-style license that can be found
18
-// in the LICENSE file.
19
-
20 14
 package prometheus
21 15
 
22 16
 import (
23 17
 	"bytes"
24
-	"compress/gzip"
25 18
 	"errors"
26 19
 	"fmt"
27
-	"io"
28
-	"net/http"
29
-	"net/url"
30 20
 	"os"
31 21
 	"sort"
32
-	"strings"
33 22
 	"sync"
34 23
 
35 24
 	"github.com/golang/protobuf/proto"
36
-	"github.com/prometheus/common/expfmt"
37 25
 
38 26
 	dto "github.com/prometheus/client_model/go"
39 27
 )
40 28
 
41
-var (
42
-	defRegistry   = newDefaultRegistry()
43
-	errAlreadyReg = errors.New("duplicate metrics collector registration attempted")
44
-)
45
-
46
-// Constants relevant to the HTTP interface.
47 29
 const (
48
-	// APIVersion is the version of the format of the exported data.  This
49
-	// will match this library's version, which subscribes to the Semantic
50
-	// Versioning scheme.
51
-	APIVersion = "0.0.4"
52
-
53
-	// DelimitedTelemetryContentType is the content type set on telemetry
54
-	// data responses in delimited protobuf format.
55
-	DelimitedTelemetryContentType = `application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited`
56
-	// TextTelemetryContentType is the content type set on telemetry data
57
-	// responses in text format.
58
-	TextTelemetryContentType = `text/plain; version=` + APIVersion
59
-	// ProtoTextTelemetryContentType is the content type set on telemetry
60
-	// data responses in protobuf text format.  (Only used for debugging.)
61
-	ProtoTextTelemetryContentType = `application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=text`
62
-	// ProtoCompactTextTelemetryContentType is the content type set on
63
-	// telemetry data responses in protobuf compact text format.  (Only used
64
-	// for debugging.)
65
-	ProtoCompactTextTelemetryContentType = `application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=compact-text`
66
-
67
-	// Constants for object pools.
68
-	numBufs           = 4
69
-	numMetricFamilies = 1000
70
-	numMetrics        = 10000
71
-
72 30
 	// Capacity for the channel to collect metrics and descriptors.
73 31
 	capMetricChan = 1000
74 32
 	capDescChan   = 10
33
+)
75 34
 
76
-	contentTypeHeader     = "Content-Type"
77
-	contentLengthHeader   = "Content-Length"
78
-	contentEncodingHeader = "Content-Encoding"
79
-
80
-	acceptEncodingHeader = "Accept-Encoding"
81
-	acceptHeader         = "Accept"
35
+// DefaultRegisterer and DefaultGatherer are the implementations of the
36
+// Registerer and Gatherer interface a number of convenience functions in this
37
+// package act on. Initially, both variables point to the same Registry, which
38
+// has a process collector (see NewProcessCollector) and a Go collector (see
39
+// NewGoCollector) already registered. This approach to keep default instances
40
+// as global state mirrors the approach of other packages in the Go standard
41
+// library. Note that there are caveats. Change the variables with caution and
42
+// only if you understand the consequences. Users who want to avoid global state
43
+// altogether should not use the convenience function and act on custom
44
+// instances instead.
45
+var (
46
+	defaultRegistry              = NewRegistry()
47
+	DefaultRegisterer Registerer = defaultRegistry
48
+	DefaultGatherer   Gatherer   = defaultRegistry
82 49
 )
83 50
 
84
-// Handler returns the HTTP handler for the global Prometheus registry. It is
85
-// already instrumented with InstrumentHandler (using "prometheus" as handler
86
-// name). Usually the handler is used to handle the "/metrics" endpoint.
51
+func init() {
52
+	MustRegister(NewProcessCollector(os.Getpid(), ""))
53
+	MustRegister(NewGoCollector())
54
+}
55
+
56
+// NewRegistry creates a new vanilla Registry without any Collectors
57
+// pre-registered.
58
+func NewRegistry() *Registry {
59
+	return &Registry{
60
+		collectorsByID:  map[uint64]Collector{},
61
+		descIDs:         map[uint64]struct{}{},
62
+		dimHashesByName: map[string]uint64{},
63
+	}
64
+}
65
+
66
+// NewPedanticRegistry returns a registry that checks during collection if each
67
+// collected Metric is consistent with its reported Desc, and if the Desc has
68
+// actually been registered with the registry.
87 69
 //
88
-// Please note the issues described in the doc comment of InstrumentHandler. You
89
-// might want to consider using UninstrumentedHandler instead.
90
-func Handler() http.Handler {
91
-	return InstrumentHandler("prometheus", defRegistry)
70
+// Usually, a Registry will be happy as long as the union of all collected
71
+// Metrics is consistent and valid even if some metrics are not consistent with
72
+// their own Desc or a Desc provided by their registered Collector. Well-behaved
73
+// Collectors and Metrics will only provide consistent Descs. This Registry is
74
+// useful to test the implementation of Collectors and Metrics.
75
+func NewPedanticRegistry() *Registry {
76
+	r := NewRegistry()
77
+	r.pedanticChecksEnabled = true
78
+	return r
92 79
 }
93 80
 
94
-// UninstrumentedHandler works in the same way as Handler, but the returned HTTP
95
-// handler is not instrumented. This is useful if no instrumentation is desired
96
-// (for whatever reason) or if the instrumentation has to happen with a
97
-// different handler name (or with a different instrumentation approach
98
-// altogether). See the InstrumentHandler example.
99
-func UninstrumentedHandler() http.Handler {
100
-	return defRegistry
81
+// Registerer is the interface for the part of a registry in charge of
82
+// registering and unregistering. Users of custom registries should use
83
+// Registerer as type for registration purposes (rather then the Registry type
84
+// directly). In that way, they are free to use custom Registerer implementation
85
+// (e.g. for testing purposes).
86
+type Registerer interface {
87
+	// Register registers a new Collector to be included in metrics
88
+	// collection. It returns an error if the descriptors provided by the
89
+	// Collector are invalid or if they — in combination with descriptors of
90
+	// already registered Collectors — do not fulfill the consistency and
91
+	// uniqueness criteria described in the documentation of metric.Desc.
92
+	//
93
+	// If the provided Collector is equal to a Collector already registered
94
+	// (which includes the case of re-registering the same Collector), the
95
+	// returned error is an instance of AlreadyRegisteredError, which
96
+	// contains the previously registered Collector.
97
+	//
98
+	// It is in general not safe to register the same Collector multiple
99
+	// times concurrently.
100
+	Register(Collector) error
101
+	// MustRegister works like Register but registers any number of
102
+	// Collectors and panics upon the first registration that causes an
103
+	// error.
104
+	MustRegister(...Collector)
105
+	// Unregister unregisters the Collector that equals the Collector passed
106
+	// in as an argument.  (Two Collectors are considered equal if their
107
+	// Describe method yields the same set of descriptors.) The function
108
+	// returns whether a Collector was unregistered.
109
+	//
110
+	// Note that even after unregistering, it will not be possible to
111
+	// register a new Collector that is inconsistent with the unregistered
112
+	// Collector, e.g. a Collector collecting metrics with the same name but
113
+	// a different help string. The rationale here is that the same registry
114
+	// instance must only collect consistent metrics throughout its
115
+	// lifetime.
116
+	Unregister(Collector) bool
101 117
 }
102 118
 
103
-// Register registers a new Collector to be included in metrics collection. It
104
-// returns an error if the descriptors provided by the Collector are invalid or
105
-// if they - in combination with descriptors of already registered Collectors -
106
-// do not fulfill the consistency and uniqueness criteria described in the Desc
107
-// documentation.
119
+// Gatherer is the interface for the part of a registry in charge of gathering
120
+// the collected metrics into a number of MetricFamilies. The Gatherer interface
121
+// comes with the same general implication as described for the Registerer
122
+// interface.
123
+type Gatherer interface {
124
+	// Gather calls the Collect method of the registered Collectors and then
125
+	// gathers the collected metrics into a lexicographically sorted slice
126
+	// of MetricFamily protobufs. Even if an error occurs, Gather attempts
127
+	// to gather as many metrics as possible. Hence, if a non-nil error is
128
+	// returned, the returned MetricFamily slice could be nil (in case of a
129
+	// fatal error that prevented any meaningful metric collection) or
130
+	// contain a number of MetricFamily protobufs, some of which might be
131
+	// incomplete, and some might be missing altogether. The returned error
132
+	// (which might be a MultiError) explains the details. In scenarios
133
+	// where complete collection is critical, the returned MetricFamily
134
+	// protobufs should be disregarded if the returned error is non-nil.
135
+	Gather() ([]*dto.MetricFamily, error)
136
+}
137
+
138
+// Register registers the provided Collector with the DefaultRegisterer.
108 139
 //
109
-// Do not register the same Collector multiple times concurrently. (Registering
110
-// the same Collector twice would result in an error anyway, but on top of that,
111
-// it is not safe to do so concurrently.)
112
-func Register(m Collector) error {
113
-	_, err := defRegistry.Register(m)
114
-	return err
140
+// Register is a shortcut for DefaultRegisterer.Register(c). See there for more
141
+// details.
142
+func Register(c Collector) error {
143
+	return DefaultRegisterer.Register(c)
115 144
 }
116 145
 
117
-// MustRegister works like Register but panics where Register would have
118
-// returned an error. MustRegister is also Variadic, where Register only
119
-// accepts a single Collector to register.
120
-func MustRegister(m ...Collector) {
121
-	for i := range m {
122
-		if err := Register(m[i]); err != nil {
123
-			panic(err)
124
-		}
125
-	}
146
+// MustRegister registers the provided Collectors with the DefaultRegisterer and
147
+// panics if any error occurs.
148
+//
149
+// MustRegister is a shortcut for DefaultRegisterer.MustRegister(cs...). See
150
+// there for more details.
151
+func MustRegister(cs ...Collector) {
152
+	DefaultRegisterer.MustRegister(cs...)
126 153
 }
127 154
 
128
-// RegisterOrGet works like Register but does not return an error if a Collector
129
-// is registered that equals a previously registered Collector. (Two Collectors
130
-// are considered equal if their Describe method yields the same set of
131
-// descriptors.) Instead, the previously registered Collector is returned (which
132
-// is helpful if the new and previously registered Collectors are equal but not
133
-// identical, i.e. not pointers to the same object).
155
+// RegisterOrGet registers the provided Collector with the DefaultRegisterer and
156
+// returns the Collector, unless an equal Collector was registered before, in
157
+// which case that Collector is returned.
134 158
 //
135
-// As for Register, it is still not safe to call RegisterOrGet with the same
136
-// Collector multiple times concurrently.
137
-func RegisterOrGet(m Collector) (Collector, error) {
138
-	return defRegistry.RegisterOrGet(m)
159
+// Deprecated: RegisterOrGet is merely a convenience function for the
160
+// implementation as described in the documentation for
161
+// AlreadyRegisteredError. As the use case is relatively rare, this function
162
+// will be removed in a future version of this package to clean up the
163
+// namespace.
164
+func RegisterOrGet(c Collector) (Collector, error) {
165
+	if err := Register(c); err != nil {
166
+		if are, ok := err.(AlreadyRegisteredError); ok {
167
+			return are.ExistingCollector, nil
168
+		}
169
+		return nil, err
170
+	}
171
+	return c, nil
139 172
 }
140 173
 
141
-// MustRegisterOrGet works like RegisterOrGet but panics where RegisterOrGet
142
-// would have returned an error.
143
-func MustRegisterOrGet(m Collector) Collector {
144
-	existing, err := RegisterOrGet(m)
174
+// MustRegisterOrGet behaves like RegisterOrGet but panics instead of returning
175
+// an error.
176
+//
177
+// Deprecated: This is deprecated for the same reason RegisterOrGet is. See
178
+// there for details.
179
+func MustRegisterOrGet(c Collector) Collector {
180
+	c, err := RegisterOrGet(c)
145 181
 	if err != nil {
146 182
 		panic(err)
147 183
 	}
148
-	return existing
184
+	return c
149 185
 }
150 186
 
151
-// Unregister unregisters the Collector that equals the Collector passed in as
152
-// an argument. (Two Collectors are considered equal if their Describe method
153
-// yields the same set of descriptors.) The function returns whether a Collector
154
-// was unregistered.
187
+// Unregister removes the registration of the provided Collector from the
188
+// DefaultRegisterer.
189
+//
190
+// Unregister is a shortcut for DefaultRegisterer.Unregister(c). See there for
191
+// more details.
155 192
 func Unregister(c Collector) bool {
156
-	return defRegistry.Unregister(c)
193
+	return DefaultRegisterer.Unregister(c)
157 194
 }
158 195
 
159
-// SetMetricFamilyInjectionHook sets a function that is called whenever metrics
160
-// are collected. The hook function must be set before metrics collection begins
161
-// (i.e. call SetMetricFamilyInjectionHook before setting the HTTP handler.) The
162
-// MetricFamily protobufs returned by the hook function are merged with the
163
-// metrics collected in the usual way.
164
-//
165
-// This is a way to directly inject MetricFamily protobufs managed and owned by
166
-// the caller. The caller has full responsibility. As no registration of the
167
-// injected metrics has happened, there is no descriptor to check against, and
168
-// there are no registration-time checks. If collect-time checks are disabled
169
-// (see function EnableCollectChecks), no sanity checks are performed on the
170
-// returned protobufs at all. If collect-checks are enabled, type and uniqueness
171
-// checks are performed, but no further consistency checks (which would require
172
-// knowledge of a metric descriptor).
173
-//
174
-// Sorting concerns: The caller is responsible for sorting the label pairs in
175
-// each metric. However, the order of metrics will be sorted by the registry as
176
-// it is required anyway after merging with the metric families collected
177
-// conventionally.
196
+// GathererFunc turns a function into a Gatherer.
197
+type GathererFunc func() ([]*dto.MetricFamily, error)
198
+
199
+// Gather implements Gatherer.
200
+func (gf GathererFunc) Gather() ([]*dto.MetricFamily, error) {
201
+	return gf()
202
+}
203
+
204
+// SetMetricFamilyInjectionHook replaces the DefaultGatherer with one that
205
+// gathers from the previous DefaultGatherers but then merges the MetricFamily
206
+// protobufs returned from the provided hook function with the MetricFamily
207
+// protobufs returned from the original DefaultGatherer.
178 208
 //
179
-// The function must be callable at any time and concurrently.
209
+// Deprecated: This function manipulates the DefaultGatherer variable. Consider
210
+// the implications, i.e. don't do this concurrently with any uses of the
211
+// DefaultGatherer. In the rare cases where you need to inject MetricFamily
212
+// protobufs directly, it is recommended to use a custom Registry and combine it
213
+// with a custom Gatherer using the Gatherers type (see
214
+// there). SetMetricFamilyInjectionHook only exists for compatibility reasons
215
+// with previous versions of this package.
180 216
 func SetMetricFamilyInjectionHook(hook func() []*dto.MetricFamily) {
181
-	defRegistry.metricFamilyInjectionHook = hook
217
+	DefaultGatherer = Gatherers{
218
+		DefaultGatherer,
219
+		GathererFunc(func() ([]*dto.MetricFamily, error) { return hook(), nil }),
220
+	}
221
+}
222
+
223
+// AlreadyRegisteredError is returned by the Register method if the Collector to
224
+// be registered has already been registered before, or a different Collector
225
+// that collects the same metrics has been registered before. Registration fails
226
+// in that case, but you can detect from the kind of error what has
227
+// happened. The error contains fields for the existing Collector and the
228
+// (rejected) new Collector that equals the existing one. This can be used to
229
+// find out if an equal Collector has been registered before and switch over to
230
+// using the old one, as demonstrated in the example.
231
+type AlreadyRegisteredError struct {
232
+	ExistingCollector, NewCollector Collector
182 233
 }
183 234
 
184
-// PanicOnCollectError sets the behavior whether a panic is caused upon an error
185
-// while metrics are collected and served to the HTTP endpoint. By default, an
186
-// internal server error (status code 500) is served with an error message.
187
-func PanicOnCollectError(b bool) {
188
-	defRegistry.panicOnCollectError = b
235
+func (err AlreadyRegisteredError) Error() string {
236
+	return "duplicate metrics collector registration attempted"
237
+}
238
+
239
+// MultiError is a slice of errors implementing the error interface. It is used
240
+// by a Gatherer to report multiple errors during MetricFamily gathering.
241
+type MultiError []error
242
+
243
+func (errs MultiError) Error() string {
244
+	if len(errs) == 0 {
245
+		return ""
246
+	}
247
+	buf := &bytes.Buffer{}
248
+	fmt.Fprintf(buf, "%d error(s) occurred:", len(errs))
249
+	for _, err := range errs {
250
+		fmt.Fprintf(buf, "\n* %s", err)
251
+	}
252
+	return buf.String()
189 253
 }
190 254
 
191
-// EnableCollectChecks enables (or disables) additional consistency checks
192
-// during metrics collection. These additional checks are not enabled by default
193
-// because they inflict a performance penalty and the errors they check for can
194
-// only happen if the used Metric and Collector types have internal programming
195
-// errors. It can be helpful to enable these checks while working with custom
196
-// Collectors or Metrics whose correctness is not well established yet.
197
-func EnableCollectChecks(b bool) {
198
-	defRegistry.collectChecksEnabled = b
255
+// MaybeUnwrap returns nil if len(errs) is 0. It returns the first and only
256
+// contained error as error if len(errs is 1). In all other cases, it returns
257
+// the MultiError directly. This is helpful for returning a MultiError in a way
258
+// that only uses the MultiError if needed.
259
+func (errs MultiError) MaybeUnwrap() error {
260
+	switch len(errs) {
261
+	case 0:
262
+		return nil
263
+	case 1:
264
+		return errs[0]
265
+	default:
266
+		return errs
267
+	}
199 268
 }
200 269
 
201
-// encoder is a function that writes a dto.MetricFamily to an io.Writer in a
202
-// certain encoding. It returns the number of bytes written and any error
203
-// encountered.  Note that pbutil.WriteDelimited and pbutil.MetricFamilyToText
204
-// are encoders.
205
-type encoder func(io.Writer, *dto.MetricFamily) (int, error)
206
-
207
-type registry struct {
208
-	mtx                       sync.RWMutex
209
-	collectorsByID            map[uint64]Collector // ID is a hash of the descIDs.
210
-	descIDs                   map[uint64]struct{}
211
-	dimHashesByName           map[string]uint64
212
-	bufPool                   chan *bytes.Buffer
213
-	metricFamilyPool          chan *dto.MetricFamily
214
-	metricPool                chan *dto.Metric
215
-	metricFamilyInjectionHook func() []*dto.MetricFamily
216
-
217
-	panicOnCollectError, collectChecksEnabled bool
270
+// Registry registers Prometheus collectors, collects their metrics, and gathers
271
+// them into MetricFamilies for exposition. It implements both Registerer and
272
+// Gatherer. The zero value is not usable. Create instances with NewRegistry or
273
+// NewPedanticRegistry.
274
+type Registry struct {
275
+	mtx                   sync.RWMutex
276
+	collectorsByID        map[uint64]Collector // ID is a hash of the descIDs.
277
+	descIDs               map[uint64]struct{}
278
+	dimHashesByName       map[string]uint64
279
+	pedanticChecksEnabled bool
218 280
 }
219 281
 
220
-func (r *registry) Register(c Collector) (Collector, error) {
221
-	descChan := make(chan *Desc, capDescChan)
282
+// Register implements Registerer.
283
+func (r *Registry) Register(c Collector) error {
284
+	var (
285
+		descChan           = make(chan *Desc, capDescChan)
286
+		newDescIDs         = map[uint64]struct{}{}
287
+		newDimHashesByName = map[string]uint64{}
288
+		collectorID        uint64 // Just a sum of all desc IDs.
289
+		duplicateDescErr   error
290
+	)
222 291
 	go func() {
223 292
 		c.Describe(descChan)
224 293
 		close(descChan)
225 294
 	}()
226
-
227
-	newDescIDs := map[uint64]struct{}{}
228
-	newDimHashesByName := map[string]uint64{}
229
-	var collectorID uint64 // Just a sum of all desc IDs.
230
-	var duplicateDescErr error
231
-
232 295
 	r.mtx.Lock()
233 296
 	defer r.mtx.Unlock()
234 297
 	// Coduct various tests...
... ...
@@ -236,7 +299,7 @@ func (r *registry) Register(c Collector) (Collector, error) {
236 236
 
237 237
 		// Is the descriptor valid at all?
238 238
 		if desc.err != nil {
239
-			return c, fmt.Errorf("descriptor %s is invalid: %s", desc, desc.err)
239
+			return fmt.Errorf("descriptor %s is invalid: %s", desc, desc.err)
240 240
 		}
241 241
 
242 242
 		// Is the descID unique?
... ...
@@ -257,13 +320,13 @@ func (r *registry) Register(c Collector) (Collector, error) {
257 257
 		// First check existing descriptors...
258 258
 		if dimHash, exists := r.dimHashesByName[desc.fqName]; exists {
259 259
 			if dimHash != desc.dimHash {
260
-				return nil, fmt.Errorf("a previously registered descriptor with the same fully-qualified name as %s has different label names or a different help string", desc)
260
+				return fmt.Errorf("a previously registered descriptor with the same fully-qualified name as %s has different label names or a different help string", desc)
261 261
 			}
262 262
 		} else {
263 263
 			// ...then check the new descriptors already seen.
264 264
 			if dimHash, exists := newDimHashesByName[desc.fqName]; exists {
265 265
 				if dimHash != desc.dimHash {
266
-					return nil, fmt.Errorf("descriptors reported by collector have inconsistent label names or help strings for the same fully-qualified name, offender is %s", desc)
266
+					return fmt.Errorf("descriptors reported by collector have inconsistent label names or help strings for the same fully-qualified name, offender is %s", desc)
267 267
 				}
268 268
 			} else {
269 269
 				newDimHashesByName[desc.fqName] = desc.dimHash
... ...
@@ -272,15 +335,18 @@ func (r *registry) Register(c Collector) (Collector, error) {
272 272
 	}
273 273
 	// Did anything happen at all?
274 274
 	if len(newDescIDs) == 0 {
275
-		return nil, errors.New("collector has no descriptors")
275
+		return errors.New("collector has no descriptors")
276 276
 	}
277 277
 	if existing, exists := r.collectorsByID[collectorID]; exists {
278
-		return existing, errAlreadyReg
278
+		return AlreadyRegisteredError{
279
+			ExistingCollector: existing,
280
+			NewCollector:      c,
281
+		}
279 282
 	}
280 283
 	// If the collectorID is new, but at least one of the descs existed
281 284
 	// before, we are in trouble.
282 285
 	if duplicateDescErr != nil {
283
-		return nil, duplicateDescErr
286
+		return duplicateDescErr
284 287
 	}
285 288
 
286 289
 	// Only after all tests have passed, actually register.
... ...
@@ -291,26 +357,20 @@ func (r *registry) Register(c Collector) (Collector, error) {
291 291
 	for name, dimHash := range newDimHashesByName {
292 292
 		r.dimHashesByName[name] = dimHash
293 293
 	}
294
-	return c, nil
295
-}
296
-
297
-func (r *registry) RegisterOrGet(m Collector) (Collector, error) {
298
-	existing, err := r.Register(m)
299
-	if err != nil && err != errAlreadyReg {
300
-		return nil, err
301
-	}
302
-	return existing, nil
294
+	return nil
303 295
 }
304 296
 
305
-func (r *registry) Unregister(c Collector) bool {
306
-	descChan := make(chan *Desc, capDescChan)
297
+// Unregister implements Registerer.
298
+func (r *Registry) Unregister(c Collector) bool {
299
+	var (
300
+		descChan    = make(chan *Desc, capDescChan)
301
+		descIDs     = map[uint64]struct{}{}
302
+		collectorID uint64 // Just a sum of the desc IDs.
303
+	)
307 304
 	go func() {
308 305
 		c.Describe(descChan)
309 306
 		close(descChan)
310 307
 	}()
311
-
312
-	descIDs := map[uint64]struct{}{}
313
-	var collectorID uint64 // Just a sum of the desc IDs.
314 308
 	for desc := range descChan {
315 309
 		if _, exists := descIDs[desc.id]; !exists {
316 310
 			collectorID += desc.id
... ...
@@ -337,72 +397,25 @@ func (r *registry) Unregister(c Collector) bool {
337 337
 	return true
338 338
 }
339 339
 
340
-func (r *registry) Push(job, instance, pushURL, method string) error {
341
-	if !strings.Contains(pushURL, "://") {
342
-		pushURL = "http://" + pushURL
343
-	}
344
-	if strings.HasSuffix(pushURL, "/") {
345
-		pushURL = pushURL[:len(pushURL)-1]
346
-	}
347
-	pushURL = fmt.Sprintf("%s/metrics/jobs/%s", pushURL, url.QueryEscape(job))
348
-	if instance != "" {
349
-		pushURL += "/instances/" + url.QueryEscape(instance)
350
-	}
351
-	buf := r.getBuf()
352
-	defer r.giveBuf(buf)
353
-	if err := r.writePB(expfmt.NewEncoder(buf, expfmt.FmtProtoDelim)); err != nil {
354
-		if r.panicOnCollectError {
340
+// MustRegister implements Registerer.
341
+func (r *Registry) MustRegister(cs ...Collector) {
342
+	for _, c := range cs {
343
+		if err := r.Register(c); err != nil {
355 344
 			panic(err)
356 345
 		}
357
-		return err
358 346
 	}
359
-	req, err := http.NewRequest(method, pushURL, buf)
360
-	if err != nil {
361
-		return err
362
-	}
363
-	req.Header.Set(contentTypeHeader, DelimitedTelemetryContentType)
364
-	resp, err := http.DefaultClient.Do(req)
365
-	if err != nil {
366
-		return err
367
-	}
368
-	defer resp.Body.Close()
369
-	if resp.StatusCode != 202 {
370
-		return fmt.Errorf("unexpected status code %d while pushing to %s", resp.StatusCode, pushURL)
371
-	}
372
-	return nil
373 347
 }
374 348
 
375
-func (r *registry) ServeHTTP(w http.ResponseWriter, req *http.Request) {
376
-	contentType := expfmt.Negotiate(req.Header)
377
-	buf := r.getBuf()
378
-	defer r.giveBuf(buf)
379
-	writer, encoding := decorateWriter(req, buf)
380
-	if err := r.writePB(expfmt.NewEncoder(writer, contentType)); err != nil {
381
-		if r.panicOnCollectError {
382
-			panic(err)
383
-		}
384
-		http.Error(w, "An error has occurred:\n\n"+err.Error(), http.StatusInternalServerError)
385
-		return
386
-	}
387
-	if closer, ok := writer.(io.Closer); ok {
388
-		closer.Close()
389
-	}
390
-	header := w.Header()
391
-	header.Set(contentTypeHeader, string(contentType))
392
-	header.Set(contentLengthHeader, fmt.Sprint(buf.Len()))
393
-	if encoding != "" {
394
-		header.Set(contentEncodingHeader, encoding)
395
-	}
396
-	w.Write(buf.Bytes())
397
-}
398
-
399
-func (r *registry) writePB(encoder expfmt.Encoder) error {
400
-	var metricHashes map[uint64]struct{}
401
-	if r.collectChecksEnabled {
402
-		metricHashes = make(map[uint64]struct{})
403
-	}
404
-	metricChan := make(chan Metric, capMetricChan)
405
-	wg := sync.WaitGroup{}
349
+// Gather implements Gatherer.
350
+func (r *Registry) Gather() ([]*dto.MetricFamily, error) {
351
+	var (
352
+		metricChan        = make(chan Metric, capMetricChan)
353
+		metricHashes      = map[uint64]struct{}{}
354
+		dimHashes         = map[string]uint64{}
355
+		wg                sync.WaitGroup
356
+		errs              MultiError          // The collected errors to return in the end.
357
+		registeredDescIDs map[uint64]struct{} // Only used for pedantic checks
358
+	)
406 359
 
407 360
 	r.mtx.RLock()
408 361
 	metricFamiliesByName := make(map[string]*dto.MetricFamily, len(r.dimHashesByName))
... ...
@@ -420,6 +433,16 @@ func (r *registry) writePB(encoder expfmt.Encoder) error {
420 420
 			collector.Collect(metricChan)
421 421
 		}(collector)
422 422
 	}
423
+
424
+	// In case pedantic checks are enabled, we have to copy the map before
425
+	// giving up the RLock.
426
+	if r.pedanticChecksEnabled {
427
+		registeredDescIDs = make(map[uint64]struct{}, len(r.descIDs))
428
+		for id := range r.descIDs {
429
+			registeredDescIDs[id] = struct{}{}
430
+		}
431
+	}
432
+
423 433
 	r.mtx.RUnlock()
424 434
 
425 435
 	// Drain metricChan in case of premature return.
... ...
@@ -434,94 +457,267 @@ func (r *registry) writePB(encoder expfmt.Encoder) error {
434 434
 		// of metricFamiliesByName (and of metricHashes if checks are
435 435
 		// enabled). Most likely not worth it.
436 436
 		desc := metric.Desc()
437
+		dtoMetric := &dto.Metric{}
438
+		if err := metric.Write(dtoMetric); err != nil {
439
+			errs = append(errs, fmt.Errorf(
440
+				"error collecting metric %v: %s", desc, err,
441
+			))
442
+			continue
443
+		}
437 444
 		metricFamily, ok := metricFamiliesByName[desc.fqName]
438
-		if !ok {
439
-			metricFamily = r.getMetricFamily()
440
-			defer r.giveMetricFamily(metricFamily)
445
+		if ok {
446
+			if metricFamily.GetHelp() != desc.help {
447
+				errs = append(errs, fmt.Errorf(
448
+					"collected metric %s %s has help %q but should have %q",
449
+					desc.fqName, dtoMetric, desc.help, metricFamily.GetHelp(),
450
+				))
451
+				continue
452
+			}
453
+			// TODO(beorn7): Simplify switch once Desc has type.
454
+			switch metricFamily.GetType() {
455
+			case dto.MetricType_COUNTER:
456
+				if dtoMetric.Counter == nil {
457
+					errs = append(errs, fmt.Errorf(
458
+						"collected metric %s %s should be a Counter",
459
+						desc.fqName, dtoMetric,
460
+					))
461
+					continue
462
+				}
463
+			case dto.MetricType_GAUGE:
464
+				if dtoMetric.Gauge == nil {
465
+					errs = append(errs, fmt.Errorf(
466
+						"collected metric %s %s should be a Gauge",
467
+						desc.fqName, dtoMetric,
468
+					))
469
+					continue
470
+				}
471
+			case dto.MetricType_SUMMARY:
472
+				if dtoMetric.Summary == nil {
473
+					errs = append(errs, fmt.Errorf(
474
+						"collected metric %s %s should be a Summary",
475
+						desc.fqName, dtoMetric,
476
+					))
477
+					continue
478
+				}
479
+			case dto.MetricType_UNTYPED:
480
+				if dtoMetric.Untyped == nil {
481
+					errs = append(errs, fmt.Errorf(
482
+						"collected metric %s %s should be Untyped",
483
+						desc.fqName, dtoMetric,
484
+					))
485
+					continue
486
+				}
487
+			case dto.MetricType_HISTOGRAM:
488
+				if dtoMetric.Histogram == nil {
489
+					errs = append(errs, fmt.Errorf(
490
+						"collected metric %s %s should be a Histogram",
491
+						desc.fqName, dtoMetric,
492
+					))
493
+					continue
494
+				}
495
+			default:
496
+				panic("encountered MetricFamily with invalid type")
497
+			}
498
+		} else {
499
+			metricFamily = &dto.MetricFamily{}
441 500
 			metricFamily.Name = proto.String(desc.fqName)
442 501
 			metricFamily.Help = proto.String(desc.help)
502
+			// TODO(beorn7): Simplify switch once Desc has type.
503
+			switch {
504
+			case dtoMetric.Gauge != nil:
505
+				metricFamily.Type = dto.MetricType_GAUGE.Enum()
506
+			case dtoMetric.Counter != nil:
507
+				metricFamily.Type = dto.MetricType_COUNTER.Enum()
508
+			case dtoMetric.Summary != nil:
509
+				metricFamily.Type = dto.MetricType_SUMMARY.Enum()
510
+			case dtoMetric.Untyped != nil:
511
+				metricFamily.Type = dto.MetricType_UNTYPED.Enum()
512
+			case dtoMetric.Histogram != nil:
513
+				metricFamily.Type = dto.MetricType_HISTOGRAM.Enum()
514
+			default:
515
+				errs = append(errs, fmt.Errorf(
516
+					"empty metric collected: %s", dtoMetric,
517
+				))
518
+				continue
519
+			}
443 520
 			metricFamiliesByName[desc.fqName] = metricFamily
444 521
 		}
445
-		dtoMetric := r.getMetric()
446
-		defer r.giveMetric(dtoMetric)
447
-		if err := metric.Write(dtoMetric); err != nil {
448
-			// TODO: Consider different means of error reporting so
449
-			// that a single erroneous metric could be skipped
450
-			// instead of blowing up the whole collection.
451
-			return fmt.Errorf("error collecting metric %v: %s", desc, err)
452
-		}
453
-		switch {
454
-		case metricFamily.Type != nil:
455
-			// Type already set. We are good.
456
-		case dtoMetric.Gauge != nil:
457
-			metricFamily.Type = dto.MetricType_GAUGE.Enum()
458
-		case dtoMetric.Counter != nil:
459
-			metricFamily.Type = dto.MetricType_COUNTER.Enum()
460
-		case dtoMetric.Summary != nil:
461
-			metricFamily.Type = dto.MetricType_SUMMARY.Enum()
462
-		case dtoMetric.Untyped != nil:
463
-			metricFamily.Type = dto.MetricType_UNTYPED.Enum()
464
-		case dtoMetric.Histogram != nil:
465
-			metricFamily.Type = dto.MetricType_HISTOGRAM.Enum()
466
-		default:
467
-			return fmt.Errorf("empty metric collected: %s", dtoMetric)
522
+		if err := checkMetricConsistency(metricFamily, dtoMetric, metricHashes, dimHashes); err != nil {
523
+			errs = append(errs, err)
524
+			continue
468 525
 		}
469
-		if r.collectChecksEnabled {
470
-			if err := r.checkConsistency(metricFamily, dtoMetric, desc, metricHashes); err != nil {
471
-				return err
526
+		if r.pedanticChecksEnabled {
527
+			// Is the desc registered at all?
528
+			if _, exist := registeredDescIDs[desc.id]; !exist {
529
+				errs = append(errs, fmt.Errorf(
530
+					"collected metric %s %s with unregistered descriptor %s",
531
+					metricFamily.GetName(), dtoMetric, desc,
532
+				))
533
+				continue
534
+			}
535
+			if err := checkDescConsistency(metricFamily, dtoMetric, desc); err != nil {
536
+				errs = append(errs, err)
537
+				continue
472 538
 			}
473 539
 		}
474 540
 		metricFamily.Metric = append(metricFamily.Metric, dtoMetric)
475 541
 	}
542
+	return normalizeMetricFamilies(metricFamiliesByName), errs.MaybeUnwrap()
543
+}
476 544
 
477
-	if r.metricFamilyInjectionHook != nil {
478
-		for _, mf := range r.metricFamilyInjectionHook() {
545
+// Gatherers is a slice of Gatherer instances that implements the Gatherer
546
+// interface itself. Its Gather method calls Gather on all Gatherers in the
547
+// slice in order and returns the merged results. Errors returned from the
548
+// Gather calles are all returned in a flattened MultiError. Duplicate and
549
+// inconsistent Metrics are skipped (first occurrence in slice order wins) and
550
+// reported in the returned error.
551
+//
552
+// Gatherers can be used to merge the Gather results from multiple
553
+// Registries. It also provides a way to directly inject existing MetricFamily
554
+// protobufs into the gathering by creating a custom Gatherer with a Gather
555
+// method that simply returns the existing MetricFamily protobufs. Note that no
556
+// registration is involved (in contrast to Collector registration), so
557
+// obviously registration-time checks cannot happen. Any inconsistencies between
558
+// the gathered MetricFamilies are reported as errors by the Gather method, and
559
+// inconsistent Metrics are dropped. Invalid parts of the MetricFamilies
560
+// (e.g. syntactically invalid metric or label names) will go undetected.
561
+type Gatherers []Gatherer
562
+
563
+// Gather implements Gatherer.
564
+func (gs Gatherers) Gather() ([]*dto.MetricFamily, error) {
565
+	var (
566
+		metricFamiliesByName = map[string]*dto.MetricFamily{}
567
+		metricHashes         = map[uint64]struct{}{}
568
+		dimHashes            = map[string]uint64{}
569
+		errs                 MultiError // The collected errors to return in the end.
570
+	)
571
+
572
+	for i, g := range gs {
573
+		mfs, err := g.Gather()
574
+		if err != nil {
575
+			if multiErr, ok := err.(MultiError); ok {
576
+				for _, err := range multiErr {
577
+					errs = append(errs, fmt.Errorf("[from Gatherer #%d] %s", i+1, err))
578
+				}
579
+			} else {
580
+				errs = append(errs, fmt.Errorf("[from Gatherer #%d] %s", i+1, err))
581
+			}
582
+		}
583
+		for _, mf := range mfs {
479 584
 			existingMF, exists := metricFamiliesByName[mf.GetName()]
480
-			if !exists {
481
-				metricFamiliesByName[mf.GetName()] = mf
482
-				if r.collectChecksEnabled {
483
-					for _, m := range mf.Metric {
484
-						if err := r.checkConsistency(mf, m, nil, metricHashes); err != nil {
485
-							return err
486
-						}
487
-					}
585
+			if exists {
586
+				if existingMF.GetHelp() != mf.GetHelp() {
587
+					errs = append(errs, fmt.Errorf(
588
+						"gathered metric family %s has help %q but should have %q",
589
+						mf.GetName(), mf.GetHelp(), existingMF.GetHelp(),
590
+					))
591
+					continue
488 592
 				}
489
-				continue
593
+				if existingMF.GetType() != mf.GetType() {
594
+					errs = append(errs, fmt.Errorf(
595
+						"gathered metric family %s has type %s but should have %s",
596
+						mf.GetName(), mf.GetType(), existingMF.GetType(),
597
+					))
598
+					continue
599
+				}
600
+			} else {
601
+				existingMF = &dto.MetricFamily{}
602
+				existingMF.Name = mf.Name
603
+				existingMF.Help = mf.Help
604
+				existingMF.Type = mf.Type
605
+				metricFamiliesByName[mf.GetName()] = existingMF
490 606
 			}
491 607
 			for _, m := range mf.Metric {
492
-				if r.collectChecksEnabled {
493
-					if err := r.checkConsistency(existingMF, m, nil, metricHashes); err != nil {
494
-						return err
495
-					}
608
+				if err := checkMetricConsistency(existingMF, m, metricHashes, dimHashes); err != nil {
609
+					errs = append(errs, err)
610
+					continue
496 611
 				}
497 612
 				existingMF.Metric = append(existingMF.Metric, m)
498 613
 			}
499 614
 		}
500 615
 	}
616
+	return normalizeMetricFamilies(metricFamiliesByName), errs.MaybeUnwrap()
617
+}
618
+
619
+// metricSorter is a sortable slice of *dto.Metric.
620
+type metricSorter []*dto.Metric
621
+
622
+func (s metricSorter) Len() int {
623
+	return len(s)
624
+}
625
+
626
+func (s metricSorter) Swap(i, j int) {
627
+	s[i], s[j] = s[j], s[i]
628
+}
501 629
 
502
-	// Now that MetricFamilies are all set, sort their Metrics
503
-	// lexicographically by their label values.
630
+func (s metricSorter) Less(i, j int) bool {
631
+	if len(s[i].Label) != len(s[j].Label) {
632
+		// This should not happen. The metrics are
633
+		// inconsistent. However, we have to deal with the fact, as
634
+		// people might use custom collectors or metric family injection
635
+		// to create inconsistent metrics. So let's simply compare the
636
+		// number of labels in this case. That will still yield
637
+		// reproducible sorting.
638
+		return len(s[i].Label) < len(s[j].Label)
639
+	}
640
+	for n, lp := range s[i].Label {
641
+		vi := lp.GetValue()
642
+		vj := s[j].Label[n].GetValue()
643
+		if vi != vj {
644
+			return vi < vj
645
+		}
646
+	}
647
+
648
+	// We should never arrive here. Multiple metrics with the same
649
+	// label set in the same scrape will lead to undefined ingestion
650
+	// behavior. However, as above, we have to provide stable sorting
651
+	// here, even for inconsistent metrics. So sort equal metrics
652
+	// by their timestamp, with missing timestamps (implying "now")
653
+	// coming last.
654
+	if s[i].TimestampMs == nil {
655
+		return false
656
+	}
657
+	if s[j].TimestampMs == nil {
658
+		return true
659
+	}
660
+	return s[i].GetTimestampMs() < s[j].GetTimestampMs()
661
+}
662
+
663
+// normalizeMetricFamilies returns a MetricFamily slice whith empty
664
+// MetricFamilies pruned and the remaining MetricFamilies sorted by name within
665
+// the slice, with the contained Metrics sorted within each MetricFamily.
666
+func normalizeMetricFamilies(metricFamiliesByName map[string]*dto.MetricFamily) []*dto.MetricFamily {
504 667
 	for _, mf := range metricFamiliesByName {
505 668
 		sort.Sort(metricSorter(mf.Metric))
506 669
 	}
507
-
508
-	// Write out MetricFamilies sorted by their name.
509 670
 	names := make([]string, 0, len(metricFamiliesByName))
510
-	for name := range metricFamiliesByName {
511
-		names = append(names, name)
671
+	for name, mf := range metricFamiliesByName {
672
+		if len(mf.Metric) > 0 {
673
+			names = append(names, name)
674
+		}
512 675
 	}
513 676
 	sort.Strings(names)
514
-
677
+	result := make([]*dto.MetricFamily, 0, len(names))
515 678
 	for _, name := range names {
516
-		if err := encoder.Encode(metricFamiliesByName[name]); err != nil {
517
-			return err
518
-		}
679
+		result = append(result, metricFamiliesByName[name])
519 680
 	}
520
-	return nil
681
+	return result
521 682
 }
522 683
 
523
-func (r *registry) checkConsistency(metricFamily *dto.MetricFamily, dtoMetric *dto.Metric, desc *Desc, metricHashes map[uint64]struct{}) error {
524
-
684
+// checkMetricConsistency checks if the provided Metric is consistent with the
685
+// provided MetricFamily. It also hashed the Metric labels and the MetricFamily
686
+// name. If the resulting hash is alread in the provided metricHashes, an error
687
+// is returned. If not, it is added to metricHashes. The provided dimHashes maps
688
+// MetricFamily names to their dimHash (hashed sorted label names). If dimHashes
689
+// doesn't yet contain a hash for the provided MetricFamily, it is
690
+// added. Otherwise, an error is returned if the existing dimHashes in not equal
691
+// the calculated dimHash.
692
+func checkMetricConsistency(
693
+	metricFamily *dto.MetricFamily,
694
+	dtoMetric *dto.Metric,
695
+	metricHashes map[uint64]struct{},
696
+	dimHashes map[string]uint64,
697
+) error {
525 698
 	// Type consistency with metric family.
526 699
 	if metricFamily.GetType() == dto.MetricType_GAUGE && dtoMetric.Gauge == nil ||
527 700
 		metricFamily.GetType() == dto.MetricType_COUNTER && dtoMetric.Counter == nil ||
... ...
@@ -538,14 +734,15 @@ func (r *registry) checkConsistency(metricFamily *dto.MetricFamily, dtoMetric *d
538 538
 	h := hashNew()
539 539
 	h = hashAdd(h, metricFamily.GetName())
540 540
 	h = hashAddByte(h, separatorByte)
541
+	dh := hashNew()
541 542
 	// Make sure label pairs are sorted. We depend on it for the consistency
542
-	// check. Label pairs must be sorted by contract. But the point of this
543
-	// method is to check for contract violations. So we better do the sort
544
-	// now.
543
+	// check.
545 544
 	sort.Sort(LabelPairSorter(dtoMetric.Label))
546 545
 	for _, lp := range dtoMetric.Label {
547 546
 		h = hashAdd(h, lp.GetValue())
548 547
 		h = hashAddByte(h, separatorByte)
548
+		dh = hashAdd(dh, lp.GetName())
549
+		dh = hashAddByte(dh, separatorByte)
549 550
 	}
550 551
 	if _, exists := metricHashes[h]; exists {
551 552
 		return fmt.Errorf(
... ...
@@ -553,19 +750,26 @@ func (r *registry) checkConsistency(metricFamily *dto.MetricFamily, dtoMetric *d
553 553
 			metricFamily.GetName(), dtoMetric,
554 554
 		)
555 555
 	}
556
-	metricHashes[h] = struct{}{}
557
-
558
-	if desc == nil {
559
-		return nil // Nothing left to check if we have no desc.
556
+	if dimHash, ok := dimHashes[metricFamily.GetName()]; ok {
557
+		if dimHash != dh {
558
+			return fmt.Errorf(
559
+				"collected metric %s %s has label dimensions inconsistent with previously collected metrics in the same metric family",
560
+				metricFamily.GetName(), dtoMetric,
561
+			)
562
+		}
563
+	} else {
564
+		dimHashes[metricFamily.GetName()] = dh
560 565
 	}
566
+	metricHashes[h] = struct{}{}
567
+	return nil
568
+}
561 569
 
562
-	// Desc consistency with metric family.
563
-	if metricFamily.GetName() != desc.fqName {
564
-		return fmt.Errorf(
565
-			"collected metric %s %s has name %q but should have %q",
566
-			metricFamily.GetName(), dtoMetric, metricFamily.GetName(), desc.fqName,
567
-		)
568
-	}
570
+func checkDescConsistency(
571
+	metricFamily *dto.MetricFamily,
572
+	dtoMetric *dto.Metric,
573
+	desc *Desc,
574
+) error {
575
+	// Desc help consistency with metric family help.
569 576
 	if metricFamily.GetHelp() != desc.help {
570 577
 		return fmt.Errorf(
571 578
 			"collected metric %s %s has help %q but should have %q",
... ...
@@ -598,144 +802,5 @@ func (r *registry) checkConsistency(metricFamily *dto.MetricFamily, dtoMetric *d
598 598
 			)
599 599
 		}
600 600
 	}
601
-
602
-	r.mtx.RLock() // Remaining checks need the read lock.
603
-	defer r.mtx.RUnlock()
604
-
605
-	// Is the desc registered?
606
-	if _, exist := r.descIDs[desc.id]; !exist {
607
-		return fmt.Errorf(
608
-			"collected metric %s %s with unregistered descriptor %s",
609
-			metricFamily.GetName(), dtoMetric, desc,
610
-		)
611
-	}
612
-
613 601
 	return nil
614 602
 }
615
-
616
-func (r *registry) getBuf() *bytes.Buffer {
617
-	select {
618
-	case buf := <-r.bufPool:
619
-		return buf
620
-	default:
621
-		return &bytes.Buffer{}
622
-	}
623
-}
624
-
625
-func (r *registry) giveBuf(buf *bytes.Buffer) {
626
-	buf.Reset()
627
-	select {
628
-	case r.bufPool <- buf:
629
-	default:
630
-	}
631
-}
632
-
633
-func (r *registry) getMetricFamily() *dto.MetricFamily {
634
-	select {
635
-	case mf := <-r.metricFamilyPool:
636
-		return mf
637
-	default:
638
-		return &dto.MetricFamily{}
639
-	}
640
-}
641
-
642
-func (r *registry) giveMetricFamily(mf *dto.MetricFamily) {
643
-	mf.Reset()
644
-	select {
645
-	case r.metricFamilyPool <- mf:
646
-	default:
647
-	}
648
-}
649
-
650
-func (r *registry) getMetric() *dto.Metric {
651
-	select {
652
-	case m := <-r.metricPool:
653
-		return m
654
-	default:
655
-		return &dto.Metric{}
656
-	}
657
-}
658
-
659
-func (r *registry) giveMetric(m *dto.Metric) {
660
-	m.Reset()
661
-	select {
662
-	case r.metricPool <- m:
663
-	default:
664
-	}
665
-}
666
-
667
-func newRegistry() *registry {
668
-	return &registry{
669
-		collectorsByID:   map[uint64]Collector{},
670
-		descIDs:          map[uint64]struct{}{},
671
-		dimHashesByName:  map[string]uint64{},
672
-		bufPool:          make(chan *bytes.Buffer, numBufs),
673
-		metricFamilyPool: make(chan *dto.MetricFamily, numMetricFamilies),
674
-		metricPool:       make(chan *dto.Metric, numMetrics),
675
-	}
676
-}
677
-
678
-func newDefaultRegistry() *registry {
679
-	r := newRegistry()
680
-	r.Register(NewProcessCollector(os.Getpid(), ""))
681
-	r.Register(NewGoCollector())
682
-	return r
683
-}
684
-
685
-// decorateWriter wraps a writer to handle gzip compression if requested.  It
686
-// returns the decorated writer and the appropriate "Content-Encoding" header
687
-// (which is empty if no compression is enabled).
688
-func decorateWriter(request *http.Request, writer io.Writer) (io.Writer, string) {
689
-	header := request.Header.Get(acceptEncodingHeader)
690
-	parts := strings.Split(header, ",")
691
-	for _, part := range parts {
692
-		part := strings.TrimSpace(part)
693
-		if part == "gzip" || strings.HasPrefix(part, "gzip;") {
694
-			return gzip.NewWriter(writer), "gzip"
695
-		}
696
-	}
697
-	return writer, ""
698
-}
699
-
700
-type metricSorter []*dto.Metric
701
-
702
-func (s metricSorter) Len() int {
703
-	return len(s)
704
-}
705
-
706
-func (s metricSorter) Swap(i, j int) {
707
-	s[i], s[j] = s[j], s[i]
708
-}
709
-
710
-func (s metricSorter) Less(i, j int) bool {
711
-	if len(s[i].Label) != len(s[j].Label) {
712
-		// This should not happen. The metrics are
713
-		// inconsistent. However, we have to deal with the fact, as
714
-		// people might use custom collectors or metric family injection
715
-		// to create inconsistent metrics. So let's simply compare the
716
-		// number of labels in this case. That will still yield
717
-		// reproducible sorting.
718
-		return len(s[i].Label) < len(s[j].Label)
719
-	}
720
-	for n, lp := range s[i].Label {
721
-		vi := lp.GetValue()
722
-		vj := s[j].Label[n].GetValue()
723
-		if vi != vj {
724
-			return vi < vj
725
-		}
726
-	}
727
-
728
-	// We should never arrive here. Multiple metrics with the same
729
-	// label set in the same scrape will lead to undefined ingestion
730
-	// behavior. However, as above, we have to provide stable sorting
731
-	// here, even for inconsistent metrics. So sort equal metrics
732
-	// by their timestamp, with missing timestamps (implying "now")
733
-	// coming last.
734
-	if s[i].TimestampMs == nil {
735
-		return false
736
-	}
737
-	if s[j].TimestampMs == nil {
738
-		return true
739
-	}
740
-	return s[i].GetTimestampMs() < s[j].GetTimestampMs()
741
-}
... ...
@@ -53,8 +53,8 @@ type Summary interface {
53 53
 	Observe(float64)
54 54
 }
55 55
 
56
+// DefObjectives are the default Summary quantile values.
56 57
 var (
57
-	// DefObjectives are the default Summary quantile values.
58 58
 	DefObjectives = map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001}
59 59
 
60 60
 	errQuantileLabelNotAllowed = fmt.Errorf(
... ...
@@ -139,11 +139,11 @@ type SummaryOpts struct {
139 139
 	BufCap uint32
140 140
 }
141 141
 
142
-// TODO: Great fuck-up with the sliding-window decay algorithm... The Merge
143
-// method of perk/quantile is actually not working as advertised - and it might
144
-// be unfixable, as the underlying algorithm is apparently not capable of
145
-// merging summaries in the first place. To avoid using Merge, we are currently
146
-// adding observations to _each_ age bucket, i.e. the effort to add a sample is
142
+// Great fuck-up with the sliding-window decay algorithm... The Merge method of
143
+// perk/quantile is actually not working as advertised - and it might be
144
+// unfixable, as the underlying algorithm is apparently not capable of merging
145
+// summaries in the first place. To avoid using Merge, we are currently adding
146
+// observations to _each_ age bucket, i.e. the effort to add a sample is
147 147
 // essentially multiplied by the number of age buckets. When rotating age
148 148
 // buckets, we empty the previous head stream. On scrape time, we simply take
149 149
 // the quantiles from the head stream (no merging required). Result: More effort
... ...
@@ -227,12 +227,12 @@ func newSummary(desc *Desc, opts SummaryOpts, labelValues ...string) Summary {
227 227
 	}
228 228
 	sort.Float64s(s.sortedObjectives)
229 229
 
230
-	s.Init(s) // Init self-collection.
230
+	s.init(s) // Init self-collection.
231 231
 	return s
232 232
 }
233 233
 
234 234
 type summary struct {
235
-	SelfCollector
235
+	selfCollector
236 236
 
237 237
 	bufMtx sync.Mutex // Protects hotBuf and hotBufExpTime.
238 238
 	mtx    sync.Mutex // Protects every other moving part.
... ...
@@ -390,7 +390,7 @@ func (s quantSort) Less(i, j int) bool {
390 390
 // (e.g. HTTP request latencies, partitioned by status code and method). Create
391 391
 // instances with NewSummaryVec.
392 392
 type SummaryVec struct {
393
-	MetricVec
393
+	*MetricVec
394 394
 }
395 395
 
396 396
 // NewSummaryVec creates a new SummaryVec based on the provided SummaryOpts and
... ...
@@ -404,13 +404,9 @@ func NewSummaryVec(opts SummaryOpts, labelNames []string) *SummaryVec {
404 404
 		opts.ConstLabels,
405 405
 	)
406 406
 	return &SummaryVec{
407
-		MetricVec: MetricVec{
408
-			children: map[uint64]Metric{},
409
-			desc:     desc,
410
-			newMetric: func(lvs ...string) Metric {
411
-				return newSummary(desc, opts, lvs...)
412
-			},
413
-		},
407
+		MetricVec: newMetricVec(desc, func(lvs ...string) Metric {
408
+			return newSummary(desc, opts, lvs...)
409
+		}),
414 410
 	}
415 411
 }
416 412
 
... ...
@@ -56,7 +56,7 @@ func NewUntyped(opts UntypedOpts) Untyped {
56 56
 // labels. This is used if you want to count the same thing partitioned by
57 57
 // various dimensions. Create instances with NewUntypedVec.
58 58
 type UntypedVec struct {
59
-	MetricVec
59
+	*MetricVec
60 60
 }
61 61
 
62 62
 // NewUntypedVec creates a new UntypedVec based on the provided UntypedOpts and
... ...
@@ -70,13 +70,9 @@ func NewUntypedVec(opts UntypedOpts, labelNames []string) *UntypedVec {
70 70
 		opts.ConstLabels,
71 71
 	)
72 72
 	return &UntypedVec{
73
-		MetricVec: MetricVec{
74
-			children: map[uint64]Metric{},
75
-			desc:     desc,
76
-			newMetric: func(lvs ...string) Metric {
77
-				return newValue(desc, UntypedValue, 0, lvs...)
78
-			},
79
-		},
73
+		MetricVec: newMetricVec(desc, func(lvs ...string) Metric {
74
+			return newValue(desc, UntypedValue, 0, lvs...)
75
+		}),
80 76
 	}
81 77
 }
82 78
 
... ...
@@ -48,7 +48,7 @@ type value struct {
48 48
 	// operations.  http://golang.org/pkg/sync/atomic/#pkg-note-BUG
49 49
 	valBits uint64
50 50
 
51
-	SelfCollector
51
+	selfCollector
52 52
 
53 53
 	desc       *Desc
54 54
 	valType    ValueType
... ...
@@ -68,7 +68,7 @@ func newValue(desc *Desc, valueType ValueType, val float64, labelValues ...strin
68 68
 		valBits:    math.Float64bits(val),
69 69
 		labelPairs: makeLabelPairs(desc, labelValues),
70 70
 	}
71
-	result.Init(result)
71
+	result.init(result)
72 72
 	return result
73 73
 }
74 74
 
... ...
@@ -113,7 +113,7 @@ func (v *value) Write(out *dto.Metric) error {
113 113
 // library to back the implementations of CounterFunc, GaugeFunc, and
114 114
 // UntypedFunc.
115 115
 type valueFunc struct {
116
-	SelfCollector
116
+	selfCollector
117 117
 
118 118
 	desc       *Desc
119 119
 	valType    ValueType
... ...
@@ -134,7 +134,7 @@ func newValueFunc(desc *Desc, valueType ValueType, function func() float64) *val
134 134
 		function:   function,
135 135
 		labelPairs: makeLabelPairs(desc, nil),
136 136
 	}
137
-	result.Init(result)
137
+	result.init(result)
138 138
 	return result
139 139
 }
140 140
 
... ...
@@ -16,6 +16,8 @@ package prometheus
16 16
 import (
17 17
 	"fmt"
18 18
 	"sync"
19
+
20
+	"github.com/prometheus/common/model"
19 21
 )
20 22
 
21 23
 // MetricVec is a Collector to bundle metrics of the same name that
... ...
@@ -25,10 +27,31 @@ import (
25 25
 // provided in this package.
26 26
 type MetricVec struct {
27 27
 	mtx      sync.RWMutex // Protects the children.
28
-	children map[uint64]Metric
28
+	children map[uint64][]metricWithLabelValues
29 29
 	desc     *Desc
30 30
 
31
-	newMetric func(labelValues ...string) Metric
31
+	newMetric   func(labelValues ...string) Metric
32
+	hashAdd     func(h uint64, s string) uint64 // replace hash function for testing collision handling
33
+	hashAddByte func(h uint64, b byte) uint64
34
+}
35
+
36
+// newMetricVec returns an initialized MetricVec. The concrete value is
37
+// returned for embedding into another struct.
38
+func newMetricVec(desc *Desc, newMetric func(lvs ...string) Metric) *MetricVec {
39
+	return &MetricVec{
40
+		children:    map[uint64][]metricWithLabelValues{},
41
+		desc:        desc,
42
+		newMetric:   newMetric,
43
+		hashAdd:     hashAdd,
44
+		hashAddByte: hashAddByte,
45
+	}
46
+}
47
+
48
+// metricWithLabelValues provides the metric and its label values for
49
+// disambiguation on hash collision.
50
+type metricWithLabelValues struct {
51
+	values []string
52
+	metric Metric
32 53
 }
33 54
 
34 55
 // Describe implements Collector. The length of the returned slice
... ...
@@ -42,8 +65,10 @@ func (m *MetricVec) Collect(ch chan<- Metric) {
42 42
 	m.mtx.RLock()
43 43
 	defer m.mtx.RUnlock()
44 44
 
45
-	for _, metric := range m.children {
46
-		ch <- metric
45
+	for _, metrics := range m.children {
46
+		for _, metric := range metrics {
47
+			ch <- metric.metric
48
+		}
47 49
 	}
48 50
 }
49 51
 
... ...
@@ -77,16 +102,7 @@ func (m *MetricVec) GetMetricWithLabelValues(lvs ...string) (Metric, error) {
77 77
 		return nil, err
78 78
 	}
79 79
 
80
-	m.mtx.RLock()
81
-	metric, ok := m.children[h]
82
-	m.mtx.RUnlock()
83
-	if ok {
84
-		return metric, nil
85
-	}
86
-
87
-	m.mtx.Lock()
88
-	defer m.mtx.Unlock()
89
-	return m.getOrCreateMetric(h, lvs...), nil
80
+	return m.getOrCreateMetricWithLabelValues(h, lvs), nil
90 81
 }
91 82
 
92 83
 // GetMetricWith returns the Metric for the given Labels map (the label names
... ...
@@ -107,20 +123,7 @@ func (m *MetricVec) GetMetricWith(labels Labels) (Metric, error) {
107 107
 		return nil, err
108 108
 	}
109 109
 
110
-	m.mtx.RLock()
111
-	metric, ok := m.children[h]
112
-	m.mtx.RUnlock()
113
-	if ok {
114
-		return metric, nil
115
-	}
116
-
117
-	lvs := make([]string, len(labels))
118
-	for i, label := range m.desc.variableLabels {
119
-		lvs[i] = labels[label]
120
-	}
121
-	m.mtx.Lock()
122
-	defer m.mtx.Unlock()
123
-	return m.getOrCreateMetric(h, lvs...), nil
110
+	return m.getOrCreateMetricWithLabels(h, labels), nil
124 111
 }
125 112
 
126 113
 // WithLabelValues works as GetMetricWithLabelValues, but panics if an error
... ...
@@ -168,11 +171,7 @@ func (m *MetricVec) DeleteLabelValues(lvs ...string) bool {
168 168
 	if err != nil {
169 169
 		return false
170 170
 	}
171
-	if _, ok := m.children[h]; !ok {
172
-		return false
173
-	}
174
-	delete(m.children, h)
175
-	return true
171
+	return m.deleteByHashWithLabelValues(h, lvs)
176 172
 }
177 173
 
178 174
 // Delete deletes the metric where the variable labels are the same as those
... ...
@@ -193,10 +192,50 @@ func (m *MetricVec) Delete(labels Labels) bool {
193 193
 	if err != nil {
194 194
 		return false
195 195
 	}
196
-	if _, ok := m.children[h]; !ok {
196
+
197
+	return m.deleteByHashWithLabels(h, labels)
198
+}
199
+
200
+// deleteByHashWithLabelValues removes the metric from the hash bucket h. If
201
+// there are multiple matches in the bucket, use lvs to select a metric and
202
+// remove only that metric.
203
+func (m *MetricVec) deleteByHashWithLabelValues(h uint64, lvs []string) bool {
204
+	metrics, ok := m.children[h]
205
+	if !ok {
197 206
 		return false
198 207
 	}
199
-	delete(m.children, h)
208
+
209
+	i := m.findMetricWithLabelValues(metrics, lvs)
210
+	if i >= len(metrics) {
211
+		return false
212
+	}
213
+
214
+	if len(metrics) > 1 {
215
+		m.children[h] = append(metrics[:i], metrics[i+1:]...)
216
+	} else {
217
+		delete(m.children, h)
218
+	}
219
+	return true
220
+}
221
+
222
+// deleteByHashWithLabels removes the metric from the hash bucket h. If there
223
+// are multiple matches in the bucket, use lvs to select a metric and remove
224
+// only that metric.
225
+func (m *MetricVec) deleteByHashWithLabels(h uint64, labels Labels) bool {
226
+	metrics, ok := m.children[h]
227
+	if !ok {
228
+		return false
229
+	}
230
+	i := m.findMetricWithLabels(metrics, labels)
231
+	if i >= len(metrics) {
232
+		return false
233
+	}
234
+
235
+	if len(metrics) > 1 {
236
+		m.children[h] = append(metrics[:i], metrics[i+1:]...)
237
+	} else {
238
+		delete(m.children, h)
239
+	}
200 240
 	return true
201 241
 }
202 242
 
... ...
@@ -216,7 +255,8 @@ func (m *MetricVec) hashLabelValues(vals []string) (uint64, error) {
216 216
 	}
217 217
 	h := hashNew()
218 218
 	for _, val := range vals {
219
-		h = hashAdd(h, val)
219
+		h = m.hashAdd(h, val)
220
+		h = m.hashAddByte(h, model.SeparatorByte)
220 221
 	}
221 222
 	return h, nil
222 223
 }
... ...
@@ -231,19 +271,134 @@ func (m *MetricVec) hashLabels(labels Labels) (uint64, error) {
231 231
 		if !ok {
232 232
 			return 0, fmt.Errorf("label name %q missing in label map", label)
233 233
 		}
234
-		h = hashAdd(h, val)
234
+		h = m.hashAdd(h, val)
235
+		h = m.hashAddByte(h, model.SeparatorByte)
235 236
 	}
236 237
 	return h, nil
237 238
 }
238 239
 
239
-func (m *MetricVec) getOrCreateMetric(hash uint64, labelValues ...string) Metric {
240
-	metric, ok := m.children[hash]
240
+// getOrCreateMetricWithLabelValues retrieves the metric by hash and label value
241
+// or creates it and returns the new one.
242
+//
243
+// This function holds the mutex.
244
+func (m *MetricVec) getOrCreateMetricWithLabelValues(hash uint64, lvs []string) Metric {
245
+	m.mtx.RLock()
246
+	metric, ok := m.getMetricWithLabelValues(hash, lvs)
247
+	m.mtx.RUnlock()
248
+	if ok {
249
+		return metric
250
+	}
251
+
252
+	m.mtx.Lock()
253
+	defer m.mtx.Unlock()
254
+	metric, ok = m.getMetricWithLabelValues(hash, lvs)
255
+	if !ok {
256
+		// Copy to avoid allocation in case wo don't go down this code path.
257
+		copiedLVs := make([]string, len(lvs))
258
+		copy(copiedLVs, lvs)
259
+		metric = m.newMetric(copiedLVs...)
260
+		m.children[hash] = append(m.children[hash], metricWithLabelValues{values: copiedLVs, metric: metric})
261
+	}
262
+	return metric
263
+}
264
+
265
+// getOrCreateMetricWithLabelValues retrieves the metric by hash and label value
266
+// or creates it and returns the new one.
267
+//
268
+// This function holds the mutex.
269
+func (m *MetricVec) getOrCreateMetricWithLabels(hash uint64, labels Labels) Metric {
270
+	m.mtx.RLock()
271
+	metric, ok := m.getMetricWithLabels(hash, labels)
272
+	m.mtx.RUnlock()
273
+	if ok {
274
+		return metric
275
+	}
276
+
277
+	m.mtx.Lock()
278
+	defer m.mtx.Unlock()
279
+	metric, ok = m.getMetricWithLabels(hash, labels)
241 280
 	if !ok {
242
-		// Copy labelValues. Otherwise, they would be allocated even if we don't go
243
-		// down this code path.
244
-		copiedLabelValues := append(make([]string, 0, len(labelValues)), labelValues...)
245
-		metric = m.newMetric(copiedLabelValues...)
246
-		m.children[hash] = metric
281
+		lvs := m.extractLabelValues(labels)
282
+		metric = m.newMetric(lvs...)
283
+		m.children[hash] = append(m.children[hash], metricWithLabelValues{values: lvs, metric: metric})
247 284
 	}
248 285
 	return metric
249 286
 }
287
+
288
+// getMetricWithLabelValues gets a metric while handling possible collisions in
289
+// the hash space. Must be called while holding read mutex.
290
+func (m *MetricVec) getMetricWithLabelValues(h uint64, lvs []string) (Metric, bool) {
291
+	metrics, ok := m.children[h]
292
+	if ok {
293
+		if i := m.findMetricWithLabelValues(metrics, lvs); i < len(metrics) {
294
+			return metrics[i].metric, true
295
+		}
296
+	}
297
+	return nil, false
298
+}
299
+
300
+// getMetricWithLabels gets a metric while handling possible collisions in
301
+// the hash space. Must be called while holding read mutex.
302
+func (m *MetricVec) getMetricWithLabels(h uint64, labels Labels) (Metric, bool) {
303
+	metrics, ok := m.children[h]
304
+	if ok {
305
+		if i := m.findMetricWithLabels(metrics, labels); i < len(metrics) {
306
+			return metrics[i].metric, true
307
+		}
308
+	}
309
+	return nil, false
310
+}
311
+
312
+// findMetricWithLabelValues returns the index of the matching metric or
313
+// len(metrics) if not found.
314
+func (m *MetricVec) findMetricWithLabelValues(metrics []metricWithLabelValues, lvs []string) int {
315
+	for i, metric := range metrics {
316
+		if m.matchLabelValues(metric.values, lvs) {
317
+			return i
318
+		}
319
+	}
320
+	return len(metrics)
321
+}
322
+
323
+// findMetricWithLabels returns the index of the matching metric or len(metrics)
324
+// if not found.
325
+func (m *MetricVec) findMetricWithLabels(metrics []metricWithLabelValues, labels Labels) int {
326
+	for i, metric := range metrics {
327
+		if m.matchLabels(metric.values, labels) {
328
+			return i
329
+		}
330
+	}
331
+	return len(metrics)
332
+}
333
+
334
+func (m *MetricVec) matchLabelValues(values []string, lvs []string) bool {
335
+	if len(values) != len(lvs) {
336
+		return false
337
+	}
338
+	for i, v := range values {
339
+		if v != lvs[i] {
340
+			return false
341
+		}
342
+	}
343
+	return true
344
+}
345
+
346
+func (m *MetricVec) matchLabels(values []string, labels Labels) bool {
347
+	if len(labels) != len(values) {
348
+		return false
349
+	}
350
+	for i, k := range m.desc.variableLabels {
351
+		if values[i] != labels[k] {
352
+			return false
353
+		}
354
+	}
355
+	return true
356
+}
357
+
358
+func (m *MetricVec) extractLabelValues(labels Labels) []string {
359
+	labelValues := make([]string, len(labels))
360
+	for i, k := range m.desc.variableLabels {
361
+		labelValues[i] = labels[k]
362
+	}
363
+	return labelValues
364
+}
... ...
@@ -6,7 +6,7 @@ components and libraries.
6 6
 
7 7
 * **config**: Common configuration structures
8 8
 * **expfmt**: Decoding and encoding for the exposition format
9
-* **log**: A logging wrapper around [logrus](https://github.com/Sirupsen/logrus)
9
+* **log**: A logging wrapper around [logrus](https://github.com/sirupsen/logrus)
10 10
 * **model**: Shared data structures
11 11
 * **route**: A routing wrapper around [httprouter](https://github.com/julienschmidt/httprouter) using `context.Context`
12
-* **version**: Version informations and metric
12
+* **version**: Version information and metrics
... ...
@@ -31,6 +31,7 @@ type Decoder interface {
31 31
 	Decode(*dto.MetricFamily) error
32 32
 }
33 33
 
34
+// DecodeOptions contains options used by the Decoder and in sample extraction.
34 35
 type DecodeOptions struct {
35 36
 	// Timestamp is added to each value from the stream that has no explicit timestamp set.
36 37
 	Timestamp model.Time
... ...
@@ -142,6 +143,8 @@ func (d *textDecoder) Decode(v *dto.MetricFamily) error {
142 142
 	return nil
143 143
 }
144 144
 
145
+// SampleDecoder wraps a Decoder to extract samples from the metric families
146
+// decoded by the wrapped Decoder.
145 147
 type SampleDecoder struct {
146 148
 	Dec  Decoder
147 149
 	Opts *DecodeOptions
... ...
@@ -149,37 +152,51 @@ type SampleDecoder struct {
149 149
 	f dto.MetricFamily
150 150
 }
151 151
 
152
+// Decode calls the Decode method of the wrapped Decoder and then extracts the
153
+// samples from the decoded MetricFamily into the provided model.Vector.
152 154
 func (sd *SampleDecoder) Decode(s *model.Vector) error {
153
-	if err := sd.Dec.Decode(&sd.f); err != nil {
155
+	err := sd.Dec.Decode(&sd.f)
156
+	if err != nil {
154 157
 		return err
155 158
 	}
156
-	*s = extractSamples(&sd.f, sd.Opts)
157
-	return nil
159
+	*s, err = extractSamples(&sd.f, sd.Opts)
160
+	return err
158 161
 }
159 162
 
160
-// Extract samples builds a slice of samples from the provided metric families.
161
-func ExtractSamples(o *DecodeOptions, fams ...*dto.MetricFamily) model.Vector {
162
-	var all model.Vector
163
+// ExtractSamples builds a slice of samples from the provided metric
164
+// families. If an error occurrs during sample extraction, it continues to
165
+// extract from the remaining metric families. The returned error is the last
166
+// error that has occurred.
167
+func ExtractSamples(o *DecodeOptions, fams ...*dto.MetricFamily) (model.Vector, error) {
168
+	var (
169
+		all     model.Vector
170
+		lastErr error
171
+	)
163 172
 	for _, f := range fams {
164
-		all = append(all, extractSamples(f, o)...)
173
+		some, err := extractSamples(f, o)
174
+		if err != nil {
175
+			lastErr = err
176
+			continue
177
+		}
178
+		all = append(all, some...)
165 179
 	}
166
-	return all
180
+	return all, lastErr
167 181
 }
168 182
 
169
-func extractSamples(f *dto.MetricFamily, o *DecodeOptions) model.Vector {
183
+func extractSamples(f *dto.MetricFamily, o *DecodeOptions) (model.Vector, error) {
170 184
 	switch f.GetType() {
171 185
 	case dto.MetricType_COUNTER:
172
-		return extractCounter(o, f)
186
+		return extractCounter(o, f), nil
173 187
 	case dto.MetricType_GAUGE:
174
-		return extractGauge(o, f)
188
+		return extractGauge(o, f), nil
175 189
 	case dto.MetricType_SUMMARY:
176
-		return extractSummary(o, f)
190
+		return extractSummary(o, f), nil
177 191
 	case dto.MetricType_UNTYPED:
178
-		return extractUntyped(o, f)
192
+		return extractUntyped(o, f), nil
179 193
 	case dto.MetricType_HISTOGRAM:
180
-		return extractHistogram(o, f)
194
+		return extractHistogram(o, f), nil
181 195
 	}
182
-	panic("expfmt.extractSamples: unknown metric family type")
196
+	return nil, fmt.Errorf("expfmt.extractSamples: unknown metric family type %v", f.GetType())
183 197
 }
184 198
 
185 199
 func extractCounter(o *DecodeOptions, f *dto.MetricFamily) model.Vector {
... ...
@@ -11,27 +11,25 @@
11 11
 // See the License for the specific language governing permissions and
12 12
 // limitations under the License.
13 13
 
14
-// A package for reading and writing Prometheus metrics.
14
+// Package expfmt contains tools for reading and writing Prometheus metrics.
15 15
 package expfmt
16 16
 
17
+// Format specifies the HTTP content type of the different wire protocols.
17 18
 type Format string
18 19
 
20
+// Constants to assemble the Content-Type values for the different wire protocols.
19 21
 const (
20
-	TextVersion = "0.0.4"
21
-
22
+	TextVersion   = "0.0.4"
22 23
 	ProtoType     = `application/vnd.google.protobuf`
23 24
 	ProtoProtocol = `io.prometheus.client.MetricFamily`
24 25
 	ProtoFmt      = ProtoType + "; proto=" + ProtoProtocol + ";"
25 26
 
26 27
 	// The Content-Type values for the different wire protocols.
27 28
 	FmtUnknown      Format = `<unknown>`
28
-	FmtText         Format = `text/plain; version=` + TextVersion
29
+	FmtText         Format = `text/plain; version=` + TextVersion + `; charset=utf-8`
29 30
 	FmtProtoDelim   Format = ProtoFmt + ` encoding=delimited`
30 31
 	FmtProtoText    Format = ProtoFmt + ` encoding=text`
31 32
 	FmtProtoCompact Format = ProtoFmt + ` encoding=compact-text`
32
-
33
-	// fmtJSON2 is hidden as it is deprecated.
34
-	fmtJSON2 Format = `application/json; version=0.0.2`
35 33
 )
36 34
 
37 35
 const (
... ...
@@ -25,9 +25,12 @@ import (
25 25
 
26 26
 // MetricFamilyToText converts a MetricFamily proto message into text format and
27 27
 // writes the resulting lines to 'out'. It returns the number of bytes written
28
-// and any error encountered.  This function does not perform checks on the
29
-// content of the metric and label names, i.e. invalid metric or label names
28
+// and any error encountered. The output will have the same order as the input,
29
+// no further sorting is performed. Furthermore, this function assumes the input
30
+// is already sanitized and does not perform any sanity checks. If the input
31
+// contains duplicate metrics or invalid metric or label names, the conversion
30 32
 // will result in invalid text format output.
33
+//
31 34
 // This method fulfills the type 'prometheus.encoder'.
32 35
 func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (int, error) {
33 36
 	var written int
... ...
@@ -47,7 +47,7 @@ func (e ParseError) Error() string {
47 47
 }
48 48
 
49 49
 // TextParser is used to parse the simple and flat text-based exchange format. Its
50
-// nil value is ready to use.
50
+// zero value is ready to use.
51 51
 type TextParser struct {
52 52
 	metricFamiliesByName map[string]*dto.MetricFamily
53 53
 	buf                  *bufio.Reader // Where the parsed input is read through.
... ...
@@ -315,6 +315,10 @@ func (p *TextParser) startLabelValue() stateFn {
315 315
 	if p.readTokenAsLabelValue(); p.err != nil {
316 316
 		return nil
317 317
 	}
318
+	if !model.LabelValue(p.currentToken.String()).IsValid() {
319
+		p.parseError(fmt.Sprintf("invalid label value %q", p.currentToken.String()))
320
+		return nil
321
+	}
318 322
 	p.currentLabelPair.Value = proto.String(p.currentToken.String())
319 323
 	// Special treatment of summaries:
320 324
 	// - Quantile labels are special, will result in dto.Quantile later.
... ...
@@ -552,8 +556,8 @@ func (p *TextParser) readTokenUntilWhitespace() {
552 552
 // byte considered is the byte already read (now in p.currentByte).  The first
553 553
 // newline byte encountered is still copied into p.currentByte, but not into
554 554
 // p.currentToken. If recognizeEscapeSequence is true, two escape sequences are
555
-// recognized: '\\' tranlates into '\', and '\n' into a line-feed character. All
556
-// other escape sequences are invalid and cause an error.
555
+// recognized: '\\' translates into '\', and '\n' into a line-feed character.
556
+// All other escape sequences are invalid and cause an error.
557 557
 func (p *TextParser) readTokenUntilNewline(recognizeEscapeSequence bool) {
558 558
 	p.currentToken.Reset()
559 559
 	escaped := false
... ...
@@ -80,14 +80,18 @@ const (
80 80
 	QuantileLabel = "quantile"
81 81
 )
82 82
 
83
-// LabelNameRE is a regular expression matching valid label names.
83
+// LabelNameRE is a regular expression matching valid label names. Note that the
84
+// IsValid method of LabelName performs the same check but faster than a match
85
+// with this regular expression.
84 86
 var LabelNameRE = regexp.MustCompile("^[a-zA-Z_][a-zA-Z0-9_]*$")
85 87
 
86 88
 // A LabelName is a key for a LabelSet or Metric.  It has a value associated
87 89
 // therewith.
88 90
 type LabelName string
89 91
 
90
-// IsValid is true iff the label name matches the pattern of LabelNameRE.
92
+// IsValid is true iff the label name matches the pattern of LabelNameRE. This
93
+// method, however, does not use LabelNameRE for the check but a much faster
94
+// hardcoded implementation.
91 95
 func (ln LabelName) IsValid() bool {
92 96
 	if len(ln) == 0 {
93 97
 		return false
... ...
@@ -106,7 +110,7 @@ func (ln *LabelName) UnmarshalYAML(unmarshal func(interface{}) error) error {
106 106
 	if err := unmarshal(&s); err != nil {
107 107
 		return err
108 108
 	}
109
-	if !LabelNameRE.MatchString(s) {
109
+	if !LabelName(s).IsValid() {
110 110
 		return fmt.Errorf("%q is not a valid label name", s)
111 111
 	}
112 112
 	*ln = LabelName(s)
... ...
@@ -119,7 +123,7 @@ func (ln *LabelName) UnmarshalJSON(b []byte) error {
119 119
 	if err := json.Unmarshal(b, &s); err != nil {
120 120
 		return err
121 121
 	}
122
-	if !LabelNameRE.MatchString(s) {
122
+	if !LabelName(s).IsValid() {
123 123
 		return fmt.Errorf("%q is not a valid label name", s)
124 124
 	}
125 125
 	*ln = LabelName(s)
... ...
@@ -160,7 +160,7 @@ func (l *LabelSet) UnmarshalJSON(b []byte) error {
160 160
 	// LabelName as a string and does not call its UnmarshalJSON method.
161 161
 	// Thus, we have to replicate the behavior here.
162 162
 	for ln := range m {
163
-		if !LabelNameRE.MatchString(string(ln)) {
163
+		if !ln.IsValid() {
164 164
 			return fmt.Errorf("%q is not a valid label name", ln)
165 165
 		}
166 166
 	}
... ...
@@ -21,8 +21,11 @@ import (
21 21
 )
22 22
 
23 23
 var (
24
-	separator    = []byte{0}
25
-	MetricNameRE = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_:]*$`)
24
+	separator = []byte{0}
25
+	// MetricNameRE is a regular expression matching valid metric
26
+	// names. Note that the IsValidMetricName function performs the same
27
+	// check but faster than a match with this regular expression.
28
+	MetricNameRE = regexp.MustCompile(`^[a-zA-Z_:][a-zA-Z0-9_:]*$`)
26 29
 )
27 30
 
28 31
 // A Metric is similar to a LabelSet, but the key difference is that a Metric is
... ...
@@ -41,7 +44,7 @@ func (m Metric) Before(o Metric) bool {
41 41
 
42 42
 // Clone returns a copy of the Metric.
43 43
 func (m Metric) Clone() Metric {
44
-	clone := Metric{}
44
+	clone := make(Metric, len(m))
45 45
 	for k, v := range m {
46 46
 		clone[k] = v
47 47
 	}
... ...
@@ -85,6 +88,8 @@ func (m Metric) FastFingerprint() Fingerprint {
85 85
 }
86 86
 
87 87
 // IsValidMetricName returns true iff name matches the pattern of MetricNameRE.
88
+// This function, however, does not use MetricNameRE for the check but a much
89
+// faster hardcoded implementation.
88 90
 func IsValidMetricName(n LabelValue) bool {
89 91
 	if len(n) == 0 {
90 92
 		return false
... ...
@@ -59,8 +59,8 @@ func (m *Matcher) Validate() error {
59 59
 	return nil
60 60
 }
61 61
 
62
-// Silence defines the representation of a silence definiton
63
-// in the Prometheus eco-system.
62
+// Silence defines the representation of a silence definition in the Prometheus
63
+// eco-system.
64 64
 type Silence struct {
65 65
 	ID uint64 `json:"id,omitempty"`
66 66
 
... ...
@@ -163,9 +163,21 @@ func (t *Time) UnmarshalJSON(b []byte) error {
163 163
 // This type should not propagate beyond the scope of input/output processing.
164 164
 type Duration time.Duration
165 165
 
166
+// Set implements pflag/flag.Value
167
+func (d *Duration) Set(s string) error {
168
+	var err error
169
+	*d, err = ParseDuration(s)
170
+	return err
171
+}
172
+
173
+// Type implements pflag.Value
174
+func (d *Duration) Type() string {
175
+	return "duration"
176
+}
177
+
166 178
 var durationRE = regexp.MustCompile("^([0-9]+)(y|w|d|h|m|s|ms)$")
167 179
 
168
-// StringToDuration parses a string into a time.Duration, assuming that a year
180
+// ParseDuration parses a string into a time.Duration, assuming that a year
169 181
 // always has 365d, a week always has 7d, and a day always has 24h.
170 182
 func ParseDuration(durationStr string) (Duration, error) {
171 183
 	matches := durationRE.FindStringSubmatch(durationStr)
... ...
@@ -202,6 +214,9 @@ func (d Duration) String() string {
202 202
 		ms   = int64(time.Duration(d) / time.Millisecond)
203 203
 		unit = "ms"
204 204
 	)
205
+	if ms == 0 {
206
+		return "0s"
207
+	}
205 208
 	factors := map[string]int64{
206 209
 		"y":  1000 * 60 * 60 * 24 * 365,
207 210
 		"w":  1000 * 60 * 60 * 24 * 7,
... ...
@@ -22,6 +22,22 @@ import (
22 22
 	"strings"
23 23
 )
24 24
 
25
+var (
26
+	// ZeroSamplePair is the pseudo zero-value of SamplePair used to signal a
27
+	// non-existing sample pair. It is a SamplePair with timestamp Earliest and
28
+	// value 0.0. Note that the natural zero value of SamplePair has a timestamp
29
+	// of 0, which is possible to appear in a real SamplePair and thus not
30
+	// suitable to signal a non-existing SamplePair.
31
+	ZeroSamplePair = SamplePair{Timestamp: Earliest}
32
+
33
+	// ZeroSample is the pseudo zero-value of Sample used to signal a
34
+	// non-existing sample. It is a Sample with timestamp Earliest, value 0.0,
35
+	// and metric nil. Note that the natural zero value of Sample has a timestamp
36
+	// of 0, which is possible to appear in a real Sample and thus not suitable
37
+	// to signal a non-existing Sample.
38
+	ZeroSample = Sample{Timestamp: Earliest}
39
+)
40
+
25 41
 // A SampleValue is a representation of a value for a given sample at a given
26 42
 // time.
27 43
 type SampleValue float64
... ...
@@ -84,7 +100,7 @@ func (s *SamplePair) UnmarshalJSON(b []byte) error {
84 84
 }
85 85
 
86 86
 // Equal returns true if this SamplePair and o have equal Values and equal
87
-// Timestamps. The sematics of Value equality is defined by SampleValue.Equal.
87
+// Timestamps. The semantics of Value equality is defined by SampleValue.Equal.
88 88
 func (s *SamplePair) Equal(o *SamplePair) bool {
89 89
 	return s == o || (s.Value.Equal(o.Value) && s.Timestamp.Equal(o.Timestamp))
90 90
 }
... ...
@@ -101,7 +117,7 @@ type Sample struct {
101 101
 }
102 102
 
103 103
 // Equal compares first the metrics, then the timestamp, then the value. The
104
-// sematics of value equality is defined by SampleValue.Equal.
104
+// semantics of value equality is defined by SampleValue.Equal.
105 105
 func (s *Sample) Equal(o *Sample) bool {
106 106
 	if s == o {
107 107
 		return true
... ...
@@ -113,11 +129,8 @@ func (s *Sample) Equal(o *Sample) bool {
113 113
 	if !s.Timestamp.Equal(o.Timestamp) {
114 114
 		return false
115 115
 	}
116
-	if s.Value.Equal(o.Value) {
117
-		return false
118
-	}
119 116
 
120
-	return true
117
+	return s.Value.Equal(o.Value)
121 118
 }
122 119
 
123 120
 func (s Sample) String() string {
... ...
@@ -8,3 +8,4 @@ backwards-incompatible ways without warnings. Use it at your own risk.
8 8
 
9 9
 [![GoDoc](https://godoc.org/github.com/prometheus/procfs?status.png)](https://godoc.org/github.com/prometheus/procfs)
10 10
 [![Build Status](https://travis-ci.org/prometheus/procfs.svg?branch=master)](https://travis-ci.org/prometheus/procfs)
11
+[![Go Report Card](https://goreportcard.com/badge/github.com/prometheus/procfs)](https://goreportcard.com/report/github.com/prometheus/procfs)
11 12
new file mode 100644
... ...
@@ -0,0 +1,95 @@
0
+// Copyright 2017 The Prometheus Authors
1
+// Licensed under the Apache License, Version 2.0 (the "License");
2
+// you may not use this file except in compliance with the License.
3
+// You may obtain a copy of the License at
4
+//
5
+// http://www.apache.org/licenses/LICENSE-2.0
6
+//
7
+// Unless required by applicable law or agreed to in writing, software
8
+// distributed under the License is distributed on an "AS IS" BASIS,
9
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+// See the License for the specific language governing permissions and
11
+// limitations under the License.
12
+
13
+package procfs
14
+
15
+import (
16
+	"bufio"
17
+	"fmt"
18
+	"io"
19
+	"os"
20
+	"strconv"
21
+	"strings"
22
+)
23
+
24
+// A BuddyInfo is the details parsed from /proc/buddyinfo.
25
+// The data is comprised of an array of free fragments of each size.
26
+// The sizes are 2^n*PAGE_SIZE, where n is the array index.
27
+type BuddyInfo struct {
28
+	Node  string
29
+	Zone  string
30
+	Sizes []float64
31
+}
32
+
33
+// NewBuddyInfo reads the buddyinfo statistics.
34
+func NewBuddyInfo() ([]BuddyInfo, error) {
35
+	fs, err := NewFS(DefaultMountPoint)
36
+	if err != nil {
37
+		return nil, err
38
+	}
39
+
40
+	return fs.NewBuddyInfo()
41
+}
42
+
43
+// NewBuddyInfo reads the buddyinfo statistics from the specified `proc` filesystem.
44
+func (fs FS) NewBuddyInfo() ([]BuddyInfo, error) {
45
+	file, err := os.Open(fs.Path("buddyinfo"))
46
+	if err != nil {
47
+		return nil, err
48
+	}
49
+	defer file.Close()
50
+
51
+	return parseBuddyInfo(file)
52
+}
53
+
54
+func parseBuddyInfo(r io.Reader) ([]BuddyInfo, error) {
55
+	var (
56
+		buddyInfo   = []BuddyInfo{}
57
+		scanner     = bufio.NewScanner(r)
58
+		bucketCount = -1
59
+	)
60
+
61
+	for scanner.Scan() {
62
+		var err error
63
+		line := scanner.Text()
64
+		parts := strings.Fields(line)
65
+
66
+		if len(parts) < 4 {
67
+			return nil, fmt.Errorf("invalid number of fields when parsing buddyinfo")
68
+		}
69
+
70
+		node := strings.TrimRight(parts[1], ",")
71
+		zone := strings.TrimRight(parts[3], ",")
72
+		arraySize := len(parts[4:])
73
+
74
+		if bucketCount == -1 {
75
+			bucketCount = arraySize
76
+		} else {
77
+			if bucketCount != arraySize {
78
+				return nil, fmt.Errorf("mismatch in number of buddyinfo buckets, previous count %d, new count %d", bucketCount, arraySize)
79
+			}
80
+		}
81
+
82
+		sizes := make([]float64, arraySize)
83
+		for i := 0; i < arraySize; i++ {
84
+			sizes[i], err = strconv.ParseFloat(parts[i+4], 64)
85
+			if err != nil {
86
+				return nil, fmt.Errorf("invalid value in buddyinfo: %s", err)
87
+			}
88
+		}
89
+
90
+		buddyInfo = append(buddyInfo, BuddyInfo{node, zone, sizes})
91
+	}
92
+
93
+	return buddyInfo, scanner.Err()
94
+}
... ...
@@ -1,9 +1,25 @@
1
+// Copyright 2018 The Prometheus Authors
2
+// Licensed under the Apache License, Version 2.0 (the "License");
3
+// you may not use this file except in compliance with the License.
4
+// You may obtain a copy of the License at
5
+//
6
+// http://www.apache.org/licenses/LICENSE-2.0
7
+//
8
+// Unless required by applicable law or agreed to in writing, software
9
+// distributed under the License is distributed on an "AS IS" BASIS,
10
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+// See the License for the specific language governing permissions and
12
+// limitations under the License.
13
+
1 14
 package procfs
2 15
 
3 16
 import (
4 17
 	"fmt"
5 18
 	"os"
6 19
 	"path"
20
+
21
+	"github.com/prometheus/procfs/nfs"
22
+	"github.com/prometheus/procfs/xfs"
7 23
 )
8 24
 
9 25
 // FS represents the pseudo-filesystem proc, which provides an interface to
... ...
@@ -31,3 +47,36 @@ func NewFS(mountPoint string) (FS, error) {
31 31
 func (fs FS) Path(p ...string) string {
32 32
 	return path.Join(append([]string{string(fs)}, p...)...)
33 33
 }
34
+
35
+// XFSStats retrieves XFS filesystem runtime statistics.
36
+func (fs FS) XFSStats() (*xfs.Stats, error) {
37
+	f, err := os.Open(fs.Path("fs/xfs/stat"))
38
+	if err != nil {
39
+		return nil, err
40
+	}
41
+	defer f.Close()
42
+
43
+	return xfs.ParseStats(f)
44
+}
45
+
46
+// NFSClientRPCStats retrieves NFS client RPC statistics.
47
+func (fs FS) NFSClientRPCStats() (*nfs.ClientRPCStats, error) {
48
+	f, err := os.Open(fs.Path("net/rpc/nfs"))
49
+	if err != nil {
50
+		return nil, err
51
+	}
52
+	defer f.Close()
53
+
54
+	return nfs.ParseClientRPCStats(f)
55
+}
56
+
57
+// NFSdServerRPCStats retrieves NFS daemon RPC statistics.
58
+func (fs FS) NFSdServerRPCStats() (*nfs.ServerRPCStats, error) {
59
+	f, err := os.Open(fs.Path("net/rpc/nfsd"))
60
+	if err != nil {
61
+		return nil, err
62
+	}
63
+	defer f.Close()
64
+
65
+	return nfs.ParseServerRPCStats(f)
66
+}
34 67
new file mode 100644
... ...
@@ -0,0 +1,46 @@
0
+// Copyright 2018 The Prometheus Authors
1
+// Licensed under the Apache License, Version 2.0 (the "License");
2
+// you may not use this file except in compliance with the License.
3
+// You may obtain a copy of the License at
4
+//
5
+// http://www.apache.org/licenses/LICENSE-2.0
6
+//
7
+// Unless required by applicable law or agreed to in writing, software
8
+// distributed under the License is distributed on an "AS IS" BASIS,
9
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+// See the License for the specific language governing permissions and
11
+// limitations under the License.
12
+
13
+package util
14
+
15
+import "strconv"
16
+
17
+// ParseUint32s parses a slice of strings into a slice of uint32s.
18
+func ParseUint32s(ss []string) ([]uint32, error) {
19
+	us := make([]uint32, 0, len(ss))
20
+	for _, s := range ss {
21
+		u, err := strconv.ParseUint(s, 10, 32)
22
+		if err != nil {
23
+			return nil, err
24
+		}
25
+
26
+		us = append(us, uint32(u))
27
+	}
28
+
29
+	return us, nil
30
+}
31
+
32
+// ParseUint64s parses a slice of strings into a slice of uint64s.
33
+func ParseUint64s(ss []string) ([]uint64, error) {
34
+	us := make([]uint64, 0, len(ss))
35
+	for _, s := range ss {
36
+		u, err := strconv.ParseUint(s, 10, 64)
37
+		if err != nil {
38
+			return nil, err
39
+		}
40
+
41
+		us = append(us, u)
42
+	}
43
+
44
+	return us, nil
45
+}
... ...
@@ -1,3 +1,16 @@
1
+// Copyright 2018 The Prometheus Authors
2
+// Licensed under the Apache License, Version 2.0 (the "License");
3
+// you may not use this file except in compliance with the License.
4
+// You may obtain a copy of the License at
5
+//
6
+// http://www.apache.org/licenses/LICENSE-2.0
7
+//
8
+// Unless required by applicable law or agreed to in writing, software
9
+// distributed under the License is distributed on an "AS IS" BASIS,
10
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+// See the License for the specific language governing permissions and
12
+// limitations under the License.
13
+
1 14
 package procfs
2 15
 
3 16
 import (
... ...
@@ -31,14 +44,16 @@ type IPVSStats struct {
31 31
 type IPVSBackendStatus struct {
32 32
 	// The local (virtual) IP address.
33 33
 	LocalAddress net.IP
34
-	// The local (virtual) port.
35
-	LocalPort uint16
36
-	// The transport protocol (TCP, UDP).
37
-	Proto string
38 34
 	// The remote (real) IP address.
39 35
 	RemoteAddress net.IP
36
+	// The local (virtual) port.
37
+	LocalPort uint16
40 38
 	// The remote (real) port.
41 39
 	RemotePort uint16
40
+	// The local firewall mark
41
+	LocalMark string
42
+	// The transport protocol (TCP, UDP).
43
+	Proto string
42 44
 	// The current number of active connections for this virtual/real address pair.
43 45
 	ActiveConn uint64
44 46
 	// The current number of inactive connections for this virtual/real address pair.
... ...
@@ -142,13 +157,14 @@ func parseIPVSBackendStatus(file io.Reader) ([]IPVSBackendStatus, error) {
142 142
 		status       []IPVSBackendStatus
143 143
 		scanner      = bufio.NewScanner(file)
144 144
 		proto        string
145
+		localMark    string
145 146
 		localAddress net.IP
146 147
 		localPort    uint16
147 148
 		err          error
148 149
 	)
149 150
 
150 151
 	for scanner.Scan() {
151
-		fields := strings.Fields(string(scanner.Text()))
152
+		fields := strings.Fields(scanner.Text())
152 153
 		if len(fields) == 0 {
153 154
 			continue
154 155
 		}
... ...
@@ -160,10 +176,19 @@ func parseIPVSBackendStatus(file io.Reader) ([]IPVSBackendStatus, error) {
160 160
 				continue
161 161
 			}
162 162
 			proto = fields[0]
163
+			localMark = ""
163 164
 			localAddress, localPort, err = parseIPPort(fields[1])
164 165
 			if err != nil {
165 166
 				return nil, err
166 167
 			}
168
+		case fields[0] == "FWM":
169
+			if len(fields) < 2 {
170
+				continue
171
+			}
172
+			proto = fields[0]
173
+			localMark = fields[1]
174
+			localAddress = nil
175
+			localPort = 0
167 176
 		case fields[0] == "->":
168 177
 			if len(fields) < 6 {
169 178
 				continue
... ...
@@ -187,6 +212,7 @@ func parseIPVSBackendStatus(file io.Reader) ([]IPVSBackendStatus, error) {
187 187
 			status = append(status, IPVSBackendStatus{
188 188
 				LocalAddress:  localAddress,
189 189
 				LocalPort:     localPort,
190
+				LocalMark:     localMark,
190 191
 				RemoteAddress: remoteAddress,
191 192
 				RemotePort:    remotePort,
192 193
 				Proto:         proto,
... ...
@@ -200,22 +226,31 @@ func parseIPVSBackendStatus(file io.Reader) ([]IPVSBackendStatus, error) {
200 200
 }
201 201
 
202 202
 func parseIPPort(s string) (net.IP, uint16, error) {
203
-	tmp := strings.SplitN(s, ":", 2)
204
-
205
-	if len(tmp) != 2 {
206
-		return nil, 0, fmt.Errorf("invalid IP:Port: %s", s)
207
-	}
203
+	var (
204
+		ip  net.IP
205
+		err error
206
+	)
208 207
 
209
-	if len(tmp[0]) != 8 && len(tmp[0]) != 32 {
210
-		return nil, 0, fmt.Errorf("invalid IP: %s", tmp[0])
208
+	switch len(s) {
209
+	case 13:
210
+		ip, err = hex.DecodeString(s[0:8])
211
+		if err != nil {
212
+			return nil, 0, err
213
+		}
214
+	case 46:
215
+		ip = net.ParseIP(s[1:40])
216
+		if ip == nil {
217
+			return nil, 0, fmt.Errorf("invalid IPv6 address: %s", s[1:40])
218
+		}
219
+	default:
220
+		return nil, 0, fmt.Errorf("unexpected IP:Port: %s", s)
211 221
 	}
212 222
 
213
-	ip, err := hex.DecodeString(tmp[0])
214
-	if err != nil {
215
-		return nil, 0, err
223
+	portString := s[len(s)-4:]
224
+	if len(portString) != 4 {
225
+		return nil, 0, fmt.Errorf("unexpected port string format: %s", portString)
216 226
 	}
217
-
218
-	port, err := strconv.ParseUint(tmp[1], 16, 16)
227
+	port, err := strconv.ParseUint(portString, 16, 16)
219 228
 	if err != nil {
220 229
 		return nil, 0, err
221 230
 	}
... ...
@@ -1,3 +1,16 @@
1
+// Copyright 2018 The Prometheus Authors
2
+// Licensed under the Apache License, Version 2.0 (the "License");
3
+// you may not use this file except in compliance with the License.
4
+// You may obtain a copy of the License at
5
+//
6
+// http://www.apache.org/licenses/LICENSE-2.0
7
+//
8
+// Unless required by applicable law or agreed to in writing, software
9
+// distributed under the License is distributed on an "AS IS" BASIS,
10
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+// See the License for the specific language governing permissions and
12
+// limitations under the License.
13
+
1 14
 package procfs
2 15
 
3 16
 import (
4 17
new file mode 100644
... ...
@@ -0,0 +1,569 @@
0
+// Copyright 2018 The Prometheus Authors
1
+// Licensed under the Apache License, Version 2.0 (the "License");
2
+// you may not use this file except in compliance with the License.
3
+// You may obtain a copy of the License at
4
+//
5
+// http://www.apache.org/licenses/LICENSE-2.0
6
+//
7
+// Unless required by applicable law or agreed to in writing, software
8
+// distributed under the License is distributed on an "AS IS" BASIS,
9
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+// See the License for the specific language governing permissions and
11
+// limitations under the License.
12
+
13
+package procfs
14
+
15
+// While implementing parsing of /proc/[pid]/mountstats, this blog was used
16
+// heavily as a reference:
17
+//   https://utcc.utoronto.ca/~cks/space/blog/linux/NFSMountstatsIndex
18
+//
19
+// Special thanks to Chris Siebenmann for all of his posts explaining the
20
+// various statistics available for NFS.
21
+
22
+import (
23
+	"bufio"
24
+	"fmt"
25
+	"io"
26
+	"strconv"
27
+	"strings"
28
+	"time"
29
+)
30
+
31
+// Constants shared between multiple functions.
32
+const (
33
+	deviceEntryLen = 8
34
+
35
+	fieldBytesLen  = 8
36
+	fieldEventsLen = 27
37
+
38
+	statVersion10 = "1.0"
39
+	statVersion11 = "1.1"
40
+
41
+	fieldTransport10Len = 10
42
+	fieldTransport11Len = 13
43
+)
44
+
45
+// A Mount is a device mount parsed from /proc/[pid]/mountstats.
46
+type Mount struct {
47
+	// Name of the device.
48
+	Device string
49
+	// The mount point of the device.
50
+	Mount string
51
+	// The filesystem type used by the device.
52
+	Type string
53
+	// If available additional statistics related to this Mount.
54
+	// Use a type assertion to determine if additional statistics are available.
55
+	Stats MountStats
56
+}
57
+
58
+// A MountStats is a type which contains detailed statistics for a specific
59
+// type of Mount.
60
+type MountStats interface {
61
+	mountStats()
62
+}
63
+
64
+// A MountStatsNFS is a MountStats implementation for NFSv3 and v4 mounts.
65
+type MountStatsNFS struct {
66
+	// The version of statistics provided.
67
+	StatVersion string
68
+	// The age of the NFS mount.
69
+	Age time.Duration
70
+	// Statistics related to byte counters for various operations.
71
+	Bytes NFSBytesStats
72
+	// Statistics related to various NFS event occurrences.
73
+	Events NFSEventsStats
74
+	// Statistics broken down by filesystem operation.
75
+	Operations []NFSOperationStats
76
+	// Statistics about the NFS RPC transport.
77
+	Transport NFSTransportStats
78
+}
79
+
80
+// mountStats implements MountStats.
81
+func (m MountStatsNFS) mountStats() {}
82
+
83
+// A NFSBytesStats contains statistics about the number of bytes read and written
84
+// by an NFS client to and from an NFS server.
85
+type NFSBytesStats struct {
86
+	// Number of bytes read using the read() syscall.
87
+	Read uint64
88
+	// Number of bytes written using the write() syscall.
89
+	Write uint64
90
+	// Number of bytes read using the read() syscall in O_DIRECT mode.
91
+	DirectRead uint64
92
+	// Number of bytes written using the write() syscall in O_DIRECT mode.
93
+	DirectWrite uint64
94
+	// Number of bytes read from the NFS server, in total.
95
+	ReadTotal uint64
96
+	// Number of bytes written to the NFS server, in total.
97
+	WriteTotal uint64
98
+	// Number of pages read directly via mmap()'d files.
99
+	ReadPages uint64
100
+	// Number of pages written directly via mmap()'d files.
101
+	WritePages uint64
102
+}
103
+
104
+// A NFSEventsStats contains statistics about NFS event occurrences.
105
+type NFSEventsStats struct {
106
+	// Number of times cached inode attributes are re-validated from the server.
107
+	InodeRevalidate uint64
108
+	// Number of times cached dentry nodes are re-validated from the server.
109
+	DnodeRevalidate uint64
110
+	// Number of times an inode cache is cleared.
111
+	DataInvalidate uint64
112
+	// Number of times cached inode attributes are invalidated.
113
+	AttributeInvalidate uint64
114
+	// Number of times files or directories have been open()'d.
115
+	VFSOpen uint64
116
+	// Number of times a directory lookup has occurred.
117
+	VFSLookup uint64
118
+	// Number of times permissions have been checked.
119
+	VFSAccess uint64
120
+	// Number of updates (and potential writes) to pages.
121
+	VFSUpdatePage uint64
122
+	// Number of pages read directly via mmap()'d files.
123
+	VFSReadPage uint64
124
+	// Number of times a group of pages have been read.
125
+	VFSReadPages uint64
126
+	// Number of pages written directly via mmap()'d files.
127
+	VFSWritePage uint64
128
+	// Number of times a group of pages have been written.
129
+	VFSWritePages uint64
130
+	// Number of times directory entries have been read with getdents().
131
+	VFSGetdents uint64
132
+	// Number of times attributes have been set on inodes.
133
+	VFSSetattr uint64
134
+	// Number of pending writes that have been forcefully flushed to the server.
135
+	VFSFlush uint64
136
+	// Number of times fsync() has been called on directories and files.
137
+	VFSFsync uint64
138
+	// Number of times locking has been attempted on a file.
139
+	VFSLock uint64
140
+	// Number of times files have been closed and released.
141
+	VFSFileRelease uint64
142
+	// Unknown.  Possibly unused.
143
+	CongestionWait uint64
144
+	// Number of times files have been truncated.
145
+	Truncation uint64
146
+	// Number of times a file has been grown due to writes beyond its existing end.
147
+	WriteExtension uint64
148
+	// Number of times a file was removed while still open by another process.
149
+	SillyRename uint64
150
+	// Number of times the NFS server gave less data than expected while reading.
151
+	ShortRead uint64
152
+	// Number of times the NFS server wrote less data than expected while writing.
153
+	ShortWrite uint64
154
+	// Number of times the NFS server indicated EJUKEBOX; retrieving data from
155
+	// offline storage.
156
+	JukeboxDelay uint64
157
+	// Number of NFS v4.1+ pNFS reads.
158
+	PNFSRead uint64
159
+	// Number of NFS v4.1+ pNFS writes.
160
+	PNFSWrite uint64
161
+}
162
+
163
+// A NFSOperationStats contains statistics for a single operation.
164
+type NFSOperationStats struct {
165
+	// The name of the operation.
166
+	Operation string
167
+	// Number of requests performed for this operation.
168
+	Requests uint64
169
+	// Number of times an actual RPC request has been transmitted for this operation.
170
+	Transmissions uint64
171
+	// Number of times a request has had a major timeout.
172
+	MajorTimeouts uint64
173
+	// Number of bytes sent for this operation, including RPC headers and payload.
174
+	BytesSent uint64
175
+	// Number of bytes received for this operation, including RPC headers and payload.
176
+	BytesReceived uint64
177
+	// Duration all requests spent queued for transmission before they were sent.
178
+	CumulativeQueueTime time.Duration
179
+	// Duration it took to get a reply back after the request was transmitted.
180
+	CumulativeTotalResponseTime time.Duration
181
+	// Duration from when a request was enqueued to when it was completely handled.
182
+	CumulativeTotalRequestTime time.Duration
183
+}
184
+
185
+// A NFSTransportStats contains statistics for the NFS mount RPC requests and
186
+// responses.
187
+type NFSTransportStats struct {
188
+	// The local port used for the NFS mount.
189
+	Port uint64
190
+	// Number of times the client has had to establish a connection from scratch
191
+	// to the NFS server.
192
+	Bind uint64
193
+	// Number of times the client has made a TCP connection to the NFS server.
194
+	Connect uint64
195
+	// Duration (in jiffies, a kernel internal unit of time) the NFS mount has
196
+	// spent waiting for connections to the server to be established.
197
+	ConnectIdleTime uint64
198
+	// Duration since the NFS mount last saw any RPC traffic.
199
+	IdleTime time.Duration
200
+	// Number of RPC requests for this mount sent to the NFS server.
201
+	Sends uint64
202
+	// Number of RPC responses for this mount received from the NFS server.
203
+	Receives uint64
204
+	// Number of times the NFS server sent a response with a transaction ID
205
+	// unknown to this client.
206
+	BadTransactionIDs uint64
207
+	// A running counter, incremented on each request as the current difference
208
+	// ebetween sends and receives.
209
+	CumulativeActiveRequests uint64
210
+	// A running counter, incremented on each request by the current backlog
211
+	// queue size.
212
+	CumulativeBacklog uint64
213
+
214
+	// Stats below only available with stat version 1.1.
215
+
216
+	// Maximum number of simultaneously active RPC requests ever used.
217
+	MaximumRPCSlotsUsed uint64
218
+	// A running counter, incremented on each request as the current size of the
219
+	// sending queue.
220
+	CumulativeSendingQueue uint64
221
+	// A running counter, incremented on each request as the current size of the
222
+	// pending queue.
223
+	CumulativePendingQueue uint64
224
+}
225
+
226
+// parseMountStats parses a /proc/[pid]/mountstats file and returns a slice
227
+// of Mount structures containing detailed information about each mount.
228
+// If available, statistics for each mount are parsed as well.
229
+func parseMountStats(r io.Reader) ([]*Mount, error) {
230
+	const (
231
+		device            = "device"
232
+		statVersionPrefix = "statvers="
233
+
234
+		nfs3Type = "nfs"
235
+		nfs4Type = "nfs4"
236
+	)
237
+
238
+	var mounts []*Mount
239
+
240
+	s := bufio.NewScanner(r)
241
+	for s.Scan() {
242
+		// Only look for device entries in this function
243
+		ss := strings.Fields(string(s.Bytes()))
244
+		if len(ss) == 0 || ss[0] != device {
245
+			continue
246
+		}
247
+
248
+		m, err := parseMount(ss)
249
+		if err != nil {
250
+			return nil, err
251
+		}
252
+
253
+		// Does this mount also possess statistics information?
254
+		if len(ss) > deviceEntryLen {
255
+			// Only NFSv3 and v4 are supported for parsing statistics
256
+			if m.Type != nfs3Type && m.Type != nfs4Type {
257
+				return nil, fmt.Errorf("cannot parse MountStats for fstype %q", m.Type)
258
+			}
259
+
260
+			statVersion := strings.TrimPrefix(ss[8], statVersionPrefix)
261
+
262
+			stats, err := parseMountStatsNFS(s, statVersion)
263
+			if err != nil {
264
+				return nil, err
265
+			}
266
+
267
+			m.Stats = stats
268
+		}
269
+
270
+		mounts = append(mounts, m)
271
+	}
272
+
273
+	return mounts, s.Err()
274
+}
275
+
276
+// parseMount parses an entry in /proc/[pid]/mountstats in the format:
277
+//   device [device] mounted on [mount] with fstype [type]
278
+func parseMount(ss []string) (*Mount, error) {
279
+	if len(ss) < deviceEntryLen {
280
+		return nil, fmt.Errorf("invalid device entry: %v", ss)
281
+	}
282
+
283
+	// Check for specific words appearing at specific indices to ensure
284
+	// the format is consistent with what we expect
285
+	format := []struct {
286
+		i int
287
+		s string
288
+	}{
289
+		{i: 0, s: "device"},
290
+		{i: 2, s: "mounted"},
291
+		{i: 3, s: "on"},
292
+		{i: 5, s: "with"},
293
+		{i: 6, s: "fstype"},
294
+	}
295
+
296
+	for _, f := range format {
297
+		if ss[f.i] != f.s {
298
+			return nil, fmt.Errorf("invalid device entry: %v", ss)
299
+		}
300
+	}
301
+
302
+	return &Mount{
303
+		Device: ss[1],
304
+		Mount:  ss[4],
305
+		Type:   ss[7],
306
+	}, nil
307
+}
308
+
309
+// parseMountStatsNFS parses a MountStatsNFS by scanning additional information
310
+// related to NFS statistics.
311
+func parseMountStatsNFS(s *bufio.Scanner, statVersion string) (*MountStatsNFS, error) {
312
+	// Field indicators for parsing specific types of data
313
+	const (
314
+		fieldAge        = "age:"
315
+		fieldBytes      = "bytes:"
316
+		fieldEvents     = "events:"
317
+		fieldPerOpStats = "per-op"
318
+		fieldTransport  = "xprt:"
319
+	)
320
+
321
+	stats := &MountStatsNFS{
322
+		StatVersion: statVersion,
323
+	}
324
+
325
+	for s.Scan() {
326
+		ss := strings.Fields(string(s.Bytes()))
327
+		if len(ss) == 0 {
328
+			break
329
+		}
330
+		if len(ss) < 2 {
331
+			return nil, fmt.Errorf("not enough information for NFS stats: %v", ss)
332
+		}
333
+
334
+		switch ss[0] {
335
+		case fieldAge:
336
+			// Age integer is in seconds
337
+			d, err := time.ParseDuration(ss[1] + "s")
338
+			if err != nil {
339
+				return nil, err
340
+			}
341
+
342
+			stats.Age = d
343
+		case fieldBytes:
344
+			bstats, err := parseNFSBytesStats(ss[1:])
345
+			if err != nil {
346
+				return nil, err
347
+			}
348
+
349
+			stats.Bytes = *bstats
350
+		case fieldEvents:
351
+			estats, err := parseNFSEventsStats(ss[1:])
352
+			if err != nil {
353
+				return nil, err
354
+			}
355
+
356
+			stats.Events = *estats
357
+		case fieldTransport:
358
+			if len(ss) < 3 {
359
+				return nil, fmt.Errorf("not enough information for NFS transport stats: %v", ss)
360
+			}
361
+
362
+			tstats, err := parseNFSTransportStats(ss[2:], statVersion)
363
+			if err != nil {
364
+				return nil, err
365
+			}
366
+
367
+			stats.Transport = *tstats
368
+		}
369
+
370
+		// When encountering "per-operation statistics", we must break this
371
+		// loop and parse them separately to ensure we can terminate parsing
372
+		// before reaching another device entry; hence why this 'if' statement
373
+		// is not just another switch case
374
+		if ss[0] == fieldPerOpStats {
375
+			break
376
+		}
377
+	}
378
+
379
+	if err := s.Err(); err != nil {
380
+		return nil, err
381
+	}
382
+
383
+	// NFS per-operation stats appear last before the next device entry
384
+	perOpStats, err := parseNFSOperationStats(s)
385
+	if err != nil {
386
+		return nil, err
387
+	}
388
+
389
+	stats.Operations = perOpStats
390
+
391
+	return stats, nil
392
+}
393
+
394
+// parseNFSBytesStats parses a NFSBytesStats line using an input set of
395
+// integer fields.
396
+func parseNFSBytesStats(ss []string) (*NFSBytesStats, error) {
397
+	if len(ss) != fieldBytesLen {
398
+		return nil, fmt.Errorf("invalid NFS bytes stats: %v", ss)
399
+	}
400
+
401
+	ns := make([]uint64, 0, fieldBytesLen)
402
+	for _, s := range ss {
403
+		n, err := strconv.ParseUint(s, 10, 64)
404
+		if err != nil {
405
+			return nil, err
406
+		}
407
+
408
+		ns = append(ns, n)
409
+	}
410
+
411
+	return &NFSBytesStats{
412
+		Read:        ns[0],
413
+		Write:       ns[1],
414
+		DirectRead:  ns[2],
415
+		DirectWrite: ns[3],
416
+		ReadTotal:   ns[4],
417
+		WriteTotal:  ns[5],
418
+		ReadPages:   ns[6],
419
+		WritePages:  ns[7],
420
+	}, nil
421
+}
422
+
423
+// parseNFSEventsStats parses a NFSEventsStats line using an input set of
424
+// integer fields.
425
+func parseNFSEventsStats(ss []string) (*NFSEventsStats, error) {
426
+	if len(ss) != fieldEventsLen {
427
+		return nil, fmt.Errorf("invalid NFS events stats: %v", ss)
428
+	}
429
+
430
+	ns := make([]uint64, 0, fieldEventsLen)
431
+	for _, s := range ss {
432
+		n, err := strconv.ParseUint(s, 10, 64)
433
+		if err != nil {
434
+			return nil, err
435
+		}
436
+
437
+		ns = append(ns, n)
438
+	}
439
+
440
+	return &NFSEventsStats{
441
+		InodeRevalidate:     ns[0],
442
+		DnodeRevalidate:     ns[1],
443
+		DataInvalidate:      ns[2],
444
+		AttributeInvalidate: ns[3],
445
+		VFSOpen:             ns[4],
446
+		VFSLookup:           ns[5],
447
+		VFSAccess:           ns[6],
448
+		VFSUpdatePage:       ns[7],
449
+		VFSReadPage:         ns[8],
450
+		VFSReadPages:        ns[9],
451
+		VFSWritePage:        ns[10],
452
+		VFSWritePages:       ns[11],
453
+		VFSGetdents:         ns[12],
454
+		VFSSetattr:          ns[13],
455
+		VFSFlush:            ns[14],
456
+		VFSFsync:            ns[15],
457
+		VFSLock:             ns[16],
458
+		VFSFileRelease:      ns[17],
459
+		CongestionWait:      ns[18],
460
+		Truncation:          ns[19],
461
+		WriteExtension:      ns[20],
462
+		SillyRename:         ns[21],
463
+		ShortRead:           ns[22],
464
+		ShortWrite:          ns[23],
465
+		JukeboxDelay:        ns[24],
466
+		PNFSRead:            ns[25],
467
+		PNFSWrite:           ns[26],
468
+	}, nil
469
+}
470
+
471
+// parseNFSOperationStats parses a slice of NFSOperationStats by scanning
472
+// additional information about per-operation statistics until an empty
473
+// line is reached.
474
+func parseNFSOperationStats(s *bufio.Scanner) ([]NFSOperationStats, error) {
475
+	const (
476
+		// Number of expected fields in each per-operation statistics set
477
+		numFields = 9
478
+	)
479
+
480
+	var ops []NFSOperationStats
481
+
482
+	for s.Scan() {
483
+		ss := strings.Fields(string(s.Bytes()))
484
+		if len(ss) == 0 {
485
+			// Must break when reading a blank line after per-operation stats to
486
+			// enable top-level function to parse the next device entry
487
+			break
488
+		}
489
+
490
+		if len(ss) != numFields {
491
+			return nil, fmt.Errorf("invalid NFS per-operations stats: %v", ss)
492
+		}
493
+
494
+		// Skip string operation name for integers
495
+		ns := make([]uint64, 0, numFields-1)
496
+		for _, st := range ss[1:] {
497
+			n, err := strconv.ParseUint(st, 10, 64)
498
+			if err != nil {
499
+				return nil, err
500
+			}
501
+
502
+			ns = append(ns, n)
503
+		}
504
+
505
+		ops = append(ops, NFSOperationStats{
506
+			Operation:                   strings.TrimSuffix(ss[0], ":"),
507
+			Requests:                    ns[0],
508
+			Transmissions:               ns[1],
509
+			MajorTimeouts:               ns[2],
510
+			BytesSent:                   ns[3],
511
+			BytesReceived:               ns[4],
512
+			CumulativeQueueTime:         time.Duration(ns[5]) * time.Millisecond,
513
+			CumulativeTotalResponseTime: time.Duration(ns[6]) * time.Millisecond,
514
+			CumulativeTotalRequestTime:  time.Duration(ns[7]) * time.Millisecond,
515
+		})
516
+	}
517
+
518
+	return ops, s.Err()
519
+}
520
+
521
+// parseNFSTransportStats parses a NFSTransportStats line using an input set of
522
+// integer fields matched to a specific stats version.
523
+func parseNFSTransportStats(ss []string, statVersion string) (*NFSTransportStats, error) {
524
+	switch statVersion {
525
+	case statVersion10:
526
+		if len(ss) != fieldTransport10Len {
527
+			return nil, fmt.Errorf("invalid NFS transport stats 1.0 statement: %v", ss)
528
+		}
529
+	case statVersion11:
530
+		if len(ss) != fieldTransport11Len {
531
+			return nil, fmt.Errorf("invalid NFS transport stats 1.1 statement: %v", ss)
532
+		}
533
+	default:
534
+		return nil, fmt.Errorf("unrecognized NFS transport stats version: %q", statVersion)
535
+	}
536
+
537
+	// Allocate enough for v1.1 stats since zero value for v1.1 stats will be okay
538
+	// in a v1.0 response.
539
+	//
540
+	// Note: slice length must be set to length of v1.1 stats to avoid a panic when
541
+	// only v1.0 stats are present.
542
+	// See: https://github.com/prometheus/node_exporter/issues/571.
543
+	ns := make([]uint64, fieldTransport11Len)
544
+	for i, s := range ss {
545
+		n, err := strconv.ParseUint(s, 10, 64)
546
+		if err != nil {
547
+			return nil, err
548
+		}
549
+
550
+		ns[i] = n
551
+	}
552
+
553
+	return &NFSTransportStats{
554
+		Port:                     ns[0],
555
+		Bind:                     ns[1],
556
+		Connect:                  ns[2],
557
+		ConnectIdleTime:          ns[3],
558
+		IdleTime:                 time.Duration(ns[4]) * time.Second,
559
+		Sends:                    ns[5],
560
+		Receives:                 ns[6],
561
+		BadTransactionIDs:        ns[7],
562
+		CumulativeActiveRequests: ns[8],
563
+		CumulativeBacklog:        ns[9],
564
+		MaximumRPCSlotsUsed:      ns[10],
565
+		CumulativeSendingQueue:   ns[11],
566
+		CumulativePendingQueue:   ns[12],
567
+	}, nil
568
+}
0 569
new file mode 100644
... ...
@@ -0,0 +1,216 @@
0
+// Copyright 2018 The Prometheus Authors
1
+// Licensed under the Apache License, Version 2.0 (the "License");
2
+// you may not use this file except in compliance with the License.
3
+// You may obtain a copy of the License at
4
+//
5
+// http://www.apache.org/licenses/LICENSE-2.0
6
+//
7
+// Unless required by applicable law or agreed to in writing, software
8
+// distributed under the License is distributed on an "AS IS" BASIS,
9
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+// See the License for the specific language governing permissions and
11
+// limitations under the License.
12
+
13
+package procfs
14
+
15
+import (
16
+	"bufio"
17
+	"errors"
18
+	"os"
19
+	"sort"
20
+	"strconv"
21
+	"strings"
22
+)
23
+
24
+// NetDevLine is single line parsed from /proc/net/dev or /proc/[pid]/net/dev.
25
+type NetDevLine struct {
26
+	Name         string `json:"name"`          // The name of the interface.
27
+	RxBytes      uint64 `json:"rx_bytes"`      // Cumulative count of bytes received.
28
+	RxPackets    uint64 `json:"rx_packets"`    // Cumulative count of packets received.
29
+	RxErrors     uint64 `json:"rx_errors"`     // Cumulative count of receive errors encountered.
30
+	RxDropped    uint64 `json:"rx_dropped"`    // Cumulative count of packets dropped while receiving.
31
+	RxFIFO       uint64 `json:"rx_fifo"`       // Cumulative count of FIFO buffer errors.
32
+	RxFrame      uint64 `json:"rx_frame"`      // Cumulative count of packet framing errors.
33
+	RxCompressed uint64 `json:"rx_compressed"` // Cumulative count of compressed packets received by the device driver.
34
+	RxMulticast  uint64 `json:"rx_multicast"`  // Cumulative count of multicast frames received by the device driver.
35
+	TxBytes      uint64 `json:"tx_bytes"`      // Cumulative count of bytes transmitted.
36
+	TxPackets    uint64 `json:"tx_packets"`    // Cumulative count of packets transmitted.
37
+	TxErrors     uint64 `json:"tx_errors"`     // Cumulative count of transmit errors encountered.
38
+	TxDropped    uint64 `json:"tx_dropped"`    // Cumulative count of packets dropped while transmitting.
39
+	TxFIFO       uint64 `json:"tx_fifo"`       // Cumulative count of FIFO buffer errors.
40
+	TxCollisions uint64 `json:"tx_collisions"` // Cumulative count of collisions detected on the interface.
41
+	TxCarrier    uint64 `json:"tx_carrier"`    // Cumulative count of carrier losses detected by the device driver.
42
+	TxCompressed uint64 `json:"tx_compressed"` // Cumulative count of compressed packets transmitted by the device driver.
43
+}
44
+
45
+// NetDev is parsed from /proc/net/dev or /proc/[pid]/net/dev. The map keys
46
+// are interface names.
47
+type NetDev map[string]NetDevLine
48
+
49
+// NewNetDev returns kernel/system statistics read from /proc/net/dev.
50
+func NewNetDev() (NetDev, error) {
51
+	fs, err := NewFS(DefaultMountPoint)
52
+	if err != nil {
53
+		return nil, err
54
+	}
55
+
56
+	return fs.NewNetDev()
57
+}
58
+
59
+// NewNetDev returns kernel/system statistics read from /proc/net/dev.
60
+func (fs FS) NewNetDev() (NetDev, error) {
61
+	return newNetDev(fs.Path("net/dev"))
62
+}
63
+
64
+// NewNetDev returns kernel/system statistics read from /proc/[pid]/net/dev.
65
+func (p Proc) NewNetDev() (NetDev, error) {
66
+	return newNetDev(p.path("net/dev"))
67
+}
68
+
69
+// newNetDev creates a new NetDev from the contents of the given file.
70
+func newNetDev(file string) (NetDev, error) {
71
+	f, err := os.Open(file)
72
+	if err != nil {
73
+		return NetDev{}, err
74
+	}
75
+	defer f.Close()
76
+
77
+	nd := NetDev{}
78
+	s := bufio.NewScanner(f)
79
+	for n := 0; s.Scan(); n++ {
80
+		// Skip the 2 header lines.
81
+		if n < 2 {
82
+			continue
83
+		}
84
+
85
+		line, err := nd.parseLine(s.Text())
86
+		if err != nil {
87
+			return nd, err
88
+		}
89
+
90
+		nd[line.Name] = *line
91
+	}
92
+
93
+	return nd, s.Err()
94
+}
95
+
96
+// parseLine parses a single line from the /proc/net/dev file. Header lines
97
+// must be filtered prior to calling this method.
98
+func (nd NetDev) parseLine(rawLine string) (*NetDevLine, error) {
99
+	parts := strings.SplitN(rawLine, ":", 2)
100
+	if len(parts) != 2 {
101
+		return nil, errors.New("invalid net/dev line, missing colon")
102
+	}
103
+	fields := strings.Fields(strings.TrimSpace(parts[1]))
104
+
105
+	var err error
106
+	line := &NetDevLine{}
107
+
108
+	// Interface Name
109
+	line.Name = strings.TrimSpace(parts[0])
110
+	if line.Name == "" {
111
+		return nil, errors.New("invalid net/dev line, empty interface name")
112
+	}
113
+
114
+	// RX
115
+	line.RxBytes, err = strconv.ParseUint(fields[0], 10, 64)
116
+	if err != nil {
117
+		return nil, err
118
+	}
119
+	line.RxPackets, err = strconv.ParseUint(fields[1], 10, 64)
120
+	if err != nil {
121
+		return nil, err
122
+	}
123
+	line.RxErrors, err = strconv.ParseUint(fields[2], 10, 64)
124
+	if err != nil {
125
+		return nil, err
126
+	}
127
+	line.RxDropped, err = strconv.ParseUint(fields[3], 10, 64)
128
+	if err != nil {
129
+		return nil, err
130
+	}
131
+	line.RxFIFO, err = strconv.ParseUint(fields[4], 10, 64)
132
+	if err != nil {
133
+		return nil, err
134
+	}
135
+	line.RxFrame, err = strconv.ParseUint(fields[5], 10, 64)
136
+	if err != nil {
137
+		return nil, err
138
+	}
139
+	line.RxCompressed, err = strconv.ParseUint(fields[6], 10, 64)
140
+	if err != nil {
141
+		return nil, err
142
+	}
143
+	line.RxMulticast, err = strconv.ParseUint(fields[7], 10, 64)
144
+	if err != nil {
145
+		return nil, err
146
+	}
147
+
148
+	// TX
149
+	line.TxBytes, err = strconv.ParseUint(fields[8], 10, 64)
150
+	if err != nil {
151
+		return nil, err
152
+	}
153
+	line.TxPackets, err = strconv.ParseUint(fields[9], 10, 64)
154
+	if err != nil {
155
+		return nil, err
156
+	}
157
+	line.TxErrors, err = strconv.ParseUint(fields[10], 10, 64)
158
+	if err != nil {
159
+		return nil, err
160
+	}
161
+	line.TxDropped, err = strconv.ParseUint(fields[11], 10, 64)
162
+	if err != nil {
163
+		return nil, err
164
+	}
165
+	line.TxFIFO, err = strconv.ParseUint(fields[12], 10, 64)
166
+	if err != nil {
167
+		return nil, err
168
+	}
169
+	line.TxCollisions, err = strconv.ParseUint(fields[13], 10, 64)
170
+	if err != nil {
171
+		return nil, err
172
+	}
173
+	line.TxCarrier, err = strconv.ParseUint(fields[14], 10, 64)
174
+	if err != nil {
175
+		return nil, err
176
+	}
177
+	line.TxCompressed, err = strconv.ParseUint(fields[15], 10, 64)
178
+	if err != nil {
179
+		return nil, err
180
+	}
181
+
182
+	return line, nil
183
+}
184
+
185
+// Total aggregates the values across interfaces and returns a new NetDevLine.
186
+// The Name field will be a sorted comma separated list of interface names.
187
+func (nd NetDev) Total() NetDevLine {
188
+	total := NetDevLine{}
189
+
190
+	names := make([]string, 0, len(nd))
191
+	for _, ifc := range nd {
192
+		names = append(names, ifc.Name)
193
+		total.RxBytes += ifc.RxBytes
194
+		total.RxPackets += ifc.RxPackets
195
+		total.RxPackets += ifc.RxPackets
196
+		total.RxErrors += ifc.RxErrors
197
+		total.RxDropped += ifc.RxDropped
198
+		total.RxFIFO += ifc.RxFIFO
199
+		total.RxFrame += ifc.RxFrame
200
+		total.RxCompressed += ifc.RxCompressed
201
+		total.RxMulticast += ifc.RxMulticast
202
+		total.TxBytes += ifc.TxBytes
203
+		total.TxPackets += ifc.TxPackets
204
+		total.TxErrors += ifc.TxErrors
205
+		total.TxDropped += ifc.TxDropped
206
+		total.TxFIFO += ifc.TxFIFO
207
+		total.TxCollisions += ifc.TxCollisions
208
+		total.TxCarrier += ifc.TxCarrier
209
+		total.TxCompressed += ifc.TxCompressed
210
+	}
211
+	sort.Strings(names)
212
+	total.Name = strings.Join(names, ", ")
213
+
214
+	return total
215
+}
0 216
new file mode 100644
... ...
@@ -0,0 +1,263 @@
0
+// Copyright 2018 The Prometheus Authors
1
+// Licensed under the Apache License, Version 2.0 (the "License");
2
+// you may not use this file except in compliance with the License.
3
+// You may obtain a copy of the License at
4
+//
5
+// http://www.apache.org/licenses/LICENSE-2.0
6
+//
7
+// Unless required by applicable law or agreed to in writing, software
8
+// distributed under the License is distributed on an "AS IS" BASIS,
9
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+// See the License for the specific language governing permissions and
11
+// limitations under the License.
12
+
13
+// Package nfs implements parsing of /proc/net/rpc/nfsd.
14
+// Fields are documented in https://www.svennd.be/nfsd-stats-explained-procnetrpcnfsd/
15
+package nfs
16
+
17
+// ReplyCache models the "rc" line.
18
+type ReplyCache struct {
19
+	Hits    uint64
20
+	Misses  uint64
21
+	NoCache uint64
22
+}
23
+
24
+// FileHandles models the "fh" line.
25
+type FileHandles struct {
26
+	Stale        uint64
27
+	TotalLookups uint64
28
+	AnonLookups  uint64
29
+	DirNoCache   uint64
30
+	NoDirNoCache uint64
31
+}
32
+
33
+// InputOutput models the "io" line.
34
+type InputOutput struct {
35
+	Read  uint64
36
+	Write uint64
37
+}
38
+
39
+// Threads models the "th" line.
40
+type Threads struct {
41
+	Threads uint64
42
+	FullCnt uint64
43
+}
44
+
45
+// ReadAheadCache models the "ra" line.
46
+type ReadAheadCache struct {
47
+	CacheSize      uint64
48
+	CacheHistogram []uint64
49
+	NotFound       uint64
50
+}
51
+
52
+// Network models the "net" line.
53
+type Network struct {
54
+	NetCount   uint64
55
+	UDPCount   uint64
56
+	TCPCount   uint64
57
+	TCPConnect uint64
58
+}
59
+
60
+// ClientRPC models the nfs "rpc" line.
61
+type ClientRPC struct {
62
+	RPCCount        uint64
63
+	Retransmissions uint64
64
+	AuthRefreshes   uint64
65
+}
66
+
67
+// ServerRPC models the nfsd "rpc" line.
68
+type ServerRPC struct {
69
+	RPCCount uint64
70
+	BadCnt   uint64
71
+	BadFmt   uint64
72
+	BadAuth  uint64
73
+	BadcInt  uint64
74
+}
75
+
76
+// V2Stats models the "proc2" line.
77
+type V2Stats struct {
78
+	Null     uint64
79
+	GetAttr  uint64
80
+	SetAttr  uint64
81
+	Root     uint64
82
+	Lookup   uint64
83
+	ReadLink uint64
84
+	Read     uint64
85
+	WrCache  uint64
86
+	Write    uint64
87
+	Create   uint64
88
+	Remove   uint64
89
+	Rename   uint64
90
+	Link     uint64
91
+	SymLink  uint64
92
+	MkDir    uint64
93
+	RmDir    uint64
94
+	ReadDir  uint64
95
+	FsStat   uint64
96
+}
97
+
98
+// V3Stats models the "proc3" line.
99
+type V3Stats struct {
100
+	Null        uint64
101
+	GetAttr     uint64
102
+	SetAttr     uint64
103
+	Lookup      uint64
104
+	Access      uint64
105
+	ReadLink    uint64
106
+	Read        uint64
107
+	Write       uint64
108
+	Create      uint64
109
+	MkDir       uint64
110
+	SymLink     uint64
111
+	MkNod       uint64
112
+	Remove      uint64
113
+	RmDir       uint64
114
+	Rename      uint64
115
+	Link        uint64
116
+	ReadDir     uint64
117
+	ReadDirPlus uint64
118
+	FsStat      uint64
119
+	FsInfo      uint64
120
+	PathConf    uint64
121
+	Commit      uint64
122
+}
123
+
124
+// ClientV4Stats models the nfs "proc4" line.
125
+type ClientV4Stats struct {
126
+	Null               uint64
127
+	Read               uint64
128
+	Write              uint64
129
+	Commit             uint64
130
+	Open               uint64
131
+	OpenConfirm        uint64
132
+	OpenNoattr         uint64
133
+	OpenDowngrade      uint64
134
+	Close              uint64
135
+	Setattr            uint64
136
+	FsInfo             uint64
137
+	Renew              uint64
138
+	SetClientID        uint64
139
+	SetClientIDConfirm uint64
140
+	Lock               uint64
141
+	Lockt              uint64
142
+	Locku              uint64
143
+	Access             uint64
144
+	Getattr            uint64
145
+	Lookup             uint64
146
+	LookupRoot         uint64
147
+	Remove             uint64
148
+	Rename             uint64
149
+	Link               uint64
150
+	Symlink            uint64
151
+	Create             uint64
152
+	Pathconf           uint64
153
+	StatFs             uint64
154
+	ReadLink           uint64
155
+	ReadDir            uint64
156
+	ServerCaps         uint64
157
+	DelegReturn        uint64
158
+	GetACL             uint64
159
+	SetACL             uint64
160
+	FsLocations        uint64
161
+	ReleaseLockowner   uint64
162
+	Secinfo            uint64
163
+	FsidPresent        uint64
164
+	ExchangeID         uint64
165
+	CreateSession      uint64
166
+	DestroySession     uint64
167
+	Sequence           uint64
168
+	GetLeaseTime       uint64
169
+	ReclaimComplete    uint64
170
+	LayoutGet          uint64
171
+	GetDeviceInfo      uint64
172
+	LayoutCommit       uint64
173
+	LayoutReturn       uint64
174
+	SecinfoNoName      uint64
175
+	TestStateID        uint64
176
+	FreeStateID        uint64
177
+	GetDeviceList      uint64
178
+	BindConnToSession  uint64
179
+	DestroyClientID    uint64
180
+	Seek               uint64
181
+	Allocate           uint64
182
+	DeAllocate         uint64
183
+	LayoutStats        uint64
184
+	Clone              uint64
185
+}
186
+
187
+// ServerV4Stats models the nfsd "proc4" line.
188
+type ServerV4Stats struct {
189
+	Null     uint64
190
+	Compound uint64
191
+}
192
+
193
+// V4Ops models the "proc4ops" line: NFSv4 operations
194
+// Variable list, see:
195
+// v4.0 https://tools.ietf.org/html/rfc3010 (38 operations)
196
+// v4.1 https://tools.ietf.org/html/rfc5661 (58 operations)
197
+// v4.2 https://tools.ietf.org/html/draft-ietf-nfsv4-minorversion2-41 (71 operations)
198
+type V4Ops struct {
199
+	//Values       uint64 // Variable depending on v4.x sub-version. TODO: Will this always at least include the fields in this struct?
200
+	Op0Unused    uint64
201
+	Op1Unused    uint64
202
+	Op2Future    uint64
203
+	Access       uint64
204
+	Close        uint64
205
+	Commit       uint64
206
+	Create       uint64
207
+	DelegPurge   uint64
208
+	DelegReturn  uint64
209
+	GetAttr      uint64
210
+	GetFH        uint64
211
+	Link         uint64
212
+	Lock         uint64
213
+	Lockt        uint64
214
+	Locku        uint64
215
+	Lookup       uint64
216
+	LookupRoot   uint64
217
+	Nverify      uint64
218
+	Open         uint64
219
+	OpenAttr     uint64
220
+	OpenConfirm  uint64
221
+	OpenDgrd     uint64
222
+	PutFH        uint64
223
+	PutPubFH     uint64
224
+	PutRootFH    uint64
225
+	Read         uint64
226
+	ReadDir      uint64
227
+	ReadLink     uint64
228
+	Remove       uint64
229
+	Rename       uint64
230
+	Renew        uint64
231
+	RestoreFH    uint64
232
+	SaveFH       uint64
233
+	SecInfo      uint64
234
+	SetAttr      uint64
235
+	Verify       uint64
236
+	Write        uint64
237
+	RelLockOwner uint64
238
+}
239
+
240
+// ClientRPCStats models all stats from /proc/net/rpc/nfs.
241
+type ClientRPCStats struct {
242
+	Network       Network
243
+	ClientRPC     ClientRPC
244
+	V2Stats       V2Stats
245
+	V3Stats       V3Stats
246
+	ClientV4Stats ClientV4Stats
247
+}
248
+
249
+// ServerRPCStats models all stats from /proc/net/rpc/nfsd.
250
+type ServerRPCStats struct {
251
+	ReplyCache     ReplyCache
252
+	FileHandles    FileHandles
253
+	InputOutput    InputOutput
254
+	Threads        Threads
255
+	ReadAheadCache ReadAheadCache
256
+	Network        Network
257
+	ServerRPC      ServerRPC
258
+	V2Stats        V2Stats
259
+	V3Stats        V3Stats
260
+	ServerV4Stats  ServerV4Stats
261
+	V4Ops          V4Ops
262
+}
0 263
new file mode 100644
... ...
@@ -0,0 +1,317 @@
0
+// Copyright 2018 The Prometheus Authors
1
+// Licensed under the Apache License, Version 2.0 (the "License");
2
+// you may not use this file except in compliance with the License.
3
+// You may obtain a copy of the License at
4
+//
5
+// http://www.apache.org/licenses/LICENSE-2.0
6
+//
7
+// Unless required by applicable law or agreed to in writing, software
8
+// distributed under the License is distributed on an "AS IS" BASIS,
9
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+// See the License for the specific language governing permissions and
11
+// limitations under the License.
12
+
13
+package nfs
14
+
15
+import (
16
+	"fmt"
17
+)
18
+
19
+func parseReplyCache(v []uint64) (ReplyCache, error) {
20
+	if len(v) != 3 {
21
+		return ReplyCache{}, fmt.Errorf("invalid ReplyCache line %q", v)
22
+	}
23
+
24
+	return ReplyCache{
25
+		Hits:    v[0],
26
+		Misses:  v[1],
27
+		NoCache: v[2],
28
+	}, nil
29
+}
30
+
31
+func parseFileHandles(v []uint64) (FileHandles, error) {
32
+	if len(v) != 5 {
33
+		return FileHandles{}, fmt.Errorf("invalid FileHandles, line %q", v)
34
+	}
35
+
36
+	return FileHandles{
37
+		Stale:        v[0],
38
+		TotalLookups: v[1],
39
+		AnonLookups:  v[2],
40
+		DirNoCache:   v[3],
41
+		NoDirNoCache: v[4],
42
+	}, nil
43
+}
44
+
45
+func parseInputOutput(v []uint64) (InputOutput, error) {
46
+	if len(v) != 2 {
47
+		return InputOutput{}, fmt.Errorf("invalid InputOutput line %q", v)
48
+	}
49
+
50
+	return InputOutput{
51
+		Read:  v[0],
52
+		Write: v[1],
53
+	}, nil
54
+}
55
+
56
+func parseThreads(v []uint64) (Threads, error) {
57
+	if len(v) != 2 {
58
+		return Threads{}, fmt.Errorf("invalid Threads line %q", v)
59
+	}
60
+
61
+	return Threads{
62
+		Threads: v[0],
63
+		FullCnt: v[1],
64
+	}, nil
65
+}
66
+
67
+func parseReadAheadCache(v []uint64) (ReadAheadCache, error) {
68
+	if len(v) != 12 {
69
+		return ReadAheadCache{}, fmt.Errorf("invalid ReadAheadCache line %q", v)
70
+	}
71
+
72
+	return ReadAheadCache{
73
+		CacheSize:      v[0],
74
+		CacheHistogram: v[1:11],
75
+		NotFound:       v[11],
76
+	}, nil
77
+}
78
+
79
+func parseNetwork(v []uint64) (Network, error) {
80
+	if len(v) != 4 {
81
+		return Network{}, fmt.Errorf("invalid Network line %q", v)
82
+	}
83
+
84
+	return Network{
85
+		NetCount:   v[0],
86
+		UDPCount:   v[1],
87
+		TCPCount:   v[2],
88
+		TCPConnect: v[3],
89
+	}, nil
90
+}
91
+
92
+func parseServerRPC(v []uint64) (ServerRPC, error) {
93
+	if len(v) != 5 {
94
+		return ServerRPC{}, fmt.Errorf("invalid RPC line %q", v)
95
+	}
96
+
97
+	return ServerRPC{
98
+		RPCCount: v[0],
99
+		BadCnt:   v[1],
100
+		BadFmt:   v[2],
101
+		BadAuth:  v[3],
102
+		BadcInt:  v[4],
103
+	}, nil
104
+}
105
+
106
+func parseClientRPC(v []uint64) (ClientRPC, error) {
107
+	if len(v) != 3 {
108
+		return ClientRPC{}, fmt.Errorf("invalid RPC line %q", v)
109
+	}
110
+
111
+	return ClientRPC{
112
+		RPCCount:        v[0],
113
+		Retransmissions: v[1],
114
+		AuthRefreshes:   v[2],
115
+	}, nil
116
+}
117
+
118
+func parseV2Stats(v []uint64) (V2Stats, error) {
119
+	values := int(v[0])
120
+	if len(v[1:]) != values || values != 18 {
121
+		return V2Stats{}, fmt.Errorf("invalid V2Stats line %q", v)
122
+	}
123
+
124
+	return V2Stats{
125
+		Null:     v[1],
126
+		GetAttr:  v[2],
127
+		SetAttr:  v[3],
128
+		Root:     v[4],
129
+		Lookup:   v[5],
130
+		ReadLink: v[6],
131
+		Read:     v[7],
132
+		WrCache:  v[8],
133
+		Write:    v[9],
134
+		Create:   v[10],
135
+		Remove:   v[11],
136
+		Rename:   v[12],
137
+		Link:     v[13],
138
+		SymLink:  v[14],
139
+		MkDir:    v[15],
140
+		RmDir:    v[16],
141
+		ReadDir:  v[17],
142
+		FsStat:   v[18],
143
+	}, nil
144
+}
145
+
146
+func parseV3Stats(v []uint64) (V3Stats, error) {
147
+	values := int(v[0])
148
+	if len(v[1:]) != values || values != 22 {
149
+		return V3Stats{}, fmt.Errorf("invalid V3Stats line %q", v)
150
+	}
151
+
152
+	return V3Stats{
153
+		Null:        v[1],
154
+		GetAttr:     v[2],
155
+		SetAttr:     v[3],
156
+		Lookup:      v[4],
157
+		Access:      v[5],
158
+		ReadLink:    v[6],
159
+		Read:        v[7],
160
+		Write:       v[8],
161
+		Create:      v[9],
162
+		MkDir:       v[10],
163
+		SymLink:     v[11],
164
+		MkNod:       v[12],
165
+		Remove:      v[13],
166
+		RmDir:       v[14],
167
+		Rename:      v[15],
168
+		Link:        v[16],
169
+		ReadDir:     v[17],
170
+		ReadDirPlus: v[18],
171
+		FsStat:      v[19],
172
+		FsInfo:      v[20],
173
+		PathConf:    v[21],
174
+		Commit:      v[22],
175
+	}, nil
176
+}
177
+
178
+func parseClientV4Stats(v []uint64) (ClientV4Stats, error) {
179
+	values := int(v[0])
180
+	if len(v[1:]) != values {
181
+		return ClientV4Stats{}, fmt.Errorf("invalid ClientV4Stats line %q", v)
182
+	}
183
+
184
+	// This function currently supports mapping 59 NFS v4 client stats.  Older
185
+	// kernels may emit fewer stats, so we must detect this and pad out the
186
+	// values to match the expected slice size.
187
+	if values < 59 {
188
+		newValues := make([]uint64, 60)
189
+		copy(newValues, v)
190
+		v = newValues
191
+	}
192
+
193
+	return ClientV4Stats{
194
+		Null:               v[1],
195
+		Read:               v[2],
196
+		Write:              v[3],
197
+		Commit:             v[4],
198
+		Open:               v[5],
199
+		OpenConfirm:        v[6],
200
+		OpenNoattr:         v[7],
201
+		OpenDowngrade:      v[8],
202
+		Close:              v[9],
203
+		Setattr:            v[10],
204
+		FsInfo:             v[11],
205
+		Renew:              v[12],
206
+		SetClientID:        v[13],
207
+		SetClientIDConfirm: v[14],
208
+		Lock:               v[15],
209
+		Lockt:              v[16],
210
+		Locku:              v[17],
211
+		Access:             v[18],
212
+		Getattr:            v[19],
213
+		Lookup:             v[20],
214
+		LookupRoot:         v[21],
215
+		Remove:             v[22],
216
+		Rename:             v[23],
217
+		Link:               v[24],
218
+		Symlink:            v[25],
219
+		Create:             v[26],
220
+		Pathconf:           v[27],
221
+		StatFs:             v[28],
222
+		ReadLink:           v[29],
223
+		ReadDir:            v[30],
224
+		ServerCaps:         v[31],
225
+		DelegReturn:        v[32],
226
+		GetACL:             v[33],
227
+		SetACL:             v[34],
228
+		FsLocations:        v[35],
229
+		ReleaseLockowner:   v[36],
230
+		Secinfo:            v[37],
231
+		FsidPresent:        v[38],
232
+		ExchangeID:         v[39],
233
+		CreateSession:      v[40],
234
+		DestroySession:     v[41],
235
+		Sequence:           v[42],
236
+		GetLeaseTime:       v[43],
237
+		ReclaimComplete:    v[44],
238
+		LayoutGet:          v[45],
239
+		GetDeviceInfo:      v[46],
240
+		LayoutCommit:       v[47],
241
+		LayoutReturn:       v[48],
242
+		SecinfoNoName:      v[49],
243
+		TestStateID:        v[50],
244
+		FreeStateID:        v[51],
245
+		GetDeviceList:      v[52],
246
+		BindConnToSession:  v[53],
247
+		DestroyClientID:    v[54],
248
+		Seek:               v[55],
249
+		Allocate:           v[56],
250
+		DeAllocate:         v[57],
251
+		LayoutStats:        v[58],
252
+		Clone:              v[59],
253
+	}, nil
254
+}
255
+
256
+func parseServerV4Stats(v []uint64) (ServerV4Stats, error) {
257
+	values := int(v[0])
258
+	if len(v[1:]) != values || values != 2 {
259
+		return ServerV4Stats{}, fmt.Errorf("invalid V4Stats line %q", v)
260
+	}
261
+
262
+	return ServerV4Stats{
263
+		Null:     v[1],
264
+		Compound: v[2],
265
+	}, nil
266
+}
267
+
268
+func parseV4Ops(v []uint64) (V4Ops, error) {
269
+	values := int(v[0])
270
+	if len(v[1:]) != values || values < 39 {
271
+		return V4Ops{}, fmt.Errorf("invalid V4Ops line %q", v)
272
+	}
273
+
274
+	stats := V4Ops{
275
+		Op0Unused:    v[1],
276
+		Op1Unused:    v[2],
277
+		Op2Future:    v[3],
278
+		Access:       v[4],
279
+		Close:        v[5],
280
+		Commit:       v[6],
281
+		Create:       v[7],
282
+		DelegPurge:   v[8],
283
+		DelegReturn:  v[9],
284
+		GetAttr:      v[10],
285
+		GetFH:        v[11],
286
+		Link:         v[12],
287
+		Lock:         v[13],
288
+		Lockt:        v[14],
289
+		Locku:        v[15],
290
+		Lookup:       v[16],
291
+		LookupRoot:   v[17],
292
+		Nverify:      v[18],
293
+		Open:         v[19],
294
+		OpenAttr:     v[20],
295
+		OpenConfirm:  v[21],
296
+		OpenDgrd:     v[22],
297
+		PutFH:        v[23],
298
+		PutPubFH:     v[24],
299
+		PutRootFH:    v[25],
300
+		Read:         v[26],
301
+		ReadDir:      v[27],
302
+		ReadLink:     v[28],
303
+		Remove:       v[29],
304
+		Rename:       v[30],
305
+		Renew:        v[31],
306
+		RestoreFH:    v[32],
307
+		SaveFH:       v[33],
308
+		SecInfo:      v[34],
309
+		SetAttr:      v[35],
310
+		Verify:       v[36],
311
+		Write:        v[37],
312
+		RelLockOwner: v[38],
313
+	}
314
+
315
+	return stats, nil
316
+}
0 317
new file mode 100644
... ...
@@ -0,0 +1,67 @@
0
+// Copyright 2018 The Prometheus Authors
1
+// Licensed under the Apache License, Version 2.0 (the "License");
2
+// you may not use this file except in compliance with the License.
3
+// You may obtain a copy of the License at
4
+//
5
+// http://www.apache.org/licenses/LICENSE-2.0
6
+//
7
+// Unless required by applicable law or agreed to in writing, software
8
+// distributed under the License is distributed on an "AS IS" BASIS,
9
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+// See the License for the specific language governing permissions and
11
+// limitations under the License.
12
+
13
+package nfs
14
+
15
+import (
16
+	"bufio"
17
+	"fmt"
18
+	"io"
19
+	"strings"
20
+
21
+	"github.com/prometheus/procfs/internal/util"
22
+)
23
+
24
+// ParseClientRPCStats returns stats read from /proc/net/rpc/nfs
25
+func ParseClientRPCStats(r io.Reader) (*ClientRPCStats, error) {
26
+	stats := &ClientRPCStats{}
27
+
28
+	scanner := bufio.NewScanner(r)
29
+	for scanner.Scan() {
30
+		line := scanner.Text()
31
+		parts := strings.Fields(scanner.Text())
32
+		// require at least <key> <value>
33
+		if len(parts) < 2 {
34
+			return nil, fmt.Errorf("invalid NFS metric line %q", line)
35
+		}
36
+
37
+		values, err := util.ParseUint64s(parts[1:])
38
+		if err != nil {
39
+			return nil, fmt.Errorf("error parsing NFS metric line: %s", err)
40
+		}
41
+
42
+		switch metricLine := parts[0]; metricLine {
43
+		case "net":
44
+			stats.Network, err = parseNetwork(values)
45
+		case "rpc":
46
+			stats.ClientRPC, err = parseClientRPC(values)
47
+		case "proc2":
48
+			stats.V2Stats, err = parseV2Stats(values)
49
+		case "proc3":
50
+			stats.V3Stats, err = parseV3Stats(values)
51
+		case "proc4":
52
+			stats.ClientV4Stats, err = parseClientV4Stats(values)
53
+		default:
54
+			return nil, fmt.Errorf("unknown NFS metric line %q", metricLine)
55
+		}
56
+		if err != nil {
57
+			return nil, fmt.Errorf("errors parsing NFS metric line: %s", err)
58
+		}
59
+	}
60
+
61
+	if err := scanner.Err(); err != nil {
62
+		return nil, fmt.Errorf("error scanning NFS file: %s", err)
63
+	}
64
+
65
+	return stats, nil
66
+}
0 67
new file mode 100644
... ...
@@ -0,0 +1,89 @@
0
+// Copyright 2018 The Prometheus Authors
1
+// Licensed under the Apache License, Version 2.0 (the "License");
2
+// you may not use this file except in compliance with the License.
3
+// You may obtain a copy of the License at
4
+//
5
+// http://www.apache.org/licenses/LICENSE-2.0
6
+//
7
+// Unless required by applicable law or agreed to in writing, software
8
+// distributed under the License is distributed on an "AS IS" BASIS,
9
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+// See the License for the specific language governing permissions and
11
+// limitations under the License.
12
+
13
+package nfs
14
+
15
+import (
16
+	"bufio"
17
+	"fmt"
18
+	"io"
19
+	"strings"
20
+
21
+	"github.com/prometheus/procfs/internal/util"
22
+)
23
+
24
+// ParseServerRPCStats returns stats read from /proc/net/rpc/nfsd
25
+func ParseServerRPCStats(r io.Reader) (*ServerRPCStats, error) {
26
+	stats := &ServerRPCStats{}
27
+
28
+	scanner := bufio.NewScanner(r)
29
+	for scanner.Scan() {
30
+		line := scanner.Text()
31
+		parts := strings.Fields(scanner.Text())
32
+		// require at least <key> <value>
33
+		if len(parts) < 2 {
34
+			return nil, fmt.Errorf("invalid NFSd metric line %q", line)
35
+		}
36
+		label := parts[0]
37
+
38
+		var values []uint64
39
+		var err error
40
+		if label == "th" {
41
+			if len(parts) < 3 {
42
+				return nil, fmt.Errorf("invalid NFSd th metric line %q", line)
43
+			}
44
+			values, err = util.ParseUint64s(parts[1:3])
45
+		} else {
46
+			values, err = util.ParseUint64s(parts[1:])
47
+		}
48
+		if err != nil {
49
+			return nil, fmt.Errorf("error parsing NFSd metric line: %s", err)
50
+		}
51
+
52
+		switch metricLine := parts[0]; metricLine {
53
+		case "rc":
54
+			stats.ReplyCache, err = parseReplyCache(values)
55
+		case "fh":
56
+			stats.FileHandles, err = parseFileHandles(values)
57
+		case "io":
58
+			stats.InputOutput, err = parseInputOutput(values)
59
+		case "th":
60
+			stats.Threads, err = parseThreads(values)
61
+		case "ra":
62
+			stats.ReadAheadCache, err = parseReadAheadCache(values)
63
+		case "net":
64
+			stats.Network, err = parseNetwork(values)
65
+		case "rpc":
66
+			stats.ServerRPC, err = parseServerRPC(values)
67
+		case "proc2":
68
+			stats.V2Stats, err = parseV2Stats(values)
69
+		case "proc3":
70
+			stats.V3Stats, err = parseV3Stats(values)
71
+		case "proc4":
72
+			stats.ServerV4Stats, err = parseServerV4Stats(values)
73
+		case "proc4ops":
74
+			stats.V4Ops, err = parseV4Ops(values)
75
+		default:
76
+			return nil, fmt.Errorf("unknown NFSd metric line %q", metricLine)
77
+		}
78
+		if err != nil {
79
+			return nil, fmt.Errorf("errors parsing NFSd metric line: %s", err)
80
+		}
81
+	}
82
+
83
+	if err := scanner.Err(); err != nil {
84
+		return nil, fmt.Errorf("error scanning NFSd file: %s", err)
85
+	}
86
+
87
+	return stats, nil
88
+}
... ...
@@ -1,6 +1,20 @@
1
+// Copyright 2018 The Prometheus Authors
2
+// Licensed under the Apache License, Version 2.0 (the "License");
3
+// you may not use this file except in compliance with the License.
4
+// You may obtain a copy of the License at
5
+//
6
+// http://www.apache.org/licenses/LICENSE-2.0
7
+//
8
+// Unless required by applicable law or agreed to in writing, software
9
+// distributed under the License is distributed on an "AS IS" BASIS,
10
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+// See the License for the specific language governing permissions and
12
+// limitations under the License.
13
+
1 14
 package procfs
2 15
 
3 16
 import (
17
+	"bytes"
4 18
 	"fmt"
5 19
 	"io/ioutil"
6 20
 	"os"
... ...
@@ -113,7 +127,7 @@ func (p Proc) CmdLine() ([]string, error) {
113 113
 		return []string{}, nil
114 114
 	}
115 115
 
116
-	return strings.Split(string(data[:len(data)-1]), string(byte(0))), nil
116
+	return strings.Split(string(bytes.TrimRight(data, string("\x00"))), string(byte(0))), nil
117 117
 }
118 118
 
119 119
 // Comm returns the command name of a process.
... ...
@@ -192,6 +206,18 @@ func (p Proc) FileDescriptorsLen() (int, error) {
192 192
 	return len(fds), nil
193 193
 }
194 194
 
195
+// MountStats retrieves statistics and configuration for mount points in a
196
+// process's namespace.
197
+func (p Proc) MountStats() ([]*Mount, error) {
198
+	f, err := os.Open(p.path("mountstats"))
199
+	if err != nil {
200
+		return nil, err
201
+	}
202
+	defer f.Close()
203
+
204
+	return parseMountStats(f)
205
+}
206
+
195 207
 func (p Proc) fileDescriptors() ([]string, error) {
196 208
 	d, err := os.Open(p.path("fd"))
197 209
 	if err != nil {
... ...
@@ -1,3 +1,16 @@
1
+// Copyright 2018 The Prometheus Authors
2
+// Licensed under the Apache License, Version 2.0 (the "License");
3
+// you may not use this file except in compliance with the License.
4
+// You may obtain a copy of the License at
5
+//
6
+// http://www.apache.org/licenses/LICENSE-2.0
7
+//
8
+// Unless required by applicable law or agreed to in writing, software
9
+// distributed under the License is distributed on an "AS IS" BASIS,
10
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+// See the License for the specific language governing permissions and
12
+// limitations under the License.
13
+
1 14
 package procfs
2 15
 
3 16
 import (
... ...
@@ -47,9 +60,6 @@ func (p Proc) NewIO() (ProcIO, error) {
47 47
 
48 48
 	_, err = fmt.Sscanf(string(data), ioFormat, &pio.RChar, &pio.WChar, &pio.SyscR,
49 49
 		&pio.SyscW, &pio.ReadBytes, &pio.WriteBytes, &pio.CancelledWriteBytes)
50
-	if err != nil {
51
-		return pio, err
52
-	}
53 50
 
54
-	return pio, nil
51
+	return pio, err
55 52
 }
... ...
@@ -1,3 +1,16 @@
1
+// Copyright 2018 The Prometheus Authors
2
+// Licensed under the Apache License, Version 2.0 (the "License");
3
+// you may not use this file except in compliance with the License.
4
+// You may obtain a copy of the License at
5
+//
6
+// http://www.apache.org/licenses/LICENSE-2.0
7
+//
8
+// Unless required by applicable law or agreed to in writing, software
9
+// distributed under the License is distributed on an "AS IS" BASIS,
10
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+// See the License for the specific language governing permissions and
12
+// limitations under the License.
13
+
1 14
 package procfs
2 15
 
3 16
 import (
... ...
@@ -13,46 +26,46 @@ import (
13 13
 // http://man7.org/linux/man-pages/man2/getrlimit.2.html.
14 14
 type ProcLimits struct {
15 15
 	// CPU time limit in seconds.
16
-	CPUTime int
16
+	CPUTime int64
17 17
 	// Maximum size of files that the process may create.
18
-	FileSize int
18
+	FileSize int64
19 19
 	// Maximum size of the process's data segment (initialized data,
20 20
 	// uninitialized data, and heap).
21
-	DataSize int
21
+	DataSize int64
22 22
 	// Maximum size of the process stack in bytes.
23
-	StackSize int
23
+	StackSize int64
24 24
 	// Maximum size of a core file.
25
-	CoreFileSize int
25
+	CoreFileSize int64
26 26
 	// Limit of the process's resident set in pages.
27
-	ResidentSet int
27
+	ResidentSet int64
28 28
 	// Maximum number of processes that can be created for the real user ID of
29 29
 	// the calling process.
30
-	Processes int
30
+	Processes int64
31 31
 	// Value one greater than the maximum file descriptor number that can be
32 32
 	// opened by this process.
33
-	OpenFiles int
33
+	OpenFiles int64
34 34
 	// Maximum number of bytes of memory that may be locked into RAM.
35
-	LockedMemory int
35
+	LockedMemory int64
36 36
 	// Maximum size of the process's virtual memory address space in bytes.
37
-	AddressSpace int
37
+	AddressSpace int64
38 38
 	// Limit on the combined number of flock(2) locks and fcntl(2) leases that
39 39
 	// this process may establish.
40
-	FileLocks int
40
+	FileLocks int64
41 41
 	// Limit of signals that may be queued for the real user ID of the calling
42 42
 	// process.
43
-	PendingSignals int
43
+	PendingSignals int64
44 44
 	// Limit on the number of bytes that can be allocated for POSIX message
45 45
 	// queues for the real user ID of the calling process.
46
-	MsqqueueSize int
46
+	MsqqueueSize int64
47 47
 	// Limit of the nice priority set using setpriority(2) or nice(2).
48
-	NicePriority int
48
+	NicePriority int64
49 49
 	// Limit of the real-time priority set using sched_setscheduler(2) or
50 50
 	// sched_setparam(2).
51
-	RealtimePriority int
51
+	RealtimePriority int64
52 52
 	// Limit (in microseconds) on the amount of CPU time that a process
53 53
 	// scheduled under a real-time scheduling policy may consume without making
54 54
 	// a blocking system call.
55
-	RealtimeTimeout int
55
+	RealtimeTimeout int64
56 56
 }
57 57
 
58 58
 const (
... ...
@@ -125,13 +138,13 @@ func (p Proc) NewLimits() (ProcLimits, error) {
125 125
 	return l, s.Err()
126 126
 }
127 127
 
128
-func parseInt(s string) (int, error) {
128
+func parseInt(s string) (int64, error) {
129 129
 	if s == limitsUnlimited {
130 130
 		return -1, nil
131 131
 	}
132
-	i, err := strconv.ParseInt(s, 10, 32)
132
+	i, err := strconv.ParseInt(s, 10, 64)
133 133
 	if err != nil {
134 134
 		return 0, fmt.Errorf("couldn't parse value %s: %s", s, err)
135 135
 	}
136
-	return int(i), nil
136
+	return i, nil
137 137
 }
138 138
new file mode 100644
... ...
@@ -0,0 +1,68 @@
0
+// Copyright 2018 The Prometheus Authors
1
+// Licensed under the Apache License, Version 2.0 (the "License");
2
+// you may not use this file except in compliance with the License.
3
+// You may obtain a copy of the License at
4
+//
5
+// http://www.apache.org/licenses/LICENSE-2.0
6
+//
7
+// Unless required by applicable law or agreed to in writing, software
8
+// distributed under the License is distributed on an "AS IS" BASIS,
9
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+// See the License for the specific language governing permissions and
11
+// limitations under the License.
12
+
13
+package procfs
14
+
15
+import (
16
+	"fmt"
17
+	"os"
18
+	"strconv"
19
+	"strings"
20
+)
21
+
22
+// Namespace represents a single namespace of a process.
23
+type Namespace struct {
24
+	Type  string // Namespace type.
25
+	Inode uint32 // Inode number of the namespace. If two processes are in the same namespace their inodes will match.
26
+}
27
+
28
+// Namespaces contains all of the namespaces that the process is contained in.
29
+type Namespaces map[string]Namespace
30
+
31
+// NewNamespaces reads from /proc/[pid/ns/* to get the namespaces of which the
32
+// process is a member.
33
+func (p Proc) NewNamespaces() (Namespaces, error) {
34
+	d, err := os.Open(p.path("ns"))
35
+	if err != nil {
36
+		return nil, err
37
+	}
38
+	defer d.Close()
39
+
40
+	names, err := d.Readdirnames(-1)
41
+	if err != nil {
42
+		return nil, fmt.Errorf("failed to read contents of ns dir: %v", err)
43
+	}
44
+
45
+	ns := make(Namespaces, len(names))
46
+	for _, name := range names {
47
+		target, err := os.Readlink(p.path("ns", name))
48
+		if err != nil {
49
+			return nil, err
50
+		}
51
+
52
+		fields := strings.SplitN(target, ":", 2)
53
+		if len(fields) != 2 {
54
+			return nil, fmt.Errorf("failed to parse namespace type and inode from '%v'", target)
55
+		}
56
+
57
+		typ := fields[0]
58
+		inode, err := strconv.ParseUint(strings.Trim(fields[1], "[]"), 10, 32)
59
+		if err != nil {
60
+			return nil, fmt.Errorf("failed to parse inode from '%v': %v", fields[1], err)
61
+		}
62
+
63
+		ns[name] = Namespace{typ, uint32(inode)}
64
+	}
65
+
66
+	return ns, nil
67
+}
... ...
@@ -1,3 +1,16 @@
1
+// Copyright 2018 The Prometheus Authors
2
+// Licensed under the Apache License, Version 2.0 (the "License");
3
+// you may not use this file except in compliance with the License.
4
+// You may obtain a copy of the License at
5
+//
6
+// http://www.apache.org/licenses/LICENSE-2.0
7
+//
8
+// Unless required by applicable law or agreed to in writing, software
9
+// distributed under the License is distributed on an "AS IS" BASIS,
10
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+// See the License for the specific language governing permissions and
12
+// limitations under the License.
13
+
1 14
 package procfs
2 15
 
3 16
 import (
... ...
@@ -1,17 +1,81 @@
1
+// Copyright 2018 The Prometheus Authors
2
+// Licensed under the Apache License, Version 2.0 (the "License");
3
+// you may not use this file except in compliance with the License.
4
+// You may obtain a copy of the License at
5
+//
6
+// http://www.apache.org/licenses/LICENSE-2.0
7
+//
8
+// Unless required by applicable law or agreed to in writing, software
9
+// distributed under the License is distributed on an "AS IS" BASIS,
10
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+// See the License for the specific language governing permissions and
12
+// limitations under the License.
13
+
1 14
 package procfs
2 15
 
3 16
 import (
4 17
 	"bufio"
5 18
 	"fmt"
19
+	"io"
6 20
 	"os"
7 21
 	"strconv"
8 22
 	"strings"
9 23
 )
10 24
 
25
+// CPUStat shows how much time the cpu spend in various stages.
26
+type CPUStat struct {
27
+	User      float64
28
+	Nice      float64
29
+	System    float64
30
+	Idle      float64
31
+	Iowait    float64
32
+	IRQ       float64
33
+	SoftIRQ   float64
34
+	Steal     float64
35
+	Guest     float64
36
+	GuestNice float64
37
+}
38
+
39
+// SoftIRQStat represent the softirq statistics as exported in the procfs stat file.
40
+// A nice introduction can be found at https://0xax.gitbooks.io/linux-insides/content/interrupts/interrupts-9.html
41
+// It is possible to get per-cpu stats by reading /proc/softirqs
42
+type SoftIRQStat struct {
43
+	Hi          uint64
44
+	Timer       uint64
45
+	NetTx       uint64
46
+	NetRx       uint64
47
+	Block       uint64
48
+	BlockIoPoll uint64
49
+	Tasklet     uint64
50
+	Sched       uint64
51
+	Hrtimer     uint64
52
+	Rcu         uint64
53
+}
54
+
11 55
 // Stat represents kernel/system statistics.
12 56
 type Stat struct {
13 57
 	// Boot time in seconds since the Epoch.
14
-	BootTime int64
58
+	BootTime uint64
59
+	// Summed up cpu statistics.
60
+	CPUTotal CPUStat
61
+	// Per-CPU statistics.
62
+	CPU []CPUStat
63
+	// Number of times interrupts were handled, which contains numbered and unnumbered IRQs.
64
+	IRQTotal uint64
65
+	// Number of times a numbered IRQ was triggered.
66
+	IRQ []uint64
67
+	// Number of times a context switch happened.
68
+	ContextSwitches uint64
69
+	// Number of times a process was created.
70
+	ProcessCreated uint64
71
+	// Number of processes currently running.
72
+	ProcessesRunning uint64
73
+	// Number of processes currently blocked (waiting for IO).
74
+	ProcessesBlocked uint64
75
+	// Number of times a softirq was scheduled.
76
+	SoftIRQTotal uint64
77
+	// Detailed softirq statistics.
78
+	SoftIRQ SoftIRQStat
15 79
 }
16 80
 
17 81
 // NewStat returns kernel/system statistics read from /proc/stat.
... ...
@@ -24,33 +88,145 @@ func NewStat() (Stat, error) {
24 24
 	return fs.NewStat()
25 25
 }
26 26
 
27
+// Parse a cpu statistics line and returns the CPUStat struct plus the cpu id (or -1 for the overall sum).
28
+func parseCPUStat(line string) (CPUStat, int64, error) {
29
+	cpuStat := CPUStat{}
30
+	var cpu string
31
+
32
+	count, err := fmt.Sscanf(line, "%s %f %f %f %f %f %f %f %f %f %f",
33
+		&cpu,
34
+		&cpuStat.User, &cpuStat.Nice, &cpuStat.System, &cpuStat.Idle,
35
+		&cpuStat.Iowait, &cpuStat.IRQ, &cpuStat.SoftIRQ, &cpuStat.Steal,
36
+		&cpuStat.Guest, &cpuStat.GuestNice)
37
+
38
+	if err != nil && err != io.EOF {
39
+		return CPUStat{}, -1, fmt.Errorf("couldn't parse %s (cpu): %s", line, err)
40
+	}
41
+	if count == 0 {
42
+		return CPUStat{}, -1, fmt.Errorf("couldn't parse %s (cpu): 0 elements parsed", line)
43
+	}
44
+
45
+	cpuStat.User /= userHZ
46
+	cpuStat.Nice /= userHZ
47
+	cpuStat.System /= userHZ
48
+	cpuStat.Idle /= userHZ
49
+	cpuStat.Iowait /= userHZ
50
+	cpuStat.IRQ /= userHZ
51
+	cpuStat.SoftIRQ /= userHZ
52
+	cpuStat.Steal /= userHZ
53
+	cpuStat.Guest /= userHZ
54
+	cpuStat.GuestNice /= userHZ
55
+
56
+	if cpu == "cpu" {
57
+		return cpuStat, -1, nil
58
+	}
59
+
60
+	cpuID, err := strconv.ParseInt(cpu[3:], 10, 64)
61
+	if err != nil {
62
+		return CPUStat{}, -1, fmt.Errorf("couldn't parse %s (cpu/cpuid): %s", line, err)
63
+	}
64
+
65
+	return cpuStat, cpuID, nil
66
+}
67
+
68
+// Parse a softirq line.
69
+func parseSoftIRQStat(line string) (SoftIRQStat, uint64, error) {
70
+	softIRQStat := SoftIRQStat{}
71
+	var total uint64
72
+	var prefix string
73
+
74
+	_, err := fmt.Sscanf(line, "%s %d %d %d %d %d %d %d %d %d %d %d",
75
+		&prefix, &total,
76
+		&softIRQStat.Hi, &softIRQStat.Timer, &softIRQStat.NetTx, &softIRQStat.NetRx,
77
+		&softIRQStat.Block, &softIRQStat.BlockIoPoll,
78
+		&softIRQStat.Tasklet, &softIRQStat.Sched,
79
+		&softIRQStat.Hrtimer, &softIRQStat.Rcu)
80
+
81
+	if err != nil {
82
+		return SoftIRQStat{}, 0, fmt.Errorf("couldn't parse %s (softirq): %s", line, err)
83
+	}
84
+
85
+	return softIRQStat, total, nil
86
+}
87
+
27 88
 // NewStat returns an information about current kernel/system statistics.
28 89
 func (fs FS) NewStat() (Stat, error) {
90
+	// See https://www.kernel.org/doc/Documentation/filesystems/proc.txt
91
+
29 92
 	f, err := os.Open(fs.Path("stat"))
30 93
 	if err != nil {
31 94
 		return Stat{}, err
32 95
 	}
33 96
 	defer f.Close()
34 97
 
35
-	s := bufio.NewScanner(f)
36
-	for s.Scan() {
37
-		line := s.Text()
38
-		if !strings.HasPrefix(line, "btime") {
98
+	stat := Stat{}
99
+
100
+	scanner := bufio.NewScanner(f)
101
+	for scanner.Scan() {
102
+		line := scanner.Text()
103
+		parts := strings.Fields(scanner.Text())
104
+		// require at least <key> <value>
105
+		if len(parts) < 2 {
39 106
 			continue
40 107
 		}
41
-		fields := strings.Fields(line)
42
-		if len(fields) != 2 {
43
-			return Stat{}, fmt.Errorf("couldn't parse %s line %s", f.Name(), line)
44
-		}
45
-		i, err := strconv.ParseInt(fields[1], 10, 32)
46
-		if err != nil {
47
-			return Stat{}, fmt.Errorf("couldn't parse %s: %s", fields[1], err)
108
+		switch {
109
+		case parts[0] == "btime":
110
+			if stat.BootTime, err = strconv.ParseUint(parts[1], 10, 64); err != nil {
111
+				return Stat{}, fmt.Errorf("couldn't parse %s (btime): %s", parts[1], err)
112
+			}
113
+		case parts[0] == "intr":
114
+			if stat.IRQTotal, err = strconv.ParseUint(parts[1], 10, 64); err != nil {
115
+				return Stat{}, fmt.Errorf("couldn't parse %s (intr): %s", parts[1], err)
116
+			}
117
+			numberedIRQs := parts[2:]
118
+			stat.IRQ = make([]uint64, len(numberedIRQs))
119
+			for i, count := range numberedIRQs {
120
+				if stat.IRQ[i], err = strconv.ParseUint(count, 10, 64); err != nil {
121
+					return Stat{}, fmt.Errorf("couldn't parse %s (intr%d): %s", count, i, err)
122
+				}
123
+			}
124
+		case parts[0] == "ctxt":
125
+			if stat.ContextSwitches, err = strconv.ParseUint(parts[1], 10, 64); err != nil {
126
+				return Stat{}, fmt.Errorf("couldn't parse %s (ctxt): %s", parts[1], err)
127
+			}
128
+		case parts[0] == "processes":
129
+			if stat.ProcessCreated, err = strconv.ParseUint(parts[1], 10, 64); err != nil {
130
+				return Stat{}, fmt.Errorf("couldn't parse %s (processes): %s", parts[1], err)
131
+			}
132
+		case parts[0] == "procs_running":
133
+			if stat.ProcessesRunning, err = strconv.ParseUint(parts[1], 10, 64); err != nil {
134
+				return Stat{}, fmt.Errorf("couldn't parse %s (procs_running): %s", parts[1], err)
135
+			}
136
+		case parts[0] == "procs_blocked":
137
+			if stat.ProcessesBlocked, err = strconv.ParseUint(parts[1], 10, 64); err != nil {
138
+				return Stat{}, fmt.Errorf("couldn't parse %s (procs_blocked): %s", parts[1], err)
139
+			}
140
+		case parts[0] == "softirq":
141
+			softIRQStats, total, err := parseSoftIRQStat(line)
142
+			if err != nil {
143
+				return Stat{}, err
144
+			}
145
+			stat.SoftIRQTotal = total
146
+			stat.SoftIRQ = softIRQStats
147
+		case strings.HasPrefix(parts[0], "cpu"):
148
+			cpuStat, cpuID, err := parseCPUStat(line)
149
+			if err != nil {
150
+				return Stat{}, err
151
+			}
152
+			if cpuID == -1 {
153
+				stat.CPUTotal = cpuStat
154
+			} else {
155
+				for int64(len(stat.CPU)) <= cpuID {
156
+					stat.CPU = append(stat.CPU, CPUStat{})
157
+				}
158
+				stat.CPU[cpuID] = cpuStat
159
+			}
48 160
 		}
49
-		return Stat{BootTime: i}, nil
50 161
 	}
51
-	if err := s.Err(); err != nil {
162
+
163
+	if err := scanner.Err(); err != nil {
52 164
 		return Stat{}, fmt.Errorf("couldn't parse %s: %s", f.Name(), err)
53 165
 	}
54 166
 
55
-	return Stat{}, fmt.Errorf("couldn't parse %s, missing btime", f.Name())
167
+	return stat, nil
56 168
 }
57 169
new file mode 100644
... ...
@@ -0,0 +1,187 @@
0
+// Copyright 2017 Prometheus Team
1
+// Licensed under the Apache License, Version 2.0 (the "License");
2
+// you may not use this file except in compliance with the License.
3
+// You may obtain a copy of the License at
4
+//
5
+// http://www.apache.org/licenses/LICENSE-2.0
6
+//
7
+// Unless required by applicable law or agreed to in writing, software
8
+// distributed under the License is distributed on an "AS IS" BASIS,
9
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+// See the License for the specific language governing permissions and
11
+// limitations under the License.
12
+
13
+package procfs
14
+
15
+import (
16
+	"bufio"
17
+	"fmt"
18
+	"os"
19
+	"strconv"
20
+	"strings"
21
+)
22
+
23
+// XfrmStat models the contents of /proc/net/xfrm_stat.
24
+type XfrmStat struct {
25
+	// All errors which are not matched by other
26
+	XfrmInError int
27
+	// No buffer is left
28
+	XfrmInBufferError int
29
+	// Header Error
30
+	XfrmInHdrError int
31
+	// No state found
32
+	// i.e. either inbound SPI, address, or IPSEC protocol at SA is wrong
33
+	XfrmInNoStates int
34
+	// Transformation protocol specific error
35
+	// e.g. SA Key is wrong
36
+	XfrmInStateProtoError int
37
+	// Transformation mode specific error
38
+	XfrmInStateModeError int
39
+	// Sequence error
40
+	// e.g. sequence number is out of window
41
+	XfrmInStateSeqError int
42
+	// State is expired
43
+	XfrmInStateExpired int
44
+	// State has mismatch option
45
+	// e.g. UDP encapsulation type is mismatched
46
+	XfrmInStateMismatch int
47
+	// State is invalid
48
+	XfrmInStateInvalid int
49
+	// No matching template for states
50
+	// e.g. Inbound SAs are correct but SP rule is wrong
51
+	XfrmInTmplMismatch int
52
+	// No policy is found for states
53
+	// e.g. Inbound SAs are correct but no SP is found
54
+	XfrmInNoPols int
55
+	// Policy discards
56
+	XfrmInPolBlock int
57
+	// Policy error
58
+	XfrmInPolError int
59
+	// All errors which are not matched by others
60
+	XfrmOutError int
61
+	// Bundle generation error
62
+	XfrmOutBundleGenError int
63
+	// Bundle check error
64
+	XfrmOutBundleCheckError int
65
+	// No state was found
66
+	XfrmOutNoStates int
67
+	// Transformation protocol specific error
68
+	XfrmOutStateProtoError int
69
+	// Transportation mode specific error
70
+	XfrmOutStateModeError int
71
+	// Sequence error
72
+	// i.e sequence number overflow
73
+	XfrmOutStateSeqError int
74
+	// State is expired
75
+	XfrmOutStateExpired int
76
+	// Policy discads
77
+	XfrmOutPolBlock int
78
+	// Policy is dead
79
+	XfrmOutPolDead int
80
+	// Policy Error
81
+	XfrmOutPolError     int
82
+	XfrmFwdHdrError     int
83
+	XfrmOutStateInvalid int
84
+	XfrmAcquireError    int
85
+}
86
+
87
+// NewXfrmStat reads the xfrm_stat statistics.
88
+func NewXfrmStat() (XfrmStat, error) {
89
+	fs, err := NewFS(DefaultMountPoint)
90
+	if err != nil {
91
+		return XfrmStat{}, err
92
+	}
93
+
94
+	return fs.NewXfrmStat()
95
+}
96
+
97
+// NewXfrmStat reads the xfrm_stat statistics from the 'proc' filesystem.
98
+func (fs FS) NewXfrmStat() (XfrmStat, error) {
99
+	file, err := os.Open(fs.Path("net/xfrm_stat"))
100
+	if err != nil {
101
+		return XfrmStat{}, err
102
+	}
103
+	defer file.Close()
104
+
105
+	var (
106
+		x = XfrmStat{}
107
+		s = bufio.NewScanner(file)
108
+	)
109
+
110
+	for s.Scan() {
111
+		fields := strings.Fields(s.Text())
112
+
113
+		if len(fields) != 2 {
114
+			return XfrmStat{}, fmt.Errorf(
115
+				"couldnt parse %s line %s", file.Name(), s.Text())
116
+		}
117
+
118
+		name := fields[0]
119
+		value, err := strconv.Atoi(fields[1])
120
+		if err != nil {
121
+			return XfrmStat{}, err
122
+		}
123
+
124
+		switch name {
125
+		case "XfrmInError":
126
+			x.XfrmInError = value
127
+		case "XfrmInBufferError":
128
+			x.XfrmInBufferError = value
129
+		case "XfrmInHdrError":
130
+			x.XfrmInHdrError = value
131
+		case "XfrmInNoStates":
132
+			x.XfrmInNoStates = value
133
+		case "XfrmInStateProtoError":
134
+			x.XfrmInStateProtoError = value
135
+		case "XfrmInStateModeError":
136
+			x.XfrmInStateModeError = value
137
+		case "XfrmInStateSeqError":
138
+			x.XfrmInStateSeqError = value
139
+		case "XfrmInStateExpired":
140
+			x.XfrmInStateExpired = value
141
+		case "XfrmInStateInvalid":
142
+			x.XfrmInStateInvalid = value
143
+		case "XfrmInTmplMismatch":
144
+			x.XfrmInTmplMismatch = value
145
+		case "XfrmInNoPols":
146
+			x.XfrmInNoPols = value
147
+		case "XfrmInPolBlock":
148
+			x.XfrmInPolBlock = value
149
+		case "XfrmInPolError":
150
+			x.XfrmInPolError = value
151
+		case "XfrmOutError":
152
+			x.XfrmOutError = value
153
+		case "XfrmInStateMismatch":
154
+			x.XfrmInStateMismatch = value
155
+		case "XfrmOutBundleGenError":
156
+			x.XfrmOutBundleGenError = value
157
+		case "XfrmOutBundleCheckError":
158
+			x.XfrmOutBundleCheckError = value
159
+		case "XfrmOutNoStates":
160
+			x.XfrmOutNoStates = value
161
+		case "XfrmOutStateProtoError":
162
+			x.XfrmOutStateProtoError = value
163
+		case "XfrmOutStateModeError":
164
+			x.XfrmOutStateModeError = value
165
+		case "XfrmOutStateSeqError":
166
+			x.XfrmOutStateSeqError = value
167
+		case "XfrmOutStateExpired":
168
+			x.XfrmOutStateExpired = value
169
+		case "XfrmOutPolBlock":
170
+			x.XfrmOutPolBlock = value
171
+		case "XfrmOutPolDead":
172
+			x.XfrmOutPolDead = value
173
+		case "XfrmOutPolError":
174
+			x.XfrmOutPolError = value
175
+		case "XfrmFwdHdrError":
176
+			x.XfrmFwdHdrError = value
177
+		case "XfrmOutStateInvalid":
178
+			x.XfrmOutStateInvalid = value
179
+		case "XfrmAcquireError":
180
+			x.XfrmAcquireError = value
181
+		}
182
+
183
+	}
184
+
185
+	return x, s.Err()
186
+}
0 187
new file mode 100644
... ...
@@ -0,0 +1,330 @@
0
+// Copyright 2017 The Prometheus Authors
1
+// Licensed under the Apache License, Version 2.0 (the "License");
2
+// you may not use this file except in compliance with the License.
3
+// You may obtain a copy of the License at
4
+//
5
+// http://www.apache.org/licenses/LICENSE-2.0
6
+//
7
+// Unless required by applicable law or agreed to in writing, software
8
+// distributed under the License is distributed on an "AS IS" BASIS,
9
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+// See the License for the specific language governing permissions and
11
+// limitations under the License.
12
+
13
+package xfs
14
+
15
+import (
16
+	"bufio"
17
+	"fmt"
18
+	"io"
19
+	"strings"
20
+
21
+	"github.com/prometheus/procfs/internal/util"
22
+)
23
+
24
+// ParseStats parses a Stats from an input io.Reader, using the format
25
+// found in /proc/fs/xfs/stat.
26
+func ParseStats(r io.Reader) (*Stats, error) {
27
+	const (
28
+		// Fields parsed into stats structures.
29
+		fieldExtentAlloc = "extent_alloc"
30
+		fieldAbt         = "abt"
31
+		fieldBlkMap      = "blk_map"
32
+		fieldBmbt        = "bmbt"
33
+		fieldDir         = "dir"
34
+		fieldTrans       = "trans"
35
+		fieldIg          = "ig"
36
+		fieldLog         = "log"
37
+		fieldRw          = "rw"
38
+		fieldAttr        = "attr"
39
+		fieldIcluster    = "icluster"
40
+		fieldVnodes      = "vnodes"
41
+		fieldBuf         = "buf"
42
+		fieldXpc         = "xpc"
43
+
44
+		// Unimplemented at this time due to lack of documentation.
45
+		fieldPushAil = "push_ail"
46
+		fieldXstrat  = "xstrat"
47
+		fieldAbtb2   = "abtb2"
48
+		fieldAbtc2   = "abtc2"
49
+		fieldBmbt2   = "bmbt2"
50
+		fieldIbt2    = "ibt2"
51
+		fieldFibt2   = "fibt2"
52
+		fieldQm      = "qm"
53
+		fieldDebug   = "debug"
54
+	)
55
+
56
+	var xfss Stats
57
+
58
+	s := bufio.NewScanner(r)
59
+	for s.Scan() {
60
+		// Expect at least a string label and a single integer value, ex:
61
+		//   - abt 0
62
+		//   - rw 1 2
63
+		ss := strings.Fields(string(s.Bytes()))
64
+		if len(ss) < 2 {
65
+			continue
66
+		}
67
+		label := ss[0]
68
+
69
+		// Extended precision counters are uint64 values.
70
+		if label == fieldXpc {
71
+			us, err := util.ParseUint64s(ss[1:])
72
+			if err != nil {
73
+				return nil, err
74
+			}
75
+
76
+			xfss.ExtendedPrecision, err = extendedPrecisionStats(us)
77
+			if err != nil {
78
+				return nil, err
79
+			}
80
+
81
+			continue
82
+		}
83
+
84
+		// All other counters are uint32 values.
85
+		us, err := util.ParseUint32s(ss[1:])
86
+		if err != nil {
87
+			return nil, err
88
+		}
89
+
90
+		switch label {
91
+		case fieldExtentAlloc:
92
+			xfss.ExtentAllocation, err = extentAllocationStats(us)
93
+		case fieldAbt:
94
+			xfss.AllocationBTree, err = btreeStats(us)
95
+		case fieldBlkMap:
96
+			xfss.BlockMapping, err = blockMappingStats(us)
97
+		case fieldBmbt:
98
+			xfss.BlockMapBTree, err = btreeStats(us)
99
+		case fieldDir:
100
+			xfss.DirectoryOperation, err = directoryOperationStats(us)
101
+		case fieldTrans:
102
+			xfss.Transaction, err = transactionStats(us)
103
+		case fieldIg:
104
+			xfss.InodeOperation, err = inodeOperationStats(us)
105
+		case fieldLog:
106
+			xfss.LogOperation, err = logOperationStats(us)
107
+		case fieldRw:
108
+			xfss.ReadWrite, err = readWriteStats(us)
109
+		case fieldAttr:
110
+			xfss.AttributeOperation, err = attributeOperationStats(us)
111
+		case fieldIcluster:
112
+			xfss.InodeClustering, err = inodeClusteringStats(us)
113
+		case fieldVnodes:
114
+			xfss.Vnode, err = vnodeStats(us)
115
+		case fieldBuf:
116
+			xfss.Buffer, err = bufferStats(us)
117
+		}
118
+		if err != nil {
119
+			return nil, err
120
+		}
121
+	}
122
+
123
+	return &xfss, s.Err()
124
+}
125
+
126
+// extentAllocationStats builds an ExtentAllocationStats from a slice of uint32s.
127
+func extentAllocationStats(us []uint32) (ExtentAllocationStats, error) {
128
+	if l := len(us); l != 4 {
129
+		return ExtentAllocationStats{}, fmt.Errorf("incorrect number of values for XFS extent allocation stats: %d", l)
130
+	}
131
+
132
+	return ExtentAllocationStats{
133
+		ExtentsAllocated: us[0],
134
+		BlocksAllocated:  us[1],
135
+		ExtentsFreed:     us[2],
136
+		BlocksFreed:      us[3],
137
+	}, nil
138
+}
139
+
140
+// btreeStats builds a BTreeStats from a slice of uint32s.
141
+func btreeStats(us []uint32) (BTreeStats, error) {
142
+	if l := len(us); l != 4 {
143
+		return BTreeStats{}, fmt.Errorf("incorrect number of values for XFS btree stats: %d", l)
144
+	}
145
+
146
+	return BTreeStats{
147
+		Lookups:         us[0],
148
+		Compares:        us[1],
149
+		RecordsInserted: us[2],
150
+		RecordsDeleted:  us[3],
151
+	}, nil
152
+}
153
+
154
+// BlockMappingStat builds a BlockMappingStats from a slice of uint32s.
155
+func blockMappingStats(us []uint32) (BlockMappingStats, error) {
156
+	if l := len(us); l != 7 {
157
+		return BlockMappingStats{}, fmt.Errorf("incorrect number of values for XFS block mapping stats: %d", l)
158
+	}
159
+
160
+	return BlockMappingStats{
161
+		Reads:                us[0],
162
+		Writes:               us[1],
163
+		Unmaps:               us[2],
164
+		ExtentListInsertions: us[3],
165
+		ExtentListDeletions:  us[4],
166
+		ExtentListLookups:    us[5],
167
+		ExtentListCompares:   us[6],
168
+	}, nil
169
+}
170
+
171
+// DirectoryOperationStats builds a DirectoryOperationStats from a slice of uint32s.
172
+func directoryOperationStats(us []uint32) (DirectoryOperationStats, error) {
173
+	if l := len(us); l != 4 {
174
+		return DirectoryOperationStats{}, fmt.Errorf("incorrect number of values for XFS directory operation stats: %d", l)
175
+	}
176
+
177
+	return DirectoryOperationStats{
178
+		Lookups:  us[0],
179
+		Creates:  us[1],
180
+		Removes:  us[2],
181
+		Getdents: us[3],
182
+	}, nil
183
+}
184
+
185
+// TransactionStats builds a TransactionStats from a slice of uint32s.
186
+func transactionStats(us []uint32) (TransactionStats, error) {
187
+	if l := len(us); l != 3 {
188
+		return TransactionStats{}, fmt.Errorf("incorrect number of values for XFS transaction stats: %d", l)
189
+	}
190
+
191
+	return TransactionStats{
192
+		Sync:  us[0],
193
+		Async: us[1],
194
+		Empty: us[2],
195
+	}, nil
196
+}
197
+
198
+// InodeOperationStats builds an InodeOperationStats from a slice of uint32s.
199
+func inodeOperationStats(us []uint32) (InodeOperationStats, error) {
200
+	if l := len(us); l != 7 {
201
+		return InodeOperationStats{}, fmt.Errorf("incorrect number of values for XFS inode operation stats: %d", l)
202
+	}
203
+
204
+	return InodeOperationStats{
205
+		Attempts:        us[0],
206
+		Found:           us[1],
207
+		Recycle:         us[2],
208
+		Missed:          us[3],
209
+		Duplicate:       us[4],
210
+		Reclaims:        us[5],
211
+		AttributeChange: us[6],
212
+	}, nil
213
+}
214
+
215
+// LogOperationStats builds a LogOperationStats from a slice of uint32s.
216
+func logOperationStats(us []uint32) (LogOperationStats, error) {
217
+	if l := len(us); l != 5 {
218
+		return LogOperationStats{}, fmt.Errorf("incorrect number of values for XFS log operation stats: %d", l)
219
+	}
220
+
221
+	return LogOperationStats{
222
+		Writes:            us[0],
223
+		Blocks:            us[1],
224
+		NoInternalBuffers: us[2],
225
+		Force:             us[3],
226
+		ForceSleep:        us[4],
227
+	}, nil
228
+}
229
+
230
+// ReadWriteStats builds a ReadWriteStats from a slice of uint32s.
231
+func readWriteStats(us []uint32) (ReadWriteStats, error) {
232
+	if l := len(us); l != 2 {
233
+		return ReadWriteStats{}, fmt.Errorf("incorrect number of values for XFS read write stats: %d", l)
234
+	}
235
+
236
+	return ReadWriteStats{
237
+		Read:  us[0],
238
+		Write: us[1],
239
+	}, nil
240
+}
241
+
242
+// AttributeOperationStats builds an AttributeOperationStats from a slice of uint32s.
243
+func attributeOperationStats(us []uint32) (AttributeOperationStats, error) {
244
+	if l := len(us); l != 4 {
245
+		return AttributeOperationStats{}, fmt.Errorf("incorrect number of values for XFS attribute operation stats: %d", l)
246
+	}
247
+
248
+	return AttributeOperationStats{
249
+		Get:    us[0],
250
+		Set:    us[1],
251
+		Remove: us[2],
252
+		List:   us[3],
253
+	}, nil
254
+}
255
+
256
+// InodeClusteringStats builds an InodeClusteringStats from a slice of uint32s.
257
+func inodeClusteringStats(us []uint32) (InodeClusteringStats, error) {
258
+	if l := len(us); l != 3 {
259
+		return InodeClusteringStats{}, fmt.Errorf("incorrect number of values for XFS inode clustering stats: %d", l)
260
+	}
261
+
262
+	return InodeClusteringStats{
263
+		Iflush:     us[0],
264
+		Flush:      us[1],
265
+		FlushInode: us[2],
266
+	}, nil
267
+}
268
+
269
+// VnodeStats builds a VnodeStats from a slice of uint32s.
270
+func vnodeStats(us []uint32) (VnodeStats, error) {
271
+	// The attribute "Free" appears to not be available on older XFS
272
+	// stats versions.  Therefore, 7 or 8 elements may appear in
273
+	// this slice.
274
+	l := len(us)
275
+	if l != 7 && l != 8 {
276
+		return VnodeStats{}, fmt.Errorf("incorrect number of values for XFS vnode stats: %d", l)
277
+	}
278
+
279
+	s := VnodeStats{
280
+		Active:   us[0],
281
+		Allocate: us[1],
282
+		Get:      us[2],
283
+		Hold:     us[3],
284
+		Release:  us[4],
285
+		Reclaim:  us[5],
286
+		Remove:   us[6],
287
+	}
288
+
289
+	// Skip adding free, unless it is present. The zero value will
290
+	// be used in place of an actual count.
291
+	if l == 7 {
292
+		return s, nil
293
+	}
294
+
295
+	s.Free = us[7]
296
+	return s, nil
297
+}
298
+
299
+// BufferStats builds a BufferStats from a slice of uint32s.
300
+func bufferStats(us []uint32) (BufferStats, error) {
301
+	if l := len(us); l != 9 {
302
+		return BufferStats{}, fmt.Errorf("incorrect number of values for XFS buffer stats: %d", l)
303
+	}
304
+
305
+	return BufferStats{
306
+		Get:             us[0],
307
+		Create:          us[1],
308
+		GetLocked:       us[2],
309
+		GetLockedWaited: us[3],
310
+		BusyLocked:      us[4],
311
+		MissLocked:      us[5],
312
+		PageRetries:     us[6],
313
+		PageFound:       us[7],
314
+		GetRead:         us[8],
315
+	}, nil
316
+}
317
+
318
+// ExtendedPrecisionStats builds an ExtendedPrecisionStats from a slice of uint32s.
319
+func extendedPrecisionStats(us []uint64) (ExtendedPrecisionStats, error) {
320
+	if l := len(us); l != 3 {
321
+		return ExtendedPrecisionStats{}, fmt.Errorf("incorrect number of values for XFS extended precision stats: %d", l)
322
+	}
323
+
324
+	return ExtendedPrecisionStats{
325
+		FlushBytes: us[0],
326
+		WriteBytes: us[1],
327
+		ReadBytes:  us[2],
328
+	}, nil
329
+}
0 330
new file mode 100644
... ...
@@ -0,0 +1,163 @@
0
+// Copyright 2017 The Prometheus Authors
1
+// Licensed under the Apache License, Version 2.0 (the "License");
2
+// you may not use this file except in compliance with the License.
3
+// You may obtain a copy of the License at
4
+//
5
+// http://www.apache.org/licenses/LICENSE-2.0
6
+//
7
+// Unless required by applicable law or agreed to in writing, software
8
+// distributed under the License is distributed on an "AS IS" BASIS,
9
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+// See the License for the specific language governing permissions and
11
+// limitations under the License.
12
+
13
+// Package xfs provides access to statistics exposed by the XFS filesystem.
14
+package xfs
15
+
16
+// Stats contains XFS filesystem runtime statistics, parsed from
17
+// /proc/fs/xfs/stat.
18
+//
19
+// The names and meanings of each statistic were taken from
20
+// http://xfs.org/index.php/Runtime_Stats and xfs_stats.h in the Linux
21
+// kernel source. Most counters are uint32s (same data types used in
22
+// xfs_stats.h), but some of the "extended precision stats" are uint64s.
23
+type Stats struct {
24
+	// The name of the filesystem used to source these statistics.
25
+	// If empty, this indicates aggregated statistics for all XFS
26
+	// filesystems on the host.
27
+	Name string
28
+
29
+	ExtentAllocation   ExtentAllocationStats
30
+	AllocationBTree    BTreeStats
31
+	BlockMapping       BlockMappingStats
32
+	BlockMapBTree      BTreeStats
33
+	DirectoryOperation DirectoryOperationStats
34
+	Transaction        TransactionStats
35
+	InodeOperation     InodeOperationStats
36
+	LogOperation       LogOperationStats
37
+	ReadWrite          ReadWriteStats
38
+	AttributeOperation AttributeOperationStats
39
+	InodeClustering    InodeClusteringStats
40
+	Vnode              VnodeStats
41
+	Buffer             BufferStats
42
+	ExtendedPrecision  ExtendedPrecisionStats
43
+}
44
+
45
+// ExtentAllocationStats contains statistics regarding XFS extent allocations.
46
+type ExtentAllocationStats struct {
47
+	ExtentsAllocated uint32
48
+	BlocksAllocated  uint32
49
+	ExtentsFreed     uint32
50
+	BlocksFreed      uint32
51
+}
52
+
53
+// BTreeStats contains statistics regarding an XFS internal B-tree.
54
+type BTreeStats struct {
55
+	Lookups         uint32
56
+	Compares        uint32
57
+	RecordsInserted uint32
58
+	RecordsDeleted  uint32
59
+}
60
+
61
+// BlockMappingStats contains statistics regarding XFS block maps.
62
+type BlockMappingStats struct {
63
+	Reads                uint32
64
+	Writes               uint32
65
+	Unmaps               uint32
66
+	ExtentListInsertions uint32
67
+	ExtentListDeletions  uint32
68
+	ExtentListLookups    uint32
69
+	ExtentListCompares   uint32
70
+}
71
+
72
+// DirectoryOperationStats contains statistics regarding XFS directory entries.
73
+type DirectoryOperationStats struct {
74
+	Lookups  uint32
75
+	Creates  uint32
76
+	Removes  uint32
77
+	Getdents uint32
78
+}
79
+
80
+// TransactionStats contains statistics regarding XFS metadata transactions.
81
+type TransactionStats struct {
82
+	Sync  uint32
83
+	Async uint32
84
+	Empty uint32
85
+}
86
+
87
+// InodeOperationStats contains statistics regarding XFS inode operations.
88
+type InodeOperationStats struct {
89
+	Attempts        uint32
90
+	Found           uint32
91
+	Recycle         uint32
92
+	Missed          uint32
93
+	Duplicate       uint32
94
+	Reclaims        uint32
95
+	AttributeChange uint32
96
+}
97
+
98
+// LogOperationStats contains statistics regarding the XFS log buffer.
99
+type LogOperationStats struct {
100
+	Writes            uint32
101
+	Blocks            uint32
102
+	NoInternalBuffers uint32
103
+	Force             uint32
104
+	ForceSleep        uint32
105
+}
106
+
107
+// ReadWriteStats contains statistics regarding the number of read and write
108
+// system calls for XFS filesystems.
109
+type ReadWriteStats struct {
110
+	Read  uint32
111
+	Write uint32
112
+}
113
+
114
+// AttributeOperationStats contains statistics regarding manipulation of
115
+// XFS extended file attributes.
116
+type AttributeOperationStats struct {
117
+	Get    uint32
118
+	Set    uint32
119
+	Remove uint32
120
+	List   uint32
121
+}
122
+
123
+// InodeClusteringStats contains statistics regarding XFS inode clustering
124
+// operations.
125
+type InodeClusteringStats struct {
126
+	Iflush     uint32
127
+	Flush      uint32
128
+	FlushInode uint32
129
+}
130
+
131
+// VnodeStats contains statistics regarding XFS vnode operations.
132
+type VnodeStats struct {
133
+	Active   uint32
134
+	Allocate uint32
135
+	Get      uint32
136
+	Hold     uint32
137
+	Release  uint32
138
+	Reclaim  uint32
139
+	Remove   uint32
140
+	Free     uint32
141
+}
142
+
143
+// BufferStats contains statistics regarding XFS read/write I/O buffers.
144
+type BufferStats struct {
145
+	Get             uint32
146
+	Create          uint32
147
+	GetLocked       uint32
148
+	GetLockedWaited uint32
149
+	BusyLocked      uint32
150
+	MissLocked      uint32
151
+	PageRetries     uint32
152
+	PageFound       uint32
153
+	GetRead         uint32
154
+}
155
+
156
+// ExtendedPrecisionStats contains high precision counters used to track the
157
+// total number of bytes read, written, or flushed, during XFS operations.
158
+type ExtendedPrecisionStats struct {
159
+	FlushBytes uint64
160
+	WriteBytes uint64
161
+	ReadBytes  uint64
162
+}