mirror of
https://github.com/prometheus/prometheus.git
synced 2024-11-16 10:34:06 -08:00
4acbb7dea6
* add custom bounds to chunks encoding * change custom buckets schema number * rename custom bounds to custom values Signed-off-by: Jeanette Tan <jeanette.tan@grafana.com>
276 lines
7.8 KiB
Go
276 lines
7.8 KiB
Go
// Copyright 2017 The Prometheus Authors
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
// The code in this file was largely written by Damian Gryski as part of
|
|
// https://github.com/dgryski/go-tsz and published under the license below.
|
|
// It received minor modifications to suit Prometheus's needs.
|
|
|
|
// Copyright (c) 2015,2016 Damian Gryski <damian@gryski.com>
|
|
// 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.
|
|
//
|
|
// 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 HOLDER 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.
|
|
|
|
package chunkenc
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"io"
|
|
)
|
|
|
|
// bstream is a stream of bits.
|
|
type bstream struct {
|
|
stream []byte // The data stream.
|
|
count uint8 // How many right-most bits are available for writing in the current byte (the last byte of the stream).
|
|
}
|
|
|
|
func (b *bstream) bytes() []byte {
|
|
return b.stream
|
|
}
|
|
|
|
type bit bool
|
|
|
|
const (
|
|
zero bit = false
|
|
one bit = true
|
|
)
|
|
|
|
func (b *bstream) writeBit(bit bit) {
|
|
if b.count == 0 {
|
|
b.stream = append(b.stream, 0)
|
|
b.count = 8
|
|
}
|
|
|
|
i := len(b.stream) - 1
|
|
|
|
if bit {
|
|
b.stream[i] |= 1 << (b.count - 1)
|
|
}
|
|
|
|
b.count--
|
|
}
|
|
|
|
func (b *bstream) writeByte(byt byte) {
|
|
if b.count == 0 {
|
|
b.stream = append(b.stream, 0)
|
|
b.count = 8
|
|
}
|
|
|
|
i := len(b.stream) - 1
|
|
|
|
// Complete the last byte with the leftmost b.count bits from byt.
|
|
b.stream[i] |= byt >> (8 - b.count)
|
|
|
|
b.stream = append(b.stream, 0)
|
|
i++
|
|
// Write the remainder, if any.
|
|
b.stream[i] = byt << b.count
|
|
}
|
|
|
|
// writeBits writes the nbits right-most bits of u to the stream
|
|
// in left-to-right order.
|
|
func (b *bstream) writeBits(u uint64, nbits int) {
|
|
u <<= 64 - uint(nbits)
|
|
for nbits >= 8 {
|
|
byt := byte(u >> 56)
|
|
b.writeByte(byt)
|
|
u <<= 8
|
|
nbits -= 8
|
|
}
|
|
|
|
for nbits > 0 {
|
|
b.writeBit((u >> 63) == 1)
|
|
u <<= 1
|
|
nbits--
|
|
}
|
|
}
|
|
|
|
// wrapper for the standard library's PutUvarint to make it work
|
|
// with our bstream.
|
|
func (b *bstream) putUvarint(x uint64) {
|
|
buf := make([]byte, 2)
|
|
l := binary.PutUvarint(buf, x)
|
|
for i := 0; i < l; i++ {
|
|
b.writeByte(buf[i])
|
|
}
|
|
}
|
|
|
|
type bstreamReader struct {
|
|
stream []byte
|
|
streamOffset int // The offset from which read the next byte from the stream.
|
|
|
|
buffer uint64 // The current buffer, filled from the stream, containing up to 8 bytes from which read bits.
|
|
valid uint8 // The number of right-most bits valid to read (from left) in the current 8 byte buffer.
|
|
last byte // A copy of the last byte of the stream.
|
|
}
|
|
|
|
func newBReader(b []byte) bstreamReader {
|
|
// The last byte of the stream can be updated later, so we take a copy.
|
|
var last byte
|
|
if len(b) > 0 {
|
|
last = b[len(b)-1]
|
|
}
|
|
return bstreamReader{
|
|
stream: b,
|
|
last: last,
|
|
}
|
|
}
|
|
|
|
func (b *bstreamReader) readBit() (bit, error) {
|
|
if b.valid == 0 {
|
|
if !b.loadNextBuffer(1) {
|
|
return false, io.EOF
|
|
}
|
|
}
|
|
|
|
return b.readBitFast()
|
|
}
|
|
|
|
// readBitFast is like readBit but can return io.EOF if the internal buffer is empty.
|
|
// If it returns io.EOF, the caller should retry reading bits calling readBit().
|
|
// This function must be kept small and a leaf in order to help the compiler inlining it
|
|
// and further improve performances.
|
|
func (b *bstreamReader) readBitFast() (bit, error) {
|
|
if b.valid == 0 {
|
|
return false, io.EOF
|
|
}
|
|
|
|
b.valid--
|
|
bitmask := uint64(1) << b.valid
|
|
return (b.buffer & bitmask) != 0, nil
|
|
}
|
|
|
|
// readBits constructs a uint64 with the nbits right-most bits
|
|
// read from the stream, and any other bits 0.
|
|
func (b *bstreamReader) readBits(nbits uint8) (uint64, error) {
|
|
if b.valid == 0 {
|
|
if !b.loadNextBuffer(nbits) {
|
|
return 0, io.EOF
|
|
}
|
|
}
|
|
|
|
if nbits <= b.valid {
|
|
return b.readBitsFast(nbits)
|
|
}
|
|
|
|
// We have to read all remaining valid bits from the current buffer and a part from the next one.
|
|
bitmask := (uint64(1) << b.valid) - 1
|
|
nbits -= b.valid
|
|
v := (b.buffer & bitmask) << nbits
|
|
b.valid = 0
|
|
|
|
if !b.loadNextBuffer(nbits) {
|
|
return 0, io.EOF
|
|
}
|
|
|
|
bitmask = (uint64(1) << nbits) - 1
|
|
v |= ((b.buffer >> (b.valid - nbits)) & bitmask)
|
|
b.valid -= nbits
|
|
|
|
return v, nil
|
|
}
|
|
|
|
// readBitsFast is like readBits but can return io.EOF if the internal buffer is empty.
|
|
// If it returns io.EOF, the caller should retry reading bits calling readBits().
|
|
// This function must be kept small and a leaf in order to help the compiler inlining it
|
|
// and further improve performances.
|
|
func (b *bstreamReader) readBitsFast(nbits uint8) (uint64, error) {
|
|
if nbits > b.valid {
|
|
return 0, io.EOF
|
|
}
|
|
|
|
bitmask := (uint64(1) << nbits) - 1
|
|
b.valid -= nbits
|
|
|
|
return (b.buffer >> b.valid) & bitmask, nil
|
|
}
|
|
|
|
func (b *bstreamReader) ReadByte() (byte, error) {
|
|
v, err := b.readBits(8)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return byte(v), nil
|
|
}
|
|
|
|
// loadNextBuffer loads the next bytes from the stream into the internal buffer.
|
|
// The input nbits is the minimum number of bits that must be read, but the implementation
|
|
// can read more (if possible) to improve performances.
|
|
func (b *bstreamReader) loadNextBuffer(nbits uint8) bool {
|
|
if b.streamOffset >= len(b.stream) {
|
|
return false
|
|
}
|
|
|
|
// Handle the case there are more then 8 bytes in the buffer (most common case)
|
|
// in a optimized way. It's guaranteed that this branch will never read from the
|
|
// very last byte of the stream (which suffers race conditions due to concurrent
|
|
// writes).
|
|
if b.streamOffset+8 < len(b.stream) {
|
|
b.buffer = binary.BigEndian.Uint64(b.stream[b.streamOffset:])
|
|
b.streamOffset += 8
|
|
b.valid = 64
|
|
return true
|
|
}
|
|
|
|
// We're here if there are 8 or less bytes left in the stream.
|
|
// The following code is slower but called less frequently.
|
|
nbytes := int((nbits / 8) + 1)
|
|
if b.streamOffset+nbytes > len(b.stream) {
|
|
nbytes = len(b.stream) - b.streamOffset
|
|
}
|
|
|
|
buffer := uint64(0)
|
|
skip := 0
|
|
if b.streamOffset+nbytes == len(b.stream) {
|
|
// There can be concurrent writes happening on the very last byte
|
|
// of the stream, so use the copy we took at initialization time.
|
|
buffer |= uint64(b.last)
|
|
// Read up to the byte before
|
|
skip = 1
|
|
}
|
|
|
|
for i := 0; i < nbytes-skip; i++ {
|
|
buffer |= (uint64(b.stream[b.streamOffset+i]) << uint(8*(nbytes-i-1)))
|
|
}
|
|
|
|
b.buffer = buffer
|
|
b.streamOffset += nbytes
|
|
b.valid = uint8(nbytes * 8)
|
|
|
|
return true
|
|
}
|
|
|
|
// wrapper for the standard library's ReadUvarint to make it work
|
|
// with our bstream.
|
|
func (b *bstreamReader) readUvarint() (uint64, error) {
|
|
return binary.ReadUvarint(b)
|
|
}
|