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>
| ... | ... |
@@ -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 |
[](https://travis-ci.org/grpc-ecosystem/go-grpc-prometheus) |
| 4 | 4 |
[](http://goreportcard.com/report/grpc-ecosystem/go-grpc-prometheus) |
| 5 | 5 |
[](https://godoc.org/github.com/grpc-ecosystem/go-grpc-prometheus) |
| 6 |
+[](https://sourcegraph.com/github.com/grpc-ecosystem/go-grpc-prometheus/?badge) |
|
| 7 |
+[](https://codecov.io/gh/grpc-ecosystem/go-grpc-prometheus) |
|
| 6 | 8 |
[](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 |
-[](https://godoc.org/github.com/prometheus/client_golang) |
|
| 1 |
+See [](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 ®istry{
|
|
| 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 |
[](https://godoc.org/github.com/prometheus/procfs) |
| 10 | 10 |
[](https://travis-ci.org/prometheus/procfs) |
| 11 |
+[](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 |
+} |