Merge pull request #2007 from huanggze/dev-custom
feat: custom monitoring
This commit is contained in:
16
vendor/github.com/golang/snappy/.gitignore
generated
vendored
Normal file
16
vendor/github.com/golang/snappy/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
cmd/snappytool/snappytool
|
||||
testdata/bench
|
||||
|
||||
# These explicitly listed benchmark data files are for an obsolete version of
|
||||
# snappy_test.go.
|
||||
testdata/alice29.txt
|
||||
testdata/asyoulik.txt
|
||||
testdata/fireworks.jpeg
|
||||
testdata/geo.protodata
|
||||
testdata/html
|
||||
testdata/html_x_4
|
||||
testdata/kppkn.gtb
|
||||
testdata/lcet10.txt
|
||||
testdata/paper-100k.pdf
|
||||
testdata/plrabn12.txt
|
||||
testdata/urls.10K
|
||||
15
vendor/github.com/golang/snappy/AUTHORS
generated
vendored
Normal file
15
vendor/github.com/golang/snappy/AUTHORS
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
# This is the official list of Snappy-Go authors for copyright purposes.
|
||||
# This file is distinct from the CONTRIBUTORS files.
|
||||
# See the latter for an explanation.
|
||||
|
||||
# Names should be added to this file as
|
||||
# Name or Organization <email address>
|
||||
# The email address is not required for organizations.
|
||||
|
||||
# Please keep the list sorted.
|
||||
|
||||
Damian Gryski <dgryski@gmail.com>
|
||||
Google Inc.
|
||||
Jan Mercl <0xjnml@gmail.com>
|
||||
Rodolfo Carvalho <rhcarvalho@gmail.com>
|
||||
Sebastien Binet <seb.binet@gmail.com>
|
||||
37
vendor/github.com/golang/snappy/CONTRIBUTORS
generated
vendored
Normal file
37
vendor/github.com/golang/snappy/CONTRIBUTORS
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
# This is the official list of people who can contribute
|
||||
# (and typically have contributed) code to the Snappy-Go repository.
|
||||
# The AUTHORS file lists the copyright holders; this file
|
||||
# lists people. For example, Google employees are listed here
|
||||
# but not in AUTHORS, because Google holds the copyright.
|
||||
#
|
||||
# The submission process automatically checks to make sure
|
||||
# that people submitting code are listed in this file (by email address).
|
||||
#
|
||||
# Names should be added to this file only after verifying that
|
||||
# the individual or the individual's organization has agreed to
|
||||
# the appropriate Contributor License Agreement, found here:
|
||||
#
|
||||
# http://code.google.com/legal/individual-cla-v1.0.html
|
||||
# http://code.google.com/legal/corporate-cla-v1.0.html
|
||||
#
|
||||
# The agreement for individuals can be filled out on the web.
|
||||
#
|
||||
# When adding J Random Contributor's name to this file,
|
||||
# either J's name or J's organization's name should be
|
||||
# added to the AUTHORS file, depending on whether the
|
||||
# individual or corporate CLA was used.
|
||||
|
||||
# Names should be added to this file like so:
|
||||
# Name <email address>
|
||||
|
||||
# Please keep the list sorted.
|
||||
|
||||
Damian Gryski <dgryski@gmail.com>
|
||||
Jan Mercl <0xjnml@gmail.com>
|
||||
Kai Backman <kaib@golang.org>
|
||||
Marc-Antoine Ruel <maruel@chromium.org>
|
||||
Nigel Tao <nigeltao@golang.org>
|
||||
Rob Pike <r@golang.org>
|
||||
Rodolfo Carvalho <rhcarvalho@gmail.com>
|
||||
Russ Cox <rsc@golang.org>
|
||||
Sebastien Binet <seb.binet@gmail.com>
|
||||
27
vendor/github.com/golang/snappy/LICENSE
generated
vendored
Normal file
27
vendor/github.com/golang/snappy/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
Copyright (c) 2011 The Snappy-Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
107
vendor/github.com/golang/snappy/README
generated
vendored
Normal file
107
vendor/github.com/golang/snappy/README
generated
vendored
Normal file
@@ -0,0 +1,107 @@
|
||||
The Snappy compression format in the Go programming language.
|
||||
|
||||
To download and install from source:
|
||||
$ go get github.com/golang/snappy
|
||||
|
||||
Unless otherwise noted, the Snappy-Go source files are distributed
|
||||
under the BSD-style license found in the LICENSE file.
|
||||
|
||||
|
||||
|
||||
Benchmarks.
|
||||
|
||||
The golang/snappy benchmarks include compressing (Z) and decompressing (U) ten
|
||||
or so files, the same set used by the C++ Snappy code (github.com/google/snappy
|
||||
and note the "google", not "golang"). On an "Intel(R) Core(TM) i7-3770 CPU @
|
||||
3.40GHz", Go's GOARCH=amd64 numbers as of 2016-05-29:
|
||||
|
||||
"go test -test.bench=."
|
||||
|
||||
_UFlat0-8 2.19GB/s ± 0% html
|
||||
_UFlat1-8 1.41GB/s ± 0% urls
|
||||
_UFlat2-8 23.5GB/s ± 2% jpg
|
||||
_UFlat3-8 1.91GB/s ± 0% jpg_200
|
||||
_UFlat4-8 14.0GB/s ± 1% pdf
|
||||
_UFlat5-8 1.97GB/s ± 0% html4
|
||||
_UFlat6-8 814MB/s ± 0% txt1
|
||||
_UFlat7-8 785MB/s ± 0% txt2
|
||||
_UFlat8-8 857MB/s ± 0% txt3
|
||||
_UFlat9-8 719MB/s ± 1% txt4
|
||||
_UFlat10-8 2.84GB/s ± 0% pb
|
||||
_UFlat11-8 1.05GB/s ± 0% gaviota
|
||||
|
||||
_ZFlat0-8 1.04GB/s ± 0% html
|
||||
_ZFlat1-8 534MB/s ± 0% urls
|
||||
_ZFlat2-8 15.7GB/s ± 1% jpg
|
||||
_ZFlat3-8 740MB/s ± 3% jpg_200
|
||||
_ZFlat4-8 9.20GB/s ± 1% pdf
|
||||
_ZFlat5-8 991MB/s ± 0% html4
|
||||
_ZFlat6-8 379MB/s ± 0% txt1
|
||||
_ZFlat7-8 352MB/s ± 0% txt2
|
||||
_ZFlat8-8 396MB/s ± 1% txt3
|
||||
_ZFlat9-8 327MB/s ± 1% txt4
|
||||
_ZFlat10-8 1.33GB/s ± 1% pb
|
||||
_ZFlat11-8 605MB/s ± 1% gaviota
|
||||
|
||||
|
||||
|
||||
"go test -test.bench=. -tags=noasm"
|
||||
|
||||
_UFlat0-8 621MB/s ± 2% html
|
||||
_UFlat1-8 494MB/s ± 1% urls
|
||||
_UFlat2-8 23.2GB/s ± 1% jpg
|
||||
_UFlat3-8 1.12GB/s ± 1% jpg_200
|
||||
_UFlat4-8 4.35GB/s ± 1% pdf
|
||||
_UFlat5-8 609MB/s ± 0% html4
|
||||
_UFlat6-8 296MB/s ± 0% txt1
|
||||
_UFlat7-8 288MB/s ± 0% txt2
|
||||
_UFlat8-8 309MB/s ± 1% txt3
|
||||
_UFlat9-8 280MB/s ± 1% txt4
|
||||
_UFlat10-8 753MB/s ± 0% pb
|
||||
_UFlat11-8 400MB/s ± 0% gaviota
|
||||
|
||||
_ZFlat0-8 409MB/s ± 1% html
|
||||
_ZFlat1-8 250MB/s ± 1% urls
|
||||
_ZFlat2-8 12.3GB/s ± 1% jpg
|
||||
_ZFlat3-8 132MB/s ± 0% jpg_200
|
||||
_ZFlat4-8 2.92GB/s ± 0% pdf
|
||||
_ZFlat5-8 405MB/s ± 1% html4
|
||||
_ZFlat6-8 179MB/s ± 1% txt1
|
||||
_ZFlat7-8 170MB/s ± 1% txt2
|
||||
_ZFlat8-8 189MB/s ± 1% txt3
|
||||
_ZFlat9-8 164MB/s ± 1% txt4
|
||||
_ZFlat10-8 479MB/s ± 1% pb
|
||||
_ZFlat11-8 270MB/s ± 1% gaviota
|
||||
|
||||
|
||||
|
||||
For comparison (Go's encoded output is byte-for-byte identical to C++'s), here
|
||||
are the numbers from C++ Snappy's
|
||||
|
||||
make CXXFLAGS="-O2 -DNDEBUG -g" clean snappy_unittest.log && cat snappy_unittest.log
|
||||
|
||||
BM_UFlat/0 2.4GB/s html
|
||||
BM_UFlat/1 1.4GB/s urls
|
||||
BM_UFlat/2 21.8GB/s jpg
|
||||
BM_UFlat/3 1.5GB/s jpg_200
|
||||
BM_UFlat/4 13.3GB/s pdf
|
||||
BM_UFlat/5 2.1GB/s html4
|
||||
BM_UFlat/6 1.0GB/s txt1
|
||||
BM_UFlat/7 959.4MB/s txt2
|
||||
BM_UFlat/8 1.0GB/s txt3
|
||||
BM_UFlat/9 864.5MB/s txt4
|
||||
BM_UFlat/10 2.9GB/s pb
|
||||
BM_UFlat/11 1.2GB/s gaviota
|
||||
|
||||
BM_ZFlat/0 944.3MB/s html (22.31 %)
|
||||
BM_ZFlat/1 501.6MB/s urls (47.78 %)
|
||||
BM_ZFlat/2 14.3GB/s jpg (99.95 %)
|
||||
BM_ZFlat/3 538.3MB/s jpg_200 (73.00 %)
|
||||
BM_ZFlat/4 8.3GB/s pdf (83.30 %)
|
||||
BM_ZFlat/5 903.5MB/s html4 (22.52 %)
|
||||
BM_ZFlat/6 336.0MB/s txt1 (57.88 %)
|
||||
BM_ZFlat/7 312.3MB/s txt2 (61.91 %)
|
||||
BM_ZFlat/8 353.1MB/s txt3 (54.99 %)
|
||||
BM_ZFlat/9 289.9MB/s txt4 (66.26 %)
|
||||
BM_ZFlat/10 1.2GB/s pb (19.68 %)
|
||||
BM_ZFlat/11 527.4MB/s gaviota (37.72 %)
|
||||
237
vendor/github.com/golang/snappy/decode.go
generated
vendored
Normal file
237
vendor/github.com/golang/snappy/decode.go
generated
vendored
Normal file
@@ -0,0 +1,237 @@
|
||||
// Copyright 2011 The Snappy-Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package snappy
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrCorrupt reports that the input is invalid.
|
||||
ErrCorrupt = errors.New("snappy: corrupt input")
|
||||
// ErrTooLarge reports that the uncompressed length is too large.
|
||||
ErrTooLarge = errors.New("snappy: decoded block is too large")
|
||||
// ErrUnsupported reports that the input isn't supported.
|
||||
ErrUnsupported = errors.New("snappy: unsupported input")
|
||||
|
||||
errUnsupportedLiteralLength = errors.New("snappy: unsupported literal length")
|
||||
)
|
||||
|
||||
// DecodedLen returns the length of the decoded block.
|
||||
func DecodedLen(src []byte) (int, error) {
|
||||
v, _, err := decodedLen(src)
|
||||
return v, err
|
||||
}
|
||||
|
||||
// decodedLen returns the length of the decoded block and the number of bytes
|
||||
// that the length header occupied.
|
||||
func decodedLen(src []byte) (blockLen, headerLen int, err error) {
|
||||
v, n := binary.Uvarint(src)
|
||||
if n <= 0 || v > 0xffffffff {
|
||||
return 0, 0, ErrCorrupt
|
||||
}
|
||||
|
||||
const wordSize = 32 << (^uint(0) >> 32 & 1)
|
||||
if wordSize == 32 && v > 0x7fffffff {
|
||||
return 0, 0, ErrTooLarge
|
||||
}
|
||||
return int(v), n, nil
|
||||
}
|
||||
|
||||
const (
|
||||
decodeErrCodeCorrupt = 1
|
||||
decodeErrCodeUnsupportedLiteralLength = 2
|
||||
)
|
||||
|
||||
// Decode returns the decoded form of src. The returned slice may be a sub-
|
||||
// slice of dst if dst was large enough to hold the entire decoded block.
|
||||
// Otherwise, a newly allocated slice will be returned.
|
||||
//
|
||||
// The dst and src must not overlap. It is valid to pass a nil dst.
|
||||
func Decode(dst, src []byte) ([]byte, error) {
|
||||
dLen, s, err := decodedLen(src)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if dLen <= len(dst) {
|
||||
dst = dst[:dLen]
|
||||
} else {
|
||||
dst = make([]byte, dLen)
|
||||
}
|
||||
switch decode(dst, src[s:]) {
|
||||
case 0:
|
||||
return dst, nil
|
||||
case decodeErrCodeUnsupportedLiteralLength:
|
||||
return nil, errUnsupportedLiteralLength
|
||||
}
|
||||
return nil, ErrCorrupt
|
||||
}
|
||||
|
||||
// NewReader returns a new Reader that decompresses from r, using the framing
|
||||
// format described at
|
||||
// https://github.com/google/snappy/blob/master/framing_format.txt
|
||||
func NewReader(r io.Reader) *Reader {
|
||||
return &Reader{
|
||||
r: r,
|
||||
decoded: make([]byte, maxBlockSize),
|
||||
buf: make([]byte, maxEncodedLenOfMaxBlockSize+checksumSize),
|
||||
}
|
||||
}
|
||||
|
||||
// Reader is an io.Reader that can read Snappy-compressed bytes.
|
||||
type Reader struct {
|
||||
r io.Reader
|
||||
err error
|
||||
decoded []byte
|
||||
buf []byte
|
||||
// decoded[i:j] contains decoded bytes that have not yet been passed on.
|
||||
i, j int
|
||||
readHeader bool
|
||||
}
|
||||
|
||||
// Reset discards any buffered data, resets all state, and switches the Snappy
|
||||
// reader to read from r. This permits reusing a Reader rather than allocating
|
||||
// a new one.
|
||||
func (r *Reader) Reset(reader io.Reader) {
|
||||
r.r = reader
|
||||
r.err = nil
|
||||
r.i = 0
|
||||
r.j = 0
|
||||
r.readHeader = false
|
||||
}
|
||||
|
||||
func (r *Reader) readFull(p []byte, allowEOF bool) (ok bool) {
|
||||
if _, r.err = io.ReadFull(r.r, p); r.err != nil {
|
||||
if r.err == io.ErrUnexpectedEOF || (r.err == io.EOF && !allowEOF) {
|
||||
r.err = ErrCorrupt
|
||||
}
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Read satisfies the io.Reader interface.
|
||||
func (r *Reader) Read(p []byte) (int, error) {
|
||||
if r.err != nil {
|
||||
return 0, r.err
|
||||
}
|
||||
for {
|
||||
if r.i < r.j {
|
||||
n := copy(p, r.decoded[r.i:r.j])
|
||||
r.i += n
|
||||
return n, nil
|
||||
}
|
||||
if !r.readFull(r.buf[:4], true) {
|
||||
return 0, r.err
|
||||
}
|
||||
chunkType := r.buf[0]
|
||||
if !r.readHeader {
|
||||
if chunkType != chunkTypeStreamIdentifier {
|
||||
r.err = ErrCorrupt
|
||||
return 0, r.err
|
||||
}
|
||||
r.readHeader = true
|
||||
}
|
||||
chunkLen := int(r.buf[1]) | int(r.buf[2])<<8 | int(r.buf[3])<<16
|
||||
if chunkLen > len(r.buf) {
|
||||
r.err = ErrUnsupported
|
||||
return 0, r.err
|
||||
}
|
||||
|
||||
// The chunk types are specified at
|
||||
// https://github.com/google/snappy/blob/master/framing_format.txt
|
||||
switch chunkType {
|
||||
case chunkTypeCompressedData:
|
||||
// Section 4.2. Compressed data (chunk type 0x00).
|
||||
if chunkLen < checksumSize {
|
||||
r.err = ErrCorrupt
|
||||
return 0, r.err
|
||||
}
|
||||
buf := r.buf[:chunkLen]
|
||||
if !r.readFull(buf, false) {
|
||||
return 0, r.err
|
||||
}
|
||||
checksum := uint32(buf[0]) | uint32(buf[1])<<8 | uint32(buf[2])<<16 | uint32(buf[3])<<24
|
||||
buf = buf[checksumSize:]
|
||||
|
||||
n, err := DecodedLen(buf)
|
||||
if err != nil {
|
||||
r.err = err
|
||||
return 0, r.err
|
||||
}
|
||||
if n > len(r.decoded) {
|
||||
r.err = ErrCorrupt
|
||||
return 0, r.err
|
||||
}
|
||||
if _, err := Decode(r.decoded, buf); err != nil {
|
||||
r.err = err
|
||||
return 0, r.err
|
||||
}
|
||||
if crc(r.decoded[:n]) != checksum {
|
||||
r.err = ErrCorrupt
|
||||
return 0, r.err
|
||||
}
|
||||
r.i, r.j = 0, n
|
||||
continue
|
||||
|
||||
case chunkTypeUncompressedData:
|
||||
// Section 4.3. Uncompressed data (chunk type 0x01).
|
||||
if chunkLen < checksumSize {
|
||||
r.err = ErrCorrupt
|
||||
return 0, r.err
|
||||
}
|
||||
buf := r.buf[:checksumSize]
|
||||
if !r.readFull(buf, false) {
|
||||
return 0, r.err
|
||||
}
|
||||
checksum := uint32(buf[0]) | uint32(buf[1])<<8 | uint32(buf[2])<<16 | uint32(buf[3])<<24
|
||||
// Read directly into r.decoded instead of via r.buf.
|
||||
n := chunkLen - checksumSize
|
||||
if n > len(r.decoded) {
|
||||
r.err = ErrCorrupt
|
||||
return 0, r.err
|
||||
}
|
||||
if !r.readFull(r.decoded[:n], false) {
|
||||
return 0, r.err
|
||||
}
|
||||
if crc(r.decoded[:n]) != checksum {
|
||||
r.err = ErrCorrupt
|
||||
return 0, r.err
|
||||
}
|
||||
r.i, r.j = 0, n
|
||||
continue
|
||||
|
||||
case chunkTypeStreamIdentifier:
|
||||
// Section 4.1. Stream identifier (chunk type 0xff).
|
||||
if chunkLen != len(magicBody) {
|
||||
r.err = ErrCorrupt
|
||||
return 0, r.err
|
||||
}
|
||||
if !r.readFull(r.buf[:len(magicBody)], false) {
|
||||
return 0, r.err
|
||||
}
|
||||
for i := 0; i < len(magicBody); i++ {
|
||||
if r.buf[i] != magicBody[i] {
|
||||
r.err = ErrCorrupt
|
||||
return 0, r.err
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if chunkType <= 0x7f {
|
||||
// Section 4.5. Reserved unskippable chunks (chunk types 0x02-0x7f).
|
||||
r.err = ErrUnsupported
|
||||
return 0, r.err
|
||||
}
|
||||
// Section 4.4 Padding (chunk type 0xfe).
|
||||
// Section 4.6. Reserved skippable chunks (chunk types 0x80-0xfd).
|
||||
if !r.readFull(r.buf[:chunkLen], false) {
|
||||
return 0, r.err
|
||||
}
|
||||
}
|
||||
}
|
||||
14
vendor/github.com/golang/snappy/decode_amd64.go
generated
vendored
Normal file
14
vendor/github.com/golang/snappy/decode_amd64.go
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
// Copyright 2016 The Snappy-Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !appengine
|
||||
// +build gc
|
||||
// +build !noasm
|
||||
|
||||
package snappy
|
||||
|
||||
// decode has the same semantics as in decode_other.go.
|
||||
//
|
||||
//go:noescape
|
||||
func decode(dst, src []byte) int
|
||||
490
vendor/github.com/golang/snappy/decode_amd64.s
generated
vendored
Normal file
490
vendor/github.com/golang/snappy/decode_amd64.s
generated
vendored
Normal file
@@ -0,0 +1,490 @@
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !appengine
|
||||
// +build gc
|
||||
// +build !noasm
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
// The asm code generally follows the pure Go code in decode_other.go, except
|
||||
// where marked with a "!!!".
|
||||
|
||||
// func decode(dst, src []byte) int
|
||||
//
|
||||
// All local variables fit into registers. The non-zero stack size is only to
|
||||
// spill registers and push args when issuing a CALL. The register allocation:
|
||||
// - AX scratch
|
||||
// - BX scratch
|
||||
// - CX length or x
|
||||
// - DX offset
|
||||
// - SI &src[s]
|
||||
// - DI &dst[d]
|
||||
// + R8 dst_base
|
||||
// + R9 dst_len
|
||||
// + R10 dst_base + dst_len
|
||||
// + R11 src_base
|
||||
// + R12 src_len
|
||||
// + R13 src_base + src_len
|
||||
// - R14 used by doCopy
|
||||
// - R15 used by doCopy
|
||||
//
|
||||
// The registers R8-R13 (marked with a "+") are set at the start of the
|
||||
// function, and after a CALL returns, and are not otherwise modified.
|
||||
//
|
||||
// The d variable is implicitly DI - R8, and len(dst)-d is R10 - DI.
|
||||
// The s variable is implicitly SI - R11, and len(src)-s is R13 - SI.
|
||||
TEXT ·decode(SB), NOSPLIT, $48-56
|
||||
// Initialize SI, DI and R8-R13.
|
||||
MOVQ dst_base+0(FP), R8
|
||||
MOVQ dst_len+8(FP), R9
|
||||
MOVQ R8, DI
|
||||
MOVQ R8, R10
|
||||
ADDQ R9, R10
|
||||
MOVQ src_base+24(FP), R11
|
||||
MOVQ src_len+32(FP), R12
|
||||
MOVQ R11, SI
|
||||
MOVQ R11, R13
|
||||
ADDQ R12, R13
|
||||
|
||||
loop:
|
||||
// for s < len(src)
|
||||
CMPQ SI, R13
|
||||
JEQ end
|
||||
|
||||
// CX = uint32(src[s])
|
||||
//
|
||||
// switch src[s] & 0x03
|
||||
MOVBLZX (SI), CX
|
||||
MOVL CX, BX
|
||||
ANDL $3, BX
|
||||
CMPL BX, $1
|
||||
JAE tagCopy
|
||||
|
||||
// ----------------------------------------
|
||||
// The code below handles literal tags.
|
||||
|
||||
// case tagLiteral:
|
||||
// x := uint32(src[s] >> 2)
|
||||
// switch
|
||||
SHRL $2, CX
|
||||
CMPL CX, $60
|
||||
JAE tagLit60Plus
|
||||
|
||||
// case x < 60:
|
||||
// s++
|
||||
INCQ SI
|
||||
|
||||
doLit:
|
||||
// This is the end of the inner "switch", when we have a literal tag.
|
||||
//
|
||||
// We assume that CX == x and x fits in a uint32, where x is the variable
|
||||
// used in the pure Go decode_other.go code.
|
||||
|
||||
// length = int(x) + 1
|
||||
//
|
||||
// Unlike the pure Go code, we don't need to check if length <= 0 because
|
||||
// CX can hold 64 bits, so the increment cannot overflow.
|
||||
INCQ CX
|
||||
|
||||
// Prepare to check if copying length bytes will run past the end of dst or
|
||||
// src.
|
||||
//
|
||||
// AX = len(dst) - d
|
||||
// BX = len(src) - s
|
||||
MOVQ R10, AX
|
||||
SUBQ DI, AX
|
||||
MOVQ R13, BX
|
||||
SUBQ SI, BX
|
||||
|
||||
// !!! Try a faster technique for short (16 or fewer bytes) copies.
|
||||
//
|
||||
// if length > 16 || len(dst)-d < 16 || len(src)-s < 16 {
|
||||
// goto callMemmove // Fall back on calling runtime·memmove.
|
||||
// }
|
||||
//
|
||||
// The C++ snappy code calls this TryFastAppend. It also checks len(src)-s
|
||||
// against 21 instead of 16, because it cannot assume that all of its input
|
||||
// is contiguous in memory and so it needs to leave enough source bytes to
|
||||
// read the next tag without refilling buffers, but Go's Decode assumes
|
||||
// contiguousness (the src argument is a []byte).
|
||||
CMPQ CX, $16
|
||||
JGT callMemmove
|
||||
CMPQ AX, $16
|
||||
JLT callMemmove
|
||||
CMPQ BX, $16
|
||||
JLT callMemmove
|
||||
|
||||
// !!! Implement the copy from src to dst as a 16-byte load and store.
|
||||
// (Decode's documentation says that dst and src must not overlap.)
|
||||
//
|
||||
// This always copies 16 bytes, instead of only length bytes, but that's
|
||||
// OK. If the input is a valid Snappy encoding then subsequent iterations
|
||||
// will fix up the overrun. Otherwise, Decode returns a nil []byte (and a
|
||||
// non-nil error), so the overrun will be ignored.
|
||||
//
|
||||
// Note that on amd64, it is legal and cheap to issue unaligned 8-byte or
|
||||
// 16-byte loads and stores. This technique probably wouldn't be as
|
||||
// effective on architectures that are fussier about alignment.
|
||||
MOVOU 0(SI), X0
|
||||
MOVOU X0, 0(DI)
|
||||
|
||||
// d += length
|
||||
// s += length
|
||||
ADDQ CX, DI
|
||||
ADDQ CX, SI
|
||||
JMP loop
|
||||
|
||||
callMemmove:
|
||||
// if length > len(dst)-d || length > len(src)-s { etc }
|
||||
CMPQ CX, AX
|
||||
JGT errCorrupt
|
||||
CMPQ CX, BX
|
||||
JGT errCorrupt
|
||||
|
||||
// copy(dst[d:], src[s:s+length])
|
||||
//
|
||||
// This means calling runtime·memmove(&dst[d], &src[s], length), so we push
|
||||
// DI, SI and CX as arguments. Coincidentally, we also need to spill those
|
||||
// three registers to the stack, to save local variables across the CALL.
|
||||
MOVQ DI, 0(SP)
|
||||
MOVQ SI, 8(SP)
|
||||
MOVQ CX, 16(SP)
|
||||
MOVQ DI, 24(SP)
|
||||
MOVQ SI, 32(SP)
|
||||
MOVQ CX, 40(SP)
|
||||
CALL runtime·memmove(SB)
|
||||
|
||||
// Restore local variables: unspill registers from the stack and
|
||||
// re-calculate R8-R13.
|
||||
MOVQ 24(SP), DI
|
||||
MOVQ 32(SP), SI
|
||||
MOVQ 40(SP), CX
|
||||
MOVQ dst_base+0(FP), R8
|
||||
MOVQ dst_len+8(FP), R9
|
||||
MOVQ R8, R10
|
||||
ADDQ R9, R10
|
||||
MOVQ src_base+24(FP), R11
|
||||
MOVQ src_len+32(FP), R12
|
||||
MOVQ R11, R13
|
||||
ADDQ R12, R13
|
||||
|
||||
// d += length
|
||||
// s += length
|
||||
ADDQ CX, DI
|
||||
ADDQ CX, SI
|
||||
JMP loop
|
||||
|
||||
tagLit60Plus:
|
||||
// !!! This fragment does the
|
||||
//
|
||||
// s += x - 58; if uint(s) > uint(len(src)) { etc }
|
||||
//
|
||||
// checks. In the asm version, we code it once instead of once per switch case.
|
||||
ADDQ CX, SI
|
||||
SUBQ $58, SI
|
||||
MOVQ SI, BX
|
||||
SUBQ R11, BX
|
||||
CMPQ BX, R12
|
||||
JA errCorrupt
|
||||
|
||||
// case x == 60:
|
||||
CMPL CX, $61
|
||||
JEQ tagLit61
|
||||
JA tagLit62Plus
|
||||
|
||||
// x = uint32(src[s-1])
|
||||
MOVBLZX -1(SI), CX
|
||||
JMP doLit
|
||||
|
||||
tagLit61:
|
||||
// case x == 61:
|
||||
// x = uint32(src[s-2]) | uint32(src[s-1])<<8
|
||||
MOVWLZX -2(SI), CX
|
||||
JMP doLit
|
||||
|
||||
tagLit62Plus:
|
||||
CMPL CX, $62
|
||||
JA tagLit63
|
||||
|
||||
// case x == 62:
|
||||
// x = uint32(src[s-3]) | uint32(src[s-2])<<8 | uint32(src[s-1])<<16
|
||||
MOVWLZX -3(SI), CX
|
||||
MOVBLZX -1(SI), BX
|
||||
SHLL $16, BX
|
||||
ORL BX, CX
|
||||
JMP doLit
|
||||
|
||||
tagLit63:
|
||||
// case x == 63:
|
||||
// x = uint32(src[s-4]) | uint32(src[s-3])<<8 | uint32(src[s-2])<<16 | uint32(src[s-1])<<24
|
||||
MOVL -4(SI), CX
|
||||
JMP doLit
|
||||
|
||||
// The code above handles literal tags.
|
||||
// ----------------------------------------
|
||||
// The code below handles copy tags.
|
||||
|
||||
tagCopy4:
|
||||
// case tagCopy4:
|
||||
// s += 5
|
||||
ADDQ $5, SI
|
||||
|
||||
// if uint(s) > uint(len(src)) { etc }
|
||||
MOVQ SI, BX
|
||||
SUBQ R11, BX
|
||||
CMPQ BX, R12
|
||||
JA errCorrupt
|
||||
|
||||
// length = 1 + int(src[s-5])>>2
|
||||
SHRQ $2, CX
|
||||
INCQ CX
|
||||
|
||||
// offset = int(uint32(src[s-4]) | uint32(src[s-3])<<8 | uint32(src[s-2])<<16 | uint32(src[s-1])<<24)
|
||||
MOVLQZX -4(SI), DX
|
||||
JMP doCopy
|
||||
|
||||
tagCopy2:
|
||||
// case tagCopy2:
|
||||
// s += 3
|
||||
ADDQ $3, SI
|
||||
|
||||
// if uint(s) > uint(len(src)) { etc }
|
||||
MOVQ SI, BX
|
||||
SUBQ R11, BX
|
||||
CMPQ BX, R12
|
||||
JA errCorrupt
|
||||
|
||||
// length = 1 + int(src[s-3])>>2
|
||||
SHRQ $2, CX
|
||||
INCQ CX
|
||||
|
||||
// offset = int(uint32(src[s-2]) | uint32(src[s-1])<<8)
|
||||
MOVWQZX -2(SI), DX
|
||||
JMP doCopy
|
||||
|
||||
tagCopy:
|
||||
// We have a copy tag. We assume that:
|
||||
// - BX == src[s] & 0x03
|
||||
// - CX == src[s]
|
||||
CMPQ BX, $2
|
||||
JEQ tagCopy2
|
||||
JA tagCopy4
|
||||
|
||||
// case tagCopy1:
|
||||
// s += 2
|
||||
ADDQ $2, SI
|
||||
|
||||
// if uint(s) > uint(len(src)) { etc }
|
||||
MOVQ SI, BX
|
||||
SUBQ R11, BX
|
||||
CMPQ BX, R12
|
||||
JA errCorrupt
|
||||
|
||||
// offset = int(uint32(src[s-2])&0xe0<<3 | uint32(src[s-1]))
|
||||
MOVQ CX, DX
|
||||
ANDQ $0xe0, DX
|
||||
SHLQ $3, DX
|
||||
MOVBQZX -1(SI), BX
|
||||
ORQ BX, DX
|
||||
|
||||
// length = 4 + int(src[s-2])>>2&0x7
|
||||
SHRQ $2, CX
|
||||
ANDQ $7, CX
|
||||
ADDQ $4, CX
|
||||
|
||||
doCopy:
|
||||
// This is the end of the outer "switch", when we have a copy tag.
|
||||
//
|
||||
// We assume that:
|
||||
// - CX == length && CX > 0
|
||||
// - DX == offset
|
||||
|
||||
// if offset <= 0 { etc }
|
||||
CMPQ DX, $0
|
||||
JLE errCorrupt
|
||||
|
||||
// if d < offset { etc }
|
||||
MOVQ DI, BX
|
||||
SUBQ R8, BX
|
||||
CMPQ BX, DX
|
||||
JLT errCorrupt
|
||||
|
||||
// if length > len(dst)-d { etc }
|
||||
MOVQ R10, BX
|
||||
SUBQ DI, BX
|
||||
CMPQ CX, BX
|
||||
JGT errCorrupt
|
||||
|
||||
// forwardCopy(dst[d:d+length], dst[d-offset:]); d += length
|
||||
//
|
||||
// Set:
|
||||
// - R14 = len(dst)-d
|
||||
// - R15 = &dst[d-offset]
|
||||
MOVQ R10, R14
|
||||
SUBQ DI, R14
|
||||
MOVQ DI, R15
|
||||
SUBQ DX, R15
|
||||
|
||||
// !!! Try a faster technique for short (16 or fewer bytes) forward copies.
|
||||
//
|
||||
// First, try using two 8-byte load/stores, similar to the doLit technique
|
||||
// above. Even if dst[d:d+length] and dst[d-offset:] can overlap, this is
|
||||
// still OK if offset >= 8. Note that this has to be two 8-byte load/stores
|
||||
// and not one 16-byte load/store, and the first store has to be before the
|
||||
// second load, due to the overlap if offset is in the range [8, 16).
|
||||
//
|
||||
// if length > 16 || offset < 8 || len(dst)-d < 16 {
|
||||
// goto slowForwardCopy
|
||||
// }
|
||||
// copy 16 bytes
|
||||
// d += length
|
||||
CMPQ CX, $16
|
||||
JGT slowForwardCopy
|
||||
CMPQ DX, $8
|
||||
JLT slowForwardCopy
|
||||
CMPQ R14, $16
|
||||
JLT slowForwardCopy
|
||||
MOVQ 0(R15), AX
|
||||
MOVQ AX, 0(DI)
|
||||
MOVQ 8(R15), BX
|
||||
MOVQ BX, 8(DI)
|
||||
ADDQ CX, DI
|
||||
JMP loop
|
||||
|
||||
slowForwardCopy:
|
||||
// !!! If the forward copy is longer than 16 bytes, or if offset < 8, we
|
||||
// can still try 8-byte load stores, provided we can overrun up to 10 extra
|
||||
// bytes. As above, the overrun will be fixed up by subsequent iterations
|
||||
// of the outermost loop.
|
||||
//
|
||||
// The C++ snappy code calls this technique IncrementalCopyFastPath. Its
|
||||
// commentary says:
|
||||
//
|
||||
// ----
|
||||
//
|
||||
// The main part of this loop is a simple copy of eight bytes at a time
|
||||
// until we've copied (at least) the requested amount of bytes. However,
|
||||
// if d and d-offset are less than eight bytes apart (indicating a
|
||||
// repeating pattern of length < 8), we first need to expand the pattern in
|
||||
// order to get the correct results. For instance, if the buffer looks like
|
||||
// this, with the eight-byte <d-offset> and <d> patterns marked as
|
||||
// intervals:
|
||||
//
|
||||
// abxxxxxxxxxxxx
|
||||
// [------] d-offset
|
||||
// [------] d
|
||||
//
|
||||
// a single eight-byte copy from <d-offset> to <d> will repeat the pattern
|
||||
// once, after which we can move <d> two bytes without moving <d-offset>:
|
||||
//
|
||||
// ababxxxxxxxxxx
|
||||
// [------] d-offset
|
||||
// [------] d
|
||||
//
|
||||
// and repeat the exercise until the two no longer overlap.
|
||||
//
|
||||
// This allows us to do very well in the special case of one single byte
|
||||
// repeated many times, without taking a big hit for more general cases.
|
||||
//
|
||||
// The worst case of extra writing past the end of the match occurs when
|
||||
// offset == 1 and length == 1; the last copy will read from byte positions
|
||||
// [0..7] and write to [4..11], whereas it was only supposed to write to
|
||||
// position 1. Thus, ten excess bytes.
|
||||
//
|
||||
// ----
|
||||
//
|
||||
// That "10 byte overrun" worst case is confirmed by Go's
|
||||
// TestSlowForwardCopyOverrun, which also tests the fixUpSlowForwardCopy
|
||||
// and finishSlowForwardCopy algorithm.
|
||||
//
|
||||
// if length > len(dst)-d-10 {
|
||||
// goto verySlowForwardCopy
|
||||
// }
|
||||
SUBQ $10, R14
|
||||
CMPQ CX, R14
|
||||
JGT verySlowForwardCopy
|
||||
|
||||
makeOffsetAtLeast8:
|
||||
// !!! As above, expand the pattern so that offset >= 8 and we can use
|
||||
// 8-byte load/stores.
|
||||
//
|
||||
// for offset < 8 {
|
||||
// copy 8 bytes from dst[d-offset:] to dst[d:]
|
||||
// length -= offset
|
||||
// d += offset
|
||||
// offset += offset
|
||||
// // The two previous lines together means that d-offset, and therefore
|
||||
// // R15, is unchanged.
|
||||
// }
|
||||
CMPQ DX, $8
|
||||
JGE fixUpSlowForwardCopy
|
||||
MOVQ (R15), BX
|
||||
MOVQ BX, (DI)
|
||||
SUBQ DX, CX
|
||||
ADDQ DX, DI
|
||||
ADDQ DX, DX
|
||||
JMP makeOffsetAtLeast8
|
||||
|
||||
fixUpSlowForwardCopy:
|
||||
// !!! Add length (which might be negative now) to d (implied by DI being
|
||||
// &dst[d]) so that d ends up at the right place when we jump back to the
|
||||
// top of the loop. Before we do that, though, we save DI to AX so that, if
|
||||
// length is positive, copying the remaining length bytes will write to the
|
||||
// right place.
|
||||
MOVQ DI, AX
|
||||
ADDQ CX, DI
|
||||
|
||||
finishSlowForwardCopy:
|
||||
// !!! Repeat 8-byte load/stores until length <= 0. Ending with a negative
|
||||
// length means that we overrun, but as above, that will be fixed up by
|
||||
// subsequent iterations of the outermost loop.
|
||||
CMPQ CX, $0
|
||||
JLE loop
|
||||
MOVQ (R15), BX
|
||||
MOVQ BX, (AX)
|
||||
ADDQ $8, R15
|
||||
ADDQ $8, AX
|
||||
SUBQ $8, CX
|
||||
JMP finishSlowForwardCopy
|
||||
|
||||
verySlowForwardCopy:
|
||||
// verySlowForwardCopy is a simple implementation of forward copy. In C
|
||||
// parlance, this is a do/while loop instead of a while loop, since we know
|
||||
// that length > 0. In Go syntax:
|
||||
//
|
||||
// for {
|
||||
// dst[d] = dst[d - offset]
|
||||
// d++
|
||||
// length--
|
||||
// if length == 0 {
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
MOVB (R15), BX
|
||||
MOVB BX, (DI)
|
||||
INCQ R15
|
||||
INCQ DI
|
||||
DECQ CX
|
||||
JNZ verySlowForwardCopy
|
||||
JMP loop
|
||||
|
||||
// The code above handles copy tags.
|
||||
// ----------------------------------------
|
||||
|
||||
end:
|
||||
// This is the end of the "for s < len(src)".
|
||||
//
|
||||
// if d != len(dst) { etc }
|
||||
CMPQ DI, R10
|
||||
JNE errCorrupt
|
||||
|
||||
// return 0
|
||||
MOVQ $0, ret+48(FP)
|
||||
RET
|
||||
|
||||
errCorrupt:
|
||||
// return decodeErrCodeCorrupt
|
||||
MOVQ $1, ret+48(FP)
|
||||
RET
|
||||
101
vendor/github.com/golang/snappy/decode_other.go
generated
vendored
Normal file
101
vendor/github.com/golang/snappy/decode_other.go
generated
vendored
Normal file
@@ -0,0 +1,101 @@
|
||||
// Copyright 2016 The Snappy-Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !amd64 appengine !gc noasm
|
||||
|
||||
package snappy
|
||||
|
||||
// decode writes the decoding of src to dst. It assumes that the varint-encoded
|
||||
// length of the decompressed bytes has already been read, and that len(dst)
|
||||
// equals that length.
|
||||
//
|
||||
// It returns 0 on success or a decodeErrCodeXxx error code on failure.
|
||||
func decode(dst, src []byte) int {
|
||||
var d, s, offset, length int
|
||||
for s < len(src) {
|
||||
switch src[s] & 0x03 {
|
||||
case tagLiteral:
|
||||
x := uint32(src[s] >> 2)
|
||||
switch {
|
||||
case x < 60:
|
||||
s++
|
||||
case x == 60:
|
||||
s += 2
|
||||
if uint(s) > uint(len(src)) { // The uint conversions catch overflow from the previous line.
|
||||
return decodeErrCodeCorrupt
|
||||
}
|
||||
x = uint32(src[s-1])
|
||||
case x == 61:
|
||||
s += 3
|
||||
if uint(s) > uint(len(src)) { // The uint conversions catch overflow from the previous line.
|
||||
return decodeErrCodeCorrupt
|
||||
}
|
||||
x = uint32(src[s-2]) | uint32(src[s-1])<<8
|
||||
case x == 62:
|
||||
s += 4
|
||||
if uint(s) > uint(len(src)) { // The uint conversions catch overflow from the previous line.
|
||||
return decodeErrCodeCorrupt
|
||||
}
|
||||
x = uint32(src[s-3]) | uint32(src[s-2])<<8 | uint32(src[s-1])<<16
|
||||
case x == 63:
|
||||
s += 5
|
||||
if uint(s) > uint(len(src)) { // The uint conversions catch overflow from the previous line.
|
||||
return decodeErrCodeCorrupt
|
||||
}
|
||||
x = uint32(src[s-4]) | uint32(src[s-3])<<8 | uint32(src[s-2])<<16 | uint32(src[s-1])<<24
|
||||
}
|
||||
length = int(x) + 1
|
||||
if length <= 0 {
|
||||
return decodeErrCodeUnsupportedLiteralLength
|
||||
}
|
||||
if length > len(dst)-d || length > len(src)-s {
|
||||
return decodeErrCodeCorrupt
|
||||
}
|
||||
copy(dst[d:], src[s:s+length])
|
||||
d += length
|
||||
s += length
|
||||
continue
|
||||
|
||||
case tagCopy1:
|
||||
s += 2
|
||||
if uint(s) > uint(len(src)) { // The uint conversions catch overflow from the previous line.
|
||||
return decodeErrCodeCorrupt
|
||||
}
|
||||
length = 4 + int(src[s-2])>>2&0x7
|
||||
offset = int(uint32(src[s-2])&0xe0<<3 | uint32(src[s-1]))
|
||||
|
||||
case tagCopy2:
|
||||
s += 3
|
||||
if uint(s) > uint(len(src)) { // The uint conversions catch overflow from the previous line.
|
||||
return decodeErrCodeCorrupt
|
||||
}
|
||||
length = 1 + int(src[s-3])>>2
|
||||
offset = int(uint32(src[s-2]) | uint32(src[s-1])<<8)
|
||||
|
||||
case tagCopy4:
|
||||
s += 5
|
||||
if uint(s) > uint(len(src)) { // The uint conversions catch overflow from the previous line.
|
||||
return decodeErrCodeCorrupt
|
||||
}
|
||||
length = 1 + int(src[s-5])>>2
|
||||
offset = int(uint32(src[s-4]) | uint32(src[s-3])<<8 | uint32(src[s-2])<<16 | uint32(src[s-1])<<24)
|
||||
}
|
||||
|
||||
if offset <= 0 || d < offset || length > len(dst)-d {
|
||||
return decodeErrCodeCorrupt
|
||||
}
|
||||
// Copy from an earlier sub-slice of dst to a later sub-slice. Unlike
|
||||
// the built-in copy function, this byte-by-byte copy always runs
|
||||
// forwards, even if the slices overlap. Conceptually, this is:
|
||||
//
|
||||
// d += forwardCopy(dst[d:d+length], dst[d-offset:])
|
||||
for end := d + length; d != end; d++ {
|
||||
dst[d] = dst[d-offset]
|
||||
}
|
||||
}
|
||||
if d != len(dst) {
|
||||
return decodeErrCodeCorrupt
|
||||
}
|
||||
return 0
|
||||
}
|
||||
285
vendor/github.com/golang/snappy/encode.go
generated
vendored
Normal file
285
vendor/github.com/golang/snappy/encode.go
generated
vendored
Normal file
@@ -0,0 +1,285 @@
|
||||
// Copyright 2011 The Snappy-Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package snappy
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
// Encode returns the encoded form of src. The returned slice may be a sub-
|
||||
// slice of dst if dst was large enough to hold the entire encoded block.
|
||||
// Otherwise, a newly allocated slice will be returned.
|
||||
//
|
||||
// The dst and src must not overlap. It is valid to pass a nil dst.
|
||||
func Encode(dst, src []byte) []byte {
|
||||
if n := MaxEncodedLen(len(src)); n < 0 {
|
||||
panic(ErrTooLarge)
|
||||
} else if len(dst) < n {
|
||||
dst = make([]byte, n)
|
||||
}
|
||||
|
||||
// The block starts with the varint-encoded length of the decompressed bytes.
|
||||
d := binary.PutUvarint(dst, uint64(len(src)))
|
||||
|
||||
for len(src) > 0 {
|
||||
p := src
|
||||
src = nil
|
||||
if len(p) > maxBlockSize {
|
||||
p, src = p[:maxBlockSize], p[maxBlockSize:]
|
||||
}
|
||||
if len(p) < minNonLiteralBlockSize {
|
||||
d += emitLiteral(dst[d:], p)
|
||||
} else {
|
||||
d += encodeBlock(dst[d:], p)
|
||||
}
|
||||
}
|
||||
return dst[:d]
|
||||
}
|
||||
|
||||
// inputMargin is the minimum number of extra input bytes to keep, inside
|
||||
// encodeBlock's inner loop. On some architectures, this margin lets us
|
||||
// implement a fast path for emitLiteral, where the copy of short (<= 16 byte)
|
||||
// literals can be implemented as a single load to and store from a 16-byte
|
||||
// register. That literal's actual length can be as short as 1 byte, so this
|
||||
// can copy up to 15 bytes too much, but that's OK as subsequent iterations of
|
||||
// the encoding loop will fix up the copy overrun, and this inputMargin ensures
|
||||
// that we don't overrun the dst and src buffers.
|
||||
const inputMargin = 16 - 1
|
||||
|
||||
// minNonLiteralBlockSize is the minimum size of the input to encodeBlock that
|
||||
// could be encoded with a copy tag. This is the minimum with respect to the
|
||||
// algorithm used by encodeBlock, not a minimum enforced by the file format.
|
||||
//
|
||||
// The encoded output must start with at least a 1 byte literal, as there are
|
||||
// no previous bytes to copy. A minimal (1 byte) copy after that, generated
|
||||
// from an emitCopy call in encodeBlock's main loop, would require at least
|
||||
// another inputMargin bytes, for the reason above: we want any emitLiteral
|
||||
// calls inside encodeBlock's main loop to use the fast path if possible, which
|
||||
// requires being able to overrun by inputMargin bytes. Thus,
|
||||
// minNonLiteralBlockSize equals 1 + 1 + inputMargin.
|
||||
//
|
||||
// The C++ code doesn't use this exact threshold, but it could, as discussed at
|
||||
// https://groups.google.com/d/topic/snappy-compression/oGbhsdIJSJ8/discussion
|
||||
// The difference between Go (2+inputMargin) and C++ (inputMargin) is purely an
|
||||
// optimization. It should not affect the encoded form. This is tested by
|
||||
// TestSameEncodingAsCppShortCopies.
|
||||
const minNonLiteralBlockSize = 1 + 1 + inputMargin
|
||||
|
||||
// MaxEncodedLen returns the maximum length of a snappy block, given its
|
||||
// uncompressed length.
|
||||
//
|
||||
// It will return a negative value if srcLen is too large to encode.
|
||||
func MaxEncodedLen(srcLen int) int {
|
||||
n := uint64(srcLen)
|
||||
if n > 0xffffffff {
|
||||
return -1
|
||||
}
|
||||
// Compressed data can be defined as:
|
||||
// compressed := item* literal*
|
||||
// item := literal* copy
|
||||
//
|
||||
// The trailing literal sequence has a space blowup of at most 62/60
|
||||
// since a literal of length 60 needs one tag byte + one extra byte
|
||||
// for length information.
|
||||
//
|
||||
// Item blowup is trickier to measure. Suppose the "copy" op copies
|
||||
// 4 bytes of data. Because of a special check in the encoding code,
|
||||
// we produce a 4-byte copy only if the offset is < 65536. Therefore
|
||||
// the copy op takes 3 bytes to encode, and this type of item leads
|
||||
// to at most the 62/60 blowup for representing literals.
|
||||
//
|
||||
// Suppose the "copy" op copies 5 bytes of data. If the offset is big
|
||||
// enough, it will take 5 bytes to encode the copy op. Therefore the
|
||||
// worst case here is a one-byte literal followed by a five-byte copy.
|
||||
// That is, 6 bytes of input turn into 7 bytes of "compressed" data.
|
||||
//
|
||||
// This last factor dominates the blowup, so the final estimate is:
|
||||
n = 32 + n + n/6
|
||||
if n > 0xffffffff {
|
||||
return -1
|
||||
}
|
||||
return int(n)
|
||||
}
|
||||
|
||||
var errClosed = errors.New("snappy: Writer is closed")
|
||||
|
||||
// NewWriter returns a new Writer that compresses to w.
|
||||
//
|
||||
// The Writer returned does not buffer writes. There is no need to Flush or
|
||||
// Close such a Writer.
|
||||
//
|
||||
// Deprecated: the Writer returned is not suitable for many small writes, only
|
||||
// for few large writes. Use NewBufferedWriter instead, which is efficient
|
||||
// regardless of the frequency and shape of the writes, and remember to Close
|
||||
// that Writer when done.
|
||||
func NewWriter(w io.Writer) *Writer {
|
||||
return &Writer{
|
||||
w: w,
|
||||
obuf: make([]byte, obufLen),
|
||||
}
|
||||
}
|
||||
|
||||
// NewBufferedWriter returns a new Writer that compresses to w, using the
|
||||
// framing format described at
|
||||
// https://github.com/google/snappy/blob/master/framing_format.txt
|
||||
//
|
||||
// The Writer returned buffers writes. Users must call Close to guarantee all
|
||||
// data has been forwarded to the underlying io.Writer. They may also call
|
||||
// Flush zero or more times before calling Close.
|
||||
func NewBufferedWriter(w io.Writer) *Writer {
|
||||
return &Writer{
|
||||
w: w,
|
||||
ibuf: make([]byte, 0, maxBlockSize),
|
||||
obuf: make([]byte, obufLen),
|
||||
}
|
||||
}
|
||||
|
||||
// Writer is an io.Writer that can write Snappy-compressed bytes.
|
||||
type Writer struct {
|
||||
w io.Writer
|
||||
err error
|
||||
|
||||
// ibuf is a buffer for the incoming (uncompressed) bytes.
|
||||
//
|
||||
// Its use is optional. For backwards compatibility, Writers created by the
|
||||
// NewWriter function have ibuf == nil, do not buffer incoming bytes, and
|
||||
// therefore do not need to be Flush'ed or Close'd.
|
||||
ibuf []byte
|
||||
|
||||
// obuf is a buffer for the outgoing (compressed) bytes.
|
||||
obuf []byte
|
||||
|
||||
// wroteStreamHeader is whether we have written the stream header.
|
||||
wroteStreamHeader bool
|
||||
}
|
||||
|
||||
// Reset discards the writer's state and switches the Snappy writer to write to
|
||||
// w. This permits reusing a Writer rather than allocating a new one.
|
||||
func (w *Writer) Reset(writer io.Writer) {
|
||||
w.w = writer
|
||||
w.err = nil
|
||||
if w.ibuf != nil {
|
||||
w.ibuf = w.ibuf[:0]
|
||||
}
|
||||
w.wroteStreamHeader = false
|
||||
}
|
||||
|
||||
// Write satisfies the io.Writer interface.
|
||||
func (w *Writer) Write(p []byte) (nRet int, errRet error) {
|
||||
if w.ibuf == nil {
|
||||
// Do not buffer incoming bytes. This does not perform or compress well
|
||||
// if the caller of Writer.Write writes many small slices. This
|
||||
// behavior is therefore deprecated, but still supported for backwards
|
||||
// compatibility with code that doesn't explicitly Flush or Close.
|
||||
return w.write(p)
|
||||
}
|
||||
|
||||
// The remainder of this method is based on bufio.Writer.Write from the
|
||||
// standard library.
|
||||
|
||||
for len(p) > (cap(w.ibuf)-len(w.ibuf)) && w.err == nil {
|
||||
var n int
|
||||
if len(w.ibuf) == 0 {
|
||||
// Large write, empty buffer.
|
||||
// Write directly from p to avoid copy.
|
||||
n, _ = w.write(p)
|
||||
} else {
|
||||
n = copy(w.ibuf[len(w.ibuf):cap(w.ibuf)], p)
|
||||
w.ibuf = w.ibuf[:len(w.ibuf)+n]
|
||||
w.Flush()
|
||||
}
|
||||
nRet += n
|
||||
p = p[n:]
|
||||
}
|
||||
if w.err != nil {
|
||||
return nRet, w.err
|
||||
}
|
||||
n := copy(w.ibuf[len(w.ibuf):cap(w.ibuf)], p)
|
||||
w.ibuf = w.ibuf[:len(w.ibuf)+n]
|
||||
nRet += n
|
||||
return nRet, nil
|
||||
}
|
||||
|
||||
func (w *Writer) write(p []byte) (nRet int, errRet error) {
|
||||
if w.err != nil {
|
||||
return 0, w.err
|
||||
}
|
||||
for len(p) > 0 {
|
||||
obufStart := len(magicChunk)
|
||||
if !w.wroteStreamHeader {
|
||||
w.wroteStreamHeader = true
|
||||
copy(w.obuf, magicChunk)
|
||||
obufStart = 0
|
||||
}
|
||||
|
||||
var uncompressed []byte
|
||||
if len(p) > maxBlockSize {
|
||||
uncompressed, p = p[:maxBlockSize], p[maxBlockSize:]
|
||||
} else {
|
||||
uncompressed, p = p, nil
|
||||
}
|
||||
checksum := crc(uncompressed)
|
||||
|
||||
// Compress the buffer, discarding the result if the improvement
|
||||
// isn't at least 12.5%.
|
||||
compressed := Encode(w.obuf[obufHeaderLen:], uncompressed)
|
||||
chunkType := uint8(chunkTypeCompressedData)
|
||||
chunkLen := 4 + len(compressed)
|
||||
obufEnd := obufHeaderLen + len(compressed)
|
||||
if len(compressed) >= len(uncompressed)-len(uncompressed)/8 {
|
||||
chunkType = chunkTypeUncompressedData
|
||||
chunkLen = 4 + len(uncompressed)
|
||||
obufEnd = obufHeaderLen
|
||||
}
|
||||
|
||||
// Fill in the per-chunk header that comes before the body.
|
||||
w.obuf[len(magicChunk)+0] = chunkType
|
||||
w.obuf[len(magicChunk)+1] = uint8(chunkLen >> 0)
|
||||
w.obuf[len(magicChunk)+2] = uint8(chunkLen >> 8)
|
||||
w.obuf[len(magicChunk)+3] = uint8(chunkLen >> 16)
|
||||
w.obuf[len(magicChunk)+4] = uint8(checksum >> 0)
|
||||
w.obuf[len(magicChunk)+5] = uint8(checksum >> 8)
|
||||
w.obuf[len(magicChunk)+6] = uint8(checksum >> 16)
|
||||
w.obuf[len(magicChunk)+7] = uint8(checksum >> 24)
|
||||
|
||||
if _, err := w.w.Write(w.obuf[obufStart:obufEnd]); err != nil {
|
||||
w.err = err
|
||||
return nRet, err
|
||||
}
|
||||
if chunkType == chunkTypeUncompressedData {
|
||||
if _, err := w.w.Write(uncompressed); err != nil {
|
||||
w.err = err
|
||||
return nRet, err
|
||||
}
|
||||
}
|
||||
nRet += len(uncompressed)
|
||||
}
|
||||
return nRet, nil
|
||||
}
|
||||
|
||||
// Flush flushes the Writer to its underlying io.Writer.
|
||||
func (w *Writer) Flush() error {
|
||||
if w.err != nil {
|
||||
return w.err
|
||||
}
|
||||
if len(w.ibuf) == 0 {
|
||||
return nil
|
||||
}
|
||||
w.write(w.ibuf)
|
||||
w.ibuf = w.ibuf[:0]
|
||||
return w.err
|
||||
}
|
||||
|
||||
// Close calls Flush and then closes the Writer.
|
||||
func (w *Writer) Close() error {
|
||||
w.Flush()
|
||||
ret := w.err
|
||||
if w.err == nil {
|
||||
w.err = errClosed
|
||||
}
|
||||
return ret
|
||||
}
|
||||
29
vendor/github.com/golang/snappy/encode_amd64.go
generated
vendored
Normal file
29
vendor/github.com/golang/snappy/encode_amd64.go
generated
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
// Copyright 2016 The Snappy-Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !appengine
|
||||
// +build gc
|
||||
// +build !noasm
|
||||
|
||||
package snappy
|
||||
|
||||
// emitLiteral has the same semantics as in encode_other.go.
|
||||
//
|
||||
//go:noescape
|
||||
func emitLiteral(dst, lit []byte) int
|
||||
|
||||
// emitCopy has the same semantics as in encode_other.go.
|
||||
//
|
||||
//go:noescape
|
||||
func emitCopy(dst []byte, offset, length int) int
|
||||
|
||||
// extendMatch has the same semantics as in encode_other.go.
|
||||
//
|
||||
//go:noescape
|
||||
func extendMatch(src []byte, i, j int) int
|
||||
|
||||
// encodeBlock has the same semantics as in encode_other.go.
|
||||
//
|
||||
//go:noescape
|
||||
func encodeBlock(dst, src []byte) (d int)
|
||||
730
vendor/github.com/golang/snappy/encode_amd64.s
generated
vendored
Normal file
730
vendor/github.com/golang/snappy/encode_amd64.s
generated
vendored
Normal file
@@ -0,0 +1,730 @@
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !appengine
|
||||
// +build gc
|
||||
// +build !noasm
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
// The XXX lines assemble on Go 1.4, 1.5 and 1.7, but not 1.6, due to a
|
||||
// Go toolchain regression. See https://github.com/golang/go/issues/15426 and
|
||||
// https://github.com/golang/snappy/issues/29
|
||||
//
|
||||
// As a workaround, the package was built with a known good assembler, and
|
||||
// those instructions were disassembled by "objdump -d" to yield the
|
||||
// 4e 0f b7 7c 5c 78 movzwq 0x78(%rsp,%r11,2),%r15
|
||||
// style comments, in AT&T asm syntax. Note that rsp here is a physical
|
||||
// register, not Go/asm's SP pseudo-register (see https://golang.org/doc/asm).
|
||||
// The instructions were then encoded as "BYTE $0x.." sequences, which assemble
|
||||
// fine on Go 1.6.
|
||||
|
||||
// The asm code generally follows the pure Go code in encode_other.go, except
|
||||
// where marked with a "!!!".
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// func emitLiteral(dst, lit []byte) int
|
||||
//
|
||||
// All local variables fit into registers. The register allocation:
|
||||
// - AX len(lit)
|
||||
// - BX n
|
||||
// - DX return value
|
||||
// - DI &dst[i]
|
||||
// - R10 &lit[0]
|
||||
//
|
||||
// The 24 bytes of stack space is to call runtime·memmove.
|
||||
//
|
||||
// The unusual register allocation of local variables, such as R10 for the
|
||||
// source pointer, matches the allocation used at the call site in encodeBlock,
|
||||
// which makes it easier to manually inline this function.
|
||||
TEXT ·emitLiteral(SB), NOSPLIT, $24-56
|
||||
MOVQ dst_base+0(FP), DI
|
||||
MOVQ lit_base+24(FP), R10
|
||||
MOVQ lit_len+32(FP), AX
|
||||
MOVQ AX, DX
|
||||
MOVL AX, BX
|
||||
SUBL $1, BX
|
||||
|
||||
CMPL BX, $60
|
||||
JLT oneByte
|
||||
CMPL BX, $256
|
||||
JLT twoBytes
|
||||
|
||||
threeBytes:
|
||||
MOVB $0xf4, 0(DI)
|
||||
MOVW BX, 1(DI)
|
||||
ADDQ $3, DI
|
||||
ADDQ $3, DX
|
||||
JMP memmove
|
||||
|
||||
twoBytes:
|
||||
MOVB $0xf0, 0(DI)
|
||||
MOVB BX, 1(DI)
|
||||
ADDQ $2, DI
|
||||
ADDQ $2, DX
|
||||
JMP memmove
|
||||
|
||||
oneByte:
|
||||
SHLB $2, BX
|
||||
MOVB BX, 0(DI)
|
||||
ADDQ $1, DI
|
||||
ADDQ $1, DX
|
||||
|
||||
memmove:
|
||||
MOVQ DX, ret+48(FP)
|
||||
|
||||
// copy(dst[i:], lit)
|
||||
//
|
||||
// This means calling runtime·memmove(&dst[i], &lit[0], len(lit)), so we push
|
||||
// DI, R10 and AX as arguments.
|
||||
MOVQ DI, 0(SP)
|
||||
MOVQ R10, 8(SP)
|
||||
MOVQ AX, 16(SP)
|
||||
CALL runtime·memmove(SB)
|
||||
RET
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// func emitCopy(dst []byte, offset, length int) int
|
||||
//
|
||||
// All local variables fit into registers. The register allocation:
|
||||
// - AX length
|
||||
// - SI &dst[0]
|
||||
// - DI &dst[i]
|
||||
// - R11 offset
|
||||
//
|
||||
// The unusual register allocation of local variables, such as R11 for the
|
||||
// offset, matches the allocation used at the call site in encodeBlock, which
|
||||
// makes it easier to manually inline this function.
|
||||
TEXT ·emitCopy(SB), NOSPLIT, $0-48
|
||||
MOVQ dst_base+0(FP), DI
|
||||
MOVQ DI, SI
|
||||
MOVQ offset+24(FP), R11
|
||||
MOVQ length+32(FP), AX
|
||||
|
||||
loop0:
|
||||
// for length >= 68 { etc }
|
||||
CMPL AX, $68
|
||||
JLT step1
|
||||
|
||||
// Emit a length 64 copy, encoded as 3 bytes.
|
||||
MOVB $0xfe, 0(DI)
|
||||
MOVW R11, 1(DI)
|
||||
ADDQ $3, DI
|
||||
SUBL $64, AX
|
||||
JMP loop0
|
||||
|
||||
step1:
|
||||
// if length > 64 { etc }
|
||||
CMPL AX, $64
|
||||
JLE step2
|
||||
|
||||
// Emit a length 60 copy, encoded as 3 bytes.
|
||||
MOVB $0xee, 0(DI)
|
||||
MOVW R11, 1(DI)
|
||||
ADDQ $3, DI
|
||||
SUBL $60, AX
|
||||
|
||||
step2:
|
||||
// if length >= 12 || offset >= 2048 { goto step3 }
|
||||
CMPL AX, $12
|
||||
JGE step3
|
||||
CMPL R11, $2048
|
||||
JGE step3
|
||||
|
||||
// Emit the remaining copy, encoded as 2 bytes.
|
||||
MOVB R11, 1(DI)
|
||||
SHRL $8, R11
|
||||
SHLB $5, R11
|
||||
SUBB $4, AX
|
||||
SHLB $2, AX
|
||||
ORB AX, R11
|
||||
ORB $1, R11
|
||||
MOVB R11, 0(DI)
|
||||
ADDQ $2, DI
|
||||
|
||||
// Return the number of bytes written.
|
||||
SUBQ SI, DI
|
||||
MOVQ DI, ret+40(FP)
|
||||
RET
|
||||
|
||||
step3:
|
||||
// Emit the remaining copy, encoded as 3 bytes.
|
||||
SUBL $1, AX
|
||||
SHLB $2, AX
|
||||
ORB $2, AX
|
||||
MOVB AX, 0(DI)
|
||||
MOVW R11, 1(DI)
|
||||
ADDQ $3, DI
|
||||
|
||||
// Return the number of bytes written.
|
||||
SUBQ SI, DI
|
||||
MOVQ DI, ret+40(FP)
|
||||
RET
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// func extendMatch(src []byte, i, j int) int
|
||||
//
|
||||
// All local variables fit into registers. The register allocation:
|
||||
// - DX &src[0]
|
||||
// - SI &src[j]
|
||||
// - R13 &src[len(src) - 8]
|
||||
// - R14 &src[len(src)]
|
||||
// - R15 &src[i]
|
||||
//
|
||||
// The unusual register allocation of local variables, such as R15 for a source
|
||||
// pointer, matches the allocation used at the call site in encodeBlock, which
|
||||
// makes it easier to manually inline this function.
|
||||
TEXT ·extendMatch(SB), NOSPLIT, $0-48
|
||||
MOVQ src_base+0(FP), DX
|
||||
MOVQ src_len+8(FP), R14
|
||||
MOVQ i+24(FP), R15
|
||||
MOVQ j+32(FP), SI
|
||||
ADDQ DX, R14
|
||||
ADDQ DX, R15
|
||||
ADDQ DX, SI
|
||||
MOVQ R14, R13
|
||||
SUBQ $8, R13
|
||||
|
||||
cmp8:
|
||||
// As long as we are 8 or more bytes before the end of src, we can load and
|
||||
// compare 8 bytes at a time. If those 8 bytes are equal, repeat.
|
||||
CMPQ SI, R13
|
||||
JA cmp1
|
||||
MOVQ (R15), AX
|
||||
MOVQ (SI), BX
|
||||
CMPQ AX, BX
|
||||
JNE bsf
|
||||
ADDQ $8, R15
|
||||
ADDQ $8, SI
|
||||
JMP cmp8
|
||||
|
||||
bsf:
|
||||
// If those 8 bytes were not equal, XOR the two 8 byte values, and return
|
||||
// the index of the first byte that differs. The BSF instruction finds the
|
||||
// least significant 1 bit, the amd64 architecture is little-endian, and
|
||||
// the shift by 3 converts a bit index to a byte index.
|
||||
XORQ AX, BX
|
||||
BSFQ BX, BX
|
||||
SHRQ $3, BX
|
||||
ADDQ BX, SI
|
||||
|
||||
// Convert from &src[ret] to ret.
|
||||
SUBQ DX, SI
|
||||
MOVQ SI, ret+40(FP)
|
||||
RET
|
||||
|
||||
cmp1:
|
||||
// In src's tail, compare 1 byte at a time.
|
||||
CMPQ SI, R14
|
||||
JAE extendMatchEnd
|
||||
MOVB (R15), AX
|
||||
MOVB (SI), BX
|
||||
CMPB AX, BX
|
||||
JNE extendMatchEnd
|
||||
ADDQ $1, R15
|
||||
ADDQ $1, SI
|
||||
JMP cmp1
|
||||
|
||||
extendMatchEnd:
|
||||
// Convert from &src[ret] to ret.
|
||||
SUBQ DX, SI
|
||||
MOVQ SI, ret+40(FP)
|
||||
RET
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// func encodeBlock(dst, src []byte) (d int)
|
||||
//
|
||||
// All local variables fit into registers, other than "var table". The register
|
||||
// allocation:
|
||||
// - AX . .
|
||||
// - BX . .
|
||||
// - CX 56 shift (note that amd64 shifts by non-immediates must use CX).
|
||||
// - DX 64 &src[0], tableSize
|
||||
// - SI 72 &src[s]
|
||||
// - DI 80 &dst[d]
|
||||
// - R9 88 sLimit
|
||||
// - R10 . &src[nextEmit]
|
||||
// - R11 96 prevHash, currHash, nextHash, offset
|
||||
// - R12 104 &src[base], skip
|
||||
// - R13 . &src[nextS], &src[len(src) - 8]
|
||||
// - R14 . len(src), bytesBetweenHashLookups, &src[len(src)], x
|
||||
// - R15 112 candidate
|
||||
//
|
||||
// The second column (56, 64, etc) is the stack offset to spill the registers
|
||||
// when calling other functions. We could pack this slightly tighter, but it's
|
||||
// simpler to have a dedicated spill map independent of the function called.
|
||||
//
|
||||
// "var table [maxTableSize]uint16" takes up 32768 bytes of stack space. An
|
||||
// extra 56 bytes, to call other functions, and an extra 64 bytes, to spill
|
||||
// local variables (registers) during calls gives 32768 + 56 + 64 = 32888.
|
||||
TEXT ·encodeBlock(SB), 0, $32888-56
|
||||
MOVQ dst_base+0(FP), DI
|
||||
MOVQ src_base+24(FP), SI
|
||||
MOVQ src_len+32(FP), R14
|
||||
|
||||
// shift, tableSize := uint32(32-8), 1<<8
|
||||
MOVQ $24, CX
|
||||
MOVQ $256, DX
|
||||
|
||||
calcShift:
|
||||
// for ; tableSize < maxTableSize && tableSize < len(src); tableSize *= 2 {
|
||||
// shift--
|
||||
// }
|
||||
CMPQ DX, $16384
|
||||
JGE varTable
|
||||
CMPQ DX, R14
|
||||
JGE varTable
|
||||
SUBQ $1, CX
|
||||
SHLQ $1, DX
|
||||
JMP calcShift
|
||||
|
||||
varTable:
|
||||
// var table [maxTableSize]uint16
|
||||
//
|
||||
// In the asm code, unlike the Go code, we can zero-initialize only the
|
||||
// first tableSize elements. Each uint16 element is 2 bytes and each MOVOU
|
||||
// writes 16 bytes, so we can do only tableSize/8 writes instead of the
|
||||
// 2048 writes that would zero-initialize all of table's 32768 bytes.
|
||||
SHRQ $3, DX
|
||||
LEAQ table-32768(SP), BX
|
||||
PXOR X0, X0
|
||||
|
||||
memclr:
|
||||
MOVOU X0, 0(BX)
|
||||
ADDQ $16, BX
|
||||
SUBQ $1, DX
|
||||
JNZ memclr
|
||||
|
||||
// !!! DX = &src[0]
|
||||
MOVQ SI, DX
|
||||
|
||||
// sLimit := len(src) - inputMargin
|
||||
MOVQ R14, R9
|
||||
SUBQ $15, R9
|
||||
|
||||
// !!! Pre-emptively spill CX, DX and R9 to the stack. Their values don't
|
||||
// change for the rest of the function.
|
||||
MOVQ CX, 56(SP)
|
||||
MOVQ DX, 64(SP)
|
||||
MOVQ R9, 88(SP)
|
||||
|
||||
// nextEmit := 0
|
||||
MOVQ DX, R10
|
||||
|
||||
// s := 1
|
||||
ADDQ $1, SI
|
||||
|
||||
// nextHash := hash(load32(src, s), shift)
|
||||
MOVL 0(SI), R11
|
||||
IMULL $0x1e35a7bd, R11
|
||||
SHRL CX, R11
|
||||
|
||||
outer:
|
||||
// for { etc }
|
||||
|
||||
// skip := 32
|
||||
MOVQ $32, R12
|
||||
|
||||
// nextS := s
|
||||
MOVQ SI, R13
|
||||
|
||||
// candidate := 0
|
||||
MOVQ $0, R15
|
||||
|
||||
inner0:
|
||||
// for { etc }
|
||||
|
||||
// s := nextS
|
||||
MOVQ R13, SI
|
||||
|
||||
// bytesBetweenHashLookups := skip >> 5
|
||||
MOVQ R12, R14
|
||||
SHRQ $5, R14
|
||||
|
||||
// nextS = s + bytesBetweenHashLookups
|
||||
ADDQ R14, R13
|
||||
|
||||
// skip += bytesBetweenHashLookups
|
||||
ADDQ R14, R12
|
||||
|
||||
// if nextS > sLimit { goto emitRemainder }
|
||||
MOVQ R13, AX
|
||||
SUBQ DX, AX
|
||||
CMPQ AX, R9
|
||||
JA emitRemainder
|
||||
|
||||
// candidate = int(table[nextHash])
|
||||
// XXX: MOVWQZX table-32768(SP)(R11*2), R15
|
||||
// XXX: 4e 0f b7 7c 5c 78 movzwq 0x78(%rsp,%r11,2),%r15
|
||||
BYTE $0x4e
|
||||
BYTE $0x0f
|
||||
BYTE $0xb7
|
||||
BYTE $0x7c
|
||||
BYTE $0x5c
|
||||
BYTE $0x78
|
||||
|
||||
// table[nextHash] = uint16(s)
|
||||
MOVQ SI, AX
|
||||
SUBQ DX, AX
|
||||
|
||||
// XXX: MOVW AX, table-32768(SP)(R11*2)
|
||||
// XXX: 66 42 89 44 5c 78 mov %ax,0x78(%rsp,%r11,2)
|
||||
BYTE $0x66
|
||||
BYTE $0x42
|
||||
BYTE $0x89
|
||||
BYTE $0x44
|
||||
BYTE $0x5c
|
||||
BYTE $0x78
|
||||
|
||||
// nextHash = hash(load32(src, nextS), shift)
|
||||
MOVL 0(R13), R11
|
||||
IMULL $0x1e35a7bd, R11
|
||||
SHRL CX, R11
|
||||
|
||||
// if load32(src, s) != load32(src, candidate) { continue } break
|
||||
MOVL 0(SI), AX
|
||||
MOVL (DX)(R15*1), BX
|
||||
CMPL AX, BX
|
||||
JNE inner0
|
||||
|
||||
fourByteMatch:
|
||||
// As per the encode_other.go code:
|
||||
//
|
||||
// A 4-byte match has been found. We'll later see etc.
|
||||
|
||||
// !!! Jump to a fast path for short (<= 16 byte) literals. See the comment
|
||||
// on inputMargin in encode.go.
|
||||
MOVQ SI, AX
|
||||
SUBQ R10, AX
|
||||
CMPQ AX, $16
|
||||
JLE emitLiteralFastPath
|
||||
|
||||
// ----------------------------------------
|
||||
// Begin inline of the emitLiteral call.
|
||||
//
|
||||
// d += emitLiteral(dst[d:], src[nextEmit:s])
|
||||
|
||||
MOVL AX, BX
|
||||
SUBL $1, BX
|
||||
|
||||
CMPL BX, $60
|
||||
JLT inlineEmitLiteralOneByte
|
||||
CMPL BX, $256
|
||||
JLT inlineEmitLiteralTwoBytes
|
||||
|
||||
inlineEmitLiteralThreeBytes:
|
||||
MOVB $0xf4, 0(DI)
|
||||
MOVW BX, 1(DI)
|
||||
ADDQ $3, DI
|
||||
JMP inlineEmitLiteralMemmove
|
||||
|
||||
inlineEmitLiteralTwoBytes:
|
||||
MOVB $0xf0, 0(DI)
|
||||
MOVB BX, 1(DI)
|
||||
ADDQ $2, DI
|
||||
JMP inlineEmitLiteralMemmove
|
||||
|
||||
inlineEmitLiteralOneByte:
|
||||
SHLB $2, BX
|
||||
MOVB BX, 0(DI)
|
||||
ADDQ $1, DI
|
||||
|
||||
inlineEmitLiteralMemmove:
|
||||
// Spill local variables (registers) onto the stack; call; unspill.
|
||||
//
|
||||
// copy(dst[i:], lit)
|
||||
//
|
||||
// This means calling runtime·memmove(&dst[i], &lit[0], len(lit)), so we push
|
||||
// DI, R10 and AX as arguments.
|
||||
MOVQ DI, 0(SP)
|
||||
MOVQ R10, 8(SP)
|
||||
MOVQ AX, 16(SP)
|
||||
ADDQ AX, DI // Finish the "d +=" part of "d += emitLiteral(etc)".
|
||||
MOVQ SI, 72(SP)
|
||||
MOVQ DI, 80(SP)
|
||||
MOVQ R15, 112(SP)
|
||||
CALL runtime·memmove(SB)
|
||||
MOVQ 56(SP), CX
|
||||
MOVQ 64(SP), DX
|
||||
MOVQ 72(SP), SI
|
||||
MOVQ 80(SP), DI
|
||||
MOVQ 88(SP), R9
|
||||
MOVQ 112(SP), R15
|
||||
JMP inner1
|
||||
|
||||
inlineEmitLiteralEnd:
|
||||
// End inline of the emitLiteral call.
|
||||
// ----------------------------------------
|
||||
|
||||
emitLiteralFastPath:
|
||||
// !!! Emit the 1-byte encoding "uint8(len(lit)-1)<<2".
|
||||
MOVB AX, BX
|
||||
SUBB $1, BX
|
||||
SHLB $2, BX
|
||||
MOVB BX, (DI)
|
||||
ADDQ $1, DI
|
||||
|
||||
// !!! Implement the copy from lit to dst as a 16-byte load and store.
|
||||
// (Encode's documentation says that dst and src must not overlap.)
|
||||
//
|
||||
// This always copies 16 bytes, instead of only len(lit) bytes, but that's
|
||||
// OK. Subsequent iterations will fix up the overrun.
|
||||
//
|
||||
// Note that on amd64, it is legal and cheap to issue unaligned 8-byte or
|
||||
// 16-byte loads and stores. This technique probably wouldn't be as
|
||||
// effective on architectures that are fussier about alignment.
|
||||
MOVOU 0(R10), X0
|
||||
MOVOU X0, 0(DI)
|
||||
ADDQ AX, DI
|
||||
|
||||
inner1:
|
||||
// for { etc }
|
||||
|
||||
// base := s
|
||||
MOVQ SI, R12
|
||||
|
||||
// !!! offset := base - candidate
|
||||
MOVQ R12, R11
|
||||
SUBQ R15, R11
|
||||
SUBQ DX, R11
|
||||
|
||||
// ----------------------------------------
|
||||
// Begin inline of the extendMatch call.
|
||||
//
|
||||
// s = extendMatch(src, candidate+4, s+4)
|
||||
|
||||
// !!! R14 = &src[len(src)]
|
||||
MOVQ src_len+32(FP), R14
|
||||
ADDQ DX, R14
|
||||
|
||||
// !!! R13 = &src[len(src) - 8]
|
||||
MOVQ R14, R13
|
||||
SUBQ $8, R13
|
||||
|
||||
// !!! R15 = &src[candidate + 4]
|
||||
ADDQ $4, R15
|
||||
ADDQ DX, R15
|
||||
|
||||
// !!! s += 4
|
||||
ADDQ $4, SI
|
||||
|
||||
inlineExtendMatchCmp8:
|
||||
// As long as we are 8 or more bytes before the end of src, we can load and
|
||||
// compare 8 bytes at a time. If those 8 bytes are equal, repeat.
|
||||
CMPQ SI, R13
|
||||
JA inlineExtendMatchCmp1
|
||||
MOVQ (R15), AX
|
||||
MOVQ (SI), BX
|
||||
CMPQ AX, BX
|
||||
JNE inlineExtendMatchBSF
|
||||
ADDQ $8, R15
|
||||
ADDQ $8, SI
|
||||
JMP inlineExtendMatchCmp8
|
||||
|
||||
inlineExtendMatchBSF:
|
||||
// If those 8 bytes were not equal, XOR the two 8 byte values, and return
|
||||
// the index of the first byte that differs. The BSF instruction finds the
|
||||
// least significant 1 bit, the amd64 architecture is little-endian, and
|
||||
// the shift by 3 converts a bit index to a byte index.
|
||||
XORQ AX, BX
|
||||
BSFQ BX, BX
|
||||
SHRQ $3, BX
|
||||
ADDQ BX, SI
|
||||
JMP inlineExtendMatchEnd
|
||||
|
||||
inlineExtendMatchCmp1:
|
||||
// In src's tail, compare 1 byte at a time.
|
||||
CMPQ SI, R14
|
||||
JAE inlineExtendMatchEnd
|
||||
MOVB (R15), AX
|
||||
MOVB (SI), BX
|
||||
CMPB AX, BX
|
||||
JNE inlineExtendMatchEnd
|
||||
ADDQ $1, R15
|
||||
ADDQ $1, SI
|
||||
JMP inlineExtendMatchCmp1
|
||||
|
||||
inlineExtendMatchEnd:
|
||||
// End inline of the extendMatch call.
|
||||
// ----------------------------------------
|
||||
|
||||
// ----------------------------------------
|
||||
// Begin inline of the emitCopy call.
|
||||
//
|
||||
// d += emitCopy(dst[d:], base-candidate, s-base)
|
||||
|
||||
// !!! length := s - base
|
||||
MOVQ SI, AX
|
||||
SUBQ R12, AX
|
||||
|
||||
inlineEmitCopyLoop0:
|
||||
// for length >= 68 { etc }
|
||||
CMPL AX, $68
|
||||
JLT inlineEmitCopyStep1
|
||||
|
||||
// Emit a length 64 copy, encoded as 3 bytes.
|
||||
MOVB $0xfe, 0(DI)
|
||||
MOVW R11, 1(DI)
|
||||
ADDQ $3, DI
|
||||
SUBL $64, AX
|
||||
JMP inlineEmitCopyLoop0
|
||||
|
||||
inlineEmitCopyStep1:
|
||||
// if length > 64 { etc }
|
||||
CMPL AX, $64
|
||||
JLE inlineEmitCopyStep2
|
||||
|
||||
// Emit a length 60 copy, encoded as 3 bytes.
|
||||
MOVB $0xee, 0(DI)
|
||||
MOVW R11, 1(DI)
|
||||
ADDQ $3, DI
|
||||
SUBL $60, AX
|
||||
|
||||
inlineEmitCopyStep2:
|
||||
// if length >= 12 || offset >= 2048 { goto inlineEmitCopyStep3 }
|
||||
CMPL AX, $12
|
||||
JGE inlineEmitCopyStep3
|
||||
CMPL R11, $2048
|
||||
JGE inlineEmitCopyStep3
|
||||
|
||||
// Emit the remaining copy, encoded as 2 bytes.
|
||||
MOVB R11, 1(DI)
|
||||
SHRL $8, R11
|
||||
SHLB $5, R11
|
||||
SUBB $4, AX
|
||||
SHLB $2, AX
|
||||
ORB AX, R11
|
||||
ORB $1, R11
|
||||
MOVB R11, 0(DI)
|
||||
ADDQ $2, DI
|
||||
JMP inlineEmitCopyEnd
|
||||
|
||||
inlineEmitCopyStep3:
|
||||
// Emit the remaining copy, encoded as 3 bytes.
|
||||
SUBL $1, AX
|
||||
SHLB $2, AX
|
||||
ORB $2, AX
|
||||
MOVB AX, 0(DI)
|
||||
MOVW R11, 1(DI)
|
||||
ADDQ $3, DI
|
||||
|
||||
inlineEmitCopyEnd:
|
||||
// End inline of the emitCopy call.
|
||||
// ----------------------------------------
|
||||
|
||||
// nextEmit = s
|
||||
MOVQ SI, R10
|
||||
|
||||
// if s >= sLimit { goto emitRemainder }
|
||||
MOVQ SI, AX
|
||||
SUBQ DX, AX
|
||||
CMPQ AX, R9
|
||||
JAE emitRemainder
|
||||
|
||||
// As per the encode_other.go code:
|
||||
//
|
||||
// We could immediately etc.
|
||||
|
||||
// x := load64(src, s-1)
|
||||
MOVQ -1(SI), R14
|
||||
|
||||
// prevHash := hash(uint32(x>>0), shift)
|
||||
MOVL R14, R11
|
||||
IMULL $0x1e35a7bd, R11
|
||||
SHRL CX, R11
|
||||
|
||||
// table[prevHash] = uint16(s-1)
|
||||
MOVQ SI, AX
|
||||
SUBQ DX, AX
|
||||
SUBQ $1, AX
|
||||
|
||||
// XXX: MOVW AX, table-32768(SP)(R11*2)
|
||||
// XXX: 66 42 89 44 5c 78 mov %ax,0x78(%rsp,%r11,2)
|
||||
BYTE $0x66
|
||||
BYTE $0x42
|
||||
BYTE $0x89
|
||||
BYTE $0x44
|
||||
BYTE $0x5c
|
||||
BYTE $0x78
|
||||
|
||||
// currHash := hash(uint32(x>>8), shift)
|
||||
SHRQ $8, R14
|
||||
MOVL R14, R11
|
||||
IMULL $0x1e35a7bd, R11
|
||||
SHRL CX, R11
|
||||
|
||||
// candidate = int(table[currHash])
|
||||
// XXX: MOVWQZX table-32768(SP)(R11*2), R15
|
||||
// XXX: 4e 0f b7 7c 5c 78 movzwq 0x78(%rsp,%r11,2),%r15
|
||||
BYTE $0x4e
|
||||
BYTE $0x0f
|
||||
BYTE $0xb7
|
||||
BYTE $0x7c
|
||||
BYTE $0x5c
|
||||
BYTE $0x78
|
||||
|
||||
// table[currHash] = uint16(s)
|
||||
ADDQ $1, AX
|
||||
|
||||
// XXX: MOVW AX, table-32768(SP)(R11*2)
|
||||
// XXX: 66 42 89 44 5c 78 mov %ax,0x78(%rsp,%r11,2)
|
||||
BYTE $0x66
|
||||
BYTE $0x42
|
||||
BYTE $0x89
|
||||
BYTE $0x44
|
||||
BYTE $0x5c
|
||||
BYTE $0x78
|
||||
|
||||
// if uint32(x>>8) == load32(src, candidate) { continue }
|
||||
MOVL (DX)(R15*1), BX
|
||||
CMPL R14, BX
|
||||
JEQ inner1
|
||||
|
||||
// nextHash = hash(uint32(x>>16), shift)
|
||||
SHRQ $8, R14
|
||||
MOVL R14, R11
|
||||
IMULL $0x1e35a7bd, R11
|
||||
SHRL CX, R11
|
||||
|
||||
// s++
|
||||
ADDQ $1, SI
|
||||
|
||||
// break out of the inner1 for loop, i.e. continue the outer loop.
|
||||
JMP outer
|
||||
|
||||
emitRemainder:
|
||||
// if nextEmit < len(src) { etc }
|
||||
MOVQ src_len+32(FP), AX
|
||||
ADDQ DX, AX
|
||||
CMPQ R10, AX
|
||||
JEQ encodeBlockEnd
|
||||
|
||||
// d += emitLiteral(dst[d:], src[nextEmit:])
|
||||
//
|
||||
// Push args.
|
||||
MOVQ DI, 0(SP)
|
||||
MOVQ $0, 8(SP) // Unnecessary, as the callee ignores it, but conservative.
|
||||
MOVQ $0, 16(SP) // Unnecessary, as the callee ignores it, but conservative.
|
||||
MOVQ R10, 24(SP)
|
||||
SUBQ R10, AX
|
||||
MOVQ AX, 32(SP)
|
||||
MOVQ AX, 40(SP) // Unnecessary, as the callee ignores it, but conservative.
|
||||
|
||||
// Spill local variables (registers) onto the stack; call; unspill.
|
||||
MOVQ DI, 80(SP)
|
||||
CALL ·emitLiteral(SB)
|
||||
MOVQ 80(SP), DI
|
||||
|
||||
// Finish the "d +=" part of "d += emitLiteral(etc)".
|
||||
ADDQ 48(SP), DI
|
||||
|
||||
encodeBlockEnd:
|
||||
MOVQ dst_base+0(FP), AX
|
||||
SUBQ AX, DI
|
||||
MOVQ DI, d+48(FP)
|
||||
RET
|
||||
238
vendor/github.com/golang/snappy/encode_other.go
generated
vendored
Normal file
238
vendor/github.com/golang/snappy/encode_other.go
generated
vendored
Normal file
@@ -0,0 +1,238 @@
|
||||
// Copyright 2016 The Snappy-Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !amd64 appengine !gc noasm
|
||||
|
||||
package snappy
|
||||
|
||||
func load32(b []byte, i int) uint32 {
|
||||
b = b[i : i+4 : len(b)] // Help the compiler eliminate bounds checks on the next line.
|
||||
return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24
|
||||
}
|
||||
|
||||
func load64(b []byte, i int) uint64 {
|
||||
b = b[i : i+8 : len(b)] // Help the compiler eliminate bounds checks on the next line.
|
||||
return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 |
|
||||
uint64(b[4])<<32 | uint64(b[5])<<40 | uint64(b[6])<<48 | uint64(b[7])<<56
|
||||
}
|
||||
|
||||
// emitLiteral writes a literal chunk and returns the number of bytes written.
|
||||
//
|
||||
// It assumes that:
|
||||
// dst is long enough to hold the encoded bytes
|
||||
// 1 <= len(lit) && len(lit) <= 65536
|
||||
func emitLiteral(dst, lit []byte) int {
|
||||
i, n := 0, uint(len(lit)-1)
|
||||
switch {
|
||||
case n < 60:
|
||||
dst[0] = uint8(n)<<2 | tagLiteral
|
||||
i = 1
|
||||
case n < 1<<8:
|
||||
dst[0] = 60<<2 | tagLiteral
|
||||
dst[1] = uint8(n)
|
||||
i = 2
|
||||
default:
|
||||
dst[0] = 61<<2 | tagLiteral
|
||||
dst[1] = uint8(n)
|
||||
dst[2] = uint8(n >> 8)
|
||||
i = 3
|
||||
}
|
||||
return i + copy(dst[i:], lit)
|
||||
}
|
||||
|
||||
// emitCopy writes a copy chunk and returns the number of bytes written.
|
||||
//
|
||||
// It assumes that:
|
||||
// dst is long enough to hold the encoded bytes
|
||||
// 1 <= offset && offset <= 65535
|
||||
// 4 <= length && length <= 65535
|
||||
func emitCopy(dst []byte, offset, length int) int {
|
||||
i := 0
|
||||
// The maximum length for a single tagCopy1 or tagCopy2 op is 64 bytes. The
|
||||
// threshold for this loop is a little higher (at 68 = 64 + 4), and the
|
||||
// length emitted down below is is a little lower (at 60 = 64 - 4), because
|
||||
// it's shorter to encode a length 67 copy as a length 60 tagCopy2 followed
|
||||
// by a length 7 tagCopy1 (which encodes as 3+2 bytes) than to encode it as
|
||||
// a length 64 tagCopy2 followed by a length 3 tagCopy2 (which encodes as
|
||||
// 3+3 bytes). The magic 4 in the 64±4 is because the minimum length for a
|
||||
// tagCopy1 op is 4 bytes, which is why a length 3 copy has to be an
|
||||
// encodes-as-3-bytes tagCopy2 instead of an encodes-as-2-bytes tagCopy1.
|
||||
for length >= 68 {
|
||||
// Emit a length 64 copy, encoded as 3 bytes.
|
||||
dst[i+0] = 63<<2 | tagCopy2
|
||||
dst[i+1] = uint8(offset)
|
||||
dst[i+2] = uint8(offset >> 8)
|
||||
i += 3
|
||||
length -= 64
|
||||
}
|
||||
if length > 64 {
|
||||
// Emit a length 60 copy, encoded as 3 bytes.
|
||||
dst[i+0] = 59<<2 | tagCopy2
|
||||
dst[i+1] = uint8(offset)
|
||||
dst[i+2] = uint8(offset >> 8)
|
||||
i += 3
|
||||
length -= 60
|
||||
}
|
||||
if length >= 12 || offset >= 2048 {
|
||||
// Emit the remaining copy, encoded as 3 bytes.
|
||||
dst[i+0] = uint8(length-1)<<2 | tagCopy2
|
||||
dst[i+1] = uint8(offset)
|
||||
dst[i+2] = uint8(offset >> 8)
|
||||
return i + 3
|
||||
}
|
||||
// Emit the remaining copy, encoded as 2 bytes.
|
||||
dst[i+0] = uint8(offset>>8)<<5 | uint8(length-4)<<2 | tagCopy1
|
||||
dst[i+1] = uint8(offset)
|
||||
return i + 2
|
||||
}
|
||||
|
||||
// extendMatch returns the largest k such that k <= len(src) and that
|
||||
// src[i:i+k-j] and src[j:k] have the same contents.
|
||||
//
|
||||
// It assumes that:
|
||||
// 0 <= i && i < j && j <= len(src)
|
||||
func extendMatch(src []byte, i, j int) int {
|
||||
for ; j < len(src) && src[i] == src[j]; i, j = i+1, j+1 {
|
||||
}
|
||||
return j
|
||||
}
|
||||
|
||||
func hash(u, shift uint32) uint32 {
|
||||
return (u * 0x1e35a7bd) >> shift
|
||||
}
|
||||
|
||||
// encodeBlock encodes a non-empty src to a guaranteed-large-enough dst. It
|
||||
// assumes that the varint-encoded length of the decompressed bytes has already
|
||||
// been written.
|
||||
//
|
||||
// It also assumes that:
|
||||
// len(dst) >= MaxEncodedLen(len(src)) &&
|
||||
// minNonLiteralBlockSize <= len(src) && len(src) <= maxBlockSize
|
||||
func encodeBlock(dst, src []byte) (d int) {
|
||||
// Initialize the hash table. Its size ranges from 1<<8 to 1<<14 inclusive.
|
||||
// The table element type is uint16, as s < sLimit and sLimit < len(src)
|
||||
// and len(src) <= maxBlockSize and maxBlockSize == 65536.
|
||||
const (
|
||||
maxTableSize = 1 << 14
|
||||
// tableMask is redundant, but helps the compiler eliminate bounds
|
||||
// checks.
|
||||
tableMask = maxTableSize - 1
|
||||
)
|
||||
shift := uint32(32 - 8)
|
||||
for tableSize := 1 << 8; tableSize < maxTableSize && tableSize < len(src); tableSize *= 2 {
|
||||
shift--
|
||||
}
|
||||
// In Go, all array elements are zero-initialized, so there is no advantage
|
||||
// to a smaller tableSize per se. However, it matches the C++ algorithm,
|
||||
// and in the asm versions of this code, we can get away with zeroing only
|
||||
// the first tableSize elements.
|
||||
var table [maxTableSize]uint16
|
||||
|
||||
// sLimit is when to stop looking for offset/length copies. The inputMargin
|
||||
// lets us use a fast path for emitLiteral in the main loop, while we are
|
||||
// looking for copies.
|
||||
sLimit := len(src) - inputMargin
|
||||
|
||||
// nextEmit is where in src the next emitLiteral should start from.
|
||||
nextEmit := 0
|
||||
|
||||
// The encoded form must start with a literal, as there are no previous
|
||||
// bytes to copy, so we start looking for hash matches at s == 1.
|
||||
s := 1
|
||||
nextHash := hash(load32(src, s), shift)
|
||||
|
||||
for {
|
||||
// Copied from the C++ snappy implementation:
|
||||
//
|
||||
// Heuristic match skipping: If 32 bytes are scanned with no matches
|
||||
// found, start looking only at every other byte. If 32 more bytes are
|
||||
// scanned (or skipped), look at every third byte, etc.. When a match
|
||||
// is found, immediately go back to looking at every byte. This is a
|
||||
// small loss (~5% performance, ~0.1% density) for compressible data
|
||||
// due to more bookkeeping, but for non-compressible data (such as
|
||||
// JPEG) it's a huge win since the compressor quickly "realizes" the
|
||||
// data is incompressible and doesn't bother looking for matches
|
||||
// everywhere.
|
||||
//
|
||||
// The "skip" variable keeps track of how many bytes there are since
|
||||
// the last match; dividing it by 32 (ie. right-shifting by five) gives
|
||||
// the number of bytes to move ahead for each iteration.
|
||||
skip := 32
|
||||
|
||||
nextS := s
|
||||
candidate := 0
|
||||
for {
|
||||
s = nextS
|
||||
bytesBetweenHashLookups := skip >> 5
|
||||
nextS = s + bytesBetweenHashLookups
|
||||
skip += bytesBetweenHashLookups
|
||||
if nextS > sLimit {
|
||||
goto emitRemainder
|
||||
}
|
||||
candidate = int(table[nextHash&tableMask])
|
||||
table[nextHash&tableMask] = uint16(s)
|
||||
nextHash = hash(load32(src, nextS), shift)
|
||||
if load32(src, s) == load32(src, candidate) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// A 4-byte match has been found. We'll later see if more than 4 bytes
|
||||
// match. But, prior to the match, src[nextEmit:s] are unmatched. Emit
|
||||
// them as literal bytes.
|
||||
d += emitLiteral(dst[d:], src[nextEmit:s])
|
||||
|
||||
// Call emitCopy, and then see if another emitCopy could be our next
|
||||
// move. Repeat until we find no match for the input immediately after
|
||||
// what was consumed by the last emitCopy call.
|
||||
//
|
||||
// If we exit this loop normally then we need to call emitLiteral next,
|
||||
// though we don't yet know how big the literal will be. We handle that
|
||||
// by proceeding to the next iteration of the main loop. We also can
|
||||
// exit this loop via goto if we get close to exhausting the input.
|
||||
for {
|
||||
// Invariant: we have a 4-byte match at s, and no need to emit any
|
||||
// literal bytes prior to s.
|
||||
base := s
|
||||
|
||||
// Extend the 4-byte match as long as possible.
|
||||
//
|
||||
// This is an inlined version of:
|
||||
// s = extendMatch(src, candidate+4, s+4)
|
||||
s += 4
|
||||
for i := candidate + 4; s < len(src) && src[i] == src[s]; i, s = i+1, s+1 {
|
||||
}
|
||||
|
||||
d += emitCopy(dst[d:], base-candidate, s-base)
|
||||
nextEmit = s
|
||||
if s >= sLimit {
|
||||
goto emitRemainder
|
||||
}
|
||||
|
||||
// We could immediately start working at s now, but to improve
|
||||
// compression we first update the hash table at s-1 and at s. If
|
||||
// another emitCopy is not our next move, also calculate nextHash
|
||||
// at s+1. At least on GOARCH=amd64, these three hash calculations
|
||||
// are faster as one load64 call (with some shifts) instead of
|
||||
// three load32 calls.
|
||||
x := load64(src, s-1)
|
||||
prevHash := hash(uint32(x>>0), shift)
|
||||
table[prevHash&tableMask] = uint16(s - 1)
|
||||
currHash := hash(uint32(x>>8), shift)
|
||||
candidate = int(table[currHash&tableMask])
|
||||
table[currHash&tableMask] = uint16(s)
|
||||
if uint32(x>>8) != load32(src, candidate) {
|
||||
nextHash = hash(uint32(x>>16), shift)
|
||||
s++
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
emitRemainder:
|
||||
if nextEmit < len(src) {
|
||||
d += emitLiteral(dst[d:], src[nextEmit:])
|
||||
}
|
||||
return d
|
||||
}
|
||||
98
vendor/github.com/golang/snappy/snappy.go
generated
vendored
Normal file
98
vendor/github.com/golang/snappy/snappy.go
generated
vendored
Normal file
@@ -0,0 +1,98 @@
|
||||
// Copyright 2011 The Snappy-Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package snappy implements the Snappy compression format. It aims for very
|
||||
// high speeds and reasonable compression.
|
||||
//
|
||||
// There are actually two Snappy formats: block and stream. They are related,
|
||||
// but different: trying to decompress block-compressed data as a Snappy stream
|
||||
// will fail, and vice versa. The block format is the Decode and Encode
|
||||
// functions and the stream format is the Reader and Writer types.
|
||||
//
|
||||
// The block format, the more common case, is used when the complete size (the
|
||||
// number of bytes) of the original data is known upfront, at the time
|
||||
// compression starts. The stream format, also known as the framing format, is
|
||||
// for when that isn't always true.
|
||||
//
|
||||
// The canonical, C++ implementation is at https://github.com/google/snappy and
|
||||
// it only implements the block format.
|
||||
package snappy // import "github.com/golang/snappy"
|
||||
|
||||
import (
|
||||
"hash/crc32"
|
||||
)
|
||||
|
||||
/*
|
||||
Each encoded block begins with the varint-encoded length of the decoded data,
|
||||
followed by a sequence of chunks. Chunks begin and end on byte boundaries. The
|
||||
first byte of each chunk is broken into its 2 least and 6 most significant bits
|
||||
called l and m: l ranges in [0, 4) and m ranges in [0, 64). l is the chunk tag.
|
||||
Zero means a literal tag. All other values mean a copy tag.
|
||||
|
||||
For literal tags:
|
||||
- If m < 60, the next 1 + m bytes are literal bytes.
|
||||
- Otherwise, let n be the little-endian unsigned integer denoted by the next
|
||||
m - 59 bytes. The next 1 + n bytes after that are literal bytes.
|
||||
|
||||
For copy tags, length bytes are copied from offset bytes ago, in the style of
|
||||
Lempel-Ziv compression algorithms. In particular:
|
||||
- For l == 1, the offset ranges in [0, 1<<11) and the length in [4, 12).
|
||||
The length is 4 + the low 3 bits of m. The high 3 bits of m form bits 8-10
|
||||
of the offset. The next byte is bits 0-7 of the offset.
|
||||
- For l == 2, the offset ranges in [0, 1<<16) and the length in [1, 65).
|
||||
The length is 1 + m. The offset is the little-endian unsigned integer
|
||||
denoted by the next 2 bytes.
|
||||
- For l == 3, this tag is a legacy format that is no longer issued by most
|
||||
encoders. Nonetheless, the offset ranges in [0, 1<<32) and the length in
|
||||
[1, 65). The length is 1 + m. The offset is the little-endian unsigned
|
||||
integer denoted by the next 4 bytes.
|
||||
*/
|
||||
const (
|
||||
tagLiteral = 0x00
|
||||
tagCopy1 = 0x01
|
||||
tagCopy2 = 0x02
|
||||
tagCopy4 = 0x03
|
||||
)
|
||||
|
||||
const (
|
||||
checksumSize = 4
|
||||
chunkHeaderSize = 4
|
||||
magicChunk = "\xff\x06\x00\x00" + magicBody
|
||||
magicBody = "sNaPpY"
|
||||
|
||||
// maxBlockSize is the maximum size of the input to encodeBlock. It is not
|
||||
// part of the wire format per se, but some parts of the encoder assume
|
||||
// that an offset fits into a uint16.
|
||||
//
|
||||
// Also, for the framing format (Writer type instead of Encode function),
|
||||
// https://github.com/google/snappy/blob/master/framing_format.txt says
|
||||
// that "the uncompressed data in a chunk must be no longer than 65536
|
||||
// bytes".
|
||||
maxBlockSize = 65536
|
||||
|
||||
// maxEncodedLenOfMaxBlockSize equals MaxEncodedLen(maxBlockSize), but is
|
||||
// hard coded to be a const instead of a variable, so that obufLen can also
|
||||
// be a const. Their equivalence is confirmed by
|
||||
// TestMaxEncodedLenOfMaxBlockSize.
|
||||
maxEncodedLenOfMaxBlockSize = 76490
|
||||
|
||||
obufHeaderLen = len(magicChunk) + checksumSize + chunkHeaderSize
|
||||
obufLen = obufHeaderLen + maxEncodedLenOfMaxBlockSize
|
||||
)
|
||||
|
||||
const (
|
||||
chunkTypeCompressedData = 0x00
|
||||
chunkTypeUncompressedData = 0x01
|
||||
chunkTypePadding = 0xfe
|
||||
chunkTypeStreamIdentifier = 0xff
|
||||
)
|
||||
|
||||
var crcTable = crc32.MakeTable(crc32.Castagnoli)
|
||||
|
||||
// crc implements the checksum specified in section 3 of
|
||||
// https://github.com/google/snappy/blob/master/framing_format.txt
|
||||
func crc(b []byte) uint32 {
|
||||
c := crc32.Update(0, crcTable, b)
|
||||
return uint32(c>>15|c<<17) + 0xa282ead8
|
||||
}
|
||||
1
vendor/github.com/opentracing/opentracing-go/.gitignore
generated
vendored
Normal file
1
vendor/github.com/opentracing/opentracing-go/.gitignore
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
coverage.txt
|
||||
20
vendor/github.com/opentracing/opentracing-go/.travis.yml
generated
vendored
Normal file
20
vendor/github.com/opentracing/opentracing-go/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
language: go
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- go: "1.11.x"
|
||||
- go: "1.12.x"
|
||||
- go: "tip"
|
||||
env:
|
||||
- LINT=true
|
||||
- COVERAGE=true
|
||||
|
||||
install:
|
||||
- if [ "$LINT" == true ]; then go get -u golang.org/x/lint/golint/... ; else echo 'skipping lint'; fi
|
||||
- go get -u github.com/stretchr/testify/...
|
||||
|
||||
script:
|
||||
- make test
|
||||
- go build ./...
|
||||
- if [ "$LINT" == true ]; then make lint ; else echo 'skipping lint'; fi
|
||||
- if [ "$COVERAGE" == true ]; then make cover && bash <(curl -s https://codecov.io/bash) ; else echo 'skipping coverage'; fi
|
||||
46
vendor/github.com/opentracing/opentracing-go/CHANGELOG.md
generated
vendored
Normal file
46
vendor/github.com/opentracing/opentracing-go/CHANGELOG.md
generated
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
Changes by Version
|
||||
==================
|
||||
|
||||
1.1.0 (2019-03-23)
|
||||
-------------------
|
||||
|
||||
Notable changes:
|
||||
- The library is now released under Apache 2.0 license
|
||||
- Use Set() instead of Add() in HTTPHeadersCarrier is functionally a breaking change (fixes issue [#159](https://github.com/opentracing/opentracing-go/issues/159))
|
||||
- 'golang.org/x/net/context' is replaced with 'context' from the standard library
|
||||
|
||||
List of all changes:
|
||||
|
||||
- Export StartSpanFromContextWithTracer (#214) <Aaron Delaney>
|
||||
- Add IsGlobalTracerRegistered() to indicate if a tracer has been registered (#201) <Mike Goldsmith>
|
||||
- Use Set() instead of Add() in HTTPHeadersCarrier (#191) <jeremyxu2010>
|
||||
- Update license to Apache 2.0 (#181) <Andrea Kao>
|
||||
- Replace 'golang.org/x/net/context' with 'context' (#176) <Tony Ghita>
|
||||
- Port of Python opentracing/harness/api_check.py to Go (#146) <chris erway>
|
||||
- Fix race condition in MockSpan.Context() (#170) <Brad>
|
||||
- Add PeerHostIPv4.SetString() (#155) <NeoCN>
|
||||
- Add a Noop log field type to log to allow for optional fields (#150) <Matt Ho>
|
||||
|
||||
|
||||
1.0.2 (2017-04-26)
|
||||
-------------------
|
||||
|
||||
- Add more semantic tags (#139) <Rustam Zagirov>
|
||||
|
||||
|
||||
1.0.1 (2017-02-06)
|
||||
-------------------
|
||||
|
||||
- Correct spelling in comments <Ben Sigelman>
|
||||
- Address race in nextMockID() (#123) <bill fumerola>
|
||||
- log: avoid panic marshaling nil error (#131) <Anthony Voutas>
|
||||
- Deprecate InitGlobalTracer in favor of SetGlobalTracer (#128) <Yuri Shkuro>
|
||||
- Drop Go 1.5 that fails in Travis (#129) <Yuri Shkuro>
|
||||
- Add convenience methods Key() and Value() to log.Field <Ben Sigelman>
|
||||
- Add convenience methods to log.Field (2 years, 6 months ago) <Radu Berinde>
|
||||
|
||||
1.0.0 (2016-09-26)
|
||||
-------------------
|
||||
|
||||
- This release implements OpenTracing Specification 1.0 (https://opentracing.io/spec)
|
||||
|
||||
201
vendor/github.com/opentracing/opentracing-go/LICENSE
generated
vendored
Normal file
201
vendor/github.com/opentracing/opentracing-go/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2016 The OpenTracing 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.
|
||||
20
vendor/github.com/opentracing/opentracing-go/Makefile
generated
vendored
Normal file
20
vendor/github.com/opentracing/opentracing-go/Makefile
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
.DEFAULT_GOAL := test-and-lint
|
||||
|
||||
.PHONY: test-and-lint
|
||||
test-and-lint: test lint
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
go test -v -cover -race ./...
|
||||
|
||||
.PHONY: cover
|
||||
cover:
|
||||
go test -v -coverprofile=coverage.txt -covermode=atomic -race ./...
|
||||
|
||||
.PHONY: lint
|
||||
lint:
|
||||
go fmt ./...
|
||||
golint ./...
|
||||
@# Run again with magic to exit non-zero if golint outputs anything.
|
||||
@! (golint ./... | read dummy)
|
||||
go vet ./...
|
||||
171
vendor/github.com/opentracing/opentracing-go/README.md
generated
vendored
Normal file
171
vendor/github.com/opentracing/opentracing-go/README.md
generated
vendored
Normal file
@@ -0,0 +1,171 @@
|
||||
[](https://gitter.im/opentracing/public) [](https://travis-ci.org/opentracing/opentracing-go) [](http://godoc.org/github.com/opentracing/opentracing-go)
|
||||
[](https://sourcegraph.com/github.com/opentracing/opentracing-go?badge)
|
||||
|
||||
# OpenTracing API for Go
|
||||
|
||||
This package is a Go platform API for OpenTracing.
|
||||
|
||||
## Required Reading
|
||||
|
||||
In order to understand the Go platform API, one must first be familiar with the
|
||||
[OpenTracing project](https://opentracing.io) and
|
||||
[terminology](https://opentracing.io/specification/) more specifically.
|
||||
|
||||
## API overview for those adding instrumentation
|
||||
|
||||
Everyday consumers of this `opentracing` package really only need to worry
|
||||
about a couple of key abstractions: the `StartSpan` function, the `Span`
|
||||
interface, and binding a `Tracer` at `main()`-time. Here are code snippets
|
||||
demonstrating some important use cases.
|
||||
|
||||
#### Singleton initialization
|
||||
|
||||
The simplest starting point is `./default_tracer.go`. As early as possible, call
|
||||
|
||||
```go
|
||||
import "github.com/opentracing/opentracing-go"
|
||||
import ".../some_tracing_impl"
|
||||
|
||||
func main() {
|
||||
opentracing.SetGlobalTracer(
|
||||
// tracing impl specific:
|
||||
some_tracing_impl.New(...),
|
||||
)
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
#### Non-Singleton initialization
|
||||
|
||||
If you prefer direct control to singletons, manage ownership of the
|
||||
`opentracing.Tracer` implementation explicitly.
|
||||
|
||||
#### Creating a Span given an existing Go `context.Context`
|
||||
|
||||
If you use `context.Context` in your application, OpenTracing's Go library will
|
||||
happily rely on it for `Span` propagation. To start a new (blocking child)
|
||||
`Span`, you can use `StartSpanFromContext`.
|
||||
|
||||
```go
|
||||
func xyz(ctx context.Context, ...) {
|
||||
...
|
||||
span, ctx := opentracing.StartSpanFromContext(ctx, "operation_name")
|
||||
defer span.Finish()
|
||||
span.LogFields(
|
||||
log.String("event", "soft error"),
|
||||
log.String("type", "cache timeout"),
|
||||
log.Int("waited.millis", 1500))
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
#### Starting an empty trace by creating a "root span"
|
||||
|
||||
It's always possible to create a "root" `Span` with no parent or other causal
|
||||
reference.
|
||||
|
||||
```go
|
||||
func xyz() {
|
||||
...
|
||||
sp := opentracing.StartSpan("operation_name")
|
||||
defer sp.Finish()
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
#### Creating a (child) Span given an existing (parent) Span
|
||||
|
||||
```go
|
||||
func xyz(parentSpan opentracing.Span, ...) {
|
||||
...
|
||||
sp := opentracing.StartSpan(
|
||||
"operation_name",
|
||||
opentracing.ChildOf(parentSpan.Context()))
|
||||
defer sp.Finish()
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
#### Serializing to the wire
|
||||
|
||||
```go
|
||||
func makeSomeRequest(ctx context.Context) ... {
|
||||
if span := opentracing.SpanFromContext(ctx); span != nil {
|
||||
httpClient := &http.Client{}
|
||||
httpReq, _ := http.NewRequest("GET", "http://myservice/", nil)
|
||||
|
||||
// Transmit the span's TraceContext as HTTP headers on our
|
||||
// outbound request.
|
||||
opentracing.GlobalTracer().Inject(
|
||||
span.Context(),
|
||||
opentracing.HTTPHeaders,
|
||||
opentracing.HTTPHeadersCarrier(httpReq.Header))
|
||||
|
||||
resp, err := httpClient.Do(httpReq)
|
||||
...
|
||||
}
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
#### Deserializing from the wire
|
||||
|
||||
```go
|
||||
http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
|
||||
var serverSpan opentracing.Span
|
||||
appSpecificOperationName := ...
|
||||
wireContext, err := opentracing.GlobalTracer().Extract(
|
||||
opentracing.HTTPHeaders,
|
||||
opentracing.HTTPHeadersCarrier(req.Header))
|
||||
if err != nil {
|
||||
// Optionally record something about err here
|
||||
}
|
||||
|
||||
// Create the span referring to the RPC client if available.
|
||||
// If wireContext == nil, a root span will be created.
|
||||
serverSpan = opentracing.StartSpan(
|
||||
appSpecificOperationName,
|
||||
ext.RPCServerOption(wireContext))
|
||||
|
||||
defer serverSpan.Finish()
|
||||
|
||||
ctx := opentracing.ContextWithSpan(context.Background(), serverSpan)
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
#### Conditionally capture a field using `log.Noop`
|
||||
|
||||
In some situations, you may want to dynamically decide whether or not
|
||||
to log a field. For example, you may want to capture additional data,
|
||||
such as a customer ID, in non-production environments:
|
||||
|
||||
```go
|
||||
func Customer(order *Order) log.Field {
|
||||
if os.Getenv("ENVIRONMENT") == "dev" {
|
||||
return log.String("customer", order.Customer.ID)
|
||||
}
|
||||
return log.Noop()
|
||||
}
|
||||
```
|
||||
|
||||
#### Goroutine-safety
|
||||
|
||||
The entire public API is goroutine-safe and does not require external
|
||||
synchronization.
|
||||
|
||||
## API pointers for those implementing a tracing system
|
||||
|
||||
Tracing system implementors may be able to reuse or copy-paste-modify the `basictracer` package, found [here](https://github.com/opentracing/basictracer-go). In particular, see `basictracer.New(...)`.
|
||||
|
||||
## API compatibility
|
||||
|
||||
For the time being, "mild" backwards-incompatible changes may be made without changing the major version number. As OpenTracing and `opentracing-go` mature, backwards compatibility will become more of a priority.
|
||||
|
||||
## Tracer test suite
|
||||
|
||||
A test suite is available in the [harness](https://godoc.org/github.com/opentracing/opentracing-go/harness) package that can assist Tracer implementors to assert that their Tracer is working correctly.
|
||||
|
||||
## Licensing
|
||||
|
||||
[Apache 2.0 License](./LICENSE).
|
||||
42
vendor/github.com/opentracing/opentracing-go/globaltracer.go
generated
vendored
Normal file
42
vendor/github.com/opentracing/opentracing-go/globaltracer.go
generated
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
package opentracing
|
||||
|
||||
type registeredTracer struct {
|
||||
tracer Tracer
|
||||
isRegistered bool
|
||||
}
|
||||
|
||||
var (
|
||||
globalTracer = registeredTracer{NoopTracer{}, false}
|
||||
)
|
||||
|
||||
// SetGlobalTracer sets the [singleton] opentracing.Tracer returned by
|
||||
// GlobalTracer(). Those who use GlobalTracer (rather than directly manage an
|
||||
// opentracing.Tracer instance) should call SetGlobalTracer as early as
|
||||
// possible in main(), prior to calling the `StartSpan` global func below.
|
||||
// Prior to calling `SetGlobalTracer`, any Spans started via the `StartSpan`
|
||||
// (etc) globals are noops.
|
||||
func SetGlobalTracer(tracer Tracer) {
|
||||
globalTracer = registeredTracer{tracer, true}
|
||||
}
|
||||
|
||||
// GlobalTracer returns the global singleton `Tracer` implementation.
|
||||
// Before `SetGlobalTracer()` is called, the `GlobalTracer()` is a noop
|
||||
// implementation that drops all data handed to it.
|
||||
func GlobalTracer() Tracer {
|
||||
return globalTracer.tracer
|
||||
}
|
||||
|
||||
// StartSpan defers to `Tracer.StartSpan`. See `GlobalTracer()`.
|
||||
func StartSpan(operationName string, opts ...StartSpanOption) Span {
|
||||
return globalTracer.tracer.StartSpan(operationName, opts...)
|
||||
}
|
||||
|
||||
// InitGlobalTracer is deprecated. Please use SetGlobalTracer.
|
||||
func InitGlobalTracer(tracer Tracer) {
|
||||
SetGlobalTracer(tracer)
|
||||
}
|
||||
|
||||
// IsGlobalTracerRegistered returns a `bool` to indicate if a tracer has been globally registered
|
||||
func IsGlobalTracerRegistered() bool {
|
||||
return globalTracer.isRegistered
|
||||
}
|
||||
60
vendor/github.com/opentracing/opentracing-go/gocontext.go
generated
vendored
Normal file
60
vendor/github.com/opentracing/opentracing-go/gocontext.go
generated
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
package opentracing
|
||||
|
||||
import "context"
|
||||
|
||||
type contextKey struct{}
|
||||
|
||||
var activeSpanKey = contextKey{}
|
||||
|
||||
// ContextWithSpan returns a new `context.Context` that holds a reference to
|
||||
// `span`'s SpanContext.
|
||||
func ContextWithSpan(ctx context.Context, span Span) context.Context {
|
||||
return context.WithValue(ctx, activeSpanKey, span)
|
||||
}
|
||||
|
||||
// SpanFromContext returns the `Span` previously associated with `ctx`, or
|
||||
// `nil` if no such `Span` could be found.
|
||||
//
|
||||
// NOTE: context.Context != SpanContext: the former is Go's intra-process
|
||||
// context propagation mechanism, and the latter houses OpenTracing's per-Span
|
||||
// identity and baggage information.
|
||||
func SpanFromContext(ctx context.Context) Span {
|
||||
val := ctx.Value(activeSpanKey)
|
||||
if sp, ok := val.(Span); ok {
|
||||
return sp
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// StartSpanFromContext starts and returns a Span with `operationName`, using
|
||||
// any Span found within `ctx` as a ChildOfRef. If no such parent could be
|
||||
// found, StartSpanFromContext creates a root (parentless) Span.
|
||||
//
|
||||
// The second return value is a context.Context object built around the
|
||||
// returned Span.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// SomeFunction(ctx context.Context, ...) {
|
||||
// sp, ctx := opentracing.StartSpanFromContext(ctx, "SomeFunction")
|
||||
// defer sp.Finish()
|
||||
// ...
|
||||
// }
|
||||
func StartSpanFromContext(ctx context.Context, operationName string, opts ...StartSpanOption) (Span, context.Context) {
|
||||
return StartSpanFromContextWithTracer(ctx, GlobalTracer(), operationName, opts...)
|
||||
}
|
||||
|
||||
// StartSpanFromContextWithTracer starts and returns a span with `operationName`
|
||||
// using a span found within the context as a ChildOfRef. If that doesn't exist
|
||||
// it creates a root span. It also returns a context.Context object built
|
||||
// around the returned span.
|
||||
//
|
||||
// It's behavior is identical to StartSpanFromContext except that it takes an explicit
|
||||
// tracer as opposed to using the global tracer.
|
||||
func StartSpanFromContextWithTracer(ctx context.Context, tracer Tracer, operationName string, opts ...StartSpanOption) (Span, context.Context) {
|
||||
if parentSpan := SpanFromContext(ctx); parentSpan != nil {
|
||||
opts = append(opts, ChildOf(parentSpan.Context()))
|
||||
}
|
||||
span := tracer.StartSpan(operationName, opts...)
|
||||
return span, ContextWithSpan(ctx, span)
|
||||
}
|
||||
269
vendor/github.com/opentracing/opentracing-go/log/field.go
generated
vendored
Normal file
269
vendor/github.com/opentracing/opentracing-go/log/field.go
generated
vendored
Normal file
@@ -0,0 +1,269 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
)
|
||||
|
||||
type fieldType int
|
||||
|
||||
const (
|
||||
stringType fieldType = iota
|
||||
boolType
|
||||
intType
|
||||
int32Type
|
||||
uint32Type
|
||||
int64Type
|
||||
uint64Type
|
||||
float32Type
|
||||
float64Type
|
||||
errorType
|
||||
objectType
|
||||
lazyLoggerType
|
||||
noopType
|
||||
)
|
||||
|
||||
// Field instances are constructed via LogBool, LogString, and so on.
|
||||
// Tracing implementations may then handle them via the Field.Marshal
|
||||
// method.
|
||||
//
|
||||
// "heavily influenced by" (i.e., partially stolen from)
|
||||
// https://github.com/uber-go/zap
|
||||
type Field struct {
|
||||
key string
|
||||
fieldType fieldType
|
||||
numericVal int64
|
||||
stringVal string
|
||||
interfaceVal interface{}
|
||||
}
|
||||
|
||||
// String adds a string-valued key:value pair to a Span.LogFields() record
|
||||
func String(key, val string) Field {
|
||||
return Field{
|
||||
key: key,
|
||||
fieldType: stringType,
|
||||
stringVal: val,
|
||||
}
|
||||
}
|
||||
|
||||
// Bool adds a bool-valued key:value pair to a Span.LogFields() record
|
||||
func Bool(key string, val bool) Field {
|
||||
var numericVal int64
|
||||
if val {
|
||||
numericVal = 1
|
||||
}
|
||||
return Field{
|
||||
key: key,
|
||||
fieldType: boolType,
|
||||
numericVal: numericVal,
|
||||
}
|
||||
}
|
||||
|
||||
// Int adds an int-valued key:value pair to a Span.LogFields() record
|
||||
func Int(key string, val int) Field {
|
||||
return Field{
|
||||
key: key,
|
||||
fieldType: intType,
|
||||
numericVal: int64(val),
|
||||
}
|
||||
}
|
||||
|
||||
// Int32 adds an int32-valued key:value pair to a Span.LogFields() record
|
||||
func Int32(key string, val int32) Field {
|
||||
return Field{
|
||||
key: key,
|
||||
fieldType: int32Type,
|
||||
numericVal: int64(val),
|
||||
}
|
||||
}
|
||||
|
||||
// Int64 adds an int64-valued key:value pair to a Span.LogFields() record
|
||||
func Int64(key string, val int64) Field {
|
||||
return Field{
|
||||
key: key,
|
||||
fieldType: int64Type,
|
||||
numericVal: val,
|
||||
}
|
||||
}
|
||||
|
||||
// Uint32 adds a uint32-valued key:value pair to a Span.LogFields() record
|
||||
func Uint32(key string, val uint32) Field {
|
||||
return Field{
|
||||
key: key,
|
||||
fieldType: uint32Type,
|
||||
numericVal: int64(val),
|
||||
}
|
||||
}
|
||||
|
||||
// Uint64 adds a uint64-valued key:value pair to a Span.LogFields() record
|
||||
func Uint64(key string, val uint64) Field {
|
||||
return Field{
|
||||
key: key,
|
||||
fieldType: uint64Type,
|
||||
numericVal: int64(val),
|
||||
}
|
||||
}
|
||||
|
||||
// Float32 adds a float32-valued key:value pair to a Span.LogFields() record
|
||||
func Float32(key string, val float32) Field {
|
||||
return Field{
|
||||
key: key,
|
||||
fieldType: float32Type,
|
||||
numericVal: int64(math.Float32bits(val)),
|
||||
}
|
||||
}
|
||||
|
||||
// Float64 adds a float64-valued key:value pair to a Span.LogFields() record
|
||||
func Float64(key string, val float64) Field {
|
||||
return Field{
|
||||
key: key,
|
||||
fieldType: float64Type,
|
||||
numericVal: int64(math.Float64bits(val)),
|
||||
}
|
||||
}
|
||||
|
||||
// Error adds an error with the key "error" to a Span.LogFields() record
|
||||
func Error(err error) Field {
|
||||
return Field{
|
||||
key: "error",
|
||||
fieldType: errorType,
|
||||
interfaceVal: err,
|
||||
}
|
||||
}
|
||||
|
||||
// Object adds an object-valued key:value pair to a Span.LogFields() record
|
||||
func Object(key string, obj interface{}) Field {
|
||||
return Field{
|
||||
key: key,
|
||||
fieldType: objectType,
|
||||
interfaceVal: obj,
|
||||
}
|
||||
}
|
||||
|
||||
// LazyLogger allows for user-defined, late-bound logging of arbitrary data
|
||||
type LazyLogger func(fv Encoder)
|
||||
|
||||
// Lazy adds a LazyLogger to a Span.LogFields() record; the tracing
|
||||
// implementation will call the LazyLogger function at an indefinite time in
|
||||
// the future (after Lazy() returns).
|
||||
func Lazy(ll LazyLogger) Field {
|
||||
return Field{
|
||||
fieldType: lazyLoggerType,
|
||||
interfaceVal: ll,
|
||||
}
|
||||
}
|
||||
|
||||
// Noop creates a no-op log field that should be ignored by the tracer.
|
||||
// It can be used to capture optional fields, for example those that should
|
||||
// only be logged in non-production environment:
|
||||
//
|
||||
// func customerField(order *Order) log.Field {
|
||||
// if os.Getenv("ENVIRONMENT") == "dev" {
|
||||
// return log.String("customer", order.Customer.ID)
|
||||
// }
|
||||
// return log.Noop()
|
||||
// }
|
||||
//
|
||||
// span.LogFields(log.String("event", "purchase"), customerField(order))
|
||||
//
|
||||
func Noop() Field {
|
||||
return Field{
|
||||
fieldType: noopType,
|
||||
}
|
||||
}
|
||||
|
||||
// Encoder allows access to the contents of a Field (via a call to
|
||||
// Field.Marshal).
|
||||
//
|
||||
// Tracer implementations typically provide an implementation of Encoder;
|
||||
// OpenTracing callers typically do not need to concern themselves with it.
|
||||
type Encoder interface {
|
||||
EmitString(key, value string)
|
||||
EmitBool(key string, value bool)
|
||||
EmitInt(key string, value int)
|
||||
EmitInt32(key string, value int32)
|
||||
EmitInt64(key string, value int64)
|
||||
EmitUint32(key string, value uint32)
|
||||
EmitUint64(key string, value uint64)
|
||||
EmitFloat32(key string, value float32)
|
||||
EmitFloat64(key string, value float64)
|
||||
EmitObject(key string, value interface{})
|
||||
EmitLazyLogger(value LazyLogger)
|
||||
}
|
||||
|
||||
// Marshal passes a Field instance through to the appropriate
|
||||
// field-type-specific method of an Encoder.
|
||||
func (lf Field) Marshal(visitor Encoder) {
|
||||
switch lf.fieldType {
|
||||
case stringType:
|
||||
visitor.EmitString(lf.key, lf.stringVal)
|
||||
case boolType:
|
||||
visitor.EmitBool(lf.key, lf.numericVal != 0)
|
||||
case intType:
|
||||
visitor.EmitInt(lf.key, int(lf.numericVal))
|
||||
case int32Type:
|
||||
visitor.EmitInt32(lf.key, int32(lf.numericVal))
|
||||
case int64Type:
|
||||
visitor.EmitInt64(lf.key, int64(lf.numericVal))
|
||||
case uint32Type:
|
||||
visitor.EmitUint32(lf.key, uint32(lf.numericVal))
|
||||
case uint64Type:
|
||||
visitor.EmitUint64(lf.key, uint64(lf.numericVal))
|
||||
case float32Type:
|
||||
visitor.EmitFloat32(lf.key, math.Float32frombits(uint32(lf.numericVal)))
|
||||
case float64Type:
|
||||
visitor.EmitFloat64(lf.key, math.Float64frombits(uint64(lf.numericVal)))
|
||||
case errorType:
|
||||
if err, ok := lf.interfaceVal.(error); ok {
|
||||
visitor.EmitString(lf.key, err.Error())
|
||||
} else {
|
||||
visitor.EmitString(lf.key, "<nil>")
|
||||
}
|
||||
case objectType:
|
||||
visitor.EmitObject(lf.key, lf.interfaceVal)
|
||||
case lazyLoggerType:
|
||||
visitor.EmitLazyLogger(lf.interfaceVal.(LazyLogger))
|
||||
case noopType:
|
||||
// intentionally left blank
|
||||
}
|
||||
}
|
||||
|
||||
// Key returns the field's key.
|
||||
func (lf Field) Key() string {
|
||||
return lf.key
|
||||
}
|
||||
|
||||
// Value returns the field's value as interface{}.
|
||||
func (lf Field) Value() interface{} {
|
||||
switch lf.fieldType {
|
||||
case stringType:
|
||||
return lf.stringVal
|
||||
case boolType:
|
||||
return lf.numericVal != 0
|
||||
case intType:
|
||||
return int(lf.numericVal)
|
||||
case int32Type:
|
||||
return int32(lf.numericVal)
|
||||
case int64Type:
|
||||
return int64(lf.numericVal)
|
||||
case uint32Type:
|
||||
return uint32(lf.numericVal)
|
||||
case uint64Type:
|
||||
return uint64(lf.numericVal)
|
||||
case float32Type:
|
||||
return math.Float32frombits(uint32(lf.numericVal))
|
||||
case float64Type:
|
||||
return math.Float64frombits(uint64(lf.numericVal))
|
||||
case errorType, objectType, lazyLoggerType:
|
||||
return lf.interfaceVal
|
||||
case noopType:
|
||||
return nil
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// String returns a string representation of the key and value.
|
||||
func (lf Field) String() string {
|
||||
return fmt.Sprint(lf.key, ":", lf.Value())
|
||||
}
|
||||
54
vendor/github.com/opentracing/opentracing-go/log/util.go
generated
vendored
Normal file
54
vendor/github.com/opentracing/opentracing-go/log/util.go
generated
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
package log
|
||||
|
||||
import "fmt"
|
||||
|
||||
// InterleavedKVToFields converts keyValues a la Span.LogKV() to a Field slice
|
||||
// a la Span.LogFields().
|
||||
func InterleavedKVToFields(keyValues ...interface{}) ([]Field, error) {
|
||||
if len(keyValues)%2 != 0 {
|
||||
return nil, fmt.Errorf("non-even keyValues len: %d", len(keyValues))
|
||||
}
|
||||
fields := make([]Field, len(keyValues)/2)
|
||||
for i := 0; i*2 < len(keyValues); i++ {
|
||||
key, ok := keyValues[i*2].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf(
|
||||
"non-string key (pair #%d): %T",
|
||||
i, keyValues[i*2])
|
||||
}
|
||||
switch typedVal := keyValues[i*2+1].(type) {
|
||||
case bool:
|
||||
fields[i] = Bool(key, typedVal)
|
||||
case string:
|
||||
fields[i] = String(key, typedVal)
|
||||
case int:
|
||||
fields[i] = Int(key, typedVal)
|
||||
case int8:
|
||||
fields[i] = Int32(key, int32(typedVal))
|
||||
case int16:
|
||||
fields[i] = Int32(key, int32(typedVal))
|
||||
case int32:
|
||||
fields[i] = Int32(key, typedVal)
|
||||
case int64:
|
||||
fields[i] = Int64(key, typedVal)
|
||||
case uint:
|
||||
fields[i] = Uint64(key, uint64(typedVal))
|
||||
case uint64:
|
||||
fields[i] = Uint64(key, typedVal)
|
||||
case uint8:
|
||||
fields[i] = Uint32(key, uint32(typedVal))
|
||||
case uint16:
|
||||
fields[i] = Uint32(key, uint32(typedVal))
|
||||
case uint32:
|
||||
fields[i] = Uint32(key, typedVal)
|
||||
case float32:
|
||||
fields[i] = Float32(key, typedVal)
|
||||
case float64:
|
||||
fields[i] = Float64(key, typedVal)
|
||||
default:
|
||||
// When in doubt, coerce to a string
|
||||
fields[i] = String(key, fmt.Sprint(typedVal))
|
||||
}
|
||||
}
|
||||
return fields, nil
|
||||
}
|
||||
64
vendor/github.com/opentracing/opentracing-go/noop.go
generated
vendored
Normal file
64
vendor/github.com/opentracing/opentracing-go/noop.go
generated
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
package opentracing
|
||||
|
||||
import "github.com/opentracing/opentracing-go/log"
|
||||
|
||||
// A NoopTracer is a trivial, minimum overhead implementation of Tracer
|
||||
// for which all operations are no-ops.
|
||||
//
|
||||
// The primary use of this implementation is in libraries, such as RPC
|
||||
// frameworks, that make tracing an optional feature controlled by the
|
||||
// end user. A no-op implementation allows said libraries to use it
|
||||
// as the default Tracer and to write instrumentation that does
|
||||
// not need to keep checking if the tracer instance is nil.
|
||||
//
|
||||
// For the same reason, the NoopTracer is the default "global" tracer
|
||||
// (see GlobalTracer and SetGlobalTracer functions).
|
||||
//
|
||||
// WARNING: NoopTracer does not support baggage propagation.
|
||||
type NoopTracer struct{}
|
||||
|
||||
type noopSpan struct{}
|
||||
type noopSpanContext struct{}
|
||||
|
||||
var (
|
||||
defaultNoopSpanContext = noopSpanContext{}
|
||||
defaultNoopSpan = noopSpan{}
|
||||
defaultNoopTracer = NoopTracer{}
|
||||
)
|
||||
|
||||
const (
|
||||
emptyString = ""
|
||||
)
|
||||
|
||||
// noopSpanContext:
|
||||
func (n noopSpanContext) ForeachBaggageItem(handler func(k, v string) bool) {}
|
||||
|
||||
// noopSpan:
|
||||
func (n noopSpan) Context() SpanContext { return defaultNoopSpanContext }
|
||||
func (n noopSpan) SetBaggageItem(key, val string) Span { return defaultNoopSpan }
|
||||
func (n noopSpan) BaggageItem(key string) string { return emptyString }
|
||||
func (n noopSpan) SetTag(key string, value interface{}) Span { return n }
|
||||
func (n noopSpan) LogFields(fields ...log.Field) {}
|
||||
func (n noopSpan) LogKV(keyVals ...interface{}) {}
|
||||
func (n noopSpan) Finish() {}
|
||||
func (n noopSpan) FinishWithOptions(opts FinishOptions) {}
|
||||
func (n noopSpan) SetOperationName(operationName string) Span { return n }
|
||||
func (n noopSpan) Tracer() Tracer { return defaultNoopTracer }
|
||||
func (n noopSpan) LogEvent(event string) {}
|
||||
func (n noopSpan) LogEventWithPayload(event string, payload interface{}) {}
|
||||
func (n noopSpan) Log(data LogData) {}
|
||||
|
||||
// StartSpan belongs to the Tracer interface.
|
||||
func (n NoopTracer) StartSpan(operationName string, opts ...StartSpanOption) Span {
|
||||
return defaultNoopSpan
|
||||
}
|
||||
|
||||
// Inject belongs to the Tracer interface.
|
||||
func (n NoopTracer) Inject(sp SpanContext, format interface{}, carrier interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Extract belongs to the Tracer interface.
|
||||
func (n NoopTracer) Extract(format interface{}, carrier interface{}) (SpanContext, error) {
|
||||
return nil, ErrSpanContextNotFound
|
||||
}
|
||||
176
vendor/github.com/opentracing/opentracing-go/propagation.go
generated
vendored
Normal file
176
vendor/github.com/opentracing/opentracing-go/propagation.go
generated
vendored
Normal file
@@ -0,0 +1,176 @@
|
||||
package opentracing
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// CORE PROPAGATION INTERFACES:
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
var (
|
||||
// ErrUnsupportedFormat occurs when the `format` passed to Tracer.Inject() or
|
||||
// Tracer.Extract() is not recognized by the Tracer implementation.
|
||||
ErrUnsupportedFormat = errors.New("opentracing: Unknown or unsupported Inject/Extract format")
|
||||
|
||||
// ErrSpanContextNotFound occurs when the `carrier` passed to
|
||||
// Tracer.Extract() is valid and uncorrupted but has insufficient
|
||||
// information to extract a SpanContext.
|
||||
ErrSpanContextNotFound = errors.New("opentracing: SpanContext not found in Extract carrier")
|
||||
|
||||
// ErrInvalidSpanContext errors occur when Tracer.Inject() is asked to
|
||||
// operate on a SpanContext which it is not prepared to handle (for
|
||||
// example, since it was created by a different tracer implementation).
|
||||
ErrInvalidSpanContext = errors.New("opentracing: SpanContext type incompatible with tracer")
|
||||
|
||||
// ErrInvalidCarrier errors occur when Tracer.Inject() or Tracer.Extract()
|
||||
// implementations expect a different type of `carrier` than they are
|
||||
// given.
|
||||
ErrInvalidCarrier = errors.New("opentracing: Invalid Inject/Extract carrier")
|
||||
|
||||
// ErrSpanContextCorrupted occurs when the `carrier` passed to
|
||||
// Tracer.Extract() is of the expected type but is corrupted.
|
||||
ErrSpanContextCorrupted = errors.New("opentracing: SpanContext data corrupted in Extract carrier")
|
||||
)
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// BUILTIN PROPAGATION FORMATS:
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// BuiltinFormat is used to demarcate the values within package `opentracing`
|
||||
// that are intended for use with the Tracer.Inject() and Tracer.Extract()
|
||||
// methods.
|
||||
type BuiltinFormat byte
|
||||
|
||||
const (
|
||||
// Binary represents SpanContexts as opaque binary data.
|
||||
//
|
||||
// For Tracer.Inject(): the carrier must be an `io.Writer`.
|
||||
//
|
||||
// For Tracer.Extract(): the carrier must be an `io.Reader`.
|
||||
Binary BuiltinFormat = iota
|
||||
|
||||
// TextMap represents SpanContexts as key:value string pairs.
|
||||
//
|
||||
// Unlike HTTPHeaders, the TextMap format does not restrict the key or
|
||||
// value character sets in any way.
|
||||
//
|
||||
// For Tracer.Inject(): the carrier must be a `TextMapWriter`.
|
||||
//
|
||||
// For Tracer.Extract(): the carrier must be a `TextMapReader`.
|
||||
TextMap
|
||||
|
||||
// HTTPHeaders represents SpanContexts as HTTP header string pairs.
|
||||
//
|
||||
// Unlike TextMap, the HTTPHeaders format requires that the keys and values
|
||||
// be valid as HTTP headers as-is (i.e., character casing may be unstable
|
||||
// and special characters are disallowed in keys, values should be
|
||||
// URL-escaped, etc).
|
||||
//
|
||||
// For Tracer.Inject(): the carrier must be a `TextMapWriter`.
|
||||
//
|
||||
// For Tracer.Extract(): the carrier must be a `TextMapReader`.
|
||||
//
|
||||
// See HTTPHeadersCarrier for an implementation of both TextMapWriter
|
||||
// and TextMapReader that defers to an http.Header instance for storage.
|
||||
// For example, Inject():
|
||||
//
|
||||
// carrier := opentracing.HTTPHeadersCarrier(httpReq.Header)
|
||||
// err := span.Tracer().Inject(
|
||||
// span.Context(), opentracing.HTTPHeaders, carrier)
|
||||
//
|
||||
// Or Extract():
|
||||
//
|
||||
// carrier := opentracing.HTTPHeadersCarrier(httpReq.Header)
|
||||
// clientContext, err := tracer.Extract(
|
||||
// opentracing.HTTPHeaders, carrier)
|
||||
//
|
||||
HTTPHeaders
|
||||
)
|
||||
|
||||
// TextMapWriter is the Inject() carrier for the TextMap builtin format. With
|
||||
// it, the caller can encode a SpanContext for propagation as entries in a map
|
||||
// of unicode strings.
|
||||
type TextMapWriter interface {
|
||||
// Set a key:value pair to the carrier. Multiple calls to Set() for the
|
||||
// same key leads to undefined behavior.
|
||||
//
|
||||
// NOTE: The backing store for the TextMapWriter may contain data unrelated
|
||||
// to SpanContext. As such, Inject() and Extract() implementations that
|
||||
// call the TextMapWriter and TextMapReader interfaces must agree on a
|
||||
// prefix or other convention to distinguish their own key:value pairs.
|
||||
Set(key, val string)
|
||||
}
|
||||
|
||||
// TextMapReader is the Extract() carrier for the TextMap builtin format. With it,
|
||||
// the caller can decode a propagated SpanContext as entries in a map of
|
||||
// unicode strings.
|
||||
type TextMapReader interface {
|
||||
// ForeachKey returns TextMap contents via repeated calls to the `handler`
|
||||
// function. If any call to `handler` returns a non-nil error, ForeachKey
|
||||
// terminates and returns that error.
|
||||
//
|
||||
// NOTE: The backing store for the TextMapReader may contain data unrelated
|
||||
// to SpanContext. As such, Inject() and Extract() implementations that
|
||||
// call the TextMapWriter and TextMapReader interfaces must agree on a
|
||||
// prefix or other convention to distinguish their own key:value pairs.
|
||||
//
|
||||
// The "foreach" callback pattern reduces unnecessary copying in some cases
|
||||
// and also allows implementations to hold locks while the map is read.
|
||||
ForeachKey(handler func(key, val string) error) error
|
||||
}
|
||||
|
||||
// TextMapCarrier allows the use of regular map[string]string
|
||||
// as both TextMapWriter and TextMapReader.
|
||||
type TextMapCarrier map[string]string
|
||||
|
||||
// ForeachKey conforms to the TextMapReader interface.
|
||||
func (c TextMapCarrier) ForeachKey(handler func(key, val string) error) error {
|
||||
for k, v := range c {
|
||||
if err := handler(k, v); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Set implements Set() of opentracing.TextMapWriter
|
||||
func (c TextMapCarrier) Set(key, val string) {
|
||||
c[key] = val
|
||||
}
|
||||
|
||||
// HTTPHeadersCarrier satisfies both TextMapWriter and TextMapReader.
|
||||
//
|
||||
// Example usage for server side:
|
||||
//
|
||||
// carrier := opentracing.HTTPHeadersCarrier(httpReq.Header)
|
||||
// clientContext, err := tracer.Extract(opentracing.HTTPHeaders, carrier)
|
||||
//
|
||||
// Example usage for client side:
|
||||
//
|
||||
// carrier := opentracing.HTTPHeadersCarrier(httpReq.Header)
|
||||
// err := tracer.Inject(
|
||||
// span.Context(),
|
||||
// opentracing.HTTPHeaders,
|
||||
// carrier)
|
||||
//
|
||||
type HTTPHeadersCarrier http.Header
|
||||
|
||||
// Set conforms to the TextMapWriter interface.
|
||||
func (c HTTPHeadersCarrier) Set(key, val string) {
|
||||
h := http.Header(c)
|
||||
h.Set(key, val)
|
||||
}
|
||||
|
||||
// ForeachKey conforms to the TextMapReader interface.
|
||||
func (c HTTPHeadersCarrier) ForeachKey(handler func(key, val string) error) error {
|
||||
for k, vals := range c {
|
||||
for _, v := range vals {
|
||||
if err := handler(k, v); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
189
vendor/github.com/opentracing/opentracing-go/span.go
generated
vendored
Normal file
189
vendor/github.com/opentracing/opentracing-go/span.go
generated
vendored
Normal file
@@ -0,0 +1,189 @@
|
||||
package opentracing
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/opentracing/opentracing-go/log"
|
||||
)
|
||||
|
||||
// SpanContext represents Span state that must propagate to descendant Spans and across process
|
||||
// boundaries (e.g., a <trace_id, span_id, sampled> tuple).
|
||||
type SpanContext interface {
|
||||
// ForeachBaggageItem grants access to all baggage items stored in the
|
||||
// SpanContext.
|
||||
// The handler function will be called for each baggage key/value pair.
|
||||
// The ordering of items is not guaranteed.
|
||||
//
|
||||
// The bool return value indicates if the handler wants to continue iterating
|
||||
// through the rest of the baggage items; for example if the handler is trying to
|
||||
// find some baggage item by pattern matching the name, it can return false
|
||||
// as soon as the item is found to stop further iterations.
|
||||
ForeachBaggageItem(handler func(k, v string) bool)
|
||||
}
|
||||
|
||||
// Span represents an active, un-finished span in the OpenTracing system.
|
||||
//
|
||||
// Spans are created by the Tracer interface.
|
||||
type Span interface {
|
||||
// Sets the end timestamp and finalizes Span state.
|
||||
//
|
||||
// With the exception of calls to Context() (which are always allowed),
|
||||
// Finish() must be the last call made to any span instance, and to do
|
||||
// otherwise leads to undefined behavior.
|
||||
Finish()
|
||||
// FinishWithOptions is like Finish() but with explicit control over
|
||||
// timestamps and log data.
|
||||
FinishWithOptions(opts FinishOptions)
|
||||
|
||||
// Context() yields the SpanContext for this Span. Note that the return
|
||||
// value of Context() is still valid after a call to Span.Finish(), as is
|
||||
// a call to Span.Context() after a call to Span.Finish().
|
||||
Context() SpanContext
|
||||
|
||||
// Sets or changes the operation name.
|
||||
//
|
||||
// Returns a reference to this Span for chaining.
|
||||
SetOperationName(operationName string) Span
|
||||
|
||||
// Adds a tag to the span.
|
||||
//
|
||||
// If there is a pre-existing tag set for `key`, it is overwritten.
|
||||
//
|
||||
// Tag values can be numeric types, strings, or bools. The behavior of
|
||||
// other tag value types is undefined at the OpenTracing level. If a
|
||||
// tracing system does not know how to handle a particular value type, it
|
||||
// may ignore the tag, but shall not panic.
|
||||
//
|
||||
// Returns a reference to this Span for chaining.
|
||||
SetTag(key string, value interface{}) Span
|
||||
|
||||
// LogFields is an efficient and type-checked way to record key:value
|
||||
// logging data about a Span, though the programming interface is a little
|
||||
// more verbose than LogKV(). Here's an example:
|
||||
//
|
||||
// span.LogFields(
|
||||
// log.String("event", "soft error"),
|
||||
// log.String("type", "cache timeout"),
|
||||
// log.Int("waited.millis", 1500))
|
||||
//
|
||||
// Also see Span.FinishWithOptions() and FinishOptions.BulkLogData.
|
||||
LogFields(fields ...log.Field)
|
||||
|
||||
// LogKV is a concise, readable way to record key:value logging data about
|
||||
// a Span, though unfortunately this also makes it less efficient and less
|
||||
// type-safe than LogFields(). Here's an example:
|
||||
//
|
||||
// span.LogKV(
|
||||
// "event", "soft error",
|
||||
// "type", "cache timeout",
|
||||
// "waited.millis", 1500)
|
||||
//
|
||||
// For LogKV (as opposed to LogFields()), the parameters must appear as
|
||||
// key-value pairs, like
|
||||
//
|
||||
// span.LogKV(key1, val1, key2, val2, key3, val3, ...)
|
||||
//
|
||||
// The keys must all be strings. The values may be strings, numeric types,
|
||||
// bools, Go error instances, or arbitrary structs.
|
||||
//
|
||||
// (Note to implementors: consider the log.InterleavedKVToFields() helper)
|
||||
LogKV(alternatingKeyValues ...interface{})
|
||||
|
||||
// SetBaggageItem sets a key:value pair on this Span and its SpanContext
|
||||
// that also propagates to descendants of this Span.
|
||||
//
|
||||
// SetBaggageItem() enables powerful functionality given a full-stack
|
||||
// opentracing integration (e.g., arbitrary application data from a mobile
|
||||
// app can make it, transparently, all the way into the depths of a storage
|
||||
// system), and with it some powerful costs: use this feature with care.
|
||||
//
|
||||
// IMPORTANT NOTE #1: SetBaggageItem() will only propagate baggage items to
|
||||
// *future* causal descendants of the associated Span.
|
||||
//
|
||||
// IMPORTANT NOTE #2: Use this thoughtfully and with care. Every key and
|
||||
// value is copied into every local *and remote* child of the associated
|
||||
// Span, and that can add up to a lot of network and cpu overhead.
|
||||
//
|
||||
// Returns a reference to this Span for chaining.
|
||||
SetBaggageItem(restrictedKey, value string) Span
|
||||
|
||||
// Gets the value for a baggage item given its key. Returns the empty string
|
||||
// if the value isn't found in this Span.
|
||||
BaggageItem(restrictedKey string) string
|
||||
|
||||
// Provides access to the Tracer that created this Span.
|
||||
Tracer() Tracer
|
||||
|
||||
// Deprecated: use LogFields or LogKV
|
||||
LogEvent(event string)
|
||||
// Deprecated: use LogFields or LogKV
|
||||
LogEventWithPayload(event string, payload interface{})
|
||||
// Deprecated: use LogFields or LogKV
|
||||
Log(data LogData)
|
||||
}
|
||||
|
||||
// LogRecord is data associated with a single Span log. Every LogRecord
|
||||
// instance must specify at least one Field.
|
||||
type LogRecord struct {
|
||||
Timestamp time.Time
|
||||
Fields []log.Field
|
||||
}
|
||||
|
||||
// FinishOptions allows Span.FinishWithOptions callers to override the finish
|
||||
// timestamp and provide log data via a bulk interface.
|
||||
type FinishOptions struct {
|
||||
// FinishTime overrides the Span's finish time, or implicitly becomes
|
||||
// time.Now() if FinishTime.IsZero().
|
||||
//
|
||||
// FinishTime must resolve to a timestamp that's >= the Span's StartTime
|
||||
// (per StartSpanOptions).
|
||||
FinishTime time.Time
|
||||
|
||||
// LogRecords allows the caller to specify the contents of many LogFields()
|
||||
// calls with a single slice. May be nil.
|
||||
//
|
||||
// None of the LogRecord.Timestamp values may be .IsZero() (i.e., they must
|
||||
// be set explicitly). Also, they must be >= the Span's start timestamp and
|
||||
// <= the FinishTime (or time.Now() if FinishTime.IsZero()). Otherwise the
|
||||
// behavior of FinishWithOptions() is undefined.
|
||||
//
|
||||
// If specified, the caller hands off ownership of LogRecords at
|
||||
// FinishWithOptions() invocation time.
|
||||
//
|
||||
// If specified, the (deprecated) BulkLogData must be nil or empty.
|
||||
LogRecords []LogRecord
|
||||
|
||||
// BulkLogData is DEPRECATED.
|
||||
BulkLogData []LogData
|
||||
}
|
||||
|
||||
// LogData is DEPRECATED
|
||||
type LogData struct {
|
||||
Timestamp time.Time
|
||||
Event string
|
||||
Payload interface{}
|
||||
}
|
||||
|
||||
// ToLogRecord converts a deprecated LogData to a non-deprecated LogRecord
|
||||
func (ld *LogData) ToLogRecord() LogRecord {
|
||||
var literalTimestamp time.Time
|
||||
if ld.Timestamp.IsZero() {
|
||||
literalTimestamp = time.Now()
|
||||
} else {
|
||||
literalTimestamp = ld.Timestamp
|
||||
}
|
||||
rval := LogRecord{
|
||||
Timestamp: literalTimestamp,
|
||||
}
|
||||
if ld.Payload == nil {
|
||||
rval.Fields = []log.Field{
|
||||
log.String("event", ld.Event),
|
||||
}
|
||||
} else {
|
||||
rval.Fields = []log.Field{
|
||||
log.String("event", ld.Event),
|
||||
log.Object("payload", ld.Payload),
|
||||
}
|
||||
}
|
||||
return rval
|
||||
}
|
||||
304
vendor/github.com/opentracing/opentracing-go/tracer.go
generated
vendored
Normal file
304
vendor/github.com/opentracing/opentracing-go/tracer.go
generated
vendored
Normal file
@@ -0,0 +1,304 @@
|
||||
package opentracing
|
||||
|
||||
import "time"
|
||||
|
||||
// Tracer is a simple, thin interface for Span creation and SpanContext
|
||||
// propagation.
|
||||
type Tracer interface {
|
||||
|
||||
// Create, start, and return a new Span with the given `operationName` and
|
||||
// incorporate the given StartSpanOption `opts`. (Note that `opts` borrows
|
||||
// from the "functional options" pattern, per
|
||||
// http://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis)
|
||||
//
|
||||
// A Span with no SpanReference options (e.g., opentracing.ChildOf() or
|
||||
// opentracing.FollowsFrom()) becomes the root of its own trace.
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// var tracer opentracing.Tracer = ...
|
||||
//
|
||||
// // The root-span case:
|
||||
// sp := tracer.StartSpan("GetFeed")
|
||||
//
|
||||
// // The vanilla child span case:
|
||||
// sp := tracer.StartSpan(
|
||||
// "GetFeed",
|
||||
// opentracing.ChildOf(parentSpan.Context()))
|
||||
//
|
||||
// // All the bells and whistles:
|
||||
// sp := tracer.StartSpan(
|
||||
// "GetFeed",
|
||||
// opentracing.ChildOf(parentSpan.Context()),
|
||||
// opentracing.Tag{"user_agent", loggedReq.UserAgent},
|
||||
// opentracing.StartTime(loggedReq.Timestamp),
|
||||
// )
|
||||
//
|
||||
StartSpan(operationName string, opts ...StartSpanOption) Span
|
||||
|
||||
// Inject() takes the `sm` SpanContext instance and injects it for
|
||||
// propagation within `carrier`. The actual type of `carrier` depends on
|
||||
// the value of `format`.
|
||||
//
|
||||
// OpenTracing defines a common set of `format` values (see BuiltinFormat),
|
||||
// and each has an expected carrier type.
|
||||
//
|
||||
// Other packages may declare their own `format` values, much like the keys
|
||||
// used by `context.Context` (see https://godoc.org/context#WithValue).
|
||||
//
|
||||
// Example usage (sans error handling):
|
||||
//
|
||||
// carrier := opentracing.HTTPHeadersCarrier(httpReq.Header)
|
||||
// err := tracer.Inject(
|
||||
// span.Context(),
|
||||
// opentracing.HTTPHeaders,
|
||||
// carrier)
|
||||
//
|
||||
// NOTE: All opentracing.Tracer implementations MUST support all
|
||||
// BuiltinFormats.
|
||||
//
|
||||
// Implementations may return opentracing.ErrUnsupportedFormat if `format`
|
||||
// is not supported by (or not known by) the implementation.
|
||||
//
|
||||
// Implementations may return opentracing.ErrInvalidCarrier or any other
|
||||
// implementation-specific error if the format is supported but injection
|
||||
// fails anyway.
|
||||
//
|
||||
// See Tracer.Extract().
|
||||
Inject(sm SpanContext, format interface{}, carrier interface{}) error
|
||||
|
||||
// Extract() returns a SpanContext instance given `format` and `carrier`.
|
||||
//
|
||||
// OpenTracing defines a common set of `format` values (see BuiltinFormat),
|
||||
// and each has an expected carrier type.
|
||||
//
|
||||
// Other packages may declare their own `format` values, much like the keys
|
||||
// used by `context.Context` (see
|
||||
// https://godoc.org/golang.org/x/net/context#WithValue).
|
||||
//
|
||||
// Example usage (with StartSpan):
|
||||
//
|
||||
//
|
||||
// carrier := opentracing.HTTPHeadersCarrier(httpReq.Header)
|
||||
// clientContext, err := tracer.Extract(opentracing.HTTPHeaders, carrier)
|
||||
//
|
||||
// // ... assuming the ultimate goal here is to resume the trace with a
|
||||
// // server-side Span:
|
||||
// var serverSpan opentracing.Span
|
||||
// if err == nil {
|
||||
// span = tracer.StartSpan(
|
||||
// rpcMethodName, ext.RPCServerOption(clientContext))
|
||||
// } else {
|
||||
// span = tracer.StartSpan(rpcMethodName)
|
||||
// }
|
||||
//
|
||||
//
|
||||
// NOTE: All opentracing.Tracer implementations MUST support all
|
||||
// BuiltinFormats.
|
||||
//
|
||||
// Return values:
|
||||
// - A successful Extract returns a SpanContext instance and a nil error
|
||||
// - If there was simply no SpanContext to extract in `carrier`, Extract()
|
||||
// returns (nil, opentracing.ErrSpanContextNotFound)
|
||||
// - If `format` is unsupported or unrecognized, Extract() returns (nil,
|
||||
// opentracing.ErrUnsupportedFormat)
|
||||
// - If there are more fundamental problems with the `carrier` object,
|
||||
// Extract() may return opentracing.ErrInvalidCarrier,
|
||||
// opentracing.ErrSpanContextCorrupted, or implementation-specific
|
||||
// errors.
|
||||
//
|
||||
// See Tracer.Inject().
|
||||
Extract(format interface{}, carrier interface{}) (SpanContext, error)
|
||||
}
|
||||
|
||||
// StartSpanOptions allows Tracer.StartSpan() callers and implementors a
|
||||
// mechanism to override the start timestamp, specify Span References, and make
|
||||
// a single Tag or multiple Tags available at Span start time.
|
||||
//
|
||||
// StartSpan() callers should look at the StartSpanOption interface and
|
||||
// implementations available in this package.
|
||||
//
|
||||
// Tracer implementations can convert a slice of `StartSpanOption` instances
|
||||
// into a `StartSpanOptions` struct like so:
|
||||
//
|
||||
// func StartSpan(opName string, opts ...opentracing.StartSpanOption) {
|
||||
// sso := opentracing.StartSpanOptions{}
|
||||
// for _, o := range opts {
|
||||
// o.Apply(&sso)
|
||||
// }
|
||||
// ...
|
||||
// }
|
||||
//
|
||||
type StartSpanOptions struct {
|
||||
// Zero or more causal references to other Spans (via their SpanContext).
|
||||
// If empty, start a "root" Span (i.e., start a new trace).
|
||||
References []SpanReference
|
||||
|
||||
// StartTime overrides the Span's start time, or implicitly becomes
|
||||
// time.Now() if StartTime.IsZero().
|
||||
StartTime time.Time
|
||||
|
||||
// Tags may have zero or more entries; the restrictions on map values are
|
||||
// identical to those for Span.SetTag(). May be nil.
|
||||
//
|
||||
// If specified, the caller hands off ownership of Tags at
|
||||
// StartSpan() invocation time.
|
||||
Tags map[string]interface{}
|
||||
}
|
||||
|
||||
// StartSpanOption instances (zero or more) may be passed to Tracer.StartSpan.
|
||||
//
|
||||
// StartSpanOption borrows from the "functional options" pattern, per
|
||||
// http://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis
|
||||
type StartSpanOption interface {
|
||||
Apply(*StartSpanOptions)
|
||||
}
|
||||
|
||||
// SpanReferenceType is an enum type describing different categories of
|
||||
// relationships between two Spans. If Span-2 refers to Span-1, the
|
||||
// SpanReferenceType describes Span-1 from Span-2's perspective. For example,
|
||||
// ChildOfRef means that Span-1 created Span-2.
|
||||
//
|
||||
// NOTE: Span-1 and Span-2 do *not* necessarily depend on each other for
|
||||
// completion; e.g., Span-2 may be part of a background job enqueued by Span-1,
|
||||
// or Span-2 may be sitting in a distributed queue behind Span-1.
|
||||
type SpanReferenceType int
|
||||
|
||||
const (
|
||||
// ChildOfRef refers to a parent Span that caused *and* somehow depends
|
||||
// upon the new child Span. Often (but not always), the parent Span cannot
|
||||
// finish until the child Span does.
|
||||
//
|
||||
// An timing diagram for a ChildOfRef that's blocked on the new Span:
|
||||
//
|
||||
// [-Parent Span---------]
|
||||
// [-Child Span----]
|
||||
//
|
||||
// See http://opentracing.io/spec/
|
||||
//
|
||||
// See opentracing.ChildOf()
|
||||
ChildOfRef SpanReferenceType = iota
|
||||
|
||||
// FollowsFromRef refers to a parent Span that does not depend in any way
|
||||
// on the result of the new child Span. For instance, one might use
|
||||
// FollowsFromRefs to describe pipeline stages separated by queues,
|
||||
// or a fire-and-forget cache insert at the tail end of a web request.
|
||||
//
|
||||
// A FollowsFromRef Span is part of the same logical trace as the new Span:
|
||||
// i.e., the new Span is somehow caused by the work of its FollowsFromRef.
|
||||
//
|
||||
// All of the following could be valid timing diagrams for children that
|
||||
// "FollowFrom" a parent.
|
||||
//
|
||||
// [-Parent Span-] [-Child Span-]
|
||||
//
|
||||
//
|
||||
// [-Parent Span--]
|
||||
// [-Child Span-]
|
||||
//
|
||||
//
|
||||
// [-Parent Span-]
|
||||
// [-Child Span-]
|
||||
//
|
||||
// See http://opentracing.io/spec/
|
||||
//
|
||||
// See opentracing.FollowsFrom()
|
||||
FollowsFromRef
|
||||
)
|
||||
|
||||
// SpanReference is a StartSpanOption that pairs a SpanReferenceType and a
|
||||
// referenced SpanContext. See the SpanReferenceType documentation for
|
||||
// supported relationships. If SpanReference is created with
|
||||
// ReferencedContext==nil, it has no effect. Thus it allows for a more concise
|
||||
// syntax for starting spans:
|
||||
//
|
||||
// sc, _ := tracer.Extract(someFormat, someCarrier)
|
||||
// span := tracer.StartSpan("operation", opentracing.ChildOf(sc))
|
||||
//
|
||||
// The `ChildOf(sc)` option above will not panic if sc == nil, it will just
|
||||
// not add the parent span reference to the options.
|
||||
type SpanReference struct {
|
||||
Type SpanReferenceType
|
||||
ReferencedContext SpanContext
|
||||
}
|
||||
|
||||
// Apply satisfies the StartSpanOption interface.
|
||||
func (r SpanReference) Apply(o *StartSpanOptions) {
|
||||
if r.ReferencedContext != nil {
|
||||
o.References = append(o.References, r)
|
||||
}
|
||||
}
|
||||
|
||||
// ChildOf returns a StartSpanOption pointing to a dependent parent span.
|
||||
// If sc == nil, the option has no effect.
|
||||
//
|
||||
// See ChildOfRef, SpanReference
|
||||
func ChildOf(sc SpanContext) SpanReference {
|
||||
return SpanReference{
|
||||
Type: ChildOfRef,
|
||||
ReferencedContext: sc,
|
||||
}
|
||||
}
|
||||
|
||||
// FollowsFrom returns a StartSpanOption pointing to a parent Span that caused
|
||||
// the child Span but does not directly depend on its result in any way.
|
||||
// If sc == nil, the option has no effect.
|
||||
//
|
||||
// See FollowsFromRef, SpanReference
|
||||
func FollowsFrom(sc SpanContext) SpanReference {
|
||||
return SpanReference{
|
||||
Type: FollowsFromRef,
|
||||
ReferencedContext: sc,
|
||||
}
|
||||
}
|
||||
|
||||
// StartTime is a StartSpanOption that sets an explicit start timestamp for the
|
||||
// new Span.
|
||||
type StartTime time.Time
|
||||
|
||||
// Apply satisfies the StartSpanOption interface.
|
||||
func (t StartTime) Apply(o *StartSpanOptions) {
|
||||
o.StartTime = time.Time(t)
|
||||
}
|
||||
|
||||
// Tags are a generic map from an arbitrary string key to an opaque value type.
|
||||
// The underlying tracing system is responsible for interpreting and
|
||||
// serializing the values.
|
||||
type Tags map[string]interface{}
|
||||
|
||||
// Apply satisfies the StartSpanOption interface.
|
||||
func (t Tags) Apply(o *StartSpanOptions) {
|
||||
if o.Tags == nil {
|
||||
o.Tags = make(map[string]interface{})
|
||||
}
|
||||
for k, v := range t {
|
||||
o.Tags[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
// Tag may be passed as a StartSpanOption to add a tag to new spans,
|
||||
// or its Set method may be used to apply the tag to an existing Span,
|
||||
// for example:
|
||||
//
|
||||
// tracer.StartSpan("opName", Tag{"Key", value})
|
||||
//
|
||||
// or
|
||||
//
|
||||
// Tag{"key", value}.Set(span)
|
||||
type Tag struct {
|
||||
Key string
|
||||
Value interface{}
|
||||
}
|
||||
|
||||
// Apply satisfies the StartSpanOption interface.
|
||||
func (t Tag) Apply(o *StartSpanOptions) {
|
||||
if o.Tags == nil {
|
||||
o.Tags = make(map[string]interface{})
|
||||
}
|
||||
o.Tags[t.Key] = t.Value
|
||||
}
|
||||
|
||||
// Set applies the tag to an existing Span.
|
||||
func (t Tag) Set(s Span) {
|
||||
s.SetTag(t.Key, t.Value)
|
||||
}
|
||||
55
vendor/github.com/prometheus/client_golang/api/client.go
generated
vendored
55
vendor/github.com/prometheus/client_golang/api/client.go
generated
vendored
@@ -25,6 +25,42 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
func NewErrorAPI(err error, warnings []string) Error {
|
||||
if err == nil && warnings == nil {
|
||||
return nil
|
||||
}
|
||||
return &ErrorAPI{err, warnings}
|
||||
}
|
||||
|
||||
type ErrorAPI struct {
|
||||
err error
|
||||
warnings []string
|
||||
}
|
||||
|
||||
func (w *ErrorAPI) Err() error {
|
||||
return w.err
|
||||
}
|
||||
|
||||
func (w *ErrorAPI) Error() string {
|
||||
if w.err != nil {
|
||||
return w.err.Error()
|
||||
}
|
||||
return "Warnings: " + strings.Join(w.warnings, " , ")
|
||||
}
|
||||
|
||||
func (w *ErrorAPI) Warnings() []string {
|
||||
return w.warnings
|
||||
}
|
||||
|
||||
// Error encapsulates an error + warning
|
||||
type Error interface {
|
||||
error
|
||||
// Err returns the underlying error.
|
||||
Err() error
|
||||
// Warnings returns a list of warnings.
|
||||
Warnings() []string
|
||||
}
|
||||
|
||||
// DefaultRoundTripper is used if no RoundTripper is set in Config.
|
||||
var DefaultRoundTripper http.RoundTripper = &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
@@ -55,14 +91,14 @@ func (cfg *Config) roundTripper() http.RoundTripper {
|
||||
// Client is the interface for an API client.
|
||||
type Client interface {
|
||||
URL(ep string, args map[string]string) *url.URL
|
||||
Do(context.Context, *http.Request) (*http.Response, []byte, error)
|
||||
Do(context.Context, *http.Request) (*http.Response, []byte, Error)
|
||||
}
|
||||
|
||||
// DoGetFallback will attempt to do the request as-is, and on a 405 it will fallback to a GET request.
|
||||
func DoGetFallback(c Client, ctx context.Context, u *url.URL, args url.Values) (*http.Response, []byte, error) {
|
||||
func DoGetFallback(c Client, ctx context.Context, u *url.URL, args url.Values) (*http.Response, []byte, Error) {
|
||||
req, err := http.NewRequest(http.MethodPost, u.String(), strings.NewReader(args.Encode()))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, nil, NewErrorAPI(err, nil)
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
||||
@@ -71,11 +107,14 @@ func DoGetFallback(c Client, ctx context.Context, u *url.URL, args url.Values) (
|
||||
u.RawQuery = args.Encode()
|
||||
req, err = http.NewRequest(http.MethodGet, u.String(), nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, nil, NewErrorAPI(err, nil)
|
||||
}
|
||||
|
||||
} else {
|
||||
return resp, body, err
|
||||
if err != nil {
|
||||
return resp, body, NewErrorAPI(err, nil)
|
||||
}
|
||||
return resp, body, nil
|
||||
}
|
||||
return c.Do(ctx, req)
|
||||
}
|
||||
@@ -115,7 +154,7 @@ func (c *httpClient) URL(ep string, args map[string]string) *url.URL {
|
||||
return &u
|
||||
}
|
||||
|
||||
func (c *httpClient) Do(ctx context.Context, req *http.Request) (*http.Response, []byte, error) {
|
||||
func (c *httpClient) Do(ctx context.Context, req *http.Request) (*http.Response, []byte, Error) {
|
||||
if ctx != nil {
|
||||
req = req.WithContext(ctx)
|
||||
}
|
||||
@@ -127,7 +166,7 @@ func (c *httpClient) Do(ctx context.Context, req *http.Request) (*http.Response,
|
||||
}()
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, nil, NewErrorAPI(err, nil)
|
||||
}
|
||||
|
||||
var body []byte
|
||||
@@ -147,5 +186,5 @@ func (c *httpClient) Do(ctx context.Context, req *http.Request) (*http.Response,
|
||||
case <-done:
|
||||
}
|
||||
|
||||
return resp, body, err
|
||||
return resp, body, NewErrorAPI(err, nil)
|
||||
}
|
||||
|
||||
364
vendor/github.com/prometheus/client_golang/api/prometheus/v1/api.go
generated
vendored
364
vendor/github.com/prometheus/client_golang/api/prometheus/v1/api.go
generated
vendored
@@ -17,17 +17,105 @@ package v1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
json "github.com/json-iterator/go"
|
||||
|
||||
"github.com/prometheus/common/model"
|
||||
|
||||
"github.com/prometheus/client_golang/api"
|
||||
"github.com/prometheus/common/model"
|
||||
)
|
||||
|
||||
func init() {
|
||||
json.RegisterTypeEncoderFunc("model.SamplePair", marshalPointJSON, marshalPointJSONIsEmpty)
|
||||
json.RegisterTypeDecoderFunc("model.SamplePair", unMarshalPointJSON)
|
||||
}
|
||||
|
||||
func unMarshalPointJSON(ptr unsafe.Pointer, iter *json.Iterator) {
|
||||
p := (*model.SamplePair)(ptr)
|
||||
if !iter.ReadArray() {
|
||||
iter.ReportError("unmarshal model.SamplePair", "SamplePair must be [timestamp, value]")
|
||||
return
|
||||
}
|
||||
t := iter.ReadNumber()
|
||||
if err := p.Timestamp.UnmarshalJSON([]byte(t)); err != nil {
|
||||
iter.ReportError("unmarshal model.SamplePair", err.Error())
|
||||
return
|
||||
}
|
||||
if !iter.ReadArray() {
|
||||
iter.ReportError("unmarshal model.SamplePair", "SamplePair missing value")
|
||||
return
|
||||
}
|
||||
|
||||
f, err := strconv.ParseFloat(iter.ReadString(), 64)
|
||||
if err != nil {
|
||||
iter.ReportError("unmarshal model.SamplePair", err.Error())
|
||||
return
|
||||
}
|
||||
p.Value = model.SampleValue(f)
|
||||
|
||||
if iter.ReadArray() {
|
||||
iter.ReportError("unmarshal model.SamplePair", "SamplePair has too many values, must be [timestamp, value]")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func marshalPointJSON(ptr unsafe.Pointer, stream *json.Stream) {
|
||||
p := *((*model.SamplePair)(ptr))
|
||||
stream.WriteArrayStart()
|
||||
// Write out the timestamp as a float divided by 1000.
|
||||
// This is ~3x faster than converting to a float.
|
||||
t := int64(p.Timestamp)
|
||||
if t < 0 {
|
||||
stream.WriteRaw(`-`)
|
||||
t = -t
|
||||
}
|
||||
stream.WriteInt64(t / 1000)
|
||||
fraction := t % 1000
|
||||
if fraction != 0 {
|
||||
stream.WriteRaw(`.`)
|
||||
if fraction < 100 {
|
||||
stream.WriteRaw(`0`)
|
||||
}
|
||||
if fraction < 10 {
|
||||
stream.WriteRaw(`0`)
|
||||
}
|
||||
stream.WriteInt64(fraction)
|
||||
}
|
||||
stream.WriteMore()
|
||||
stream.WriteRaw(`"`)
|
||||
|
||||
// Taken from https://github.com/json-iterator/go/blob/master/stream_float.go#L71 as a workaround
|
||||
// to https://github.com/json-iterator/go/issues/365 (jsoniter, to follow json standard, doesn't allow inf/nan)
|
||||
buf := stream.Buffer()
|
||||
abs := math.Abs(float64(p.Value))
|
||||
fmt := byte('f')
|
||||
// Note: Must use float32 comparisons for underlying float32 value to get precise cutoffs right.
|
||||
if abs != 0 {
|
||||
if abs < 1e-6 || abs >= 1e21 {
|
||||
fmt = 'e'
|
||||
fmt = 'e'
|
||||
}
|
||||
}
|
||||
buf = strconv.AppendFloat(buf, float64(p.Value), fmt, -1, 64)
|
||||
stream.SetBuffer(buf)
|
||||
|
||||
stream.WriteRaw(`"`)
|
||||
stream.WriteArrayEnd()
|
||||
|
||||
}
|
||||
|
||||
func marshalPointJSONIsEmpty(ptr unsafe.Pointer) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
const (
|
||||
statusAPIError = 422
|
||||
|
||||
@@ -40,6 +128,7 @@ const (
|
||||
epLabelValues = apiPrefix + "/label/:name/values"
|
||||
epSeries = apiPrefix + "/series"
|
||||
epTargets = apiPrefix + "/targets"
|
||||
epTargetsMetadata = apiPrefix + "/targets/metadata"
|
||||
epRules = apiPrefix + "/rules"
|
||||
epSnapshot = apiPrefix + "/admin/tsdb/snapshot"
|
||||
epDeleteSeries = apiPrefix + "/admin/tsdb/delete_series"
|
||||
@@ -63,6 +152,9 @@ type RuleType string
|
||||
// RuleHealth models the health status of a rule.
|
||||
type RuleHealth string
|
||||
|
||||
// MetricType models the type of a metric.
|
||||
type MetricType string
|
||||
|
||||
const (
|
||||
// Possible values for AlertState.
|
||||
AlertStateFiring AlertState = "firing"
|
||||
@@ -91,17 +183,40 @@ const (
|
||||
RuleHealthGood = "ok"
|
||||
RuleHealthUnknown = "unknown"
|
||||
RuleHealthBad = "err"
|
||||
|
||||
// Possible values for MetricType
|
||||
MetricTypeCounter MetricType = "counter"
|
||||
MetricTypeGauge MetricType = "gauge"
|
||||
MetricTypeHistogram MetricType = "histogram"
|
||||
MetricTypeGaugeHistogram MetricType = "gaugehistogram"
|
||||
MetricTypeSummary MetricType = "summary"
|
||||
MetricTypeInfo MetricType = "info"
|
||||
MetricTypeStateset MetricType = "stateset"
|
||||
MetricTypeUnknown MetricType = "unknown"
|
||||
)
|
||||
|
||||
// Error is an error returned by the API.
|
||||
type Error struct {
|
||||
Type ErrorType
|
||||
Msg string
|
||||
Detail string
|
||||
Type ErrorType
|
||||
Msg string
|
||||
Detail string
|
||||
warnings []string
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
return fmt.Sprintf("%s: %s", e.Type, e.Msg)
|
||||
if e.Type != "" || e.Msg != "" {
|
||||
return fmt.Sprintf("%s: %s", e.Type, e.Msg)
|
||||
}
|
||||
|
||||
return "Warnings: " + strings.Join(e.warnings, " , ")
|
||||
}
|
||||
|
||||
func (w *Error) Err() error {
|
||||
return w
|
||||
}
|
||||
|
||||
func (w *Error) Warnings() []string {
|
||||
return w.warnings
|
||||
}
|
||||
|
||||
// Range represents a sliced time range.
|
||||
@@ -115,32 +230,34 @@ type Range struct {
|
||||
// API provides bindings for Prometheus's v1 API.
|
||||
type API interface {
|
||||
// Alerts returns a list of all active alerts.
|
||||
Alerts(ctx context.Context) (AlertsResult, error)
|
||||
Alerts(ctx context.Context) (AlertsResult, api.Error)
|
||||
// AlertManagers returns an overview of the current state of the Prometheus alert manager discovery.
|
||||
AlertManagers(ctx context.Context) (AlertManagersResult, error)
|
||||
AlertManagers(ctx context.Context) (AlertManagersResult, api.Error)
|
||||
// CleanTombstones removes the deleted data from disk and cleans up the existing tombstones.
|
||||
CleanTombstones(ctx context.Context) error
|
||||
CleanTombstones(ctx context.Context) api.Error
|
||||
// Config returns the current Prometheus configuration.
|
||||
Config(ctx context.Context) (ConfigResult, error)
|
||||
Config(ctx context.Context) (ConfigResult, api.Error)
|
||||
// DeleteSeries deletes data for a selection of series in a time range.
|
||||
DeleteSeries(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) error
|
||||
DeleteSeries(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) api.Error
|
||||
// Flags returns the flag values that Prometheus was launched with.
|
||||
Flags(ctx context.Context) (FlagsResult, error)
|
||||
Flags(ctx context.Context) (FlagsResult, api.Error)
|
||||
// LabelValues performs a query for the values of the given label.
|
||||
LabelValues(ctx context.Context, label string) (model.LabelValues, error)
|
||||
LabelValues(ctx context.Context, label string) (model.LabelValues, api.Error)
|
||||
// Query performs a query for the given time.
|
||||
Query(ctx context.Context, query string, ts time.Time) (model.Value, error)
|
||||
Query(ctx context.Context, query string, ts time.Time) (model.Value, api.Error)
|
||||
// QueryRange performs a query for the given range.
|
||||
QueryRange(ctx context.Context, query string, r Range) (model.Value, error)
|
||||
QueryRange(ctx context.Context, query string, r Range) (model.Value, api.Error)
|
||||
// Series finds series by label matchers.
|
||||
Series(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) ([]model.LabelSet, error)
|
||||
Series(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) ([]model.LabelSet, api.Error)
|
||||
// Snapshot creates a snapshot of all current data into snapshots/<datetime>-<rand>
|
||||
// under the TSDB's data directory and returns the directory as response.
|
||||
Snapshot(ctx context.Context, skipHead bool) (SnapshotResult, error)
|
||||
Snapshot(ctx context.Context, skipHead bool) (SnapshotResult, api.Error)
|
||||
// Rules returns a list of alerting and recording rules that are currently loaded.
|
||||
Rules(ctx context.Context) (RulesResult, error)
|
||||
Rules(ctx context.Context) (RulesResult, api.Error)
|
||||
// Targets returns an overview of the current state of the Prometheus target discovery.
|
||||
Targets(ctx context.Context) (TargetsResult, error)
|
||||
Targets(ctx context.Context) (TargetsResult, api.Error)
|
||||
// TargetsMetadata returns metadata about metrics currently scraped by the target.
|
||||
TargetsMetadata(ctx context.Context, matchTarget string, metric string, limit string) ([]MetricMetadata, api.Error)
|
||||
}
|
||||
|
||||
// AlertsResult contains the result from querying the alerts endpoint.
|
||||
@@ -226,7 +343,7 @@ type Alert struct {
|
||||
Annotations model.LabelSet
|
||||
Labels model.LabelSet
|
||||
State AlertState
|
||||
Value float64
|
||||
Value string
|
||||
}
|
||||
|
||||
// TargetsResult contains the result from querying the targets endpoint.
|
||||
@@ -250,6 +367,15 @@ type DroppedTarget struct {
|
||||
DiscoveredLabels map[string]string `json:"discoveredLabels"`
|
||||
}
|
||||
|
||||
// MetricMetadata models the metadata of a metric.
|
||||
type MetricMetadata struct {
|
||||
Target map[string]string `json:"target"`
|
||||
Metric string `json:"metric,omitempty"`
|
||||
Type MetricType `json:"type"`
|
||||
Help string `json:"help"`
|
||||
Unit string `json:"unit"`
|
||||
}
|
||||
|
||||
// queryResult contains result data for a query.
|
||||
type queryResult struct {
|
||||
Type model.ValueType `json:"resultType"`
|
||||
@@ -408,73 +534,73 @@ type httpAPI struct {
|
||||
client api.Client
|
||||
}
|
||||
|
||||
func (h *httpAPI) Alerts(ctx context.Context) (AlertsResult, error) {
|
||||
func (h *httpAPI) Alerts(ctx context.Context) (AlertsResult, api.Error) {
|
||||
u := h.client.URL(epAlerts, nil)
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
|
||||
if err != nil {
|
||||
return AlertsResult{}, err
|
||||
return AlertsResult{}, api.NewErrorAPI(err, nil)
|
||||
}
|
||||
|
||||
_, body, err := h.client.Do(ctx, req)
|
||||
if err != nil {
|
||||
return AlertsResult{}, err
|
||||
_, body, apiErr := h.client.Do(ctx, req)
|
||||
if apiErr != nil {
|
||||
return AlertsResult{}, apiErr
|
||||
}
|
||||
|
||||
var res AlertsResult
|
||||
err = json.Unmarshal(body, &res)
|
||||
return res, err
|
||||
return res, api.NewErrorAPI(err, nil)
|
||||
}
|
||||
|
||||
func (h *httpAPI) AlertManagers(ctx context.Context) (AlertManagersResult, error) {
|
||||
func (h *httpAPI) AlertManagers(ctx context.Context) (AlertManagersResult, api.Error) {
|
||||
u := h.client.URL(epAlertManagers, nil)
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
|
||||
if err != nil {
|
||||
return AlertManagersResult{}, err
|
||||
return AlertManagersResult{}, api.NewErrorAPI(err, nil)
|
||||
}
|
||||
|
||||
_, body, err := h.client.Do(ctx, req)
|
||||
if err != nil {
|
||||
return AlertManagersResult{}, err
|
||||
_, body, apiErr := h.client.Do(ctx, req)
|
||||
if apiErr != nil {
|
||||
return AlertManagersResult{}, apiErr
|
||||
}
|
||||
|
||||
var res AlertManagersResult
|
||||
err = json.Unmarshal(body, &res)
|
||||
return res, err
|
||||
return res, api.NewErrorAPI(err, nil)
|
||||
}
|
||||
|
||||
func (h *httpAPI) CleanTombstones(ctx context.Context) error {
|
||||
func (h *httpAPI) CleanTombstones(ctx context.Context) api.Error {
|
||||
u := h.client.URL(epCleanTombstones, nil)
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, u.String(), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
return api.NewErrorAPI(err, nil)
|
||||
}
|
||||
|
||||
_, _, err = h.client.Do(ctx, req)
|
||||
return err
|
||||
_, _, apiErr := h.client.Do(ctx, req)
|
||||
return apiErr
|
||||
}
|
||||
|
||||
func (h *httpAPI) Config(ctx context.Context) (ConfigResult, error) {
|
||||
func (h *httpAPI) Config(ctx context.Context) (ConfigResult, api.Error) {
|
||||
u := h.client.URL(epConfig, nil)
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
|
||||
if err != nil {
|
||||
return ConfigResult{}, err
|
||||
return ConfigResult{}, api.NewErrorAPI(err, nil)
|
||||
}
|
||||
|
||||
_, body, err := h.client.Do(ctx, req)
|
||||
if err != nil {
|
||||
return ConfigResult{}, err
|
||||
_, body, apiErr := h.client.Do(ctx, req)
|
||||
if apiErr != nil {
|
||||
return ConfigResult{}, apiErr
|
||||
}
|
||||
|
||||
var res ConfigResult
|
||||
err = json.Unmarshal(body, &res)
|
||||
return res, err
|
||||
return res, api.NewErrorAPI(err, nil)
|
||||
}
|
||||
|
||||
func (h *httpAPI) DeleteSeries(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) error {
|
||||
func (h *httpAPI) DeleteSeries(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) api.Error {
|
||||
u := h.client.URL(epDeleteSeries, nil)
|
||||
q := u.Query()
|
||||
|
||||
@@ -489,47 +615,47 @@ func (h *httpAPI) DeleteSeries(ctx context.Context, matches []string, startTime
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, u.String(), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
return api.NewErrorAPI(err, nil)
|
||||
}
|
||||
|
||||
_, _, err = h.client.Do(ctx, req)
|
||||
return err
|
||||
_, _, apiErr := h.client.Do(ctx, req)
|
||||
return apiErr
|
||||
}
|
||||
|
||||
func (h *httpAPI) Flags(ctx context.Context) (FlagsResult, error) {
|
||||
func (h *httpAPI) Flags(ctx context.Context) (FlagsResult, api.Error) {
|
||||
u := h.client.URL(epFlags, nil)
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
|
||||
if err != nil {
|
||||
return FlagsResult{}, err
|
||||
return FlagsResult{}, api.NewErrorAPI(err, nil)
|
||||
}
|
||||
|
||||
_, body, err := h.client.Do(ctx, req)
|
||||
if err != nil {
|
||||
return FlagsResult{}, err
|
||||
_, body, apiErr := h.client.Do(ctx, req)
|
||||
if apiErr != nil {
|
||||
return FlagsResult{}, apiErr
|
||||
}
|
||||
|
||||
var res FlagsResult
|
||||
err = json.Unmarshal(body, &res)
|
||||
return res, err
|
||||
return res, api.NewErrorAPI(err, nil)
|
||||
}
|
||||
|
||||
func (h *httpAPI) LabelValues(ctx context.Context, label string) (model.LabelValues, error) {
|
||||
func (h *httpAPI) LabelValues(ctx context.Context, label string) (model.LabelValues, api.Error) {
|
||||
u := h.client.URL(epLabelValues, map[string]string{"name": label})
|
||||
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, api.NewErrorAPI(err, nil)
|
||||
}
|
||||
_, body, err := h.client.Do(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
_, body, apiErr := h.client.Do(ctx, req)
|
||||
if apiErr != nil {
|
||||
return nil, apiErr
|
||||
}
|
||||
var labelValues model.LabelValues
|
||||
err = json.Unmarshal(body, &labelValues)
|
||||
return labelValues, err
|
||||
return labelValues, api.NewErrorAPI(err, nil)
|
||||
}
|
||||
|
||||
func (h *httpAPI) Query(ctx context.Context, query string, ts time.Time) (model.Value, error) {
|
||||
func (h *httpAPI) Query(ctx context.Context, query string, ts time.Time) (model.Value, api.Error) {
|
||||
u := h.client.URL(epQuery, nil)
|
||||
q := u.Query()
|
||||
|
||||
@@ -538,18 +664,16 @@ func (h *httpAPI) Query(ctx context.Context, query string, ts time.Time) (model.
|
||||
q.Set("time", ts.Format(time.RFC3339Nano))
|
||||
}
|
||||
|
||||
_, body, err := api.DoGetFallback(h.client, ctx, u, q)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
_, body, apiErr := api.DoGetFallback(h.client, ctx, u, q)
|
||||
if apiErr != nil {
|
||||
return nil, apiErr
|
||||
}
|
||||
|
||||
var qres queryResult
|
||||
err = json.Unmarshal(body, &qres)
|
||||
|
||||
return model.Value(qres.v), err
|
||||
return model.Value(qres.v), api.NewErrorAPI(json.Unmarshal(body, &qres), nil)
|
||||
}
|
||||
|
||||
func (h *httpAPI) QueryRange(ctx context.Context, query string, r Range) (model.Value, error) {
|
||||
func (h *httpAPI) QueryRange(ctx context.Context, query string, r Range) (model.Value, api.Error) {
|
||||
u := h.client.URL(epQueryRange, nil)
|
||||
q := u.Query()
|
||||
|
||||
@@ -564,18 +688,17 @@ func (h *httpAPI) QueryRange(ctx context.Context, query string, r Range) (model.
|
||||
q.Set("end", end)
|
||||
q.Set("step", step)
|
||||
|
||||
_, body, err := api.DoGetFallback(h.client, ctx, u, q)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
_, body, apiErr := api.DoGetFallback(h.client, ctx, u, q)
|
||||
if apiErr != nil {
|
||||
return nil, apiErr
|
||||
}
|
||||
|
||||
var qres queryResult
|
||||
err = json.Unmarshal(body, &qres)
|
||||
|
||||
return model.Value(qres.v), err
|
||||
return model.Value(qres.v), api.NewErrorAPI(json.Unmarshal(body, &qres), nil)
|
||||
}
|
||||
|
||||
func (h *httpAPI) Series(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) ([]model.LabelSet, error) {
|
||||
func (h *httpAPI) Series(ctx context.Context, matches []string, startTime time.Time, endTime time.Time) ([]model.LabelSet, api.Error) {
|
||||
u := h.client.URL(epSeries, nil)
|
||||
q := u.Query()
|
||||
|
||||
@@ -590,20 +713,20 @@ func (h *httpAPI) Series(ctx context.Context, matches []string, startTime time.T
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, api.NewErrorAPI(err, nil)
|
||||
}
|
||||
|
||||
_, body, err := h.client.Do(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
_, body, apiErr := h.client.Do(ctx, req)
|
||||
if apiErr != nil {
|
||||
return nil, apiErr
|
||||
}
|
||||
|
||||
var mset []model.LabelSet
|
||||
err = json.Unmarshal(body, &mset)
|
||||
return mset, err
|
||||
return mset, api.NewErrorAPI(err, nil)
|
||||
}
|
||||
|
||||
func (h *httpAPI) Snapshot(ctx context.Context, skipHead bool) (SnapshotResult, error) {
|
||||
func (h *httpAPI) Snapshot(ctx context.Context, skipHead bool) (SnapshotResult, api.Error) {
|
||||
u := h.client.URL(epSnapshot, nil)
|
||||
q := u.Query()
|
||||
|
||||
@@ -613,53 +736,78 @@ func (h *httpAPI) Snapshot(ctx context.Context, skipHead bool) (SnapshotResult,
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, u.String(), nil)
|
||||
if err != nil {
|
||||
return SnapshotResult{}, err
|
||||
return SnapshotResult{}, api.NewErrorAPI(err, nil)
|
||||
}
|
||||
|
||||
_, body, err := h.client.Do(ctx, req)
|
||||
if err != nil {
|
||||
return SnapshotResult{}, err
|
||||
_, body, apiErr := h.client.Do(ctx, req)
|
||||
if apiErr != nil {
|
||||
return SnapshotResult{}, apiErr
|
||||
}
|
||||
|
||||
var res SnapshotResult
|
||||
err = json.Unmarshal(body, &res)
|
||||
return res, err
|
||||
return res, api.NewErrorAPI(err, nil)
|
||||
}
|
||||
|
||||
func (h *httpAPI) Rules(ctx context.Context) (RulesResult, error) {
|
||||
func (h *httpAPI) Rules(ctx context.Context) (RulesResult, api.Error) {
|
||||
u := h.client.URL(epRules, nil)
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
|
||||
if err != nil {
|
||||
return RulesResult{}, err
|
||||
return RulesResult{}, api.NewErrorAPI(err, nil)
|
||||
}
|
||||
|
||||
_, body, err := h.client.Do(ctx, req)
|
||||
if err != nil {
|
||||
return RulesResult{}, err
|
||||
_, body, apiErr := h.client.Do(ctx, req)
|
||||
if apiErr != nil {
|
||||
return RulesResult{}, apiErr
|
||||
}
|
||||
|
||||
var res RulesResult
|
||||
err = json.Unmarshal(body, &res)
|
||||
return res, err
|
||||
return res, api.NewErrorAPI(err, nil)
|
||||
}
|
||||
|
||||
func (h *httpAPI) Targets(ctx context.Context) (TargetsResult, error) {
|
||||
func (h *httpAPI) Targets(ctx context.Context) (TargetsResult, api.Error) {
|
||||
u := h.client.URL(epTargets, nil)
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
|
||||
if err != nil {
|
||||
return TargetsResult{}, err
|
||||
return TargetsResult{}, api.NewErrorAPI(err, nil)
|
||||
}
|
||||
|
||||
_, body, err := h.client.Do(ctx, req)
|
||||
if err != nil {
|
||||
return TargetsResult{}, err
|
||||
_, body, apiErr := h.client.Do(ctx, req)
|
||||
if apiErr != nil {
|
||||
return TargetsResult{}, apiErr
|
||||
}
|
||||
|
||||
var res TargetsResult
|
||||
err = json.Unmarshal(body, &res)
|
||||
return res, err
|
||||
return res, api.NewErrorAPI(err, nil)
|
||||
}
|
||||
|
||||
func (h *httpAPI) TargetsMetadata(ctx context.Context, matchTarget string, metric string, limit string) ([]MetricMetadata, api.Error) {
|
||||
u := h.client.URL(epTargetsMetadata, nil)
|
||||
q := u.Query()
|
||||
|
||||
q.Set("match_target", matchTarget)
|
||||
q.Set("metric", metric)
|
||||
q.Set("limit", limit)
|
||||
|
||||
u.RawQuery = q.Encode()
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
|
||||
if err != nil {
|
||||
return nil, api.NewErrorAPI(err, nil)
|
||||
}
|
||||
|
||||
_, body, apiErr := h.client.Do(ctx, req)
|
||||
if apiErr != nil {
|
||||
return nil, apiErr
|
||||
}
|
||||
|
||||
var res []MetricMetadata
|
||||
err = json.Unmarshal(body, &res)
|
||||
return res, api.NewErrorAPI(err, nil)
|
||||
}
|
||||
|
||||
// apiClient wraps a regular client and processes successful API responses.
|
||||
@@ -673,6 +821,7 @@ type apiResponse struct {
|
||||
Data json.RawMessage `json:"data"`
|
||||
ErrorType ErrorType `json:"errorType"`
|
||||
Error string `json:"error"`
|
||||
Warnings []string `json:"warnings,omitempty"`
|
||||
}
|
||||
|
||||
func apiError(code int) bool {
|
||||
@@ -690,14 +839,16 @@ func errorTypeAndMsgFor(resp *http.Response) (ErrorType, string) {
|
||||
return ErrBadResponse, fmt.Sprintf("bad response code %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
func (c apiClient) Do(ctx context.Context, req *http.Request) (*http.Response, []byte, error) {
|
||||
resp, body, err := c.Client.Do(ctx, req)
|
||||
if err != nil {
|
||||
return resp, body, err
|
||||
func (c apiClient) Do(ctx context.Context, req *http.Request) (*http.Response, []byte, api.Error) {
|
||||
resp, body, apiErr := c.Client.Do(ctx, req)
|
||||
if apiErr != nil {
|
||||
return resp, body, apiErr
|
||||
}
|
||||
|
||||
code := resp.StatusCode
|
||||
|
||||
var err api.Error
|
||||
|
||||
if code/100 != 2 && !apiError(code) {
|
||||
errorType, errorMsg := errorTypeAndMsgFor(resp)
|
||||
return resp, body, &Error{
|
||||
@@ -710,27 +861,30 @@ func (c apiClient) Do(ctx context.Context, req *http.Request) (*http.Response, [
|
||||
var result apiResponse
|
||||
|
||||
if http.StatusNoContent != code {
|
||||
if err = json.Unmarshal(body, &result); err != nil {
|
||||
if jsonErr := json.Unmarshal(body, &result); jsonErr != nil {
|
||||
return resp, body, &Error{
|
||||
Type: ErrBadResponse,
|
||||
Msg: err.Error(),
|
||||
Msg: jsonErr.Error(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if apiError(code) != (result.Status == "error") {
|
||||
err = &Error{
|
||||
Type: ErrBadResponse,
|
||||
Msg: "inconsistent body for response code",
|
||||
Type: ErrBadResponse,
|
||||
Msg: "inconsistent body for response code",
|
||||
warnings: result.Warnings,
|
||||
}
|
||||
}
|
||||
|
||||
if apiError(code) && result.Status == "error" {
|
||||
err = &Error{
|
||||
Type: result.ErrorType,
|
||||
Msg: result.Error,
|
||||
Type: result.ErrorType,
|
||||
Msg: result.Error,
|
||||
warnings: result.Warnings,
|
||||
}
|
||||
}
|
||||
|
||||
return resp, []byte(result.Data), err
|
||||
|
||||
}
|
||||
|
||||
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"
|
||||
}
|
||||
32
vendor/github.com/prometheus/client_golang/prometheus/go_collector.go
generated
vendored
32
vendor/github.com/prometheus/client_golang/prometheus/go_collector.go
generated
vendored
@@ -36,7 +36,7 @@ type goCollector struct {
|
||||
msMaxAge time.Duration // Maximum allowed age of old memstats.
|
||||
}
|
||||
|
||||
// NewGoCollector returns a collector which exports metrics about the current Go
|
||||
// NewGoCollector returns a collector that exports metrics about the current Go
|
||||
// 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
|
||||
@@ -364,3 +364,33 @@ type memStatsMetrics []struct {
|
||||
eval func(*runtime.MemStats) float64
|
||||
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
|
||||
}
|
||||
|
||||
6
vendor/github.com/prometheus/client_golang/prometheus/process_collector.go
generated
vendored
6
vendor/github.com/prometheus/client_golang/prometheus/process_collector.go
generated
vendored
@@ -126,7 +126,7 @@ func NewProcessCollector(opts ProcessCollectorOpts) Collector {
|
||||
}
|
||||
|
||||
// Set up process metric collection if supported by the runtime.
|
||||
if _, err := procfs.NewStat(); err == nil {
|
||||
if _, err := procfs.NewDefaultFS(); err == nil {
|
||||
c.collectFn = c.processCollect
|
||||
} else {
|
||||
c.collectFn = func(ch chan<- Metric) {
|
||||
@@ -166,7 +166,7 @@ func (c *processCollector) processCollect(ch chan<- Metric) {
|
||||
return
|
||||
}
|
||||
|
||||
if stat, err := p.NewStat(); err == nil {
|
||||
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()))
|
||||
@@ -185,7 +185,7 @@ func (c *processCollector) processCollect(ch chan<- Metric) {
|
||||
c.reportError(ch, c.openFDs, err)
|
||||
}
|
||||
|
||||
if limits, err := p.NewLimits(); err == nil {
|
||||
if limits, err := p.Limits(); err == nil {
|
||||
ch <- MustNewConstMetric(c.maxFDs, GaugeValue, float64(limits.OpenFiles))
|
||||
ch <- MustNewConstMetric(c.maxVsize, GaugeValue, float64(limits.AddressSpace))
|
||||
} else {
|
||||
|
||||
47
vendor/github.com/prometheus/client_golang/prometheus/promhttp/http.go
generated
vendored
47
vendor/github.com/prometheus/client_golang/prometheus/promhttp/http.go
generated
vendored
@@ -84,10 +84,32 @@ func Handler() http.Handler {
|
||||
// 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{}
|
||||
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 {
|
||||
@@ -106,6 +128,7 @@ func HandlerFor(reg prometheus.Gatherer, opts HandlerOpts) http.Handler {
|
||||
if opts.ErrorLog != nil {
|
||||
opts.ErrorLog.Println("error gathering metrics:", err)
|
||||
}
|
||||
errCnt.WithLabelValues("gathering").Inc()
|
||||
switch opts.ErrorHandling {
|
||||
case PanicOnError:
|
||||
panic(err)
|
||||
@@ -146,6 +169,7 @@ func HandlerFor(reg prometheus.Gatherer, opts HandlerOpts) http.Handler {
|
||||
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)
|
||||
@@ -236,9 +260,12 @@ const (
|
||||
// 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. It is recommended to at least
|
||||
// log errors (by providing an ErrorLog in HandlerOpts) to not mask
|
||||
// errors completely.
|
||||
// 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
|
||||
@@ -261,6 +288,18 @@ type HandlerOpts struct {
|
||||
// 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
|
||||
|
||||
8
vendor/github.com/prometheus/client_golang/prometheus/summary.go
generated
vendored
8
vendor/github.com/prometheus/client_golang/prometheus/summary.go
generated
vendored
@@ -39,7 +39,7 @@ const quantileLabel = "quantile"
|
||||
// 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
|
||||
// as rank estimations. However, the default behavior will change in the
|
||||
// upcoming v0.10 of the library. There will be no rank estimations at all by
|
||||
// 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.
|
||||
//
|
||||
@@ -61,7 +61,7 @@ type Summary interface {
|
||||
// DefObjectives are the default Summary quantile values.
|
||||
//
|
||||
// Deprecated: DefObjectives will not be used as the default objectives in
|
||||
// v0.10 of the library. The default Summary will have no quantiles then.
|
||||
// v1.0.0 of the library. The default Summary will have no quantiles then.
|
||||
var (
|
||||
DefObjectives = map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001}
|
||||
|
||||
@@ -86,7 +86,7 @@ const (
|
||||
// mandatory to set Name to a non-empty string. While all other fields are
|
||||
// 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 v0.10 of the library.
|
||||
// as the default value will change in the upcoming v1.0.0 of the library.
|
||||
type SummaryOpts struct {
|
||||
// Namespace, Subsystem, and Name are components of the fully-qualified
|
||||
// name of the Summary (created by joining these components with
|
||||
@@ -128,7 +128,7 @@ type SummaryOpts struct {
|
||||
// set it to an empty map (i.e. map[float64]float64{}).
|
||||
//
|
||||
// Note that the current value of DefObjectives is deprecated. It will
|
||||
// be replaced by an empty map in v0.10 of the library. Please
|
||||
// be replaced by an empty map in v1.0.0 of the library. Please
|
||||
// explicitly set Objectives to the desired value to avoid problems
|
||||
// during the transition.
|
||||
Objectives map[float64]float64
|
||||
|
||||
1
vendor/github.com/prometheus/procfs/Makefile
generated
vendored
1
vendor/github.com/prometheus/procfs/Makefile
generated
vendored
@@ -14,6 +14,7 @@
|
||||
include Makefile.common
|
||||
|
||||
%/.unpacked: %.ttar
|
||||
@echo ">> extracting fixtures"
|
||||
./ttar -C $(dir $*) -x -f $*.ttar
|
||||
touch $@
|
||||
|
||||
|
||||
2
vendor/github.com/prometheus/procfs/Makefile.common
generated
vendored
2
vendor/github.com/prometheus/procfs/Makefile.common
generated
vendored
@@ -69,7 +69,7 @@ else
|
||||
GO_BUILD_PLATFORM ?= $(GOHOSTOS)-$(GOHOSTARCH)
|
||||
endif
|
||||
|
||||
PROMU_VERSION ?= 0.3.0
|
||||
PROMU_VERSION ?= 0.4.0
|
||||
PROMU_URL := https://github.com/prometheus/promu/releases/download/v$(PROMU_VERSION)/promu-$(PROMU_VERSION).$(GO_BUILD_PLATFORM).tar.gz
|
||||
|
||||
GOLANGCI_LINT :=
|
||||
|
||||
44
vendor/github.com/prometheus/procfs/README.md
generated
vendored
44
vendor/github.com/prometheus/procfs/README.md
generated
vendored
@@ -1,7 +1,7 @@
|
||||
# procfs
|
||||
|
||||
This procfs 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
|
||||
backwards-incompatible ways without warnings. Use it at your own risk.
|
||||
@@ -9,3 +9,45 @@ backwards-incompatible ways without warnings. Use it at your own risk.
|
||||
[](https://godoc.org/github.com/prometheus/procfs)
|
||||
[](https://travis-ci.org/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, current 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()
|
||||
```
|
||||
|
||||
## Building and Testing
|
||||
|
||||
The procfs library is normally built as part of another application. However, when making
|
||||
changes to the library, the `make test` command can be used to run the API test suite.
|
||||
|
||||
### 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`.
|
||||
|
||||
12
vendor/github.com/prometheus/procfs/buddyinfo.go
generated
vendored
12
vendor/github.com/prometheus/procfs/buddyinfo.go
generated
vendored
@@ -31,18 +31,8 @@ type BuddyInfo struct {
|
||||
Sizes []float64
|
||||
}
|
||||
|
||||
// NewBuddyInfo reads the buddyinfo statistics.
|
||||
func NewBuddyInfo() ([]BuddyInfo, error) {
|
||||
fs, err := NewFS(DefaultMountPoint)
|
||||
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) {
|
||||
func (fs FS) BuddyInfo() ([]BuddyInfo, error) {
|
||||
file, err := os.Open(fs.proc.Path("buddyinfo"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
144
vendor/github.com/prometheus/procfs/fixtures.ttar
generated
vendored
144
vendor/github.com/prometheus/procfs/fixtures.ttar
generated
vendored
@@ -75,13 +75,13 @@ Max realtime timeout unlimited unlimited us
|
||||
Mode: 644
|
||||
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Path: fixtures/proc/26231/mountstats
|
||||
Lines: 19
|
||||
Lines: 20
|
||||
device rootfs mounted on / with fstype rootfs
|
||||
device sysfs mounted on /sys with fstype sysfs
|
||||
device proc mounted on /proc with fstype proc
|
||||
device /dev/sda1 mounted on / with fstype ext4
|
||||
device 192.168.1.1:/srv/test mounted on /mnt/nfs/test with fstype nfs4 statvers=1.1
|
||||
opts: rw,vers=4.0,rsize=1048576,wsize=1048576,namlen=255,acregmin=3,acregmax=60,acdirmin=30,acdirmax=60,hard,proto=tcp,port=0,timeo=600,retrans=2,sec=sys,clientaddr=192.168.1.5,local_lock=none
|
||||
opts: rw,vers=4.0,rsize=1048576,wsize=1048576,namlen=255,acregmin=3,acregmax=60,acdirmin=30,acdirmax=60,hard,proto=tcp,port=0,timeo=600,retrans=2,sec=sys,mountaddr=192.168.1.1,clientaddr=192.168.1.5,local_lock=none
|
||||
age: 13968
|
||||
caps: caps=0xfff7,wtmult=512,dtsize=32768,bsize=0,namlen=255
|
||||
nfsv4: bm0=0xfdffafff,bm1=0xf9be3e,bm2=0x0,acl=0x0,pnfs=not configured
|
||||
@@ -94,6 +94,7 @@ device 192.168.1.1:/srv/test mounted on /mnt/nfs/test with fstype nfs4 statvers=
|
||||
NULL: 0 0 0 0 0 0 0 0
|
||||
READ: 1298 1298 0 207680 1210292152 6 79386 79407
|
||||
WRITE: 0 0 0 0 0 0 0 0
|
||||
ACCESS: 2927395007 2927394995 0 526931094212 362996810236 18446743919241604546 1667369447 1953587717
|
||||
|
||||
Mode: 644
|
||||
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
@@ -125,6 +126,63 @@ Lines: 1
|
||||
26231 (vim) R 5392 7446 5392 34835 7446 4218880 32533 309516 26 82 1677 44 158 99 20 0 1 0 82375 56274944 1981 18446744073709551615 4194304 6294284 140736914091744 140736914087944 139965136429984 0 0 12288 1870679807 0 0 0 17 0 0 0 31 0 0 8391624 8481048 16420864 140736914093252 140736914093279 140736914093279 140736914096107 0
|
||||
Mode: 644
|
||||
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Path: fixtures/proc/26231/status
|
||||
Lines: 53
|
||||
|
||||
Name: prometheus
|
||||
Umask: 0022
|
||||
State: S (sleeping)
|
||||
Tgid: 1
|
||||
Ngid: 0
|
||||
Pid: 1
|
||||
PPid: 0
|
||||
TracerPid: 0
|
||||
Uid: 0 0 0 0
|
||||
Gid: 0 0 0 0
|
||||
FDSize: 128
|
||||
Groups:
|
||||
NStgid: 1
|
||||
NSpid: 1
|
||||
NSpgid: 1
|
||||
NSsid: 1
|
||||
VmPeak: 58472 kB
|
||||
VmSize: 58440 kB
|
||||
VmLck: 0 kB
|
||||
VmPin: 0 kB
|
||||
VmHWM: 8028 kB
|
||||
VmRSS: 6716 kB
|
||||
RssAnon: 2092 kB
|
||||
RssFile: 4624 kB
|
||||
RssShmem: 0 kB
|
||||
VmData: 2580 kB
|
||||
VmStk: 136 kB
|
||||
VmExe: 948 kB
|
||||
VmLib: 6816 kB
|
||||
VmPTE: 128 kB
|
||||
VmPMD: 12 kB
|
||||
VmSwap: 660 kB
|
||||
HugetlbPages: 0 kB
|
||||
Threads: 1
|
||||
SigQ: 8/63965
|
||||
SigPnd: 0000000000000000
|
||||
ShdPnd: 0000000000000000
|
||||
SigBlk: 7be3c0fe28014a03
|
||||
SigIgn: 0000000000001000
|
||||
SigCgt: 00000001800004ec
|
||||
CapInh: 0000000000000000
|
||||
CapPrm: 0000003fffffffff
|
||||
CapEff: 0000003fffffffff
|
||||
CapBnd: 0000003fffffffff
|
||||
CapAmb: 0000000000000000
|
||||
Seccomp: 0
|
||||
Cpus_allowed: ff
|
||||
Cpus_allowed_list: 0-7
|
||||
Mems_allowed: 00000000,00000001
|
||||
Mems_allowed_list: 0
|
||||
voluntary_ctxt_switches: 4742839
|
||||
nonvoluntary_ctxt_switches: 1727500
|
||||
Mode: 644
|
||||
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Directory: fixtures/proc/26232
|
||||
Mode: 755
|
||||
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
@@ -160,23 +218,23 @@ SymlinkTo: ../../symlinktargets/xyz
|
||||
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Path: fixtures/proc/26232/limits
|
||||
Lines: 17
|
||||
Limit Soft Limit Hard Limit Units
|
||||
Max cpu time unlimited unlimited seconds
|
||||
Max file size unlimited unlimited bytes
|
||||
Max data size unlimited unlimited bytes
|
||||
Max stack size 8388608 unlimited bytes
|
||||
Max core file size 0 unlimited bytes
|
||||
Max resident set unlimited unlimited bytes
|
||||
Max processes 29436 29436 processes
|
||||
Max open files 1024 4096 files
|
||||
Max locked memory 65536 65536 bytes
|
||||
Max address space unlimited unlimited bytes
|
||||
Max file locks unlimited unlimited locks
|
||||
Max pending signals 29436 29436 signals
|
||||
Max msgqueue size 819200 819200 bytes
|
||||
Max nice priority 0 0
|
||||
Max realtime priority 0 0
|
||||
Max realtime timeout unlimited unlimited us
|
||||
Limit Soft Limit Hard Limit Units
|
||||
Max cpu time unlimited unlimited seconds
|
||||
Max file size unlimited unlimited bytes
|
||||
Max data size unlimited unlimited bytes
|
||||
Max stack size 8388608 unlimited bytes
|
||||
Max core file size 0 unlimited bytes
|
||||
Max resident set unlimited unlimited bytes
|
||||
Max processes 29436 29436 processes
|
||||
Max open files 1024 4096 files
|
||||
Max locked memory 65536 65536 bytes
|
||||
Max address space unlimited unlimited bytes
|
||||
Max file locks unlimited unlimited locks
|
||||
Max pending signals 29436 29436 signals
|
||||
Max msgqueue size 819200 819200 bytes
|
||||
Max nice priority 0 0
|
||||
Max realtime priority 0 0
|
||||
Max realtime timeout unlimited unlimited us
|
||||
Mode: 644
|
||||
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Path: fixtures/proc/26232/root
|
||||
@@ -206,9 +264,9 @@ Mode: 644
|
||||
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Path: fixtures/proc/buddyinfo
|
||||
Lines: 3
|
||||
Node 0, zone DMA 1 0 1 0 2 1 1 0 1 1 3
|
||||
Node 0, zone DMA32 759 572 791 475 194 45 12 0 0 0 0
|
||||
Node 0, zone Normal 4381 1093 185 1530 567 102 4 0 0 0 0
|
||||
Node 0, zone DMA 1 0 1 0 2 1 1 0 1 1 3
|
||||
Node 0, zone DMA32 759 572 791 475 194 45 12 0 0 0 0
|
||||
Node 0, zone Normal 4381 1093 185 1530 567 102 4 0 0 0 0
|
||||
Mode: 644
|
||||
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Path: fixtures/proc/diskstats
|
||||
@@ -302,13 +360,13 @@ Lines: 26
|
||||
Personalities : [linear] [multipath] [raid0] [raid1] [raid6] [raid5] [raid4] [raid10]
|
||||
md3 : active raid6 sda1[8] sdh1[7] sdg1[6] sdf1[5] sde1[11] sdd1[3] sdc1[10] sdb1[9]
|
||||
5853468288 blocks super 1.2 level 6, 64k chunk, algorithm 2 [8/8] [UUUUUUUU]
|
||||
|
||||
|
||||
md127 : active raid1 sdi2[0] sdj2[1]
|
||||
312319552 blocks [2/2] [UU]
|
||||
|
||||
|
||||
md0 : active raid1 sdk[2](S) sdi1[0] sdj1[1]
|
||||
248896 blocks [2/2] [UU]
|
||||
|
||||
|
||||
md4 : inactive raid1 sda3[0] sdb3[1]
|
||||
4883648 blocks [2/2] [UU]
|
||||
|
||||
@@ -402,6 +460,26 @@ proc4 2 2 10853
|
||||
proc4ops 72 0 0 0 1098 2 0 0 0 0 8179 5896 0 0 0 0 5900 0 0 2 0 2 0 9609 0 2 150 1272 0 0 0 1236 0 0 0 0 3 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
Mode: 644
|
||||
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Path: fixtures/proc/net/unix
|
||||
Lines: 6
|
||||
Num RefCount Protocol Flags Type St Inode Path
|
||||
0000000000000000: 00000002 00000000 00010000 0001 01 3442596 /var/run/postgresql/.s.PGSQL.5432
|
||||
0000000000000000: 0000000a 00000000 00010000 0005 01 10061 /run/udev/control
|
||||
0000000000000000: 00000007 00000000 00000000 0002 01 12392 /dev/log
|
||||
0000000000000000: 00000003 00000000 00000000 0001 03 4787297 /var/run/postgresql/.s.PGSQL.5432
|
||||
0000000000000000: 00000003 00000000 00000000 0001 03 5091797
|
||||
Mode: 644
|
||||
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Path: fixtures/proc/net/unix_without_inode
|
||||
Lines: 6
|
||||
Num RefCount Protocol Flags Type St Path
|
||||
0000000000000000: 00000002 00000000 00010000 0001 01 /var/run/postgresql/.s.PGSQL.5432
|
||||
0000000000000000: 0000000a 00000000 00010000 0005 01 /run/udev/control
|
||||
0000000000000000: 00000007 00000000 00000000 0002 01 /dev/log
|
||||
0000000000000000: 00000003 00000000 00000000 0001 03 /var/run/postgresql/.s.PGSQL.5432
|
||||
0000000000000000: 00000003 00000000 00000000 0001 03
|
||||
Mode: 644
|
||||
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Path: fixtures/proc/net/xfrm_stat
|
||||
Lines: 28
|
||||
XfrmInError 1
|
||||
@@ -1107,6 +1185,22 @@ Mode: 644
|
||||
Directory: fixtures/sys/devices/system
|
||||
Mode: 775
|
||||
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Directory: fixtures/sys/devices/system/clocksource
|
||||
Mode: 775
|
||||
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Directory: fixtures/sys/devices/system/clocksource/clocksource0
|
||||
Mode: 775
|
||||
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Path: fixtures/sys/devices/system/clocksource/clocksource0/available_clocksource
|
||||
Lines: 1
|
||||
tsc hpet acpi_pm
|
||||
Mode: 444
|
||||
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Path: fixtures/sys/devices/system/clocksource/clocksource0/current_clocksource
|
||||
Lines: 1
|
||||
tsc
|
||||
Mode: 644
|
||||
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
Directory: fixtures/sys/devices/system/cpu
|
||||
Mode: 775
|
||||
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
8
vendor/github.com/prometheus/procfs/fs.go
generated
vendored
8
vendor/github.com/prometheus/procfs/fs.go
generated
vendored
@@ -26,8 +26,14 @@ type FS struct {
|
||||
// DefaultMountPoint is the common mount point of the proc filesystem.
|
||||
const DefaultMountPoint = fs.DefaultProcMountPoint
|
||||
|
||||
// NewDefaultFS returns a new proc FS mounted under the default proc mountPoint.
|
||||
// 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 dirctory can't be read or is a file.
|
||||
// if the mount point directory can't be read or is a file.
|
||||
func NewFS(mountPoint string) (FS, error) {
|
||||
fs, err := fs.NewFS(mountPoint)
|
||||
if err != nil {
|
||||
|
||||
28
vendor/github.com/prometheus/procfs/ipvs.go
generated
vendored
28
vendor/github.com/prometheus/procfs/ipvs.go
generated
vendored
@@ -62,18 +62,8 @@ type IPVSBackendStatus struct {
|
||||
Weight uint64
|
||||
}
|
||||
|
||||
// NewIPVSStats reads the IPVS statistics.
|
||||
func NewIPVSStats() (IPVSStats, error) {
|
||||
fs, err := NewFS(DefaultMountPoint)
|
||||
if err != nil {
|
||||
return IPVSStats{}, err
|
||||
}
|
||||
|
||||
return fs.NewIPVSStats()
|
||||
}
|
||||
|
||||
// NewIPVSStats reads the IPVS statistics from the specified `proc` filesystem.
|
||||
func (fs FS) NewIPVSStats() (IPVSStats, error) {
|
||||
// IPVSStats reads the IPVS statistics from the specified `proc` filesystem.
|
||||
func (fs FS) IPVSStats() (IPVSStats, error) {
|
||||
file, err := os.Open(fs.proc.Path("net/ip_vs_stats"))
|
||||
if err != nil {
|
||||
return IPVSStats{}, err
|
||||
@@ -131,18 +121,8 @@ func parseIPVSStats(file io.Reader) (IPVSStats, error) {
|
||||
return stats, nil
|
||||
}
|
||||
|
||||
// NewIPVSBackendStatus reads and returns the status of all (virtual,real) server pairs.
|
||||
func NewIPVSBackendStatus() ([]IPVSBackendStatus, error) {
|
||||
fs, err := NewFS(DefaultMountPoint)
|
||||
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) {
|
||||
// IPVSBackendStatus reads and returns the status of all (virtual,real) server pairs from the specified `proc` filesystem.
|
||||
func (fs FS) IPVSBackendStatus() ([]IPVSBackendStatus, error) {
|
||||
file, err := os.Open(fs.proc.Path("net/ip_vs"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
74
vendor/github.com/prometheus/procfs/mdstat.go
generated
vendored
74
vendor/github.com/prometheus/procfs/mdstat.go
generated
vendored
@@ -42,64 +42,64 @@ type MDStat struct {
|
||||
BlocksSynced int64
|
||||
}
|
||||
|
||||
// ParseMDStat parses an mdstat-file and returns a struct with the relevant infos.
|
||||
func (fs FS) ParseMDStat() (mdstates []MDStat, err error) {
|
||||
mdStatusFilePath := fs.proc.Path("mdstat")
|
||||
content, err := ioutil.ReadFile(mdStatusFilePath)
|
||||
// MDStat parses an mdstat-file (/proc/mdstat) and returns a slice of
|
||||
// structs containing the relevant info. More information available here:
|
||||
// https://raid.wiki.kernel.org/index.php/Mdstat
|
||||
func (fs FS) MDStat() ([]MDStat, error) {
|
||||
data, err := ioutil.ReadFile(fs.proc.Path("mdstat"))
|
||||
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{}
|
||||
lines := strings.Split(string(content), "\n")
|
||||
// parseMDStat parses data from mdstat file (/proc/mdstat) and returns a slice of
|
||||
// structs containing the relevant info.
|
||||
func parseMDStat(mdstatData []byte) ([]MDStat, error) {
|
||||
mdStats := []MDStat{}
|
||||
lines := strings.Split(string(mdstatData), "\n")
|
||||
for i, l := range lines {
|
||||
if l == "" {
|
||||
continue
|
||||
}
|
||||
if l[0] == ' ' {
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(l, "Personalities") || strings.HasPrefix(l, "unused") {
|
||||
if strings.TrimSpace(l) == "" || l[0] == ' ' ||
|
||||
strings.HasPrefix(l, "Personalities") || strings.HasPrefix(l, "unused") {
|
||||
continue
|
||||
}
|
||||
|
||||
mainLine := strings.Split(l, " ")
|
||||
if len(mainLine) < 3 {
|
||||
return mdStates, fmt.Errorf("error parsing mdline: %s", l)
|
||||
deviceFields := strings.Fields(l)
|
||||
if len(deviceFields) < 3 {
|
||||
return nil, fmt.Errorf("not enough fields in mdline (expected at least 3): %s", l)
|
||||
}
|
||||
mdName := mainLine[0]
|
||||
activityState := mainLine[2]
|
||||
mdName := deviceFields[0]
|
||||
activityState := deviceFields[2]
|
||||
|
||||
if len(lines) <= i+3 {
|
||||
return mdStates, fmt.Errorf(
|
||||
"error parsing %s: too few lines for md device %s",
|
||||
mdStatusFilePath,
|
||||
mdName,
|
||||
)
|
||||
return mdStats, fmt.Errorf("missing lines for md device %s", mdName)
|
||||
}
|
||||
|
||||
active, total, size, err := evalStatusline(lines[i+1])
|
||||
active, total, size, err := evalStatusLine(lines[i+1])
|
||||
if err != nil {
|
||||
return mdStates, fmt.Errorf("error parsing %s: %s", mdStatusFilePath, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// j is the line number of the syncing-line.
|
||||
j := i + 2
|
||||
syncLineIdx := i + 2
|
||||
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 recovering/syncing at the moment, get the number of currently
|
||||
// synced bytes, otherwise that number equals the size of the device.
|
||||
syncedBlocks := size
|
||||
if strings.Contains(lines[j], "recovery") || strings.Contains(lines[j], "resync") {
|
||||
syncedBlocks, err = evalBuildline(lines[j])
|
||||
if strings.Contains(lines[syncLineIdx], "recovery") || strings.Contains(lines[syncLineIdx], "resync") {
|
||||
syncedBlocks, err = evalRecoveryLine(lines[syncLineIdx])
|
||||
if err != nil {
|
||||
return mdStates, fmt.Errorf("error parsing %s: %s", mdStatusFilePath, err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
mdStates = append(mdStates, MDStat{
|
||||
mdStats = append(mdStats, MDStat{
|
||||
Name: mdName,
|
||||
ActivityState: activityState,
|
||||
DisksActive: active,
|
||||
@@ -109,10 +109,10 @@ func (fs FS) ParseMDStat() (mdstates []MDStat, err error) {
|
||||
})
|
||||
}
|
||||
|
||||
return mdStates, nil
|
||||
return mdStats, nil
|
||||
}
|
||||
|
||||
func evalStatusline(statusline string) (active, total, size int64, err error) {
|
||||
func evalStatusLine(statusline string) (active, total, size int64, err error) {
|
||||
matches := statuslineRE.FindStringSubmatch(statusline)
|
||||
if len(matches) != 4 {
|
||||
return 0, 0, 0, fmt.Errorf("unexpected statusline: %s", statusline)
|
||||
@@ -136,7 +136,7 @@ func evalStatusline(statusline string) (active, total, size int64, err error) {
|
||||
return active, total, size, nil
|
||||
}
|
||||
|
||||
func evalBuildline(buildline string) (syncedBlocks int64, err error) {
|
||||
func evalRecoveryLine(buildline string) (syncedBlocks int64, err error) {
|
||||
matches := buildlineRE.FindStringSubmatch(buildline)
|
||||
if len(matches) != 2 {
|
||||
return 0, fmt.Errorf("unexpected buildline: %s", buildline)
|
||||
|
||||
41
vendor/github.com/prometheus/procfs/mountstats.go
generated
vendored
41
vendor/github.com/prometheus/procfs/mountstats.go
generated
vendored
@@ -69,8 +69,8 @@ type MountStats interface {
|
||||
type MountStatsNFS struct {
|
||||
// The version of statistics provided.
|
||||
StatVersion string
|
||||
// The optional mountaddr of the NFS mount.
|
||||
MountAddress string
|
||||
// The mount options of the NFS mount.
|
||||
Opts map[string]string
|
||||
// The age of the NFS mount.
|
||||
Age time.Duration
|
||||
// Statistics related to byte counters for various operations.
|
||||
@@ -181,11 +181,11 @@ type NFSOperationStats struct {
|
||||
// Number of bytes received for this operation, including RPC headers and payload.
|
||||
BytesReceived uint64
|
||||
// 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.
|
||||
CumulativeTotalResponseTime time.Duration
|
||||
CumulativeTotalResponseMilliseconds uint64
|
||||
// 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
|
||||
@@ -204,7 +204,7 @@ type NFSTransportStats struct {
|
||||
// spent waiting for connections to the server to be established.
|
||||
ConnectIdleTime uint64
|
||||
// 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.
|
||||
Sends uint64
|
||||
// Number of RPC responses for this mount received from the NFS server.
|
||||
@@ -342,10 +342,15 @@ func parseMountStatsNFS(s *bufio.Scanner, statVersion string) (*MountStatsNFS, e
|
||||
|
||||
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 && split[0] == "mountaddr" {
|
||||
stats.MountAddress = split[1]
|
||||
if len(split) == 2 {
|
||||
stats.Opts[split[0]] = split[1]
|
||||
} else {
|
||||
stats.Opts[opt] = ""
|
||||
}
|
||||
}
|
||||
case fieldAge:
|
||||
@@ -519,15 +524,15 @@ func parseNFSOperationStats(s *bufio.Scanner) ([]NFSOperationStats, error) {
|
||||
}
|
||||
|
||||
ops = append(ops, NFSOperationStats{
|
||||
Operation: strings.TrimSuffix(ss[0], ":"),
|
||||
Requests: ns[0],
|
||||
Transmissions: ns[1],
|
||||
MajorTimeouts: ns[2],
|
||||
BytesSent: ns[3],
|
||||
BytesReceived: ns[4],
|
||||
CumulativeQueueTime: time.Duration(ns[5]) * time.Millisecond,
|
||||
CumulativeTotalResponseTime: time.Duration(ns[6]) * time.Millisecond,
|
||||
CumulativeTotalRequestTime: time.Duration(ns[7]) * time.Millisecond,
|
||||
Operation: strings.TrimSuffix(ss[0], ":"),
|
||||
Requests: ns[0],
|
||||
Transmissions: ns[1],
|
||||
MajorTimeouts: ns[2],
|
||||
BytesSent: ns[3],
|
||||
BytesReceived: ns[4],
|
||||
CumulativeQueueMilliseconds: ns[5],
|
||||
CumulativeTotalResponseMilliseconds: ns[6],
|
||||
CumulativeTotalRequestMilliseconds: ns[7],
|
||||
})
|
||||
}
|
||||
|
||||
@@ -603,7 +608,7 @@ func parseNFSTransportStats(ss []string, statVersion string) (*NFSTransportStats
|
||||
Bind: ns[1],
|
||||
Connect: ns[2],
|
||||
ConnectIdleTime: ns[3],
|
||||
IdleTime: time.Duration(ns[4]) * time.Second,
|
||||
IdleTimeSeconds: ns[4],
|
||||
Sends: ns[5],
|
||||
Receives: ns[6],
|
||||
BadTransactionIDs: ns[7],
|
||||
|
||||
36
vendor/github.com/prometheus/procfs/net_dev.go
generated
vendored
36
vendor/github.com/prometheus/procfs/net_dev.go
generated
vendored
@@ -47,23 +47,13 @@ type NetDevLine struct {
|
||||
// are interface names.
|
||||
type NetDev map[string]NetDevLine
|
||||
|
||||
// NewNetDev returns kernel/system statistics read from /proc/net/dev.
|
||||
func NewNetDev() (NetDev, error) {
|
||||
fs, err := NewFS(DefaultMountPoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return fs.NewNetDev()
|
||||
}
|
||||
|
||||
// NewNetDev returns kernel/system statistics read from /proc/net/dev.
|
||||
func (fs FS) NewNetDev() (NetDev, error) {
|
||||
// NetDev returns kernel/system statistics read from /proc/net/dev.
|
||||
func (fs FS) NetDev() (NetDev, error) {
|
||||
return newNetDev(fs.proc.Path("net/dev"))
|
||||
}
|
||||
|
||||
// NewNetDev returns kernel/system statistics read from /proc/[pid]/net/dev.
|
||||
func (p Proc) NewNetDev() (NetDev, error) {
|
||||
// NetDev returns kernel/system statistics read from /proc/[pid]/net/dev.
|
||||
func (p Proc) NetDev() (NetDev, error) {
|
||||
return newNetDev(p.path("net/dev"))
|
||||
}
|
||||
|
||||
@@ -75,7 +65,7 @@ func newNetDev(file string) (NetDev, error) {
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
nd := NetDev{}
|
||||
netDev := NetDev{}
|
||||
s := bufio.NewScanner(f)
|
||||
for n := 0; s.Scan(); n++ {
|
||||
// Skip the 2 header lines.
|
||||
@@ -83,20 +73,20 @@ func newNetDev(file string) (NetDev, error) {
|
||||
continue
|
||||
}
|
||||
|
||||
line, err := nd.parseLine(s.Text())
|
||||
line, err := netDev.parseLine(s.Text())
|
||||
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
|
||||
// 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)
|
||||
if len(parts) != 2 {
|
||||
return nil, errors.New("invalid net/dev line, missing colon")
|
||||
@@ -185,11 +175,11 @@ func (nd NetDev) parseLine(rawLine string) (*NetDevLine, error) {
|
||||
|
||||
// Total aggregates the values across interfaces and returns a new NetDevLine.
|
||||
// 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{}
|
||||
|
||||
names := make([]string, 0, len(nd))
|
||||
for _, ifc := range nd {
|
||||
names := make([]string, 0, len(netDev))
|
||||
for _, ifc := range netDev {
|
||||
names = append(names, ifc.Name)
|
||||
total.RxBytes += ifc.RxBytes
|
||||
total.RxPackets += ifc.RxPackets
|
||||
|
||||
275
vendor/github.com/prometheus/procfs/net_unix.go
generated
vendored
Normal file
275
vendor/github.com/prometheus/procfs/net_unix.go
generated
vendored
Normal file
@@ -0,0 +1,275 @@
|
||||
// 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"
|
||||
"errors"
|
||||
"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.
|
||||
|
||||
const (
|
||||
netUnixKernelPtrIdx = iota
|
||||
netUnixRefCountIdx
|
||||
_
|
||||
netUnixFlagsIdx
|
||||
netUnixTypeIdx
|
||||
netUnixStateIdx
|
||||
netUnixInodeIdx
|
||||
|
||||
// Inode and Path are optional.
|
||||
netUnixStaticFieldsCnt = 6
|
||||
)
|
||||
|
||||
const (
|
||||
netUnixTypeStream = 1
|
||||
netUnixTypeDgram = 2
|
||||
netUnixTypeSeqpacket = 5
|
||||
|
||||
netUnixFlagListen = 1 << 16
|
||||
|
||||
netUnixStateUnconnected = 1
|
||||
netUnixStateConnecting = 2
|
||||
netUnixStateConnected = 3
|
||||
netUnixStateDisconnected = 4
|
||||
)
|
||||
|
||||
var errInvalidKernelPtrFmt = errors.New("Invalid Num(the kernel table slot number) format")
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// NewNetUnix returns data read from /proc/net/unix.
|
||||
func NewNetUnix() (*NetUnix, error) {
|
||||
fs, err := NewFS(DefaultMountPoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return fs.NewNetUnix()
|
||||
}
|
||||
|
||||
// NewNetUnix returns data read from /proc/net/unix.
|
||||
func (fs FS) NewNetUnix() (*NetUnix, error) {
|
||||
return NewNetUnixByPath(fs.proc.Path("net/unix"))
|
||||
}
|
||||
|
||||
// NewNetUnixByPath returns data read from /proc/net/unix by file path.
|
||||
// It might returns an error with partial parsed data, if an error occur after some data parsed.
|
||||
func NewNetUnixByPath(path string) (*NetUnix, error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
return NewNetUnixByReader(f)
|
||||
}
|
||||
|
||||
// NewNetUnixByReader returns data read from /proc/net/unix by a reader.
|
||||
// It might returns an error with partial parsed data, if an error occur after some data parsed.
|
||||
func NewNetUnixByReader(reader io.Reader) (*NetUnix, error) {
|
||||
nu := &NetUnix{
|
||||
Rows: make([]*NetUnixLine, 0, 32),
|
||||
}
|
||||
scanner := bufio.NewScanner(reader)
|
||||
// Omit the header line.
|
||||
scanner.Scan()
|
||||
header := scanner.Text()
|
||||
// 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(header, "Inode")
|
||||
|
||||
minFieldsCnt := netUnixStaticFieldsCnt
|
||||
if hasInode {
|
||||
minFieldsCnt++
|
||||
}
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
item, err := nu.parseLine(line, hasInode, minFieldsCnt)
|
||||
if err != nil {
|
||||
return nu, err
|
||||
}
|
||||
nu.Rows = append(nu.Rows, item)
|
||||
}
|
||||
|
||||
return nu, scanner.Err()
|
||||
}
|
||||
|
||||
func (u *NetUnix) parseLine(line string, hasInode bool, minFieldsCnt int) (*NetUnixLine, error) {
|
||||
fields := strings.Fields(line)
|
||||
fieldsLen := len(fields)
|
||||
if fieldsLen < minFieldsCnt {
|
||||
return nil, fmt.Errorf(
|
||||
"Parse Unix domain failed: expect at least %d fields but got %d",
|
||||
minFieldsCnt, fieldsLen)
|
||||
}
|
||||
kernelPtr, err := u.parseKernelPtr(fields[netUnixKernelPtrIdx])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Parse Unix domain num(%s) failed: %s", fields[netUnixKernelPtrIdx], err)
|
||||
}
|
||||
users, err := u.parseUsers(fields[netUnixRefCountIdx])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Parse Unix domain ref count(%s) failed: %s", fields[netUnixRefCountIdx], err)
|
||||
}
|
||||
flags, err := u.parseFlags(fields[netUnixFlagsIdx])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Parse Unix domain flags(%s) failed: %s", fields[netUnixFlagsIdx], err)
|
||||
}
|
||||
typ, err := u.parseType(fields[netUnixTypeIdx])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Parse Unix domain type(%s) failed: %s", fields[netUnixTypeIdx], err)
|
||||
}
|
||||
state, err := u.parseState(fields[netUnixStateIdx])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Parse Unix domain state(%s) failed: %s", fields[netUnixStateIdx], err)
|
||||
}
|
||||
var inode uint64
|
||||
if hasInode {
|
||||
inodeStr := fields[netUnixInodeIdx]
|
||||
inode, err = u.parseInode(inodeStr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Parse Unix domain inode(%s) failed: %s", inodeStr, err)
|
||||
}
|
||||
}
|
||||
|
||||
nuLine := &NetUnixLine{
|
||||
KernelPtr: kernelPtr,
|
||||
RefCount: users,
|
||||
Type: typ,
|
||||
Flags: flags,
|
||||
State: state,
|
||||
Inode: inode,
|
||||
}
|
||||
|
||||
// Path field is optional.
|
||||
if fieldsLen > minFieldsCnt {
|
||||
pathIdx := netUnixInodeIdx + 1
|
||||
if !hasInode {
|
||||
pathIdx--
|
||||
}
|
||||
nuLine.Path = fields[pathIdx]
|
||||
}
|
||||
|
||||
return nuLine, nil
|
||||
}
|
||||
|
||||
func (u NetUnix) parseKernelPtr(str string) (string, error) {
|
||||
if !strings.HasSuffix(str, ":") {
|
||||
return "", errInvalidKernelPtrFmt
|
||||
}
|
||||
return str[:len(str)-1], nil
|
||||
}
|
||||
|
||||
func (u NetUnix) parseUsers(hexStr string) (uint64, error) {
|
||||
return strconv.ParseUint(hexStr, 16, 32)
|
||||
}
|
||||
|
||||
func (u NetUnix) parseProtocol(hexStr string) (uint64, error) {
|
||||
return strconv.ParseUint(hexStr, 16, 32)
|
||||
}
|
||||
|
||||
func (u NetUnix) parseType(hexStr string) (NetUnixType, error) {
|
||||
typ, err := strconv.ParseUint(hexStr, 16, 16)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return NetUnixType(typ), nil
|
||||
}
|
||||
|
||||
func (u NetUnix) parseFlags(hexStr string) (NetUnixFlags, error) {
|
||||
flags, err := strconv.ParseUint(hexStr, 16, 32)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return NetUnixFlags(flags), nil
|
||||
}
|
||||
|
||||
func (u NetUnix) parseState(hexStr string) (NetUnixState, error) {
|
||||
st, err := strconv.ParseInt(hexStr, 16, 8)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return NetUnixState(st), nil
|
||||
}
|
||||
|
||||
func (u NetUnix) parseInode(inodeStr string) (uint64, error) {
|
||||
return strconv.ParseUint(inodeStr, 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"
|
||||
}
|
||||
11
vendor/github.com/prometheus/procfs/proc.go
generated
vendored
11
vendor/github.com/prometheus/procfs/proc.go
generated
vendored
@@ -54,7 +54,7 @@ func NewProc(pid int) (Proc, error) {
|
||||
if err != nil {
|
||||
return Proc{}, err
|
||||
}
|
||||
return fs.NewProc(pid)
|
||||
return fs.Proc(pid)
|
||||
}
|
||||
|
||||
// AllProcs returns a list of all currently available processes under /proc.
|
||||
@@ -76,11 +76,18 @@ func (fs FS) Self() (Proc, error) {
|
||||
if err != nil {
|
||||
return Proc{}, err
|
||||
}
|
||||
return fs.NewProc(pid)
|
||||
return fs.Proc(pid)
|
||||
}
|
||||
|
||||
// NewProc returns a process for the given pid.
|
||||
//
|
||||
// Deprecated: use fs.Proc() instead
|
||||
func (fs FS) NewProc(pid int) (Proc, error) {
|
||||
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
|
||||
}
|
||||
|
||||
4
vendor/github.com/prometheus/procfs/proc_io.go
generated
vendored
4
vendor/github.com/prometheus/procfs/proc_io.go
generated
vendored
@@ -39,8 +39,8 @@ type ProcIO struct {
|
||||
CancelledWriteBytes int64
|
||||
}
|
||||
|
||||
// NewIO creates a new ProcIO instance from a given Proc instance.
|
||||
func (p Proc) NewIO() (ProcIO, error) {
|
||||
// IO creates a new ProcIO instance from a given Proc instance.
|
||||
func (p Proc) IO() (ProcIO, error) {
|
||||
pio := ProcIO{}
|
||||
|
||||
f, err := os.Open(p.path("io"))
|
||||
|
||||
7
vendor/github.com/prometheus/procfs/proc_limits.go
generated
vendored
7
vendor/github.com/prometheus/procfs/proc_limits.go
generated
vendored
@@ -78,7 +78,14 @@ var (
|
||||
)
|
||||
|
||||
// NewLimits returns the current soft limits of the process.
|
||||
//
|
||||
// Deprecated: use p.Limits() instead
|
||||
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"))
|
||||
if err != nil {
|
||||
return ProcLimits{}, err
|
||||
|
||||
4
vendor/github.com/prometheus/procfs/proc_ns.go
generated
vendored
4
vendor/github.com/prometheus/procfs/proc_ns.go
generated
vendored
@@ -29,9 +29,9 @@ type Namespace struct {
|
||||
// Namespaces contains all of the namespaces that the process is contained in.
|
||||
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.
|
||||
func (p Proc) NewNamespaces() (Namespaces, error) {
|
||||
func (p Proc) Namespaces() (Namespaces, error) {
|
||||
d, err := os.Open(p.path("ns"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
17
vendor/github.com/prometheus/procfs/proc_psi.go
generated
vendored
17
vendor/github.com/prometheus/procfs/proc_psi.go
generated
vendored
@@ -51,19 +51,10 @@ type PSIStats struct {
|
||||
Full *PSILine
|
||||
}
|
||||
|
||||
// NewPSIStatsForResource reads pressure stall information for the specified
|
||||
// resource. At time of writing this can be either "cpu", "memory" or "io".
|
||||
func NewPSIStatsForResource(resource string) (PSIStats, error) {
|
||||
fs, err := NewFS(DefaultMountPoint)
|
||||
if err != nil {
|
||||
return PSIStats{}, err
|
||||
}
|
||||
|
||||
return fs.NewPSIStatsForResource(resource)
|
||||
}
|
||||
|
||||
// NewPSIStatsForResource reads pressure stall information from /proc/pressure/<resource>
|
||||
func (fs FS) NewPSIStatsForResource(resource string) (PSIStats, error) {
|
||||
// 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) {
|
||||
file, err := os.Open(fs.proc.Path(fmt.Sprintf("%s/%s", "pressure", resource)))
|
||||
if err != nil {
|
||||
return PSIStats{}, fmt.Errorf("psi_stats: unavailable for %s", resource)
|
||||
|
||||
9
vendor/github.com/prometheus/procfs/proc_stat.go
generated
vendored
9
vendor/github.com/prometheus/procfs/proc_stat.go
generated
vendored
@@ -105,7 +105,14 @@ type ProcStat struct {
|
||||
}
|
||||
|
||||
// NewStat returns the current status information of the process.
|
||||
//
|
||||
// Deprecated: use NewStat() instead
|
||||
func (p Proc) NewStat() (ProcStat, error) {
|
||||
return p.Stat()
|
||||
}
|
||||
|
||||
// Stat returns the current status information of the process.
|
||||
func (p Proc) Stat() (ProcStat, error) {
|
||||
f, err := os.Open(p.path("stat"))
|
||||
if err != nil {
|
||||
return ProcStat{}, err
|
||||
@@ -178,7 +185,7 @@ func (s ProcStat) ResidentMemory() int {
|
||||
// StartTime returns the unix timestamp of the process in seconds.
|
||||
func (s ProcStat) StartTime() (float64, error) {
|
||||
fs := FS{proc: s.proc}
|
||||
stat, err := fs.NewStat()
|
||||
stat, err := fs.Stat()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
162
vendor/github.com/prometheus/procfs/proc_status.go
generated
vendored
Normal file
162
vendor/github.com/prometheus/procfs/proc_status.go
generated
vendored
Normal file
@@ -0,0 +1,162 @@
|
||||
// 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"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ProcStat provides status information about the process,
|
||||
// read from /proc/[pid]/stat.
|
||||
type ProcStatus struct {
|
||||
// The process ID.
|
||||
PID int
|
||||
// The process name.
|
||||
Name string
|
||||
|
||||
// Peak virtual memory size.
|
||||
VmPeak uint64
|
||||
// Virtual memory size.
|
||||
VmSize uint64
|
||||
// Locked memory size.
|
||||
VmLck uint64
|
||||
// Pinned memory size.
|
||||
VmPin uint64
|
||||
// Peak resident set size.
|
||||
VmHWM uint64
|
||||
// Resident set size (sum of RssAnnon RssFile and RssShmem).
|
||||
VmRSS uint64
|
||||
// Size of resident anonymous memory.
|
||||
RssAnon uint64
|
||||
// Size of resident file mappings.
|
||||
RssFile uint64
|
||||
// Size of resident shared memory.
|
||||
RssShmem uint64
|
||||
// Size of data segments.
|
||||
VmData uint64
|
||||
// Size of stack segments.
|
||||
VmStk uint64
|
||||
// Size of text segments.
|
||||
VmExe uint64
|
||||
// Shared library code size.
|
||||
VmLib uint64
|
||||
// Page table entries size.
|
||||
VmPTE uint64
|
||||
// Size of second-level page tables.
|
||||
VmPMD uint64
|
||||
// Swapped-out virtual memory size by anonymous private.
|
||||
VmSwap uint64
|
||||
// Size of hugetlb memory portions
|
||||
HugetlbPages uint64
|
||||
|
||||
// Number of voluntary context switches.
|
||||
VoluntaryCtxtSwitches uint64
|
||||
// Number of involuntary context switches.
|
||||
NonVoluntaryCtxtSwitches uint64
|
||||
}
|
||||
|
||||
// NewStatus returns the current status information of the process.
|
||||
func (p Proc) NewStatus() (ProcStatus, error) {
|
||||
f, err := os.Open(p.path("status"))
|
||||
if err != nil {
|
||||
return ProcStatus{}, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
data, err := ioutil.ReadAll(f)
|
||||
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 "Name":
|
||||
s.Name = vString
|
||||
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
|
||||
}
|
||||
36
vendor/github.com/prometheus/procfs/stat.go
generated
vendored
36
vendor/github.com/prometheus/procfs/stat.go
generated
vendored
@@ -20,6 +20,8 @@ import (
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/prometheus/procfs/internal/fs"
|
||||
)
|
||||
|
||||
// CPUStat shows how much time the cpu spend in various stages.
|
||||
@@ -78,16 +80,6 @@ type Stat struct {
|
||||
SoftIRQ SoftIRQStat
|
||||
}
|
||||
|
||||
// NewStat returns kernel/system statistics read from /proc/stat.
|
||||
func NewStat() (Stat, error) {
|
||||
fs, err := NewFS(DefaultMountPoint)
|
||||
if err != nil {
|
||||
return Stat{}, err
|
||||
}
|
||||
|
||||
return fs.NewStat()
|
||||
}
|
||||
|
||||
// Parse a cpu statistics line and returns the CPUStat struct plus the cpu id (or -1 for the overall sum).
|
||||
func parseCPUStat(line string) (CPUStat, int64, error) {
|
||||
cpuStat := CPUStat{}
|
||||
@@ -149,9 +141,29 @@ func parseSoftIRQStat(line string) (SoftIRQStat, uint64, error) {
|
||||
return softIRQStat, total, nil
|
||||
}
|
||||
|
||||
// NewStat returns an information about current kernel/system statistics.
|
||||
// NewStat returns information about current cpu/process statistics.
|
||||
// See https://www.kernel.org/doc/Documentation/filesystems/proc.txt
|
||||
//
|
||||
// Deprecated: use fs.Stat() instead
|
||||
func NewStat() (Stat, error) {
|
||||
fs, err := NewFS(fs.DefaultProcMountPoint)
|
||||
if err != nil {
|
||||
return Stat{}, err
|
||||
}
|
||||
return fs.Stat()
|
||||
}
|
||||
|
||||
// NewStat returns information about current cpu/process statistics.
|
||||
// See https://www.kernel.org/doc/Documentation/filesystems/proc.txt
|
||||
//
|
||||
// Deprecated: use fs.Stat() instead
|
||||
func (fs FS) NewStat() (Stat, error) {
|
||||
// See https://www.kernel.org/doc/Documentation/filesystems/proc.txt
|
||||
return fs.Stat()
|
||||
}
|
||||
|
||||
// Stat returns information about current cpu/process statistics.
|
||||
// See https://www.kernel.org/doc/Documentation/filesystems/proc.txt
|
||||
func (fs FS) Stat() (Stat, error) {
|
||||
|
||||
f, err := os.Open(fs.proc.Path("stat"))
|
||||
if err != nil {
|
||||
|
||||
42
vendor/github.com/prometheus/procfs/ttar
generated
vendored
42
vendor/github.com/prometheus/procfs/ttar
generated
vendored
@@ -86,8 +86,10 @@ Usage: $bname [-C <DIR>] -c -f <ARCHIVE> <FILE...> (create archive)
|
||||
$bname [-C <DIR>] -x -f <ARCHIVE> (extract archive)
|
||||
|
||||
Options:
|
||||
-C <DIR> (change directory)
|
||||
-v (verbose)
|
||||
-C <DIR> (change directory)
|
||||
-v (verbose)
|
||||
--recursive-unlink (recursively delete existing directory if path
|
||||
collides with file or directory to extract)
|
||||
|
||||
Example: Change to sysfs directory, create ttar file from fixtures directory
|
||||
$bname -C sysfs -c -f sysfs/fixtures.ttar fixtures/
|
||||
@@ -111,8 +113,9 @@ function set_cmd {
|
||||
}
|
||||
|
||||
unset VERBOSE
|
||||
unset RECURSIVE_UNLINK
|
||||
|
||||
while getopts :cf:htxvC: opt; do
|
||||
while getopts :cf:-:htxvC: opt; do
|
||||
case $opt in
|
||||
c)
|
||||
set_cmd "create"
|
||||
@@ -136,6 +139,18 @@ while getopts :cf:htxvC: opt; do
|
||||
C)
|
||||
CDIR=$OPTARG
|
||||
;;
|
||||
-)
|
||||
case $OPTARG in
|
||||
recursive-unlink)
|
||||
RECURSIVE_UNLINK="yes"
|
||||
;;
|
||||
*)
|
||||
echo -e "Error: invalid option -$OPTARG"
|
||||
echo
|
||||
usage 1
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
*)
|
||||
echo >&2 "ERROR: invalid option -$OPTARG"
|
||||
echo
|
||||
@@ -212,16 +227,16 @@ function extract {
|
||||
local eof_without_newline
|
||||
if [ "$size" -gt 0 ]; then
|
||||
if [[ "$line" =~ [^\\]EOF ]]; then
|
||||
# An EOF not preceeded by a backslash indicates that the line
|
||||
# An EOF not preceded by a backslash indicates that the line
|
||||
# does not end with a newline
|
||||
eof_without_newline=1
|
||||
else
|
||||
eof_without_newline=0
|
||||
fi
|
||||
# Replace NULLBYTE with null byte if at beginning of line
|
||||
# Replace NULLBYTE with null byte unless preceeded by backslash
|
||||
# Replace NULLBYTE with null byte unless preceded by backslash
|
||||
# Remove one backslash in front of NULLBYTE (if any)
|
||||
# Remove EOF unless preceeded by backslash
|
||||
# Remove EOF unless preceded by backslash
|
||||
# Remove one backslash in front of EOF
|
||||
if [ $USE_PYTHON -eq 1 ]; then
|
||||
echo -n "$line" | python -c "$PYTHON_EXTRACT_FILTER" >> "$path"
|
||||
@@ -245,7 +260,16 @@ function extract {
|
||||
fi
|
||||
if [[ $line =~ ^Path:\ (.*)$ ]]; then
|
||||
path=${BASH_REMATCH[1]}
|
||||
if [ -e "$path" ] || [ -L "$path" ]; then
|
||||
if [ -L "$path" ]; then
|
||||
rm "$path"
|
||||
elif [ -d "$path" ]; then
|
||||
if [ "${RECURSIVE_UNLINK:-}" == "yes" ]; then
|
||||
rm -r "$path"
|
||||
else
|
||||
# Safe because symlinks to directories are dealt with above
|
||||
rmdir "$path"
|
||||
fi
|
||||
elif [ -e "$path" ]; then
|
||||
rm "$path"
|
||||
fi
|
||||
elif [[ $line =~ ^Lines:\ (.*)$ ]]; then
|
||||
@@ -338,8 +362,8 @@ function _create {
|
||||
else
|
||||
< "$file" \
|
||||
sed 's/EOF/\\EOF/g;
|
||||
s/NULLBYTE/\\NULLBYTE/g;
|
||||
s/\x0/NULLBYTE/g;
|
||||
s/NULLBYTE/\\NULLBYTE/g;
|
||||
s/\x0/NULLBYTE/g;
|
||||
'
|
||||
fi
|
||||
if [[ "$eof_without_newline" -eq 1 ]]; then
|
||||
|
||||
201
vendor/github.com/prometheus/prometheus/LICENSE
generated
vendored
Normal file
201
vendor/github.com/prometheus/prometheus/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
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.
|
||||
87
vendor/github.com/prometheus/prometheus/NOTICE
generated
vendored
Normal file
87
vendor/github.com/prometheus/prometheus/NOTICE
generated
vendored
Normal file
@@ -0,0 +1,87 @@
|
||||
The Prometheus systems and service monitoring server
|
||||
Copyright 2012-2015 The Prometheus Authors
|
||||
|
||||
This product includes software developed at
|
||||
SoundCloud Ltd. (http://soundcloud.com/).
|
||||
|
||||
|
||||
The following components are included in this product:
|
||||
|
||||
Bootstrap
|
||||
http://getbootstrap.com
|
||||
Copyright 2011-2014 Twitter, Inc.
|
||||
Licensed under the MIT License
|
||||
|
||||
bootstrap3-typeahead.js
|
||||
https://github.com/bassjobsen/Bootstrap-3-Typeahead
|
||||
Original written by @mdo and @fat
|
||||
Copyright 2014 Bass Jobsen @bassjobsen
|
||||
Licensed under the Apache License, Version 2.0
|
||||
|
||||
fuzzy
|
||||
https://github.com/mattyork/fuzzy
|
||||
Original written by @mattyork
|
||||
Copyright 2012 Matt York
|
||||
Licensed under the MIT License
|
||||
|
||||
bootstrap-datetimepicker.js
|
||||
https://github.com/Eonasdan/bootstrap-datetimepicker
|
||||
Copyright 2015 Jonathan Peterson (@Eonasdan)
|
||||
Licensed under the MIT License
|
||||
|
||||
moment.js
|
||||
https://github.com/moment/moment/
|
||||
Copyright JS Foundation and other contributors
|
||||
Licensed under the MIT License
|
||||
|
||||
Rickshaw
|
||||
https://github.com/shutterstock/rickshaw
|
||||
Copyright 2011-2014 by Shutterstock Images, LLC
|
||||
See https://github.com/shutterstock/rickshaw/blob/master/LICENSE for license details
|
||||
|
||||
mustache.js
|
||||
https://github.com/janl/mustache.js
|
||||
Copyright 2009 Chris Wanstrath (Ruby)
|
||||
Copyright 2010-2014 Jan Lehnardt (JavaScript)
|
||||
Copyright 2010-2015 The mustache.js community
|
||||
Licensed under the MIT License
|
||||
|
||||
jQuery
|
||||
https://jquery.org
|
||||
Copyright jQuery Foundation and other contributors
|
||||
Licensed under the MIT License
|
||||
|
||||
Go support for Protocol Buffers - Google's data interchange format
|
||||
http://github.com/golang/protobuf/
|
||||
Copyright 2010 The Go Authors
|
||||
See source code for license details.
|
||||
|
||||
Go support for leveled logs, analogous to
|
||||
https://code.google.com/p/google-glog/
|
||||
Copyright 2013 Google Inc.
|
||||
Licensed under the Apache License, Version 2.0
|
||||
|
||||
Support for streaming Protocol Buffer messages for the Go language (golang).
|
||||
https://github.com/matttproud/golang_protobuf_extensions
|
||||
Copyright 2013 Matt T. Proud
|
||||
Licensed under the Apache License, Version 2.0
|
||||
|
||||
DNS library in Go
|
||||
http://miek.nl/posts/2014/Aug/16/go-dns-package/
|
||||
Copyright 2009 The Go Authors, 2011 Miek Gieben
|
||||
See https://github.com/miekg/dns/blob/master/LICENSE for license details.
|
||||
|
||||
LevelDB key/value database in Go
|
||||
https://github.com/syndtr/goleveldb
|
||||
Copyright 2012 Suryandaru Triandana
|
||||
See https://github.com/syndtr/goleveldb/blob/master/LICENSE for license details.
|
||||
|
||||
gosnappy - a fork of code.google.com/p/snappy-go
|
||||
https://github.com/syndtr/gosnappy
|
||||
Copyright 2011 The Snappy-Go Authors
|
||||
See https://github.com/syndtr/gosnappy/blob/master/LICENSE for license details.
|
||||
|
||||
go-zookeeper - Native ZooKeeper client for Go
|
||||
https://github.com/samuel/go-zookeeper
|
||||
Copyright (c) 2013, Samuel Stauffer <samuel@descolada.com>
|
||||
See https://github.com/samuel/go-zookeeper/blob/master/LICENSE for license details.
|
||||
317
vendor/github.com/prometheus/prometheus/promql/ast.go
generated
vendored
Normal file
317
vendor/github.com/prometheus/prometheus/promql/ast.go
generated
vendored
Normal file
@@ -0,0 +1,317 @@
|
||||
// Copyright 2015 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 promql
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/common/model"
|
||||
|
||||
"github.com/prometheus/prometheus/storage/local"
|
||||
"github.com/prometheus/prometheus/storage/metric"
|
||||
)
|
||||
|
||||
// Node is a generic interface for all nodes in an AST.
|
||||
//
|
||||
// Whenever numerous nodes are listed such as in a switch-case statement
|
||||
// or a chain of function definitions (e.g. String(), expr(), etc.) convention is
|
||||
// to list them as follows:
|
||||
//
|
||||
// - Statements
|
||||
// - statement types (alphabetical)
|
||||
// - ...
|
||||
// - Expressions
|
||||
// - expression types (alphabetical)
|
||||
// - ...
|
||||
//
|
||||
type Node interface {
|
||||
// String representation of the node that returns the given node when parsed
|
||||
// as part of a valid query.
|
||||
String() string
|
||||
}
|
||||
|
||||
// Statement is a generic interface for all statements.
|
||||
type Statement interface {
|
||||
Node
|
||||
|
||||
// stmt ensures that no other type accidentally implements the interface
|
||||
stmt()
|
||||
}
|
||||
|
||||
// Statements is a list of statement nodes that implements Node.
|
||||
type Statements []Statement
|
||||
|
||||
// AlertStmt represents an added alert rule.
|
||||
type AlertStmt struct {
|
||||
Name string
|
||||
Expr Expr
|
||||
Duration time.Duration
|
||||
Labels model.LabelSet
|
||||
Annotations model.LabelSet
|
||||
}
|
||||
|
||||
// EvalStmt holds an expression and information on the range it should
|
||||
// be evaluated on.
|
||||
type EvalStmt struct {
|
||||
Expr Expr // Expression to be evaluated.
|
||||
|
||||
// The time boundaries for the evaluation. If Start equals End an instant
|
||||
// is evaluated.
|
||||
Start, End model.Time
|
||||
// Time between two evaluated instants for the range [Start:End].
|
||||
Interval time.Duration
|
||||
}
|
||||
|
||||
// RecordStmt represents an added recording rule.
|
||||
type RecordStmt struct {
|
||||
Name string
|
||||
Expr Expr
|
||||
Labels model.LabelSet
|
||||
}
|
||||
|
||||
func (*AlertStmt) stmt() {}
|
||||
func (*EvalStmt) stmt() {}
|
||||
func (*RecordStmt) stmt() {}
|
||||
|
||||
// Expr is a generic interface for all expression types.
|
||||
type Expr interface {
|
||||
Node
|
||||
|
||||
// Type returns the type the expression evaluates to. It does not perform
|
||||
// in-depth checks as this is done at parsing-time.
|
||||
Type() model.ValueType
|
||||
// expr ensures that no other types accidentally implement the interface.
|
||||
expr()
|
||||
}
|
||||
|
||||
// Expressions is a list of expression nodes that implements Node.
|
||||
type Expressions []Expr
|
||||
|
||||
// AggregateExpr represents an aggregation operation on a vector.
|
||||
type AggregateExpr struct {
|
||||
Op itemType // The used aggregation operation.
|
||||
Expr Expr // The vector expression over which is aggregated.
|
||||
Param Expr // Parameter used by some aggregators.
|
||||
Grouping model.LabelNames // The labels by which to group the vector.
|
||||
Without bool // Whether to drop the given labels rather than keep them.
|
||||
KeepCommonLabels bool // Whether to keep common labels among result elements.
|
||||
}
|
||||
|
||||
// BinaryExpr represents a binary expression between two child expressions.
|
||||
type BinaryExpr struct {
|
||||
Op itemType // The operation of the expression.
|
||||
LHS, RHS Expr // The operands on the respective sides of the operator.
|
||||
|
||||
// The matching behavior for the operation if both operands are vectors.
|
||||
// If they are not this field is nil.
|
||||
VectorMatching *VectorMatching
|
||||
|
||||
// If a comparison operator, return 0/1 rather than filtering.
|
||||
ReturnBool bool
|
||||
}
|
||||
|
||||
// Call represents a function call.
|
||||
type Call struct {
|
||||
Func *Function // The function that was called.
|
||||
Args Expressions // Arguments used in the call.
|
||||
}
|
||||
|
||||
// MatrixSelector represents a matrix selection.
|
||||
type MatrixSelector struct {
|
||||
Name string
|
||||
Range time.Duration
|
||||
Offset time.Duration
|
||||
LabelMatchers metric.LabelMatchers
|
||||
|
||||
// The series iterators are populated at query preparation time.
|
||||
iterators []local.SeriesIterator
|
||||
}
|
||||
|
||||
// NumberLiteral represents a number.
|
||||
type NumberLiteral struct {
|
||||
Val model.SampleValue
|
||||
}
|
||||
|
||||
// ParenExpr wraps an expression so it cannot be disassembled as a consequence
|
||||
// of operator precedence.
|
||||
type ParenExpr struct {
|
||||
Expr Expr
|
||||
}
|
||||
|
||||
// StringLiteral represents a string.
|
||||
type StringLiteral struct {
|
||||
Val string
|
||||
}
|
||||
|
||||
// UnaryExpr represents a unary operation on another expression.
|
||||
// Currently unary operations are only supported for scalars.
|
||||
type UnaryExpr struct {
|
||||
Op itemType
|
||||
Expr Expr
|
||||
}
|
||||
|
||||
// VectorSelector represents a vector selection.
|
||||
type VectorSelector struct {
|
||||
Name string
|
||||
Offset time.Duration
|
||||
LabelMatchers metric.LabelMatchers
|
||||
|
||||
// The series iterators are populated at query preparation time.
|
||||
iterators []local.SeriesIterator
|
||||
}
|
||||
|
||||
func (e *AggregateExpr) Type() model.ValueType { return model.ValVector }
|
||||
func (e *Call) Type() model.ValueType { return e.Func.ReturnType }
|
||||
func (e *MatrixSelector) Type() model.ValueType { return model.ValMatrix }
|
||||
func (e *NumberLiteral) Type() model.ValueType { return model.ValScalar }
|
||||
func (e *ParenExpr) Type() model.ValueType { return e.Expr.Type() }
|
||||
func (e *StringLiteral) Type() model.ValueType { return model.ValString }
|
||||
func (e *UnaryExpr) Type() model.ValueType { return e.Expr.Type() }
|
||||
func (e *VectorSelector) Type() model.ValueType { return model.ValVector }
|
||||
func (e *BinaryExpr) Type() model.ValueType {
|
||||
if e.LHS.Type() == model.ValScalar && e.RHS.Type() == model.ValScalar {
|
||||
return model.ValScalar
|
||||
}
|
||||
return model.ValVector
|
||||
}
|
||||
|
||||
func (*AggregateExpr) expr() {}
|
||||
func (*BinaryExpr) expr() {}
|
||||
func (*Call) expr() {}
|
||||
func (*MatrixSelector) expr() {}
|
||||
func (*NumberLiteral) expr() {}
|
||||
func (*ParenExpr) expr() {}
|
||||
func (*StringLiteral) expr() {}
|
||||
func (*UnaryExpr) expr() {}
|
||||
func (*VectorSelector) expr() {}
|
||||
|
||||
// VectorMatchCardinality describes the cardinality relationship
|
||||
// of two vectors in a binary operation.
|
||||
type VectorMatchCardinality int
|
||||
|
||||
const (
|
||||
CardOneToOne VectorMatchCardinality = iota
|
||||
CardManyToOne
|
||||
CardOneToMany
|
||||
CardManyToMany
|
||||
)
|
||||
|
||||
func (vmc VectorMatchCardinality) String() string {
|
||||
switch vmc {
|
||||
case CardOneToOne:
|
||||
return "one-to-one"
|
||||
case CardManyToOne:
|
||||
return "many-to-one"
|
||||
case CardOneToMany:
|
||||
return "one-to-many"
|
||||
case CardManyToMany:
|
||||
return "many-to-many"
|
||||
}
|
||||
panic("promql.VectorMatchCardinality.String: unknown match cardinality")
|
||||
}
|
||||
|
||||
// VectorMatching describes how elements from two vectors in a binary
|
||||
// operation are supposed to be matched.
|
||||
type VectorMatching struct {
|
||||
// The cardinality of the two vectors.
|
||||
Card VectorMatchCardinality
|
||||
// MatchingLabels contains the labels which define equality of a pair of
|
||||
// elements from the vectors.
|
||||
MatchingLabels model.LabelNames
|
||||
// On includes the given label names from matching,
|
||||
// rather than excluding them.
|
||||
On bool
|
||||
// Include contains additional labels that should be included in
|
||||
// the result from the side with the lower cardinality.
|
||||
Include model.LabelNames
|
||||
}
|
||||
|
||||
// Visitor allows visiting a Node and its child nodes. The Visit method is
|
||||
// invoked for each node encountered by Walk. If the result visitor w is not
|
||||
// nil, Walk visits each of the children of node with the visitor w, followed
|
||||
// by a call of w.Visit(nil).
|
||||
type Visitor interface {
|
||||
Visit(node Node) (w Visitor)
|
||||
}
|
||||
|
||||
// Walk traverses an AST in depth-first order: It starts by calling
|
||||
// v.Visit(node); node must not be nil. If the visitor w returned by
|
||||
// v.Visit(node) is not nil, Walk is invoked recursively with visitor
|
||||
// w for each of the non-nil children of node, followed by a call of
|
||||
// w.Visit(nil).
|
||||
func Walk(v Visitor, node Node) {
|
||||
if v = v.Visit(node); v == nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch n := node.(type) {
|
||||
case Statements:
|
||||
for _, s := range n {
|
||||
Walk(v, s)
|
||||
}
|
||||
case *AlertStmt:
|
||||
Walk(v, n.Expr)
|
||||
|
||||
case *EvalStmt:
|
||||
Walk(v, n.Expr)
|
||||
|
||||
case *RecordStmt:
|
||||
Walk(v, n.Expr)
|
||||
|
||||
case Expressions:
|
||||
for _, e := range n {
|
||||
Walk(v, e)
|
||||
}
|
||||
case *AggregateExpr:
|
||||
Walk(v, n.Expr)
|
||||
|
||||
case *BinaryExpr:
|
||||
Walk(v, n.LHS)
|
||||
Walk(v, n.RHS)
|
||||
|
||||
case *Call:
|
||||
Walk(v, n.Args)
|
||||
|
||||
case *ParenExpr:
|
||||
Walk(v, n.Expr)
|
||||
|
||||
case *UnaryExpr:
|
||||
Walk(v, n.Expr)
|
||||
|
||||
case *MatrixSelector, *NumberLiteral, *StringLiteral, *VectorSelector:
|
||||
// nothing to do
|
||||
|
||||
default:
|
||||
panic(fmt.Errorf("promql.Walk: unhandled node type %T", node))
|
||||
}
|
||||
|
||||
v.Visit(nil)
|
||||
}
|
||||
|
||||
type inspector func(Node) bool
|
||||
|
||||
func (f inspector) Visit(node Node) Visitor {
|
||||
if f(node) {
|
||||
return f
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Inspect traverses an AST in depth-first order: It starts by calling
|
||||
// f(node); node must not be nil. If f returns true, Inspect invokes f
|
||||
// for all the non-nil children of node, recursively.
|
||||
func Inspect(node Node, f func(Node) bool) {
|
||||
Walk(inspector(f), node)
|
||||
}
|
||||
1436
vendor/github.com/prometheus/prometheus/promql/engine.go
generated
vendored
Normal file
1436
vendor/github.com/prometheus/prometheus/promql/engine.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1338
vendor/github.com/prometheus/prometheus/promql/functions.go
generated
vendored
Normal file
1338
vendor/github.com/prometheus/prometheus/promql/functions.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
87
vendor/github.com/prometheus/prometheus/promql/fuzz.go
generated
vendored
Normal file
87
vendor/github.com/prometheus/prometheus/promql/fuzz.go
generated
vendored
Normal file
@@ -0,0 +1,87 @@
|
||||
// Copyright 2015 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.
|
||||
|
||||
// Only build when go-fuzz is in use
|
||||
// +build gofuzz
|
||||
|
||||
package promql
|
||||
|
||||
// PromQL parser fuzzing instrumentation for use with
|
||||
// https://github.com/dvyukov/go-fuzz.
|
||||
//
|
||||
// Fuzz each parser by building appropriately instrumented parser, ex.
|
||||
// FuzzParseMetric and execute it with it's
|
||||
//
|
||||
// go-fuzz-build -func FuzzParseMetric -o FuzzParseMetric.zip github.com/prometheus/prometheus/promql
|
||||
//
|
||||
// And then run the tests with the appropriate inputs
|
||||
//
|
||||
// go-fuzz -bin FuzzParseMetric.zip -workdir fuzz-data/ParseMetric
|
||||
//
|
||||
// Further input samples should go in the folders fuzz-data/ParseMetric/corpus.
|
||||
//
|
||||
// Repeat for ParseMetricSeletion, ParseExpr and ParseStmt.
|
||||
|
||||
// Tuning which value is returned from Fuzz*-functions has a strong influence
|
||||
// on how quick the fuzzer converges on "interesting" cases. At least try
|
||||
// switching between fuzzMeh (= included in corpus, but not a priority) and
|
||||
// fuzzDiscard (=don't use this input for re-building later inputs) when
|
||||
// experimenting.
|
||||
const (
|
||||
fuzzInteresting = 1
|
||||
fuzzMeh = 0
|
||||
fuzzDiscard = -1
|
||||
)
|
||||
|
||||
// Fuzz the metric parser.
|
||||
//
|
||||
// Note that his is not the parser for the text-based exposition-format; that
|
||||
// lives in github.com/prometheus/client_golang/text.
|
||||
func FuzzParseMetric(in []byte) int {
|
||||
_, err := ParseMetric(string(in))
|
||||
if err == nil {
|
||||
return fuzzInteresting
|
||||
}
|
||||
|
||||
return fuzzMeh
|
||||
}
|
||||
|
||||
// Fuzz the metric selector parser.
|
||||
func FuzzParseMetricSelector(in []byte) int {
|
||||
_, err := ParseMetricSelector(string(in))
|
||||
if err == nil {
|
||||
return fuzzInteresting
|
||||
}
|
||||
|
||||
return fuzzMeh
|
||||
}
|
||||
|
||||
// Fuzz the expression parser.
|
||||
func FuzzParseExpr(in []byte) int {
|
||||
_, err := ParseExpr(string(in))
|
||||
if err == nil {
|
||||
return fuzzInteresting
|
||||
}
|
||||
|
||||
return fuzzMeh
|
||||
}
|
||||
|
||||
// Fuzz the parser.
|
||||
func FuzzParseStmts(in []byte) int {
|
||||
_, err := ParseStmts(string(in))
|
||||
if err == nil {
|
||||
return fuzzInteresting
|
||||
}
|
||||
|
||||
return fuzzMeh
|
||||
}
|
||||
908
vendor/github.com/prometheus/prometheus/promql/lex.go
generated
vendored
Normal file
908
vendor/github.com/prometheus/prometheus/promql/lex.go
generated
vendored
Normal file
@@ -0,0 +1,908 @@
|
||||
// Copyright 2015 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 promql
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// item represents a token or text string returned from the scanner.
|
||||
type item struct {
|
||||
typ itemType // The type of this item.
|
||||
pos Pos // The starting position, in bytes, of this item in the input string.
|
||||
val string // The value of this item.
|
||||
}
|
||||
|
||||
// String returns a descriptive string for the item.
|
||||
func (i item) String() string {
|
||||
switch {
|
||||
case i.typ == itemEOF:
|
||||
return "EOF"
|
||||
case i.typ == itemError:
|
||||
return i.val
|
||||
case i.typ == itemIdentifier || i.typ == itemMetricIdentifier:
|
||||
return fmt.Sprintf("%q", i.val)
|
||||
case i.typ.isKeyword():
|
||||
return fmt.Sprintf("<%s>", i.val)
|
||||
case i.typ.isOperator():
|
||||
return fmt.Sprintf("<op:%s>", i.val)
|
||||
case i.typ.isAggregator():
|
||||
return fmt.Sprintf("<aggr:%s>", i.val)
|
||||
case len(i.val) > 10:
|
||||
return fmt.Sprintf("%.10q...", i.val)
|
||||
}
|
||||
return fmt.Sprintf("%q", i.val)
|
||||
}
|
||||
|
||||
// isOperator returns true if the item corresponds to a arithmetic or set operator.
|
||||
// Returns false otherwise.
|
||||
func (i itemType) isOperator() bool { return i > operatorsStart && i < operatorsEnd }
|
||||
|
||||
// isAggregator returns true if the item belongs to the aggregator functions.
|
||||
// Returns false otherwise
|
||||
func (i itemType) isAggregator() bool { return i > aggregatorsStart && i < aggregatorsEnd }
|
||||
|
||||
// isAggregator returns true if the item is an aggregator that takes a parameter.
|
||||
// Returns false otherwise
|
||||
func (i itemType) isAggregatorWithParam() bool {
|
||||
return i == itemTopK || i == itemBottomK || i == itemCountValues || i == itemQuantile
|
||||
}
|
||||
|
||||
// isKeyword returns true if the item corresponds to a keyword.
|
||||
// Returns false otherwise.
|
||||
func (i itemType) isKeyword() bool { return i > keywordsStart && i < keywordsEnd }
|
||||
|
||||
// isCompairsonOperator returns true if the item corresponds to a comparison operator.
|
||||
// Returns false otherwise.
|
||||
func (i itemType) isComparisonOperator() bool {
|
||||
switch i {
|
||||
case itemEQL, itemNEQ, itemLTE, itemLSS, itemGTE, itemGTR:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// isSetOperator returns whether the item corresponds to a set operator.
|
||||
func (i itemType) isSetOperator() bool {
|
||||
switch i {
|
||||
case itemLAND, itemLOR, itemLUnless:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// LowestPrec is a constant for operator precedence in expressions.
|
||||
const LowestPrec = 0 // Non-operators.
|
||||
|
||||
// Precedence returns the operator precedence of the binary
|
||||
// operator op. If op is not a binary operator, the result
|
||||
// is LowestPrec.
|
||||
func (i itemType) precedence() int {
|
||||
switch i {
|
||||
case itemLOR:
|
||||
return 1
|
||||
case itemLAND, itemLUnless:
|
||||
return 2
|
||||
case itemEQL, itemNEQ, itemLTE, itemLSS, itemGTE, itemGTR:
|
||||
return 3
|
||||
case itemADD, itemSUB:
|
||||
return 4
|
||||
case itemMUL, itemDIV, itemMOD:
|
||||
return 5
|
||||
case itemPOW:
|
||||
return 6
|
||||
default:
|
||||
return LowestPrec
|
||||
}
|
||||
}
|
||||
|
||||
func (i itemType) isRightAssociative() bool {
|
||||
switch i {
|
||||
case itemPOW:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
type itemType int
|
||||
|
||||
const (
|
||||
itemError itemType = iota // Error occurred, value is error message
|
||||
itemEOF
|
||||
itemComment
|
||||
itemIdentifier
|
||||
itemMetricIdentifier
|
||||
itemLeftParen
|
||||
itemRightParen
|
||||
itemLeftBrace
|
||||
itemRightBrace
|
||||
itemLeftBracket
|
||||
itemRightBracket
|
||||
itemComma
|
||||
itemAssign
|
||||
itemSemicolon
|
||||
itemString
|
||||
itemNumber
|
||||
itemDuration
|
||||
itemBlank
|
||||
itemTimes
|
||||
|
||||
operatorsStart
|
||||
// Operators.
|
||||
itemSUB
|
||||
itemADD
|
||||
itemMUL
|
||||
itemMOD
|
||||
itemDIV
|
||||
itemLAND
|
||||
itemLOR
|
||||
itemLUnless
|
||||
itemEQL
|
||||
itemNEQ
|
||||
itemLTE
|
||||
itemLSS
|
||||
itemGTE
|
||||
itemGTR
|
||||
itemEQLRegex
|
||||
itemNEQRegex
|
||||
itemPOW
|
||||
operatorsEnd
|
||||
|
||||
aggregatorsStart
|
||||
// Aggregators.
|
||||
itemAvg
|
||||
itemCount
|
||||
itemSum
|
||||
itemMin
|
||||
itemMax
|
||||
itemStddev
|
||||
itemStdvar
|
||||
itemTopK
|
||||
itemBottomK
|
||||
itemCountValues
|
||||
itemQuantile
|
||||
aggregatorsEnd
|
||||
|
||||
keywordsStart
|
||||
// Keywords.
|
||||
itemAlert
|
||||
itemIf
|
||||
itemFor
|
||||
itemLabels
|
||||
itemAnnotations
|
||||
itemKeepCommon
|
||||
itemOffset
|
||||
itemBy
|
||||
itemWithout
|
||||
itemOn
|
||||
itemIgnoring
|
||||
itemGroupLeft
|
||||
itemGroupRight
|
||||
itemBool
|
||||
keywordsEnd
|
||||
)
|
||||
|
||||
var key = map[string]itemType{
|
||||
// Operators.
|
||||
"and": itemLAND,
|
||||
"or": itemLOR,
|
||||
"unless": itemLUnless,
|
||||
|
||||
// Aggregators.
|
||||
"sum": itemSum,
|
||||
"avg": itemAvg,
|
||||
"count": itemCount,
|
||||
"min": itemMin,
|
||||
"max": itemMax,
|
||||
"stddev": itemStddev,
|
||||
"stdvar": itemStdvar,
|
||||
"topk": itemTopK,
|
||||
"bottomk": itemBottomK,
|
||||
"count_values": itemCountValues,
|
||||
"quantile": itemQuantile,
|
||||
|
||||
// Keywords.
|
||||
"alert": itemAlert,
|
||||
"if": itemIf,
|
||||
"for": itemFor,
|
||||
"labels": itemLabels,
|
||||
"annotations": itemAnnotations,
|
||||
"offset": itemOffset,
|
||||
"by": itemBy,
|
||||
"without": itemWithout,
|
||||
"keep_common": itemKeepCommon,
|
||||
"on": itemOn,
|
||||
"ignoring": itemIgnoring,
|
||||
"group_left": itemGroupLeft,
|
||||
"group_right": itemGroupRight,
|
||||
"bool": itemBool,
|
||||
}
|
||||
|
||||
// These are the default string representations for common items. It does not
|
||||
// imply that those are the only character sequences that can be lexed to such an item.
|
||||
var itemTypeStr = map[itemType]string{
|
||||
itemLeftParen: "(",
|
||||
itemRightParen: ")",
|
||||
itemLeftBrace: "{",
|
||||
itemRightBrace: "}",
|
||||
itemLeftBracket: "[",
|
||||
itemRightBracket: "]",
|
||||
itemComma: ",",
|
||||
itemAssign: "=",
|
||||
itemSemicolon: ";",
|
||||
itemBlank: "_",
|
||||
itemTimes: "x",
|
||||
|
||||
itemSUB: "-",
|
||||
itemADD: "+",
|
||||
itemMUL: "*",
|
||||
itemMOD: "%",
|
||||
itemDIV: "/",
|
||||
itemEQL: "==",
|
||||
itemNEQ: "!=",
|
||||
itemLTE: "<=",
|
||||
itemLSS: "<",
|
||||
itemGTE: ">=",
|
||||
itemGTR: ">",
|
||||
itemEQLRegex: "=~",
|
||||
itemNEQRegex: "!~",
|
||||
itemPOW: "^",
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Add keywords to item type strings.
|
||||
for s, ty := range key {
|
||||
itemTypeStr[ty] = s
|
||||
}
|
||||
// Special numbers.
|
||||
key["inf"] = itemNumber
|
||||
key["nan"] = itemNumber
|
||||
}
|
||||
|
||||
func (i itemType) String() string {
|
||||
if s, ok := itemTypeStr[i]; ok {
|
||||
return s
|
||||
}
|
||||
return fmt.Sprintf("<item %d>", i)
|
||||
}
|
||||
|
||||
func (i item) desc() string {
|
||||
if _, ok := itemTypeStr[i.typ]; ok {
|
||||
return i.String()
|
||||
}
|
||||
if i.typ == itemEOF {
|
||||
return i.typ.desc()
|
||||
}
|
||||
return fmt.Sprintf("%s %s", i.typ.desc(), i)
|
||||
}
|
||||
|
||||
func (i itemType) desc() string {
|
||||
switch i {
|
||||
case itemError:
|
||||
return "error"
|
||||
case itemEOF:
|
||||
return "end of input"
|
||||
case itemComment:
|
||||
return "comment"
|
||||
case itemIdentifier:
|
||||
return "identifier"
|
||||
case itemMetricIdentifier:
|
||||
return "metric identifier"
|
||||
case itemString:
|
||||
return "string"
|
||||
case itemNumber:
|
||||
return "number"
|
||||
case itemDuration:
|
||||
return "duration"
|
||||
}
|
||||
return fmt.Sprintf("%q", i)
|
||||
}
|
||||
|
||||
const eof = -1
|
||||
|
||||
// stateFn represents the state of the scanner as a function that returns the next state.
|
||||
type stateFn func(*lexer) stateFn
|
||||
|
||||
// Pos is the position in a string.
|
||||
type Pos int
|
||||
|
||||
// lexer holds the state of the scanner.
|
||||
type lexer struct {
|
||||
input string // The string being scanned.
|
||||
state stateFn // The next lexing function to enter.
|
||||
pos Pos // Current position in the input.
|
||||
start Pos // Start position of this item.
|
||||
width Pos // Width of last rune read from input.
|
||||
lastPos Pos // Position of most recent item returned by nextItem.
|
||||
items chan item // Channel of scanned items.
|
||||
|
||||
parenDepth int // Nesting depth of ( ) exprs.
|
||||
braceOpen bool // Whether a { is opened.
|
||||
bracketOpen bool // Whether a [ is opened.
|
||||
stringOpen rune // Quote rune of the string currently being read.
|
||||
|
||||
// seriesDesc is set when a series description for the testing
|
||||
// language is lexed.
|
||||
seriesDesc bool
|
||||
}
|
||||
|
||||
// next returns the next rune in the input.
|
||||
func (l *lexer) next() rune {
|
||||
if int(l.pos) >= len(l.input) {
|
||||
l.width = 0
|
||||
return eof
|
||||
}
|
||||
r, w := utf8.DecodeRuneInString(l.input[l.pos:])
|
||||
l.width = Pos(w)
|
||||
l.pos += l.width
|
||||
return r
|
||||
}
|
||||
|
||||
// peek returns but does not consume the next rune in the input.
|
||||
func (l *lexer) peek() rune {
|
||||
r := l.next()
|
||||
l.backup()
|
||||
return r
|
||||
}
|
||||
|
||||
// backup steps back one rune. Can only be called once per call of next.
|
||||
func (l *lexer) backup() {
|
||||
l.pos -= l.width
|
||||
}
|
||||
|
||||
// emit passes an item back to the client.
|
||||
func (l *lexer) emit(t itemType) {
|
||||
l.items <- item{t, l.start, l.input[l.start:l.pos]}
|
||||
l.start = l.pos
|
||||
}
|
||||
|
||||
// ignore skips over the pending input before this point.
|
||||
func (l *lexer) ignore() {
|
||||
l.start = l.pos
|
||||
}
|
||||
|
||||
// accept consumes the next rune if it's from the valid set.
|
||||
func (l *lexer) accept(valid string) bool {
|
||||
if strings.ContainsRune(valid, l.next()) {
|
||||
return true
|
||||
}
|
||||
l.backup()
|
||||
return false
|
||||
}
|
||||
|
||||
// acceptRun consumes a run of runes from the valid set.
|
||||
func (l *lexer) acceptRun(valid string) {
|
||||
for strings.ContainsRune(valid, l.next()) {
|
||||
// consume
|
||||
}
|
||||
l.backup()
|
||||
}
|
||||
|
||||
// lineNumber reports which line we're on, based on the position of
|
||||
// the previous item returned by nextItem. Doing it this way
|
||||
// means we don't have to worry about peek double counting.
|
||||
func (l *lexer) lineNumber() int {
|
||||
return 1 + strings.Count(l.input[:l.lastPos], "\n")
|
||||
}
|
||||
|
||||
// linePosition reports at which character in the current line
|
||||
// we are on.
|
||||
func (l *lexer) linePosition() int {
|
||||
lb := strings.LastIndex(l.input[:l.lastPos], "\n")
|
||||
if lb == -1 {
|
||||
return 1 + int(l.lastPos)
|
||||
}
|
||||
return 1 + int(l.lastPos) - lb
|
||||
}
|
||||
|
||||
// errorf returns an error token and terminates the scan by passing
|
||||
// back a nil pointer that will be the next state, terminating l.nextItem.
|
||||
func (l *lexer) errorf(format string, args ...interface{}) stateFn {
|
||||
l.items <- item{itemError, l.start, fmt.Sprintf(format, args...)}
|
||||
return nil
|
||||
}
|
||||
|
||||
// nextItem returns the next item from the input.
|
||||
func (l *lexer) nextItem() item {
|
||||
item := <-l.items
|
||||
l.lastPos = item.pos
|
||||
return item
|
||||
}
|
||||
|
||||
// lex creates a new scanner for the input string.
|
||||
func lex(input string) *lexer {
|
||||
l := &lexer{
|
||||
input: input,
|
||||
items: make(chan item),
|
||||
}
|
||||
go l.run()
|
||||
return l
|
||||
}
|
||||
|
||||
// run runs the state machine for the lexer.
|
||||
func (l *lexer) run() {
|
||||
for l.state = lexStatements; l.state != nil; {
|
||||
l.state = l.state(l)
|
||||
}
|
||||
close(l.items)
|
||||
}
|
||||
|
||||
// lineComment is the character that starts a line comment.
|
||||
const lineComment = "#"
|
||||
|
||||
// lexStatements is the top-level state for lexing.
|
||||
func lexStatements(l *lexer) stateFn {
|
||||
if l.braceOpen {
|
||||
return lexInsideBraces
|
||||
}
|
||||
if strings.HasPrefix(l.input[l.pos:], lineComment) {
|
||||
return lexLineComment
|
||||
}
|
||||
|
||||
switch r := l.next(); {
|
||||
case r == eof:
|
||||
if l.parenDepth != 0 {
|
||||
return l.errorf("unclosed left parenthesis")
|
||||
} else if l.bracketOpen {
|
||||
return l.errorf("unclosed left bracket")
|
||||
}
|
||||
l.emit(itemEOF)
|
||||
return nil
|
||||
case r == ',':
|
||||
l.emit(itemComma)
|
||||
case isSpace(r):
|
||||
return lexSpace
|
||||
case r == '*':
|
||||
l.emit(itemMUL)
|
||||
case r == '/':
|
||||
l.emit(itemDIV)
|
||||
case r == '%':
|
||||
l.emit(itemMOD)
|
||||
case r == '+':
|
||||
l.emit(itemADD)
|
||||
case r == '-':
|
||||
l.emit(itemSUB)
|
||||
case r == '^':
|
||||
l.emit(itemPOW)
|
||||
case r == '=':
|
||||
if t := l.peek(); t == '=' {
|
||||
l.next()
|
||||
l.emit(itemEQL)
|
||||
} else if t == '~' {
|
||||
return l.errorf("unexpected character after '=': %q", t)
|
||||
} else {
|
||||
l.emit(itemAssign)
|
||||
}
|
||||
case r == '!':
|
||||
if t := l.next(); t == '=' {
|
||||
l.emit(itemNEQ)
|
||||
} else {
|
||||
return l.errorf("unexpected character after '!': %q", t)
|
||||
}
|
||||
case r == '<':
|
||||
if t := l.peek(); t == '=' {
|
||||
l.next()
|
||||
l.emit(itemLTE)
|
||||
} else {
|
||||
l.emit(itemLSS)
|
||||
}
|
||||
case r == '>':
|
||||
if t := l.peek(); t == '=' {
|
||||
l.next()
|
||||
l.emit(itemGTE)
|
||||
} else {
|
||||
l.emit(itemGTR)
|
||||
}
|
||||
case isDigit(r) || (r == '.' && isDigit(l.peek())):
|
||||
l.backup()
|
||||
return lexNumberOrDuration
|
||||
case r == '"' || r == '\'':
|
||||
l.stringOpen = r
|
||||
return lexString
|
||||
case r == '`':
|
||||
l.stringOpen = r
|
||||
return lexRawString
|
||||
case isAlpha(r) || r == ':':
|
||||
l.backup()
|
||||
return lexKeywordOrIdentifier
|
||||
case r == '(':
|
||||
l.emit(itemLeftParen)
|
||||
l.parenDepth++
|
||||
return lexStatements
|
||||
case r == ')':
|
||||
l.emit(itemRightParen)
|
||||
l.parenDepth--
|
||||
if l.parenDepth < 0 {
|
||||
return l.errorf("unexpected right parenthesis %q", r)
|
||||
}
|
||||
return lexStatements
|
||||
case r == '{':
|
||||
l.emit(itemLeftBrace)
|
||||
l.braceOpen = true
|
||||
return lexInsideBraces(l)
|
||||
case r == '[':
|
||||
if l.bracketOpen {
|
||||
return l.errorf("unexpected left bracket %q", r)
|
||||
}
|
||||
l.emit(itemLeftBracket)
|
||||
l.bracketOpen = true
|
||||
return lexDuration
|
||||
case r == ']':
|
||||
if !l.bracketOpen {
|
||||
return l.errorf("unexpected right bracket %q", r)
|
||||
}
|
||||
l.emit(itemRightBracket)
|
||||
l.bracketOpen = false
|
||||
|
||||
default:
|
||||
return l.errorf("unexpected character: %q", r)
|
||||
}
|
||||
return lexStatements
|
||||
}
|
||||
|
||||
// lexInsideBraces scans the inside of a vector selector. Keywords are ignored and
|
||||
// scanned as identifiers.
|
||||
func lexInsideBraces(l *lexer) stateFn {
|
||||
if strings.HasPrefix(l.input[l.pos:], lineComment) {
|
||||
return lexLineComment
|
||||
}
|
||||
|
||||
switch r := l.next(); {
|
||||
case r == eof:
|
||||
return l.errorf("unexpected end of input inside braces")
|
||||
case isSpace(r):
|
||||
return lexSpace
|
||||
case isAlpha(r):
|
||||
l.backup()
|
||||
return lexIdentifier
|
||||
case r == ',':
|
||||
l.emit(itemComma)
|
||||
case r == '"' || r == '\'':
|
||||
l.stringOpen = r
|
||||
return lexString
|
||||
case r == '`':
|
||||
l.stringOpen = r
|
||||
return lexRawString
|
||||
case r == '=':
|
||||
if l.next() == '~' {
|
||||
l.emit(itemEQLRegex)
|
||||
break
|
||||
}
|
||||
l.backup()
|
||||
l.emit(itemEQL)
|
||||
case r == '!':
|
||||
switch nr := l.next(); {
|
||||
case nr == '~':
|
||||
l.emit(itemNEQRegex)
|
||||
case nr == '=':
|
||||
l.emit(itemNEQ)
|
||||
default:
|
||||
return l.errorf("unexpected character after '!' inside braces: %q", nr)
|
||||
}
|
||||
case r == '{':
|
||||
return l.errorf("unexpected left brace %q", r)
|
||||
case r == '}':
|
||||
l.emit(itemRightBrace)
|
||||
l.braceOpen = false
|
||||
|
||||
if l.seriesDesc {
|
||||
return lexValueSequence
|
||||
}
|
||||
return lexStatements
|
||||
default:
|
||||
return l.errorf("unexpected character inside braces: %q", r)
|
||||
}
|
||||
return lexInsideBraces
|
||||
}
|
||||
|
||||
// lexValueSequence scans a value sequence of a series description.
|
||||
func lexValueSequence(l *lexer) stateFn {
|
||||
switch r := l.next(); {
|
||||
case r == eof:
|
||||
return lexStatements
|
||||
case isSpace(r):
|
||||
lexSpace(l)
|
||||
case r == '+':
|
||||
l.emit(itemADD)
|
||||
case r == '-':
|
||||
l.emit(itemSUB)
|
||||
case r == 'x':
|
||||
l.emit(itemTimes)
|
||||
case r == '_':
|
||||
l.emit(itemBlank)
|
||||
case isDigit(r) || (r == '.' && isDigit(l.peek())):
|
||||
l.backup()
|
||||
lexNumber(l)
|
||||
case isAlpha(r):
|
||||
l.backup()
|
||||
// We might lex invalid items here but this will be caught by the parser.
|
||||
return lexKeywordOrIdentifier
|
||||
default:
|
||||
return l.errorf("unexpected character in series sequence: %q", r)
|
||||
}
|
||||
return lexValueSequence
|
||||
}
|
||||
|
||||
// lexEscape scans a string escape sequence. The initial escaping character (\)
|
||||
// has already been seen.
|
||||
//
|
||||
// NOTE: This function as well as the helper function digitVal() and associated
|
||||
// tests have been adapted from the corresponding functions in the "go/scanner"
|
||||
// package of the Go standard library to work for Prometheus-style strings.
|
||||
// None of the actual escaping/quoting logic was changed in this function - it
|
||||
// was only modified to integrate with our lexer.
|
||||
func lexEscape(l *lexer) {
|
||||
var n int
|
||||
var base, max uint32
|
||||
|
||||
ch := l.next()
|
||||
switch ch {
|
||||
case 'a', 'b', 'f', 'n', 'r', 't', 'v', '\\', l.stringOpen:
|
||||
return
|
||||
case '0', '1', '2', '3', '4', '5', '6', '7':
|
||||
n, base, max = 3, 8, 255
|
||||
case 'x':
|
||||
ch = l.next()
|
||||
n, base, max = 2, 16, 255
|
||||
case 'u':
|
||||
ch = l.next()
|
||||
n, base, max = 4, 16, unicode.MaxRune
|
||||
case 'U':
|
||||
ch = l.next()
|
||||
n, base, max = 8, 16, unicode.MaxRune
|
||||
case eof:
|
||||
l.errorf("escape sequence not terminated")
|
||||
default:
|
||||
l.errorf("unknown escape sequence %#U", ch)
|
||||
}
|
||||
|
||||
var x uint32
|
||||
for n > 0 {
|
||||
d := uint32(digitVal(ch))
|
||||
if d >= base {
|
||||
if ch == eof {
|
||||
l.errorf("escape sequence not terminated")
|
||||
}
|
||||
l.errorf("illegal character %#U in escape sequence", ch)
|
||||
}
|
||||
x = x*base + d
|
||||
ch = l.next()
|
||||
n--
|
||||
}
|
||||
|
||||
if x > max || 0xD800 <= x && x < 0xE000 {
|
||||
l.errorf("escape sequence is an invalid Unicode code point")
|
||||
}
|
||||
}
|
||||
|
||||
// digitVal returns the digit value of a rune or 16 in case the rune does not
|
||||
// represent a valid digit.
|
||||
func digitVal(ch rune) int {
|
||||
switch {
|
||||
case '0' <= ch && ch <= '9':
|
||||
return int(ch - '0')
|
||||
case 'a' <= ch && ch <= 'f':
|
||||
return int(ch - 'a' + 10)
|
||||
case 'A' <= ch && ch <= 'F':
|
||||
return int(ch - 'A' + 10)
|
||||
}
|
||||
return 16 // Larger than any legal digit val.
|
||||
}
|
||||
|
||||
// lexString scans a quoted string. The initial quote has already been seen.
|
||||
func lexString(l *lexer) stateFn {
|
||||
Loop:
|
||||
for {
|
||||
switch l.next() {
|
||||
case '\\':
|
||||
lexEscape(l)
|
||||
case utf8.RuneError:
|
||||
return l.errorf("invalid UTF-8 rune")
|
||||
case eof, '\n':
|
||||
return l.errorf("unterminated quoted string")
|
||||
case l.stringOpen:
|
||||
break Loop
|
||||
}
|
||||
}
|
||||
l.emit(itemString)
|
||||
return lexStatements
|
||||
}
|
||||
|
||||
// lexRawString scans a raw quoted string. The initial quote has already been seen.
|
||||
func lexRawString(l *lexer) stateFn {
|
||||
Loop:
|
||||
for {
|
||||
switch l.next() {
|
||||
case utf8.RuneError:
|
||||
return l.errorf("invalid UTF-8 rune")
|
||||
case eof:
|
||||
return l.errorf("unterminated raw string")
|
||||
case l.stringOpen:
|
||||
break Loop
|
||||
}
|
||||
}
|
||||
l.emit(itemString)
|
||||
return lexStatements
|
||||
}
|
||||
|
||||
// lexSpace scans a run of space characters. One space has already been seen.
|
||||
func lexSpace(l *lexer) stateFn {
|
||||
for isSpace(l.peek()) {
|
||||
l.next()
|
||||
}
|
||||
l.ignore()
|
||||
return lexStatements
|
||||
}
|
||||
|
||||
// lexLineComment scans a line comment. Left comment marker is known to be present.
|
||||
func lexLineComment(l *lexer) stateFn {
|
||||
l.pos += Pos(len(lineComment))
|
||||
for r := l.next(); !isEndOfLine(r) && r != eof; {
|
||||
r = l.next()
|
||||
}
|
||||
l.backup()
|
||||
l.emit(itemComment)
|
||||
return lexStatements
|
||||
}
|
||||
|
||||
func lexDuration(l *lexer) stateFn {
|
||||
if l.scanNumber() {
|
||||
return l.errorf("missing unit character in duration")
|
||||
}
|
||||
// Next two chars must be a valid unit and a non-alphanumeric.
|
||||
if l.accept("smhdwy") {
|
||||
if isAlphaNumeric(l.next()) {
|
||||
return l.errorf("bad duration syntax: %q", l.input[l.start:l.pos])
|
||||
}
|
||||
l.backup()
|
||||
l.emit(itemDuration)
|
||||
return lexStatements
|
||||
}
|
||||
return l.errorf("bad duration syntax: %q", l.input[l.start:l.pos])
|
||||
}
|
||||
|
||||
// lexNumber scans a number: decimal, hex, oct or float.
|
||||
func lexNumber(l *lexer) stateFn {
|
||||
if !l.scanNumber() {
|
||||
return l.errorf("bad number syntax: %q", l.input[l.start:l.pos])
|
||||
}
|
||||
l.emit(itemNumber)
|
||||
return lexStatements
|
||||
}
|
||||
|
||||
// lexNumberOrDuration scans a number or a duration item.
|
||||
func lexNumberOrDuration(l *lexer) stateFn {
|
||||
if l.scanNumber() {
|
||||
l.emit(itemNumber)
|
||||
return lexStatements
|
||||
}
|
||||
// Next two chars must be a valid unit and a non-alphanumeric.
|
||||
if l.accept("smhdwy") {
|
||||
if isAlphaNumeric(l.next()) {
|
||||
return l.errorf("bad number or duration syntax: %q", l.input[l.start:l.pos])
|
||||
}
|
||||
l.backup()
|
||||
l.emit(itemDuration)
|
||||
return lexStatements
|
||||
}
|
||||
return l.errorf("bad number or duration syntax: %q", l.input[l.start:l.pos])
|
||||
}
|
||||
|
||||
// scanNumber scans numbers of different formats. The scanned item is
|
||||
// not necessarily a valid number. This case is caught by the parser.
|
||||
func (l *lexer) scanNumber() bool {
|
||||
digits := "0123456789"
|
||||
// Disallow hexadecimal in series descriptions as the syntax is ambiguous.
|
||||
if !l.seriesDesc && l.accept("0") && l.accept("xX") {
|
||||
digits = "0123456789abcdefABCDEF"
|
||||
}
|
||||
l.acceptRun(digits)
|
||||
if l.accept(".") {
|
||||
l.acceptRun(digits)
|
||||
}
|
||||
if l.accept("eE") {
|
||||
l.accept("+-")
|
||||
l.acceptRun("0123456789")
|
||||
}
|
||||
// Next thing must not be alphanumeric unless it's the times token
|
||||
// for series repetitions.
|
||||
if r := l.peek(); (l.seriesDesc && r == 'x') || !isAlphaNumeric(r) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// lexIdentifier scans an alphanumeric identifier. The next character
|
||||
// is known to be a letter.
|
||||
func lexIdentifier(l *lexer) stateFn {
|
||||
for isAlphaNumeric(l.next()) {
|
||||
// absorb
|
||||
}
|
||||
l.backup()
|
||||
l.emit(itemIdentifier)
|
||||
return lexStatements
|
||||
}
|
||||
|
||||
// lexKeywordOrIdentifier scans an alphanumeric identifier which may contain
|
||||
// a colon rune. If the identifier is a keyword the respective keyword item
|
||||
// is scanned.
|
||||
func lexKeywordOrIdentifier(l *lexer) stateFn {
|
||||
Loop:
|
||||
for {
|
||||
switch r := l.next(); {
|
||||
case isAlphaNumeric(r) || r == ':':
|
||||
// absorb.
|
||||
default:
|
||||
l.backup()
|
||||
word := l.input[l.start:l.pos]
|
||||
if kw, ok := key[strings.ToLower(word)]; ok {
|
||||
l.emit(kw)
|
||||
} else if !strings.Contains(word, ":") {
|
||||
l.emit(itemIdentifier)
|
||||
} else {
|
||||
l.emit(itemMetricIdentifier)
|
||||
}
|
||||
break Loop
|
||||
}
|
||||
}
|
||||
if l.seriesDesc && l.peek() != '{' {
|
||||
return lexValueSequence
|
||||
}
|
||||
return lexStatements
|
||||
}
|
||||
|
||||
func isSpace(r rune) bool {
|
||||
return r == ' ' || r == '\t' || r == '\n' || r == '\r'
|
||||
}
|
||||
|
||||
// isEndOfLine reports whether r is an end-of-line character.
|
||||
func isEndOfLine(r rune) bool {
|
||||
return r == '\r' || r == '\n'
|
||||
}
|
||||
|
||||
// isAlphaNumeric reports whether r is an alphabetic, digit, or underscore.
|
||||
func isAlphaNumeric(r rune) bool {
|
||||
return isAlpha(r) || isDigit(r)
|
||||
}
|
||||
|
||||
// isDigit reports whether r is a digit. Note: we cannot use unicode.IsDigit()
|
||||
// instead because that also classifies non-Latin digits as digits. See
|
||||
// https://github.com/prometheus/prometheus/issues/939.
|
||||
func isDigit(r rune) bool {
|
||||
return '0' <= r && r <= '9'
|
||||
}
|
||||
|
||||
// isAlpha reports whether r is an alphabetic or underscore.
|
||||
func isAlpha(r rune) bool {
|
||||
return r == '_' || ('a' <= r && r <= 'z') || ('A' <= r && r <= 'Z')
|
||||
}
|
||||
|
||||
// isLabel reports whether the string can be used as label.
|
||||
func isLabel(s string) bool {
|
||||
if len(s) == 0 || !isAlpha(rune(s[0])) {
|
||||
return false
|
||||
}
|
||||
for _, c := range s[1:] {
|
||||
if !isAlphaNumeric(c) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
1146
vendor/github.com/prometheus/prometheus/promql/parse.go
generated
vendored
Normal file
1146
vendor/github.com/prometheus/prometheus/promql/parse.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
236
vendor/github.com/prometheus/prometheus/promql/printer.go
generated
vendored
Normal file
236
vendor/github.com/prometheus/prometheus/promql/printer.go
generated
vendored
Normal file
@@ -0,0 +1,236 @@
|
||||
// Copyright 2015 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 promql
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/common/model"
|
||||
|
||||
"github.com/prometheus/prometheus/storage/metric"
|
||||
)
|
||||
|
||||
// Tree returns a string of the tree structure of the given node.
|
||||
func Tree(node Node) string {
|
||||
return tree(node, "")
|
||||
}
|
||||
|
||||
func tree(node Node, level string) string {
|
||||
if node == nil {
|
||||
return fmt.Sprintf("%s |---- %T\n", level, node)
|
||||
}
|
||||
typs := strings.Split(fmt.Sprintf("%T", node), ".")[1]
|
||||
|
||||
var t string
|
||||
// Only print the number of statements for readability.
|
||||
if stmts, ok := node.(Statements); ok {
|
||||
t = fmt.Sprintf("%s |---- %s :: %d\n", level, typs, len(stmts))
|
||||
} else {
|
||||
t = fmt.Sprintf("%s |---- %s :: %s\n", level, typs, node)
|
||||
}
|
||||
|
||||
level += " · · ·"
|
||||
|
||||
switch n := node.(type) {
|
||||
case Statements:
|
||||
for _, s := range n {
|
||||
t += tree(s, level)
|
||||
}
|
||||
case *AlertStmt:
|
||||
t += tree(n.Expr, level)
|
||||
|
||||
case *EvalStmt:
|
||||
t += tree(n.Expr, level)
|
||||
|
||||
case *RecordStmt:
|
||||
t += tree(n.Expr, level)
|
||||
|
||||
case Expressions:
|
||||
for _, e := range n {
|
||||
t += tree(e, level)
|
||||
}
|
||||
case *AggregateExpr:
|
||||
t += tree(n.Expr, level)
|
||||
|
||||
case *BinaryExpr:
|
||||
t += tree(n.LHS, level)
|
||||
t += tree(n.RHS, level)
|
||||
|
||||
case *Call:
|
||||
t += tree(n.Args, level)
|
||||
|
||||
case *ParenExpr:
|
||||
t += tree(n.Expr, level)
|
||||
|
||||
case *UnaryExpr:
|
||||
t += tree(n.Expr, level)
|
||||
|
||||
case *MatrixSelector, *NumberLiteral, *StringLiteral, *VectorSelector:
|
||||
// nothing to do
|
||||
|
||||
default:
|
||||
panic("promql.Tree: not all node types covered")
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
func (stmts Statements) String() (s string) {
|
||||
if len(stmts) == 0 {
|
||||
return ""
|
||||
}
|
||||
for _, stmt := range stmts {
|
||||
s += stmt.String()
|
||||
s += "\n\n"
|
||||
}
|
||||
return s[:len(s)-2]
|
||||
}
|
||||
|
||||
func (node *AlertStmt) String() string {
|
||||
s := fmt.Sprintf("ALERT %s", node.Name)
|
||||
s += fmt.Sprintf("\n\tIF %s", node.Expr)
|
||||
if node.Duration > 0 {
|
||||
s += fmt.Sprintf("\n\tFOR %s", model.Duration(node.Duration))
|
||||
}
|
||||
if len(node.Labels) > 0 {
|
||||
s += fmt.Sprintf("\n\tLABELS %s", node.Labels)
|
||||
}
|
||||
if len(node.Annotations) > 0 {
|
||||
s += fmt.Sprintf("\n\tANNOTATIONS %s", node.Annotations)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (node *EvalStmt) String() string {
|
||||
return "EVAL " + node.Expr.String()
|
||||
}
|
||||
|
||||
func (node *RecordStmt) String() string {
|
||||
s := fmt.Sprintf("%s%s = %s", node.Name, node.Labels, node.Expr)
|
||||
return s
|
||||
}
|
||||
|
||||
func (es Expressions) String() (s string) {
|
||||
if len(es) == 0 {
|
||||
return ""
|
||||
}
|
||||
for _, e := range es {
|
||||
s += e.String()
|
||||
s += ", "
|
||||
}
|
||||
return s[:len(s)-2]
|
||||
}
|
||||
|
||||
func (node *AggregateExpr) String() string {
|
||||
aggrString := fmt.Sprintf("%s(", node.Op)
|
||||
if node.Op.isAggregatorWithParam() {
|
||||
aggrString += fmt.Sprintf("%s, ", node.Param)
|
||||
}
|
||||
aggrString += fmt.Sprintf("%s)", node.Expr)
|
||||
if len(node.Grouping) > 0 {
|
||||
var format string
|
||||
if node.Without {
|
||||
format = "%s WITHOUT (%s)"
|
||||
} else {
|
||||
format = "%s BY (%s)"
|
||||
}
|
||||
aggrString = fmt.Sprintf(format, aggrString, node.Grouping)
|
||||
}
|
||||
if node.KeepCommonLabels {
|
||||
aggrString += " KEEP_COMMON"
|
||||
}
|
||||
return aggrString
|
||||
}
|
||||
|
||||
func (node *BinaryExpr) String() string {
|
||||
returnBool := ""
|
||||
if node.ReturnBool {
|
||||
returnBool = " BOOL"
|
||||
}
|
||||
|
||||
matching := ""
|
||||
vm := node.VectorMatching
|
||||
if vm != nil && (len(vm.MatchingLabels) > 0 || vm.On) {
|
||||
if vm.On {
|
||||
matching = fmt.Sprintf(" ON(%s)", vm.MatchingLabels)
|
||||
} else {
|
||||
matching = fmt.Sprintf(" IGNORING(%s)", vm.MatchingLabels)
|
||||
}
|
||||
if vm.Card == CardManyToOne || vm.Card == CardOneToMany {
|
||||
matching += " GROUP_"
|
||||
if vm.Card == CardManyToOne {
|
||||
matching += "LEFT"
|
||||
} else {
|
||||
matching += "RIGHT"
|
||||
}
|
||||
matching += fmt.Sprintf("(%s)", vm.Include)
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("%s %s%s%s %s", node.LHS, node.Op, returnBool, matching, node.RHS)
|
||||
}
|
||||
|
||||
func (node *Call) String() string {
|
||||
return fmt.Sprintf("%s(%s)", node.Func.Name, node.Args)
|
||||
}
|
||||
|
||||
func (node *MatrixSelector) String() string {
|
||||
vecSelector := &VectorSelector{
|
||||
Name: node.Name,
|
||||
LabelMatchers: node.LabelMatchers,
|
||||
}
|
||||
offset := ""
|
||||
if node.Offset != time.Duration(0) {
|
||||
offset = fmt.Sprintf(" OFFSET %s", model.Duration(node.Offset))
|
||||
}
|
||||
return fmt.Sprintf("%s[%s]%s", vecSelector.String(), model.Duration(node.Range), offset)
|
||||
}
|
||||
|
||||
func (node *NumberLiteral) String() string {
|
||||
return fmt.Sprint(node.Val)
|
||||
}
|
||||
|
||||
func (node *ParenExpr) String() string {
|
||||
return fmt.Sprintf("(%s)", node.Expr)
|
||||
}
|
||||
|
||||
func (node *StringLiteral) String() string {
|
||||
return fmt.Sprintf("%q", node.Val)
|
||||
}
|
||||
|
||||
func (node *UnaryExpr) String() string {
|
||||
return fmt.Sprintf("%s%s", node.Op, node.Expr)
|
||||
}
|
||||
|
||||
func (node *VectorSelector) String() string {
|
||||
labelStrings := make([]string, 0, len(node.LabelMatchers)-1)
|
||||
for _, matcher := range node.LabelMatchers {
|
||||
// Only include the __name__ label if its no equality matching.
|
||||
if matcher.Name == model.MetricNameLabel && matcher.Type == metric.Equal {
|
||||
continue
|
||||
}
|
||||
labelStrings = append(labelStrings, matcher.String())
|
||||
}
|
||||
offset := ""
|
||||
if node.Offset != time.Duration(0) {
|
||||
offset = fmt.Sprintf(" OFFSET %s", model.Duration(node.Offset))
|
||||
}
|
||||
|
||||
if len(labelStrings) == 0 {
|
||||
return fmt.Sprintf("%s%s", node.Name, offset)
|
||||
}
|
||||
sort.Strings(labelStrings)
|
||||
return fmt.Sprintf("%s{%s}%s", node.Name, strings.Join(labelStrings, ","), offset)
|
||||
}
|
||||
185
vendor/github.com/prometheus/prometheus/promql/quantile.go
generated
vendored
Normal file
185
vendor/github.com/prometheus/prometheus/promql/quantile.go
generated
vendored
Normal file
@@ -0,0 +1,185 @@
|
||||
// Copyright 2015 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 promql
|
||||
|
||||
import (
|
||||
"math"
|
||||
"sort"
|
||||
|
||||
"github.com/prometheus/common/model"
|
||||
|
||||
"github.com/prometheus/prometheus/storage/metric"
|
||||
)
|
||||
|
||||
// Helpers to calculate quantiles.
|
||||
|
||||
// excludedLabels are the labels to exclude from signature calculation for
|
||||
// quantiles.
|
||||
var excludedLabels = map[model.LabelName]struct{}{
|
||||
model.MetricNameLabel: {},
|
||||
model.BucketLabel: {},
|
||||
}
|
||||
|
||||
type bucket struct {
|
||||
upperBound float64
|
||||
count model.SampleValue
|
||||
}
|
||||
|
||||
// buckets implements sort.Interface.
|
||||
type buckets []bucket
|
||||
|
||||
func (b buckets) Len() int { return len(b) }
|
||||
func (b buckets) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
|
||||
func (b buckets) Less(i, j int) bool { return b[i].upperBound < b[j].upperBound }
|
||||
|
||||
type metricWithBuckets struct {
|
||||
metric metric.Metric
|
||||
buckets buckets
|
||||
}
|
||||
|
||||
// bucketQuantile calculates the quantile 'q' based on the given buckets. The
|
||||
// buckets will be sorted by upperBound by this function (i.e. no sorting
|
||||
// needed before calling this function). The quantile value is interpolated
|
||||
// assuming a linear distribution within a bucket. However, if the quantile
|
||||
// falls into the highest bucket, the upper bound of the 2nd highest bucket is
|
||||
// returned. A natural lower bound of 0 is assumed if the upper bound of the
|
||||
// lowest bucket is greater 0. In that case, interpolation in the lowest bucket
|
||||
// happens linearly between 0 and the upper bound of the lowest bucket.
|
||||
// However, if the lowest bucket has an upper bound less or equal 0, this upper
|
||||
// bound is returned if the quantile falls into the lowest bucket.
|
||||
//
|
||||
// There are a number of special cases (once we have a way to report errors
|
||||
// happening during evaluations of AST functions, we should report those
|
||||
// explicitly):
|
||||
//
|
||||
// If 'buckets' has fewer than 2 elements, NaN is returned.
|
||||
//
|
||||
// If the highest bucket is not +Inf, NaN is returned.
|
||||
//
|
||||
// If q<0, -Inf is returned.
|
||||
//
|
||||
// If q>1, +Inf is returned.
|
||||
func bucketQuantile(q model.SampleValue, buckets buckets) float64 {
|
||||
if q < 0 {
|
||||
return math.Inf(-1)
|
||||
}
|
||||
if q > 1 {
|
||||
return math.Inf(+1)
|
||||
}
|
||||
if len(buckets) < 2 {
|
||||
return math.NaN()
|
||||
}
|
||||
sort.Sort(buckets)
|
||||
if !math.IsInf(buckets[len(buckets)-1].upperBound, +1) {
|
||||
return math.NaN()
|
||||
}
|
||||
|
||||
ensureMonotonic(buckets)
|
||||
|
||||
rank := q * buckets[len(buckets)-1].count
|
||||
b := sort.Search(len(buckets)-1, func(i int) bool { return buckets[i].count >= rank })
|
||||
|
||||
if b == len(buckets)-1 {
|
||||
return buckets[len(buckets)-2].upperBound
|
||||
}
|
||||
if b == 0 && buckets[0].upperBound <= 0 {
|
||||
return buckets[0].upperBound
|
||||
}
|
||||
var (
|
||||
bucketStart float64
|
||||
bucketEnd = buckets[b].upperBound
|
||||
count = buckets[b].count
|
||||
)
|
||||
if b > 0 {
|
||||
bucketStart = buckets[b-1].upperBound
|
||||
count -= buckets[b-1].count
|
||||
rank -= buckets[b-1].count
|
||||
}
|
||||
return bucketStart + (bucketEnd-bucketStart)*float64(rank/count)
|
||||
}
|
||||
|
||||
// The assumption that bucket counts increase monotonically with increasing
|
||||
// upperBound may be violated during:
|
||||
//
|
||||
// * Recording rule evaluation of histogram_quantile, especially when rate()
|
||||
// has been applied to the underlying bucket timeseries.
|
||||
// * Evaluation of histogram_quantile computed over federated bucket
|
||||
// timeseries, especially when rate() has been applied.
|
||||
//
|
||||
// This is because scraped data is not made available to rule evaluation or
|
||||
// federation atomically, so some buckets are computed with data from the
|
||||
// most recent scrapes, but the other buckets are missing data from the most
|
||||
// recent scrape.
|
||||
//
|
||||
// Monotonicity is usually guaranteed because if a bucket with upper bound
|
||||
// u1 has count c1, then any bucket with a higher upper bound u > u1 must
|
||||
// have counted all c1 observations and perhaps more, so that c >= c1.
|
||||
//
|
||||
// Randomly interspersed partial sampling breaks that guarantee, and rate()
|
||||
// exacerbates it. Specifically, suppose bucket le=1000 has a count of 10 from
|
||||
// 4 samples but the bucket with le=2000 has a count of 7 from 3 samples. The
|
||||
// monotonicity is broken. It is exacerbated by rate() because under normal
|
||||
// operation, cumulative counting of buckets will cause the bucket counts to
|
||||
// diverge such that small differences from missing samples are not a problem.
|
||||
// rate() removes this divergence.)
|
||||
//
|
||||
// bucketQuantile depends on that monotonicity to do a binary search for the
|
||||
// bucket with the φ-quantile count, so breaking the monotonicity
|
||||
// guarantee causes bucketQuantile() to return undefined (nonsense) results.
|
||||
//
|
||||
// As a somewhat hacky solution until ingestion is atomic per scrape, we
|
||||
// calculate the "envelope" of the histogram buckets, essentially removing
|
||||
// any decreases in the count between successive buckets.
|
||||
|
||||
func ensureMonotonic(buckets buckets) {
|
||||
max := buckets[0].count
|
||||
for i := range buckets[1:] {
|
||||
switch {
|
||||
case buckets[i].count > max:
|
||||
max = buckets[i].count
|
||||
case buckets[i].count < max:
|
||||
buckets[i].count = max
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// qauntile calculates the given quantile of a vector of samples.
|
||||
//
|
||||
// The vector will be sorted.
|
||||
// If 'values' has zero elements, NaN is returned.
|
||||
// If q<0, -Inf is returned.
|
||||
// If q>1, +Inf is returned.
|
||||
func quantile(q float64, values vectorByValueHeap) float64 {
|
||||
if len(values) == 0 {
|
||||
return math.NaN()
|
||||
}
|
||||
if q < 0 {
|
||||
return math.Inf(-1)
|
||||
}
|
||||
if q > 1 {
|
||||
return math.Inf(+1)
|
||||
}
|
||||
sort.Sort(values)
|
||||
|
||||
n := float64(len(values))
|
||||
// When the quantile lies between two samples,
|
||||
// we use a weighted average of the two samples.
|
||||
rank := q * (n - 1)
|
||||
|
||||
lowerIndex := math.Max(0, math.Floor(rank))
|
||||
upperIndex := math.Min(n-1, lowerIndex+1)
|
||||
|
||||
weight := rank - math.Floor(rank)
|
||||
return float64(values[int(lowerIndex)].Value)*(1-weight) + float64(values[int(upperIndex)].Value)*weight
|
||||
}
|
||||
525
vendor/github.com/prometheus/prometheus/promql/test.go
generated
vendored
Normal file
525
vendor/github.com/prometheus/prometheus/promql/test.go
generated
vendored
Normal file
@@ -0,0 +1,525 @@
|
||||
// Copyright 2015 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 promql
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/common/model"
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/prometheus/prometheus/storage"
|
||||
"github.com/prometheus/prometheus/storage/local"
|
||||
"github.com/prometheus/prometheus/util/testutil"
|
||||
)
|
||||
|
||||
var (
|
||||
minNormal = math.Float64frombits(0x0010000000000000) // The smallest positive normal value of type float64.
|
||||
|
||||
patSpace = regexp.MustCompile("[\t ]+")
|
||||
patLoad = regexp.MustCompile(`^load\s+(.+?)$`)
|
||||
patEvalInstant = regexp.MustCompile(`^eval(?:_(fail|ordered))?\s+instant\s+(?:at\s+(.+?))?\s+(.+)$`)
|
||||
)
|
||||
|
||||
const (
|
||||
testStartTime = model.Time(0)
|
||||
epsilon = 0.000001 // Relative error allowed for sample values.
|
||||
)
|
||||
|
||||
// Test is a sequence of read and write commands that are run
|
||||
// against a test storage.
|
||||
type Test struct {
|
||||
testutil.T
|
||||
|
||||
cmds []testCommand
|
||||
|
||||
storage local.Storage
|
||||
closeStorage func()
|
||||
queryEngine *Engine
|
||||
context context.Context
|
||||
cancelCtx context.CancelFunc
|
||||
}
|
||||
|
||||
// NewTest returns an initialized empty Test.
|
||||
func NewTest(t testutil.T, input string) (*Test, error) {
|
||||
test := &Test{
|
||||
T: t,
|
||||
cmds: []testCommand{},
|
||||
}
|
||||
err := test.parse(input)
|
||||
test.clear()
|
||||
|
||||
return test, err
|
||||
}
|
||||
|
||||
func newTestFromFile(t testutil.T, filename string) (*Test, error) {
|
||||
content, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewTest(t, string(content))
|
||||
}
|
||||
|
||||
// QueryEngine returns the test's query engine.
|
||||
func (t *Test) QueryEngine() *Engine {
|
||||
return t.queryEngine
|
||||
}
|
||||
|
||||
// Context returns the test's context.
|
||||
func (t *Test) Context() context.Context {
|
||||
return t.context
|
||||
}
|
||||
|
||||
// Storage returns the test's storage.
|
||||
func (t *Test) Storage() local.Storage {
|
||||
return t.storage
|
||||
}
|
||||
|
||||
func raise(line int, format string, v ...interface{}) error {
|
||||
return &ParseErr{
|
||||
Line: line + 1,
|
||||
Err: fmt.Errorf(format, v...),
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Test) parseLoad(lines []string, i int) (int, *loadCmd, error) {
|
||||
if !patLoad.MatchString(lines[i]) {
|
||||
return i, nil, raise(i, "invalid load command. (load <step:duration>)")
|
||||
}
|
||||
parts := patLoad.FindStringSubmatch(lines[i])
|
||||
|
||||
gap, err := model.ParseDuration(parts[1])
|
||||
if err != nil {
|
||||
return i, nil, raise(i, "invalid step definition %q: %s", parts[1], err)
|
||||
}
|
||||
cmd := newLoadCmd(time.Duration(gap))
|
||||
for i+1 < len(lines) {
|
||||
i++
|
||||
defLine := lines[i]
|
||||
if len(defLine) == 0 {
|
||||
i--
|
||||
break
|
||||
}
|
||||
metric, vals, err := parseSeriesDesc(defLine)
|
||||
if err != nil {
|
||||
if perr, ok := err.(*ParseErr); ok {
|
||||
perr.Line = i + 1
|
||||
}
|
||||
return i, nil, err
|
||||
}
|
||||
cmd.set(metric, vals...)
|
||||
}
|
||||
return i, cmd, nil
|
||||
}
|
||||
|
||||
func (t *Test) parseEval(lines []string, i int) (int, *evalCmd, error) {
|
||||
if !patEvalInstant.MatchString(lines[i]) {
|
||||
return i, nil, raise(i, "invalid evaluation command. (eval[_fail|_ordered] instant [at <offset:duration>] <query>")
|
||||
}
|
||||
parts := patEvalInstant.FindStringSubmatch(lines[i])
|
||||
var (
|
||||
mod = parts[1]
|
||||
at = parts[2]
|
||||
qry = parts[3]
|
||||
)
|
||||
expr, err := ParseExpr(qry)
|
||||
if err != nil {
|
||||
if perr, ok := err.(*ParseErr); ok {
|
||||
perr.Line = i + 1
|
||||
perr.Pos += strings.Index(lines[i], qry)
|
||||
}
|
||||
return i, nil, err
|
||||
}
|
||||
|
||||
offset, err := model.ParseDuration(at)
|
||||
if err != nil {
|
||||
return i, nil, raise(i, "invalid step definition %q: %s", parts[1], err)
|
||||
}
|
||||
ts := testStartTime.Add(time.Duration(offset))
|
||||
|
||||
cmd := newEvalCmd(expr, ts, ts, 0)
|
||||
switch mod {
|
||||
case "ordered":
|
||||
cmd.ordered = true
|
||||
case "fail":
|
||||
cmd.fail = true
|
||||
}
|
||||
|
||||
for j := 1; i+1 < len(lines); j++ {
|
||||
i++
|
||||
defLine := lines[i]
|
||||
if len(defLine) == 0 {
|
||||
i--
|
||||
break
|
||||
}
|
||||
if f, err := parseNumber(defLine); err == nil {
|
||||
cmd.expect(0, nil, sequenceValue{value: model.SampleValue(f)})
|
||||
break
|
||||
}
|
||||
metric, vals, err := parseSeriesDesc(defLine)
|
||||
if err != nil {
|
||||
if perr, ok := err.(*ParseErr); ok {
|
||||
perr.Line = i + 1
|
||||
}
|
||||
return i, nil, err
|
||||
}
|
||||
|
||||
// Currently, we are not expecting any matrices.
|
||||
if len(vals) > 1 {
|
||||
return i, nil, raise(i, "expecting multiple values in instant evaluation not allowed")
|
||||
}
|
||||
cmd.expect(j, metric, vals...)
|
||||
}
|
||||
return i, cmd, nil
|
||||
}
|
||||
|
||||
// parse the given command sequence and appends it to the test.
|
||||
func (t *Test) parse(input string) error {
|
||||
// Trim lines and remove comments.
|
||||
lines := strings.Split(input, "\n")
|
||||
for i, l := range lines {
|
||||
l = strings.TrimSpace(l)
|
||||
if strings.HasPrefix(l, "#") {
|
||||
l = ""
|
||||
}
|
||||
lines[i] = l
|
||||
}
|
||||
var err error
|
||||
|
||||
// Scan for steps line by line.
|
||||
for i := 0; i < len(lines); i++ {
|
||||
l := lines[i]
|
||||
if len(l) == 0 {
|
||||
continue
|
||||
}
|
||||
var cmd testCommand
|
||||
|
||||
switch c := strings.ToLower(patSpace.Split(l, 2)[0]); {
|
||||
case c == "clear":
|
||||
cmd = &clearCmd{}
|
||||
case c == "load":
|
||||
i, cmd, err = t.parseLoad(lines, i)
|
||||
case strings.HasPrefix(c, "eval"):
|
||||
i, cmd, err = t.parseEval(lines, i)
|
||||
default:
|
||||
return raise(i, "invalid command %q", l)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t.cmds = append(t.cmds, cmd)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// testCommand is an interface that ensures that only the package internal
|
||||
// types can be a valid command for a test.
|
||||
type testCommand interface {
|
||||
testCmd()
|
||||
}
|
||||
|
||||
func (*clearCmd) testCmd() {}
|
||||
func (*loadCmd) testCmd() {}
|
||||
func (*evalCmd) testCmd() {}
|
||||
|
||||
// loadCmd is a command that loads sequences of sample values for specific
|
||||
// metrics into the storage.
|
||||
type loadCmd struct {
|
||||
gap time.Duration
|
||||
metrics map[model.Fingerprint]model.Metric
|
||||
defs map[model.Fingerprint][]model.SamplePair
|
||||
}
|
||||
|
||||
func newLoadCmd(gap time.Duration) *loadCmd {
|
||||
return &loadCmd{
|
||||
gap: gap,
|
||||
metrics: map[model.Fingerprint]model.Metric{},
|
||||
defs: map[model.Fingerprint][]model.SamplePair{},
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd loadCmd) String() string {
|
||||
return "load"
|
||||
}
|
||||
|
||||
// set a sequence of sample values for the given metric.
|
||||
func (cmd *loadCmd) set(m model.Metric, vals ...sequenceValue) {
|
||||
fp := m.Fingerprint()
|
||||
|
||||
samples := make([]model.SamplePair, 0, len(vals))
|
||||
ts := testStartTime
|
||||
for _, v := range vals {
|
||||
if !v.omitted {
|
||||
samples = append(samples, model.SamplePair{
|
||||
Timestamp: ts,
|
||||
Value: v.value,
|
||||
})
|
||||
}
|
||||
ts = ts.Add(cmd.gap)
|
||||
}
|
||||
cmd.defs[fp] = samples
|
||||
cmd.metrics[fp] = m
|
||||
}
|
||||
|
||||
// append the defined time series to the storage.
|
||||
func (cmd *loadCmd) append(a storage.SampleAppender) {
|
||||
for fp, samples := range cmd.defs {
|
||||
met := cmd.metrics[fp]
|
||||
for _, smpl := range samples {
|
||||
s := &model.Sample{
|
||||
Metric: met,
|
||||
Value: smpl.Value,
|
||||
Timestamp: smpl.Timestamp,
|
||||
}
|
||||
a.Append(s)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// evalCmd is a command that evaluates an expression for the given time (range)
|
||||
// and expects a specific result.
|
||||
type evalCmd struct {
|
||||
expr Expr
|
||||
start, end model.Time
|
||||
interval time.Duration
|
||||
|
||||
instant bool
|
||||
fail, ordered bool
|
||||
|
||||
metrics map[model.Fingerprint]model.Metric
|
||||
expected map[model.Fingerprint]entry
|
||||
}
|
||||
|
||||
type entry struct {
|
||||
pos int
|
||||
vals []sequenceValue
|
||||
}
|
||||
|
||||
func (e entry) String() string {
|
||||
return fmt.Sprintf("%d: %s", e.pos, e.vals)
|
||||
}
|
||||
|
||||
func newEvalCmd(expr Expr, start, end model.Time, interval time.Duration) *evalCmd {
|
||||
return &evalCmd{
|
||||
expr: expr,
|
||||
start: start,
|
||||
end: end,
|
||||
interval: interval,
|
||||
instant: start == end && interval == 0,
|
||||
|
||||
metrics: map[model.Fingerprint]model.Metric{},
|
||||
expected: map[model.Fingerprint]entry{},
|
||||
}
|
||||
}
|
||||
|
||||
func (ev *evalCmd) String() string {
|
||||
return "eval"
|
||||
}
|
||||
|
||||
// expect adds a new metric with a sequence of values to the set of expected
|
||||
// results for the query.
|
||||
func (ev *evalCmd) expect(pos int, m model.Metric, vals ...sequenceValue) {
|
||||
if m == nil {
|
||||
ev.expected[0] = entry{pos: pos, vals: vals}
|
||||
return
|
||||
}
|
||||
fp := m.Fingerprint()
|
||||
ev.metrics[fp] = m
|
||||
ev.expected[fp] = entry{pos: pos, vals: vals}
|
||||
}
|
||||
|
||||
// compareResult compares the result value with the defined expectation.
|
||||
func (ev *evalCmd) compareResult(result model.Value) error {
|
||||
switch val := result.(type) {
|
||||
case model.Matrix:
|
||||
if ev.instant {
|
||||
return fmt.Errorf("received range result on instant evaluation")
|
||||
}
|
||||
seen := map[model.Fingerprint]bool{}
|
||||
for pos, v := range val {
|
||||
fp := v.Metric.Fingerprint()
|
||||
if _, ok := ev.metrics[fp]; !ok {
|
||||
return fmt.Errorf("unexpected metric %s in result", v.Metric)
|
||||
}
|
||||
exp := ev.expected[fp]
|
||||
if ev.ordered && exp.pos != pos+1 {
|
||||
return fmt.Errorf("expected metric %s with %v at position %d but was at %d", v.Metric, exp.vals, exp.pos, pos+1)
|
||||
}
|
||||
for i, expVal := range exp.vals {
|
||||
if !almostEqual(float64(expVal.value), float64(v.Values[i].Value)) {
|
||||
return fmt.Errorf("expected %v for %s but got %v", expVal, v.Metric, v.Values)
|
||||
}
|
||||
}
|
||||
seen[fp] = true
|
||||
}
|
||||
for fp, expVals := range ev.expected {
|
||||
if !seen[fp] {
|
||||
return fmt.Errorf("expected metric %s with %v not found", ev.metrics[fp], expVals)
|
||||
}
|
||||
}
|
||||
|
||||
case model.Vector:
|
||||
if !ev.instant {
|
||||
return fmt.Errorf("received instant result on range evaluation")
|
||||
}
|
||||
seen := map[model.Fingerprint]bool{}
|
||||
for pos, v := range val {
|
||||
fp := v.Metric.Fingerprint()
|
||||
if _, ok := ev.metrics[fp]; !ok {
|
||||
return fmt.Errorf("unexpected metric %s in result", v.Metric)
|
||||
}
|
||||
exp := ev.expected[fp]
|
||||
if ev.ordered && exp.pos != pos+1 {
|
||||
return fmt.Errorf("expected metric %s with %v at position %d but was at %d", v.Metric, exp.vals, exp.pos, pos+1)
|
||||
}
|
||||
if !almostEqual(float64(exp.vals[0].value), float64(v.Value)) {
|
||||
return fmt.Errorf("expected %v for %s but got %v", exp.vals[0].value, v.Metric, v.Value)
|
||||
}
|
||||
|
||||
seen[fp] = true
|
||||
}
|
||||
for fp, expVals := range ev.expected {
|
||||
if !seen[fp] {
|
||||
return fmt.Errorf("expected metric %s with %v not found", ev.metrics[fp], expVals)
|
||||
}
|
||||
}
|
||||
|
||||
case *model.Scalar:
|
||||
if !almostEqual(float64(ev.expected[0].vals[0].value), float64(val.Value)) {
|
||||
return fmt.Errorf("expected scalar %v but got %v", val.Value, ev.expected[0].vals[0].value)
|
||||
}
|
||||
|
||||
default:
|
||||
panic(fmt.Errorf("promql.Test.compareResult: unexpected result type %T", result))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// clearCmd is a command that wipes the test's storage state.
|
||||
type clearCmd struct{}
|
||||
|
||||
func (cmd clearCmd) String() string {
|
||||
return "clear"
|
||||
}
|
||||
|
||||
// Run executes the command sequence of the test. Until the maximum error number
|
||||
// is reached, evaluation errors do not terminate execution.
|
||||
func (t *Test) Run() error {
|
||||
for _, cmd := range t.cmds {
|
||||
err := t.exec(cmd)
|
||||
// TODO(fabxc): aggregate command errors, yield diffs for result
|
||||
// comparison errors.
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// exec processes a single step of the test.
|
||||
func (t *Test) exec(tc testCommand) error {
|
||||
switch cmd := tc.(type) {
|
||||
case *clearCmd:
|
||||
t.clear()
|
||||
|
||||
case *loadCmd:
|
||||
cmd.append(t.storage)
|
||||
t.storage.WaitForIndexing()
|
||||
|
||||
case *evalCmd:
|
||||
q := t.queryEngine.newQuery(cmd.expr, cmd.start, cmd.end, cmd.interval)
|
||||
res := q.Exec(t.context)
|
||||
if res.Err != nil {
|
||||
if cmd.fail {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("error evaluating query: %s", res.Err)
|
||||
}
|
||||
if res.Err == nil && cmd.fail {
|
||||
return fmt.Errorf("expected error evaluating query but got none")
|
||||
}
|
||||
|
||||
err := cmd.compareResult(res.Value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error in %s %s: %s", cmd, cmd.expr, err)
|
||||
}
|
||||
|
||||
default:
|
||||
panic("promql.Test.exec: unknown test command type")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// clear the current test storage of all inserted samples.
|
||||
func (t *Test) clear() {
|
||||
if t.closeStorage != nil {
|
||||
t.closeStorage()
|
||||
}
|
||||
if t.cancelCtx != nil {
|
||||
t.cancelCtx()
|
||||
}
|
||||
|
||||
var closer testutil.Closer
|
||||
t.storage, closer = local.NewTestStorage(t, 2)
|
||||
|
||||
t.closeStorage = closer.Close
|
||||
t.queryEngine = NewEngine(t.storage, nil)
|
||||
t.context, t.cancelCtx = context.WithCancel(context.Background())
|
||||
}
|
||||
|
||||
// Close closes resources associated with the Test.
|
||||
func (t *Test) Close() {
|
||||
t.cancelCtx()
|
||||
t.closeStorage()
|
||||
}
|
||||
|
||||
// samplesAlmostEqual returns true if the two sample lines only differ by a
|
||||
// small relative error in their sample value.
|
||||
func almostEqual(a, b float64) bool {
|
||||
// NaN has no equality but for testing we still want to know whether both values
|
||||
// are NaN.
|
||||
if math.IsNaN(a) && math.IsNaN(b) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Cf. http://floating-point-gui.de/errors/comparison/
|
||||
if a == b {
|
||||
return true
|
||||
}
|
||||
|
||||
diff := math.Abs(a - b)
|
||||
|
||||
if a == 0 || b == 0 || diff < minNormal {
|
||||
return diff < epsilon*minNormal
|
||||
}
|
||||
return diff/(math.Abs(a)+math.Abs(b)) < epsilon
|
||||
}
|
||||
|
||||
func parseNumber(s string) (float64, error) {
|
||||
n, err := strconv.ParseInt(s, 0, 64)
|
||||
f := float64(n)
|
||||
if err != nil {
|
||||
f, err = strconv.ParseFloat(s, 64)
|
||||
}
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("error parsing number: %s", err)
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
494
vendor/github.com/prometheus/prometheus/storage/local/chunk/chunk.go
generated
vendored
Normal file
494
vendor/github.com/prometheus/prometheus/storage/local/chunk/chunk.go
generated
vendored
Normal file
@@ -0,0 +1,494 @@
|
||||
// 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 chunk
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/prometheus/common/model"
|
||||
|
||||
"github.com/prometheus/prometheus/storage/metric"
|
||||
)
|
||||
|
||||
// ChunkLen is the length of a chunk in bytes.
|
||||
const ChunkLen = 1024
|
||||
|
||||
// DefaultEncoding can be changed via a flag.
|
||||
var DefaultEncoding = DoubleDelta
|
||||
|
||||
var (
|
||||
errChunkBoundsExceeded = errors.New("attempted access outside of chunk boundaries")
|
||||
errAddedToEvictedChunk = errors.New("attempted to add sample to evicted chunk")
|
||||
)
|
||||
|
||||
// EvictRequest is a request to evict a chunk from memory.
|
||||
type EvictRequest struct {
|
||||
Desc *Desc
|
||||
Evict bool
|
||||
}
|
||||
|
||||
// Encoding defines which encoding we are using, delta, doubledelta, or varbit
|
||||
type Encoding byte
|
||||
|
||||
// String implements flag.Value.
|
||||
func (e Encoding) String() string {
|
||||
return fmt.Sprintf("%d", e)
|
||||
}
|
||||
|
||||
// Set implements flag.Value.
|
||||
func (e *Encoding) Set(s string) error {
|
||||
switch s {
|
||||
case "0":
|
||||
*e = Delta
|
||||
case "1":
|
||||
*e = DoubleDelta
|
||||
case "2":
|
||||
*e = Varbit
|
||||
default:
|
||||
return fmt.Errorf("invalid chunk encoding: %s", s)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
const (
|
||||
// Delta encoding
|
||||
Delta Encoding = iota
|
||||
// DoubleDelta encoding
|
||||
DoubleDelta
|
||||
// Varbit encoding
|
||||
Varbit
|
||||
)
|
||||
|
||||
// Desc contains meta-data for a chunk. Pay special attention to the
|
||||
// documented requirements for calling its methods concurrently (WRT pinning and
|
||||
// locking). The doc comments spell out the requirements for each method, but
|
||||
// here is an overview and general explanation:
|
||||
//
|
||||
// Everything that changes the pinning of the underlying chunk or deals with its
|
||||
// eviction is protected by a mutex. This affects the following methods: Pin,
|
||||
// Unpin, RefCount, IsEvicted, MaybeEvict. These methods can be called at any
|
||||
// time without further prerequisites.
|
||||
//
|
||||
// Another group of methods acts on (or sets) the underlying chunk. These
|
||||
// methods involve no locking. They may only be called if the caller has pinned
|
||||
// the chunk (to guarantee the chunk is not evicted concurrently). Also, the
|
||||
// caller must make sure nobody else will call these methods concurrently,
|
||||
// either by holding the sole reference to the Desc (usually during loading
|
||||
// or creation) or by locking the fingerprint of the series the Desc
|
||||
// belongs to. The affected methods are: Add, MaybePopulateLastTime, SetChunk.
|
||||
//
|
||||
// Finally, there are the special cases FirstTime and LastTime. LastTime requires
|
||||
// to have locked the fingerprint of the series but the chunk does not need to
|
||||
// be pinned. That's because the ChunkLastTime field in Desc gets populated
|
||||
// upon completion of the chunk (when it is still pinned, and which happens
|
||||
// while the series's fingerprint is locked). Once that has happened, calling
|
||||
// LastTime does not require the chunk to be loaded anymore. Before that has
|
||||
// happened, the chunk is pinned anyway. The ChunkFirstTime field in Desc
|
||||
// is populated upon creation of a Desc, so it is alway safe to call
|
||||
// FirstTime. The FirstTime method is arguably not needed and only there for
|
||||
// consistency with LastTime.
|
||||
type Desc struct {
|
||||
sync.Mutex // Protects pinning.
|
||||
C Chunk // nil if chunk is evicted.
|
||||
rCnt int
|
||||
ChunkFirstTime model.Time // Populated at creation. Immutable.
|
||||
ChunkLastTime model.Time // Populated on closing of the chunk, model.Earliest if unset.
|
||||
|
||||
// EvictListElement is nil if the chunk is not in the evict list.
|
||||
// EvictListElement is _not_ protected by the Desc mutex.
|
||||
// It must only be touched by the evict list handler in MemorySeriesStorage.
|
||||
EvictListElement *list.Element
|
||||
}
|
||||
|
||||
// NewDesc creates a new Desc pointing to the provided chunk. The provided chunk
|
||||
// is assumed to be not persisted yet. Therefore, the refCount of the new
|
||||
// Desc is 1 (preventing eviction prior to persisting).
|
||||
func NewDesc(c Chunk, firstTime model.Time) *Desc {
|
||||
Ops.WithLabelValues(CreateAndPin).Inc()
|
||||
atomic.AddInt64(&NumMemChunks, 1)
|
||||
NumMemDescs.Inc()
|
||||
return &Desc{
|
||||
C: c,
|
||||
rCnt: 1,
|
||||
ChunkFirstTime: firstTime,
|
||||
ChunkLastTime: model.Earliest,
|
||||
}
|
||||
}
|
||||
|
||||
// Add adds a sample pair to the underlying chunk. For safe concurrent access,
|
||||
// The chunk must be pinned, and the caller must have locked the fingerprint of
|
||||
// the series.
|
||||
func (d *Desc) Add(s model.SamplePair) ([]Chunk, error) {
|
||||
if d.C == nil {
|
||||
return nil, errAddedToEvictedChunk
|
||||
}
|
||||
return d.C.Add(s)
|
||||
}
|
||||
|
||||
// Pin increments the refCount by one. Upon increment from 0 to 1, this
|
||||
// Desc is removed from the evict list. To enable the latter, the
|
||||
// evictRequests channel has to be provided. This method can be called
|
||||
// concurrently at any time.
|
||||
func (d *Desc) Pin(evictRequests chan<- EvictRequest) {
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
|
||||
if d.rCnt == 0 {
|
||||
// Remove ourselves from the evict list.
|
||||
evictRequests <- EvictRequest{d, false}
|
||||
}
|
||||
d.rCnt++
|
||||
}
|
||||
|
||||
// Unpin decrements the refCount by one. Upon decrement from 1 to 0, this
|
||||
// Desc is added to the evict list. To enable the latter, the evictRequests
|
||||
// channel has to be provided. This method can be called concurrently at any
|
||||
// time.
|
||||
func (d *Desc) Unpin(evictRequests chan<- EvictRequest) {
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
|
||||
if d.rCnt == 0 {
|
||||
panic("cannot unpin already unpinned chunk")
|
||||
}
|
||||
d.rCnt--
|
||||
if d.rCnt == 0 {
|
||||
// Add ourselves to the back of the evict list.
|
||||
evictRequests <- EvictRequest{d, true}
|
||||
}
|
||||
}
|
||||
|
||||
// RefCount returns the number of pins. This method can be called concurrently
|
||||
// at any time.
|
||||
func (d *Desc) RefCount() int {
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
|
||||
return d.rCnt
|
||||
}
|
||||
|
||||
// FirstTime returns the timestamp of the first sample in the chunk. This method
|
||||
// can be called concurrently at any time. It only returns the immutable
|
||||
// d.ChunkFirstTime without any locking. Arguably, this method is
|
||||
// useless. However, it provides consistency with the LastTime method.
|
||||
func (d *Desc) FirstTime() model.Time {
|
||||
return d.ChunkFirstTime
|
||||
}
|
||||
|
||||
// LastTime returns the timestamp of the last sample in the chunk. For safe
|
||||
// concurrent access, this method requires the fingerprint of the time series to
|
||||
// be locked.
|
||||
func (d *Desc) LastTime() (model.Time, error) {
|
||||
if d.ChunkLastTime != model.Earliest || d.C == nil {
|
||||
return d.ChunkLastTime, nil
|
||||
}
|
||||
return d.C.NewIterator().LastTimestamp()
|
||||
}
|
||||
|
||||
// MaybePopulateLastTime populates the ChunkLastTime from the underlying chunk
|
||||
// if it has not yet happened. Call this method directly after having added the
|
||||
// last sample to a chunk or after closing a head chunk due to age. For safe
|
||||
// concurrent access, the chunk must be pinned, and the caller must have locked
|
||||
// the fingerprint of the series.
|
||||
func (d *Desc) MaybePopulateLastTime() error {
|
||||
if d.ChunkLastTime == model.Earliest && d.C != nil {
|
||||
t, err := d.C.NewIterator().LastTimestamp()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.ChunkLastTime = t
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsEvicted returns whether the chunk is evicted. For safe concurrent access,
|
||||
// the caller must have locked the fingerprint of the series.
|
||||
func (d *Desc) IsEvicted() bool {
|
||||
// Locking required here because we do not want the caller to force
|
||||
// pinning the chunk first, so it could be evicted while this method is
|
||||
// called.
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
|
||||
return d.C == nil
|
||||
}
|
||||
|
||||
// SetChunk sets the underlying chunk. The caller must have locked the
|
||||
// fingerprint of the series and must have "pre-pinned" the chunk (i.e. first
|
||||
// call Pin and then set the chunk).
|
||||
func (d *Desc) SetChunk(c Chunk) {
|
||||
if d.C != nil {
|
||||
panic("chunk already set")
|
||||
}
|
||||
d.C = c
|
||||
}
|
||||
|
||||
// MaybeEvict evicts the chunk if the refCount is 0. It returns whether the chunk
|
||||
// is now evicted, which includes the case that the chunk was evicted even
|
||||
// before this method was called. It can be called concurrently at any time.
|
||||
func (d *Desc) MaybeEvict() bool {
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
|
||||
if d.C == nil {
|
||||
return true
|
||||
}
|
||||
if d.rCnt != 0 {
|
||||
return false
|
||||
}
|
||||
if d.ChunkLastTime == model.Earliest {
|
||||
// This must never happen.
|
||||
panic("ChunkLastTime not populated for evicted chunk")
|
||||
}
|
||||
d.C = nil
|
||||
Ops.WithLabelValues(Evict).Inc()
|
||||
atomic.AddInt64(&NumMemChunks, -1)
|
||||
return true
|
||||
}
|
||||
|
||||
// Chunk is the interface for all chunks. Chunks are generally not
|
||||
// goroutine-safe.
|
||||
type Chunk interface {
|
||||
// Add adds a SamplePair to the chunks, performs any necessary
|
||||
// re-encoding, and adds any necessary overflow chunks. It returns the
|
||||
// new version of the original chunk, followed by overflow chunks, if
|
||||
// any. The first chunk returned might be the same as the original one
|
||||
// or a newly allocated version. In any case, take the returned chunk as
|
||||
// the relevant one and discard the original chunk.
|
||||
Add(sample model.SamplePair) ([]Chunk, error)
|
||||
Clone() Chunk
|
||||
FirstTime() model.Time
|
||||
NewIterator() Iterator
|
||||
Marshal(io.Writer) error
|
||||
MarshalToBuf([]byte) error
|
||||
Unmarshal(io.Reader) error
|
||||
UnmarshalFromBuf([]byte) error
|
||||
Encoding() Encoding
|
||||
Utilization() float64
|
||||
|
||||
// Len returns the number of samples in the chunk. Implementations may be
|
||||
// expensive.
|
||||
Len() int
|
||||
}
|
||||
|
||||
// Iterator enables efficient access to the content of a chunk. It is
|
||||
// generally not safe to use an Iterator concurrently with or after chunk
|
||||
// mutation.
|
||||
type Iterator interface {
|
||||
// Gets the last timestamp in the chunk.
|
||||
LastTimestamp() (model.Time, error)
|
||||
// Whether a given timestamp is contained between first and last value
|
||||
// in the chunk.
|
||||
Contains(model.Time) (bool, error)
|
||||
// Scans the next value in the chunk. Directly after the iterator has
|
||||
// been created, the next value is the first value in the
|
||||
// chunk. Otherwise, it is the value following the last value scanned or
|
||||
// found (by one of the Find... methods). Returns false if either the
|
||||
// end of the chunk is reached or an error has occurred.
|
||||
Scan() bool
|
||||
// Finds the most recent value at or before the provided time. Returns
|
||||
// false if either the chunk contains no value at or before the provided
|
||||
// time, or an error has occurred.
|
||||
FindAtOrBefore(model.Time) bool
|
||||
// Finds the oldest value at or after the provided time. Returns false
|
||||
// if either the chunk contains no value at or after the provided time,
|
||||
// or an error has occurred.
|
||||
FindAtOrAfter(model.Time) bool
|
||||
// Returns the last value scanned (by the scan method) or found (by one
|
||||
// of the find... methods). It returns model.ZeroSamplePair before any of
|
||||
// those methods were called.
|
||||
Value() model.SamplePair
|
||||
// Returns the last error encountered. In general, an error signals data
|
||||
// corruption in the chunk and requires quarantining.
|
||||
Err() error
|
||||
}
|
||||
|
||||
// RangeValues is a utility function that retrieves all values within the given
|
||||
// range from an Iterator.
|
||||
func RangeValues(it Iterator, in metric.Interval) ([]model.SamplePair, error) {
|
||||
result := []model.SamplePair{}
|
||||
if !it.FindAtOrAfter(in.OldestInclusive) {
|
||||
return result, it.Err()
|
||||
}
|
||||
for !it.Value().Timestamp.After(in.NewestInclusive) {
|
||||
result = append(result, it.Value())
|
||||
if !it.Scan() {
|
||||
break
|
||||
}
|
||||
}
|
||||
return result, it.Err()
|
||||
}
|
||||
|
||||
// addToOverflowChunk is a utility function that creates a new chunk as overflow
|
||||
// chunk, adds the provided sample to it, and returns a chunk slice containing
|
||||
// the provided old chunk followed by the new overflow chunk.
|
||||
func addToOverflowChunk(c Chunk, s model.SamplePair) ([]Chunk, error) {
|
||||
overflowChunks, err := New().Add(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []Chunk{c, overflowChunks[0]}, nil
|
||||
}
|
||||
|
||||
// transcodeAndAdd is a utility function that transcodes the dst chunk into the
|
||||
// provided src chunk (plus the necessary overflow chunks) and then adds the
|
||||
// provided sample. It returns the new chunks (transcoded plus overflow) with
|
||||
// the new sample at the end.
|
||||
func transcodeAndAdd(dst Chunk, src Chunk, s model.SamplePair) ([]Chunk, error) {
|
||||
Ops.WithLabelValues(Transcode).Inc()
|
||||
|
||||
var (
|
||||
head = dst
|
||||
body, NewChunks []Chunk
|
||||
err error
|
||||
)
|
||||
|
||||
it := src.NewIterator()
|
||||
for it.Scan() {
|
||||
if NewChunks, err = head.Add(it.Value()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
body = append(body, NewChunks[:len(NewChunks)-1]...)
|
||||
head = NewChunks[len(NewChunks)-1]
|
||||
}
|
||||
if it.Err() != nil {
|
||||
return nil, it.Err()
|
||||
}
|
||||
|
||||
if NewChunks, err = head.Add(s); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return append(body, NewChunks...), nil
|
||||
}
|
||||
|
||||
// New creates a new chunk according to the encoding set by the
|
||||
// DefaultEncoding flag.
|
||||
func New() Chunk {
|
||||
chunk, err := NewForEncoding(DefaultEncoding)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return chunk
|
||||
}
|
||||
|
||||
// NewForEncoding allows configuring what chunk type you want
|
||||
func NewForEncoding(encoding Encoding) (Chunk, error) {
|
||||
switch encoding {
|
||||
case Delta:
|
||||
return newDeltaEncodedChunk(d1, d0, true, ChunkLen), nil
|
||||
case DoubleDelta:
|
||||
return newDoubleDeltaEncodedChunk(d1, d0, true, ChunkLen), nil
|
||||
case Varbit:
|
||||
return newVarbitChunk(varbitZeroEncoding), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown chunk encoding: %v", encoding)
|
||||
}
|
||||
}
|
||||
|
||||
// indexAccessor allows accesses to samples by index.
|
||||
type indexAccessor interface {
|
||||
timestampAtIndex(int) model.Time
|
||||
sampleValueAtIndex(int) model.SampleValue
|
||||
err() error
|
||||
}
|
||||
|
||||
// indexAccessingChunkIterator is a chunk iterator for chunks for which an
|
||||
// indexAccessor implementation exists.
|
||||
type indexAccessingChunkIterator struct {
|
||||
len int
|
||||
pos int
|
||||
lastValue model.SamplePair
|
||||
acc indexAccessor
|
||||
}
|
||||
|
||||
func newIndexAccessingChunkIterator(len int, acc indexAccessor) *indexAccessingChunkIterator {
|
||||
return &indexAccessingChunkIterator{
|
||||
len: len,
|
||||
pos: -1,
|
||||
lastValue: model.ZeroSamplePair,
|
||||
acc: acc,
|
||||
}
|
||||
}
|
||||
|
||||
// lastTimestamp implements Iterator.
|
||||
func (it *indexAccessingChunkIterator) LastTimestamp() (model.Time, error) {
|
||||
return it.acc.timestampAtIndex(it.len - 1), it.acc.err()
|
||||
}
|
||||
|
||||
// contains implements Iterator.
|
||||
func (it *indexAccessingChunkIterator) Contains(t model.Time) (bool, error) {
|
||||
return !t.Before(it.acc.timestampAtIndex(0)) &&
|
||||
!t.After(it.acc.timestampAtIndex(it.len-1)), it.acc.err()
|
||||
}
|
||||
|
||||
// scan implements Iterator.
|
||||
func (it *indexAccessingChunkIterator) Scan() bool {
|
||||
it.pos++
|
||||
if it.pos >= it.len {
|
||||
return false
|
||||
}
|
||||
it.lastValue = model.SamplePair{
|
||||
Timestamp: it.acc.timestampAtIndex(it.pos),
|
||||
Value: it.acc.sampleValueAtIndex(it.pos),
|
||||
}
|
||||
return it.acc.err() == nil
|
||||
}
|
||||
|
||||
// findAtOrBefore implements Iterator.
|
||||
func (it *indexAccessingChunkIterator) FindAtOrBefore(t model.Time) bool {
|
||||
i := sort.Search(it.len, func(i int) bool {
|
||||
return it.acc.timestampAtIndex(i).After(t)
|
||||
})
|
||||
if i == 0 || it.acc.err() != nil {
|
||||
return false
|
||||
}
|
||||
it.pos = i - 1
|
||||
it.lastValue = model.SamplePair{
|
||||
Timestamp: it.acc.timestampAtIndex(i - 1),
|
||||
Value: it.acc.sampleValueAtIndex(i - 1),
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// findAtOrAfter implements Iterator.
|
||||
func (it *indexAccessingChunkIterator) FindAtOrAfter(t model.Time) bool {
|
||||
i := sort.Search(it.len, func(i int) bool {
|
||||
return !it.acc.timestampAtIndex(i).Before(t)
|
||||
})
|
||||
if i == it.len || it.acc.err() != nil {
|
||||
return false
|
||||
}
|
||||
it.pos = i
|
||||
it.lastValue = model.SamplePair{
|
||||
Timestamp: it.acc.timestampAtIndex(i),
|
||||
Value: it.acc.sampleValueAtIndex(i),
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// value implements Iterator.
|
||||
func (it *indexAccessingChunkIterator) Value() model.SamplePair {
|
||||
return it.lastValue
|
||||
}
|
||||
|
||||
// err implements Iterator.
|
||||
func (it *indexAccessingChunkIterator) Err() error {
|
||||
return it.acc.err()
|
||||
}
|
||||
379
vendor/github.com/prometheus/prometheus/storage/local/chunk/delta.go
generated
vendored
Normal file
379
vendor/github.com/prometheus/prometheus/storage/local/chunk/delta.go
generated
vendored
Normal file
@@ -0,0 +1,379 @@
|
||||
// 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 chunk
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
|
||||
"github.com/prometheus/common/model"
|
||||
)
|
||||
|
||||
// The 21-byte header of a delta-encoded chunk looks like:
|
||||
//
|
||||
// - time delta bytes: 1 bytes
|
||||
// - value delta bytes: 1 bytes
|
||||
// - is integer: 1 byte
|
||||
// - base time: 8 bytes
|
||||
// - base value: 8 bytes
|
||||
// - used buf bytes: 2 bytes
|
||||
const (
|
||||
deltaHeaderBytes = 21
|
||||
|
||||
deltaHeaderTimeBytesOffset = 0
|
||||
deltaHeaderValueBytesOffset = 1
|
||||
deltaHeaderIsIntOffset = 2
|
||||
deltaHeaderBaseTimeOffset = 3
|
||||
deltaHeaderBaseValueOffset = 11
|
||||
deltaHeaderBufLenOffset = 19
|
||||
)
|
||||
|
||||
// A deltaEncodedChunk adaptively stores sample timestamps and values with a
|
||||
// delta encoding of various types (int, float) and bit widths. However, once 8
|
||||
// bytes would be needed to encode a delta value, a fall-back to the absolute
|
||||
// numbers happens (so that timestamps are saved directly as int64 and values as
|
||||
// float64). It implements the chunk interface.
|
||||
type deltaEncodedChunk []byte
|
||||
|
||||
// newDeltaEncodedChunk returns a newly allocated deltaEncodedChunk.
|
||||
func newDeltaEncodedChunk(tb, vb deltaBytes, isInt bool, length int) *deltaEncodedChunk {
|
||||
if tb < 1 {
|
||||
panic("need at least 1 time delta byte")
|
||||
}
|
||||
if length < deltaHeaderBytes+16 {
|
||||
panic(fmt.Errorf(
|
||||
"chunk length %d bytes is insufficient, need at least %d",
|
||||
length, deltaHeaderBytes+16,
|
||||
))
|
||||
}
|
||||
c := make(deltaEncodedChunk, deltaHeaderIsIntOffset+1, length)
|
||||
|
||||
c[deltaHeaderTimeBytesOffset] = byte(tb)
|
||||
c[deltaHeaderValueBytesOffset] = byte(vb)
|
||||
if vb < d8 && isInt { // Only use int for fewer than 8 value delta bytes.
|
||||
c[deltaHeaderIsIntOffset] = 1
|
||||
} else {
|
||||
c[deltaHeaderIsIntOffset] = 0
|
||||
}
|
||||
|
||||
return &c
|
||||
}
|
||||
|
||||
// Add implements chunk.
|
||||
func (c deltaEncodedChunk) Add(s model.SamplePair) ([]Chunk, error) {
|
||||
// TODO(beorn7): Since we return &c, this method might cause an unnecessary allocation.
|
||||
if c.Len() == 0 {
|
||||
c = c[:deltaHeaderBytes]
|
||||
binary.LittleEndian.PutUint64(c[deltaHeaderBaseTimeOffset:], uint64(s.Timestamp))
|
||||
binary.LittleEndian.PutUint64(c[deltaHeaderBaseValueOffset:], math.Float64bits(float64(s.Value)))
|
||||
}
|
||||
|
||||
remainingBytes := cap(c) - len(c)
|
||||
sampleSize := c.sampleSize()
|
||||
|
||||
// Do we generally have space for another sample in this chunk? If not,
|
||||
// overflow into a new one.
|
||||
if remainingBytes < sampleSize {
|
||||
return addToOverflowChunk(&c, s)
|
||||
}
|
||||
|
||||
baseValue := c.baseValue()
|
||||
dt := s.Timestamp - c.baseTime()
|
||||
if dt < 0 {
|
||||
return nil, fmt.Errorf("time delta is less than zero: %v", dt)
|
||||
}
|
||||
|
||||
dv := s.Value - baseValue
|
||||
tb := c.timeBytes()
|
||||
vb := c.valueBytes()
|
||||
isInt := c.isInt()
|
||||
|
||||
// If the new sample is incompatible with the current encoding, reencode the
|
||||
// existing chunk data into new chunk(s).
|
||||
|
||||
ntb, nvb, nInt := tb, vb, isInt
|
||||
if isInt && !isInt64(dv) {
|
||||
// int->float.
|
||||
nvb = d4
|
||||
nInt = false
|
||||
} else if !isInt && vb == d4 && baseValue+model.SampleValue(float32(dv)) != s.Value {
|
||||
// float32->float64.
|
||||
nvb = d8
|
||||
} else {
|
||||
if tb < d8 {
|
||||
// Maybe more bytes for timestamp.
|
||||
ntb = max(tb, bytesNeededForUnsignedTimestampDelta(dt))
|
||||
}
|
||||
if c.isInt() && vb < d8 {
|
||||
// Maybe more bytes for sample value.
|
||||
nvb = max(vb, bytesNeededForIntegerSampleValueDelta(dv))
|
||||
}
|
||||
}
|
||||
if tb != ntb || vb != nvb || isInt != nInt {
|
||||
if len(c)*2 < cap(c) {
|
||||
return transcodeAndAdd(newDeltaEncodedChunk(ntb, nvb, nInt, cap(c)), &c, s)
|
||||
}
|
||||
// Chunk is already half full. Better create a new one and save the transcoding efforts.
|
||||
return addToOverflowChunk(&c, s)
|
||||
}
|
||||
|
||||
offset := len(c)
|
||||
c = c[:offset+sampleSize]
|
||||
|
||||
switch tb {
|
||||
case d1:
|
||||
c[offset] = byte(dt)
|
||||
case d2:
|
||||
binary.LittleEndian.PutUint16(c[offset:], uint16(dt))
|
||||
case d4:
|
||||
binary.LittleEndian.PutUint32(c[offset:], uint32(dt))
|
||||
case d8:
|
||||
// Store the absolute value (no delta) in case of d8.
|
||||
binary.LittleEndian.PutUint64(c[offset:], uint64(s.Timestamp))
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid number of bytes for time delta: %d", tb)
|
||||
}
|
||||
|
||||
offset += int(tb)
|
||||
|
||||
if c.isInt() {
|
||||
switch vb {
|
||||
case d0:
|
||||
// No-op. Constant value is stored as base value.
|
||||
case d1:
|
||||
c[offset] = byte(int8(dv))
|
||||
case d2:
|
||||
binary.LittleEndian.PutUint16(c[offset:], uint16(int16(dv)))
|
||||
case d4:
|
||||
binary.LittleEndian.PutUint32(c[offset:], uint32(int32(dv)))
|
||||
// d8 must not happen. Those samples are encoded as float64.
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid number of bytes for integer delta: %d", vb)
|
||||
}
|
||||
} else {
|
||||
switch vb {
|
||||
case d4:
|
||||
binary.LittleEndian.PutUint32(c[offset:], math.Float32bits(float32(dv)))
|
||||
case d8:
|
||||
// Store the absolute value (no delta) in case of d8.
|
||||
binary.LittleEndian.PutUint64(c[offset:], math.Float64bits(float64(s.Value)))
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid number of bytes for floating point delta: %d", vb)
|
||||
}
|
||||
}
|
||||
return []Chunk{&c}, nil
|
||||
}
|
||||
|
||||
// Clone implements chunk.
|
||||
func (c deltaEncodedChunk) Clone() Chunk {
|
||||
clone := make(deltaEncodedChunk, len(c), cap(c))
|
||||
copy(clone, c)
|
||||
return &clone
|
||||
}
|
||||
|
||||
// FirstTime implements chunk.
|
||||
func (c deltaEncodedChunk) FirstTime() model.Time {
|
||||
return c.baseTime()
|
||||
}
|
||||
|
||||
// NewIterator implements chunk.
|
||||
func (c *deltaEncodedChunk) NewIterator() Iterator {
|
||||
return newIndexAccessingChunkIterator(c.Len(), &deltaEncodedIndexAccessor{
|
||||
c: *c,
|
||||
baseT: c.baseTime(),
|
||||
baseV: c.baseValue(),
|
||||
tBytes: c.timeBytes(),
|
||||
vBytes: c.valueBytes(),
|
||||
isInt: c.isInt(),
|
||||
})
|
||||
}
|
||||
|
||||
// Marshal implements chunk.
|
||||
func (c deltaEncodedChunk) Marshal(w io.Writer) error {
|
||||
if len(c) > math.MaxUint16 {
|
||||
panic("chunk buffer length would overflow a 16 bit uint.")
|
||||
}
|
||||
binary.LittleEndian.PutUint16(c[deltaHeaderBufLenOffset:], uint16(len(c)))
|
||||
|
||||
n, err := w.Write(c[:cap(c)])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n != cap(c) {
|
||||
return fmt.Errorf("wanted to write %d bytes, wrote %d", cap(c), n)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalToBuf implements chunk.
|
||||
func (c deltaEncodedChunk) MarshalToBuf(buf []byte) error {
|
||||
if len(c) > math.MaxUint16 {
|
||||
panic("chunk buffer length would overflow a 16 bit uint")
|
||||
}
|
||||
binary.LittleEndian.PutUint16(c[deltaHeaderBufLenOffset:], uint16(len(c)))
|
||||
|
||||
n := copy(buf, c)
|
||||
if n != len(c) {
|
||||
return fmt.Errorf("wanted to copy %d bytes to buffer, copied %d", len(c), n)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unmarshal implements chunk.
|
||||
func (c *deltaEncodedChunk) Unmarshal(r io.Reader) error {
|
||||
*c = (*c)[:cap(*c)]
|
||||
if _, err := io.ReadFull(r, *c); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.setLen()
|
||||
}
|
||||
|
||||
// UnmarshalFromBuf implements chunk.
|
||||
func (c *deltaEncodedChunk) UnmarshalFromBuf(buf []byte) error {
|
||||
*c = (*c)[:cap(*c)]
|
||||
copy(*c, buf)
|
||||
return c.setLen()
|
||||
}
|
||||
|
||||
// setLen sets the length of the underlying slice and performs some sanity checks.
|
||||
func (c *deltaEncodedChunk) setLen() error {
|
||||
l := binary.LittleEndian.Uint16((*c)[deltaHeaderBufLenOffset:])
|
||||
if int(l) > cap(*c) {
|
||||
return fmt.Errorf("delta chunk length exceeded during unmarshaling: %d", l)
|
||||
}
|
||||
if int(l) < deltaHeaderBytes {
|
||||
return fmt.Errorf("delta chunk length less than header size: %d < %d", l, deltaHeaderBytes)
|
||||
}
|
||||
switch c.timeBytes() {
|
||||
case d1, d2, d4, d8:
|
||||
// Pass.
|
||||
default:
|
||||
return fmt.Errorf("invalid number of time bytes in delta chunk: %d", c.timeBytes())
|
||||
}
|
||||
switch c.valueBytes() {
|
||||
case d0, d1, d2, d4, d8:
|
||||
// Pass.
|
||||
default:
|
||||
return fmt.Errorf("invalid number of value bytes in delta chunk: %d", c.valueBytes())
|
||||
}
|
||||
*c = (*c)[:l]
|
||||
return nil
|
||||
}
|
||||
|
||||
// Encoding implements chunk.
|
||||
func (c deltaEncodedChunk) Encoding() Encoding { return Delta }
|
||||
|
||||
// Utilization implements chunk.
|
||||
func (c deltaEncodedChunk) Utilization() float64 {
|
||||
return float64(len(c)) / float64(cap(c))
|
||||
}
|
||||
|
||||
func (c deltaEncodedChunk) timeBytes() deltaBytes {
|
||||
return deltaBytes(c[deltaHeaderTimeBytesOffset])
|
||||
}
|
||||
|
||||
func (c deltaEncodedChunk) valueBytes() deltaBytes {
|
||||
return deltaBytes(c[deltaHeaderValueBytesOffset])
|
||||
}
|
||||
|
||||
func (c deltaEncodedChunk) isInt() bool {
|
||||
return c[deltaHeaderIsIntOffset] == 1
|
||||
}
|
||||
|
||||
func (c deltaEncodedChunk) baseTime() model.Time {
|
||||
return model.Time(binary.LittleEndian.Uint64(c[deltaHeaderBaseTimeOffset:]))
|
||||
}
|
||||
|
||||
func (c deltaEncodedChunk) baseValue() model.SampleValue {
|
||||
return model.SampleValue(math.Float64frombits(binary.LittleEndian.Uint64(c[deltaHeaderBaseValueOffset:])))
|
||||
}
|
||||
|
||||
func (c deltaEncodedChunk) sampleSize() int {
|
||||
return int(c.timeBytes() + c.valueBytes())
|
||||
}
|
||||
|
||||
// Len implements Chunk. Runs in constant time.
|
||||
func (c deltaEncodedChunk) Len() int {
|
||||
if len(c) < deltaHeaderBytes {
|
||||
return 0
|
||||
}
|
||||
return (len(c) - deltaHeaderBytes) / c.sampleSize()
|
||||
}
|
||||
|
||||
// deltaEncodedIndexAccessor implements indexAccessor.
|
||||
type deltaEncodedIndexAccessor struct {
|
||||
c deltaEncodedChunk
|
||||
baseT model.Time
|
||||
baseV model.SampleValue
|
||||
tBytes, vBytes deltaBytes
|
||||
isInt bool
|
||||
lastErr error
|
||||
}
|
||||
|
||||
func (acc *deltaEncodedIndexAccessor) err() error {
|
||||
return acc.lastErr
|
||||
}
|
||||
|
||||
func (acc *deltaEncodedIndexAccessor) timestampAtIndex(idx int) model.Time {
|
||||
offset := deltaHeaderBytes + idx*int(acc.tBytes+acc.vBytes)
|
||||
|
||||
switch acc.tBytes {
|
||||
case d1:
|
||||
return acc.baseT + model.Time(uint8(acc.c[offset]))
|
||||
case d2:
|
||||
return acc.baseT + model.Time(binary.LittleEndian.Uint16(acc.c[offset:]))
|
||||
case d4:
|
||||
return acc.baseT + model.Time(binary.LittleEndian.Uint32(acc.c[offset:]))
|
||||
case d8:
|
||||
// Take absolute value for d8.
|
||||
return model.Time(binary.LittleEndian.Uint64(acc.c[offset:]))
|
||||
default:
|
||||
acc.lastErr = fmt.Errorf("invalid number of bytes for time delta: %d", acc.tBytes)
|
||||
return model.Earliest
|
||||
}
|
||||
}
|
||||
|
||||
func (acc *deltaEncodedIndexAccessor) sampleValueAtIndex(idx int) model.SampleValue {
|
||||
offset := deltaHeaderBytes + idx*int(acc.tBytes+acc.vBytes) + int(acc.tBytes)
|
||||
|
||||
if acc.isInt {
|
||||
switch acc.vBytes {
|
||||
case d0:
|
||||
return acc.baseV
|
||||
case d1:
|
||||
return acc.baseV + model.SampleValue(int8(acc.c[offset]))
|
||||
case d2:
|
||||
return acc.baseV + model.SampleValue(int16(binary.LittleEndian.Uint16(acc.c[offset:])))
|
||||
case d4:
|
||||
return acc.baseV + model.SampleValue(int32(binary.LittleEndian.Uint32(acc.c[offset:])))
|
||||
// No d8 for ints.
|
||||
default:
|
||||
acc.lastErr = fmt.Errorf("invalid number of bytes for integer delta: %d", acc.vBytes)
|
||||
return 0
|
||||
}
|
||||
} else {
|
||||
switch acc.vBytes {
|
||||
case d4:
|
||||
return acc.baseV + model.SampleValue(math.Float32frombits(binary.LittleEndian.Uint32(acc.c[offset:])))
|
||||
case d8:
|
||||
// Take absolute value for d8.
|
||||
return model.SampleValue(math.Float64frombits(binary.LittleEndian.Uint64(acc.c[offset:])))
|
||||
default:
|
||||
acc.lastErr = fmt.Errorf("invalid number of bytes for floating point delta: %d", acc.vBytes)
|
||||
return 0
|
||||
}
|
||||
}
|
||||
}
|
||||
84
vendor/github.com/prometheus/prometheus/storage/local/chunk/delta_helpers.go
generated
vendored
Normal file
84
vendor/github.com/prometheus/prometheus/storage/local/chunk/delta_helpers.go
generated
vendored
Normal file
@@ -0,0 +1,84 @@
|
||||
// Copyright 2015 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 chunk
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/prometheus/common/model"
|
||||
)
|
||||
|
||||
type deltaBytes byte
|
||||
|
||||
const (
|
||||
d0 deltaBytes = 0
|
||||
d1 deltaBytes = 1
|
||||
d2 deltaBytes = 2
|
||||
d4 deltaBytes = 4
|
||||
d8 deltaBytes = 8
|
||||
)
|
||||
|
||||
func bytesNeededForUnsignedTimestampDelta(deltaT model.Time) deltaBytes {
|
||||
switch {
|
||||
case deltaT > math.MaxUint32:
|
||||
return d8
|
||||
case deltaT > math.MaxUint16:
|
||||
return d4
|
||||
case deltaT > math.MaxUint8:
|
||||
return d2
|
||||
default:
|
||||
return d1
|
||||
}
|
||||
}
|
||||
|
||||
func bytesNeededForSignedTimestampDelta(deltaT model.Time) deltaBytes {
|
||||
switch {
|
||||
case deltaT > math.MaxInt32 || deltaT < math.MinInt32:
|
||||
return d8
|
||||
case deltaT > math.MaxInt16 || deltaT < math.MinInt16:
|
||||
return d4
|
||||
case deltaT > math.MaxInt8 || deltaT < math.MinInt8:
|
||||
return d2
|
||||
default:
|
||||
return d1
|
||||
}
|
||||
}
|
||||
|
||||
func bytesNeededForIntegerSampleValueDelta(deltaV model.SampleValue) deltaBytes {
|
||||
switch {
|
||||
case deltaV < math.MinInt32 || deltaV > math.MaxInt32:
|
||||
return d8
|
||||
case deltaV < math.MinInt16 || deltaV > math.MaxInt16:
|
||||
return d4
|
||||
case deltaV < math.MinInt8 || deltaV > math.MaxInt8:
|
||||
return d2
|
||||
case deltaV != 0:
|
||||
return d1
|
||||
default:
|
||||
return d0
|
||||
}
|
||||
}
|
||||
|
||||
func max(a, b deltaBytes) deltaBytes {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// isInt64 returns true if v can be represented as an int64.
|
||||
func isInt64(v model.SampleValue) bool {
|
||||
// Note: Using math.Modf is slower than the conversion approach below.
|
||||
return model.SampleValue(int64(v)) == v
|
||||
}
|
||||
525
vendor/github.com/prometheus/prometheus/storage/local/chunk/doubledelta.go
generated
vendored
Normal file
525
vendor/github.com/prometheus/prometheus/storage/local/chunk/doubledelta.go
generated
vendored
Normal file
@@ -0,0 +1,525 @@
|
||||
// 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 chunk
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
|
||||
"github.com/prometheus/common/model"
|
||||
)
|
||||
|
||||
// The 37-byte header of a delta-encoded chunk looks like:
|
||||
//
|
||||
// - used buf bytes: 2 bytes
|
||||
// - time double-delta bytes: 1 bytes
|
||||
// - value double-delta bytes: 1 bytes
|
||||
// - is integer: 1 byte
|
||||
// - base time: 8 bytes
|
||||
// - base value: 8 bytes
|
||||
// - base time delta: 8 bytes
|
||||
// - base value delta: 8 bytes
|
||||
const (
|
||||
doubleDeltaHeaderBytes = 37
|
||||
doubleDeltaHeaderMinBytes = 21 // header isn't full for chunk w/ one sample
|
||||
|
||||
doubleDeltaHeaderBufLenOffset = 0
|
||||
doubleDeltaHeaderTimeBytesOffset = 2
|
||||
doubleDeltaHeaderValueBytesOffset = 3
|
||||
doubleDeltaHeaderIsIntOffset = 4
|
||||
doubleDeltaHeaderBaseTimeOffset = 5
|
||||
doubleDeltaHeaderBaseValueOffset = 13
|
||||
doubleDeltaHeaderBaseTimeDeltaOffset = 21
|
||||
doubleDeltaHeaderBaseValueDeltaOffset = 29
|
||||
)
|
||||
|
||||
// A doubleDeltaEncodedChunk adaptively stores sample timestamps and values with
|
||||
// a double-delta encoding of various types (int, float) and bit widths. A base
|
||||
// value and timestamp and a base delta for each is saved in the header. The
|
||||
// payload consists of double-deltas, i.e. deviations from the values and
|
||||
// timestamps calculated by applying the base value and time and the base deltas.
|
||||
// However, once 8 bytes would be needed to encode a double-delta value, a
|
||||
// fall-back to the absolute numbers happens (so that timestamps are saved
|
||||
// directly as int64 and values as float64).
|
||||
// doubleDeltaEncodedChunk implements the chunk interface.
|
||||
type doubleDeltaEncodedChunk []byte
|
||||
|
||||
// newDoubleDeltaEncodedChunk returns a newly allocated doubleDeltaEncodedChunk.
|
||||
func newDoubleDeltaEncodedChunk(tb, vb deltaBytes, isInt bool, length int) *doubleDeltaEncodedChunk {
|
||||
if tb < 1 {
|
||||
panic("need at least 1 time delta byte")
|
||||
}
|
||||
if length < doubleDeltaHeaderBytes+16 {
|
||||
panic(fmt.Errorf(
|
||||
"chunk length %d bytes is insufficient, need at least %d",
|
||||
length, doubleDeltaHeaderBytes+16,
|
||||
))
|
||||
}
|
||||
c := make(doubleDeltaEncodedChunk, doubleDeltaHeaderIsIntOffset+1, length)
|
||||
|
||||
c[doubleDeltaHeaderTimeBytesOffset] = byte(tb)
|
||||
c[doubleDeltaHeaderValueBytesOffset] = byte(vb)
|
||||
if vb < d8 && isInt { // Only use int for fewer than 8 value double-delta bytes.
|
||||
c[doubleDeltaHeaderIsIntOffset] = 1
|
||||
} else {
|
||||
c[doubleDeltaHeaderIsIntOffset] = 0
|
||||
}
|
||||
return &c
|
||||
}
|
||||
|
||||
// Add implements chunk.
|
||||
func (c doubleDeltaEncodedChunk) Add(s model.SamplePair) ([]Chunk, error) {
|
||||
// TODO(beorn7): Since we return &c, this method might cause an unnecessary allocation.
|
||||
if c.Len() == 0 {
|
||||
return c.addFirstSample(s), nil
|
||||
}
|
||||
|
||||
tb := c.timeBytes()
|
||||
vb := c.valueBytes()
|
||||
|
||||
if c.Len() == 1 {
|
||||
return c.addSecondSample(s, tb, vb)
|
||||
}
|
||||
|
||||
remainingBytes := cap(c) - len(c)
|
||||
sampleSize := c.sampleSize()
|
||||
|
||||
// Do we generally have space for another sample in this chunk? If not,
|
||||
// overflow into a new one.
|
||||
if remainingBytes < sampleSize {
|
||||
return addToOverflowChunk(&c, s)
|
||||
}
|
||||
|
||||
projectedTime := c.baseTime() + model.Time(c.Len())*c.baseTimeDelta()
|
||||
ddt := s.Timestamp - projectedTime
|
||||
|
||||
projectedValue := c.baseValue() + model.SampleValue(c.Len())*c.baseValueDelta()
|
||||
ddv := s.Value - projectedValue
|
||||
|
||||
ntb, nvb, nInt := tb, vb, c.isInt()
|
||||
// If the new sample is incompatible with the current encoding, reencode the
|
||||
// existing chunk data into new chunk(s).
|
||||
if c.isInt() && !isInt64(ddv) {
|
||||
// int->float.
|
||||
nvb = d4
|
||||
nInt = false
|
||||
} else if !c.isInt() && vb == d4 && projectedValue+model.SampleValue(float32(ddv)) != s.Value {
|
||||
// float32->float64.
|
||||
nvb = d8
|
||||
} else {
|
||||
if tb < d8 {
|
||||
// Maybe more bytes for timestamp.
|
||||
ntb = max(tb, bytesNeededForSignedTimestampDelta(ddt))
|
||||
}
|
||||
if c.isInt() && vb < d8 {
|
||||
// Maybe more bytes for sample value.
|
||||
nvb = max(vb, bytesNeededForIntegerSampleValueDelta(ddv))
|
||||
}
|
||||
}
|
||||
if tb != ntb || vb != nvb || c.isInt() != nInt {
|
||||
if len(c)*2 < cap(c) {
|
||||
return transcodeAndAdd(newDoubleDeltaEncodedChunk(ntb, nvb, nInt, cap(c)), &c, s)
|
||||
}
|
||||
// Chunk is already half full. Better create a new one and save the transcoding efforts.
|
||||
return addToOverflowChunk(&c, s)
|
||||
}
|
||||
|
||||
offset := len(c)
|
||||
c = c[:offset+sampleSize]
|
||||
|
||||
switch tb {
|
||||
case d1:
|
||||
c[offset] = byte(ddt)
|
||||
case d2:
|
||||
binary.LittleEndian.PutUint16(c[offset:], uint16(ddt))
|
||||
case d4:
|
||||
binary.LittleEndian.PutUint32(c[offset:], uint32(ddt))
|
||||
case d8:
|
||||
// Store the absolute value (no delta) in case of d8.
|
||||
binary.LittleEndian.PutUint64(c[offset:], uint64(s.Timestamp))
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid number of bytes for time delta: %d", tb)
|
||||
}
|
||||
|
||||
offset += int(tb)
|
||||
|
||||
if c.isInt() {
|
||||
switch vb {
|
||||
case d0:
|
||||
// No-op. Constant delta is stored as base value.
|
||||
case d1:
|
||||
c[offset] = byte(int8(ddv))
|
||||
case d2:
|
||||
binary.LittleEndian.PutUint16(c[offset:], uint16(int16(ddv)))
|
||||
case d4:
|
||||
binary.LittleEndian.PutUint32(c[offset:], uint32(int32(ddv)))
|
||||
// d8 must not happen. Those samples are encoded as float64.
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid number of bytes for integer delta: %d", vb)
|
||||
}
|
||||
} else {
|
||||
switch vb {
|
||||
case d4:
|
||||
binary.LittleEndian.PutUint32(c[offset:], math.Float32bits(float32(ddv)))
|
||||
case d8:
|
||||
// Store the absolute value (no delta) in case of d8.
|
||||
binary.LittleEndian.PutUint64(c[offset:], math.Float64bits(float64(s.Value)))
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid number of bytes for floating point delta: %d", vb)
|
||||
}
|
||||
}
|
||||
return []Chunk{&c}, nil
|
||||
}
|
||||
|
||||
// Clone implements chunk.
|
||||
func (c doubleDeltaEncodedChunk) Clone() Chunk {
|
||||
clone := make(doubleDeltaEncodedChunk, len(c), cap(c))
|
||||
copy(clone, c)
|
||||
return &clone
|
||||
}
|
||||
|
||||
// FirstTime implements chunk.
|
||||
func (c doubleDeltaEncodedChunk) FirstTime() model.Time {
|
||||
return c.baseTime()
|
||||
}
|
||||
|
||||
// NewIterator( implements chunk.
|
||||
func (c *doubleDeltaEncodedChunk) NewIterator() Iterator {
|
||||
return newIndexAccessingChunkIterator(c.Len(), &doubleDeltaEncodedIndexAccessor{
|
||||
c: *c,
|
||||
baseT: c.baseTime(),
|
||||
baseΔT: c.baseTimeDelta(),
|
||||
baseV: c.baseValue(),
|
||||
baseΔV: c.baseValueDelta(),
|
||||
tBytes: c.timeBytes(),
|
||||
vBytes: c.valueBytes(),
|
||||
isInt: c.isInt(),
|
||||
})
|
||||
}
|
||||
|
||||
// Marshal implements chunk.
|
||||
func (c doubleDeltaEncodedChunk) Marshal(w io.Writer) error {
|
||||
if len(c) > math.MaxUint16 {
|
||||
panic("chunk buffer length would overflow a 16 bit uint")
|
||||
}
|
||||
binary.LittleEndian.PutUint16(c[doubleDeltaHeaderBufLenOffset:], uint16(len(c)))
|
||||
|
||||
n, err := w.Write(c[:cap(c)])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n != cap(c) {
|
||||
return fmt.Errorf("wanted to write %d bytes, wrote %d", cap(c), n)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalToBuf implements chunk.
|
||||
func (c doubleDeltaEncodedChunk) MarshalToBuf(buf []byte) error {
|
||||
if len(c) > math.MaxUint16 {
|
||||
panic("chunk buffer length would overflow a 16 bit uint")
|
||||
}
|
||||
binary.LittleEndian.PutUint16(c[doubleDeltaHeaderBufLenOffset:], uint16(len(c)))
|
||||
|
||||
n := copy(buf, c)
|
||||
if n != len(c) {
|
||||
return fmt.Errorf("wanted to copy %d bytes to buffer, copied %d", len(c), n)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unmarshal implements chunk.
|
||||
func (c *doubleDeltaEncodedChunk) Unmarshal(r io.Reader) error {
|
||||
*c = (*c)[:cap(*c)]
|
||||
if _, err := io.ReadFull(r, *c); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.setLen()
|
||||
}
|
||||
|
||||
// UnmarshalFromBuf implements chunk.
|
||||
func (c *doubleDeltaEncodedChunk) UnmarshalFromBuf(buf []byte) error {
|
||||
*c = (*c)[:cap(*c)]
|
||||
copy(*c, buf)
|
||||
return c.setLen()
|
||||
}
|
||||
|
||||
// setLen sets the length of the underlying slice and performs some sanity checks.
|
||||
func (c *doubleDeltaEncodedChunk) setLen() error {
|
||||
l := binary.LittleEndian.Uint16((*c)[doubleDeltaHeaderBufLenOffset:])
|
||||
if int(l) > cap(*c) {
|
||||
return fmt.Errorf("doubledelta chunk length exceeded during unmarshaling: %d", l)
|
||||
}
|
||||
if int(l) < doubleDeltaHeaderMinBytes {
|
||||
return fmt.Errorf("doubledelta chunk length less than header size: %d < %d", l, doubleDeltaHeaderMinBytes)
|
||||
}
|
||||
switch c.timeBytes() {
|
||||
case d1, d2, d4, d8:
|
||||
// Pass.
|
||||
default:
|
||||
return fmt.Errorf("invalid number of time bytes in doubledelta chunk: %d", c.timeBytes())
|
||||
}
|
||||
switch c.valueBytes() {
|
||||
case d0, d1, d2, d4, d8:
|
||||
// Pass.
|
||||
default:
|
||||
return fmt.Errorf("invalid number of value bytes in doubledelta chunk: %d", c.valueBytes())
|
||||
}
|
||||
*c = (*c)[:l]
|
||||
return nil
|
||||
}
|
||||
|
||||
// Encoding implements chunk.
|
||||
func (c doubleDeltaEncodedChunk) Encoding() Encoding { return DoubleDelta }
|
||||
|
||||
// Utilization implements chunk.
|
||||
func (c doubleDeltaEncodedChunk) Utilization() float64 {
|
||||
return float64(len(c)-doubleDeltaHeaderIsIntOffset-1) / float64(cap(c))
|
||||
}
|
||||
|
||||
func (c doubleDeltaEncodedChunk) baseTime() model.Time {
|
||||
return model.Time(
|
||||
binary.LittleEndian.Uint64(
|
||||
c[doubleDeltaHeaderBaseTimeOffset:],
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
func (c doubleDeltaEncodedChunk) baseValue() model.SampleValue {
|
||||
return model.SampleValue(
|
||||
math.Float64frombits(
|
||||
binary.LittleEndian.Uint64(
|
||||
c[doubleDeltaHeaderBaseValueOffset:],
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
func (c doubleDeltaEncodedChunk) baseTimeDelta() model.Time {
|
||||
if len(c) < doubleDeltaHeaderBaseTimeDeltaOffset+8 {
|
||||
return 0
|
||||
}
|
||||
return model.Time(
|
||||
binary.LittleEndian.Uint64(
|
||||
c[doubleDeltaHeaderBaseTimeDeltaOffset:],
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
func (c doubleDeltaEncodedChunk) baseValueDelta() model.SampleValue {
|
||||
if len(c) < doubleDeltaHeaderBaseValueDeltaOffset+8 {
|
||||
return 0
|
||||
}
|
||||
return model.SampleValue(
|
||||
math.Float64frombits(
|
||||
binary.LittleEndian.Uint64(
|
||||
c[doubleDeltaHeaderBaseValueDeltaOffset:],
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
func (c doubleDeltaEncodedChunk) timeBytes() deltaBytes {
|
||||
return deltaBytes(c[doubleDeltaHeaderTimeBytesOffset])
|
||||
}
|
||||
|
||||
func (c doubleDeltaEncodedChunk) valueBytes() deltaBytes {
|
||||
return deltaBytes(c[doubleDeltaHeaderValueBytesOffset])
|
||||
}
|
||||
|
||||
func (c doubleDeltaEncodedChunk) sampleSize() int {
|
||||
return int(c.timeBytes() + c.valueBytes())
|
||||
}
|
||||
|
||||
// Len implements Chunk. Runs in constant time.
|
||||
func (c doubleDeltaEncodedChunk) Len() int {
|
||||
if len(c) <= doubleDeltaHeaderIsIntOffset+1 {
|
||||
return 0
|
||||
}
|
||||
if len(c) <= doubleDeltaHeaderBaseValueOffset+8 {
|
||||
return 1
|
||||
}
|
||||
return (len(c)-doubleDeltaHeaderBytes)/c.sampleSize() + 2
|
||||
}
|
||||
|
||||
func (c doubleDeltaEncodedChunk) isInt() bool {
|
||||
return c[doubleDeltaHeaderIsIntOffset] == 1
|
||||
}
|
||||
|
||||
// addFirstSample is a helper method only used by c.add(). It adds timestamp and
|
||||
// value as base time and value.
|
||||
func (c doubleDeltaEncodedChunk) addFirstSample(s model.SamplePair) []Chunk {
|
||||
c = c[:doubleDeltaHeaderBaseValueOffset+8]
|
||||
binary.LittleEndian.PutUint64(
|
||||
c[doubleDeltaHeaderBaseTimeOffset:],
|
||||
uint64(s.Timestamp),
|
||||
)
|
||||
binary.LittleEndian.PutUint64(
|
||||
c[doubleDeltaHeaderBaseValueOffset:],
|
||||
math.Float64bits(float64(s.Value)),
|
||||
)
|
||||
return []Chunk{&c}
|
||||
}
|
||||
|
||||
// addSecondSample is a helper method only used by c.add(). It calculates the
|
||||
// base delta from the provided sample and adds it to the chunk.
|
||||
func (c doubleDeltaEncodedChunk) addSecondSample(s model.SamplePair, tb, vb deltaBytes) ([]Chunk, error) {
|
||||
baseTimeDelta := s.Timestamp - c.baseTime()
|
||||
if baseTimeDelta < 0 {
|
||||
return nil, fmt.Errorf("base time delta is less than zero: %v", baseTimeDelta)
|
||||
}
|
||||
c = c[:doubleDeltaHeaderBytes]
|
||||
if tb >= d8 || bytesNeededForUnsignedTimestampDelta(baseTimeDelta) >= d8 {
|
||||
// If already the base delta needs d8 (or we are at d8
|
||||
// already, anyway), we better encode this timestamp
|
||||
// directly rather than as a delta and switch everything
|
||||
// to d8.
|
||||
c[doubleDeltaHeaderTimeBytesOffset] = byte(d8)
|
||||
binary.LittleEndian.PutUint64(
|
||||
c[doubleDeltaHeaderBaseTimeDeltaOffset:],
|
||||
uint64(s.Timestamp),
|
||||
)
|
||||
} else {
|
||||
binary.LittleEndian.PutUint64(
|
||||
c[doubleDeltaHeaderBaseTimeDeltaOffset:],
|
||||
uint64(baseTimeDelta),
|
||||
)
|
||||
}
|
||||
baseValue := c.baseValue()
|
||||
baseValueDelta := s.Value - baseValue
|
||||
if vb >= d8 || baseValue+baseValueDelta != s.Value {
|
||||
// If we can't reproduce the original sample value (or
|
||||
// if we are at d8 already, anyway), we better encode
|
||||
// this value directly rather than as a delta and switch
|
||||
// everything to d8.
|
||||
c[doubleDeltaHeaderValueBytesOffset] = byte(d8)
|
||||
c[doubleDeltaHeaderIsIntOffset] = 0
|
||||
binary.LittleEndian.PutUint64(
|
||||
c[doubleDeltaHeaderBaseValueDeltaOffset:],
|
||||
math.Float64bits(float64(s.Value)),
|
||||
)
|
||||
} else {
|
||||
binary.LittleEndian.PutUint64(
|
||||
c[doubleDeltaHeaderBaseValueDeltaOffset:],
|
||||
math.Float64bits(float64(baseValueDelta)),
|
||||
)
|
||||
}
|
||||
return []Chunk{&c}, nil
|
||||
}
|
||||
|
||||
// doubleDeltaEncodedIndexAccessor implements indexAccessor.
|
||||
type doubleDeltaEncodedIndexAccessor struct {
|
||||
c doubleDeltaEncodedChunk
|
||||
baseT, baseΔT model.Time
|
||||
baseV, baseΔV model.SampleValue
|
||||
tBytes, vBytes deltaBytes
|
||||
isInt bool
|
||||
lastErr error
|
||||
}
|
||||
|
||||
func (acc *doubleDeltaEncodedIndexAccessor) err() error {
|
||||
return acc.lastErr
|
||||
}
|
||||
|
||||
func (acc *doubleDeltaEncodedIndexAccessor) timestampAtIndex(idx int) model.Time {
|
||||
if idx == 0 {
|
||||
return acc.baseT
|
||||
}
|
||||
if idx == 1 {
|
||||
// If time bytes are at d8, the time is saved directly rather
|
||||
// than as a difference.
|
||||
if acc.tBytes == d8 {
|
||||
return acc.baseΔT
|
||||
}
|
||||
return acc.baseT + acc.baseΔT
|
||||
}
|
||||
|
||||
offset := doubleDeltaHeaderBytes + (idx-2)*int(acc.tBytes+acc.vBytes)
|
||||
|
||||
switch acc.tBytes {
|
||||
case d1:
|
||||
return acc.baseT +
|
||||
model.Time(idx)*acc.baseΔT +
|
||||
model.Time(int8(acc.c[offset]))
|
||||
case d2:
|
||||
return acc.baseT +
|
||||
model.Time(idx)*acc.baseΔT +
|
||||
model.Time(int16(binary.LittleEndian.Uint16(acc.c[offset:])))
|
||||
case d4:
|
||||
return acc.baseT +
|
||||
model.Time(idx)*acc.baseΔT +
|
||||
model.Time(int32(binary.LittleEndian.Uint32(acc.c[offset:])))
|
||||
case d8:
|
||||
// Take absolute value for d8.
|
||||
return model.Time(binary.LittleEndian.Uint64(acc.c[offset:]))
|
||||
default:
|
||||
acc.lastErr = fmt.Errorf("invalid number of bytes for time delta: %d", acc.tBytes)
|
||||
return model.Earliest
|
||||
}
|
||||
}
|
||||
|
||||
func (acc *doubleDeltaEncodedIndexAccessor) sampleValueAtIndex(idx int) model.SampleValue {
|
||||
if idx == 0 {
|
||||
return acc.baseV
|
||||
}
|
||||
if idx == 1 {
|
||||
// If value bytes are at d8, the value is saved directly rather
|
||||
// than as a difference.
|
||||
if acc.vBytes == d8 {
|
||||
return acc.baseΔV
|
||||
}
|
||||
return acc.baseV + acc.baseΔV
|
||||
}
|
||||
|
||||
offset := doubleDeltaHeaderBytes + (idx-2)*int(acc.tBytes+acc.vBytes) + int(acc.tBytes)
|
||||
|
||||
if acc.isInt {
|
||||
switch acc.vBytes {
|
||||
case d0:
|
||||
return acc.baseV +
|
||||
model.SampleValue(idx)*acc.baseΔV
|
||||
case d1:
|
||||
return acc.baseV +
|
||||
model.SampleValue(idx)*acc.baseΔV +
|
||||
model.SampleValue(int8(acc.c[offset]))
|
||||
case d2:
|
||||
return acc.baseV +
|
||||
model.SampleValue(idx)*acc.baseΔV +
|
||||
model.SampleValue(int16(binary.LittleEndian.Uint16(acc.c[offset:])))
|
||||
case d4:
|
||||
return acc.baseV +
|
||||
model.SampleValue(idx)*acc.baseΔV +
|
||||
model.SampleValue(int32(binary.LittleEndian.Uint32(acc.c[offset:])))
|
||||
// No d8 for ints.
|
||||
default:
|
||||
acc.lastErr = fmt.Errorf("invalid number of bytes for integer delta: %d", acc.vBytes)
|
||||
return 0
|
||||
}
|
||||
} else {
|
||||
switch acc.vBytes {
|
||||
case d4:
|
||||
return acc.baseV +
|
||||
model.SampleValue(idx)*acc.baseΔV +
|
||||
model.SampleValue(math.Float32frombits(binary.LittleEndian.Uint32(acc.c[offset:])))
|
||||
case d8:
|
||||
// Take absolute value for d8.
|
||||
return model.SampleValue(math.Float64frombits(binary.LittleEndian.Uint64(acc.c[offset:])))
|
||||
default:
|
||||
acc.lastErr = fmt.Errorf("invalid number of bytes for floating point delta: %d", acc.vBytes)
|
||||
return 0
|
||||
}
|
||||
}
|
||||
}
|
||||
90
vendor/github.com/prometheus/prometheus/storage/local/chunk/instrumentation.go
generated
vendored
Normal file
90
vendor/github.com/prometheus/prometheus/storage/local/chunk/instrumentation.go
generated
vendored
Normal file
@@ -0,0 +1,90 @@
|
||||
// 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 chunk
|
||||
|
||||
import "github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
// Usually, a separate file for instrumentation is frowned upon. Metrics should
|
||||
// be close to where they are used. However, the metrics below are set all over
|
||||
// the place, so we go for a separate instrumentation file in this case.
|
||||
var (
|
||||
Ops = prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: "chunk_ops_total",
|
||||
Help: "The total number of chunk operations by their type.",
|
||||
},
|
||||
[]string{OpTypeLabel},
|
||||
)
|
||||
DescOps = prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: "chunkdesc_ops_total",
|
||||
Help: "The total number of chunk descriptor operations by their type.",
|
||||
},
|
||||
[]string{OpTypeLabel},
|
||||
)
|
||||
NumMemDescs = prometheus.NewGauge(prometheus.GaugeOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: "memory_chunkdescs",
|
||||
Help: "The current number of chunk descriptors in memory.",
|
||||
})
|
||||
)
|
||||
|
||||
const (
|
||||
namespace = "prometheus"
|
||||
subsystem = "local_storage"
|
||||
|
||||
// OpTypeLabel is the label name for chunk operation types.
|
||||
OpTypeLabel = "type"
|
||||
|
||||
// Op-types for ChunkOps.
|
||||
|
||||
// CreateAndPin is the label value for create-and-pin chunk ops.
|
||||
CreateAndPin = "create" // A Desc creation with refCount=1.
|
||||
// PersistAndUnpin is the label value for persist chunk ops.
|
||||
PersistAndUnpin = "persist"
|
||||
// Pin is the label value for pin chunk ops (excludes pin on creation).
|
||||
Pin = "pin"
|
||||
// Unpin is the label value for unpin chunk ops (excludes the unpin on persisting).
|
||||
Unpin = "unpin"
|
||||
// Clone is the label value for clone chunk ops.
|
||||
Clone = "clone"
|
||||
// Transcode is the label value for transcode chunk ops.
|
||||
Transcode = "transcode"
|
||||
// Drop is the label value for drop chunk ops.
|
||||
Drop = "drop"
|
||||
|
||||
// Op-types for ChunkOps and ChunkDescOps.
|
||||
|
||||
// Evict is the label value for evict chunk desc ops.
|
||||
Evict = "evict"
|
||||
// Load is the label value for load chunk and chunk desc ops.
|
||||
Load = "load"
|
||||
)
|
||||
|
||||
func init() {
|
||||
prometheus.MustRegister(Ops)
|
||||
prometheus.MustRegister(DescOps)
|
||||
prometheus.MustRegister(NumMemDescs)
|
||||
}
|
||||
|
||||
// NumMemChunks is the total number of chunks in memory. This is a global
|
||||
// counter, also used internally, so not implemented as metrics. Collected in
|
||||
// MemorySeriesStorage.
|
||||
// TODO(beorn7): Having this as an exported global variable is really bad.
|
||||
var NumMemChunks int64
|
||||
1210
vendor/github.com/prometheus/prometheus/storage/local/chunk/varbit.go
generated
vendored
Normal file
1210
vendor/github.com/prometheus/prometheus/storage/local/chunk/varbit.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
75
vendor/github.com/prometheus/prometheus/storage/local/chunk/varbit_helpers.go
generated
vendored
Normal file
75
vendor/github.com/prometheus/prometheus/storage/local/chunk/varbit_helpers.go
generated
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
// 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 chunk
|
||||
|
||||
import "github.com/prometheus/common/model"
|
||||
|
||||
var (
|
||||
// bit masks for consecutive bits in a byte at various offsets.
|
||||
bitMask = [][]byte{
|
||||
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // 0 bit
|
||||
{0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01}, // 1 bit
|
||||
{0xC0, 0x60, 0x30, 0x18, 0x0C, 0x06, 0x03, 0x01}, // 2 bit
|
||||
{0xE0, 0x70, 0x38, 0x1C, 0x0E, 0x07, 0x03, 0x01}, // 3 bit
|
||||
{0xF0, 0x78, 0x3C, 0x1E, 0x0F, 0x07, 0x03, 0x01}, // 4 bit
|
||||
{0xF8, 0x7C, 0x3E, 0x1F, 0x0F, 0x07, 0x03, 0x01}, // 5 bit
|
||||
{0xFC, 0x7E, 0x3F, 0x1F, 0x0F, 0x07, 0x03, 0x01}, // 6 bit
|
||||
{0xFE, 0x7F, 0x3F, 0x1F, 0x0F, 0x07, 0x03, 0x01}, // 7 bit
|
||||
{0xFF, 0x7F, 0x3F, 0x1F, 0x0F, 0x07, 0x03, 0x01}, // 8 bit
|
||||
}
|
||||
)
|
||||
|
||||
// isInt32 returns true if v can be represented as an int32.
|
||||
func isInt32(v model.SampleValue) bool {
|
||||
return model.SampleValue(int32(v)) == v
|
||||
}
|
||||
|
||||
// countBits returs the number of leading zero bits and the number of
|
||||
// significant bits after that in the given bit pattern. The maximum number of
|
||||
// leading zeros is 31 (so that it can be represented by a 5bit number). Leading
|
||||
// zeros beyond that are considered part of the significant bits.
|
||||
func countBits(pattern uint64) (leading, significant byte) {
|
||||
// TODO(beorn7): This would probably be faster with ugly endless switch
|
||||
// statements.
|
||||
if pattern == 0 {
|
||||
return
|
||||
}
|
||||
for pattern < 1<<63 {
|
||||
leading++
|
||||
pattern <<= 1
|
||||
}
|
||||
for pattern > 0 {
|
||||
significant++
|
||||
pattern <<= 1
|
||||
}
|
||||
if leading > 31 { // 5 bit limit.
|
||||
significant += leading - 31
|
||||
leading = 31
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// isSignedIntN returns if n can be represented as a signed int with the given
|
||||
// bit length.
|
||||
func isSignedIntN(i int64, n byte) bool {
|
||||
upper := int64(1) << (n - 1)
|
||||
if i >= upper {
|
||||
return false
|
||||
}
|
||||
lower := upper - (1 << n)
|
||||
if i < lower {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
467
vendor/github.com/prometheus/prometheus/storage/local/codable/codable.go
generated
vendored
Normal file
467
vendor/github.com/prometheus/prometheus/storage/local/codable/codable.go
generated
vendored
Normal file
@@ -0,0 +1,467 @@
|
||||
// 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 codable provides types that implement encoding.BinaryMarshaler and
|
||||
// encoding.BinaryUnmarshaler and functions that help to encode and decode
|
||||
// primitives. The Prometheus storage backend uses them to persist objects to
|
||||
// files and to save objects in LevelDB.
|
||||
//
|
||||
// The encodings used in this package are designed in a way that objects can be
|
||||
// unmarshaled from a continuous byte stream, i.e. the information when to stop
|
||||
// reading is determined by the format. No separate termination information is
|
||||
// needed.
|
||||
//
|
||||
// Strings are encoded as the length of their bytes as a varint followed by
|
||||
// their bytes.
|
||||
//
|
||||
// Slices are encoded as their length as a varint followed by their elements.
|
||||
//
|
||||
// Maps are encoded as the number of mappings as a varint, followed by the
|
||||
// mappings, each of which consists of the key followed by the value.
|
||||
package codable
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"github.com/prometheus/common/model"
|
||||
)
|
||||
|
||||
// A byteReader is an io.ByteReader that also implements the vanilla io.Reader
|
||||
// interface.
|
||||
type byteReader interface {
|
||||
io.Reader
|
||||
io.ByteReader
|
||||
}
|
||||
|
||||
// bufPool is a pool for staging buffers. Using a pool allows concurrency-safe
|
||||
// reuse of buffers
|
||||
var bufPool sync.Pool
|
||||
|
||||
// getBuf returns a buffer from the pool. The length of the returned slice is l.
|
||||
func getBuf(l int) []byte {
|
||||
x := bufPool.Get()
|
||||
if x == nil {
|
||||
return make([]byte, l)
|
||||
}
|
||||
buf := x.([]byte)
|
||||
if cap(buf) < l {
|
||||
return make([]byte, l)
|
||||
}
|
||||
return buf[:l]
|
||||
}
|
||||
|
||||
// putBuf returns a buffer to the pool.
|
||||
func putBuf(buf []byte) {
|
||||
bufPool.Put(buf)
|
||||
}
|
||||
|
||||
// EncodeVarint encodes an int64 as a varint and writes it to an io.Writer.
|
||||
// It returns the number of bytes written.
|
||||
// This is a GC-friendly implementation that takes the required staging buffer
|
||||
// from a buffer pool.
|
||||
func EncodeVarint(w io.Writer, i int64) (int, error) {
|
||||
buf := getBuf(binary.MaxVarintLen64)
|
||||
defer putBuf(buf)
|
||||
|
||||
bytesWritten := binary.PutVarint(buf, i)
|
||||
_, err := w.Write(buf[:bytesWritten])
|
||||
return bytesWritten, err
|
||||
}
|
||||
|
||||
// EncodeUvarint encodes an uint64 as a varint and writes it to an io.Writer.
|
||||
// It returns the number of bytes written.
|
||||
// This is a GC-friendly implementation that takes the required staging buffer
|
||||
// from a buffer pool.
|
||||
func EncodeUvarint(w io.Writer, i uint64) (int, error) {
|
||||
buf := getBuf(binary.MaxVarintLen64)
|
||||
defer putBuf(buf)
|
||||
|
||||
bytesWritten := binary.PutUvarint(buf, i)
|
||||
_, err := w.Write(buf[:bytesWritten])
|
||||
return bytesWritten, err
|
||||
}
|
||||
|
||||
// EncodeUint64 writes an uint64 to an io.Writer in big-endian byte-order.
|
||||
// This is a GC-friendly implementation that takes the required staging buffer
|
||||
// from a buffer pool.
|
||||
func EncodeUint64(w io.Writer, u uint64) error {
|
||||
buf := getBuf(8)
|
||||
defer putBuf(buf)
|
||||
|
||||
binary.BigEndian.PutUint64(buf, u)
|
||||
_, err := w.Write(buf)
|
||||
return err
|
||||
}
|
||||
|
||||
// DecodeUint64 reads an uint64 from an io.Reader in big-endian byte-order.
|
||||
// This is a GC-friendly implementation that takes the required staging buffer
|
||||
// from a buffer pool.
|
||||
func DecodeUint64(r io.Reader) (uint64, error) {
|
||||
buf := getBuf(8)
|
||||
defer putBuf(buf)
|
||||
|
||||
if _, err := io.ReadFull(r, buf); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return binary.BigEndian.Uint64(buf), nil
|
||||
}
|
||||
|
||||
// encodeString writes the varint encoded length followed by the bytes of s to
|
||||
// b.
|
||||
func encodeString(b *bytes.Buffer, s string) error {
|
||||
// Note that this should have used EncodeUvarint but a glitch happened
|
||||
// while designing the checkpoint format.
|
||||
if _, err := EncodeVarint(b, int64(len(s))); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := b.WriteString(s); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// decodeString decodes a string encoded by encodeString.
|
||||
func decodeString(b byteReader) (string, error) {
|
||||
length, err := binary.ReadVarint(b)
|
||||
if length < 0 {
|
||||
err = fmt.Errorf("found negative string length during decoding: %d", length)
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
buf := getBuf(int(length))
|
||||
defer putBuf(buf)
|
||||
|
||||
if _, err := io.ReadFull(b, buf); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(buf), nil
|
||||
}
|
||||
|
||||
// A Metric is a model.Metric that implements
|
||||
// encoding.BinaryMarshaler and encoding.BinaryUnmarshaler.
|
||||
type Metric model.Metric
|
||||
|
||||
// MarshalBinary implements encoding.BinaryMarshaler.
|
||||
func (m Metric) MarshalBinary() ([]byte, error) {
|
||||
buf := &bytes.Buffer{}
|
||||
// Note that this should have used EncodeUvarint but a glitch happened
|
||||
// while designing the checkpoint format.
|
||||
if _, err := EncodeVarint(buf, int64(len(m))); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for l, v := range m {
|
||||
if err := encodeString(buf, string(l)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := encodeString(buf, string(v)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements encoding.BinaryUnmarshaler. It can be used with the
|
||||
// zero value of Metric.
|
||||
func (m *Metric) UnmarshalBinary(buf []byte) error {
|
||||
return m.UnmarshalFromReader(bytes.NewReader(buf))
|
||||
}
|
||||
|
||||
// UnmarshalFromReader unmarshals a Metric from a reader that implements
|
||||
// both, io.Reader and io.ByteReader. It can be used with the zero value of
|
||||
// Metric.
|
||||
func (m *Metric) UnmarshalFromReader(r byteReader) error {
|
||||
numLabelPairs, err := binary.ReadVarint(r)
|
||||
if numLabelPairs < 0 {
|
||||
err = fmt.Errorf("found negative numLabelPairs during unmarshaling: %d", numLabelPairs)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*m = make(Metric, numLabelPairs)
|
||||
|
||||
for ; numLabelPairs > 0; numLabelPairs-- {
|
||||
ln, err := decodeString(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
lv, err := decodeString(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
(*m)[model.LabelName(ln)] = model.LabelValue(lv)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// A Fingerprint is a model.Fingerprint that implements
|
||||
// encoding.BinaryMarshaler and encoding.BinaryUnmarshaler. The implementation
|
||||
// depends on model.Fingerprint to be convertible to uint64. It encodes
|
||||
// the fingerprint as a big-endian uint64.
|
||||
type Fingerprint model.Fingerprint
|
||||
|
||||
// MarshalBinary implements encoding.BinaryMarshaler.
|
||||
func (fp Fingerprint) MarshalBinary() ([]byte, error) {
|
||||
b := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(b, uint64(fp))
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements encoding.BinaryUnmarshaler.
|
||||
func (fp *Fingerprint) UnmarshalBinary(buf []byte) error {
|
||||
*fp = Fingerprint(binary.BigEndian.Uint64(buf))
|
||||
return nil
|
||||
}
|
||||
|
||||
// FingerprintSet is a map[model.Fingerprint]struct{} that
|
||||
// implements encoding.BinaryMarshaler and encoding.BinaryUnmarshaler. Its
|
||||
// binary form is identical to that of Fingerprints.
|
||||
type FingerprintSet map[model.Fingerprint]struct{}
|
||||
|
||||
// MarshalBinary implements encoding.BinaryMarshaler.
|
||||
func (fps FingerprintSet) MarshalBinary() ([]byte, error) {
|
||||
b := make([]byte, binary.MaxVarintLen64+len(fps)*8)
|
||||
lenBytes := binary.PutVarint(b, int64(len(fps)))
|
||||
offset := lenBytes
|
||||
|
||||
for fp := range fps {
|
||||
binary.BigEndian.PutUint64(b[offset:], uint64(fp))
|
||||
offset += 8
|
||||
}
|
||||
return b[:len(fps)*8+lenBytes], nil
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements encoding.BinaryUnmarshaler.
|
||||
func (fps *FingerprintSet) UnmarshalBinary(buf []byte) error {
|
||||
numFPs, offset := binary.Varint(buf)
|
||||
if offset <= 0 {
|
||||
return fmt.Errorf("could not decode length of Fingerprints, varint decoding returned %d", offset)
|
||||
}
|
||||
*fps = make(FingerprintSet, numFPs)
|
||||
|
||||
for i := 0; i < int(numFPs); i++ {
|
||||
(*fps)[model.Fingerprint(binary.BigEndian.Uint64(buf[offset+i*8:]))] = struct{}{}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Fingerprints is a model.Fingerprints that implements
|
||||
// encoding.BinaryMarshaler and encoding.BinaryUnmarshaler. Its binary form is
|
||||
// identical to that of FingerprintSet.
|
||||
type Fingerprints model.Fingerprints
|
||||
|
||||
// MarshalBinary implements encoding.BinaryMarshaler.
|
||||
func (fps Fingerprints) MarshalBinary() ([]byte, error) {
|
||||
b := make([]byte, binary.MaxVarintLen64+len(fps)*8)
|
||||
lenBytes := binary.PutVarint(b, int64(len(fps)))
|
||||
|
||||
for i, fp := range fps {
|
||||
binary.BigEndian.PutUint64(b[i*8+lenBytes:], uint64(fp))
|
||||
}
|
||||
return b[:len(fps)*8+lenBytes], nil
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements encoding.BinaryUnmarshaler.
|
||||
func (fps *Fingerprints) UnmarshalBinary(buf []byte) error {
|
||||
numFPs, offset := binary.Varint(buf)
|
||||
if offset <= 0 {
|
||||
return fmt.Errorf("could not decode length of Fingerprints, varint decoding returned %d", offset)
|
||||
}
|
||||
*fps = make(Fingerprints, numFPs)
|
||||
|
||||
for i := range *fps {
|
||||
(*fps)[i] = model.Fingerprint(binary.BigEndian.Uint64(buf[offset+i*8:]))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LabelPair is a model.LabelPair that implements
|
||||
// encoding.BinaryMarshaler and encoding.BinaryUnmarshaler.
|
||||
type LabelPair model.LabelPair
|
||||
|
||||
// MarshalBinary implements encoding.BinaryMarshaler.
|
||||
func (lp LabelPair) MarshalBinary() ([]byte, error) {
|
||||
buf := &bytes.Buffer{}
|
||||
if err := encodeString(buf, string(lp.Name)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := encodeString(buf, string(lp.Value)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements encoding.BinaryUnmarshaler.
|
||||
func (lp *LabelPair) UnmarshalBinary(buf []byte) error {
|
||||
r := bytes.NewReader(buf)
|
||||
n, err := decodeString(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v, err := decodeString(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
lp.Name = model.LabelName(n)
|
||||
lp.Value = model.LabelValue(v)
|
||||
return nil
|
||||
}
|
||||
|
||||
// LabelName is a model.LabelName that implements
|
||||
// encoding.BinaryMarshaler and encoding.BinaryUnmarshaler.
|
||||
type LabelName model.LabelName
|
||||
|
||||
// MarshalBinary implements encoding.BinaryMarshaler.
|
||||
func (l LabelName) MarshalBinary() ([]byte, error) {
|
||||
buf := &bytes.Buffer{}
|
||||
if err := encodeString(buf, string(l)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements encoding.BinaryUnmarshaler.
|
||||
func (l *LabelName) UnmarshalBinary(buf []byte) error {
|
||||
r := bytes.NewReader(buf)
|
||||
n, err := decodeString(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*l = LabelName(n)
|
||||
return nil
|
||||
}
|
||||
|
||||
// LabelValueSet is a map[model.LabelValue]struct{} that implements
|
||||
// encoding.BinaryMarshaler and encoding.BinaryUnmarshaler. Its binary form is
|
||||
// identical to that of LabelValues.
|
||||
type LabelValueSet map[model.LabelValue]struct{}
|
||||
|
||||
// MarshalBinary implements encoding.BinaryMarshaler.
|
||||
func (vs LabelValueSet) MarshalBinary() ([]byte, error) {
|
||||
buf := &bytes.Buffer{}
|
||||
// Note that this should have used EncodeUvarint but a glitch happened
|
||||
// while designing the checkpoint format.
|
||||
if _, err := EncodeVarint(buf, int64(len(vs))); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for v := range vs {
|
||||
if err := encodeString(buf, string(v)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements encoding.BinaryUnmarshaler.
|
||||
func (vs *LabelValueSet) UnmarshalBinary(buf []byte) error {
|
||||
r := bytes.NewReader(buf)
|
||||
numValues, err := binary.ReadVarint(r)
|
||||
if numValues < 0 {
|
||||
err = fmt.Errorf("found negative number of values during unmarshaling: %d", numValues)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*vs = make(LabelValueSet, numValues)
|
||||
|
||||
for i := int64(0); i < numValues; i++ {
|
||||
v, err := decodeString(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
(*vs)[model.LabelValue(v)] = struct{}{}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LabelValues is a model.LabelValues that implements
|
||||
// encoding.BinaryMarshaler and encoding.BinaryUnmarshaler. Its binary form is
|
||||
// identical to that of LabelValueSet.
|
||||
type LabelValues model.LabelValues
|
||||
|
||||
// MarshalBinary implements encoding.BinaryMarshaler.
|
||||
func (vs LabelValues) MarshalBinary() ([]byte, error) {
|
||||
buf := &bytes.Buffer{}
|
||||
// Note that this should have used EncodeUvarint but a glitch happened
|
||||
// while designing the checkpoint format.
|
||||
if _, err := EncodeVarint(buf, int64(len(vs))); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, v := range vs {
|
||||
if err := encodeString(buf, string(v)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements encoding.BinaryUnmarshaler.
|
||||
func (vs *LabelValues) UnmarshalBinary(buf []byte) error {
|
||||
r := bytes.NewReader(buf)
|
||||
numValues, err := binary.ReadVarint(r)
|
||||
if numValues < 0 {
|
||||
err = fmt.Errorf("found negative number of values during unmarshaling: %d", numValues)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*vs = make(LabelValues, numValues)
|
||||
|
||||
for i := range *vs {
|
||||
v, err := decodeString(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
(*vs)[i] = model.LabelValue(v)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TimeRange is used to define a time range and implements
|
||||
// encoding.BinaryMarshaler and encoding.BinaryUnmarshaler.
|
||||
type TimeRange struct {
|
||||
First, Last model.Time
|
||||
}
|
||||
|
||||
// MarshalBinary implements encoding.BinaryMarshaler.
|
||||
func (tr TimeRange) MarshalBinary() ([]byte, error) {
|
||||
buf := &bytes.Buffer{}
|
||||
if _, err := EncodeVarint(buf, int64(tr.First)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := EncodeVarint(buf, int64(tr.Last)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements encoding.BinaryUnmarshaler.
|
||||
func (tr *TimeRange) UnmarshalBinary(buf []byte) error {
|
||||
r := bytes.NewReader(buf)
|
||||
first, err := binary.ReadVarint(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
last, err := binary.ReadVarint(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tr.First = model.Time(first)
|
||||
tr.Last = model.Time(last)
|
||||
return nil
|
||||
}
|
||||
559
vendor/github.com/prometheus/prometheus/storage/local/crashrecovery.go
generated
vendored
Normal file
559
vendor/github.com/prometheus/prometheus/storage/local/crashrecovery.go
generated
vendored
Normal file
@@ -0,0 +1,559 @@
|
||||
// Copyright 2015 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 local
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/prometheus/common/log"
|
||||
"github.com/prometheus/common/model"
|
||||
|
||||
"github.com/prometheus/prometheus/storage/local/chunk"
|
||||
"github.com/prometheus/prometheus/storage/local/codable"
|
||||
"github.com/prometheus/prometheus/storage/local/index"
|
||||
)
|
||||
|
||||
// recoverFromCrash is called by loadSeriesMapAndHeads if the persistence
|
||||
// appears to be dirty after the loading (either because the loading resulted in
|
||||
// an error or because the persistence was dirty from the start). Not goroutine
|
||||
// safe. Only call before anything else is running (except index processing
|
||||
// queue as started by newPersistence).
|
||||
func (p *persistence) recoverFromCrash(fingerprintToSeries map[model.Fingerprint]*memorySeries) error {
|
||||
// TODO(beorn): We need proper tests for the crash recovery.
|
||||
log.Warn("Starting crash recovery. Prometheus is inoperational until complete.")
|
||||
log.Warn("To avoid crash recovery in the future, shut down Prometheus with SIGTERM or a HTTP POST to /-/quit.")
|
||||
|
||||
fpsSeen := map[model.Fingerprint]struct{}{}
|
||||
count := 0
|
||||
seriesDirNameFmt := fmt.Sprintf("%%0%dx", seriesDirNameLen)
|
||||
|
||||
// Delete the fingerprint mapping file as it might be stale or
|
||||
// corrupt. We'll rebuild the mappings as we go.
|
||||
if err := os.RemoveAll(p.mappingsFileName()); err != nil {
|
||||
return fmt.Errorf("couldn't remove old fingerprint mapping file %s: %s", p.mappingsFileName(), err)
|
||||
}
|
||||
// The mappings to rebuild.
|
||||
fpm := fpMappings{}
|
||||
|
||||
log.Info("Scanning files.")
|
||||
for i := 0; i < 1<<(seriesDirNameLen*4); i++ {
|
||||
dirname := filepath.Join(p.basePath, fmt.Sprintf(seriesDirNameFmt, i))
|
||||
dir, err := os.Open(dirname)
|
||||
if os.IsNotExist(err) {
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for fis := []os.FileInfo{}; err != io.EOF; fis, err = dir.Readdir(1024) {
|
||||
if err != nil {
|
||||
dir.Close()
|
||||
return err
|
||||
}
|
||||
for _, fi := range fis {
|
||||
fp, ok := p.sanitizeSeries(dirname, fi, fingerprintToSeries, fpm)
|
||||
if ok {
|
||||
fpsSeen[fp] = struct{}{}
|
||||
}
|
||||
count++
|
||||
if count%10000 == 0 {
|
||||
log.Infof("%d files scanned.", count)
|
||||
}
|
||||
}
|
||||
}
|
||||
dir.Close()
|
||||
}
|
||||
log.Infof("File scan complete. %d series found.", len(fpsSeen))
|
||||
|
||||
log.Info("Checking for series without series file.")
|
||||
for fp, s := range fingerprintToSeries {
|
||||
if _, seen := fpsSeen[fp]; !seen {
|
||||
// fp exists in fingerprintToSeries, but has no representation on disk.
|
||||
if s.persistWatermark >= len(s.chunkDescs) {
|
||||
// Oops, everything including the head chunk was
|
||||
// already persisted, but nothing on disk. Or
|
||||
// the persistWatermark is plainly wrong. Thus,
|
||||
// we lost that series completely. Clean up the
|
||||
// remnants.
|
||||
delete(fingerprintToSeries, fp)
|
||||
if err := p.purgeArchivedMetric(fp); err != nil {
|
||||
// Purging the archived metric didn't work, so try
|
||||
// to unindex it, just in case it's in the indexes.
|
||||
p.unindexMetric(fp, s.metric)
|
||||
}
|
||||
log.Warnf("Lost series detected: fingerprint %v, metric %v.", fp, s.metric)
|
||||
continue
|
||||
}
|
||||
// If we are here, the only chunks we have are the chunks in the checkpoint.
|
||||
// Adjust things accordingly.
|
||||
if s.persistWatermark > 0 || s.chunkDescsOffset != 0 {
|
||||
minLostChunks := s.persistWatermark + s.chunkDescsOffset
|
||||
if minLostChunks <= 0 {
|
||||
log.Warnf(
|
||||
"Possible loss of chunks for fingerprint %v, metric %v.",
|
||||
fp, s.metric,
|
||||
)
|
||||
} else {
|
||||
log.Warnf(
|
||||
"Lost at least %d chunks for fingerprint %v, metric %v.",
|
||||
minLostChunks, fp, s.metric,
|
||||
)
|
||||
}
|
||||
s.chunkDescs = append(
|
||||
make([]*chunk.Desc, 0, len(s.chunkDescs)-s.persistWatermark),
|
||||
s.chunkDescs[s.persistWatermark:]...,
|
||||
)
|
||||
chunk.NumMemDescs.Sub(float64(s.persistWatermark))
|
||||
s.persistWatermark = 0
|
||||
s.chunkDescsOffset = 0
|
||||
}
|
||||
maybeAddMapping(fp, s.metric, fpm)
|
||||
fpsSeen[fp] = struct{}{} // Add so that fpsSeen is complete.
|
||||
}
|
||||
}
|
||||
log.Info("Check for series without series file complete.")
|
||||
|
||||
if err := p.cleanUpArchiveIndexes(fingerprintToSeries, fpsSeen, fpm); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := p.rebuildLabelIndexes(fingerprintToSeries); err != nil {
|
||||
return err
|
||||
}
|
||||
// Finally rewrite the mappings file if there are any mappings.
|
||||
if len(fpm) > 0 {
|
||||
if err := p.checkpointFPMappings(fpm); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
p.dirtyMtx.Lock()
|
||||
// Only declare storage clean if it didn't become dirty during crash recovery.
|
||||
if !p.becameDirty {
|
||||
p.dirty = false
|
||||
}
|
||||
p.dirtyMtx.Unlock()
|
||||
|
||||
log.Warn("Crash recovery complete.")
|
||||
return nil
|
||||
}
|
||||
|
||||
// sanitizeSeries sanitizes a series based on its series file as defined by the
|
||||
// provided directory and FileInfo. The method returns the fingerprint as
|
||||
// derived from the directory and file name, and whether the provided file has
|
||||
// been sanitized. A file that failed to be sanitized is moved into the
|
||||
// "orphaned" sub-directory, if possible.
|
||||
//
|
||||
// The following steps are performed:
|
||||
//
|
||||
// - A file whose name doesn't comply with the naming scheme of a series file is
|
||||
// simply moved into the orphaned directory.
|
||||
//
|
||||
// - If the size of the series file isn't a multiple of the chunk size,
|
||||
// extraneous bytes are truncated. If the truncation fails, the file is
|
||||
// moved into the orphaned directory.
|
||||
//
|
||||
// - A file that is empty (after truncation) is deleted.
|
||||
//
|
||||
// - A series that is not archived (i.e. it is in the fingerprintToSeries map)
|
||||
// is checked for consistency of its various parameters (like persist
|
||||
// watermark, offset of chunkDescs etc.). In particular, overlap between an
|
||||
// in-memory head chunk with the most recent persisted chunk is
|
||||
// checked. Inconsistencies are rectified.
|
||||
//
|
||||
// - A series that is archived (i.e. it is not in the fingerprintToSeries map)
|
||||
// is checked for its presence in the index of archived series. If it cannot
|
||||
// be found there, it is moved into the orphaned directory.
|
||||
func (p *persistence) sanitizeSeries(
|
||||
dirname string, fi os.FileInfo,
|
||||
fingerprintToSeries map[model.Fingerprint]*memorySeries,
|
||||
fpm fpMappings,
|
||||
) (model.Fingerprint, bool) {
|
||||
var (
|
||||
fp model.Fingerprint
|
||||
err error
|
||||
filename = filepath.Join(dirname, fi.Name())
|
||||
s *memorySeries
|
||||
)
|
||||
|
||||
purge := func() {
|
||||
if fp != 0 {
|
||||
var metric model.Metric
|
||||
if s != nil {
|
||||
metric = s.metric
|
||||
}
|
||||
if err = p.quarantineSeriesFile(
|
||||
fp, errors.New("purge during crash recovery"), metric,
|
||||
); err == nil {
|
||||
return
|
||||
}
|
||||
log.
|
||||
With("file", filename).
|
||||
With("error", err).
|
||||
Error("Failed to move lost series file to orphaned directory.")
|
||||
}
|
||||
// If we are here, we are either purging an incorrectly named
|
||||
// file, or quarantining has failed. So simply delete the file.
|
||||
if err = os.Remove(filename); err != nil {
|
||||
log.
|
||||
With("file", filename).
|
||||
With("error", err).
|
||||
Error("Failed to delete lost series file.")
|
||||
}
|
||||
}
|
||||
|
||||
if len(fi.Name()) != fpLen-seriesDirNameLen+len(seriesFileSuffix) ||
|
||||
!strings.HasSuffix(fi.Name(), seriesFileSuffix) {
|
||||
log.Warnf("Unexpected series file name %s.", filename)
|
||||
purge()
|
||||
return fp, false
|
||||
}
|
||||
if fp, err = model.FingerprintFromString(filepath.Base(dirname) + fi.Name()[:fpLen-seriesDirNameLen]); err != nil {
|
||||
log.Warnf("Error parsing file name %s: %s", filename, err)
|
||||
purge()
|
||||
return fp, false
|
||||
}
|
||||
|
||||
bytesToTrim := fi.Size() % int64(chunkLenWithHeader)
|
||||
chunksInFile := int(fi.Size()) / chunkLenWithHeader
|
||||
modTime := fi.ModTime()
|
||||
if bytesToTrim != 0 {
|
||||
log.Warnf(
|
||||
"Truncating file %s to exactly %d chunks, trimming %d extraneous bytes.",
|
||||
filename, chunksInFile, bytesToTrim,
|
||||
)
|
||||
f, err := os.OpenFile(filename, os.O_WRONLY, 0640)
|
||||
if err != nil {
|
||||
log.Errorf("Could not open file %s: %s", filename, err)
|
||||
purge()
|
||||
return fp, false
|
||||
}
|
||||
if err := f.Truncate(fi.Size() - bytesToTrim); err != nil {
|
||||
log.Errorf("Failed to truncate file %s: %s", filename, err)
|
||||
purge()
|
||||
return fp, false
|
||||
}
|
||||
}
|
||||
if chunksInFile == 0 {
|
||||
log.Warnf("No chunks left in file %s.", filename)
|
||||
purge()
|
||||
return fp, false
|
||||
}
|
||||
|
||||
s, ok := fingerprintToSeries[fp]
|
||||
if ok { // This series is supposed to not be archived.
|
||||
if s == nil {
|
||||
panic("fingerprint mapped to nil pointer")
|
||||
}
|
||||
maybeAddMapping(fp, s.metric, fpm)
|
||||
if !p.pedanticChecks &&
|
||||
bytesToTrim == 0 &&
|
||||
s.chunkDescsOffset != -1 &&
|
||||
chunksInFile == s.chunkDescsOffset+s.persistWatermark &&
|
||||
modTime.Equal(s.modTime) {
|
||||
// Everything is consistent. We are good.
|
||||
return fp, true
|
||||
}
|
||||
// If we are here, we cannot be sure the series file is
|
||||
// consistent with the checkpoint, so we have to take a closer
|
||||
// look.
|
||||
if s.headChunkClosed {
|
||||
// This is the easy case as we have all chunks on
|
||||
// disk. Treat this series as a freshly unarchived one
|
||||
// by loading the chunkDescs and setting all parameters
|
||||
// based on the loaded chunkDescs.
|
||||
cds, err := p.loadChunkDescs(fp, 0)
|
||||
if err != nil {
|
||||
log.Errorf(
|
||||
"Failed to load chunk descriptors for metric %v, fingerprint %v: %s",
|
||||
s.metric, fp, err,
|
||||
)
|
||||
purge()
|
||||
return fp, false
|
||||
}
|
||||
log.Warnf(
|
||||
"Treating recovered metric %v, fingerprint %v, as freshly unarchived, with %d chunks in series file.",
|
||||
s.metric, fp, len(cds),
|
||||
)
|
||||
s.chunkDescs = cds
|
||||
s.chunkDescsOffset = 0
|
||||
s.savedFirstTime = cds[0].FirstTime()
|
||||
s.lastTime, err = cds[len(cds)-1].LastTime()
|
||||
if err != nil {
|
||||
log.Errorf(
|
||||
"Failed to determine time of the last sample for metric %v, fingerprint %v: %s",
|
||||
s.metric, fp, err,
|
||||
)
|
||||
purge()
|
||||
return fp, false
|
||||
}
|
||||
s.persistWatermark = len(cds)
|
||||
s.modTime = modTime
|
||||
// Finally, evict again all chunk.Descs except the latest one to save memory.
|
||||
s.evictChunkDescs(len(cds) - 1)
|
||||
return fp, true
|
||||
}
|
||||
// This is the tricky one: We have chunks from heads.db, but
|
||||
// some of those chunks might already be in the series
|
||||
// file. Strategy: Take the last time of the most recent chunk
|
||||
// in the series file. Then find the oldest chunk among those
|
||||
// from heads.db that has a first time later or equal to the
|
||||
// last time from the series file. Throw away the older chunks
|
||||
// from heads.db and stitch the parts together.
|
||||
|
||||
// First, throw away the chunkDescs without chunks.
|
||||
s.chunkDescs = s.chunkDescs[s.persistWatermark:]
|
||||
chunk.NumMemDescs.Sub(float64(s.persistWatermark))
|
||||
cds, err := p.loadChunkDescs(fp, 0)
|
||||
if err != nil {
|
||||
log.Errorf(
|
||||
"Failed to load chunk descriptors for metric %v, fingerprint %v: %s",
|
||||
s.metric, fp, err,
|
||||
)
|
||||
purge()
|
||||
return fp, false
|
||||
}
|
||||
s.persistWatermark = len(cds)
|
||||
s.chunkDescsOffset = 0
|
||||
s.savedFirstTime = cds[0].FirstTime()
|
||||
s.modTime = modTime
|
||||
|
||||
lastTime, err := cds[len(cds)-1].LastTime()
|
||||
if err != nil {
|
||||
log.Errorf(
|
||||
"Failed to determine time of the last sample for metric %v, fingerprint %v: %s",
|
||||
s.metric, fp, err,
|
||||
)
|
||||
purge()
|
||||
return fp, false
|
||||
}
|
||||
keepIdx := -1
|
||||
for i, cd := range s.chunkDescs {
|
||||
if cd.FirstTime() >= lastTime {
|
||||
keepIdx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if keepIdx == -1 {
|
||||
log.Warnf(
|
||||
"Recovered metric %v, fingerprint %v: all %d chunks recovered from series file.",
|
||||
s.metric, fp, chunksInFile,
|
||||
)
|
||||
chunk.NumMemDescs.Sub(float64(len(s.chunkDescs)))
|
||||
atomic.AddInt64(&chunk.NumMemChunks, int64(-len(s.chunkDescs)))
|
||||
s.chunkDescs = cds
|
||||
s.headChunkClosed = true
|
||||
// Finally, evict again all chunk.Descs except the latest one to save memory.
|
||||
s.evictChunkDescs(len(cds) - 1)
|
||||
return fp, true
|
||||
}
|
||||
log.Warnf(
|
||||
"Recovered metric %v, fingerprint %v: recovered %d chunks from series file, recovered %d chunks from checkpoint.",
|
||||
s.metric, fp, chunksInFile, len(s.chunkDescs)-keepIdx,
|
||||
)
|
||||
chunk.NumMemDescs.Sub(float64(keepIdx))
|
||||
atomic.AddInt64(&chunk.NumMemChunks, int64(-keepIdx))
|
||||
chunkDescsToEvict := len(cds)
|
||||
if keepIdx == len(s.chunkDescs) {
|
||||
// No chunks from series file left, head chunk is evicted, so declare it closed.
|
||||
s.headChunkClosed = true
|
||||
chunkDescsToEvict-- // Keep one chunk.Desc in this case to avoid a series with zero chunk.Descs.
|
||||
}
|
||||
s.chunkDescs = append(cds, s.chunkDescs[keepIdx:]...)
|
||||
// Finally, evict again chunk.Descs without chunk to save memory.
|
||||
s.evictChunkDescs(chunkDescsToEvict)
|
||||
return fp, true
|
||||
}
|
||||
// This series is supposed to be archived.
|
||||
metric, err := p.archivedMetric(fp)
|
||||
if err != nil {
|
||||
log.Errorf(
|
||||
"Fingerprint %v assumed archived but couldn't be looked up in archived index: %s",
|
||||
fp, err,
|
||||
)
|
||||
purge()
|
||||
return fp, false
|
||||
}
|
||||
if metric == nil {
|
||||
log.Warnf(
|
||||
"Fingerprint %v assumed archived but couldn't be found in archived index.",
|
||||
fp,
|
||||
)
|
||||
purge()
|
||||
return fp, false
|
||||
}
|
||||
// This series looks like a properly archived one.
|
||||
maybeAddMapping(fp, metric, fpm)
|
||||
return fp, true
|
||||
}
|
||||
|
||||
func (p *persistence) cleanUpArchiveIndexes(
|
||||
fpToSeries map[model.Fingerprint]*memorySeries,
|
||||
fpsSeen map[model.Fingerprint]struct{},
|
||||
fpm fpMappings,
|
||||
) error {
|
||||
log.Info("Cleaning up archive indexes.")
|
||||
var fp codable.Fingerprint
|
||||
var m codable.Metric
|
||||
count := 0
|
||||
if err := p.archivedFingerprintToMetrics.ForEach(func(kv index.KeyValueAccessor) error {
|
||||
count++
|
||||
if count%10000 == 0 {
|
||||
log.Infof("%d archived metrics checked.", count)
|
||||
}
|
||||
if err := kv.Key(&fp); err != nil {
|
||||
return err
|
||||
}
|
||||
_, fpSeen := fpsSeen[model.Fingerprint(fp)]
|
||||
inMemory := false
|
||||
if fpSeen {
|
||||
_, inMemory = fpToSeries[model.Fingerprint(fp)]
|
||||
}
|
||||
if !fpSeen || inMemory {
|
||||
if inMemory {
|
||||
log.Warnf("Archive clean-up: Fingerprint %v is not archived. Purging from archive indexes.", model.Fingerprint(fp))
|
||||
}
|
||||
if !fpSeen {
|
||||
log.Warnf("Archive clean-up: Fingerprint %v is unknown. Purging from archive indexes.", model.Fingerprint(fp))
|
||||
}
|
||||
// It's fine if the fp is not in the archive indexes.
|
||||
if _, err := p.archivedFingerprintToMetrics.Delete(fp); err != nil {
|
||||
return err
|
||||
}
|
||||
// Delete from timerange index, too.
|
||||
_, err := p.archivedFingerprintToTimeRange.Delete(fp)
|
||||
return err
|
||||
}
|
||||
// fp is legitimately archived. Now we need the metric to check for a mapped fingerprint.
|
||||
if err := kv.Value(&m); err != nil {
|
||||
return err
|
||||
}
|
||||
maybeAddMapping(model.Fingerprint(fp), model.Metric(m), fpm)
|
||||
// Make sure it is in timerange index, too.
|
||||
has, err := p.archivedFingerprintToTimeRange.Has(fp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if has {
|
||||
return nil // All good.
|
||||
}
|
||||
log.Warnf("Archive clean-up: Fingerprint %v is not in time-range index. Unarchiving it for recovery.")
|
||||
// Again, it's fine if fp is not in the archive index.
|
||||
if _, err := p.archivedFingerprintToMetrics.Delete(fp); err != nil {
|
||||
return err
|
||||
}
|
||||
cds, err := p.loadChunkDescs(model.Fingerprint(fp), 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
series, err := newMemorySeries(model.Metric(m), cds, p.seriesFileModTime(model.Fingerprint(fp)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fpToSeries[model.Fingerprint(fp)] = series
|
||||
// Evict all but one chunk.Desc to save memory.
|
||||
series.evictChunkDescs(len(cds) - 1)
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
count = 0
|
||||
if err := p.archivedFingerprintToTimeRange.ForEach(func(kv index.KeyValueAccessor) error {
|
||||
count++
|
||||
if count%10000 == 0 {
|
||||
log.Infof("%d archived time ranges checked.", count)
|
||||
}
|
||||
if err := kv.Key(&fp); err != nil {
|
||||
return err
|
||||
}
|
||||
has, err := p.archivedFingerprintToMetrics.Has(fp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if has {
|
||||
return nil // All good.
|
||||
}
|
||||
log.Warnf("Archive clean-up: Purging unknown fingerprint %v in time-range index.", fp)
|
||||
deleted, err := p.archivedFingerprintToTimeRange.Delete(fp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !deleted {
|
||||
log.Errorf("Fingerprint %v to be deleted from archivedFingerprintToTimeRange not found. This should never happen.", fp)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Info("Clean-up of archive indexes complete.")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *persistence) rebuildLabelIndexes(
|
||||
fpToSeries map[model.Fingerprint]*memorySeries,
|
||||
) error {
|
||||
count := 0
|
||||
log.Info("Rebuilding label indexes.")
|
||||
log.Info("Indexing metrics in memory.")
|
||||
for fp, s := range fpToSeries {
|
||||
p.indexMetric(fp, s.metric)
|
||||
count++
|
||||
if count%10000 == 0 {
|
||||
log.Infof("%d metrics queued for indexing.", count)
|
||||
}
|
||||
}
|
||||
log.Info("Indexing archived metrics.")
|
||||
var fp codable.Fingerprint
|
||||
var m codable.Metric
|
||||
if err := p.archivedFingerprintToMetrics.ForEach(func(kv index.KeyValueAccessor) error {
|
||||
if err := kv.Key(&fp); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := kv.Value(&m); err != nil {
|
||||
return err
|
||||
}
|
||||
p.indexMetric(model.Fingerprint(fp), model.Metric(m))
|
||||
count++
|
||||
if count%10000 == 0 {
|
||||
log.Infof("%d metrics queued for indexing.", count)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Info("All requests for rebuilding the label indexes queued. (Actual processing may lag behind.)")
|
||||
return nil
|
||||
}
|
||||
|
||||
// maybeAddMapping adds a fingerprint mapping to fpm if the FastFingerprint of m is different from fp.
|
||||
func maybeAddMapping(fp model.Fingerprint, m model.Metric, fpm fpMappings) {
|
||||
if rawFP := m.FastFingerprint(); rawFP != fp {
|
||||
log.Warnf(
|
||||
"Metric %v with fingerprint %v is mapped from raw fingerprint %v.",
|
||||
m, fp, rawFP,
|
||||
)
|
||||
if mappedFPs, ok := fpm[rawFP]; ok {
|
||||
mappedFPs[metricToUniqueString(m)] = fp
|
||||
} else {
|
||||
fpm[rawFP] = map[string]model.Fingerprint{
|
||||
metricToUniqueString(m): fp,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
261
vendor/github.com/prometheus/prometheus/storage/local/heads.go
generated
vendored
Normal file
261
vendor/github.com/prometheus/prometheus/storage/local/heads.go
generated
vendored
Normal file
@@ -0,0 +1,261 @@
|
||||
// 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 local
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/common/model"
|
||||
|
||||
"github.com/prometheus/prometheus/storage/local/chunk"
|
||||
"github.com/prometheus/prometheus/storage/local/codable"
|
||||
)
|
||||
|
||||
const (
|
||||
headsFileName = "heads.db"
|
||||
headsTempFileName = "heads.db.tmp"
|
||||
headsFormatVersion = 2
|
||||
headsFormatLegacyVersion = 1 // Can read, but will never write.
|
||||
headsMagicString = "PrometheusHeads"
|
||||
)
|
||||
|
||||
// headsScanner is a scanner to read time series with their heads from a
|
||||
// heads.db file. It follows a similar semantics as the bufio.Scanner.
|
||||
// It is not safe to use a headsScanner concurrently.
|
||||
type headsScanner struct {
|
||||
f *os.File
|
||||
r *bufio.Reader
|
||||
fp model.Fingerprint // Read after each scan() call that has returned true.
|
||||
series *memorySeries // Read after each scan() call that has returned true.
|
||||
version int64 // Read after newHeadsScanner has returned.
|
||||
seriesTotal uint64 // Read after newHeadsScanner has returned.
|
||||
seriesCurrent uint64
|
||||
chunksToPersistTotal int64 // Read after scan() has returned false.
|
||||
err error // Read after scan() has returned false.
|
||||
}
|
||||
|
||||
func newHeadsScanner(filename string) *headsScanner {
|
||||
hs := &headsScanner{}
|
||||
defer func() {
|
||||
if hs.f != nil && hs.err != nil {
|
||||
hs.f.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
if hs.f, hs.err = os.Open(filename); hs.err != nil {
|
||||
return hs
|
||||
}
|
||||
hs.r = bufio.NewReaderSize(hs.f, fileBufSize)
|
||||
|
||||
buf := make([]byte, len(headsMagicString))
|
||||
if _, hs.err = io.ReadFull(hs.r, buf); hs.err != nil {
|
||||
return hs
|
||||
}
|
||||
magic := string(buf)
|
||||
if magic != headsMagicString {
|
||||
hs.err = fmt.Errorf(
|
||||
"unexpected magic string, want %q, got %q",
|
||||
headsMagicString, magic,
|
||||
)
|
||||
return hs
|
||||
}
|
||||
hs.version, hs.err = binary.ReadVarint(hs.r)
|
||||
if (hs.version != headsFormatVersion && hs.version != headsFormatLegacyVersion) || hs.err != nil {
|
||||
hs.err = fmt.Errorf(
|
||||
"unknown or unreadable heads format version, want %d, got %d, error: %s",
|
||||
headsFormatVersion, hs.version, hs.err,
|
||||
)
|
||||
return hs
|
||||
}
|
||||
if hs.seriesTotal, hs.err = codable.DecodeUint64(hs.r); hs.err != nil {
|
||||
return hs
|
||||
}
|
||||
return hs
|
||||
}
|
||||
|
||||
// scan works like bufio.Scanner.Scan.
|
||||
func (hs *headsScanner) scan() bool {
|
||||
if hs.seriesCurrent == hs.seriesTotal || hs.err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
var (
|
||||
seriesFlags byte
|
||||
fpAsInt uint64
|
||||
metric codable.Metric
|
||||
persistWatermark int64
|
||||
modTimeNano int64
|
||||
modTime time.Time
|
||||
chunkDescsOffset int64
|
||||
savedFirstTime int64
|
||||
numChunkDescs int64
|
||||
firstTime int64
|
||||
lastTime int64
|
||||
encoding byte
|
||||
ch chunk.Chunk
|
||||
lastTimeHead model.Time
|
||||
)
|
||||
if seriesFlags, hs.err = hs.r.ReadByte(); hs.err != nil {
|
||||
return false
|
||||
}
|
||||
headChunkPersisted := seriesFlags&flagHeadChunkPersisted != 0
|
||||
if fpAsInt, hs.err = codable.DecodeUint64(hs.r); hs.err != nil {
|
||||
return false
|
||||
}
|
||||
hs.fp = model.Fingerprint(fpAsInt)
|
||||
|
||||
if hs.err = metric.UnmarshalFromReader(hs.r); hs.err != nil {
|
||||
return false
|
||||
}
|
||||
if hs.version != headsFormatLegacyVersion {
|
||||
// persistWatermark only present in v2.
|
||||
persistWatermark, hs.err = binary.ReadVarint(hs.r)
|
||||
if persistWatermark < 0 {
|
||||
hs.err = fmt.Errorf("found negative persist watermark in checkpoint: %d", persistWatermark)
|
||||
}
|
||||
if hs.err != nil {
|
||||
return false
|
||||
}
|
||||
modTimeNano, hs.err = binary.ReadVarint(hs.r)
|
||||
if hs.err != nil {
|
||||
return false
|
||||
}
|
||||
if modTimeNano != -1 {
|
||||
modTime = time.Unix(0, modTimeNano)
|
||||
}
|
||||
}
|
||||
if chunkDescsOffset, hs.err = binary.ReadVarint(hs.r); hs.err != nil {
|
||||
return false
|
||||
}
|
||||
if savedFirstTime, hs.err = binary.ReadVarint(hs.r); hs.err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if numChunkDescs, hs.err = binary.ReadVarint(hs.r); hs.err != nil {
|
||||
return false
|
||||
}
|
||||
if numChunkDescs < 0 {
|
||||
hs.err = fmt.Errorf("found negative number of chunk descriptors in checkpoint: %d", numChunkDescs)
|
||||
return false
|
||||
}
|
||||
|
||||
chunkDescs := make([]*chunk.Desc, numChunkDescs)
|
||||
if hs.version == headsFormatLegacyVersion {
|
||||
if headChunkPersisted {
|
||||
persistWatermark = numChunkDescs
|
||||
} else {
|
||||
persistWatermark = numChunkDescs - 1
|
||||
}
|
||||
}
|
||||
headChunkClosed := true // Initial assumption.
|
||||
for i := int64(0); i < numChunkDescs; i++ {
|
||||
if i < persistWatermark {
|
||||
if firstTime, hs.err = binary.ReadVarint(hs.r); hs.err != nil {
|
||||
return false
|
||||
}
|
||||
if lastTime, hs.err = binary.ReadVarint(hs.r); hs.err != nil {
|
||||
return false
|
||||
}
|
||||
chunkDescs[i] = &chunk.Desc{
|
||||
ChunkFirstTime: model.Time(firstTime),
|
||||
ChunkLastTime: model.Time(lastTime),
|
||||
}
|
||||
chunk.NumMemDescs.Inc()
|
||||
} else {
|
||||
// Non-persisted chunk.
|
||||
// If there are non-persisted chunks at all, we consider
|
||||
// the head chunk not to be closed yet.
|
||||
headChunkClosed = false
|
||||
if encoding, hs.err = hs.r.ReadByte(); hs.err != nil {
|
||||
return false
|
||||
}
|
||||
if ch, hs.err = chunk.NewForEncoding(chunk.Encoding(encoding)); hs.err != nil {
|
||||
return false
|
||||
}
|
||||
if hs.err = ch.Unmarshal(hs.r); hs.err != nil {
|
||||
return false
|
||||
}
|
||||
cd := chunk.NewDesc(ch, ch.FirstTime())
|
||||
if i < numChunkDescs-1 {
|
||||
// This is NOT the head chunk. So it's a chunk
|
||||
// to be persisted, and we need to populate lastTime.
|
||||
hs.chunksToPersistTotal++
|
||||
if hs.err = cd.MaybePopulateLastTime(); hs.err != nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
chunkDescs[i] = cd
|
||||
}
|
||||
}
|
||||
|
||||
if lastTimeHead, hs.err = chunkDescs[len(chunkDescs)-1].LastTime(); hs.err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
hs.series = &memorySeries{
|
||||
metric: model.Metric(metric),
|
||||
chunkDescs: chunkDescs,
|
||||
persistWatermark: int(persistWatermark),
|
||||
modTime: modTime,
|
||||
chunkDescsOffset: int(chunkDescsOffset),
|
||||
savedFirstTime: model.Time(savedFirstTime),
|
||||
lastTime: lastTimeHead,
|
||||
headChunkClosed: headChunkClosed,
|
||||
}
|
||||
hs.seriesCurrent++
|
||||
return true
|
||||
}
|
||||
|
||||
// close closes the underlying file if required.
|
||||
func (hs *headsScanner) close() {
|
||||
if hs.f != nil {
|
||||
hs.f.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// DumpHeads writes the metadata of the provided heads file in a human-readable
|
||||
// form.
|
||||
func DumpHeads(filename string, out io.Writer) error {
|
||||
hs := newHeadsScanner(filename)
|
||||
defer hs.close()
|
||||
|
||||
if hs.err == nil {
|
||||
fmt.Fprintf(
|
||||
out,
|
||||
">>> Dumping %d series from heads file %q with format version %d. <<<\n",
|
||||
hs.seriesTotal, filename, hs.version,
|
||||
)
|
||||
}
|
||||
for hs.scan() {
|
||||
s := hs.series
|
||||
fmt.Fprintf(
|
||||
out,
|
||||
"FP=%v\tMETRIC=%s\tlen(chunkDescs)=%d\tpersistWatermark=%d\tchunkDescOffset=%d\tsavedFirstTime=%v\tlastTime=%v\theadChunkClosed=%t\n",
|
||||
hs.fp, s.metric, len(s.chunkDescs), s.persistWatermark, s.chunkDescsOffset, s.savedFirstTime, s.lastTime, s.headChunkClosed,
|
||||
)
|
||||
}
|
||||
if hs.err == nil {
|
||||
fmt.Fprintf(
|
||||
out,
|
||||
">>> Dump complete. %d chunks to persist. <<<\n",
|
||||
hs.chunksToPersistTotal,
|
||||
)
|
||||
}
|
||||
return hs.err
|
||||
}
|
||||
303
vendor/github.com/prometheus/prometheus/storage/local/index/index.go
generated
vendored
Normal file
303
vendor/github.com/prometheus/prometheus/storage/local/index/index.go
generated
vendored
Normal file
@@ -0,0 +1,303 @@
|
||||
// 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 index provides a number of indexes backed by persistent key-value
|
||||
// stores. The only supported implementation of a key-value store is currently
|
||||
// goleveldb, but other implementations can easily be added.
|
||||
package index
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/prometheus/common/model"
|
||||
|
||||
"github.com/prometheus/prometheus/storage/local/codable"
|
||||
)
|
||||
|
||||
// Directory names for LevelDB indices.
|
||||
const (
|
||||
FingerprintToMetricDir = "archived_fingerprint_to_metric"
|
||||
FingerprintTimeRangeDir = "archived_fingerprint_to_timerange"
|
||||
LabelNameToLabelValuesDir = "labelname_to_labelvalues"
|
||||
LabelPairToFingerprintsDir = "labelpair_to_fingerprints"
|
||||
)
|
||||
|
||||
// LevelDB cache sizes, changeable via flags.
|
||||
var (
|
||||
FingerprintMetricCacheSize = 10 * 1024 * 1024
|
||||
FingerprintTimeRangeCacheSize = 5 * 1024 * 1024
|
||||
LabelNameLabelValuesCacheSize = 10 * 1024 * 1024
|
||||
LabelPairFingerprintsCacheSize = 20 * 1024 * 1024
|
||||
)
|
||||
|
||||
// FingerprintMetricMapping is an in-memory map of fingerprints to metrics.
|
||||
type FingerprintMetricMapping map[model.Fingerprint]model.Metric
|
||||
|
||||
// FingerprintMetricIndex models a database mapping fingerprints to metrics.
|
||||
type FingerprintMetricIndex struct {
|
||||
KeyValueStore
|
||||
}
|
||||
|
||||
// IndexBatch indexes a batch of mappings from fingerprints to metrics.
|
||||
//
|
||||
// This method is goroutine-safe, but note that no specific order of execution
|
||||
// can be guaranteed (especially critical if IndexBatch and UnindexBatch are
|
||||
// called concurrently for the same fingerprint).
|
||||
func (i *FingerprintMetricIndex) IndexBatch(mapping FingerprintMetricMapping) error {
|
||||
b := i.NewBatch()
|
||||
|
||||
for fp, m := range mapping {
|
||||
if err := b.Put(codable.Fingerprint(fp), codable.Metric(m)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return i.Commit(b)
|
||||
}
|
||||
|
||||
// UnindexBatch unindexes a batch of mappings from fingerprints to metrics.
|
||||
//
|
||||
// This method is goroutine-safe, but note that no specific order of execution
|
||||
// can be guaranteed (especially critical if IndexBatch and UnindexBatch are
|
||||
// called concurrently for the same fingerprint).
|
||||
func (i *FingerprintMetricIndex) UnindexBatch(mapping FingerprintMetricMapping) error {
|
||||
b := i.NewBatch()
|
||||
|
||||
for fp := range mapping {
|
||||
if err := b.Delete(codable.Fingerprint(fp)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return i.Commit(b)
|
||||
}
|
||||
|
||||
// Lookup looks up a metric by fingerprint. Looking up a non-existing
|
||||
// fingerprint is not an error. In that case, (nil, false, nil) is returned.
|
||||
//
|
||||
// This method is goroutine-safe.
|
||||
func (i *FingerprintMetricIndex) Lookup(fp model.Fingerprint) (metric model.Metric, ok bool, err error) {
|
||||
ok, err = i.Get(codable.Fingerprint(fp), (*codable.Metric)(&metric))
|
||||
return
|
||||
}
|
||||
|
||||
// NewFingerprintMetricIndex returns a LevelDB-backed FingerprintMetricIndex
|
||||
// ready to use.
|
||||
func NewFingerprintMetricIndex(basePath string) (*FingerprintMetricIndex, error) {
|
||||
fingerprintToMetricDB, err := NewLevelDB(LevelDBOptions{
|
||||
Path: filepath.Join(basePath, FingerprintToMetricDir),
|
||||
CacheSizeBytes: FingerprintMetricCacheSize,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &FingerprintMetricIndex{
|
||||
KeyValueStore: fingerprintToMetricDB,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// LabelNameLabelValuesMapping is an in-memory map of label names to
|
||||
// label values.
|
||||
type LabelNameLabelValuesMapping map[model.LabelName]codable.LabelValueSet
|
||||
|
||||
// LabelNameLabelValuesIndex is a KeyValueStore that maps existing label names
|
||||
// to all label values stored for that label name.
|
||||
type LabelNameLabelValuesIndex struct {
|
||||
KeyValueStore
|
||||
}
|
||||
|
||||
// IndexBatch adds a batch of label name to label values mappings to the
|
||||
// index. A mapping of a label name to an empty slice of label values results in
|
||||
// a deletion of that mapping from the index.
|
||||
//
|
||||
// While this method is fundamentally goroutine-safe, note that the order of
|
||||
// execution for multiple batches executed concurrently is undefined.
|
||||
func (i *LabelNameLabelValuesIndex) IndexBatch(b LabelNameLabelValuesMapping) error {
|
||||
batch := i.NewBatch()
|
||||
|
||||
for name, values := range b {
|
||||
if len(values) == 0 {
|
||||
if err := batch.Delete(codable.LabelName(name)); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := batch.Put(codable.LabelName(name), values); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return i.Commit(batch)
|
||||
}
|
||||
|
||||
// Lookup looks up all label values for a given label name and returns them as
|
||||
// model.LabelValues (which is a slice). Looking up a non-existing label
|
||||
// name is not an error. In that case, (nil, false, nil) is returned.
|
||||
//
|
||||
// This method is goroutine-safe.
|
||||
func (i *LabelNameLabelValuesIndex) Lookup(l model.LabelName) (values model.LabelValues, ok bool, err error) {
|
||||
ok, err = i.Get(codable.LabelName(l), (*codable.LabelValues)(&values))
|
||||
return
|
||||
}
|
||||
|
||||
// LookupSet looks up all label values for a given label name and returns them
|
||||
// as a set. Looking up a non-existing label name is not an error. In that case,
|
||||
// (nil, false, nil) is returned.
|
||||
//
|
||||
// This method is goroutine-safe.
|
||||
func (i *LabelNameLabelValuesIndex) LookupSet(l model.LabelName) (values map[model.LabelValue]struct{}, ok bool, err error) {
|
||||
ok, err = i.Get(codable.LabelName(l), (*codable.LabelValueSet)(&values))
|
||||
if values == nil {
|
||||
values = map[model.LabelValue]struct{}{}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// NewLabelNameLabelValuesIndex returns a LevelDB-backed
|
||||
// LabelNameLabelValuesIndex ready to use.
|
||||
func NewLabelNameLabelValuesIndex(basePath string) (*LabelNameLabelValuesIndex, error) {
|
||||
labelNameToLabelValuesDB, err := NewLevelDB(LevelDBOptions{
|
||||
Path: filepath.Join(basePath, LabelNameToLabelValuesDir),
|
||||
CacheSizeBytes: LabelNameLabelValuesCacheSize,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &LabelNameLabelValuesIndex{
|
||||
KeyValueStore: labelNameToLabelValuesDB,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DeleteLabelNameLabelValuesIndex deletes the LevelDB-backed
|
||||
// LabelNameLabelValuesIndex. Use only for a not yet opened index.
|
||||
func DeleteLabelNameLabelValuesIndex(basePath string) error {
|
||||
return os.RemoveAll(path.Join(basePath, LabelNameToLabelValuesDir))
|
||||
}
|
||||
|
||||
// LabelPairFingerprintsMapping is an in-memory map of label pairs to
|
||||
// fingerprints.
|
||||
type LabelPairFingerprintsMapping map[model.LabelPair]codable.FingerprintSet
|
||||
|
||||
// LabelPairFingerprintIndex is a KeyValueStore that maps existing label pairs
|
||||
// to the fingerprints of all metrics containing those label pairs.
|
||||
type LabelPairFingerprintIndex struct {
|
||||
KeyValueStore
|
||||
}
|
||||
|
||||
// IndexBatch indexes a batch of mappings from label pairs to fingerprints. A
|
||||
// mapping to an empty slice of fingerprints results in deletion of that mapping
|
||||
// from the index.
|
||||
//
|
||||
// While this method is fundamentally goroutine-safe, note that the order of
|
||||
// execution for multiple batches executed concurrently is undefined.
|
||||
func (i *LabelPairFingerprintIndex) IndexBatch(m LabelPairFingerprintsMapping) (err error) {
|
||||
batch := i.NewBatch()
|
||||
|
||||
for pair, fps := range m {
|
||||
if len(fps) == 0 {
|
||||
err = batch.Delete(codable.LabelPair(pair))
|
||||
} else {
|
||||
err = batch.Put(codable.LabelPair(pair), fps)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return i.Commit(batch)
|
||||
}
|
||||
|
||||
// Lookup looks up all fingerprints for a given label pair. Looking up a
|
||||
// non-existing label pair is not an error. In that case, (nil, false, nil) is
|
||||
// returned.
|
||||
//
|
||||
// This method is goroutine-safe.
|
||||
func (i *LabelPairFingerprintIndex) Lookup(p model.LabelPair) (fps model.Fingerprints, ok bool, err error) {
|
||||
ok, err = i.Get((codable.LabelPair)(p), (*codable.Fingerprints)(&fps))
|
||||
return
|
||||
}
|
||||
|
||||
// LookupSet looks up all fingerprints for a given label pair. Looking up a
|
||||
// non-existing label pair is not an error. In that case, (nil, false, nil) is
|
||||
// returned.
|
||||
//
|
||||
// This method is goroutine-safe.
|
||||
func (i *LabelPairFingerprintIndex) LookupSet(p model.LabelPair) (fps map[model.Fingerprint]struct{}, ok bool, err error) {
|
||||
ok, err = i.Get((codable.LabelPair)(p), (*codable.FingerprintSet)(&fps))
|
||||
if fps == nil {
|
||||
fps = map[model.Fingerprint]struct{}{}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// NewLabelPairFingerprintIndex returns a LevelDB-backed
|
||||
// LabelPairFingerprintIndex ready to use.
|
||||
func NewLabelPairFingerprintIndex(basePath string) (*LabelPairFingerprintIndex, error) {
|
||||
labelPairToFingerprintsDB, err := NewLevelDB(LevelDBOptions{
|
||||
Path: filepath.Join(basePath, LabelPairToFingerprintsDir),
|
||||
CacheSizeBytes: LabelPairFingerprintsCacheSize,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &LabelPairFingerprintIndex{
|
||||
KeyValueStore: labelPairToFingerprintsDB,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DeleteLabelPairFingerprintIndex deletes the LevelDB-backed
|
||||
// LabelPairFingerprintIndex. Use only for a not yet opened index.
|
||||
func DeleteLabelPairFingerprintIndex(basePath string) error {
|
||||
return os.RemoveAll(path.Join(basePath, LabelPairToFingerprintsDir))
|
||||
}
|
||||
|
||||
// FingerprintTimeRangeIndex models a database tracking the time ranges
|
||||
// of metrics by their fingerprints.
|
||||
type FingerprintTimeRangeIndex struct {
|
||||
KeyValueStore
|
||||
}
|
||||
|
||||
// Lookup returns the time range for the given fingerprint. Looking up a
|
||||
// non-existing fingerprint is not an error. In that case, (0, 0, false, nil) is
|
||||
// returned.
|
||||
//
|
||||
// This method is goroutine-safe.
|
||||
func (i *FingerprintTimeRangeIndex) Lookup(fp model.Fingerprint) (firstTime, lastTime model.Time, ok bool, err error) {
|
||||
var tr codable.TimeRange
|
||||
ok, err = i.Get(codable.Fingerprint(fp), &tr)
|
||||
return tr.First, tr.Last, ok, err
|
||||
}
|
||||
|
||||
// NewFingerprintTimeRangeIndex returns a LevelDB-backed
|
||||
// FingerprintTimeRangeIndex ready to use.
|
||||
func NewFingerprintTimeRangeIndex(basePath string) (*FingerprintTimeRangeIndex, error) {
|
||||
fingerprintTimeRangeDB, err := NewLevelDB(LevelDBOptions{
|
||||
Path: filepath.Join(basePath, FingerprintTimeRangeDir),
|
||||
CacheSizeBytes: FingerprintTimeRangeCacheSize,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &FingerprintTimeRangeIndex{
|
||||
KeyValueStore: fingerprintTimeRangeDB,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DeleteFingerprintTimeRangeIndex deletes the LevelDB-backed
|
||||
// FingerprintTimeRangeIndex. Use only for a not yet opened index.
|
||||
func DeleteFingerprintTimeRangeIndex(basePath string) error {
|
||||
return os.RemoveAll(path.Join(basePath, FingerprintTimeRangeDir))
|
||||
}
|
||||
61
vendor/github.com/prometheus/prometheus/storage/local/index/interface.go
generated
vendored
Normal file
61
vendor/github.com/prometheus/prometheus/storage/local/index/interface.go
generated
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
// 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 index
|
||||
|
||||
import "encoding"
|
||||
|
||||
// KeyValueStore persists key/value pairs. Implementations must be fundamentally
|
||||
// goroutine-safe. However, it is the caller's responsibility that keys and
|
||||
// values can be safely marshaled and unmarshaled (via the MarshalBinary and
|
||||
// UnmarshalBinary methods of the keys and values). For example, if you call the
|
||||
// Put method of a KeyValueStore implementation, but the key or the value are
|
||||
// modified concurrently while being marshaled into its binary representation,
|
||||
// you obviously have a problem. Methods of KeyValueStore return only after
|
||||
// (un)marshaling is complete.
|
||||
type KeyValueStore interface {
|
||||
Put(key, value encoding.BinaryMarshaler) error
|
||||
// Get unmarshals the result into value. It returns false if no entry
|
||||
// could be found for key. If value is nil, Get behaves like Has.
|
||||
Get(key encoding.BinaryMarshaler, value encoding.BinaryUnmarshaler) (bool, error)
|
||||
Has(key encoding.BinaryMarshaler) (bool, error)
|
||||
// Delete returns (false, nil) if key does not exist.
|
||||
Delete(key encoding.BinaryMarshaler) (bool, error)
|
||||
|
||||
NewBatch() Batch
|
||||
Commit(b Batch) error
|
||||
|
||||
// ForEach iterates through the complete KeyValueStore and calls the
|
||||
// supplied function for each mapping.
|
||||
ForEach(func(kv KeyValueAccessor) error) error
|
||||
|
||||
Close() error
|
||||
}
|
||||
|
||||
// KeyValueAccessor allows access to the key and value of an entry in a
|
||||
// KeyValueStore.
|
||||
type KeyValueAccessor interface {
|
||||
Key(encoding.BinaryUnmarshaler) error
|
||||
Value(encoding.BinaryUnmarshaler) error
|
||||
}
|
||||
|
||||
// Batch allows KeyValueStore mutations to be pooled and committed together. An
|
||||
// implementation does not have to be goroutine-safe. Never modify a Batch
|
||||
// concurrently or commit the same batch multiple times concurrently. Marshaling
|
||||
// of keys and values is guaranteed to be complete when the Put or Delete methods
|
||||
// have returned.
|
||||
type Batch interface {
|
||||
Put(key, value encoding.BinaryMarshaler) error
|
||||
Delete(key encoding.BinaryMarshaler) error
|
||||
Reset()
|
||||
}
|
||||
210
vendor/github.com/prometheus/prometheus/storage/local/index/leveldb.go
generated
vendored
Normal file
210
vendor/github.com/prometheus/prometheus/storage/local/index/leveldb.go
generated
vendored
Normal file
@@ -0,0 +1,210 @@
|
||||
// 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 index
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
|
||||
"github.com/syndtr/goleveldb/leveldb"
|
||||
leveldb_filter "github.com/syndtr/goleveldb/leveldb/filter"
|
||||
leveldb_iterator "github.com/syndtr/goleveldb/leveldb/iterator"
|
||||
leveldb_opt "github.com/syndtr/goleveldb/leveldb/opt"
|
||||
leveldb_util "github.com/syndtr/goleveldb/leveldb/util"
|
||||
)
|
||||
|
||||
var (
|
||||
keyspace = &leveldb_util.Range{
|
||||
Start: nil,
|
||||
Limit: nil,
|
||||
}
|
||||
|
||||
iteratorOpts = &leveldb_opt.ReadOptions{
|
||||
DontFillCache: true,
|
||||
}
|
||||
)
|
||||
|
||||
// LevelDB is a LevelDB-backed sorted KeyValueStore.
|
||||
type LevelDB struct {
|
||||
storage *leveldb.DB
|
||||
readOpts *leveldb_opt.ReadOptions
|
||||
writeOpts *leveldb_opt.WriteOptions
|
||||
}
|
||||
|
||||
// LevelDBOptions provides options for a LevelDB.
|
||||
type LevelDBOptions struct {
|
||||
Path string // Base path to store files.
|
||||
CacheSizeBytes int
|
||||
}
|
||||
|
||||
// NewLevelDB returns a newly allocated LevelDB-backed KeyValueStore ready to
|
||||
// use.
|
||||
func NewLevelDB(o LevelDBOptions) (KeyValueStore, error) {
|
||||
options := &leveldb_opt.Options{
|
||||
BlockCacheCapacity: o.CacheSizeBytes,
|
||||
Filter: leveldb_filter.NewBloomFilter(10),
|
||||
}
|
||||
|
||||
storage, err := leveldb.OpenFile(o.Path, options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &LevelDB{
|
||||
storage: storage,
|
||||
readOpts: &leveldb_opt.ReadOptions{},
|
||||
writeOpts: &leveldb_opt.WriteOptions{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// NewBatch implements KeyValueStore.
|
||||
func (l *LevelDB) NewBatch() Batch {
|
||||
return &LevelDBBatch{
|
||||
batch: &leveldb.Batch{},
|
||||
}
|
||||
}
|
||||
|
||||
// Close implements KeyValueStore.
|
||||
func (l *LevelDB) Close() error {
|
||||
return l.storage.Close()
|
||||
}
|
||||
|
||||
// Get implements KeyValueStore.
|
||||
func (l *LevelDB) Get(key encoding.BinaryMarshaler, value encoding.BinaryUnmarshaler) (bool, error) {
|
||||
k, err := key.MarshalBinary()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
raw, err := l.storage.Get(k, l.readOpts)
|
||||
if err == leveldb.ErrNotFound {
|
||||
return false, nil
|
||||
}
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if value == nil {
|
||||
return true, nil
|
||||
}
|
||||
return true, value.UnmarshalBinary(raw)
|
||||
}
|
||||
|
||||
// Has implements KeyValueStore.
|
||||
func (l *LevelDB) Has(key encoding.BinaryMarshaler) (has bool, err error) {
|
||||
return l.Get(key, nil)
|
||||
}
|
||||
|
||||
// Delete implements KeyValueStore.
|
||||
func (l *LevelDB) Delete(key encoding.BinaryMarshaler) (bool, error) {
|
||||
k, err := key.MarshalBinary()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
// Note that Delete returns nil if k does not exist. So we have to test
|
||||
// for existence with Has first.
|
||||
if has, err := l.storage.Has(k, l.readOpts); !has || err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err = l.storage.Delete(k, l.writeOpts); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Put implements KeyValueStore.
|
||||
func (l *LevelDB) Put(key, value encoding.BinaryMarshaler) error {
|
||||
k, err := key.MarshalBinary()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v, err := value.MarshalBinary()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return l.storage.Put(k, v, l.writeOpts)
|
||||
}
|
||||
|
||||
// Commit implements KeyValueStore.
|
||||
func (l *LevelDB) Commit(b Batch) error {
|
||||
return l.storage.Write(b.(*LevelDBBatch).batch, l.writeOpts)
|
||||
}
|
||||
|
||||
// ForEach implements KeyValueStore.
|
||||
func (l *LevelDB) ForEach(cb func(kv KeyValueAccessor) error) error {
|
||||
snap, err := l.storage.GetSnapshot()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer snap.Release()
|
||||
|
||||
iter := snap.NewIterator(keyspace, iteratorOpts)
|
||||
|
||||
kv := &levelDBKeyValueAccessor{it: iter}
|
||||
|
||||
for valid := iter.First(); valid; valid = iter.Next() {
|
||||
if err = iter.Error(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := cb(kv); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LevelDBBatch is a Batch implementation for LevelDB.
|
||||
type LevelDBBatch struct {
|
||||
batch *leveldb.Batch
|
||||
}
|
||||
|
||||
// Put implements Batch.
|
||||
func (b *LevelDBBatch) Put(key, value encoding.BinaryMarshaler) error {
|
||||
k, err := key.MarshalBinary()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v, err := value.MarshalBinary()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.batch.Put(k, v)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete implements Batch.
|
||||
func (b *LevelDBBatch) Delete(key encoding.BinaryMarshaler) error {
|
||||
k, err := key.MarshalBinary()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.batch.Delete(k)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Reset implements Batch.
|
||||
func (b *LevelDBBatch) Reset() {
|
||||
b.batch.Reset()
|
||||
}
|
||||
|
||||
// levelDBKeyValueAccessor implements KeyValueAccessor.
|
||||
type levelDBKeyValueAccessor struct {
|
||||
it leveldb_iterator.Iterator
|
||||
}
|
||||
|
||||
func (i *levelDBKeyValueAccessor) Key(key encoding.BinaryUnmarshaler) error {
|
||||
return key.UnmarshalBinary(i.it.Key())
|
||||
}
|
||||
|
||||
func (i *levelDBKeyValueAccessor) Value(value encoding.BinaryUnmarshaler) error {
|
||||
return value.UnmarshalBinary(i.it.Value())
|
||||
}
|
||||
46
vendor/github.com/prometheus/prometheus/storage/local/instrumentation.go
generated
vendored
Normal file
46
vendor/github.com/prometheus/prometheus/storage/local/instrumentation.go
generated
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
// 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 local
|
||||
|
||||
const (
|
||||
namespace = "prometheus"
|
||||
subsystem = "local_storage"
|
||||
|
||||
opTypeLabel = "type"
|
||||
|
||||
// Op-types for seriesOps.
|
||||
create = "create"
|
||||
archive = "archive"
|
||||
unarchive = "unarchive"
|
||||
memoryPurge = "purge_from_memory"
|
||||
archivePurge = "purge_from_archive"
|
||||
requestedPurge = "purge_on_request"
|
||||
memoryMaintenance = "maintenance_in_memory"
|
||||
archiveMaintenance = "maintenance_in_archive"
|
||||
completedQurantine = "quarantine_completed"
|
||||
droppedQuarantine = "quarantine_dropped"
|
||||
failedQuarantine = "quarantine_failed"
|
||||
|
||||
seriesLocationLabel = "location"
|
||||
|
||||
// Maintenance types for maintainSeriesDuration.
|
||||
maintainInMemory = "memory"
|
||||
maintainArchived = "archived"
|
||||
|
||||
discardReasonLabel = "reason"
|
||||
|
||||
// Reasons to discard samples.
|
||||
outOfOrderTimestamp = "timestamp_out_of_order"
|
||||
duplicateSample = "multiple_values_for_timestamp"
|
||||
)
|
||||
106
vendor/github.com/prometheus/prometheus/storage/local/interface.go
generated
vendored
Normal file
106
vendor/github.com/prometheus/prometheus/storage/local/interface.go
generated
vendored
Normal file
@@ -0,0 +1,106 @@
|
||||
// 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 local
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/common/model"
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/prometheus/prometheus/storage"
|
||||
"github.com/prometheus/prometheus/storage/metric"
|
||||
)
|
||||
|
||||
// Storage ingests and manages samples, along with various indexes. All methods
|
||||
// are goroutine-safe. Storage implements storage.SampleAppender.
|
||||
type Storage interface {
|
||||
// Querier returns a new Querier on the storage.
|
||||
Querier() (Querier, error)
|
||||
|
||||
// This SampleAppender needs multiple samples for the same fingerprint to be
|
||||
// submitted in chronological order, from oldest to newest. When Append has
|
||||
// returned, the appended sample might not be queryable immediately. (Use
|
||||
// WaitForIndexing to wait for complete processing.) The implementation might
|
||||
// remove labels with empty value from the provided Sample as those labels
|
||||
// are considered equivalent to a label not present at all.
|
||||
//
|
||||
// Appending is throttled if the Storage has too many chunks in memory
|
||||
// already or has too many chunks waiting for persistence.
|
||||
storage.SampleAppender
|
||||
|
||||
// Drop all time series associated with the given label matchers. Returns
|
||||
// the number series that were dropped.
|
||||
DropMetricsForLabelMatchers(context.Context, ...*metric.LabelMatcher) (int, error)
|
||||
// Run the various maintenance loops in goroutines. Returns when the
|
||||
// storage is ready to use. Keeps everything running in the background
|
||||
// until Stop is called.
|
||||
Start() error
|
||||
// Stop shuts down the Storage gracefully, flushes all pending
|
||||
// operations, stops all maintenance loops,and frees all resources.
|
||||
Stop() error
|
||||
// WaitForIndexing returns once all samples in the storage are
|
||||
// indexed. Indexing is needed for FingerprintsForLabelMatchers and
|
||||
// LabelValuesForLabelName and may lag behind.
|
||||
WaitForIndexing()
|
||||
}
|
||||
|
||||
// Querier allows querying a time series storage.
|
||||
type Querier interface {
|
||||
// Close closes the querier. Behavior for subsequent calls to Querier methods
|
||||
// is undefined.
|
||||
Close() error
|
||||
// QueryRange returns a list of series iterators for the selected
|
||||
// time range and label matchers. The iterators need to be closed
|
||||
// after usage.
|
||||
QueryRange(ctx context.Context, from, through model.Time, matchers ...*metric.LabelMatcher) ([]SeriesIterator, error)
|
||||
// QueryInstant returns a list of series iterators for the selected
|
||||
// instant and label matchers. The iterators need to be closed after usage.
|
||||
QueryInstant(ctx context.Context, ts model.Time, stalenessDelta time.Duration, matchers ...*metric.LabelMatcher) ([]SeriesIterator, error)
|
||||
// MetricsForLabelMatchers returns the metrics from storage that satisfy
|
||||
// the given sets of label matchers. Each set of matchers must contain at
|
||||
// least one label matcher that does not match the empty string. Otherwise,
|
||||
// an empty list is returned. Within one set of matchers, the intersection
|
||||
// of matching series is computed. The final return value will be the union
|
||||
// of the per-set results. The times from and through are hints for the
|
||||
// storage to optimize the search. The storage MAY exclude metrics that
|
||||
// have no samples in the specified interval from the returned map. In
|
||||
// doubt, specify model.Earliest for from and model.Latest for through.
|
||||
MetricsForLabelMatchers(ctx context.Context, from, through model.Time, matcherSets ...metric.LabelMatchers) ([]metric.Metric, error)
|
||||
// LastSampleForLabelMatchers returns the last samples that have been
|
||||
// ingested for the time series matching the given set of label matchers.
|
||||
// The label matching behavior is the same as in MetricsForLabelMatchers.
|
||||
// All returned samples are between the specified cutoff time and now.
|
||||
LastSampleForLabelMatchers(ctx context.Context, cutoff model.Time, matcherSets ...metric.LabelMatchers) (model.Vector, error)
|
||||
// Get all of the label values that are associated with a given label name.
|
||||
LabelValuesForLabelName(context.Context, model.LabelName) (model.LabelValues, error)
|
||||
}
|
||||
|
||||
// SeriesIterator enables efficient access of sample values in a series. Its
|
||||
// methods are not goroutine-safe. A SeriesIterator iterates over a snapshot of
|
||||
// a series, i.e. it is safe to continue using a SeriesIterator after or during
|
||||
// modifying the corresponding series, but the iterator will represent the state
|
||||
// of the series prior to the modification.
|
||||
type SeriesIterator interface {
|
||||
// Gets the value that is closest before the given time. In case a value
|
||||
// exists at precisely the given time, that value is returned. If no
|
||||
// applicable value exists, model.ZeroSamplePair is returned.
|
||||
ValueAtOrBeforeTime(model.Time) model.SamplePair
|
||||
// Gets all values contained within a given interval.
|
||||
RangeValues(metric.Interval) []model.SamplePair
|
||||
// Returns the metric of the series that the iterator corresponds to.
|
||||
Metric() metric.Metric
|
||||
// Closes the iterator and releases the underlying data.
|
||||
Close()
|
||||
}
|
||||
79
vendor/github.com/prometheus/prometheus/storage/local/locker.go
generated
vendored
Normal file
79
vendor/github.com/prometheus/prometheus/storage/local/locker.go
generated
vendored
Normal file
@@ -0,0 +1,79 @@
|
||||
// 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 local
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
"github.com/prometheus/common/model"
|
||||
)
|
||||
|
||||
const (
|
||||
cacheLineSize = 64
|
||||
)
|
||||
|
||||
// Avoid false sharing when using array of mutexes.
|
||||
type paddedMutex struct {
|
||||
sync.Mutex
|
||||
pad [cacheLineSize - unsafe.Sizeof(sync.Mutex{})]byte
|
||||
}
|
||||
|
||||
// fingerprintLocker allows locking individual fingerprints. To limit the number
|
||||
// of mutexes needed for that, only a fixed number of mutexes are
|
||||
// allocated. Fingerprints to be locked are assigned to those pre-allocated
|
||||
// mutexes by their value. Collisions are not detected. If two fingerprints get
|
||||
// assigned to the same mutex, only one of them can be locked at the same
|
||||
// time. As long as the number of pre-allocated mutexes is much larger than the
|
||||
// number of goroutines requiring a fingerprint lock concurrently, the loss in
|
||||
// efficiency is small. However, a goroutine must never lock more than one
|
||||
// fingerprint at the same time. (In that case a collision would try to acquire
|
||||
// the same mutex twice).
|
||||
type fingerprintLocker struct {
|
||||
fpMtxs []paddedMutex
|
||||
numFpMtxs uint
|
||||
}
|
||||
|
||||
// newFingerprintLocker returns a new fingerprintLocker ready for use. At least
|
||||
// 1024 preallocated mutexes are used, even if preallocatedMutexes is lower.
|
||||
func newFingerprintLocker(preallocatedMutexes int) *fingerprintLocker {
|
||||
if preallocatedMutexes < 1024 {
|
||||
preallocatedMutexes = 1024
|
||||
}
|
||||
return &fingerprintLocker{
|
||||
make([]paddedMutex, preallocatedMutexes),
|
||||
uint(preallocatedMutexes),
|
||||
}
|
||||
}
|
||||
|
||||
// Lock locks the given fingerprint.
|
||||
func (l *fingerprintLocker) Lock(fp model.Fingerprint) {
|
||||
l.fpMtxs[hashFP(fp)%l.numFpMtxs].Lock()
|
||||
}
|
||||
|
||||
// Unlock unlocks the given fingerprint.
|
||||
func (l *fingerprintLocker) Unlock(fp model.Fingerprint) {
|
||||
l.fpMtxs[hashFP(fp)%l.numFpMtxs].Unlock()
|
||||
}
|
||||
|
||||
// hashFP simply moves entropy from the most significant 48 bits of the
|
||||
// fingerprint into the least significant 16 bits (by XORing) so that a simple
|
||||
// MOD on the result can be used to pick a mutex while still making use of
|
||||
// changes in more significant bits of the fingerprint. (The fast fingerprinting
|
||||
// function we use is prone to only change a few bits for similar metrics. We
|
||||
// really want to make use of every change in the fingerprint to vary mutex
|
||||
// selection.)
|
||||
func hashFP(fp model.Fingerprint) uint {
|
||||
return uint(fp ^ (fp >> 32) ^ (fp >> 16))
|
||||
}
|
||||
218
vendor/github.com/prometheus/prometheus/storage/local/mapper.go
generated
vendored
Normal file
218
vendor/github.com/prometheus/prometheus/storage/local/mapper.go
generated
vendored
Normal file
@@ -0,0 +1,218 @@
|
||||
// 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 local
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/common/log"
|
||||
|
||||
"github.com/prometheus/common/model"
|
||||
)
|
||||
|
||||
const maxMappedFP = 1 << 20 // About 1M fingerprints reserved for mapping.
|
||||
|
||||
var separatorString = string([]byte{model.SeparatorByte})
|
||||
|
||||
// fpMappings maps original fingerprints to a map of string representations of
|
||||
// metrics to the truly unique fingerprint.
|
||||
type fpMappings map[model.Fingerprint]map[string]model.Fingerprint
|
||||
|
||||
// fpMapper is used to map fingerprints in order to work around fingerprint
|
||||
// collisions.
|
||||
type fpMapper struct {
|
||||
// highestMappedFP has to be aligned for atomic operations.
|
||||
highestMappedFP model.Fingerprint
|
||||
|
||||
mtx sync.RWMutex // Protects mappings.
|
||||
mappings fpMappings
|
||||
|
||||
fpToSeries *seriesMap
|
||||
p *persistence
|
||||
|
||||
mappingsCounter prometheus.Counter
|
||||
}
|
||||
|
||||
// newFPMapper loads the collision map from the persistence and
|
||||
// returns an fpMapper ready to use.
|
||||
func newFPMapper(fpToSeries *seriesMap, p *persistence) (*fpMapper, error) {
|
||||
m := &fpMapper{
|
||||
fpToSeries: fpToSeries,
|
||||
p: p,
|
||||
mappingsCounter: prometheus.NewCounter(prometheus.CounterOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: "fingerprint_mappings_total",
|
||||
Help: "The total number of fingerprints being mapped to avoid collisions.",
|
||||
}),
|
||||
}
|
||||
mappings, nextFP, err := p.loadFPMappings()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m.mappings = mappings
|
||||
m.mappingsCounter.Add(float64(len(m.mappings)))
|
||||
m.highestMappedFP = nextFP
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// checkpoint persists the current mappings. The caller has to ensure that the
|
||||
// provided mappings are not changed concurrently. This method is only called
|
||||
// upon shutdown, when no samples are ingested anymore.
|
||||
func (m *fpMapper) checkpoint() error {
|
||||
return m.p.checkpointFPMappings(m.mappings)
|
||||
}
|
||||
|
||||
// mapFP takes a raw fingerprint (as returned by Metrics.FastFingerprint) and
|
||||
// returns a truly unique fingerprint. The caller must have locked the raw
|
||||
// fingerprint.
|
||||
//
|
||||
// If an error is encountered, it is returned together with the unchanged raw
|
||||
// fingerprint.
|
||||
func (m *fpMapper) mapFP(fp model.Fingerprint, metric model.Metric) model.Fingerprint {
|
||||
// First check if we are in the reserved FP space, in which case this is
|
||||
// automatically a collision that has to be mapped.
|
||||
if fp <= maxMappedFP {
|
||||
return m.maybeAddMapping(fp, metric)
|
||||
}
|
||||
|
||||
// Then check the most likely case: This fp belongs to a series that is
|
||||
// already in memory.
|
||||
s, ok := m.fpToSeries.get(fp)
|
||||
if ok {
|
||||
// FP exists in memory, but is it for the same metric?
|
||||
if metric.Equal(s.metric) {
|
||||
// Yupp. We are done.
|
||||
return fp
|
||||
}
|
||||
// Collision detected!
|
||||
return m.maybeAddMapping(fp, metric)
|
||||
}
|
||||
// Metric is not in memory. Before doing the expensive archive lookup,
|
||||
// check if we have a mapping for this metric in place already.
|
||||
m.mtx.RLock()
|
||||
mappedFPs, fpAlreadyMapped := m.mappings[fp]
|
||||
m.mtx.RUnlock()
|
||||
if fpAlreadyMapped {
|
||||
// We indeed have mapped fp historically.
|
||||
ms := metricToUniqueString(metric)
|
||||
// fp is locked by the caller, so no further locking of
|
||||
// 'collisions' required (it is specific to fp).
|
||||
mappedFP, ok := mappedFPs[ms]
|
||||
if ok {
|
||||
// Historical mapping found, return the mapped FP.
|
||||
return mappedFP
|
||||
}
|
||||
}
|
||||
// If we are here, FP does not exist in memory and is either not mapped
|
||||
// at all, or existing mappings for FP are not for m. Check if we have
|
||||
// something for FP in the archive.
|
||||
archivedMetric, err := m.p.archivedMetric(fp)
|
||||
if err != nil || archivedMetric == nil {
|
||||
// Either the archive lookup has returend an error, or fp does
|
||||
// not exist in the archive. In the former case, the storage has
|
||||
// been marked as dirty already. We just carry on for as long as
|
||||
// it goes, assuming that fp does not exist. In either case,
|
||||
// since now we know (or assume) now that fp does not exist,
|
||||
// neither in memory nor in archive, we can safely keep it
|
||||
// unmapped.
|
||||
return fp
|
||||
}
|
||||
// FP exists in archive, but is it for the same metric?
|
||||
if metric.Equal(archivedMetric) {
|
||||
// Yupp. We are done.
|
||||
return fp
|
||||
}
|
||||
// Collision detected!
|
||||
return m.maybeAddMapping(fp, metric)
|
||||
}
|
||||
|
||||
// maybeAddMapping is only used internally. It takes a detected collision and
|
||||
// adds it to the collisions map if not yet there. In any case, it returns the
|
||||
// truly unique fingerprint for the colliding metric.
|
||||
func (m *fpMapper) maybeAddMapping(
|
||||
fp model.Fingerprint,
|
||||
collidingMetric model.Metric,
|
||||
) model.Fingerprint {
|
||||
ms := metricToUniqueString(collidingMetric)
|
||||
m.mtx.RLock()
|
||||
mappedFPs, ok := m.mappings[fp]
|
||||
m.mtx.RUnlock()
|
||||
if ok {
|
||||
// fp is locked by the caller, so no further locking required.
|
||||
mappedFP, ok := mappedFPs[ms]
|
||||
if ok {
|
||||
return mappedFP // Existing mapping.
|
||||
}
|
||||
// A new mapping has to be created.
|
||||
mappedFP = m.nextMappedFP()
|
||||
mappedFPs[ms] = mappedFP
|
||||
log.Infof(
|
||||
"Collision detected for fingerprint %v, metric %v, mapping to new fingerprint %v.",
|
||||
fp, collidingMetric, mappedFP,
|
||||
)
|
||||
return mappedFP
|
||||
}
|
||||
// This is the first collision for fp.
|
||||
mappedFP := m.nextMappedFP()
|
||||
mappedFPs = map[string]model.Fingerprint{ms: mappedFP}
|
||||
m.mtx.Lock()
|
||||
m.mappings[fp] = mappedFPs
|
||||
m.mappingsCounter.Inc()
|
||||
m.mtx.Unlock()
|
||||
log.Infof(
|
||||
"Collision detected for fingerprint %v, metric %v, mapping to new fingerprint %v.",
|
||||
fp, collidingMetric, mappedFP,
|
||||
)
|
||||
return mappedFP
|
||||
}
|
||||
|
||||
func (m *fpMapper) nextMappedFP() model.Fingerprint {
|
||||
mappedFP := model.Fingerprint(atomic.AddUint64((*uint64)(&m.highestMappedFP), 1))
|
||||
if mappedFP > maxMappedFP {
|
||||
panic(fmt.Errorf("more than %v fingerprints mapped in collision detection", maxMappedFP))
|
||||
}
|
||||
return mappedFP
|
||||
}
|
||||
|
||||
// Describe implements prometheus.Collector.
|
||||
func (m *fpMapper) Describe(ch chan<- *prometheus.Desc) {
|
||||
ch <- m.mappingsCounter.Desc()
|
||||
}
|
||||
|
||||
// Collect implements prometheus.Collector.
|
||||
func (m *fpMapper) Collect(ch chan<- prometheus.Metric) {
|
||||
ch <- m.mappingsCounter
|
||||
}
|
||||
|
||||
// metricToUniqueString turns a metric into a string in a reproducible and
|
||||
// unique way, i.e. the same metric will always create the same string, and
|
||||
// different metrics will always create different strings. In a way, it is the
|
||||
// "ideal" fingerprint function, only that it is more expensive than the
|
||||
// FastFingerprint function, and its result is not suitable as a key for maps
|
||||
// and indexes as it might become really large, causing a lot of hashing effort
|
||||
// in maps and a lot of storage overhead in indexes.
|
||||
func metricToUniqueString(m model.Metric) string {
|
||||
parts := make([]string, 0, len(m))
|
||||
for ln, lv := range m {
|
||||
parts = append(parts, string(ln)+separatorString+string(lv))
|
||||
}
|
||||
sort.Strings(parts)
|
||||
return strings.Join(parts, separatorString)
|
||||
}
|
||||
100
vendor/github.com/prometheus/prometheus/storage/local/noop_storage.go
generated
vendored
Normal file
100
vendor/github.com/prometheus/prometheus/storage/local/noop_storage.go
generated
vendored
Normal file
@@ -0,0 +1,100 @@
|
||||
// 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 local
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/common/model"
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/prometheus/prometheus/storage/metric"
|
||||
)
|
||||
|
||||
// NoopStorage is a dummy storage for use when Prometheus's local storage is
|
||||
// disabled. It throws away any appended samples and returns empty results.
|
||||
type NoopStorage struct{}
|
||||
|
||||
// Start implements Storage.
|
||||
func (s *NoopStorage) Start() (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop implements Storage.
|
||||
func (s *NoopStorage) Stop() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// WaitForIndexing implements Storage.
|
||||
func (s *NoopStorage) WaitForIndexing() {
|
||||
}
|
||||
|
||||
// Querier implements Storage.
|
||||
func (s *NoopStorage) Querier() (Querier, error) {
|
||||
return &NoopQuerier{}, nil
|
||||
}
|
||||
|
||||
// NoopQuerier is a dummy Querier for use when Prometheus's local storage is
|
||||
// disabled. It is returned by the NoopStorage Querier method and always returns
|
||||
// empty results.
|
||||
type NoopQuerier struct{}
|
||||
|
||||
// Close implements Querier.
|
||||
func (s *NoopQuerier) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// LastSampleForLabelMatchers implements Querier.
|
||||
func (s *NoopQuerier) LastSampleForLabelMatchers(ctx context.Context, cutoff model.Time, matcherSets ...metric.LabelMatchers) (model.Vector, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// QueryRange implements Querier
|
||||
func (s *NoopQuerier) QueryRange(ctx context.Context, from, through model.Time, matchers ...*metric.LabelMatcher) ([]SeriesIterator, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// QueryInstant implements Querier.
|
||||
func (s *NoopQuerier) QueryInstant(ctx context.Context, ts model.Time, stalenessDelta time.Duration, matchers ...*metric.LabelMatcher) ([]SeriesIterator, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// MetricsForLabelMatchers implements Querier.
|
||||
func (s *NoopQuerier) MetricsForLabelMatchers(
|
||||
ctx context.Context,
|
||||
from, through model.Time,
|
||||
matcherSets ...metric.LabelMatchers,
|
||||
) ([]metric.Metric, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// LabelValuesForLabelName implements Querier.
|
||||
func (s *NoopQuerier) LabelValuesForLabelName(ctx context.Context, labelName model.LabelName) (model.LabelValues, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// DropMetricsForLabelMatchers implements Storage.
|
||||
func (s *NoopStorage) DropMetricsForLabelMatchers(ctx context.Context, matchers ...*metric.LabelMatcher) (int, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// Append implements Storage.
|
||||
func (s *NoopStorage) Append(sample *model.Sample) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// NeedsThrottling implements Storage.
|
||||
func (s *NoopStorage) NeedsThrottling() bool {
|
||||
return false
|
||||
}
|
||||
1722
vendor/github.com/prometheus/prometheus/storage/local/persistence.go
generated
vendored
Normal file
1722
vendor/github.com/prometheus/prometheus/storage/local/persistence.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
728
vendor/github.com/prometheus/prometheus/storage/local/series.go
generated
vendored
Normal file
728
vendor/github.com/prometheus/prometheus/storage/local/series.go
generated
vendored
Normal file
@@ -0,0 +1,728 @@
|
||||
// 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 local
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/common/model"
|
||||
|
||||
"github.com/prometheus/prometheus/storage/local/chunk"
|
||||
"github.com/prometheus/prometheus/storage/metric"
|
||||
)
|
||||
|
||||
// fingerprintSeriesPair pairs a fingerprint with a memorySeries pointer.
|
||||
type fingerprintSeriesPair struct {
|
||||
fp model.Fingerprint
|
||||
series *memorySeries
|
||||
}
|
||||
|
||||
// seriesMap maps fingerprints to memory series. All its methods are
|
||||
// goroutine-safe. A SeriesMap is effectively is a goroutine-safe version of
|
||||
// map[model.Fingerprint]*memorySeries.
|
||||
type seriesMap struct {
|
||||
mtx sync.RWMutex
|
||||
m map[model.Fingerprint]*memorySeries
|
||||
}
|
||||
|
||||
// newSeriesMap returns a newly allocated empty seriesMap. To create a seriesMap
|
||||
// based on a prefilled map, use an explicit initializer.
|
||||
func newSeriesMap() *seriesMap {
|
||||
return &seriesMap{m: make(map[model.Fingerprint]*memorySeries)}
|
||||
}
|
||||
|
||||
// length returns the number of mappings in the seriesMap.
|
||||
func (sm *seriesMap) length() int {
|
||||
sm.mtx.RLock()
|
||||
defer sm.mtx.RUnlock()
|
||||
|
||||
return len(sm.m)
|
||||
}
|
||||
|
||||
// get returns a memorySeries for a fingerprint. Return values have the same
|
||||
// semantics as the native Go map.
|
||||
func (sm *seriesMap) get(fp model.Fingerprint) (s *memorySeries, ok bool) {
|
||||
sm.mtx.RLock()
|
||||
s, ok = sm.m[fp]
|
||||
// Note that the RUnlock is not done via defer for performance reasons.
|
||||
// TODO(beorn7): Once https://github.com/golang/go/issues/14939 is
|
||||
// fixed, revert to the usual defer idiom.
|
||||
sm.mtx.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
// put adds a mapping to the seriesMap. It panics if s == nil.
|
||||
func (sm *seriesMap) put(fp model.Fingerprint, s *memorySeries) {
|
||||
sm.mtx.Lock()
|
||||
defer sm.mtx.Unlock()
|
||||
|
||||
if s == nil {
|
||||
panic("tried to add nil pointer to seriesMap")
|
||||
}
|
||||
sm.m[fp] = s
|
||||
}
|
||||
|
||||
// del removes a mapping from the series Map.
|
||||
func (sm *seriesMap) del(fp model.Fingerprint) {
|
||||
sm.mtx.Lock()
|
||||
defer sm.mtx.Unlock()
|
||||
|
||||
delete(sm.m, fp)
|
||||
}
|
||||
|
||||
// iter returns a channel that produces all mappings in the seriesMap. The
|
||||
// channel will be closed once all fingerprints have been received. Not
|
||||
// consuming all fingerprints from the channel will leak a goroutine. The
|
||||
// semantics of concurrent modification of seriesMap is the similar as the one
|
||||
// for iterating over a map with a 'range' clause. However, if the next element
|
||||
// in iteration order is removed after the current element has been received
|
||||
// from the channel, it will still be produced by the channel.
|
||||
func (sm *seriesMap) iter() <-chan fingerprintSeriesPair {
|
||||
ch := make(chan fingerprintSeriesPair)
|
||||
go func() {
|
||||
sm.mtx.RLock()
|
||||
for fp, s := range sm.m {
|
||||
sm.mtx.RUnlock()
|
||||
ch <- fingerprintSeriesPair{fp, s}
|
||||
sm.mtx.RLock()
|
||||
}
|
||||
sm.mtx.RUnlock()
|
||||
close(ch)
|
||||
}()
|
||||
return ch
|
||||
}
|
||||
|
||||
// sortedFPs returns a sorted slice of all the fingerprints in the seriesMap.
|
||||
func (sm *seriesMap) sortedFPs() model.Fingerprints {
|
||||
sm.mtx.RLock()
|
||||
fps := make(model.Fingerprints, 0, len(sm.m))
|
||||
for fp := range sm.m {
|
||||
fps = append(fps, fp)
|
||||
}
|
||||
sm.mtx.RUnlock()
|
||||
|
||||
// Sorting could take some time, so do it outside of the lock.
|
||||
sort.Sort(fps)
|
||||
return fps
|
||||
}
|
||||
|
||||
type memorySeries struct {
|
||||
metric model.Metric
|
||||
// Sorted by start time, overlapping chunk ranges are forbidden.
|
||||
chunkDescs []*chunk.Desc
|
||||
// The index (within chunkDescs above) of the first chunk.Desc that
|
||||
// points to a non-persisted chunk. If all chunks are persisted, then
|
||||
// persistWatermark == len(chunkDescs).
|
||||
persistWatermark int
|
||||
// The modification time of the series file. The zero value of time.Time
|
||||
// is used to mark an unknown modification time.
|
||||
modTime time.Time
|
||||
// The chunkDescs in memory might not have all the chunkDescs for the
|
||||
// chunks that are persisted to disk. The missing chunkDescs are all
|
||||
// contiguous and at the tail end. chunkDescsOffset is the index of the
|
||||
// chunk on disk that corresponds to the first chunk.Desc in memory. If
|
||||
// it is 0, the chunkDescs are all loaded. A value of -1 denotes a
|
||||
// special case: There are chunks on disk, but the offset to the
|
||||
// chunkDescs in memory is unknown. Also, in this special case, there is
|
||||
// no overlap between chunks on disk and chunks in memory (implying that
|
||||
// upon first persisting of a chunk in memory, the offset has to be
|
||||
// set).
|
||||
chunkDescsOffset int
|
||||
// The savedFirstTime field is used as a fallback when the
|
||||
// chunkDescsOffset is not 0. It can be used to save the FirstTime of the
|
||||
// first chunk before its chunk desc is evicted. In doubt, this field is
|
||||
// just set to the oldest possible timestamp.
|
||||
savedFirstTime model.Time
|
||||
// The timestamp of the last sample in this series. Needed for fast
|
||||
// access for federation and to ensure timestamp monotonicity during
|
||||
// ingestion.
|
||||
lastTime model.Time
|
||||
// The last ingested sample value. Needed for fast access for
|
||||
// federation.
|
||||
lastSampleValue model.SampleValue
|
||||
// Whether lastSampleValue has been set already.
|
||||
lastSampleValueSet bool
|
||||
// Whether the current head chunk has already been finished. If true,
|
||||
// the current head chunk must not be modified anymore.
|
||||
headChunkClosed bool
|
||||
// Whether the current head chunk is used by an iterator. In that case,
|
||||
// a non-closed head chunk has to be cloned before more samples are
|
||||
// appended.
|
||||
headChunkUsedByIterator bool
|
||||
// Whether the series is inconsistent with the last checkpoint in a way
|
||||
// that would require a disk seek during crash recovery.
|
||||
dirty bool
|
||||
}
|
||||
|
||||
// newMemorySeries returns a pointer to a newly allocated memorySeries for the
|
||||
// given metric. chunkDescs and modTime in the new series are set according to
|
||||
// the provided parameters. chunkDescs can be nil or empty if this is a
|
||||
// genuinely new time series (i.e. not one that is being unarchived). In that
|
||||
// case, headChunkClosed is set to false, and firstTime and lastTime are both
|
||||
// set to model.Earliest. The zero value for modTime can be used if the
|
||||
// modification time of the series file is unknown (e.g. if this is a genuinely
|
||||
// new series).
|
||||
func newMemorySeries(m model.Metric, chunkDescs []*chunk.Desc, modTime time.Time) (*memorySeries, error) {
|
||||
var err error
|
||||
firstTime := model.Earliest
|
||||
lastTime := model.Earliest
|
||||
if len(chunkDescs) > 0 {
|
||||
firstTime = chunkDescs[0].FirstTime()
|
||||
if lastTime, err = chunkDescs[len(chunkDescs)-1].LastTime(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &memorySeries{
|
||||
metric: m,
|
||||
chunkDescs: chunkDescs,
|
||||
headChunkClosed: len(chunkDescs) > 0,
|
||||
savedFirstTime: firstTime,
|
||||
lastTime: lastTime,
|
||||
persistWatermark: len(chunkDescs),
|
||||
modTime: modTime,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// add adds a sample pair to the series. It returns the number of newly
|
||||
// completed chunks (which are now eligible for persistence).
|
||||
//
|
||||
// The caller must have locked the fingerprint of the series.
|
||||
func (s *memorySeries) add(v model.SamplePair) (int, error) {
|
||||
if len(s.chunkDescs) == 0 || s.headChunkClosed {
|
||||
newHead := chunk.NewDesc(chunk.New(), v.Timestamp)
|
||||
s.chunkDescs = append(s.chunkDescs, newHead)
|
||||
s.headChunkClosed = false
|
||||
} else if s.headChunkUsedByIterator && s.head().RefCount() > 1 {
|
||||
// We only need to clone the head chunk if the current head
|
||||
// chunk was used in an iterator at all and if the refCount is
|
||||
// still greater than the 1 we always have because the head
|
||||
// chunk is not yet persisted. The latter is just an
|
||||
// approximation. We will still clone unnecessarily if an older
|
||||
// iterator using a previous version of the head chunk is still
|
||||
// around and keep the head chunk pinned. We needed to track
|
||||
// pins by version of the head chunk, which is probably not
|
||||
// worth the effort.
|
||||
chunk.Ops.WithLabelValues(chunk.Clone).Inc()
|
||||
// No locking needed here because a non-persisted head chunk can
|
||||
// not get evicted concurrently.
|
||||
s.head().C = s.head().C.Clone()
|
||||
s.headChunkUsedByIterator = false
|
||||
}
|
||||
|
||||
chunks, err := s.head().Add(v)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
s.head().C = chunks[0]
|
||||
|
||||
for _, c := range chunks[1:] {
|
||||
s.chunkDescs = append(s.chunkDescs, chunk.NewDesc(c, c.FirstTime()))
|
||||
}
|
||||
|
||||
// Populate lastTime of now-closed chunks.
|
||||
for _, cd := range s.chunkDescs[len(s.chunkDescs)-len(chunks) : len(s.chunkDescs)-1] {
|
||||
if err := cd.MaybePopulateLastTime(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
s.lastTime = v.Timestamp
|
||||
s.lastSampleValue = v.Value
|
||||
s.lastSampleValueSet = true
|
||||
return len(chunks) - 1, nil
|
||||
}
|
||||
|
||||
// maybeCloseHeadChunk closes the head chunk if it has not been touched for the
|
||||
// provided duration. It returns whether the head chunk was closed. If the head
|
||||
// chunk is already closed, the method is a no-op and returns false.
|
||||
//
|
||||
// The caller must have locked the fingerprint of the series.
|
||||
func (s *memorySeries) maybeCloseHeadChunk(timeout time.Duration) (bool, error) {
|
||||
if s.headChunkClosed {
|
||||
return false, nil
|
||||
}
|
||||
if time.Since(s.lastTime.Time()) > timeout {
|
||||
s.headChunkClosed = true
|
||||
// Since we cannot modify the head chunk from now on, we
|
||||
// don't need to bother with cloning anymore.
|
||||
s.headChunkUsedByIterator = false
|
||||
return true, s.head().MaybePopulateLastTime()
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// evictChunkDescs evicts chunkDescs. lenToEvict is the index within the current
|
||||
// chunkDescs of the oldest chunk that is not evicted.
|
||||
func (s *memorySeries) evictChunkDescs(lenToEvict int) {
|
||||
if lenToEvict < 1 {
|
||||
return
|
||||
}
|
||||
if s.chunkDescsOffset < 0 {
|
||||
panic("chunk desc eviction requested with unknown chunk desc offset")
|
||||
}
|
||||
lenToKeep := len(s.chunkDescs) - lenToEvict
|
||||
s.savedFirstTime = s.firstTime()
|
||||
s.chunkDescsOffset += lenToEvict
|
||||
s.persistWatermark -= lenToEvict
|
||||
chunk.DescOps.WithLabelValues(chunk.Evict).Add(float64(lenToEvict))
|
||||
chunk.NumMemDescs.Sub(float64(lenToEvict))
|
||||
s.chunkDescs = append(
|
||||
make([]*chunk.Desc, 0, lenToKeep),
|
||||
s.chunkDescs[lenToEvict:]...,
|
||||
)
|
||||
s.dirty = true
|
||||
}
|
||||
|
||||
// dropChunks removes chunkDescs older than t. The caller must have locked the
|
||||
// fingerprint of the series.
|
||||
func (s *memorySeries) dropChunks(t model.Time) error {
|
||||
keepIdx := len(s.chunkDescs)
|
||||
for i, cd := range s.chunkDescs {
|
||||
lt, err := cd.LastTime()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !lt.Before(t) {
|
||||
keepIdx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if keepIdx == len(s.chunkDescs) && !s.headChunkClosed {
|
||||
// Never drop an open head chunk.
|
||||
keepIdx--
|
||||
}
|
||||
if keepIdx <= 0 {
|
||||
// Nothing to drop.
|
||||
return nil
|
||||
}
|
||||
s.chunkDescs = append(
|
||||
make([]*chunk.Desc, 0, len(s.chunkDescs)-keepIdx),
|
||||
s.chunkDescs[keepIdx:]...,
|
||||
)
|
||||
s.persistWatermark -= keepIdx
|
||||
if s.persistWatermark < 0 {
|
||||
panic("dropped unpersisted chunks from memory")
|
||||
}
|
||||
if s.chunkDescsOffset != -1 {
|
||||
s.chunkDescsOffset += keepIdx
|
||||
}
|
||||
chunk.NumMemDescs.Sub(float64(keepIdx))
|
||||
s.dirty = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// preloadChunks is an internal helper method.
|
||||
func (s *memorySeries) preloadChunks(
|
||||
indexes []int, fp model.Fingerprint, mss *MemorySeriesStorage,
|
||||
) (SeriesIterator, error) {
|
||||
loadIndexes := []int{}
|
||||
pinnedChunkDescs := make([]*chunk.Desc, 0, len(indexes))
|
||||
for _, idx := range indexes {
|
||||
cd := s.chunkDescs[idx]
|
||||
pinnedChunkDescs = append(pinnedChunkDescs, cd)
|
||||
cd.Pin(mss.evictRequests) // Have to pin everything first to prevent immediate eviction on chunk loading.
|
||||
if cd.IsEvicted() {
|
||||
loadIndexes = append(loadIndexes, idx)
|
||||
}
|
||||
}
|
||||
chunk.Ops.WithLabelValues(chunk.Pin).Add(float64(len(pinnedChunkDescs)))
|
||||
|
||||
if len(loadIndexes) > 0 {
|
||||
if s.chunkDescsOffset == -1 {
|
||||
panic("requested loading chunks from persistence in a situation where we must not have persisted data for chunk descriptors in memory")
|
||||
}
|
||||
chunks, err := mss.loadChunks(fp, loadIndexes, s.chunkDescsOffset)
|
||||
if err != nil {
|
||||
// Unpin the chunks since we won't return them as pinned chunks now.
|
||||
for _, cd := range pinnedChunkDescs {
|
||||
cd.Unpin(mss.evictRequests)
|
||||
}
|
||||
chunk.Ops.WithLabelValues(chunk.Unpin).Add(float64(len(pinnedChunkDescs)))
|
||||
return nopIter, err
|
||||
}
|
||||
for i, c := range chunks {
|
||||
s.chunkDescs[loadIndexes[i]].SetChunk(c)
|
||||
}
|
||||
}
|
||||
|
||||
if !s.headChunkClosed && indexes[len(indexes)-1] == len(s.chunkDescs)-1 {
|
||||
s.headChunkUsedByIterator = true
|
||||
}
|
||||
|
||||
curriedQuarantineSeries := func(err error) {
|
||||
mss.quarantineSeries(fp, s.metric, err)
|
||||
}
|
||||
|
||||
iter := &boundedIterator{
|
||||
it: s.newIterator(pinnedChunkDescs, curriedQuarantineSeries, mss.evictRequests),
|
||||
start: model.Now().Add(-mss.dropAfter),
|
||||
}
|
||||
|
||||
return iter, nil
|
||||
}
|
||||
|
||||
// newIterator returns a new SeriesIterator for the provided chunkDescs (which
|
||||
// must be pinned).
|
||||
//
|
||||
// The caller must have locked the fingerprint of the memorySeries.
|
||||
func (s *memorySeries) newIterator(
|
||||
pinnedChunkDescs []*chunk.Desc,
|
||||
quarantine func(error),
|
||||
evictRequests chan<- chunk.EvictRequest,
|
||||
) SeriesIterator {
|
||||
chunks := make([]chunk.Chunk, 0, len(pinnedChunkDescs))
|
||||
for _, cd := range pinnedChunkDescs {
|
||||
// It's OK to directly access cd.c here (without locking) as the
|
||||
// series FP is locked and the chunk is pinned.
|
||||
chunks = append(chunks, cd.C)
|
||||
}
|
||||
return &memorySeriesIterator{
|
||||
chunks: chunks,
|
||||
chunkIts: make([]chunk.Iterator, len(chunks)),
|
||||
quarantine: quarantine,
|
||||
metric: s.metric,
|
||||
pinnedChunkDescs: pinnedChunkDescs,
|
||||
evictRequests: evictRequests,
|
||||
}
|
||||
}
|
||||
|
||||
// preloadChunksForInstant preloads chunks for the latest value in the given
|
||||
// range. If the last sample saved in the memorySeries itself is the latest
|
||||
// value in the given range, it will in fact preload zero chunks and just take
|
||||
// that value.
|
||||
func (s *memorySeries) preloadChunksForInstant(
|
||||
fp model.Fingerprint,
|
||||
from model.Time, through model.Time,
|
||||
mss *MemorySeriesStorage,
|
||||
) (SeriesIterator, error) {
|
||||
// If we have a lastSamplePair in the series, and this last samplePair
|
||||
// is in the interval, just take it in a singleSampleSeriesIterator. No
|
||||
// need to pin or load anything.
|
||||
lastSample := s.lastSamplePair()
|
||||
if !through.Before(lastSample.Timestamp) &&
|
||||
!from.After(lastSample.Timestamp) &&
|
||||
lastSample != model.ZeroSamplePair {
|
||||
iter := &boundedIterator{
|
||||
it: &singleSampleSeriesIterator{
|
||||
samplePair: lastSample,
|
||||
metric: s.metric,
|
||||
},
|
||||
start: model.Now().Add(-mss.dropAfter),
|
||||
}
|
||||
return iter, nil
|
||||
}
|
||||
// If we are here, we are out of luck and have to delegate to the more
|
||||
// expensive method.
|
||||
return s.preloadChunksForRange(fp, from, through, mss)
|
||||
}
|
||||
|
||||
// preloadChunksForRange loads chunks for the given range from the persistence.
|
||||
// The caller must have locked the fingerprint of the series.
|
||||
func (s *memorySeries) preloadChunksForRange(
|
||||
fp model.Fingerprint,
|
||||
from model.Time, through model.Time,
|
||||
mss *MemorySeriesStorage,
|
||||
) (SeriesIterator, error) {
|
||||
firstChunkDescTime := model.Latest
|
||||
if len(s.chunkDescs) > 0 {
|
||||
firstChunkDescTime = s.chunkDescs[0].FirstTime()
|
||||
}
|
||||
if s.chunkDescsOffset != 0 && from.Before(firstChunkDescTime) {
|
||||
cds, err := mss.loadChunkDescs(fp, s.persistWatermark)
|
||||
if err != nil {
|
||||
return nopIter, err
|
||||
}
|
||||
if s.chunkDescsOffset != -1 && len(cds) != s.chunkDescsOffset {
|
||||
return nopIter, fmt.Errorf(
|
||||
"unexpected number of chunk descs loaded for fingerprint %v: expected %d, got %d",
|
||||
fp, s.chunkDescsOffset, len(cds),
|
||||
)
|
||||
}
|
||||
s.persistWatermark += len(cds)
|
||||
s.chunkDescs = append(cds, s.chunkDescs...)
|
||||
s.chunkDescsOffset = 0
|
||||
if len(s.chunkDescs) > 0 {
|
||||
firstChunkDescTime = s.chunkDescs[0].FirstTime()
|
||||
}
|
||||
}
|
||||
|
||||
if len(s.chunkDescs) == 0 || through.Before(firstChunkDescTime) {
|
||||
return nopIter, nil
|
||||
}
|
||||
|
||||
// Find first chunk with start time after "from".
|
||||
fromIdx := sort.Search(len(s.chunkDescs), func(i int) bool {
|
||||
return s.chunkDescs[i].FirstTime().After(from)
|
||||
})
|
||||
// Find first chunk with start time after "through".
|
||||
throughIdx := sort.Search(len(s.chunkDescs), func(i int) bool {
|
||||
return s.chunkDescs[i].FirstTime().After(through)
|
||||
})
|
||||
if fromIdx == len(s.chunkDescs) {
|
||||
// Even the last chunk starts before "from". Find out if the
|
||||
// series ends before "from" and we don't need to do anything.
|
||||
lt, err := s.chunkDescs[len(s.chunkDescs)-1].LastTime()
|
||||
if err != nil {
|
||||
return nopIter, err
|
||||
}
|
||||
if lt.Before(from) {
|
||||
return nopIter, nil
|
||||
}
|
||||
}
|
||||
if fromIdx > 0 {
|
||||
fromIdx--
|
||||
}
|
||||
if throughIdx == len(s.chunkDescs) {
|
||||
throughIdx--
|
||||
}
|
||||
if fromIdx > throughIdx {
|
||||
// Guard against nonsensical result. The caller will quarantine the series with a meaningful log entry.
|
||||
return nopIter, fmt.Errorf("fromIdx=%d is greater than throughIdx=%d, likely caused by data corruption", fromIdx, throughIdx)
|
||||
}
|
||||
|
||||
pinIndexes := make([]int, 0, throughIdx-fromIdx+1)
|
||||
for i := fromIdx; i <= throughIdx; i++ {
|
||||
pinIndexes = append(pinIndexes, i)
|
||||
}
|
||||
return s.preloadChunks(pinIndexes, fp, mss)
|
||||
}
|
||||
|
||||
// head returns a pointer to the head chunk descriptor. The caller must have
|
||||
// locked the fingerprint of the memorySeries. This method will panic if this
|
||||
// series has no chunk descriptors.
|
||||
func (s *memorySeries) head() *chunk.Desc {
|
||||
return s.chunkDescs[len(s.chunkDescs)-1]
|
||||
}
|
||||
|
||||
// firstTime returns the timestamp of the first sample in the series.
|
||||
//
|
||||
// The caller must have locked the fingerprint of the memorySeries.
|
||||
func (s *memorySeries) firstTime() model.Time {
|
||||
if s.chunkDescsOffset == 0 && len(s.chunkDescs) > 0 {
|
||||
return s.chunkDescs[0].FirstTime()
|
||||
}
|
||||
return s.savedFirstTime
|
||||
}
|
||||
|
||||
// lastSamplePair returns the last ingested SamplePair. It returns
|
||||
// model.ZeroSamplePair if this memorySeries has never received a sample (via the add
|
||||
// method), which is the case for freshly unarchived series or newly created
|
||||
// ones and also for all series after a server restart. However, in that case,
|
||||
// series will most likely be considered stale anyway.
|
||||
//
|
||||
// The caller must have locked the fingerprint of the memorySeries.
|
||||
func (s *memorySeries) lastSamplePair() model.SamplePair {
|
||||
if !s.lastSampleValueSet {
|
||||
return model.ZeroSamplePair
|
||||
}
|
||||
return model.SamplePair{
|
||||
Timestamp: s.lastTime,
|
||||
Value: s.lastSampleValue,
|
||||
}
|
||||
}
|
||||
|
||||
// chunksToPersist returns a slice of chunkDescs eligible for persistence. It's
|
||||
// the caller's responsibility to actually persist the returned chunks
|
||||
// afterwards. The method sets the persistWatermark and the dirty flag
|
||||
// accordingly.
|
||||
//
|
||||
// The caller must have locked the fingerprint of the series.
|
||||
func (s *memorySeries) chunksToPersist() []*chunk.Desc {
|
||||
newWatermark := len(s.chunkDescs)
|
||||
if !s.headChunkClosed {
|
||||
newWatermark--
|
||||
}
|
||||
if newWatermark == s.persistWatermark {
|
||||
return nil
|
||||
}
|
||||
cds := s.chunkDescs[s.persistWatermark:newWatermark]
|
||||
s.dirty = true
|
||||
s.persistWatermark = newWatermark
|
||||
return cds
|
||||
}
|
||||
|
||||
// memorySeriesIterator implements SeriesIterator.
|
||||
type memorySeriesIterator struct {
|
||||
// Last chunk.Iterator used by ValueAtOrBeforeTime.
|
||||
chunkIt chunk.Iterator
|
||||
// Caches chunkIterators.
|
||||
chunkIts []chunk.Iterator
|
||||
// The actual sample chunks.
|
||||
chunks []chunk.Chunk
|
||||
// Call to quarantine the series this iterator belongs to.
|
||||
quarantine func(error)
|
||||
// The metric corresponding to the iterator.
|
||||
metric model.Metric
|
||||
// Chunks that were pinned for this iterator.
|
||||
pinnedChunkDescs []*chunk.Desc
|
||||
// Where to send evict requests when unpinning pinned chunks.
|
||||
evictRequests chan<- chunk.EvictRequest
|
||||
}
|
||||
|
||||
// ValueAtOrBeforeTime implements SeriesIterator.
|
||||
func (it *memorySeriesIterator) ValueAtOrBeforeTime(t model.Time) model.SamplePair {
|
||||
// The most common case. We are iterating through a chunk.
|
||||
if it.chunkIt != nil {
|
||||
containsT, err := it.chunkIt.Contains(t)
|
||||
if err != nil {
|
||||
it.quarantine(err)
|
||||
return model.ZeroSamplePair
|
||||
}
|
||||
if containsT {
|
||||
if it.chunkIt.FindAtOrBefore(t) {
|
||||
return it.chunkIt.Value()
|
||||
}
|
||||
if it.chunkIt.Err() != nil {
|
||||
it.quarantine(it.chunkIt.Err())
|
||||
}
|
||||
return model.ZeroSamplePair
|
||||
}
|
||||
}
|
||||
|
||||
if len(it.chunks) == 0 {
|
||||
return model.ZeroSamplePair
|
||||
}
|
||||
|
||||
// Find the last chunk where FirstTime() is before or equal to t.
|
||||
l := len(it.chunks) - 1
|
||||
i := sort.Search(len(it.chunks), func(i int) bool {
|
||||
return !it.chunks[l-i].FirstTime().After(t)
|
||||
})
|
||||
if i == len(it.chunks) {
|
||||
// Even the first chunk starts after t.
|
||||
return model.ZeroSamplePair
|
||||
}
|
||||
it.chunkIt = it.chunkIterator(l - i)
|
||||
if it.chunkIt.FindAtOrBefore(t) {
|
||||
return it.chunkIt.Value()
|
||||
}
|
||||
if it.chunkIt.Err() != nil {
|
||||
it.quarantine(it.chunkIt.Err())
|
||||
}
|
||||
return model.ZeroSamplePair
|
||||
}
|
||||
|
||||
// RangeValues implements SeriesIterator.
|
||||
func (it *memorySeriesIterator) RangeValues(in metric.Interval) []model.SamplePair {
|
||||
// Find the first chunk for which the first sample is within the interval.
|
||||
i := sort.Search(len(it.chunks), func(i int) bool {
|
||||
return !it.chunks[i].FirstTime().Before(in.OldestInclusive)
|
||||
})
|
||||
// Only now check the last timestamp of the previous chunk (which is
|
||||
// fairly expensive).
|
||||
if i > 0 {
|
||||
lt, err := it.chunkIterator(i - 1).LastTimestamp()
|
||||
if err != nil {
|
||||
it.quarantine(err)
|
||||
return nil
|
||||
}
|
||||
if !lt.Before(in.OldestInclusive) {
|
||||
i--
|
||||
}
|
||||
}
|
||||
|
||||
values := []model.SamplePair{}
|
||||
for j, c := range it.chunks[i:] {
|
||||
if c.FirstTime().After(in.NewestInclusive) {
|
||||
break
|
||||
}
|
||||
chValues, err := chunk.RangeValues(it.chunkIterator(i+j), in)
|
||||
if err != nil {
|
||||
it.quarantine(err)
|
||||
return nil
|
||||
}
|
||||
values = append(values, chValues...)
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
func (it *memorySeriesIterator) Metric() metric.Metric {
|
||||
return metric.Metric{Metric: it.metric}
|
||||
}
|
||||
|
||||
// chunkIterator returns the chunk.Iterator for the chunk at position i (and
|
||||
// creates it if needed).
|
||||
func (it *memorySeriesIterator) chunkIterator(i int) chunk.Iterator {
|
||||
chunkIt := it.chunkIts[i]
|
||||
if chunkIt == nil {
|
||||
chunkIt = it.chunks[i].NewIterator()
|
||||
it.chunkIts[i] = chunkIt
|
||||
}
|
||||
return chunkIt
|
||||
}
|
||||
|
||||
func (it *memorySeriesIterator) Close() {
|
||||
for _, cd := range it.pinnedChunkDescs {
|
||||
cd.Unpin(it.evictRequests)
|
||||
}
|
||||
chunk.Ops.WithLabelValues(chunk.Unpin).Add(float64(len(it.pinnedChunkDescs)))
|
||||
}
|
||||
|
||||
// singleSampleSeriesIterator implements Series Iterator. It is a "shortcut
|
||||
// iterator" that returns a single sample only. The sample is saved in the
|
||||
// iterator itself, so no chunks need to be pinned.
|
||||
type singleSampleSeriesIterator struct {
|
||||
samplePair model.SamplePair
|
||||
metric model.Metric
|
||||
}
|
||||
|
||||
// ValueAtTime implements SeriesIterator.
|
||||
func (it *singleSampleSeriesIterator) ValueAtOrBeforeTime(t model.Time) model.SamplePair {
|
||||
if it.samplePair.Timestamp.After(t) {
|
||||
return model.ZeroSamplePair
|
||||
}
|
||||
return it.samplePair
|
||||
}
|
||||
|
||||
// RangeValues implements SeriesIterator.
|
||||
func (it *singleSampleSeriesIterator) RangeValues(in metric.Interval) []model.SamplePair {
|
||||
if it.samplePair.Timestamp.After(in.NewestInclusive) ||
|
||||
it.samplePair.Timestamp.Before(in.OldestInclusive) {
|
||||
return []model.SamplePair{}
|
||||
}
|
||||
return []model.SamplePair{it.samplePair}
|
||||
}
|
||||
|
||||
func (it *singleSampleSeriesIterator) Metric() metric.Metric {
|
||||
return metric.Metric{Metric: it.metric}
|
||||
}
|
||||
|
||||
// Close implements SeriesIterator.
|
||||
func (it *singleSampleSeriesIterator) Close() {}
|
||||
|
||||
// nopSeriesIterator implements Series Iterator. It never returns any values.
|
||||
type nopSeriesIterator struct{}
|
||||
|
||||
// ValueAtTime implements SeriesIterator.
|
||||
func (i nopSeriesIterator) ValueAtOrBeforeTime(t model.Time) model.SamplePair {
|
||||
return model.ZeroSamplePair
|
||||
}
|
||||
|
||||
// RangeValues implements SeriesIterator.
|
||||
func (i nopSeriesIterator) RangeValues(in metric.Interval) []model.SamplePair {
|
||||
return []model.SamplePair{}
|
||||
}
|
||||
|
||||
// Metric implements SeriesIterator.
|
||||
func (i nopSeriesIterator) Metric() metric.Metric {
|
||||
return metric.Metric{}
|
||||
}
|
||||
|
||||
// Close implements SeriesIterator.
|
||||
func (i nopSeriesIterator) Close() {}
|
||||
|
||||
var nopIter nopSeriesIterator // A nopSeriesIterator for convenience. Can be shared.
|
||||
2029
vendor/github.com/prometheus/prometheus/storage/local/storage.go
generated
vendored
Normal file
2029
vendor/github.com/prometheus/prometheus/storage/local/storage.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
72
vendor/github.com/prometheus/prometheus/storage/local/test_helpers.go
generated
vendored
Normal file
72
vendor/github.com/prometheus/prometheus/storage/local/test_helpers.go
generated
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
// 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.
|
||||
|
||||
// NOTE ON FILENAME: Do not rename this file helpers_test.go (which might appear
|
||||
// an obvious choice). We need NewTestStorage in tests outside of the local
|
||||
// package, too. On the other hand, moving NewTestStorage in its own package
|
||||
// would cause circular dependencies in the tests in packages local.
|
||||
|
||||
package local
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/common/model"
|
||||
"github.com/prometheus/prometheus/storage/local/chunk"
|
||||
"github.com/prometheus/prometheus/util/testutil"
|
||||
)
|
||||
|
||||
type testStorageCloser struct {
|
||||
storage Storage
|
||||
directory testutil.Closer
|
||||
}
|
||||
|
||||
func (t *testStorageCloser) Close() {
|
||||
if err := t.storage.Stop(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
t.directory.Close()
|
||||
}
|
||||
|
||||
// NewTestStorage creates a storage instance backed by files in a temporary
|
||||
// directory. The returned storage is already in serving state. Upon closing the
|
||||
// returned test.Closer, the temporary directory is cleaned up.
|
||||
func NewTestStorage(t testutil.T, encoding chunk.Encoding) (*MemorySeriesStorage, testutil.Closer) {
|
||||
chunk.DefaultEncoding = encoding
|
||||
directory := testutil.NewTemporaryDirectory("test_storage", t)
|
||||
o := &MemorySeriesStorageOptions{
|
||||
TargetHeapSize: 1000000000,
|
||||
PersistenceRetentionPeriod: 24 * time.Hour * 365 * 100, // Enough to never trigger purging.
|
||||
PersistenceStoragePath: directory.Path(),
|
||||
HeadChunkTimeout: 5 * time.Minute,
|
||||
CheckpointInterval: time.Hour,
|
||||
SyncStrategy: Adaptive,
|
||||
}
|
||||
storage := NewMemorySeriesStorage(o)
|
||||
storage.archiveHighWatermark = model.Latest
|
||||
if err := storage.Start(); err != nil {
|
||||
directory.Close()
|
||||
t.Fatalf("Error creating storage: %s", err)
|
||||
}
|
||||
|
||||
closer := &testStorageCloser{
|
||||
storage: storage,
|
||||
directory: directory,
|
||||
}
|
||||
|
||||
return storage, closer
|
||||
}
|
||||
|
||||
func makeFingerprintSeriesPair(s *MemorySeriesStorage, fp model.Fingerprint) fingerprintSeriesPair {
|
||||
return fingerprintSeriesPair{fp, s.seriesForRange(fp, model.Earliest, model.Latest)}
|
||||
}
|
||||
209
vendor/github.com/prometheus/prometheus/storage/metric/matcher.go
generated
vendored
Normal file
209
vendor/github.com/prometheus/prometheus/storage/metric/matcher.go
generated
vendored
Normal file
@@ -0,0 +1,209 @@
|
||||
// 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 metric
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/prometheus/common/model"
|
||||
)
|
||||
|
||||
// MatchType is an enum for label matching types.
|
||||
type MatchType int
|
||||
|
||||
// Possible MatchTypes.
|
||||
const (
|
||||
Equal MatchType = iota
|
||||
NotEqual
|
||||
RegexMatch
|
||||
RegexNoMatch
|
||||
)
|
||||
|
||||
func (m MatchType) String() string {
|
||||
typeToStr := map[MatchType]string{
|
||||
Equal: "=",
|
||||
NotEqual: "!=",
|
||||
RegexMatch: "=~",
|
||||
RegexNoMatch: "!~",
|
||||
}
|
||||
if str, ok := typeToStr[m]; ok {
|
||||
return str
|
||||
}
|
||||
panic("unknown match type")
|
||||
}
|
||||
|
||||
// LabelMatchers is a slice of LabelMatcher objects. By implementing the
|
||||
// sort.Interface, it is sortable by cardinality score, i.e. after sorting, the
|
||||
// LabelMatcher that is expected to yield the fewest matches is first in the
|
||||
// slice, and LabelMatchers that match the empty string are last.
|
||||
type LabelMatchers []*LabelMatcher
|
||||
|
||||
func (lms LabelMatchers) Len() int { return len(lms) }
|
||||
func (lms LabelMatchers) Swap(i, j int) { lms[i], lms[j] = lms[j], lms[i] }
|
||||
func (lms LabelMatchers) Less(i, j int) bool { return lms[i].score < lms[j].score }
|
||||
|
||||
func (lms LabelMatchers) String() string {
|
||||
result := make([]string, 0, len(lms))
|
||||
for _, lm := range lms {
|
||||
result = append(result, lm.String())
|
||||
}
|
||||
return strings.Join(result, ",")
|
||||
}
|
||||
|
||||
// LabelMatcher models the matching of a label. Create with NewLabelMatcher.
|
||||
type LabelMatcher struct {
|
||||
Type MatchType
|
||||
Name model.LabelName
|
||||
Value model.LabelValue
|
||||
re *regexp.Regexp
|
||||
score float64 // Cardinality score, between 0 and 1, 0 is lowest cardinality.
|
||||
}
|
||||
|
||||
// NewLabelMatcher returns a LabelMatcher object ready to use.
|
||||
func NewLabelMatcher(matchType MatchType, name model.LabelName, value model.LabelValue) (*LabelMatcher, error) {
|
||||
m := &LabelMatcher{
|
||||
Type: matchType,
|
||||
Name: name,
|
||||
Value: value,
|
||||
}
|
||||
if matchType == RegexMatch || matchType == RegexNoMatch {
|
||||
re, err := regexp.Compile("^(?:" + string(value) + ")$")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m.re = re
|
||||
}
|
||||
m.calculateScore()
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// calculateScore is a helper method only called in the constructor. It
|
||||
// calculates the cardinality score upfront, so that sorting by it is faster and
|
||||
// doesn't change internal state of the matcher.
|
||||
//
|
||||
// The score is based on a pretty bad but still quite helpful heuristics for
|
||||
// now. Note that this is an interim solution until the work in progress to
|
||||
// properly intersect matchers is complete. We intend to not invest any further
|
||||
// effort into tweaking the score calculation, as this could easily devolve into
|
||||
// a rabbit hole.
|
||||
//
|
||||
// The heuristics works along the following lines:
|
||||
//
|
||||
// - A matcher that is known to match nothing would have a score of 0. (This
|
||||
// case doesn't happen in the scope of this method.)
|
||||
//
|
||||
// - A matcher that matches the empty string has a score of 1.
|
||||
//
|
||||
// - Equal matchers have a score <= 0.5. The order in score for other matchers
|
||||
// are RegexMatch, RegexNoMatch, NotEqual.
|
||||
//
|
||||
// - There are a number of score adjustments for known "magic" parts, like
|
||||
// instance labels, metric names containing a colon (which are probably
|
||||
// recording rules) and such.
|
||||
//
|
||||
// - On top, there is a tiny adjustment for the length of the matcher, following
|
||||
// the blunt expectation that a long label name and/or value is more specific
|
||||
// and will therefore have a lower cardinality.
|
||||
//
|
||||
// To reiterate on the above: PLEASE RESIST THE TEMPTATION TO TWEAK THIS
|
||||
// METHOD. IT IS "MAGIC" ENOUGH ALREADY AND WILL GO AWAY WITH THE UPCOMING MORE
|
||||
// POWERFUL INDEXING.
|
||||
func (m *LabelMatcher) calculateScore() {
|
||||
if m.Match("") {
|
||||
m.score = 1
|
||||
return
|
||||
}
|
||||
// lengthCorrection is between 0 (for length 0) and 0.1 (for length +Inf).
|
||||
lengthCorrection := 0.1 * (1 - 1/float64(len(m.Name)+len(m.Value)+1))
|
||||
switch m.Type {
|
||||
case Equal:
|
||||
m.score = 0.3 - lengthCorrection
|
||||
case RegexMatch:
|
||||
m.score = 0.6 - lengthCorrection
|
||||
case RegexNoMatch:
|
||||
m.score = 0.8 + lengthCorrection
|
||||
case NotEqual:
|
||||
m.score = 0.9 + lengthCorrection
|
||||
}
|
||||
if m.Type != Equal {
|
||||
// Don't bother anymore in this case.
|
||||
return
|
||||
}
|
||||
switch m.Name {
|
||||
case model.InstanceLabel:
|
||||
// Matches only metrics from a single instance, which clearly
|
||||
// limits the damage.
|
||||
m.score -= 0.2
|
||||
case model.JobLabel:
|
||||
// The usual case is a relatively low number of jobs with many
|
||||
// metrics each.
|
||||
m.score += 0.1
|
||||
case model.BucketLabel, model.QuantileLabel:
|
||||
// Magic labels for buckets and quantiles will match copiously.
|
||||
m.score += 0.2
|
||||
case model.MetricNameLabel:
|
||||
if strings.Contains(string(m.Value), ":") {
|
||||
// Probably a recording rule with limited cardinality.
|
||||
m.score -= 0.1
|
||||
return
|
||||
}
|
||||
if m.Value == "up" || m.Value == "scrape_duration_seconds" {
|
||||
// Synthetic metrics which are contained in every scrape
|
||||
// exactly once. There might be less frequent metric
|
||||
// names, but the worst case is limited here, so give it
|
||||
// a bump.
|
||||
m.score -= 0.05
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MatchesEmptyString returns true if the LabelMatcher matches the empty string.
|
||||
func (m *LabelMatcher) MatchesEmptyString() bool {
|
||||
return m.score >= 1
|
||||
}
|
||||
|
||||
func (m *LabelMatcher) String() string {
|
||||
return fmt.Sprintf("%s%s%q", m.Name, m.Type, m.Value)
|
||||
}
|
||||
|
||||
// Match returns true if the label matcher matches the supplied label value.
|
||||
func (m *LabelMatcher) Match(v model.LabelValue) bool {
|
||||
switch m.Type {
|
||||
case Equal:
|
||||
return m.Value == v
|
||||
case NotEqual:
|
||||
return m.Value != v
|
||||
case RegexMatch:
|
||||
return m.re.MatchString(string(v))
|
||||
case RegexNoMatch:
|
||||
return !m.re.MatchString(string(v))
|
||||
default:
|
||||
panic("invalid match type")
|
||||
}
|
||||
}
|
||||
|
||||
// Filter takes a list of label values and returns all label values which match
|
||||
// the label matcher.
|
||||
func (m *LabelMatcher) Filter(in model.LabelValues) model.LabelValues {
|
||||
out := model.LabelValues{}
|
||||
for _, v := range in {
|
||||
if m.Match(v) {
|
||||
out = append(out, v)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
63
vendor/github.com/prometheus/prometheus/storage/metric/metric.go
generated
vendored
Normal file
63
vendor/github.com/prometheus/prometheus/storage/metric/metric.go
generated
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
// 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 metric
|
||||
|
||||
import "github.com/prometheus/common/model"
|
||||
|
||||
// Metric wraps a model.Metric and copies it upon modification if Copied is false.
|
||||
type Metric struct {
|
||||
Copied bool
|
||||
Metric model.Metric
|
||||
}
|
||||
|
||||
// Set sets a label name in the wrapped Metric to a given value and copies the
|
||||
// Metric initially, if it is not already a copy.
|
||||
func (m *Metric) Set(ln model.LabelName, lv model.LabelValue) {
|
||||
m.Copy()
|
||||
m.Metric[ln] = lv
|
||||
}
|
||||
|
||||
// Del deletes a given label name from the wrapped Metric and copies the
|
||||
// Metric initially, if it is not already a copy.
|
||||
func (m *Metric) Del(ln model.LabelName) {
|
||||
m.Copy()
|
||||
delete(m.Metric, ln)
|
||||
}
|
||||
|
||||
// Get the value for the given label name. An empty value is returned
|
||||
// if the label does not exist in the metric.
|
||||
func (m *Metric) Get(ln model.LabelName) model.LabelValue {
|
||||
return m.Metric[ln]
|
||||
}
|
||||
|
||||
// Gets behaves as Get but the returned boolean is false iff the label
|
||||
// does not exist.
|
||||
func (m *Metric) Gets(ln model.LabelName) (model.LabelValue, bool) {
|
||||
lv, ok := m.Metric[ln]
|
||||
return lv, ok
|
||||
}
|
||||
|
||||
// Copy the underlying Metric if it is not already a copy.
|
||||
func (m *Metric) Copy() *Metric {
|
||||
if !m.Copied {
|
||||
m.Metric = m.Metric.Clone()
|
||||
m.Copied = true
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// String implements fmt.Stringer.
|
||||
func (m Metric) String() string {
|
||||
return m.Metric.String()
|
||||
}
|
||||
22
vendor/github.com/prometheus/prometheus/storage/metric/sample.go
generated
vendored
Normal file
22
vendor/github.com/prometheus/prometheus/storage/metric/sample.go
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
// Copyright 2013 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 metric
|
||||
|
||||
import "github.com/prometheus/common/model"
|
||||
|
||||
// Interval describes the inclusive interval between two Timestamps.
|
||||
type Interval struct {
|
||||
OldestInclusive model.Time
|
||||
NewestInclusive model.Time
|
||||
}
|
||||
76
vendor/github.com/prometheus/prometheus/storage/storage.go
generated
vendored
Normal file
76
vendor/github.com/prometheus/prometheus/storage/storage.go
generated
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
// Copyright 2015 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 storage
|
||||
|
||||
import (
|
||||
"github.com/prometheus/common/model"
|
||||
)
|
||||
|
||||
// SampleAppender is the interface to append samples to both, local and remote
|
||||
// storage. All methods are goroutine-safe.
|
||||
type SampleAppender interface {
|
||||
// Append appends a sample to the underlying storage. Depending on the
|
||||
// storage implementation, there are different guarantees for the fate
|
||||
// of the sample after Append has returned. Remote storage
|
||||
// implementation will simply drop samples if they cannot keep up with
|
||||
// sending samples. Local storage implementations will only drop metrics
|
||||
// upon unrecoverable errors.
|
||||
Append(*model.Sample) error
|
||||
// NeedsThrottling returns true if the underlying storage wishes to not
|
||||
// receive any more samples. Append will still work but might lead to
|
||||
// undue resource usage. It is recommended to call NeedsThrottling once
|
||||
// before an upcoming batch of Append calls (e.g. a full scrape of a
|
||||
// target or the evaluation of a rule group) and only proceed with the
|
||||
// batch if NeedsThrottling returns false. In that way, the result of a
|
||||
// scrape or of an evaluation of a rule group will always be appended
|
||||
// completely or not at all, and the work of scraping or evaluation will
|
||||
// not be performed in vain. Also, a call of NeedsThrottling is
|
||||
// potentially expensive, so limiting the number of calls is reasonable.
|
||||
//
|
||||
// Only SampleAppenders for which it is considered critical to receive
|
||||
// each and every sample should ever return true. SampleAppenders that
|
||||
// tolerate not receiving all samples should always return false and
|
||||
// instead drop samples as they see fit to avoid overload.
|
||||
NeedsThrottling() bool
|
||||
}
|
||||
|
||||
// Fanout is a SampleAppender that appends every sample to each SampleAppender
|
||||
// in its list.
|
||||
type Fanout []SampleAppender
|
||||
|
||||
// Append implements SampleAppender. It appends the provided sample to all
|
||||
// SampleAppenders in the Fanout slice and waits for each append to complete
|
||||
// before proceeding with the next.
|
||||
// If any of the SampleAppenders returns an error, the first one is returned
|
||||
// at the end.
|
||||
func (f Fanout) Append(s *model.Sample) error {
|
||||
var err error
|
||||
for _, a := range f {
|
||||
if e := a.Append(s); e != nil && err == nil {
|
||||
err = e
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// NeedsThrottling returns true if at least one of the SampleAppenders in the
|
||||
// Fanout slice is throttled.
|
||||
func (f Fanout) NeedsThrottling() bool {
|
||||
for _, a := range f {
|
||||
if a.NeedsThrottling() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
46
vendor/github.com/prometheus/prometheus/util/flock/flock.go
generated
vendored
Normal file
46
vendor/github.com/prometheus/prometheus/util/flock/flock.go
generated
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
// 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 flock provides portable file locking. It is essentially ripped out
|
||||
// from the code of github.com/syndtr/goleveldb. Strange enough that the
|
||||
// standard library does not provide this functionality. Once this package has
|
||||
// proven to work as expected, we should probably turn it into a separate
|
||||
// general purpose package for humanity.
|
||||
package flock
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// Releaser provides the Release method to release a file lock.
|
||||
type Releaser interface {
|
||||
Release() error
|
||||
}
|
||||
|
||||
// New locks the file with the provided name. If the file does not exist, it is
|
||||
// created. The returned Releaser is used to release the lock. existed is true
|
||||
// if the file to lock already existed. A non-nil error is returned if the
|
||||
// locking has failed. Neither this function nor the returned Releaser is
|
||||
// goroutine-safe.
|
||||
func New(fileName string) (r Releaser, existed bool, err error) {
|
||||
if err = os.MkdirAll(filepath.Dir(fileName), 0755); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = os.Stat(fileName)
|
||||
existed = err == nil
|
||||
|
||||
r, err = newLock(fileName)
|
||||
return
|
||||
}
|
||||
32
vendor/github.com/prometheus/prometheus/util/flock/flock_plan9.go
generated
vendored
Normal file
32
vendor/github.com/prometheus/prometheus/util/flock/flock_plan9.go
generated
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
// 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 flock
|
||||
|
||||
import "os"
|
||||
|
||||
type plan9Lock struct {
|
||||
f *os.File
|
||||
}
|
||||
|
||||
func (l *plan9Lock) Release() error {
|
||||
return l.f.Close()
|
||||
}
|
||||
|
||||
func newLock(fileName string) (Releaser, error) {
|
||||
f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, os.ModeExclusive|0644)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &plan9Lock{f}, nil
|
||||
}
|
||||
59
vendor/github.com/prometheus/prometheus/util/flock/flock_solaris.go
generated
vendored
Normal file
59
vendor/github.com/prometheus/prometheus/util/flock/flock_solaris.go
generated
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
// 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.
|
||||
|
||||
// +build solaris
|
||||
|
||||
package flock
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
type unixLock struct {
|
||||
f *os.File
|
||||
}
|
||||
|
||||
func (l *unixLock) Release() error {
|
||||
if err := l.set(false); err != nil {
|
||||
return err
|
||||
}
|
||||
return l.f.Close()
|
||||
}
|
||||
|
||||
func (l *unixLock) set(lock bool) error {
|
||||
flock := syscall.Flock_t{
|
||||
Type: syscall.F_UNLCK,
|
||||
Start: 0,
|
||||
Len: 0,
|
||||
Whence: 1,
|
||||
}
|
||||
if lock {
|
||||
flock.Type = syscall.F_WRLCK
|
||||
}
|
||||
return syscall.FcntlFlock(l.f.Fd(), syscall.F_SETLK, &flock)
|
||||
}
|
||||
|
||||
func newLock(fileName string) (Releaser, error) {
|
||||
f, err := os.OpenFile(fileName, os.O_RDWR|os.O_CREATE, 0644)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
l := &unixLock{f}
|
||||
err = l.set(true)
|
||||
if err != nil {
|
||||
f.Close()
|
||||
return nil, err
|
||||
}
|
||||
return l, nil
|
||||
}
|
||||
54
vendor/github.com/prometheus/prometheus/util/flock/flock_unix.go
generated
vendored
Normal file
54
vendor/github.com/prometheus/prometheus/util/flock/flock_unix.go
generated
vendored
Normal file
@@ -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.
|
||||
|
||||
// +build darwin dragonfly freebsd linux netbsd openbsd
|
||||
|
||||
package flock
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
type unixLock struct {
|
||||
f *os.File
|
||||
}
|
||||
|
||||
func (l *unixLock) Release() error {
|
||||
if err := l.set(false); err != nil {
|
||||
return err
|
||||
}
|
||||
return l.f.Close()
|
||||
}
|
||||
|
||||
func (l *unixLock) set(lock bool) error {
|
||||
how := syscall.LOCK_UN
|
||||
if lock {
|
||||
how = syscall.LOCK_EX
|
||||
}
|
||||
return syscall.Flock(int(l.f.Fd()), how|syscall.LOCK_NB)
|
||||
}
|
||||
|
||||
func newLock(fileName string) (Releaser, error) {
|
||||
f, err := os.OpenFile(fileName, os.O_RDWR|os.O_CREATE, 0644)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
l := &unixLock{f}
|
||||
err = l.set(true)
|
||||
if err != nil {
|
||||
f.Close()
|
||||
return nil, err
|
||||
}
|
||||
return l, nil
|
||||
}
|
||||
36
vendor/github.com/prometheus/prometheus/util/flock/flock_windows.go
generated
vendored
Normal file
36
vendor/github.com/prometheus/prometheus/util/flock/flock_windows.go
generated
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
// 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 flock
|
||||
|
||||
import "syscall"
|
||||
|
||||
type windowsLock struct {
|
||||
fd syscall.Handle
|
||||
}
|
||||
|
||||
func (fl *windowsLock) Release() error {
|
||||
return syscall.Close(fl.fd)
|
||||
}
|
||||
|
||||
func newLock(fileName string) (Releaser, error) {
|
||||
pathp, err := syscall.UTF16PtrFromString(fileName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fd, err := syscall.CreateFile(pathp, syscall.GENERIC_READ|syscall.GENERIC_WRITE, 0, nil, syscall.CREATE_ALWAYS, syscall.FILE_ATTRIBUTE_NORMAL, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &windowsLock{fd}, nil
|
||||
}
|
||||
48
vendor/github.com/prometheus/prometheus/util/stats/query_stats.go
generated
vendored
Normal file
48
vendor/github.com/prometheus/prometheus/util/stats/query_stats.go
generated
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
// Copyright 2013 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 stats
|
||||
|
||||
// QueryTiming identifies the code area or functionality in which time is spent
|
||||
// during a query.
|
||||
type QueryTiming int
|
||||
|
||||
// Query timings.
|
||||
const (
|
||||
TotalEvalTime QueryTiming = iota
|
||||
ResultSortTime
|
||||
QueryPreparationTime
|
||||
InnerEvalTime
|
||||
ResultAppendTime
|
||||
ExecQueueTime
|
||||
)
|
||||
|
||||
// Return a string representation of a QueryTiming identifier.
|
||||
func (s QueryTiming) String() string {
|
||||
switch s {
|
||||
case TotalEvalTime:
|
||||
return "Total eval time"
|
||||
case ResultSortTime:
|
||||
return "Result sorting time"
|
||||
case QueryPreparationTime:
|
||||
return "Query preparation time"
|
||||
case InnerEvalTime:
|
||||
return "Inner eval time"
|
||||
case ResultAppendTime:
|
||||
return "Result append time"
|
||||
case ExecQueueTime:
|
||||
return "Exec queue wait time"
|
||||
default:
|
||||
return "Unknown query timing"
|
||||
}
|
||||
}
|
||||
108
vendor/github.com/prometheus/prometheus/util/stats/timer.go
generated
vendored
Normal file
108
vendor/github.com/prometheus/prometheus/util/stats/timer.go
generated
vendored
Normal file
@@ -0,0 +1,108 @@
|
||||
// Copyright 2013 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 stats
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"sort"
|
||||
"time"
|
||||
)
|
||||
|
||||
// A Timer that can be started and stopped and accumulates the total time it
|
||||
// was running (the time between Start() and Stop()).
|
||||
type Timer struct {
|
||||
name fmt.Stringer
|
||||
created time.Time
|
||||
start time.Time
|
||||
duration time.Duration
|
||||
}
|
||||
|
||||
// Start the timer.
|
||||
func (t *Timer) Start() *Timer {
|
||||
t.start = time.Now()
|
||||
return t
|
||||
}
|
||||
|
||||
// Stop the timer.
|
||||
func (t *Timer) Stop() {
|
||||
t.duration += time.Since(t.start)
|
||||
}
|
||||
|
||||
// ElapsedTime returns the time that passed since starting the timer.
|
||||
func (t *Timer) ElapsedTime() time.Duration {
|
||||
return time.Since(t.start)
|
||||
}
|
||||
|
||||
// Return a string representation of the Timer.
|
||||
func (t *Timer) String() string {
|
||||
return fmt.Sprintf("%s: %s", t.name, t.duration)
|
||||
}
|
||||
|
||||
// A TimerGroup represents a group of timers relevant to a single query.
|
||||
type TimerGroup struct {
|
||||
timers map[fmt.Stringer]*Timer
|
||||
}
|
||||
|
||||
// NewTimerGroup constructs a new TimerGroup.
|
||||
func NewTimerGroup() *TimerGroup {
|
||||
return &TimerGroup{timers: map[fmt.Stringer]*Timer{}}
|
||||
}
|
||||
|
||||
// GetTimer gets (and creates, if necessary) the Timer for a given code section.
|
||||
func (t *TimerGroup) GetTimer(name fmt.Stringer) *Timer {
|
||||
if timer, exists := t.timers[name]; exists {
|
||||
return timer
|
||||
}
|
||||
timer := &Timer{
|
||||
name: name,
|
||||
created: time.Now(),
|
||||
}
|
||||
t.timers[name] = timer
|
||||
return timer
|
||||
}
|
||||
|
||||
// Timers is a slice of Timer pointers that implements Len and Swap from
|
||||
// sort.Interface.
|
||||
type Timers []*Timer
|
||||
|
||||
type byCreationTimeSorter struct{ Timers }
|
||||
|
||||
// Len implements sort.Interface.
|
||||
func (t Timers) Len() int {
|
||||
return len(t)
|
||||
}
|
||||
|
||||
// Swap implements sort.Interface.
|
||||
func (t Timers) Swap(i, j int) {
|
||||
t[i], t[j] = t[j], t[i]
|
||||
}
|
||||
|
||||
func (s byCreationTimeSorter) Less(i, j int) bool {
|
||||
return s.Timers[i].created.Before(s.Timers[j].created)
|
||||
}
|
||||
|
||||
// Return a string representation of a TimerGroup.
|
||||
func (t *TimerGroup) String() string {
|
||||
timers := byCreationTimeSorter{}
|
||||
for _, timer := range t.timers {
|
||||
timers.Timers = append(timers.Timers, timer)
|
||||
}
|
||||
sort.Sort(timers)
|
||||
result := &bytes.Buffer{}
|
||||
for _, timer := range timers.Timers {
|
||||
fmt.Fprintf(result, "%s\n", timer)
|
||||
}
|
||||
return result.String()
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user