mirror of https://github.com/docker/cli.git
vendor: update prometheus deps to match docker/docker
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
parent
338e83837b
commit
ed339fa5c1
11
vendor.conf
11
vendor.conf
|
@ -2,6 +2,7 @@ cloud.google.com/go 0ebda48a7f143b1cce9eb37a8c11
|
||||||
github.com/agl/ed25519 5312a61534124124185d41f09206b9fef1d88403
|
github.com/agl/ed25519 5312a61534124124185d41f09206b9fef1d88403
|
||||||
github.com/Azure/go-ansiterm d6e3b3328b783f23731bc4d058875b0371ff8109
|
github.com/Azure/go-ansiterm d6e3b3328b783f23731bc4d058875b0371ff8109
|
||||||
github.com/beorn7/perks 37c8de3658fcb183f997c4e13e8337516ab753e6 # v1.0.1
|
github.com/beorn7/perks 37c8de3658fcb183f997c4e13e8337516ab753e6 # v1.0.1
|
||||||
|
github.com/cespare/xxhash/v2 d7df74196a9e781ede915320c11c378c1b2f3a1f # v2.1.1
|
||||||
github.com/containerd/console 8375c3424e4d7b114e8a90a4a40c8e1b40d1d4e6 # v1.0.0
|
github.com/containerd/console 8375c3424e4d7b114e8a90a4a40c8e1b40d1d4e6 # v1.0.0
|
||||||
github.com/containerd/containerd e9f94064b9616ab36a8a51d632a63f97f7783c3d # v1.4.0-rc.1
|
github.com/containerd/containerd e9f94064b9616ab36a8a51d632a63f97f7783c3d # v1.4.0-rc.1
|
||||||
github.com/containerd/continuity efbc4488d8fe1bdc16bde3b2d2990d9b3a899165
|
github.com/containerd/continuity efbc4488d8fe1bdc16bde3b2d2990d9b3a899165
|
||||||
|
@ -17,7 +18,7 @@ github.com/docker/docker-credential-helpers 54f0238b6bf101fc3ad3b34114cb
|
||||||
github.com/docker/go d30aec9fd63c35133f8f79c3412ad91a3b08be06 # Contains a customized version of canonical/json and is used by Notary. The package is periodically rebased on current Go versions.
|
github.com/docker/go d30aec9fd63c35133f8f79c3412ad91a3b08be06 # Contains a customized version of canonical/json and is used by Notary. The package is periodically rebased on current Go versions.
|
||||||
github.com/docker/go-connections 7395e3f8aa162843a74ed6d48e79627d9792ac55 # v0.4.0
|
github.com/docker/go-connections 7395e3f8aa162843a74ed6d48e79627d9792ac55 # v0.4.0
|
||||||
github.com/docker/go-events e31b211e4f1cd09aa76fe4ac244571fab96ae47f
|
github.com/docker/go-events e31b211e4f1cd09aa76fe4ac244571fab96ae47f
|
||||||
github.com/docker/go-metrics d466d4f6fd960e01820085bd7e1a24426ee7ef18
|
github.com/docker/go-metrics b619b3592b65de4f087d9f16863a7e6ff905973c # v0.0.1
|
||||||
github.com/docker/go-units 519db1ee28dcc9fd2474ae59fca29a810482bfb1 # v0.4.0
|
github.com/docker/go-units 519db1ee28dcc9fd2474ae59fca29a810482bfb1 # v0.4.0
|
||||||
github.com/docker/swarmkit d6592ddefd8a5319aadff74c558b816b1a0b2590
|
github.com/docker/swarmkit d6592ddefd8a5319aadff74c558b816b1a0b2590
|
||||||
github.com/evanphx/json-patch 72bf35d0ff611848c1dc9df0f976c81192392fa5 # v4.1.0
|
github.com/evanphx/json-patch 72bf35d0ff611848c1dc9df0f976c81192392fa5 # v4.1.0
|
||||||
|
@ -57,10 +58,10 @@ github.com/opencontainers/image-spec d60099175f88c47cd379c4738d15
|
||||||
github.com/opencontainers/runc ff819c7e9184c13b7c2607fe6c30ae19403a7aff # v1.0.0-rc92
|
github.com/opencontainers/runc ff819c7e9184c13b7c2607fe6c30ae19403a7aff # v1.0.0-rc92
|
||||||
github.com/opentracing/opentracing-go 1361b9cd60be79c4c3a7fa9841b3c132e40066a7
|
github.com/opentracing/opentracing-go 1361b9cd60be79c4c3a7fa9841b3c132e40066a7
|
||||||
github.com/pkg/errors 614d223910a179a466c1767a985424175c39b465 # v0.9.1
|
github.com/pkg/errors 614d223910a179a466c1767a985424175c39b465 # v0.9.1
|
||||||
github.com/prometheus/client_golang c5b7fccd204277076155f10851dad72b76a49317 # v0.8.0
|
github.com/prometheus/client_golang 6edbbd9e560190e318cdc5b4d3e630b442858380 # v1.6.0
|
||||||
github.com/prometheus/client_model 6f3806018612930941127f2a7c6c453ba2c527d2
|
github.com/prometheus/client_model 7bc5445566f0fe75b15de23e6b93886e982d7bf9 # v0.2.0
|
||||||
github.com/prometheus/common 7600349dcfe1abd18d72d3a1770870d9800a7801
|
github.com/prometheus/common d978bcb1309602d68bb4ba69cf3f8ed900e07308 # v0.9.1
|
||||||
github.com/prometheus/procfs 7d6f385de8bea29190f15ba9931442a0eaef9af7
|
github.com/prometheus/procfs 46159f73e74d1cb8dc223deef9b2d049286f46b1 # v0.0.11
|
||||||
github.com/russross/blackfriday/v2 d3b5b032dc8e8927d31a5071b56e14c89f045135 # v2.0.1
|
github.com/russross/blackfriday/v2 d3b5b032dc8e8927d31a5071b56e14c89f045135 # v2.0.1
|
||||||
github.com/shurcooL/sanitized_anchor_name 7bfe4c7ecddb3666a94b053b422cdd8f5aaa3615 # v1.0.0
|
github.com/shurcooL/sanitized_anchor_name 7bfe4c7ecddb3666a94b053b422cdd8f5aaa3615 # v1.0.0
|
||||||
github.com/sirupsen/logrus 60c74ad9be0d874af0ab0daef6ab07c5c5911f0d # v1.6.0
|
github.com/sirupsen/logrus 60c74ad9be0d874af0ab0daef6ab07c5c5911f0d # v1.6.0
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
Copyright (c) 2016 Caleb Spare
|
||||||
|
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of this software and associated documentation files (the
|
||||||
|
"Software"), to deal in the Software without restriction, including
|
||||||
|
without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -0,0 +1,67 @@
|
||||||
|
# xxhash
|
||||||
|
|
||||||
|
[![GoDoc](https://godoc.org/github.com/cespare/xxhash?status.svg)](https://godoc.org/github.com/cespare/xxhash)
|
||||||
|
[![Build Status](https://travis-ci.org/cespare/xxhash.svg?branch=master)](https://travis-ci.org/cespare/xxhash)
|
||||||
|
|
||||||
|
xxhash is a Go implementation of the 64-bit
|
||||||
|
[xxHash](http://cyan4973.github.io/xxHash/) algorithm, XXH64. This is a
|
||||||
|
high-quality hashing algorithm that is much faster than anything in the Go
|
||||||
|
standard library.
|
||||||
|
|
||||||
|
This package provides a straightforward API:
|
||||||
|
|
||||||
|
```
|
||||||
|
func Sum64(b []byte) uint64
|
||||||
|
func Sum64String(s string) uint64
|
||||||
|
type Digest struct{ ... }
|
||||||
|
func New() *Digest
|
||||||
|
```
|
||||||
|
|
||||||
|
The `Digest` type implements hash.Hash64. Its key methods are:
|
||||||
|
|
||||||
|
```
|
||||||
|
func (*Digest) Write([]byte) (int, error)
|
||||||
|
func (*Digest) WriteString(string) (int, error)
|
||||||
|
func (*Digest) Sum64() uint64
|
||||||
|
```
|
||||||
|
|
||||||
|
This implementation provides a fast pure-Go implementation and an even faster
|
||||||
|
assembly implementation for amd64.
|
||||||
|
|
||||||
|
## Compatibility
|
||||||
|
|
||||||
|
This package is in a module and the latest code is in version 2 of the module.
|
||||||
|
You need a version of Go with at least "minimal module compatibility" to use
|
||||||
|
github.com/cespare/xxhash/v2:
|
||||||
|
|
||||||
|
* 1.9.7+ for Go 1.9
|
||||||
|
* 1.10.3+ for Go 1.10
|
||||||
|
* Go 1.11 or later
|
||||||
|
|
||||||
|
I recommend using the latest release of Go.
|
||||||
|
|
||||||
|
## Benchmarks
|
||||||
|
|
||||||
|
Here are some quick benchmarks comparing the pure-Go and assembly
|
||||||
|
implementations of Sum64.
|
||||||
|
|
||||||
|
| input size | purego | asm |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| 5 B | 979.66 MB/s | 1291.17 MB/s |
|
||||||
|
| 100 B | 7475.26 MB/s | 7973.40 MB/s |
|
||||||
|
| 4 KB | 17573.46 MB/s | 17602.65 MB/s |
|
||||||
|
| 10 MB | 17131.46 MB/s | 17142.16 MB/s |
|
||||||
|
|
||||||
|
These numbers were generated on Ubuntu 18.04 with an Intel i7-8700K CPU using
|
||||||
|
the following commands under Go 1.11.2:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ go test -tags purego -benchtime 10s -bench '/xxhash,direct,bytes'
|
||||||
|
$ go test -benchtime 10s -bench '/xxhash,direct,bytes'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Projects using this package
|
||||||
|
|
||||||
|
- [InfluxDB](https://github.com/influxdata/influxdb)
|
||||||
|
- [Prometheus](https://github.com/prometheus/prometheus)
|
||||||
|
- [FreeCache](https://github.com/coocood/freecache)
|
|
@ -0,0 +1,3 @@
|
||||||
|
module github.com/cespare/xxhash/v2
|
||||||
|
|
||||||
|
go 1.11
|
|
@ -0,0 +1,236 @@
|
||||||
|
// Package xxhash implements the 64-bit variant of xxHash (XXH64) as described
|
||||||
|
// at http://cyan4973.github.io/xxHash/.
|
||||||
|
package xxhash
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"math/bits"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
prime1 uint64 = 11400714785074694791
|
||||||
|
prime2 uint64 = 14029467366897019727
|
||||||
|
prime3 uint64 = 1609587929392839161
|
||||||
|
prime4 uint64 = 9650029242287828579
|
||||||
|
prime5 uint64 = 2870177450012600261
|
||||||
|
)
|
||||||
|
|
||||||
|
// NOTE(caleb): I'm using both consts and vars of the primes. Using consts where
|
||||||
|
// possible in the Go code is worth a small (but measurable) performance boost
|
||||||
|
// by avoiding some MOVQs. Vars are needed for the asm and also are useful for
|
||||||
|
// convenience in the Go code in a few places where we need to intentionally
|
||||||
|
// avoid constant arithmetic (e.g., v1 := prime1 + prime2 fails because the
|
||||||
|
// result overflows a uint64).
|
||||||
|
var (
|
||||||
|
prime1v = prime1
|
||||||
|
prime2v = prime2
|
||||||
|
prime3v = prime3
|
||||||
|
prime4v = prime4
|
||||||
|
prime5v = prime5
|
||||||
|
)
|
||||||
|
|
||||||
|
// Digest implements hash.Hash64.
|
||||||
|
type Digest struct {
|
||||||
|
v1 uint64
|
||||||
|
v2 uint64
|
||||||
|
v3 uint64
|
||||||
|
v4 uint64
|
||||||
|
total uint64
|
||||||
|
mem [32]byte
|
||||||
|
n int // how much of mem is used
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a new Digest that computes the 64-bit xxHash algorithm.
|
||||||
|
func New() *Digest {
|
||||||
|
var d Digest
|
||||||
|
d.Reset()
|
||||||
|
return &d
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset clears the Digest's state so that it can be reused.
|
||||||
|
func (d *Digest) Reset() {
|
||||||
|
d.v1 = prime1v + prime2
|
||||||
|
d.v2 = prime2
|
||||||
|
d.v3 = 0
|
||||||
|
d.v4 = -prime1v
|
||||||
|
d.total = 0
|
||||||
|
d.n = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size always returns 8 bytes.
|
||||||
|
func (d *Digest) Size() int { return 8 }
|
||||||
|
|
||||||
|
// BlockSize always returns 32 bytes.
|
||||||
|
func (d *Digest) BlockSize() int { return 32 }
|
||||||
|
|
||||||
|
// Write adds more data to d. It always returns len(b), nil.
|
||||||
|
func (d *Digest) Write(b []byte) (n int, err error) {
|
||||||
|
n = len(b)
|
||||||
|
d.total += uint64(n)
|
||||||
|
|
||||||
|
if d.n+n < 32 {
|
||||||
|
// This new data doesn't even fill the current block.
|
||||||
|
copy(d.mem[d.n:], b)
|
||||||
|
d.n += n
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.n > 0 {
|
||||||
|
// Finish off the partial block.
|
||||||
|
copy(d.mem[d.n:], b)
|
||||||
|
d.v1 = round(d.v1, u64(d.mem[0:8]))
|
||||||
|
d.v2 = round(d.v2, u64(d.mem[8:16]))
|
||||||
|
d.v3 = round(d.v3, u64(d.mem[16:24]))
|
||||||
|
d.v4 = round(d.v4, u64(d.mem[24:32]))
|
||||||
|
b = b[32-d.n:]
|
||||||
|
d.n = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(b) >= 32 {
|
||||||
|
// One or more full blocks left.
|
||||||
|
nw := writeBlocks(d, b)
|
||||||
|
b = b[nw:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store any remaining partial block.
|
||||||
|
copy(d.mem[:], b)
|
||||||
|
d.n = len(b)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sum appends the current hash to b and returns the resulting slice.
|
||||||
|
func (d *Digest) Sum(b []byte) []byte {
|
||||||
|
s := d.Sum64()
|
||||||
|
return append(
|
||||||
|
b,
|
||||||
|
byte(s>>56),
|
||||||
|
byte(s>>48),
|
||||||
|
byte(s>>40),
|
||||||
|
byte(s>>32),
|
||||||
|
byte(s>>24),
|
||||||
|
byte(s>>16),
|
||||||
|
byte(s>>8),
|
||||||
|
byte(s),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sum64 returns the current hash.
|
||||||
|
func (d *Digest) Sum64() uint64 {
|
||||||
|
var h uint64
|
||||||
|
|
||||||
|
if d.total >= 32 {
|
||||||
|
v1, v2, v3, v4 := d.v1, d.v2, d.v3, d.v4
|
||||||
|
h = rol1(v1) + rol7(v2) + rol12(v3) + rol18(v4)
|
||||||
|
h = mergeRound(h, v1)
|
||||||
|
h = mergeRound(h, v2)
|
||||||
|
h = mergeRound(h, v3)
|
||||||
|
h = mergeRound(h, v4)
|
||||||
|
} else {
|
||||||
|
h = d.v3 + prime5
|
||||||
|
}
|
||||||
|
|
||||||
|
h += d.total
|
||||||
|
|
||||||
|
i, end := 0, d.n
|
||||||
|
for ; i+8 <= end; i += 8 {
|
||||||
|
k1 := round(0, u64(d.mem[i:i+8]))
|
||||||
|
h ^= k1
|
||||||
|
h = rol27(h)*prime1 + prime4
|
||||||
|
}
|
||||||
|
if i+4 <= end {
|
||||||
|
h ^= uint64(u32(d.mem[i:i+4])) * prime1
|
||||||
|
h = rol23(h)*prime2 + prime3
|
||||||
|
i += 4
|
||||||
|
}
|
||||||
|
for i < end {
|
||||||
|
h ^= uint64(d.mem[i]) * prime5
|
||||||
|
h = rol11(h) * prime1
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
h ^= h >> 33
|
||||||
|
h *= prime2
|
||||||
|
h ^= h >> 29
|
||||||
|
h *= prime3
|
||||||
|
h ^= h >> 32
|
||||||
|
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
magic = "xxh\x06"
|
||||||
|
marshaledSize = len(magic) + 8*5 + 32
|
||||||
|
)
|
||||||
|
|
||||||
|
// MarshalBinary implements the encoding.BinaryMarshaler interface.
|
||||||
|
func (d *Digest) MarshalBinary() ([]byte, error) {
|
||||||
|
b := make([]byte, 0, marshaledSize)
|
||||||
|
b = append(b, magic...)
|
||||||
|
b = appendUint64(b, d.v1)
|
||||||
|
b = appendUint64(b, d.v2)
|
||||||
|
b = appendUint64(b, d.v3)
|
||||||
|
b = appendUint64(b, d.v4)
|
||||||
|
b = appendUint64(b, d.total)
|
||||||
|
b = append(b, d.mem[:d.n]...)
|
||||||
|
b = b[:len(b)+len(d.mem)-d.n]
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface.
|
||||||
|
func (d *Digest) UnmarshalBinary(b []byte) error {
|
||||||
|
if len(b) < len(magic) || string(b[:len(magic)]) != magic {
|
||||||
|
return errors.New("xxhash: invalid hash state identifier")
|
||||||
|
}
|
||||||
|
if len(b) != marshaledSize {
|
||||||
|
return errors.New("xxhash: invalid hash state size")
|
||||||
|
}
|
||||||
|
b = b[len(magic):]
|
||||||
|
b, d.v1 = consumeUint64(b)
|
||||||
|
b, d.v2 = consumeUint64(b)
|
||||||
|
b, d.v3 = consumeUint64(b)
|
||||||
|
b, d.v4 = consumeUint64(b)
|
||||||
|
b, d.total = consumeUint64(b)
|
||||||
|
copy(d.mem[:], b)
|
||||||
|
b = b[len(d.mem):]
|
||||||
|
d.n = int(d.total % uint64(len(d.mem)))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendUint64(b []byte, x uint64) []byte {
|
||||||
|
var a [8]byte
|
||||||
|
binary.LittleEndian.PutUint64(a[:], x)
|
||||||
|
return append(b, a[:]...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func consumeUint64(b []byte) ([]byte, uint64) {
|
||||||
|
x := u64(b)
|
||||||
|
return b[8:], x
|
||||||
|
}
|
||||||
|
|
||||||
|
func u64(b []byte) uint64 { return binary.LittleEndian.Uint64(b) }
|
||||||
|
func u32(b []byte) uint32 { return binary.LittleEndian.Uint32(b) }
|
||||||
|
|
||||||
|
func round(acc, input uint64) uint64 {
|
||||||
|
acc += input * prime2
|
||||||
|
acc = rol31(acc)
|
||||||
|
acc *= prime1
|
||||||
|
return acc
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergeRound(acc, val uint64) uint64 {
|
||||||
|
val = round(0, val)
|
||||||
|
acc ^= val
|
||||||
|
acc = acc*prime1 + prime4
|
||||||
|
return acc
|
||||||
|
}
|
||||||
|
|
||||||
|
func rol1(x uint64) uint64 { return bits.RotateLeft64(x, 1) }
|
||||||
|
func rol7(x uint64) uint64 { return bits.RotateLeft64(x, 7) }
|
||||||
|
func rol11(x uint64) uint64 { return bits.RotateLeft64(x, 11) }
|
||||||
|
func rol12(x uint64) uint64 { return bits.RotateLeft64(x, 12) }
|
||||||
|
func rol18(x uint64) uint64 { return bits.RotateLeft64(x, 18) }
|
||||||
|
func rol23(x uint64) uint64 { return bits.RotateLeft64(x, 23) }
|
||||||
|
func rol27(x uint64) uint64 { return bits.RotateLeft64(x, 27) }
|
||||||
|
func rol31(x uint64) uint64 { return bits.RotateLeft64(x, 31) }
|
|
@ -0,0 +1,13 @@
|
||||||
|
// +build !appengine
|
||||||
|
// +build gc
|
||||||
|
// +build !purego
|
||||||
|
|
||||||
|
package xxhash
|
||||||
|
|
||||||
|
// Sum64 computes the 64-bit xxHash digest of b.
|
||||||
|
//
|
||||||
|
//go:noescape
|
||||||
|
func Sum64(b []byte) uint64
|
||||||
|
|
||||||
|
//go:noescape
|
||||||
|
func writeBlocks(d *Digest, b []byte) int
|
|
@ -0,0 +1,215 @@
|
||||||
|
// +build !appengine
|
||||||
|
// +build gc
|
||||||
|
// +build !purego
|
||||||
|
|
||||||
|
#include "textflag.h"
|
||||||
|
|
||||||
|
// Register allocation:
|
||||||
|
// AX h
|
||||||
|
// CX pointer to advance through b
|
||||||
|
// DX n
|
||||||
|
// BX loop end
|
||||||
|
// R8 v1, k1
|
||||||
|
// R9 v2
|
||||||
|
// R10 v3
|
||||||
|
// R11 v4
|
||||||
|
// R12 tmp
|
||||||
|
// R13 prime1v
|
||||||
|
// R14 prime2v
|
||||||
|
// R15 prime4v
|
||||||
|
|
||||||
|
// round reads from and advances the buffer pointer in CX.
|
||||||
|
// It assumes that R13 has prime1v and R14 has prime2v.
|
||||||
|
#define round(r) \
|
||||||
|
MOVQ (CX), R12 \
|
||||||
|
ADDQ $8, CX \
|
||||||
|
IMULQ R14, R12 \
|
||||||
|
ADDQ R12, r \
|
||||||
|
ROLQ $31, r \
|
||||||
|
IMULQ R13, r
|
||||||
|
|
||||||
|
// mergeRound applies a merge round on the two registers acc and val.
|
||||||
|
// It assumes that R13 has prime1v, R14 has prime2v, and R15 has prime4v.
|
||||||
|
#define mergeRound(acc, val) \
|
||||||
|
IMULQ R14, val \
|
||||||
|
ROLQ $31, val \
|
||||||
|
IMULQ R13, val \
|
||||||
|
XORQ val, acc \
|
||||||
|
IMULQ R13, acc \
|
||||||
|
ADDQ R15, acc
|
||||||
|
|
||||||
|
// func Sum64(b []byte) uint64
|
||||||
|
TEXT ·Sum64(SB), NOSPLIT, $0-32
|
||||||
|
// Load fixed primes.
|
||||||
|
MOVQ ·prime1v(SB), R13
|
||||||
|
MOVQ ·prime2v(SB), R14
|
||||||
|
MOVQ ·prime4v(SB), R15
|
||||||
|
|
||||||
|
// Load slice.
|
||||||
|
MOVQ b_base+0(FP), CX
|
||||||
|
MOVQ b_len+8(FP), DX
|
||||||
|
LEAQ (CX)(DX*1), BX
|
||||||
|
|
||||||
|
// The first loop limit will be len(b)-32.
|
||||||
|
SUBQ $32, BX
|
||||||
|
|
||||||
|
// Check whether we have at least one block.
|
||||||
|
CMPQ DX, $32
|
||||||
|
JLT noBlocks
|
||||||
|
|
||||||
|
// Set up initial state (v1, v2, v3, v4).
|
||||||
|
MOVQ R13, R8
|
||||||
|
ADDQ R14, R8
|
||||||
|
MOVQ R14, R9
|
||||||
|
XORQ R10, R10
|
||||||
|
XORQ R11, R11
|
||||||
|
SUBQ R13, R11
|
||||||
|
|
||||||
|
// Loop until CX > BX.
|
||||||
|
blockLoop:
|
||||||
|
round(R8)
|
||||||
|
round(R9)
|
||||||
|
round(R10)
|
||||||
|
round(R11)
|
||||||
|
|
||||||
|
CMPQ CX, BX
|
||||||
|
JLE blockLoop
|
||||||
|
|
||||||
|
MOVQ R8, AX
|
||||||
|
ROLQ $1, AX
|
||||||
|
MOVQ R9, R12
|
||||||
|
ROLQ $7, R12
|
||||||
|
ADDQ R12, AX
|
||||||
|
MOVQ R10, R12
|
||||||
|
ROLQ $12, R12
|
||||||
|
ADDQ R12, AX
|
||||||
|
MOVQ R11, R12
|
||||||
|
ROLQ $18, R12
|
||||||
|
ADDQ R12, AX
|
||||||
|
|
||||||
|
mergeRound(AX, R8)
|
||||||
|
mergeRound(AX, R9)
|
||||||
|
mergeRound(AX, R10)
|
||||||
|
mergeRound(AX, R11)
|
||||||
|
|
||||||
|
JMP afterBlocks
|
||||||
|
|
||||||
|
noBlocks:
|
||||||
|
MOVQ ·prime5v(SB), AX
|
||||||
|
|
||||||
|
afterBlocks:
|
||||||
|
ADDQ DX, AX
|
||||||
|
|
||||||
|
// Right now BX has len(b)-32, and we want to loop until CX > len(b)-8.
|
||||||
|
ADDQ $24, BX
|
||||||
|
|
||||||
|
CMPQ CX, BX
|
||||||
|
JG fourByte
|
||||||
|
|
||||||
|
wordLoop:
|
||||||
|
// Calculate k1.
|
||||||
|
MOVQ (CX), R8
|
||||||
|
ADDQ $8, CX
|
||||||
|
IMULQ R14, R8
|
||||||
|
ROLQ $31, R8
|
||||||
|
IMULQ R13, R8
|
||||||
|
|
||||||
|
XORQ R8, AX
|
||||||
|
ROLQ $27, AX
|
||||||
|
IMULQ R13, AX
|
||||||
|
ADDQ R15, AX
|
||||||
|
|
||||||
|
CMPQ CX, BX
|
||||||
|
JLE wordLoop
|
||||||
|
|
||||||
|
fourByte:
|
||||||
|
ADDQ $4, BX
|
||||||
|
CMPQ CX, BX
|
||||||
|
JG singles
|
||||||
|
|
||||||
|
MOVL (CX), R8
|
||||||
|
ADDQ $4, CX
|
||||||
|
IMULQ R13, R8
|
||||||
|
XORQ R8, AX
|
||||||
|
|
||||||
|
ROLQ $23, AX
|
||||||
|
IMULQ R14, AX
|
||||||
|
ADDQ ·prime3v(SB), AX
|
||||||
|
|
||||||
|
singles:
|
||||||
|
ADDQ $4, BX
|
||||||
|
CMPQ CX, BX
|
||||||
|
JGE finalize
|
||||||
|
|
||||||
|
singlesLoop:
|
||||||
|
MOVBQZX (CX), R12
|
||||||
|
ADDQ $1, CX
|
||||||
|
IMULQ ·prime5v(SB), R12
|
||||||
|
XORQ R12, AX
|
||||||
|
|
||||||
|
ROLQ $11, AX
|
||||||
|
IMULQ R13, AX
|
||||||
|
|
||||||
|
CMPQ CX, BX
|
||||||
|
JL singlesLoop
|
||||||
|
|
||||||
|
finalize:
|
||||||
|
MOVQ AX, R12
|
||||||
|
SHRQ $33, R12
|
||||||
|
XORQ R12, AX
|
||||||
|
IMULQ R14, AX
|
||||||
|
MOVQ AX, R12
|
||||||
|
SHRQ $29, R12
|
||||||
|
XORQ R12, AX
|
||||||
|
IMULQ ·prime3v(SB), AX
|
||||||
|
MOVQ AX, R12
|
||||||
|
SHRQ $32, R12
|
||||||
|
XORQ R12, AX
|
||||||
|
|
||||||
|
MOVQ AX, ret+24(FP)
|
||||||
|
RET
|
||||||
|
|
||||||
|
// writeBlocks uses the same registers as above except that it uses AX to store
|
||||||
|
// the d pointer.
|
||||||
|
|
||||||
|
// func writeBlocks(d *Digest, b []byte) int
|
||||||
|
TEXT ·writeBlocks(SB), NOSPLIT, $0-40
|
||||||
|
// Load fixed primes needed for round.
|
||||||
|
MOVQ ·prime1v(SB), R13
|
||||||
|
MOVQ ·prime2v(SB), R14
|
||||||
|
|
||||||
|
// Load slice.
|
||||||
|
MOVQ b_base+8(FP), CX
|
||||||
|
MOVQ b_len+16(FP), DX
|
||||||
|
LEAQ (CX)(DX*1), BX
|
||||||
|
SUBQ $32, BX
|
||||||
|
|
||||||
|
// Load vN from d.
|
||||||
|
MOVQ d+0(FP), AX
|
||||||
|
MOVQ 0(AX), R8 // v1
|
||||||
|
MOVQ 8(AX), R9 // v2
|
||||||
|
MOVQ 16(AX), R10 // v3
|
||||||
|
MOVQ 24(AX), R11 // v4
|
||||||
|
|
||||||
|
// We don't need to check the loop condition here; this function is
|
||||||
|
// always called with at least one block of data to process.
|
||||||
|
blockLoop:
|
||||||
|
round(R8)
|
||||||
|
round(R9)
|
||||||
|
round(R10)
|
||||||
|
round(R11)
|
||||||
|
|
||||||
|
CMPQ CX, BX
|
||||||
|
JLE blockLoop
|
||||||
|
|
||||||
|
// Copy vN back to d.
|
||||||
|
MOVQ R8, 0(AX)
|
||||||
|
MOVQ R9, 8(AX)
|
||||||
|
MOVQ R10, 16(AX)
|
||||||
|
MOVQ R11, 24(AX)
|
||||||
|
|
||||||
|
// The number of bytes written is CX minus the old base pointer.
|
||||||
|
SUBQ b_base+8(FP), CX
|
||||||
|
MOVQ CX, ret+32(FP)
|
||||||
|
|
||||||
|
RET
|
|
@ -0,0 +1,76 @@
|
||||||
|
// +build !amd64 appengine !gc purego
|
||||||
|
|
||||||
|
package xxhash
|
||||||
|
|
||||||
|
// Sum64 computes the 64-bit xxHash digest of b.
|
||||||
|
func Sum64(b []byte) uint64 {
|
||||||
|
// A simpler version would be
|
||||||
|
// d := New()
|
||||||
|
// d.Write(b)
|
||||||
|
// return d.Sum64()
|
||||||
|
// but this is faster, particularly for small inputs.
|
||||||
|
|
||||||
|
n := len(b)
|
||||||
|
var h uint64
|
||||||
|
|
||||||
|
if n >= 32 {
|
||||||
|
v1 := prime1v + prime2
|
||||||
|
v2 := prime2
|
||||||
|
v3 := uint64(0)
|
||||||
|
v4 := -prime1v
|
||||||
|
for len(b) >= 32 {
|
||||||
|
v1 = round(v1, u64(b[0:8:len(b)]))
|
||||||
|
v2 = round(v2, u64(b[8:16:len(b)]))
|
||||||
|
v3 = round(v3, u64(b[16:24:len(b)]))
|
||||||
|
v4 = round(v4, u64(b[24:32:len(b)]))
|
||||||
|
b = b[32:len(b):len(b)]
|
||||||
|
}
|
||||||
|
h = rol1(v1) + rol7(v2) + rol12(v3) + rol18(v4)
|
||||||
|
h = mergeRound(h, v1)
|
||||||
|
h = mergeRound(h, v2)
|
||||||
|
h = mergeRound(h, v3)
|
||||||
|
h = mergeRound(h, v4)
|
||||||
|
} else {
|
||||||
|
h = prime5
|
||||||
|
}
|
||||||
|
|
||||||
|
h += uint64(n)
|
||||||
|
|
||||||
|
i, end := 0, len(b)
|
||||||
|
for ; i+8 <= end; i += 8 {
|
||||||
|
k1 := round(0, u64(b[i:i+8:len(b)]))
|
||||||
|
h ^= k1
|
||||||
|
h = rol27(h)*prime1 + prime4
|
||||||
|
}
|
||||||
|
if i+4 <= end {
|
||||||
|
h ^= uint64(u32(b[i:i+4:len(b)])) * prime1
|
||||||
|
h = rol23(h)*prime2 + prime3
|
||||||
|
i += 4
|
||||||
|
}
|
||||||
|
for ; i < end; i++ {
|
||||||
|
h ^= uint64(b[i]) * prime5
|
||||||
|
h = rol11(h) * prime1
|
||||||
|
}
|
||||||
|
|
||||||
|
h ^= h >> 33
|
||||||
|
h *= prime2
|
||||||
|
h ^= h >> 29
|
||||||
|
h *= prime3
|
||||||
|
h ^= h >> 32
|
||||||
|
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeBlocks(d *Digest, b []byte) int {
|
||||||
|
v1, v2, v3, v4 := d.v1, d.v2, d.v3, d.v4
|
||||||
|
n := len(b)
|
||||||
|
for len(b) >= 32 {
|
||||||
|
v1 = round(v1, u64(b[0:8:len(b)]))
|
||||||
|
v2 = round(v2, u64(b[8:16:len(b)]))
|
||||||
|
v3 = round(v3, u64(b[16:24:len(b)]))
|
||||||
|
v4 = round(v4, u64(b[24:32:len(b)]))
|
||||||
|
b = b[32:len(b):len(b)]
|
||||||
|
}
|
||||||
|
d.v1, d.v2, d.v3, d.v4 = v1, v2, v3, v4
|
||||||
|
return n - len(b)
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
// +build appengine
|
||||||
|
|
||||||
|
// This file contains the safe implementations of otherwise unsafe-using code.
|
||||||
|
|
||||||
|
package xxhash
|
||||||
|
|
||||||
|
// Sum64String computes the 64-bit xxHash digest of s.
|
||||||
|
func Sum64String(s string) uint64 {
|
||||||
|
return Sum64([]byte(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteString adds more data to d. It always returns len(s), nil.
|
||||||
|
func (d *Digest) WriteString(s string) (n int, err error) {
|
||||||
|
return d.Write([]byte(s))
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
// +build !appengine
|
||||||
|
|
||||||
|
// This file encapsulates usage of unsafe.
|
||||||
|
// xxhash_safe.go contains the safe implementations.
|
||||||
|
|
||||||
|
package xxhash
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Notes:
|
||||||
|
//
|
||||||
|
// See https://groups.google.com/d/msg/golang-nuts/dcjzJy-bSpw/tcZYBzQqAQAJ
|
||||||
|
// for some discussion about these unsafe conversions.
|
||||||
|
//
|
||||||
|
// In the future it's possible that compiler optimizations will make these
|
||||||
|
// unsafe operations unnecessary: https://golang.org/issue/2205.
|
||||||
|
//
|
||||||
|
// Both of these wrapper functions still incur function call overhead since they
|
||||||
|
// will not be inlined. We could write Go/asm copies of Sum64 and Digest.Write
|
||||||
|
// for strings to squeeze out a bit more speed. Mid-stack inlining should
|
||||||
|
// eventually fix this.
|
||||||
|
|
||||||
|
// Sum64String computes the 64-bit xxHash digest of s.
|
||||||
|
// It may be faster than Sum64([]byte(s)) by avoiding a copy.
|
||||||
|
func Sum64String(s string) uint64 {
|
||||||
|
var b []byte
|
||||||
|
bh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
|
||||||
|
bh.Data = (*reflect.StringHeader)(unsafe.Pointer(&s)).Data
|
||||||
|
bh.Len = len(s)
|
||||||
|
bh.Cap = len(s)
|
||||||
|
return Sum64(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteString adds more data to d. It always returns len(s), nil.
|
||||||
|
// It may be faster than Write([]byte(s)) by avoiding a copy.
|
||||||
|
func (d *Digest) WriteString(s string) (n int, err error) {
|
||||||
|
var b []byte
|
||||||
|
bh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
|
||||||
|
bh.Data = (*reflect.StringHeader)(unsafe.Pointer(&s)).Data
|
||||||
|
bh.Len = len(s)
|
||||||
|
bh.Cap = len(s)
|
||||||
|
return d.Write(b)
|
||||||
|
}
|
|
@ -68,9 +68,21 @@ If you need to use a unit but it is not defined in the package please open a PR
|
||||||
|
|
||||||
Package documentation can be found [here](https://godoc.org/github.com/docker/go-metrics).
|
Package documentation can be found [here](https://godoc.org/github.com/docker/go-metrics).
|
||||||
|
|
||||||
|
## HTTP Metrics
|
||||||
|
|
||||||
|
To instrument a http handler, you can wrap the code like this:
|
||||||
|
|
||||||
|
```go
|
||||||
|
namespace := metrics.NewNamespace("docker_distribution", "http", metrics.Labels{"handler": "your_http_handler_name"})
|
||||||
|
httpMetrics := namespace.NewDefaultHttpMetrics()
|
||||||
|
metrics.Register(namespace)
|
||||||
|
instrumentedHandler = metrics.InstrumentHandler(httpMetrics, unInstrumentedHandler)
|
||||||
|
```
|
||||||
|
Note: The `handler` label must be provided when a new namespace is created.
|
||||||
|
|
||||||
## Additional Metrics
|
## Additional Metrics
|
||||||
|
|
||||||
Additional metrics are also defined here that are not avaliable in the prometheus client.
|
Additional metrics are also defined here that are not available in the prometheus client.
|
||||||
If you need a custom metrics and it is generic enough to be used by multiple projects, define it here.
|
If you need a custom metrics and it is generic enough to be used by multiple projects, define it here.
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
module github.com/docker/go-metrics
|
||||||
|
|
||||||
|
go 1.11
|
||||||
|
|
||||||
|
require github.com/prometheus/client_golang v1.1.0
|
|
@ -4,10 +4,71 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HTTPHandlerOpts describes a set of configurable options of http metrics
|
||||||
|
type HTTPHandlerOpts struct {
|
||||||
|
DurationBuckets []float64
|
||||||
|
RequestSizeBuckets []float64
|
||||||
|
ResponseSizeBuckets []float64
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
InstrumentHandlerResponseSize = iota
|
||||||
|
InstrumentHandlerRequestSize
|
||||||
|
InstrumentHandlerDuration
|
||||||
|
InstrumentHandlerCounter
|
||||||
|
InstrumentHandlerInFlight
|
||||||
|
)
|
||||||
|
|
||||||
|
type HTTPMetric struct {
|
||||||
|
prometheus.Collector
|
||||||
|
handlerType int
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
defaultDurationBuckets = []float64{.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10, 25, 60}
|
||||||
|
defaultRequestSizeBuckets = prometheus.ExponentialBuckets(1024, 2, 22) //1K to 4G
|
||||||
|
defaultResponseSizeBuckets = defaultRequestSizeBuckets
|
||||||
)
|
)
|
||||||
|
|
||||||
// Handler returns the global http.Handler that provides the prometheus
|
// Handler returns the global http.Handler that provides the prometheus
|
||||||
// metrics format on GET requests
|
// metrics format on GET requests. This handler is no longer instrumented.
|
||||||
func Handler() http.Handler {
|
func Handler() http.Handler {
|
||||||
return prometheus.Handler()
|
return promhttp.Handler()
|
||||||
|
}
|
||||||
|
|
||||||
|
func InstrumentHandler(metrics []*HTTPMetric, handler http.Handler) http.HandlerFunc {
|
||||||
|
return InstrumentHandlerFunc(metrics, handler.ServeHTTP)
|
||||||
|
}
|
||||||
|
|
||||||
|
func InstrumentHandlerFunc(metrics []*HTTPMetric, handlerFunc http.HandlerFunc) http.HandlerFunc {
|
||||||
|
var handler http.Handler
|
||||||
|
handler = http.HandlerFunc(handlerFunc)
|
||||||
|
for _, metric := range metrics {
|
||||||
|
switch metric.handlerType {
|
||||||
|
case InstrumentHandlerResponseSize:
|
||||||
|
if collector, ok := metric.Collector.(prometheus.ObserverVec); ok {
|
||||||
|
handler = promhttp.InstrumentHandlerResponseSize(collector, handler)
|
||||||
|
}
|
||||||
|
case InstrumentHandlerRequestSize:
|
||||||
|
if collector, ok := metric.Collector.(prometheus.ObserverVec); ok {
|
||||||
|
handler = promhttp.InstrumentHandlerRequestSize(collector, handler)
|
||||||
|
}
|
||||||
|
case InstrumentHandlerDuration:
|
||||||
|
if collector, ok := metric.Collector.(prometheus.ObserverVec); ok {
|
||||||
|
handler = promhttp.InstrumentHandlerDuration(collector, handler)
|
||||||
|
}
|
||||||
|
case InstrumentHandlerCounter:
|
||||||
|
if collector, ok := metric.Collector.(*prometheus.CounterVec); ok {
|
||||||
|
handler = promhttp.InstrumentHandlerCounter(collector, handler)
|
||||||
|
}
|
||||||
|
case InstrumentHandlerInFlight:
|
||||||
|
if collector, ok := metric.Collector.(prometheus.Gauge); ok {
|
||||||
|
handler = promhttp.InstrumentHandlerInFlight(collector, handler)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return handler.ServeHTTP
|
||||||
}
|
}
|
||||||
|
|
|
@ -179,3 +179,137 @@ func makeName(name string, unit Unit) string {
|
||||||
|
|
||||||
return fmt.Sprintf("%s_%s", name, unit)
|
return fmt.Sprintf("%s_%s", name, unit)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (n *Namespace) NewDefaultHttpMetrics(handlerName string) []*HTTPMetric {
|
||||||
|
return n.NewHttpMetricsWithOpts(handlerName, HTTPHandlerOpts{
|
||||||
|
DurationBuckets: defaultDurationBuckets,
|
||||||
|
RequestSizeBuckets: defaultResponseSizeBuckets,
|
||||||
|
ResponseSizeBuckets: defaultResponseSizeBuckets,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Namespace) NewHttpMetrics(handlerName string, durationBuckets, requestSizeBuckets, responseSizeBuckets []float64) []*HTTPMetric {
|
||||||
|
return n.NewHttpMetricsWithOpts(handlerName, HTTPHandlerOpts{
|
||||||
|
DurationBuckets: durationBuckets,
|
||||||
|
RequestSizeBuckets: requestSizeBuckets,
|
||||||
|
ResponseSizeBuckets: responseSizeBuckets,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Namespace) NewHttpMetricsWithOpts(handlerName string, opts HTTPHandlerOpts) []*HTTPMetric {
|
||||||
|
var httpMetrics []*HTTPMetric
|
||||||
|
inFlightMetric := n.NewInFlightGaugeMetric(handlerName)
|
||||||
|
requestTotalMetric := n.NewRequestTotalMetric(handlerName)
|
||||||
|
requestDurationMetric := n.NewRequestDurationMetric(handlerName, opts.DurationBuckets)
|
||||||
|
requestSizeMetric := n.NewRequestSizeMetric(handlerName, opts.RequestSizeBuckets)
|
||||||
|
responseSizeMetric := n.NewResponseSizeMetric(handlerName, opts.ResponseSizeBuckets)
|
||||||
|
httpMetrics = append(httpMetrics, inFlightMetric, requestDurationMetric, requestTotalMetric, requestSizeMetric, responseSizeMetric)
|
||||||
|
return httpMetrics
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Namespace) NewInFlightGaugeMetric(handlerName string) *HTTPMetric {
|
||||||
|
labels := prometheus.Labels(n.labels)
|
||||||
|
labels["handler"] = handlerName
|
||||||
|
metric := prometheus.NewGauge(prometheus.GaugeOpts{
|
||||||
|
Namespace: n.name,
|
||||||
|
Subsystem: n.subsystem,
|
||||||
|
Name: "in_flight_requests",
|
||||||
|
Help: "The in-flight HTTP requests",
|
||||||
|
ConstLabels: prometheus.Labels(labels),
|
||||||
|
})
|
||||||
|
httpMetric := &HTTPMetric{
|
||||||
|
Collector: metric,
|
||||||
|
handlerType: InstrumentHandlerInFlight,
|
||||||
|
}
|
||||||
|
n.Add(httpMetric)
|
||||||
|
return httpMetric
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Namespace) NewRequestTotalMetric(handlerName string) *HTTPMetric {
|
||||||
|
labels := prometheus.Labels(n.labels)
|
||||||
|
labels["handler"] = handlerName
|
||||||
|
metric := prometheus.NewCounterVec(
|
||||||
|
prometheus.CounterOpts{
|
||||||
|
Namespace: n.name,
|
||||||
|
Subsystem: n.subsystem,
|
||||||
|
Name: "requests_total",
|
||||||
|
Help: "Total number of HTTP requests made.",
|
||||||
|
ConstLabels: prometheus.Labels(labels),
|
||||||
|
},
|
||||||
|
[]string{"code", "method"},
|
||||||
|
)
|
||||||
|
httpMetric := &HTTPMetric{
|
||||||
|
Collector: metric,
|
||||||
|
handlerType: InstrumentHandlerCounter,
|
||||||
|
}
|
||||||
|
n.Add(httpMetric)
|
||||||
|
return httpMetric
|
||||||
|
}
|
||||||
|
func (n *Namespace) NewRequestDurationMetric(handlerName string, buckets []float64) *HTTPMetric {
|
||||||
|
if len(buckets) == 0 {
|
||||||
|
panic("DurationBuckets must be provided")
|
||||||
|
}
|
||||||
|
labels := prometheus.Labels(n.labels)
|
||||||
|
labels["handler"] = handlerName
|
||||||
|
opts := prometheus.HistogramOpts{
|
||||||
|
Namespace: n.name,
|
||||||
|
Subsystem: n.subsystem,
|
||||||
|
Name: "request_duration_seconds",
|
||||||
|
Help: "The HTTP request latencies in seconds.",
|
||||||
|
Buckets: buckets,
|
||||||
|
ConstLabels: prometheus.Labels(labels),
|
||||||
|
}
|
||||||
|
metric := prometheus.NewHistogramVec(opts, []string{"method"})
|
||||||
|
httpMetric := &HTTPMetric{
|
||||||
|
Collector: metric,
|
||||||
|
handlerType: InstrumentHandlerDuration,
|
||||||
|
}
|
||||||
|
n.Add(httpMetric)
|
||||||
|
return httpMetric
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Namespace) NewRequestSizeMetric(handlerName string, buckets []float64) *HTTPMetric {
|
||||||
|
if len(buckets) == 0 {
|
||||||
|
panic("RequestSizeBuckets must be provided")
|
||||||
|
}
|
||||||
|
labels := prometheus.Labels(n.labels)
|
||||||
|
labels["handler"] = handlerName
|
||||||
|
opts := prometheus.HistogramOpts{
|
||||||
|
Namespace: n.name,
|
||||||
|
Subsystem: n.subsystem,
|
||||||
|
Name: "request_size_bytes",
|
||||||
|
Help: "The HTTP request sizes in bytes.",
|
||||||
|
Buckets: buckets,
|
||||||
|
ConstLabels: prometheus.Labels(labels),
|
||||||
|
}
|
||||||
|
metric := prometheus.NewHistogramVec(opts, []string{})
|
||||||
|
httpMetric := &HTTPMetric{
|
||||||
|
Collector: metric,
|
||||||
|
handlerType: InstrumentHandlerRequestSize,
|
||||||
|
}
|
||||||
|
n.Add(httpMetric)
|
||||||
|
return httpMetric
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Namespace) NewResponseSizeMetric(handlerName string, buckets []float64) *HTTPMetric {
|
||||||
|
if len(buckets) == 0 {
|
||||||
|
panic("ResponseSizeBuckets must be provided")
|
||||||
|
}
|
||||||
|
labels := prometheus.Labels(n.labels)
|
||||||
|
labels["handler"] = handlerName
|
||||||
|
opts := prometheus.HistogramOpts{
|
||||||
|
Namespace: n.name,
|
||||||
|
Subsystem: n.subsystem,
|
||||||
|
Name: "response_size_bytes",
|
||||||
|
Help: "The HTTP response sizes in bytes.",
|
||||||
|
Buckets: buckets,
|
||||||
|
ConstLabels: prometheus.Labels(labels),
|
||||||
|
}
|
||||||
|
metrics := prometheus.NewHistogramVec(opts, []string{})
|
||||||
|
httpMetric := &HTTPMetric{
|
||||||
|
Collector: metrics,
|
||||||
|
handlerType: InstrumentHandlerResponseSize,
|
||||||
|
}
|
||||||
|
n.Add(httpMetric)
|
||||||
|
return httpMetric
|
||||||
|
}
|
||||||
|
|
|
@ -28,15 +28,27 @@ type Timer interface {
|
||||||
|
|
||||||
// LabeledTimer is a timer that must have label values populated before use.
|
// LabeledTimer is a timer that must have label values populated before use.
|
||||||
type LabeledTimer interface {
|
type LabeledTimer interface {
|
||||||
WithValues(labels ...string) Timer
|
WithValues(labels ...string) *labeledTimerObserver
|
||||||
}
|
}
|
||||||
|
|
||||||
type labeledTimer struct {
|
type labeledTimer struct {
|
||||||
m *prometheus.HistogramVec
|
m *prometheus.HistogramVec
|
||||||
}
|
}
|
||||||
|
|
||||||
func (lt *labeledTimer) WithValues(labels ...string) Timer {
|
type labeledTimerObserver struct {
|
||||||
return &timer{m: lt.m.WithLabelValues(labels...)}
|
m prometheus.Observer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lbo *labeledTimerObserver) Update(duration time.Duration) {
|
||||||
|
lbo.m.Observe(duration.Seconds())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lbo *labeledTimerObserver) UpdateSince(since time.Time) {
|
||||||
|
lbo.m.Observe(time.Since(since).Seconds())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lt *labeledTimer) WithValues(labels ...string) *labeledTimerObserver {
|
||||||
|
return &labeledTimerObserver{m: lt.m.WithLabelValues(labels...)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (lt *labeledTimer) Describe(c chan<- *prometheus.Desc) {
|
func (lt *labeledTimer) Describe(c chan<- *prometheus.Desc) {
|
||||||
|
@ -48,7 +60,7 @@ func (lt *labeledTimer) Collect(c chan<- prometheus.Metric) {
|
||||||
}
|
}
|
||||||
|
|
||||||
type timer struct {
|
type timer struct {
|
||||||
m prometheus.Histogram
|
m prometheus.Observer
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *timer) Update(duration time.Duration) {
|
func (t *timer) Update(duration time.Duration) {
|
||||||
|
@ -60,9 +72,14 @@ func (t *timer) UpdateSince(since time.Time) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *timer) Describe(c chan<- *prometheus.Desc) {
|
func (t *timer) Describe(c chan<- *prometheus.Desc) {
|
||||||
t.m.Describe(c)
|
c <- t.m.(prometheus.Metric).Desc()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *timer) Collect(c chan<- prometheus.Metric) {
|
func (t *timer) Collect(c chan<- prometheus.Metric) {
|
||||||
t.m.Collect(c)
|
// Are there any observers that don't implement Collector? It is really
|
||||||
|
// unclear what the point of the upstream change was, but we'll let this
|
||||||
|
// panic if we get an observer that doesn't implement collector. In this
|
||||||
|
// case, we should almost always see metricVec objects, so this should
|
||||||
|
// never panic.
|
||||||
|
t.m.(prometheus.Collector).Collect(c)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,34 @@
|
||||||
# Prometheus Go client library
|
# Prometheus Go client library
|
||||||
|
|
||||||
[![Build Status](https://travis-ci.org/prometheus/client_golang.svg?branch=master)](https://travis-ci.org/prometheus/client_golang)
|
[![Build Status](https://travis-ci.org/prometheus/client_golang.svg?branch=master)](https://travis-ci.org/prometheus/client_golang)
|
||||||
|
[![Go Report Card](https://goreportcard.com/badge/github.com/prometheus/client_golang)](https://goreportcard.com/report/github.com/prometheus/client_golang)
|
||||||
|
[![go-doc](https://godoc.org/github.com/prometheus/client_golang?status.svg)](https://godoc.org/github.com/prometheus/client_golang)
|
||||||
|
|
||||||
This is the [Go](http://golang.org) client library for
|
This is the [Go](http://golang.org) client library for
|
||||||
[Prometheus](http://prometheus.io). It has two separate parts, one for
|
[Prometheus](http://prometheus.io). It has two separate parts, one for
|
||||||
instrumenting application code, and one for creating clients that talk to the
|
instrumenting application code, and one for creating clients that talk to the
|
||||||
Prometheus HTTP API.
|
Prometheus HTTP API.
|
||||||
|
|
||||||
|
__This library requires Go1.9 or later.__ The minimum required patch releases for older Go versions are Go1.9.7 and Go1.10.3.
|
||||||
|
|
||||||
|
## Important note about releases and stability
|
||||||
|
|
||||||
|
This repository generally follows [Semantic
|
||||||
|
Versioning](https://semver.org/). However, the API client in
|
||||||
|
prometheus/client_golang/api/… is still considered experimental. Breaking
|
||||||
|
changes of the API client will _not_ trigger a new major release. The same is
|
||||||
|
true for selected other new features explicitly marked as **EXPERIMENTAL** in
|
||||||
|
CHANGELOG.md.
|
||||||
|
|
||||||
|
Features that require breaking changes in the stable parts of the repository
|
||||||
|
are being batched up and tracked in the [v2
|
||||||
|
milestone](https://github.com/prometheus/client_golang/milestone/2). The v2
|
||||||
|
development happens in a [separate
|
||||||
|
branch](https://github.com/prometheus/client_golang/tree/dev-v2) for the time
|
||||||
|
being. v2 releases off that branch will happen once sufficient stability is
|
||||||
|
reached. In view of the widespread use of this repository, v1 and v2 will
|
||||||
|
coexist for a while to enable a convenient transition.
|
||||||
|
|
||||||
## Instrumenting applications
|
## Instrumenting applications
|
||||||
|
|
||||||
[![code-coverage](http://gocover.io/_badge/github.com/prometheus/client_golang/prometheus)](http://gocover.io/github.com/prometheus/client_golang/prometheus) [![go-doc](https://godoc.org/github.com/prometheus/client_golang/prometheus?status.svg)](https://godoc.org/github.com/prometheus/client_golang/prometheus)
|
[![code-coverage](http://gocover.io/_badge/github.com/prometheus/client_golang/prometheus)](http://gocover.io/github.com/prometheus/client_golang/prometheus) [![go-doc](https://godoc.org/github.com/prometheus/client_golang/prometheus?status.svg)](https://godoc.org/github.com/prometheus/client_golang/prometheus)
|
||||||
|
@ -14,8 +36,8 @@ Prometheus HTTP API.
|
||||||
The
|
The
|
||||||
[`prometheus` directory](https://github.com/prometheus/client_golang/tree/master/prometheus)
|
[`prometheus` directory](https://github.com/prometheus/client_golang/tree/master/prometheus)
|
||||||
contains the instrumentation library. See the
|
contains the instrumentation library. See the
|
||||||
[best practices section](http://prometheus.io/docs/practices/naming/) of the
|
[guide](https://prometheus.io/docs/guides/go-application/) on the Prometheus
|
||||||
Prometheus documentation to learn more about instrumenting applications.
|
website to learn more about instrumenting applications.
|
||||||
|
|
||||||
The
|
The
|
||||||
[`examples` directory](https://github.com/prometheus/client_golang/tree/master/examples)
|
[`examples` directory](https://github.com/prometheus/client_golang/tree/master/examples)
|
||||||
|
@ -23,13 +45,14 @@ contains simple examples of instrumented code.
|
||||||
|
|
||||||
## Client for the Prometheus HTTP API
|
## Client for the Prometheus HTTP API
|
||||||
|
|
||||||
[![code-coverage](http://gocover.io/_badge/github.com/prometheus/client_golang/api/prometheus)](http://gocover.io/github.com/prometheus/client_golang/api/prometheus) [![go-doc](https://godoc.org/github.com/prometheus/client_golang/api/prometheus?status.svg)](https://godoc.org/github.com/prometheus/client_golang/api/prometheus)
|
[![code-coverage](http://gocover.io/_badge/github.com/prometheus/client_golang/api/prometheus/v1)](http://gocover.io/github.com/prometheus/client_golang/api/prometheus/v1) [![go-doc](https://godoc.org/github.com/prometheus/client_golang/api/prometheus?status.svg)](https://godoc.org/github.com/prometheus/client_golang/api)
|
||||||
|
|
||||||
The
|
The
|
||||||
[`api/prometheus` directory](https://github.com/prometheus/client_golang/tree/master/api/prometheus)
|
[`api/prometheus` directory](https://github.com/prometheus/client_golang/tree/master/api/prometheus)
|
||||||
contains the client for the
|
contains the client for the
|
||||||
[Prometheus HTTP API](http://prometheus.io/docs/querying/api/). It allows you
|
[Prometheus HTTP API](http://prometheus.io/docs/querying/api/). It allows you
|
||||||
to write Go applications that query time series data from a Prometheus server.
|
to write Go applications that query time series data from a Prometheus
|
||||||
|
server. It is still in alpha stage.
|
||||||
|
|
||||||
## Where is `model`, `extraction`, and `text`?
|
## Where is `model`, `extraction`, and `text`?
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
module github.com/prometheus/client_golang
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/beorn7/perks v1.0.1
|
||||||
|
github.com/cespare/xxhash/v2 v2.1.1
|
||||||
|
github.com/golang/protobuf v1.4.0
|
||||||
|
github.com/json-iterator/go v1.1.9
|
||||||
|
github.com/kr/pretty v0.1.0 // indirect
|
||||||
|
github.com/prometheus/client_model v0.2.0
|
||||||
|
github.com/prometheus/common v0.9.1
|
||||||
|
github.com/prometheus/procfs v0.0.11
|
||||||
|
github.com/stretchr/testify v1.4.0 // indirect
|
||||||
|
golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f
|
||||||
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
||||||
|
gopkg.in/yaml.v2 v2.2.5 // indirect
|
||||||
|
)
|
||||||
|
|
||||||
|
go 1.11
|
29
vendor/github.com/prometheus/client_golang/prometheus/build_info.go
generated
vendored
Normal file
29
vendor/github.com/prometheus/client_golang/prometheus/build_info.go
generated
vendored
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
// Copyright 2019 The Prometheus Authors
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// +build go1.12
|
||||||
|
|
||||||
|
package prometheus
|
||||||
|
|
||||||
|
import "runtime/debug"
|
||||||
|
|
||||||
|
// readBuildInfo is a wrapper around debug.ReadBuildInfo for Go 1.12+.
|
||||||
|
func readBuildInfo() (path, version, sum string) {
|
||||||
|
path, version, sum = "unknown", "unknown", "unknown"
|
||||||
|
if bi, ok := debug.ReadBuildInfo(); ok {
|
||||||
|
path = bi.Main.Path
|
||||||
|
version = bi.Main.Version
|
||||||
|
sum = bi.Main.Sum
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
22
vendor/github.com/prometheus/client_golang/prometheus/build_info_pre_1.12.go
generated
vendored
Normal file
22
vendor/github.com/prometheus/client_golang/prometheus/build_info_pre_1.12.go
generated
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
// Copyright 2019 The Prometheus Authors
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// +build !go1.12
|
||||||
|
|
||||||
|
package prometheus
|
||||||
|
|
||||||
|
// readBuildInfo is a wrapper around debug.ReadBuildInfo for Go versions before
|
||||||
|
// 1.12. Remove this whole file once the minimum supported Go version is 1.12.
|
||||||
|
func readBuildInfo() (path, version, sum string) {
|
||||||
|
return "unknown", "unknown", "unknown"
|
||||||
|
}
|
|
@ -29,27 +29,72 @@ type Collector interface {
|
||||||
// collected by this Collector to the provided channel and returns once
|
// collected by this Collector to the provided channel and returns once
|
||||||
// the last descriptor has been sent. The sent descriptors fulfill the
|
// the last descriptor has been sent. The sent descriptors fulfill the
|
||||||
// consistency and uniqueness requirements described in the Desc
|
// consistency and uniqueness requirements described in the Desc
|
||||||
// documentation. (It is valid if one and the same Collector sends
|
// documentation.
|
||||||
// duplicate descriptors. Those duplicates are simply ignored. However,
|
//
|
||||||
// two different Collectors must not send duplicate descriptors.) This
|
// It is valid if one and the same Collector sends duplicate
|
||||||
// method idempotently sends the same descriptors throughout the
|
// descriptors. Those duplicates are simply ignored. However, two
|
||||||
// lifetime of the Collector. If a Collector encounters an error while
|
// different Collectors must not send duplicate descriptors.
|
||||||
// executing this method, it must send an invalid descriptor (created
|
//
|
||||||
// with NewInvalidDesc) to signal the error to the registry.
|
// Sending no descriptor at all marks the Collector as “unchecked”,
|
||||||
|
// i.e. no checks will be performed at registration time, and the
|
||||||
|
// Collector may yield any Metric it sees fit in its Collect method.
|
||||||
|
//
|
||||||
|
// This method idempotently sends the same descriptors throughout the
|
||||||
|
// lifetime of the Collector. It may be called concurrently and
|
||||||
|
// therefore must be implemented in a concurrency safe way.
|
||||||
|
//
|
||||||
|
// If a Collector encounters an error while executing this method, it
|
||||||
|
// must send an invalid descriptor (created with NewInvalidDesc) to
|
||||||
|
// signal the error to the registry.
|
||||||
Describe(chan<- *Desc)
|
Describe(chan<- *Desc)
|
||||||
// Collect is called by the Prometheus registry when collecting
|
// Collect is called by the Prometheus registry when collecting
|
||||||
// metrics. The implementation sends each collected metric via the
|
// metrics. The implementation sends each collected metric via the
|
||||||
// provided channel and returns once the last metric has been sent. The
|
// provided channel and returns once the last metric has been sent. The
|
||||||
// descriptor of each sent metric is one of those returned by
|
// descriptor of each sent metric is one of those returned by Describe
|
||||||
// Describe. Returned metrics that share the same descriptor must differ
|
// (unless the Collector is unchecked, see above). Returned metrics that
|
||||||
// in their variable label values. This method may be called
|
// share the same descriptor must differ in their variable label
|
||||||
// concurrently and must therefore be implemented in a concurrency safe
|
// values.
|
||||||
// way. Blocking occurs at the expense of total performance of rendering
|
//
|
||||||
// all registered metrics. Ideally, Collector implementations support
|
// This method may be called concurrently and must therefore be
|
||||||
// concurrent readers.
|
// implemented in a concurrency safe way. Blocking occurs at the expense
|
||||||
|
// of total performance of rendering all registered metrics. Ideally,
|
||||||
|
// Collector implementations support concurrent readers.
|
||||||
Collect(chan<- Metric)
|
Collect(chan<- Metric)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DescribeByCollect is a helper to implement the Describe method of a custom
|
||||||
|
// Collector. It collects the metrics from the provided Collector and sends
|
||||||
|
// their descriptors to the provided channel.
|
||||||
|
//
|
||||||
|
// If a Collector collects the same metrics throughout its lifetime, its
|
||||||
|
// Describe method can simply be implemented as:
|
||||||
|
//
|
||||||
|
// func (c customCollector) Describe(ch chan<- *Desc) {
|
||||||
|
// DescribeByCollect(c, ch)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// However, this will not work if the metrics collected change dynamically over
|
||||||
|
// the lifetime of the Collector in a way that their combined set of descriptors
|
||||||
|
// changes as well. The shortcut implementation will then violate the contract
|
||||||
|
// of the Describe method. If a Collector sometimes collects no metrics at all
|
||||||
|
// (for example vectors like CounterVec, GaugeVec, etc., which only collect
|
||||||
|
// metrics after a metric with a fully specified label set has been accessed),
|
||||||
|
// it might even get registered as an unchecked Collector (cf. the Register
|
||||||
|
// method of the Registerer interface). Hence, only use this shortcut
|
||||||
|
// implementation of Describe if you are certain to fulfill the contract.
|
||||||
|
//
|
||||||
|
// The Collector example demonstrates a use of DescribeByCollect.
|
||||||
|
func DescribeByCollect(c Collector, descs chan<- *Desc) {
|
||||||
|
metrics := make(chan Metric)
|
||||||
|
go func() {
|
||||||
|
c.Collect(metrics)
|
||||||
|
close(metrics)
|
||||||
|
}()
|
||||||
|
for m := range metrics {
|
||||||
|
descs <- m.Desc()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// selfCollector implements Collector for a single Metric so that the Metric
|
// selfCollector implements Collector for a single Metric so that the Metric
|
||||||
// collects itself. Add it as an anonymous field to a struct that implements
|
// collects itself. Add it as an anonymous field to a struct that implements
|
||||||
// Metric, and call init with the Metric itself as an argument.
|
// Metric, and call init with the Metric itself as an argument.
|
||||||
|
|
|
@ -15,6 +15,11 @@ package prometheus
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"math"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
dto "github.com/prometheus/client_model/go"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Counter is a Metric that represents a single numerical value that only ever
|
// Counter is a Metric that represents a single numerical value that only ever
|
||||||
|
@ -30,26 +35,42 @@ type Counter interface {
|
||||||
Metric
|
Metric
|
||||||
Collector
|
Collector
|
||||||
|
|
||||||
// Set is used to set the Counter to an arbitrary value. It is only used
|
// Inc increments the counter by 1. Use Add to increment it by arbitrary
|
||||||
// if you have to transfer a value from an external counter into this
|
// non-negative values.
|
||||||
// Prometheus metric. Do not use it for regular handling of a
|
|
||||||
// Prometheus counter (as it can be used to break the contract of
|
|
||||||
// monotonically increasing values).
|
|
||||||
//
|
|
||||||
// Deprecated: Use NewConstMetric to create a counter for an external
|
|
||||||
// value. A Counter should never be set.
|
|
||||||
Set(float64)
|
|
||||||
// Inc increments the counter by 1.
|
|
||||||
Inc()
|
Inc()
|
||||||
// Add adds the given value to the counter. It panics if the value is <
|
// Add adds the given value to the counter. It panics if the value is <
|
||||||
// 0.
|
// 0.
|
||||||
Add(float64)
|
Add(float64)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExemplarAdder is implemented by Counters that offer the option of adding a
|
||||||
|
// value to the Counter together with an exemplar. Its AddWithExemplar method
|
||||||
|
// works like the Add method of the Counter interface but also replaces the
|
||||||
|
// currently saved exemplar (if any) with a new one, created from the provided
|
||||||
|
// value, the current time as timestamp, and the provided labels. Empty Labels
|
||||||
|
// will lead to a valid (label-less) exemplar. But if Labels is nil, the current
|
||||||
|
// exemplar is left in place. AddWithExemplar panics if the value is < 0, if any
|
||||||
|
// of the provided labels are invalid, or if the provided labels contain more
|
||||||
|
// than 64 runes in total.
|
||||||
|
type ExemplarAdder interface {
|
||||||
|
AddWithExemplar(value float64, exemplar Labels)
|
||||||
|
}
|
||||||
|
|
||||||
// CounterOpts is an alias for Opts. See there for doc comments.
|
// CounterOpts is an alias for Opts. See there for doc comments.
|
||||||
type CounterOpts Opts
|
type CounterOpts Opts
|
||||||
|
|
||||||
// NewCounter creates a new Counter based on the provided CounterOpts.
|
// NewCounter creates a new Counter based on the provided CounterOpts.
|
||||||
|
//
|
||||||
|
// The returned implementation also implements ExemplarAdder. It is safe to
|
||||||
|
// perform the corresponding type assertion.
|
||||||
|
//
|
||||||
|
// The returned implementation tracks the counter value in two separate
|
||||||
|
// variables, a float64 and a uint64. The latter is used to track calls of the
|
||||||
|
// Inc method and calls of the Add method with a value that can be represented
|
||||||
|
// as a uint64. This allows atomic increments of the counter with optimal
|
||||||
|
// performance. (It is common to have an Inc call in very hot execution paths.)
|
||||||
|
// Both internal tracking values are added up in the Write method. This has to
|
||||||
|
// be taken into account when it comes to precision and overflow behavior.
|
||||||
func NewCounter(opts CounterOpts) Counter {
|
func NewCounter(opts CounterOpts) Counter {
|
||||||
desc := NewDesc(
|
desc := NewDesc(
|
||||||
BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
|
BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
|
||||||
|
@ -57,20 +78,83 @@ func NewCounter(opts CounterOpts) Counter {
|
||||||
nil,
|
nil,
|
||||||
opts.ConstLabels,
|
opts.ConstLabels,
|
||||||
)
|
)
|
||||||
result := &counter{value: value{desc: desc, valType: CounterValue, labelPairs: desc.constLabelPairs}}
|
result := &counter{desc: desc, labelPairs: desc.constLabelPairs, now: time.Now}
|
||||||
result.init(result) // Init self-collection.
|
result.init(result) // Init self-collection.
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
type counter struct {
|
type counter struct {
|
||||||
value
|
// valBits contains the bits of the represented float64 value, while
|
||||||
|
// valInt stores values that are exact integers. Both have to go first
|
||||||
|
// in the struct to guarantee alignment for atomic operations.
|
||||||
|
// http://golang.org/pkg/sync/atomic/#pkg-note-BUG
|
||||||
|
valBits uint64
|
||||||
|
valInt uint64
|
||||||
|
|
||||||
|
selfCollector
|
||||||
|
desc *Desc
|
||||||
|
|
||||||
|
labelPairs []*dto.LabelPair
|
||||||
|
exemplar atomic.Value // Containing nil or a *dto.Exemplar.
|
||||||
|
|
||||||
|
now func() time.Time // To mock out time.Now() for testing.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *counter) Desc() *Desc {
|
||||||
|
return c.desc
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *counter) Add(v float64) {
|
func (c *counter) Add(v float64) {
|
||||||
if v < 0 {
|
if v < 0 {
|
||||||
panic(errors.New("counter cannot decrease in value"))
|
panic(errors.New("counter cannot decrease in value"))
|
||||||
}
|
}
|
||||||
c.value.Add(v)
|
|
||||||
|
ival := uint64(v)
|
||||||
|
if float64(ival) == v {
|
||||||
|
atomic.AddUint64(&c.valInt, ival)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
oldBits := atomic.LoadUint64(&c.valBits)
|
||||||
|
newBits := math.Float64bits(math.Float64frombits(oldBits) + v)
|
||||||
|
if atomic.CompareAndSwapUint64(&c.valBits, oldBits, newBits) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *counter) AddWithExemplar(v float64, e Labels) {
|
||||||
|
c.Add(v)
|
||||||
|
c.updateExemplar(v, e)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *counter) Inc() {
|
||||||
|
atomic.AddUint64(&c.valInt, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *counter) Write(out *dto.Metric) error {
|
||||||
|
fval := math.Float64frombits(atomic.LoadUint64(&c.valBits))
|
||||||
|
ival := atomic.LoadUint64(&c.valInt)
|
||||||
|
val := fval + float64(ival)
|
||||||
|
|
||||||
|
var exemplar *dto.Exemplar
|
||||||
|
if e := c.exemplar.Load(); e != nil {
|
||||||
|
exemplar = e.(*dto.Exemplar)
|
||||||
|
}
|
||||||
|
|
||||||
|
return populateMetric(CounterValue, val, c.labelPairs, exemplar, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *counter) updateExemplar(v float64, l Labels) {
|
||||||
|
if l == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
e, err := newExemplar(v, c.now(), l)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
c.exemplar.Store(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CounterVec is a Collector that bundles a set of Counters that all share the
|
// CounterVec is a Collector that bundles a set of Counters that all share the
|
||||||
|
@ -78,16 +162,12 @@ func (c *counter) Add(v float64) {
|
||||||
// if you want to count the same thing partitioned by various dimensions
|
// if you want to count the same thing partitioned by various dimensions
|
||||||
// (e.g. number of HTTP requests, partitioned by response code and
|
// (e.g. number of HTTP requests, partitioned by response code and
|
||||||
// method). Create instances with NewCounterVec.
|
// method). Create instances with NewCounterVec.
|
||||||
//
|
|
||||||
// CounterVec embeds MetricVec. See there for a full list of methods with
|
|
||||||
// detailed documentation.
|
|
||||||
type CounterVec struct {
|
type CounterVec struct {
|
||||||
*MetricVec
|
*metricVec
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCounterVec creates a new CounterVec based on the provided CounterOpts and
|
// NewCounterVec creates a new CounterVec based on the provided CounterOpts and
|
||||||
// partitioned by the given label names. At least one label name must be
|
// partitioned by the given label names.
|
||||||
// provided.
|
|
||||||
func NewCounterVec(opts CounterOpts, labelNames []string) *CounterVec {
|
func NewCounterVec(opts CounterOpts, labelNames []string) *CounterVec {
|
||||||
desc := NewDesc(
|
desc := NewDesc(
|
||||||
BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
|
BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
|
||||||
|
@ -96,34 +176,62 @@ func NewCounterVec(opts CounterOpts, labelNames []string) *CounterVec {
|
||||||
opts.ConstLabels,
|
opts.ConstLabels,
|
||||||
)
|
)
|
||||||
return &CounterVec{
|
return &CounterVec{
|
||||||
MetricVec: newMetricVec(desc, func(lvs ...string) Metric {
|
metricVec: newMetricVec(desc, func(lvs ...string) Metric {
|
||||||
result := &counter{value: value{
|
if len(lvs) != len(desc.variableLabels) {
|
||||||
desc: desc,
|
panic(makeInconsistentCardinalityError(desc.fqName, desc.variableLabels, lvs))
|
||||||
valType: CounterValue,
|
}
|
||||||
labelPairs: makeLabelPairs(desc, lvs),
|
result := &counter{desc: desc, labelPairs: makeLabelPairs(desc, lvs), now: time.Now}
|
||||||
}}
|
|
||||||
result.init(result) // Init self-collection.
|
result.init(result) // Init self-collection.
|
||||||
return result
|
return result
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetMetricWithLabelValues replaces the method of the same name in
|
// GetMetricWithLabelValues returns the Counter for the given slice of label
|
||||||
// MetricVec. The difference is that this method returns a Counter and not a
|
// values (same order as the VariableLabels in Desc). If that combination of
|
||||||
// Metric so that no type conversion is required.
|
// label values is accessed for the first time, a new Counter is created.
|
||||||
func (m *CounterVec) GetMetricWithLabelValues(lvs ...string) (Counter, error) {
|
//
|
||||||
metric, err := m.MetricVec.GetMetricWithLabelValues(lvs...)
|
// It is possible to call this method without using the returned Counter to only
|
||||||
|
// create the new Counter but leave it at its starting value 0. See also the
|
||||||
|
// SummaryVec example.
|
||||||
|
//
|
||||||
|
// Keeping the Counter for later use is possible (and should be considered if
|
||||||
|
// performance is critical), but keep in mind that Reset, DeleteLabelValues and
|
||||||
|
// Delete can be used to delete the Counter from the CounterVec. In that case,
|
||||||
|
// the Counter will still exist, but it will not be exported anymore, even if a
|
||||||
|
// Counter with the same label values is created later.
|
||||||
|
//
|
||||||
|
// An error is returned if the number of label values is not the same as the
|
||||||
|
// number of VariableLabels in Desc (minus any curried labels).
|
||||||
|
//
|
||||||
|
// Note that for more than one label value, this method is prone to mistakes
|
||||||
|
// caused by an incorrect order of arguments. Consider GetMetricWith(Labels) as
|
||||||
|
// an alternative to avoid that type of mistake. For higher label numbers, the
|
||||||
|
// latter has a much more readable (albeit more verbose) syntax, but it comes
|
||||||
|
// with a performance overhead (for creating and processing the Labels map).
|
||||||
|
// See also the GaugeVec example.
|
||||||
|
func (v *CounterVec) GetMetricWithLabelValues(lvs ...string) (Counter, error) {
|
||||||
|
metric, err := v.metricVec.getMetricWithLabelValues(lvs...)
|
||||||
if metric != nil {
|
if metric != nil {
|
||||||
return metric.(Counter), err
|
return metric.(Counter), err
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetMetricWith replaces the method of the same name in MetricVec. The
|
// GetMetricWith returns the Counter for the given Labels map (the label names
|
||||||
// difference is that this method returns a Counter and not a Metric so that no
|
// must match those of the VariableLabels in Desc). If that label map is
|
||||||
// type conversion is required.
|
// accessed for the first time, a new Counter is created. Implications of
|
||||||
func (m *CounterVec) GetMetricWith(labels Labels) (Counter, error) {
|
// creating a Counter without using it and keeping the Counter for later use are
|
||||||
metric, err := m.MetricVec.GetMetricWith(labels)
|
// the same as for GetMetricWithLabelValues.
|
||||||
|
//
|
||||||
|
// An error is returned if the number and names of the Labels are inconsistent
|
||||||
|
// with those of the VariableLabels in Desc (minus any curried labels).
|
||||||
|
//
|
||||||
|
// This method is used for the same purpose as
|
||||||
|
// GetMetricWithLabelValues(...string). See there for pros and cons of the two
|
||||||
|
// methods.
|
||||||
|
func (v *CounterVec) GetMetricWith(labels Labels) (Counter, error) {
|
||||||
|
metric, err := v.metricVec.getMetricWith(labels)
|
||||||
if metric != nil {
|
if metric != nil {
|
||||||
return metric.(Counter), err
|
return metric.(Counter), err
|
||||||
}
|
}
|
||||||
|
@ -131,18 +239,57 @@ func (m *CounterVec) GetMetricWith(labels Labels) (Counter, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithLabelValues works as GetMetricWithLabelValues, but panics where
|
// WithLabelValues works as GetMetricWithLabelValues, but panics where
|
||||||
// GetMetricWithLabelValues would have returned an error. By not returning an
|
// GetMetricWithLabelValues would have returned an error. Not returning an
|
||||||
// error, WithLabelValues allows shortcuts like
|
// error allows shortcuts like
|
||||||
// myVec.WithLabelValues("404", "GET").Add(42)
|
// myVec.WithLabelValues("404", "GET").Add(42)
|
||||||
func (m *CounterVec) WithLabelValues(lvs ...string) Counter {
|
func (v *CounterVec) WithLabelValues(lvs ...string) Counter {
|
||||||
return m.MetricVec.WithLabelValues(lvs...).(Counter)
|
c, err := v.GetMetricWithLabelValues(lvs...)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
// With works as GetMetricWith, but panics where GetMetricWithLabels would have
|
// With works as GetMetricWith, but panics where GetMetricWithLabels would have
|
||||||
// returned an error. By not returning an error, With allows shortcuts like
|
// returned an error. Not returning an error allows shortcuts like
|
||||||
// myVec.With(Labels{"code": "404", "method": "GET"}).Add(42)
|
// myVec.With(prometheus.Labels{"code": "404", "method": "GET"}).Add(42)
|
||||||
func (m *CounterVec) With(labels Labels) Counter {
|
func (v *CounterVec) With(labels Labels) Counter {
|
||||||
return m.MetricVec.With(labels).(Counter)
|
c, err := v.GetMetricWith(labels)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// CurryWith returns a vector curried with the provided labels, i.e. the
|
||||||
|
// returned vector has those labels pre-set for all labeled operations performed
|
||||||
|
// on it. The cardinality of the curried vector is reduced accordingly. The
|
||||||
|
// order of the remaining labels stays the same (just with the curried labels
|
||||||
|
// taken out of the sequence – which is relevant for the
|
||||||
|
// (GetMetric)WithLabelValues methods). It is possible to curry a curried
|
||||||
|
// vector, but only with labels not yet used for currying before.
|
||||||
|
//
|
||||||
|
// The metrics contained in the CounterVec are shared between the curried and
|
||||||
|
// uncurried vectors. They are just accessed differently. Curried and uncurried
|
||||||
|
// vectors behave identically in terms of collection. Only one must be
|
||||||
|
// registered with a given registry (usually the uncurried version). The Reset
|
||||||
|
// method deletes all metrics, even if called on a curried vector.
|
||||||
|
func (v *CounterVec) CurryWith(labels Labels) (*CounterVec, error) {
|
||||||
|
vec, err := v.curryWith(labels)
|
||||||
|
if vec != nil {
|
||||||
|
return &CounterVec{vec}, err
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustCurryWith works as CurryWith but panics where CurryWith would have
|
||||||
|
// returned an error.
|
||||||
|
func (v *CounterVec) MustCurryWith(labels Labels) *CounterVec {
|
||||||
|
vec, err := v.CurryWith(labels)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return vec
|
||||||
}
|
}
|
||||||
|
|
||||||
// CounterFunc is a Counter whose value is determined at collect time by calling a
|
// CounterFunc is a Counter whose value is determined at collect time by calling a
|
||||||
|
@ -162,6 +309,8 @@ type CounterFunc interface {
|
||||||
// provided function must be concurrency-safe. The function should also honor
|
// provided function must be concurrency-safe. The function should also honor
|
||||||
// the contract for a Counter (values only go up, not down), but compliance will
|
// the contract for a Counter (values only go up, not down), but compliance will
|
||||||
// not be checked.
|
// not be checked.
|
||||||
|
//
|
||||||
|
// Check out the ExampleGaugeFunc examples for the similar GaugeFunc.
|
||||||
func NewCounterFunc(opts CounterOpts, function func() float64) CounterFunc {
|
func NewCounterFunc(opts CounterOpts, function func() float64) CounterFunc {
|
||||||
return newValueFunc(NewDesc(
|
return newValueFunc(NewDesc(
|
||||||
BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
|
BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
|
||||||
|
|
|
@ -16,33 +16,16 @@ package prometheus
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/cespare/xxhash/v2"
|
||||||
"github.com/golang/protobuf/proto"
|
"github.com/golang/protobuf/proto"
|
||||||
|
"github.com/prometheus/common/model"
|
||||||
|
|
||||||
dto "github.com/prometheus/client_model/go"
|
dto "github.com/prometheus/client_model/go"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
metricNameRE = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_:]*$`)
|
|
||||||
labelNameRE = regexp.MustCompile("^[a-zA-Z_][a-zA-Z0-9_]*$")
|
|
||||||
)
|
|
||||||
|
|
||||||
// reservedLabelPrefix is a prefix which is not legal in user-supplied
|
|
||||||
// label names.
|
|
||||||
const reservedLabelPrefix = "__"
|
|
||||||
|
|
||||||
// Labels represents a collection of label name -> value mappings. This type is
|
|
||||||
// commonly used with the With(Labels) and GetMetricWith(Labels) methods of
|
|
||||||
// metric vector Collectors, e.g.:
|
|
||||||
// myVec.With(Labels{"code": "404", "method": "GET"}).Add(42)
|
|
||||||
//
|
|
||||||
// The other use-case is the specification of constant label pairs in Opts or to
|
|
||||||
// create a Desc.
|
|
||||||
type Labels map[string]string
|
|
||||||
|
|
||||||
// Desc is the descriptor used by every Prometheus Metric. It is essentially
|
// Desc is the descriptor used by every Prometheus Metric. It is essentially
|
||||||
// the immutable meta-data of a Metric. The normal Metric implementations
|
// the immutable meta-data of a Metric. The normal Metric implementations
|
||||||
// included in this package manage their Desc under the hood. Users only have to
|
// included in this package manage their Desc under the hood. Users only have to
|
||||||
|
@ -78,32 +61,27 @@ type Desc struct {
|
||||||
// Help string. Each Desc with the same fqName must have the same
|
// Help string. Each Desc with the same fqName must have the same
|
||||||
// dimHash.
|
// dimHash.
|
||||||
dimHash uint64
|
dimHash uint64
|
||||||
// err is an error that occured during construction. It is reported on
|
// err is an error that occurred during construction. It is reported on
|
||||||
// registration time.
|
// registration time.
|
||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDesc allocates and initializes a new Desc. Errors are recorded in the Desc
|
// NewDesc allocates and initializes a new Desc. Errors are recorded in the Desc
|
||||||
// and will be reported on registration time. variableLabels and constLabels can
|
// and will be reported on registration time. variableLabels and constLabels can
|
||||||
// be nil if no such labels should be set. fqName and help must not be empty.
|
// be nil if no such labels should be set. fqName must not be empty.
|
||||||
//
|
//
|
||||||
// variableLabels only contain the label names. Their label values are variable
|
// variableLabels only contain the label names. Their label values are variable
|
||||||
// and therefore not part of the Desc. (They are managed within the Metric.)
|
// and therefore not part of the Desc. (They are managed within the Metric.)
|
||||||
//
|
//
|
||||||
// For constLabels, the label values are constant. Therefore, they are fully
|
// For constLabels, the label values are constant. Therefore, they are fully
|
||||||
// specified in the Desc. See the Opts documentation for the implications of
|
// specified in the Desc. See the Collector example for a usage pattern.
|
||||||
// constant labels.
|
|
||||||
func NewDesc(fqName, help string, variableLabels []string, constLabels Labels) *Desc {
|
func NewDesc(fqName, help string, variableLabels []string, constLabels Labels) *Desc {
|
||||||
d := &Desc{
|
d := &Desc{
|
||||||
fqName: fqName,
|
fqName: fqName,
|
||||||
help: help,
|
help: help,
|
||||||
variableLabels: variableLabels,
|
variableLabels: variableLabels,
|
||||||
}
|
}
|
||||||
if help == "" {
|
if !model.IsValidMetricName(model.LabelValue(fqName)) {
|
||||||
d.err = errors.New("empty help string")
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
if !metricNameRE.MatchString(fqName) {
|
|
||||||
d.err = fmt.Errorf("%q is not a valid metric name", fqName)
|
d.err = fmt.Errorf("%q is not a valid metric name", fqName)
|
||||||
return d
|
return d
|
||||||
}
|
}
|
||||||
|
@ -116,7 +94,7 @@ func NewDesc(fqName, help string, variableLabels []string, constLabels Labels) *
|
||||||
// First add only the const label names and sort them...
|
// First add only the const label names and sort them...
|
||||||
for labelName := range constLabels {
|
for labelName := range constLabels {
|
||||||
if !checkLabelName(labelName) {
|
if !checkLabelName(labelName) {
|
||||||
d.err = fmt.Errorf("%q is not a valid label name", labelName)
|
d.err = fmt.Errorf("%q is not a valid label name for metric %q", labelName, fqName)
|
||||||
return d
|
return d
|
||||||
}
|
}
|
||||||
labelNames = append(labelNames, labelName)
|
labelNames = append(labelNames, labelName)
|
||||||
|
@ -127,12 +105,18 @@ func NewDesc(fqName, help string, variableLabels []string, constLabels Labels) *
|
||||||
for _, labelName := range labelNames {
|
for _, labelName := range labelNames {
|
||||||
labelValues = append(labelValues, constLabels[labelName])
|
labelValues = append(labelValues, constLabels[labelName])
|
||||||
}
|
}
|
||||||
|
// Validate the const label values. They can't have a wrong cardinality, so
|
||||||
|
// use in len(labelValues) as expectedNumberOfValues.
|
||||||
|
if err := validateLabelValues(labelValues, len(labelValues)); err != nil {
|
||||||
|
d.err = err
|
||||||
|
return d
|
||||||
|
}
|
||||||
// Now add the variable label names, but prefix them with something that
|
// Now add the variable label names, but prefix them with something that
|
||||||
// cannot be in a regular label name. That prevents matching the label
|
// cannot be in a regular label name. That prevents matching the label
|
||||||
// dimension with a different mix between preset and variable labels.
|
// dimension with a different mix between preset and variable labels.
|
||||||
for _, labelName := range variableLabels {
|
for _, labelName := range variableLabels {
|
||||||
if !checkLabelName(labelName) {
|
if !checkLabelName(labelName) {
|
||||||
d.err = fmt.Errorf("%q is not a valid label name", labelName)
|
d.err = fmt.Errorf("%q is not a valid label name for metric %q", labelName, fqName)
|
||||||
return d
|
return d
|
||||||
}
|
}
|
||||||
labelNames = append(labelNames, "$"+labelName)
|
labelNames = append(labelNames, "$"+labelName)
|
||||||
|
@ -142,24 +126,25 @@ func NewDesc(fqName, help string, variableLabels []string, constLabels Labels) *
|
||||||
d.err = errors.New("duplicate label names")
|
d.err = errors.New("duplicate label names")
|
||||||
return d
|
return d
|
||||||
}
|
}
|
||||||
vh := hashNew()
|
|
||||||
|
xxh := xxhash.New()
|
||||||
for _, val := range labelValues {
|
for _, val := range labelValues {
|
||||||
vh = hashAdd(vh, val)
|
xxh.WriteString(val)
|
||||||
vh = hashAddByte(vh, separatorByte)
|
xxh.Write(separatorByteSlice)
|
||||||
}
|
}
|
||||||
d.id = vh
|
d.id = xxh.Sum64()
|
||||||
// Sort labelNames so that order doesn't matter for the hash.
|
// Sort labelNames so that order doesn't matter for the hash.
|
||||||
sort.Strings(labelNames)
|
sort.Strings(labelNames)
|
||||||
// Now hash together (in this order) the help string and the sorted
|
// Now hash together (in this order) the help string and the sorted
|
||||||
// label names.
|
// label names.
|
||||||
lh := hashNew()
|
xxh.Reset()
|
||||||
lh = hashAdd(lh, help)
|
xxh.WriteString(help)
|
||||||
lh = hashAddByte(lh, separatorByte)
|
xxh.Write(separatorByteSlice)
|
||||||
for _, labelName := range labelNames {
|
for _, labelName := range labelNames {
|
||||||
lh = hashAdd(lh, labelName)
|
xxh.WriteString(labelName)
|
||||||
lh = hashAddByte(lh, separatorByte)
|
xxh.Write(separatorByteSlice)
|
||||||
}
|
}
|
||||||
d.dimHash = lh
|
d.dimHash = xxh.Sum64()
|
||||||
|
|
||||||
d.constLabelPairs = make([]*dto.LabelPair, 0, len(constLabels))
|
d.constLabelPairs = make([]*dto.LabelPair, 0, len(constLabels))
|
||||||
for n, v := range constLabels {
|
for n, v := range constLabels {
|
||||||
|
@ -168,7 +153,7 @@ func NewDesc(fqName, help string, variableLabels []string, constLabels Labels) *
|
||||||
Value: proto.String(v),
|
Value: proto.String(v),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
sort.Sort(LabelPairSorter(d.constLabelPairs))
|
sort.Sort(labelPairSorter(d.constLabelPairs))
|
||||||
return d
|
return d
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -198,8 +183,3 @@ func (d *Desc) String() string {
|
||||||
d.variableLabels,
|
d.variableLabels,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkLabelName(l string) bool {
|
|
||||||
return labelNameRE.MatchString(l) &&
|
|
||||||
!strings.HasPrefix(l, reservedLabelPrefix)
|
|
||||||
}
|
|
||||||
|
|
|
@ -11,13 +11,15 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
// Package prometheus provides metrics primitives to instrument code for
|
// Package prometheus is the core instrumentation package. It provides metrics
|
||||||
// monitoring. It also offers a registry for metrics. Sub-packages allow to
|
// primitives to instrument code for monitoring. It also offers a registry for
|
||||||
// expose the registered metrics via HTTP (package promhttp) or push them to a
|
// metrics. Sub-packages allow to expose the registered metrics via HTTP
|
||||||
// Pushgateway (package push).
|
// (package promhttp) or push them to a Pushgateway (package push). There is
|
||||||
|
// also a sub-package promauto, which provides metrics constructors with
|
||||||
|
// automatic registration.
|
||||||
//
|
//
|
||||||
// All exported functions and methods are safe to be used concurrently unless
|
// All exported functions and methods are safe to be used concurrently unless
|
||||||
//specified otherwise.
|
// specified otherwise.
|
||||||
//
|
//
|
||||||
// A Basic Example
|
// A Basic Example
|
||||||
//
|
//
|
||||||
|
@ -26,6 +28,7 @@
|
||||||
// package main
|
// package main
|
||||||
//
|
//
|
||||||
// import (
|
// import (
|
||||||
|
// "log"
|
||||||
// "net/http"
|
// "net/http"
|
||||||
//
|
//
|
||||||
// "github.com/prometheus/client_golang/prometheus"
|
// "github.com/prometheus/client_golang/prometheus"
|
||||||
|
@ -59,7 +62,7 @@
|
||||||
// // The Handler function provides a default handler to expose metrics
|
// // The Handler function provides a default handler to expose metrics
|
||||||
// // via an HTTP server. "/metrics" is the usual endpoint for that.
|
// // via an HTTP server. "/metrics" is the usual endpoint for that.
|
||||||
// http.Handle("/metrics", promhttp.Handler())
|
// http.Handle("/metrics", promhttp.Handler())
|
||||||
// http.ListenAndServe(":8080", nil)
|
// log.Fatal(http.ListenAndServe(":8080", nil))
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
//
|
//
|
||||||
|
@ -69,34 +72,33 @@
|
||||||
// Metrics
|
// Metrics
|
||||||
//
|
//
|
||||||
// The number of exported identifiers in this package might appear a bit
|
// The number of exported identifiers in this package might appear a bit
|
||||||
// overwhelming. Hovever, in addition to the basic plumbing shown in the example
|
// overwhelming. However, in addition to the basic plumbing shown in the example
|
||||||
// above, you only need to understand the different metric types and their
|
// above, you only need to understand the different metric types and their
|
||||||
// vector versions for basic usage.
|
// vector versions for basic usage. Furthermore, if you are not concerned with
|
||||||
|
// fine-grained control of when and how to register metrics with the registry,
|
||||||
|
// have a look at the promauto package, which will effectively allow you to
|
||||||
|
// ignore registration altogether in simple cases.
|
||||||
//
|
//
|
||||||
// Above, you have already touched the Counter and the Gauge. There are two more
|
// Above, you have already touched the Counter and the Gauge. There are two more
|
||||||
// advanced metric types: the Summary and Histogram. A more thorough description
|
// advanced metric types: the Summary and Histogram. A more thorough description
|
||||||
// of those four metric types can be found in the Prometheus docs:
|
// of those four metric types can be found in the Prometheus docs:
|
||||||
// https://prometheus.io/docs/concepts/metric_types/
|
// https://prometheus.io/docs/concepts/metric_types/
|
||||||
//
|
//
|
||||||
// A fifth "type" of metric is Untyped. It behaves like a Gauge, but signals the
|
// In addition to the fundamental metric types Gauge, Counter, Summary, and
|
||||||
// Prometheus server not to assume anything about its type.
|
// Histogram, a very important part of the Prometheus data model is the
|
||||||
//
|
// partitioning of samples along dimensions called labels, which results in
|
||||||
// In addition to the fundamental metric types Gauge, Counter, Summary,
|
|
||||||
// Histogram, and Untyped, a very important part of the Prometheus data model is
|
|
||||||
// the partitioning of samples along dimensions called labels, which results in
|
|
||||||
// metric vectors. The fundamental types are GaugeVec, CounterVec, SummaryVec,
|
// metric vectors. The fundamental types are GaugeVec, CounterVec, SummaryVec,
|
||||||
// HistogramVec, and UntypedVec.
|
// and HistogramVec.
|
||||||
//
|
//
|
||||||
// While only the fundamental metric types implement the Metric interface, both
|
// While only the fundamental metric types implement the Metric interface, both
|
||||||
// the metrics and their vector versions implement the Collector interface. A
|
// the metrics and their vector versions implement the Collector interface. A
|
||||||
// Collector manages the collection of a number of Metrics, but for convenience,
|
// Collector manages the collection of a number of Metrics, but for convenience,
|
||||||
// a Metric can also “collect itself”. Note that Gauge, Counter, Summary,
|
// a Metric can also “collect itself”. Note that Gauge, Counter, Summary, and
|
||||||
// Histogram, and Untyped are interfaces themselves while GaugeVec, CounterVec,
|
// Histogram are interfaces themselves while GaugeVec, CounterVec, SummaryVec,
|
||||||
// SummaryVec, HistogramVec, and UntypedVec are not.
|
// and HistogramVec are not.
|
||||||
//
|
//
|
||||||
// To create instances of Metrics and their vector versions, you need a suitable
|
// To create instances of Metrics and their vector versions, you need a suitable
|
||||||
// …Opts struct, i.e. GaugeOpts, CounterOpts, SummaryOpts,
|
// …Opts struct, i.e. GaugeOpts, CounterOpts, SummaryOpts, or HistogramOpts.
|
||||||
// HistogramOpts, or UntypedOpts.
|
|
||||||
//
|
//
|
||||||
// Custom Collectors and constant Metrics
|
// Custom Collectors and constant Metrics
|
||||||
//
|
//
|
||||||
|
@ -112,10 +114,23 @@
|
||||||
// existing numbers into Prometheus Metrics during collection. An own
|
// existing numbers into Prometheus Metrics during collection. An own
|
||||||
// implementation of the Collector interface is perfect for that. You can create
|
// implementation of the Collector interface is perfect for that. You can create
|
||||||
// Metric instances “on the fly” using NewConstMetric, NewConstHistogram, and
|
// Metric instances “on the fly” using NewConstMetric, NewConstHistogram, and
|
||||||
// NewConstSummary (and their respective Must… versions). That will happen in
|
// NewConstSummary (and their respective Must… versions). NewConstMetric is used
|
||||||
// the Collect method. The Describe method has to return separate Desc
|
// for all metric types with just a float64 as their value: Counter, Gauge, and
|
||||||
// instances, representative of the “throw-away” metrics to be created
|
// a special “type” called Untyped. Use the latter if you are not sure if the
|
||||||
// later. NewDesc comes in handy to create those Desc instances.
|
// mirrored metric is a Counter or a Gauge. Creation of the Metric instance
|
||||||
|
// happens in the Collect method. The Describe method has to return separate
|
||||||
|
// Desc instances, representative of the “throw-away” metrics to be created
|
||||||
|
// later. NewDesc comes in handy to create those Desc instances. Alternatively,
|
||||||
|
// you could return no Desc at all, which will mark the Collector “unchecked”.
|
||||||
|
// No checks are performed at registration time, but metric consistency will
|
||||||
|
// still be ensured at scrape time, i.e. any inconsistencies will lead to scrape
|
||||||
|
// errors. Thus, with unchecked Collectors, the responsibility to not collect
|
||||||
|
// metrics that lead to inconsistencies in the total scrape result lies with the
|
||||||
|
// implementer of the Collector. While this is not a desirable state, it is
|
||||||
|
// sometimes necessary. The typical use case is a situation where the exact
|
||||||
|
// metrics to be returned by a Collector cannot be predicted at registration
|
||||||
|
// time, but the implementer has sufficient knowledge of the whole system to
|
||||||
|
// guarantee metric consistency.
|
||||||
//
|
//
|
||||||
// The Collector example illustrates the use case. You can also look at the
|
// The Collector example illustrates the use case. You can also look at the
|
||||||
// source code of the processCollector (mirroring process metrics), the
|
// source code of the processCollector (mirroring process metrics), the
|
||||||
|
@ -129,34 +144,34 @@
|
||||||
// Advanced Uses of the Registry
|
// Advanced Uses of the Registry
|
||||||
//
|
//
|
||||||
// While MustRegister is the by far most common way of registering a Collector,
|
// While MustRegister is the by far most common way of registering a Collector,
|
||||||
// sometimes you might want to handle the errors the registration might
|
// sometimes you might want to handle the errors the registration might cause.
|
||||||
// cause. As suggested by the name, MustRegister panics if an error occurs. With
|
// As suggested by the name, MustRegister panics if an error occurs. With the
|
||||||
// the Register function, the error is returned and can be handled.
|
// Register function, the error is returned and can be handled.
|
||||||
//
|
//
|
||||||
// An error is returned if the registered Collector is incompatible or
|
// An error is returned if the registered Collector is incompatible or
|
||||||
// inconsistent with already registered metrics. The registry aims for
|
// inconsistent with already registered metrics. The registry aims for
|
||||||
// consistency of the collected metrics according to the Prometheus data
|
// consistency of the collected metrics according to the Prometheus data model.
|
||||||
// model. Inconsistencies are ideally detected at registration time, not at
|
// Inconsistencies are ideally detected at registration time, not at collect
|
||||||
// collect time. The former will usually be detected at start-up time of a
|
// time. The former will usually be detected at start-up time of a program,
|
||||||
// program, while the latter will only happen at scrape time, possibly not even
|
// while the latter will only happen at scrape time, possibly not even on the
|
||||||
// on the first scrape if the inconsistency only becomes relevant later. That is
|
// first scrape if the inconsistency only becomes relevant later. That is the
|
||||||
// the main reason why a Collector and a Metric have to describe themselves to
|
// main reason why a Collector and a Metric have to describe themselves to the
|
||||||
// the registry.
|
// registry.
|
||||||
//
|
//
|
||||||
// So far, everything we did operated on the so-called default registry, as it
|
// So far, everything we did operated on the so-called default registry, as it
|
||||||
// can be found in the global DefaultRegistry variable. With NewRegistry, you
|
// can be found in the global DefaultRegisterer variable. With NewRegistry, you
|
||||||
// can create a custom registry, or you can even implement the Registerer or
|
// can create a custom registry, or you can even implement the Registerer or
|
||||||
// Gatherer interfaces yourself. The methods Register and Unregister work in
|
// Gatherer interfaces yourself. The methods Register and Unregister work in the
|
||||||
// the same way on a custom registry as the global functions Register and
|
// same way on a custom registry as the global functions Register and Unregister
|
||||||
// Unregister on the default registry.
|
// on the default registry.
|
||||||
//
|
//
|
||||||
// There are a number of uses for custom registries: You can use registries
|
// There are a number of uses for custom registries: You can use registries with
|
||||||
// with special properties, see NewPedanticRegistry. You can avoid global state,
|
// special properties, see NewPedanticRegistry. You can avoid global state, as
|
||||||
// as it is imposed by the DefaultRegistry. You can use multiple registries at
|
// it is imposed by the DefaultRegisterer. You can use multiple registries at
|
||||||
// the same time to expose different metrics in different ways. You can use
|
// the same time to expose different metrics in different ways. You can use
|
||||||
// separate registries for testing purposes.
|
// separate registries for testing purposes.
|
||||||
//
|
//
|
||||||
// Also note that the DefaultRegistry comes registered with a Collector for Go
|
// Also note that the DefaultRegisterer comes registered with a Collector for Go
|
||||||
// runtime metrics (via NewGoCollector) and a Collector for process metrics (via
|
// runtime metrics (via NewGoCollector) and a Collector for process metrics (via
|
||||||
// NewProcessCollector). With a custom registry, you are in control and decide
|
// NewProcessCollector). With a custom registry, you are in control and decide
|
||||||
// yourself about the Collectors to register.
|
// yourself about the Collectors to register.
|
||||||
|
@ -166,16 +181,19 @@
|
||||||
// The Registry implements the Gatherer interface. The caller of the Gather
|
// The Registry implements the Gatherer interface. The caller of the Gather
|
||||||
// method can then expose the gathered metrics in some way. Usually, the metrics
|
// method can then expose the gathered metrics in some way. Usually, the metrics
|
||||||
// are served via HTTP on the /metrics endpoint. That's happening in the example
|
// are served via HTTP on the /metrics endpoint. That's happening in the example
|
||||||
// above. The tools to expose metrics via HTTP are in the promhttp
|
// above. The tools to expose metrics via HTTP are in the promhttp sub-package.
|
||||||
// sub-package. (The top-level functions in the prometheus package are
|
|
||||||
// deprecated.)
|
|
||||||
//
|
//
|
||||||
// Pushing to the Pushgateway
|
// Pushing to the Pushgateway
|
||||||
//
|
//
|
||||||
// Function for pushing to the Pushgateway can be found in the push sub-package.
|
// Function for pushing to the Pushgateway can be found in the push sub-package.
|
||||||
//
|
//
|
||||||
|
// Graphite Bridge
|
||||||
|
//
|
||||||
|
// Functions and examples to push metrics from a Gatherer to Graphite can be
|
||||||
|
// found in the graphite sub-package.
|
||||||
|
//
|
||||||
// Other Means of Exposition
|
// Other Means of Exposition
|
||||||
//
|
//
|
||||||
// More ways of exposing metrics can easily be added. Sending metrics to
|
// More ways of exposing metrics can easily be added by following the approaches
|
||||||
// Graphite would be an example that will soon be implemented.
|
// of the existing implementations.
|
||||||
package prometheus
|
package prometheus
|
||||||
|
|
|
@ -1,3 +1,16 @@
|
||||||
|
// Copyright 2018 The Prometheus Authors
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
package prometheus
|
package prometheus
|
||||||
|
|
||||||
// Inline and byte-free variant of hash/fnv's fnv64a.
|
// Inline and byte-free variant of hash/fnv's fnv64a.
|
||||||
|
|
|
@ -13,6 +13,14 @@
|
||||||
|
|
||||||
package prometheus
|
package prometheus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
dto "github.com/prometheus/client_model/go"
|
||||||
|
)
|
||||||
|
|
||||||
// Gauge is a Metric that represents a single numerical value that can
|
// Gauge is a Metric that represents a single numerical value that can
|
||||||
// arbitrarily go up and down.
|
// arbitrarily go up and down.
|
||||||
//
|
//
|
||||||
|
@ -27,29 +35,95 @@ type Gauge interface {
|
||||||
|
|
||||||
// Set sets the Gauge to an arbitrary value.
|
// Set sets the Gauge to an arbitrary value.
|
||||||
Set(float64)
|
Set(float64)
|
||||||
// Inc increments the Gauge by 1.
|
// Inc increments the Gauge by 1. Use Add to increment it by arbitrary
|
||||||
|
// values.
|
||||||
Inc()
|
Inc()
|
||||||
// Dec decrements the Gauge by 1.
|
// Dec decrements the Gauge by 1. Use Sub to decrement it by arbitrary
|
||||||
|
// values.
|
||||||
Dec()
|
Dec()
|
||||||
// Add adds the given value to the Gauge. (The value can be
|
// Add adds the given value to the Gauge. (The value can be negative,
|
||||||
// negative, resulting in a decrease of the Gauge.)
|
// resulting in a decrease of the Gauge.)
|
||||||
Add(float64)
|
Add(float64)
|
||||||
// Sub subtracts the given value from the Gauge. (The value can be
|
// Sub subtracts the given value from the Gauge. (The value can be
|
||||||
// negative, resulting in an increase of the Gauge.)
|
// negative, resulting in an increase of the Gauge.)
|
||||||
Sub(float64)
|
Sub(float64)
|
||||||
|
|
||||||
|
// SetToCurrentTime sets the Gauge to the current Unix time in seconds.
|
||||||
|
SetToCurrentTime()
|
||||||
}
|
}
|
||||||
|
|
||||||
// GaugeOpts is an alias for Opts. See there for doc comments.
|
// GaugeOpts is an alias for Opts. See there for doc comments.
|
||||||
type GaugeOpts Opts
|
type GaugeOpts Opts
|
||||||
|
|
||||||
// NewGauge creates a new Gauge based on the provided GaugeOpts.
|
// NewGauge creates a new Gauge based on the provided GaugeOpts.
|
||||||
|
//
|
||||||
|
// The returned implementation is optimized for a fast Set method. If you have a
|
||||||
|
// choice for managing the value of a Gauge via Set vs. Inc/Dec/Add/Sub, pick
|
||||||
|
// the former. For example, the Inc method of the returned Gauge is slower than
|
||||||
|
// the Inc method of a Counter returned by NewCounter. This matches the typical
|
||||||
|
// scenarios for Gauges and Counters, where the former tends to be Set-heavy and
|
||||||
|
// the latter Inc-heavy.
|
||||||
func NewGauge(opts GaugeOpts) Gauge {
|
func NewGauge(opts GaugeOpts) Gauge {
|
||||||
return newValue(NewDesc(
|
desc := NewDesc(
|
||||||
BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
|
BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
|
||||||
opts.Help,
|
opts.Help,
|
||||||
nil,
|
nil,
|
||||||
opts.ConstLabels,
|
opts.ConstLabels,
|
||||||
), GaugeValue, 0)
|
)
|
||||||
|
result := &gauge{desc: desc, labelPairs: desc.constLabelPairs}
|
||||||
|
result.init(result) // Init self-collection.
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
type gauge struct {
|
||||||
|
// valBits contains the bits of the represented float64 value. It has
|
||||||
|
// to go first in the struct to guarantee alignment for atomic
|
||||||
|
// operations. http://golang.org/pkg/sync/atomic/#pkg-note-BUG
|
||||||
|
valBits uint64
|
||||||
|
|
||||||
|
selfCollector
|
||||||
|
|
||||||
|
desc *Desc
|
||||||
|
labelPairs []*dto.LabelPair
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *gauge) Desc() *Desc {
|
||||||
|
return g.desc
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *gauge) Set(val float64) {
|
||||||
|
atomic.StoreUint64(&g.valBits, math.Float64bits(val))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *gauge) SetToCurrentTime() {
|
||||||
|
g.Set(float64(time.Now().UnixNano()) / 1e9)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *gauge) Inc() {
|
||||||
|
g.Add(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *gauge) Dec() {
|
||||||
|
g.Add(-1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *gauge) Add(val float64) {
|
||||||
|
for {
|
||||||
|
oldBits := atomic.LoadUint64(&g.valBits)
|
||||||
|
newBits := math.Float64bits(math.Float64frombits(oldBits) + val)
|
||||||
|
if atomic.CompareAndSwapUint64(&g.valBits, oldBits, newBits) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *gauge) Sub(val float64) {
|
||||||
|
g.Add(val * -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *gauge) Write(out *dto.Metric) error {
|
||||||
|
val := math.Float64frombits(atomic.LoadUint64(&g.valBits))
|
||||||
|
return populateMetric(GaugeValue, val, g.labelPairs, nil, out)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GaugeVec is a Collector that bundles a set of Gauges that all share the same
|
// GaugeVec is a Collector that bundles a set of Gauges that all share the same
|
||||||
|
@ -58,12 +132,11 @@ func NewGauge(opts GaugeOpts) Gauge {
|
||||||
// (e.g. number of operations queued, partitioned by user and operation
|
// (e.g. number of operations queued, partitioned by user and operation
|
||||||
// type). Create instances with NewGaugeVec.
|
// type). Create instances with NewGaugeVec.
|
||||||
type GaugeVec struct {
|
type GaugeVec struct {
|
||||||
*MetricVec
|
*metricVec
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewGaugeVec creates a new GaugeVec based on the provided GaugeOpts and
|
// NewGaugeVec creates a new GaugeVec based on the provided GaugeOpts and
|
||||||
// partitioned by the given label names. At least one label name must be
|
// partitioned by the given label names.
|
||||||
// provided.
|
|
||||||
func NewGaugeVec(opts GaugeOpts, labelNames []string) *GaugeVec {
|
func NewGaugeVec(opts GaugeOpts, labelNames []string) *GaugeVec {
|
||||||
desc := NewDesc(
|
desc := NewDesc(
|
||||||
BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
|
BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
|
||||||
|
@ -72,28 +145,62 @@ func NewGaugeVec(opts GaugeOpts, labelNames []string) *GaugeVec {
|
||||||
opts.ConstLabels,
|
opts.ConstLabels,
|
||||||
)
|
)
|
||||||
return &GaugeVec{
|
return &GaugeVec{
|
||||||
MetricVec: newMetricVec(desc, func(lvs ...string) Metric {
|
metricVec: newMetricVec(desc, func(lvs ...string) Metric {
|
||||||
return newValue(desc, GaugeValue, 0, lvs...)
|
if len(lvs) != len(desc.variableLabels) {
|
||||||
|
panic(makeInconsistentCardinalityError(desc.fqName, desc.variableLabels, lvs))
|
||||||
|
}
|
||||||
|
result := &gauge{desc: desc, labelPairs: makeLabelPairs(desc, lvs)}
|
||||||
|
result.init(result) // Init self-collection.
|
||||||
|
return result
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetMetricWithLabelValues replaces the method of the same name in
|
// GetMetricWithLabelValues returns the Gauge for the given slice of label
|
||||||
// MetricVec. The difference is that this method returns a Gauge and not a
|
// values (same order as the VariableLabels in Desc). If that combination of
|
||||||
// Metric so that no type conversion is required.
|
// label values is accessed for the first time, a new Gauge is created.
|
||||||
func (m *GaugeVec) GetMetricWithLabelValues(lvs ...string) (Gauge, error) {
|
//
|
||||||
metric, err := m.MetricVec.GetMetricWithLabelValues(lvs...)
|
// It is possible to call this method without using the returned Gauge to only
|
||||||
|
// create the new Gauge but leave it at its starting value 0. See also the
|
||||||
|
// SummaryVec example.
|
||||||
|
//
|
||||||
|
// Keeping the Gauge for later use is possible (and should be considered if
|
||||||
|
// performance is critical), but keep in mind that Reset, DeleteLabelValues and
|
||||||
|
// Delete can be used to delete the Gauge from the GaugeVec. In that case, the
|
||||||
|
// Gauge will still exist, but it will not be exported anymore, even if a
|
||||||
|
// Gauge with the same label values is created later. See also the CounterVec
|
||||||
|
// example.
|
||||||
|
//
|
||||||
|
// An error is returned if the number of label values is not the same as the
|
||||||
|
// number of VariableLabels in Desc (minus any curried labels).
|
||||||
|
//
|
||||||
|
// Note that for more than one label value, this method is prone to mistakes
|
||||||
|
// caused by an incorrect order of arguments. Consider GetMetricWith(Labels) as
|
||||||
|
// an alternative to avoid that type of mistake. For higher label numbers, the
|
||||||
|
// latter has a much more readable (albeit more verbose) syntax, but it comes
|
||||||
|
// with a performance overhead (for creating and processing the Labels map).
|
||||||
|
func (v *GaugeVec) GetMetricWithLabelValues(lvs ...string) (Gauge, error) {
|
||||||
|
metric, err := v.metricVec.getMetricWithLabelValues(lvs...)
|
||||||
if metric != nil {
|
if metric != nil {
|
||||||
return metric.(Gauge), err
|
return metric.(Gauge), err
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetMetricWith replaces the method of the same name in MetricVec. The
|
// GetMetricWith returns the Gauge for the given Labels map (the label names
|
||||||
// difference is that this method returns a Gauge and not a Metric so that no
|
// must match those of the VariableLabels in Desc). If that label map is
|
||||||
// type conversion is required.
|
// accessed for the first time, a new Gauge is created. Implications of
|
||||||
func (m *GaugeVec) GetMetricWith(labels Labels) (Gauge, error) {
|
// creating a Gauge without using it and keeping the Gauge for later use are
|
||||||
metric, err := m.MetricVec.GetMetricWith(labels)
|
// the same as for GetMetricWithLabelValues.
|
||||||
|
//
|
||||||
|
// An error is returned if the number and names of the Labels are inconsistent
|
||||||
|
// with those of the VariableLabels in Desc (minus any curried labels).
|
||||||
|
//
|
||||||
|
// This method is used for the same purpose as
|
||||||
|
// GetMetricWithLabelValues(...string). See there for pros and cons of the two
|
||||||
|
// methods.
|
||||||
|
func (v *GaugeVec) GetMetricWith(labels Labels) (Gauge, error) {
|
||||||
|
metric, err := v.metricVec.getMetricWith(labels)
|
||||||
if metric != nil {
|
if metric != nil {
|
||||||
return metric.(Gauge), err
|
return metric.(Gauge), err
|
||||||
}
|
}
|
||||||
|
@ -101,18 +208,57 @@ func (m *GaugeVec) GetMetricWith(labels Labels) (Gauge, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithLabelValues works as GetMetricWithLabelValues, but panics where
|
// WithLabelValues works as GetMetricWithLabelValues, but panics where
|
||||||
// GetMetricWithLabelValues would have returned an error. By not returning an
|
// GetMetricWithLabelValues would have returned an error. Not returning an
|
||||||
// error, WithLabelValues allows shortcuts like
|
// error allows shortcuts like
|
||||||
// myVec.WithLabelValues("404", "GET").Add(42)
|
// myVec.WithLabelValues("404", "GET").Add(42)
|
||||||
func (m *GaugeVec) WithLabelValues(lvs ...string) Gauge {
|
func (v *GaugeVec) WithLabelValues(lvs ...string) Gauge {
|
||||||
return m.MetricVec.WithLabelValues(lvs...).(Gauge)
|
g, err := v.GetMetricWithLabelValues(lvs...)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return g
|
||||||
}
|
}
|
||||||
|
|
||||||
// With works as GetMetricWith, but panics where GetMetricWithLabels would have
|
// With works as GetMetricWith, but panics where GetMetricWithLabels would have
|
||||||
// returned an error. By not returning an error, With allows shortcuts like
|
// returned an error. Not returning an error allows shortcuts like
|
||||||
// myVec.With(Labels{"code": "404", "method": "GET"}).Add(42)
|
// myVec.With(prometheus.Labels{"code": "404", "method": "GET"}).Add(42)
|
||||||
func (m *GaugeVec) With(labels Labels) Gauge {
|
func (v *GaugeVec) With(labels Labels) Gauge {
|
||||||
return m.MetricVec.With(labels).(Gauge)
|
g, err := v.GetMetricWith(labels)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return g
|
||||||
|
}
|
||||||
|
|
||||||
|
// CurryWith returns a vector curried with the provided labels, i.e. the
|
||||||
|
// returned vector has those labels pre-set for all labeled operations performed
|
||||||
|
// on it. The cardinality of the curried vector is reduced accordingly. The
|
||||||
|
// order of the remaining labels stays the same (just with the curried labels
|
||||||
|
// taken out of the sequence – which is relevant for the
|
||||||
|
// (GetMetric)WithLabelValues methods). It is possible to curry a curried
|
||||||
|
// vector, but only with labels not yet used for currying before.
|
||||||
|
//
|
||||||
|
// The metrics contained in the GaugeVec are shared between the curried and
|
||||||
|
// uncurried vectors. They are just accessed differently. Curried and uncurried
|
||||||
|
// vectors behave identically in terms of collection. Only one must be
|
||||||
|
// registered with a given registry (usually the uncurried version). The Reset
|
||||||
|
// method deletes all metrics, even if called on a curried vector.
|
||||||
|
func (v *GaugeVec) CurryWith(labels Labels) (*GaugeVec, error) {
|
||||||
|
vec, err := v.curryWith(labels)
|
||||||
|
if vec != nil {
|
||||||
|
return &GaugeVec{vec}, err
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustCurryWith works as CurryWith but panics where CurryWith would have
|
||||||
|
// returned an error.
|
||||||
|
func (v *GaugeVec) MustCurryWith(labels Labels) *GaugeVec {
|
||||||
|
vec, err := v.CurryWith(labels)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return vec
|
||||||
}
|
}
|
||||||
|
|
||||||
// GaugeFunc is a Gauge whose value is determined at collect time by calling a
|
// GaugeFunc is a Gauge whose value is determined at collect time by calling a
|
||||||
|
@ -127,9 +273,12 @@ type GaugeFunc interface {
|
||||||
// NewGaugeFunc creates a new GaugeFunc based on the provided GaugeOpts. The
|
// NewGaugeFunc creates a new GaugeFunc based on the provided GaugeOpts. The
|
||||||
// value reported is determined by calling the given function from within the
|
// value reported is determined by calling the given function from within the
|
||||||
// Write method. Take into account that metric collection may happen
|
// Write method. Take into account that metric collection may happen
|
||||||
// concurrently. If that results in concurrent calls to Write, like in the case
|
// concurrently. Therefore, it must be safe to call the provided function
|
||||||
// where a GaugeFunc is directly registered with Prometheus, the provided
|
// concurrently.
|
||||||
// function must be concurrency-safe.
|
//
|
||||||
|
// NewGaugeFunc is a good way to create an “info” style metric with a constant
|
||||||
|
// value of 1. Example:
|
||||||
|
// https://github.com/prometheus/common/blob/8558a5b7db3c84fa38b4766966059a7bd5bfa2ee/version/info.go#L36-L56
|
||||||
func NewGaugeFunc(opts GaugeOpts, function func() float64) GaugeFunc {
|
func NewGaugeFunc(opts GaugeOpts, function func() float64) GaugeFunc {
|
||||||
return newValueFunc(NewDesc(
|
return newValueFunc(NewDesc(
|
||||||
BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
|
BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
|
||||||
|
|
|
@ -1,34 +1,89 @@
|
||||||
|
// Copyright 2018 The Prometheus Authors
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
package prometheus
|
package prometheus
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type goCollector struct {
|
type goCollector struct {
|
||||||
goroutines Gauge
|
goroutinesDesc *Desc
|
||||||
gcDesc *Desc
|
threadsDesc *Desc
|
||||||
|
gcDesc *Desc
|
||||||
|
goInfoDesc *Desc
|
||||||
|
|
||||||
// metrics to describe and collect
|
// ms... are memstats related.
|
||||||
metrics memStatsMetrics
|
msLast *runtime.MemStats // Previously collected memstats.
|
||||||
|
msLastTimestamp time.Time
|
||||||
|
msMtx sync.Mutex // Protects msLast and msLastTimestamp.
|
||||||
|
msMetrics memStatsMetrics
|
||||||
|
msRead func(*runtime.MemStats) // For mocking in tests.
|
||||||
|
msMaxWait time.Duration // Wait time for fresh memstats.
|
||||||
|
msMaxAge time.Duration // Maximum allowed age of old memstats.
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewGoCollector returns a collector which exports metrics about the current
|
// NewGoCollector returns a collector that exports metrics about the current Go
|
||||||
// go process.
|
// process. This includes memory stats. To collect those, runtime.ReadMemStats
|
||||||
|
// is called. This requires to “stop the world”, which usually only happens for
|
||||||
|
// garbage collection (GC). Take the following implications into account when
|
||||||
|
// deciding whether to use the Go collector:
|
||||||
|
//
|
||||||
|
// 1. The performance impact of stopping the world is the more relevant the more
|
||||||
|
// frequently metrics are collected. However, with Go1.9 or later the
|
||||||
|
// stop-the-world time per metrics collection is very short (~25µs) so that the
|
||||||
|
// performance impact will only matter in rare cases. However, with older Go
|
||||||
|
// versions, the stop-the-world duration depends on the heap size and can be
|
||||||
|
// quite significant (~1.7 ms/GiB as per
|
||||||
|
// https://go-review.googlesource.com/c/go/+/34937).
|
||||||
|
//
|
||||||
|
// 2. During an ongoing GC, nothing else can stop the world. Therefore, if the
|
||||||
|
// metrics collection happens to coincide with GC, it will only complete after
|
||||||
|
// GC has finished. Usually, GC is fast enough to not cause problems. However,
|
||||||
|
// with a very large heap, GC might take multiple seconds, which is enough to
|
||||||
|
// cause scrape timeouts in common setups. To avoid this problem, the Go
|
||||||
|
// collector will use the memstats from a previous collection if
|
||||||
|
// runtime.ReadMemStats takes more than 1s. However, if there are no previously
|
||||||
|
// collected memstats, or their collection is more than 5m ago, the collection
|
||||||
|
// will block until runtime.ReadMemStats succeeds. (The problem might be solved
|
||||||
|
// in Go1.13, see https://github.com/golang/go/issues/19812 for the related Go
|
||||||
|
// issue.)
|
||||||
func NewGoCollector() Collector {
|
func NewGoCollector() Collector {
|
||||||
return &goCollector{
|
return &goCollector{
|
||||||
goroutines: NewGauge(GaugeOpts{
|
goroutinesDesc: NewDesc(
|
||||||
Namespace: "go",
|
"go_goroutines",
|
||||||
Name: "goroutines",
|
"Number of goroutines that currently exist.",
|
||||||
Help: "Number of goroutines that currently exist.",
|
nil, nil),
|
||||||
}),
|
threadsDesc: NewDesc(
|
||||||
|
"go_threads",
|
||||||
|
"Number of OS threads created.",
|
||||||
|
nil, nil),
|
||||||
gcDesc: NewDesc(
|
gcDesc: NewDesc(
|
||||||
"go_gc_duration_seconds",
|
"go_gc_duration_seconds",
|
||||||
"A summary of the GC invocation durations.",
|
"A summary of the pause duration of garbage collection cycles.",
|
||||||
nil, nil),
|
nil, nil),
|
||||||
metrics: memStatsMetrics{
|
goInfoDesc: NewDesc(
|
||||||
|
"go_info",
|
||||||
|
"Information about the Go environment.",
|
||||||
|
nil, Labels{"version": runtime.Version()}),
|
||||||
|
msLast: &runtime.MemStats{},
|
||||||
|
msRead: runtime.ReadMemStats,
|
||||||
|
msMaxWait: time.Second,
|
||||||
|
msMaxAge: 5 * time.Minute,
|
||||||
|
msMetrics: memStatsMetrics{
|
||||||
{
|
{
|
||||||
desc: NewDesc(
|
desc: NewDesc(
|
||||||
memstatNamespace("alloc_bytes"),
|
memstatNamespace("alloc_bytes"),
|
||||||
|
@ -48,7 +103,7 @@ func NewGoCollector() Collector {
|
||||||
}, {
|
}, {
|
||||||
desc: NewDesc(
|
desc: NewDesc(
|
||||||
memstatNamespace("sys_bytes"),
|
memstatNamespace("sys_bytes"),
|
||||||
"Number of bytes obtained by system. Sum of all system allocations.",
|
"Number of bytes obtained from system.",
|
||||||
nil, nil,
|
nil, nil,
|
||||||
),
|
),
|
||||||
eval: func(ms *runtime.MemStats) float64 { return float64(ms.Sys) },
|
eval: func(ms *runtime.MemStats) float64 { return float64(ms.Sys) },
|
||||||
|
@ -111,12 +166,12 @@ func NewGoCollector() Collector {
|
||||||
valType: GaugeValue,
|
valType: GaugeValue,
|
||||||
}, {
|
}, {
|
||||||
desc: NewDesc(
|
desc: NewDesc(
|
||||||
memstatNamespace("heap_released_bytes_total"),
|
memstatNamespace("heap_released_bytes"),
|
||||||
"Total number of heap bytes released to OS.",
|
"Number of heap bytes released to OS.",
|
||||||
nil, nil,
|
nil, nil,
|
||||||
),
|
),
|
||||||
eval: func(ms *runtime.MemStats) float64 { return float64(ms.HeapReleased) },
|
eval: func(ms *runtime.MemStats) float64 { return float64(ms.HeapReleased) },
|
||||||
valType: CounterValue,
|
valType: GaugeValue,
|
||||||
}, {
|
}, {
|
||||||
desc: NewDesc(
|
desc: NewDesc(
|
||||||
memstatNamespace("heap_objects"),
|
memstatNamespace("heap_objects"),
|
||||||
|
@ -213,29 +268,53 @@ func NewGoCollector() Collector {
|
||||||
),
|
),
|
||||||
eval: func(ms *runtime.MemStats) float64 { return float64(ms.LastGC) / 1e9 },
|
eval: func(ms *runtime.MemStats) float64 { return float64(ms.LastGC) / 1e9 },
|
||||||
valType: GaugeValue,
|
valType: GaugeValue,
|
||||||
|
}, {
|
||||||
|
desc: NewDesc(
|
||||||
|
memstatNamespace("gc_cpu_fraction"),
|
||||||
|
"The fraction of this program's available CPU time used by the GC since the program started.",
|
||||||
|
nil, nil,
|
||||||
|
),
|
||||||
|
eval: func(ms *runtime.MemStats) float64 { return ms.GCCPUFraction },
|
||||||
|
valType: GaugeValue,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func memstatNamespace(s string) string {
|
func memstatNamespace(s string) string {
|
||||||
return fmt.Sprintf("go_memstats_%s", s)
|
return "go_memstats_" + s
|
||||||
}
|
}
|
||||||
|
|
||||||
// Describe returns all descriptions of the collector.
|
// Describe returns all descriptions of the collector.
|
||||||
func (c *goCollector) Describe(ch chan<- *Desc) {
|
func (c *goCollector) Describe(ch chan<- *Desc) {
|
||||||
ch <- c.goroutines.Desc()
|
ch <- c.goroutinesDesc
|
||||||
|
ch <- c.threadsDesc
|
||||||
ch <- c.gcDesc
|
ch <- c.gcDesc
|
||||||
|
ch <- c.goInfoDesc
|
||||||
for _, i := range c.metrics {
|
for _, i := range c.msMetrics {
|
||||||
ch <- i.desc
|
ch <- i.desc
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect returns the current state of all metrics of the collector.
|
// Collect returns the current state of all metrics of the collector.
|
||||||
func (c *goCollector) Collect(ch chan<- Metric) {
|
func (c *goCollector) Collect(ch chan<- Metric) {
|
||||||
c.goroutines.Set(float64(runtime.NumGoroutine()))
|
var (
|
||||||
ch <- c.goroutines
|
ms = &runtime.MemStats{}
|
||||||
|
done = make(chan struct{})
|
||||||
|
)
|
||||||
|
// Start reading memstats first as it might take a while.
|
||||||
|
go func() {
|
||||||
|
c.msRead(ms)
|
||||||
|
c.msMtx.Lock()
|
||||||
|
c.msLast = ms
|
||||||
|
c.msLastTimestamp = time.Now()
|
||||||
|
c.msMtx.Unlock()
|
||||||
|
close(done)
|
||||||
|
}()
|
||||||
|
|
||||||
|
ch <- MustNewConstMetric(c.goroutinesDesc, GaugeValue, float64(runtime.NumGoroutine()))
|
||||||
|
n, _ := runtime.ThreadCreateProfile(nil)
|
||||||
|
ch <- MustNewConstMetric(c.threadsDesc, GaugeValue, float64(n))
|
||||||
|
|
||||||
var stats debug.GCStats
|
var stats debug.GCStats
|
||||||
stats.PauseQuantiles = make([]time.Duration, 5)
|
stats.PauseQuantiles = make([]time.Duration, 5)
|
||||||
|
@ -246,11 +325,35 @@ func (c *goCollector) Collect(ch chan<- Metric) {
|
||||||
quantiles[float64(idx+1)/float64(len(stats.PauseQuantiles)-1)] = pq.Seconds()
|
quantiles[float64(idx+1)/float64(len(stats.PauseQuantiles)-1)] = pq.Seconds()
|
||||||
}
|
}
|
||||||
quantiles[0.0] = stats.PauseQuantiles[0].Seconds()
|
quantiles[0.0] = stats.PauseQuantiles[0].Seconds()
|
||||||
ch <- MustNewConstSummary(c.gcDesc, uint64(stats.NumGC), float64(stats.PauseTotal.Seconds()), quantiles)
|
ch <- MustNewConstSummary(c.gcDesc, uint64(stats.NumGC), stats.PauseTotal.Seconds(), quantiles)
|
||||||
|
|
||||||
ms := &runtime.MemStats{}
|
ch <- MustNewConstMetric(c.goInfoDesc, GaugeValue, 1)
|
||||||
runtime.ReadMemStats(ms)
|
|
||||||
for _, i := range c.metrics {
|
timer := time.NewTimer(c.msMaxWait)
|
||||||
|
select {
|
||||||
|
case <-done: // Our own ReadMemStats succeeded in time. Use it.
|
||||||
|
timer.Stop() // Important for high collection frequencies to not pile up timers.
|
||||||
|
c.msCollect(ch, ms)
|
||||||
|
return
|
||||||
|
case <-timer.C: // Time out, use last memstats if possible. Continue below.
|
||||||
|
}
|
||||||
|
c.msMtx.Lock()
|
||||||
|
if time.Since(c.msLastTimestamp) < c.msMaxAge {
|
||||||
|
// Last memstats are recent enough. Collect from them under the lock.
|
||||||
|
c.msCollect(ch, c.msLast)
|
||||||
|
c.msMtx.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// If we are here, the last memstats are too old or don't exist. We have
|
||||||
|
// to wait until our own ReadMemStats finally completes. For that to
|
||||||
|
// happen, we have to release the lock.
|
||||||
|
c.msMtx.Unlock()
|
||||||
|
<-done
|
||||||
|
c.msCollect(ch, ms)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *goCollector) msCollect(ch chan<- Metric, ms *runtime.MemStats) {
|
||||||
|
for _, i := range c.msMetrics {
|
||||||
ch <- MustNewConstMetric(i.desc, i.valType, i.eval(ms))
|
ch <- MustNewConstMetric(i.desc, i.valType, i.eval(ms))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -261,3 +364,33 @@ type memStatsMetrics []struct {
|
||||||
eval func(*runtime.MemStats) float64
|
eval func(*runtime.MemStats) float64
|
||||||
valType ValueType
|
valType ValueType
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewBuildInfoCollector returns a collector collecting a single metric
|
||||||
|
// "go_build_info" with the constant value 1 and three labels "path", "version",
|
||||||
|
// and "checksum". Their label values contain the main module path, version, and
|
||||||
|
// checksum, respectively. The labels will only have meaningful values if the
|
||||||
|
// binary is built with Go module support and from source code retrieved from
|
||||||
|
// the source repository (rather than the local file system). This is usually
|
||||||
|
// accomplished by building from outside of GOPATH, specifying the full address
|
||||||
|
// of the main package, e.g. "GO111MODULE=on go run
|
||||||
|
// github.com/prometheus/client_golang/examples/random". If built without Go
|
||||||
|
// module support, all label values will be "unknown". If built with Go module
|
||||||
|
// support but using the source code from the local file system, the "path" will
|
||||||
|
// be set appropriately, but "checksum" will be empty and "version" will be
|
||||||
|
// "(devel)".
|
||||||
|
//
|
||||||
|
// This collector uses only the build information for the main module. See
|
||||||
|
// https://github.com/povilasv/prommod for an example of a collector for the
|
||||||
|
// module dependencies.
|
||||||
|
func NewBuildInfoCollector() Collector {
|
||||||
|
path, version, sum := readBuildInfo()
|
||||||
|
c := &selfCollector{MustNewConstMetric(
|
||||||
|
NewDesc(
|
||||||
|
"go_build_info",
|
||||||
|
"Build information about the main Go module.",
|
||||||
|
nil, Labels{"path": path, "version": version, "checksum": sum},
|
||||||
|
),
|
||||||
|
GaugeValue, 1)}
|
||||||
|
c.init(c.self)
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
|
@ -16,8 +16,11 @@ package prometheus
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
|
"runtime"
|
||||||
"sort"
|
"sort"
|
||||||
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/golang/protobuf/proto"
|
"github.com/golang/protobuf/proto"
|
||||||
|
|
||||||
|
@ -108,8 +111,9 @@ func ExponentialBuckets(start, factor float64, count int) []float64 {
|
||||||
}
|
}
|
||||||
|
|
||||||
// HistogramOpts bundles the options for creating a Histogram metric. It is
|
// HistogramOpts bundles the options for creating a Histogram metric. It is
|
||||||
// mandatory to set Name and Help to a non-empty string. All other fields are
|
// mandatory to set Name to a non-empty string. All other fields are optional
|
||||||
// optional and can safely be left at their zero value.
|
// and can safely be left at their zero value, although it is strongly
|
||||||
|
// encouraged to set a Help string.
|
||||||
type HistogramOpts struct {
|
type HistogramOpts struct {
|
||||||
// Namespace, Subsystem, and Name are components of the fully-qualified
|
// Namespace, Subsystem, and Name are components of the fully-qualified
|
||||||
// name of the Histogram (created by joining these components with
|
// name of the Histogram (created by joining these components with
|
||||||
|
@ -120,29 +124,22 @@ type HistogramOpts struct {
|
||||||
Subsystem string
|
Subsystem string
|
||||||
Name string
|
Name string
|
||||||
|
|
||||||
// Help provides information about this Histogram. Mandatory!
|
// Help provides information about this Histogram.
|
||||||
//
|
//
|
||||||
// Metrics with the same fully-qualified name must have the same Help
|
// Metrics with the same fully-qualified name must have the same Help
|
||||||
// string.
|
// string.
|
||||||
Help string
|
Help string
|
||||||
|
|
||||||
// ConstLabels are used to attach fixed labels to this
|
// ConstLabels are used to attach fixed labels to this metric. Metrics
|
||||||
// Histogram. Histograms with the same fully-qualified name must have the
|
// with the same fully-qualified name must have the same label names in
|
||||||
// same label names in their ConstLabels.
|
// their ConstLabels.
|
||||||
//
|
//
|
||||||
// Note that in most cases, labels have a value that varies during the
|
// ConstLabels are only used rarely. In particular, do not use them to
|
||||||
// lifetime of a process. Those labels are usually managed with a
|
// attach the same labels to all your metrics. Those use cases are
|
||||||
// HistogramVec. ConstLabels serve only special purposes. One is for the
|
// better covered by target labels set by the scraping Prometheus
|
||||||
// special case where the value of a label does not change during the
|
// server, or by one specific metric (e.g. a build_info or a
|
||||||
// lifetime of a process, e.g. if the revision of the running binary is
|
// machine_role metric). See also
|
||||||
// put into a label. Another, more advanced purpose is if more than one
|
// https://prometheus.io/docs/instrumenting/writing_exporters/#target-labels-not-static-scraped-labels
|
||||||
// Collector needs to collect Histograms with the same fully-qualified
|
|
||||||
// name. In that case, those Summaries must differ in the values of
|
|
||||||
// their ConstLabels. See the Collector examples.
|
|
||||||
//
|
|
||||||
// If the value of a label never changes (not even between binaries),
|
|
||||||
// that label most likely should not be a label at all (but part of the
|
|
||||||
// metric name).
|
|
||||||
ConstLabels Labels
|
ConstLabels Labels
|
||||||
|
|
||||||
// Buckets defines the buckets into which observations are counted. Each
|
// Buckets defines the buckets into which observations are counted. Each
|
||||||
|
@ -155,6 +152,10 @@ type HistogramOpts struct {
|
||||||
|
|
||||||
// NewHistogram creates a new Histogram based on the provided HistogramOpts. It
|
// NewHistogram creates a new Histogram based on the provided HistogramOpts. It
|
||||||
// panics if the buckets in HistogramOpts are not in strictly increasing order.
|
// panics if the buckets in HistogramOpts are not in strictly increasing order.
|
||||||
|
//
|
||||||
|
// The returned implementation also implements ExemplarObserver. It is safe to
|
||||||
|
// perform the corresponding type assertion. Exemplars are tracked separately
|
||||||
|
// for each bucket.
|
||||||
func NewHistogram(opts HistogramOpts) Histogram {
|
func NewHistogram(opts HistogramOpts) Histogram {
|
||||||
return newHistogram(
|
return newHistogram(
|
||||||
NewDesc(
|
NewDesc(
|
||||||
|
@ -169,7 +170,7 @@ func NewHistogram(opts HistogramOpts) Histogram {
|
||||||
|
|
||||||
func newHistogram(desc *Desc, opts HistogramOpts, labelValues ...string) Histogram {
|
func newHistogram(desc *Desc, opts HistogramOpts, labelValues ...string) Histogram {
|
||||||
if len(desc.variableLabels) != len(labelValues) {
|
if len(desc.variableLabels) != len(labelValues) {
|
||||||
panic(errInconsistentCardinality)
|
panic(makeInconsistentCardinalityError(desc.fqName, desc.variableLabels, labelValues))
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, n := range desc.variableLabels {
|
for _, n := range desc.variableLabels {
|
||||||
|
@ -191,6 +192,8 @@ func newHistogram(desc *Desc, opts HistogramOpts, labelValues ...string) Histogr
|
||||||
desc: desc,
|
desc: desc,
|
||||||
upperBounds: opts.Buckets,
|
upperBounds: opts.Buckets,
|
||||||
labelPairs: makeLabelPairs(desc, labelValues),
|
labelPairs: makeLabelPairs(desc, labelValues),
|
||||||
|
counts: [2]*histogramCounts{{}, {}},
|
||||||
|
now: time.Now,
|
||||||
}
|
}
|
||||||
for i, upperBound := range h.upperBounds {
|
for i, upperBound := range h.upperBounds {
|
||||||
if i < len(h.upperBounds)-1 {
|
if i < len(h.upperBounds)-1 {
|
||||||
|
@ -207,30 +210,60 @@ func newHistogram(desc *Desc, opts HistogramOpts, labelValues ...string) Histogr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Finally we know the final length of h.upperBounds and can make counts.
|
// Finally we know the final length of h.upperBounds and can make buckets
|
||||||
h.counts = make([]uint64, len(h.upperBounds))
|
// for both counts as well as exemplars:
|
||||||
|
h.counts[0].buckets = make([]uint64, len(h.upperBounds))
|
||||||
|
h.counts[1].buckets = make([]uint64, len(h.upperBounds))
|
||||||
|
h.exemplars = make([]atomic.Value, len(h.upperBounds)+1)
|
||||||
|
|
||||||
h.init(h) // Init self-collection.
|
h.init(h) // Init self-collection.
|
||||||
return h
|
return h
|
||||||
}
|
}
|
||||||
|
|
||||||
type histogram struct {
|
type histogramCounts struct {
|
||||||
// sumBits contains the bits of the float64 representing the sum of all
|
// sumBits contains the bits of the float64 representing the sum of all
|
||||||
// observations. sumBits and count have to go first in the struct to
|
// observations. sumBits and count have to go first in the struct to
|
||||||
// guarantee alignment for atomic operations.
|
// guarantee alignment for atomic operations.
|
||||||
// http://golang.org/pkg/sync/atomic/#pkg-note-BUG
|
// http://golang.org/pkg/sync/atomic/#pkg-note-BUG
|
||||||
sumBits uint64
|
sumBits uint64
|
||||||
count uint64
|
count uint64
|
||||||
|
buckets []uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
type histogram struct {
|
||||||
|
// countAndHotIdx enables lock-free writes with use of atomic updates.
|
||||||
|
// The most significant bit is the hot index [0 or 1] of the count field
|
||||||
|
// below. Observe calls update the hot one. All remaining bits count the
|
||||||
|
// number of Observe calls. Observe starts by incrementing this counter,
|
||||||
|
// and finish by incrementing the count field in the respective
|
||||||
|
// histogramCounts, as a marker for completion.
|
||||||
|
//
|
||||||
|
// Calls of the Write method (which are non-mutating reads from the
|
||||||
|
// perspective of the histogram) swap the hot–cold under the writeMtx
|
||||||
|
// lock. A cooldown is awaited (while locked) by comparing the number of
|
||||||
|
// observations with the initiation count. Once they match, then the
|
||||||
|
// last observation on the now cool one has completed. All cool fields must
|
||||||
|
// be merged into the new hot before releasing writeMtx.
|
||||||
|
//
|
||||||
|
// Fields with atomic access first! See alignment constraint:
|
||||||
|
// http://golang.org/pkg/sync/atomic/#pkg-note-BUG
|
||||||
|
countAndHotIdx uint64
|
||||||
|
|
||||||
selfCollector
|
selfCollector
|
||||||
// Note that there is no mutex required.
|
desc *Desc
|
||||||
|
writeMtx sync.Mutex // Only used in the Write method.
|
||||||
|
|
||||||
desc *Desc
|
// Two counts, one is "hot" for lock-free observations, the other is
|
||||||
|
// "cold" for writing out a dto.Metric. It has to be an array of
|
||||||
|
// pointers to guarantee 64bit alignment of the histogramCounts, see
|
||||||
|
// http://golang.org/pkg/sync/atomic/#pkg-note-BUG.
|
||||||
|
counts [2]*histogramCounts
|
||||||
|
|
||||||
upperBounds []float64
|
upperBounds []float64
|
||||||
counts []uint64
|
labelPairs []*dto.LabelPair
|
||||||
|
exemplars []atomic.Value // One more than buckets (to include +Inf), each a *dto.Exemplar.
|
||||||
|
|
||||||
labelPairs []*dto.LabelPair
|
now func() time.Time // To mock out time.Now() for testing.
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *histogram) Desc() *Desc {
|
func (h *histogram) Desc() *Desc {
|
||||||
|
@ -238,6 +271,89 @@ func (h *histogram) Desc() *Desc {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *histogram) Observe(v float64) {
|
func (h *histogram) Observe(v float64) {
|
||||||
|
h.observe(v, h.findBucket(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *histogram) ObserveWithExemplar(v float64, e Labels) {
|
||||||
|
i := h.findBucket(v)
|
||||||
|
h.observe(v, i)
|
||||||
|
h.updateExemplar(v, i, e)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *histogram) Write(out *dto.Metric) error {
|
||||||
|
// For simplicity, we protect this whole method by a mutex. It is not in
|
||||||
|
// the hot path, i.e. Observe is called much more often than Write. The
|
||||||
|
// complication of making Write lock-free isn't worth it, if possible at
|
||||||
|
// all.
|
||||||
|
h.writeMtx.Lock()
|
||||||
|
defer h.writeMtx.Unlock()
|
||||||
|
|
||||||
|
// Adding 1<<63 switches the hot index (from 0 to 1 or from 1 to 0)
|
||||||
|
// without touching the count bits. See the struct comments for a full
|
||||||
|
// description of the algorithm.
|
||||||
|
n := atomic.AddUint64(&h.countAndHotIdx, 1<<63)
|
||||||
|
// count is contained unchanged in the lower 63 bits.
|
||||||
|
count := n & ((1 << 63) - 1)
|
||||||
|
// The most significant bit tells us which counts is hot. The complement
|
||||||
|
// is thus the cold one.
|
||||||
|
hotCounts := h.counts[n>>63]
|
||||||
|
coldCounts := h.counts[(^n)>>63]
|
||||||
|
|
||||||
|
// Await cooldown.
|
||||||
|
for count != atomic.LoadUint64(&coldCounts.count) {
|
||||||
|
runtime.Gosched() // Let observations get work done.
|
||||||
|
}
|
||||||
|
|
||||||
|
his := &dto.Histogram{
|
||||||
|
Bucket: make([]*dto.Bucket, len(h.upperBounds)),
|
||||||
|
SampleCount: proto.Uint64(count),
|
||||||
|
SampleSum: proto.Float64(math.Float64frombits(atomic.LoadUint64(&coldCounts.sumBits))),
|
||||||
|
}
|
||||||
|
var cumCount uint64
|
||||||
|
for i, upperBound := range h.upperBounds {
|
||||||
|
cumCount += atomic.LoadUint64(&coldCounts.buckets[i])
|
||||||
|
his.Bucket[i] = &dto.Bucket{
|
||||||
|
CumulativeCount: proto.Uint64(cumCount),
|
||||||
|
UpperBound: proto.Float64(upperBound),
|
||||||
|
}
|
||||||
|
if e := h.exemplars[i].Load(); e != nil {
|
||||||
|
his.Bucket[i].Exemplar = e.(*dto.Exemplar)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If there is an exemplar for the +Inf bucket, we have to add that bucket explicitly.
|
||||||
|
if e := h.exemplars[len(h.upperBounds)].Load(); e != nil {
|
||||||
|
b := &dto.Bucket{
|
||||||
|
CumulativeCount: proto.Uint64(count),
|
||||||
|
UpperBound: proto.Float64(math.Inf(1)),
|
||||||
|
Exemplar: e.(*dto.Exemplar),
|
||||||
|
}
|
||||||
|
his.Bucket = append(his.Bucket, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
out.Histogram = his
|
||||||
|
out.Label = h.labelPairs
|
||||||
|
|
||||||
|
// Finally add all the cold counts to the new hot counts and reset the cold counts.
|
||||||
|
atomic.AddUint64(&hotCounts.count, count)
|
||||||
|
atomic.StoreUint64(&coldCounts.count, 0)
|
||||||
|
for {
|
||||||
|
oldBits := atomic.LoadUint64(&hotCounts.sumBits)
|
||||||
|
newBits := math.Float64bits(math.Float64frombits(oldBits) + his.GetSampleSum())
|
||||||
|
if atomic.CompareAndSwapUint64(&hotCounts.sumBits, oldBits, newBits) {
|
||||||
|
atomic.StoreUint64(&coldCounts.sumBits, 0)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i := range h.upperBounds {
|
||||||
|
atomic.AddUint64(&hotCounts.buckets[i], atomic.LoadUint64(&coldCounts.buckets[i]))
|
||||||
|
atomic.StoreUint64(&coldCounts.buckets[i], 0)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// findBucket returns the index of the bucket for the provided value, or
|
||||||
|
// len(h.upperBounds) for the +Inf bucket.
|
||||||
|
func (h *histogram) findBucket(v float64) int {
|
||||||
// TODO(beorn7): For small numbers of buckets (<30), a linear search is
|
// TODO(beorn7): For small numbers of buckets (<30), a linear search is
|
||||||
// slightly faster than the binary search. If we really care, we could
|
// slightly faster than the binary search. If we really care, we could
|
||||||
// switch from one search strategy to the other depending on the number
|
// switch from one search strategy to the other depending on the number
|
||||||
|
@ -247,38 +363,43 @@ func (h *histogram) Observe(v float64) {
|
||||||
// 11 buckets: 38.3 ns/op linear - binary 48.7 ns/op
|
// 11 buckets: 38.3 ns/op linear - binary 48.7 ns/op
|
||||||
// 100 buckets: 78.1 ns/op linear - binary 54.9 ns/op
|
// 100 buckets: 78.1 ns/op linear - binary 54.9 ns/op
|
||||||
// 300 buckets: 154 ns/op linear - binary 61.6 ns/op
|
// 300 buckets: 154 ns/op linear - binary 61.6 ns/op
|
||||||
i := sort.SearchFloat64s(h.upperBounds, v)
|
return sort.SearchFloat64s(h.upperBounds, v)
|
||||||
if i < len(h.counts) {
|
}
|
||||||
atomic.AddUint64(&h.counts[i], 1)
|
|
||||||
|
// observe is the implementation for Observe without the findBucket part.
|
||||||
|
func (h *histogram) observe(v float64, bucket int) {
|
||||||
|
// We increment h.countAndHotIdx so that the counter in the lower
|
||||||
|
// 63 bits gets incremented. At the same time, we get the new value
|
||||||
|
// back, which we can use to find the currently-hot counts.
|
||||||
|
n := atomic.AddUint64(&h.countAndHotIdx, 1)
|
||||||
|
hotCounts := h.counts[n>>63]
|
||||||
|
|
||||||
|
if bucket < len(h.upperBounds) {
|
||||||
|
atomic.AddUint64(&hotCounts.buckets[bucket], 1)
|
||||||
}
|
}
|
||||||
atomic.AddUint64(&h.count, 1)
|
|
||||||
for {
|
for {
|
||||||
oldBits := atomic.LoadUint64(&h.sumBits)
|
oldBits := atomic.LoadUint64(&hotCounts.sumBits)
|
||||||
newBits := math.Float64bits(math.Float64frombits(oldBits) + v)
|
newBits := math.Float64bits(math.Float64frombits(oldBits) + v)
|
||||||
if atomic.CompareAndSwapUint64(&h.sumBits, oldBits, newBits) {
|
if atomic.CompareAndSwapUint64(&hotCounts.sumBits, oldBits, newBits) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Increment count last as we take it as a signal that the observation
|
||||||
|
// is complete.
|
||||||
|
atomic.AddUint64(&hotCounts.count, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *histogram) Write(out *dto.Metric) error {
|
// updateExemplar replaces the exemplar for the provided bucket. With empty
|
||||||
his := &dto.Histogram{}
|
// labels, it's a no-op. It panics if any of the labels is invalid.
|
||||||
buckets := make([]*dto.Bucket, len(h.upperBounds))
|
func (h *histogram) updateExemplar(v float64, bucket int, l Labels) {
|
||||||
|
if l == nil {
|
||||||
his.SampleSum = proto.Float64(math.Float64frombits(atomic.LoadUint64(&h.sumBits)))
|
return
|
||||||
his.SampleCount = proto.Uint64(atomic.LoadUint64(&h.count))
|
|
||||||
var count uint64
|
|
||||||
for i, upperBound := range h.upperBounds {
|
|
||||||
count += atomic.LoadUint64(&h.counts[i])
|
|
||||||
buckets[i] = &dto.Bucket{
|
|
||||||
CumulativeCount: proto.Uint64(count),
|
|
||||||
UpperBound: proto.Float64(upperBound),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
his.Bucket = buckets
|
e, err := newExemplar(v, h.now(), l)
|
||||||
out.Histogram = his
|
if err != nil {
|
||||||
out.Label = h.labelPairs
|
panic(err)
|
||||||
return nil
|
}
|
||||||
|
h.exemplars[bucket].Store(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
// HistogramVec is a Collector that bundles a set of Histograms that all share the
|
// HistogramVec is a Collector that bundles a set of Histograms that all share the
|
||||||
|
@ -287,12 +408,11 @@ func (h *histogram) Write(out *dto.Metric) error {
|
||||||
// (e.g. HTTP request latencies, partitioned by status code and method). Create
|
// (e.g. HTTP request latencies, partitioned by status code and method). Create
|
||||||
// instances with NewHistogramVec.
|
// instances with NewHistogramVec.
|
||||||
type HistogramVec struct {
|
type HistogramVec struct {
|
||||||
*MetricVec
|
*metricVec
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewHistogramVec creates a new HistogramVec based on the provided HistogramOpts and
|
// NewHistogramVec creates a new HistogramVec based on the provided HistogramOpts and
|
||||||
// partitioned by the given label names. At least one label name must be
|
// partitioned by the given label names.
|
||||||
// provided.
|
|
||||||
func NewHistogramVec(opts HistogramOpts, labelNames []string) *HistogramVec {
|
func NewHistogramVec(opts HistogramOpts, labelNames []string) *HistogramVec {
|
||||||
desc := NewDesc(
|
desc := NewDesc(
|
||||||
BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
|
BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
|
||||||
|
@ -301,47 +421,116 @@ func NewHistogramVec(opts HistogramOpts, labelNames []string) *HistogramVec {
|
||||||
opts.ConstLabels,
|
opts.ConstLabels,
|
||||||
)
|
)
|
||||||
return &HistogramVec{
|
return &HistogramVec{
|
||||||
MetricVec: newMetricVec(desc, func(lvs ...string) Metric {
|
metricVec: newMetricVec(desc, func(lvs ...string) Metric {
|
||||||
return newHistogram(desc, opts, lvs...)
|
return newHistogram(desc, opts, lvs...)
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetMetricWithLabelValues replaces the method of the same name in
|
// GetMetricWithLabelValues returns the Histogram for the given slice of label
|
||||||
// MetricVec. The difference is that this method returns a Histogram and not a
|
// values (same order as the VariableLabels in Desc). If that combination of
|
||||||
// Metric so that no type conversion is required.
|
// label values is accessed for the first time, a new Histogram is created.
|
||||||
func (m *HistogramVec) GetMetricWithLabelValues(lvs ...string) (Histogram, error) {
|
//
|
||||||
metric, err := m.MetricVec.GetMetricWithLabelValues(lvs...)
|
// It is possible to call this method without using the returned Histogram to only
|
||||||
|
// create the new Histogram but leave it at its starting value, a Histogram without
|
||||||
|
// any observations.
|
||||||
|
//
|
||||||
|
// Keeping the Histogram for later use is possible (and should be considered if
|
||||||
|
// performance is critical), but keep in mind that Reset, DeleteLabelValues and
|
||||||
|
// Delete can be used to delete the Histogram from the HistogramVec. In that case, the
|
||||||
|
// Histogram will still exist, but it will not be exported anymore, even if a
|
||||||
|
// Histogram with the same label values is created later. See also the CounterVec
|
||||||
|
// example.
|
||||||
|
//
|
||||||
|
// An error is returned if the number of label values is not the same as the
|
||||||
|
// number of VariableLabels in Desc (minus any curried labels).
|
||||||
|
//
|
||||||
|
// Note that for more than one label value, this method is prone to mistakes
|
||||||
|
// caused by an incorrect order of arguments. Consider GetMetricWith(Labels) as
|
||||||
|
// an alternative to avoid that type of mistake. For higher label numbers, the
|
||||||
|
// latter has a much more readable (albeit more verbose) syntax, but it comes
|
||||||
|
// with a performance overhead (for creating and processing the Labels map).
|
||||||
|
// See also the GaugeVec example.
|
||||||
|
func (v *HistogramVec) GetMetricWithLabelValues(lvs ...string) (Observer, error) {
|
||||||
|
metric, err := v.metricVec.getMetricWithLabelValues(lvs...)
|
||||||
if metric != nil {
|
if metric != nil {
|
||||||
return metric.(Histogram), err
|
return metric.(Observer), err
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetMetricWith replaces the method of the same name in MetricVec. The
|
// GetMetricWith returns the Histogram for the given Labels map (the label names
|
||||||
// difference is that this method returns a Histogram and not a Metric so that no
|
// must match those of the VariableLabels in Desc). If that label map is
|
||||||
// type conversion is required.
|
// accessed for the first time, a new Histogram is created. Implications of
|
||||||
func (m *HistogramVec) GetMetricWith(labels Labels) (Histogram, error) {
|
// creating a Histogram without using it and keeping the Histogram for later use
|
||||||
metric, err := m.MetricVec.GetMetricWith(labels)
|
// are the same as for GetMetricWithLabelValues.
|
||||||
|
//
|
||||||
|
// An error is returned if the number and names of the Labels are inconsistent
|
||||||
|
// with those of the VariableLabels in Desc (minus any curried labels).
|
||||||
|
//
|
||||||
|
// This method is used for the same purpose as
|
||||||
|
// GetMetricWithLabelValues(...string). See there for pros and cons of the two
|
||||||
|
// methods.
|
||||||
|
func (v *HistogramVec) GetMetricWith(labels Labels) (Observer, error) {
|
||||||
|
metric, err := v.metricVec.getMetricWith(labels)
|
||||||
if metric != nil {
|
if metric != nil {
|
||||||
return metric.(Histogram), err
|
return metric.(Observer), err
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithLabelValues works as GetMetricWithLabelValues, but panics where
|
// WithLabelValues works as GetMetricWithLabelValues, but panics where
|
||||||
// GetMetricWithLabelValues would have returned an error. By not returning an
|
// GetMetricWithLabelValues would have returned an error. Not returning an
|
||||||
// error, WithLabelValues allows shortcuts like
|
// error allows shortcuts like
|
||||||
// myVec.WithLabelValues("404", "GET").Observe(42.21)
|
// myVec.WithLabelValues("404", "GET").Observe(42.21)
|
||||||
func (m *HistogramVec) WithLabelValues(lvs ...string) Histogram {
|
func (v *HistogramVec) WithLabelValues(lvs ...string) Observer {
|
||||||
return m.MetricVec.WithLabelValues(lvs...).(Histogram)
|
h, err := v.GetMetricWithLabelValues(lvs...)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return h
|
||||||
}
|
}
|
||||||
|
|
||||||
// With works as GetMetricWith, but panics where GetMetricWithLabels would have
|
// With works as GetMetricWith but panics where GetMetricWithLabels would have
|
||||||
// returned an error. By not returning an error, With allows shortcuts like
|
// returned an error. Not returning an error allows shortcuts like
|
||||||
// myVec.With(Labels{"code": "404", "method": "GET"}).Observe(42.21)
|
// myVec.With(prometheus.Labels{"code": "404", "method": "GET"}).Observe(42.21)
|
||||||
func (m *HistogramVec) With(labels Labels) Histogram {
|
func (v *HistogramVec) With(labels Labels) Observer {
|
||||||
return m.MetricVec.With(labels).(Histogram)
|
h, err := v.GetMetricWith(labels)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
// CurryWith returns a vector curried with the provided labels, i.e. the
|
||||||
|
// returned vector has those labels pre-set for all labeled operations performed
|
||||||
|
// on it. The cardinality of the curried vector is reduced accordingly. The
|
||||||
|
// order of the remaining labels stays the same (just with the curried labels
|
||||||
|
// taken out of the sequence – which is relevant for the
|
||||||
|
// (GetMetric)WithLabelValues methods). It is possible to curry a curried
|
||||||
|
// vector, but only with labels not yet used for currying before.
|
||||||
|
//
|
||||||
|
// The metrics contained in the HistogramVec are shared between the curried and
|
||||||
|
// uncurried vectors. They are just accessed differently. Curried and uncurried
|
||||||
|
// vectors behave identically in terms of collection. Only one must be
|
||||||
|
// registered with a given registry (usually the uncurried version). The Reset
|
||||||
|
// method deletes all metrics, even if called on a curried vector.
|
||||||
|
func (v *HistogramVec) CurryWith(labels Labels) (ObserverVec, error) {
|
||||||
|
vec, err := v.curryWith(labels)
|
||||||
|
if vec != nil {
|
||||||
|
return &HistogramVec{vec}, err
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustCurryWith works as CurryWith but panics where CurryWith would have
|
||||||
|
// returned an error.
|
||||||
|
func (v *HistogramVec) MustCurryWith(labels Labels) ObserverVec {
|
||||||
|
vec, err := v.CurryWith(labels)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return vec
|
||||||
}
|
}
|
||||||
|
|
||||||
type constHistogram struct {
|
type constHistogram struct {
|
||||||
|
@ -393,7 +582,7 @@ func (h *constHistogram) Write(out *dto.Metric) error {
|
||||||
// bucket.
|
// bucket.
|
||||||
//
|
//
|
||||||
// NewConstHistogram returns an error if the length of labelValues is not
|
// NewConstHistogram returns an error if the length of labelValues is not
|
||||||
// consistent with the variable labels in Desc.
|
// consistent with the variable labels in Desc or if Desc is invalid.
|
||||||
func NewConstHistogram(
|
func NewConstHistogram(
|
||||||
desc *Desc,
|
desc *Desc,
|
||||||
count uint64,
|
count uint64,
|
||||||
|
@ -401,8 +590,11 @@ func NewConstHistogram(
|
||||||
buckets map[float64]uint64,
|
buckets map[float64]uint64,
|
||||||
labelValues ...string,
|
labelValues ...string,
|
||||||
) (Metric, error) {
|
) (Metric, error) {
|
||||||
if len(desc.variableLabels) != len(labelValues) {
|
if desc.err != nil {
|
||||||
return nil, errInconsistentCardinality
|
return nil, desc.err
|
||||||
|
}
|
||||||
|
if err := validateLabelValues(labelValues, len(desc.variableLabels)); err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
return &constHistogram{
|
return &constHistogram{
|
||||||
desc: desc,
|
desc: desc,
|
||||||
|
|
|
@ -1,490 +0,0 @@
|
||||||
// Copyright 2014 The Prometheus Authors
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package prometheus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"bytes"
|
|
||||||
"compress/gzip"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/prometheus/common/expfmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TODO(beorn7): Remove this whole file. It is a partial mirror of
|
|
||||||
// promhttp/http.go (to avoid circular import chains) where everything HTTP
|
|
||||||
// related should live. The functions here are just for avoiding
|
|
||||||
// breakage. Everything is deprecated.
|
|
||||||
|
|
||||||
const (
|
|
||||||
contentTypeHeader = "Content-Type"
|
|
||||||
contentLengthHeader = "Content-Length"
|
|
||||||
contentEncodingHeader = "Content-Encoding"
|
|
||||||
acceptEncodingHeader = "Accept-Encoding"
|
|
||||||
)
|
|
||||||
|
|
||||||
var bufPool sync.Pool
|
|
||||||
|
|
||||||
func getBuf() *bytes.Buffer {
|
|
||||||
buf := bufPool.Get()
|
|
||||||
if buf == nil {
|
|
||||||
return &bytes.Buffer{}
|
|
||||||
}
|
|
||||||
return buf.(*bytes.Buffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
func giveBuf(buf *bytes.Buffer) {
|
|
||||||
buf.Reset()
|
|
||||||
bufPool.Put(buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handler returns an HTTP handler for the DefaultGatherer. It is
|
|
||||||
// already instrumented with InstrumentHandler (using "prometheus" as handler
|
|
||||||
// name).
|
|
||||||
//
|
|
||||||
// Deprecated: Please note the issues described in the doc comment of
|
|
||||||
// InstrumentHandler. You might want to consider using promhttp.Handler instead
|
|
||||||
// (which is non instrumented).
|
|
||||||
func Handler() http.Handler {
|
|
||||||
return InstrumentHandler("prometheus", UninstrumentedHandler())
|
|
||||||
}
|
|
||||||
|
|
||||||
// UninstrumentedHandler returns an HTTP handler for the DefaultGatherer.
|
|
||||||
//
|
|
||||||
// Deprecated: Use promhttp.Handler instead. See there for further documentation.
|
|
||||||
func UninstrumentedHandler() http.Handler {
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
|
||||||
mfs, err := DefaultGatherer.Gather()
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, "An error has occurred during metrics collection:\n\n"+err.Error(), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
contentType := expfmt.Negotiate(req.Header)
|
|
||||||
buf := getBuf()
|
|
||||||
defer giveBuf(buf)
|
|
||||||
writer, encoding := decorateWriter(req, buf)
|
|
||||||
enc := expfmt.NewEncoder(writer, contentType)
|
|
||||||
var lastErr error
|
|
||||||
for _, mf := range mfs {
|
|
||||||
if err := enc.Encode(mf); err != nil {
|
|
||||||
lastErr = err
|
|
||||||
http.Error(w, "An error has occurred during metrics encoding:\n\n"+err.Error(), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if closer, ok := writer.(io.Closer); ok {
|
|
||||||
closer.Close()
|
|
||||||
}
|
|
||||||
if lastErr != nil && buf.Len() == 0 {
|
|
||||||
http.Error(w, "No metrics encoded, last error:\n\n"+err.Error(), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
header := w.Header()
|
|
||||||
header.Set(contentTypeHeader, string(contentType))
|
|
||||||
header.Set(contentLengthHeader, fmt.Sprint(buf.Len()))
|
|
||||||
if encoding != "" {
|
|
||||||
header.Set(contentEncodingHeader, encoding)
|
|
||||||
}
|
|
||||||
w.Write(buf.Bytes())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// decorateWriter wraps a writer to handle gzip compression if requested. It
|
|
||||||
// returns the decorated writer and the appropriate "Content-Encoding" header
|
|
||||||
// (which is empty if no compression is enabled).
|
|
||||||
func decorateWriter(request *http.Request, writer io.Writer) (io.Writer, string) {
|
|
||||||
header := request.Header.Get(acceptEncodingHeader)
|
|
||||||
parts := strings.Split(header, ",")
|
|
||||||
for _, part := range parts {
|
|
||||||
part := strings.TrimSpace(part)
|
|
||||||
if part == "gzip" || strings.HasPrefix(part, "gzip;") {
|
|
||||||
return gzip.NewWriter(writer), "gzip"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return writer, ""
|
|
||||||
}
|
|
||||||
|
|
||||||
var instLabels = []string{"method", "code"}
|
|
||||||
|
|
||||||
type nower interface {
|
|
||||||
Now() time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
type nowFunc func() time.Time
|
|
||||||
|
|
||||||
func (n nowFunc) Now() time.Time {
|
|
||||||
return n()
|
|
||||||
}
|
|
||||||
|
|
||||||
var now nower = nowFunc(func() time.Time {
|
|
||||||
return time.Now()
|
|
||||||
})
|
|
||||||
|
|
||||||
func nowSeries(t ...time.Time) nower {
|
|
||||||
return nowFunc(func() time.Time {
|
|
||||||
defer func() {
|
|
||||||
t = t[1:]
|
|
||||||
}()
|
|
||||||
|
|
||||||
return t[0]
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// InstrumentHandler wraps the given HTTP handler for instrumentation. It
|
|
||||||
// registers four metric collectors (if not already done) and reports HTTP
|
|
||||||
// metrics to the (newly or already) registered collectors: http_requests_total
|
|
||||||
// (CounterVec), http_request_duration_microseconds (Summary),
|
|
||||||
// http_request_size_bytes (Summary), http_response_size_bytes (Summary). Each
|
|
||||||
// has a constant label named "handler" with the provided handlerName as
|
|
||||||
// value. http_requests_total is a metric vector partitioned by HTTP method
|
|
||||||
// (label name "method") and HTTP status code (label name "code").
|
|
||||||
//
|
|
||||||
// Deprecated: InstrumentHandler has several issues:
|
|
||||||
//
|
|
||||||
// - It uses Summaries rather than Histograms. Summaries are not useful if
|
|
||||||
// aggregation across multiple instances is required.
|
|
||||||
//
|
|
||||||
// - It uses microseconds as unit, which is deprecated and should be replaced by
|
|
||||||
// seconds.
|
|
||||||
//
|
|
||||||
// - The size of the request is calculated in a separate goroutine. Since this
|
|
||||||
// calculator requires access to the request header, it creates a race with
|
|
||||||
// any writes to the header performed during request handling.
|
|
||||||
// httputil.ReverseProxy is a prominent example for a handler
|
|
||||||
// performing such writes.
|
|
||||||
//
|
|
||||||
// Upcoming versions of this package will provide ways of instrumenting HTTP
|
|
||||||
// handlers that are more flexible and have fewer issues. Please prefer direct
|
|
||||||
// instrumentation in the meantime.
|
|
||||||
func InstrumentHandler(handlerName string, handler http.Handler) http.HandlerFunc {
|
|
||||||
return InstrumentHandlerFunc(handlerName, handler.ServeHTTP)
|
|
||||||
}
|
|
||||||
|
|
||||||
// InstrumentHandlerFunc wraps the given function for instrumentation. It
|
|
||||||
// otherwise works in the same way as InstrumentHandler (and shares the same
|
|
||||||
// issues).
|
|
||||||
//
|
|
||||||
// Deprecated: InstrumentHandlerFunc is deprecated for the same reasons as
|
|
||||||
// InstrumentHandler is.
|
|
||||||
func InstrumentHandlerFunc(handlerName string, handlerFunc func(http.ResponseWriter, *http.Request)) http.HandlerFunc {
|
|
||||||
return InstrumentHandlerFuncWithOpts(
|
|
||||||
SummaryOpts{
|
|
||||||
Subsystem: "http",
|
|
||||||
ConstLabels: Labels{"handler": handlerName},
|
|
||||||
},
|
|
||||||
handlerFunc,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// InstrumentHandlerWithOpts works like InstrumentHandler (and shares the same
|
|
||||||
// issues) but provides more flexibility (at the cost of a more complex call
|
|
||||||
// syntax). As InstrumentHandler, this function registers four metric
|
|
||||||
// collectors, but it uses the provided SummaryOpts to create them. However, the
|
|
||||||
// fields "Name" and "Help" in the SummaryOpts are ignored. "Name" is replaced
|
|
||||||
// by "requests_total", "request_duration_microseconds", "request_size_bytes",
|
|
||||||
// and "response_size_bytes", respectively. "Help" is replaced by an appropriate
|
|
||||||
// help string. The names of the variable labels of the http_requests_total
|
|
||||||
// CounterVec are "method" (get, post, etc.), and "code" (HTTP status code).
|
|
||||||
//
|
|
||||||
// If InstrumentHandlerWithOpts is called as follows, it mimics exactly the
|
|
||||||
// behavior of InstrumentHandler:
|
|
||||||
//
|
|
||||||
// prometheus.InstrumentHandlerWithOpts(
|
|
||||||
// prometheus.SummaryOpts{
|
|
||||||
// Subsystem: "http",
|
|
||||||
// ConstLabels: prometheus.Labels{"handler": handlerName},
|
|
||||||
// },
|
|
||||||
// handler,
|
|
||||||
// )
|
|
||||||
//
|
|
||||||
// Technical detail: "requests_total" is a CounterVec, not a SummaryVec, so it
|
|
||||||
// cannot use SummaryOpts. Instead, a CounterOpts struct is created internally,
|
|
||||||
// and all its fields are set to the equally named fields in the provided
|
|
||||||
// SummaryOpts.
|
|
||||||
//
|
|
||||||
// Deprecated: InstrumentHandlerWithOpts is deprecated for the same reasons as
|
|
||||||
// InstrumentHandler is.
|
|
||||||
func InstrumentHandlerWithOpts(opts SummaryOpts, handler http.Handler) http.HandlerFunc {
|
|
||||||
return InstrumentHandlerFuncWithOpts(opts, handler.ServeHTTP)
|
|
||||||
}
|
|
||||||
|
|
||||||
// InstrumentHandlerFuncWithOpts works like InstrumentHandlerFunc (and shares
|
|
||||||
// the same issues) but provides more flexibility (at the cost of a more complex
|
|
||||||
// call syntax). See InstrumentHandlerWithOpts for details how the provided
|
|
||||||
// SummaryOpts are used.
|
|
||||||
//
|
|
||||||
// Deprecated: InstrumentHandlerFuncWithOpts is deprecated for the same reasons
|
|
||||||
// as InstrumentHandler is.
|
|
||||||
func InstrumentHandlerFuncWithOpts(opts SummaryOpts, handlerFunc func(http.ResponseWriter, *http.Request)) http.HandlerFunc {
|
|
||||||
reqCnt := NewCounterVec(
|
|
||||||
CounterOpts{
|
|
||||||
Namespace: opts.Namespace,
|
|
||||||
Subsystem: opts.Subsystem,
|
|
||||||
Name: "requests_total",
|
|
||||||
Help: "Total number of HTTP requests made.",
|
|
||||||
ConstLabels: opts.ConstLabels,
|
|
||||||
},
|
|
||||||
instLabels,
|
|
||||||
)
|
|
||||||
|
|
||||||
opts.Name = "request_duration_microseconds"
|
|
||||||
opts.Help = "The HTTP request latencies in microseconds."
|
|
||||||
reqDur := NewSummary(opts)
|
|
||||||
|
|
||||||
opts.Name = "request_size_bytes"
|
|
||||||
opts.Help = "The HTTP request sizes in bytes."
|
|
||||||
reqSz := NewSummary(opts)
|
|
||||||
|
|
||||||
opts.Name = "response_size_bytes"
|
|
||||||
opts.Help = "The HTTP response sizes in bytes."
|
|
||||||
resSz := NewSummary(opts)
|
|
||||||
|
|
||||||
regReqCnt := MustRegisterOrGet(reqCnt).(*CounterVec)
|
|
||||||
regReqDur := MustRegisterOrGet(reqDur).(Summary)
|
|
||||||
regReqSz := MustRegisterOrGet(reqSz).(Summary)
|
|
||||||
regResSz := MustRegisterOrGet(resSz).(Summary)
|
|
||||||
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
now := time.Now()
|
|
||||||
|
|
||||||
delegate := &responseWriterDelegator{ResponseWriter: w}
|
|
||||||
out := make(chan int)
|
|
||||||
urlLen := 0
|
|
||||||
if r.URL != nil {
|
|
||||||
urlLen = len(r.URL.String())
|
|
||||||
}
|
|
||||||
go computeApproximateRequestSize(r, out, urlLen)
|
|
||||||
|
|
||||||
_, cn := w.(http.CloseNotifier)
|
|
||||||
_, fl := w.(http.Flusher)
|
|
||||||
_, hj := w.(http.Hijacker)
|
|
||||||
_, rf := w.(io.ReaderFrom)
|
|
||||||
var rw http.ResponseWriter
|
|
||||||
if cn && fl && hj && rf {
|
|
||||||
rw = &fancyResponseWriterDelegator{delegate}
|
|
||||||
} else {
|
|
||||||
rw = delegate
|
|
||||||
}
|
|
||||||
handlerFunc(rw, r)
|
|
||||||
|
|
||||||
elapsed := float64(time.Since(now)) / float64(time.Microsecond)
|
|
||||||
|
|
||||||
method := sanitizeMethod(r.Method)
|
|
||||||
code := sanitizeCode(delegate.status)
|
|
||||||
regReqCnt.WithLabelValues(method, code).Inc()
|
|
||||||
regReqDur.Observe(elapsed)
|
|
||||||
regResSz.Observe(float64(delegate.written))
|
|
||||||
regReqSz.Observe(float64(<-out))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func computeApproximateRequestSize(r *http.Request, out chan int, s int) {
|
|
||||||
s += len(r.Method)
|
|
||||||
s += len(r.Proto)
|
|
||||||
for name, values := range r.Header {
|
|
||||||
s += len(name)
|
|
||||||
for _, value := range values {
|
|
||||||
s += len(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
s += len(r.Host)
|
|
||||||
|
|
||||||
// N.B. r.Form and r.MultipartForm are assumed to be included in r.URL.
|
|
||||||
|
|
||||||
if r.ContentLength != -1 {
|
|
||||||
s += int(r.ContentLength)
|
|
||||||
}
|
|
||||||
out <- s
|
|
||||||
}
|
|
||||||
|
|
||||||
type responseWriterDelegator struct {
|
|
||||||
http.ResponseWriter
|
|
||||||
|
|
||||||
handler, method string
|
|
||||||
status int
|
|
||||||
written int64
|
|
||||||
wroteHeader bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *responseWriterDelegator) WriteHeader(code int) {
|
|
||||||
r.status = code
|
|
||||||
r.wroteHeader = true
|
|
||||||
r.ResponseWriter.WriteHeader(code)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *responseWriterDelegator) Write(b []byte) (int, error) {
|
|
||||||
if !r.wroteHeader {
|
|
||||||
r.WriteHeader(http.StatusOK)
|
|
||||||
}
|
|
||||||
n, err := r.ResponseWriter.Write(b)
|
|
||||||
r.written += int64(n)
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
|
|
||||||
type fancyResponseWriterDelegator struct {
|
|
||||||
*responseWriterDelegator
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *fancyResponseWriterDelegator) CloseNotify() <-chan bool {
|
|
||||||
return f.ResponseWriter.(http.CloseNotifier).CloseNotify()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *fancyResponseWriterDelegator) Flush() {
|
|
||||||
f.ResponseWriter.(http.Flusher).Flush()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *fancyResponseWriterDelegator) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
|
||||||
return f.ResponseWriter.(http.Hijacker).Hijack()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *fancyResponseWriterDelegator) ReadFrom(r io.Reader) (int64, error) {
|
|
||||||
if !f.wroteHeader {
|
|
||||||
f.WriteHeader(http.StatusOK)
|
|
||||||
}
|
|
||||||
n, err := f.ResponseWriter.(io.ReaderFrom).ReadFrom(r)
|
|
||||||
f.written += n
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func sanitizeMethod(m string) string {
|
|
||||||
switch m {
|
|
||||||
case "GET", "get":
|
|
||||||
return "get"
|
|
||||||
case "PUT", "put":
|
|
||||||
return "put"
|
|
||||||
case "HEAD", "head":
|
|
||||||
return "head"
|
|
||||||
case "POST", "post":
|
|
||||||
return "post"
|
|
||||||
case "DELETE", "delete":
|
|
||||||
return "delete"
|
|
||||||
case "CONNECT", "connect":
|
|
||||||
return "connect"
|
|
||||||
case "OPTIONS", "options":
|
|
||||||
return "options"
|
|
||||||
case "NOTIFY", "notify":
|
|
||||||
return "notify"
|
|
||||||
default:
|
|
||||||
return strings.ToLower(m)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func sanitizeCode(s int) string {
|
|
||||||
switch s {
|
|
||||||
case 100:
|
|
||||||
return "100"
|
|
||||||
case 101:
|
|
||||||
return "101"
|
|
||||||
|
|
||||||
case 200:
|
|
||||||
return "200"
|
|
||||||
case 201:
|
|
||||||
return "201"
|
|
||||||
case 202:
|
|
||||||
return "202"
|
|
||||||
case 203:
|
|
||||||
return "203"
|
|
||||||
case 204:
|
|
||||||
return "204"
|
|
||||||
case 205:
|
|
||||||
return "205"
|
|
||||||
case 206:
|
|
||||||
return "206"
|
|
||||||
|
|
||||||
case 300:
|
|
||||||
return "300"
|
|
||||||
case 301:
|
|
||||||
return "301"
|
|
||||||
case 302:
|
|
||||||
return "302"
|
|
||||||
case 304:
|
|
||||||
return "304"
|
|
||||||
case 305:
|
|
||||||
return "305"
|
|
||||||
case 307:
|
|
||||||
return "307"
|
|
||||||
|
|
||||||
case 400:
|
|
||||||
return "400"
|
|
||||||
case 401:
|
|
||||||
return "401"
|
|
||||||
case 402:
|
|
||||||
return "402"
|
|
||||||
case 403:
|
|
||||||
return "403"
|
|
||||||
case 404:
|
|
||||||
return "404"
|
|
||||||
case 405:
|
|
||||||
return "405"
|
|
||||||
case 406:
|
|
||||||
return "406"
|
|
||||||
case 407:
|
|
||||||
return "407"
|
|
||||||
case 408:
|
|
||||||
return "408"
|
|
||||||
case 409:
|
|
||||||
return "409"
|
|
||||||
case 410:
|
|
||||||
return "410"
|
|
||||||
case 411:
|
|
||||||
return "411"
|
|
||||||
case 412:
|
|
||||||
return "412"
|
|
||||||
case 413:
|
|
||||||
return "413"
|
|
||||||
case 414:
|
|
||||||
return "414"
|
|
||||||
case 415:
|
|
||||||
return "415"
|
|
||||||
case 416:
|
|
||||||
return "416"
|
|
||||||
case 417:
|
|
||||||
return "417"
|
|
||||||
case 418:
|
|
||||||
return "418"
|
|
||||||
|
|
||||||
case 500:
|
|
||||||
return "500"
|
|
||||||
case 501:
|
|
||||||
return "501"
|
|
||||||
case 502:
|
|
||||||
return "502"
|
|
||||||
case 503:
|
|
||||||
return "503"
|
|
||||||
case 504:
|
|
||||||
return "504"
|
|
||||||
case 505:
|
|
||||||
return "505"
|
|
||||||
|
|
||||||
case 428:
|
|
||||||
return "428"
|
|
||||||
case 429:
|
|
||||||
return "429"
|
|
||||||
case 431:
|
|
||||||
return "431"
|
|
||||||
case 511:
|
|
||||||
return "511"
|
|
||||||
|
|
||||||
default:
|
|
||||||
return strconv.Itoa(s)
|
|
||||||
}
|
|
||||||
}
|
|
85
vendor/github.com/prometheus/client_golang/prometheus/internal/metric.go
generated
vendored
Normal file
85
vendor/github.com/prometheus/client_golang/prometheus/internal/metric.go
generated
vendored
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
// Copyright 2018 The Prometheus Authors
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
dto "github.com/prometheus/client_model/go"
|
||||||
|
)
|
||||||
|
|
||||||
|
// metricSorter is a sortable slice of *dto.Metric.
|
||||||
|
type metricSorter []*dto.Metric
|
||||||
|
|
||||||
|
func (s metricSorter) Len() int {
|
||||||
|
return len(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s metricSorter) Swap(i, j int) {
|
||||||
|
s[i], s[j] = s[j], s[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s metricSorter) Less(i, j int) bool {
|
||||||
|
if len(s[i].Label) != len(s[j].Label) {
|
||||||
|
// This should not happen. The metrics are
|
||||||
|
// inconsistent. However, we have to deal with the fact, as
|
||||||
|
// people might use custom collectors or metric family injection
|
||||||
|
// to create inconsistent metrics. So let's simply compare the
|
||||||
|
// number of labels in this case. That will still yield
|
||||||
|
// reproducible sorting.
|
||||||
|
return len(s[i].Label) < len(s[j].Label)
|
||||||
|
}
|
||||||
|
for n, lp := range s[i].Label {
|
||||||
|
vi := lp.GetValue()
|
||||||
|
vj := s[j].Label[n].GetValue()
|
||||||
|
if vi != vj {
|
||||||
|
return vi < vj
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We should never arrive here. Multiple metrics with the same
|
||||||
|
// label set in the same scrape will lead to undefined ingestion
|
||||||
|
// behavior. However, as above, we have to provide stable sorting
|
||||||
|
// here, even for inconsistent metrics. So sort equal metrics
|
||||||
|
// by their timestamp, with missing timestamps (implying "now")
|
||||||
|
// coming last.
|
||||||
|
if s[i].TimestampMs == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if s[j].TimestampMs == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return s[i].GetTimestampMs() < s[j].GetTimestampMs()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NormalizeMetricFamilies returns a MetricFamily slice with empty
|
||||||
|
// MetricFamilies pruned and the remaining MetricFamilies sorted by name within
|
||||||
|
// the slice, with the contained Metrics sorted within each MetricFamily.
|
||||||
|
func NormalizeMetricFamilies(metricFamiliesByName map[string]*dto.MetricFamily) []*dto.MetricFamily {
|
||||||
|
for _, mf := range metricFamiliesByName {
|
||||||
|
sort.Sort(metricSorter(mf.Metric))
|
||||||
|
}
|
||||||
|
names := make([]string, 0, len(metricFamiliesByName))
|
||||||
|
for name, mf := range metricFamiliesByName {
|
||||||
|
if len(mf.Metric) > 0 {
|
||||||
|
names = append(names, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort.Strings(names)
|
||||||
|
result := make([]*dto.MetricFamily, 0, len(names))
|
||||||
|
for _, name := range names {
|
||||||
|
result = append(result, metricFamiliesByName[name])
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
|
@ -0,0 +1,87 @@
|
||||||
|
// Copyright 2018 The Prometheus Authors
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package prometheus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"github.com/prometheus/common/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Labels represents a collection of label name -> value mappings. This type is
|
||||||
|
// commonly used with the With(Labels) and GetMetricWith(Labels) methods of
|
||||||
|
// metric vector Collectors, e.g.:
|
||||||
|
// myVec.With(Labels{"code": "404", "method": "GET"}).Add(42)
|
||||||
|
//
|
||||||
|
// The other use-case is the specification of constant label pairs in Opts or to
|
||||||
|
// create a Desc.
|
||||||
|
type Labels map[string]string
|
||||||
|
|
||||||
|
// reservedLabelPrefix is a prefix which is not legal in user-supplied
|
||||||
|
// label names.
|
||||||
|
const reservedLabelPrefix = "__"
|
||||||
|
|
||||||
|
var errInconsistentCardinality = errors.New("inconsistent label cardinality")
|
||||||
|
|
||||||
|
func makeInconsistentCardinalityError(fqName string, labels, labelValues []string) error {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"%s: %q has %d variable labels named %q but %d values %q were provided",
|
||||||
|
errInconsistentCardinality, fqName,
|
||||||
|
len(labels), labels,
|
||||||
|
len(labelValues), labelValues,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateValuesInLabels(labels Labels, expectedNumberOfValues int) error {
|
||||||
|
if len(labels) != expectedNumberOfValues {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"%s: expected %d label values but got %d in %#v",
|
||||||
|
errInconsistentCardinality, expectedNumberOfValues,
|
||||||
|
len(labels), labels,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, val := range labels {
|
||||||
|
if !utf8.ValidString(val) {
|
||||||
|
return fmt.Errorf("label %s: value %q is not valid UTF-8", name, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateLabelValues(vals []string, expectedNumberOfValues int) error {
|
||||||
|
if len(vals) != expectedNumberOfValues {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"%s: expected %d label values but got %d in %#v",
|
||||||
|
errInconsistentCardinality, expectedNumberOfValues,
|
||||||
|
len(vals), vals,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, val := range vals {
|
||||||
|
if !utf8.ValidString(val) {
|
||||||
|
return fmt.Errorf("label value %q is not valid UTF-8", val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkLabelName(l string) bool {
|
||||||
|
return model.LabelName(l).IsValid() && !strings.HasPrefix(l, reservedLabelPrefix)
|
||||||
|
}
|
|
@ -15,11 +15,15 @@ package prometheus
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/golang/protobuf/proto"
|
||||||
|
"github.com/prometheus/common/model"
|
||||||
|
|
||||||
dto "github.com/prometheus/client_model/go"
|
dto "github.com/prometheus/client_model/go"
|
||||||
)
|
)
|
||||||
|
|
||||||
const separatorByte byte = 255
|
var separatorByteSlice = []byte{model.SeparatorByte} // For convenient use with xxhash.
|
||||||
|
|
||||||
// A Metric models a single sample value with its meta data being exported to
|
// A Metric models a single sample value with its meta data being exported to
|
||||||
// Prometheus. Implementations of Metric in this package are Gauge, Counter,
|
// Prometheus. Implementations of Metric in this package are Gauge, Counter,
|
||||||
|
@ -43,9 +47,8 @@ type Metric interface {
|
||||||
// While populating dto.Metric, it is the responsibility of the
|
// While populating dto.Metric, it is the responsibility of the
|
||||||
// implementation to ensure validity of the Metric protobuf (like valid
|
// implementation to ensure validity of the Metric protobuf (like valid
|
||||||
// UTF-8 strings or syntactically valid metric and label names). It is
|
// UTF-8 strings or syntactically valid metric and label names). It is
|
||||||
// recommended to sort labels lexicographically. (Implementers may find
|
// recommended to sort labels lexicographically. Callers of Write should
|
||||||
// LabelPairSorter useful for that.) Callers of Write should still make
|
// still make sure of sorting if they depend on it.
|
||||||
// sure of sorting if they depend on it.
|
|
||||||
Write(*dto.Metric) error
|
Write(*dto.Metric) error
|
||||||
// TODO(beorn7): The original rationale of passing in a pre-allocated
|
// TODO(beorn7): The original rationale of passing in a pre-allocated
|
||||||
// dto.Metric protobuf to save allocations has disappeared. The
|
// dto.Metric protobuf to save allocations has disappeared. The
|
||||||
|
@ -57,8 +60,9 @@ type Metric interface {
|
||||||
// implementation XXX has its own XXXOpts type, but in most cases, it is just be
|
// implementation XXX has its own XXXOpts type, but in most cases, it is just be
|
||||||
// an alias of this type (which might change when the requirement arises.)
|
// an alias of this type (which might change when the requirement arises.)
|
||||||
//
|
//
|
||||||
// It is mandatory to set Name and Help to a non-empty string. All other fields
|
// It is mandatory to set Name to a non-empty string. All other fields are
|
||||||
// are optional and can safely be left at their zero value.
|
// optional and can safely be left at their zero value, although it is strongly
|
||||||
|
// encouraged to set a Help string.
|
||||||
type Opts struct {
|
type Opts struct {
|
||||||
// Namespace, Subsystem, and Name are components of the fully-qualified
|
// Namespace, Subsystem, and Name are components of the fully-qualified
|
||||||
// name of the Metric (created by joining these components with
|
// name of the Metric (created by joining these components with
|
||||||
|
@ -69,7 +73,7 @@ type Opts struct {
|
||||||
Subsystem string
|
Subsystem string
|
||||||
Name string
|
Name string
|
||||||
|
|
||||||
// Help provides information about this metric. Mandatory!
|
// Help provides information about this metric.
|
||||||
//
|
//
|
||||||
// Metrics with the same fully-qualified name must have the same Help
|
// Metrics with the same fully-qualified name must have the same Help
|
||||||
// string.
|
// string.
|
||||||
|
@ -79,20 +83,12 @@ type Opts struct {
|
||||||
// with the same fully-qualified name must have the same label names in
|
// with the same fully-qualified name must have the same label names in
|
||||||
// their ConstLabels.
|
// their ConstLabels.
|
||||||
//
|
//
|
||||||
// Note that in most cases, labels have a value that varies during the
|
// ConstLabels are only used rarely. In particular, do not use them to
|
||||||
// lifetime of a process. Those labels are usually managed with a metric
|
// attach the same labels to all your metrics. Those use cases are
|
||||||
// vector collector (like CounterVec, GaugeVec, UntypedVec). ConstLabels
|
// better covered by target labels set by the scraping Prometheus
|
||||||
// serve only special purposes. One is for the special case where the
|
// server, or by one specific metric (e.g. a build_info or a
|
||||||
// value of a label does not change during the lifetime of a process,
|
// machine_role metric). See also
|
||||||
// e.g. if the revision of the running binary is put into a
|
// https://prometheus.io/docs/instrumenting/writing_exporters/#target-labels,-not-static-scraped-labels
|
||||||
// label. Another, more advanced purpose is if more than one Collector
|
|
||||||
// needs to collect Metrics with the same fully-qualified name. In that
|
|
||||||
// case, those Metrics must differ in the values of their
|
|
||||||
// ConstLabels. See the Collector examples.
|
|
||||||
//
|
|
||||||
// If the value of a label never changes (not even between binaries),
|
|
||||||
// that label most likely should not be a label at all (but part of the
|
|
||||||
// metric name).
|
|
||||||
ConstLabels Labels
|
ConstLabels Labels
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,37 +114,22 @@ func BuildFQName(namespace, subsystem, name string) string {
|
||||||
return name
|
return name
|
||||||
}
|
}
|
||||||
|
|
||||||
// LabelPairSorter implements sort.Interface. It is used to sort a slice of
|
// labelPairSorter implements sort.Interface. It is used to sort a slice of
|
||||||
// dto.LabelPair pointers. This is useful for implementing the Write method of
|
// dto.LabelPair pointers.
|
||||||
// custom metrics.
|
type labelPairSorter []*dto.LabelPair
|
||||||
type LabelPairSorter []*dto.LabelPair
|
|
||||||
|
|
||||||
func (s LabelPairSorter) Len() int {
|
func (s labelPairSorter) Len() int {
|
||||||
return len(s)
|
return len(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s LabelPairSorter) Swap(i, j int) {
|
func (s labelPairSorter) Swap(i, j int) {
|
||||||
s[i], s[j] = s[j], s[i]
|
s[i], s[j] = s[j], s[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s LabelPairSorter) Less(i, j int) bool {
|
func (s labelPairSorter) Less(i, j int) bool {
|
||||||
return s[i].GetName() < s[j].GetName()
|
return s[i].GetName() < s[j].GetName()
|
||||||
}
|
}
|
||||||
|
|
||||||
type hashSorter []uint64
|
|
||||||
|
|
||||||
func (s hashSorter) Len() int {
|
|
||||||
return len(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s hashSorter) Swap(i, j int) {
|
|
||||||
s[i], s[j] = s[j], s[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s hashSorter) Less(i, j int) bool {
|
|
||||||
return s[i] < s[j]
|
|
||||||
}
|
|
||||||
|
|
||||||
type invalidMetric struct {
|
type invalidMetric struct {
|
||||||
desc *Desc
|
desc *Desc
|
||||||
err error
|
err error
|
||||||
|
@ -164,3 +145,31 @@ func NewInvalidMetric(desc *Desc, err error) Metric {
|
||||||
func (m *invalidMetric) Desc() *Desc { return m.desc }
|
func (m *invalidMetric) Desc() *Desc { return m.desc }
|
||||||
|
|
||||||
func (m *invalidMetric) Write(*dto.Metric) error { return m.err }
|
func (m *invalidMetric) Write(*dto.Metric) error { return m.err }
|
||||||
|
|
||||||
|
type timestampedMetric struct {
|
||||||
|
Metric
|
||||||
|
t time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m timestampedMetric) Write(pb *dto.Metric) error {
|
||||||
|
e := m.Metric.Write(pb)
|
||||||
|
pb.TimestampMs = proto.Int64(m.t.Unix()*1000 + int64(m.t.Nanosecond()/1000000))
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMetricWithTimestamp returns a new Metric wrapping the provided Metric in a
|
||||||
|
// way that it has an explicit timestamp set to the provided Time. This is only
|
||||||
|
// useful in rare cases as the timestamp of a Prometheus metric should usually
|
||||||
|
// be set by the Prometheus server during scraping. Exceptions include mirroring
|
||||||
|
// metrics with given timestamps from other metric
|
||||||
|
// sources.
|
||||||
|
//
|
||||||
|
// NewMetricWithTimestamp works best with MustNewConstMetric,
|
||||||
|
// MustNewConstHistogram, and MustNewConstSummary, see example.
|
||||||
|
//
|
||||||
|
// Currently, the exposition formats used by Prometheus are limited to
|
||||||
|
// millisecond resolution. Thus, the provided time will be rounded down to the
|
||||||
|
// next full millisecond value.
|
||||||
|
func NewMetricWithTimestamp(t time.Time, m Metric) Metric {
|
||||||
|
return timestampedMetric{Metric: m, t: t}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
// Copyright 2017 The Prometheus Authors
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package prometheus
|
||||||
|
|
||||||
|
// Observer is the interface that wraps the Observe method, which is used by
|
||||||
|
// Histogram and Summary to add observations.
|
||||||
|
type Observer interface {
|
||||||
|
Observe(float64)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The ObserverFunc type is an adapter to allow the use of ordinary
|
||||||
|
// functions as Observers. If f is a function with the appropriate
|
||||||
|
// signature, ObserverFunc(f) is an Observer that calls f.
|
||||||
|
//
|
||||||
|
// This adapter is usually used in connection with the Timer type, and there are
|
||||||
|
// two general use cases:
|
||||||
|
//
|
||||||
|
// The most common one is to use a Gauge as the Observer for a Timer.
|
||||||
|
// See the "Gauge" Timer example.
|
||||||
|
//
|
||||||
|
// The more advanced use case is to create a function that dynamically decides
|
||||||
|
// which Observer to use for observing the duration. See the "Complex" Timer
|
||||||
|
// example.
|
||||||
|
type ObserverFunc func(float64)
|
||||||
|
|
||||||
|
// Observe calls f(value). It implements Observer.
|
||||||
|
func (f ObserverFunc) Observe(value float64) {
|
||||||
|
f(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ObserverVec is an interface implemented by `HistogramVec` and `SummaryVec`.
|
||||||
|
type ObserverVec interface {
|
||||||
|
GetMetricWith(Labels) (Observer, error)
|
||||||
|
GetMetricWithLabelValues(lvs ...string) (Observer, error)
|
||||||
|
With(Labels) Observer
|
||||||
|
WithLabelValues(...string) Observer
|
||||||
|
CurryWith(Labels) (ObserverVec, error)
|
||||||
|
MustCurryWith(Labels) ObserverVec
|
||||||
|
|
||||||
|
Collector
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExemplarObserver is implemented by Observers that offer the option of
|
||||||
|
// observing a value together with an exemplar. Its ObserveWithExemplar method
|
||||||
|
// works like the Observe method of an Observer but also replaces the currently
|
||||||
|
// saved exemplar (if any) with a new one, created from the provided value, the
|
||||||
|
// current time as timestamp, and the provided Labels. Empty Labels will lead to
|
||||||
|
// a valid (label-less) exemplar. But if Labels is nil, the current exemplar is
|
||||||
|
// left in place. ObserveWithExemplar panics if any of the provided labels are
|
||||||
|
// invalid or if the provided labels contain more than 64 runes in total.
|
||||||
|
type ExemplarObserver interface {
|
||||||
|
ObserveWithExemplar(value float64, exemplar Labels)
|
||||||
|
}
|
|
@ -13,89 +13,126 @@
|
||||||
|
|
||||||
package prometheus
|
package prometheus
|
||||||
|
|
||||||
import "github.com/prometheus/procfs"
|
import (
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
type processCollector struct {
|
type processCollector struct {
|
||||||
pid int
|
|
||||||
collectFn func(chan<- Metric)
|
collectFn func(chan<- Metric)
|
||||||
pidFn func() (int, error)
|
pidFn func() (int, error)
|
||||||
cpuTotal Counter
|
reportErrors bool
|
||||||
openFDs, maxFDs Gauge
|
cpuTotal *Desc
|
||||||
vsize, rss Gauge
|
openFDs, maxFDs *Desc
|
||||||
startTime Gauge
|
vsize, maxVsize *Desc
|
||||||
|
rss *Desc
|
||||||
|
startTime *Desc
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProcessCollectorOpts defines the behavior of a process metrics collector
|
||||||
|
// created with NewProcessCollector.
|
||||||
|
type ProcessCollectorOpts struct {
|
||||||
|
// PidFn returns the PID of the process the collector collects metrics
|
||||||
|
// for. It is called upon each collection. By default, the PID of the
|
||||||
|
// current process is used, as determined on construction time by
|
||||||
|
// calling os.Getpid().
|
||||||
|
PidFn func() (int, error)
|
||||||
|
// If non-empty, each of the collected metrics is prefixed by the
|
||||||
|
// provided string and an underscore ("_").
|
||||||
|
Namespace string
|
||||||
|
// If true, any error encountered during collection is reported as an
|
||||||
|
// invalid metric (see NewInvalidMetric). Otherwise, errors are ignored
|
||||||
|
// and the collected metrics will be incomplete. (Possibly, no metrics
|
||||||
|
// will be collected at all.) While that's usually not desired, it is
|
||||||
|
// appropriate for the common "mix-in" of process metrics, where process
|
||||||
|
// metrics are nice to have, but failing to collect them should not
|
||||||
|
// disrupt the collection of the remaining metrics.
|
||||||
|
ReportErrors bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewProcessCollector returns a collector which exports the current state of
|
// NewProcessCollector returns a collector which exports the current state of
|
||||||
// process metrics including cpu, memory and file descriptor usage as well as
|
// process metrics including CPU, memory and file descriptor usage as well as
|
||||||
// the process start time for the given process id under the given namespace.
|
// the process start time. The detailed behavior is defined by the provided
|
||||||
func NewProcessCollector(pid int, namespace string) Collector {
|
// ProcessCollectorOpts. The zero value of ProcessCollectorOpts creates a
|
||||||
return NewProcessCollectorPIDFn(
|
// collector for the current process with an empty namespace string and no error
|
||||||
func() (int, error) { return pid, nil },
|
// reporting.
|
||||||
namespace,
|
//
|
||||||
)
|
// The collector only works on operating systems with a Linux-style proc
|
||||||
}
|
// filesystem and on Microsoft Windows. On other operating systems, it will not
|
||||||
|
// collect any metrics.
|
||||||
|
func NewProcessCollector(opts ProcessCollectorOpts) Collector {
|
||||||
|
ns := ""
|
||||||
|
if len(opts.Namespace) > 0 {
|
||||||
|
ns = opts.Namespace + "_"
|
||||||
|
}
|
||||||
|
|
||||||
// NewProcessCollectorPIDFn returns a collector which exports the current state
|
c := &processCollector{
|
||||||
// of process metrics including cpu, memory and file descriptor usage as well
|
reportErrors: opts.ReportErrors,
|
||||||
// as the process start time under the given namespace. The given pidFn is
|
cpuTotal: NewDesc(
|
||||||
// called on each collect and is used to determine the process to export
|
ns+"process_cpu_seconds_total",
|
||||||
// metrics for.
|
"Total user and system CPU time spent in seconds.",
|
||||||
func NewProcessCollectorPIDFn(
|
nil, nil,
|
||||||
pidFn func() (int, error),
|
),
|
||||||
namespace string,
|
openFDs: NewDesc(
|
||||||
) Collector {
|
ns+"process_open_fds",
|
||||||
c := processCollector{
|
"Number of open file descriptors.",
|
||||||
pidFn: pidFn,
|
nil, nil,
|
||||||
collectFn: func(chan<- Metric) {},
|
),
|
||||||
|
maxFDs: NewDesc(
|
||||||
|
ns+"process_max_fds",
|
||||||
|
"Maximum number of open file descriptors.",
|
||||||
|
nil, nil,
|
||||||
|
),
|
||||||
|
vsize: NewDesc(
|
||||||
|
ns+"process_virtual_memory_bytes",
|
||||||
|
"Virtual memory size in bytes.",
|
||||||
|
nil, nil,
|
||||||
|
),
|
||||||
|
maxVsize: NewDesc(
|
||||||
|
ns+"process_virtual_memory_max_bytes",
|
||||||
|
"Maximum amount of virtual memory available in bytes.",
|
||||||
|
nil, nil,
|
||||||
|
),
|
||||||
|
rss: NewDesc(
|
||||||
|
ns+"process_resident_memory_bytes",
|
||||||
|
"Resident memory size in bytes.",
|
||||||
|
nil, nil,
|
||||||
|
),
|
||||||
|
startTime: NewDesc(
|
||||||
|
ns+"process_start_time_seconds",
|
||||||
|
"Start time of the process since unix epoch in seconds.",
|
||||||
|
nil, nil,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
cpuTotal: NewCounter(CounterOpts{
|
if opts.PidFn == nil {
|
||||||
Namespace: namespace,
|
pid := os.Getpid()
|
||||||
Name: "process_cpu_seconds_total",
|
c.pidFn = func() (int, error) { return pid, nil }
|
||||||
Help: "Total user and system CPU time spent in seconds.",
|
} else {
|
||||||
}),
|
c.pidFn = opts.PidFn
|
||||||
openFDs: NewGauge(GaugeOpts{
|
|
||||||
Namespace: namespace,
|
|
||||||
Name: "process_open_fds",
|
|
||||||
Help: "Number of open file descriptors.",
|
|
||||||
}),
|
|
||||||
maxFDs: NewGauge(GaugeOpts{
|
|
||||||
Namespace: namespace,
|
|
||||||
Name: "process_max_fds",
|
|
||||||
Help: "Maximum number of open file descriptors.",
|
|
||||||
}),
|
|
||||||
vsize: NewGauge(GaugeOpts{
|
|
||||||
Namespace: namespace,
|
|
||||||
Name: "process_virtual_memory_bytes",
|
|
||||||
Help: "Virtual memory size in bytes.",
|
|
||||||
}),
|
|
||||||
rss: NewGauge(GaugeOpts{
|
|
||||||
Namespace: namespace,
|
|
||||||
Name: "process_resident_memory_bytes",
|
|
||||||
Help: "Resident memory size in bytes.",
|
|
||||||
}),
|
|
||||||
startTime: NewGauge(GaugeOpts{
|
|
||||||
Namespace: namespace,
|
|
||||||
Name: "process_start_time_seconds",
|
|
||||||
Help: "Start time of the process since unix epoch in seconds.",
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up process metric collection if supported by the runtime.
|
// Set up process metric collection if supported by the runtime.
|
||||||
if _, err := procfs.NewStat(); err == nil {
|
if canCollectProcess() {
|
||||||
c.collectFn = c.processCollect
|
c.collectFn = c.processCollect
|
||||||
|
} else {
|
||||||
|
c.collectFn = func(ch chan<- Metric) {
|
||||||
|
c.reportError(ch, nil, errors.New("process metrics not supported on this platform"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
// Describe returns all descriptions of the collector.
|
// Describe returns all descriptions of the collector.
|
||||||
func (c *processCollector) Describe(ch chan<- *Desc) {
|
func (c *processCollector) Describe(ch chan<- *Desc) {
|
||||||
ch <- c.cpuTotal.Desc()
|
ch <- c.cpuTotal
|
||||||
ch <- c.openFDs.Desc()
|
ch <- c.openFDs
|
||||||
ch <- c.maxFDs.Desc()
|
ch <- c.maxFDs
|
||||||
ch <- c.vsize.Desc()
|
ch <- c.vsize
|
||||||
ch <- c.rss.Desc()
|
ch <- c.maxVsize
|
||||||
ch <- c.startTime.Desc()
|
ch <- c.rss
|
||||||
|
ch <- c.startTime
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect returns the current state of all metrics of the collector.
|
// Collect returns the current state of all metrics of the collector.
|
||||||
|
@ -103,40 +140,12 @@ func (c *processCollector) Collect(ch chan<- Metric) {
|
||||||
c.collectFn(ch)
|
c.collectFn(ch)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(ts): Bring back error reporting by reverting 7faf9e7 as soon as the
|
func (c *processCollector) reportError(ch chan<- Metric, desc *Desc, err error) {
|
||||||
// client allows users to configure the error behavior.
|
if !c.reportErrors {
|
||||||
func (c *processCollector) processCollect(ch chan<- Metric) {
|
|
||||||
pid, err := c.pidFn()
|
|
||||||
if err != nil {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if desc == nil {
|
||||||
p, err := procfs.NewProc(pid)
|
desc = NewInvalidDesc(err)
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if stat, err := p.NewStat(); err == nil {
|
|
||||||
c.cpuTotal.Set(stat.CPUTime())
|
|
||||||
ch <- c.cpuTotal
|
|
||||||
c.vsize.Set(float64(stat.VirtualMemory()))
|
|
||||||
ch <- c.vsize
|
|
||||||
c.rss.Set(float64(stat.ResidentMemory()))
|
|
||||||
ch <- c.rss
|
|
||||||
|
|
||||||
if startTime, err := stat.StartTime(); err == nil {
|
|
||||||
c.startTime.Set(startTime)
|
|
||||||
ch <- c.startTime
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if fds, err := p.FileDescriptorsLen(); err == nil {
|
|
||||||
c.openFDs.Set(float64(fds))
|
|
||||||
ch <- c.openFDs
|
|
||||||
}
|
|
||||||
|
|
||||||
if limits, err := p.NewLimits(); err == nil {
|
|
||||||
c.maxFDs.Set(float64(limits.OpenFiles))
|
|
||||||
ch <- c.maxFDs
|
|
||||||
}
|
}
|
||||||
|
ch <- NewInvalidMetric(desc, err)
|
||||||
}
|
}
|
||||||
|
|
65
vendor/github.com/prometheus/client_golang/prometheus/process_collector_other.go
generated
vendored
Normal file
65
vendor/github.com/prometheus/client_golang/prometheus/process_collector_other.go
generated
vendored
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
// Copyright 2019 The Prometheus Authors
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package prometheus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/prometheus/procfs"
|
||||||
|
)
|
||||||
|
|
||||||
|
func canCollectProcess() bool {
|
||||||
|
_, err := procfs.NewDefaultFS()
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *processCollector) processCollect(ch chan<- Metric) {
|
||||||
|
pid, err := c.pidFn()
|
||||||
|
if err != nil {
|
||||||
|
c.reportError(ch, nil, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
p, err := procfs.NewProc(pid)
|
||||||
|
if err != nil {
|
||||||
|
c.reportError(ch, nil, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if stat, err := p.Stat(); err == nil {
|
||||||
|
ch <- MustNewConstMetric(c.cpuTotal, CounterValue, stat.CPUTime())
|
||||||
|
ch <- MustNewConstMetric(c.vsize, GaugeValue, float64(stat.VirtualMemory()))
|
||||||
|
ch <- MustNewConstMetric(c.rss, GaugeValue, float64(stat.ResidentMemory()))
|
||||||
|
if startTime, err := stat.StartTime(); err == nil {
|
||||||
|
ch <- MustNewConstMetric(c.startTime, GaugeValue, startTime)
|
||||||
|
} else {
|
||||||
|
c.reportError(ch, c.startTime, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
c.reportError(ch, nil, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if fds, err := p.FileDescriptorsLen(); err == nil {
|
||||||
|
ch <- MustNewConstMetric(c.openFDs, GaugeValue, float64(fds))
|
||||||
|
} else {
|
||||||
|
c.reportError(ch, c.openFDs, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if limits, err := p.Limits(); err == nil {
|
||||||
|
ch <- MustNewConstMetric(c.maxFDs, GaugeValue, float64(limits.OpenFiles))
|
||||||
|
ch <- MustNewConstMetric(c.maxVsize, GaugeValue, float64(limits.AddressSpace))
|
||||||
|
} else {
|
||||||
|
c.reportError(ch, nil, err)
|
||||||
|
}
|
||||||
|
}
|
116
vendor/github.com/prometheus/client_golang/prometheus/process_collector_windows.go
generated
vendored
Normal file
116
vendor/github.com/prometheus/client_golang/prometheus/process_collector_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
// Copyright 2019 The Prometheus Authors
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package prometheus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
func canCollectProcess() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
modpsapi = syscall.NewLazyDLL("psapi.dll")
|
||||||
|
modkernel32 = syscall.NewLazyDLL("kernel32.dll")
|
||||||
|
|
||||||
|
procGetProcessMemoryInfo = modpsapi.NewProc("GetProcessMemoryInfo")
|
||||||
|
procGetProcessHandleCount = modkernel32.NewProc("GetProcessHandleCount")
|
||||||
|
)
|
||||||
|
|
||||||
|
type processMemoryCounters struct {
|
||||||
|
// System interface description
|
||||||
|
// https://docs.microsoft.com/en-us/windows/desktop/api/psapi/ns-psapi-process_memory_counters_ex
|
||||||
|
|
||||||
|
// Refer to the Golang internal implementation
|
||||||
|
// https://golang.org/src/internal/syscall/windows/psapi_windows.go
|
||||||
|
_ uint32
|
||||||
|
PageFaultCount uint32
|
||||||
|
PeakWorkingSetSize uintptr
|
||||||
|
WorkingSetSize uintptr
|
||||||
|
QuotaPeakPagedPoolUsage uintptr
|
||||||
|
QuotaPagedPoolUsage uintptr
|
||||||
|
QuotaPeakNonPagedPoolUsage uintptr
|
||||||
|
QuotaNonPagedPoolUsage uintptr
|
||||||
|
PagefileUsage uintptr
|
||||||
|
PeakPagefileUsage uintptr
|
||||||
|
PrivateUsage uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
func getProcessMemoryInfo(handle windows.Handle) (processMemoryCounters, error) {
|
||||||
|
mem := processMemoryCounters{}
|
||||||
|
r1, _, err := procGetProcessMemoryInfo.Call(
|
||||||
|
uintptr(handle),
|
||||||
|
uintptr(unsafe.Pointer(&mem)),
|
||||||
|
uintptr(unsafe.Sizeof(mem)),
|
||||||
|
)
|
||||||
|
if r1 != 1 {
|
||||||
|
return mem, err
|
||||||
|
} else {
|
||||||
|
return mem, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getProcessHandleCount(handle windows.Handle) (uint32, error) {
|
||||||
|
var count uint32
|
||||||
|
r1, _, err := procGetProcessHandleCount.Call(
|
||||||
|
uintptr(handle),
|
||||||
|
uintptr(unsafe.Pointer(&count)),
|
||||||
|
)
|
||||||
|
if r1 != 1 {
|
||||||
|
return 0, err
|
||||||
|
} else {
|
||||||
|
return count, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *processCollector) processCollect(ch chan<- Metric) {
|
||||||
|
h, err := windows.GetCurrentProcess()
|
||||||
|
if err != nil {
|
||||||
|
c.reportError(ch, nil, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var startTime, exitTime, kernelTime, userTime windows.Filetime
|
||||||
|
err = windows.GetProcessTimes(h, &startTime, &exitTime, &kernelTime, &userTime)
|
||||||
|
if err != nil {
|
||||||
|
c.reportError(ch, nil, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ch <- MustNewConstMetric(c.startTime, GaugeValue, float64(startTime.Nanoseconds()/1e9))
|
||||||
|
ch <- MustNewConstMetric(c.cpuTotal, CounterValue, fileTimeToSeconds(kernelTime)+fileTimeToSeconds(userTime))
|
||||||
|
|
||||||
|
mem, err := getProcessMemoryInfo(h)
|
||||||
|
if err != nil {
|
||||||
|
c.reportError(ch, nil, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ch <- MustNewConstMetric(c.vsize, GaugeValue, float64(mem.PrivateUsage))
|
||||||
|
ch <- MustNewConstMetric(c.rss, GaugeValue, float64(mem.WorkingSetSize))
|
||||||
|
|
||||||
|
handles, err := getProcessHandleCount(h)
|
||||||
|
if err != nil {
|
||||||
|
c.reportError(ch, nil, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ch <- MustNewConstMetric(c.openFDs, GaugeValue, float64(handles))
|
||||||
|
ch <- MustNewConstMetric(c.maxFDs, GaugeValue, float64(16*1024*1024)) // Windows has a hard-coded max limit, not per-process.
|
||||||
|
}
|
||||||
|
|
||||||
|
func fileTimeToSeconds(ft windows.Filetime) float64 {
|
||||||
|
return float64(uint64(ft.HighDateTime)<<32+uint64(ft.LowDateTime)) / 1e7
|
||||||
|
}
|
370
vendor/github.com/prometheus/client_golang/prometheus/promhttp/delegator.go
generated
vendored
Normal file
370
vendor/github.com/prometheus/client_golang/prometheus/promhttp/delegator.go
generated
vendored
Normal file
|
@ -0,0 +1,370 @@
|
||||||
|
// Copyright 2017 The Prometheus Authors
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package promhttp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
closeNotifier = 1 << iota
|
||||||
|
flusher
|
||||||
|
hijacker
|
||||||
|
readerFrom
|
||||||
|
pusher
|
||||||
|
)
|
||||||
|
|
||||||
|
type delegator interface {
|
||||||
|
http.ResponseWriter
|
||||||
|
|
||||||
|
Status() int
|
||||||
|
Written() int64
|
||||||
|
}
|
||||||
|
|
||||||
|
type responseWriterDelegator struct {
|
||||||
|
http.ResponseWriter
|
||||||
|
|
||||||
|
status int
|
||||||
|
written int64
|
||||||
|
wroteHeader bool
|
||||||
|
observeWriteHeader func(int)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *responseWriterDelegator) Status() int {
|
||||||
|
return r.status
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *responseWriterDelegator) Written() int64 {
|
||||||
|
return r.written
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *responseWriterDelegator) WriteHeader(code int) {
|
||||||
|
if r.observeWriteHeader != nil && !r.wroteHeader {
|
||||||
|
// Only call observeWriteHeader for the 1st time. It's a bug if
|
||||||
|
// WriteHeader is called more than once, but we want to protect
|
||||||
|
// against it here. Note that we still delegate the WriteHeader
|
||||||
|
// to the original ResponseWriter to not mask the bug from it.
|
||||||
|
r.observeWriteHeader(code)
|
||||||
|
}
|
||||||
|
r.status = code
|
||||||
|
r.wroteHeader = true
|
||||||
|
r.ResponseWriter.WriteHeader(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *responseWriterDelegator) Write(b []byte) (int, error) {
|
||||||
|
// If applicable, call WriteHeader here so that observeWriteHeader is
|
||||||
|
// handled appropriately.
|
||||||
|
if !r.wroteHeader {
|
||||||
|
r.WriteHeader(http.StatusOK)
|
||||||
|
}
|
||||||
|
n, err := r.ResponseWriter.Write(b)
|
||||||
|
r.written += int64(n)
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
type closeNotifierDelegator struct{ *responseWriterDelegator }
|
||||||
|
type flusherDelegator struct{ *responseWriterDelegator }
|
||||||
|
type hijackerDelegator struct{ *responseWriterDelegator }
|
||||||
|
type readerFromDelegator struct{ *responseWriterDelegator }
|
||||||
|
type pusherDelegator struct{ *responseWriterDelegator }
|
||||||
|
|
||||||
|
func (d closeNotifierDelegator) CloseNotify() <-chan bool {
|
||||||
|
//lint:ignore SA1019 http.CloseNotifier is deprecated but we don't want to
|
||||||
|
//remove support from client_golang yet.
|
||||||
|
return d.ResponseWriter.(http.CloseNotifier).CloseNotify()
|
||||||
|
}
|
||||||
|
func (d flusherDelegator) Flush() {
|
||||||
|
// If applicable, call WriteHeader here so that observeWriteHeader is
|
||||||
|
// handled appropriately.
|
||||||
|
if !d.wroteHeader {
|
||||||
|
d.WriteHeader(http.StatusOK)
|
||||||
|
}
|
||||||
|
d.ResponseWriter.(http.Flusher).Flush()
|
||||||
|
}
|
||||||
|
func (d hijackerDelegator) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||||
|
return d.ResponseWriter.(http.Hijacker).Hijack()
|
||||||
|
}
|
||||||
|
func (d readerFromDelegator) ReadFrom(re io.Reader) (int64, error) {
|
||||||
|
// If applicable, call WriteHeader here so that observeWriteHeader is
|
||||||
|
// handled appropriately.
|
||||||
|
if !d.wroteHeader {
|
||||||
|
d.WriteHeader(http.StatusOK)
|
||||||
|
}
|
||||||
|
n, err := d.ResponseWriter.(io.ReaderFrom).ReadFrom(re)
|
||||||
|
d.written += n
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
func (d pusherDelegator) Push(target string, opts *http.PushOptions) error {
|
||||||
|
return d.ResponseWriter.(http.Pusher).Push(target, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
var pickDelegator = make([]func(*responseWriterDelegator) delegator, 32)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// TODO(beorn7): Code generation would help here.
|
||||||
|
pickDelegator[0] = func(d *responseWriterDelegator) delegator { // 0
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
pickDelegator[closeNotifier] = func(d *responseWriterDelegator) delegator { // 1
|
||||||
|
return closeNotifierDelegator{d}
|
||||||
|
}
|
||||||
|
pickDelegator[flusher] = func(d *responseWriterDelegator) delegator { // 2
|
||||||
|
return flusherDelegator{d}
|
||||||
|
}
|
||||||
|
pickDelegator[flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 3
|
||||||
|
return struct {
|
||||||
|
*responseWriterDelegator
|
||||||
|
http.Flusher
|
||||||
|
http.CloseNotifier
|
||||||
|
}{d, flusherDelegator{d}, closeNotifierDelegator{d}}
|
||||||
|
}
|
||||||
|
pickDelegator[hijacker] = func(d *responseWriterDelegator) delegator { // 4
|
||||||
|
return hijackerDelegator{d}
|
||||||
|
}
|
||||||
|
pickDelegator[hijacker+closeNotifier] = func(d *responseWriterDelegator) delegator { // 5
|
||||||
|
return struct {
|
||||||
|
*responseWriterDelegator
|
||||||
|
http.Hijacker
|
||||||
|
http.CloseNotifier
|
||||||
|
}{d, hijackerDelegator{d}, closeNotifierDelegator{d}}
|
||||||
|
}
|
||||||
|
pickDelegator[hijacker+flusher] = func(d *responseWriterDelegator) delegator { // 6
|
||||||
|
return struct {
|
||||||
|
*responseWriterDelegator
|
||||||
|
http.Hijacker
|
||||||
|
http.Flusher
|
||||||
|
}{d, hijackerDelegator{d}, flusherDelegator{d}}
|
||||||
|
}
|
||||||
|
pickDelegator[hijacker+flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 7
|
||||||
|
return struct {
|
||||||
|
*responseWriterDelegator
|
||||||
|
http.Hijacker
|
||||||
|
http.Flusher
|
||||||
|
http.CloseNotifier
|
||||||
|
}{d, hijackerDelegator{d}, flusherDelegator{d}, closeNotifierDelegator{d}}
|
||||||
|
}
|
||||||
|
pickDelegator[readerFrom] = func(d *responseWriterDelegator) delegator { // 8
|
||||||
|
return readerFromDelegator{d}
|
||||||
|
}
|
||||||
|
pickDelegator[readerFrom+closeNotifier] = func(d *responseWriterDelegator) delegator { // 9
|
||||||
|
return struct {
|
||||||
|
*responseWriterDelegator
|
||||||
|
io.ReaderFrom
|
||||||
|
http.CloseNotifier
|
||||||
|
}{d, readerFromDelegator{d}, closeNotifierDelegator{d}}
|
||||||
|
}
|
||||||
|
pickDelegator[readerFrom+flusher] = func(d *responseWriterDelegator) delegator { // 10
|
||||||
|
return struct {
|
||||||
|
*responseWriterDelegator
|
||||||
|
io.ReaderFrom
|
||||||
|
http.Flusher
|
||||||
|
}{d, readerFromDelegator{d}, flusherDelegator{d}}
|
||||||
|
}
|
||||||
|
pickDelegator[readerFrom+flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 11
|
||||||
|
return struct {
|
||||||
|
*responseWriterDelegator
|
||||||
|
io.ReaderFrom
|
||||||
|
http.Flusher
|
||||||
|
http.CloseNotifier
|
||||||
|
}{d, readerFromDelegator{d}, flusherDelegator{d}, closeNotifierDelegator{d}}
|
||||||
|
}
|
||||||
|
pickDelegator[readerFrom+hijacker] = func(d *responseWriterDelegator) delegator { // 12
|
||||||
|
return struct {
|
||||||
|
*responseWriterDelegator
|
||||||
|
io.ReaderFrom
|
||||||
|
http.Hijacker
|
||||||
|
}{d, readerFromDelegator{d}, hijackerDelegator{d}}
|
||||||
|
}
|
||||||
|
pickDelegator[readerFrom+hijacker+closeNotifier] = func(d *responseWriterDelegator) delegator { // 13
|
||||||
|
return struct {
|
||||||
|
*responseWriterDelegator
|
||||||
|
io.ReaderFrom
|
||||||
|
http.Hijacker
|
||||||
|
http.CloseNotifier
|
||||||
|
}{d, readerFromDelegator{d}, hijackerDelegator{d}, closeNotifierDelegator{d}}
|
||||||
|
}
|
||||||
|
pickDelegator[readerFrom+hijacker+flusher] = func(d *responseWriterDelegator) delegator { // 14
|
||||||
|
return struct {
|
||||||
|
*responseWriterDelegator
|
||||||
|
io.ReaderFrom
|
||||||
|
http.Hijacker
|
||||||
|
http.Flusher
|
||||||
|
}{d, readerFromDelegator{d}, hijackerDelegator{d}, flusherDelegator{d}}
|
||||||
|
}
|
||||||
|
pickDelegator[readerFrom+hijacker+flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 15
|
||||||
|
return struct {
|
||||||
|
*responseWriterDelegator
|
||||||
|
io.ReaderFrom
|
||||||
|
http.Hijacker
|
||||||
|
http.Flusher
|
||||||
|
http.CloseNotifier
|
||||||
|
}{d, readerFromDelegator{d}, hijackerDelegator{d}, flusherDelegator{d}, closeNotifierDelegator{d}}
|
||||||
|
}
|
||||||
|
pickDelegator[pusher] = func(d *responseWriterDelegator) delegator { // 16
|
||||||
|
return pusherDelegator{d}
|
||||||
|
}
|
||||||
|
pickDelegator[pusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 17
|
||||||
|
return struct {
|
||||||
|
*responseWriterDelegator
|
||||||
|
http.Pusher
|
||||||
|
http.CloseNotifier
|
||||||
|
}{d, pusherDelegator{d}, closeNotifierDelegator{d}}
|
||||||
|
}
|
||||||
|
pickDelegator[pusher+flusher] = func(d *responseWriterDelegator) delegator { // 18
|
||||||
|
return struct {
|
||||||
|
*responseWriterDelegator
|
||||||
|
http.Pusher
|
||||||
|
http.Flusher
|
||||||
|
}{d, pusherDelegator{d}, flusherDelegator{d}}
|
||||||
|
}
|
||||||
|
pickDelegator[pusher+flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 19
|
||||||
|
return struct {
|
||||||
|
*responseWriterDelegator
|
||||||
|
http.Pusher
|
||||||
|
http.Flusher
|
||||||
|
http.CloseNotifier
|
||||||
|
}{d, pusherDelegator{d}, flusherDelegator{d}, closeNotifierDelegator{d}}
|
||||||
|
}
|
||||||
|
pickDelegator[pusher+hijacker] = func(d *responseWriterDelegator) delegator { // 20
|
||||||
|
return struct {
|
||||||
|
*responseWriterDelegator
|
||||||
|
http.Pusher
|
||||||
|
http.Hijacker
|
||||||
|
}{d, pusherDelegator{d}, hijackerDelegator{d}}
|
||||||
|
}
|
||||||
|
pickDelegator[pusher+hijacker+closeNotifier] = func(d *responseWriterDelegator) delegator { // 21
|
||||||
|
return struct {
|
||||||
|
*responseWriterDelegator
|
||||||
|
http.Pusher
|
||||||
|
http.Hijacker
|
||||||
|
http.CloseNotifier
|
||||||
|
}{d, pusherDelegator{d}, hijackerDelegator{d}, closeNotifierDelegator{d}}
|
||||||
|
}
|
||||||
|
pickDelegator[pusher+hijacker+flusher] = func(d *responseWriterDelegator) delegator { // 22
|
||||||
|
return struct {
|
||||||
|
*responseWriterDelegator
|
||||||
|
http.Pusher
|
||||||
|
http.Hijacker
|
||||||
|
http.Flusher
|
||||||
|
}{d, pusherDelegator{d}, hijackerDelegator{d}, flusherDelegator{d}}
|
||||||
|
}
|
||||||
|
pickDelegator[pusher+hijacker+flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { //23
|
||||||
|
return struct {
|
||||||
|
*responseWriterDelegator
|
||||||
|
http.Pusher
|
||||||
|
http.Hijacker
|
||||||
|
http.Flusher
|
||||||
|
http.CloseNotifier
|
||||||
|
}{d, pusherDelegator{d}, hijackerDelegator{d}, flusherDelegator{d}, closeNotifierDelegator{d}}
|
||||||
|
}
|
||||||
|
pickDelegator[pusher+readerFrom] = func(d *responseWriterDelegator) delegator { // 24
|
||||||
|
return struct {
|
||||||
|
*responseWriterDelegator
|
||||||
|
http.Pusher
|
||||||
|
io.ReaderFrom
|
||||||
|
}{d, pusherDelegator{d}, readerFromDelegator{d}}
|
||||||
|
}
|
||||||
|
pickDelegator[pusher+readerFrom+closeNotifier] = func(d *responseWriterDelegator) delegator { // 25
|
||||||
|
return struct {
|
||||||
|
*responseWriterDelegator
|
||||||
|
http.Pusher
|
||||||
|
io.ReaderFrom
|
||||||
|
http.CloseNotifier
|
||||||
|
}{d, pusherDelegator{d}, readerFromDelegator{d}, closeNotifierDelegator{d}}
|
||||||
|
}
|
||||||
|
pickDelegator[pusher+readerFrom+flusher] = func(d *responseWriterDelegator) delegator { // 26
|
||||||
|
return struct {
|
||||||
|
*responseWriterDelegator
|
||||||
|
http.Pusher
|
||||||
|
io.ReaderFrom
|
||||||
|
http.Flusher
|
||||||
|
}{d, pusherDelegator{d}, readerFromDelegator{d}, flusherDelegator{d}}
|
||||||
|
}
|
||||||
|
pickDelegator[pusher+readerFrom+flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 27
|
||||||
|
return struct {
|
||||||
|
*responseWriterDelegator
|
||||||
|
http.Pusher
|
||||||
|
io.ReaderFrom
|
||||||
|
http.Flusher
|
||||||
|
http.CloseNotifier
|
||||||
|
}{d, pusherDelegator{d}, readerFromDelegator{d}, flusherDelegator{d}, closeNotifierDelegator{d}}
|
||||||
|
}
|
||||||
|
pickDelegator[pusher+readerFrom+hijacker] = func(d *responseWriterDelegator) delegator { // 28
|
||||||
|
return struct {
|
||||||
|
*responseWriterDelegator
|
||||||
|
http.Pusher
|
||||||
|
io.ReaderFrom
|
||||||
|
http.Hijacker
|
||||||
|
}{d, pusherDelegator{d}, readerFromDelegator{d}, hijackerDelegator{d}}
|
||||||
|
}
|
||||||
|
pickDelegator[pusher+readerFrom+hijacker+closeNotifier] = func(d *responseWriterDelegator) delegator { // 29
|
||||||
|
return struct {
|
||||||
|
*responseWriterDelegator
|
||||||
|
http.Pusher
|
||||||
|
io.ReaderFrom
|
||||||
|
http.Hijacker
|
||||||
|
http.CloseNotifier
|
||||||
|
}{d, pusherDelegator{d}, readerFromDelegator{d}, hijackerDelegator{d}, closeNotifierDelegator{d}}
|
||||||
|
}
|
||||||
|
pickDelegator[pusher+readerFrom+hijacker+flusher] = func(d *responseWriterDelegator) delegator { // 30
|
||||||
|
return struct {
|
||||||
|
*responseWriterDelegator
|
||||||
|
http.Pusher
|
||||||
|
io.ReaderFrom
|
||||||
|
http.Hijacker
|
||||||
|
http.Flusher
|
||||||
|
}{d, pusherDelegator{d}, readerFromDelegator{d}, hijackerDelegator{d}, flusherDelegator{d}}
|
||||||
|
}
|
||||||
|
pickDelegator[pusher+readerFrom+hijacker+flusher+closeNotifier] = func(d *responseWriterDelegator) delegator { // 31
|
||||||
|
return struct {
|
||||||
|
*responseWriterDelegator
|
||||||
|
http.Pusher
|
||||||
|
io.ReaderFrom
|
||||||
|
http.Hijacker
|
||||||
|
http.Flusher
|
||||||
|
http.CloseNotifier
|
||||||
|
}{d, pusherDelegator{d}, readerFromDelegator{d}, hijackerDelegator{d}, flusherDelegator{d}, closeNotifierDelegator{d}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDelegator(w http.ResponseWriter, observeWriteHeaderFunc func(int)) delegator {
|
||||||
|
d := &responseWriterDelegator{
|
||||||
|
ResponseWriter: w,
|
||||||
|
observeWriteHeader: observeWriteHeaderFunc,
|
||||||
|
}
|
||||||
|
|
||||||
|
id := 0
|
||||||
|
//lint:ignore SA1019 http.CloseNotifier is deprecated but we don't want to
|
||||||
|
//remove support from client_golang yet.
|
||||||
|
if _, ok := w.(http.CloseNotifier); ok {
|
||||||
|
id += closeNotifier
|
||||||
|
}
|
||||||
|
if _, ok := w.(http.Flusher); ok {
|
||||||
|
id += flusher
|
||||||
|
}
|
||||||
|
if _, ok := w.(http.Hijacker); ok {
|
||||||
|
id += hijacker
|
||||||
|
}
|
||||||
|
if _, ok := w.(io.ReaderFrom); ok {
|
||||||
|
id += readerFrom
|
||||||
|
}
|
||||||
|
if _, ok := w.(http.Pusher); ok {
|
||||||
|
id += pusher
|
||||||
|
}
|
||||||
|
|
||||||
|
return pickDelegator[id](d)
|
||||||
|
}
|
379
vendor/github.com/prometheus/client_golang/prometheus/promhttp/http.go
generated
vendored
Normal file
379
vendor/github.com/prometheus/client_golang/prometheus/promhttp/http.go
generated
vendored
Normal file
|
@ -0,0 +1,379 @@
|
||||||
|
// Copyright 2016 The Prometheus Authors
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// Package promhttp provides tooling around HTTP servers and clients.
|
||||||
|
//
|
||||||
|
// First, the package allows the creation of http.Handler instances to expose
|
||||||
|
// Prometheus metrics via HTTP. promhttp.Handler acts on the
|
||||||
|
// prometheus.DefaultGatherer. With HandlerFor, you can create a handler for a
|
||||||
|
// custom registry or anything that implements the Gatherer interface. It also
|
||||||
|
// allows the creation of handlers that act differently on errors or allow to
|
||||||
|
// log errors.
|
||||||
|
//
|
||||||
|
// Second, the package provides tooling to instrument instances of http.Handler
|
||||||
|
// via middleware. Middleware wrappers follow the naming scheme
|
||||||
|
// InstrumentHandlerX, where X describes the intended use of the middleware.
|
||||||
|
// See each function's doc comment for specific details.
|
||||||
|
//
|
||||||
|
// Finally, the package allows for an http.RoundTripper to be instrumented via
|
||||||
|
// middleware. Middleware wrappers follow the naming scheme
|
||||||
|
// InstrumentRoundTripperX, where X describes the intended use of the
|
||||||
|
// middleware. See each function's doc comment for specific details.
|
||||||
|
package promhttp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"compress/gzip"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/prometheus/common/expfmt"
|
||||||
|
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
contentTypeHeader = "Content-Type"
|
||||||
|
contentEncodingHeader = "Content-Encoding"
|
||||||
|
acceptEncodingHeader = "Accept-Encoding"
|
||||||
|
)
|
||||||
|
|
||||||
|
var gzipPool = sync.Pool{
|
||||||
|
New: func() interface{} {
|
||||||
|
return gzip.NewWriter(nil)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handler returns an http.Handler for the prometheus.DefaultGatherer, using
|
||||||
|
// default HandlerOpts, i.e. it reports the first error as an HTTP error, it has
|
||||||
|
// no error logging, and it applies compression if requested by the client.
|
||||||
|
//
|
||||||
|
// The returned http.Handler is already instrumented using the
|
||||||
|
// InstrumentMetricHandler function and the prometheus.DefaultRegisterer. If you
|
||||||
|
// create multiple http.Handlers by separate calls of the Handler function, the
|
||||||
|
// metrics used for instrumentation will be shared between them, providing
|
||||||
|
// global scrape counts.
|
||||||
|
//
|
||||||
|
// This function is meant to cover the bulk of basic use cases. If you are doing
|
||||||
|
// anything that requires more customization (including using a non-default
|
||||||
|
// Gatherer, different instrumentation, and non-default HandlerOpts), use the
|
||||||
|
// HandlerFor function. See there for details.
|
||||||
|
func Handler() http.Handler {
|
||||||
|
return InstrumentMetricHandler(
|
||||||
|
prometheus.DefaultRegisterer, HandlerFor(prometheus.DefaultGatherer, HandlerOpts{}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandlerFor returns an uninstrumented http.Handler for the provided
|
||||||
|
// Gatherer. The behavior of the Handler is defined by the provided
|
||||||
|
// HandlerOpts. Thus, HandlerFor is useful to create http.Handlers for custom
|
||||||
|
// Gatherers, with non-default HandlerOpts, and/or with custom (or no)
|
||||||
|
// instrumentation. Use the InstrumentMetricHandler function to apply the same
|
||||||
|
// kind of instrumentation as it is used by the Handler function.
|
||||||
|
func HandlerFor(reg prometheus.Gatherer, opts HandlerOpts) http.Handler {
|
||||||
|
var (
|
||||||
|
inFlightSem chan struct{}
|
||||||
|
errCnt = prometheus.NewCounterVec(
|
||||||
|
prometheus.CounterOpts{
|
||||||
|
Name: "promhttp_metric_handler_errors_total",
|
||||||
|
Help: "Total number of internal errors encountered by the promhttp metric handler.",
|
||||||
|
},
|
||||||
|
[]string{"cause"},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if opts.MaxRequestsInFlight > 0 {
|
||||||
|
inFlightSem = make(chan struct{}, opts.MaxRequestsInFlight)
|
||||||
|
}
|
||||||
|
if opts.Registry != nil {
|
||||||
|
// Initialize all possibilites that can occur below.
|
||||||
|
errCnt.WithLabelValues("gathering")
|
||||||
|
errCnt.WithLabelValues("encoding")
|
||||||
|
if err := opts.Registry.Register(errCnt); err != nil {
|
||||||
|
if are, ok := err.(prometheus.AlreadyRegisteredError); ok {
|
||||||
|
errCnt = are.ExistingCollector.(*prometheus.CounterVec)
|
||||||
|
} else {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h := http.HandlerFunc(func(rsp http.ResponseWriter, req *http.Request) {
|
||||||
|
if inFlightSem != nil {
|
||||||
|
select {
|
||||||
|
case inFlightSem <- struct{}{}: // All good, carry on.
|
||||||
|
defer func() { <-inFlightSem }()
|
||||||
|
default:
|
||||||
|
http.Error(rsp, fmt.Sprintf(
|
||||||
|
"Limit of concurrent requests reached (%d), try again later.", opts.MaxRequestsInFlight,
|
||||||
|
), http.StatusServiceUnavailable)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mfs, err := reg.Gather()
|
||||||
|
if err != nil {
|
||||||
|
if opts.ErrorLog != nil {
|
||||||
|
opts.ErrorLog.Println("error gathering metrics:", err)
|
||||||
|
}
|
||||||
|
errCnt.WithLabelValues("gathering").Inc()
|
||||||
|
switch opts.ErrorHandling {
|
||||||
|
case PanicOnError:
|
||||||
|
panic(err)
|
||||||
|
case ContinueOnError:
|
||||||
|
if len(mfs) == 0 {
|
||||||
|
// Still report the error if no metrics have been gathered.
|
||||||
|
httpError(rsp, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case HTTPErrorOnError:
|
||||||
|
httpError(rsp, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var contentType expfmt.Format
|
||||||
|
if opts.EnableOpenMetrics {
|
||||||
|
contentType = expfmt.NegotiateIncludingOpenMetrics(req.Header)
|
||||||
|
} else {
|
||||||
|
contentType = expfmt.Negotiate(req.Header)
|
||||||
|
}
|
||||||
|
header := rsp.Header()
|
||||||
|
header.Set(contentTypeHeader, string(contentType))
|
||||||
|
|
||||||
|
w := io.Writer(rsp)
|
||||||
|
if !opts.DisableCompression && gzipAccepted(req.Header) {
|
||||||
|
header.Set(contentEncodingHeader, "gzip")
|
||||||
|
gz := gzipPool.Get().(*gzip.Writer)
|
||||||
|
defer gzipPool.Put(gz)
|
||||||
|
|
||||||
|
gz.Reset(w)
|
||||||
|
defer gz.Close()
|
||||||
|
|
||||||
|
w = gz
|
||||||
|
}
|
||||||
|
|
||||||
|
enc := expfmt.NewEncoder(w, contentType)
|
||||||
|
|
||||||
|
// handleError handles the error according to opts.ErrorHandling
|
||||||
|
// and returns true if we have to abort after the handling.
|
||||||
|
handleError := func(err error) bool {
|
||||||
|
if err == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if opts.ErrorLog != nil {
|
||||||
|
opts.ErrorLog.Println("error encoding and sending metric family:", err)
|
||||||
|
}
|
||||||
|
errCnt.WithLabelValues("encoding").Inc()
|
||||||
|
switch opts.ErrorHandling {
|
||||||
|
case PanicOnError:
|
||||||
|
panic(err)
|
||||||
|
case HTTPErrorOnError:
|
||||||
|
// We cannot really send an HTTP error at this
|
||||||
|
// point because we most likely have written
|
||||||
|
// something to rsp already. But at least we can
|
||||||
|
// stop sending.
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// Do nothing in all other cases, including ContinueOnError.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, mf := range mfs {
|
||||||
|
if handleError(enc.Encode(mf)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if closer, ok := enc.(expfmt.Closer); ok {
|
||||||
|
// This in particular takes care of the final "# EOF\n" line for OpenMetrics.
|
||||||
|
if handleError(closer.Close()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if opts.Timeout <= 0 {
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
return http.TimeoutHandler(h, opts.Timeout, fmt.Sprintf(
|
||||||
|
"Exceeded configured timeout of %v.\n",
|
||||||
|
opts.Timeout,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
// InstrumentMetricHandler is usually used with an http.Handler returned by the
|
||||||
|
// HandlerFor function. It instruments the provided http.Handler with two
|
||||||
|
// metrics: A counter vector "promhttp_metric_handler_requests_total" to count
|
||||||
|
// scrapes partitioned by HTTP status code, and a gauge
|
||||||
|
// "promhttp_metric_handler_requests_in_flight" to track the number of
|
||||||
|
// simultaneous scrapes. This function idempotently registers collectors for
|
||||||
|
// both metrics with the provided Registerer. It panics if the registration
|
||||||
|
// fails. The provided metrics are useful to see how many scrapes hit the
|
||||||
|
// monitored target (which could be from different Prometheus servers or other
|
||||||
|
// scrapers), and how often they overlap (which would result in more than one
|
||||||
|
// scrape in flight at the same time). Note that the scrapes-in-flight gauge
|
||||||
|
// will contain the scrape by which it is exposed, while the scrape counter will
|
||||||
|
// only get incremented after the scrape is complete (as only then the status
|
||||||
|
// code is known). For tracking scrape durations, use the
|
||||||
|
// "scrape_duration_seconds" gauge created by the Prometheus server upon each
|
||||||
|
// scrape.
|
||||||
|
func InstrumentMetricHandler(reg prometheus.Registerer, handler http.Handler) http.Handler {
|
||||||
|
cnt := prometheus.NewCounterVec(
|
||||||
|
prometheus.CounterOpts{
|
||||||
|
Name: "promhttp_metric_handler_requests_total",
|
||||||
|
Help: "Total number of scrapes by HTTP status code.",
|
||||||
|
},
|
||||||
|
[]string{"code"},
|
||||||
|
)
|
||||||
|
// Initialize the most likely HTTP status codes.
|
||||||
|
cnt.WithLabelValues("200")
|
||||||
|
cnt.WithLabelValues("500")
|
||||||
|
cnt.WithLabelValues("503")
|
||||||
|
if err := reg.Register(cnt); err != nil {
|
||||||
|
if are, ok := err.(prometheus.AlreadyRegisteredError); ok {
|
||||||
|
cnt = are.ExistingCollector.(*prometheus.CounterVec)
|
||||||
|
} else {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gge := prometheus.NewGauge(prometheus.GaugeOpts{
|
||||||
|
Name: "promhttp_metric_handler_requests_in_flight",
|
||||||
|
Help: "Current number of scrapes being served.",
|
||||||
|
})
|
||||||
|
if err := reg.Register(gge); err != nil {
|
||||||
|
if are, ok := err.(prometheus.AlreadyRegisteredError); ok {
|
||||||
|
gge = are.ExistingCollector.(prometheus.Gauge)
|
||||||
|
} else {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return InstrumentHandlerCounter(cnt, InstrumentHandlerInFlight(gge, handler))
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandlerErrorHandling defines how a Handler serving metrics will handle
|
||||||
|
// errors.
|
||||||
|
type HandlerErrorHandling int
|
||||||
|
|
||||||
|
// These constants cause handlers serving metrics to behave as described if
|
||||||
|
// errors are encountered.
|
||||||
|
const (
|
||||||
|
// Serve an HTTP status code 500 upon the first error
|
||||||
|
// encountered. Report the error message in the body. Note that HTTP
|
||||||
|
// errors cannot be served anymore once the beginning of a regular
|
||||||
|
// payload has been sent. Thus, in the (unlikely) case that encoding the
|
||||||
|
// payload into the negotiated wire format fails, serving the response
|
||||||
|
// will simply be aborted. Set an ErrorLog in HandlerOpts to detect
|
||||||
|
// those errors.
|
||||||
|
HTTPErrorOnError HandlerErrorHandling = iota
|
||||||
|
// Ignore errors and try to serve as many metrics as possible. However,
|
||||||
|
// if no metrics can be served, serve an HTTP status code 500 and the
|
||||||
|
// last error message in the body. Only use this in deliberate "best
|
||||||
|
// effort" metrics collection scenarios. In this case, it is highly
|
||||||
|
// recommended to provide other means of detecting errors: By setting an
|
||||||
|
// ErrorLog in HandlerOpts, the errors are logged. By providing a
|
||||||
|
// Registry in HandlerOpts, the exposed metrics include an error counter
|
||||||
|
// "promhttp_metric_handler_errors_total", which can be used for
|
||||||
|
// alerts.
|
||||||
|
ContinueOnError
|
||||||
|
// Panic upon the first error encountered (useful for "crash only" apps).
|
||||||
|
PanicOnError
|
||||||
|
)
|
||||||
|
|
||||||
|
// Logger is the minimal interface HandlerOpts needs for logging. Note that
|
||||||
|
// log.Logger from the standard library implements this interface, and it is
|
||||||
|
// easy to implement by custom loggers, if they don't do so already anyway.
|
||||||
|
type Logger interface {
|
||||||
|
Println(v ...interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandlerOpts specifies options how to serve metrics via an http.Handler. The
|
||||||
|
// zero value of HandlerOpts is a reasonable default.
|
||||||
|
type HandlerOpts struct {
|
||||||
|
// ErrorLog specifies an optional logger for errors collecting and
|
||||||
|
// serving metrics. If nil, errors are not logged at all.
|
||||||
|
ErrorLog Logger
|
||||||
|
// ErrorHandling defines how errors are handled. Note that errors are
|
||||||
|
// logged regardless of the configured ErrorHandling provided ErrorLog
|
||||||
|
// is not nil.
|
||||||
|
ErrorHandling HandlerErrorHandling
|
||||||
|
// If Registry is not nil, it is used to register a metric
|
||||||
|
// "promhttp_metric_handler_errors_total", partitioned by "cause". A
|
||||||
|
// failed registration causes a panic. Note that this error counter is
|
||||||
|
// different from the instrumentation you get from the various
|
||||||
|
// InstrumentHandler... helpers. It counts errors that don't necessarily
|
||||||
|
// result in a non-2xx HTTP status code. There are two typical cases:
|
||||||
|
// (1) Encoding errors that only happen after streaming of the HTTP body
|
||||||
|
// has already started (and the status code 200 has been sent). This
|
||||||
|
// should only happen with custom collectors. (2) Collection errors with
|
||||||
|
// no effect on the HTTP status code because ErrorHandling is set to
|
||||||
|
// ContinueOnError.
|
||||||
|
Registry prometheus.Registerer
|
||||||
|
// If DisableCompression is true, the handler will never compress the
|
||||||
|
// response, even if requested by the client.
|
||||||
|
DisableCompression bool
|
||||||
|
// The number of concurrent HTTP requests is limited to
|
||||||
|
// MaxRequestsInFlight. Additional requests are responded to with 503
|
||||||
|
// Service Unavailable and a suitable message in the body. If
|
||||||
|
// MaxRequestsInFlight is 0 or negative, no limit is applied.
|
||||||
|
MaxRequestsInFlight int
|
||||||
|
// If handling a request takes longer than Timeout, it is responded to
|
||||||
|
// with 503 ServiceUnavailable and a suitable Message. No timeout is
|
||||||
|
// applied if Timeout is 0 or negative. Note that with the current
|
||||||
|
// implementation, reaching the timeout simply ends the HTTP requests as
|
||||||
|
// described above (and even that only if sending of the body hasn't
|
||||||
|
// started yet), while the bulk work of gathering all the metrics keeps
|
||||||
|
// running in the background (with the eventual result to be thrown
|
||||||
|
// away). Until the implementation is improved, it is recommended to
|
||||||
|
// implement a separate timeout in potentially slow Collectors.
|
||||||
|
Timeout time.Duration
|
||||||
|
// If true, the experimental OpenMetrics encoding is added to the
|
||||||
|
// possible options during content negotiation. Note that Prometheus
|
||||||
|
// 2.5.0+ will negotiate OpenMetrics as first priority. OpenMetrics is
|
||||||
|
// the only way to transmit exemplars. However, the move to OpenMetrics
|
||||||
|
// is not completely transparent. Most notably, the values of "quantile"
|
||||||
|
// labels of Summaries and "le" labels of Histograms are formatted with
|
||||||
|
// a trailing ".0" if they would otherwise look like integer numbers
|
||||||
|
// (which changes the identity of the resulting series on the Prometheus
|
||||||
|
// server).
|
||||||
|
EnableOpenMetrics bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// gzipAccepted returns whether the client will accept gzip-encoded content.
|
||||||
|
func gzipAccepted(header http.Header) bool {
|
||||||
|
a := header.Get(acceptEncodingHeader)
|
||||||
|
parts := strings.Split(a, ",")
|
||||||
|
for _, part := range parts {
|
||||||
|
part = strings.TrimSpace(part)
|
||||||
|
if part == "gzip" || strings.HasPrefix(part, "gzip;") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// httpError removes any content-encoding header and then calls http.Error with
|
||||||
|
// the provided error and http.StatusInternalServerError. Error contents is
|
||||||
|
// supposed to be uncompressed plain text. Same as with a plain http.Error, this
|
||||||
|
// must not be called if the header or any payload has already been sent.
|
||||||
|
func httpError(rsp http.ResponseWriter, err error) {
|
||||||
|
rsp.Header().Del(contentEncodingHeader)
|
||||||
|
http.Error(
|
||||||
|
rsp,
|
||||||
|
"An error has occurred while serving metrics:\n\n"+err.Error(),
|
||||||
|
http.StatusInternalServerError,
|
||||||
|
)
|
||||||
|
}
|
219
vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_client.go
generated
vendored
Normal file
219
vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_client.go
generated
vendored
Normal file
|
@ -0,0 +1,219 @@
|
||||||
|
// Copyright 2017 The Prometheus Authors
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package promhttp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptrace"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The RoundTripperFunc type is an adapter to allow the use of ordinary
|
||||||
|
// functions as RoundTrippers. If f is a function with the appropriate
|
||||||
|
// signature, RountTripperFunc(f) is a RoundTripper that calls f.
|
||||||
|
type RoundTripperFunc func(req *http.Request) (*http.Response, error)
|
||||||
|
|
||||||
|
// RoundTrip implements the RoundTripper interface.
|
||||||
|
func (rt RoundTripperFunc) RoundTrip(r *http.Request) (*http.Response, error) {
|
||||||
|
return rt(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InstrumentRoundTripperInFlight is a middleware that wraps the provided
|
||||||
|
// http.RoundTripper. It sets the provided prometheus.Gauge to the number of
|
||||||
|
// requests currently handled by the wrapped http.RoundTripper.
|
||||||
|
//
|
||||||
|
// See the example for ExampleInstrumentRoundTripperDuration for example usage.
|
||||||
|
func InstrumentRoundTripperInFlight(gauge prometheus.Gauge, next http.RoundTripper) RoundTripperFunc {
|
||||||
|
return RoundTripperFunc(func(r *http.Request) (*http.Response, error) {
|
||||||
|
gauge.Inc()
|
||||||
|
defer gauge.Dec()
|
||||||
|
return next.RoundTrip(r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// InstrumentRoundTripperCounter is a middleware that wraps the provided
|
||||||
|
// http.RoundTripper to observe the request result with the provided CounterVec.
|
||||||
|
// The CounterVec must have zero, one, or two non-const non-curried labels. For
|
||||||
|
// those, the only allowed label names are "code" and "method". The function
|
||||||
|
// panics otherwise. Partitioning of the CounterVec happens by HTTP status code
|
||||||
|
// and/or HTTP method if the respective instance label names are present in the
|
||||||
|
// CounterVec. For unpartitioned counting, use a CounterVec with zero labels.
|
||||||
|
//
|
||||||
|
// If the wrapped RoundTripper panics or returns a non-nil error, the Counter
|
||||||
|
// is not incremented.
|
||||||
|
//
|
||||||
|
// See the example for ExampleInstrumentRoundTripperDuration for example usage.
|
||||||
|
func InstrumentRoundTripperCounter(counter *prometheus.CounterVec, next http.RoundTripper) RoundTripperFunc {
|
||||||
|
code, method := checkLabels(counter)
|
||||||
|
|
||||||
|
return RoundTripperFunc(func(r *http.Request) (*http.Response, error) {
|
||||||
|
resp, err := next.RoundTrip(r)
|
||||||
|
if err == nil {
|
||||||
|
counter.With(labels(code, method, r.Method, resp.StatusCode)).Inc()
|
||||||
|
}
|
||||||
|
return resp, err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// InstrumentRoundTripperDuration is a middleware that wraps the provided
|
||||||
|
// http.RoundTripper to observe the request duration with the provided
|
||||||
|
// ObserverVec. The ObserverVec must have zero, one, or two non-const
|
||||||
|
// non-curried labels. For those, the only allowed label names are "code" and
|
||||||
|
// "method". The function panics otherwise. The Observe method of the Observer
|
||||||
|
// in the ObserverVec is called with the request duration in
|
||||||
|
// seconds. Partitioning happens by HTTP status code and/or HTTP method if the
|
||||||
|
// respective instance label names are present in the ObserverVec. For
|
||||||
|
// unpartitioned observations, use an ObserverVec with zero labels. Note that
|
||||||
|
// partitioning of Histograms is expensive and should be used judiciously.
|
||||||
|
//
|
||||||
|
// If the wrapped RoundTripper panics or returns a non-nil error, no values are
|
||||||
|
// reported.
|
||||||
|
//
|
||||||
|
// Note that this method is only guaranteed to never observe negative durations
|
||||||
|
// if used with Go1.9+.
|
||||||
|
func InstrumentRoundTripperDuration(obs prometheus.ObserverVec, next http.RoundTripper) RoundTripperFunc {
|
||||||
|
code, method := checkLabels(obs)
|
||||||
|
|
||||||
|
return RoundTripperFunc(func(r *http.Request) (*http.Response, error) {
|
||||||
|
start := time.Now()
|
||||||
|
resp, err := next.RoundTrip(r)
|
||||||
|
if err == nil {
|
||||||
|
obs.With(labels(code, method, r.Method, resp.StatusCode)).Observe(time.Since(start).Seconds())
|
||||||
|
}
|
||||||
|
return resp, err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// InstrumentTrace is used to offer flexibility in instrumenting the available
|
||||||
|
// httptrace.ClientTrace hook functions. Each function is passed a float64
|
||||||
|
// representing the time in seconds since the start of the http request. A user
|
||||||
|
// may choose to use separately buckets Histograms, or implement custom
|
||||||
|
// instance labels on a per function basis.
|
||||||
|
type InstrumentTrace struct {
|
||||||
|
GotConn func(float64)
|
||||||
|
PutIdleConn func(float64)
|
||||||
|
GotFirstResponseByte func(float64)
|
||||||
|
Got100Continue func(float64)
|
||||||
|
DNSStart func(float64)
|
||||||
|
DNSDone func(float64)
|
||||||
|
ConnectStart func(float64)
|
||||||
|
ConnectDone func(float64)
|
||||||
|
TLSHandshakeStart func(float64)
|
||||||
|
TLSHandshakeDone func(float64)
|
||||||
|
WroteHeaders func(float64)
|
||||||
|
Wait100Continue func(float64)
|
||||||
|
WroteRequest func(float64)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InstrumentRoundTripperTrace is a middleware that wraps the provided
|
||||||
|
// RoundTripper and reports times to hook functions provided in the
|
||||||
|
// InstrumentTrace struct. Hook functions that are not present in the provided
|
||||||
|
// InstrumentTrace struct are ignored. Times reported to the hook functions are
|
||||||
|
// time since the start of the request. Only with Go1.9+, those times are
|
||||||
|
// guaranteed to never be negative. (Earlier Go versions are not using a
|
||||||
|
// monotonic clock.) Note that partitioning of Histograms is expensive and
|
||||||
|
// should be used judiciously.
|
||||||
|
//
|
||||||
|
// For hook functions that receive an error as an argument, no observations are
|
||||||
|
// made in the event of a non-nil error value.
|
||||||
|
//
|
||||||
|
// See the example for ExampleInstrumentRoundTripperDuration for example usage.
|
||||||
|
func InstrumentRoundTripperTrace(it *InstrumentTrace, next http.RoundTripper) RoundTripperFunc {
|
||||||
|
return RoundTripperFunc(func(r *http.Request) (*http.Response, error) {
|
||||||
|
start := time.Now()
|
||||||
|
|
||||||
|
trace := &httptrace.ClientTrace{
|
||||||
|
GotConn: func(_ httptrace.GotConnInfo) {
|
||||||
|
if it.GotConn != nil {
|
||||||
|
it.GotConn(time.Since(start).Seconds())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
PutIdleConn: func(err error) {
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if it.PutIdleConn != nil {
|
||||||
|
it.PutIdleConn(time.Since(start).Seconds())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
DNSStart: func(_ httptrace.DNSStartInfo) {
|
||||||
|
if it.DNSStart != nil {
|
||||||
|
it.DNSStart(time.Since(start).Seconds())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
DNSDone: func(_ httptrace.DNSDoneInfo) {
|
||||||
|
if it.DNSDone != nil {
|
||||||
|
it.DNSDone(time.Since(start).Seconds())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ConnectStart: func(_, _ string) {
|
||||||
|
if it.ConnectStart != nil {
|
||||||
|
it.ConnectStart(time.Since(start).Seconds())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ConnectDone: func(_, _ string, err error) {
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if it.ConnectDone != nil {
|
||||||
|
it.ConnectDone(time.Since(start).Seconds())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
GotFirstResponseByte: func() {
|
||||||
|
if it.GotFirstResponseByte != nil {
|
||||||
|
it.GotFirstResponseByte(time.Since(start).Seconds())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Got100Continue: func() {
|
||||||
|
if it.Got100Continue != nil {
|
||||||
|
it.Got100Continue(time.Since(start).Seconds())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
TLSHandshakeStart: func() {
|
||||||
|
if it.TLSHandshakeStart != nil {
|
||||||
|
it.TLSHandshakeStart(time.Since(start).Seconds())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
TLSHandshakeDone: func(_ tls.ConnectionState, err error) {
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if it.TLSHandshakeDone != nil {
|
||||||
|
it.TLSHandshakeDone(time.Since(start).Seconds())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
WroteHeaders: func() {
|
||||||
|
if it.WroteHeaders != nil {
|
||||||
|
it.WroteHeaders(time.Since(start).Seconds())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Wait100Continue: func() {
|
||||||
|
if it.Wait100Continue != nil {
|
||||||
|
it.Wait100Continue(time.Since(start).Seconds())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
WroteRequest: func(_ httptrace.WroteRequestInfo) {
|
||||||
|
if it.WroteRequest != nil {
|
||||||
|
it.WroteRequest(time.Since(start).Seconds())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
r = r.WithContext(httptrace.WithClientTrace(r.Context(), trace))
|
||||||
|
|
||||||
|
return next.RoundTrip(r)
|
||||||
|
})
|
||||||
|
}
|
447
vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_server.go
generated
vendored
Normal file
447
vendor/github.com/prometheus/client_golang/prometheus/promhttp/instrument_server.go
generated
vendored
Normal file
|
@ -0,0 +1,447 @@
|
||||||
|
// Copyright 2017 The Prometheus Authors
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package promhttp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
dto "github.com/prometheus/client_model/go"
|
||||||
|
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// magicString is used for the hacky label test in checkLabels. Remove once fixed.
|
||||||
|
const magicString = "zZgWfBxLqvG8kc8IMv3POi2Bb0tZI3vAnBx+gBaFi9FyPzB/CzKUer1yufDa"
|
||||||
|
|
||||||
|
// InstrumentHandlerInFlight is a middleware that wraps the provided
|
||||||
|
// http.Handler. It sets the provided prometheus.Gauge to the number of
|
||||||
|
// requests currently handled by the wrapped http.Handler.
|
||||||
|
//
|
||||||
|
// See the example for InstrumentHandlerDuration for example usage.
|
||||||
|
func InstrumentHandlerInFlight(g prometheus.Gauge, next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
g.Inc()
|
||||||
|
defer g.Dec()
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// InstrumentHandlerDuration is a middleware that wraps the provided
|
||||||
|
// http.Handler to observe the request duration with the provided ObserverVec.
|
||||||
|
// The ObserverVec must have zero, one, or two non-const non-curried labels. For
|
||||||
|
// those, the only allowed label names are "code" and "method". The function
|
||||||
|
// panics otherwise. The Observe method of the Observer in the ObserverVec is
|
||||||
|
// called with the request duration in seconds. Partitioning happens by HTTP
|
||||||
|
// status code and/or HTTP method if the respective instance label names are
|
||||||
|
// present in the ObserverVec. For unpartitioned observations, use an
|
||||||
|
// ObserverVec with zero labels. Note that partitioning of Histograms is
|
||||||
|
// expensive and should be used judiciously.
|
||||||
|
//
|
||||||
|
// If the wrapped Handler does not set a status code, a status code of 200 is assumed.
|
||||||
|
//
|
||||||
|
// If the wrapped Handler panics, no values are reported.
|
||||||
|
//
|
||||||
|
// Note that this method is only guaranteed to never observe negative durations
|
||||||
|
// if used with Go1.9+.
|
||||||
|
func InstrumentHandlerDuration(obs prometheus.ObserverVec, next http.Handler) http.HandlerFunc {
|
||||||
|
code, method := checkLabels(obs)
|
||||||
|
|
||||||
|
if code {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
now := time.Now()
|
||||||
|
d := newDelegator(w, nil)
|
||||||
|
next.ServeHTTP(d, r)
|
||||||
|
|
||||||
|
obs.With(labels(code, method, r.Method, d.Status())).Observe(time.Since(now).Seconds())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
now := time.Now()
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
obs.With(labels(code, method, r.Method, 0)).Observe(time.Since(now).Seconds())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// InstrumentHandlerCounter is a middleware that wraps the provided http.Handler
|
||||||
|
// to observe the request result with the provided CounterVec. The CounterVec
|
||||||
|
// must have zero, one, or two non-const non-curried labels. For those, the only
|
||||||
|
// allowed label names are "code" and "method". The function panics
|
||||||
|
// otherwise. Partitioning of the CounterVec happens by HTTP status code and/or
|
||||||
|
// HTTP method if the respective instance label names are present in the
|
||||||
|
// CounterVec. For unpartitioned counting, use a CounterVec with zero labels.
|
||||||
|
//
|
||||||
|
// If the wrapped Handler does not set a status code, a status code of 200 is assumed.
|
||||||
|
//
|
||||||
|
// If the wrapped Handler panics, the Counter is not incremented.
|
||||||
|
//
|
||||||
|
// See the example for InstrumentHandlerDuration for example usage.
|
||||||
|
func InstrumentHandlerCounter(counter *prometheus.CounterVec, next http.Handler) http.HandlerFunc {
|
||||||
|
code, method := checkLabels(counter)
|
||||||
|
|
||||||
|
if code {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
d := newDelegator(w, nil)
|
||||||
|
next.ServeHTTP(d, r)
|
||||||
|
counter.With(labels(code, method, r.Method, d.Status())).Inc()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
counter.With(labels(code, method, r.Method, 0)).Inc()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// InstrumentHandlerTimeToWriteHeader is a middleware that wraps the provided
|
||||||
|
// http.Handler to observe with the provided ObserverVec the request duration
|
||||||
|
// until the response headers are written. The ObserverVec must have zero, one,
|
||||||
|
// or two non-const non-curried labels. For those, the only allowed label names
|
||||||
|
// are "code" and "method". The function panics otherwise. The Observe method of
|
||||||
|
// the Observer in the ObserverVec is called with the request duration in
|
||||||
|
// seconds. Partitioning happens by HTTP status code and/or HTTP method if the
|
||||||
|
// respective instance label names are present in the ObserverVec. For
|
||||||
|
// unpartitioned observations, use an ObserverVec with zero labels. Note that
|
||||||
|
// partitioning of Histograms is expensive and should be used judiciously.
|
||||||
|
//
|
||||||
|
// If the wrapped Handler panics before calling WriteHeader, no value is
|
||||||
|
// reported.
|
||||||
|
//
|
||||||
|
// Note that this method is only guaranteed to never observe negative durations
|
||||||
|
// if used with Go1.9+.
|
||||||
|
//
|
||||||
|
// See the example for InstrumentHandlerDuration for example usage.
|
||||||
|
func InstrumentHandlerTimeToWriteHeader(obs prometheus.ObserverVec, next http.Handler) http.HandlerFunc {
|
||||||
|
code, method := checkLabels(obs)
|
||||||
|
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
now := time.Now()
|
||||||
|
d := newDelegator(w, func(status int) {
|
||||||
|
obs.With(labels(code, method, r.Method, status)).Observe(time.Since(now).Seconds())
|
||||||
|
})
|
||||||
|
next.ServeHTTP(d, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// InstrumentHandlerRequestSize is a middleware that wraps the provided
|
||||||
|
// http.Handler to observe the request size with the provided ObserverVec. The
|
||||||
|
// ObserverVec must have zero, one, or two non-const non-curried labels. For
|
||||||
|
// those, the only allowed label names are "code" and "method". The function
|
||||||
|
// panics otherwise. The Observe method of the Observer in the ObserverVec is
|
||||||
|
// called with the request size in bytes. Partitioning happens by HTTP status
|
||||||
|
// code and/or HTTP method if the respective instance label names are present in
|
||||||
|
// the ObserverVec. For unpartitioned observations, use an ObserverVec with zero
|
||||||
|
// labels. Note that partitioning of Histograms is expensive and should be used
|
||||||
|
// judiciously.
|
||||||
|
//
|
||||||
|
// If the wrapped Handler does not set a status code, a status code of 200 is assumed.
|
||||||
|
//
|
||||||
|
// If the wrapped Handler panics, no values are reported.
|
||||||
|
//
|
||||||
|
// See the example for InstrumentHandlerDuration for example usage.
|
||||||
|
func InstrumentHandlerRequestSize(obs prometheus.ObserverVec, next http.Handler) http.HandlerFunc {
|
||||||
|
code, method := checkLabels(obs)
|
||||||
|
|
||||||
|
if code {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
d := newDelegator(w, nil)
|
||||||
|
next.ServeHTTP(d, r)
|
||||||
|
size := computeApproximateRequestSize(r)
|
||||||
|
obs.With(labels(code, method, r.Method, d.Status())).Observe(float64(size))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
size := computeApproximateRequestSize(r)
|
||||||
|
obs.With(labels(code, method, r.Method, 0)).Observe(float64(size))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// InstrumentHandlerResponseSize is a middleware that wraps the provided
|
||||||
|
// http.Handler to observe the response size with the provided ObserverVec. The
|
||||||
|
// ObserverVec must have zero, one, or two non-const non-curried labels. For
|
||||||
|
// those, the only allowed label names are "code" and "method". The function
|
||||||
|
// panics otherwise. The Observe method of the Observer in the ObserverVec is
|
||||||
|
// called with the response size in bytes. Partitioning happens by HTTP status
|
||||||
|
// code and/or HTTP method if the respective instance label names are present in
|
||||||
|
// the ObserverVec. For unpartitioned observations, use an ObserverVec with zero
|
||||||
|
// labels. Note that partitioning of Histograms is expensive and should be used
|
||||||
|
// judiciously.
|
||||||
|
//
|
||||||
|
// If the wrapped Handler does not set a status code, a status code of 200 is assumed.
|
||||||
|
//
|
||||||
|
// If the wrapped Handler panics, no values are reported.
|
||||||
|
//
|
||||||
|
// See the example for InstrumentHandlerDuration for example usage.
|
||||||
|
func InstrumentHandlerResponseSize(obs prometheus.ObserverVec, next http.Handler) http.Handler {
|
||||||
|
code, method := checkLabels(obs)
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
d := newDelegator(w, nil)
|
||||||
|
next.ServeHTTP(d, r)
|
||||||
|
obs.With(labels(code, method, r.Method, d.Status())).Observe(float64(d.Written()))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkLabels(c prometheus.Collector) (code bool, method bool) {
|
||||||
|
// TODO(beorn7): Remove this hacky way to check for instance labels
|
||||||
|
// once Descriptors can have their dimensionality queried.
|
||||||
|
var (
|
||||||
|
desc *prometheus.Desc
|
||||||
|
m prometheus.Metric
|
||||||
|
pm dto.Metric
|
||||||
|
lvs []string
|
||||||
|
)
|
||||||
|
|
||||||
|
// Get the Desc from the Collector.
|
||||||
|
descc := make(chan *prometheus.Desc, 1)
|
||||||
|
c.Describe(descc)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case desc = <-descc:
|
||||||
|
default:
|
||||||
|
panic("no description provided by collector")
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case <-descc:
|
||||||
|
panic("more than one description provided by collector")
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
close(descc)
|
||||||
|
|
||||||
|
// Create a ConstMetric with the Desc. Since we don't know how many
|
||||||
|
// variable labels there are, try for as long as it needs.
|
||||||
|
for err := errors.New("dummy"); err != nil; lvs = append(lvs, magicString) {
|
||||||
|
m, err = prometheus.NewConstMetric(desc, prometheus.UntypedValue, 0, lvs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write out the metric into a proto message and look at the labels.
|
||||||
|
// If the value is not the magicString, it is a constLabel, which doesn't interest us.
|
||||||
|
// If the label is curried, it doesn't interest us.
|
||||||
|
// In all other cases, only "code" or "method" is allowed.
|
||||||
|
if err := m.Write(&pm); err != nil {
|
||||||
|
panic("error checking metric for labels")
|
||||||
|
}
|
||||||
|
for _, label := range pm.Label {
|
||||||
|
name, value := label.GetName(), label.GetValue()
|
||||||
|
if value != magicString || isLabelCurried(c, name) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch name {
|
||||||
|
case "code":
|
||||||
|
code = true
|
||||||
|
case "method":
|
||||||
|
method = true
|
||||||
|
default:
|
||||||
|
panic("metric partitioned with non-supported labels")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func isLabelCurried(c prometheus.Collector, label string) bool {
|
||||||
|
// This is even hackier than the label test above.
|
||||||
|
// We essentially try to curry again and see if it works.
|
||||||
|
// But for that, we need to type-convert to the two
|
||||||
|
// types we use here, ObserverVec or *CounterVec.
|
||||||
|
switch v := c.(type) {
|
||||||
|
case *prometheus.CounterVec:
|
||||||
|
if _, err := v.CurryWith(prometheus.Labels{label: "dummy"}); err == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case prometheus.ObserverVec:
|
||||||
|
if _, err := v.CurryWith(prometheus.Labels{label: "dummy"}); err == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
panic("unsupported metric vec type")
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// emptyLabels is a one-time allocation for non-partitioned metrics to avoid
|
||||||
|
// unnecessary allocations on each request.
|
||||||
|
var emptyLabels = prometheus.Labels{}
|
||||||
|
|
||||||
|
func labels(code, method bool, reqMethod string, status int) prometheus.Labels {
|
||||||
|
if !(code || method) {
|
||||||
|
return emptyLabels
|
||||||
|
}
|
||||||
|
labels := prometheus.Labels{}
|
||||||
|
|
||||||
|
if code {
|
||||||
|
labels["code"] = sanitizeCode(status)
|
||||||
|
}
|
||||||
|
if method {
|
||||||
|
labels["method"] = sanitizeMethod(reqMethod)
|
||||||
|
}
|
||||||
|
|
||||||
|
return labels
|
||||||
|
}
|
||||||
|
|
||||||
|
func computeApproximateRequestSize(r *http.Request) int {
|
||||||
|
s := 0
|
||||||
|
if r.URL != nil {
|
||||||
|
s += len(r.URL.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
s += len(r.Method)
|
||||||
|
s += len(r.Proto)
|
||||||
|
for name, values := range r.Header {
|
||||||
|
s += len(name)
|
||||||
|
for _, value := range values {
|
||||||
|
s += len(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s += len(r.Host)
|
||||||
|
|
||||||
|
// N.B. r.Form and r.MultipartForm are assumed to be included in r.URL.
|
||||||
|
|
||||||
|
if r.ContentLength != -1 {
|
||||||
|
s += int(r.ContentLength)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func sanitizeMethod(m string) string {
|
||||||
|
switch m {
|
||||||
|
case "GET", "get":
|
||||||
|
return "get"
|
||||||
|
case "PUT", "put":
|
||||||
|
return "put"
|
||||||
|
case "HEAD", "head":
|
||||||
|
return "head"
|
||||||
|
case "POST", "post":
|
||||||
|
return "post"
|
||||||
|
case "DELETE", "delete":
|
||||||
|
return "delete"
|
||||||
|
case "CONNECT", "connect":
|
||||||
|
return "connect"
|
||||||
|
case "OPTIONS", "options":
|
||||||
|
return "options"
|
||||||
|
case "NOTIFY", "notify":
|
||||||
|
return "notify"
|
||||||
|
default:
|
||||||
|
return strings.ToLower(m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the wrapped http.Handler has not set a status code, i.e. the value is
|
||||||
|
// currently 0, santizeCode will return 200, for consistency with behavior in
|
||||||
|
// the stdlib.
|
||||||
|
func sanitizeCode(s int) string {
|
||||||
|
switch s {
|
||||||
|
case 100:
|
||||||
|
return "100"
|
||||||
|
case 101:
|
||||||
|
return "101"
|
||||||
|
|
||||||
|
case 200, 0:
|
||||||
|
return "200"
|
||||||
|
case 201:
|
||||||
|
return "201"
|
||||||
|
case 202:
|
||||||
|
return "202"
|
||||||
|
case 203:
|
||||||
|
return "203"
|
||||||
|
case 204:
|
||||||
|
return "204"
|
||||||
|
case 205:
|
||||||
|
return "205"
|
||||||
|
case 206:
|
||||||
|
return "206"
|
||||||
|
|
||||||
|
case 300:
|
||||||
|
return "300"
|
||||||
|
case 301:
|
||||||
|
return "301"
|
||||||
|
case 302:
|
||||||
|
return "302"
|
||||||
|
case 304:
|
||||||
|
return "304"
|
||||||
|
case 305:
|
||||||
|
return "305"
|
||||||
|
case 307:
|
||||||
|
return "307"
|
||||||
|
|
||||||
|
case 400:
|
||||||
|
return "400"
|
||||||
|
case 401:
|
||||||
|
return "401"
|
||||||
|
case 402:
|
||||||
|
return "402"
|
||||||
|
case 403:
|
||||||
|
return "403"
|
||||||
|
case 404:
|
||||||
|
return "404"
|
||||||
|
case 405:
|
||||||
|
return "405"
|
||||||
|
case 406:
|
||||||
|
return "406"
|
||||||
|
case 407:
|
||||||
|
return "407"
|
||||||
|
case 408:
|
||||||
|
return "408"
|
||||||
|
case 409:
|
||||||
|
return "409"
|
||||||
|
case 410:
|
||||||
|
return "410"
|
||||||
|
case 411:
|
||||||
|
return "411"
|
||||||
|
case 412:
|
||||||
|
return "412"
|
||||||
|
case 413:
|
||||||
|
return "413"
|
||||||
|
case 414:
|
||||||
|
return "414"
|
||||||
|
case 415:
|
||||||
|
return "415"
|
||||||
|
case 416:
|
||||||
|
return "416"
|
||||||
|
case 417:
|
||||||
|
return "417"
|
||||||
|
case 418:
|
||||||
|
return "418"
|
||||||
|
|
||||||
|
case 500:
|
||||||
|
return "500"
|
||||||
|
case 501:
|
||||||
|
return "501"
|
||||||
|
case 502:
|
||||||
|
return "502"
|
||||||
|
case 503:
|
||||||
|
return "503"
|
||||||
|
case 504:
|
||||||
|
return "504"
|
||||||
|
case 505:
|
||||||
|
return "505"
|
||||||
|
|
||||||
|
case 428:
|
||||||
|
return "428"
|
||||||
|
case 429:
|
||||||
|
return "429"
|
||||||
|
case 431:
|
||||||
|
return "431"
|
||||||
|
case 511:
|
||||||
|
return "511"
|
||||||
|
|
||||||
|
default:
|
||||||
|
return strconv.Itoa(s)
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,15 +15,23 @@ package prometheus
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"github.com/cespare/xxhash/v2"
|
||||||
"github.com/golang/protobuf/proto"
|
"github.com/golang/protobuf/proto"
|
||||||
|
"github.com/prometheus/common/expfmt"
|
||||||
|
|
||||||
dto "github.com/prometheus/client_model/go"
|
dto "github.com/prometheus/client_model/go"
|
||||||
|
|
||||||
|
"github.com/prometheus/client_golang/prometheus/internal"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -35,13 +43,14 @@ const (
|
||||||
// DefaultRegisterer and DefaultGatherer are the implementations of the
|
// DefaultRegisterer and DefaultGatherer are the implementations of the
|
||||||
// Registerer and Gatherer interface a number of convenience functions in this
|
// Registerer and Gatherer interface a number of convenience functions in this
|
||||||
// package act on. Initially, both variables point to the same Registry, which
|
// package act on. Initially, both variables point to the same Registry, which
|
||||||
// has a process collector (see NewProcessCollector) and a Go collector (see
|
// has a process collector (currently on Linux only, see NewProcessCollector)
|
||||||
// NewGoCollector) already registered. This approach to keep default instances
|
// and a Go collector (see NewGoCollector, in particular the note about
|
||||||
// as global state mirrors the approach of other packages in the Go standard
|
// stop-the-world implication with Go versions older than 1.9) already
|
||||||
// library. Note that there are caveats. Change the variables with caution and
|
// registered. This approach to keep default instances as global state mirrors
|
||||||
// only if you understand the consequences. Users who want to avoid global state
|
// the approach of other packages in the Go standard library. Note that there
|
||||||
// altogether should not use the convenience function and act on custom
|
// are caveats. Change the variables with caution and only if you understand the
|
||||||
// instances instead.
|
// consequences. Users who want to avoid global state altogether should not use
|
||||||
|
// the convenience functions and act on custom instances instead.
|
||||||
var (
|
var (
|
||||||
defaultRegistry = NewRegistry()
|
defaultRegistry = NewRegistry()
|
||||||
DefaultRegisterer Registerer = defaultRegistry
|
DefaultRegisterer Registerer = defaultRegistry
|
||||||
|
@ -49,7 +58,7 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
MustRegister(NewProcessCollector(os.Getpid(), ""))
|
MustRegister(NewProcessCollector(ProcessCollectorOpts{}))
|
||||||
MustRegister(NewGoCollector())
|
MustRegister(NewGoCollector())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,7 +74,8 @@ func NewRegistry() *Registry {
|
||||||
|
|
||||||
// NewPedanticRegistry returns a registry that checks during collection if each
|
// NewPedanticRegistry returns a registry that checks during collection if each
|
||||||
// collected Metric is consistent with its reported Desc, and if the Desc has
|
// collected Metric is consistent with its reported Desc, and if the Desc has
|
||||||
// actually been registered with the registry.
|
// actually been registered with the registry. Unchecked Collectors (those whose
|
||||||
|
// Describe method does not yield any descriptors) are excluded from the check.
|
||||||
//
|
//
|
||||||
// Usually, a Registry will be happy as long as the union of all collected
|
// Usually, a Registry will be happy as long as the union of all collected
|
||||||
// Metrics is consistent and valid even if some metrics are not consistent with
|
// Metrics is consistent and valid even if some metrics are not consistent with
|
||||||
|
@ -80,7 +90,7 @@ func NewPedanticRegistry() *Registry {
|
||||||
|
|
||||||
// Registerer is the interface for the part of a registry in charge of
|
// Registerer is the interface for the part of a registry in charge of
|
||||||
// registering and unregistering. Users of custom registries should use
|
// registering and unregistering. Users of custom registries should use
|
||||||
// Registerer as type for registration purposes (rather then the Registry type
|
// Registerer as type for registration purposes (rather than the Registry type
|
||||||
// directly). In that way, they are free to use custom Registerer implementation
|
// directly). In that way, they are free to use custom Registerer implementation
|
||||||
// (e.g. for testing purposes).
|
// (e.g. for testing purposes).
|
||||||
type Registerer interface {
|
type Registerer interface {
|
||||||
|
@ -95,8 +105,13 @@ type Registerer interface {
|
||||||
// returned error is an instance of AlreadyRegisteredError, which
|
// returned error is an instance of AlreadyRegisteredError, which
|
||||||
// contains the previously registered Collector.
|
// contains the previously registered Collector.
|
||||||
//
|
//
|
||||||
// It is in general not safe to register the same Collector multiple
|
// A Collector whose Describe method does not yield any Desc is treated
|
||||||
// times concurrently.
|
// as unchecked. Registration will always succeed. No check for
|
||||||
|
// re-registering (see previous paragraph) is performed. Thus, the
|
||||||
|
// caller is responsible for not double-registering the same unchecked
|
||||||
|
// Collector, and for providing a Collector that will not cause
|
||||||
|
// inconsistent metrics on collection. (This would lead to scrape
|
||||||
|
// errors.)
|
||||||
Register(Collector) error
|
Register(Collector) error
|
||||||
// MustRegister works like Register but registers any number of
|
// MustRegister works like Register but registers any number of
|
||||||
// Collectors and panics upon the first registration that causes an
|
// Collectors and panics upon the first registration that causes an
|
||||||
|
@ -105,7 +120,9 @@ type Registerer interface {
|
||||||
// Unregister unregisters the Collector that equals the Collector passed
|
// Unregister unregisters the Collector that equals the Collector passed
|
||||||
// in as an argument. (Two Collectors are considered equal if their
|
// in as an argument. (Two Collectors are considered equal if their
|
||||||
// Describe method yields the same set of descriptors.) The function
|
// Describe method yields the same set of descriptors.) The function
|
||||||
// returns whether a Collector was unregistered.
|
// returns whether a Collector was unregistered. Note that an unchecked
|
||||||
|
// Collector cannot be unregistered (as its Describe method does not
|
||||||
|
// yield any descriptor).
|
||||||
//
|
//
|
||||||
// Note that even after unregistering, it will not be possible to
|
// Note that even after unregistering, it will not be possible to
|
||||||
// register a new Collector that is inconsistent with the unregistered
|
// register a new Collector that is inconsistent with the unregistered
|
||||||
|
@ -123,15 +140,23 @@ type Registerer interface {
|
||||||
type Gatherer interface {
|
type Gatherer interface {
|
||||||
// Gather calls the Collect method of the registered Collectors and then
|
// Gather calls the Collect method of the registered Collectors and then
|
||||||
// gathers the collected metrics into a lexicographically sorted slice
|
// gathers the collected metrics into a lexicographically sorted slice
|
||||||
// of MetricFamily protobufs. Even if an error occurs, Gather attempts
|
// of uniquely named MetricFamily protobufs. Gather ensures that the
|
||||||
// to gather as many metrics as possible. Hence, if a non-nil error is
|
// returned slice is valid and self-consistent so that it can be used
|
||||||
// returned, the returned MetricFamily slice could be nil (in case of a
|
// for valid exposition. As an exception to the strict consistency
|
||||||
// fatal error that prevented any meaningful metric collection) or
|
// requirements described for metric.Desc, Gather will tolerate
|
||||||
// contain a number of MetricFamily protobufs, some of which might be
|
// different sets of label names for metrics of the same metric family.
|
||||||
// incomplete, and some might be missing altogether. The returned error
|
//
|
||||||
// (which might be a MultiError) explains the details. In scenarios
|
// Even if an error occurs, Gather attempts to gather as many metrics as
|
||||||
// where complete collection is critical, the returned MetricFamily
|
// possible. Hence, if a non-nil error is returned, the returned
|
||||||
// protobufs should be disregarded if the returned error is non-nil.
|
// MetricFamily slice could be nil (in case of a fatal error that
|
||||||
|
// prevented any meaningful metric collection) or contain a number of
|
||||||
|
// MetricFamily protobufs, some of which might be incomplete, and some
|
||||||
|
// might be missing altogether. The returned error (which might be a
|
||||||
|
// MultiError) explains the details. Note that this is mostly useful for
|
||||||
|
// debugging purposes. If the gathered protobufs are to be used for
|
||||||
|
// exposition in actual monitoring, it is almost always better to not
|
||||||
|
// expose an incomplete result and instead disregard the returned
|
||||||
|
// MetricFamily protobufs in case the returned error is non-nil.
|
||||||
Gather() ([]*dto.MetricFamily, error)
|
Gather() ([]*dto.MetricFamily, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,38 +177,6 @@ func MustRegister(cs ...Collector) {
|
||||||
DefaultRegisterer.MustRegister(cs...)
|
DefaultRegisterer.MustRegister(cs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterOrGet registers the provided Collector with the DefaultRegisterer and
|
|
||||||
// returns the Collector, unless an equal Collector was registered before, in
|
|
||||||
// which case that Collector is returned.
|
|
||||||
//
|
|
||||||
// Deprecated: RegisterOrGet is merely a convenience function for the
|
|
||||||
// implementation as described in the documentation for
|
|
||||||
// AlreadyRegisteredError. As the use case is relatively rare, this function
|
|
||||||
// will be removed in a future version of this package to clean up the
|
|
||||||
// namespace.
|
|
||||||
func RegisterOrGet(c Collector) (Collector, error) {
|
|
||||||
if err := Register(c); err != nil {
|
|
||||||
if are, ok := err.(AlreadyRegisteredError); ok {
|
|
||||||
return are.ExistingCollector, nil
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MustRegisterOrGet behaves like RegisterOrGet but panics instead of returning
|
|
||||||
// an error.
|
|
||||||
//
|
|
||||||
// Deprecated: This is deprecated for the same reason RegisterOrGet is. See
|
|
||||||
// there for details.
|
|
||||||
func MustRegisterOrGet(c Collector) Collector {
|
|
||||||
c, err := RegisterOrGet(c)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unregister removes the registration of the provided Collector from the
|
// Unregister removes the registration of the provided Collector from the
|
||||||
// DefaultRegisterer.
|
// DefaultRegisterer.
|
||||||
//
|
//
|
||||||
|
@ -201,25 +194,6 @@ func (gf GathererFunc) Gather() ([]*dto.MetricFamily, error) {
|
||||||
return gf()
|
return gf()
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetMetricFamilyInjectionHook replaces the DefaultGatherer with one that
|
|
||||||
// gathers from the previous DefaultGatherers but then merges the MetricFamily
|
|
||||||
// protobufs returned from the provided hook function with the MetricFamily
|
|
||||||
// protobufs returned from the original DefaultGatherer.
|
|
||||||
//
|
|
||||||
// Deprecated: This function manipulates the DefaultGatherer variable. Consider
|
|
||||||
// the implications, i.e. don't do this concurrently with any uses of the
|
|
||||||
// DefaultGatherer. In the rare cases where you need to inject MetricFamily
|
|
||||||
// protobufs directly, it is recommended to use a custom Registry and combine it
|
|
||||||
// with a custom Gatherer using the Gatherers type (see
|
|
||||||
// there). SetMetricFamilyInjectionHook only exists for compatibility reasons
|
|
||||||
// with previous versions of this package.
|
|
||||||
func SetMetricFamilyInjectionHook(hook func() []*dto.MetricFamily) {
|
|
||||||
DefaultGatherer = Gatherers{
|
|
||||||
DefaultGatherer,
|
|
||||||
GathererFunc(func() ([]*dto.MetricFamily, error) { return hook(), nil }),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// AlreadyRegisteredError is returned by the Register method if the Collector to
|
// AlreadyRegisteredError is returned by the Register method if the Collector to
|
||||||
// be registered has already been registered before, or a different Collector
|
// be registered has already been registered before, or a different Collector
|
||||||
// that collects the same metrics has been registered before. Registration fails
|
// that collects the same metrics has been registered before. Registration fails
|
||||||
|
@ -252,6 +226,13 @@ func (errs MultiError) Error() string {
|
||||||
return buf.String()
|
return buf.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Append appends the provided error if it is not nil.
|
||||||
|
func (errs *MultiError) Append(err error) {
|
||||||
|
if err != nil {
|
||||||
|
*errs = append(*errs, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MaybeUnwrap returns nil if len(errs) is 0. It returns the first and only
|
// MaybeUnwrap returns nil if len(errs) is 0. It returns the first and only
|
||||||
// contained error as error if len(errs is 1). In all other cases, it returns
|
// contained error as error if len(errs is 1). In all other cases, it returns
|
||||||
// the MultiError directly. This is helpful for returning a MultiError in a way
|
// the MultiError directly. This is helpful for returning a MultiError in a way
|
||||||
|
@ -276,6 +257,7 @@ type Registry struct {
|
||||||
collectorsByID map[uint64]Collector // ID is a hash of the descIDs.
|
collectorsByID map[uint64]Collector // ID is a hash of the descIDs.
|
||||||
descIDs map[uint64]struct{}
|
descIDs map[uint64]struct{}
|
||||||
dimHashesByName map[string]uint64
|
dimHashesByName map[string]uint64
|
||||||
|
uncheckedCollectors []Collector
|
||||||
pedanticChecksEnabled bool
|
pedanticChecksEnabled bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -285,7 +267,7 @@ func (r *Registry) Register(c Collector) error {
|
||||||
descChan = make(chan *Desc, capDescChan)
|
descChan = make(chan *Desc, capDescChan)
|
||||||
newDescIDs = map[uint64]struct{}{}
|
newDescIDs = map[uint64]struct{}{}
|
||||||
newDimHashesByName = map[string]uint64{}
|
newDimHashesByName = map[string]uint64{}
|
||||||
collectorID uint64 // Just a sum of all desc IDs.
|
collectorID uint64 // All desc IDs XOR'd together.
|
||||||
duplicateDescErr error
|
duplicateDescErr error
|
||||||
)
|
)
|
||||||
go func() {
|
go func() {
|
||||||
|
@ -293,8 +275,13 @@ func (r *Registry) Register(c Collector) error {
|
||||||
close(descChan)
|
close(descChan)
|
||||||
}()
|
}()
|
||||||
r.mtx.Lock()
|
r.mtx.Lock()
|
||||||
defer r.mtx.Unlock()
|
defer func() {
|
||||||
// Coduct various tests...
|
// Drain channel in case of premature return to not leak a goroutine.
|
||||||
|
for range descChan {
|
||||||
|
}
|
||||||
|
r.mtx.Unlock()
|
||||||
|
}()
|
||||||
|
// Conduct various tests...
|
||||||
for desc := range descChan {
|
for desc := range descChan {
|
||||||
|
|
||||||
// Is the descriptor valid at all?
|
// Is the descriptor valid at all?
|
||||||
|
@ -307,12 +294,12 @@ func (r *Registry) Register(c Collector) error {
|
||||||
if _, exists := r.descIDs[desc.id]; exists {
|
if _, exists := r.descIDs[desc.id]; exists {
|
||||||
duplicateDescErr = fmt.Errorf("descriptor %s already exists with the same fully-qualified name and const label values", desc)
|
duplicateDescErr = fmt.Errorf("descriptor %s already exists with the same fully-qualified name and const label values", desc)
|
||||||
}
|
}
|
||||||
// If it is not a duplicate desc in this collector, add it to
|
// If it is not a duplicate desc in this collector, XOR it to
|
||||||
// the collectorID. (We allow duplicate descs within the same
|
// the collectorID. (We allow duplicate descs within the same
|
||||||
// collector, but their existence must be a no-op.)
|
// collector, but their existence must be a no-op.)
|
||||||
if _, exists := newDescIDs[desc.id]; !exists {
|
if _, exists := newDescIDs[desc.id]; !exists {
|
||||||
newDescIDs[desc.id] = struct{}{}
|
newDescIDs[desc.id] = struct{}{}
|
||||||
collectorID += desc.id
|
collectorID ^= desc.id
|
||||||
}
|
}
|
||||||
|
|
||||||
// Are all the label names and the help string consistent with
|
// Are all the label names and the help string consistent with
|
||||||
|
@ -333,14 +320,23 @@ func (r *Registry) Register(c Collector) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Did anything happen at all?
|
// A Collector yielding no Desc at all is considered unchecked.
|
||||||
if len(newDescIDs) == 0 {
|
if len(newDescIDs) == 0 {
|
||||||
return errors.New("collector has no descriptors")
|
r.uncheckedCollectors = append(r.uncheckedCollectors, c)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
if existing, exists := r.collectorsByID[collectorID]; exists {
|
if existing, exists := r.collectorsByID[collectorID]; exists {
|
||||||
return AlreadyRegisteredError{
|
switch e := existing.(type) {
|
||||||
ExistingCollector: existing,
|
case *wrappingCollector:
|
||||||
NewCollector: c,
|
return AlreadyRegisteredError{
|
||||||
|
ExistingCollector: e.unwrapRecursively(),
|
||||||
|
NewCollector: c,
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return AlreadyRegisteredError{
|
||||||
|
ExistingCollector: e,
|
||||||
|
NewCollector: c,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If the collectorID is new, but at least one of the descs existed
|
// If the collectorID is new, but at least one of the descs existed
|
||||||
|
@ -365,7 +361,7 @@ func (r *Registry) Unregister(c Collector) bool {
|
||||||
var (
|
var (
|
||||||
descChan = make(chan *Desc, capDescChan)
|
descChan = make(chan *Desc, capDescChan)
|
||||||
descIDs = map[uint64]struct{}{}
|
descIDs = map[uint64]struct{}{}
|
||||||
collectorID uint64 // Just a sum of the desc IDs.
|
collectorID uint64 // All desc IDs XOR'd together.
|
||||||
)
|
)
|
||||||
go func() {
|
go func() {
|
||||||
c.Describe(descChan)
|
c.Describe(descChan)
|
||||||
|
@ -373,7 +369,7 @@ func (r *Registry) Unregister(c Collector) bool {
|
||||||
}()
|
}()
|
||||||
for desc := range descChan {
|
for desc := range descChan {
|
||||||
if _, exists := descIDs[desc.id]; !exists {
|
if _, exists := descIDs[desc.id]; !exists {
|
||||||
collectorID += desc.id
|
collectorID ^= desc.id
|
||||||
descIDs[desc.id] = struct{}{}
|
descIDs[desc.id] = struct{}{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -409,31 +405,25 @@ func (r *Registry) MustRegister(cs ...Collector) {
|
||||||
// Gather implements Gatherer.
|
// Gather implements Gatherer.
|
||||||
func (r *Registry) Gather() ([]*dto.MetricFamily, error) {
|
func (r *Registry) Gather() ([]*dto.MetricFamily, error) {
|
||||||
var (
|
var (
|
||||||
metricChan = make(chan Metric, capMetricChan)
|
checkedMetricChan = make(chan Metric, capMetricChan)
|
||||||
metricHashes = map[uint64]struct{}{}
|
uncheckedMetricChan = make(chan Metric, capMetricChan)
|
||||||
dimHashes = map[string]uint64{}
|
metricHashes = map[uint64]struct{}{}
|
||||||
wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
errs MultiError // The collected errors to return in the end.
|
errs MultiError // The collected errors to return in the end.
|
||||||
registeredDescIDs map[uint64]struct{} // Only used for pedantic checks
|
registeredDescIDs map[uint64]struct{} // Only used for pedantic checks
|
||||||
)
|
)
|
||||||
|
|
||||||
r.mtx.RLock()
|
r.mtx.RLock()
|
||||||
|
goroutineBudget := len(r.collectorsByID) + len(r.uncheckedCollectors)
|
||||||
metricFamiliesByName := make(map[string]*dto.MetricFamily, len(r.dimHashesByName))
|
metricFamiliesByName := make(map[string]*dto.MetricFamily, len(r.dimHashesByName))
|
||||||
|
checkedCollectors := make(chan Collector, len(r.collectorsByID))
|
||||||
// Scatter.
|
uncheckedCollectors := make(chan Collector, len(r.uncheckedCollectors))
|
||||||
// (Collectors could be complex and slow, so we call them all at once.)
|
|
||||||
wg.Add(len(r.collectorsByID))
|
|
||||||
go func() {
|
|
||||||
wg.Wait()
|
|
||||||
close(metricChan)
|
|
||||||
}()
|
|
||||||
for _, collector := range r.collectorsByID {
|
for _, collector := range r.collectorsByID {
|
||||||
go func(collector Collector) {
|
checkedCollectors <- collector
|
||||||
defer wg.Done()
|
}
|
||||||
collector.Collect(metricChan)
|
for _, collector := range r.uncheckedCollectors {
|
||||||
}(collector)
|
uncheckedCollectors <- collector
|
||||||
}
|
}
|
||||||
|
|
||||||
// In case pedantic checks are enabled, we have to copy the map before
|
// In case pedantic checks are enabled, we have to copy the map before
|
||||||
// giving up the RLock.
|
// giving up the RLock.
|
||||||
if r.pedanticChecksEnabled {
|
if r.pedanticChecksEnabled {
|
||||||
|
@ -442,133 +432,264 @@ func (r *Registry) Gather() ([]*dto.MetricFamily, error) {
|
||||||
registeredDescIDs[id] = struct{}{}
|
registeredDescIDs[id] = struct{}{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
r.mtx.RUnlock()
|
r.mtx.RUnlock()
|
||||||
|
|
||||||
// Drain metricChan in case of premature return.
|
wg.Add(goroutineBudget)
|
||||||
|
|
||||||
|
collectWorker := func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case collector := <-checkedCollectors:
|
||||||
|
collector.Collect(checkedMetricChan)
|
||||||
|
case collector := <-uncheckedCollectors:
|
||||||
|
collector.Collect(uncheckedMetricChan)
|
||||||
|
default:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
wg.Done()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the first worker now to make sure at least one is running.
|
||||||
|
go collectWorker()
|
||||||
|
goroutineBudget--
|
||||||
|
|
||||||
|
// Close checkedMetricChan and uncheckedMetricChan once all collectors
|
||||||
|
// are collected.
|
||||||
|
go func() {
|
||||||
|
wg.Wait()
|
||||||
|
close(checkedMetricChan)
|
||||||
|
close(uncheckedMetricChan)
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Drain checkedMetricChan and uncheckedMetricChan in case of premature return.
|
||||||
defer func() {
|
defer func() {
|
||||||
for _ = range metricChan {
|
if checkedMetricChan != nil {
|
||||||
|
for range checkedMetricChan {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if uncheckedMetricChan != nil {
|
||||||
|
for range uncheckedMetricChan {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Gather.
|
// Copy the channel references so we can nil them out later to remove
|
||||||
for metric := range metricChan {
|
// them from the select statements below.
|
||||||
// This could be done concurrently, too, but it required locking
|
cmc := checkedMetricChan
|
||||||
// of metricFamiliesByName (and of metricHashes if checks are
|
umc := uncheckedMetricChan
|
||||||
// enabled). Most likely not worth it.
|
|
||||||
desc := metric.Desc()
|
for {
|
||||||
dtoMetric := &dto.Metric{}
|
select {
|
||||||
if err := metric.Write(dtoMetric); err != nil {
|
case metric, ok := <-cmc:
|
||||||
errs = append(errs, fmt.Errorf(
|
if !ok {
|
||||||
"error collecting metric %v: %s", desc, err,
|
cmc = nil
|
||||||
|
break
|
||||||
|
}
|
||||||
|
errs.Append(processMetric(
|
||||||
|
metric, metricFamiliesByName,
|
||||||
|
metricHashes,
|
||||||
|
registeredDescIDs,
|
||||||
))
|
))
|
||||||
continue
|
case metric, ok := <-umc:
|
||||||
|
if !ok {
|
||||||
|
umc = nil
|
||||||
|
break
|
||||||
|
}
|
||||||
|
errs.Append(processMetric(
|
||||||
|
metric, metricFamiliesByName,
|
||||||
|
metricHashes,
|
||||||
|
nil,
|
||||||
|
))
|
||||||
|
default:
|
||||||
|
if goroutineBudget <= 0 || len(checkedCollectors)+len(uncheckedCollectors) == 0 {
|
||||||
|
// All collectors are already being worked on or
|
||||||
|
// we have already as many goroutines started as
|
||||||
|
// there are collectors. Do the same as above,
|
||||||
|
// just without the default.
|
||||||
|
select {
|
||||||
|
case metric, ok := <-cmc:
|
||||||
|
if !ok {
|
||||||
|
cmc = nil
|
||||||
|
break
|
||||||
|
}
|
||||||
|
errs.Append(processMetric(
|
||||||
|
metric, metricFamiliesByName,
|
||||||
|
metricHashes,
|
||||||
|
registeredDescIDs,
|
||||||
|
))
|
||||||
|
case metric, ok := <-umc:
|
||||||
|
if !ok {
|
||||||
|
umc = nil
|
||||||
|
break
|
||||||
|
}
|
||||||
|
errs.Append(processMetric(
|
||||||
|
metric, metricFamiliesByName,
|
||||||
|
metricHashes,
|
||||||
|
nil,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// Start more workers.
|
||||||
|
go collectWorker()
|
||||||
|
goroutineBudget--
|
||||||
|
runtime.Gosched()
|
||||||
}
|
}
|
||||||
metricFamily, ok := metricFamiliesByName[desc.fqName]
|
// Once both checkedMetricChan and uncheckdMetricChan are closed
|
||||||
if ok {
|
// and drained, the contraption above will nil out cmc and umc,
|
||||||
if metricFamily.GetHelp() != desc.help {
|
// and then we can leave the collect loop here.
|
||||||
errs = append(errs, fmt.Errorf(
|
if cmc == nil && umc == nil {
|
||||||
"collected metric %s %s has help %q but should have %q",
|
break
|
||||||
desc.fqName, dtoMetric, desc.help, metricFamily.GetHelp(),
|
|
||||||
))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// TODO(beorn7): Simplify switch once Desc has type.
|
|
||||||
switch metricFamily.GetType() {
|
|
||||||
case dto.MetricType_COUNTER:
|
|
||||||
if dtoMetric.Counter == nil {
|
|
||||||
errs = append(errs, fmt.Errorf(
|
|
||||||
"collected metric %s %s should be a Counter",
|
|
||||||
desc.fqName, dtoMetric,
|
|
||||||
))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
case dto.MetricType_GAUGE:
|
|
||||||
if dtoMetric.Gauge == nil {
|
|
||||||
errs = append(errs, fmt.Errorf(
|
|
||||||
"collected metric %s %s should be a Gauge",
|
|
||||||
desc.fqName, dtoMetric,
|
|
||||||
))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
case dto.MetricType_SUMMARY:
|
|
||||||
if dtoMetric.Summary == nil {
|
|
||||||
errs = append(errs, fmt.Errorf(
|
|
||||||
"collected metric %s %s should be a Summary",
|
|
||||||
desc.fqName, dtoMetric,
|
|
||||||
))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
case dto.MetricType_UNTYPED:
|
|
||||||
if dtoMetric.Untyped == nil {
|
|
||||||
errs = append(errs, fmt.Errorf(
|
|
||||||
"collected metric %s %s should be Untyped",
|
|
||||||
desc.fqName, dtoMetric,
|
|
||||||
))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
case dto.MetricType_HISTOGRAM:
|
|
||||||
if dtoMetric.Histogram == nil {
|
|
||||||
errs = append(errs, fmt.Errorf(
|
|
||||||
"collected metric %s %s should be a Histogram",
|
|
||||||
desc.fqName, dtoMetric,
|
|
||||||
))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
panic("encountered MetricFamily with invalid type")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
metricFamily = &dto.MetricFamily{}
|
|
||||||
metricFamily.Name = proto.String(desc.fqName)
|
|
||||||
metricFamily.Help = proto.String(desc.help)
|
|
||||||
// TODO(beorn7): Simplify switch once Desc has type.
|
|
||||||
switch {
|
|
||||||
case dtoMetric.Gauge != nil:
|
|
||||||
metricFamily.Type = dto.MetricType_GAUGE.Enum()
|
|
||||||
case dtoMetric.Counter != nil:
|
|
||||||
metricFamily.Type = dto.MetricType_COUNTER.Enum()
|
|
||||||
case dtoMetric.Summary != nil:
|
|
||||||
metricFamily.Type = dto.MetricType_SUMMARY.Enum()
|
|
||||||
case dtoMetric.Untyped != nil:
|
|
||||||
metricFamily.Type = dto.MetricType_UNTYPED.Enum()
|
|
||||||
case dtoMetric.Histogram != nil:
|
|
||||||
metricFamily.Type = dto.MetricType_HISTOGRAM.Enum()
|
|
||||||
default:
|
|
||||||
errs = append(errs, fmt.Errorf(
|
|
||||||
"empty metric collected: %s", dtoMetric,
|
|
||||||
))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
metricFamiliesByName[desc.fqName] = metricFamily
|
|
||||||
}
|
}
|
||||||
if err := checkMetricConsistency(metricFamily, dtoMetric, metricHashes, dimHashes); err != nil {
|
|
||||||
errs = append(errs, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if r.pedanticChecksEnabled {
|
|
||||||
// Is the desc registered at all?
|
|
||||||
if _, exist := registeredDescIDs[desc.id]; !exist {
|
|
||||||
errs = append(errs, fmt.Errorf(
|
|
||||||
"collected metric %s %s with unregistered descriptor %s",
|
|
||||||
metricFamily.GetName(), dtoMetric, desc,
|
|
||||||
))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err := checkDescConsistency(metricFamily, dtoMetric, desc); err != nil {
|
|
||||||
errs = append(errs, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
metricFamily.Metric = append(metricFamily.Metric, dtoMetric)
|
|
||||||
}
|
}
|
||||||
return normalizeMetricFamilies(metricFamiliesByName), errs.MaybeUnwrap()
|
return internal.NormalizeMetricFamilies(metricFamiliesByName), errs.MaybeUnwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteToTextfile calls Gather on the provided Gatherer, encodes the result in the
|
||||||
|
// Prometheus text format, and writes it to a temporary file. Upon success, the
|
||||||
|
// temporary file is renamed to the provided filename.
|
||||||
|
//
|
||||||
|
// This is intended for use with the textfile collector of the node exporter.
|
||||||
|
// Note that the node exporter expects the filename to be suffixed with ".prom".
|
||||||
|
func WriteToTextfile(filename string, g Gatherer) error {
|
||||||
|
tmp, err := ioutil.TempFile(filepath.Dir(filename), filepath.Base(filename))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer os.Remove(tmp.Name())
|
||||||
|
|
||||||
|
mfs, err := g.Gather()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, mf := range mfs {
|
||||||
|
if _, err := expfmt.MetricFamilyToText(tmp, mf); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := tmp.Close(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.Chmod(tmp.Name(), 0644); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return os.Rename(tmp.Name(), filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
// processMetric is an internal helper method only used by the Gather method.
|
||||||
|
func processMetric(
|
||||||
|
metric Metric,
|
||||||
|
metricFamiliesByName map[string]*dto.MetricFamily,
|
||||||
|
metricHashes map[uint64]struct{},
|
||||||
|
registeredDescIDs map[uint64]struct{},
|
||||||
|
) error {
|
||||||
|
desc := metric.Desc()
|
||||||
|
// Wrapped metrics collected by an unchecked Collector can have an
|
||||||
|
// invalid Desc.
|
||||||
|
if desc.err != nil {
|
||||||
|
return desc.err
|
||||||
|
}
|
||||||
|
dtoMetric := &dto.Metric{}
|
||||||
|
if err := metric.Write(dtoMetric); err != nil {
|
||||||
|
return fmt.Errorf("error collecting metric %v: %s", desc, err)
|
||||||
|
}
|
||||||
|
metricFamily, ok := metricFamiliesByName[desc.fqName]
|
||||||
|
if ok { // Existing name.
|
||||||
|
if metricFamily.GetHelp() != desc.help {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"collected metric %s %s has help %q but should have %q",
|
||||||
|
desc.fqName, dtoMetric, desc.help, metricFamily.GetHelp(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// TODO(beorn7): Simplify switch once Desc has type.
|
||||||
|
switch metricFamily.GetType() {
|
||||||
|
case dto.MetricType_COUNTER:
|
||||||
|
if dtoMetric.Counter == nil {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"collected metric %s %s should be a Counter",
|
||||||
|
desc.fqName, dtoMetric,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
case dto.MetricType_GAUGE:
|
||||||
|
if dtoMetric.Gauge == nil {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"collected metric %s %s should be a Gauge",
|
||||||
|
desc.fqName, dtoMetric,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
case dto.MetricType_SUMMARY:
|
||||||
|
if dtoMetric.Summary == nil {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"collected metric %s %s should be a Summary",
|
||||||
|
desc.fqName, dtoMetric,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
case dto.MetricType_UNTYPED:
|
||||||
|
if dtoMetric.Untyped == nil {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"collected metric %s %s should be Untyped",
|
||||||
|
desc.fqName, dtoMetric,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
case dto.MetricType_HISTOGRAM:
|
||||||
|
if dtoMetric.Histogram == nil {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"collected metric %s %s should be a Histogram",
|
||||||
|
desc.fqName, dtoMetric,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
panic("encountered MetricFamily with invalid type")
|
||||||
|
}
|
||||||
|
} else { // New name.
|
||||||
|
metricFamily = &dto.MetricFamily{}
|
||||||
|
metricFamily.Name = proto.String(desc.fqName)
|
||||||
|
metricFamily.Help = proto.String(desc.help)
|
||||||
|
// TODO(beorn7): Simplify switch once Desc has type.
|
||||||
|
switch {
|
||||||
|
case dtoMetric.Gauge != nil:
|
||||||
|
metricFamily.Type = dto.MetricType_GAUGE.Enum()
|
||||||
|
case dtoMetric.Counter != nil:
|
||||||
|
metricFamily.Type = dto.MetricType_COUNTER.Enum()
|
||||||
|
case dtoMetric.Summary != nil:
|
||||||
|
metricFamily.Type = dto.MetricType_SUMMARY.Enum()
|
||||||
|
case dtoMetric.Untyped != nil:
|
||||||
|
metricFamily.Type = dto.MetricType_UNTYPED.Enum()
|
||||||
|
case dtoMetric.Histogram != nil:
|
||||||
|
metricFamily.Type = dto.MetricType_HISTOGRAM.Enum()
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("empty metric collected: %s", dtoMetric)
|
||||||
|
}
|
||||||
|
if err := checkSuffixCollisions(metricFamily, metricFamiliesByName); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
metricFamiliesByName[desc.fqName] = metricFamily
|
||||||
|
}
|
||||||
|
if err := checkMetricConsistency(metricFamily, dtoMetric, metricHashes); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if registeredDescIDs != nil {
|
||||||
|
// Is the desc registered at all?
|
||||||
|
if _, exist := registeredDescIDs[desc.id]; !exist {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"collected metric %s %s with unregistered descriptor %s",
|
||||||
|
metricFamily.GetName(), dtoMetric, desc,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if err := checkDescConsistency(metricFamily, dtoMetric, desc); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
metricFamily.Metric = append(metricFamily.Metric, dtoMetric)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gatherers is a slice of Gatherer instances that implements the Gatherer
|
// Gatherers is a slice of Gatherer instances that implements the Gatherer
|
||||||
// interface itself. Its Gather method calls Gather on all Gatherers in the
|
// interface itself. Its Gather method calls Gather on all Gatherers in the
|
||||||
// slice in order and returns the merged results. Errors returned from the
|
// slice in order and returns the merged results. Errors returned from the
|
||||||
// Gather calles are all returned in a flattened MultiError. Duplicate and
|
// Gather calls are all returned in a flattened MultiError. Duplicate and
|
||||||
// inconsistent Metrics are skipped (first occurrence in slice order wins) and
|
// inconsistent Metrics are skipped (first occurrence in slice order wins) and
|
||||||
// reported in the returned error.
|
// reported in the returned error.
|
||||||
//
|
//
|
||||||
|
@ -588,7 +709,6 @@ func (gs Gatherers) Gather() ([]*dto.MetricFamily, error) {
|
||||||
var (
|
var (
|
||||||
metricFamiliesByName = map[string]*dto.MetricFamily{}
|
metricFamiliesByName = map[string]*dto.MetricFamily{}
|
||||||
metricHashes = map[uint64]struct{}{}
|
metricHashes = map[uint64]struct{}{}
|
||||||
dimHashes = map[string]uint64{}
|
|
||||||
errs MultiError // The collected errors to return in the end.
|
errs MultiError // The collected errors to return in the end.
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -625,10 +745,14 @@ func (gs Gatherers) Gather() ([]*dto.MetricFamily, error) {
|
||||||
existingMF.Name = mf.Name
|
existingMF.Name = mf.Name
|
||||||
existingMF.Help = mf.Help
|
existingMF.Help = mf.Help
|
||||||
existingMF.Type = mf.Type
|
existingMF.Type = mf.Type
|
||||||
|
if err := checkSuffixCollisions(existingMF, metricFamiliesByName); err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
metricFamiliesByName[mf.GetName()] = existingMF
|
metricFamiliesByName[mf.GetName()] = existingMF
|
||||||
}
|
}
|
||||||
for _, m := range mf.Metric {
|
for _, m := range mf.Metric {
|
||||||
if err := checkMetricConsistency(existingMF, m, metricHashes, dimHashes); err != nil {
|
if err := checkMetricConsistency(existingMF, m, metricHashes); err != nil {
|
||||||
errs = append(errs, err)
|
errs = append(errs, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -636,88 +760,80 @@ func (gs Gatherers) Gather() ([]*dto.MetricFamily, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return normalizeMetricFamilies(metricFamiliesByName), errs.MaybeUnwrap()
|
return internal.NormalizeMetricFamilies(metricFamiliesByName), errs.MaybeUnwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
// metricSorter is a sortable slice of *dto.Metric.
|
// checkSuffixCollisions checks for collisions with the “magic” suffixes the
|
||||||
type metricSorter []*dto.Metric
|
// Prometheus text format and the internal metric representation of the
|
||||||
|
// Prometheus server add while flattening Summaries and Histograms.
|
||||||
func (s metricSorter) Len() int {
|
func checkSuffixCollisions(mf *dto.MetricFamily, mfs map[string]*dto.MetricFamily) error {
|
||||||
return len(s)
|
var (
|
||||||
}
|
newName = mf.GetName()
|
||||||
|
newType = mf.GetType()
|
||||||
func (s metricSorter) Swap(i, j int) {
|
newNameWithoutSuffix = ""
|
||||||
s[i], s[j] = s[j], s[i]
|
)
|
||||||
}
|
switch {
|
||||||
|
case strings.HasSuffix(newName, "_count"):
|
||||||
func (s metricSorter) Less(i, j int) bool {
|
newNameWithoutSuffix = newName[:len(newName)-6]
|
||||||
if len(s[i].Label) != len(s[j].Label) {
|
case strings.HasSuffix(newName, "_sum"):
|
||||||
// This should not happen. The metrics are
|
newNameWithoutSuffix = newName[:len(newName)-4]
|
||||||
// inconsistent. However, we have to deal with the fact, as
|
case strings.HasSuffix(newName, "_bucket"):
|
||||||
// people might use custom collectors or metric family injection
|
newNameWithoutSuffix = newName[:len(newName)-7]
|
||||||
// to create inconsistent metrics. So let's simply compare the
|
|
||||||
// number of labels in this case. That will still yield
|
|
||||||
// reproducible sorting.
|
|
||||||
return len(s[i].Label) < len(s[j].Label)
|
|
||||||
}
|
}
|
||||||
for n, lp := range s[i].Label {
|
if newNameWithoutSuffix != "" {
|
||||||
vi := lp.GetValue()
|
if existingMF, ok := mfs[newNameWithoutSuffix]; ok {
|
||||||
vj := s[j].Label[n].GetValue()
|
switch existingMF.GetType() {
|
||||||
if vi != vj {
|
case dto.MetricType_SUMMARY:
|
||||||
return vi < vj
|
if !strings.HasSuffix(newName, "_bucket") {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"collected metric named %q collides with previously collected summary named %q",
|
||||||
|
newName, newNameWithoutSuffix,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
case dto.MetricType_HISTOGRAM:
|
||||||
|
return fmt.Errorf(
|
||||||
|
"collected metric named %q collides with previously collected histogram named %q",
|
||||||
|
newName, newNameWithoutSuffix,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if newType == dto.MetricType_SUMMARY || newType == dto.MetricType_HISTOGRAM {
|
||||||
// We should never arrive here. Multiple metrics with the same
|
if _, ok := mfs[newName+"_count"]; ok {
|
||||||
// label set in the same scrape will lead to undefined ingestion
|
return fmt.Errorf(
|
||||||
// behavior. However, as above, we have to provide stable sorting
|
"collected histogram or summary named %q collides with previously collected metric named %q",
|
||||||
// here, even for inconsistent metrics. So sort equal metrics
|
newName, newName+"_count",
|
||||||
// by their timestamp, with missing timestamps (implying "now")
|
)
|
||||||
// coming last.
|
}
|
||||||
if s[i].TimestampMs == nil {
|
if _, ok := mfs[newName+"_sum"]; ok {
|
||||||
return false
|
return fmt.Errorf(
|
||||||
}
|
"collected histogram or summary named %q collides with previously collected metric named %q",
|
||||||
if s[j].TimestampMs == nil {
|
newName, newName+"_sum",
|
||||||
return true
|
)
|
||||||
}
|
|
||||||
return s[i].GetTimestampMs() < s[j].GetTimestampMs()
|
|
||||||
}
|
|
||||||
|
|
||||||
// normalizeMetricFamilies returns a MetricFamily slice whith empty
|
|
||||||
// MetricFamilies pruned and the remaining MetricFamilies sorted by name within
|
|
||||||
// the slice, with the contained Metrics sorted within each MetricFamily.
|
|
||||||
func normalizeMetricFamilies(metricFamiliesByName map[string]*dto.MetricFamily) []*dto.MetricFamily {
|
|
||||||
for _, mf := range metricFamiliesByName {
|
|
||||||
sort.Sort(metricSorter(mf.Metric))
|
|
||||||
}
|
|
||||||
names := make([]string, 0, len(metricFamiliesByName))
|
|
||||||
for name, mf := range metricFamiliesByName {
|
|
||||||
if len(mf.Metric) > 0 {
|
|
||||||
names = append(names, name)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sort.Strings(names)
|
if newType == dto.MetricType_HISTOGRAM {
|
||||||
result := make([]*dto.MetricFamily, 0, len(names))
|
if _, ok := mfs[newName+"_bucket"]; ok {
|
||||||
for _, name := range names {
|
return fmt.Errorf(
|
||||||
result = append(result, metricFamiliesByName[name])
|
"collected histogram named %q collides with previously collected metric named %q",
|
||||||
|
newName, newName+"_bucket",
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return result
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkMetricConsistency checks if the provided Metric is consistent with the
|
// checkMetricConsistency checks if the provided Metric is consistent with the
|
||||||
// provided MetricFamily. It also hashed the Metric labels and the MetricFamily
|
// provided MetricFamily. It also hashes the Metric labels and the MetricFamily
|
||||||
// name. If the resulting hash is alread in the provided metricHashes, an error
|
// name. If the resulting hash is already in the provided metricHashes, an error
|
||||||
// is returned. If not, it is added to metricHashes. The provided dimHashes maps
|
// is returned. If not, it is added to metricHashes.
|
||||||
// MetricFamily names to their dimHash (hashed sorted label names). If dimHashes
|
|
||||||
// doesn't yet contain a hash for the provided MetricFamily, it is
|
|
||||||
// added. Otherwise, an error is returned if the existing dimHashes in not equal
|
|
||||||
// the calculated dimHash.
|
|
||||||
func checkMetricConsistency(
|
func checkMetricConsistency(
|
||||||
metricFamily *dto.MetricFamily,
|
metricFamily *dto.MetricFamily,
|
||||||
dtoMetric *dto.Metric,
|
dtoMetric *dto.Metric,
|
||||||
metricHashes map[uint64]struct{},
|
metricHashes map[uint64]struct{},
|
||||||
dimHashes map[string]uint64,
|
|
||||||
) error {
|
) error {
|
||||||
|
name := metricFamily.GetName()
|
||||||
|
|
||||||
// Type consistency with metric family.
|
// Type consistency with metric family.
|
||||||
if metricFamily.GetType() == dto.MetricType_GAUGE && dtoMetric.Gauge == nil ||
|
if metricFamily.GetType() == dto.MetricType_GAUGE && dtoMetric.Gauge == nil ||
|
||||||
metricFamily.GetType() == dto.MetricType_COUNTER && dtoMetric.Counter == nil ||
|
metricFamily.GetType() == dto.MetricType_COUNTER && dtoMetric.Counter == nil ||
|
||||||
|
@ -725,42 +841,67 @@ func checkMetricConsistency(
|
||||||
metricFamily.GetType() == dto.MetricType_HISTOGRAM && dtoMetric.Histogram == nil ||
|
metricFamily.GetType() == dto.MetricType_HISTOGRAM && dtoMetric.Histogram == nil ||
|
||||||
metricFamily.GetType() == dto.MetricType_UNTYPED && dtoMetric.Untyped == nil {
|
metricFamily.GetType() == dto.MetricType_UNTYPED && dtoMetric.Untyped == nil {
|
||||||
return fmt.Errorf(
|
return fmt.Errorf(
|
||||||
"collected metric %s %s is not a %s",
|
"collected metric %q { %s} is not a %s",
|
||||||
metricFamily.GetName(), dtoMetric, metricFamily.GetType(),
|
name, dtoMetric, metricFamily.GetType(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Is the metric unique (i.e. no other metric with the same name and the same label values)?
|
previousLabelName := ""
|
||||||
h := hashNew()
|
for _, labelPair := range dtoMetric.GetLabel() {
|
||||||
h = hashAdd(h, metricFamily.GetName())
|
labelName := labelPair.GetName()
|
||||||
h = hashAddByte(h, separatorByte)
|
if labelName == previousLabelName {
|
||||||
dh := hashNew()
|
|
||||||
// Make sure label pairs are sorted. We depend on it for the consistency
|
|
||||||
// check.
|
|
||||||
sort.Sort(LabelPairSorter(dtoMetric.Label))
|
|
||||||
for _, lp := range dtoMetric.Label {
|
|
||||||
h = hashAdd(h, lp.GetValue())
|
|
||||||
h = hashAddByte(h, separatorByte)
|
|
||||||
dh = hashAdd(dh, lp.GetName())
|
|
||||||
dh = hashAddByte(dh, separatorByte)
|
|
||||||
}
|
|
||||||
if _, exists := metricHashes[h]; exists {
|
|
||||||
return fmt.Errorf(
|
|
||||||
"collected metric %s %s was collected before with the same name and label values",
|
|
||||||
metricFamily.GetName(), dtoMetric,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if dimHash, ok := dimHashes[metricFamily.GetName()]; ok {
|
|
||||||
if dimHash != dh {
|
|
||||||
return fmt.Errorf(
|
return fmt.Errorf(
|
||||||
"collected metric %s %s has label dimensions inconsistent with previously collected metrics in the same metric family",
|
"collected metric %q { %s} has two or more labels with the same name: %s",
|
||||||
metricFamily.GetName(), dtoMetric,
|
name, dtoMetric, labelName,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
if !checkLabelName(labelName) {
|
||||||
dimHashes[metricFamily.GetName()] = dh
|
return fmt.Errorf(
|
||||||
|
"collected metric %q { %s} has a label with an invalid name: %s",
|
||||||
|
name, dtoMetric, labelName,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if dtoMetric.Summary != nil && labelName == quantileLabel {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"collected metric %q { %s} must not have an explicit %q label",
|
||||||
|
name, dtoMetric, quantileLabel,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if !utf8.ValidString(labelPair.GetValue()) {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"collected metric %q { %s} has a label named %q whose value is not utf8: %#v",
|
||||||
|
name, dtoMetric, labelName, labelPair.GetValue())
|
||||||
|
}
|
||||||
|
previousLabelName = labelName
|
||||||
}
|
}
|
||||||
metricHashes[h] = struct{}{}
|
|
||||||
|
// Is the metric unique (i.e. no other metric with the same name and the same labels)?
|
||||||
|
h := xxhash.New()
|
||||||
|
h.WriteString(name)
|
||||||
|
h.Write(separatorByteSlice)
|
||||||
|
// Make sure label pairs are sorted. We depend on it for the consistency
|
||||||
|
// check.
|
||||||
|
if !sort.IsSorted(labelPairSorter(dtoMetric.Label)) {
|
||||||
|
// We cannot sort dtoMetric.Label in place as it is immutable by contract.
|
||||||
|
copiedLabels := make([]*dto.LabelPair, len(dtoMetric.Label))
|
||||||
|
copy(copiedLabels, dtoMetric.Label)
|
||||||
|
sort.Sort(labelPairSorter(copiedLabels))
|
||||||
|
dtoMetric.Label = copiedLabels
|
||||||
|
}
|
||||||
|
for _, lp := range dtoMetric.Label {
|
||||||
|
h.WriteString(lp.GetName())
|
||||||
|
h.Write(separatorByteSlice)
|
||||||
|
h.WriteString(lp.GetValue())
|
||||||
|
h.Write(separatorByteSlice)
|
||||||
|
}
|
||||||
|
hSum := h.Sum64()
|
||||||
|
if _, exists := metricHashes[hSum]; exists {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"collected metric %q { %s} was collected before with the same name and label values",
|
||||||
|
name, dtoMetric,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
metricHashes[hSum] = struct{}{}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -778,8 +919,8 @@ func checkDescConsistency(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Is the desc consistent with the content of the metric?
|
// Is the desc consistent with the content of the metric?
|
||||||
lpsFromDesc := make([]*dto.LabelPair, 0, len(dtoMetric.Label))
|
lpsFromDesc := make([]*dto.LabelPair, len(desc.constLabelPairs), len(dtoMetric.Label))
|
||||||
lpsFromDesc = append(lpsFromDesc, desc.constLabelPairs...)
|
copy(lpsFromDesc, desc.constLabelPairs)
|
||||||
for _, l := range desc.variableLabels {
|
for _, l := range desc.variableLabels {
|
||||||
lpsFromDesc = append(lpsFromDesc, &dto.LabelPair{
|
lpsFromDesc = append(lpsFromDesc, &dto.LabelPair{
|
||||||
Name: proto.String(l),
|
Name: proto.String(l),
|
||||||
|
@ -791,7 +932,7 @@ func checkDescConsistency(
|
||||||
metricFamily.GetName(), dtoMetric, desc,
|
metricFamily.GetName(), dtoMetric, desc,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
sort.Sort(LabelPairSorter(lpsFromDesc))
|
sort.Sort(labelPairSorter(lpsFromDesc))
|
||||||
for i, lpFromDesc := range lpsFromDesc {
|
for i, lpFromDesc := range lpsFromDesc {
|
||||||
lpFromMetric := dtoMetric.Label[i]
|
lpFromMetric := dtoMetric.Label[i]
|
||||||
if lpFromDesc.GetName() != lpFromMetric.GetName() ||
|
if lpFromDesc.GetName() != lpFromMetric.GetName() ||
|
||||||
|
|
|
@ -16,8 +16,10 @@ package prometheus
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
|
"runtime"
|
||||||
"sort"
|
"sort"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/beorn7/perks/quantile"
|
"github.com/beorn7/perks/quantile"
|
||||||
|
@ -36,7 +38,10 @@ const quantileLabel = "quantile"
|
||||||
//
|
//
|
||||||
// A typical use-case is the observation of request latencies. By default, a
|
// A typical use-case is the observation of request latencies. By default, a
|
||||||
// Summary provides the median, the 90th and the 99th percentile of the latency
|
// Summary provides the median, the 90th and the 99th percentile of the latency
|
||||||
// as rank estimations.
|
// as rank estimations. However, the default behavior will change in the
|
||||||
|
// upcoming v1.0.0 of the library. There will be no rank estimations at all by
|
||||||
|
// default. For a sane transition, it is recommended to set the desired rank
|
||||||
|
// estimations explicitly.
|
||||||
//
|
//
|
||||||
// Note that the rank estimations cannot be aggregated in a meaningful way with
|
// Note that the rank estimations cannot be aggregated in a meaningful way with
|
||||||
// the Prometheus query language (i.e. you cannot average or add them). If you
|
// the Prometheus query language (i.e. you cannot average or add them). If you
|
||||||
|
@ -53,13 +58,8 @@ type Summary interface {
|
||||||
Observe(float64)
|
Observe(float64)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefObjectives are the default Summary quantile values.
|
var errQuantileLabelNotAllowed = fmt.Errorf(
|
||||||
var (
|
"%q is not allowed as label name in summaries", quantileLabel,
|
||||||
DefObjectives = map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001}
|
|
||||||
|
|
||||||
errQuantileLabelNotAllowed = fmt.Errorf(
|
|
||||||
"%q is not allowed as label name in summaries", quantileLabel,
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Default values for SummaryOpts.
|
// Default values for SummaryOpts.
|
||||||
|
@ -75,8 +75,10 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
// SummaryOpts bundles the options for creating a Summary metric. It is
|
// SummaryOpts bundles the options for creating a Summary metric. It is
|
||||||
// mandatory to set Name and Help to a non-empty string. All other fields are
|
// mandatory to set Name to a non-empty string. While all other fields are
|
||||||
// optional and can safely be left at their zero value.
|
// optional and can safely be left at their zero value, it is recommended to set
|
||||||
|
// a help string and to explicitly set the Objectives field to the desired value
|
||||||
|
// as the default value will change in the upcoming v1.0.0 of the library.
|
||||||
type SummaryOpts struct {
|
type SummaryOpts struct {
|
||||||
// Namespace, Subsystem, and Name are components of the fully-qualified
|
// Namespace, Subsystem, and Name are components of the fully-qualified
|
||||||
// name of the Summary (created by joining these components with
|
// name of the Summary (created by joining these components with
|
||||||
|
@ -87,35 +89,34 @@ type SummaryOpts struct {
|
||||||
Subsystem string
|
Subsystem string
|
||||||
Name string
|
Name string
|
||||||
|
|
||||||
// Help provides information about this Summary. Mandatory!
|
// Help provides information about this Summary.
|
||||||
//
|
//
|
||||||
// Metrics with the same fully-qualified name must have the same Help
|
// Metrics with the same fully-qualified name must have the same Help
|
||||||
// string.
|
// string.
|
||||||
Help string
|
Help string
|
||||||
|
|
||||||
// ConstLabels are used to attach fixed labels to this
|
// ConstLabels are used to attach fixed labels to this metric. Metrics
|
||||||
// Summary. Summaries with the same fully-qualified name must have the
|
// with the same fully-qualified name must have the same label names in
|
||||||
// same label names in their ConstLabels.
|
// their ConstLabels.
|
||||||
//
|
//
|
||||||
// Note that in most cases, labels have a value that varies during the
|
// Due to the way a Summary is represented in the Prometheus text format
|
||||||
// lifetime of a process. Those labels are usually managed with a
|
// and how it is handled by the Prometheus server internally, “quantile”
|
||||||
// SummaryVec. ConstLabels serve only special purposes. One is for the
|
// is an illegal label name. Construction of a Summary or SummaryVec
|
||||||
// special case where the value of a label does not change during the
|
// will panic if this label name is used in ConstLabels.
|
||||||
// lifetime of a process, e.g. if the revision of the running binary is
|
|
||||||
// put into a label. Another, more advanced purpose is if more than one
|
|
||||||
// Collector needs to collect Summaries with the same fully-qualified
|
|
||||||
// name. In that case, those Summaries must differ in the values of
|
|
||||||
// their ConstLabels. See the Collector examples.
|
|
||||||
//
|
//
|
||||||
// If the value of a label never changes (not even between binaries),
|
// ConstLabels are only used rarely. In particular, do not use them to
|
||||||
// that label most likely should not be a label at all (but part of the
|
// attach the same labels to all your metrics. Those use cases are
|
||||||
// metric name).
|
// better covered by target labels set by the scraping Prometheus
|
||||||
|
// server, or by one specific metric (e.g. a build_info or a
|
||||||
|
// machine_role metric). See also
|
||||||
|
// https://prometheus.io/docs/instrumenting/writing_exporters/#target-labels,-not-static-scraped-labels
|
||||||
ConstLabels Labels
|
ConstLabels Labels
|
||||||
|
|
||||||
// Objectives defines the quantile rank estimates with their respective
|
// Objectives defines the quantile rank estimates with their respective
|
||||||
// absolute error. If Objectives[q] = e, then the value reported
|
// absolute error. If Objectives[q] = e, then the value reported for q
|
||||||
// for q will be the φ-quantile value for some φ between q-e and q+e.
|
// will be the φ-quantile value for some φ between q-e and q+e. The
|
||||||
// The default value is DefObjectives.
|
// default value is an empty map, resulting in a summary without
|
||||||
|
// quantiles.
|
||||||
Objectives map[float64]float64
|
Objectives map[float64]float64
|
||||||
|
|
||||||
// MaxAge defines the duration for which an observation stays relevant
|
// MaxAge defines the duration for which an observation stays relevant
|
||||||
|
@ -139,7 +140,7 @@ type SummaryOpts struct {
|
||||||
BufCap uint32
|
BufCap uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
// Great fuck-up with the sliding-window decay algorithm... The Merge method of
|
// Problem with the sliding-window decay algorithm... The Merge method of
|
||||||
// perk/quantile is actually not working as advertised - and it might be
|
// perk/quantile is actually not working as advertised - and it might be
|
||||||
// unfixable, as the underlying algorithm is apparently not capable of merging
|
// unfixable, as the underlying algorithm is apparently not capable of merging
|
||||||
// summaries in the first place. To avoid using Merge, we are currently adding
|
// summaries in the first place. To avoid using Merge, we are currently adding
|
||||||
|
@ -169,7 +170,7 @@ func NewSummary(opts SummaryOpts) Summary {
|
||||||
|
|
||||||
func newSummary(desc *Desc, opts SummaryOpts, labelValues ...string) Summary {
|
func newSummary(desc *Desc, opts SummaryOpts, labelValues ...string) Summary {
|
||||||
if len(desc.variableLabels) != len(labelValues) {
|
if len(desc.variableLabels) != len(labelValues) {
|
||||||
panic(errInconsistentCardinality)
|
panic(makeInconsistentCardinalityError(desc.fqName, desc.variableLabels, labelValues))
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, n := range desc.variableLabels {
|
for _, n := range desc.variableLabels {
|
||||||
|
@ -183,8 +184,8 @@ func newSummary(desc *Desc, opts SummaryOpts, labelValues ...string) Summary {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(opts.Objectives) == 0 {
|
if opts.Objectives == nil {
|
||||||
opts.Objectives = DefObjectives
|
opts.Objectives = map[float64]float64{}
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.MaxAge < 0 {
|
if opts.MaxAge < 0 {
|
||||||
|
@ -202,6 +203,17 @@ func newSummary(desc *Desc, opts SummaryOpts, labelValues ...string) Summary {
|
||||||
opts.BufCap = DefBufCap
|
opts.BufCap = DefBufCap
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(opts.Objectives) == 0 {
|
||||||
|
// Use the lock-free implementation of a Summary without objectives.
|
||||||
|
s := &noObjectivesSummary{
|
||||||
|
desc: desc,
|
||||||
|
labelPairs: makeLabelPairs(desc, labelValues),
|
||||||
|
counts: [2]*summaryCounts{{}, {}},
|
||||||
|
}
|
||||||
|
s.init(s) // Init self-collection.
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
s := &summary{
|
s := &summary{
|
||||||
desc: desc,
|
desc: desc,
|
||||||
|
|
||||||
|
@ -370,6 +382,116 @@ func (s *summary) swapBufs(now time.Time) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type summaryCounts struct {
|
||||||
|
// sumBits contains the bits of the float64 representing the sum of all
|
||||||
|
// observations. sumBits and count have to go first in the struct to
|
||||||
|
// guarantee alignment for atomic operations.
|
||||||
|
// http://golang.org/pkg/sync/atomic/#pkg-note-BUG
|
||||||
|
sumBits uint64
|
||||||
|
count uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
type noObjectivesSummary struct {
|
||||||
|
// countAndHotIdx enables lock-free writes with use of atomic updates.
|
||||||
|
// The most significant bit is the hot index [0 or 1] of the count field
|
||||||
|
// below. Observe calls update the hot one. All remaining bits count the
|
||||||
|
// number of Observe calls. Observe starts by incrementing this counter,
|
||||||
|
// and finish by incrementing the count field in the respective
|
||||||
|
// summaryCounts, as a marker for completion.
|
||||||
|
//
|
||||||
|
// Calls of the Write method (which are non-mutating reads from the
|
||||||
|
// perspective of the summary) swap the hot–cold under the writeMtx
|
||||||
|
// lock. A cooldown is awaited (while locked) by comparing the number of
|
||||||
|
// observations with the initiation count. Once they match, then the
|
||||||
|
// last observation on the now cool one has completed. All cool fields must
|
||||||
|
// be merged into the new hot before releasing writeMtx.
|
||||||
|
|
||||||
|
// Fields with atomic access first! See alignment constraint:
|
||||||
|
// http://golang.org/pkg/sync/atomic/#pkg-note-BUG
|
||||||
|
countAndHotIdx uint64
|
||||||
|
|
||||||
|
selfCollector
|
||||||
|
desc *Desc
|
||||||
|
writeMtx sync.Mutex // Only used in the Write method.
|
||||||
|
|
||||||
|
// Two counts, one is "hot" for lock-free observations, the other is
|
||||||
|
// "cold" for writing out a dto.Metric. It has to be an array of
|
||||||
|
// pointers to guarantee 64bit alignment of the histogramCounts, see
|
||||||
|
// http://golang.org/pkg/sync/atomic/#pkg-note-BUG.
|
||||||
|
counts [2]*summaryCounts
|
||||||
|
|
||||||
|
labelPairs []*dto.LabelPair
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *noObjectivesSummary) Desc() *Desc {
|
||||||
|
return s.desc
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *noObjectivesSummary) Observe(v float64) {
|
||||||
|
// We increment h.countAndHotIdx so that the counter in the lower
|
||||||
|
// 63 bits gets incremented. At the same time, we get the new value
|
||||||
|
// back, which we can use to find the currently-hot counts.
|
||||||
|
n := atomic.AddUint64(&s.countAndHotIdx, 1)
|
||||||
|
hotCounts := s.counts[n>>63]
|
||||||
|
|
||||||
|
for {
|
||||||
|
oldBits := atomic.LoadUint64(&hotCounts.sumBits)
|
||||||
|
newBits := math.Float64bits(math.Float64frombits(oldBits) + v)
|
||||||
|
if atomic.CompareAndSwapUint64(&hotCounts.sumBits, oldBits, newBits) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Increment count last as we take it as a signal that the observation
|
||||||
|
// is complete.
|
||||||
|
atomic.AddUint64(&hotCounts.count, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *noObjectivesSummary) Write(out *dto.Metric) error {
|
||||||
|
// For simplicity, we protect this whole method by a mutex. It is not in
|
||||||
|
// the hot path, i.e. Observe is called much more often than Write. The
|
||||||
|
// complication of making Write lock-free isn't worth it, if possible at
|
||||||
|
// all.
|
||||||
|
s.writeMtx.Lock()
|
||||||
|
defer s.writeMtx.Unlock()
|
||||||
|
|
||||||
|
// Adding 1<<63 switches the hot index (from 0 to 1 or from 1 to 0)
|
||||||
|
// without touching the count bits. See the struct comments for a full
|
||||||
|
// description of the algorithm.
|
||||||
|
n := atomic.AddUint64(&s.countAndHotIdx, 1<<63)
|
||||||
|
// count is contained unchanged in the lower 63 bits.
|
||||||
|
count := n & ((1 << 63) - 1)
|
||||||
|
// The most significant bit tells us which counts is hot. The complement
|
||||||
|
// is thus the cold one.
|
||||||
|
hotCounts := s.counts[n>>63]
|
||||||
|
coldCounts := s.counts[(^n)>>63]
|
||||||
|
|
||||||
|
// Await cooldown.
|
||||||
|
for count != atomic.LoadUint64(&coldCounts.count) {
|
||||||
|
runtime.Gosched() // Let observations get work done.
|
||||||
|
}
|
||||||
|
|
||||||
|
sum := &dto.Summary{
|
||||||
|
SampleCount: proto.Uint64(count),
|
||||||
|
SampleSum: proto.Float64(math.Float64frombits(atomic.LoadUint64(&coldCounts.sumBits))),
|
||||||
|
}
|
||||||
|
|
||||||
|
out.Summary = sum
|
||||||
|
out.Label = s.labelPairs
|
||||||
|
|
||||||
|
// Finally add all the cold counts to the new hot counts and reset the cold counts.
|
||||||
|
atomic.AddUint64(&hotCounts.count, count)
|
||||||
|
atomic.StoreUint64(&coldCounts.count, 0)
|
||||||
|
for {
|
||||||
|
oldBits := atomic.LoadUint64(&hotCounts.sumBits)
|
||||||
|
newBits := math.Float64bits(math.Float64frombits(oldBits) + sum.GetSampleSum())
|
||||||
|
if atomic.CompareAndSwapUint64(&hotCounts.sumBits, oldBits, newBits) {
|
||||||
|
atomic.StoreUint64(&coldCounts.sumBits, 0)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type quantSort []*dto.Quantile
|
type quantSort []*dto.Quantile
|
||||||
|
|
||||||
func (s quantSort) Len() int {
|
func (s quantSort) Len() int {
|
||||||
|
@ -390,13 +512,21 @@ func (s quantSort) Less(i, j int) bool {
|
||||||
// (e.g. HTTP request latencies, partitioned by status code and method). Create
|
// (e.g. HTTP request latencies, partitioned by status code and method). Create
|
||||||
// instances with NewSummaryVec.
|
// instances with NewSummaryVec.
|
||||||
type SummaryVec struct {
|
type SummaryVec struct {
|
||||||
*MetricVec
|
*metricVec
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSummaryVec creates a new SummaryVec based on the provided SummaryOpts and
|
// NewSummaryVec creates a new SummaryVec based on the provided SummaryOpts and
|
||||||
// partitioned by the given label names. At least one label name must be
|
// partitioned by the given label names.
|
||||||
// provided.
|
//
|
||||||
|
// Due to the way a Summary is represented in the Prometheus text format and how
|
||||||
|
// it is handled by the Prometheus server internally, “quantile” is an illegal
|
||||||
|
// label name. NewSummaryVec will panic if this label name is used.
|
||||||
func NewSummaryVec(opts SummaryOpts, labelNames []string) *SummaryVec {
|
func NewSummaryVec(opts SummaryOpts, labelNames []string) *SummaryVec {
|
||||||
|
for _, ln := range labelNames {
|
||||||
|
if ln == quantileLabel {
|
||||||
|
panic(errQuantileLabelNotAllowed)
|
||||||
|
}
|
||||||
|
}
|
||||||
desc := NewDesc(
|
desc := NewDesc(
|
||||||
BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
|
BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
|
||||||
opts.Help,
|
opts.Help,
|
||||||
|
@ -404,47 +534,116 @@ func NewSummaryVec(opts SummaryOpts, labelNames []string) *SummaryVec {
|
||||||
opts.ConstLabels,
|
opts.ConstLabels,
|
||||||
)
|
)
|
||||||
return &SummaryVec{
|
return &SummaryVec{
|
||||||
MetricVec: newMetricVec(desc, func(lvs ...string) Metric {
|
metricVec: newMetricVec(desc, func(lvs ...string) Metric {
|
||||||
return newSummary(desc, opts, lvs...)
|
return newSummary(desc, opts, lvs...)
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetMetricWithLabelValues replaces the method of the same name in
|
// GetMetricWithLabelValues returns the Summary for the given slice of label
|
||||||
// MetricVec. The difference is that this method returns a Summary and not a
|
// values (same order as the VariableLabels in Desc). If that combination of
|
||||||
// Metric so that no type conversion is required.
|
// label values is accessed for the first time, a new Summary is created.
|
||||||
func (m *SummaryVec) GetMetricWithLabelValues(lvs ...string) (Summary, error) {
|
//
|
||||||
metric, err := m.MetricVec.GetMetricWithLabelValues(lvs...)
|
// It is possible to call this method without using the returned Summary to only
|
||||||
|
// create the new Summary but leave it at its starting value, a Summary without
|
||||||
|
// any observations.
|
||||||
|
//
|
||||||
|
// Keeping the Summary for later use is possible (and should be considered if
|
||||||
|
// performance is critical), but keep in mind that Reset, DeleteLabelValues and
|
||||||
|
// Delete can be used to delete the Summary from the SummaryVec. In that case,
|
||||||
|
// the Summary will still exist, but it will not be exported anymore, even if a
|
||||||
|
// Summary with the same label values is created later. See also the CounterVec
|
||||||
|
// example.
|
||||||
|
//
|
||||||
|
// An error is returned if the number of label values is not the same as the
|
||||||
|
// number of VariableLabels in Desc (minus any curried labels).
|
||||||
|
//
|
||||||
|
// Note that for more than one label value, this method is prone to mistakes
|
||||||
|
// caused by an incorrect order of arguments. Consider GetMetricWith(Labels) as
|
||||||
|
// an alternative to avoid that type of mistake. For higher label numbers, the
|
||||||
|
// latter has a much more readable (albeit more verbose) syntax, but it comes
|
||||||
|
// with a performance overhead (for creating and processing the Labels map).
|
||||||
|
// See also the GaugeVec example.
|
||||||
|
func (v *SummaryVec) GetMetricWithLabelValues(lvs ...string) (Observer, error) {
|
||||||
|
metric, err := v.metricVec.getMetricWithLabelValues(lvs...)
|
||||||
if metric != nil {
|
if metric != nil {
|
||||||
return metric.(Summary), err
|
return metric.(Observer), err
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetMetricWith replaces the method of the same name in MetricVec. The
|
// GetMetricWith returns the Summary for the given Labels map (the label names
|
||||||
// difference is that this method returns a Summary and not a Metric so that no
|
// must match those of the VariableLabels in Desc). If that label map is
|
||||||
// type conversion is required.
|
// accessed for the first time, a new Summary is created. Implications of
|
||||||
func (m *SummaryVec) GetMetricWith(labels Labels) (Summary, error) {
|
// creating a Summary without using it and keeping the Summary for later use are
|
||||||
metric, err := m.MetricVec.GetMetricWith(labels)
|
// the same as for GetMetricWithLabelValues.
|
||||||
|
//
|
||||||
|
// An error is returned if the number and names of the Labels are inconsistent
|
||||||
|
// with those of the VariableLabels in Desc (minus any curried labels).
|
||||||
|
//
|
||||||
|
// This method is used for the same purpose as
|
||||||
|
// GetMetricWithLabelValues(...string). See there for pros and cons of the two
|
||||||
|
// methods.
|
||||||
|
func (v *SummaryVec) GetMetricWith(labels Labels) (Observer, error) {
|
||||||
|
metric, err := v.metricVec.getMetricWith(labels)
|
||||||
if metric != nil {
|
if metric != nil {
|
||||||
return metric.(Summary), err
|
return metric.(Observer), err
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithLabelValues works as GetMetricWithLabelValues, but panics where
|
// WithLabelValues works as GetMetricWithLabelValues, but panics where
|
||||||
// GetMetricWithLabelValues would have returned an error. By not returning an
|
// GetMetricWithLabelValues would have returned an error. Not returning an
|
||||||
// error, WithLabelValues allows shortcuts like
|
// error allows shortcuts like
|
||||||
// myVec.WithLabelValues("404", "GET").Observe(42.21)
|
// myVec.WithLabelValues("404", "GET").Observe(42.21)
|
||||||
func (m *SummaryVec) WithLabelValues(lvs ...string) Summary {
|
func (v *SummaryVec) WithLabelValues(lvs ...string) Observer {
|
||||||
return m.MetricVec.WithLabelValues(lvs...).(Summary)
|
s, err := v.GetMetricWithLabelValues(lvs...)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
// With works as GetMetricWith, but panics where GetMetricWithLabels would have
|
// With works as GetMetricWith, but panics where GetMetricWithLabels would have
|
||||||
// returned an error. By not returning an error, With allows shortcuts like
|
// returned an error. Not returning an error allows shortcuts like
|
||||||
// myVec.With(Labels{"code": "404", "method": "GET"}).Observe(42.21)
|
// myVec.With(prometheus.Labels{"code": "404", "method": "GET"}).Observe(42.21)
|
||||||
func (m *SummaryVec) With(labels Labels) Summary {
|
func (v *SummaryVec) With(labels Labels) Observer {
|
||||||
return m.MetricVec.With(labels).(Summary)
|
s, err := v.GetMetricWith(labels)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// CurryWith returns a vector curried with the provided labels, i.e. the
|
||||||
|
// returned vector has those labels pre-set for all labeled operations performed
|
||||||
|
// on it. The cardinality of the curried vector is reduced accordingly. The
|
||||||
|
// order of the remaining labels stays the same (just with the curried labels
|
||||||
|
// taken out of the sequence – which is relevant for the
|
||||||
|
// (GetMetric)WithLabelValues methods). It is possible to curry a curried
|
||||||
|
// vector, but only with labels not yet used for currying before.
|
||||||
|
//
|
||||||
|
// The metrics contained in the SummaryVec are shared between the curried and
|
||||||
|
// uncurried vectors. They are just accessed differently. Curried and uncurried
|
||||||
|
// vectors behave identically in terms of collection. Only one must be
|
||||||
|
// registered with a given registry (usually the uncurried version). The Reset
|
||||||
|
// method deletes all metrics, even if called on a curried vector.
|
||||||
|
func (v *SummaryVec) CurryWith(labels Labels) (ObserverVec, error) {
|
||||||
|
vec, err := v.curryWith(labels)
|
||||||
|
if vec != nil {
|
||||||
|
return &SummaryVec{vec}, err
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustCurryWith works as CurryWith but panics where CurryWith would have
|
||||||
|
// returned an error.
|
||||||
|
func (v *SummaryVec) MustCurryWith(labels Labels) ObserverVec {
|
||||||
|
vec, err := v.CurryWith(labels)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return vec
|
||||||
}
|
}
|
||||||
|
|
||||||
type constSummary struct {
|
type constSummary struct {
|
||||||
|
@ -497,7 +696,7 @@ func (s *constSummary) Write(out *dto.Metric) error {
|
||||||
// map[float64]float64{0.5: 0.23, 0.99: 0.56}
|
// map[float64]float64{0.5: 0.23, 0.99: 0.56}
|
||||||
//
|
//
|
||||||
// NewConstSummary returns an error if the length of labelValues is not
|
// NewConstSummary returns an error if the length of labelValues is not
|
||||||
// consistent with the variable labels in Desc.
|
// consistent with the variable labels in Desc or if Desc is invalid.
|
||||||
func NewConstSummary(
|
func NewConstSummary(
|
||||||
desc *Desc,
|
desc *Desc,
|
||||||
count uint64,
|
count uint64,
|
||||||
|
@ -505,8 +704,11 @@ func NewConstSummary(
|
||||||
quantiles map[float64]float64,
|
quantiles map[float64]float64,
|
||||||
labelValues ...string,
|
labelValues ...string,
|
||||||
) (Metric, error) {
|
) (Metric, error) {
|
||||||
if len(desc.variableLabels) != len(labelValues) {
|
if desc.err != nil {
|
||||||
return nil, errInconsistentCardinality
|
return nil, desc.err
|
||||||
|
}
|
||||||
|
if err := validateLabelValues(labelValues, len(desc.variableLabels)); err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
return &constSummary{
|
return &constSummary{
|
||||||
desc: desc,
|
desc: desc,
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
// Copyright 2016 The Prometheus Authors
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package prometheus
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// Timer is a helper type to time functions. Use NewTimer to create new
|
||||||
|
// instances.
|
||||||
|
type Timer struct {
|
||||||
|
begin time.Time
|
||||||
|
observer Observer
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTimer creates a new Timer. The provided Observer is used to observe a
|
||||||
|
// duration in seconds. Timer is usually used to time a function call in the
|
||||||
|
// following way:
|
||||||
|
// func TimeMe() {
|
||||||
|
// timer := NewTimer(myHistogram)
|
||||||
|
// defer timer.ObserveDuration()
|
||||||
|
// // Do actual work.
|
||||||
|
// }
|
||||||
|
func NewTimer(o Observer) *Timer {
|
||||||
|
return &Timer{
|
||||||
|
begin: time.Now(),
|
||||||
|
observer: o,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ObserveDuration records the duration passed since the Timer was created with
|
||||||
|
// NewTimer. It calls the Observe method of the Observer provided during
|
||||||
|
// construction with the duration in seconds as an argument. The observed
|
||||||
|
// duration is also returned. ObserveDuration is usually called with a defer
|
||||||
|
// statement.
|
||||||
|
//
|
||||||
|
// Note that this method is only guaranteed to never observe negative durations
|
||||||
|
// if used with Go1.9+.
|
||||||
|
func (t *Timer) ObserveDuration() time.Duration {
|
||||||
|
d := time.Since(t.begin)
|
||||||
|
if t.observer != nil {
|
||||||
|
t.observer.Observe(d.Seconds())
|
||||||
|
}
|
||||||
|
return d
|
||||||
|
}
|
|
@ -13,108 +13,12 @@
|
||||||
|
|
||||||
package prometheus
|
package prometheus
|
||||||
|
|
||||||
// Untyped is a Metric that represents a single numerical value that can
|
|
||||||
// arbitrarily go up and down.
|
|
||||||
//
|
|
||||||
// An Untyped metric works the same as a Gauge. The only difference is that to
|
|
||||||
// no type information is implied.
|
|
||||||
//
|
|
||||||
// To create Untyped instances, use NewUntyped.
|
|
||||||
type Untyped interface {
|
|
||||||
Metric
|
|
||||||
Collector
|
|
||||||
|
|
||||||
// Set sets the Untyped metric to an arbitrary value.
|
|
||||||
Set(float64)
|
|
||||||
// Inc increments the Untyped metric by 1.
|
|
||||||
Inc()
|
|
||||||
// Dec decrements the Untyped metric by 1.
|
|
||||||
Dec()
|
|
||||||
// Add adds the given value to the Untyped metric. (The value can be
|
|
||||||
// negative, resulting in a decrease.)
|
|
||||||
Add(float64)
|
|
||||||
// Sub subtracts the given value from the Untyped metric. (The value can
|
|
||||||
// be negative, resulting in an increase.)
|
|
||||||
Sub(float64)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UntypedOpts is an alias for Opts. See there for doc comments.
|
// UntypedOpts is an alias for Opts. See there for doc comments.
|
||||||
type UntypedOpts Opts
|
type UntypedOpts Opts
|
||||||
|
|
||||||
// NewUntyped creates a new Untyped metric from the provided UntypedOpts.
|
// UntypedFunc works like GaugeFunc but the collected metric is of type
|
||||||
func NewUntyped(opts UntypedOpts) Untyped {
|
// "Untyped". UntypedFunc is useful to mirror an external metric of unknown
|
||||||
return newValue(NewDesc(
|
// type.
|
||||||
BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
|
|
||||||
opts.Help,
|
|
||||||
nil,
|
|
||||||
opts.ConstLabels,
|
|
||||||
), UntypedValue, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UntypedVec is a Collector that bundles a set of Untyped metrics that all
|
|
||||||
// share the same Desc, but have different values for their variable
|
|
||||||
// labels. This is used if you want to count the same thing partitioned by
|
|
||||||
// various dimensions. Create instances with NewUntypedVec.
|
|
||||||
type UntypedVec struct {
|
|
||||||
*MetricVec
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewUntypedVec creates a new UntypedVec based on the provided UntypedOpts and
|
|
||||||
// partitioned by the given label names. At least one label name must be
|
|
||||||
// provided.
|
|
||||||
func NewUntypedVec(opts UntypedOpts, labelNames []string) *UntypedVec {
|
|
||||||
desc := NewDesc(
|
|
||||||
BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
|
|
||||||
opts.Help,
|
|
||||||
labelNames,
|
|
||||||
opts.ConstLabels,
|
|
||||||
)
|
|
||||||
return &UntypedVec{
|
|
||||||
MetricVec: newMetricVec(desc, func(lvs ...string) Metric {
|
|
||||||
return newValue(desc, UntypedValue, 0, lvs...)
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMetricWithLabelValues replaces the method of the same name in
|
|
||||||
// MetricVec. The difference is that this method returns an Untyped and not a
|
|
||||||
// Metric so that no type conversion is required.
|
|
||||||
func (m *UntypedVec) GetMetricWithLabelValues(lvs ...string) (Untyped, error) {
|
|
||||||
metric, err := m.MetricVec.GetMetricWithLabelValues(lvs...)
|
|
||||||
if metric != nil {
|
|
||||||
return metric.(Untyped), err
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMetricWith replaces the method of the same name in MetricVec. The
|
|
||||||
// difference is that this method returns an Untyped and not a Metric so that no
|
|
||||||
// type conversion is required.
|
|
||||||
func (m *UntypedVec) GetMetricWith(labels Labels) (Untyped, error) {
|
|
||||||
metric, err := m.MetricVec.GetMetricWith(labels)
|
|
||||||
if metric != nil {
|
|
||||||
return metric.(Untyped), err
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithLabelValues works as GetMetricWithLabelValues, but panics where
|
|
||||||
// GetMetricWithLabelValues would have returned an error. By not returning an
|
|
||||||
// error, WithLabelValues allows shortcuts like
|
|
||||||
// myVec.WithLabelValues("404", "GET").Add(42)
|
|
||||||
func (m *UntypedVec) WithLabelValues(lvs ...string) Untyped {
|
|
||||||
return m.MetricVec.WithLabelValues(lvs...).(Untyped)
|
|
||||||
}
|
|
||||||
|
|
||||||
// With works as GetMetricWith, but panics where GetMetricWithLabels would have
|
|
||||||
// returned an error. By not returning an error, With allows shortcuts like
|
|
||||||
// myVec.With(Labels{"code": "404", "method": "GET"}).Add(42)
|
|
||||||
func (m *UntypedVec) With(labels Labels) Untyped {
|
|
||||||
return m.MetricVec.With(labels).(Untyped)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UntypedFunc is an Untyped whose value is determined at collect time by
|
|
||||||
// calling a provided function.
|
|
||||||
//
|
//
|
||||||
// To create UntypedFunc instances, use NewUntypedFunc.
|
// To create UntypedFunc instances, use NewUntypedFunc.
|
||||||
type UntypedFunc interface {
|
type UntypedFunc interface {
|
||||||
|
|
|
@ -14,21 +14,22 @@
|
||||||
package prometheus
|
package prometheus
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
|
||||||
"sort"
|
"sort"
|
||||||
"sync/atomic"
|
"time"
|
||||||
|
"unicode/utf8"
|
||||||
dto "github.com/prometheus/client_model/go"
|
|
||||||
|
|
||||||
"github.com/golang/protobuf/proto"
|
"github.com/golang/protobuf/proto"
|
||||||
|
"github.com/golang/protobuf/ptypes"
|
||||||
|
|
||||||
|
dto "github.com/prometheus/client_model/go"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ValueType is an enumeration of metric types that represent a simple value.
|
// ValueType is an enumeration of metric types that represent a simple value.
|
||||||
type ValueType int
|
type ValueType int
|
||||||
|
|
||||||
// Possible values for the ValueType enum.
|
// Possible values for the ValueType enum. Use UntypedValue to mark a metric
|
||||||
|
// with an unknown type.
|
||||||
const (
|
const (
|
||||||
_ ValueType = iota
|
_ ValueType = iota
|
||||||
CounterValue
|
CounterValue
|
||||||
|
@ -36,77 +37,6 @@ const (
|
||||||
UntypedValue
|
UntypedValue
|
||||||
)
|
)
|
||||||
|
|
||||||
var errInconsistentCardinality = errors.New("inconsistent label cardinality")
|
|
||||||
|
|
||||||
// value is a generic metric for simple values. It implements Metric, Collector,
|
|
||||||
// Counter, Gauge, and Untyped. Its effective type is determined by
|
|
||||||
// ValueType. This is a low-level building block used by the library to back the
|
|
||||||
// implementations of Counter, Gauge, and Untyped.
|
|
||||||
type value struct {
|
|
||||||
// valBits containst the bits of the represented float64 value. It has
|
|
||||||
// to go first in the struct to guarantee alignment for atomic
|
|
||||||
// operations. http://golang.org/pkg/sync/atomic/#pkg-note-BUG
|
|
||||||
valBits uint64
|
|
||||||
|
|
||||||
selfCollector
|
|
||||||
|
|
||||||
desc *Desc
|
|
||||||
valType ValueType
|
|
||||||
labelPairs []*dto.LabelPair
|
|
||||||
}
|
|
||||||
|
|
||||||
// newValue returns a newly allocated value with the given Desc, ValueType,
|
|
||||||
// sample value and label values. It panics if the number of label
|
|
||||||
// values is different from the number of variable labels in Desc.
|
|
||||||
func newValue(desc *Desc, valueType ValueType, val float64, labelValues ...string) *value {
|
|
||||||
if len(labelValues) != len(desc.variableLabels) {
|
|
||||||
panic(errInconsistentCardinality)
|
|
||||||
}
|
|
||||||
result := &value{
|
|
||||||
desc: desc,
|
|
||||||
valType: valueType,
|
|
||||||
valBits: math.Float64bits(val),
|
|
||||||
labelPairs: makeLabelPairs(desc, labelValues),
|
|
||||||
}
|
|
||||||
result.init(result)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *value) Desc() *Desc {
|
|
||||||
return v.desc
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *value) Set(val float64) {
|
|
||||||
atomic.StoreUint64(&v.valBits, math.Float64bits(val))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *value) Inc() {
|
|
||||||
v.Add(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *value) Dec() {
|
|
||||||
v.Add(-1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *value) Add(val float64) {
|
|
||||||
for {
|
|
||||||
oldBits := atomic.LoadUint64(&v.valBits)
|
|
||||||
newBits := math.Float64bits(math.Float64frombits(oldBits) + val)
|
|
||||||
if atomic.CompareAndSwapUint64(&v.valBits, oldBits, newBits) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *value) Sub(val float64) {
|
|
||||||
v.Add(val * -1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *value) Write(out *dto.Metric) error {
|
|
||||||
val := math.Float64frombits(atomic.LoadUint64(&v.valBits))
|
|
||||||
return populateMetric(v.valType, val, v.labelPairs, out)
|
|
||||||
}
|
|
||||||
|
|
||||||
// valueFunc is a generic metric for simple values retrieved on collect time
|
// valueFunc is a generic metric for simple values retrieved on collect time
|
||||||
// from a function. It implements Metric and Collector. Its effective type is
|
// from a function. It implements Metric and Collector. Its effective type is
|
||||||
// determined by ValueType. This is a low-level building block used by the
|
// determined by ValueType. This is a low-level building block used by the
|
||||||
|
@ -143,7 +73,7 @@ func (v *valueFunc) Desc() *Desc {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *valueFunc) Write(out *dto.Metric) error {
|
func (v *valueFunc) Write(out *dto.Metric) error {
|
||||||
return populateMetric(v.valType, v.function(), v.labelPairs, out)
|
return populateMetric(v.valType, v.function(), v.labelPairs, nil, out)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewConstMetric returns a metric with one fixed value that cannot be
|
// NewConstMetric returns a metric with one fixed value that cannot be
|
||||||
|
@ -151,10 +81,14 @@ func (v *valueFunc) Write(out *dto.Metric) error {
|
||||||
// operations. However, when implementing custom Collectors, it is useful as a
|
// operations. However, when implementing custom Collectors, it is useful as a
|
||||||
// throw-away metric that is generated on the fly to send it to Prometheus in
|
// throw-away metric that is generated on the fly to send it to Prometheus in
|
||||||
// the Collect method. NewConstMetric returns an error if the length of
|
// the Collect method. NewConstMetric returns an error if the length of
|
||||||
// labelValues is not consistent with the variable labels in Desc.
|
// labelValues is not consistent with the variable labels in Desc or if Desc is
|
||||||
|
// invalid.
|
||||||
func NewConstMetric(desc *Desc, valueType ValueType, value float64, labelValues ...string) (Metric, error) {
|
func NewConstMetric(desc *Desc, valueType ValueType, value float64, labelValues ...string) (Metric, error) {
|
||||||
if len(desc.variableLabels) != len(labelValues) {
|
if desc.err != nil {
|
||||||
return nil, errInconsistentCardinality
|
return nil, desc.err
|
||||||
|
}
|
||||||
|
if err := validateLabelValues(labelValues, len(desc.variableLabels)); err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
return &constMetric{
|
return &constMetric{
|
||||||
desc: desc,
|
desc: desc,
|
||||||
|
@ -186,19 +120,20 @@ func (m *constMetric) Desc() *Desc {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *constMetric) Write(out *dto.Metric) error {
|
func (m *constMetric) Write(out *dto.Metric) error {
|
||||||
return populateMetric(m.valType, m.val, m.labelPairs, out)
|
return populateMetric(m.valType, m.val, m.labelPairs, nil, out)
|
||||||
}
|
}
|
||||||
|
|
||||||
func populateMetric(
|
func populateMetric(
|
||||||
t ValueType,
|
t ValueType,
|
||||||
v float64,
|
v float64,
|
||||||
labelPairs []*dto.LabelPair,
|
labelPairs []*dto.LabelPair,
|
||||||
|
e *dto.Exemplar,
|
||||||
m *dto.Metric,
|
m *dto.Metric,
|
||||||
) error {
|
) error {
|
||||||
m.Label = labelPairs
|
m.Label = labelPairs
|
||||||
switch t {
|
switch t {
|
||||||
case CounterValue:
|
case CounterValue:
|
||||||
m.Counter = &dto.Counter{Value: proto.Float64(v)}
|
m.Counter = &dto.Counter{Value: proto.Float64(v), Exemplar: e}
|
||||||
case GaugeValue:
|
case GaugeValue:
|
||||||
m.Gauge = &dto.Gauge{Value: proto.Float64(v)}
|
m.Gauge = &dto.Gauge{Value: proto.Float64(v)}
|
||||||
case UntypedValue:
|
case UntypedValue:
|
||||||
|
@ -226,9 +161,44 @@ func makeLabelPairs(desc *Desc, labelValues []string) []*dto.LabelPair {
|
||||||
Value: proto.String(labelValues[i]),
|
Value: proto.String(labelValues[i]),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
for _, lp := range desc.constLabelPairs {
|
labelPairs = append(labelPairs, desc.constLabelPairs...)
|
||||||
labelPairs = append(labelPairs, lp)
|
sort.Sort(labelPairSorter(labelPairs))
|
||||||
}
|
|
||||||
sort.Sort(LabelPairSorter(labelPairs))
|
|
||||||
return labelPairs
|
return labelPairs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExemplarMaxRunes is the max total number of runes allowed in exemplar labels.
|
||||||
|
const ExemplarMaxRunes = 64
|
||||||
|
|
||||||
|
// newExemplar creates a new dto.Exemplar from the provided values. An error is
|
||||||
|
// returned if any of the label names or values are invalid or if the total
|
||||||
|
// number of runes in the label names and values exceeds ExemplarMaxRunes.
|
||||||
|
func newExemplar(value float64, ts time.Time, l Labels) (*dto.Exemplar, error) {
|
||||||
|
e := &dto.Exemplar{}
|
||||||
|
e.Value = proto.Float64(value)
|
||||||
|
tsProto, err := ptypes.TimestampProto(ts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
e.Timestamp = tsProto
|
||||||
|
labelPairs := make([]*dto.LabelPair, 0, len(l))
|
||||||
|
var runes int
|
||||||
|
for name, value := range l {
|
||||||
|
if !checkLabelName(name) {
|
||||||
|
return nil, fmt.Errorf("exemplar label name %q is invalid", name)
|
||||||
|
}
|
||||||
|
runes += utf8.RuneCountInString(name)
|
||||||
|
if !utf8.ValidString(value) {
|
||||||
|
return nil, fmt.Errorf("exemplar label value %q is not valid UTF-8", value)
|
||||||
|
}
|
||||||
|
runes += utf8.RuneCountInString(value)
|
||||||
|
labelPairs = append(labelPairs, &dto.LabelPair{
|
||||||
|
Name: proto.String(name),
|
||||||
|
Value: proto.String(value),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if runes > ExemplarMaxRunes {
|
||||||
|
return nil, fmt.Errorf("exemplar labels have %d runes, exceeding the limit of %d", runes, ExemplarMaxRunes)
|
||||||
|
}
|
||||||
|
e.Label = labelPairs
|
||||||
|
return e, nil
|
||||||
|
}
|
||||||
|
|
|
@ -20,33 +20,192 @@ import (
|
||||||
"github.com/prometheus/common/model"
|
"github.com/prometheus/common/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MetricVec is a Collector to bundle metrics of the same name that
|
// metricVec is a Collector to bundle metrics of the same name that differ in
|
||||||
// differ in their label values. MetricVec is usually not used directly but as a
|
// their label values. metricVec is not used directly (and therefore
|
||||||
// building block for implementations of vectors of a given metric
|
// unexported). It is used as a building block for implementations of vectors of
|
||||||
// type. GaugeVec, CounterVec, SummaryVec, and UntypedVec are examples already
|
// a given metric type, like GaugeVec, CounterVec, SummaryVec, and HistogramVec.
|
||||||
// provided in this package.
|
// It also handles label currying.
|
||||||
type MetricVec struct {
|
type metricVec struct {
|
||||||
mtx sync.RWMutex // Protects the children.
|
*metricMap
|
||||||
children map[uint64][]metricWithLabelValues
|
|
||||||
desc *Desc
|
|
||||||
|
|
||||||
newMetric func(labelValues ...string) Metric
|
curry []curriedLabelValue
|
||||||
hashAdd func(h uint64, s string) uint64 // replace hash function for testing collision handling
|
|
||||||
|
// hashAdd and hashAddByte can be replaced for testing collision handling.
|
||||||
|
hashAdd func(h uint64, s string) uint64
|
||||||
hashAddByte func(h uint64, b byte) uint64
|
hashAddByte func(h uint64, b byte) uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
// newMetricVec returns an initialized MetricVec. The concrete value is
|
// newMetricVec returns an initialized metricVec.
|
||||||
// returned for embedding into another struct.
|
func newMetricVec(desc *Desc, newMetric func(lvs ...string) Metric) *metricVec {
|
||||||
func newMetricVec(desc *Desc, newMetric func(lvs ...string) Metric) *MetricVec {
|
return &metricVec{
|
||||||
return &MetricVec{
|
metricMap: &metricMap{
|
||||||
children: map[uint64][]metricWithLabelValues{},
|
metrics: map[uint64][]metricWithLabelValues{},
|
||||||
desc: desc,
|
desc: desc,
|
||||||
newMetric: newMetric,
|
newMetric: newMetric,
|
||||||
|
},
|
||||||
hashAdd: hashAdd,
|
hashAdd: hashAdd,
|
||||||
hashAddByte: hashAddByte,
|
hashAddByte: hashAddByte,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeleteLabelValues removes the metric where the variable labels are the same
|
||||||
|
// as those passed in as labels (same order as the VariableLabels in Desc). It
|
||||||
|
// returns true if a metric was deleted.
|
||||||
|
//
|
||||||
|
// It is not an error if the number of label values is not the same as the
|
||||||
|
// number of VariableLabels in Desc. However, such inconsistent label count can
|
||||||
|
// never match an actual metric, so the method will always return false in that
|
||||||
|
// case.
|
||||||
|
//
|
||||||
|
// Note that for more than one label value, this method is prone to mistakes
|
||||||
|
// caused by an incorrect order of arguments. Consider Delete(Labels) as an
|
||||||
|
// alternative to avoid that type of mistake. For higher label numbers, the
|
||||||
|
// latter has a much more readable (albeit more verbose) syntax, but it comes
|
||||||
|
// with a performance overhead (for creating and processing the Labels map).
|
||||||
|
// See also the CounterVec example.
|
||||||
|
func (m *metricVec) DeleteLabelValues(lvs ...string) bool {
|
||||||
|
h, err := m.hashLabelValues(lvs)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return m.metricMap.deleteByHashWithLabelValues(h, lvs, m.curry)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete deletes the metric where the variable labels are the same as those
|
||||||
|
// passed in as labels. It returns true if a metric was deleted.
|
||||||
|
//
|
||||||
|
// It is not an error if the number and names of the Labels are inconsistent
|
||||||
|
// with those of the VariableLabels in Desc. However, such inconsistent Labels
|
||||||
|
// can never match an actual metric, so the method will always return false in
|
||||||
|
// that case.
|
||||||
|
//
|
||||||
|
// This method is used for the same purpose as DeleteLabelValues(...string). See
|
||||||
|
// there for pros and cons of the two methods.
|
||||||
|
func (m *metricVec) Delete(labels Labels) bool {
|
||||||
|
h, err := m.hashLabels(labels)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return m.metricMap.deleteByHashWithLabels(h, labels, m.curry)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Without explicit forwarding of Describe, Collect, Reset, those methods won't
|
||||||
|
// show up in GoDoc.
|
||||||
|
|
||||||
|
// Describe implements Collector.
|
||||||
|
func (m *metricVec) Describe(ch chan<- *Desc) { m.metricMap.Describe(ch) }
|
||||||
|
|
||||||
|
// Collect implements Collector.
|
||||||
|
func (m *metricVec) Collect(ch chan<- Metric) { m.metricMap.Collect(ch) }
|
||||||
|
|
||||||
|
// Reset deletes all metrics in this vector.
|
||||||
|
func (m *metricVec) Reset() { m.metricMap.Reset() }
|
||||||
|
|
||||||
|
func (m *metricVec) curryWith(labels Labels) (*metricVec, error) {
|
||||||
|
var (
|
||||||
|
newCurry []curriedLabelValue
|
||||||
|
oldCurry = m.curry
|
||||||
|
iCurry int
|
||||||
|
)
|
||||||
|
for i, label := range m.desc.variableLabels {
|
||||||
|
val, ok := labels[label]
|
||||||
|
if iCurry < len(oldCurry) && oldCurry[iCurry].index == i {
|
||||||
|
if ok {
|
||||||
|
return nil, fmt.Errorf("label name %q is already curried", label)
|
||||||
|
}
|
||||||
|
newCurry = append(newCurry, oldCurry[iCurry])
|
||||||
|
iCurry++
|
||||||
|
} else {
|
||||||
|
if !ok {
|
||||||
|
continue // Label stays uncurried.
|
||||||
|
}
|
||||||
|
newCurry = append(newCurry, curriedLabelValue{i, val})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if l := len(oldCurry) + len(labels) - len(newCurry); l > 0 {
|
||||||
|
return nil, fmt.Errorf("%d unknown label(s) found during currying", l)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &metricVec{
|
||||||
|
metricMap: m.metricMap,
|
||||||
|
curry: newCurry,
|
||||||
|
hashAdd: m.hashAdd,
|
||||||
|
hashAddByte: m.hashAddByte,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *metricVec) getMetricWithLabelValues(lvs ...string) (Metric, error) {
|
||||||
|
h, err := m.hashLabelValues(lvs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return m.metricMap.getOrCreateMetricWithLabelValues(h, lvs, m.curry), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *metricVec) getMetricWith(labels Labels) (Metric, error) {
|
||||||
|
h, err := m.hashLabels(labels)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return m.metricMap.getOrCreateMetricWithLabels(h, labels, m.curry), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *metricVec) hashLabelValues(vals []string) (uint64, error) {
|
||||||
|
if err := validateLabelValues(vals, len(m.desc.variableLabels)-len(m.curry)); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
h = hashNew()
|
||||||
|
curry = m.curry
|
||||||
|
iVals, iCurry int
|
||||||
|
)
|
||||||
|
for i := 0; i < len(m.desc.variableLabels); i++ {
|
||||||
|
if iCurry < len(curry) && curry[iCurry].index == i {
|
||||||
|
h = m.hashAdd(h, curry[iCurry].value)
|
||||||
|
iCurry++
|
||||||
|
} else {
|
||||||
|
h = m.hashAdd(h, vals[iVals])
|
||||||
|
iVals++
|
||||||
|
}
|
||||||
|
h = m.hashAddByte(h, model.SeparatorByte)
|
||||||
|
}
|
||||||
|
return h, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *metricVec) hashLabels(labels Labels) (uint64, error) {
|
||||||
|
if err := validateValuesInLabels(labels, len(m.desc.variableLabels)-len(m.curry)); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
h = hashNew()
|
||||||
|
curry = m.curry
|
||||||
|
iCurry int
|
||||||
|
)
|
||||||
|
for i, label := range m.desc.variableLabels {
|
||||||
|
val, ok := labels[label]
|
||||||
|
if iCurry < len(curry) && curry[iCurry].index == i {
|
||||||
|
if ok {
|
||||||
|
return 0, fmt.Errorf("label name %q is already curried", label)
|
||||||
|
}
|
||||||
|
h = m.hashAdd(h, curry[iCurry].value)
|
||||||
|
iCurry++
|
||||||
|
} else {
|
||||||
|
if !ok {
|
||||||
|
return 0, fmt.Errorf("label name %q missing in label map", label)
|
||||||
|
}
|
||||||
|
h = m.hashAdd(h, val)
|
||||||
|
}
|
||||||
|
h = m.hashAddByte(h, model.SeparatorByte)
|
||||||
|
}
|
||||||
|
return h, nil
|
||||||
|
}
|
||||||
|
|
||||||
// metricWithLabelValues provides the metric and its label values for
|
// metricWithLabelValues provides the metric and its label values for
|
||||||
// disambiguation on hash collision.
|
// disambiguation on hash collision.
|
||||||
type metricWithLabelValues struct {
|
type metricWithLabelValues struct {
|
||||||
|
@ -54,166 +213,72 @@ type metricWithLabelValues struct {
|
||||||
metric Metric
|
metric Metric
|
||||||
}
|
}
|
||||||
|
|
||||||
// Describe implements Collector. The length of the returned slice
|
// curriedLabelValue sets the curried value for a label at the given index.
|
||||||
// is always one.
|
type curriedLabelValue struct {
|
||||||
func (m *MetricVec) Describe(ch chan<- *Desc) {
|
index int
|
||||||
|
value string
|
||||||
|
}
|
||||||
|
|
||||||
|
// metricMap is a helper for metricVec and shared between differently curried
|
||||||
|
// metricVecs.
|
||||||
|
type metricMap struct {
|
||||||
|
mtx sync.RWMutex // Protects metrics.
|
||||||
|
metrics map[uint64][]metricWithLabelValues
|
||||||
|
desc *Desc
|
||||||
|
newMetric func(labelValues ...string) Metric
|
||||||
|
}
|
||||||
|
|
||||||
|
// Describe implements Collector. It will send exactly one Desc to the provided
|
||||||
|
// channel.
|
||||||
|
func (m *metricMap) Describe(ch chan<- *Desc) {
|
||||||
ch <- m.desc
|
ch <- m.desc
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect implements Collector.
|
// Collect implements Collector.
|
||||||
func (m *MetricVec) Collect(ch chan<- Metric) {
|
func (m *metricMap) Collect(ch chan<- Metric) {
|
||||||
m.mtx.RLock()
|
m.mtx.RLock()
|
||||||
defer m.mtx.RUnlock()
|
defer m.mtx.RUnlock()
|
||||||
|
|
||||||
for _, metrics := range m.children {
|
for _, metrics := range m.metrics {
|
||||||
for _, metric := range metrics {
|
for _, metric := range metrics {
|
||||||
ch <- metric.metric
|
ch <- metric.metric
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetMetricWithLabelValues returns the Metric for the given slice of label
|
// Reset deletes all metrics in this vector.
|
||||||
// values (same order as the VariableLabels in Desc). If that combination of
|
func (m *metricMap) Reset() {
|
||||||
// label values is accessed for the first time, a new Metric is created.
|
|
||||||
//
|
|
||||||
// It is possible to call this method without using the returned Metric to only
|
|
||||||
// create the new Metric but leave it at its start value (e.g. a Summary or
|
|
||||||
// Histogram without any observations). See also the SummaryVec example.
|
|
||||||
//
|
|
||||||
// Keeping the Metric for later use is possible (and should be considered if
|
|
||||||
// performance is critical), but keep in mind that Reset, DeleteLabelValues and
|
|
||||||
// Delete can be used to delete the Metric from the MetricVec. In that case, the
|
|
||||||
// Metric will still exist, but it will not be exported anymore, even if a
|
|
||||||
// Metric with the same label values is created later. See also the CounterVec
|
|
||||||
// example.
|
|
||||||
//
|
|
||||||
// An error is returned if the number of label values is not the same as the
|
|
||||||
// number of VariableLabels in Desc.
|
|
||||||
//
|
|
||||||
// Note that for more than one label value, this method is prone to mistakes
|
|
||||||
// caused by an incorrect order of arguments. Consider GetMetricWith(Labels) as
|
|
||||||
// an alternative to avoid that type of mistake. For higher label numbers, the
|
|
||||||
// latter has a much more readable (albeit more verbose) syntax, but it comes
|
|
||||||
// with a performance overhead (for creating and processing the Labels map).
|
|
||||||
// See also the GaugeVec example.
|
|
||||||
func (m *MetricVec) GetMetricWithLabelValues(lvs ...string) (Metric, error) {
|
|
||||||
h, err := m.hashLabelValues(lvs)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return m.getOrCreateMetricWithLabelValues(h, lvs), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMetricWith returns the Metric for the given Labels map (the label names
|
|
||||||
// must match those of the VariableLabels in Desc). If that label map is
|
|
||||||
// accessed for the first time, a new Metric is created. Implications of
|
|
||||||
// creating a Metric without using it and keeping the Metric for later use are
|
|
||||||
// the same as for GetMetricWithLabelValues.
|
|
||||||
//
|
|
||||||
// An error is returned if the number and names of the Labels are inconsistent
|
|
||||||
// with those of the VariableLabels in Desc.
|
|
||||||
//
|
|
||||||
// This method is used for the same purpose as
|
|
||||||
// GetMetricWithLabelValues(...string). See there for pros and cons of the two
|
|
||||||
// methods.
|
|
||||||
func (m *MetricVec) GetMetricWith(labels Labels) (Metric, error) {
|
|
||||||
h, err := m.hashLabels(labels)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return m.getOrCreateMetricWithLabels(h, labels), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithLabelValues works as GetMetricWithLabelValues, but panics if an error
|
|
||||||
// occurs. The method allows neat syntax like:
|
|
||||||
// httpReqs.WithLabelValues("404", "POST").Inc()
|
|
||||||
func (m *MetricVec) WithLabelValues(lvs ...string) Metric {
|
|
||||||
metric, err := m.GetMetricWithLabelValues(lvs...)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return metric
|
|
||||||
}
|
|
||||||
|
|
||||||
// With works as GetMetricWith, but panics if an error occurs. The method allows
|
|
||||||
// neat syntax like:
|
|
||||||
// httpReqs.With(Labels{"status":"404", "method":"POST"}).Inc()
|
|
||||||
func (m *MetricVec) With(labels Labels) Metric {
|
|
||||||
metric, err := m.GetMetricWith(labels)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return metric
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteLabelValues removes the metric where the variable labels are the same
|
|
||||||
// as those passed in as labels (same order as the VariableLabels in Desc). It
|
|
||||||
// returns true if a metric was deleted.
|
|
||||||
//
|
|
||||||
// It is not an error if the number of label values is not the same as the
|
|
||||||
// number of VariableLabels in Desc. However, such inconsistent label count can
|
|
||||||
// never match an actual Metric, so the method will always return false in that
|
|
||||||
// case.
|
|
||||||
//
|
|
||||||
// Note that for more than one label value, this method is prone to mistakes
|
|
||||||
// caused by an incorrect order of arguments. Consider Delete(Labels) as an
|
|
||||||
// alternative to avoid that type of mistake. For higher label numbers, the
|
|
||||||
// latter has a much more readable (albeit more verbose) syntax, but it comes
|
|
||||||
// with a performance overhead (for creating and processing the Labels map).
|
|
||||||
// See also the CounterVec example.
|
|
||||||
func (m *MetricVec) DeleteLabelValues(lvs ...string) bool {
|
|
||||||
m.mtx.Lock()
|
m.mtx.Lock()
|
||||||
defer m.mtx.Unlock()
|
defer m.mtx.Unlock()
|
||||||
|
|
||||||
h, err := m.hashLabelValues(lvs)
|
for h := range m.metrics {
|
||||||
if err != nil {
|
delete(m.metrics, h)
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
return m.deleteByHashWithLabelValues(h, lvs)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete deletes the metric where the variable labels are the same as those
|
|
||||||
// passed in as labels. It returns true if a metric was deleted.
|
|
||||||
//
|
|
||||||
// It is not an error if the number and names of the Labels are inconsistent
|
|
||||||
// with those of the VariableLabels in the Desc of the MetricVec. However, such
|
|
||||||
// inconsistent Labels can never match an actual Metric, so the method will
|
|
||||||
// always return false in that case.
|
|
||||||
//
|
|
||||||
// This method is used for the same purpose as DeleteLabelValues(...string). See
|
|
||||||
// there for pros and cons of the two methods.
|
|
||||||
func (m *MetricVec) Delete(labels Labels) bool {
|
|
||||||
m.mtx.Lock()
|
|
||||||
defer m.mtx.Unlock()
|
|
||||||
|
|
||||||
h, err := m.hashLabels(labels)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return m.deleteByHashWithLabels(h, labels)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// deleteByHashWithLabelValues removes the metric from the hash bucket h. If
|
// deleteByHashWithLabelValues removes the metric from the hash bucket h. If
|
||||||
// there are multiple matches in the bucket, use lvs to select a metric and
|
// there are multiple matches in the bucket, use lvs to select a metric and
|
||||||
// remove only that metric.
|
// remove only that metric.
|
||||||
func (m *MetricVec) deleteByHashWithLabelValues(h uint64, lvs []string) bool {
|
func (m *metricMap) deleteByHashWithLabelValues(
|
||||||
metrics, ok := m.children[h]
|
h uint64, lvs []string, curry []curriedLabelValue,
|
||||||
|
) bool {
|
||||||
|
m.mtx.Lock()
|
||||||
|
defer m.mtx.Unlock()
|
||||||
|
|
||||||
|
metrics, ok := m.metrics[h]
|
||||||
if !ok {
|
if !ok {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
i := m.findMetricWithLabelValues(metrics, lvs)
|
i := findMetricWithLabelValues(metrics, lvs, curry)
|
||||||
if i >= len(metrics) {
|
if i >= len(metrics) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(metrics) > 1 {
|
if len(metrics) > 1 {
|
||||||
m.children[h] = append(metrics[:i], metrics[i+1:]...)
|
m.metrics[h] = append(metrics[:i], metrics[i+1:]...)
|
||||||
} else {
|
} else {
|
||||||
delete(m.children, h)
|
delete(m.metrics, h)
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -221,69 +286,38 @@ func (m *MetricVec) deleteByHashWithLabelValues(h uint64, lvs []string) bool {
|
||||||
// deleteByHashWithLabels removes the metric from the hash bucket h. If there
|
// deleteByHashWithLabels removes the metric from the hash bucket h. If there
|
||||||
// are multiple matches in the bucket, use lvs to select a metric and remove
|
// are multiple matches in the bucket, use lvs to select a metric and remove
|
||||||
// only that metric.
|
// only that metric.
|
||||||
func (m *MetricVec) deleteByHashWithLabels(h uint64, labels Labels) bool {
|
func (m *metricMap) deleteByHashWithLabels(
|
||||||
metrics, ok := m.children[h]
|
h uint64, labels Labels, curry []curriedLabelValue,
|
||||||
|
) bool {
|
||||||
|
m.mtx.Lock()
|
||||||
|
defer m.mtx.Unlock()
|
||||||
|
|
||||||
|
metrics, ok := m.metrics[h]
|
||||||
if !ok {
|
if !ok {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
i := m.findMetricWithLabels(metrics, labels)
|
i := findMetricWithLabels(m.desc, metrics, labels, curry)
|
||||||
if i >= len(metrics) {
|
if i >= len(metrics) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(metrics) > 1 {
|
if len(metrics) > 1 {
|
||||||
m.children[h] = append(metrics[:i], metrics[i+1:]...)
|
m.metrics[h] = append(metrics[:i], metrics[i+1:]...)
|
||||||
} else {
|
} else {
|
||||||
delete(m.children, h)
|
delete(m.metrics, h)
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset deletes all metrics in this vector.
|
|
||||||
func (m *MetricVec) Reset() {
|
|
||||||
m.mtx.Lock()
|
|
||||||
defer m.mtx.Unlock()
|
|
||||||
|
|
||||||
for h := range m.children {
|
|
||||||
delete(m.children, h)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MetricVec) hashLabelValues(vals []string) (uint64, error) {
|
|
||||||
if len(vals) != len(m.desc.variableLabels) {
|
|
||||||
return 0, errInconsistentCardinality
|
|
||||||
}
|
|
||||||
h := hashNew()
|
|
||||||
for _, val := range vals {
|
|
||||||
h = m.hashAdd(h, val)
|
|
||||||
h = m.hashAddByte(h, model.SeparatorByte)
|
|
||||||
}
|
|
||||||
return h, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MetricVec) hashLabels(labels Labels) (uint64, error) {
|
|
||||||
if len(labels) != len(m.desc.variableLabels) {
|
|
||||||
return 0, errInconsistentCardinality
|
|
||||||
}
|
|
||||||
h := hashNew()
|
|
||||||
for _, label := range m.desc.variableLabels {
|
|
||||||
val, ok := labels[label]
|
|
||||||
if !ok {
|
|
||||||
return 0, fmt.Errorf("label name %q missing in label map", label)
|
|
||||||
}
|
|
||||||
h = m.hashAdd(h, val)
|
|
||||||
h = m.hashAddByte(h, model.SeparatorByte)
|
|
||||||
}
|
|
||||||
return h, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// getOrCreateMetricWithLabelValues retrieves the metric by hash and label value
|
// getOrCreateMetricWithLabelValues retrieves the metric by hash and label value
|
||||||
// or creates it and returns the new one.
|
// or creates it and returns the new one.
|
||||||
//
|
//
|
||||||
// This function holds the mutex.
|
// This function holds the mutex.
|
||||||
func (m *MetricVec) getOrCreateMetricWithLabelValues(hash uint64, lvs []string) Metric {
|
func (m *metricMap) getOrCreateMetricWithLabelValues(
|
||||||
|
hash uint64, lvs []string, curry []curriedLabelValue,
|
||||||
|
) Metric {
|
||||||
m.mtx.RLock()
|
m.mtx.RLock()
|
||||||
metric, ok := m.getMetricWithLabelValues(hash, lvs)
|
metric, ok := m.getMetricWithHashAndLabelValues(hash, lvs, curry)
|
||||||
m.mtx.RUnlock()
|
m.mtx.RUnlock()
|
||||||
if ok {
|
if ok {
|
||||||
return metric
|
return metric
|
||||||
|
@ -291,13 +325,11 @@ func (m *MetricVec) getOrCreateMetricWithLabelValues(hash uint64, lvs []string)
|
||||||
|
|
||||||
m.mtx.Lock()
|
m.mtx.Lock()
|
||||||
defer m.mtx.Unlock()
|
defer m.mtx.Unlock()
|
||||||
metric, ok = m.getMetricWithLabelValues(hash, lvs)
|
metric, ok = m.getMetricWithHashAndLabelValues(hash, lvs, curry)
|
||||||
if !ok {
|
if !ok {
|
||||||
// Copy to avoid allocation in case wo don't go down this code path.
|
inlinedLVs := inlineLabelValues(lvs, curry)
|
||||||
copiedLVs := make([]string, len(lvs))
|
metric = m.newMetric(inlinedLVs...)
|
||||||
copy(copiedLVs, lvs)
|
m.metrics[hash] = append(m.metrics[hash], metricWithLabelValues{values: inlinedLVs, metric: metric})
|
||||||
metric = m.newMetric(copiedLVs...)
|
|
||||||
m.children[hash] = append(m.children[hash], metricWithLabelValues{values: copiedLVs, metric: metric})
|
|
||||||
}
|
}
|
||||||
return metric
|
return metric
|
||||||
}
|
}
|
||||||
|
@ -306,9 +338,11 @@ func (m *MetricVec) getOrCreateMetricWithLabelValues(hash uint64, lvs []string)
|
||||||
// or creates it and returns the new one.
|
// or creates it and returns the new one.
|
||||||
//
|
//
|
||||||
// This function holds the mutex.
|
// This function holds the mutex.
|
||||||
func (m *MetricVec) getOrCreateMetricWithLabels(hash uint64, labels Labels) Metric {
|
func (m *metricMap) getOrCreateMetricWithLabels(
|
||||||
|
hash uint64, labels Labels, curry []curriedLabelValue,
|
||||||
|
) Metric {
|
||||||
m.mtx.RLock()
|
m.mtx.RLock()
|
||||||
metric, ok := m.getMetricWithLabels(hash, labels)
|
metric, ok := m.getMetricWithHashAndLabels(hash, labels, curry)
|
||||||
m.mtx.RUnlock()
|
m.mtx.RUnlock()
|
||||||
if ok {
|
if ok {
|
||||||
return metric
|
return metric
|
||||||
|
@ -316,33 +350,37 @@ func (m *MetricVec) getOrCreateMetricWithLabels(hash uint64, labels Labels) Metr
|
||||||
|
|
||||||
m.mtx.Lock()
|
m.mtx.Lock()
|
||||||
defer m.mtx.Unlock()
|
defer m.mtx.Unlock()
|
||||||
metric, ok = m.getMetricWithLabels(hash, labels)
|
metric, ok = m.getMetricWithHashAndLabels(hash, labels, curry)
|
||||||
if !ok {
|
if !ok {
|
||||||
lvs := m.extractLabelValues(labels)
|
lvs := extractLabelValues(m.desc, labels, curry)
|
||||||
metric = m.newMetric(lvs...)
|
metric = m.newMetric(lvs...)
|
||||||
m.children[hash] = append(m.children[hash], metricWithLabelValues{values: lvs, metric: metric})
|
m.metrics[hash] = append(m.metrics[hash], metricWithLabelValues{values: lvs, metric: metric})
|
||||||
}
|
}
|
||||||
return metric
|
return metric
|
||||||
}
|
}
|
||||||
|
|
||||||
// getMetricWithLabelValues gets a metric while handling possible collisions in
|
// getMetricWithHashAndLabelValues gets a metric while handling possible
|
||||||
// the hash space. Must be called while holding read mutex.
|
// collisions in the hash space. Must be called while holding the read mutex.
|
||||||
func (m *MetricVec) getMetricWithLabelValues(h uint64, lvs []string) (Metric, bool) {
|
func (m *metricMap) getMetricWithHashAndLabelValues(
|
||||||
metrics, ok := m.children[h]
|
h uint64, lvs []string, curry []curriedLabelValue,
|
||||||
|
) (Metric, bool) {
|
||||||
|
metrics, ok := m.metrics[h]
|
||||||
if ok {
|
if ok {
|
||||||
if i := m.findMetricWithLabelValues(metrics, lvs); i < len(metrics) {
|
if i := findMetricWithLabelValues(metrics, lvs, curry); i < len(metrics) {
|
||||||
return metrics[i].metric, true
|
return metrics[i].metric, true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
// getMetricWithLabels gets a metric while handling possible collisions in
|
// getMetricWithHashAndLabels gets a metric while handling possible collisions in
|
||||||
// the hash space. Must be called while holding read mutex.
|
// the hash space. Must be called while holding read mutex.
|
||||||
func (m *MetricVec) getMetricWithLabels(h uint64, labels Labels) (Metric, bool) {
|
func (m *metricMap) getMetricWithHashAndLabels(
|
||||||
metrics, ok := m.children[h]
|
h uint64, labels Labels, curry []curriedLabelValue,
|
||||||
|
) (Metric, bool) {
|
||||||
|
metrics, ok := m.metrics[h]
|
||||||
if ok {
|
if ok {
|
||||||
if i := m.findMetricWithLabels(metrics, labels); i < len(metrics) {
|
if i := findMetricWithLabels(m.desc, metrics, labels, curry); i < len(metrics) {
|
||||||
return metrics[i].metric, true
|
return metrics[i].metric, true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -351,9 +389,11 @@ func (m *MetricVec) getMetricWithLabels(h uint64, labels Labels) (Metric, bool)
|
||||||
|
|
||||||
// findMetricWithLabelValues returns the index of the matching metric or
|
// findMetricWithLabelValues returns the index of the matching metric or
|
||||||
// len(metrics) if not found.
|
// len(metrics) if not found.
|
||||||
func (m *MetricVec) findMetricWithLabelValues(metrics []metricWithLabelValues, lvs []string) int {
|
func findMetricWithLabelValues(
|
||||||
|
metrics []metricWithLabelValues, lvs []string, curry []curriedLabelValue,
|
||||||
|
) int {
|
||||||
for i, metric := range metrics {
|
for i, metric := range metrics {
|
||||||
if m.matchLabelValues(metric.values, lvs) {
|
if matchLabelValues(metric.values, lvs, curry) {
|
||||||
return i
|
return i
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -362,32 +402,51 @@ func (m *MetricVec) findMetricWithLabelValues(metrics []metricWithLabelValues, l
|
||||||
|
|
||||||
// findMetricWithLabels returns the index of the matching metric or len(metrics)
|
// findMetricWithLabels returns the index of the matching metric or len(metrics)
|
||||||
// if not found.
|
// if not found.
|
||||||
func (m *MetricVec) findMetricWithLabels(metrics []metricWithLabelValues, labels Labels) int {
|
func findMetricWithLabels(
|
||||||
|
desc *Desc, metrics []metricWithLabelValues, labels Labels, curry []curriedLabelValue,
|
||||||
|
) int {
|
||||||
for i, metric := range metrics {
|
for i, metric := range metrics {
|
||||||
if m.matchLabels(metric.values, labels) {
|
if matchLabels(desc, metric.values, labels, curry) {
|
||||||
return i
|
return i
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return len(metrics)
|
return len(metrics)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MetricVec) matchLabelValues(values []string, lvs []string) bool {
|
func matchLabelValues(values []string, lvs []string, curry []curriedLabelValue) bool {
|
||||||
if len(values) != len(lvs) {
|
if len(values) != len(lvs)+len(curry) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
var iLVs, iCurry int
|
||||||
for i, v := range values {
|
for i, v := range values {
|
||||||
if v != lvs[i] {
|
if iCurry < len(curry) && curry[iCurry].index == i {
|
||||||
|
if v != curry[iCurry].value {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
iCurry++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if v != lvs[iLVs] {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
iLVs++
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MetricVec) matchLabels(values []string, labels Labels) bool {
|
func matchLabels(desc *Desc, values []string, labels Labels, curry []curriedLabelValue) bool {
|
||||||
if len(labels) != len(values) {
|
if len(values) != len(labels)+len(curry) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
for i, k := range m.desc.variableLabels {
|
iCurry := 0
|
||||||
|
for i, k := range desc.variableLabels {
|
||||||
|
if iCurry < len(curry) && curry[iCurry].index == i {
|
||||||
|
if values[i] != curry[iCurry].value {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
iCurry++
|
||||||
|
continue
|
||||||
|
}
|
||||||
if values[i] != labels[k] {
|
if values[i] != labels[k] {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -395,10 +454,31 @@ func (m *MetricVec) matchLabels(values []string, labels Labels) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MetricVec) extractLabelValues(labels Labels) []string {
|
func extractLabelValues(desc *Desc, labels Labels, curry []curriedLabelValue) []string {
|
||||||
labelValues := make([]string, len(labels))
|
labelValues := make([]string, len(labels)+len(curry))
|
||||||
for i, k := range m.desc.variableLabels {
|
iCurry := 0
|
||||||
|
for i, k := range desc.variableLabels {
|
||||||
|
if iCurry < len(curry) && curry[iCurry].index == i {
|
||||||
|
labelValues[i] = curry[iCurry].value
|
||||||
|
iCurry++
|
||||||
|
continue
|
||||||
|
}
|
||||||
labelValues[i] = labels[k]
|
labelValues[i] = labels[k]
|
||||||
}
|
}
|
||||||
return labelValues
|
return labelValues
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func inlineLabelValues(lvs []string, curry []curriedLabelValue) []string {
|
||||||
|
labelValues := make([]string, len(lvs)+len(curry))
|
||||||
|
var iCurry, iLVs int
|
||||||
|
for i := range labelValues {
|
||||||
|
if iCurry < len(curry) && curry[iCurry].index == i {
|
||||||
|
labelValues[i] = curry[iCurry].value
|
||||||
|
iCurry++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
labelValues[i] = lvs[iLVs]
|
||||||
|
iLVs++
|
||||||
|
}
|
||||||
|
return labelValues
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,200 @@
|
||||||
|
// Copyright 2018 The Prometheus Authors
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package prometheus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/golang/protobuf/proto"
|
||||||
|
|
||||||
|
dto "github.com/prometheus/client_model/go"
|
||||||
|
)
|
||||||
|
|
||||||
|
// WrapRegistererWith returns a Registerer wrapping the provided
|
||||||
|
// Registerer. Collectors registered with the returned Registerer will be
|
||||||
|
// registered with the wrapped Registerer in a modified way. The modified
|
||||||
|
// Collector adds the provided Labels to all Metrics it collects (as
|
||||||
|
// ConstLabels). The Metrics collected by the unmodified Collector must not
|
||||||
|
// duplicate any of those labels.
|
||||||
|
//
|
||||||
|
// WrapRegistererWith provides a way to add fixed labels to a subset of
|
||||||
|
// Collectors. It should not be used to add fixed labels to all metrics exposed.
|
||||||
|
//
|
||||||
|
// Conflicts between Collectors registered through the original Registerer with
|
||||||
|
// Collectors registered through the wrapping Registerer will still be
|
||||||
|
// detected. Any AlreadyRegisteredError returned by the Register method of
|
||||||
|
// either Registerer will contain the ExistingCollector in the form it was
|
||||||
|
// provided to the respective registry.
|
||||||
|
//
|
||||||
|
// The Collector example demonstrates a use of WrapRegistererWith.
|
||||||
|
func WrapRegistererWith(labels Labels, reg Registerer) Registerer {
|
||||||
|
return &wrappingRegisterer{
|
||||||
|
wrappedRegisterer: reg,
|
||||||
|
labels: labels,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WrapRegistererWithPrefix returns a Registerer wrapping the provided
|
||||||
|
// Registerer. Collectors registered with the returned Registerer will be
|
||||||
|
// registered with the wrapped Registerer in a modified way. The modified
|
||||||
|
// Collector adds the provided prefix to the name of all Metrics it collects.
|
||||||
|
//
|
||||||
|
// WrapRegistererWithPrefix is useful to have one place to prefix all metrics of
|
||||||
|
// a sub-system. To make this work, register metrics of the sub-system with the
|
||||||
|
// wrapping Registerer returned by WrapRegistererWithPrefix. It is rarely useful
|
||||||
|
// to use the same prefix for all metrics exposed. In particular, do not prefix
|
||||||
|
// metric names that are standardized across applications, as that would break
|
||||||
|
// horizontal monitoring, for example the metrics provided by the Go collector
|
||||||
|
// (see NewGoCollector) and the process collector (see NewProcessCollector). (In
|
||||||
|
// fact, those metrics are already prefixed with “go_” or “process_”,
|
||||||
|
// respectively.)
|
||||||
|
//
|
||||||
|
// Conflicts between Collectors registered through the original Registerer with
|
||||||
|
// Collectors registered through the wrapping Registerer will still be
|
||||||
|
// detected. Any AlreadyRegisteredError returned by the Register method of
|
||||||
|
// either Registerer will contain the ExistingCollector in the form it was
|
||||||
|
// provided to the respective registry.
|
||||||
|
func WrapRegistererWithPrefix(prefix string, reg Registerer) Registerer {
|
||||||
|
return &wrappingRegisterer{
|
||||||
|
wrappedRegisterer: reg,
|
||||||
|
prefix: prefix,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type wrappingRegisterer struct {
|
||||||
|
wrappedRegisterer Registerer
|
||||||
|
prefix string
|
||||||
|
labels Labels
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *wrappingRegisterer) Register(c Collector) error {
|
||||||
|
return r.wrappedRegisterer.Register(&wrappingCollector{
|
||||||
|
wrappedCollector: c,
|
||||||
|
prefix: r.prefix,
|
||||||
|
labels: r.labels,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *wrappingRegisterer) MustRegister(cs ...Collector) {
|
||||||
|
for _, c := range cs {
|
||||||
|
if err := r.Register(c); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *wrappingRegisterer) Unregister(c Collector) bool {
|
||||||
|
return r.wrappedRegisterer.Unregister(&wrappingCollector{
|
||||||
|
wrappedCollector: c,
|
||||||
|
prefix: r.prefix,
|
||||||
|
labels: r.labels,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type wrappingCollector struct {
|
||||||
|
wrappedCollector Collector
|
||||||
|
prefix string
|
||||||
|
labels Labels
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *wrappingCollector) Collect(ch chan<- Metric) {
|
||||||
|
wrappedCh := make(chan Metric)
|
||||||
|
go func() {
|
||||||
|
c.wrappedCollector.Collect(wrappedCh)
|
||||||
|
close(wrappedCh)
|
||||||
|
}()
|
||||||
|
for m := range wrappedCh {
|
||||||
|
ch <- &wrappingMetric{
|
||||||
|
wrappedMetric: m,
|
||||||
|
prefix: c.prefix,
|
||||||
|
labels: c.labels,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *wrappingCollector) Describe(ch chan<- *Desc) {
|
||||||
|
wrappedCh := make(chan *Desc)
|
||||||
|
go func() {
|
||||||
|
c.wrappedCollector.Describe(wrappedCh)
|
||||||
|
close(wrappedCh)
|
||||||
|
}()
|
||||||
|
for desc := range wrappedCh {
|
||||||
|
ch <- wrapDesc(desc, c.prefix, c.labels)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *wrappingCollector) unwrapRecursively() Collector {
|
||||||
|
switch wc := c.wrappedCollector.(type) {
|
||||||
|
case *wrappingCollector:
|
||||||
|
return wc.unwrapRecursively()
|
||||||
|
default:
|
||||||
|
return wc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type wrappingMetric struct {
|
||||||
|
wrappedMetric Metric
|
||||||
|
prefix string
|
||||||
|
labels Labels
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *wrappingMetric) Desc() *Desc {
|
||||||
|
return wrapDesc(m.wrappedMetric.Desc(), m.prefix, m.labels)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *wrappingMetric) Write(out *dto.Metric) error {
|
||||||
|
if err := m.wrappedMetric.Write(out); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(m.labels) == 0 {
|
||||||
|
// No wrapping labels.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for ln, lv := range m.labels {
|
||||||
|
out.Label = append(out.Label, &dto.LabelPair{
|
||||||
|
Name: proto.String(ln),
|
||||||
|
Value: proto.String(lv),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
sort.Sort(labelPairSorter(out.Label))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func wrapDesc(desc *Desc, prefix string, labels Labels) *Desc {
|
||||||
|
constLabels := Labels{}
|
||||||
|
for _, lp := range desc.constLabelPairs {
|
||||||
|
constLabels[*lp.Name] = *lp.Value
|
||||||
|
}
|
||||||
|
for ln, lv := range labels {
|
||||||
|
if _, alreadyUsed := constLabels[ln]; alreadyUsed {
|
||||||
|
return &Desc{
|
||||||
|
fqName: desc.fqName,
|
||||||
|
help: desc.help,
|
||||||
|
variableLabels: desc.variableLabels,
|
||||||
|
constLabelPairs: desc.constLabelPairs,
|
||||||
|
err: fmt.Errorf("attempted wrapping with already existing label name %q", ln),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
constLabels[ln] = lv
|
||||||
|
}
|
||||||
|
// NewDesc will do remaining validations.
|
||||||
|
newDesc := NewDesc(prefix+desc.fqName, desc.help, desc.variableLabels, constLabels)
|
||||||
|
// Propagate errors if there was any. This will override any errer
|
||||||
|
// created by NewDesc above, i.e. earlier errors get precedence.
|
||||||
|
if desc.err != nil {
|
||||||
|
newDesc.err = desc.err
|
||||||
|
}
|
||||||
|
return newDesc
|
||||||
|
}
|
|
@ -1,26 +1,34 @@
|
||||||
# Background
|
# Deprecation note
|
||||||
Under most circumstances, manually downloading this repository should never
|
|
||||||
be required.
|
|
||||||
|
|
||||||
# Prerequisites
|
This repository used to contain the [protocol
|
||||||
# Base
|
buffer](https://developers.google.com/protocol-buffers) code that defined both
|
||||||
* [Google Protocol Buffers](https://developers.google.com/protocol-buffers)
|
the data model and the exposition format of Prometheus metrics.
|
||||||
|
|
||||||
## Java
|
Starting with v2.0.0, the [Prometheus
|
||||||
* [Apache Maven](http://maven.apache.org)
|
server](https://github.com/prometheus/prometheus) does not ingest the
|
||||||
* [Prometheus Maven Repository](https://github.com/prometheus/io.prometheus-maven-repository) checked out into ../io.prometheus-maven-repository
|
protobuf-based exposition format anymore. Currently, all but one of the
|
||||||
|
[official instrumentation
|
||||||
|
libraries](https://prometheus.io/docs/instrumenting/clientlibs/) do not expose
|
||||||
|
the protobuf-based exposition format. The [Go instrumentation
|
||||||
|
library](https://github.com/prometheus/client_golang), however, has been built
|
||||||
|
around the protobuf-based data model. As a byproduct thereof, it is still able
|
||||||
|
to expose the protobuf-based exposition format. The Go instrumentation library
|
||||||
|
is the only remaining repository within the [Prometheus GitHub
|
||||||
|
org](https://github.com/prometheus) directly using the prometheus/client_model
|
||||||
|
repository.
|
||||||
|
|
||||||
## Go
|
Therefore, formerly existing support for languages other than Go (namely C++,
|
||||||
* [Go](http://golang.org)
|
Java, Python, Ruby) has been removed from this repository. If you are a 3rd
|
||||||
* [goprotobuf](https://code.google.com/p/goprotobuf)
|
party user of those languages, you can go back to [commit
|
||||||
|
14fe0d1](https://github.com/prometheus/client_model/commit/14fe0d1b01d4d5fc031dd4bec1823bd3ebbe8016)
|
||||||
|
to keep using the old code, or you can consume
|
||||||
|
[`metrics.proto`](https://github.com/prometheus/client_model/blob/master/metrics.proto)
|
||||||
|
directly with your own protobuf tooling. Note, however, that changes of
|
||||||
|
`metrics.proto` after [commit
|
||||||
|
14fe0d1](https://github.com/prometheus/client_model/commit/14fe0d1b01d4d5fc031dd4bec1823bd3ebbe8016)
|
||||||
|
are solely informed by requirements of the Go instrumentation library and will
|
||||||
|
not take into account any requirements of other languages or stability concerns
|
||||||
|
for the protobuf-based exposition format.
|
||||||
|
|
||||||
## Ruby
|
Check out the [OpenMetrics project](https://openmetrics.io/) for the future of
|
||||||
* [Ruby](https://www.ruby-lang.org)
|
the data model and exposition format used by Prometheus and others.
|
||||||
* [bundler](https://rubygems.org/gems/bundler)
|
|
||||||
|
|
||||||
# Building
|
|
||||||
$ make
|
|
||||||
|
|
||||||
# Getting Started
|
|
||||||
* The Go source code is periodically indexed: [Go Protocol Buffer Model](http://godoc.org/github.com/prometheus/client_model/go).
|
|
||||||
* All of the core developers are accessible via the [Prometheus Developers Mailinglist](https://groups.google.com/forum/?fromgroups#!forum/prometheus-developers).
|
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
module github.com/prometheus/client_model
|
||||||
|
|
||||||
|
go 1.9
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/golang/protobuf v1.2.0
|
||||||
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f // indirect
|
||||||
|
)
|
|
@ -1,34 +1,26 @@
|
||||||
// Code generated by protoc-gen-go.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// source: metrics.proto
|
// source: metrics.proto
|
||||||
// DO NOT EDIT!
|
|
||||||
|
|
||||||
/*
|
|
||||||
Package io_prometheus_client is a generated protocol buffer package.
|
|
||||||
|
|
||||||
It is generated from these files:
|
|
||||||
metrics.proto
|
|
||||||
|
|
||||||
It has these top-level messages:
|
|
||||||
LabelPair
|
|
||||||
Gauge
|
|
||||||
Counter
|
|
||||||
Quantile
|
|
||||||
Summary
|
|
||||||
Untyped
|
|
||||||
Histogram
|
|
||||||
Bucket
|
|
||||||
Metric
|
|
||||||
MetricFamily
|
|
||||||
*/
|
|
||||||
package io_prometheus_client
|
package io_prometheus_client
|
||||||
|
|
||||||
import proto "github.com/golang/protobuf/proto"
|
import (
|
||||||
import math "math"
|
fmt "fmt"
|
||||||
|
proto "github.com/golang/protobuf/proto"
|
||||||
|
timestamp "github.com/golang/protobuf/ptypes/timestamp"
|
||||||
|
math "math"
|
||||||
|
)
|
||||||
|
|
||||||
// Reference imports to suppress errors if they are not otherwise used.
|
// Reference imports to suppress errors if they are not otherwise used.
|
||||||
var _ = proto.Marshal
|
var _ = proto.Marshal
|
||||||
|
var _ = fmt.Errorf
|
||||||
var _ = math.Inf
|
var _ = math.Inf
|
||||||
|
|
||||||
|
// This is a compile-time assertion to ensure that this generated file
|
||||||
|
// is compatible with the proto package it is being compiled against.
|
||||||
|
// A compilation error at this line likely means your copy of the
|
||||||
|
// proto package needs to be updated.
|
||||||
|
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
|
||||||
|
|
||||||
type MetricType int32
|
type MetricType int32
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -46,6 +38,7 @@ var MetricType_name = map[int32]string{
|
||||||
3: "UNTYPED",
|
3: "UNTYPED",
|
||||||
4: "HISTOGRAM",
|
4: "HISTOGRAM",
|
||||||
}
|
}
|
||||||
|
|
||||||
var MetricType_value = map[string]int32{
|
var MetricType_value = map[string]int32{
|
||||||
"COUNTER": 0,
|
"COUNTER": 0,
|
||||||
"GAUGE": 1,
|
"GAUGE": 1,
|
||||||
|
@ -59,9 +52,11 @@ func (x MetricType) Enum() *MetricType {
|
||||||
*p = x
|
*p = x
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x MetricType) String() string {
|
func (x MetricType) String() string {
|
||||||
return proto.EnumName(MetricType_name, int32(x))
|
return proto.EnumName(MetricType_name, int32(x))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *MetricType) UnmarshalJSON(data []byte) error {
|
func (x *MetricType) UnmarshalJSON(data []byte) error {
|
||||||
value, err := proto.UnmarshalJSONEnum(MetricType_value, data, "MetricType")
|
value, err := proto.UnmarshalJSONEnum(MetricType_value, data, "MetricType")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -71,15 +66,42 @@ func (x *MetricType) UnmarshalJSON(data []byte) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (MetricType) EnumDescriptor() ([]byte, []int) {
|
||||||
|
return fileDescriptor_6039342a2ba47b72, []int{0}
|
||||||
|
}
|
||||||
|
|
||||||
type LabelPair struct {
|
type LabelPair struct {
|
||||||
Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
|
Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
|
||||||
Value *string `protobuf:"bytes,2,opt,name=value" json:"value,omitempty"`
|
Value *string `protobuf:"bytes,2,opt,name=value" json:"value,omitempty"`
|
||||||
XXX_unrecognized []byte `json:"-"`
|
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||||
|
XXX_unrecognized []byte `json:"-"`
|
||||||
|
XXX_sizecache int32 `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *LabelPair) Reset() { *m = LabelPair{} }
|
func (m *LabelPair) Reset() { *m = LabelPair{} }
|
||||||
func (m *LabelPair) String() string { return proto.CompactTextString(m) }
|
func (m *LabelPair) String() string { return proto.CompactTextString(m) }
|
||||||
func (*LabelPair) ProtoMessage() {}
|
func (*LabelPair) ProtoMessage() {}
|
||||||
|
func (*LabelPair) Descriptor() ([]byte, []int) {
|
||||||
|
return fileDescriptor_6039342a2ba47b72, []int{0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *LabelPair) XXX_Unmarshal(b []byte) error {
|
||||||
|
return xxx_messageInfo_LabelPair.Unmarshal(m, b)
|
||||||
|
}
|
||||||
|
func (m *LabelPair) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||||
|
return xxx_messageInfo_LabelPair.Marshal(b, m, deterministic)
|
||||||
|
}
|
||||||
|
func (m *LabelPair) XXX_Merge(src proto.Message) {
|
||||||
|
xxx_messageInfo_LabelPair.Merge(m, src)
|
||||||
|
}
|
||||||
|
func (m *LabelPair) XXX_Size() int {
|
||||||
|
return xxx_messageInfo_LabelPair.Size(m)
|
||||||
|
}
|
||||||
|
func (m *LabelPair) XXX_DiscardUnknown() {
|
||||||
|
xxx_messageInfo_LabelPair.DiscardUnknown(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
var xxx_messageInfo_LabelPair proto.InternalMessageInfo
|
||||||
|
|
||||||
func (m *LabelPair) GetName() string {
|
func (m *LabelPair) GetName() string {
|
||||||
if m != nil && m.Name != nil {
|
if m != nil && m.Name != nil {
|
||||||
|
@ -96,13 +118,36 @@ func (m *LabelPair) GetValue() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Gauge struct {
|
type Gauge struct {
|
||||||
Value *float64 `protobuf:"fixed64,1,opt,name=value" json:"value,omitempty"`
|
Value *float64 `protobuf:"fixed64,1,opt,name=value" json:"value,omitempty"`
|
||||||
XXX_unrecognized []byte `json:"-"`
|
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||||
|
XXX_unrecognized []byte `json:"-"`
|
||||||
|
XXX_sizecache int32 `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Gauge) Reset() { *m = Gauge{} }
|
func (m *Gauge) Reset() { *m = Gauge{} }
|
||||||
func (m *Gauge) String() string { return proto.CompactTextString(m) }
|
func (m *Gauge) String() string { return proto.CompactTextString(m) }
|
||||||
func (*Gauge) ProtoMessage() {}
|
func (*Gauge) ProtoMessage() {}
|
||||||
|
func (*Gauge) Descriptor() ([]byte, []int) {
|
||||||
|
return fileDescriptor_6039342a2ba47b72, []int{1}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Gauge) XXX_Unmarshal(b []byte) error {
|
||||||
|
return xxx_messageInfo_Gauge.Unmarshal(m, b)
|
||||||
|
}
|
||||||
|
func (m *Gauge) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||||
|
return xxx_messageInfo_Gauge.Marshal(b, m, deterministic)
|
||||||
|
}
|
||||||
|
func (m *Gauge) XXX_Merge(src proto.Message) {
|
||||||
|
xxx_messageInfo_Gauge.Merge(m, src)
|
||||||
|
}
|
||||||
|
func (m *Gauge) XXX_Size() int {
|
||||||
|
return xxx_messageInfo_Gauge.Size(m)
|
||||||
|
}
|
||||||
|
func (m *Gauge) XXX_DiscardUnknown() {
|
||||||
|
xxx_messageInfo_Gauge.DiscardUnknown(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
var xxx_messageInfo_Gauge proto.InternalMessageInfo
|
||||||
|
|
||||||
func (m *Gauge) GetValue() float64 {
|
func (m *Gauge) GetValue() float64 {
|
||||||
if m != nil && m.Value != nil {
|
if m != nil && m.Value != nil {
|
||||||
|
@ -112,13 +157,37 @@ func (m *Gauge) GetValue() float64 {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Counter struct {
|
type Counter struct {
|
||||||
Value *float64 `protobuf:"fixed64,1,opt,name=value" json:"value,omitempty"`
|
Value *float64 `protobuf:"fixed64,1,opt,name=value" json:"value,omitempty"`
|
||||||
XXX_unrecognized []byte `json:"-"`
|
Exemplar *Exemplar `protobuf:"bytes,2,opt,name=exemplar" json:"exemplar,omitempty"`
|
||||||
|
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||||
|
XXX_unrecognized []byte `json:"-"`
|
||||||
|
XXX_sizecache int32 `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Counter) Reset() { *m = Counter{} }
|
func (m *Counter) Reset() { *m = Counter{} }
|
||||||
func (m *Counter) String() string { return proto.CompactTextString(m) }
|
func (m *Counter) String() string { return proto.CompactTextString(m) }
|
||||||
func (*Counter) ProtoMessage() {}
|
func (*Counter) ProtoMessage() {}
|
||||||
|
func (*Counter) Descriptor() ([]byte, []int) {
|
||||||
|
return fileDescriptor_6039342a2ba47b72, []int{2}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Counter) XXX_Unmarshal(b []byte) error {
|
||||||
|
return xxx_messageInfo_Counter.Unmarshal(m, b)
|
||||||
|
}
|
||||||
|
func (m *Counter) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||||
|
return xxx_messageInfo_Counter.Marshal(b, m, deterministic)
|
||||||
|
}
|
||||||
|
func (m *Counter) XXX_Merge(src proto.Message) {
|
||||||
|
xxx_messageInfo_Counter.Merge(m, src)
|
||||||
|
}
|
||||||
|
func (m *Counter) XXX_Size() int {
|
||||||
|
return xxx_messageInfo_Counter.Size(m)
|
||||||
|
}
|
||||||
|
func (m *Counter) XXX_DiscardUnknown() {
|
||||||
|
xxx_messageInfo_Counter.DiscardUnknown(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
var xxx_messageInfo_Counter proto.InternalMessageInfo
|
||||||
|
|
||||||
func (m *Counter) GetValue() float64 {
|
func (m *Counter) GetValue() float64 {
|
||||||
if m != nil && m.Value != nil {
|
if m != nil && m.Value != nil {
|
||||||
|
@ -127,15 +196,45 @@ func (m *Counter) GetValue() float64 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Counter) GetExemplar() *Exemplar {
|
||||||
|
if m != nil {
|
||||||
|
return m.Exemplar
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type Quantile struct {
|
type Quantile struct {
|
||||||
Quantile *float64 `protobuf:"fixed64,1,opt,name=quantile" json:"quantile,omitempty"`
|
Quantile *float64 `protobuf:"fixed64,1,opt,name=quantile" json:"quantile,omitempty"`
|
||||||
Value *float64 `protobuf:"fixed64,2,opt,name=value" json:"value,omitempty"`
|
Value *float64 `protobuf:"fixed64,2,opt,name=value" json:"value,omitempty"`
|
||||||
XXX_unrecognized []byte `json:"-"`
|
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||||
|
XXX_unrecognized []byte `json:"-"`
|
||||||
|
XXX_sizecache int32 `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Quantile) Reset() { *m = Quantile{} }
|
func (m *Quantile) Reset() { *m = Quantile{} }
|
||||||
func (m *Quantile) String() string { return proto.CompactTextString(m) }
|
func (m *Quantile) String() string { return proto.CompactTextString(m) }
|
||||||
func (*Quantile) ProtoMessage() {}
|
func (*Quantile) ProtoMessage() {}
|
||||||
|
func (*Quantile) Descriptor() ([]byte, []int) {
|
||||||
|
return fileDescriptor_6039342a2ba47b72, []int{3}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Quantile) XXX_Unmarshal(b []byte) error {
|
||||||
|
return xxx_messageInfo_Quantile.Unmarshal(m, b)
|
||||||
|
}
|
||||||
|
func (m *Quantile) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||||
|
return xxx_messageInfo_Quantile.Marshal(b, m, deterministic)
|
||||||
|
}
|
||||||
|
func (m *Quantile) XXX_Merge(src proto.Message) {
|
||||||
|
xxx_messageInfo_Quantile.Merge(m, src)
|
||||||
|
}
|
||||||
|
func (m *Quantile) XXX_Size() int {
|
||||||
|
return xxx_messageInfo_Quantile.Size(m)
|
||||||
|
}
|
||||||
|
func (m *Quantile) XXX_DiscardUnknown() {
|
||||||
|
xxx_messageInfo_Quantile.DiscardUnknown(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
var xxx_messageInfo_Quantile proto.InternalMessageInfo
|
||||||
|
|
||||||
func (m *Quantile) GetQuantile() float64 {
|
func (m *Quantile) GetQuantile() float64 {
|
||||||
if m != nil && m.Quantile != nil {
|
if m != nil && m.Quantile != nil {
|
||||||
|
@ -152,15 +251,38 @@ func (m *Quantile) GetValue() float64 {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Summary struct {
|
type Summary struct {
|
||||||
SampleCount *uint64 `protobuf:"varint,1,opt,name=sample_count" json:"sample_count,omitempty"`
|
SampleCount *uint64 `protobuf:"varint,1,opt,name=sample_count,json=sampleCount" json:"sample_count,omitempty"`
|
||||||
SampleSum *float64 `protobuf:"fixed64,2,opt,name=sample_sum" json:"sample_sum,omitempty"`
|
SampleSum *float64 `protobuf:"fixed64,2,opt,name=sample_sum,json=sampleSum" json:"sample_sum,omitempty"`
|
||||||
Quantile []*Quantile `protobuf:"bytes,3,rep,name=quantile" json:"quantile,omitempty"`
|
Quantile []*Quantile `protobuf:"bytes,3,rep,name=quantile" json:"quantile,omitempty"`
|
||||||
XXX_unrecognized []byte `json:"-"`
|
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||||
|
XXX_unrecognized []byte `json:"-"`
|
||||||
|
XXX_sizecache int32 `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Summary) Reset() { *m = Summary{} }
|
func (m *Summary) Reset() { *m = Summary{} }
|
||||||
func (m *Summary) String() string { return proto.CompactTextString(m) }
|
func (m *Summary) String() string { return proto.CompactTextString(m) }
|
||||||
func (*Summary) ProtoMessage() {}
|
func (*Summary) ProtoMessage() {}
|
||||||
|
func (*Summary) Descriptor() ([]byte, []int) {
|
||||||
|
return fileDescriptor_6039342a2ba47b72, []int{4}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Summary) XXX_Unmarshal(b []byte) error {
|
||||||
|
return xxx_messageInfo_Summary.Unmarshal(m, b)
|
||||||
|
}
|
||||||
|
func (m *Summary) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||||
|
return xxx_messageInfo_Summary.Marshal(b, m, deterministic)
|
||||||
|
}
|
||||||
|
func (m *Summary) XXX_Merge(src proto.Message) {
|
||||||
|
xxx_messageInfo_Summary.Merge(m, src)
|
||||||
|
}
|
||||||
|
func (m *Summary) XXX_Size() int {
|
||||||
|
return xxx_messageInfo_Summary.Size(m)
|
||||||
|
}
|
||||||
|
func (m *Summary) XXX_DiscardUnknown() {
|
||||||
|
xxx_messageInfo_Summary.DiscardUnknown(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
var xxx_messageInfo_Summary proto.InternalMessageInfo
|
||||||
|
|
||||||
func (m *Summary) GetSampleCount() uint64 {
|
func (m *Summary) GetSampleCount() uint64 {
|
||||||
if m != nil && m.SampleCount != nil {
|
if m != nil && m.SampleCount != nil {
|
||||||
|
@ -184,13 +306,36 @@ func (m *Summary) GetQuantile() []*Quantile {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Untyped struct {
|
type Untyped struct {
|
||||||
Value *float64 `protobuf:"fixed64,1,opt,name=value" json:"value,omitempty"`
|
Value *float64 `protobuf:"fixed64,1,opt,name=value" json:"value,omitempty"`
|
||||||
XXX_unrecognized []byte `json:"-"`
|
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||||
|
XXX_unrecognized []byte `json:"-"`
|
||||||
|
XXX_sizecache int32 `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Untyped) Reset() { *m = Untyped{} }
|
func (m *Untyped) Reset() { *m = Untyped{} }
|
||||||
func (m *Untyped) String() string { return proto.CompactTextString(m) }
|
func (m *Untyped) String() string { return proto.CompactTextString(m) }
|
||||||
func (*Untyped) ProtoMessage() {}
|
func (*Untyped) ProtoMessage() {}
|
||||||
|
func (*Untyped) Descriptor() ([]byte, []int) {
|
||||||
|
return fileDescriptor_6039342a2ba47b72, []int{5}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Untyped) XXX_Unmarshal(b []byte) error {
|
||||||
|
return xxx_messageInfo_Untyped.Unmarshal(m, b)
|
||||||
|
}
|
||||||
|
func (m *Untyped) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||||
|
return xxx_messageInfo_Untyped.Marshal(b, m, deterministic)
|
||||||
|
}
|
||||||
|
func (m *Untyped) XXX_Merge(src proto.Message) {
|
||||||
|
xxx_messageInfo_Untyped.Merge(m, src)
|
||||||
|
}
|
||||||
|
func (m *Untyped) XXX_Size() int {
|
||||||
|
return xxx_messageInfo_Untyped.Size(m)
|
||||||
|
}
|
||||||
|
func (m *Untyped) XXX_DiscardUnknown() {
|
||||||
|
xxx_messageInfo_Untyped.DiscardUnknown(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
var xxx_messageInfo_Untyped proto.InternalMessageInfo
|
||||||
|
|
||||||
func (m *Untyped) GetValue() float64 {
|
func (m *Untyped) GetValue() float64 {
|
||||||
if m != nil && m.Value != nil {
|
if m != nil && m.Value != nil {
|
||||||
|
@ -200,15 +345,38 @@ func (m *Untyped) GetValue() float64 {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Histogram struct {
|
type Histogram struct {
|
||||||
SampleCount *uint64 `protobuf:"varint,1,opt,name=sample_count" json:"sample_count,omitempty"`
|
SampleCount *uint64 `protobuf:"varint,1,opt,name=sample_count,json=sampleCount" json:"sample_count,omitempty"`
|
||||||
SampleSum *float64 `protobuf:"fixed64,2,opt,name=sample_sum" json:"sample_sum,omitempty"`
|
SampleSum *float64 `protobuf:"fixed64,2,opt,name=sample_sum,json=sampleSum" json:"sample_sum,omitempty"`
|
||||||
Bucket []*Bucket `protobuf:"bytes,3,rep,name=bucket" json:"bucket,omitempty"`
|
Bucket []*Bucket `protobuf:"bytes,3,rep,name=bucket" json:"bucket,omitempty"`
|
||||||
XXX_unrecognized []byte `json:"-"`
|
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||||
|
XXX_unrecognized []byte `json:"-"`
|
||||||
|
XXX_sizecache int32 `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Histogram) Reset() { *m = Histogram{} }
|
func (m *Histogram) Reset() { *m = Histogram{} }
|
||||||
func (m *Histogram) String() string { return proto.CompactTextString(m) }
|
func (m *Histogram) String() string { return proto.CompactTextString(m) }
|
||||||
func (*Histogram) ProtoMessage() {}
|
func (*Histogram) ProtoMessage() {}
|
||||||
|
func (*Histogram) Descriptor() ([]byte, []int) {
|
||||||
|
return fileDescriptor_6039342a2ba47b72, []int{6}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Histogram) XXX_Unmarshal(b []byte) error {
|
||||||
|
return xxx_messageInfo_Histogram.Unmarshal(m, b)
|
||||||
|
}
|
||||||
|
func (m *Histogram) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||||
|
return xxx_messageInfo_Histogram.Marshal(b, m, deterministic)
|
||||||
|
}
|
||||||
|
func (m *Histogram) XXX_Merge(src proto.Message) {
|
||||||
|
xxx_messageInfo_Histogram.Merge(m, src)
|
||||||
|
}
|
||||||
|
func (m *Histogram) XXX_Size() int {
|
||||||
|
return xxx_messageInfo_Histogram.Size(m)
|
||||||
|
}
|
||||||
|
func (m *Histogram) XXX_DiscardUnknown() {
|
||||||
|
xxx_messageInfo_Histogram.DiscardUnknown(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
var xxx_messageInfo_Histogram proto.InternalMessageInfo
|
||||||
|
|
||||||
func (m *Histogram) GetSampleCount() uint64 {
|
func (m *Histogram) GetSampleCount() uint64 {
|
||||||
if m != nil && m.SampleCount != nil {
|
if m != nil && m.SampleCount != nil {
|
||||||
|
@ -232,14 +400,38 @@ func (m *Histogram) GetBucket() []*Bucket {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Bucket struct {
|
type Bucket struct {
|
||||||
CumulativeCount *uint64 `protobuf:"varint,1,opt,name=cumulative_count" json:"cumulative_count,omitempty"`
|
CumulativeCount *uint64 `protobuf:"varint,1,opt,name=cumulative_count,json=cumulativeCount" json:"cumulative_count,omitempty"`
|
||||||
UpperBound *float64 `protobuf:"fixed64,2,opt,name=upper_bound" json:"upper_bound,omitempty"`
|
UpperBound *float64 `protobuf:"fixed64,2,opt,name=upper_bound,json=upperBound" json:"upper_bound,omitempty"`
|
||||||
XXX_unrecognized []byte `json:"-"`
|
Exemplar *Exemplar `protobuf:"bytes,3,opt,name=exemplar" json:"exemplar,omitempty"`
|
||||||
|
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||||
|
XXX_unrecognized []byte `json:"-"`
|
||||||
|
XXX_sizecache int32 `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Bucket) Reset() { *m = Bucket{} }
|
func (m *Bucket) Reset() { *m = Bucket{} }
|
||||||
func (m *Bucket) String() string { return proto.CompactTextString(m) }
|
func (m *Bucket) String() string { return proto.CompactTextString(m) }
|
||||||
func (*Bucket) ProtoMessage() {}
|
func (*Bucket) ProtoMessage() {}
|
||||||
|
func (*Bucket) Descriptor() ([]byte, []int) {
|
||||||
|
return fileDescriptor_6039342a2ba47b72, []int{7}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Bucket) XXX_Unmarshal(b []byte) error {
|
||||||
|
return xxx_messageInfo_Bucket.Unmarshal(m, b)
|
||||||
|
}
|
||||||
|
func (m *Bucket) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||||
|
return xxx_messageInfo_Bucket.Marshal(b, m, deterministic)
|
||||||
|
}
|
||||||
|
func (m *Bucket) XXX_Merge(src proto.Message) {
|
||||||
|
xxx_messageInfo_Bucket.Merge(m, src)
|
||||||
|
}
|
||||||
|
func (m *Bucket) XXX_Size() int {
|
||||||
|
return xxx_messageInfo_Bucket.Size(m)
|
||||||
|
}
|
||||||
|
func (m *Bucket) XXX_DiscardUnknown() {
|
||||||
|
xxx_messageInfo_Bucket.DiscardUnknown(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
var xxx_messageInfo_Bucket proto.InternalMessageInfo
|
||||||
|
|
||||||
func (m *Bucket) GetCumulativeCount() uint64 {
|
func (m *Bucket) GetCumulativeCount() uint64 {
|
||||||
if m != nil && m.CumulativeCount != nil {
|
if m != nil && m.CumulativeCount != nil {
|
||||||
|
@ -255,20 +447,105 @@ func (m *Bucket) GetUpperBound() float64 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Bucket) GetExemplar() *Exemplar {
|
||||||
|
if m != nil {
|
||||||
|
return m.Exemplar
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Exemplar struct {
|
||||||
|
Label []*LabelPair `protobuf:"bytes,1,rep,name=label" json:"label,omitempty"`
|
||||||
|
Value *float64 `protobuf:"fixed64,2,opt,name=value" json:"value,omitempty"`
|
||||||
|
Timestamp *timestamp.Timestamp `protobuf:"bytes,3,opt,name=timestamp" json:"timestamp,omitempty"`
|
||||||
|
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||||
|
XXX_unrecognized []byte `json:"-"`
|
||||||
|
XXX_sizecache int32 `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Exemplar) Reset() { *m = Exemplar{} }
|
||||||
|
func (m *Exemplar) String() string { return proto.CompactTextString(m) }
|
||||||
|
func (*Exemplar) ProtoMessage() {}
|
||||||
|
func (*Exemplar) Descriptor() ([]byte, []int) {
|
||||||
|
return fileDescriptor_6039342a2ba47b72, []int{8}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Exemplar) XXX_Unmarshal(b []byte) error {
|
||||||
|
return xxx_messageInfo_Exemplar.Unmarshal(m, b)
|
||||||
|
}
|
||||||
|
func (m *Exemplar) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||||
|
return xxx_messageInfo_Exemplar.Marshal(b, m, deterministic)
|
||||||
|
}
|
||||||
|
func (m *Exemplar) XXX_Merge(src proto.Message) {
|
||||||
|
xxx_messageInfo_Exemplar.Merge(m, src)
|
||||||
|
}
|
||||||
|
func (m *Exemplar) XXX_Size() int {
|
||||||
|
return xxx_messageInfo_Exemplar.Size(m)
|
||||||
|
}
|
||||||
|
func (m *Exemplar) XXX_DiscardUnknown() {
|
||||||
|
xxx_messageInfo_Exemplar.DiscardUnknown(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
var xxx_messageInfo_Exemplar proto.InternalMessageInfo
|
||||||
|
|
||||||
|
func (m *Exemplar) GetLabel() []*LabelPair {
|
||||||
|
if m != nil {
|
||||||
|
return m.Label
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Exemplar) GetValue() float64 {
|
||||||
|
if m != nil && m.Value != nil {
|
||||||
|
return *m.Value
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Exemplar) GetTimestamp() *timestamp.Timestamp {
|
||||||
|
if m != nil {
|
||||||
|
return m.Timestamp
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type Metric struct {
|
type Metric struct {
|
||||||
Label []*LabelPair `protobuf:"bytes,1,rep,name=label" json:"label,omitempty"`
|
Label []*LabelPair `protobuf:"bytes,1,rep,name=label" json:"label,omitempty"`
|
||||||
Gauge *Gauge `protobuf:"bytes,2,opt,name=gauge" json:"gauge,omitempty"`
|
Gauge *Gauge `protobuf:"bytes,2,opt,name=gauge" json:"gauge,omitempty"`
|
||||||
Counter *Counter `protobuf:"bytes,3,opt,name=counter" json:"counter,omitempty"`
|
Counter *Counter `protobuf:"bytes,3,opt,name=counter" json:"counter,omitempty"`
|
||||||
Summary *Summary `protobuf:"bytes,4,opt,name=summary" json:"summary,omitempty"`
|
Summary *Summary `protobuf:"bytes,4,opt,name=summary" json:"summary,omitempty"`
|
||||||
Untyped *Untyped `protobuf:"bytes,5,opt,name=untyped" json:"untyped,omitempty"`
|
Untyped *Untyped `protobuf:"bytes,5,opt,name=untyped" json:"untyped,omitempty"`
|
||||||
Histogram *Histogram `protobuf:"bytes,7,opt,name=histogram" json:"histogram,omitempty"`
|
Histogram *Histogram `protobuf:"bytes,7,opt,name=histogram" json:"histogram,omitempty"`
|
||||||
TimestampMs *int64 `protobuf:"varint,6,opt,name=timestamp_ms" json:"timestamp_ms,omitempty"`
|
TimestampMs *int64 `protobuf:"varint,6,opt,name=timestamp_ms,json=timestampMs" json:"timestamp_ms,omitempty"`
|
||||||
XXX_unrecognized []byte `json:"-"`
|
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||||
|
XXX_unrecognized []byte `json:"-"`
|
||||||
|
XXX_sizecache int32 `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Metric) Reset() { *m = Metric{} }
|
func (m *Metric) Reset() { *m = Metric{} }
|
||||||
func (m *Metric) String() string { return proto.CompactTextString(m) }
|
func (m *Metric) String() string { return proto.CompactTextString(m) }
|
||||||
func (*Metric) ProtoMessage() {}
|
func (*Metric) ProtoMessage() {}
|
||||||
|
func (*Metric) Descriptor() ([]byte, []int) {
|
||||||
|
return fileDescriptor_6039342a2ba47b72, []int{9}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Metric) XXX_Unmarshal(b []byte) error {
|
||||||
|
return xxx_messageInfo_Metric.Unmarshal(m, b)
|
||||||
|
}
|
||||||
|
func (m *Metric) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||||
|
return xxx_messageInfo_Metric.Marshal(b, m, deterministic)
|
||||||
|
}
|
||||||
|
func (m *Metric) XXX_Merge(src proto.Message) {
|
||||||
|
xxx_messageInfo_Metric.Merge(m, src)
|
||||||
|
}
|
||||||
|
func (m *Metric) XXX_Size() int {
|
||||||
|
return xxx_messageInfo_Metric.Size(m)
|
||||||
|
}
|
||||||
|
func (m *Metric) XXX_DiscardUnknown() {
|
||||||
|
xxx_messageInfo_Metric.DiscardUnknown(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
var xxx_messageInfo_Metric proto.InternalMessageInfo
|
||||||
|
|
||||||
func (m *Metric) GetLabel() []*LabelPair {
|
func (m *Metric) GetLabel() []*LabelPair {
|
||||||
if m != nil {
|
if m != nil {
|
||||||
|
@ -320,16 +597,39 @@ func (m *Metric) GetTimestampMs() int64 {
|
||||||
}
|
}
|
||||||
|
|
||||||
type MetricFamily struct {
|
type MetricFamily struct {
|
||||||
Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
|
Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
|
||||||
Help *string `protobuf:"bytes,2,opt,name=help" json:"help,omitempty"`
|
Help *string `protobuf:"bytes,2,opt,name=help" json:"help,omitempty"`
|
||||||
Type *MetricType `protobuf:"varint,3,opt,name=type,enum=io.prometheus.client.MetricType" json:"type,omitempty"`
|
Type *MetricType `protobuf:"varint,3,opt,name=type,enum=io.prometheus.client.MetricType" json:"type,omitempty"`
|
||||||
Metric []*Metric `protobuf:"bytes,4,rep,name=metric" json:"metric,omitempty"`
|
Metric []*Metric `protobuf:"bytes,4,rep,name=metric" json:"metric,omitempty"`
|
||||||
XXX_unrecognized []byte `json:"-"`
|
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||||
|
XXX_unrecognized []byte `json:"-"`
|
||||||
|
XXX_sizecache int32 `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MetricFamily) Reset() { *m = MetricFamily{} }
|
func (m *MetricFamily) Reset() { *m = MetricFamily{} }
|
||||||
func (m *MetricFamily) String() string { return proto.CompactTextString(m) }
|
func (m *MetricFamily) String() string { return proto.CompactTextString(m) }
|
||||||
func (*MetricFamily) ProtoMessage() {}
|
func (*MetricFamily) ProtoMessage() {}
|
||||||
|
func (*MetricFamily) Descriptor() ([]byte, []int) {
|
||||||
|
return fileDescriptor_6039342a2ba47b72, []int{10}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MetricFamily) XXX_Unmarshal(b []byte) error {
|
||||||
|
return xxx_messageInfo_MetricFamily.Unmarshal(m, b)
|
||||||
|
}
|
||||||
|
func (m *MetricFamily) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||||
|
return xxx_messageInfo_MetricFamily.Marshal(b, m, deterministic)
|
||||||
|
}
|
||||||
|
func (m *MetricFamily) XXX_Merge(src proto.Message) {
|
||||||
|
xxx_messageInfo_MetricFamily.Merge(m, src)
|
||||||
|
}
|
||||||
|
func (m *MetricFamily) XXX_Size() int {
|
||||||
|
return xxx_messageInfo_MetricFamily.Size(m)
|
||||||
|
}
|
||||||
|
func (m *MetricFamily) XXX_DiscardUnknown() {
|
||||||
|
xxx_messageInfo_MetricFamily.DiscardUnknown(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
var xxx_messageInfo_MetricFamily proto.InternalMessageInfo
|
||||||
|
|
||||||
func (m *MetricFamily) GetName() string {
|
func (m *MetricFamily) GetName() string {
|
||||||
if m != nil && m.Name != nil {
|
if m != nil && m.Name != nil {
|
||||||
|
@ -361,4 +661,63 @@ func (m *MetricFamily) GetMetric() []*Metric {
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
proto.RegisterEnum("io.prometheus.client.MetricType", MetricType_name, MetricType_value)
|
proto.RegisterEnum("io.prometheus.client.MetricType", MetricType_name, MetricType_value)
|
||||||
|
proto.RegisterType((*LabelPair)(nil), "io.prometheus.client.LabelPair")
|
||||||
|
proto.RegisterType((*Gauge)(nil), "io.prometheus.client.Gauge")
|
||||||
|
proto.RegisterType((*Counter)(nil), "io.prometheus.client.Counter")
|
||||||
|
proto.RegisterType((*Quantile)(nil), "io.prometheus.client.Quantile")
|
||||||
|
proto.RegisterType((*Summary)(nil), "io.prometheus.client.Summary")
|
||||||
|
proto.RegisterType((*Untyped)(nil), "io.prometheus.client.Untyped")
|
||||||
|
proto.RegisterType((*Histogram)(nil), "io.prometheus.client.Histogram")
|
||||||
|
proto.RegisterType((*Bucket)(nil), "io.prometheus.client.Bucket")
|
||||||
|
proto.RegisterType((*Exemplar)(nil), "io.prometheus.client.Exemplar")
|
||||||
|
proto.RegisterType((*Metric)(nil), "io.prometheus.client.Metric")
|
||||||
|
proto.RegisterType((*MetricFamily)(nil), "io.prometheus.client.MetricFamily")
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() { proto.RegisterFile("metrics.proto", fileDescriptor_6039342a2ba47b72) }
|
||||||
|
|
||||||
|
var fileDescriptor_6039342a2ba47b72 = []byte{
|
||||||
|
// 665 bytes of a gzipped FileDescriptorProto
|
||||||
|
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x54, 0xcd, 0x6e, 0xd3, 0x4c,
|
||||||
|
0x14, 0xfd, 0xdc, 0x38, 0x3f, 0xbe, 0x69, 0x3f, 0xa2, 0x51, 0x17, 0x56, 0xa1, 0x24, 0x78, 0x55,
|
||||||
|
0x58, 0x38, 0xa2, 0x6a, 0x05, 0x2a, 0xb0, 0x68, 0x4b, 0x48, 0x91, 0x48, 0x5b, 0x26, 0xc9, 0xa2,
|
||||||
|
0xb0, 0x88, 0x1c, 0x77, 0x70, 0x2c, 0x3c, 0xb1, 0xb1, 0x67, 0x2a, 0xb2, 0x66, 0xc1, 0x16, 0x5e,
|
||||||
|
0x81, 0x17, 0x05, 0xcd, 0x8f, 0x6d, 0x2a, 0xb9, 0x95, 0x40, 0xec, 0x66, 0xee, 0x3d, 0xe7, 0xfa,
|
||||||
|
0xcc, 0xf8, 0x9c, 0x81, 0x0d, 0x4a, 0x58, 0x1a, 0xfa, 0x99, 0x9b, 0xa4, 0x31, 0x8b, 0xd1, 0x66,
|
||||||
|
0x18, 0x8b, 0x15, 0x25, 0x6c, 0x41, 0x78, 0xe6, 0xfa, 0x51, 0x48, 0x96, 0x6c, 0xab, 0x1b, 0xc4,
|
||||||
|
0x71, 0x10, 0x91, 0xbe, 0xc4, 0xcc, 0xf9, 0x87, 0x3e, 0x0b, 0x29, 0xc9, 0x98, 0x47, 0x13, 0x45,
|
||||||
|
0x73, 0xf6, 0xc1, 0x7a, 0xe3, 0xcd, 0x49, 0x74, 0xee, 0x85, 0x29, 0x42, 0x60, 0x2e, 0x3d, 0x4a,
|
||||||
|
0x6c, 0xa3, 0x67, 0xec, 0x58, 0x58, 0xae, 0xd1, 0x26, 0xd4, 0xaf, 0xbc, 0x88, 0x13, 0x7b, 0x4d,
|
||||||
|
0x16, 0xd5, 0xc6, 0xd9, 0x86, 0xfa, 0xd0, 0xe3, 0xc1, 0x6f, 0x6d, 0xc1, 0x31, 0xf2, 0xf6, 0x7b,
|
||||||
|
0x68, 0x1e, 0xc7, 0x7c, 0xc9, 0x48, 0x5a, 0x0d, 0x40, 0x07, 0xd0, 0x22, 0x9f, 0x09, 0x4d, 0x22,
|
||||||
|
0x2f, 0x95, 0x83, 0xdb, 0xbb, 0xf7, 0xdd, 0xaa, 0x03, 0xb8, 0x03, 0x8d, 0xc2, 0x05, 0xde, 0x79,
|
||||||
|
0x0e, 0xad, 0xb7, 0xdc, 0x5b, 0xb2, 0x30, 0x22, 0x68, 0x0b, 0x5a, 0x9f, 0xf4, 0x5a, 0x7f, 0xa0,
|
||||||
|
0xd8, 0x5f, 0x57, 0x5e, 0x48, 0xfb, 0x6a, 0x40, 0x73, 0xcc, 0x29, 0xf5, 0xd2, 0x15, 0x7a, 0x00,
|
||||||
|
0xeb, 0x99, 0x47, 0x93, 0x88, 0xcc, 0x7c, 0xa1, 0x56, 0x4e, 0x30, 0x71, 0x5b, 0xd5, 0xe4, 0x01,
|
||||||
|
0xd0, 0x36, 0x80, 0x86, 0x64, 0x9c, 0xea, 0x49, 0x96, 0xaa, 0x8c, 0x39, 0x15, 0xe7, 0x28, 0xbe,
|
||||||
|
0x5f, 0xeb, 0xd5, 0x6e, 0x3e, 0x47, 0xae, 0xb8, 0xd4, 0xe7, 0x74, 0xa1, 0x39, 0x5d, 0xb2, 0x55,
|
||||||
|
0x42, 0x2e, 0x6f, 0xb8, 0xc5, 0x2f, 0x06, 0x58, 0x27, 0x61, 0xc6, 0xe2, 0x20, 0xf5, 0xe8, 0x3f,
|
||||||
|
0x10, 0xbb, 0x07, 0x8d, 0x39, 0xf7, 0x3f, 0x12, 0xa6, 0xa5, 0xde, 0xab, 0x96, 0x7a, 0x24, 0x31,
|
||||||
|
0x58, 0x63, 0x9d, 0x6f, 0x06, 0x34, 0x54, 0x09, 0x3d, 0x84, 0x8e, 0xcf, 0x29, 0x8f, 0x3c, 0x16,
|
||||||
|
0x5e, 0x5d, 0x97, 0x71, 0xa7, 0xac, 0x2b, 0x29, 0x5d, 0x68, 0xf3, 0x24, 0x21, 0xe9, 0x6c, 0x1e,
|
||||||
|
0xf3, 0xe5, 0xa5, 0xd6, 0x02, 0xb2, 0x74, 0x24, 0x2a, 0xd7, 0x1c, 0x50, 0xfb, 0x43, 0x07, 0x7c,
|
||||||
|
0x37, 0xa0, 0x95, 0x97, 0xd1, 0x3e, 0xd4, 0x23, 0xe1, 0x60, 0xdb, 0x90, 0x87, 0xea, 0x56, 0x4f,
|
||||||
|
0x29, 0x4c, 0x8e, 0x15, 0xba, 0xda, 0x1d, 0xe8, 0x29, 0x58, 0x45, 0x42, 0xb4, 0xac, 0x2d, 0x57,
|
||||||
|
0x65, 0xc8, 0xcd, 0x33, 0xe4, 0x4e, 0x72, 0x04, 0x2e, 0xc1, 0xce, 0xcf, 0x35, 0x68, 0x8c, 0x64,
|
||||||
|
0x22, 0xff, 0x56, 0xd1, 0x63, 0xa8, 0x07, 0x22, 0x53, 0x3a, 0x10, 0x77, 0xab, 0x69, 0x32, 0x76,
|
||||||
|
0x58, 0x21, 0xd1, 0x13, 0x68, 0xfa, 0x2a, 0x67, 0x5a, 0xec, 0x76, 0x35, 0x49, 0x87, 0x11, 0xe7,
|
||||||
|
0x68, 0x41, 0xcc, 0x54, 0x08, 0x6c, 0xf3, 0x36, 0xa2, 0x4e, 0x0a, 0xce, 0xd1, 0x82, 0xc8, 0x95,
|
||||||
|
0x69, 0xed, 0xfa, 0x6d, 0x44, 0xed, 0x6c, 0x9c, 0xa3, 0xd1, 0x0b, 0xb0, 0x16, 0xb9, 0x97, 0xed,
|
||||||
|
0xa6, 0xa4, 0xde, 0x70, 0x31, 0x85, 0xe5, 0x71, 0xc9, 0x10, 0xee, 0x2f, 0xee, 0x7a, 0x46, 0x33,
|
||||||
|
0xbb, 0xd1, 0x33, 0x76, 0x6a, 0xb8, 0x5d, 0xd4, 0x46, 0x99, 0xf3, 0xc3, 0x80, 0x75, 0xf5, 0x07,
|
||||||
|
0x5e, 0x79, 0x34, 0x8c, 0x56, 0x95, 0xcf, 0x19, 0x02, 0x73, 0x41, 0xa2, 0x44, 0xbf, 0x66, 0x72,
|
||||||
|
0x8d, 0xf6, 0xc0, 0x14, 0x1a, 0xe5, 0x15, 0xfe, 0xbf, 0xdb, 0xab, 0x56, 0xa5, 0x26, 0x4f, 0x56,
|
||||||
|
0x09, 0xc1, 0x12, 0x2d, 0xd2, 0xa4, 0x5e, 0x60, 0xdb, 0xbc, 0x2d, 0x4d, 0x8a, 0x87, 0x35, 0xf6,
|
||||||
|
0xd1, 0x08, 0xa0, 0x9c, 0x84, 0xda, 0xd0, 0x3c, 0x3e, 0x9b, 0x9e, 0x4e, 0x06, 0xb8, 0xf3, 0x1f,
|
||||||
|
0xb2, 0xa0, 0x3e, 0x3c, 0x9c, 0x0e, 0x07, 0x1d, 0x43, 0xd4, 0xc7, 0xd3, 0xd1, 0xe8, 0x10, 0x5f,
|
||||||
|
0x74, 0xd6, 0xc4, 0x66, 0x7a, 0x3a, 0xb9, 0x38, 0x1f, 0xbc, 0xec, 0xd4, 0xd0, 0x06, 0x58, 0x27,
|
||||||
|
0xaf, 0xc7, 0x93, 0xb3, 0x21, 0x3e, 0x1c, 0x75, 0xcc, 0x23, 0x0c, 0x95, 0xef, 0xfe, 0xbb, 0x83,
|
||||||
|
0x20, 0x64, 0x0b, 0x3e, 0x77, 0xfd, 0x98, 0xf6, 0xcb, 0x6e, 0x5f, 0x75, 0x67, 0x34, 0xbe, 0x24,
|
||||||
|
0x51, 0x3f, 0x88, 0x9f, 0x85, 0xf1, 0xac, 0xec, 0xce, 0x54, 0xf7, 0x57, 0x00, 0x00, 0x00, 0xff,
|
||||||
|
0xff, 0xd0, 0x84, 0x91, 0x73, 0x59, 0x06, 0x00, 0x00,
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,11 @@ components and libraries.
|
||||||
|
|
||||||
* **config**: Common configuration structures
|
* **config**: Common configuration structures
|
||||||
* **expfmt**: Decoding and encoding for the exposition format
|
* **expfmt**: Decoding and encoding for the exposition format
|
||||||
* **log**: A logging wrapper around [logrus](https://github.com/sirupsen/logrus)
|
|
||||||
* **model**: Shared data structures
|
* **model**: Shared data structures
|
||||||
|
* **promlog**: A logging wrapper around [go-kit/log](https://github.com/go-kit/kit/tree/master/log)
|
||||||
* **route**: A routing wrapper around [httprouter](https://github.com/julienschmidt/httprouter) using `context.Context`
|
* **route**: A routing wrapper around [httprouter](https://github.com/julienschmidt/httprouter) using `context.Context`
|
||||||
|
* **server**: Common servers
|
||||||
* **version**: Version information and metrics
|
* **version**: Version information and metrics
|
||||||
|
|
||||||
|
## Deprecated
|
||||||
|
* **log**: A logging wrapper around [logrus](https://github.com/sirupsen/logrus)
|
||||||
|
|
|
@ -30,17 +30,38 @@ type Encoder interface {
|
||||||
Encode(*dto.MetricFamily) error
|
Encode(*dto.MetricFamily) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type encoder func(*dto.MetricFamily) error
|
// Closer is implemented by Encoders that need to be closed to finalize
|
||||||
|
// encoding. (For example, OpenMetrics needs a final `# EOF` line.)
|
||||||
func (e encoder) Encode(v *dto.MetricFamily) error {
|
//
|
||||||
return e(v)
|
// Note that all Encoder implementations returned from this package implement
|
||||||
|
// Closer, too, even if the Close call is a no-op. This happens in preparation
|
||||||
|
// for adding a Close method to the Encoder interface directly in a (mildly
|
||||||
|
// breaking) release in the future.
|
||||||
|
type Closer interface {
|
||||||
|
Close() error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Negotiate returns the Content-Type based on the given Accept header.
|
type encoderCloser struct {
|
||||||
// If no appropriate accepted type is found, FmtText is returned.
|
encode func(*dto.MetricFamily) error
|
||||||
|
close func() error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ec encoderCloser) Encode(v *dto.MetricFamily) error {
|
||||||
|
return ec.encode(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ec encoderCloser) Close() error {
|
||||||
|
return ec.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Negotiate returns the Content-Type based on the given Accept header. If no
|
||||||
|
// appropriate accepted type is found, FmtText is returned (which is the
|
||||||
|
// Prometheus text format). This function will never negotiate FmtOpenMetrics,
|
||||||
|
// as the support is still experimental. To include the option to negotiate
|
||||||
|
// FmtOpenMetrics, use NegotiateOpenMetrics.
|
||||||
func Negotiate(h http.Header) Format {
|
func Negotiate(h http.Header) Format {
|
||||||
for _, ac := range goautoneg.ParseAccept(h.Get(hdrAccept)) {
|
for _, ac := range goautoneg.ParseAccept(h.Get(hdrAccept)) {
|
||||||
// Check for protocol buffer
|
ver := ac.Params["version"]
|
||||||
if ac.Type+"/"+ac.SubType == ProtoType && ac.Params["proto"] == ProtoProtocol {
|
if ac.Type+"/"+ac.SubType == ProtoType && ac.Params["proto"] == ProtoProtocol {
|
||||||
switch ac.Params["encoding"] {
|
switch ac.Params["encoding"] {
|
||||||
case "delimited":
|
case "delimited":
|
||||||
|
@ -51,8 +72,6 @@ func Negotiate(h http.Header) Format {
|
||||||
return FmtProtoCompact
|
return FmtProtoCompact
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Check for text format.
|
|
||||||
ver := ac.Params["version"]
|
|
||||||
if ac.Type == "text" && ac.SubType == "plain" && (ver == TextVersion || ver == "") {
|
if ac.Type == "text" && ac.SubType == "plain" && (ver == TextVersion || ver == "") {
|
||||||
return FmtText
|
return FmtText
|
||||||
}
|
}
|
||||||
|
@ -60,29 +79,84 @@ func Negotiate(h http.Header) Format {
|
||||||
return FmtText
|
return FmtText
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewEncoder returns a new encoder based on content type negotiation.
|
// NegotiateIncludingOpenMetrics works like Negotiate but includes
|
||||||
|
// FmtOpenMetrics as an option for the result. Note that this function is
|
||||||
|
// temporary and will disappear once FmtOpenMetrics is fully supported and as
|
||||||
|
// such may be negotiated by the normal Negotiate function.
|
||||||
|
func NegotiateIncludingOpenMetrics(h http.Header) Format {
|
||||||
|
for _, ac := range goautoneg.ParseAccept(h.Get(hdrAccept)) {
|
||||||
|
ver := ac.Params["version"]
|
||||||
|
if ac.Type+"/"+ac.SubType == ProtoType && ac.Params["proto"] == ProtoProtocol {
|
||||||
|
switch ac.Params["encoding"] {
|
||||||
|
case "delimited":
|
||||||
|
return FmtProtoDelim
|
||||||
|
case "text":
|
||||||
|
return FmtProtoText
|
||||||
|
case "compact-text":
|
||||||
|
return FmtProtoCompact
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ac.Type == "text" && ac.SubType == "plain" && (ver == TextVersion || ver == "") {
|
||||||
|
return FmtText
|
||||||
|
}
|
||||||
|
if ac.Type+"/"+ac.SubType == OpenMetricsType && (ver == OpenMetricsVersion || ver == "") {
|
||||||
|
return FmtOpenMetrics
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return FmtText
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEncoder returns a new encoder based on content type negotiation. All
|
||||||
|
// Encoder implementations returned by NewEncoder also implement Closer, and
|
||||||
|
// callers should always call the Close method. It is currently only required
|
||||||
|
// for FmtOpenMetrics, but a future (breaking) release will add the Close method
|
||||||
|
// to the Encoder interface directly. The current version of the Encoder
|
||||||
|
// interface is kept for backwards compatibility.
|
||||||
func NewEncoder(w io.Writer, format Format) Encoder {
|
func NewEncoder(w io.Writer, format Format) Encoder {
|
||||||
switch format {
|
switch format {
|
||||||
case FmtProtoDelim:
|
case FmtProtoDelim:
|
||||||
return encoder(func(v *dto.MetricFamily) error {
|
return encoderCloser{
|
||||||
_, err := pbutil.WriteDelimited(w, v)
|
encode: func(v *dto.MetricFamily) error {
|
||||||
return err
|
_, err := pbutil.WriteDelimited(w, v)
|
||||||
})
|
return err
|
||||||
|
},
|
||||||
|
close: func() error { return nil },
|
||||||
|
}
|
||||||
case FmtProtoCompact:
|
case FmtProtoCompact:
|
||||||
return encoder(func(v *dto.MetricFamily) error {
|
return encoderCloser{
|
||||||
_, err := fmt.Fprintln(w, v.String())
|
encode: func(v *dto.MetricFamily) error {
|
||||||
return err
|
_, err := fmt.Fprintln(w, v.String())
|
||||||
})
|
return err
|
||||||
|
},
|
||||||
|
close: func() error { return nil },
|
||||||
|
}
|
||||||
case FmtProtoText:
|
case FmtProtoText:
|
||||||
return encoder(func(v *dto.MetricFamily) error {
|
return encoderCloser{
|
||||||
_, err := fmt.Fprintln(w, proto.MarshalTextString(v))
|
encode: func(v *dto.MetricFamily) error {
|
||||||
return err
|
_, err := fmt.Fprintln(w, proto.MarshalTextString(v))
|
||||||
})
|
return err
|
||||||
|
},
|
||||||
|
close: func() error { return nil },
|
||||||
|
}
|
||||||
case FmtText:
|
case FmtText:
|
||||||
return encoder(func(v *dto.MetricFamily) error {
|
return encoderCloser{
|
||||||
_, err := MetricFamilyToText(w, v)
|
encode: func(v *dto.MetricFamily) error {
|
||||||
return err
|
_, err := MetricFamilyToText(w, v)
|
||||||
})
|
return err
|
||||||
|
},
|
||||||
|
close: func() error { return nil },
|
||||||
|
}
|
||||||
|
case FmtOpenMetrics:
|
||||||
|
return encoderCloser{
|
||||||
|
encode: func(v *dto.MetricFamily) error {
|
||||||
|
_, err := MetricFamilyToOpenMetrics(w, v)
|
||||||
|
return err
|
||||||
|
},
|
||||||
|
close: func() error {
|
||||||
|
_, err := FinalizeOpenMetrics(w)
|
||||||
|
return err
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
panic("expfmt.NewEncoder: unknown format")
|
panic(fmt.Errorf("expfmt.NewEncoder: unknown format %q", format))
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,10 +19,12 @@ type Format string
|
||||||
|
|
||||||
// Constants to assemble the Content-Type values for the different wire protocols.
|
// Constants to assemble the Content-Type values for the different wire protocols.
|
||||||
const (
|
const (
|
||||||
TextVersion = "0.0.4"
|
TextVersion = "0.0.4"
|
||||||
ProtoType = `application/vnd.google.protobuf`
|
ProtoType = `application/vnd.google.protobuf`
|
||||||
ProtoProtocol = `io.prometheus.client.MetricFamily`
|
ProtoProtocol = `io.prometheus.client.MetricFamily`
|
||||||
ProtoFmt = ProtoType + "; proto=" + ProtoProtocol + ";"
|
ProtoFmt = ProtoType + "; proto=" + ProtoProtocol + ";"
|
||||||
|
OpenMetricsType = `application/openmetrics-text`
|
||||||
|
OpenMetricsVersion = "0.0.1"
|
||||||
|
|
||||||
// The Content-Type values for the different wire protocols.
|
// The Content-Type values for the different wire protocols.
|
||||||
FmtUnknown Format = `<unknown>`
|
FmtUnknown Format = `<unknown>`
|
||||||
|
@ -30,6 +32,7 @@ const (
|
||||||
FmtProtoDelim Format = ProtoFmt + ` encoding=delimited`
|
FmtProtoDelim Format = ProtoFmt + ` encoding=delimited`
|
||||||
FmtProtoText Format = ProtoFmt + ` encoding=text`
|
FmtProtoText Format = ProtoFmt + ` encoding=text`
|
||||||
FmtProtoCompact Format = ProtoFmt + ` encoding=compact-text`
|
FmtProtoCompact Format = ProtoFmt + ` encoding=compact-text`
|
||||||
|
FmtOpenMetrics Format = OpenMetricsType + `; version=` + OpenMetricsVersion + `; charset=utf-8`
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
@ -0,0 +1,527 @@
|
||||||
|
// Copyright 2020 The Prometheus Authors
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package expfmt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/golang/protobuf/ptypes"
|
||||||
|
"github.com/prometheus/common/model"
|
||||||
|
|
||||||
|
dto "github.com/prometheus/client_model/go"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MetricFamilyToOpenMetrics converts a MetricFamily proto message into the
|
||||||
|
// OpenMetrics text format and writes the resulting lines to 'out'. It returns
|
||||||
|
// the number of bytes written and any error encountered. The output will have
|
||||||
|
// the same order as the input, no further sorting is performed. Furthermore,
|
||||||
|
// this function assumes the input is already sanitized and does not perform any
|
||||||
|
// sanity checks. If the input contains duplicate metrics or invalid metric or
|
||||||
|
// label names, the conversion will result in invalid text format output.
|
||||||
|
//
|
||||||
|
// This function fulfills the type 'expfmt.encoder'.
|
||||||
|
//
|
||||||
|
// Note that OpenMetrics requires a final `# EOF` line. Since this function acts
|
||||||
|
// on individual metric families, it is the responsibility of the caller to
|
||||||
|
// append this line to 'out' once all metric families have been written.
|
||||||
|
// Conveniently, this can be done by calling FinalizeOpenMetrics.
|
||||||
|
//
|
||||||
|
// The output should be fully OpenMetrics compliant. However, there are a few
|
||||||
|
// missing features and peculiarities to avoid complications when switching from
|
||||||
|
// Prometheus to OpenMetrics or vice versa:
|
||||||
|
//
|
||||||
|
// - Counters are expected to have the `_total` suffix in their metric name. In
|
||||||
|
// the output, the suffix will be truncated from the `# TYPE` and `# HELP`
|
||||||
|
// line. A counter with a missing `_total` suffix is not an error. However,
|
||||||
|
// its type will be set to `unknown` in that case to avoid invalid OpenMetrics
|
||||||
|
// output.
|
||||||
|
//
|
||||||
|
// - No support for the following (optional) features: `# UNIT` line, `_created`
|
||||||
|
// line, info type, stateset type, gaugehistogram type.
|
||||||
|
//
|
||||||
|
// - The size of exemplar labels is not checked (i.e. it's possible to create
|
||||||
|
// exemplars that are larger than allowed by the OpenMetrics specification).
|
||||||
|
//
|
||||||
|
// - The value of Counters is not checked. (OpenMetrics doesn't allow counters
|
||||||
|
// with a `NaN` value.)
|
||||||
|
func MetricFamilyToOpenMetrics(out io.Writer, in *dto.MetricFamily) (written int, err error) {
|
||||||
|
name := in.GetName()
|
||||||
|
if name == "" {
|
||||||
|
return 0, fmt.Errorf("MetricFamily has no name: %s", in)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try the interface upgrade. If it doesn't work, we'll use a
|
||||||
|
// bufio.Writer from the sync.Pool.
|
||||||
|
w, ok := out.(enhancedWriter)
|
||||||
|
if !ok {
|
||||||
|
b := bufPool.Get().(*bufio.Writer)
|
||||||
|
b.Reset(out)
|
||||||
|
w = b
|
||||||
|
defer func() {
|
||||||
|
bErr := b.Flush()
|
||||||
|
if err == nil {
|
||||||
|
err = bErr
|
||||||
|
}
|
||||||
|
bufPool.Put(b)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
n int
|
||||||
|
metricType = in.GetType()
|
||||||
|
shortName = name
|
||||||
|
)
|
||||||
|
if metricType == dto.MetricType_COUNTER && strings.HasSuffix(shortName, "_total") {
|
||||||
|
shortName = name[:len(name)-6]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Comments, first HELP, then TYPE.
|
||||||
|
if in.Help != nil {
|
||||||
|
n, err = w.WriteString("# HELP ")
|
||||||
|
written += n
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
n, err = w.WriteString(shortName)
|
||||||
|
written += n
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = w.WriteByte(' ')
|
||||||
|
written++
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
n, err = writeEscapedString(w, *in.Help, true)
|
||||||
|
written += n
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = w.WriteByte('\n')
|
||||||
|
written++
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
n, err = w.WriteString("# TYPE ")
|
||||||
|
written += n
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
n, err = w.WriteString(shortName)
|
||||||
|
written += n
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch metricType {
|
||||||
|
case dto.MetricType_COUNTER:
|
||||||
|
if strings.HasSuffix(name, "_total") {
|
||||||
|
n, err = w.WriteString(" counter\n")
|
||||||
|
} else {
|
||||||
|
n, err = w.WriteString(" unknown\n")
|
||||||
|
}
|
||||||
|
case dto.MetricType_GAUGE:
|
||||||
|
n, err = w.WriteString(" gauge\n")
|
||||||
|
case dto.MetricType_SUMMARY:
|
||||||
|
n, err = w.WriteString(" summary\n")
|
||||||
|
case dto.MetricType_UNTYPED:
|
||||||
|
n, err = w.WriteString(" unknown\n")
|
||||||
|
case dto.MetricType_HISTOGRAM:
|
||||||
|
n, err = w.WriteString(" histogram\n")
|
||||||
|
default:
|
||||||
|
return written, fmt.Errorf("unknown metric type %s", metricType.String())
|
||||||
|
}
|
||||||
|
written += n
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally the samples, one line for each.
|
||||||
|
for _, metric := range in.Metric {
|
||||||
|
switch metricType {
|
||||||
|
case dto.MetricType_COUNTER:
|
||||||
|
if metric.Counter == nil {
|
||||||
|
return written, fmt.Errorf(
|
||||||
|
"expected counter in metric %s %s", name, metric,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// Note that we have ensured above that either the name
|
||||||
|
// ends on `_total` or that the rendered type is
|
||||||
|
// `unknown`. Therefore, no `_total` must be added here.
|
||||||
|
n, err = writeOpenMetricsSample(
|
||||||
|
w, name, "", metric, "", 0,
|
||||||
|
metric.Counter.GetValue(), 0, false,
|
||||||
|
metric.Counter.Exemplar,
|
||||||
|
)
|
||||||
|
case dto.MetricType_GAUGE:
|
||||||
|
if metric.Gauge == nil {
|
||||||
|
return written, fmt.Errorf(
|
||||||
|
"expected gauge in metric %s %s", name, metric,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
n, err = writeOpenMetricsSample(
|
||||||
|
w, name, "", metric, "", 0,
|
||||||
|
metric.Gauge.GetValue(), 0, false,
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
case dto.MetricType_UNTYPED:
|
||||||
|
if metric.Untyped == nil {
|
||||||
|
return written, fmt.Errorf(
|
||||||
|
"expected untyped in metric %s %s", name, metric,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
n, err = writeOpenMetricsSample(
|
||||||
|
w, name, "", metric, "", 0,
|
||||||
|
metric.Untyped.GetValue(), 0, false,
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
case dto.MetricType_SUMMARY:
|
||||||
|
if metric.Summary == nil {
|
||||||
|
return written, fmt.Errorf(
|
||||||
|
"expected summary in metric %s %s", name, metric,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
for _, q := range metric.Summary.Quantile {
|
||||||
|
n, err = writeOpenMetricsSample(
|
||||||
|
w, name, "", metric,
|
||||||
|
model.QuantileLabel, q.GetQuantile(),
|
||||||
|
q.GetValue(), 0, false,
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
written += n
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
n, err = writeOpenMetricsSample(
|
||||||
|
w, name, "_sum", metric, "", 0,
|
||||||
|
metric.Summary.GetSampleSum(), 0, false,
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
written += n
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
n, err = writeOpenMetricsSample(
|
||||||
|
w, name, "_count", metric, "", 0,
|
||||||
|
0, metric.Summary.GetSampleCount(), true,
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
case dto.MetricType_HISTOGRAM:
|
||||||
|
if metric.Histogram == nil {
|
||||||
|
return written, fmt.Errorf(
|
||||||
|
"expected histogram in metric %s %s", name, metric,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
infSeen := false
|
||||||
|
for _, b := range metric.Histogram.Bucket {
|
||||||
|
n, err = writeOpenMetricsSample(
|
||||||
|
w, name, "_bucket", metric,
|
||||||
|
model.BucketLabel, b.GetUpperBound(),
|
||||||
|
0, b.GetCumulativeCount(), true,
|
||||||
|
b.Exemplar,
|
||||||
|
)
|
||||||
|
written += n
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if math.IsInf(b.GetUpperBound(), +1) {
|
||||||
|
infSeen = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !infSeen {
|
||||||
|
n, err = writeOpenMetricsSample(
|
||||||
|
w, name, "_bucket", metric,
|
||||||
|
model.BucketLabel, math.Inf(+1),
|
||||||
|
0, metric.Histogram.GetSampleCount(), true,
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
written += n
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
n, err = writeOpenMetricsSample(
|
||||||
|
w, name, "_sum", metric, "", 0,
|
||||||
|
metric.Histogram.GetSampleSum(), 0, false,
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
written += n
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
n, err = writeOpenMetricsSample(
|
||||||
|
w, name, "_count", metric, "", 0,
|
||||||
|
0, metric.Histogram.GetSampleCount(), true,
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
default:
|
||||||
|
return written, fmt.Errorf(
|
||||||
|
"unexpected type in metric %s %s", name, metric,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
written += n
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// FinalizeOpenMetrics writes the final `# EOF\n` line required by OpenMetrics.
|
||||||
|
func FinalizeOpenMetrics(w io.Writer) (written int, err error) {
|
||||||
|
return w.Write([]byte("# EOF\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeOpenMetricsSample writes a single sample in OpenMetrics text format to
|
||||||
|
// w, given the metric name, the metric proto message itself, optionally an
|
||||||
|
// additional label name with a float64 value (use empty string as label name if
|
||||||
|
// not required), the value (optionally as float64 or uint64, determined by
|
||||||
|
// useIntValue), and optionally an exemplar (use nil if not required). The
|
||||||
|
// function returns the number of bytes written and any error encountered.
|
||||||
|
func writeOpenMetricsSample(
|
||||||
|
w enhancedWriter,
|
||||||
|
name, suffix string,
|
||||||
|
metric *dto.Metric,
|
||||||
|
additionalLabelName string, additionalLabelValue float64,
|
||||||
|
floatValue float64, intValue uint64, useIntValue bool,
|
||||||
|
exemplar *dto.Exemplar,
|
||||||
|
) (int, error) {
|
||||||
|
var written int
|
||||||
|
n, err := w.WriteString(name)
|
||||||
|
written += n
|
||||||
|
if err != nil {
|
||||||
|
return written, err
|
||||||
|
}
|
||||||
|
if suffix != "" {
|
||||||
|
n, err = w.WriteString(suffix)
|
||||||
|
written += n
|
||||||
|
if err != nil {
|
||||||
|
return written, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
n, err = writeOpenMetricsLabelPairs(
|
||||||
|
w, metric.Label, additionalLabelName, additionalLabelValue,
|
||||||
|
)
|
||||||
|
written += n
|
||||||
|
if err != nil {
|
||||||
|
return written, err
|
||||||
|
}
|
||||||
|
err = w.WriteByte(' ')
|
||||||
|
written++
|
||||||
|
if err != nil {
|
||||||
|
return written, err
|
||||||
|
}
|
||||||
|
if useIntValue {
|
||||||
|
n, err = writeUint(w, intValue)
|
||||||
|
} else {
|
||||||
|
n, err = writeOpenMetricsFloat(w, floatValue)
|
||||||
|
}
|
||||||
|
written += n
|
||||||
|
if err != nil {
|
||||||
|
return written, err
|
||||||
|
}
|
||||||
|
if metric.TimestampMs != nil {
|
||||||
|
err = w.WriteByte(' ')
|
||||||
|
written++
|
||||||
|
if err != nil {
|
||||||
|
return written, err
|
||||||
|
}
|
||||||
|
// TODO(beorn7): Format this directly without converting to a float first.
|
||||||
|
n, err = writeOpenMetricsFloat(w, float64(*metric.TimestampMs)/1000)
|
||||||
|
written += n
|
||||||
|
if err != nil {
|
||||||
|
return written, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if exemplar != nil {
|
||||||
|
n, err = writeExemplar(w, exemplar)
|
||||||
|
written += n
|
||||||
|
if err != nil {
|
||||||
|
return written, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = w.WriteByte('\n')
|
||||||
|
written++
|
||||||
|
if err != nil {
|
||||||
|
return written, err
|
||||||
|
}
|
||||||
|
return written, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeOpenMetricsLabelPairs works like writeOpenMetrics but formats the float
|
||||||
|
// in OpenMetrics style.
|
||||||
|
func writeOpenMetricsLabelPairs(
|
||||||
|
w enhancedWriter,
|
||||||
|
in []*dto.LabelPair,
|
||||||
|
additionalLabelName string, additionalLabelValue float64,
|
||||||
|
) (int, error) {
|
||||||
|
if len(in) == 0 && additionalLabelName == "" {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
written int
|
||||||
|
separator byte = '{'
|
||||||
|
)
|
||||||
|
for _, lp := range in {
|
||||||
|
err := w.WriteByte(separator)
|
||||||
|
written++
|
||||||
|
if err != nil {
|
||||||
|
return written, err
|
||||||
|
}
|
||||||
|
n, err := w.WriteString(lp.GetName())
|
||||||
|
written += n
|
||||||
|
if err != nil {
|
||||||
|
return written, err
|
||||||
|
}
|
||||||
|
n, err = w.WriteString(`="`)
|
||||||
|
written += n
|
||||||
|
if err != nil {
|
||||||
|
return written, err
|
||||||
|
}
|
||||||
|
n, err = writeEscapedString(w, lp.GetValue(), true)
|
||||||
|
written += n
|
||||||
|
if err != nil {
|
||||||
|
return written, err
|
||||||
|
}
|
||||||
|
err = w.WriteByte('"')
|
||||||
|
written++
|
||||||
|
if err != nil {
|
||||||
|
return written, err
|
||||||
|
}
|
||||||
|
separator = ','
|
||||||
|
}
|
||||||
|
if additionalLabelName != "" {
|
||||||
|
err := w.WriteByte(separator)
|
||||||
|
written++
|
||||||
|
if err != nil {
|
||||||
|
return written, err
|
||||||
|
}
|
||||||
|
n, err := w.WriteString(additionalLabelName)
|
||||||
|
written += n
|
||||||
|
if err != nil {
|
||||||
|
return written, err
|
||||||
|
}
|
||||||
|
n, err = w.WriteString(`="`)
|
||||||
|
written += n
|
||||||
|
if err != nil {
|
||||||
|
return written, err
|
||||||
|
}
|
||||||
|
n, err = writeOpenMetricsFloat(w, additionalLabelValue)
|
||||||
|
written += n
|
||||||
|
if err != nil {
|
||||||
|
return written, err
|
||||||
|
}
|
||||||
|
err = w.WriteByte('"')
|
||||||
|
written++
|
||||||
|
if err != nil {
|
||||||
|
return written, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err := w.WriteByte('}')
|
||||||
|
written++
|
||||||
|
if err != nil {
|
||||||
|
return written, err
|
||||||
|
}
|
||||||
|
return written, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeExemplar writes the provided exemplar in OpenMetrics format to w. The
|
||||||
|
// function returns the number of bytes written and any error encountered.
|
||||||
|
func writeExemplar(w enhancedWriter, e *dto.Exemplar) (int, error) {
|
||||||
|
written := 0
|
||||||
|
n, err := w.WriteString(" # ")
|
||||||
|
written += n
|
||||||
|
if err != nil {
|
||||||
|
return written, err
|
||||||
|
}
|
||||||
|
n, err = writeOpenMetricsLabelPairs(w, e.Label, "", 0)
|
||||||
|
written += n
|
||||||
|
if err != nil {
|
||||||
|
return written, err
|
||||||
|
}
|
||||||
|
err = w.WriteByte(' ')
|
||||||
|
written++
|
||||||
|
if err != nil {
|
||||||
|
return written, err
|
||||||
|
}
|
||||||
|
n, err = writeOpenMetricsFloat(w, e.GetValue())
|
||||||
|
written += n
|
||||||
|
if err != nil {
|
||||||
|
return written, err
|
||||||
|
}
|
||||||
|
if e.Timestamp != nil {
|
||||||
|
err = w.WriteByte(' ')
|
||||||
|
written++
|
||||||
|
if err != nil {
|
||||||
|
return written, err
|
||||||
|
}
|
||||||
|
ts, err := ptypes.Timestamp((*e).Timestamp)
|
||||||
|
if err != nil {
|
||||||
|
return written, err
|
||||||
|
}
|
||||||
|
// TODO(beorn7): Format this directly from components of ts to
|
||||||
|
// avoid overflow/underflow and precision issues of the float
|
||||||
|
// conversion.
|
||||||
|
n, err = writeOpenMetricsFloat(w, float64(ts.UnixNano())/1e9)
|
||||||
|
written += n
|
||||||
|
if err != nil {
|
||||||
|
return written, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return written, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeOpenMetricsFloat works like writeFloat but appends ".0" if the resulting
|
||||||
|
// number would otherwise contain neither a "." nor an "e".
|
||||||
|
func writeOpenMetricsFloat(w enhancedWriter, f float64) (int, error) {
|
||||||
|
switch {
|
||||||
|
case f == 1:
|
||||||
|
return w.WriteString("1.0")
|
||||||
|
case f == 0:
|
||||||
|
return w.WriteString("0.0")
|
||||||
|
case f == -1:
|
||||||
|
return w.WriteString("-1.0")
|
||||||
|
case math.IsNaN(f):
|
||||||
|
return w.WriteString("NaN")
|
||||||
|
case math.IsInf(f, +1):
|
||||||
|
return w.WriteString("+Inf")
|
||||||
|
case math.IsInf(f, -1):
|
||||||
|
return w.WriteString("-Inf")
|
||||||
|
default:
|
||||||
|
bp := numBufPool.Get().(*[]byte)
|
||||||
|
*bp = strconv.AppendFloat((*bp)[:0], f, 'g', -1, 64)
|
||||||
|
if !bytes.ContainsAny(*bp, "e.") {
|
||||||
|
*bp = append(*bp, '.', '0')
|
||||||
|
}
|
||||||
|
written, err := w.Write(*bp)
|
||||||
|
numBufPool.Put(bp)
|
||||||
|
return written, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeUint is like writeInt just for uint64.
|
||||||
|
func writeUint(w enhancedWriter, u uint64) (int, error) {
|
||||||
|
bp := numBufPool.Get().(*[]byte)
|
||||||
|
*bp = strconv.AppendUint((*bp)[:0], u, 10)
|
||||||
|
written, err := w.Write(*bp)
|
||||||
|
numBufPool.Put(bp)
|
||||||
|
return written, err
|
||||||
|
}
|
|
@ -14,13 +14,45 @@
|
||||||
package expfmt
|
package expfmt
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"math"
|
"math"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/prometheus/common/model"
|
||||||
|
|
||||||
dto "github.com/prometheus/client_model/go"
|
dto "github.com/prometheus/client_model/go"
|
||||||
"github.com/prometheus/common/model"
|
)
|
||||||
|
|
||||||
|
// enhancedWriter has all the enhanced write functions needed here. bufio.Writer
|
||||||
|
// implements it.
|
||||||
|
type enhancedWriter interface {
|
||||||
|
io.Writer
|
||||||
|
WriteRune(r rune) (n int, err error)
|
||||||
|
WriteString(s string) (n int, err error)
|
||||||
|
WriteByte(c byte) error
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
initialNumBufSize = 24
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
bufPool = sync.Pool{
|
||||||
|
New: func() interface{} {
|
||||||
|
return bufio.NewWriter(ioutil.Discard)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
numBufPool = sync.Pool{
|
||||||
|
New: func() interface{} {
|
||||||
|
b := make([]byte, 0, initialNumBufSize)
|
||||||
|
return &b
|
||||||
|
},
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// MetricFamilyToText converts a MetricFamily proto message into text format and
|
// MetricFamilyToText converts a MetricFamily proto message into text format and
|
||||||
|
@ -32,37 +64,90 @@ import (
|
||||||
// will result in invalid text format output.
|
// will result in invalid text format output.
|
||||||
//
|
//
|
||||||
// This method fulfills the type 'prometheus.encoder'.
|
// This method fulfills the type 'prometheus.encoder'.
|
||||||
func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (int, error) {
|
func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (written int, err error) {
|
||||||
var written int
|
|
||||||
|
|
||||||
// Fail-fast checks.
|
// Fail-fast checks.
|
||||||
if len(in.Metric) == 0 {
|
if len(in.Metric) == 0 {
|
||||||
return written, fmt.Errorf("MetricFamily has no metrics: %s", in)
|
return 0, fmt.Errorf("MetricFamily has no metrics: %s", in)
|
||||||
}
|
}
|
||||||
name := in.GetName()
|
name := in.GetName()
|
||||||
if name == "" {
|
if name == "" {
|
||||||
return written, fmt.Errorf("MetricFamily has no name: %s", in)
|
return 0, fmt.Errorf("MetricFamily has no name: %s", in)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Try the interface upgrade. If it doesn't work, we'll use a
|
||||||
|
// bufio.Writer from the sync.Pool.
|
||||||
|
w, ok := out.(enhancedWriter)
|
||||||
|
if !ok {
|
||||||
|
b := bufPool.Get().(*bufio.Writer)
|
||||||
|
b.Reset(out)
|
||||||
|
w = b
|
||||||
|
defer func() {
|
||||||
|
bErr := b.Flush()
|
||||||
|
if err == nil {
|
||||||
|
err = bErr
|
||||||
|
}
|
||||||
|
bufPool.Put(b)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
var n int
|
||||||
|
|
||||||
// Comments, first HELP, then TYPE.
|
// Comments, first HELP, then TYPE.
|
||||||
if in.Help != nil {
|
if in.Help != nil {
|
||||||
n, err := fmt.Fprintf(
|
n, err = w.WriteString("# HELP ")
|
||||||
out, "# HELP %s %s\n",
|
|
||||||
name, escapeString(*in.Help, false),
|
|
||||||
)
|
|
||||||
written += n
|
written += n
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return written, err
|
return
|
||||||
|
}
|
||||||
|
n, err = w.WriteString(name)
|
||||||
|
written += n
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = w.WriteByte(' ')
|
||||||
|
written++
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
n, err = writeEscapedString(w, *in.Help, false)
|
||||||
|
written += n
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = w.WriteByte('\n')
|
||||||
|
written++
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
metricType := in.GetType()
|
n, err = w.WriteString("# TYPE ")
|
||||||
n, err := fmt.Fprintf(
|
|
||||||
out, "# TYPE %s %s\n",
|
|
||||||
name, strings.ToLower(metricType.String()),
|
|
||||||
)
|
|
||||||
written += n
|
written += n
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return written, err
|
return
|
||||||
|
}
|
||||||
|
n, err = w.WriteString(name)
|
||||||
|
written += n
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
metricType := in.GetType()
|
||||||
|
switch metricType {
|
||||||
|
case dto.MetricType_COUNTER:
|
||||||
|
n, err = w.WriteString(" counter\n")
|
||||||
|
case dto.MetricType_GAUGE:
|
||||||
|
n, err = w.WriteString(" gauge\n")
|
||||||
|
case dto.MetricType_SUMMARY:
|
||||||
|
n, err = w.WriteString(" summary\n")
|
||||||
|
case dto.MetricType_UNTYPED:
|
||||||
|
n, err = w.WriteString(" untyped\n")
|
||||||
|
case dto.MetricType_HISTOGRAM:
|
||||||
|
n, err = w.WriteString(" histogram\n")
|
||||||
|
default:
|
||||||
|
return written, fmt.Errorf("unknown metric type %s", metricType.String())
|
||||||
|
}
|
||||||
|
written += n
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finally the samples, one line for each.
|
// Finally the samples, one line for each.
|
||||||
|
@ -75,9 +160,8 @@ func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (int, error) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
n, err = writeSample(
|
n, err = writeSample(
|
||||||
name, metric, "", "",
|
w, name, "", metric, "", 0,
|
||||||
metric.Counter.GetValue(),
|
metric.Counter.GetValue(),
|
||||||
out,
|
|
||||||
)
|
)
|
||||||
case dto.MetricType_GAUGE:
|
case dto.MetricType_GAUGE:
|
||||||
if metric.Gauge == nil {
|
if metric.Gauge == nil {
|
||||||
|
@ -86,9 +170,8 @@ func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (int, error) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
n, err = writeSample(
|
n, err = writeSample(
|
||||||
name, metric, "", "",
|
w, name, "", metric, "", 0,
|
||||||
metric.Gauge.GetValue(),
|
metric.Gauge.GetValue(),
|
||||||
out,
|
|
||||||
)
|
)
|
||||||
case dto.MetricType_UNTYPED:
|
case dto.MetricType_UNTYPED:
|
||||||
if metric.Untyped == nil {
|
if metric.Untyped == nil {
|
||||||
|
@ -97,9 +180,8 @@ func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (int, error) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
n, err = writeSample(
|
n, err = writeSample(
|
||||||
name, metric, "", "",
|
w, name, "", metric, "", 0,
|
||||||
metric.Untyped.GetValue(),
|
metric.Untyped.GetValue(),
|
||||||
out,
|
|
||||||
)
|
)
|
||||||
case dto.MetricType_SUMMARY:
|
case dto.MetricType_SUMMARY:
|
||||||
if metric.Summary == nil {
|
if metric.Summary == nil {
|
||||||
|
@ -109,29 +191,26 @@ func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (int, error) {
|
||||||
}
|
}
|
||||||
for _, q := range metric.Summary.Quantile {
|
for _, q := range metric.Summary.Quantile {
|
||||||
n, err = writeSample(
|
n, err = writeSample(
|
||||||
name, metric,
|
w, name, "", metric,
|
||||||
model.QuantileLabel, fmt.Sprint(q.GetQuantile()),
|
model.QuantileLabel, q.GetQuantile(),
|
||||||
q.GetValue(),
|
q.GetValue(),
|
||||||
out,
|
|
||||||
)
|
)
|
||||||
written += n
|
written += n
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return written, err
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
n, err = writeSample(
|
n, err = writeSample(
|
||||||
name+"_sum", metric, "", "",
|
w, name, "_sum", metric, "", 0,
|
||||||
metric.Summary.GetSampleSum(),
|
metric.Summary.GetSampleSum(),
|
||||||
out,
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
|
||||||
return written, err
|
|
||||||
}
|
|
||||||
written += n
|
written += n
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
n, err = writeSample(
|
n, err = writeSample(
|
||||||
name+"_count", metric, "", "",
|
w, name, "_count", metric, "", 0,
|
||||||
float64(metric.Summary.GetSampleCount()),
|
float64(metric.Summary.GetSampleCount()),
|
||||||
out,
|
|
||||||
)
|
)
|
||||||
case dto.MetricType_HISTOGRAM:
|
case dto.MetricType_HISTOGRAM:
|
||||||
if metric.Histogram == nil {
|
if metric.Histogram == nil {
|
||||||
|
@ -140,46 +219,42 @@ func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (int, error) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
infSeen := false
|
infSeen := false
|
||||||
for _, q := range metric.Histogram.Bucket {
|
for _, b := range metric.Histogram.Bucket {
|
||||||
n, err = writeSample(
|
n, err = writeSample(
|
||||||
name+"_bucket", metric,
|
w, name, "_bucket", metric,
|
||||||
model.BucketLabel, fmt.Sprint(q.GetUpperBound()),
|
model.BucketLabel, b.GetUpperBound(),
|
||||||
float64(q.GetCumulativeCount()),
|
float64(b.GetCumulativeCount()),
|
||||||
out,
|
|
||||||
)
|
)
|
||||||
written += n
|
written += n
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return written, err
|
return
|
||||||
}
|
}
|
||||||
if math.IsInf(q.GetUpperBound(), +1) {
|
if math.IsInf(b.GetUpperBound(), +1) {
|
||||||
infSeen = true
|
infSeen = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !infSeen {
|
if !infSeen {
|
||||||
n, err = writeSample(
|
n, err = writeSample(
|
||||||
name+"_bucket", metric,
|
w, name, "_bucket", metric,
|
||||||
model.BucketLabel, "+Inf",
|
model.BucketLabel, math.Inf(+1),
|
||||||
float64(metric.Histogram.GetSampleCount()),
|
float64(metric.Histogram.GetSampleCount()),
|
||||||
out,
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
|
||||||
return written, err
|
|
||||||
}
|
|
||||||
written += n
|
written += n
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
n, err = writeSample(
|
n, err = writeSample(
|
||||||
name+"_sum", metric, "", "",
|
w, name, "_sum", metric, "", 0,
|
||||||
metric.Histogram.GetSampleSum(),
|
metric.Histogram.GetSampleSum(),
|
||||||
out,
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
|
||||||
return written, err
|
|
||||||
}
|
|
||||||
written += n
|
written += n
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
n, err = writeSample(
|
n, err = writeSample(
|
||||||
name+"_count", metric, "", "",
|
w, name, "_count", metric, "", 0,
|
||||||
float64(metric.Histogram.GetSampleCount()),
|
float64(metric.Histogram.GetSampleCount()),
|
||||||
out,
|
|
||||||
)
|
)
|
||||||
default:
|
default:
|
||||||
return written, fmt.Errorf(
|
return written, fmt.Errorf(
|
||||||
|
@ -188,116 +263,203 @@ func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (int, error) {
|
||||||
}
|
}
|
||||||
written += n
|
written += n
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return written, err
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return written, nil
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// writeSample writes a single sample in text format to out, given the metric
|
// writeSample writes a single sample in text format to w, given the metric
|
||||||
// name, the metric proto message itself, optionally an additional label name
|
// name, the metric proto message itself, optionally an additional label name
|
||||||
// and value (use empty strings if not required), and the value. The function
|
// with a float64 value (use empty string as label name if not required), and
|
||||||
// returns the number of bytes written and any error encountered.
|
// the value. The function returns the number of bytes written and any error
|
||||||
|
// encountered.
|
||||||
func writeSample(
|
func writeSample(
|
||||||
name string,
|
w enhancedWriter,
|
||||||
|
name, suffix string,
|
||||||
metric *dto.Metric,
|
metric *dto.Metric,
|
||||||
additionalLabelName, additionalLabelValue string,
|
additionalLabelName string, additionalLabelValue float64,
|
||||||
value float64,
|
value float64,
|
||||||
out io.Writer,
|
|
||||||
) (int, error) {
|
) (int, error) {
|
||||||
var written int
|
var written int
|
||||||
n, err := fmt.Fprint(out, name)
|
n, err := w.WriteString(name)
|
||||||
written += n
|
written += n
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return written, err
|
return written, err
|
||||||
}
|
}
|
||||||
n, err = labelPairsToText(
|
if suffix != "" {
|
||||||
metric.Label,
|
n, err = w.WriteString(suffix)
|
||||||
additionalLabelName, additionalLabelValue,
|
|
||||||
out,
|
|
||||||
)
|
|
||||||
written += n
|
|
||||||
if err != nil {
|
|
||||||
return written, err
|
|
||||||
}
|
|
||||||
n, err = fmt.Fprintf(out, " %v", value)
|
|
||||||
written += n
|
|
||||||
if err != nil {
|
|
||||||
return written, err
|
|
||||||
}
|
|
||||||
if metric.TimestampMs != nil {
|
|
||||||
n, err = fmt.Fprintf(out, " %v", *metric.TimestampMs)
|
|
||||||
written += n
|
written += n
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return written, err
|
return written, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
n, err = out.Write([]byte{'\n'})
|
n, err = writeLabelPairs(
|
||||||
|
w, metric.Label, additionalLabelName, additionalLabelValue,
|
||||||
|
)
|
||||||
written += n
|
written += n
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return written, err
|
return written, err
|
||||||
}
|
}
|
||||||
|
err = w.WriteByte(' ')
|
||||||
|
written++
|
||||||
|
if err != nil {
|
||||||
|
return written, err
|
||||||
|
}
|
||||||
|
n, err = writeFloat(w, value)
|
||||||
|
written += n
|
||||||
|
if err != nil {
|
||||||
|
return written, err
|
||||||
|
}
|
||||||
|
if metric.TimestampMs != nil {
|
||||||
|
err = w.WriteByte(' ')
|
||||||
|
written++
|
||||||
|
if err != nil {
|
||||||
|
return written, err
|
||||||
|
}
|
||||||
|
n, err = writeInt(w, *metric.TimestampMs)
|
||||||
|
written += n
|
||||||
|
if err != nil {
|
||||||
|
return written, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = w.WriteByte('\n')
|
||||||
|
written++
|
||||||
|
if err != nil {
|
||||||
|
return written, err
|
||||||
|
}
|
||||||
return written, nil
|
return written, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// labelPairsToText converts a slice of LabelPair proto messages plus the
|
// writeLabelPairs converts a slice of LabelPair proto messages plus the
|
||||||
// explicitly given additional label pair into text formatted as required by the
|
// explicitly given additional label pair into text formatted as required by the
|
||||||
// text format and writes it to 'out'. An empty slice in combination with an
|
// text format and writes it to 'w'. An empty slice in combination with an empty
|
||||||
// empty string 'additionalLabelName' results in nothing being
|
// string 'additionalLabelName' results in nothing being written. Otherwise, the
|
||||||
// written. Otherwise, the label pairs are written, escaped as required by the
|
// label pairs are written, escaped as required by the text format, and enclosed
|
||||||
// text format, and enclosed in '{...}'. The function returns the number of
|
// in '{...}'. The function returns the number of bytes written and any error
|
||||||
// bytes written and any error encountered.
|
// encountered.
|
||||||
func labelPairsToText(
|
func writeLabelPairs(
|
||||||
|
w enhancedWriter,
|
||||||
in []*dto.LabelPair,
|
in []*dto.LabelPair,
|
||||||
additionalLabelName, additionalLabelValue string,
|
additionalLabelName string, additionalLabelValue float64,
|
||||||
out io.Writer,
|
|
||||||
) (int, error) {
|
) (int, error) {
|
||||||
if len(in) == 0 && additionalLabelName == "" {
|
if len(in) == 0 && additionalLabelName == "" {
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
var written int
|
var (
|
||||||
separator := '{'
|
written int
|
||||||
|
separator byte = '{'
|
||||||
|
)
|
||||||
for _, lp := range in {
|
for _, lp := range in {
|
||||||
n, err := fmt.Fprintf(
|
err := w.WriteByte(separator)
|
||||||
out, `%c%s="%s"`,
|
written++
|
||||||
separator, lp.GetName(), escapeString(lp.GetValue(), true),
|
if err != nil {
|
||||||
)
|
return written, err
|
||||||
|
}
|
||||||
|
n, err := w.WriteString(lp.GetName())
|
||||||
written += n
|
written += n
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return written, err
|
return written, err
|
||||||
}
|
}
|
||||||
|
n, err = w.WriteString(`="`)
|
||||||
|
written += n
|
||||||
|
if err != nil {
|
||||||
|
return written, err
|
||||||
|
}
|
||||||
|
n, err = writeEscapedString(w, lp.GetValue(), true)
|
||||||
|
written += n
|
||||||
|
if err != nil {
|
||||||
|
return written, err
|
||||||
|
}
|
||||||
|
err = w.WriteByte('"')
|
||||||
|
written++
|
||||||
|
if err != nil {
|
||||||
|
return written, err
|
||||||
|
}
|
||||||
separator = ','
|
separator = ','
|
||||||
}
|
}
|
||||||
if additionalLabelName != "" {
|
if additionalLabelName != "" {
|
||||||
n, err := fmt.Fprintf(
|
err := w.WriteByte(separator)
|
||||||
out, `%c%s="%s"`,
|
written++
|
||||||
separator, additionalLabelName,
|
if err != nil {
|
||||||
escapeString(additionalLabelValue, true),
|
return written, err
|
||||||
)
|
}
|
||||||
|
n, err := w.WriteString(additionalLabelName)
|
||||||
written += n
|
written += n
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return written, err
|
return written, err
|
||||||
}
|
}
|
||||||
|
n, err = w.WriteString(`="`)
|
||||||
|
written += n
|
||||||
|
if err != nil {
|
||||||
|
return written, err
|
||||||
|
}
|
||||||
|
n, err = writeFloat(w, additionalLabelValue)
|
||||||
|
written += n
|
||||||
|
if err != nil {
|
||||||
|
return written, err
|
||||||
|
}
|
||||||
|
err = w.WriteByte('"')
|
||||||
|
written++
|
||||||
|
if err != nil {
|
||||||
|
return written, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
n, err := out.Write([]byte{'}'})
|
err := w.WriteByte('}')
|
||||||
written += n
|
written++
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return written, err
|
return written, err
|
||||||
}
|
}
|
||||||
return written, nil
|
return written, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// writeEscapedString replaces '\' by '\\', new line character by '\n', and - if
|
||||||
|
// includeDoubleQuote is true - '"' by '\"'.
|
||||||
var (
|
var (
|
||||||
escape = strings.NewReplacer("\\", `\\`, "\n", `\n`)
|
escaper = strings.NewReplacer("\\", `\\`, "\n", `\n`)
|
||||||
escapeWithDoubleQuote = strings.NewReplacer("\\", `\\`, "\n", `\n`, "\"", `\"`)
|
quotedEscaper = strings.NewReplacer("\\", `\\`, "\n", `\n`, "\"", `\"`)
|
||||||
)
|
)
|
||||||
|
|
||||||
// escapeString replaces '\' by '\\', new line character by '\n', and - if
|
func writeEscapedString(w enhancedWriter, v string, includeDoubleQuote bool) (int, error) {
|
||||||
// includeDoubleQuote is true - '"' by '\"'.
|
|
||||||
func escapeString(v string, includeDoubleQuote bool) string {
|
|
||||||
if includeDoubleQuote {
|
if includeDoubleQuote {
|
||||||
return escapeWithDoubleQuote.Replace(v)
|
return quotedEscaper.WriteString(w, v)
|
||||||
}
|
}
|
||||||
|
return escaper.WriteString(w, v)
|
||||||
return escape.Replace(v)
|
}
|
||||||
|
|
||||||
|
// writeFloat is equivalent to fmt.Fprint with a float64 argument but hardcodes
|
||||||
|
// a few common cases for increased efficiency. For non-hardcoded cases, it uses
|
||||||
|
// strconv.AppendFloat to avoid allocations, similar to writeInt.
|
||||||
|
func writeFloat(w enhancedWriter, f float64) (int, error) {
|
||||||
|
switch {
|
||||||
|
case f == 1:
|
||||||
|
return 1, w.WriteByte('1')
|
||||||
|
case f == 0:
|
||||||
|
return 1, w.WriteByte('0')
|
||||||
|
case f == -1:
|
||||||
|
return w.WriteString("-1")
|
||||||
|
case math.IsNaN(f):
|
||||||
|
return w.WriteString("NaN")
|
||||||
|
case math.IsInf(f, +1):
|
||||||
|
return w.WriteString("+Inf")
|
||||||
|
case math.IsInf(f, -1):
|
||||||
|
return w.WriteString("-Inf")
|
||||||
|
default:
|
||||||
|
bp := numBufPool.Get().(*[]byte)
|
||||||
|
*bp = strconv.AppendFloat((*bp)[:0], f, 'g', -1, 64)
|
||||||
|
written, err := w.Write(*bp)
|
||||||
|
numBufPool.Put(bp)
|
||||||
|
return written, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeInt is equivalent to fmt.Fprint with an int64 argument but uses
|
||||||
|
// strconv.AppendInt with a byte slice taken from a sync.Pool to avoid
|
||||||
|
// allocations.
|
||||||
|
func writeInt(w enhancedWriter, i int64) (int, error) {
|
||||||
|
bp := numBufPool.Get().(*[]byte)
|
||||||
|
*bp = strconv.AppendInt((*bp)[:0], i, 10)
|
||||||
|
written, err := w.Write(*bp)
|
||||||
|
numBufPool.Put(bp)
|
||||||
|
return written, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -325,7 +325,7 @@ func (p *TextParser) startLabelValue() stateFn {
|
||||||
// - Other labels have to be added to currentLabels for signature calculation.
|
// - Other labels have to be added to currentLabels for signature calculation.
|
||||||
if p.currentMF.GetType() == dto.MetricType_SUMMARY {
|
if p.currentMF.GetType() == dto.MetricType_SUMMARY {
|
||||||
if p.currentLabelPair.GetName() == model.QuantileLabel {
|
if p.currentLabelPair.GetName() == model.QuantileLabel {
|
||||||
if p.currentQuantile, p.err = strconv.ParseFloat(p.currentLabelPair.GetValue(), 64); p.err != nil {
|
if p.currentQuantile, p.err = parseFloat(p.currentLabelPair.GetValue()); p.err != nil {
|
||||||
// Create a more helpful error message.
|
// Create a more helpful error message.
|
||||||
p.parseError(fmt.Sprintf("expected float as value for 'quantile' label, got %q", p.currentLabelPair.GetValue()))
|
p.parseError(fmt.Sprintf("expected float as value for 'quantile' label, got %q", p.currentLabelPair.GetValue()))
|
||||||
return nil
|
return nil
|
||||||
|
@ -337,7 +337,7 @@ func (p *TextParser) startLabelValue() stateFn {
|
||||||
// Similar special treatment of histograms.
|
// Similar special treatment of histograms.
|
||||||
if p.currentMF.GetType() == dto.MetricType_HISTOGRAM {
|
if p.currentMF.GetType() == dto.MetricType_HISTOGRAM {
|
||||||
if p.currentLabelPair.GetName() == model.BucketLabel {
|
if p.currentLabelPair.GetName() == model.BucketLabel {
|
||||||
if p.currentBucket, p.err = strconv.ParseFloat(p.currentLabelPair.GetValue(), 64); p.err != nil {
|
if p.currentBucket, p.err = parseFloat(p.currentLabelPair.GetValue()); p.err != nil {
|
||||||
// Create a more helpful error message.
|
// Create a more helpful error message.
|
||||||
p.parseError(fmt.Sprintf("expected float as value for 'le' label, got %q", p.currentLabelPair.GetValue()))
|
p.parseError(fmt.Sprintf("expected float as value for 'le' label, got %q", p.currentLabelPair.GetValue()))
|
||||||
return nil
|
return nil
|
||||||
|
@ -359,7 +359,7 @@ func (p *TextParser) startLabelValue() stateFn {
|
||||||
}
|
}
|
||||||
return p.readingValue
|
return p.readingValue
|
||||||
default:
|
default:
|
||||||
p.parseError(fmt.Sprintf("unexpected end of label value %q", p.currentLabelPair.Value))
|
p.parseError(fmt.Sprintf("unexpected end of label value %q", p.currentLabelPair.GetValue()))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -392,7 +392,7 @@ func (p *TextParser) readingValue() stateFn {
|
||||||
if p.readTokenUntilWhitespace(); p.err != nil {
|
if p.readTokenUntilWhitespace(); p.err != nil {
|
||||||
return nil // Unexpected end of input.
|
return nil // Unexpected end of input.
|
||||||
}
|
}
|
||||||
value, err := strconv.ParseFloat(p.currentToken.String(), 64)
|
value, err := parseFloat(p.currentToken.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Create a more helpful error message.
|
// Create a more helpful error message.
|
||||||
p.parseError(fmt.Sprintf("expected float as value, got %q", p.currentToken.String()))
|
p.parseError(fmt.Sprintf("expected float as value, got %q", p.currentToken.String()))
|
||||||
|
@ -755,3 +755,10 @@ func histogramMetricName(name string) string {
|
||||||
return name
|
return name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseFloat(s string) (float64, error) {
|
||||||
|
if strings.ContainsAny(s, "pP_") {
|
||||||
|
return 0, fmt.Errorf("unsupported character in float")
|
||||||
|
}
|
||||||
|
return strconv.ParseFloat(s, 64)
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
module github.com/prometheus/common
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect
|
||||||
|
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4 // indirect
|
||||||
|
github.com/go-kit/kit v0.9.0
|
||||||
|
github.com/go-logfmt/logfmt v0.4.0 // indirect
|
||||||
|
github.com/golang/protobuf v1.3.2
|
||||||
|
github.com/julienschmidt/httprouter v1.2.0
|
||||||
|
github.com/matttproud/golang_protobuf_extensions v1.0.1
|
||||||
|
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223
|
||||||
|
github.com/pkg/errors v0.8.1
|
||||||
|
github.com/prometheus/client_golang v1.0.0
|
||||||
|
github.com/prometheus/client_model v0.2.0
|
||||||
|
github.com/sirupsen/logrus v1.4.2
|
||||||
|
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980 // indirect
|
||||||
|
golang.org/x/sys v0.0.0-20190422165155-953cdadca894
|
||||||
|
gopkg.in/alecthomas/kingpin.v2 v2.2.6
|
||||||
|
gopkg.in/yaml.v2 v2.2.4
|
||||||
|
)
|
||||||
|
|
||||||
|
go 1.11
|
6
vendor/github.com/prometheus/common/internal/bitbucket.org/ww/goautoneg/autoneg.go
generated
vendored
6
vendor/github.com/prometheus/common/internal/bitbucket.org/ww/goautoneg/autoneg.go
generated
vendored
|
@ -1,12 +1,12 @@
|
||||||
/*
|
/*
|
||||||
|
Copyright (c) 2011, Open Knowledge Foundation Ltd.
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
HTTP Content-Type Autonegotiation.
|
HTTP Content-Type Autonegotiation.
|
||||||
|
|
||||||
The functions in this package implement the behaviour specified in
|
The functions in this package implement the behaviour specified in
|
||||||
http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
|
http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
|
||||||
|
|
||||||
Copyright (c) 2011, Open Knowledge Foundation Ltd.
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
modification, are permitted provided that the following conditions are
|
modification, are permitted provided that the following conditions are
|
||||||
met:
|
met:
|
||||||
|
|
|
@ -21,7 +21,6 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
separator = []byte{0}
|
|
||||||
// MetricNameRE is a regular expression matching valid metric
|
// MetricNameRE is a regular expression matching valid metric
|
||||||
// names. Note that the IsValidMetricName function performs the same
|
// names. Note that the IsValidMetricName function performs the same
|
||||||
// check but faster than a match with this regular expression.
|
// check but faster than a match with this regular expression.
|
||||||
|
|
|
@ -43,7 +43,7 @@ const (
|
||||||
// (1970-01-01 00:00 UTC) excluding leap seconds.
|
// (1970-01-01 00:00 UTC) excluding leap seconds.
|
||||||
type Time int64
|
type Time int64
|
||||||
|
|
||||||
// Interval describes and interval between two timestamps.
|
// Interval describes an interval between two timestamps.
|
||||||
type Interval struct {
|
type Interval struct {
|
||||||
Start, End Time
|
Start, End Time
|
||||||
}
|
}
|
||||||
|
@ -150,7 +150,13 @@ func (t *Time) UnmarshalJSON(b []byte) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
*t = Time(v + va)
|
// If the value was something like -0.1 the negative is lost in the
|
||||||
|
// parsing because of the leading zero, this ensures that we capture it.
|
||||||
|
if len(p[0]) > 0 && p[0][0] == '-' && v+va > 0 {
|
||||||
|
*t = Time(v+va) * -1
|
||||||
|
} else {
|
||||||
|
*t = Time(v + va)
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("invalid time %q", string(b))
|
return fmt.Errorf("invalid time %q", string(b))
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# procfs
|
# procfs
|
||||||
|
|
||||||
This procfs package provides functions to retrieve system, kernel and process
|
This package provides functions to retrieve system, kernel, and process
|
||||||
metrics from the pseudo-filesystem proc.
|
metrics from the pseudo-filesystems /proc and /sys.
|
||||||
|
|
||||||
*WARNING*: This package is a work in progress. Its API may still break in
|
*WARNING*: This package is a work in progress. Its API may still break in
|
||||||
backwards-incompatible ways without warnings. Use it at your own risk.
|
backwards-incompatible ways without warnings. Use it at your own risk.
|
||||||
|
@ -9,3 +9,53 @@ backwards-incompatible ways without warnings. Use it at your own risk.
|
||||||
[![GoDoc](https://godoc.org/github.com/prometheus/procfs?status.png)](https://godoc.org/github.com/prometheus/procfs)
|
[![GoDoc](https://godoc.org/github.com/prometheus/procfs?status.png)](https://godoc.org/github.com/prometheus/procfs)
|
||||||
[![Build Status](https://travis-ci.org/prometheus/procfs.svg?branch=master)](https://travis-ci.org/prometheus/procfs)
|
[![Build Status](https://travis-ci.org/prometheus/procfs.svg?branch=master)](https://travis-ci.org/prometheus/procfs)
|
||||||
[![Go Report Card](https://goreportcard.com/badge/github.com/prometheus/procfs)](https://goreportcard.com/report/github.com/prometheus/procfs)
|
[![Go Report Card](https://goreportcard.com/badge/github.com/prometheus/procfs)](https://goreportcard.com/report/github.com/prometheus/procfs)
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
The procfs library is organized by packages based on whether the gathered data is coming from
|
||||||
|
/proc, /sys, or both. Each package contains an `FS` type which represents the path to either /proc,
|
||||||
|
/sys, or both. For example, cpu statistics are gathered from
|
||||||
|
`/proc/stat` and are available via the root procfs package. First, the proc filesystem mount
|
||||||
|
point is initialized, and then the stat information is read.
|
||||||
|
|
||||||
|
```go
|
||||||
|
fs, err := procfs.NewFS("/proc")
|
||||||
|
stats, err := fs.Stat()
|
||||||
|
```
|
||||||
|
|
||||||
|
Some sub-packages such as `blockdevice`, require access to both the proc and sys filesystems.
|
||||||
|
|
||||||
|
```go
|
||||||
|
fs, err := blockdevice.NewFS("/proc", "/sys")
|
||||||
|
stats, err := fs.ProcDiskstats()
|
||||||
|
```
|
||||||
|
|
||||||
|
## Package Organization
|
||||||
|
|
||||||
|
The packages in this project are organized according to (1) whether the data comes from the `/proc` or
|
||||||
|
`/sys` filesystem and (2) the type of information being retrieved. For example, most process information
|
||||||
|
can be gathered from the functions in the root `procfs` package. Information about block devices such as disk drives
|
||||||
|
is available in the `blockdevices` sub-package.
|
||||||
|
|
||||||
|
## Building and Testing
|
||||||
|
|
||||||
|
The procfs library is intended to be built as part of another application, so there are no distributable binaries.
|
||||||
|
However, most of the API includes unit tests which can be run with `make test`.
|
||||||
|
|
||||||
|
### Updating Test Fixtures
|
||||||
|
|
||||||
|
The procfs library includes a set of test fixtures which include many example files from
|
||||||
|
the `/proc` and `/sys` filesystems. These fixtures are included as a [ttar](https://github.com/ideaship/ttar) file
|
||||||
|
which is extracted automatically during testing. To add/update the test fixtures, first
|
||||||
|
ensure the `fixtures` directory is up to date by removing the existing directory and then
|
||||||
|
extracting the ttar file using `make fixtures/.unpacked` or just `make test`.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rm -rf fixtures
|
||||||
|
make test
|
||||||
|
```
|
||||||
|
|
||||||
|
Next, make the required changes to the extracted files in the `fixtures` directory. When
|
||||||
|
the changes are complete, run `make update_fixtures` to create a new `fixtures.ttar` file
|
||||||
|
based on the updated `fixtures` directory. And finally, verify the changes using
|
||||||
|
`git diff fixtures.ttar`.
|
||||||
|
|
|
@ -0,0 +1,85 @@
|
||||||
|
// Copyright 2019 The Prometheus Authors
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package procfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ARPEntry contains a single row of the columnar data represented in
|
||||||
|
// /proc/net/arp.
|
||||||
|
type ARPEntry struct {
|
||||||
|
// IP address
|
||||||
|
IPAddr net.IP
|
||||||
|
// MAC address
|
||||||
|
HWAddr net.HardwareAddr
|
||||||
|
// Name of the device
|
||||||
|
Device string
|
||||||
|
}
|
||||||
|
|
||||||
|
// GatherARPEntries retrieves all the ARP entries, parse the relevant columns,
|
||||||
|
// and then return a slice of ARPEntry's.
|
||||||
|
func (fs FS) GatherARPEntries() ([]ARPEntry, error) {
|
||||||
|
data, err := ioutil.ReadFile(fs.proc.Path("net/arp"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error reading arp %s: %s", fs.proc.Path("net/arp"), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseARPEntries(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseARPEntries(data []byte) ([]ARPEntry, error) {
|
||||||
|
lines := strings.Split(string(data), "\n")
|
||||||
|
entries := make([]ARPEntry, 0)
|
||||||
|
var err error
|
||||||
|
const (
|
||||||
|
expectedDataWidth = 6
|
||||||
|
expectedHeaderWidth = 9
|
||||||
|
)
|
||||||
|
for _, line := range lines {
|
||||||
|
columns := strings.Fields(line)
|
||||||
|
width := len(columns)
|
||||||
|
|
||||||
|
if width == expectedHeaderWidth || width == 0 {
|
||||||
|
continue
|
||||||
|
} else if width == expectedDataWidth {
|
||||||
|
entry, err := parseARPEntry(columns)
|
||||||
|
if err != nil {
|
||||||
|
return []ARPEntry{}, fmt.Errorf("failed to parse ARP entry: %s", err)
|
||||||
|
}
|
||||||
|
entries = append(entries, entry)
|
||||||
|
} else {
|
||||||
|
return []ARPEntry{}, fmt.Errorf("%d columns were detected, but %d were expected", width, expectedDataWidth)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return entries, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseARPEntry(columns []string) (ARPEntry, error) {
|
||||||
|
ip := net.ParseIP(columns[0])
|
||||||
|
mac := net.HardwareAddr(columns[3])
|
||||||
|
|
||||||
|
entry := ARPEntry{
|
||||||
|
IPAddr: ip,
|
||||||
|
HWAddr: mac,
|
||||||
|
Device: columns[5],
|
||||||
|
}
|
||||||
|
|
||||||
|
return entry, nil
|
||||||
|
}
|
|
@ -31,19 +31,9 @@ type BuddyInfo struct {
|
||||||
Sizes []float64
|
Sizes []float64
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBuddyInfo reads the buddyinfo statistics.
|
// BuddyInfo reads the buddyinfo statistics from the specified `proc` filesystem.
|
||||||
func NewBuddyInfo() ([]BuddyInfo, error) {
|
func (fs FS) BuddyInfo() ([]BuddyInfo, error) {
|
||||||
fs, err := NewFS(DefaultMountPoint)
|
file, err := os.Open(fs.proc.Path("buddyinfo"))
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return fs.NewBuddyInfo()
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewBuddyInfo reads the buddyinfo statistics from the specified `proc` filesystem.
|
|
||||||
func (fs FS) NewBuddyInfo() ([]BuddyInfo, error) {
|
|
||||||
file, err := os.Open(fs.Path("buddyinfo"))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,167 @@
|
||||||
|
// Copyright 2019 The Prometheus Authors
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package procfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/prometheus/procfs/internal/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CPUInfo contains general information about a system CPU found in /proc/cpuinfo
|
||||||
|
type CPUInfo struct {
|
||||||
|
Processor uint
|
||||||
|
VendorID string
|
||||||
|
CPUFamily string
|
||||||
|
Model string
|
||||||
|
ModelName string
|
||||||
|
Stepping string
|
||||||
|
Microcode string
|
||||||
|
CPUMHz float64
|
||||||
|
CacheSize string
|
||||||
|
PhysicalID string
|
||||||
|
Siblings uint
|
||||||
|
CoreID string
|
||||||
|
CPUCores uint
|
||||||
|
APICID string
|
||||||
|
InitialAPICID string
|
||||||
|
FPU string
|
||||||
|
FPUException string
|
||||||
|
CPUIDLevel uint
|
||||||
|
WP string
|
||||||
|
Flags []string
|
||||||
|
Bugs []string
|
||||||
|
BogoMips float64
|
||||||
|
CLFlushSize uint
|
||||||
|
CacheAlignment uint
|
||||||
|
AddressSizes string
|
||||||
|
PowerManagement string
|
||||||
|
}
|
||||||
|
|
||||||
|
// CPUInfo returns information about current system CPUs.
|
||||||
|
// See https://www.kernel.org/doc/Documentation/filesystems/proc.txt
|
||||||
|
func (fs FS) CPUInfo() ([]CPUInfo, error) {
|
||||||
|
data, err := util.ReadFileNoStat(fs.proc.Path("cpuinfo"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return parseCPUInfo(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseCPUInfo parses data from /proc/cpuinfo
|
||||||
|
func parseCPUInfo(info []byte) ([]CPUInfo, error) {
|
||||||
|
cpuinfo := []CPUInfo{}
|
||||||
|
i := -1
|
||||||
|
scanner := bufio.NewScanner(bytes.NewReader(info))
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := scanner.Text()
|
||||||
|
if strings.TrimSpace(line) == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
field := strings.SplitN(line, ": ", 2)
|
||||||
|
switch strings.TrimSpace(field[0]) {
|
||||||
|
case "processor":
|
||||||
|
cpuinfo = append(cpuinfo, CPUInfo{}) // start of the next processor
|
||||||
|
i++
|
||||||
|
v, err := strconv.ParseUint(field[1], 0, 32)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cpuinfo[i].Processor = uint(v)
|
||||||
|
case "vendor_id":
|
||||||
|
cpuinfo[i].VendorID = field[1]
|
||||||
|
case "cpu family":
|
||||||
|
cpuinfo[i].CPUFamily = field[1]
|
||||||
|
case "model":
|
||||||
|
cpuinfo[i].Model = field[1]
|
||||||
|
case "model name":
|
||||||
|
cpuinfo[i].ModelName = field[1]
|
||||||
|
case "stepping":
|
||||||
|
cpuinfo[i].Stepping = field[1]
|
||||||
|
case "microcode":
|
||||||
|
cpuinfo[i].Microcode = field[1]
|
||||||
|
case "cpu MHz":
|
||||||
|
v, err := strconv.ParseFloat(field[1], 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cpuinfo[i].CPUMHz = v
|
||||||
|
case "cache size":
|
||||||
|
cpuinfo[i].CacheSize = field[1]
|
||||||
|
case "physical id":
|
||||||
|
cpuinfo[i].PhysicalID = field[1]
|
||||||
|
case "siblings":
|
||||||
|
v, err := strconv.ParseUint(field[1], 0, 32)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cpuinfo[i].Siblings = uint(v)
|
||||||
|
case "core id":
|
||||||
|
cpuinfo[i].CoreID = field[1]
|
||||||
|
case "cpu cores":
|
||||||
|
v, err := strconv.ParseUint(field[1], 0, 32)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cpuinfo[i].CPUCores = uint(v)
|
||||||
|
case "apicid":
|
||||||
|
cpuinfo[i].APICID = field[1]
|
||||||
|
case "initial apicid":
|
||||||
|
cpuinfo[i].InitialAPICID = field[1]
|
||||||
|
case "fpu":
|
||||||
|
cpuinfo[i].FPU = field[1]
|
||||||
|
case "fpu_exception":
|
||||||
|
cpuinfo[i].FPUException = field[1]
|
||||||
|
case "cpuid level":
|
||||||
|
v, err := strconv.ParseUint(field[1], 0, 32)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cpuinfo[i].CPUIDLevel = uint(v)
|
||||||
|
case "wp":
|
||||||
|
cpuinfo[i].WP = field[1]
|
||||||
|
case "flags":
|
||||||
|
cpuinfo[i].Flags = strings.Fields(field[1])
|
||||||
|
case "bugs":
|
||||||
|
cpuinfo[i].Bugs = strings.Fields(field[1])
|
||||||
|
case "bogomips":
|
||||||
|
v, err := strconv.ParseFloat(field[1], 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cpuinfo[i].BogoMips = v
|
||||||
|
case "clflush size":
|
||||||
|
v, err := strconv.ParseUint(field[1], 0, 32)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cpuinfo[i].CLFlushSize = uint(v)
|
||||||
|
case "cache_alignment":
|
||||||
|
v, err := strconv.ParseUint(field[1], 0, 32)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cpuinfo[i].CacheAlignment = uint(v)
|
||||||
|
case "address sizes":
|
||||||
|
cpuinfo[i].AddressSizes = field[1]
|
||||||
|
case "power management":
|
||||||
|
cpuinfo[i].PowerManagement = field[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cpuinfo, nil
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,153 @@
|
||||||
|
// Copyright 2019 The Prometheus Authors
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package procfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/prometheus/procfs/internal/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Crypto holds info parsed from /proc/crypto.
|
||||||
|
type Crypto struct {
|
||||||
|
Alignmask *uint64
|
||||||
|
Async bool
|
||||||
|
Blocksize *uint64
|
||||||
|
Chunksize *uint64
|
||||||
|
Ctxsize *uint64
|
||||||
|
Digestsize *uint64
|
||||||
|
Driver string
|
||||||
|
Geniv string
|
||||||
|
Internal string
|
||||||
|
Ivsize *uint64
|
||||||
|
Maxauthsize *uint64
|
||||||
|
MaxKeysize *uint64
|
||||||
|
MinKeysize *uint64
|
||||||
|
Module string
|
||||||
|
Name string
|
||||||
|
Priority *int64
|
||||||
|
Refcnt *int64
|
||||||
|
Seedsize *uint64
|
||||||
|
Selftest string
|
||||||
|
Type string
|
||||||
|
Walksize *uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// Crypto parses an crypto-file (/proc/crypto) and returns a slice of
|
||||||
|
// structs containing the relevant info. More information available here:
|
||||||
|
// https://kernel.readthedocs.io/en/sphinx-samples/crypto-API.html
|
||||||
|
func (fs FS) Crypto() ([]Crypto, error) {
|
||||||
|
path := fs.proc.Path("crypto")
|
||||||
|
b, err := util.ReadFileNoStat(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error reading crypto %s: %s", path, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
crypto, err := parseCrypto(bytes.NewReader(b))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error parsing crypto %s: %s", path, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return crypto, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseCrypto parses a /proc/crypto stream into Crypto elements.
|
||||||
|
func parseCrypto(r io.Reader) ([]Crypto, error) {
|
||||||
|
var out []Crypto
|
||||||
|
|
||||||
|
s := bufio.NewScanner(r)
|
||||||
|
for s.Scan() {
|
||||||
|
text := s.Text()
|
||||||
|
switch {
|
||||||
|
case strings.HasPrefix(text, "name"):
|
||||||
|
// Each crypto element begins with its name.
|
||||||
|
out = append(out, Crypto{})
|
||||||
|
case text == "":
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
kv := strings.Split(text, ":")
|
||||||
|
if len(kv) != 2 {
|
||||||
|
return nil, fmt.Errorf("malformed crypto line: %q", text)
|
||||||
|
}
|
||||||
|
|
||||||
|
k := strings.TrimSpace(kv[0])
|
||||||
|
v := strings.TrimSpace(kv[1])
|
||||||
|
|
||||||
|
// Parse the key/value pair into the currently focused element.
|
||||||
|
c := &out[len(out)-1]
|
||||||
|
if err := c.parseKV(k, v); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseKV parses a key/value pair into the appropriate field of c.
|
||||||
|
func (c *Crypto) parseKV(k, v string) error {
|
||||||
|
vp := util.NewValueParser(v)
|
||||||
|
|
||||||
|
switch k {
|
||||||
|
case "async":
|
||||||
|
// Interpret literal yes as true.
|
||||||
|
c.Async = v == "yes"
|
||||||
|
case "blocksize":
|
||||||
|
c.Blocksize = vp.PUInt64()
|
||||||
|
case "chunksize":
|
||||||
|
c.Chunksize = vp.PUInt64()
|
||||||
|
case "digestsize":
|
||||||
|
c.Digestsize = vp.PUInt64()
|
||||||
|
case "driver":
|
||||||
|
c.Driver = v
|
||||||
|
case "geniv":
|
||||||
|
c.Geniv = v
|
||||||
|
case "internal":
|
||||||
|
c.Internal = v
|
||||||
|
case "ivsize":
|
||||||
|
c.Ivsize = vp.PUInt64()
|
||||||
|
case "maxauthsize":
|
||||||
|
c.Maxauthsize = vp.PUInt64()
|
||||||
|
case "max keysize":
|
||||||
|
c.MaxKeysize = vp.PUInt64()
|
||||||
|
case "min keysize":
|
||||||
|
c.MinKeysize = vp.PUInt64()
|
||||||
|
case "module":
|
||||||
|
c.Module = v
|
||||||
|
case "name":
|
||||||
|
c.Name = v
|
||||||
|
case "priority":
|
||||||
|
c.Priority = vp.PInt64()
|
||||||
|
case "refcnt":
|
||||||
|
c.Refcnt = vp.PInt64()
|
||||||
|
case "seedsize":
|
||||||
|
c.Seedsize = vp.PUInt64()
|
||||||
|
case "selftest":
|
||||||
|
c.Selftest = v
|
||||||
|
case "type":
|
||||||
|
c.Type = v
|
||||||
|
case "walksize":
|
||||||
|
c.Walksize = vp.PUInt64()
|
||||||
|
}
|
||||||
|
|
||||||
|
return vp.Err()
|
||||||
|
}
|
|
@ -14,69 +14,30 @@
|
||||||
package procfs
|
package procfs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"github.com/prometheus/procfs/internal/fs"
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
|
|
||||||
"github.com/prometheus/procfs/nfs"
|
|
||||||
"github.com/prometheus/procfs/xfs"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// FS represents the pseudo-filesystem proc, which provides an interface to
|
// FS represents the pseudo-filesystem sys, which provides an interface to
|
||||||
// kernel data structures.
|
// kernel data structures.
|
||||||
type FS string
|
type FS struct {
|
||||||
|
proc fs.FS
|
||||||
|
}
|
||||||
|
|
||||||
// DefaultMountPoint is the common mount point of the proc filesystem.
|
// DefaultMountPoint is the common mount point of the proc filesystem.
|
||||||
const DefaultMountPoint = "/proc"
|
const DefaultMountPoint = fs.DefaultProcMountPoint
|
||||||
|
|
||||||
// NewFS returns a new FS mounted under the given mountPoint. It will error
|
// NewDefaultFS returns a new proc FS mounted under the default proc mountPoint.
|
||||||
// if the mount point can't be read.
|
// It will error if the mount point directory can't be read or is a file.
|
||||||
|
func NewDefaultFS() (FS, error) {
|
||||||
|
return NewFS(DefaultMountPoint)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFS returns a new proc FS mounted under the given proc mountPoint. It will error
|
||||||
|
// if the mount point directory can't be read or is a file.
|
||||||
func NewFS(mountPoint string) (FS, error) {
|
func NewFS(mountPoint string) (FS, error) {
|
||||||
info, err := os.Stat(mountPoint)
|
fs, err := fs.NewFS(mountPoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("could not read %s: %s", mountPoint, err)
|
return FS{}, err
|
||||||
}
|
}
|
||||||
if !info.IsDir() {
|
return FS{fs}, nil
|
||||||
return "", fmt.Errorf("mount point %s is not a directory", mountPoint)
|
|
||||||
}
|
|
||||||
|
|
||||||
return FS(mountPoint), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Path returns the path of the given subsystem relative to the procfs root.
|
|
||||||
func (fs FS) Path(p ...string) string {
|
|
||||||
return path.Join(append([]string{string(fs)}, p...)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// XFSStats retrieves XFS filesystem runtime statistics.
|
|
||||||
func (fs FS) XFSStats() (*xfs.Stats, error) {
|
|
||||||
f, err := os.Open(fs.Path("fs/xfs/stat"))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
return xfs.ParseStats(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NFSClientRPCStats retrieves NFS client RPC statistics.
|
|
||||||
func (fs FS) NFSClientRPCStats() (*nfs.ClientRPCStats, error) {
|
|
||||||
f, err := os.Open(fs.Path("net/rpc/nfs"))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
return nfs.ParseClientRPCStats(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NFSdServerRPCStats retrieves NFS daemon RPC statistics.
|
|
||||||
func (fs FS) NFSdServerRPCStats() (*nfs.ServerRPCStats, error) {
|
|
||||||
f, err := os.Open(fs.Path("net/rpc/nfsd"))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
return nfs.ParseServerRPCStats(f)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
module github.com/prometheus/procfs
|
||||||
|
|
||||||
|
go 1.12
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/google/go-cmp v0.3.1
|
||||||
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e
|
||||||
|
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e
|
||||||
|
)
|
|
@ -0,0 +1,55 @@
|
||||||
|
// Copyright 2019 The Prometheus Authors
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package fs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// DefaultProcMountPoint is the common mount point of the proc filesystem.
|
||||||
|
DefaultProcMountPoint = "/proc"
|
||||||
|
|
||||||
|
// DefaultSysMountPoint is the common mount point of the sys filesystem.
|
||||||
|
DefaultSysMountPoint = "/sys"
|
||||||
|
|
||||||
|
// DefaultConfigfsMountPoint is the common mount point of the configfs
|
||||||
|
DefaultConfigfsMountPoint = "/sys/kernel/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FS represents a pseudo-filesystem, normally /proc or /sys, which provides an
|
||||||
|
// interface to kernel data structures.
|
||||||
|
type FS string
|
||||||
|
|
||||||
|
// NewFS returns a new FS mounted under the given mountPoint. It will error
|
||||||
|
// if the mount point can't be read.
|
||||||
|
func NewFS(mountPoint string) (FS, error) {
|
||||||
|
info, err := os.Stat(mountPoint)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("could not read %s: %s", mountPoint, err)
|
||||||
|
}
|
||||||
|
if !info.IsDir() {
|
||||||
|
return "", fmt.Errorf("mount point %s is not a directory", mountPoint)
|
||||||
|
}
|
||||||
|
|
||||||
|
return FS(mountPoint), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Path appends the given path elements to the filesystem path, adding separators
|
||||||
|
// as necessary.
|
||||||
|
func (fs FS) Path(p ...string) string {
|
||||||
|
return filepath.Join(append([]string{string(fs)}, p...)...)
|
||||||
|
}
|
|
@ -13,7 +13,11 @@
|
||||||
|
|
||||||
package util
|
package util
|
||||||
|
|
||||||
import "strconv"
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
// ParseUint32s parses a slice of strings into a slice of uint32s.
|
// ParseUint32s parses a slice of strings into a slice of uint32s.
|
||||||
func ParseUint32s(ss []string) ([]uint32, error) {
|
func ParseUint32s(ss []string) ([]uint32, error) {
|
||||||
|
@ -44,3 +48,41 @@ func ParseUint64s(ss []string) ([]uint64, error) {
|
||||||
|
|
||||||
return us, nil
|
return us, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ParsePInt64s parses a slice of strings into a slice of int64 pointers.
|
||||||
|
func ParsePInt64s(ss []string) ([]*int64, error) {
|
||||||
|
us := make([]*int64, 0, len(ss))
|
||||||
|
for _, s := range ss {
|
||||||
|
u, err := strconv.ParseInt(s, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
us = append(us, &u)
|
||||||
|
}
|
||||||
|
|
||||||
|
return us, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadUintFromFile reads a file and attempts to parse a uint64 from it.
|
||||||
|
func ReadUintFromFile(path string) (uint64, error) {
|
||||||
|
data, err := ioutil.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return strconv.ParseUint(strings.TrimSpace(string(data)), 10, 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseBool parses a string into a boolean pointer.
|
||||||
|
func ParseBool(b string) *bool {
|
||||||
|
var truth bool
|
||||||
|
switch b {
|
||||||
|
case "enabled":
|
||||||
|
truth = true
|
||||||
|
case "disabled":
|
||||||
|
truth = false
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &truth
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
// Copyright 2019 The Prometheus Authors
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ReadFileNoStat uses ioutil.ReadAll to read contents of entire file.
|
||||||
|
// This is similar to ioutil.ReadFile but without the call to os.Stat, because
|
||||||
|
// many files in /proc and /sys report incorrect file sizes (either 0 or 4096).
|
||||||
|
// Reads a max file size of 512kB. For files larger than this, a scanner
|
||||||
|
// should be used.
|
||||||
|
func ReadFileNoStat(filename string) ([]byte, error) {
|
||||||
|
const maxBufferSize = 1024 * 512
|
||||||
|
|
||||||
|
f, err := os.Open(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
reader := io.LimitReader(f, maxBufferSize)
|
||||||
|
return ioutil.ReadAll(reader)
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
// Copyright 2018 The Prometheus Authors
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// +build linux,!appengine
|
||||||
|
|
||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SysReadFile is a simplified ioutil.ReadFile that invokes syscall.Read directly.
|
||||||
|
// https://github.com/prometheus/node_exporter/pull/728/files
|
||||||
|
//
|
||||||
|
// Note that this function will not read files larger than 128 bytes.
|
||||||
|
func SysReadFile(file string) (string, error) {
|
||||||
|
f, err := os.Open(file)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
// On some machines, hwmon drivers are broken and return EAGAIN. This causes
|
||||||
|
// Go's ioutil.ReadFile implementation to poll forever.
|
||||||
|
//
|
||||||
|
// Since we either want to read data or bail immediately, do the simplest
|
||||||
|
// possible read using syscall directly.
|
||||||
|
const sysFileBufferSize = 128
|
||||||
|
b := make([]byte, sysFileBufferSize)
|
||||||
|
n, err := syscall.Read(int(f.Fd()), b)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(bytes.TrimSpace(b[:n])), nil
|
||||||
|
}
|
26
vendor/github.com/prometheus/procfs/internal/util/sysreadfile_compat.go
generated
vendored
Normal file
26
vendor/github.com/prometheus/procfs/internal/util/sysreadfile_compat.go
generated
vendored
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
// Copyright 2019 The Prometheus Authors
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// +build linux,appengine !linux
|
||||||
|
|
||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SysReadFile is here implemented as a noop for builds that do not support
|
||||||
|
// the read syscall. For example Windows, or Linux on Google App Engine.
|
||||||
|
func SysReadFile(file string) (string, error) {
|
||||||
|
return "", fmt.Errorf("not supported on this platform")
|
||||||
|
}
|
|
@ -0,0 +1,91 @@
|
||||||
|
// Copyright 2019 The Prometheus Authors
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO(mdlayher): util packages are an anti-pattern and this should be moved
|
||||||
|
// somewhere else that is more focused in the future.
|
||||||
|
|
||||||
|
// A ValueParser enables parsing a single string into a variety of data types
|
||||||
|
// in a concise and safe way. The Err method must be invoked after invoking
|
||||||
|
// any other methods to ensure a value was successfully parsed.
|
||||||
|
type ValueParser struct {
|
||||||
|
v string
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewValueParser creates a ValueParser using the input string.
|
||||||
|
func NewValueParser(v string) *ValueParser {
|
||||||
|
return &ValueParser{v: v}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int interprets the underlying value as an int and returns that value.
|
||||||
|
func (vp *ValueParser) Int() int { return int(vp.int64()) }
|
||||||
|
|
||||||
|
// PInt64 interprets the underlying value as an int64 and returns a pointer to
|
||||||
|
// that value.
|
||||||
|
func (vp *ValueParser) PInt64() *int64 {
|
||||||
|
if vp.err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
v := vp.int64()
|
||||||
|
return &v
|
||||||
|
}
|
||||||
|
|
||||||
|
// int64 interprets the underlying value as an int64 and returns that value.
|
||||||
|
// TODO: export if/when necessary.
|
||||||
|
func (vp *ValueParser) int64() int64 {
|
||||||
|
if vp.err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// A base value of zero makes ParseInt infer the correct base using the
|
||||||
|
// string's prefix, if any.
|
||||||
|
const base = 0
|
||||||
|
v, err := strconv.ParseInt(vp.v, base, 64)
|
||||||
|
if err != nil {
|
||||||
|
vp.err = err
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// PUInt64 interprets the underlying value as an uint64 and returns a pointer to
|
||||||
|
// that value.
|
||||||
|
func (vp *ValueParser) PUInt64() *uint64 {
|
||||||
|
if vp.err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// A base value of zero makes ParseInt infer the correct base using the
|
||||||
|
// string's prefix, if any.
|
||||||
|
const base = 0
|
||||||
|
v, err := strconv.ParseUint(vp.v, base, 64)
|
||||||
|
if err != nil {
|
||||||
|
vp.err = err
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Err returns the last error, if any, encountered by the ValueParser.
|
||||||
|
func (vp *ValueParser) Err() error {
|
||||||
|
return vp.err
|
||||||
|
}
|
|
@ -15,6 +15,7 @@ package procfs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"bytes"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -24,6 +25,8 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/prometheus/procfs/internal/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// IPVSStats holds IPVS statistics, as exposed by the kernel in `/proc/net/ip_vs_stats`.
|
// IPVSStats holds IPVS statistics, as exposed by the kernel in `/proc/net/ip_vs_stats`.
|
||||||
|
@ -62,29 +65,18 @@ type IPVSBackendStatus struct {
|
||||||
Weight uint64
|
Weight uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewIPVSStats reads the IPVS statistics.
|
// IPVSStats reads the IPVS statistics from the specified `proc` filesystem.
|
||||||
func NewIPVSStats() (IPVSStats, error) {
|
func (fs FS) IPVSStats() (IPVSStats, error) {
|
||||||
fs, err := NewFS(DefaultMountPoint)
|
data, err := util.ReadFileNoStat(fs.proc.Path("net/ip_vs_stats"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return IPVSStats{}, err
|
return IPVSStats{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return fs.NewIPVSStats()
|
return parseIPVSStats(bytes.NewReader(data))
|
||||||
}
|
|
||||||
|
|
||||||
// NewIPVSStats reads the IPVS statistics from the specified `proc` filesystem.
|
|
||||||
func (fs FS) NewIPVSStats() (IPVSStats, error) {
|
|
||||||
file, err := os.Open(fs.Path("net/ip_vs_stats"))
|
|
||||||
if err != nil {
|
|
||||||
return IPVSStats{}, err
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
return parseIPVSStats(file)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseIPVSStats performs the actual parsing of `ip_vs_stats`.
|
// parseIPVSStats performs the actual parsing of `ip_vs_stats`.
|
||||||
func parseIPVSStats(file io.Reader) (IPVSStats, error) {
|
func parseIPVSStats(r io.Reader) (IPVSStats, error) {
|
||||||
var (
|
var (
|
||||||
statContent []byte
|
statContent []byte
|
||||||
statLines []string
|
statLines []string
|
||||||
|
@ -92,7 +84,7 @@ func parseIPVSStats(file io.Reader) (IPVSStats, error) {
|
||||||
stats IPVSStats
|
stats IPVSStats
|
||||||
)
|
)
|
||||||
|
|
||||||
statContent, err := ioutil.ReadAll(file)
|
statContent, err := ioutil.ReadAll(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return IPVSStats{}, err
|
return IPVSStats{}, err
|
||||||
}
|
}
|
||||||
|
@ -131,19 +123,9 @@ func parseIPVSStats(file io.Reader) (IPVSStats, error) {
|
||||||
return stats, nil
|
return stats, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewIPVSBackendStatus reads and returns the status of all (virtual,real) server pairs.
|
// IPVSBackendStatus reads and returns the status of all (virtual,real) server pairs from the specified `proc` filesystem.
|
||||||
func NewIPVSBackendStatus() ([]IPVSBackendStatus, error) {
|
func (fs FS) IPVSBackendStatus() ([]IPVSBackendStatus, error) {
|
||||||
fs, err := NewFS(DefaultMountPoint)
|
file, err := os.Open(fs.proc.Path("net/ip_vs"))
|
||||||
if err != nil {
|
|
||||||
return []IPVSBackendStatus{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return fs.NewIPVSBackendStatus()
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewIPVSBackendStatus reads and returns the status of all (virtual,real) server pairs from the specified `proc` filesystem.
|
|
||||||
func (fs FS) NewIPVSBackendStatus() ([]IPVSBackendStatus, error) {
|
|
||||||
file, err := os.Open(fs.Path("net/ip_vs"))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
// Copyright 2019 The Prometheus Authors
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package procfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/prometheus/procfs/internal/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LoadAvg represents an entry in /proc/loadavg
|
||||||
|
type LoadAvg struct {
|
||||||
|
Load1 float64
|
||||||
|
Load5 float64
|
||||||
|
Load15 float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadAvg returns loadavg from /proc.
|
||||||
|
func (fs FS) LoadAvg() (*LoadAvg, error) {
|
||||||
|
path := fs.proc.Path("loadavg")
|
||||||
|
|
||||||
|
data, err := util.ReadFileNoStat(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return parseLoad(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse /proc loadavg and return 1m, 5m and 15m.
|
||||||
|
func parseLoad(loadavgBytes []byte) (*LoadAvg, error) {
|
||||||
|
loads := make([]float64, 3)
|
||||||
|
parts := strings.Fields(string(loadavgBytes))
|
||||||
|
if len(parts) < 3 {
|
||||||
|
return nil, fmt.Errorf("malformed loadavg line: too few fields in loadavg string: %s", string(loadavgBytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
for i, load := range parts[0:3] {
|
||||||
|
loads[i], err = strconv.ParseFloat(load, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("could not parse load '%s': %s", load, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &LoadAvg{
|
||||||
|
Load1: loads[0],
|
||||||
|
Load5: loads[1],
|
||||||
|
Load15: loads[2],
|
||||||
|
}, nil
|
||||||
|
}
|
|
@ -22,8 +22,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
statuslineRE = regexp.MustCompile(`(\d+) blocks .*\[(\d+)/(\d+)\] \[[U_]+\]`)
|
statusLineRE = regexp.MustCompile(`(\d+) blocks .*\[(\d+)/(\d+)\] \[[U_]+\]`)
|
||||||
buildlineRE = regexp.MustCompile(`\((\d+)/\d+\)`)
|
recoveryLineRE = regexp.MustCompile(`\((\d+)/\d+\)`)
|
||||||
)
|
)
|
||||||
|
|
||||||
// MDStat holds info parsed from /proc/mdstat.
|
// MDStat holds info parsed from /proc/mdstat.
|
||||||
|
@ -34,117 +34,160 @@ type MDStat struct {
|
||||||
ActivityState string
|
ActivityState string
|
||||||
// Number of active disks.
|
// Number of active disks.
|
||||||
DisksActive int64
|
DisksActive int64
|
||||||
// Total number of disks the device consists of.
|
// Total number of disks the device requires.
|
||||||
DisksTotal int64
|
DisksTotal int64
|
||||||
|
// Number of failed disks.
|
||||||
|
DisksFailed int64
|
||||||
|
// Spare disks in the device.
|
||||||
|
DisksSpare int64
|
||||||
// Number of blocks the device holds.
|
// Number of blocks the device holds.
|
||||||
BlocksTotal int64
|
BlocksTotal int64
|
||||||
// Number of blocks on the device that are in sync.
|
// Number of blocks on the device that are in sync.
|
||||||
BlocksSynced int64
|
BlocksSynced int64
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseMDStat parses an mdstat-file and returns a struct with the relevant infos.
|
// MDStat parses an mdstat-file (/proc/mdstat) and returns a slice of
|
||||||
func (fs FS) ParseMDStat() (mdstates []MDStat, err error) {
|
// structs containing the relevant info. More information available here:
|
||||||
mdStatusFilePath := fs.Path("mdstat")
|
// https://raid.wiki.kernel.org/index.php/Mdstat
|
||||||
content, err := ioutil.ReadFile(mdStatusFilePath)
|
func (fs FS) MDStat() ([]MDStat, error) {
|
||||||
|
data, err := ioutil.ReadFile(fs.proc.Path("mdstat"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []MDStat{}, fmt.Errorf("error parsing %s: %s", mdStatusFilePath, err)
|
return nil, fmt.Errorf("error parsing mdstat %s: %s", fs.proc.Path("mdstat"), err)
|
||||||
}
|
}
|
||||||
|
mdstat, err := parseMDStat(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error parsing mdstat %s: %s", fs.proc.Path("mdstat"), err)
|
||||||
|
}
|
||||||
|
return mdstat, nil
|
||||||
|
}
|
||||||
|
|
||||||
mdStates := []MDStat{}
|
// parseMDStat parses data from mdstat file (/proc/mdstat) and returns a slice of
|
||||||
lines := strings.Split(string(content), "\n")
|
// structs containing the relevant info.
|
||||||
for i, l := range lines {
|
func parseMDStat(mdStatData []byte) ([]MDStat, error) {
|
||||||
if l == "" {
|
mdStats := []MDStat{}
|
||||||
continue
|
lines := strings.Split(string(mdStatData), "\n")
|
||||||
}
|
|
||||||
if l[0] == ' ' {
|
for i, line := range lines {
|
||||||
continue
|
if strings.TrimSpace(line) == "" || line[0] == ' ' ||
|
||||||
}
|
strings.HasPrefix(line, "Personalities") ||
|
||||||
if strings.HasPrefix(l, "Personalities") || strings.HasPrefix(l, "unused") {
|
strings.HasPrefix(line, "unused") {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
mainLine := strings.Split(l, " ")
|
deviceFields := strings.Fields(line)
|
||||||
if len(mainLine) < 3 {
|
if len(deviceFields) < 3 {
|
||||||
return mdStates, fmt.Errorf("error parsing mdline: %s", l)
|
return nil, fmt.Errorf("not enough fields in mdline (expected at least 3): %s", line)
|
||||||
}
|
}
|
||||||
mdName := mainLine[0]
|
mdName := deviceFields[0] // mdx
|
||||||
activityState := mainLine[2]
|
state := deviceFields[2] // active or inactive
|
||||||
|
|
||||||
if len(lines) <= i+3 {
|
if len(lines) <= i+3 {
|
||||||
return mdStates, fmt.Errorf(
|
return nil, fmt.Errorf(
|
||||||
"error parsing %s: too few lines for md device %s",
|
"error parsing %s: too few lines for md device",
|
||||||
mdStatusFilePath,
|
|
||||||
mdName,
|
mdName,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
active, total, size, err := evalStatusline(lines[i+1])
|
// Failed disks have the suffix (F) & Spare disks have the suffix (S).
|
||||||
|
fail := int64(strings.Count(line, "(F)"))
|
||||||
|
spare := int64(strings.Count(line, "(S)"))
|
||||||
|
active, total, size, err := evalStatusLine(lines[i], lines[i+1])
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return mdStates, fmt.Errorf("error parsing %s: %s", mdStatusFilePath, err)
|
return nil, fmt.Errorf("error parsing md device lines: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// j is the line number of the syncing-line.
|
syncLineIdx := i + 2
|
||||||
j := i + 2
|
|
||||||
if strings.Contains(lines[i+2], "bitmap") { // skip bitmap line
|
if strings.Contains(lines[i+2], "bitmap") { // skip bitmap line
|
||||||
j = i + 3
|
syncLineIdx++
|
||||||
}
|
}
|
||||||
|
|
||||||
// If device is syncing at the moment, get the number of currently
|
// If device is syncing at the moment, get the number of currently
|
||||||
// synced bytes, otherwise that number equals the size of the device.
|
// synced bytes, otherwise that number equals the size of the device.
|
||||||
syncedBlocks := size
|
syncedBlocks := size
|
||||||
if strings.Contains(lines[j], "recovery") || strings.Contains(lines[j], "resync") {
|
recovering := strings.Contains(lines[syncLineIdx], "recovery")
|
||||||
syncedBlocks, err = evalBuildline(lines[j])
|
resyncing := strings.Contains(lines[syncLineIdx], "resync")
|
||||||
if err != nil {
|
|
||||||
return mdStates, fmt.Errorf("error parsing %s: %s", mdStatusFilePath, err)
|
// Append recovery and resyncing state info.
|
||||||
|
if recovering || resyncing {
|
||||||
|
if recovering {
|
||||||
|
state = "recovering"
|
||||||
|
} else {
|
||||||
|
state = "resyncing"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle case when resync=PENDING or resync=DELAYED.
|
||||||
|
if strings.Contains(lines[syncLineIdx], "PENDING") ||
|
||||||
|
strings.Contains(lines[syncLineIdx], "DELAYED") {
|
||||||
|
syncedBlocks = 0
|
||||||
|
} else {
|
||||||
|
syncedBlocks, err = evalRecoveryLine(lines[syncLineIdx])
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error parsing sync line in md device %s: %s", mdName, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mdStates = append(mdStates, MDStat{
|
mdStats = append(mdStats, MDStat{
|
||||||
Name: mdName,
|
Name: mdName,
|
||||||
ActivityState: activityState,
|
ActivityState: state,
|
||||||
DisksActive: active,
|
DisksActive: active,
|
||||||
|
DisksFailed: fail,
|
||||||
|
DisksSpare: spare,
|
||||||
DisksTotal: total,
|
DisksTotal: total,
|
||||||
BlocksTotal: size,
|
BlocksTotal: size,
|
||||||
BlocksSynced: syncedBlocks,
|
BlocksSynced: syncedBlocks,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return mdStates, nil
|
return mdStats, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func evalStatusline(statusline string) (active, total, size int64, err error) {
|
func evalStatusLine(deviceLine, statusLine string) (active, total, size int64, err error) {
|
||||||
matches := statuslineRE.FindStringSubmatch(statusline)
|
|
||||||
if len(matches) != 4 {
|
sizeStr := strings.Fields(statusLine)[0]
|
||||||
return 0, 0, 0, fmt.Errorf("unexpected statusline: %s", statusline)
|
size, err = strconv.ParseInt(sizeStr, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, 0, fmt.Errorf("unexpected statusLine %s: %s", statusLine, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
size, err = strconv.ParseInt(matches[1], 10, 64)
|
if strings.Contains(deviceLine, "raid0") || strings.Contains(deviceLine, "linear") {
|
||||||
if err != nil {
|
// In the device deviceLine, only disks have a number associated with them in [].
|
||||||
return 0, 0, 0, fmt.Errorf("unexpected statusline %s: %s", statusline, err)
|
total = int64(strings.Count(deviceLine, "["))
|
||||||
|
return total, total, size, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Contains(deviceLine, "inactive") {
|
||||||
|
return 0, 0, size, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
matches := statusLineRE.FindStringSubmatch(statusLine)
|
||||||
|
if len(matches) != 4 {
|
||||||
|
return 0, 0, 0, fmt.Errorf("couldn't find all the substring matches: %s", statusLine)
|
||||||
}
|
}
|
||||||
|
|
||||||
total, err = strconv.ParseInt(matches[2], 10, 64)
|
total, err = strconv.ParseInt(matches[2], 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, 0, 0, fmt.Errorf("unexpected statusline %s: %s", statusline, err)
|
return 0, 0, 0, fmt.Errorf("unexpected statusLine %s: %s", statusLine, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
active, err = strconv.ParseInt(matches[3], 10, 64)
|
active, err = strconv.ParseInt(matches[3], 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, 0, 0, fmt.Errorf("unexpected statusline %s: %s", statusline, err)
|
return 0, 0, 0, fmt.Errorf("unexpected statusLine %s: %s", statusLine, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return active, total, size, nil
|
return active, total, size, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func evalBuildline(buildline string) (syncedBlocks int64, err error) {
|
func evalRecoveryLine(recoveryLine string) (syncedBlocks int64, err error) {
|
||||||
matches := buildlineRE.FindStringSubmatch(buildline)
|
matches := recoveryLineRE.FindStringSubmatch(recoveryLine)
|
||||||
if len(matches) != 2 {
|
if len(matches) != 2 {
|
||||||
return 0, fmt.Errorf("unexpected buildline: %s", buildline)
|
return 0, fmt.Errorf("unexpected recoveryLine: %s", recoveryLine)
|
||||||
}
|
}
|
||||||
|
|
||||||
syncedBlocks, err = strconv.ParseInt(matches[1], 10, 64)
|
syncedBlocks, err = strconv.ParseInt(matches[1], 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf("%s in buildline: %s", err, buildline)
|
return 0, fmt.Errorf("%s in recoveryLine: %s", err, recoveryLine)
|
||||||
}
|
}
|
||||||
|
|
||||||
return syncedBlocks, nil
|
return syncedBlocks, nil
|
||||||
|
|
|
@ -0,0 +1,277 @@
|
||||||
|
// Copyright 2019 The Prometheus Authors
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package procfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/prometheus/procfs/internal/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Meminfo represents memory statistics.
|
||||||
|
type Meminfo struct {
|
||||||
|
// Total usable ram (i.e. physical ram minus a few reserved
|
||||||
|
// bits and the kernel binary code)
|
||||||
|
MemTotal uint64
|
||||||
|
// The sum of LowFree+HighFree
|
||||||
|
MemFree uint64
|
||||||
|
// An estimate of how much memory is available for starting
|
||||||
|
// new applications, without swapping. Calculated from
|
||||||
|
// MemFree, SReclaimable, the size of the file LRU lists, and
|
||||||
|
// the low watermarks in each zone. The estimate takes into
|
||||||
|
// account that the system needs some page cache to function
|
||||||
|
// well, and that not all reclaimable slab will be
|
||||||
|
// reclaimable, due to items being in use. The impact of those
|
||||||
|
// factors will vary from system to system.
|
||||||
|
MemAvailable uint64
|
||||||
|
// Relatively temporary storage for raw disk blocks shouldn't
|
||||||
|
// get tremendously large (20MB or so)
|
||||||
|
Buffers uint64
|
||||||
|
Cached uint64
|
||||||
|
// Memory that once was swapped out, is swapped back in but
|
||||||
|
// still also is in the swapfile (if memory is needed it
|
||||||
|
// doesn't need to be swapped out AGAIN because it is already
|
||||||
|
// in the swapfile. This saves I/O)
|
||||||
|
SwapCached uint64
|
||||||
|
// Memory that has been used more recently and usually not
|
||||||
|
// reclaimed unless absolutely necessary.
|
||||||
|
Active uint64
|
||||||
|
// Memory which has been less recently used. It is more
|
||||||
|
// eligible to be reclaimed for other purposes
|
||||||
|
Inactive uint64
|
||||||
|
ActiveAnon uint64
|
||||||
|
InactiveAnon uint64
|
||||||
|
ActiveFile uint64
|
||||||
|
InactiveFile uint64
|
||||||
|
Unevictable uint64
|
||||||
|
Mlocked uint64
|
||||||
|
// total amount of swap space available
|
||||||
|
SwapTotal uint64
|
||||||
|
// Memory which has been evicted from RAM, and is temporarily
|
||||||
|
// on the disk
|
||||||
|
SwapFree uint64
|
||||||
|
// Memory which is waiting to get written back to the disk
|
||||||
|
Dirty uint64
|
||||||
|
// Memory which is actively being written back to the disk
|
||||||
|
Writeback uint64
|
||||||
|
// Non-file backed pages mapped into userspace page tables
|
||||||
|
AnonPages uint64
|
||||||
|
// files which have been mapped, such as libraries
|
||||||
|
Mapped uint64
|
||||||
|
Shmem uint64
|
||||||
|
// in-kernel data structures cache
|
||||||
|
Slab uint64
|
||||||
|
// Part of Slab, that might be reclaimed, such as caches
|
||||||
|
SReclaimable uint64
|
||||||
|
// Part of Slab, that cannot be reclaimed on memory pressure
|
||||||
|
SUnreclaim uint64
|
||||||
|
KernelStack uint64
|
||||||
|
// amount of memory dedicated to the lowest level of page
|
||||||
|
// tables.
|
||||||
|
PageTables uint64
|
||||||
|
// NFS pages sent to the server, but not yet committed to
|
||||||
|
// stable storage
|
||||||
|
NFSUnstable uint64
|
||||||
|
// Memory used for block device "bounce buffers"
|
||||||
|
Bounce uint64
|
||||||
|
// Memory used by FUSE for temporary writeback buffers
|
||||||
|
WritebackTmp uint64
|
||||||
|
// Based on the overcommit ratio ('vm.overcommit_ratio'),
|
||||||
|
// this is the total amount of memory currently available to
|
||||||
|
// be allocated on the system. This limit is only adhered to
|
||||||
|
// if strict overcommit accounting is enabled (mode 2 in
|
||||||
|
// 'vm.overcommit_memory').
|
||||||
|
// The CommitLimit is calculated with the following formula:
|
||||||
|
// CommitLimit = ([total RAM pages] - [total huge TLB pages]) *
|
||||||
|
// overcommit_ratio / 100 + [total swap pages]
|
||||||
|
// For example, on a system with 1G of physical RAM and 7G
|
||||||
|
// of swap with a `vm.overcommit_ratio` of 30 it would
|
||||||
|
// yield a CommitLimit of 7.3G.
|
||||||
|
// For more details, see the memory overcommit documentation
|
||||||
|
// in vm/overcommit-accounting.
|
||||||
|
CommitLimit uint64
|
||||||
|
// The amount of memory presently allocated on the system.
|
||||||
|
// The committed memory is a sum of all of the memory which
|
||||||
|
// has been allocated by processes, even if it has not been
|
||||||
|
// "used" by them as of yet. A process which malloc()'s 1G
|
||||||
|
// of memory, but only touches 300M of it will show up as
|
||||||
|
// using 1G. This 1G is memory which has been "committed" to
|
||||||
|
// by the VM and can be used at any time by the allocating
|
||||||
|
// application. With strict overcommit enabled on the system
|
||||||
|
// (mode 2 in 'vm.overcommit_memory'),allocations which would
|
||||||
|
// exceed the CommitLimit (detailed above) will not be permitted.
|
||||||
|
// This is useful if one needs to guarantee that processes will
|
||||||
|
// not fail due to lack of memory once that memory has been
|
||||||
|
// successfully allocated.
|
||||||
|
CommittedAS uint64
|
||||||
|
// total size of vmalloc memory area
|
||||||
|
VmallocTotal uint64
|
||||||
|
// amount of vmalloc area which is used
|
||||||
|
VmallocUsed uint64
|
||||||
|
// largest contiguous block of vmalloc area which is free
|
||||||
|
VmallocChunk uint64
|
||||||
|
HardwareCorrupted uint64
|
||||||
|
AnonHugePages uint64
|
||||||
|
ShmemHugePages uint64
|
||||||
|
ShmemPmdMapped uint64
|
||||||
|
CmaTotal uint64
|
||||||
|
CmaFree uint64
|
||||||
|
HugePagesTotal uint64
|
||||||
|
HugePagesFree uint64
|
||||||
|
HugePagesRsvd uint64
|
||||||
|
HugePagesSurp uint64
|
||||||
|
Hugepagesize uint64
|
||||||
|
DirectMap4k uint64
|
||||||
|
DirectMap2M uint64
|
||||||
|
DirectMap1G uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// Meminfo returns an information about current kernel/system memory statistics.
|
||||||
|
// See https://www.kernel.org/doc/Documentation/filesystems/proc.txt
|
||||||
|
func (fs FS) Meminfo() (Meminfo, error) {
|
||||||
|
b, err := util.ReadFileNoStat(fs.proc.Path("meminfo"))
|
||||||
|
if err != nil {
|
||||||
|
return Meminfo{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
m, err := parseMemInfo(bytes.NewReader(b))
|
||||||
|
if err != nil {
|
||||||
|
return Meminfo{}, fmt.Errorf("failed to parse meminfo: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return *m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseMemInfo(r io.Reader) (*Meminfo, error) {
|
||||||
|
var m Meminfo
|
||||||
|
s := bufio.NewScanner(r)
|
||||||
|
for s.Scan() {
|
||||||
|
// Each line has at least a name and value; we ignore the unit.
|
||||||
|
fields := strings.Fields(s.Text())
|
||||||
|
if len(fields) < 2 {
|
||||||
|
return nil, fmt.Errorf("malformed meminfo line: %q", s.Text())
|
||||||
|
}
|
||||||
|
|
||||||
|
v, err := strconv.ParseUint(fields[1], 0, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch fields[0] {
|
||||||
|
case "MemTotal:":
|
||||||
|
m.MemTotal = v
|
||||||
|
case "MemFree:":
|
||||||
|
m.MemFree = v
|
||||||
|
case "MemAvailable:":
|
||||||
|
m.MemAvailable = v
|
||||||
|
case "Buffers:":
|
||||||
|
m.Buffers = v
|
||||||
|
case "Cached:":
|
||||||
|
m.Cached = v
|
||||||
|
case "SwapCached:":
|
||||||
|
m.SwapCached = v
|
||||||
|
case "Active:":
|
||||||
|
m.Active = v
|
||||||
|
case "Inactive:":
|
||||||
|
m.Inactive = v
|
||||||
|
case "Active(anon):":
|
||||||
|
m.ActiveAnon = v
|
||||||
|
case "Inactive(anon):":
|
||||||
|
m.InactiveAnon = v
|
||||||
|
case "Active(file):":
|
||||||
|
m.ActiveFile = v
|
||||||
|
case "Inactive(file):":
|
||||||
|
m.InactiveFile = v
|
||||||
|
case "Unevictable:":
|
||||||
|
m.Unevictable = v
|
||||||
|
case "Mlocked:":
|
||||||
|
m.Mlocked = v
|
||||||
|
case "SwapTotal:":
|
||||||
|
m.SwapTotal = v
|
||||||
|
case "SwapFree:":
|
||||||
|
m.SwapFree = v
|
||||||
|
case "Dirty:":
|
||||||
|
m.Dirty = v
|
||||||
|
case "Writeback:":
|
||||||
|
m.Writeback = v
|
||||||
|
case "AnonPages:":
|
||||||
|
m.AnonPages = v
|
||||||
|
case "Mapped:":
|
||||||
|
m.Mapped = v
|
||||||
|
case "Shmem:":
|
||||||
|
m.Shmem = v
|
||||||
|
case "Slab:":
|
||||||
|
m.Slab = v
|
||||||
|
case "SReclaimable:":
|
||||||
|
m.SReclaimable = v
|
||||||
|
case "SUnreclaim:":
|
||||||
|
m.SUnreclaim = v
|
||||||
|
case "KernelStack:":
|
||||||
|
m.KernelStack = v
|
||||||
|
case "PageTables:":
|
||||||
|
m.PageTables = v
|
||||||
|
case "NFS_Unstable:":
|
||||||
|
m.NFSUnstable = v
|
||||||
|
case "Bounce:":
|
||||||
|
m.Bounce = v
|
||||||
|
case "WritebackTmp:":
|
||||||
|
m.WritebackTmp = v
|
||||||
|
case "CommitLimit:":
|
||||||
|
m.CommitLimit = v
|
||||||
|
case "Committed_AS:":
|
||||||
|
m.CommittedAS = v
|
||||||
|
case "VmallocTotal:":
|
||||||
|
m.VmallocTotal = v
|
||||||
|
case "VmallocUsed:":
|
||||||
|
m.VmallocUsed = v
|
||||||
|
case "VmallocChunk:":
|
||||||
|
m.VmallocChunk = v
|
||||||
|
case "HardwareCorrupted:":
|
||||||
|
m.HardwareCorrupted = v
|
||||||
|
case "AnonHugePages:":
|
||||||
|
m.AnonHugePages = v
|
||||||
|
case "ShmemHugePages:":
|
||||||
|
m.ShmemHugePages = v
|
||||||
|
case "ShmemPmdMapped:":
|
||||||
|
m.ShmemPmdMapped = v
|
||||||
|
case "CmaTotal:":
|
||||||
|
m.CmaTotal = v
|
||||||
|
case "CmaFree:":
|
||||||
|
m.CmaFree = v
|
||||||
|
case "HugePages_Total:":
|
||||||
|
m.HugePagesTotal = v
|
||||||
|
case "HugePages_Free:":
|
||||||
|
m.HugePagesFree = v
|
||||||
|
case "HugePages_Rsvd:":
|
||||||
|
m.HugePagesRsvd = v
|
||||||
|
case "HugePages_Surp:":
|
||||||
|
m.HugePagesSurp = v
|
||||||
|
case "Hugepagesize:":
|
||||||
|
m.Hugepagesize = v
|
||||||
|
case "DirectMap4k:":
|
||||||
|
m.DirectMap4k = v
|
||||||
|
case "DirectMap2M:":
|
||||||
|
m.DirectMap2M = v
|
||||||
|
case "DirectMap1G:":
|
||||||
|
m.DirectMap1G = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &m, nil
|
||||||
|
}
|
|
@ -0,0 +1,180 @@
|
||||||
|
// Copyright 2019 The Prometheus Authors
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package procfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/prometheus/procfs/internal/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A MountInfo is a type that describes the details, options
|
||||||
|
// for each mount, parsed from /proc/self/mountinfo.
|
||||||
|
// The fields described in each entry of /proc/self/mountinfo
|
||||||
|
// is described in the following man page.
|
||||||
|
// http://man7.org/linux/man-pages/man5/proc.5.html
|
||||||
|
type MountInfo struct {
|
||||||
|
// Unique ID for the mount
|
||||||
|
MountID int
|
||||||
|
// The ID of the parent mount
|
||||||
|
ParentID int
|
||||||
|
// The value of `st_dev` for the files on this FS
|
||||||
|
MajorMinorVer string
|
||||||
|
// The pathname of the directory in the FS that forms
|
||||||
|
// the root for this mount
|
||||||
|
Root string
|
||||||
|
// The pathname of the mount point relative to the root
|
||||||
|
MountPoint string
|
||||||
|
// Mount options
|
||||||
|
Options map[string]string
|
||||||
|
// Zero or more optional fields
|
||||||
|
OptionalFields map[string]string
|
||||||
|
// The Filesystem type
|
||||||
|
FSType string
|
||||||
|
// FS specific information or "none"
|
||||||
|
Source string
|
||||||
|
// Superblock options
|
||||||
|
SuperOptions map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reads each line of the mountinfo file, and returns a list of formatted MountInfo structs.
|
||||||
|
func parseMountInfo(info []byte) ([]*MountInfo, error) {
|
||||||
|
mounts := []*MountInfo{}
|
||||||
|
scanner := bufio.NewScanner(bytes.NewReader(info))
|
||||||
|
for scanner.Scan() {
|
||||||
|
mountString := scanner.Text()
|
||||||
|
parsedMounts, err := parseMountInfoString(mountString)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
mounts = append(mounts, parsedMounts)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := scanner.Err()
|
||||||
|
return mounts, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parses a mountinfo file line, and converts it to a MountInfo struct.
|
||||||
|
// An important check here is to see if the hyphen separator, as if it does not exist,
|
||||||
|
// it means that the line is malformed.
|
||||||
|
func parseMountInfoString(mountString string) (*MountInfo, error) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
mountInfo := strings.Split(mountString, " ")
|
||||||
|
mountInfoLength := len(mountInfo)
|
||||||
|
if mountInfoLength < 11 {
|
||||||
|
return nil, fmt.Errorf("couldn't find enough fields in mount string: %s", mountString)
|
||||||
|
}
|
||||||
|
|
||||||
|
if mountInfo[mountInfoLength-4] != "-" {
|
||||||
|
return nil, fmt.Errorf("couldn't find separator in expected field: %s", mountInfo[mountInfoLength-4])
|
||||||
|
}
|
||||||
|
|
||||||
|
mount := &MountInfo{
|
||||||
|
MajorMinorVer: mountInfo[2],
|
||||||
|
Root: mountInfo[3],
|
||||||
|
MountPoint: mountInfo[4],
|
||||||
|
Options: mountOptionsParser(mountInfo[5]),
|
||||||
|
OptionalFields: nil,
|
||||||
|
FSType: mountInfo[mountInfoLength-3],
|
||||||
|
Source: mountInfo[mountInfoLength-2],
|
||||||
|
SuperOptions: mountOptionsParser(mountInfo[mountInfoLength-1]),
|
||||||
|
}
|
||||||
|
|
||||||
|
mount.MountID, err = strconv.Atoi(mountInfo[0])
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse mount ID")
|
||||||
|
}
|
||||||
|
mount.ParentID, err = strconv.Atoi(mountInfo[1])
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse parent ID")
|
||||||
|
}
|
||||||
|
// Has optional fields, which is a space separated list of values.
|
||||||
|
// Example: shared:2 master:7
|
||||||
|
if mountInfo[6] != "" {
|
||||||
|
mount.OptionalFields, err = mountOptionsParseOptionalFields(mountInfo[6 : mountInfoLength-4])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mount, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// mountOptionsIsValidField checks a string against a valid list of optional fields keys.
|
||||||
|
func mountOptionsIsValidField(s string) bool {
|
||||||
|
switch s {
|
||||||
|
case
|
||||||
|
"shared",
|
||||||
|
"master",
|
||||||
|
"propagate_from",
|
||||||
|
"unbindable":
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// mountOptionsParseOptionalFields parses a list of optional fields strings into a double map of strings.
|
||||||
|
func mountOptionsParseOptionalFields(o []string) (map[string]string, error) {
|
||||||
|
optionalFields := make(map[string]string)
|
||||||
|
for _, field := range o {
|
||||||
|
optionSplit := strings.SplitN(field, ":", 2)
|
||||||
|
value := ""
|
||||||
|
if len(optionSplit) == 2 {
|
||||||
|
value = optionSplit[1]
|
||||||
|
}
|
||||||
|
if mountOptionsIsValidField(optionSplit[0]) {
|
||||||
|
optionalFields[optionSplit[0]] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return optionalFields, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parses the mount options, superblock options.
|
||||||
|
func mountOptionsParser(mountOptions string) map[string]string {
|
||||||
|
opts := make(map[string]string)
|
||||||
|
options := strings.Split(mountOptions, ",")
|
||||||
|
for _, opt := range options {
|
||||||
|
splitOption := strings.Split(opt, "=")
|
||||||
|
if len(splitOption) < 2 {
|
||||||
|
key := splitOption[0]
|
||||||
|
opts[key] = ""
|
||||||
|
} else {
|
||||||
|
key, value := splitOption[0], splitOption[1]
|
||||||
|
opts[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return opts
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieves mountinfo information from `/proc/self/mountinfo`.
|
||||||
|
func GetMounts() ([]*MountInfo, error) {
|
||||||
|
data, err := util.ReadFileNoStat("/proc/self/mountinfo")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return parseMountInfo(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieves mountinfo information from a processes' `/proc/<pid>/mountinfo`.
|
||||||
|
func GetProcMounts(pid int) ([]*MountInfo, error) {
|
||||||
|
data, err := util.ReadFileNoStat(fmt.Sprintf("/proc/%d/mountinfo", pid))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return parseMountInfo(data)
|
||||||
|
}
|
|
@ -39,8 +39,11 @@ const (
|
||||||
statVersion10 = "1.0"
|
statVersion10 = "1.0"
|
||||||
statVersion11 = "1.1"
|
statVersion11 = "1.1"
|
||||||
|
|
||||||
fieldTransport10Len = 10
|
fieldTransport10TCPLen = 10
|
||||||
fieldTransport11Len = 13
|
fieldTransport10UDPLen = 7
|
||||||
|
|
||||||
|
fieldTransport11TCPLen = 13
|
||||||
|
fieldTransport11UDPLen = 10
|
||||||
)
|
)
|
||||||
|
|
||||||
// A Mount is a device mount parsed from /proc/[pid]/mountstats.
|
// A Mount is a device mount parsed from /proc/[pid]/mountstats.
|
||||||
|
@ -66,6 +69,8 @@ type MountStats interface {
|
||||||
type MountStatsNFS struct {
|
type MountStatsNFS struct {
|
||||||
// The version of statistics provided.
|
// The version of statistics provided.
|
||||||
StatVersion string
|
StatVersion string
|
||||||
|
// The mount options of the NFS mount.
|
||||||
|
Opts map[string]string
|
||||||
// The age of the NFS mount.
|
// The age of the NFS mount.
|
||||||
Age time.Duration
|
Age time.Duration
|
||||||
// Statistics related to byte counters for various operations.
|
// Statistics related to byte counters for various operations.
|
||||||
|
@ -176,16 +181,18 @@ type NFSOperationStats struct {
|
||||||
// Number of bytes received for this operation, including RPC headers and payload.
|
// Number of bytes received for this operation, including RPC headers and payload.
|
||||||
BytesReceived uint64
|
BytesReceived uint64
|
||||||
// Duration all requests spent queued for transmission before they were sent.
|
// Duration all requests spent queued for transmission before they were sent.
|
||||||
CumulativeQueueTime time.Duration
|
CumulativeQueueMilliseconds uint64
|
||||||
// Duration it took to get a reply back after the request was transmitted.
|
// Duration it took to get a reply back after the request was transmitted.
|
||||||
CumulativeTotalResponseTime time.Duration
|
CumulativeTotalResponseMilliseconds uint64
|
||||||
// Duration from when a request was enqueued to when it was completely handled.
|
// Duration from when a request was enqueued to when it was completely handled.
|
||||||
CumulativeTotalRequestTime time.Duration
|
CumulativeTotalRequestMilliseconds uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
// A NFSTransportStats contains statistics for the NFS mount RPC requests and
|
// A NFSTransportStats contains statistics for the NFS mount RPC requests and
|
||||||
// responses.
|
// responses.
|
||||||
type NFSTransportStats struct {
|
type NFSTransportStats struct {
|
||||||
|
// The transport protocol used for the NFS mount.
|
||||||
|
Protocol string
|
||||||
// The local port used for the NFS mount.
|
// The local port used for the NFS mount.
|
||||||
Port uint64
|
Port uint64
|
||||||
// Number of times the client has had to establish a connection from scratch
|
// Number of times the client has had to establish a connection from scratch
|
||||||
|
@ -197,7 +204,7 @@ type NFSTransportStats struct {
|
||||||
// spent waiting for connections to the server to be established.
|
// spent waiting for connections to the server to be established.
|
||||||
ConnectIdleTime uint64
|
ConnectIdleTime uint64
|
||||||
// Duration since the NFS mount last saw any RPC traffic.
|
// Duration since the NFS mount last saw any RPC traffic.
|
||||||
IdleTime time.Duration
|
IdleTimeSeconds uint64
|
||||||
// Number of RPC requests for this mount sent to the NFS server.
|
// Number of RPC requests for this mount sent to the NFS server.
|
||||||
Sends uint64
|
Sends uint64
|
||||||
// Number of RPC responses for this mount received from the NFS server.
|
// Number of RPC responses for this mount received from the NFS server.
|
||||||
|
@ -312,6 +319,7 @@ func parseMount(ss []string) (*Mount, error) {
|
||||||
func parseMountStatsNFS(s *bufio.Scanner, statVersion string) (*MountStatsNFS, error) {
|
func parseMountStatsNFS(s *bufio.Scanner, statVersion string) (*MountStatsNFS, error) {
|
||||||
// Field indicators for parsing specific types of data
|
// Field indicators for parsing specific types of data
|
||||||
const (
|
const (
|
||||||
|
fieldOpts = "opts:"
|
||||||
fieldAge = "age:"
|
fieldAge = "age:"
|
||||||
fieldBytes = "bytes:"
|
fieldBytes = "bytes:"
|
||||||
fieldEvents = "events:"
|
fieldEvents = "events:"
|
||||||
|
@ -333,6 +341,18 @@ func parseMountStatsNFS(s *bufio.Scanner, statVersion string) (*MountStatsNFS, e
|
||||||
}
|
}
|
||||||
|
|
||||||
switch ss[0] {
|
switch ss[0] {
|
||||||
|
case fieldOpts:
|
||||||
|
if stats.Opts == nil {
|
||||||
|
stats.Opts = map[string]string{}
|
||||||
|
}
|
||||||
|
for _, opt := range strings.Split(ss[1], ",") {
|
||||||
|
split := strings.Split(opt, "=")
|
||||||
|
if len(split) == 2 {
|
||||||
|
stats.Opts[split[0]] = split[1]
|
||||||
|
} else {
|
||||||
|
stats.Opts[opt] = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
case fieldAge:
|
case fieldAge:
|
||||||
// Age integer is in seconds
|
// Age integer is in seconds
|
||||||
d, err := time.ParseDuration(ss[1] + "s")
|
d, err := time.ParseDuration(ss[1] + "s")
|
||||||
|
@ -360,7 +380,7 @@ func parseMountStatsNFS(s *bufio.Scanner, statVersion string) (*MountStatsNFS, e
|
||||||
return nil, fmt.Errorf("not enough information for NFS transport stats: %v", ss)
|
return nil, fmt.Errorf("not enough information for NFS transport stats: %v", ss)
|
||||||
}
|
}
|
||||||
|
|
||||||
tstats, err := parseNFSTransportStats(ss[2:], statVersion)
|
tstats, err := parseNFSTransportStats(ss[1:], statVersion)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -504,15 +524,15 @@ func parseNFSOperationStats(s *bufio.Scanner) ([]NFSOperationStats, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ops = append(ops, NFSOperationStats{
|
ops = append(ops, NFSOperationStats{
|
||||||
Operation: strings.TrimSuffix(ss[0], ":"),
|
Operation: strings.TrimSuffix(ss[0], ":"),
|
||||||
Requests: ns[0],
|
Requests: ns[0],
|
||||||
Transmissions: ns[1],
|
Transmissions: ns[1],
|
||||||
MajorTimeouts: ns[2],
|
MajorTimeouts: ns[2],
|
||||||
BytesSent: ns[3],
|
BytesSent: ns[3],
|
||||||
BytesReceived: ns[4],
|
BytesReceived: ns[4],
|
||||||
CumulativeQueueTime: time.Duration(ns[5]) * time.Millisecond,
|
CumulativeQueueMilliseconds: ns[5],
|
||||||
CumulativeTotalResponseTime: time.Duration(ns[6]) * time.Millisecond,
|
CumulativeTotalResponseMilliseconds: ns[6],
|
||||||
CumulativeTotalRequestTime: time.Duration(ns[7]) * time.Millisecond,
|
CumulativeTotalRequestMilliseconds: ns[7],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -522,13 +542,33 @@ func parseNFSOperationStats(s *bufio.Scanner) ([]NFSOperationStats, error) {
|
||||||
// parseNFSTransportStats parses a NFSTransportStats line using an input set of
|
// parseNFSTransportStats parses a NFSTransportStats line using an input set of
|
||||||
// integer fields matched to a specific stats version.
|
// integer fields matched to a specific stats version.
|
||||||
func parseNFSTransportStats(ss []string, statVersion string) (*NFSTransportStats, error) {
|
func parseNFSTransportStats(ss []string, statVersion string) (*NFSTransportStats, error) {
|
||||||
|
// Extract the protocol field. It is the only string value in the line
|
||||||
|
protocol := ss[0]
|
||||||
|
ss = ss[1:]
|
||||||
|
|
||||||
switch statVersion {
|
switch statVersion {
|
||||||
case statVersion10:
|
case statVersion10:
|
||||||
if len(ss) != fieldTransport10Len {
|
var expectedLength int
|
||||||
|
if protocol == "tcp" {
|
||||||
|
expectedLength = fieldTransport10TCPLen
|
||||||
|
} else if protocol == "udp" {
|
||||||
|
expectedLength = fieldTransport10UDPLen
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("invalid NFS protocol \"%s\" in stats 1.0 statement: %v", protocol, ss)
|
||||||
|
}
|
||||||
|
if len(ss) != expectedLength {
|
||||||
return nil, fmt.Errorf("invalid NFS transport stats 1.0 statement: %v", ss)
|
return nil, fmt.Errorf("invalid NFS transport stats 1.0 statement: %v", ss)
|
||||||
}
|
}
|
||||||
case statVersion11:
|
case statVersion11:
|
||||||
if len(ss) != fieldTransport11Len {
|
var expectedLength int
|
||||||
|
if protocol == "tcp" {
|
||||||
|
expectedLength = fieldTransport11TCPLen
|
||||||
|
} else if protocol == "udp" {
|
||||||
|
expectedLength = fieldTransport11UDPLen
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("invalid NFS protocol \"%s\" in stats 1.1 statement: %v", protocol, ss)
|
||||||
|
}
|
||||||
|
if len(ss) != expectedLength {
|
||||||
return nil, fmt.Errorf("invalid NFS transport stats 1.1 statement: %v", ss)
|
return nil, fmt.Errorf("invalid NFS transport stats 1.1 statement: %v", ss)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
@ -536,12 +576,13 @@ func parseNFSTransportStats(ss []string, statVersion string) (*NFSTransportStats
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allocate enough for v1.1 stats since zero value for v1.1 stats will be okay
|
// Allocate enough for v1.1 stats since zero value for v1.1 stats will be okay
|
||||||
// in a v1.0 response.
|
// in a v1.0 response. Since the stat length is bigger for TCP stats, we use
|
||||||
|
// the TCP length here.
|
||||||
//
|
//
|
||||||
// Note: slice length must be set to length of v1.1 stats to avoid a panic when
|
// Note: slice length must be set to length of v1.1 stats to avoid a panic when
|
||||||
// only v1.0 stats are present.
|
// only v1.0 stats are present.
|
||||||
// See: https://github.com/prometheus/node_exporter/issues/571.
|
// See: https://github.com/prometheus/node_exporter/issues/571.
|
||||||
ns := make([]uint64, fieldTransport11Len)
|
ns := make([]uint64, fieldTransport11TCPLen)
|
||||||
for i, s := range ss {
|
for i, s := range ss {
|
||||||
n, err := strconv.ParseUint(s, 10, 64)
|
n, err := strconv.ParseUint(s, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -551,12 +592,23 @@ func parseNFSTransportStats(ss []string, statVersion string) (*NFSTransportStats
|
||||||
ns[i] = n
|
ns[i] = n
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The fields differ depending on the transport protocol (TCP or UDP)
|
||||||
|
// From https://utcc.utoronto.ca/%7Ecks/space/blog/linux/NFSMountstatsXprt
|
||||||
|
//
|
||||||
|
// For the udp RPC transport there is no connection count, connect idle time,
|
||||||
|
// or idle time (fields #3, #4, and #5); all other fields are the same. So
|
||||||
|
// we set them to 0 here.
|
||||||
|
if protocol == "udp" {
|
||||||
|
ns = append(ns[:2], append(make([]uint64, 3), ns[2:]...)...)
|
||||||
|
}
|
||||||
|
|
||||||
return &NFSTransportStats{
|
return &NFSTransportStats{
|
||||||
|
Protocol: protocol,
|
||||||
Port: ns[0],
|
Port: ns[0],
|
||||||
Bind: ns[1],
|
Bind: ns[1],
|
||||||
Connect: ns[2],
|
Connect: ns[2],
|
||||||
ConnectIdleTime: ns[3],
|
ConnectIdleTime: ns[3],
|
||||||
IdleTime: time.Duration(ns[4]) * time.Second,
|
IdleTimeSeconds: ns[4],
|
||||||
Sends: ns[5],
|
Sends: ns[5],
|
||||||
Receives: ns[6],
|
Receives: ns[6],
|
||||||
BadTransactionIDs: ns[7],
|
BadTransactionIDs: ns[7],
|
||||||
|
|
|
@ -0,0 +1,153 @@
|
||||||
|
// Copyright 2020 The Prometheus Authors
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package procfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/prometheus/procfs/internal/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A ConntrackStatEntry represents one line from net/stat/nf_conntrack
|
||||||
|
// and contains netfilter conntrack statistics at one CPU core
|
||||||
|
type ConntrackStatEntry struct {
|
||||||
|
Entries uint64
|
||||||
|
Found uint64
|
||||||
|
Invalid uint64
|
||||||
|
Ignore uint64
|
||||||
|
Insert uint64
|
||||||
|
InsertFailed uint64
|
||||||
|
Drop uint64
|
||||||
|
EarlyDrop uint64
|
||||||
|
SearchRestart uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieves netfilter's conntrack statistics, split by CPU cores
|
||||||
|
func (fs FS) ConntrackStat() ([]ConntrackStatEntry, error) {
|
||||||
|
return readConntrackStat(fs.proc.Path("net", "stat", "nf_conntrack"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parses a slice of ConntrackStatEntries from the given filepath
|
||||||
|
func readConntrackStat(path string) ([]ConntrackStatEntry, error) {
|
||||||
|
// This file is small and can be read with one syscall.
|
||||||
|
b, err := util.ReadFileNoStat(path)
|
||||||
|
if err != nil {
|
||||||
|
// Do not wrap this error so the caller can detect os.IsNotExist and
|
||||||
|
// similar conditions.
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
stat, err := parseConntrackStat(bytes.NewReader(b))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read conntrack stats from %q: %v", path, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return stat, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reads the contents of a conntrack statistics file and parses a slice of ConntrackStatEntries
|
||||||
|
func parseConntrackStat(r io.Reader) ([]ConntrackStatEntry, error) {
|
||||||
|
var entries []ConntrackStatEntry
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(r)
|
||||||
|
scanner.Scan()
|
||||||
|
for scanner.Scan() {
|
||||||
|
fields := strings.Fields(scanner.Text())
|
||||||
|
conntrackEntry, err := parseConntrackStatEntry(fields)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
entries = append(entries, *conntrackEntry)
|
||||||
|
}
|
||||||
|
|
||||||
|
return entries, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parses a ConntrackStatEntry from given array of fields
|
||||||
|
func parseConntrackStatEntry(fields []string) (*ConntrackStatEntry, error) {
|
||||||
|
if len(fields) != 17 {
|
||||||
|
return nil, fmt.Errorf("invalid conntrackstat entry, missing fields")
|
||||||
|
}
|
||||||
|
entry := &ConntrackStatEntry{}
|
||||||
|
|
||||||
|
entries, err := parseConntrackStatField(fields[0])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
entry.Entries = entries
|
||||||
|
|
||||||
|
found, err := parseConntrackStatField(fields[2])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
entry.Found = found
|
||||||
|
|
||||||
|
invalid, err := parseConntrackStatField(fields[4])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
entry.Invalid = invalid
|
||||||
|
|
||||||
|
ignore, err := parseConntrackStatField(fields[5])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
entry.Ignore = ignore
|
||||||
|
|
||||||
|
insert, err := parseConntrackStatField(fields[8])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
entry.Insert = insert
|
||||||
|
|
||||||
|
insertFailed, err := parseConntrackStatField(fields[9])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
entry.InsertFailed = insertFailed
|
||||||
|
|
||||||
|
drop, err := parseConntrackStatField(fields[10])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
entry.Drop = drop
|
||||||
|
|
||||||
|
earlyDrop, err := parseConntrackStatField(fields[11])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
entry.EarlyDrop = earlyDrop
|
||||||
|
|
||||||
|
searchRestart, err := parseConntrackStatField(fields[16])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
entry.SearchRestart = searchRestart
|
||||||
|
|
||||||
|
return entry, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parses a uint64 from given hex in string
|
||||||
|
func parseConntrackStatField(field string) (uint64, error) {
|
||||||
|
val, err := strconv.ParseUint(field, 16, 64)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("couldn't parse \"%s\" field: %s", field, err)
|
||||||
|
}
|
||||||
|
return val, err
|
||||||
|
}
|
|
@ -47,23 +47,13 @@ type NetDevLine struct {
|
||||||
// are interface names.
|
// are interface names.
|
||||||
type NetDev map[string]NetDevLine
|
type NetDev map[string]NetDevLine
|
||||||
|
|
||||||
// NewNetDev returns kernel/system statistics read from /proc/net/dev.
|
// NetDev returns kernel/system statistics read from /proc/net/dev.
|
||||||
func NewNetDev() (NetDev, error) {
|
func (fs FS) NetDev() (NetDev, error) {
|
||||||
fs, err := NewFS(DefaultMountPoint)
|
return newNetDev(fs.proc.Path("net/dev"))
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return fs.NewNetDev()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewNetDev returns kernel/system statistics read from /proc/net/dev.
|
// NetDev returns kernel/system statistics read from /proc/[pid]/net/dev.
|
||||||
func (fs FS) NewNetDev() (NetDev, error) {
|
func (p Proc) NetDev() (NetDev, error) {
|
||||||
return newNetDev(fs.Path("net/dev"))
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewNetDev returns kernel/system statistics read from /proc/[pid]/net/dev.
|
|
||||||
func (p Proc) NewNetDev() (NetDev, error) {
|
|
||||||
return newNetDev(p.path("net/dev"))
|
return newNetDev(p.path("net/dev"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,7 +65,7 @@ func newNetDev(file string) (NetDev, error) {
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
nd := NetDev{}
|
netDev := NetDev{}
|
||||||
s := bufio.NewScanner(f)
|
s := bufio.NewScanner(f)
|
||||||
for n := 0; s.Scan(); n++ {
|
for n := 0; s.Scan(); n++ {
|
||||||
// Skip the 2 header lines.
|
// Skip the 2 header lines.
|
||||||
|
@ -83,20 +73,20 @@ func newNetDev(file string) (NetDev, error) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
line, err := nd.parseLine(s.Text())
|
line, err := netDev.parseLine(s.Text())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nd, err
|
return netDev, err
|
||||||
}
|
}
|
||||||
|
|
||||||
nd[line.Name] = *line
|
netDev[line.Name] = *line
|
||||||
}
|
}
|
||||||
|
|
||||||
return nd, s.Err()
|
return netDev, s.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseLine parses a single line from the /proc/net/dev file. Header lines
|
// parseLine parses a single line from the /proc/net/dev file. Header lines
|
||||||
// must be filtered prior to calling this method.
|
// must be filtered prior to calling this method.
|
||||||
func (nd NetDev) parseLine(rawLine string) (*NetDevLine, error) {
|
func (netDev NetDev) parseLine(rawLine string) (*NetDevLine, error) {
|
||||||
parts := strings.SplitN(rawLine, ":", 2)
|
parts := strings.SplitN(rawLine, ":", 2)
|
||||||
if len(parts) != 2 {
|
if len(parts) != 2 {
|
||||||
return nil, errors.New("invalid net/dev line, missing colon")
|
return nil, errors.New("invalid net/dev line, missing colon")
|
||||||
|
@ -185,15 +175,14 @@ func (nd NetDev) parseLine(rawLine string) (*NetDevLine, error) {
|
||||||
|
|
||||||
// Total aggregates the values across interfaces and returns a new NetDevLine.
|
// Total aggregates the values across interfaces and returns a new NetDevLine.
|
||||||
// The Name field will be a sorted comma separated list of interface names.
|
// The Name field will be a sorted comma separated list of interface names.
|
||||||
func (nd NetDev) Total() NetDevLine {
|
func (netDev NetDev) Total() NetDevLine {
|
||||||
total := NetDevLine{}
|
total := NetDevLine{}
|
||||||
|
|
||||||
names := make([]string, 0, len(nd))
|
names := make([]string, 0, len(netDev))
|
||||||
for _, ifc := range nd {
|
for _, ifc := range netDev {
|
||||||
names = append(names, ifc.Name)
|
names = append(names, ifc.Name)
|
||||||
total.RxBytes += ifc.RxBytes
|
total.RxBytes += ifc.RxBytes
|
||||||
total.RxPackets += ifc.RxPackets
|
total.RxPackets += ifc.RxPackets
|
||||||
total.RxPackets += ifc.RxPackets
|
|
||||||
total.RxErrors += ifc.RxErrors
|
total.RxErrors += ifc.RxErrors
|
||||||
total.RxDropped += ifc.RxDropped
|
total.RxDropped += ifc.RxDropped
|
||||||
total.RxFIFO += ifc.RxFIFO
|
total.RxFIFO += ifc.RxFIFO
|
||||||
|
|
|
@ -0,0 +1,163 @@
|
||||||
|
// Copyright 2019 The Prometheus Authors
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package procfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/prometheus/procfs/internal/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A NetSockstat contains the output of /proc/net/sockstat{,6} for IPv4 or IPv6,
|
||||||
|
// respectively.
|
||||||
|
type NetSockstat struct {
|
||||||
|
// Used is non-nil for IPv4 sockstat results, but nil for IPv6.
|
||||||
|
Used *int
|
||||||
|
Protocols []NetSockstatProtocol
|
||||||
|
}
|
||||||
|
|
||||||
|
// A NetSockstatProtocol contains statistics about a given socket protocol.
|
||||||
|
// Pointer fields indicate that the value may or may not be present on any
|
||||||
|
// given protocol.
|
||||||
|
type NetSockstatProtocol struct {
|
||||||
|
Protocol string
|
||||||
|
InUse int
|
||||||
|
Orphan *int
|
||||||
|
TW *int
|
||||||
|
Alloc *int
|
||||||
|
Mem *int
|
||||||
|
Memory *int
|
||||||
|
}
|
||||||
|
|
||||||
|
// NetSockstat retrieves IPv4 socket statistics.
|
||||||
|
func (fs FS) NetSockstat() (*NetSockstat, error) {
|
||||||
|
return readSockstat(fs.proc.Path("net", "sockstat"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NetSockstat6 retrieves IPv6 socket statistics.
|
||||||
|
//
|
||||||
|
// If IPv6 is disabled on this kernel, the returned error can be checked with
|
||||||
|
// os.IsNotExist.
|
||||||
|
func (fs FS) NetSockstat6() (*NetSockstat, error) {
|
||||||
|
return readSockstat(fs.proc.Path("net", "sockstat6"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// readSockstat opens and parses a NetSockstat from the input file.
|
||||||
|
func readSockstat(name string) (*NetSockstat, error) {
|
||||||
|
// This file is small and can be read with one syscall.
|
||||||
|
b, err := util.ReadFileNoStat(name)
|
||||||
|
if err != nil {
|
||||||
|
// Do not wrap this error so the caller can detect os.IsNotExist and
|
||||||
|
// similar conditions.
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
stat, err := parseSockstat(bytes.NewReader(b))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read sockstats from %q: %v", name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return stat, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseSockstat reads the contents of a sockstat file and parses a NetSockstat.
|
||||||
|
func parseSockstat(r io.Reader) (*NetSockstat, error) {
|
||||||
|
var stat NetSockstat
|
||||||
|
s := bufio.NewScanner(r)
|
||||||
|
for s.Scan() {
|
||||||
|
// Expect a minimum of a protocol and one key/value pair.
|
||||||
|
fields := strings.Split(s.Text(), " ")
|
||||||
|
if len(fields) < 3 {
|
||||||
|
return nil, fmt.Errorf("malformed sockstat line: %q", s.Text())
|
||||||
|
}
|
||||||
|
|
||||||
|
// The remaining fields are key/value pairs.
|
||||||
|
kvs, err := parseSockstatKVs(fields[1:])
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error parsing sockstat key/value pairs from %q: %v", s.Text(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The first field is the protocol. We must trim its colon suffix.
|
||||||
|
proto := strings.TrimSuffix(fields[0], ":")
|
||||||
|
switch proto {
|
||||||
|
case "sockets":
|
||||||
|
// Special case: IPv4 has a sockets "used" key/value pair that we
|
||||||
|
// embed at the top level of the structure.
|
||||||
|
used := kvs["used"]
|
||||||
|
stat.Used = &used
|
||||||
|
default:
|
||||||
|
// Parse all other lines as individual protocols.
|
||||||
|
nsp := parseSockstatProtocol(kvs)
|
||||||
|
nsp.Protocol = proto
|
||||||
|
stat.Protocols = append(stat.Protocols, nsp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &stat, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseSockstatKVs parses a string slice into a map of key/value pairs.
|
||||||
|
func parseSockstatKVs(kvs []string) (map[string]int, error) {
|
||||||
|
if len(kvs)%2 != 0 {
|
||||||
|
return nil, errors.New("odd number of fields in key/value pairs")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate two values at a time to gather key/value pairs.
|
||||||
|
out := make(map[string]int, len(kvs)/2)
|
||||||
|
for i := 0; i < len(kvs); i += 2 {
|
||||||
|
vp := util.NewValueParser(kvs[i+1])
|
||||||
|
out[kvs[i]] = vp.Int()
|
||||||
|
|
||||||
|
if err := vp.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseSockstatProtocol parses a NetSockstatProtocol from the input kvs map.
|
||||||
|
func parseSockstatProtocol(kvs map[string]int) NetSockstatProtocol {
|
||||||
|
var nsp NetSockstatProtocol
|
||||||
|
for k, v := range kvs {
|
||||||
|
// Capture the range variable to ensure we get unique pointers for
|
||||||
|
// each of the optional fields.
|
||||||
|
v := v
|
||||||
|
switch k {
|
||||||
|
case "inuse":
|
||||||
|
nsp.InUse = v
|
||||||
|
case "orphan":
|
||||||
|
nsp.Orphan = &v
|
||||||
|
case "tw":
|
||||||
|
nsp.TW = &v
|
||||||
|
case "alloc":
|
||||||
|
nsp.Alloc = &v
|
||||||
|
case "mem":
|
||||||
|
nsp.Mem = &v
|
||||||
|
case "memory":
|
||||||
|
nsp.Memory = &v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nsp
|
||||||
|
}
|
|
@ -0,0 +1,102 @@
|
||||||
|
// Copyright 2019 The Prometheus Authors
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package procfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/prometheus/procfs/internal/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// For the proc file format details,
|
||||||
|
// See:
|
||||||
|
// * Linux 2.6.23 https://elixir.bootlin.com/linux/v2.6.23/source/net/core/dev.c#L2343
|
||||||
|
// * Linux 4.17 https://elixir.bootlin.com/linux/v4.17/source/net/core/net-procfs.c#L162
|
||||||
|
// and https://elixir.bootlin.com/linux/v4.17/source/include/linux/netdevice.h#L2810.
|
||||||
|
|
||||||
|
// SoftnetStat contains a single row of data from /proc/net/softnet_stat
|
||||||
|
type SoftnetStat struct {
|
||||||
|
// Number of processed packets
|
||||||
|
Processed uint32
|
||||||
|
// Number of dropped packets
|
||||||
|
Dropped uint32
|
||||||
|
// Number of times processing packets ran out of quota
|
||||||
|
TimeSqueezed uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
var softNetProcFile = "net/softnet_stat"
|
||||||
|
|
||||||
|
// NetSoftnetStat reads data from /proc/net/softnet_stat.
|
||||||
|
func (fs FS) NetSoftnetStat() ([]SoftnetStat, error) {
|
||||||
|
b, err := util.ReadFileNoStat(fs.proc.Path(softNetProcFile))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
entries, err := parseSoftnet(bytes.NewReader(b))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse /proc/net/softnet_stat: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return entries, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseSoftnet(r io.Reader) ([]SoftnetStat, error) {
|
||||||
|
const minColumns = 9
|
||||||
|
|
||||||
|
s := bufio.NewScanner(r)
|
||||||
|
|
||||||
|
var stats []SoftnetStat
|
||||||
|
for s.Scan() {
|
||||||
|
columns := strings.Fields(s.Text())
|
||||||
|
width := len(columns)
|
||||||
|
|
||||||
|
if width < minColumns {
|
||||||
|
return nil, fmt.Errorf("%d columns were detected, but at least %d were expected", width, minColumns)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We only parse the first three columns at the moment.
|
||||||
|
us, err := parseHexUint32s(columns[0:3])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
stats = append(stats, SoftnetStat{
|
||||||
|
Processed: us[0],
|
||||||
|
Dropped: us[1],
|
||||||
|
TimeSqueezed: us[2],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return stats, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseHexUint32s(ss []string) ([]uint32, error) {
|
||||||
|
us := make([]uint32, 0, len(ss))
|
||||||
|
for _, s := range ss {
|
||||||
|
u, err := strconv.ParseUint(s, 16, 32)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
us = append(us, uint32(u))
|
||||||
|
}
|
||||||
|
|
||||||
|
return us, nil
|
||||||
|
}
|
|
@ -0,0 +1,229 @@
|
||||||
|
// Copyright 2020 The Prometheus Authors
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package procfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// readLimit is used by io.LimitReader while reading the content of the
|
||||||
|
// /proc/net/udp{,6} files. The number of lines inside such a file is dynamic
|
||||||
|
// as each line represents a single used socket.
|
||||||
|
// In theory, the number of available sockets is 65535 (2^16 - 1) per IP.
|
||||||
|
// With e.g. 150 Byte per line and the maximum number of 65535,
|
||||||
|
// the reader needs to handle 150 Byte * 65535 =~ 10 MB for a single IP.
|
||||||
|
readLimit = 4294967296 // Byte -> 4 GiB
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
// NetUDP represents the contents of /proc/net/udp{,6} file without the header.
|
||||||
|
NetUDP []*netUDPLine
|
||||||
|
|
||||||
|
// NetUDPSummary provides already computed values like the total queue lengths or
|
||||||
|
// the total number of used sockets. In contrast to NetUDP it does not collect
|
||||||
|
// the parsed lines into a slice.
|
||||||
|
NetUDPSummary struct {
|
||||||
|
// TxQueueLength shows the total queue length of all parsed tx_queue lengths.
|
||||||
|
TxQueueLength uint64
|
||||||
|
// RxQueueLength shows the total queue length of all parsed rx_queue lengths.
|
||||||
|
RxQueueLength uint64
|
||||||
|
// UsedSockets shows the total number of parsed lines representing the
|
||||||
|
// number of used sockets.
|
||||||
|
UsedSockets uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// netUDPLine represents the fields parsed from a single line
|
||||||
|
// in /proc/net/udp{,6}. Fields which are not used by UDP are skipped.
|
||||||
|
// For the proc file format details, see https://linux.die.net/man/5/proc.
|
||||||
|
netUDPLine struct {
|
||||||
|
Sl uint64
|
||||||
|
LocalAddr net.IP
|
||||||
|
LocalPort uint64
|
||||||
|
RemAddr net.IP
|
||||||
|
RemPort uint64
|
||||||
|
St uint64
|
||||||
|
TxQueue uint64
|
||||||
|
RxQueue uint64
|
||||||
|
UID uint64
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// NetUDP returns the IPv4 kernel/networking statistics for UDP datagrams
|
||||||
|
// read from /proc/net/udp.
|
||||||
|
func (fs FS) NetUDP() (NetUDP, error) {
|
||||||
|
return newNetUDP(fs.proc.Path("net/udp"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NetUDP6 returns the IPv6 kernel/networking statistics for UDP datagrams
|
||||||
|
// read from /proc/net/udp6.
|
||||||
|
func (fs FS) NetUDP6() (NetUDP, error) {
|
||||||
|
return newNetUDP(fs.proc.Path("net/udp6"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NetUDPSummary returns already computed statistics like the total queue lengths
|
||||||
|
// for UDP datagrams read from /proc/net/udp.
|
||||||
|
func (fs FS) NetUDPSummary() (*NetUDPSummary, error) {
|
||||||
|
return newNetUDPSummary(fs.proc.Path("net/udp"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NetUDP6Summary returns already computed statistics like the total queue lengths
|
||||||
|
// for UDP datagrams read from /proc/net/udp6.
|
||||||
|
func (fs FS) NetUDP6Summary() (*NetUDPSummary, error) {
|
||||||
|
return newNetUDPSummary(fs.proc.Path("net/udp6"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// newNetUDP creates a new NetUDP{,6} from the contents of the given file.
|
||||||
|
func newNetUDP(file string) (NetUDP, error) {
|
||||||
|
f, err := os.Open(file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
netUDP := NetUDP{}
|
||||||
|
|
||||||
|
lr := io.LimitReader(f, readLimit)
|
||||||
|
s := bufio.NewScanner(lr)
|
||||||
|
s.Scan() // skip first line with headers
|
||||||
|
for s.Scan() {
|
||||||
|
fields := strings.Fields(s.Text())
|
||||||
|
line, err := parseNetUDPLine(fields)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
netUDP = append(netUDP, line)
|
||||||
|
}
|
||||||
|
if err := s.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return netUDP, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// newNetUDPSummary creates a new NetUDP{,6} from the contents of the given file.
|
||||||
|
func newNetUDPSummary(file string) (*NetUDPSummary, error) {
|
||||||
|
f, err := os.Open(file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
netUDPSummary := &NetUDPSummary{}
|
||||||
|
|
||||||
|
lr := io.LimitReader(f, readLimit)
|
||||||
|
s := bufio.NewScanner(lr)
|
||||||
|
s.Scan() // skip first line with headers
|
||||||
|
for s.Scan() {
|
||||||
|
fields := strings.Fields(s.Text())
|
||||||
|
line, err := parseNetUDPLine(fields)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
netUDPSummary.TxQueueLength += line.TxQueue
|
||||||
|
netUDPSummary.RxQueueLength += line.RxQueue
|
||||||
|
netUDPSummary.UsedSockets++
|
||||||
|
}
|
||||||
|
if err := s.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return netUDPSummary, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseNetUDPLine parses a single line, represented by a list of fields.
|
||||||
|
func parseNetUDPLine(fields []string) (*netUDPLine, error) {
|
||||||
|
line := &netUDPLine{}
|
||||||
|
if len(fields) < 8 {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"cannot parse net udp socket line as it has less then 8 columns: %s",
|
||||||
|
strings.Join(fields, " "),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
var err error // parse error
|
||||||
|
|
||||||
|
// sl
|
||||||
|
s := strings.Split(fields[0], ":")
|
||||||
|
if len(s) != 2 {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"cannot parse sl field in udp socket line: %s", fields[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
if line.Sl, err = strconv.ParseUint(s[0], 0, 64); err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot parse sl value in udp socket line: %s", err)
|
||||||
|
}
|
||||||
|
// local_address
|
||||||
|
l := strings.Split(fields[1], ":")
|
||||||
|
if len(l) != 2 {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"cannot parse local_address field in udp socket line: %s", fields[1])
|
||||||
|
}
|
||||||
|
if line.LocalAddr, err = hex.DecodeString(l[0]); err != nil {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"cannot parse local_address value in udp socket line: %s", err)
|
||||||
|
}
|
||||||
|
if line.LocalPort, err = strconv.ParseUint(l[1], 16, 64); err != nil {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"cannot parse local_address port value in udp socket line: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// remote_address
|
||||||
|
r := strings.Split(fields[2], ":")
|
||||||
|
if len(r) != 2 {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"cannot parse rem_address field in udp socket line: %s", fields[1])
|
||||||
|
}
|
||||||
|
if line.RemAddr, err = hex.DecodeString(r[0]); err != nil {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"cannot parse rem_address value in udp socket line: %s", err)
|
||||||
|
}
|
||||||
|
if line.RemPort, err = strconv.ParseUint(r[1], 16, 64); err != nil {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"cannot parse rem_address port value in udp socket line: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// st
|
||||||
|
if line.St, err = strconv.ParseUint(fields[3], 16, 64); err != nil {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"cannot parse st value in udp socket line: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// tx_queue and rx_queue
|
||||||
|
q := strings.Split(fields[4], ":")
|
||||||
|
if len(q) != 2 {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"cannot parse tx/rx queues in udp socket line as it has a missing colon: %s",
|
||||||
|
fields[4],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if line.TxQueue, err = strconv.ParseUint(q[0], 16, 64); err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot parse tx_queue value in udp socket line: %s", err)
|
||||||
|
}
|
||||||
|
if line.RxQueue, err = strconv.ParseUint(q[1], 16, 64); err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot parse rx_queue value in udp socket line: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// uid
|
||||||
|
if line.UID, err = strconv.ParseUint(fields[7], 0, 64); err != nil {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"cannot parse uid value in udp socket line: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return line, nil
|
||||||
|
}
|
|
@ -0,0 +1,257 @@
|
||||||
|
// Copyright 2018 The Prometheus Authors
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package procfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// For the proc file format details,
|
||||||
|
// see https://elixir.bootlin.com/linux/v4.17/source/net/unix/af_unix.c#L2815
|
||||||
|
// and https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/net.h#L48.
|
||||||
|
|
||||||
|
// Constants for the various /proc/net/unix enumerations.
|
||||||
|
// TODO: match against x/sys/unix or similar?
|
||||||
|
const (
|
||||||
|
netUnixTypeStream = 1
|
||||||
|
netUnixTypeDgram = 2
|
||||||
|
netUnixTypeSeqpacket = 5
|
||||||
|
|
||||||
|
netUnixFlagDefault = 0
|
||||||
|
netUnixFlagListen = 1 << 16
|
||||||
|
|
||||||
|
netUnixStateUnconnected = 1
|
||||||
|
netUnixStateConnecting = 2
|
||||||
|
netUnixStateConnected = 3
|
||||||
|
netUnixStateDisconnected = 4
|
||||||
|
)
|
||||||
|
|
||||||
|
// NetUNIXType is the type of the type field.
|
||||||
|
type NetUNIXType uint64
|
||||||
|
|
||||||
|
// NetUNIXFlags is the type of the flags field.
|
||||||
|
type NetUNIXFlags uint64
|
||||||
|
|
||||||
|
// NetUNIXState is the type of the state field.
|
||||||
|
type NetUNIXState uint64
|
||||||
|
|
||||||
|
// NetUNIXLine represents a line of /proc/net/unix.
|
||||||
|
type NetUNIXLine struct {
|
||||||
|
KernelPtr string
|
||||||
|
RefCount uint64
|
||||||
|
Protocol uint64
|
||||||
|
Flags NetUNIXFlags
|
||||||
|
Type NetUNIXType
|
||||||
|
State NetUNIXState
|
||||||
|
Inode uint64
|
||||||
|
Path string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NetUNIX holds the data read from /proc/net/unix.
|
||||||
|
type NetUNIX struct {
|
||||||
|
Rows []*NetUNIXLine
|
||||||
|
}
|
||||||
|
|
||||||
|
// NetUNIX returns data read from /proc/net/unix.
|
||||||
|
func (fs FS) NetUNIX() (*NetUNIX, error) {
|
||||||
|
return readNetUNIX(fs.proc.Path("net/unix"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// readNetUNIX reads data in /proc/net/unix format from the specified file.
|
||||||
|
func readNetUNIX(file string) (*NetUNIX, error) {
|
||||||
|
// This file could be quite large and a streaming read is desirable versus
|
||||||
|
// reading the entire contents at once.
|
||||||
|
f, err := os.Open(file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
return parseNetUNIX(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseNetUNIX creates a NetUnix structure from the incoming stream.
|
||||||
|
func parseNetUNIX(r io.Reader) (*NetUNIX, error) {
|
||||||
|
// Begin scanning by checking for the existence of Inode.
|
||||||
|
s := bufio.NewScanner(r)
|
||||||
|
s.Scan()
|
||||||
|
|
||||||
|
// From the man page of proc(5), it does not contain an Inode field,
|
||||||
|
// but in actually it exists. This code works for both cases.
|
||||||
|
hasInode := strings.Contains(s.Text(), "Inode")
|
||||||
|
|
||||||
|
// Expect a minimum number of fields, but Inode and Path are optional:
|
||||||
|
// Num RefCount Protocol Flags Type St Inode Path
|
||||||
|
minFields := 6
|
||||||
|
if hasInode {
|
||||||
|
minFields++
|
||||||
|
}
|
||||||
|
|
||||||
|
var nu NetUNIX
|
||||||
|
for s.Scan() {
|
||||||
|
line := s.Text()
|
||||||
|
item, err := nu.parseLine(line, hasInode, minFields)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse /proc/net/unix data %q: %v", line, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
nu.Rows = append(nu.Rows, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.Err(); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to scan /proc/net/unix data: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &nu, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *NetUNIX) parseLine(line string, hasInode bool, min int) (*NetUNIXLine, error) {
|
||||||
|
fields := strings.Fields(line)
|
||||||
|
|
||||||
|
l := len(fields)
|
||||||
|
if l < min {
|
||||||
|
return nil, fmt.Errorf("expected at least %d fields but got %d", min, l)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Field offsets are as follows:
|
||||||
|
// Num RefCount Protocol Flags Type St Inode Path
|
||||||
|
|
||||||
|
kernelPtr := strings.TrimSuffix(fields[0], ":")
|
||||||
|
|
||||||
|
users, err := u.parseUsers(fields[1])
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse ref count(%s): %v", fields[1], err)
|
||||||
|
}
|
||||||
|
|
||||||
|
flags, err := u.parseFlags(fields[3])
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse flags(%s): %v", fields[3], err)
|
||||||
|
}
|
||||||
|
|
||||||
|
typ, err := u.parseType(fields[4])
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse type(%s): %v", fields[4], err)
|
||||||
|
}
|
||||||
|
|
||||||
|
state, err := u.parseState(fields[5])
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse state(%s): %v", fields[5], err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var inode uint64
|
||||||
|
if hasInode {
|
||||||
|
inode, err = u.parseInode(fields[6])
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse inode(%s): %v", fields[6], err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
n := &NetUNIXLine{
|
||||||
|
KernelPtr: kernelPtr,
|
||||||
|
RefCount: users,
|
||||||
|
Type: typ,
|
||||||
|
Flags: flags,
|
||||||
|
State: state,
|
||||||
|
Inode: inode,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Path field is optional.
|
||||||
|
if l > min {
|
||||||
|
// Path occurs at either index 6 or 7 depending on whether inode is
|
||||||
|
// already present.
|
||||||
|
pathIdx := 7
|
||||||
|
if !hasInode {
|
||||||
|
pathIdx--
|
||||||
|
}
|
||||||
|
|
||||||
|
n.Path = fields[pathIdx]
|
||||||
|
}
|
||||||
|
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u NetUNIX) parseUsers(s string) (uint64, error) {
|
||||||
|
return strconv.ParseUint(s, 16, 32)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u NetUNIX) parseType(s string) (NetUNIXType, error) {
|
||||||
|
typ, err := strconv.ParseUint(s, 16, 16)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return NetUNIXType(typ), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u NetUNIX) parseFlags(s string) (NetUNIXFlags, error) {
|
||||||
|
flags, err := strconv.ParseUint(s, 16, 32)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return NetUNIXFlags(flags), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u NetUNIX) parseState(s string) (NetUNIXState, error) {
|
||||||
|
st, err := strconv.ParseInt(s, 16, 8)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return NetUNIXState(st), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u NetUNIX) parseInode(s string) (uint64, error) {
|
||||||
|
return strconv.ParseUint(s, 10, 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t NetUNIXType) String() string {
|
||||||
|
switch t {
|
||||||
|
case netUnixTypeStream:
|
||||||
|
return "stream"
|
||||||
|
case netUnixTypeDgram:
|
||||||
|
return "dgram"
|
||||||
|
case netUnixTypeSeqpacket:
|
||||||
|
return "seqpacket"
|
||||||
|
}
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f NetUNIXFlags) String() string {
|
||||||
|
switch f {
|
||||||
|
case netUnixFlagListen:
|
||||||
|
return "listen"
|
||||||
|
default:
|
||||||
|
return "default"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s NetUNIXState) String() string {
|
||||||
|
switch s {
|
||||||
|
case netUnixStateUnconnected:
|
||||||
|
return "unconnected"
|
||||||
|
case netUnixStateConnecting:
|
||||||
|
return "connecting"
|
||||||
|
case netUnixStateConnected:
|
||||||
|
return "connected"
|
||||||
|
case netUnixStateDisconnected:
|
||||||
|
return "disconnected"
|
||||||
|
}
|
||||||
|
return "unknown"
|
||||||
|
}
|
|
@ -1,263 +0,0 @@
|
||||||
// Copyright 2018 The Prometheus Authors
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
// Package nfs implements parsing of /proc/net/rpc/nfsd.
|
|
||||||
// Fields are documented in https://www.svennd.be/nfsd-stats-explained-procnetrpcnfsd/
|
|
||||||
package nfs
|
|
||||||
|
|
||||||
// ReplyCache models the "rc" line.
|
|
||||||
type ReplyCache struct {
|
|
||||||
Hits uint64
|
|
||||||
Misses uint64
|
|
||||||
NoCache uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
// FileHandles models the "fh" line.
|
|
||||||
type FileHandles struct {
|
|
||||||
Stale uint64
|
|
||||||
TotalLookups uint64
|
|
||||||
AnonLookups uint64
|
|
||||||
DirNoCache uint64
|
|
||||||
NoDirNoCache uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
// InputOutput models the "io" line.
|
|
||||||
type InputOutput struct {
|
|
||||||
Read uint64
|
|
||||||
Write uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
// Threads models the "th" line.
|
|
||||||
type Threads struct {
|
|
||||||
Threads uint64
|
|
||||||
FullCnt uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadAheadCache models the "ra" line.
|
|
||||||
type ReadAheadCache struct {
|
|
||||||
CacheSize uint64
|
|
||||||
CacheHistogram []uint64
|
|
||||||
NotFound uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
// Network models the "net" line.
|
|
||||||
type Network struct {
|
|
||||||
NetCount uint64
|
|
||||||
UDPCount uint64
|
|
||||||
TCPCount uint64
|
|
||||||
TCPConnect uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
// ClientRPC models the nfs "rpc" line.
|
|
||||||
type ClientRPC struct {
|
|
||||||
RPCCount uint64
|
|
||||||
Retransmissions uint64
|
|
||||||
AuthRefreshes uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServerRPC models the nfsd "rpc" line.
|
|
||||||
type ServerRPC struct {
|
|
||||||
RPCCount uint64
|
|
||||||
BadCnt uint64
|
|
||||||
BadFmt uint64
|
|
||||||
BadAuth uint64
|
|
||||||
BadcInt uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
// V2Stats models the "proc2" line.
|
|
||||||
type V2Stats struct {
|
|
||||||
Null uint64
|
|
||||||
GetAttr uint64
|
|
||||||
SetAttr uint64
|
|
||||||
Root uint64
|
|
||||||
Lookup uint64
|
|
||||||
ReadLink uint64
|
|
||||||
Read uint64
|
|
||||||
WrCache uint64
|
|
||||||
Write uint64
|
|
||||||
Create uint64
|
|
||||||
Remove uint64
|
|
||||||
Rename uint64
|
|
||||||
Link uint64
|
|
||||||
SymLink uint64
|
|
||||||
MkDir uint64
|
|
||||||
RmDir uint64
|
|
||||||
ReadDir uint64
|
|
||||||
FsStat uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
// V3Stats models the "proc3" line.
|
|
||||||
type V3Stats struct {
|
|
||||||
Null uint64
|
|
||||||
GetAttr uint64
|
|
||||||
SetAttr uint64
|
|
||||||
Lookup uint64
|
|
||||||
Access uint64
|
|
||||||
ReadLink uint64
|
|
||||||
Read uint64
|
|
||||||
Write uint64
|
|
||||||
Create uint64
|
|
||||||
MkDir uint64
|
|
||||||
SymLink uint64
|
|
||||||
MkNod uint64
|
|
||||||
Remove uint64
|
|
||||||
RmDir uint64
|
|
||||||
Rename uint64
|
|
||||||
Link uint64
|
|
||||||
ReadDir uint64
|
|
||||||
ReadDirPlus uint64
|
|
||||||
FsStat uint64
|
|
||||||
FsInfo uint64
|
|
||||||
PathConf uint64
|
|
||||||
Commit uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
// ClientV4Stats models the nfs "proc4" line.
|
|
||||||
type ClientV4Stats struct {
|
|
||||||
Null uint64
|
|
||||||
Read uint64
|
|
||||||
Write uint64
|
|
||||||
Commit uint64
|
|
||||||
Open uint64
|
|
||||||
OpenConfirm uint64
|
|
||||||
OpenNoattr uint64
|
|
||||||
OpenDowngrade uint64
|
|
||||||
Close uint64
|
|
||||||
Setattr uint64
|
|
||||||
FsInfo uint64
|
|
||||||
Renew uint64
|
|
||||||
SetClientID uint64
|
|
||||||
SetClientIDConfirm uint64
|
|
||||||
Lock uint64
|
|
||||||
Lockt uint64
|
|
||||||
Locku uint64
|
|
||||||
Access uint64
|
|
||||||
Getattr uint64
|
|
||||||
Lookup uint64
|
|
||||||
LookupRoot uint64
|
|
||||||
Remove uint64
|
|
||||||
Rename uint64
|
|
||||||
Link uint64
|
|
||||||
Symlink uint64
|
|
||||||
Create uint64
|
|
||||||
Pathconf uint64
|
|
||||||
StatFs uint64
|
|
||||||
ReadLink uint64
|
|
||||||
ReadDir uint64
|
|
||||||
ServerCaps uint64
|
|
||||||
DelegReturn uint64
|
|
||||||
GetACL uint64
|
|
||||||
SetACL uint64
|
|
||||||
FsLocations uint64
|
|
||||||
ReleaseLockowner uint64
|
|
||||||
Secinfo uint64
|
|
||||||
FsidPresent uint64
|
|
||||||
ExchangeID uint64
|
|
||||||
CreateSession uint64
|
|
||||||
DestroySession uint64
|
|
||||||
Sequence uint64
|
|
||||||
GetLeaseTime uint64
|
|
||||||
ReclaimComplete uint64
|
|
||||||
LayoutGet uint64
|
|
||||||
GetDeviceInfo uint64
|
|
||||||
LayoutCommit uint64
|
|
||||||
LayoutReturn uint64
|
|
||||||
SecinfoNoName uint64
|
|
||||||
TestStateID uint64
|
|
||||||
FreeStateID uint64
|
|
||||||
GetDeviceList uint64
|
|
||||||
BindConnToSession uint64
|
|
||||||
DestroyClientID uint64
|
|
||||||
Seek uint64
|
|
||||||
Allocate uint64
|
|
||||||
DeAllocate uint64
|
|
||||||
LayoutStats uint64
|
|
||||||
Clone uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServerV4Stats models the nfsd "proc4" line.
|
|
||||||
type ServerV4Stats struct {
|
|
||||||
Null uint64
|
|
||||||
Compound uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
// V4Ops models the "proc4ops" line: NFSv4 operations
|
|
||||||
// Variable list, see:
|
|
||||||
// v4.0 https://tools.ietf.org/html/rfc3010 (38 operations)
|
|
||||||
// v4.1 https://tools.ietf.org/html/rfc5661 (58 operations)
|
|
||||||
// v4.2 https://tools.ietf.org/html/draft-ietf-nfsv4-minorversion2-41 (71 operations)
|
|
||||||
type V4Ops struct {
|
|
||||||
//Values uint64 // Variable depending on v4.x sub-version. TODO: Will this always at least include the fields in this struct?
|
|
||||||
Op0Unused uint64
|
|
||||||
Op1Unused uint64
|
|
||||||
Op2Future uint64
|
|
||||||
Access uint64
|
|
||||||
Close uint64
|
|
||||||
Commit uint64
|
|
||||||
Create uint64
|
|
||||||
DelegPurge uint64
|
|
||||||
DelegReturn uint64
|
|
||||||
GetAttr uint64
|
|
||||||
GetFH uint64
|
|
||||||
Link uint64
|
|
||||||
Lock uint64
|
|
||||||
Lockt uint64
|
|
||||||
Locku uint64
|
|
||||||
Lookup uint64
|
|
||||||
LookupRoot uint64
|
|
||||||
Nverify uint64
|
|
||||||
Open uint64
|
|
||||||
OpenAttr uint64
|
|
||||||
OpenConfirm uint64
|
|
||||||
OpenDgrd uint64
|
|
||||||
PutFH uint64
|
|
||||||
PutPubFH uint64
|
|
||||||
PutRootFH uint64
|
|
||||||
Read uint64
|
|
||||||
ReadDir uint64
|
|
||||||
ReadLink uint64
|
|
||||||
Remove uint64
|
|
||||||
Rename uint64
|
|
||||||
Renew uint64
|
|
||||||
RestoreFH uint64
|
|
||||||
SaveFH uint64
|
|
||||||
SecInfo uint64
|
|
||||||
SetAttr uint64
|
|
||||||
Verify uint64
|
|
||||||
Write uint64
|
|
||||||
RelLockOwner uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
// ClientRPCStats models all stats from /proc/net/rpc/nfs.
|
|
||||||
type ClientRPCStats struct {
|
|
||||||
Network Network
|
|
||||||
ClientRPC ClientRPC
|
|
||||||
V2Stats V2Stats
|
|
||||||
V3Stats V3Stats
|
|
||||||
ClientV4Stats ClientV4Stats
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServerRPCStats models all stats from /proc/net/rpc/nfsd.
|
|
||||||
type ServerRPCStats struct {
|
|
||||||
ReplyCache ReplyCache
|
|
||||||
FileHandles FileHandles
|
|
||||||
InputOutput InputOutput
|
|
||||||
Threads Threads
|
|
||||||
ReadAheadCache ReadAheadCache
|
|
||||||
Network Network
|
|
||||||
ServerRPC ServerRPC
|
|
||||||
V2Stats V2Stats
|
|
||||||
V3Stats V3Stats
|
|
||||||
ServerV4Stats ServerV4Stats
|
|
||||||
V4Ops V4Ops
|
|
||||||
}
|
|
|
@ -1,317 +0,0 @@
|
||||||
// Copyright 2018 The Prometheus Authors
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package nfs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
func parseReplyCache(v []uint64) (ReplyCache, error) {
|
|
||||||
if len(v) != 3 {
|
|
||||||
return ReplyCache{}, fmt.Errorf("invalid ReplyCache line %q", v)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ReplyCache{
|
|
||||||
Hits: v[0],
|
|
||||||
Misses: v[1],
|
|
||||||
NoCache: v[2],
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseFileHandles(v []uint64) (FileHandles, error) {
|
|
||||||
if len(v) != 5 {
|
|
||||||
return FileHandles{}, fmt.Errorf("invalid FileHandles, line %q", v)
|
|
||||||
}
|
|
||||||
|
|
||||||
return FileHandles{
|
|
||||||
Stale: v[0],
|
|
||||||
TotalLookups: v[1],
|
|
||||||
AnonLookups: v[2],
|
|
||||||
DirNoCache: v[3],
|
|
||||||
NoDirNoCache: v[4],
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseInputOutput(v []uint64) (InputOutput, error) {
|
|
||||||
if len(v) != 2 {
|
|
||||||
return InputOutput{}, fmt.Errorf("invalid InputOutput line %q", v)
|
|
||||||
}
|
|
||||||
|
|
||||||
return InputOutput{
|
|
||||||
Read: v[0],
|
|
||||||
Write: v[1],
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseThreads(v []uint64) (Threads, error) {
|
|
||||||
if len(v) != 2 {
|
|
||||||
return Threads{}, fmt.Errorf("invalid Threads line %q", v)
|
|
||||||
}
|
|
||||||
|
|
||||||
return Threads{
|
|
||||||
Threads: v[0],
|
|
||||||
FullCnt: v[1],
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseReadAheadCache(v []uint64) (ReadAheadCache, error) {
|
|
||||||
if len(v) != 12 {
|
|
||||||
return ReadAheadCache{}, fmt.Errorf("invalid ReadAheadCache line %q", v)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ReadAheadCache{
|
|
||||||
CacheSize: v[0],
|
|
||||||
CacheHistogram: v[1:11],
|
|
||||||
NotFound: v[11],
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseNetwork(v []uint64) (Network, error) {
|
|
||||||
if len(v) != 4 {
|
|
||||||
return Network{}, fmt.Errorf("invalid Network line %q", v)
|
|
||||||
}
|
|
||||||
|
|
||||||
return Network{
|
|
||||||
NetCount: v[0],
|
|
||||||
UDPCount: v[1],
|
|
||||||
TCPCount: v[2],
|
|
||||||
TCPConnect: v[3],
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseServerRPC(v []uint64) (ServerRPC, error) {
|
|
||||||
if len(v) != 5 {
|
|
||||||
return ServerRPC{}, fmt.Errorf("invalid RPC line %q", v)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ServerRPC{
|
|
||||||
RPCCount: v[0],
|
|
||||||
BadCnt: v[1],
|
|
||||||
BadFmt: v[2],
|
|
||||||
BadAuth: v[3],
|
|
||||||
BadcInt: v[4],
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseClientRPC(v []uint64) (ClientRPC, error) {
|
|
||||||
if len(v) != 3 {
|
|
||||||
return ClientRPC{}, fmt.Errorf("invalid RPC line %q", v)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ClientRPC{
|
|
||||||
RPCCount: v[0],
|
|
||||||
Retransmissions: v[1],
|
|
||||||
AuthRefreshes: v[2],
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseV2Stats(v []uint64) (V2Stats, error) {
|
|
||||||
values := int(v[0])
|
|
||||||
if len(v[1:]) != values || values != 18 {
|
|
||||||
return V2Stats{}, fmt.Errorf("invalid V2Stats line %q", v)
|
|
||||||
}
|
|
||||||
|
|
||||||
return V2Stats{
|
|
||||||
Null: v[1],
|
|
||||||
GetAttr: v[2],
|
|
||||||
SetAttr: v[3],
|
|
||||||
Root: v[4],
|
|
||||||
Lookup: v[5],
|
|
||||||
ReadLink: v[6],
|
|
||||||
Read: v[7],
|
|
||||||
WrCache: v[8],
|
|
||||||
Write: v[9],
|
|
||||||
Create: v[10],
|
|
||||||
Remove: v[11],
|
|
||||||
Rename: v[12],
|
|
||||||
Link: v[13],
|
|
||||||
SymLink: v[14],
|
|
||||||
MkDir: v[15],
|
|
||||||
RmDir: v[16],
|
|
||||||
ReadDir: v[17],
|
|
||||||
FsStat: v[18],
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseV3Stats(v []uint64) (V3Stats, error) {
|
|
||||||
values := int(v[0])
|
|
||||||
if len(v[1:]) != values || values != 22 {
|
|
||||||
return V3Stats{}, fmt.Errorf("invalid V3Stats line %q", v)
|
|
||||||
}
|
|
||||||
|
|
||||||
return V3Stats{
|
|
||||||
Null: v[1],
|
|
||||||
GetAttr: v[2],
|
|
||||||
SetAttr: v[3],
|
|
||||||
Lookup: v[4],
|
|
||||||
Access: v[5],
|
|
||||||
ReadLink: v[6],
|
|
||||||
Read: v[7],
|
|
||||||
Write: v[8],
|
|
||||||
Create: v[9],
|
|
||||||
MkDir: v[10],
|
|
||||||
SymLink: v[11],
|
|
||||||
MkNod: v[12],
|
|
||||||
Remove: v[13],
|
|
||||||
RmDir: v[14],
|
|
||||||
Rename: v[15],
|
|
||||||
Link: v[16],
|
|
||||||
ReadDir: v[17],
|
|
||||||
ReadDirPlus: v[18],
|
|
||||||
FsStat: v[19],
|
|
||||||
FsInfo: v[20],
|
|
||||||
PathConf: v[21],
|
|
||||||
Commit: v[22],
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseClientV4Stats(v []uint64) (ClientV4Stats, error) {
|
|
||||||
values := int(v[0])
|
|
||||||
if len(v[1:]) != values {
|
|
||||||
return ClientV4Stats{}, fmt.Errorf("invalid ClientV4Stats line %q", v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// This function currently supports mapping 59 NFS v4 client stats. Older
|
|
||||||
// kernels may emit fewer stats, so we must detect this and pad out the
|
|
||||||
// values to match the expected slice size.
|
|
||||||
if values < 59 {
|
|
||||||
newValues := make([]uint64, 60)
|
|
||||||
copy(newValues, v)
|
|
||||||
v = newValues
|
|
||||||
}
|
|
||||||
|
|
||||||
return ClientV4Stats{
|
|
||||||
Null: v[1],
|
|
||||||
Read: v[2],
|
|
||||||
Write: v[3],
|
|
||||||
Commit: v[4],
|
|
||||||
Open: v[5],
|
|
||||||
OpenConfirm: v[6],
|
|
||||||
OpenNoattr: v[7],
|
|
||||||
OpenDowngrade: v[8],
|
|
||||||
Close: v[9],
|
|
||||||
Setattr: v[10],
|
|
||||||
FsInfo: v[11],
|
|
||||||
Renew: v[12],
|
|
||||||
SetClientID: v[13],
|
|
||||||
SetClientIDConfirm: v[14],
|
|
||||||
Lock: v[15],
|
|
||||||
Lockt: v[16],
|
|
||||||
Locku: v[17],
|
|
||||||
Access: v[18],
|
|
||||||
Getattr: v[19],
|
|
||||||
Lookup: v[20],
|
|
||||||
LookupRoot: v[21],
|
|
||||||
Remove: v[22],
|
|
||||||
Rename: v[23],
|
|
||||||
Link: v[24],
|
|
||||||
Symlink: v[25],
|
|
||||||
Create: v[26],
|
|
||||||
Pathconf: v[27],
|
|
||||||
StatFs: v[28],
|
|
||||||
ReadLink: v[29],
|
|
||||||
ReadDir: v[30],
|
|
||||||
ServerCaps: v[31],
|
|
||||||
DelegReturn: v[32],
|
|
||||||
GetACL: v[33],
|
|
||||||
SetACL: v[34],
|
|
||||||
FsLocations: v[35],
|
|
||||||
ReleaseLockowner: v[36],
|
|
||||||
Secinfo: v[37],
|
|
||||||
FsidPresent: v[38],
|
|
||||||
ExchangeID: v[39],
|
|
||||||
CreateSession: v[40],
|
|
||||||
DestroySession: v[41],
|
|
||||||
Sequence: v[42],
|
|
||||||
GetLeaseTime: v[43],
|
|
||||||
ReclaimComplete: v[44],
|
|
||||||
LayoutGet: v[45],
|
|
||||||
GetDeviceInfo: v[46],
|
|
||||||
LayoutCommit: v[47],
|
|
||||||
LayoutReturn: v[48],
|
|
||||||
SecinfoNoName: v[49],
|
|
||||||
TestStateID: v[50],
|
|
||||||
FreeStateID: v[51],
|
|
||||||
GetDeviceList: v[52],
|
|
||||||
BindConnToSession: v[53],
|
|
||||||
DestroyClientID: v[54],
|
|
||||||
Seek: v[55],
|
|
||||||
Allocate: v[56],
|
|
||||||
DeAllocate: v[57],
|
|
||||||
LayoutStats: v[58],
|
|
||||||
Clone: v[59],
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseServerV4Stats(v []uint64) (ServerV4Stats, error) {
|
|
||||||
values := int(v[0])
|
|
||||||
if len(v[1:]) != values || values != 2 {
|
|
||||||
return ServerV4Stats{}, fmt.Errorf("invalid V4Stats line %q", v)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ServerV4Stats{
|
|
||||||
Null: v[1],
|
|
||||||
Compound: v[2],
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseV4Ops(v []uint64) (V4Ops, error) {
|
|
||||||
values := int(v[0])
|
|
||||||
if len(v[1:]) != values || values < 39 {
|
|
||||||
return V4Ops{}, fmt.Errorf("invalid V4Ops line %q", v)
|
|
||||||
}
|
|
||||||
|
|
||||||
stats := V4Ops{
|
|
||||||
Op0Unused: v[1],
|
|
||||||
Op1Unused: v[2],
|
|
||||||
Op2Future: v[3],
|
|
||||||
Access: v[4],
|
|
||||||
Close: v[5],
|
|
||||||
Commit: v[6],
|
|
||||||
Create: v[7],
|
|
||||||
DelegPurge: v[8],
|
|
||||||
DelegReturn: v[9],
|
|
||||||
GetAttr: v[10],
|
|
||||||
GetFH: v[11],
|
|
||||||
Link: v[12],
|
|
||||||
Lock: v[13],
|
|
||||||
Lockt: v[14],
|
|
||||||
Locku: v[15],
|
|
||||||
Lookup: v[16],
|
|
||||||
LookupRoot: v[17],
|
|
||||||
Nverify: v[18],
|
|
||||||
Open: v[19],
|
|
||||||
OpenAttr: v[20],
|
|
||||||
OpenConfirm: v[21],
|
|
||||||
OpenDgrd: v[22],
|
|
||||||
PutFH: v[23],
|
|
||||||
PutPubFH: v[24],
|
|
||||||
PutRootFH: v[25],
|
|
||||||
Read: v[26],
|
|
||||||
ReadDir: v[27],
|
|
||||||
ReadLink: v[28],
|
|
||||||
Remove: v[29],
|
|
||||||
Rename: v[30],
|
|
||||||
Renew: v[31],
|
|
||||||
RestoreFH: v[32],
|
|
||||||
SaveFH: v[33],
|
|
||||||
SecInfo: v[34],
|
|
||||||
SetAttr: v[35],
|
|
||||||
Verify: v[36],
|
|
||||||
Write: v[37],
|
|
||||||
RelLockOwner: v[38],
|
|
||||||
}
|
|
||||||
|
|
||||||
return stats, nil
|
|
||||||
}
|
|
|
@ -1,67 +0,0 @@
|
||||||
// Copyright 2018 The Prometheus Authors
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package nfs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/prometheus/procfs/internal/util"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ParseClientRPCStats returns stats read from /proc/net/rpc/nfs
|
|
||||||
func ParseClientRPCStats(r io.Reader) (*ClientRPCStats, error) {
|
|
||||||
stats := &ClientRPCStats{}
|
|
||||||
|
|
||||||
scanner := bufio.NewScanner(r)
|
|
||||||
for scanner.Scan() {
|
|
||||||
line := scanner.Text()
|
|
||||||
parts := strings.Fields(scanner.Text())
|
|
||||||
// require at least <key> <value>
|
|
||||||
if len(parts) < 2 {
|
|
||||||
return nil, fmt.Errorf("invalid NFS metric line %q", line)
|
|
||||||
}
|
|
||||||
|
|
||||||
values, err := util.ParseUint64s(parts[1:])
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error parsing NFS metric line: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch metricLine := parts[0]; metricLine {
|
|
||||||
case "net":
|
|
||||||
stats.Network, err = parseNetwork(values)
|
|
||||||
case "rpc":
|
|
||||||
stats.ClientRPC, err = parseClientRPC(values)
|
|
||||||
case "proc2":
|
|
||||||
stats.V2Stats, err = parseV2Stats(values)
|
|
||||||
case "proc3":
|
|
||||||
stats.V3Stats, err = parseV3Stats(values)
|
|
||||||
case "proc4":
|
|
||||||
stats.ClientV4Stats, err = parseClientV4Stats(values)
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("unknown NFS metric line %q", metricLine)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("errors parsing NFS metric line: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := scanner.Err(); err != nil {
|
|
||||||
return nil, fmt.Errorf("error scanning NFS file: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return stats, nil
|
|
||||||
}
|
|
|
@ -1,89 +0,0 @@
|
||||||
// Copyright 2018 The Prometheus Authors
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package nfs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/prometheus/procfs/internal/util"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ParseServerRPCStats returns stats read from /proc/net/rpc/nfsd
|
|
||||||
func ParseServerRPCStats(r io.Reader) (*ServerRPCStats, error) {
|
|
||||||
stats := &ServerRPCStats{}
|
|
||||||
|
|
||||||
scanner := bufio.NewScanner(r)
|
|
||||||
for scanner.Scan() {
|
|
||||||
line := scanner.Text()
|
|
||||||
parts := strings.Fields(scanner.Text())
|
|
||||||
// require at least <key> <value>
|
|
||||||
if len(parts) < 2 {
|
|
||||||
return nil, fmt.Errorf("invalid NFSd metric line %q", line)
|
|
||||||
}
|
|
||||||
label := parts[0]
|
|
||||||
|
|
||||||
var values []uint64
|
|
||||||
var err error
|
|
||||||
if label == "th" {
|
|
||||||
if len(parts) < 3 {
|
|
||||||
return nil, fmt.Errorf("invalid NFSd th metric line %q", line)
|
|
||||||
}
|
|
||||||
values, err = util.ParseUint64s(parts[1:3])
|
|
||||||
} else {
|
|
||||||
values, err = util.ParseUint64s(parts[1:])
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error parsing NFSd metric line: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch metricLine := parts[0]; metricLine {
|
|
||||||
case "rc":
|
|
||||||
stats.ReplyCache, err = parseReplyCache(values)
|
|
||||||
case "fh":
|
|
||||||
stats.FileHandles, err = parseFileHandles(values)
|
|
||||||
case "io":
|
|
||||||
stats.InputOutput, err = parseInputOutput(values)
|
|
||||||
case "th":
|
|
||||||
stats.Threads, err = parseThreads(values)
|
|
||||||
case "ra":
|
|
||||||
stats.ReadAheadCache, err = parseReadAheadCache(values)
|
|
||||||
case "net":
|
|
||||||
stats.Network, err = parseNetwork(values)
|
|
||||||
case "rpc":
|
|
||||||
stats.ServerRPC, err = parseServerRPC(values)
|
|
||||||
case "proc2":
|
|
||||||
stats.V2Stats, err = parseV2Stats(values)
|
|
||||||
case "proc3":
|
|
||||||
stats.V3Stats, err = parseV3Stats(values)
|
|
||||||
case "proc4":
|
|
||||||
stats.ServerV4Stats, err = parseServerV4Stats(values)
|
|
||||||
case "proc4ops":
|
|
||||||
stats.V4Ops, err = parseV4Ops(values)
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("unknown NFSd metric line %q", metricLine)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("errors parsing NFSd metric line: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := scanner.Err(); err != nil {
|
|
||||||
return nil, fmt.Errorf("error scanning NFSd file: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return stats, nil
|
|
||||||
}
|
|
|
@ -20,6 +20,9 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/prometheus/procfs/internal/fs"
|
||||||
|
"github.com/prometheus/procfs/internal/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Proc provides information about a running process.
|
// Proc provides information about a running process.
|
||||||
|
@ -27,7 +30,7 @@ type Proc struct {
|
||||||
// The process ID.
|
// The process ID.
|
||||||
PID int
|
PID int
|
||||||
|
|
||||||
fs FS
|
fs fs.FS
|
||||||
}
|
}
|
||||||
|
|
||||||
// Procs represents a list of Proc structs.
|
// Procs represents a list of Proc structs.
|
||||||
|
@ -52,7 +55,7 @@ func NewProc(pid int) (Proc, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Proc{}, err
|
return Proc{}, err
|
||||||
}
|
}
|
||||||
return fs.NewProc(pid)
|
return fs.Proc(pid)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AllProcs returns a list of all currently available processes under /proc.
|
// AllProcs returns a list of all currently available processes under /proc.
|
||||||
|
@ -66,28 +69,35 @@ func AllProcs() (Procs, error) {
|
||||||
|
|
||||||
// Self returns a process for the current process.
|
// Self returns a process for the current process.
|
||||||
func (fs FS) Self() (Proc, error) {
|
func (fs FS) Self() (Proc, error) {
|
||||||
p, err := os.Readlink(fs.Path("self"))
|
p, err := os.Readlink(fs.proc.Path("self"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Proc{}, err
|
return Proc{}, err
|
||||||
}
|
}
|
||||||
pid, err := strconv.Atoi(strings.Replace(p, string(fs), "", -1))
|
pid, err := strconv.Atoi(strings.Replace(p, string(fs.proc), "", -1))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Proc{}, err
|
return Proc{}, err
|
||||||
}
|
}
|
||||||
return fs.NewProc(pid)
|
return fs.Proc(pid)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewProc returns a process for the given pid.
|
// NewProc returns a process for the given pid.
|
||||||
|
//
|
||||||
|
// Deprecated: use fs.Proc() instead
|
||||||
func (fs FS) NewProc(pid int) (Proc, error) {
|
func (fs FS) NewProc(pid int) (Proc, error) {
|
||||||
if _, err := os.Stat(fs.Path(strconv.Itoa(pid))); err != nil {
|
return fs.Proc(pid)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Proc returns a process for the given pid.
|
||||||
|
func (fs FS) Proc(pid int) (Proc, error) {
|
||||||
|
if _, err := os.Stat(fs.proc.Path(strconv.Itoa(pid))); err != nil {
|
||||||
return Proc{}, err
|
return Proc{}, err
|
||||||
}
|
}
|
||||||
return Proc{PID: pid, fs: fs}, nil
|
return Proc{PID: pid, fs: fs.proc}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// AllProcs returns a list of all currently available processes.
|
// AllProcs returns a list of all currently available processes.
|
||||||
func (fs FS) AllProcs() (Procs, error) {
|
func (fs FS) AllProcs() (Procs, error) {
|
||||||
d, err := os.Open(fs.Path())
|
d, err := os.Open(fs.proc.Path())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Procs{}, err
|
return Procs{}, err
|
||||||
}
|
}
|
||||||
|
@ -104,7 +114,7 @@ func (fs FS) AllProcs() (Procs, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
p = append(p, Proc{PID: int(pid), fs: fs})
|
p = append(p, Proc{PID: int(pid), fs: fs.proc})
|
||||||
}
|
}
|
||||||
|
|
||||||
return p, nil
|
return p, nil
|
||||||
|
@ -112,13 +122,7 @@ func (fs FS) AllProcs() (Procs, error) {
|
||||||
|
|
||||||
// CmdLine returns the command line of a process.
|
// CmdLine returns the command line of a process.
|
||||||
func (p Proc) CmdLine() ([]string, error) {
|
func (p Proc) CmdLine() ([]string, error) {
|
||||||
f, err := os.Open(p.path("cmdline"))
|
data, err := util.ReadFileNoStat(p.path("cmdline"))
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
data, err := ioutil.ReadAll(f)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -132,13 +136,7 @@ func (p Proc) CmdLine() ([]string, error) {
|
||||||
|
|
||||||
// Comm returns the command name of a process.
|
// Comm returns the command name of a process.
|
||||||
func (p Proc) Comm() (string, error) {
|
func (p Proc) Comm() (string, error) {
|
||||||
f, err := os.Open(p.path("comm"))
|
data, err := util.ReadFileNoStat(p.path("comm"))
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
data, err := ioutil.ReadAll(f)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -156,6 +154,26 @@ func (p Proc) Executable() (string, error) {
|
||||||
return exe, err
|
return exe, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cwd returns the absolute path to the current working directory of the process.
|
||||||
|
func (p Proc) Cwd() (string, error) {
|
||||||
|
wd, err := os.Readlink(p.path("cwd"))
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return wd, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// RootDir returns the absolute path to the process's root directory (as set by chroot)
|
||||||
|
func (p Proc) RootDir() (string, error) {
|
||||||
|
rdir, err := os.Readlink(p.path("root"))
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return rdir, err
|
||||||
|
}
|
||||||
|
|
||||||
// FileDescriptors returns the currently open file descriptors of a process.
|
// FileDescriptors returns the currently open file descriptors of a process.
|
||||||
func (p Proc) FileDescriptors() ([]uintptr, error) {
|
func (p Proc) FileDescriptors() ([]uintptr, error) {
|
||||||
names, err := p.fileDescriptors()
|
names, err := p.fileDescriptors()
|
||||||
|
@ -218,6 +236,18 @@ func (p Proc) MountStats() ([]*Mount, error) {
|
||||||
return parseMountStats(f)
|
return parseMountStats(f)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MountInfo retrieves mount information for mount points in a
|
||||||
|
// process's namespace.
|
||||||
|
// It supplies information missing in `/proc/self/mounts` and
|
||||||
|
// fixes various other problems with that file too.
|
||||||
|
func (p Proc) MountInfo() ([]*MountInfo, error) {
|
||||||
|
data, err := util.ReadFileNoStat(p.path("mountinfo"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return parseMountInfo(data)
|
||||||
|
}
|
||||||
|
|
||||||
func (p Proc) fileDescriptors() ([]string, error) {
|
func (p Proc) fileDescriptors() ([]string, error) {
|
||||||
d, err := os.Open(p.path("fd"))
|
d, err := os.Open(p.path("fd"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -236,3 +266,33 @@ func (p Proc) fileDescriptors() ([]string, error) {
|
||||||
func (p Proc) path(pa ...string) string {
|
func (p Proc) path(pa ...string) string {
|
||||||
return p.fs.Path(append([]string{strconv.Itoa(p.PID)}, pa...)...)
|
return p.fs.Path(append([]string{strconv.Itoa(p.PID)}, pa...)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FileDescriptorsInfo retrieves information about all file descriptors of
|
||||||
|
// the process.
|
||||||
|
func (p Proc) FileDescriptorsInfo() (ProcFDInfos, error) {
|
||||||
|
names, err := p.fileDescriptors()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var fdinfos ProcFDInfos
|
||||||
|
|
||||||
|
for _, n := range names {
|
||||||
|
fdinfo, err := p.FDInfo(n)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fdinfos = append(fdinfos, *fdinfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fdinfos, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Schedstat returns task scheduling information for the process.
|
||||||
|
func (p Proc) Schedstat() (ProcSchedstat, error) {
|
||||||
|
contents, err := ioutil.ReadFile(p.path("schedstat"))
|
||||||
|
if err != nil {
|
||||||
|
return ProcSchedstat{}, err
|
||||||
|
}
|
||||||
|
return parseProcSchedstat(string(contents))
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
// Copyright 2019 The Prometheus Authors
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package procfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/prometheus/procfs/internal/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Environ reads process environments from /proc/<pid>/environ
|
||||||
|
func (p Proc) Environ() ([]string, error) {
|
||||||
|
environments := make([]string, 0)
|
||||||
|
|
||||||
|
data, err := util.ReadFileNoStat(p.path("environ"))
|
||||||
|
if err != nil {
|
||||||
|
return environments, err
|
||||||
|
}
|
||||||
|
|
||||||
|
environments = strings.Split(string(data), "\000")
|
||||||
|
if len(environments) > 0 {
|
||||||
|
environments = environments[:len(environments)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
return environments, nil
|
||||||
|
}
|
|
@ -0,0 +1,133 @@
|
||||||
|
// Copyright 2019 The Prometheus Authors
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package procfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"regexp"
|
||||||
|
|
||||||
|
"github.com/prometheus/procfs/internal/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Regexp variables
|
||||||
|
var (
|
||||||
|
rPos = regexp.MustCompile(`^pos:\s+(\d+)$`)
|
||||||
|
rFlags = regexp.MustCompile(`^flags:\s+(\d+)$`)
|
||||||
|
rMntID = regexp.MustCompile(`^mnt_id:\s+(\d+)$`)
|
||||||
|
rInotify = regexp.MustCompile(`^inotify`)
|
||||||
|
rInotifyParts = regexp.MustCompile(`^inotify\s+wd:([0-9a-f]+)\s+ino:([0-9a-f]+)\s+sdev:([0-9a-f]+)(?:\s+mask:([0-9a-f]+))?`)
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProcFDInfo contains represents file descriptor information.
|
||||||
|
type ProcFDInfo struct {
|
||||||
|
// File descriptor
|
||||||
|
FD string
|
||||||
|
// File offset
|
||||||
|
Pos string
|
||||||
|
// File access mode and status flags
|
||||||
|
Flags string
|
||||||
|
// Mount point ID
|
||||||
|
MntID string
|
||||||
|
// List of inotify lines (structed) in the fdinfo file (kernel 3.8+ only)
|
||||||
|
InotifyInfos []InotifyInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
// FDInfo constructor. On kernels older than 3.8, InotifyInfos will always be empty.
|
||||||
|
func (p Proc) FDInfo(fd string) (*ProcFDInfo, error) {
|
||||||
|
data, err := util.ReadFileNoStat(p.path("fdinfo", fd))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var text, pos, flags, mntid string
|
||||||
|
var inotify []InotifyInfo
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(bytes.NewReader(data))
|
||||||
|
for scanner.Scan() {
|
||||||
|
text = scanner.Text()
|
||||||
|
if rPos.MatchString(text) {
|
||||||
|
pos = rPos.FindStringSubmatch(text)[1]
|
||||||
|
} else if rFlags.MatchString(text) {
|
||||||
|
flags = rFlags.FindStringSubmatch(text)[1]
|
||||||
|
} else if rMntID.MatchString(text) {
|
||||||
|
mntid = rMntID.FindStringSubmatch(text)[1]
|
||||||
|
} else if rInotify.MatchString(text) {
|
||||||
|
newInotify, err := parseInotifyInfo(text)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
inotify = append(inotify, *newInotify)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
i := &ProcFDInfo{
|
||||||
|
FD: fd,
|
||||||
|
Pos: pos,
|
||||||
|
Flags: flags,
|
||||||
|
MntID: mntid,
|
||||||
|
InotifyInfos: inotify,
|
||||||
|
}
|
||||||
|
|
||||||
|
return i, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// InotifyInfo represents a single inotify line in the fdinfo file.
|
||||||
|
type InotifyInfo struct {
|
||||||
|
// Watch descriptor number
|
||||||
|
WD string
|
||||||
|
// Inode number
|
||||||
|
Ino string
|
||||||
|
// Device ID
|
||||||
|
Sdev string
|
||||||
|
// Mask of events being monitored
|
||||||
|
Mask string
|
||||||
|
}
|
||||||
|
|
||||||
|
// InotifyInfo constructor. Only available on kernel 3.8+.
|
||||||
|
func parseInotifyInfo(line string) (*InotifyInfo, error) {
|
||||||
|
m := rInotifyParts.FindStringSubmatch(line)
|
||||||
|
if len(m) >= 4 {
|
||||||
|
var mask string
|
||||||
|
if len(m) == 5 {
|
||||||
|
mask = m[4]
|
||||||
|
}
|
||||||
|
i := &InotifyInfo{
|
||||||
|
WD: m[1],
|
||||||
|
Ino: m[2],
|
||||||
|
Sdev: m[3],
|
||||||
|
Mask: mask,
|
||||||
|
}
|
||||||
|
return i, nil
|
||||||
|
}
|
||||||
|
return nil, errors.New("invalid inode entry: " + line)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProcFDInfos represents a list of ProcFDInfo structs.
|
||||||
|
type ProcFDInfos []ProcFDInfo
|
||||||
|
|
||||||
|
func (p ProcFDInfos) Len() int { return len(p) }
|
||||||
|
func (p ProcFDInfos) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
||||||
|
func (p ProcFDInfos) Less(i, j int) bool { return p[i].FD < p[j].FD }
|
||||||
|
|
||||||
|
// InotifyWatchLen returns the total number of inotify watches
|
||||||
|
func (p ProcFDInfos) InotifyWatchLen() (int, error) {
|
||||||
|
length := 0
|
||||||
|
for _, f := range p {
|
||||||
|
length += len(f.InotifyInfos)
|
||||||
|
}
|
||||||
|
|
||||||
|
return length, nil
|
||||||
|
}
|
|
@ -15,8 +15,8 @@ package procfs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"github.com/prometheus/procfs/internal/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ProcIO models the content of /proc/<pid>/io.
|
// ProcIO models the content of /proc/<pid>/io.
|
||||||
|
@ -39,17 +39,11 @@ type ProcIO struct {
|
||||||
CancelledWriteBytes int64
|
CancelledWriteBytes int64
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewIO creates a new ProcIO instance from a given Proc instance.
|
// IO creates a new ProcIO instance from a given Proc instance.
|
||||||
func (p Proc) NewIO() (ProcIO, error) {
|
func (p Proc) IO() (ProcIO, error) {
|
||||||
pio := ProcIO{}
|
pio := ProcIO{}
|
||||||
|
|
||||||
f, err := os.Open(p.path("io"))
|
data, err := util.ReadFileNoStat(p.path("io"))
|
||||||
if err != nil {
|
|
||||||
return pio, err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
data, err := ioutil.ReadAll(f)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return pio, err
|
return pio, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,7 +78,14 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewLimits returns the current soft limits of the process.
|
// NewLimits returns the current soft limits of the process.
|
||||||
|
//
|
||||||
|
// Deprecated: use p.Limits() instead
|
||||||
func (p Proc) NewLimits() (ProcLimits, error) {
|
func (p Proc) NewLimits() (ProcLimits, error) {
|
||||||
|
return p.Limits()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limits returns the current soft limits of the process.
|
||||||
|
func (p Proc) Limits() (ProcLimits, error) {
|
||||||
f, err := os.Open(p.path("limits"))
|
f, err := os.Open(p.path("limits"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ProcLimits{}, err
|
return ProcLimits{}, err
|
||||||
|
|
|
@ -0,0 +1,208 @@
|
||||||
|
// Copyright 2019 The Prometheus Authors
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package procfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ProcMapPermissions struct {
|
||||||
|
// mapping has the [R]ead flag set
|
||||||
|
Read bool
|
||||||
|
// mapping has the [W]rite flag set
|
||||||
|
Write bool
|
||||||
|
// mapping has the [X]ecutable flag set
|
||||||
|
Execute bool
|
||||||
|
// mapping has the [S]hared flag set
|
||||||
|
Shared bool
|
||||||
|
// mapping is marked as [P]rivate (copy on write)
|
||||||
|
Private bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProcMap contains the process memory-mappings of the process,
|
||||||
|
// read from /proc/[pid]/maps
|
||||||
|
type ProcMap struct {
|
||||||
|
// The start address of current mapping.
|
||||||
|
StartAddr uintptr
|
||||||
|
// The end address of the current mapping
|
||||||
|
EndAddr uintptr
|
||||||
|
// The permissions for this mapping
|
||||||
|
Perms *ProcMapPermissions
|
||||||
|
// The current offset into the file/fd (e.g., shared libs)
|
||||||
|
Offset int64
|
||||||
|
// Device owner of this mapping (major:minor) in Mkdev format.
|
||||||
|
Dev uint64
|
||||||
|
// The inode of the device above
|
||||||
|
Inode uint64
|
||||||
|
// The file or psuedofile (or empty==anonymous)
|
||||||
|
Pathname string
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseDevice parses the device token of a line and converts it to a dev_t
|
||||||
|
// (mkdev) like structure.
|
||||||
|
func parseDevice(s string) (uint64, error) {
|
||||||
|
toks := strings.Split(s, ":")
|
||||||
|
if len(toks) < 2 {
|
||||||
|
return 0, fmt.Errorf("unexpected number of fields")
|
||||||
|
}
|
||||||
|
|
||||||
|
major, err := strconv.ParseUint(toks[0], 16, 0)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
minor, err := strconv.ParseUint(toks[1], 16, 0)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return unix.Mkdev(uint32(major), uint32(minor)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseAddress just converts a hex-string to a uintptr
|
||||||
|
func parseAddress(s string) (uintptr, error) {
|
||||||
|
a, err := strconv.ParseUint(s, 16, 0)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return uintptr(a), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseAddresses parses the start-end address
|
||||||
|
func parseAddresses(s string) (uintptr, uintptr, error) {
|
||||||
|
toks := strings.Split(s, "-")
|
||||||
|
if len(toks) < 2 {
|
||||||
|
return 0, 0, fmt.Errorf("invalid address")
|
||||||
|
}
|
||||||
|
|
||||||
|
saddr, err := parseAddress(toks[0])
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
eaddr, err := parseAddress(toks[1])
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return saddr, eaddr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parsePermissions parses a token and returns any that are set.
|
||||||
|
func parsePermissions(s string) (*ProcMapPermissions, error) {
|
||||||
|
if len(s) < 4 {
|
||||||
|
return nil, fmt.Errorf("invalid permissions token")
|
||||||
|
}
|
||||||
|
|
||||||
|
perms := ProcMapPermissions{}
|
||||||
|
for _, ch := range s {
|
||||||
|
switch ch {
|
||||||
|
case 'r':
|
||||||
|
perms.Read = true
|
||||||
|
case 'w':
|
||||||
|
perms.Write = true
|
||||||
|
case 'x':
|
||||||
|
perms.Execute = true
|
||||||
|
case 'p':
|
||||||
|
perms.Private = true
|
||||||
|
case 's':
|
||||||
|
perms.Shared = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &perms, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseProcMap will attempt to parse a single line within a proc/[pid]/maps
|
||||||
|
// buffer.
|
||||||
|
func parseProcMap(text string) (*ProcMap, error) {
|
||||||
|
fields := strings.Fields(text)
|
||||||
|
if len(fields) < 5 {
|
||||||
|
return nil, fmt.Errorf("truncated procmap entry")
|
||||||
|
}
|
||||||
|
|
||||||
|
saddr, eaddr, err := parseAddresses(fields[0])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
perms, err := parsePermissions(fields[1])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
offset, err := strconv.ParseInt(fields[2], 16, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
device, err := parseDevice(fields[3])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
inode, err := strconv.ParseUint(fields[4], 10, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pathname := ""
|
||||||
|
|
||||||
|
if len(fields) >= 5 {
|
||||||
|
pathname = strings.Join(fields[5:], " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ProcMap{
|
||||||
|
StartAddr: saddr,
|
||||||
|
EndAddr: eaddr,
|
||||||
|
Perms: perms,
|
||||||
|
Offset: offset,
|
||||||
|
Dev: device,
|
||||||
|
Inode: inode,
|
||||||
|
Pathname: pathname,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProcMaps reads from /proc/[pid]/maps to get the memory-mappings of the
|
||||||
|
// process.
|
||||||
|
func (p Proc) ProcMaps() ([]*ProcMap, error) {
|
||||||
|
file, err := os.Open(p.path("maps"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
maps := []*ProcMap{}
|
||||||
|
scan := bufio.NewScanner(file)
|
||||||
|
|
||||||
|
for scan.Scan() {
|
||||||
|
m, err := parseProcMap(scan.Text())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
maps = append(maps, m)
|
||||||
|
}
|
||||||
|
|
||||||
|
return maps, nil
|
||||||
|
}
|
|
@ -29,9 +29,9 @@ type Namespace struct {
|
||||||
// Namespaces contains all of the namespaces that the process is contained in.
|
// Namespaces contains all of the namespaces that the process is contained in.
|
||||||
type Namespaces map[string]Namespace
|
type Namespaces map[string]Namespace
|
||||||
|
|
||||||
// NewNamespaces reads from /proc/[pid/ns/* to get the namespaces of which the
|
// Namespaces reads from /proc/<pid>/ns/* to get the namespaces of which the
|
||||||
// process is a member.
|
// process is a member.
|
||||||
func (p Proc) NewNamespaces() (Namespaces, error) {
|
func (p Proc) Namespaces() (Namespaces, error) {
|
||||||
d, err := os.Open(p.path("ns"))
|
d, err := os.Open(p.path("ns"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -0,0 +1,100 @@
|
||||||
|
// Copyright 2019 The Prometheus Authors
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package procfs
|
||||||
|
|
||||||
|
// The PSI / pressure interface is described at
|
||||||
|
// https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/accounting/psi.txt
|
||||||
|
// Each resource (cpu, io, memory, ...) is exposed as a single file.
|
||||||
|
// Each file may contain up to two lines, one for "some" pressure and one for "full" pressure.
|
||||||
|
// Each line contains several averages (over n seconds) and a total in µs.
|
||||||
|
//
|
||||||
|
// Example io pressure file:
|
||||||
|
// > some avg10=0.06 avg60=0.21 avg300=0.99 total=8537362
|
||||||
|
// > full avg10=0.00 avg60=0.13 avg300=0.96 total=8183134
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/prometheus/procfs/internal/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
const lineFormat = "avg10=%f avg60=%f avg300=%f total=%d"
|
||||||
|
|
||||||
|
// PSILine is a single line of values as returned by /proc/pressure/*
|
||||||
|
// The Avg entries are averages over n seconds, as a percentage
|
||||||
|
// The Total line is in microseconds
|
||||||
|
type PSILine struct {
|
||||||
|
Avg10 float64
|
||||||
|
Avg60 float64
|
||||||
|
Avg300 float64
|
||||||
|
Total uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// PSIStats represent pressure stall information from /proc/pressure/*
|
||||||
|
// Some indicates the share of time in which at least some tasks are stalled
|
||||||
|
// Full indicates the share of time in which all non-idle tasks are stalled simultaneously
|
||||||
|
type PSIStats struct {
|
||||||
|
Some *PSILine
|
||||||
|
Full *PSILine
|
||||||
|
}
|
||||||
|
|
||||||
|
// PSIStatsForResource reads pressure stall information for the specified
|
||||||
|
// resource from /proc/pressure/<resource>. At time of writing this can be
|
||||||
|
// either "cpu", "memory" or "io".
|
||||||
|
func (fs FS) PSIStatsForResource(resource string) (PSIStats, error) {
|
||||||
|
data, err := util.ReadFileNoStat(fs.proc.Path(fmt.Sprintf("%s/%s", "pressure", resource)))
|
||||||
|
if err != nil {
|
||||||
|
return PSIStats{}, fmt.Errorf("psi_stats: unavailable for %s", resource)
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsePSIStats(resource, bytes.NewReader(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
// parsePSIStats parses the specified file for pressure stall information
|
||||||
|
func parsePSIStats(resource string, r io.Reader) (PSIStats, error) {
|
||||||
|
psiStats := PSIStats{}
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(r)
|
||||||
|
for scanner.Scan() {
|
||||||
|
l := scanner.Text()
|
||||||
|
prefix := strings.Split(l, " ")[0]
|
||||||
|
switch prefix {
|
||||||
|
case "some":
|
||||||
|
psi := PSILine{}
|
||||||
|
_, err := fmt.Sscanf(l, fmt.Sprintf("some %s", lineFormat), &psi.Avg10, &psi.Avg60, &psi.Avg300, &psi.Total)
|
||||||
|
if err != nil {
|
||||||
|
return PSIStats{}, err
|
||||||
|
}
|
||||||
|
psiStats.Some = &psi
|
||||||
|
case "full":
|
||||||
|
psi := PSILine{}
|
||||||
|
_, err := fmt.Sscanf(l, fmt.Sprintf("full %s", lineFormat), &psi.Avg10, &psi.Avg60, &psi.Avg300, &psi.Total)
|
||||||
|
if err != nil {
|
||||||
|
return PSIStats{}, err
|
||||||
|
}
|
||||||
|
psiStats.Full = &psi
|
||||||
|
default:
|
||||||
|
// If we encounter a line with an unknown prefix, ignore it and move on
|
||||||
|
// Should new measurement types be added in the future we'll simply ignore them instead
|
||||||
|
// of erroring on retrieval
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return psiStats, nil
|
||||||
|
}
|
|
@ -16,8 +16,10 @@ package procfs
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/prometheus/procfs/internal/fs"
|
||||||
|
"github.com/prometheus/procfs/internal/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Originally, this USER_HZ value was dynamically retrieved via a sysconf call
|
// Originally, this USER_HZ value was dynamically retrieved via a sysconf call
|
||||||
|
@ -95,22 +97,23 @@ type ProcStat struct {
|
||||||
// in clock ticks.
|
// in clock ticks.
|
||||||
Starttime uint64
|
Starttime uint64
|
||||||
// Virtual memory size in bytes.
|
// Virtual memory size in bytes.
|
||||||
VSize int
|
VSize uint
|
||||||
// Resident set size in pages.
|
// Resident set size in pages.
|
||||||
RSS int
|
RSS int
|
||||||
|
|
||||||
fs FS
|
proc fs.FS
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewStat returns the current status information of the process.
|
// NewStat returns the current status information of the process.
|
||||||
|
//
|
||||||
|
// Deprecated: use p.Stat() instead
|
||||||
func (p Proc) NewStat() (ProcStat, error) {
|
func (p Proc) NewStat() (ProcStat, error) {
|
||||||
f, err := os.Open(p.path("stat"))
|
return p.Stat()
|
||||||
if err != nil {
|
}
|
||||||
return ProcStat{}, err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
data, err := ioutil.ReadAll(f)
|
// Stat returns the current status information of the process.
|
||||||
|
func (p Proc) Stat() (ProcStat, error) {
|
||||||
|
data, err := util.ReadFileNoStat(p.path("stat"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ProcStat{}, err
|
return ProcStat{}, err
|
||||||
}
|
}
|
||||||
|
@ -118,7 +121,7 @@ func (p Proc) NewStat() (ProcStat, error) {
|
||||||
var (
|
var (
|
||||||
ignore int
|
ignore int
|
||||||
|
|
||||||
s = ProcStat{PID: p.PID, fs: p.fs}
|
s = ProcStat{PID: p.PID, proc: p.fs}
|
||||||
l = bytes.Index(data, []byte("("))
|
l = bytes.Index(data, []byte("("))
|
||||||
r = bytes.LastIndex(data, []byte(")"))
|
r = bytes.LastIndex(data, []byte(")"))
|
||||||
)
|
)
|
||||||
|
@ -164,7 +167,7 @@ func (p Proc) NewStat() (ProcStat, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// VirtualMemory returns the virtual memory size in bytes.
|
// VirtualMemory returns the virtual memory size in bytes.
|
||||||
func (s ProcStat) VirtualMemory() int {
|
func (s ProcStat) VirtualMemory() uint {
|
||||||
return s.VSize
|
return s.VSize
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -175,7 +178,8 @@ func (s ProcStat) ResidentMemory() int {
|
||||||
|
|
||||||
// StartTime returns the unix timestamp of the process in seconds.
|
// StartTime returns the unix timestamp of the process in seconds.
|
||||||
func (s ProcStat) StartTime() (float64, error) {
|
func (s ProcStat) StartTime() (float64, error) {
|
||||||
stat, err := s.fs.NewStat()
|
fs := FS{proc: s.proc}
|
||||||
|
stat, err := fs.Stat()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,166 @@
|
||||||
|
// Copyright 2018 The Prometheus Authors
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package procfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/prometheus/procfs/internal/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProcStatus provides status information about the process,
|
||||||
|
// read from /proc/[pid]/stat.
|
||||||
|
type ProcStatus struct {
|
||||||
|
// The process ID.
|
||||||
|
PID int
|
||||||
|
// The process name.
|
||||||
|
Name string
|
||||||
|
|
||||||
|
// Thread group ID.
|
||||||
|
TGID int
|
||||||
|
|
||||||
|
// Peak virtual memory size.
|
||||||
|
VmPeak uint64 // nolint:golint
|
||||||
|
// Virtual memory size.
|
||||||
|
VmSize uint64 // nolint:golint
|
||||||
|
// Locked memory size.
|
||||||
|
VmLck uint64 // nolint:golint
|
||||||
|
// Pinned memory size.
|
||||||
|
VmPin uint64 // nolint:golint
|
||||||
|
// Peak resident set size.
|
||||||
|
VmHWM uint64 // nolint:golint
|
||||||
|
// Resident set size (sum of RssAnnon RssFile and RssShmem).
|
||||||
|
VmRSS uint64 // nolint:golint
|
||||||
|
// Size of resident anonymous memory.
|
||||||
|
RssAnon uint64 // nolint:golint
|
||||||
|
// Size of resident file mappings.
|
||||||
|
RssFile uint64 // nolint:golint
|
||||||
|
// Size of resident shared memory.
|
||||||
|
RssShmem uint64 // nolint:golint
|
||||||
|
// Size of data segments.
|
||||||
|
VmData uint64 // nolint:golint
|
||||||
|
// Size of stack segments.
|
||||||
|
VmStk uint64 // nolint:golint
|
||||||
|
// Size of text segments.
|
||||||
|
VmExe uint64 // nolint:golint
|
||||||
|
// Shared library code size.
|
||||||
|
VmLib uint64 // nolint:golint
|
||||||
|
// Page table entries size.
|
||||||
|
VmPTE uint64 // nolint:golint
|
||||||
|
// Size of second-level page tables.
|
||||||
|
VmPMD uint64 // nolint:golint
|
||||||
|
// Swapped-out virtual memory size by anonymous private.
|
||||||
|
VmSwap uint64 // nolint:golint
|
||||||
|
// Size of hugetlb memory portions
|
||||||
|
HugetlbPages uint64
|
||||||
|
|
||||||
|
// Number of voluntary context switches.
|
||||||
|
VoluntaryCtxtSwitches uint64
|
||||||
|
// Number of involuntary context switches.
|
||||||
|
NonVoluntaryCtxtSwitches uint64
|
||||||
|
|
||||||
|
// UIDs of the process (Real, effective, saved set, and filesystem UIDs (GIDs))
|
||||||
|
UIDs [4]string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStatus returns the current status information of the process.
|
||||||
|
func (p Proc) NewStatus() (ProcStatus, error) {
|
||||||
|
data, err := util.ReadFileNoStat(p.path("status"))
|
||||||
|
if err != nil {
|
||||||
|
return ProcStatus{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
s := ProcStatus{PID: p.PID}
|
||||||
|
|
||||||
|
lines := strings.Split(string(data), "\n")
|
||||||
|
for _, line := range lines {
|
||||||
|
if !bytes.Contains([]byte(line), []byte(":")) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
kv := strings.SplitN(line, ":", 2)
|
||||||
|
|
||||||
|
// removes spaces
|
||||||
|
k := string(strings.TrimSpace(kv[0]))
|
||||||
|
v := string(strings.TrimSpace(kv[1]))
|
||||||
|
// removes "kB"
|
||||||
|
v = string(bytes.Trim([]byte(v), " kB"))
|
||||||
|
|
||||||
|
// value to int when possible
|
||||||
|
// we can skip error check here, 'cause vKBytes is not used when value is a string
|
||||||
|
vKBytes, _ := strconv.ParseUint(v, 10, 64)
|
||||||
|
// convert kB to B
|
||||||
|
vBytes := vKBytes * 1024
|
||||||
|
|
||||||
|
s.fillStatus(k, v, vKBytes, vBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ProcStatus) fillStatus(k string, vString string, vUint uint64, vUintBytes uint64) {
|
||||||
|
switch k {
|
||||||
|
case "Tgid":
|
||||||
|
s.TGID = int(vUint)
|
||||||
|
case "Name":
|
||||||
|
s.Name = vString
|
||||||
|
case "Uid":
|
||||||
|
copy(s.UIDs[:], strings.Split(vString, "\t"))
|
||||||
|
case "VmPeak":
|
||||||
|
s.VmPeak = vUintBytes
|
||||||
|
case "VmSize":
|
||||||
|
s.VmSize = vUintBytes
|
||||||
|
case "VmLck":
|
||||||
|
s.VmLck = vUintBytes
|
||||||
|
case "VmPin":
|
||||||
|
s.VmPin = vUintBytes
|
||||||
|
case "VmHWM":
|
||||||
|
s.VmHWM = vUintBytes
|
||||||
|
case "VmRSS":
|
||||||
|
s.VmRSS = vUintBytes
|
||||||
|
case "RssAnon":
|
||||||
|
s.RssAnon = vUintBytes
|
||||||
|
case "RssFile":
|
||||||
|
s.RssFile = vUintBytes
|
||||||
|
case "RssShmem":
|
||||||
|
s.RssShmem = vUintBytes
|
||||||
|
case "VmData":
|
||||||
|
s.VmData = vUintBytes
|
||||||
|
case "VmStk":
|
||||||
|
s.VmStk = vUintBytes
|
||||||
|
case "VmExe":
|
||||||
|
s.VmExe = vUintBytes
|
||||||
|
case "VmLib":
|
||||||
|
s.VmLib = vUintBytes
|
||||||
|
case "VmPTE":
|
||||||
|
s.VmPTE = vUintBytes
|
||||||
|
case "VmPMD":
|
||||||
|
s.VmPMD = vUintBytes
|
||||||
|
case "VmSwap":
|
||||||
|
s.VmSwap = vUintBytes
|
||||||
|
case "HugetlbPages":
|
||||||
|
s.HugetlbPages = vUintBytes
|
||||||
|
case "voluntary_ctxt_switches":
|
||||||
|
s.VoluntaryCtxtSwitches = vUint
|
||||||
|
case "nonvoluntary_ctxt_switches":
|
||||||
|
s.NonVoluntaryCtxtSwitches = vUint
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TotalCtxtSwitches returns the total context switch.
|
||||||
|
func (s ProcStatus) TotalCtxtSwitches() uint64 {
|
||||||
|
return s.VoluntaryCtxtSwitches + s.NonVoluntaryCtxtSwitches
|
||||||
|
}
|
|
@ -0,0 +1,118 @@
|
||||||
|
// Copyright 2019 The Prometheus Authors
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package procfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
cpuLineRE = regexp.MustCompile(`cpu(\d+) (\d+) (\d+) (\d+) (\d+) (\d+) (\d+) (\d+) (\d+) (\d+)`)
|
||||||
|
procLineRE = regexp.MustCompile(`(\d+) (\d+) (\d+)`)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Schedstat contains scheduler statistics from /proc/schedstat
|
||||||
|
//
|
||||||
|
// See
|
||||||
|
// https://www.kernel.org/doc/Documentation/scheduler/sched-stats.txt
|
||||||
|
// for a detailed description of what these numbers mean.
|
||||||
|
//
|
||||||
|
// Note the current kernel documentation claims some of the time units are in
|
||||||
|
// jiffies when they are actually in nanoseconds since 2.6.23 with the
|
||||||
|
// introduction of CFS. A fix to the documentation is pending. See
|
||||||
|
// https://lore.kernel.org/patchwork/project/lkml/list/?series=403473
|
||||||
|
type Schedstat struct {
|
||||||
|
CPUs []*SchedstatCPU
|
||||||
|
}
|
||||||
|
|
||||||
|
// SchedstatCPU contains the values from one "cpu<N>" line
|
||||||
|
type SchedstatCPU struct {
|
||||||
|
CPUNum string
|
||||||
|
|
||||||
|
RunningNanoseconds uint64
|
||||||
|
WaitingNanoseconds uint64
|
||||||
|
RunTimeslices uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProcSchedstat contains the values from /proc/<pid>/schedstat
|
||||||
|
type ProcSchedstat struct {
|
||||||
|
RunningNanoseconds uint64
|
||||||
|
WaitingNanoseconds uint64
|
||||||
|
RunTimeslices uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// Schedstat reads data from /proc/schedstat
|
||||||
|
func (fs FS) Schedstat() (*Schedstat, error) {
|
||||||
|
file, err := os.Open(fs.proc.Path("schedstat"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
stats := &Schedstat{}
|
||||||
|
scanner := bufio.NewScanner(file)
|
||||||
|
|
||||||
|
for scanner.Scan() {
|
||||||
|
match := cpuLineRE.FindStringSubmatch(scanner.Text())
|
||||||
|
if match != nil {
|
||||||
|
cpu := &SchedstatCPU{}
|
||||||
|
cpu.CPUNum = match[1]
|
||||||
|
|
||||||
|
cpu.RunningNanoseconds, err = strconv.ParseUint(match[8], 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
cpu.WaitingNanoseconds, err = strconv.ParseUint(match[9], 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
cpu.RunTimeslices, err = strconv.ParseUint(match[10], 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
stats.CPUs = append(stats.CPUs, cpu)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return stats, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseProcSchedstat(contents string) (stats ProcSchedstat, err error) {
|
||||||
|
match := procLineRE.FindStringSubmatch(contents)
|
||||||
|
|
||||||
|
if match != nil {
|
||||||
|
stats.RunningNanoseconds, err = strconv.ParseUint(match[1], 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
stats.WaitingNanoseconds, err = strconv.ParseUint(match[2], 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
stats.RunTimeslices, err = strconv.ParseUint(match[3], 10, 64)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = errors.New("could not parse schedstat")
|
||||||
|
return
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue