mirror of https://github.com/etcd-io/dbtester.git
commit
4e7ef31729
|
|
@ -20,8 +20,8 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/coreos/dbtester/dbtesterpb"
|
"github.com/coreos/dbtester/dbtesterpb"
|
||||||
"github.com/coreos/dbtester/pkg/netutil"
|
|
||||||
"github.com/coreos/dbtester/pkg/ntp"
|
"github.com/coreos/dbtester/pkg/ntp"
|
||||||
|
"github.com/coreos/etcd/pkg/netutil"
|
||||||
"github.com/coreos/pkg/capnslog"
|
"github.com/coreos/pkg/capnslog"
|
||||||
"github.com/gyuho/psn"
|
"github.com/gyuho/psn"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,8 @@ import (
|
||||||
|
|
||||||
"github.com/coreos/dbtester"
|
"github.com/coreos/dbtester"
|
||||||
"github.com/coreos/dbtester/dbtesterpb"
|
"github.com/coreos/dbtester/dbtesterpb"
|
||||||
"github.com/coreos/dbtester/pkg/netutil"
|
|
||||||
"github.com/coreos/dbtester/pkg/ntp"
|
"github.com/coreos/dbtester/pkg/ntp"
|
||||||
|
"github.com/coreos/etcd/pkg/netutil"
|
||||||
"github.com/gyuho/psn"
|
"github.com/gyuho/psn"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ import (
|
||||||
"sort"
|
"sort"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/coreos/dbtester/pkg/report"
|
"github.com/coreos/etcd/pkg/report"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CumulativeKeyNumToAvgLatency wraps the cumulative number of keys
|
// CumulativeKeyNumToAvgLatency wraps the cumulative number of keys
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/coreos/dbtester/pkg/report"
|
"github.com/coreos/etcd/pkg/report"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestFindRangesLatency(t *testing.T) {
|
func TestFindRangesLatency(t *testing.T) {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
hash: 6bd04d9644a95f1caa2c1df864c296078eadff2a349eb724fbc0486aa5a2a72f
|
hash: d814a9bd4d998e7b38f52abf3bf6284b4ce1b9e0d1bbb3aecfcedb9dcbf9de16
|
||||||
updated: 2017-02-13T13:49:21.228470942-08:00
|
updated: 2017-02-15T11:01:56.215783528-08:00
|
||||||
imports:
|
imports:
|
||||||
- name: bitbucket.org/zombiezen/gopdf
|
- name: bitbucket.org/zombiezen/gopdf
|
||||||
version: 1c63dc69751bc45441c2ce1f56b631c55294b4d5
|
version: 1c63dc69751bc45441c2ce1f56b631c55294b4d5
|
||||||
|
|
@ -14,10 +14,6 @@ imports:
|
||||||
- storage
|
- storage
|
||||||
- name: github.com/ajstarks/svgo
|
- name: github.com/ajstarks/svgo
|
||||||
version: 9f597be83ea4fd9bbc17dfc950ced53b9dbc7f19
|
version: 9f597be83ea4fd9bbc17dfc950ced53b9dbc7f19
|
||||||
- name: github.com/beorn7/perks
|
|
||||||
version: 4c0e84591b9aa9e6dcfdf3e020114cd81f89d5f9
|
|
||||||
subpackages:
|
|
||||||
- quantile
|
|
||||||
- name: github.com/biogo/graphics
|
- name: github.com/biogo/graphics
|
||||||
version: ee9e4a4ad8d5c6c55688ec8da3f96f8bd3f09b85
|
version: ee9e4a4ad8d5c6c55688ec8da3f96f8bd3f09b85
|
||||||
subpackages:
|
subpackages:
|
||||||
|
|
@ -25,7 +21,7 @@ imports:
|
||||||
- name: github.com/cheggaaa/pb
|
- name: github.com/cheggaaa/pb
|
||||||
version: d7e6ca3010b6f084d8056847f55d7f572f180678
|
version: d7e6ca3010b6f084d8056847f55d7f572f180678
|
||||||
- name: github.com/coreos/etcd
|
- name: github.com/coreos/etcd
|
||||||
version: bad2f03cd027c8cee60df2f55713946b86a36659
|
version: 2510a1488c51eb503c396c7e4e44ee910e4857a6
|
||||||
subpackages:
|
subpackages:
|
||||||
- auth/authpb
|
- auth/authpb
|
||||||
- client
|
- client
|
||||||
|
|
@ -33,11 +29,18 @@ imports:
|
||||||
- etcdserver/api/v3rpc/rpctypes
|
- etcdserver/api/v3rpc/rpctypes
|
||||||
- etcdserver/etcdserverpb
|
- etcdserver/etcdserverpb
|
||||||
- mvcc/mvccpb
|
- mvcc/mvccpb
|
||||||
|
- pkg/cpuutil
|
||||||
- pkg/netutil
|
- pkg/netutil
|
||||||
- pkg/pathutil
|
- pkg/pathutil
|
||||||
- pkg/report
|
- pkg/report
|
||||||
|
- pkg/report
|
||||||
- pkg/tlsutil
|
- pkg/tlsutil
|
||||||
- pkg/types
|
- pkg/types
|
||||||
|
- version
|
||||||
|
- name: github.com/coreos/go-semver
|
||||||
|
version: 568e959cd89871e61434c1143528d9162da89ef2
|
||||||
|
subpackages:
|
||||||
|
- semver
|
||||||
- name: github.com/coreos/go-systemd
|
- name: github.com/coreos/go-systemd
|
||||||
version: 48702e0da86bd25e76cfef347e2adeb434a0d0a6
|
version: 48702e0da86bd25e76cfef347e2adeb434a0d0a6
|
||||||
subpackages:
|
subpackages:
|
||||||
|
|
@ -50,8 +53,6 @@ imports:
|
||||||
- capnslog
|
- capnslog
|
||||||
- name: github.com/dustin/go-humanize
|
- name: github.com/dustin/go-humanize
|
||||||
version: ef638b6c2e62b857442c6443dace9366a48c0ee2
|
version: ef638b6c2e62b857442c6443dace9366a48c0ee2
|
||||||
- name: github.com/ghodss/yaml
|
|
||||||
version: 73d445a93680fa1a78ae23a5839bad48f32ba1ee
|
|
||||||
- name: github.com/gogo/protobuf
|
- name: github.com/gogo/protobuf
|
||||||
version: 8d70fb3182befc465c4a1eac8ad4d38ff49778e2
|
version: 8d70fb3182befc465c4a1eac8ad4d38ff49778e2
|
||||||
subpackages:
|
subpackages:
|
||||||
|
|
@ -89,8 +90,6 @@ imports:
|
||||||
- vg/vgsvg
|
- vg/vgsvg
|
||||||
- name: github.com/googleapis/gax-go
|
- name: github.com/googleapis/gax-go
|
||||||
version: da06d194a00e19ce00d9011a13931c3f6f6887c7
|
version: da06d194a00e19ce00d9011a13931c3f6f6887c7
|
||||||
- name: github.com/grpc-ecosystem/go-grpc-prometheus
|
|
||||||
version: 6b7015e65d366bf3f19b2b2a000a831940f0f7e0
|
|
||||||
- name: github.com/grpc-ecosystem/grpc-gateway
|
- name: github.com/grpc-ecosystem/grpc-gateway
|
||||||
version: 84398b94e188ee336f307779b57b3aa91af7063c
|
version: 84398b94e188ee336f307779b57b3aa91af7063c
|
||||||
subpackages:
|
subpackages:
|
||||||
|
|
@ -124,28 +123,8 @@ imports:
|
||||||
- draw2dimg
|
- draw2dimg
|
||||||
- name: github.com/mattn/go-runewidth
|
- name: github.com/mattn/go-runewidth
|
||||||
version: 14207d285c6c197daabb5c9793d63e7af9ab2d50
|
version: 14207d285c6c197daabb5c9793d63e7af9ab2d50
|
||||||
- name: github.com/matttproud/golang_protobuf_extensions
|
|
||||||
version: c12348ce28de40eed0136aa2b644d0ee0650e56c
|
|
||||||
subpackages:
|
|
||||||
- pbutil
|
|
||||||
- name: github.com/olekukonko/tablewriter
|
- name: github.com/olekukonko/tablewriter
|
||||||
version: febf2d34b54a69ce7530036c7503b1c9fbfdf0bb
|
version: febf2d34b54a69ce7530036c7503b1c9fbfdf0bb
|
||||||
- name: github.com/prometheus/client_golang
|
|
||||||
version: c5b7fccd204277076155f10851dad72b76a49317
|
|
||||||
subpackages:
|
|
||||||
- prometheus
|
|
||||||
- name: github.com/prometheus/client_model
|
|
||||||
version: fa8ad6fec33561be4280a8f0514318c79d7f6cb6
|
|
||||||
subpackages:
|
|
||||||
- go
|
|
||||||
- name: github.com/prometheus/common
|
|
||||||
version: dd2f054febf4a6c00f2343686efb775948a8bff4
|
|
||||||
subpackages:
|
|
||||||
- expfmt
|
|
||||||
- internal/bitbucket.org/ww/goautoneg
|
|
||||||
- model
|
|
||||||
- name: github.com/prometheus/procfs
|
|
||||||
version: 1878d9fbb537119d24b21ca07effd591627cd160
|
|
||||||
- name: github.com/samuel/go-zookeeper
|
- name: github.com/samuel/go-zookeeper
|
||||||
version: 1d7be4effb13d2d908342d349d71a284a7542693
|
version: 1d7be4effb13d2d908342d349d71a284a7542693
|
||||||
subpackages:
|
subpackages:
|
||||||
|
|
@ -159,7 +138,7 @@ imports:
|
||||||
subpackages:
|
subpackages:
|
||||||
- codec
|
- codec
|
||||||
- name: golang.org/x/image
|
- name: golang.org/x/image
|
||||||
version: df2aa51d4407e30b309afc8c4d6fdca045d97fb0
|
version: 306b8294319cca33073246c5f75e5142f54f4cca
|
||||||
subpackages:
|
subpackages:
|
||||||
- draw
|
- draw
|
||||||
- font
|
- font
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import:
|
||||||
- package: github.com/cheggaaa/pb
|
- package: github.com/cheggaaa/pb
|
||||||
version: d7e6ca3010b6f084d8056847f55d7f572f180678
|
version: d7e6ca3010b6f084d8056847f55d7f572f180678
|
||||||
- package: github.com/coreos/etcd
|
- package: github.com/coreos/etcd
|
||||||
version: bad2f03cd027c8cee60df2f55713946b86a36659
|
version: 2510a1488c51eb503c396c7e4e44ee910e4857a6
|
||||||
subpackages:
|
subpackages:
|
||||||
- auth/authpb
|
- auth/authpb
|
||||||
- client
|
- client
|
||||||
|
|
@ -22,6 +22,7 @@ import:
|
||||||
- pkg/tlsutil
|
- pkg/tlsutil
|
||||||
- pkg/netutil
|
- pkg/netutil
|
||||||
- pkg/types
|
- pkg/types
|
||||||
|
- pkg/report
|
||||||
- package: github.com/coreos/pkg
|
- package: github.com/coreos/pkg
|
||||||
version: fa29b1d70f0beaddd4c7021607cc3c3be8ce94b8
|
version: fa29b1d70f0beaddd4c7021607cc3c3be8ce94b8
|
||||||
subpackages:
|
subpackages:
|
||||||
|
|
|
||||||
|
|
@ -1,263 +0,0 @@
|
||||||
// Copyright 2015 The etcd 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 netutil
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"net"
|
|
||||||
"net/url"
|
|
||||||
"reflect"
|
|
||||||
"strconv"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestResolveTCPAddrs(t *testing.T) {
|
|
||||||
defer func() { resolveTCPAddr = net.ResolveTCPAddr }()
|
|
||||||
tests := []struct {
|
|
||||||
urls [][]url.URL
|
|
||||||
expected [][]url.URL
|
|
||||||
hostMap map[string]string
|
|
||||||
hasError bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
urls: [][]url.URL{
|
|
||||||
{
|
|
||||||
{Scheme: "http", Host: "127.0.0.1:4001"},
|
|
||||||
{Scheme: "http", Host: "127.0.0.1:2379"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
{Scheme: "http", Host: "127.0.0.1:7001"},
|
|
||||||
{Scheme: "http", Host: "127.0.0.1:2380"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expected: [][]url.URL{
|
|
||||||
{
|
|
||||||
{Scheme: "http", Host: "127.0.0.1:4001"},
|
|
||||||
{Scheme: "http", Host: "127.0.0.1:2379"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
{Scheme: "http", Host: "127.0.0.1:7001"},
|
|
||||||
{Scheme: "http", Host: "127.0.0.1:2380"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
urls: [][]url.URL{
|
|
||||||
{
|
|
||||||
{Scheme: "http", Host: "infra0.example.com:4001"},
|
|
||||||
{Scheme: "http", Host: "infra0.example.com:2379"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
{Scheme: "http", Host: "infra0.example.com:7001"},
|
|
||||||
{Scheme: "http", Host: "infra0.example.com:2380"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expected: [][]url.URL{
|
|
||||||
{
|
|
||||||
{Scheme: "http", Host: "10.0.1.10:4001"},
|
|
||||||
{Scheme: "http", Host: "10.0.1.10:2379"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
{Scheme: "http", Host: "10.0.1.10:7001"},
|
|
||||||
{Scheme: "http", Host: "10.0.1.10:2380"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
hostMap: map[string]string{
|
|
||||||
"infra0.example.com": "10.0.1.10",
|
|
||||||
},
|
|
||||||
hasError: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
urls: [][]url.URL{
|
|
||||||
{
|
|
||||||
{Scheme: "http", Host: "infra0.example.com:4001"},
|
|
||||||
{Scheme: "http", Host: "infra0.example.com:2379"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
{Scheme: "http", Host: "infra0.example.com:7001"},
|
|
||||||
{Scheme: "http", Host: "infra0.example.com:2380"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
hostMap: map[string]string{
|
|
||||||
"infra0.example.com": "",
|
|
||||||
},
|
|
||||||
hasError: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
urls: [][]url.URL{
|
|
||||||
{
|
|
||||||
{Scheme: "http", Host: "ssh://infra0.example.com:4001"},
|
|
||||||
{Scheme: "http", Host: "ssh://infra0.example.com:2379"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
{Scheme: "http", Host: "ssh://infra0.example.com:7001"},
|
|
||||||
{Scheme: "http", Host: "ssh://infra0.example.com:2380"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
hasError: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
resolveTCPAddr = func(network, addr string) (*net.TCPAddr, error) {
|
|
||||||
host, port, err := net.SplitHostPort(addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if tt.hostMap[host] == "" {
|
|
||||||
return nil, errors.New("cannot resolve host.")
|
|
||||||
}
|
|
||||||
i, err := strconv.Atoi(port)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &net.TCPAddr{IP: net.ParseIP(tt.hostMap[host]), Port: i, Zone: ""}, nil
|
|
||||||
}
|
|
||||||
ctx, cancel := context.WithTimeout(context.TODO(), time.Second)
|
|
||||||
urls, err := resolveTCPAddrs(ctx, tt.urls)
|
|
||||||
cancel()
|
|
||||||
if tt.hasError {
|
|
||||||
if err == nil {
|
|
||||||
t.Errorf("expected error")
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(urls, tt.expected) {
|
|
||||||
t.Errorf("expected: %v, got %v", tt.expected, urls)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestURLsEqual(t *testing.T) {
|
|
||||||
defer func() { resolveTCPAddr = net.ResolveTCPAddr }()
|
|
||||||
hostm := map[string]string{
|
|
||||||
"example.com": "10.0.10.1",
|
|
||||||
"first.com": "10.0.11.1",
|
|
||||||
"second.com": "10.0.11.2",
|
|
||||||
}
|
|
||||||
resolveTCPAddr = func(network, addr string) (*net.TCPAddr, error) {
|
|
||||||
host, port, err := net.SplitHostPort(addr)
|
|
||||||
if _, ok := hostm[host]; !ok {
|
|
||||||
return nil, errors.New("cannot resolve host.")
|
|
||||||
}
|
|
||||||
i, err := strconv.Atoi(port)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &net.TCPAddr{IP: net.ParseIP(hostm[host]), Port: i, Zone: ""}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
a []url.URL
|
|
||||||
b []url.URL
|
|
||||||
expect bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
a: []url.URL{{Scheme: "http", Host: "127.0.0.1:2379"}},
|
|
||||||
b: []url.URL{{Scheme: "http", Host: "127.0.0.1:2379"}},
|
|
||||||
expect: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
a: []url.URL{{Scheme: "http", Host: "example.com:2379"}},
|
|
||||||
b: []url.URL{{Scheme: "http", Host: "10.0.10.1:2379"}},
|
|
||||||
expect: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
a: []url.URL{{Scheme: "http", Host: "127.0.0.1:2379"}, {Scheme: "http", Host: "127.0.0.1:2380"}},
|
|
||||||
b: []url.URL{{Scheme: "http", Host: "127.0.0.1:2379"}, {Scheme: "http", Host: "127.0.0.1:2380"}},
|
|
||||||
expect: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
a: []url.URL{{Scheme: "http", Host: "example.com:2379"}, {Scheme: "http", Host: "127.0.0.1:2380"}},
|
|
||||||
b: []url.URL{{Scheme: "http", Host: "example.com:2379"}, {Scheme: "http", Host: "127.0.0.1:2380"}},
|
|
||||||
expect: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
a: []url.URL{{Scheme: "http", Host: "10.0.10.1:2379"}, {Scheme: "http", Host: "127.0.0.1:2380"}},
|
|
||||||
b: []url.URL{{Scheme: "http", Host: "example.com:2379"}, {Scheme: "http", Host: "127.0.0.1:2380"}},
|
|
||||||
expect: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
a: []url.URL{{Scheme: "http", Host: "127.0.0.1:2379"}},
|
|
||||||
b: []url.URL{{Scheme: "http", Host: "127.0.0.1:2380"}},
|
|
||||||
expect: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
a: []url.URL{{Scheme: "http", Host: "example.com:2380"}},
|
|
||||||
b: []url.URL{{Scheme: "http", Host: "10.0.10.1:2379"}},
|
|
||||||
expect: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
a: []url.URL{{Scheme: "http", Host: "127.0.0.1:2379"}},
|
|
||||||
b: []url.URL{{Scheme: "http", Host: "10.0.0.1:2379"}},
|
|
||||||
expect: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
a: []url.URL{{Scheme: "http", Host: "example.com:2379"}},
|
|
||||||
b: []url.URL{{Scheme: "http", Host: "10.0.0.1:2379"}},
|
|
||||||
expect: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
a: []url.URL{{Scheme: "http", Host: "127.0.0.1:2379"}, {Scheme: "http", Host: "127.0.0.1:2380"}},
|
|
||||||
b: []url.URL{{Scheme: "http", Host: "127.0.0.1:2380"}, {Scheme: "http", Host: "127.0.0.1:2380"}},
|
|
||||||
expect: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
a: []url.URL{{Scheme: "http", Host: "example.com:2379"}, {Scheme: "http", Host: "127.0.0.1:2380"}},
|
|
||||||
b: []url.URL{{Scheme: "http", Host: "127.0.0.1:2380"}, {Scheme: "http", Host: "127.0.0.1:2380"}},
|
|
||||||
expect: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
a: []url.URL{{Scheme: "http", Host: "127.0.0.1:2379"}, {Scheme: "http", Host: "127.0.0.1:2380"}},
|
|
||||||
b: []url.URL{{Scheme: "http", Host: "10.0.0.1:2379"}, {Scheme: "http", Host: "127.0.0.1:2380"}},
|
|
||||||
expect: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
a: []url.URL{{Scheme: "http", Host: "example.com:2379"}, {Scheme: "http", Host: "127.0.0.1:2380"}},
|
|
||||||
b: []url.URL{{Scheme: "http", Host: "10.0.0.1:2379"}, {Scheme: "http", Host: "127.0.0.1:2380"}},
|
|
||||||
expect: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
a: []url.URL{{Scheme: "http", Host: "10.0.0.1:2379"}},
|
|
||||||
b: []url.URL{{Scheme: "http", Host: "10.0.0.1:2379"}, {Scheme: "http", Host: "127.0.0.1:2380"}},
|
|
||||||
expect: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
a: []url.URL{{Scheme: "http", Host: "first.com:2379"}, {Scheme: "http", Host: "second.com:2380"}},
|
|
||||||
b: []url.URL{{Scheme: "http", Host: "10.0.11.1:2379"}, {Scheme: "http", Host: "10.0.11.2:2380"}},
|
|
||||||
expect: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
a: []url.URL{{Scheme: "http", Host: "second.com:2380"}, {Scheme: "http", Host: "first.com:2379"}},
|
|
||||||
b: []url.URL{{Scheme: "http", Host: "10.0.11.1:2379"}, {Scheme: "http", Host: "10.0.11.2:2380"}},
|
|
||||||
expect: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range tests {
|
|
||||||
result := urlsEqual(context.TODO(), test.a, test.b)
|
|
||||||
if result != test.expect {
|
|
||||||
t.Errorf("a:%v b:%v, expected %v but %v", test.a, test.b, test.expect, result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func TestURLStringsEqual(t *testing.T) {
|
|
||||||
result := URLStringsEqual(context.TODO(), []string{"http://127.0.0.1:8080"}, []string{"http://127.0.0.1:8080"})
|
|
||||||
if !result {
|
|
||||||
t.Errorf("unexpected result %v", result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
// Copyright 2016 The etcd Authors
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
// +build linux
|
|
||||||
|
|
||||||
package netutil
|
|
||||||
|
|
||||||
import "testing"
|
|
||||||
|
|
||||||
func TestGetDefaultInterface(t *testing.T) {
|
|
||||||
ifc, err := GetDefaultInterfaces()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
t.Logf("default network interfaces: %+v\n", ifc)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetDefaultHost(t *testing.T) {
|
|
||||||
ip, err := GetDefaultHost()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
t.Logf("default ip: %v", ip)
|
|
||||||
}
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
// Copyright 2017 The etcd 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 report
|
|
||||||
|
|
||||||
import "testing"
|
|
||||||
|
|
||||||
func TestPercentiles(t *testing.T) {
|
|
||||||
nums := make([]float64, 100)
|
|
||||||
nums[99] = 1 // 99-percentile (1 out of 100)
|
|
||||||
data := percentiles(nums)
|
|
||||||
if data[len(pctls)-2] != 1 {
|
|
||||||
t.Fatalf("99-percentile expected 1, got %f", data[len(pctls)-2])
|
|
||||||
}
|
|
||||||
|
|
||||||
nums = make([]float64, 1000)
|
|
||||||
nums[999] = 1 // 99.9-percentile (1 out of 1000)
|
|
||||||
data = percentiles(nums)
|
|
||||||
if data[len(pctls)-1] != 1 {
|
|
||||||
t.Fatalf("99.9-percentile expected 1, got %f", data[len(pctls)-1])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
// Copyright 2016 The etcd 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 report
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestGetTimeseries(t *testing.T) {
|
|
||||||
sp := newSecondPoints()
|
|
||||||
now := time.Now()
|
|
||||||
sp.Add(now, time.Second)
|
|
||||||
sp.Add(now.Add(5*time.Second), time.Second)
|
|
||||||
n := sp.getTimeSeries().Len()
|
|
||||||
if n < 3 {
|
|
||||||
t.Fatalf("expected at 6 points of time series, got %s", sp.getTimeSeries())
|
|
||||||
}
|
|
||||||
|
|
||||||
// add a point with duplicate timestamp
|
|
||||||
sp.Add(now, 3*time.Second)
|
|
||||||
ts := sp.getTimeSeries()
|
|
||||||
if ts[0].MinLatency != time.Second {
|
|
||||||
t.Fatalf("ts[0] min latency expected %v, got %s", time.Second, ts[0].MinLatency)
|
|
||||||
}
|
|
||||||
if ts[0].AvgLatency != 2*time.Second {
|
|
||||||
t.Fatalf("ts[0] average latency expected %v, got %s", 2*time.Second, ts[0].AvgLatency)
|
|
||||||
}
|
|
||||||
if ts[0].MaxLatency != 3*time.Second {
|
|
||||||
t.Fatalf("ts[0] max latency expected %v, got %s", 3*time.Second, ts[0].MaxLatency)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
// Copyright 2015 The etcd 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 types declares various data types and implements type-checking
|
|
||||||
// functions.
|
|
||||||
package types
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
// Copyright 2015 The etcd 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 types
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ID represents a generic identifier which is canonically
|
|
||||||
// stored as a uint64 but is typically represented as a
|
|
||||||
// base-16 string for input/output
|
|
||||||
type ID uint64
|
|
||||||
|
|
||||||
func (i ID) String() string {
|
|
||||||
return strconv.FormatUint(uint64(i), 16)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IDFromString attempts to create an ID from a base-16 string.
|
|
||||||
func IDFromString(s string) (ID, error) {
|
|
||||||
i, err := strconv.ParseUint(s, 16, 64)
|
|
||||||
return ID(i), err
|
|
||||||
}
|
|
||||||
|
|
||||||
// IDSlice implements the sort interface
|
|
||||||
type IDSlice []ID
|
|
||||||
|
|
||||||
func (p IDSlice) Len() int { return len(p) }
|
|
||||||
func (p IDSlice) Less(i, j int) bool { return uint64(p[i]) < uint64(p[j]) }
|
|
||||||
func (p IDSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
|
||||||
|
|
@ -1,95 +0,0 @@
|
||||||
// Copyright 2015 The etcd 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 types
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"sort"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestIDString(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
input ID
|
|
||||||
want string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
input: 12,
|
|
||||||
want: "c",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: 4918257920282737594,
|
|
||||||
want: "444129853c343bba",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, tt := range tests {
|
|
||||||
got := tt.input.String()
|
|
||||||
if tt.want != got {
|
|
||||||
t.Errorf("#%d: ID.String failure: want=%v, got=%v", i, tt.want, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIDFromString(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
input string
|
|
||||||
want ID
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
input: "17",
|
|
||||||
want: 23,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: "612840dae127353",
|
|
||||||
want: 437557308098245459,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, tt := range tests {
|
|
||||||
got, err := IDFromString(tt.input)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("#%d: IDFromString failure: err=%v", i, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if tt.want != got {
|
|
||||||
t.Errorf("#%d: IDFromString failure: want=%v, got=%v", i, tt.want, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIDFromStringFail(t *testing.T) {
|
|
||||||
tests := []string{
|
|
||||||
"",
|
|
||||||
"XXX",
|
|
||||||
"612840dae127353612840dae127353",
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, tt := range tests {
|
|
||||||
_, err := IDFromString(tt)
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("#%d: IDFromString expected error, but err=nil", i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIDSlice(t *testing.T) {
|
|
||||||
g := []ID{10, 500, 5, 1, 100, 25}
|
|
||||||
w := []ID{1, 5, 10, 25, 100, 500}
|
|
||||||
sort.Sort(IDSlice(g))
|
|
||||||
if !reflect.DeepEqual(g, w) {
|
|
||||||
t.Errorf("slice after sort = %#v, want %#v", g, w)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
178
pkg/types/set.go
178
pkg/types/set.go
|
|
@ -1,178 +0,0 @@
|
||||||
// Copyright 2015 The etcd 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 types
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"sort"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Set interface {
|
|
||||||
Add(string)
|
|
||||||
Remove(string)
|
|
||||||
Contains(string) bool
|
|
||||||
Equals(Set) bool
|
|
||||||
Length() int
|
|
||||||
Values() []string
|
|
||||||
Copy() Set
|
|
||||||
Sub(Set) Set
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewUnsafeSet(values ...string) *unsafeSet {
|
|
||||||
set := &unsafeSet{make(map[string]struct{})}
|
|
||||||
for _, v := range values {
|
|
||||||
set.Add(v)
|
|
||||||
}
|
|
||||||
return set
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewThreadsafeSet(values ...string) *tsafeSet {
|
|
||||||
us := NewUnsafeSet(values...)
|
|
||||||
return &tsafeSet{us, sync.RWMutex{}}
|
|
||||||
}
|
|
||||||
|
|
||||||
type unsafeSet struct {
|
|
||||||
d map[string]struct{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add adds a new value to the set (no-op if the value is already present)
|
|
||||||
func (us *unsafeSet) Add(value string) {
|
|
||||||
us.d[value] = struct{}{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove removes the given value from the set
|
|
||||||
func (us *unsafeSet) Remove(value string) {
|
|
||||||
delete(us.d, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Contains returns whether the set contains the given value
|
|
||||||
func (us *unsafeSet) Contains(value string) (exists bool) {
|
|
||||||
_, exists = us.d[value]
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// ContainsAll returns whether the set contains all given values
|
|
||||||
func (us *unsafeSet) ContainsAll(values []string) bool {
|
|
||||||
for _, s := range values {
|
|
||||||
if !us.Contains(s) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Equals returns whether the contents of two sets are identical
|
|
||||||
func (us *unsafeSet) Equals(other Set) bool {
|
|
||||||
v1 := sort.StringSlice(us.Values())
|
|
||||||
v2 := sort.StringSlice(other.Values())
|
|
||||||
v1.Sort()
|
|
||||||
v2.Sort()
|
|
||||||
return reflect.DeepEqual(v1, v2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Length returns the number of elements in the set
|
|
||||||
func (us *unsafeSet) Length() int {
|
|
||||||
return len(us.d)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Values returns the values of the Set in an unspecified order.
|
|
||||||
func (us *unsafeSet) Values() (values []string) {
|
|
||||||
values = make([]string, 0)
|
|
||||||
for val := range us.d {
|
|
||||||
values = append(values, val)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy creates a new Set containing the values of the first
|
|
||||||
func (us *unsafeSet) Copy() Set {
|
|
||||||
cp := NewUnsafeSet()
|
|
||||||
for val := range us.d {
|
|
||||||
cp.Add(val)
|
|
||||||
}
|
|
||||||
|
|
||||||
return cp
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sub removes all elements in other from the set
|
|
||||||
func (us *unsafeSet) Sub(other Set) Set {
|
|
||||||
oValues := other.Values()
|
|
||||||
result := us.Copy().(*unsafeSet)
|
|
||||||
|
|
||||||
for _, val := range oValues {
|
|
||||||
if _, ok := result.d[val]; !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
delete(result.d, val)
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
type tsafeSet struct {
|
|
||||||
us *unsafeSet
|
|
||||||
m sync.RWMutex
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ts *tsafeSet) Add(value string) {
|
|
||||||
ts.m.Lock()
|
|
||||||
defer ts.m.Unlock()
|
|
||||||
ts.us.Add(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ts *tsafeSet) Remove(value string) {
|
|
||||||
ts.m.Lock()
|
|
||||||
defer ts.m.Unlock()
|
|
||||||
ts.us.Remove(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ts *tsafeSet) Contains(value string) (exists bool) {
|
|
||||||
ts.m.RLock()
|
|
||||||
defer ts.m.RUnlock()
|
|
||||||
return ts.us.Contains(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ts *tsafeSet) Equals(other Set) bool {
|
|
||||||
ts.m.RLock()
|
|
||||||
defer ts.m.RUnlock()
|
|
||||||
return ts.us.Equals(other)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ts *tsafeSet) Length() int {
|
|
||||||
ts.m.RLock()
|
|
||||||
defer ts.m.RUnlock()
|
|
||||||
return ts.us.Length()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ts *tsafeSet) Values() (values []string) {
|
|
||||||
ts.m.RLock()
|
|
||||||
defer ts.m.RUnlock()
|
|
||||||
return ts.us.Values()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ts *tsafeSet) Copy() Set {
|
|
||||||
ts.m.RLock()
|
|
||||||
defer ts.m.RUnlock()
|
|
||||||
usResult := ts.us.Copy().(*unsafeSet)
|
|
||||||
return &tsafeSet{usResult, sync.RWMutex{}}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ts *tsafeSet) Sub(other Set) Set {
|
|
||||||
ts.m.RLock()
|
|
||||||
defer ts.m.RUnlock()
|
|
||||||
usResult := ts.us.Sub(other).(*unsafeSet)
|
|
||||||
return &tsafeSet{usResult, sync.RWMutex{}}
|
|
||||||
}
|
|
||||||
|
|
@ -1,186 +0,0 @@
|
||||||
// Copyright 2015 The etcd 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 types
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"sort"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestUnsafeSet(t *testing.T) {
|
|
||||||
driveSetTests(t, NewUnsafeSet())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestThreadsafeSet(t *testing.T) {
|
|
||||||
driveSetTests(t, NewThreadsafeSet())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check that two slices contents are equal; order is irrelevant
|
|
||||||
func equal(a, b []string) bool {
|
|
||||||
as := sort.StringSlice(a)
|
|
||||||
bs := sort.StringSlice(b)
|
|
||||||
as.Sort()
|
|
||||||
bs.Sort()
|
|
||||||
return reflect.DeepEqual(as, bs)
|
|
||||||
}
|
|
||||||
|
|
||||||
func driveSetTests(t *testing.T, s Set) {
|
|
||||||
// Verify operations on an empty set
|
|
||||||
eValues := []string{}
|
|
||||||
values := s.Values()
|
|
||||||
if !reflect.DeepEqual(values, eValues) {
|
|
||||||
t.Fatalf("Expect values=%v got %v", eValues, values)
|
|
||||||
}
|
|
||||||
if l := s.Length(); l != 0 {
|
|
||||||
t.Fatalf("Expected length=0, got %d", l)
|
|
||||||
}
|
|
||||||
for _, v := range []string{"foo", "bar", "baz"} {
|
|
||||||
if s.Contains(v) {
|
|
||||||
t.Fatalf("Expect s.Contains(%q) to be fale, got true", v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add three items, ensure they show up
|
|
||||||
s.Add("foo")
|
|
||||||
s.Add("bar")
|
|
||||||
s.Add("baz")
|
|
||||||
|
|
||||||
eValues = []string{"foo", "bar", "baz"}
|
|
||||||
values = s.Values()
|
|
||||||
if !equal(values, eValues) {
|
|
||||||
t.Fatalf("Expect values=%v got %v", eValues, values)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, v := range eValues {
|
|
||||||
if !s.Contains(v) {
|
|
||||||
t.Fatalf("Expect s.Contains(%q) to be true, got false", v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if l := s.Length(); l != 3 {
|
|
||||||
t.Fatalf("Expected length=3, got %d", l)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the same item a second time, ensuring it is not duplicated
|
|
||||||
s.Add("foo")
|
|
||||||
|
|
||||||
values = s.Values()
|
|
||||||
if !equal(values, eValues) {
|
|
||||||
t.Fatalf("Expect values=%v got %v", eValues, values)
|
|
||||||
}
|
|
||||||
if l := s.Length(); l != 3 {
|
|
||||||
t.Fatalf("Expected length=3, got %d", l)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove all items, ensure they are gone
|
|
||||||
s.Remove("foo")
|
|
||||||
s.Remove("bar")
|
|
||||||
s.Remove("baz")
|
|
||||||
|
|
||||||
eValues = []string{}
|
|
||||||
values = s.Values()
|
|
||||||
if !equal(values, eValues) {
|
|
||||||
t.Fatalf("Expect values=%v got %v", eValues, values)
|
|
||||||
}
|
|
||||||
|
|
||||||
if l := s.Length(); l != 0 {
|
|
||||||
t.Fatalf("Expected length=0, got %d", l)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create new copies of the set, and ensure they are unlinked to the
|
|
||||||
// original Set by making modifications
|
|
||||||
s.Add("foo")
|
|
||||||
s.Add("bar")
|
|
||||||
cp1 := s.Copy()
|
|
||||||
cp2 := s.Copy()
|
|
||||||
s.Remove("foo")
|
|
||||||
cp3 := s.Copy()
|
|
||||||
cp1.Add("baz")
|
|
||||||
|
|
||||||
for i, tt := range []struct {
|
|
||||||
want []string
|
|
||||||
got []string
|
|
||||||
}{
|
|
||||||
{[]string{"bar"}, s.Values()},
|
|
||||||
{[]string{"foo", "bar", "baz"}, cp1.Values()},
|
|
||||||
{[]string{"foo", "bar"}, cp2.Values()},
|
|
||||||
{[]string{"bar"}, cp3.Values()},
|
|
||||||
} {
|
|
||||||
if !equal(tt.want, tt.got) {
|
|
||||||
t.Fatalf("case %d: expect values=%v got %v", i, tt.want, tt.got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, tt := range []struct {
|
|
||||||
want bool
|
|
||||||
got bool
|
|
||||||
}{
|
|
||||||
{true, s.Equals(cp3)},
|
|
||||||
{true, cp3.Equals(s)},
|
|
||||||
{false, s.Equals(cp2)},
|
|
||||||
{false, s.Equals(cp1)},
|
|
||||||
{false, cp1.Equals(s)},
|
|
||||||
{false, cp2.Equals(s)},
|
|
||||||
{false, cp2.Equals(cp1)},
|
|
||||||
} {
|
|
||||||
if tt.got != tt.want {
|
|
||||||
t.Fatalf("case %d: want %t, got %t", i, tt.want, tt.got)
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Subtract values from a Set, ensuring a new Set is created and
|
|
||||||
// the original Sets are unmodified
|
|
||||||
sub1 := cp1.Sub(s)
|
|
||||||
sub2 := cp2.Sub(cp1)
|
|
||||||
|
|
||||||
for i, tt := range []struct {
|
|
||||||
want []string
|
|
||||||
got []string
|
|
||||||
}{
|
|
||||||
{[]string{"foo", "bar", "baz"}, cp1.Values()},
|
|
||||||
{[]string{"foo", "bar"}, cp2.Values()},
|
|
||||||
{[]string{"bar"}, s.Values()},
|
|
||||||
{[]string{"foo", "baz"}, sub1.Values()},
|
|
||||||
{[]string{}, sub2.Values()},
|
|
||||||
} {
|
|
||||||
if !equal(tt.want, tt.got) {
|
|
||||||
t.Fatalf("case %d: expect values=%v got %v", i, tt.want, tt.got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUnsafeSetContainsAll(t *testing.T) {
|
|
||||||
vals := []string{"foo", "bar", "baz"}
|
|
||||||
s := NewUnsafeSet(vals...)
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
strs []string
|
|
||||||
wcontain bool
|
|
||||||
}{
|
|
||||||
{[]string{}, true},
|
|
||||||
{vals[:1], true},
|
|
||||||
{vals[:2], true},
|
|
||||||
{vals, true},
|
|
||||||
{[]string{"cuz"}, false},
|
|
||||||
{[]string{vals[0], "cuz"}, false},
|
|
||||||
}
|
|
||||||
for i, tt := range tests {
|
|
||||||
if g := s.ContainsAll(tt.strs); g != tt.wcontain {
|
|
||||||
t.Errorf("#%d: ok = %v, want %v", i, g, tt.wcontain)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
// Copyright 2015 The etcd 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 types
|
|
||||||
|
|
||||||
// Uint64Slice implements sort interface
|
|
||||||
type Uint64Slice []uint64
|
|
||||||
|
|
||||||
func (p Uint64Slice) Len() int { return len(p) }
|
|
||||||
func (p Uint64Slice) Less(i, j int) bool { return p[i] < p[j] }
|
|
||||||
func (p Uint64Slice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
// Copyright 2015 The etcd 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 types
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"sort"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestUint64Slice(t *testing.T) {
|
|
||||||
g := Uint64Slice{10, 500, 5, 1, 100, 25}
|
|
||||||
w := Uint64Slice{1, 5, 10, 25, 100, 500}
|
|
||||||
sort.Sort(g)
|
|
||||||
if !reflect.DeepEqual(g, w) {
|
|
||||||
t.Errorf("slice after sort = %#v, want %#v", g, w)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,82 +0,0 @@
|
||||||
// Copyright 2015 The etcd 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 types
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"net/url"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type URLs []url.URL
|
|
||||||
|
|
||||||
func NewURLs(strs []string) (URLs, error) {
|
|
||||||
all := make([]url.URL, len(strs))
|
|
||||||
if len(all) == 0 {
|
|
||||||
return nil, errors.New("no valid URLs given")
|
|
||||||
}
|
|
||||||
for i, in := range strs {
|
|
||||||
in = strings.TrimSpace(in)
|
|
||||||
u, err := url.Parse(in)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if u.Scheme != "http" && u.Scheme != "https" && u.Scheme != "unix" && u.Scheme != "unixs" {
|
|
||||||
return nil, fmt.Errorf("URL scheme must be http, https, unix, or unixs: %s", in)
|
|
||||||
}
|
|
||||||
if _, _, err := net.SplitHostPort(u.Host); err != nil {
|
|
||||||
return nil, fmt.Errorf(`URL address does not have the form "host:port": %s`, in)
|
|
||||||
}
|
|
||||||
if u.Path != "" {
|
|
||||||
return nil, fmt.Errorf("URL must not contain a path: %s", in)
|
|
||||||
}
|
|
||||||
all[i] = *u
|
|
||||||
}
|
|
||||||
us := URLs(all)
|
|
||||||
us.Sort()
|
|
||||||
|
|
||||||
return us, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func MustNewURLs(strs []string) URLs {
|
|
||||||
urls, err := NewURLs(strs)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return urls
|
|
||||||
}
|
|
||||||
|
|
||||||
func (us URLs) String() string {
|
|
||||||
return strings.Join(us.StringSlice(), ",")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (us *URLs) Sort() {
|
|
||||||
sort.Sort(us)
|
|
||||||
}
|
|
||||||
func (us URLs) Len() int { return len(us) }
|
|
||||||
func (us URLs) Less(i, j int) bool { return us[i].String() < us[j].String() }
|
|
||||||
func (us URLs) Swap(i, j int) { us[i], us[j] = us[j], us[i] }
|
|
||||||
|
|
||||||
func (us URLs) StringSlice() []string {
|
|
||||||
out := make([]string, len(us))
|
|
||||||
for i := range us {
|
|
||||||
out[i] = us[i].String()
|
|
||||||
}
|
|
||||||
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
@ -1,107 +0,0 @@
|
||||||
// Copyright 2015 The etcd 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 types
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// URLsMap is a map from a name to its URLs.
|
|
||||||
type URLsMap map[string]URLs
|
|
||||||
|
|
||||||
// NewURLsMap returns a URLsMap instantiated from the given string,
|
|
||||||
// which consists of discovery-formatted names-to-URLs, like:
|
|
||||||
// mach0=http://1.1.1.1:2380,mach0=http://2.2.2.2::2380,mach1=http://3.3.3.3:2380,mach2=http://4.4.4.4:2380
|
|
||||||
func NewURLsMap(s string) (URLsMap, error) {
|
|
||||||
m := parse(s)
|
|
||||||
|
|
||||||
cl := URLsMap{}
|
|
||||||
for name, urls := range m {
|
|
||||||
us, err := NewURLs(urls)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
cl[name] = us
|
|
||||||
}
|
|
||||||
return cl, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewURLsMapFromStringMap takes a map of strings and returns a URLsMap. The
|
|
||||||
// string values in the map can be multiple values separated by the sep string.
|
|
||||||
func NewURLsMapFromStringMap(m map[string]string, sep string) (URLsMap, error) {
|
|
||||||
var err error
|
|
||||||
um := URLsMap{}
|
|
||||||
for k, v := range m {
|
|
||||||
um[k], err = NewURLs(strings.Split(v, sep))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return um, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// String turns URLsMap into discovery-formatted name-to-URLs sorted by name.
|
|
||||||
func (c URLsMap) String() string {
|
|
||||||
var pairs []string
|
|
||||||
for name, urls := range c {
|
|
||||||
for _, url := range urls {
|
|
||||||
pairs = append(pairs, fmt.Sprintf("%s=%s", name, url.String()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sort.Strings(pairs)
|
|
||||||
return strings.Join(pairs, ",")
|
|
||||||
}
|
|
||||||
|
|
||||||
// URLs returns a list of all URLs.
|
|
||||||
// The returned list is sorted in ascending lexicographical order.
|
|
||||||
func (c URLsMap) URLs() []string {
|
|
||||||
var urls []string
|
|
||||||
for _, us := range c {
|
|
||||||
for _, u := range us {
|
|
||||||
urls = append(urls, u.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sort.Strings(urls)
|
|
||||||
return urls
|
|
||||||
}
|
|
||||||
|
|
||||||
// Len returns the size of URLsMap.
|
|
||||||
func (c URLsMap) Len() int {
|
|
||||||
return len(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse parses the given string and returns a map listing the values specified for each key.
|
|
||||||
func parse(s string) map[string][]string {
|
|
||||||
m := make(map[string][]string)
|
|
||||||
for s != "" {
|
|
||||||
key := s
|
|
||||||
if i := strings.IndexAny(key, ","); i >= 0 {
|
|
||||||
key, s = key[:i], key[i+1:]
|
|
||||||
} else {
|
|
||||||
s = ""
|
|
||||||
}
|
|
||||||
if key == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
value := ""
|
|
||||||
if i := strings.Index(key, "="); i >= 0 {
|
|
||||||
key, value = key[:i], key[i+1:]
|
|
||||||
}
|
|
||||||
m[key] = append(m[key], value)
|
|
||||||
}
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
@ -20,7 +20,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/cheggaaa/pb"
|
"github.com/cheggaaa/pb"
|
||||||
"github.com/coreos/dbtester/pkg/report"
|
"github.com/coreos/etcd/pkg/report"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ import (
|
||||||
|
|
||||||
"github.com/coreos/dbtester/dbtesterpb"
|
"github.com/coreos/dbtester/dbtesterpb"
|
||||||
"github.com/coreos/dbtester/pkg/remotestorage"
|
"github.com/coreos/dbtester/pkg/remotestorage"
|
||||||
"github.com/coreos/dbtester/pkg/report"
|
"github.com/coreos/etcd/pkg/report"
|
||||||
humanize "github.com/dustin/go-humanize"
|
humanize "github.com/dustin/go-humanize"
|
||||||
"github.com/gyuho/dataframe"
|
"github.com/gyuho/dataframe"
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ if ! [[ "$0" =~ "scripts/tests.sh" ]]; then
|
||||||
exit 255
|
exit 255
|
||||||
fi
|
fi
|
||||||
gofmt -l -s -d *.go
|
gofmt -l -s -d *.go
|
||||||
TESTS="./analyze ./pkg/fileinspect ./pkg/netutil ./pkg/ntp ./pkg/report ./pkg/types"
|
TESTS="./analyze ./pkg/fileinspect ./pkg/ntp"
|
||||||
|
|
||||||
echo "Checking gofmt..."
|
echo "Checking gofmt..."
|
||||||
fmtRes=$(gofmt -l -s -d $TESTS)
|
fmtRes=$(gofmt -l -s -d $TESTS)
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,8 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/coreos/dbtester/dbtesterpb"
|
"github.com/coreos/dbtester/dbtesterpb"
|
||||||
"github.com/coreos/dbtester/pkg/report"
|
|
||||||
"github.com/coreos/etcd/clientv3"
|
"github.com/coreos/etcd/clientv3"
|
||||||
|
"github.com/coreos/etcd/pkg/report"
|
||||||
consulapi "github.com/hashicorp/consul/api"
|
consulapi "github.com/hashicorp/consul/api"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
"golang.org/x/time/rate"
|
"golang.org/x/time/rate"
|
||||||
|
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 99 KiB |
|
|
@ -1,20 +0,0 @@
|
||||||
Copyright (C) 2013 Blake Mizerany
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
a copy of this software and associated documentation files (the
|
|
||||||
"Software"), to deal in the Software without restriction, including
|
|
||||||
without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be
|
|
||||||
included in all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
||||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
||||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
||||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
|
|
@ -1,292 +0,0 @@
|
||||||
// Package quantile computes approximate quantiles over an unbounded data
|
|
||||||
// stream within low memory and CPU bounds.
|
|
||||||
//
|
|
||||||
// A small amount of accuracy is traded to achieve the above properties.
|
|
||||||
//
|
|
||||||
// Multiple streams can be merged before calling Query to generate a single set
|
|
||||||
// of results. This is meaningful when the streams represent the same type of
|
|
||||||
// data. See Merge and Samples.
|
|
||||||
//
|
|
||||||
// For more detailed information about the algorithm used, see:
|
|
||||||
//
|
|
||||||
// Effective Computation of Biased Quantiles over Data Streams
|
|
||||||
//
|
|
||||||
// http://www.cs.rutgers.edu/~muthu/bquant.pdf
|
|
||||||
package quantile
|
|
||||||
|
|
||||||
import (
|
|
||||||
"math"
|
|
||||||
"sort"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Sample holds an observed value and meta information for compression. JSON
|
|
||||||
// tags have been added for convenience.
|
|
||||||
type Sample struct {
|
|
||||||
Value float64 `json:",string"`
|
|
||||||
Width float64 `json:",string"`
|
|
||||||
Delta float64 `json:",string"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Samples represents a slice of samples. It implements sort.Interface.
|
|
||||||
type Samples []Sample
|
|
||||||
|
|
||||||
func (a Samples) Len() int { return len(a) }
|
|
||||||
func (a Samples) Less(i, j int) bool { return a[i].Value < a[j].Value }
|
|
||||||
func (a Samples) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
|
||||||
|
|
||||||
type invariant func(s *stream, r float64) float64
|
|
||||||
|
|
||||||
// NewLowBiased returns an initialized Stream for low-biased quantiles
|
|
||||||
// (e.g. 0.01, 0.1, 0.5) where the needed quantiles are not known a priori, but
|
|
||||||
// error guarantees can still be given even for the lower ranks of the data
|
|
||||||
// distribution.
|
|
||||||
//
|
|
||||||
// The provided epsilon is a relative error, i.e. the true quantile of a value
|
|
||||||
// returned by a query is guaranteed to be within (1±Epsilon)*Quantile.
|
|
||||||
//
|
|
||||||
// See http://www.cs.rutgers.edu/~muthu/bquant.pdf for time, space, and error
|
|
||||||
// properties.
|
|
||||||
func NewLowBiased(epsilon float64) *Stream {
|
|
||||||
ƒ := func(s *stream, r float64) float64 {
|
|
||||||
return 2 * epsilon * r
|
|
||||||
}
|
|
||||||
return newStream(ƒ)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewHighBiased returns an initialized Stream for high-biased quantiles
|
|
||||||
// (e.g. 0.01, 0.1, 0.5) where the needed quantiles are not known a priori, but
|
|
||||||
// error guarantees can still be given even for the higher ranks of the data
|
|
||||||
// distribution.
|
|
||||||
//
|
|
||||||
// The provided epsilon is a relative error, i.e. the true quantile of a value
|
|
||||||
// returned by a query is guaranteed to be within 1-(1±Epsilon)*(1-Quantile).
|
|
||||||
//
|
|
||||||
// See http://www.cs.rutgers.edu/~muthu/bquant.pdf for time, space, and error
|
|
||||||
// properties.
|
|
||||||
func NewHighBiased(epsilon float64) *Stream {
|
|
||||||
ƒ := func(s *stream, r float64) float64 {
|
|
||||||
return 2 * epsilon * (s.n - r)
|
|
||||||
}
|
|
||||||
return newStream(ƒ)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewTargeted returns an initialized Stream concerned with a particular set of
|
|
||||||
// quantile values that are supplied a priori. Knowing these a priori reduces
|
|
||||||
// space and computation time. The targets map maps the desired quantiles to
|
|
||||||
// their absolute errors, i.e. the true quantile of a value returned by a query
|
|
||||||
// is guaranteed to be within (Quantile±Epsilon).
|
|
||||||
//
|
|
||||||
// See http://www.cs.rutgers.edu/~muthu/bquant.pdf for time, space, and error properties.
|
|
||||||
func NewTargeted(targets map[float64]float64) *Stream {
|
|
||||||
ƒ := func(s *stream, r float64) float64 {
|
|
||||||
var m = math.MaxFloat64
|
|
||||||
var f float64
|
|
||||||
for quantile, epsilon := range targets {
|
|
||||||
if quantile*s.n <= r {
|
|
||||||
f = (2 * epsilon * r) / quantile
|
|
||||||
} else {
|
|
||||||
f = (2 * epsilon * (s.n - r)) / (1 - quantile)
|
|
||||||
}
|
|
||||||
if f < m {
|
|
||||||
m = f
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
return newStream(ƒ)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stream computes quantiles for a stream of float64s. It is not thread-safe by
|
|
||||||
// design. Take care when using across multiple goroutines.
|
|
||||||
type Stream struct {
|
|
||||||
*stream
|
|
||||||
b Samples
|
|
||||||
sorted bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func newStream(ƒ invariant) *Stream {
|
|
||||||
x := &stream{ƒ: ƒ}
|
|
||||||
return &Stream{x, make(Samples, 0, 500), true}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Insert inserts v into the stream.
|
|
||||||
func (s *Stream) Insert(v float64) {
|
|
||||||
s.insert(Sample{Value: v, Width: 1})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Stream) insert(sample Sample) {
|
|
||||||
s.b = append(s.b, sample)
|
|
||||||
s.sorted = false
|
|
||||||
if len(s.b) == cap(s.b) {
|
|
||||||
s.flush()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Query returns the computed qth percentiles value. If s was created with
|
|
||||||
// NewTargeted, and q is not in the set of quantiles provided a priori, Query
|
|
||||||
// will return an unspecified result.
|
|
||||||
func (s *Stream) Query(q float64) float64 {
|
|
||||||
if !s.flushed() {
|
|
||||||
// Fast path when there hasn't been enough data for a flush;
|
|
||||||
// this also yields better accuracy for small sets of data.
|
|
||||||
l := len(s.b)
|
|
||||||
if l == 0 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
i := int(math.Ceil(float64(l) * q))
|
|
||||||
if i > 0 {
|
|
||||||
i -= 1
|
|
||||||
}
|
|
||||||
s.maybeSort()
|
|
||||||
return s.b[i].Value
|
|
||||||
}
|
|
||||||
s.flush()
|
|
||||||
return s.stream.query(q)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Merge merges samples into the underlying streams samples. This is handy when
|
|
||||||
// merging multiple streams from separate threads, database shards, etc.
|
|
||||||
//
|
|
||||||
// ATTENTION: This method is broken and does not yield correct results. The
|
|
||||||
// underlying algorithm is not capable of merging streams correctly.
|
|
||||||
func (s *Stream) Merge(samples Samples) {
|
|
||||||
sort.Sort(samples)
|
|
||||||
s.stream.merge(samples)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset reinitializes and clears the list reusing the samples buffer memory.
|
|
||||||
func (s *Stream) Reset() {
|
|
||||||
s.stream.reset()
|
|
||||||
s.b = s.b[:0]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Samples returns stream samples held by s.
|
|
||||||
func (s *Stream) Samples() Samples {
|
|
||||||
if !s.flushed() {
|
|
||||||
return s.b
|
|
||||||
}
|
|
||||||
s.flush()
|
|
||||||
return s.stream.samples()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Count returns the total number of samples observed in the stream
|
|
||||||
// since initialization.
|
|
||||||
func (s *Stream) Count() int {
|
|
||||||
return len(s.b) + s.stream.count()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Stream) flush() {
|
|
||||||
s.maybeSort()
|
|
||||||
s.stream.merge(s.b)
|
|
||||||
s.b = s.b[:0]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Stream) maybeSort() {
|
|
||||||
if !s.sorted {
|
|
||||||
s.sorted = true
|
|
||||||
sort.Sort(s.b)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Stream) flushed() bool {
|
|
||||||
return len(s.stream.l) > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
type stream struct {
|
|
||||||
n float64
|
|
||||||
l []Sample
|
|
||||||
ƒ invariant
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stream) reset() {
|
|
||||||
s.l = s.l[:0]
|
|
||||||
s.n = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stream) insert(v float64) {
|
|
||||||
s.merge(Samples{{v, 1, 0}})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stream) merge(samples Samples) {
|
|
||||||
// TODO(beorn7): This tries to merge not only individual samples, but
|
|
||||||
// whole summaries. The paper doesn't mention merging summaries at
|
|
||||||
// all. Unittests show that the merging is inaccurate. Find out how to
|
|
||||||
// do merges properly.
|
|
||||||
var r float64
|
|
||||||
i := 0
|
|
||||||
for _, sample := range samples {
|
|
||||||
for ; i < len(s.l); i++ {
|
|
||||||
c := s.l[i]
|
|
||||||
if c.Value > sample.Value {
|
|
||||||
// Insert at position i.
|
|
||||||
s.l = append(s.l, Sample{})
|
|
||||||
copy(s.l[i+1:], s.l[i:])
|
|
||||||
s.l[i] = Sample{
|
|
||||||
sample.Value,
|
|
||||||
sample.Width,
|
|
||||||
math.Max(sample.Delta, math.Floor(s.ƒ(s, r))-1),
|
|
||||||
// TODO(beorn7): How to calculate delta correctly?
|
|
||||||
}
|
|
||||||
i++
|
|
||||||
goto inserted
|
|
||||||
}
|
|
||||||
r += c.Width
|
|
||||||
}
|
|
||||||
s.l = append(s.l, Sample{sample.Value, sample.Width, 0})
|
|
||||||
i++
|
|
||||||
inserted:
|
|
||||||
s.n += sample.Width
|
|
||||||
r += sample.Width
|
|
||||||
}
|
|
||||||
s.compress()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stream) count() int {
|
|
||||||
return int(s.n)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stream) query(q float64) float64 {
|
|
||||||
t := math.Ceil(q * s.n)
|
|
||||||
t += math.Ceil(s.ƒ(s, t) / 2)
|
|
||||||
p := s.l[0]
|
|
||||||
var r float64
|
|
||||||
for _, c := range s.l[1:] {
|
|
||||||
r += p.Width
|
|
||||||
if r+c.Width+c.Delta > t {
|
|
||||||
return p.Value
|
|
||||||
}
|
|
||||||
p = c
|
|
||||||
}
|
|
||||||
return p.Value
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stream) compress() {
|
|
||||||
if len(s.l) < 2 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
x := s.l[len(s.l)-1]
|
|
||||||
xi := len(s.l) - 1
|
|
||||||
r := s.n - 1 - x.Width
|
|
||||||
|
|
||||||
for i := len(s.l) - 2; i >= 0; i-- {
|
|
||||||
c := s.l[i]
|
|
||||||
if c.Width+x.Width+x.Delta <= s.ƒ(s, r) {
|
|
||||||
x.Width += c.Width
|
|
||||||
s.l[xi] = x
|
|
||||||
// Remove element at i.
|
|
||||||
copy(s.l[i:], s.l[i+1:])
|
|
||||||
s.l = s.l[:len(s.l)-1]
|
|
||||||
xi -= 1
|
|
||||||
} else {
|
|
||||||
x = c
|
|
||||||
xi = i
|
|
||||||
}
|
|
||||||
r -= c.Width
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stream) samples() Samples {
|
|
||||||
samples := make(Samples, len(s.l))
|
|
||||||
copy(samples, s.l)
|
|
||||||
return samples
|
|
||||||
}
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
|
@ -27,6 +28,8 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/coreos/etcd/version"
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -201,6 +204,9 @@ type Client interface {
|
||||||
// returned
|
// returned
|
||||||
SetEndpoints(eps []string) error
|
SetEndpoints(eps []string) error
|
||||||
|
|
||||||
|
// GetVersion retrieves the current etcd server and cluster version
|
||||||
|
GetVersion(ctx context.Context) (*version.Versions, error)
|
||||||
|
|
||||||
httpClient
|
httpClient
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -477,6 +483,33 @@ func (c *httpClusterClient) AutoSync(ctx context.Context, interval time.Duration
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *httpClusterClient) GetVersion(ctx context.Context) (*version.Versions, error) {
|
||||||
|
act := &getAction{Prefix: "/version"}
|
||||||
|
|
||||||
|
resp, body, err := c.Do(ctx, act)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch resp.StatusCode {
|
||||||
|
case http.StatusOK:
|
||||||
|
if len(body) == 0 {
|
||||||
|
return nil, ErrEmptyBody
|
||||||
|
}
|
||||||
|
var vresp version.Versions
|
||||||
|
if err := json.Unmarshal(body, &vresp); err != nil {
|
||||||
|
return nil, ErrInvalidJSON
|
||||||
|
}
|
||||||
|
return &vresp, nil
|
||||||
|
default:
|
||||||
|
var etcdErr Error
|
||||||
|
if err := json.Unmarshal(body, &etcdErr); err != nil {
|
||||||
|
return nil, ErrInvalidJSON
|
||||||
|
}
|
||||||
|
return nil, etcdErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type roundTripResponse struct {
|
type roundTripResponse struct {
|
||||||
resp *http.Response
|
resp *http.Response
|
||||||
err error
|
err error
|
||||||
|
|
|
||||||
|
|
@ -43,8 +43,7 @@ type simpleBalancer struct {
|
||||||
|
|
||||||
// mu protects upEps, pinAddr, and connectingAddr
|
// mu protects upEps, pinAddr, and connectingAddr
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
// upEps holds the current endpoints that have an active connection
|
|
||||||
upEps map[string]struct{}
|
|
||||||
// upc closes when upEps transitions from empty to non-zero or the balancer closes.
|
// upc closes when upEps transitions from empty to non-zero or the balancer closes.
|
||||||
upc chan struct{}
|
upc chan struct{}
|
||||||
|
|
||||||
|
|
@ -71,7 +70,6 @@ func newSimpleBalancer(eps []string) *simpleBalancer {
|
||||||
addrs: addrs,
|
addrs: addrs,
|
||||||
notifyCh: notifyCh,
|
notifyCh: notifyCh,
|
||||||
readyc: make(chan struct{}),
|
readyc: make(chan struct{}),
|
||||||
upEps: make(map[string]struct{}),
|
|
||||||
upc: make(chan struct{}),
|
upc: make(chan struct{}),
|
||||||
host2ep: getHost2ep(eps),
|
host2ep: getHost2ep(eps),
|
||||||
}
|
}
|
||||||
|
|
@ -126,8 +124,12 @@ func (b *simpleBalancer) updateAddrs(eps []string) {
|
||||||
addrs = append(addrs, grpc.Address{Addr: getHost(eps[i])})
|
addrs = append(addrs, grpc.Address{Addr: getHost(eps[i])})
|
||||||
}
|
}
|
||||||
b.addrs = addrs
|
b.addrs = addrs
|
||||||
|
// updating notifyCh can trigger new connections,
|
||||||
|
// but balancer only expects new connections if all connections are down
|
||||||
|
if b.pinAddr == "" {
|
||||||
b.notifyCh <- addrs
|
b.notifyCh <- addrs
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (b *simpleBalancer) Up(addr grpc.Address) func(error) {
|
func (b *simpleBalancer) Up(addr grpc.Address) func(error) {
|
||||||
b.mu.Lock()
|
b.mu.Lock()
|
||||||
|
|
@ -140,48 +142,45 @@ func (b *simpleBalancer) Up(addr grpc.Address) func(error) {
|
||||||
return func(err error) {}
|
return func(err error) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(b.upEps) == 0 {
|
if b.pinAddr == "" {
|
||||||
// notify waiting Get()s and pin first connected address
|
// notify waiting Get()s and pin first connected address
|
||||||
close(b.upc)
|
close(b.upc)
|
||||||
b.pinAddr = addr.Addr
|
b.pinAddr = addr.Addr
|
||||||
}
|
|
||||||
b.upEps[addr.Addr] = struct{}{}
|
|
||||||
|
|
||||||
// notify client that a connection is up
|
// notify client that a connection is up
|
||||||
b.readyOnce.Do(func() { close(b.readyc) })
|
b.readyOnce.Do(func() { close(b.readyc) })
|
||||||
|
// close opened connections that are not pinAddr
|
||||||
|
// this ensures only one connection is open per client
|
||||||
|
b.notifyCh <- []grpc.Address{addr}
|
||||||
|
}
|
||||||
|
|
||||||
return func(err error) {
|
return func(err error) {
|
||||||
b.mu.Lock()
|
b.mu.Lock()
|
||||||
delete(b.upEps, addr.Addr)
|
if b.pinAddr == addr.Addr {
|
||||||
if len(b.upEps) == 0 && b.pinAddr != "" {
|
|
||||||
b.upc = make(chan struct{})
|
b.upc = make(chan struct{})
|
||||||
} else if b.pinAddr == addr.Addr {
|
b.pinAddr = ""
|
||||||
// choose new random up endpoint
|
b.notifyCh <- b.addrs
|
||||||
for k := range b.upEps {
|
|
||||||
b.pinAddr = k
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
b.mu.Unlock()
|
b.mu.Unlock()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *simpleBalancer) Get(ctx context.Context, opts grpc.BalancerGetOptions) (grpc.Address, func(), error) {
|
func (b *simpleBalancer) Get(ctx context.Context, opts grpc.BalancerGetOptions) (grpc.Address, func(), error) {
|
||||||
var addr string
|
var (
|
||||||
|
addr string
|
||||||
|
closed bool
|
||||||
|
)
|
||||||
|
|
||||||
// If opts.BlockingWait is false (for fail-fast RPCs), it should return
|
// If opts.BlockingWait is false (for fail-fast RPCs), it should return
|
||||||
// an address it has notified via Notify immediately instead of blocking.
|
// an address it has notified via Notify immediately instead of blocking.
|
||||||
if !opts.BlockingWait {
|
if !opts.BlockingWait {
|
||||||
b.mu.RLock()
|
b.mu.RLock()
|
||||||
closed := b.closed
|
closed = b.closed
|
||||||
addr = b.pinAddr
|
addr = b.pinAddr
|
||||||
upEps := len(b.upEps)
|
|
||||||
b.mu.RUnlock()
|
b.mu.RUnlock()
|
||||||
if closed {
|
if closed {
|
||||||
return grpc.Address{Addr: ""}, nil, grpc.ErrClientConnClosing
|
return grpc.Address{Addr: ""}, nil, grpc.ErrClientConnClosing
|
||||||
}
|
}
|
||||||
|
if addr == "" {
|
||||||
if upEps == 0 {
|
|
||||||
return grpc.Address{Addr: ""}, nil, ErrNoAddrAvilable
|
return grpc.Address{Addr: ""}, nil, ErrNoAddrAvilable
|
||||||
}
|
}
|
||||||
return grpc.Address{Addr: addr}, func() {}, nil
|
return grpc.Address{Addr: addr}, func() {}, nil
|
||||||
|
|
@ -197,13 +196,14 @@ func (b *simpleBalancer) Get(ctx context.Context, opts grpc.BalancerGetOptions)
|
||||||
return grpc.Address{Addr: ""}, nil, ctx.Err()
|
return grpc.Address{Addr: ""}, nil, ctx.Err()
|
||||||
}
|
}
|
||||||
b.mu.RLock()
|
b.mu.RLock()
|
||||||
|
closed = b.closed
|
||||||
addr = b.pinAddr
|
addr = b.pinAddr
|
||||||
upEps := len(b.upEps)
|
|
||||||
b.mu.RUnlock()
|
b.mu.RUnlock()
|
||||||
if addr == "" {
|
// Close() which sets b.closed = true can be called before Get(), Get() must exit if balancer is closed.
|
||||||
|
if closed {
|
||||||
return grpc.Address{Addr: ""}, nil, grpc.ErrClientConnClosing
|
return grpc.Address{Addr: ""}, nil, grpc.ErrClientConnClosing
|
||||||
}
|
}
|
||||||
if upEps > 0 {
|
if addr != "" {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -222,9 +222,18 @@ func (b *simpleBalancer) Close() error {
|
||||||
}
|
}
|
||||||
b.closed = true
|
b.closed = true
|
||||||
close(b.notifyCh)
|
close(b.notifyCh)
|
||||||
// terminate all waiting Get()s
|
|
||||||
b.pinAddr = ""
|
b.pinAddr = ""
|
||||||
if len(b.upEps) == 0 {
|
|
||||||
|
// In the case of following scenario:
|
||||||
|
// 1. upc is not closed; no pinned address
|
||||||
|
// 2. client issues an rpc, calling invoke(), which calls Get(), enters for loop, blocks
|
||||||
|
// 3. clientconn.Close() calls balancer.Close(); closed = true
|
||||||
|
// 4. for loop in Get() never exits since ctx is the context passed in by the client and may not be canceled
|
||||||
|
// we must close upc so Get() exits from blocking on upc
|
||||||
|
select {
|
||||||
|
case <-b.upc:
|
||||||
|
default:
|
||||||
|
// terminate all waiting Get()s
|
||||||
close(b.upc)
|
close(b.upc)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,6 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes"
|
"github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes"
|
||||||
prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
|
|
@ -79,15 +78,6 @@ func NewFromURL(url string) (*Client, error) {
|
||||||
return New(Config{Endpoints: []string{url}})
|
return New(Config{Endpoints: []string{url}})
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFromConfigFile creates a new etcdv3 client from a configuration file.
|
|
||||||
func NewFromConfigFile(path string) (*Client, error) {
|
|
||||||
cfg, err := configFromFile(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return New(*cfg)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close shuts down the client's etcd connections.
|
// Close shuts down the client's etcd connections.
|
||||||
func (c *Client) Close() error {
|
func (c *Client) Close() error {
|
||||||
c.cancel()
|
c.cancel()
|
||||||
|
|
@ -221,7 +211,8 @@ func (c *Client) dialSetupOpts(endpoint string, dopts ...grpc.DialOption) (opts
|
||||||
return nil, c.ctx.Err()
|
return nil, c.ctx.Err()
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
return net.DialTimeout(proto, host, t)
|
dialer := &net.Dialer{Timeout: t}
|
||||||
|
return dialer.DialContext(c.ctx, proto, host)
|
||||||
}
|
}
|
||||||
opts = append(opts, grpc.WithDialer(f))
|
opts = append(opts, grpc.WithDialer(f))
|
||||||
|
|
||||||
|
|
@ -289,9 +280,7 @@ func (c *Client) dial(endpoint string, dopts ...grpc.DialOption) (*grpc.ClientCo
|
||||||
opts = append(opts, grpc.WithPerRPCCredentials(c.tokenCred))
|
opts = append(opts, grpc.WithPerRPCCredentials(c.tokenCred))
|
||||||
}
|
}
|
||||||
|
|
||||||
// add metrics options
|
opts = append(opts, c.cfg.DialOptions...)
|
||||||
opts = append(opts, grpc.WithUnaryInterceptor(prometheus.UnaryClientInterceptor))
|
|
||||||
opts = append(opts, grpc.WithStreamInterceptor(prometheus.StreamClientInterceptor))
|
|
||||||
|
|
||||||
conn, err := grpc.Dial(host, opts...)
|
conn, err := grpc.Dial(host, opts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -16,98 +16,31 @@ package clientv3
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
|
||||||
"io/ioutil"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/coreos/etcd/pkg/tlsutil"
|
"google.golang.org/grpc"
|
||||||
"github.com/ghodss/yaml"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
// Endpoints is a list of URLs
|
// Endpoints is a list of URLs.
|
||||||
Endpoints []string
|
Endpoints []string `json:"endpoints"`
|
||||||
|
|
||||||
// AutoSyncInterval is the interval to update endpoints with its latest members.
|
// AutoSyncInterval is the interval to update endpoints with its latest members.
|
||||||
// 0 disables auto-sync. By default auto-sync is disabled.
|
// 0 disables auto-sync. By default auto-sync is disabled.
|
||||||
AutoSyncInterval time.Duration
|
AutoSyncInterval time.Duration `json:"auto-sync-interval"`
|
||||||
|
|
||||||
// DialTimeout is the timeout for failing to establish a connection.
|
// DialTimeout is the timeout for failing to establish a connection.
|
||||||
DialTimeout time.Duration
|
DialTimeout time.Duration `json:"dial-timeout"`
|
||||||
|
|
||||||
// TLS holds the client secure credentials, if any.
|
// TLS holds the client secure credentials, if any.
|
||||||
TLS *tls.Config
|
TLS *tls.Config
|
||||||
|
|
||||||
// Username is a username for authentication
|
// Username is a username for authentication.
|
||||||
Username string
|
Username string `json:"username"`
|
||||||
|
|
||||||
// Password is a password for authentication
|
// Password is a password for authentication.
|
||||||
Password string
|
Password string `json:"password"`
|
||||||
}
|
|
||||||
|
|
||||||
type yamlConfig struct {
|
// DialOptions is a list of dial options for the grpc client (e.g., for interceptors).
|
||||||
Endpoints []string `json:"endpoints"`
|
DialOptions []grpc.DialOption
|
||||||
AutoSyncInterval time.Duration `json:"auto-sync-interval"`
|
|
||||||
DialTimeout time.Duration `json:"dial-timeout"`
|
|
||||||
InsecureTransport bool `json:"insecure-transport"`
|
|
||||||
InsecureSkipTLSVerify bool `json:"insecure-skip-tls-verify"`
|
|
||||||
Certfile string `json:"cert-file"`
|
|
||||||
Keyfile string `json:"key-file"`
|
|
||||||
CAfile string `json:"ca-file"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func configFromFile(fpath string) (*Config, error) {
|
|
||||||
b, err := ioutil.ReadFile(fpath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
yc := &yamlConfig{}
|
|
||||||
|
|
||||||
err = yaml.Unmarshal(b, yc)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg := &Config{
|
|
||||||
Endpoints: yc.Endpoints,
|
|
||||||
AutoSyncInterval: yc.AutoSyncInterval,
|
|
||||||
DialTimeout: yc.DialTimeout,
|
|
||||||
}
|
|
||||||
|
|
||||||
if yc.InsecureTransport {
|
|
||||||
cfg.TLS = nil
|
|
||||||
return cfg, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
cert *tls.Certificate
|
|
||||||
cp *x509.CertPool
|
|
||||||
)
|
|
||||||
|
|
||||||
if yc.Certfile != "" && yc.Keyfile != "" {
|
|
||||||
cert, err = tlsutil.NewCert(yc.Certfile, yc.Keyfile, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if yc.CAfile != "" {
|
|
||||||
cp, err = tlsutil.NewCertPool([]string{yc.CAfile})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tlscfg := &tls.Config{
|
|
||||||
MinVersion: tls.VersionTLS10,
|
|
||||||
InsecureSkipVerify: yc.InsecureSkipTLSVerify,
|
|
||||||
RootCAs: cp,
|
|
||||||
}
|
|
||||||
if cert != nil {
|
|
||||||
tlscfg.Certificates = []tls.Certificate{*cert}
|
|
||||||
}
|
|
||||||
cfg.TLS = tlscfg
|
|
||||||
|
|
||||||
return cfg, nil
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -148,7 +148,7 @@ func (kv *kv) do(ctx context.Context, op Op) (OpResponse, error) {
|
||||||
}
|
}
|
||||||
case tPut:
|
case tPut:
|
||||||
var resp *pb.PutResponse
|
var resp *pb.PutResponse
|
||||||
r := &pb.PutRequest{Key: op.key, Value: op.val, Lease: int64(op.leaseID), PrevKv: op.prevKV, IgnoreValue: op.ignoreValue}
|
r := &pb.PutRequest{Key: op.key, Value: op.val, Lease: int64(op.leaseID), PrevKv: op.prevKV, IgnoreValue: op.ignoreValue, IgnoreLease: op.ignoreLease}
|
||||||
resp, err = kv.remote.Put(ctx, r)
|
resp, err = kv.remote.Put(ctx, r)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return OpResponse{put: (*PutResponse)(resp)}, nil
|
return OpResponse{put: (*PutResponse)(resp)}, nil
|
||||||
|
|
|
||||||
|
|
@ -126,6 +126,9 @@ type lessor struct {
|
||||||
// firstKeepAliveTimeout is the timeout for the first keepalive request
|
// firstKeepAliveTimeout is the timeout for the first keepalive request
|
||||||
// before the actual TTL is known to the lease client
|
// before the actual TTL is known to the lease client
|
||||||
firstKeepAliveTimeout time.Duration
|
firstKeepAliveTimeout time.Duration
|
||||||
|
|
||||||
|
// firstKeepAliveOnce ensures stream starts after first KeepAlive call.
|
||||||
|
firstKeepAliveOnce sync.Once
|
||||||
}
|
}
|
||||||
|
|
||||||
// keepAlive multiplexes a keepalive for a lease over multiple channels
|
// keepAlive multiplexes a keepalive for a lease over multiple channels
|
||||||
|
|
@ -152,19 +155,13 @@ func NewLease(c *Client) Lease {
|
||||||
}
|
}
|
||||||
|
|
||||||
l.stopCtx, l.stopCancel = context.WithCancel(context.Background())
|
l.stopCtx, l.stopCancel = context.WithCancel(context.Background())
|
||||||
go l.recvKeepAliveLoop()
|
|
||||||
go l.deadlineLoop()
|
|
||||||
return l
|
return l
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *lessor) Grant(ctx context.Context, ttl int64) (*LeaseGrantResponse, error) {
|
func (l *lessor) Grant(ctx context.Context, ttl int64) (*LeaseGrantResponse, error) {
|
||||||
cctx, cancel := context.WithCancel(ctx)
|
|
||||||
done := cancelWhenStop(cancel, l.stopCtx.Done())
|
|
||||||
defer close(done)
|
|
||||||
|
|
||||||
for {
|
for {
|
||||||
r := &pb.LeaseGrantRequest{TTL: ttl}
|
r := &pb.LeaseGrantRequest{TTL: ttl}
|
||||||
resp, err := l.remote.LeaseGrant(cctx, r)
|
resp, err := l.remote.LeaseGrant(ctx, r)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
gresp := &LeaseGrantResponse{
|
gresp := &LeaseGrantResponse{
|
||||||
ResponseHeader: resp.GetHeader(),
|
ResponseHeader: resp.GetHeader(),
|
||||||
|
|
@ -174,20 +171,16 @@ func (l *lessor) Grant(ctx context.Context, ttl int64) (*LeaseGrantResponse, err
|
||||||
}
|
}
|
||||||
return gresp, nil
|
return gresp, nil
|
||||||
}
|
}
|
||||||
if isHaltErr(cctx, err) {
|
if isHaltErr(ctx, err) {
|
||||||
return nil, toErr(cctx, err)
|
return nil, toErr(ctx, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *lessor) Revoke(ctx context.Context, id LeaseID) (*LeaseRevokeResponse, error) {
|
func (l *lessor) Revoke(ctx context.Context, id LeaseID) (*LeaseRevokeResponse, error) {
|
||||||
cctx, cancel := context.WithCancel(ctx)
|
|
||||||
done := cancelWhenStop(cancel, l.stopCtx.Done())
|
|
||||||
defer close(done)
|
|
||||||
|
|
||||||
for {
|
for {
|
||||||
r := &pb.LeaseRevokeRequest{ID: int64(id)}
|
r := &pb.LeaseRevokeRequest{ID: int64(id)}
|
||||||
resp, err := l.remote.LeaseRevoke(cctx, r)
|
resp, err := l.remote.LeaseRevoke(ctx, r)
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return (*LeaseRevokeResponse)(resp), nil
|
return (*LeaseRevokeResponse)(resp), nil
|
||||||
|
|
@ -199,13 +192,9 @@ func (l *lessor) Revoke(ctx context.Context, id LeaseID) (*LeaseRevokeResponse,
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *lessor) TimeToLive(ctx context.Context, id LeaseID, opts ...LeaseOption) (*LeaseTimeToLiveResponse, error) {
|
func (l *lessor) TimeToLive(ctx context.Context, id LeaseID, opts ...LeaseOption) (*LeaseTimeToLiveResponse, error) {
|
||||||
cctx, cancel := context.WithCancel(ctx)
|
|
||||||
done := cancelWhenStop(cancel, l.stopCtx.Done())
|
|
||||||
defer close(done)
|
|
||||||
|
|
||||||
for {
|
for {
|
||||||
r := toLeaseTimeToLiveRequest(id, opts...)
|
r := toLeaseTimeToLiveRequest(id, opts...)
|
||||||
resp, err := l.remote.LeaseTimeToLive(cctx, r, grpc.FailFast(false))
|
resp, err := l.remote.LeaseTimeToLive(ctx, r, grpc.FailFast(false))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
gresp := &LeaseTimeToLiveResponse{
|
gresp := &LeaseTimeToLiveResponse{
|
||||||
ResponseHeader: resp.GetHeader(),
|
ResponseHeader: resp.GetHeader(),
|
||||||
|
|
@ -216,8 +205,8 @@ func (l *lessor) TimeToLive(ctx context.Context, id LeaseID, opts ...LeaseOption
|
||||||
}
|
}
|
||||||
return gresp, nil
|
return gresp, nil
|
||||||
}
|
}
|
||||||
if isHaltErr(cctx, err) {
|
if isHaltErr(ctx, err) {
|
||||||
return nil, toErr(cctx, err)
|
return nil, toErr(ctx, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -254,19 +243,19 @@ func (l *lessor) KeepAlive(ctx context.Context, id LeaseID) (<-chan *LeaseKeepAl
|
||||||
l.mu.Unlock()
|
l.mu.Unlock()
|
||||||
|
|
||||||
go l.keepAliveCtxCloser(id, ctx, ka.donec)
|
go l.keepAliveCtxCloser(id, ctx, ka.donec)
|
||||||
|
l.firstKeepAliveOnce.Do(func() {
|
||||||
|
go l.recvKeepAliveLoop()
|
||||||
|
go l.deadlineLoop()
|
||||||
|
})
|
||||||
|
|
||||||
return ch, nil
|
return ch, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *lessor) KeepAliveOnce(ctx context.Context, id LeaseID) (*LeaseKeepAliveResponse, error) {
|
func (l *lessor) KeepAliveOnce(ctx context.Context, id LeaseID) (*LeaseKeepAliveResponse, error) {
|
||||||
cctx, cancel := context.WithCancel(ctx)
|
|
||||||
done := cancelWhenStop(cancel, l.stopCtx.Done())
|
|
||||||
defer close(done)
|
|
||||||
|
|
||||||
for {
|
for {
|
||||||
resp, err := l.keepAliveOnce(cctx, id)
|
resp, err := l.keepAliveOnce(ctx, id)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if resp.TTL == 0 {
|
if resp.TTL <= 0 {
|
||||||
err = rpctypes.ErrLeaseNotFound
|
err = rpctypes.ErrLeaseNotFound
|
||||||
}
|
}
|
||||||
return resp, err
|
return resp, err
|
||||||
|
|
@ -279,6 +268,8 @@ func (l *lessor) KeepAliveOnce(ctx context.Context, id LeaseID) (*LeaseKeepAlive
|
||||||
|
|
||||||
func (l *lessor) Close() error {
|
func (l *lessor) Close() error {
|
||||||
l.stopCancel()
|
l.stopCancel()
|
||||||
|
// close for synchronous teardown if stream goroutines never launched
|
||||||
|
l.firstKeepAliveOnce.Do(func() { close(l.donec) })
|
||||||
<-l.donec
|
<-l.donec
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -452,16 +443,6 @@ func (l *lessor) deadlineLoop() {
|
||||||
// sendKeepAliveLoop sends LeaseKeepAliveRequests for the lifetime of a lease stream
|
// sendKeepAliveLoop sends LeaseKeepAliveRequests for the lifetime of a lease stream
|
||||||
func (l *lessor) sendKeepAliveLoop(stream pb.Lease_LeaseKeepAliveClient) {
|
func (l *lessor) sendKeepAliveLoop(stream pb.Lease_LeaseKeepAliveClient) {
|
||||||
for {
|
for {
|
||||||
select {
|
|
||||||
case <-time.After(500 * time.Millisecond):
|
|
||||||
case <-stream.Context().Done():
|
|
||||||
return
|
|
||||||
case <-l.donec:
|
|
||||||
return
|
|
||||||
case <-l.stopCtx.Done():
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var tosend []LeaseID
|
var tosend []LeaseID
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
|
|
@ -480,6 +461,16 @@ func (l *lessor) sendKeepAliveLoop(stream pb.Lease_LeaseKeepAliveClient) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-time.After(500 * time.Millisecond):
|
||||||
|
case <-stream.Context().Done():
|
||||||
|
return
|
||||||
|
case <-l.donec:
|
||||||
|
return
|
||||||
|
case <-l.stopCtx.Done():
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -489,20 +480,3 @@ func (ka *keepAlive) Close() {
|
||||||
close(ch)
|
close(ch)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// cancelWhenStop calls cancel when the given stopc fires. It returns a done chan. done
|
|
||||||
// should be closed when the work is finished. When done fires, cancelWhenStop will release
|
|
||||||
// its internal resource.
|
|
||||||
func cancelWhenStop(cancel context.CancelFunc, stopc <-chan struct{}) chan<- struct{} {
|
|
||||||
done := make(chan struct{}, 1)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
select {
|
|
||||||
case <-stopc:
|
|
||||||
case <-done:
|
|
||||||
}
|
|
||||||
cancel()
|
|
||||||
}()
|
|
||||||
|
|
||||||
return done
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,7 @@ type Op struct {
|
||||||
|
|
||||||
// for put
|
// for put
|
||||||
ignoreValue bool
|
ignoreValue bool
|
||||||
|
ignoreLease bool
|
||||||
|
|
||||||
// progressNotify is for progress updates.
|
// progressNotify is for progress updates.
|
||||||
progressNotify bool
|
progressNotify bool
|
||||||
|
|
@ -97,7 +98,7 @@ func (op Op) toRequestOp() *pb.RequestOp {
|
||||||
case tRange:
|
case tRange:
|
||||||
return &pb.RequestOp{Request: &pb.RequestOp_RequestRange{RequestRange: op.toRangeRequest()}}
|
return &pb.RequestOp{Request: &pb.RequestOp_RequestRange{RequestRange: op.toRangeRequest()}}
|
||||||
case tPut:
|
case tPut:
|
||||||
r := &pb.PutRequest{Key: op.key, Value: op.val, Lease: int64(op.leaseID), PrevKv: op.prevKV, IgnoreValue: op.ignoreValue}
|
r := &pb.PutRequest{Key: op.key, Value: op.val, Lease: int64(op.leaseID), PrevKv: op.prevKV, IgnoreValue: op.ignoreValue, IgnoreLease: op.ignoreLease}
|
||||||
return &pb.RequestOp{Request: &pb.RequestOp_RequestPut{RequestPut: r}}
|
return &pb.RequestOp{Request: &pb.RequestOp_RequestPut{RequestPut: r}}
|
||||||
case tDeleteRange:
|
case tDeleteRange:
|
||||||
r := &pb.DeleteRangeRequest{Key: op.key, RangeEnd: op.end, PrevKv: op.prevKV}
|
r := &pb.DeleteRangeRequest{Key: op.key, RangeEnd: op.end, PrevKv: op.prevKV}
|
||||||
|
|
@ -210,6 +211,7 @@ func WithLease(leaseID LeaseID) OpOption {
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithLimit limits the number of results to return from 'Get' request.
|
// WithLimit limits the number of results to return from 'Get' request.
|
||||||
|
// If WithLimit is given a 0 limit, it is treated as no limit.
|
||||||
func WithLimit(n int64) OpOption { return func(op *Op) { op.limit = n } }
|
func WithLimit(n int64) OpOption { return func(op *Op) { op.limit = n } }
|
||||||
|
|
||||||
// WithRev specifies the store revision for 'Get' request.
|
// WithRev specifies the store revision for 'Get' request.
|
||||||
|
|
@ -372,6 +374,15 @@ func WithIgnoreValue() OpOption {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithIgnoreLease updates the key using its current lease.
|
||||||
|
// Empty lease should be passed when ignore_lease is set.
|
||||||
|
// Returns an error if the key does not exist.
|
||||||
|
func WithIgnoreLease() OpOption {
|
||||||
|
return func(op *Op) {
|
||||||
|
op.ignoreLease = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// LeaseOp represents an Operation that lease can execute.
|
// LeaseOp represents an Operation that lease can execute.
|
||||||
type LeaseOp struct {
|
type LeaseOp struct {
|
||||||
id LeaseID
|
id LeaseID
|
||||||
|
|
|
||||||
|
|
@ -49,8 +49,6 @@ type Txn interface {
|
||||||
|
|
||||||
// Commit tries to commit the transaction.
|
// Commit tries to commit the transaction.
|
||||||
Commit() (*TxnResponse, error)
|
Commit() (*TxnResponse, error)
|
||||||
|
|
||||||
// TODO: add a Do for shortcut the txn without any condition?
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type txn struct {
|
type txn struct {
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,8 @@ var (
|
||||||
// server-side error
|
// server-side error
|
||||||
ErrGRPCEmptyKey = grpc.Errorf(codes.InvalidArgument, "etcdserver: key is not provided")
|
ErrGRPCEmptyKey = grpc.Errorf(codes.InvalidArgument, "etcdserver: key is not provided")
|
||||||
ErrGRPCKeyNotFound = grpc.Errorf(codes.InvalidArgument, "etcdserver: key not found")
|
ErrGRPCKeyNotFound = grpc.Errorf(codes.InvalidArgument, "etcdserver: key not found")
|
||||||
ErrGRPCValue = grpc.Errorf(codes.InvalidArgument, "etcdserver: value is provided")
|
ErrGRPCValueProvided = grpc.Errorf(codes.InvalidArgument, "etcdserver: value is provided")
|
||||||
|
ErrGRPCLeaseProvided = grpc.Errorf(codes.InvalidArgument, "etcdserver: lease is provided")
|
||||||
ErrGRPCTooManyOps = grpc.Errorf(codes.InvalidArgument, "etcdserver: too many operations in txn request")
|
ErrGRPCTooManyOps = grpc.Errorf(codes.InvalidArgument, "etcdserver: too many operations in txn request")
|
||||||
ErrGRPCDuplicateKey = grpc.Errorf(codes.InvalidArgument, "etcdserver: duplicate key given in txn request")
|
ErrGRPCDuplicateKey = grpc.Errorf(codes.InvalidArgument, "etcdserver: duplicate key given in txn request")
|
||||||
ErrGRPCCompacted = grpc.Errorf(codes.OutOfRange, "etcdserver: mvcc: required revision has been compacted")
|
ErrGRPCCompacted = grpc.Errorf(codes.OutOfRange, "etcdserver: mvcc: required revision has been compacted")
|
||||||
|
|
@ -67,7 +68,9 @@ var (
|
||||||
errStringToError = map[string]error{
|
errStringToError = map[string]error{
|
||||||
grpc.ErrorDesc(ErrGRPCEmptyKey): ErrGRPCEmptyKey,
|
grpc.ErrorDesc(ErrGRPCEmptyKey): ErrGRPCEmptyKey,
|
||||||
grpc.ErrorDesc(ErrGRPCKeyNotFound): ErrGRPCKeyNotFound,
|
grpc.ErrorDesc(ErrGRPCKeyNotFound): ErrGRPCKeyNotFound,
|
||||||
grpc.ErrorDesc(ErrGRPCValue): ErrGRPCValue,
|
grpc.ErrorDesc(ErrGRPCValueProvided): ErrGRPCValueProvided,
|
||||||
|
grpc.ErrorDesc(ErrGRPCLeaseProvided): ErrGRPCLeaseProvided,
|
||||||
|
|
||||||
grpc.ErrorDesc(ErrGRPCTooManyOps): ErrGRPCTooManyOps,
|
grpc.ErrorDesc(ErrGRPCTooManyOps): ErrGRPCTooManyOps,
|
||||||
grpc.ErrorDesc(ErrGRPCDuplicateKey): ErrGRPCDuplicateKey,
|
grpc.ErrorDesc(ErrGRPCDuplicateKey): ErrGRPCDuplicateKey,
|
||||||
grpc.ErrorDesc(ErrGRPCCompacted): ErrGRPCCompacted,
|
grpc.ErrorDesc(ErrGRPCCompacted): ErrGRPCCompacted,
|
||||||
|
|
@ -112,7 +115,8 @@ var (
|
||||||
// client-side error
|
// client-side error
|
||||||
ErrEmptyKey = Error(ErrGRPCEmptyKey)
|
ErrEmptyKey = Error(ErrGRPCEmptyKey)
|
||||||
ErrKeyNotFound = Error(ErrGRPCKeyNotFound)
|
ErrKeyNotFound = Error(ErrGRPCKeyNotFound)
|
||||||
ErrValue = Error(ErrGRPCValue)
|
ErrValueProvided = Error(ErrGRPCValueProvided)
|
||||||
|
ErrLeaseProvided = Error(ErrGRPCLeaseProvided)
|
||||||
ErrTooManyOps = Error(ErrGRPCTooManyOps)
|
ErrTooManyOps = Error(ErrGRPCTooManyOps)
|
||||||
ErrDuplicateKey = Error(ErrGRPCDuplicateKey)
|
ErrDuplicateKey = Error(ErrGRPCDuplicateKey)
|
||||||
ErrCompacted = Error(ErrGRPCCompacted)
|
ErrCompacted = Error(ErrGRPCCompacted)
|
||||||
|
|
|
||||||
|
|
@ -228,11 +228,12 @@ type RangeRequest struct {
|
||||||
Key []byte `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
|
Key []byte `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
|
||||||
// range_end is the upper bound on the requested range [key, range_end).
|
// range_end is the upper bound on the requested range [key, range_end).
|
||||||
// If range_end is '\0', the range is all keys >= key.
|
// If range_end is '\0', the range is all keys >= key.
|
||||||
// If the range_end is one bit larger than the given key,
|
// If range_end is key plus one (e.g., "aa"+1 == "ab", "a\xff"+1 == "b"),
|
||||||
// then the range requests get the all keys with the prefix (the given key).
|
// then the range request gets all keys prefixed with key.
|
||||||
// If both key and range_end are '\0', then range requests returns all keys.
|
// If both key and range_end are '\0', then the range request returns all keys.
|
||||||
RangeEnd []byte `protobuf:"bytes,2,opt,name=range_end,json=rangeEnd,proto3" json:"range_end,omitempty"`
|
RangeEnd []byte `protobuf:"bytes,2,opt,name=range_end,json=rangeEnd,proto3" json:"range_end,omitempty"`
|
||||||
// limit is a limit on the number of keys returned for the request.
|
// limit is a limit on the number of keys returned for the request. When limit is set to 0,
|
||||||
|
// it is treated as no limit.
|
||||||
Limit int64 `protobuf:"varint,3,opt,name=limit,proto3" json:"limit,omitempty"`
|
Limit int64 `protobuf:"varint,3,opt,name=limit,proto3" json:"limit,omitempty"`
|
||||||
// revision is the point-in-time of the key-value store to use for the range.
|
// revision is the point-in-time of the key-value store to use for the range.
|
||||||
// If revision is less or equal to zero, the range is over the newest key-value store.
|
// If revision is less or equal to zero, the range is over the newest key-value store.
|
||||||
|
|
@ -316,6 +317,9 @@ type PutRequest struct {
|
||||||
// If ignore_value is set, etcd updates the key using its current value.
|
// If ignore_value is set, etcd updates the key using its current value.
|
||||||
// Returns an error if the key does not exist.
|
// Returns an error if the key does not exist.
|
||||||
IgnoreValue bool `protobuf:"varint,5,opt,name=ignore_value,json=ignoreValue,proto3" json:"ignore_value,omitempty"`
|
IgnoreValue bool `protobuf:"varint,5,opt,name=ignore_value,json=ignoreValue,proto3" json:"ignore_value,omitempty"`
|
||||||
|
// If ignore_lease is set, etcd updates the key using its current lease.
|
||||||
|
// Returns an error if the key does not exist.
|
||||||
|
IgnoreLease bool `protobuf:"varint,6,opt,name=ignore_lease,json=ignoreLease,proto3" json:"ignore_lease,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *PutRequest) Reset() { *m = PutRequest{} }
|
func (m *PutRequest) Reset() { *m = PutRequest{} }
|
||||||
|
|
@ -3954,6 +3958,16 @@ func (m *PutRequest) MarshalTo(dAtA []byte) (int, error) {
|
||||||
}
|
}
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
|
if m.IgnoreLease {
|
||||||
|
dAtA[i] = 0x30
|
||||||
|
i++
|
||||||
|
if m.IgnoreLease {
|
||||||
|
dAtA[i] = 1
|
||||||
|
} else {
|
||||||
|
dAtA[i] = 0
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
return i, nil
|
return i, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -6626,6 +6640,9 @@ func (m *PutRequest) Size() (n int) {
|
||||||
if m.IgnoreValue {
|
if m.IgnoreValue {
|
||||||
n += 2
|
n += 2
|
||||||
}
|
}
|
||||||
|
if m.IgnoreLease {
|
||||||
|
n += 2
|
||||||
|
}
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -8449,6 +8466,26 @@ func (m *PutRequest) Unmarshal(dAtA []byte) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
m.IgnoreValue = bool(v != 0)
|
m.IgnoreValue = bool(v != 0)
|
||||||
|
case 6:
|
||||||
|
if wireType != 0 {
|
||||||
|
return fmt.Errorf("proto: wrong wireType = %d for field IgnoreLease", wireType)
|
||||||
|
}
|
||||||
|
var v int
|
||||||
|
for shift := uint(0); ; shift += 7 {
|
||||||
|
if shift >= 64 {
|
||||||
|
return ErrIntOverflowRpc
|
||||||
|
}
|
||||||
|
if iNdEx >= l {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
b := dAtA[iNdEx]
|
||||||
|
iNdEx++
|
||||||
|
v |= (int(b) & 0x7F) << shift
|
||||||
|
if b < 0x80 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m.IgnoreLease = bool(v != 0)
|
||||||
default:
|
default:
|
||||||
iNdEx = preIndex
|
iNdEx = preIndex
|
||||||
skippy, err := skipRpc(dAtA[iNdEx:])
|
skippy, err := skipRpc(dAtA[iNdEx:])
|
||||||
|
|
@ -16077,219 +16114,220 @@ var (
|
||||||
func init() { proto.RegisterFile("rpc.proto", fileDescriptorRpc) }
|
func init() { proto.RegisterFile("rpc.proto", fileDescriptorRpc) }
|
||||||
|
|
||||||
var fileDescriptorRpc = []byte{
|
var fileDescriptorRpc = []byte{
|
||||||
// 3423 bytes of a gzipped FileDescriptorProto
|
// 3431 bytes of a gzipped FileDescriptorProto
|
||||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xb4, 0x5b, 0xcd, 0x73, 0x1b, 0xc7,
|
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xb4, 0x5b, 0xcd, 0x73, 0x1b, 0xc7,
|
||||||
0xb1, 0xe7, 0x02, 0x04, 0x40, 0x34, 0x3e, 0x08, 0x0d, 0x29, 0x09, 0x84, 0x24, 0x8a, 0x1a, 0x7d,
|
0xb1, 0xe7, 0x02, 0x04, 0x40, 0x34, 0x3e, 0x08, 0x0d, 0x29, 0x09, 0x84, 0x24, 0x8a, 0x1a, 0x7d,
|
||||||
0x51, 0x92, 0x4d, 0xda, 0xb4, 0xdf, 0x3b, 0xe8, 0xb9, 0x5c, 0x8f, 0x22, 0x61, 0x91, 0x8f, 0x14,
|
0x51, 0x92, 0x4d, 0xda, 0xb4, 0xdf, 0x3b, 0xe8, 0xb9, 0x5c, 0x8f, 0x22, 0x61, 0x91, 0x8f, 0x14,
|
||||||
0x29, 0x2f, 0x29, 0xd9, 0xaf, 0xca, 0x15, 0xd4, 0x12, 0x18, 0x81, 0x5b, 0x04, 0x76, 0xe1, 0xdd,
|
0x29, 0x2f, 0x29, 0xd9, 0xaf, 0xca, 0x15, 0xd4, 0x12, 0x18, 0x81, 0x5b, 0x04, 0x76, 0xe1, 0xdd,
|
||||||
0x05, 0x44, 0x3a, 0x49, 0x55, 0xca, 0xb1, 0x93, 0x4a, 0x8e, 0xf1, 0x21, 0x5f, 0xc7, 0x54, 0x0e,
|
0x05, 0x44, 0x3a, 0x49, 0x55, 0xca, 0xb1, 0x2b, 0x95, 0x1c, 0xe3, 0x43, 0xbe, 0x8e, 0xa9, 0x1c,
|
||||||
0xf9, 0x03, 0x72, 0xcb, 0x1f, 0x90, 0xca, 0x25, 0xa9, 0xca, 0x3f, 0x90, 0x72, 0x72, 0xc8, 0x21,
|
0xfc, 0x07, 0xe4, 0x96, 0x3f, 0x20, 0x95, 0x4b, 0x52, 0x95, 0x7f, 0x20, 0xe5, 0xe4, 0x90, 0x43,
|
||||||
0xf7, 0x9c, 0x52, 0x49, 0xcd, 0xd7, 0xee, 0xec, 0x62, 0x17, 0x94, 0xb3, 0xf1, 0x45, 0xdc, 0xe9,
|
0xee, 0x39, 0xa5, 0x92, 0x9a, 0xaf, 0xdd, 0xd9, 0xc5, 0x2e, 0x28, 0x67, 0xe3, 0x8b, 0xb8, 0xd3,
|
||||||
0xe9, 0xe9, 0x5f, 0x4f, 0xcf, 0x74, 0x4f, 0x4f, 0x0f, 0x04, 0x45, 0x67, 0xd0, 0x5e, 0x19, 0x38,
|
0xd3, 0xd3, 0xbf, 0x9e, 0x9e, 0xe9, 0x9e, 0x9e, 0x1e, 0x08, 0x8a, 0xce, 0xa0, 0xbd, 0x32, 0x70,
|
||||||
0xb6, 0x67, 0xa3, 0x32, 0xf1, 0xda, 0x1d, 0x97, 0x38, 0x23, 0xe2, 0x0c, 0x8e, 0x1a, 0xf3, 0x5d,
|
0x6c, 0xcf, 0x46, 0x65, 0xe2, 0xb5, 0x3b, 0x2e, 0x71, 0x46, 0xc4, 0x19, 0x1c, 0x35, 0xe6, 0xbb,
|
||||||
0xbb, 0x6b, 0xb3, 0x8e, 0x55, 0xfa, 0xc5, 0x79, 0x1a, 0x0b, 0x94, 0x67, 0xb5, 0x3f, 0x6a, 0xb7,
|
0x76, 0xd7, 0x66, 0x1d, 0xab, 0xf4, 0x8b, 0xf3, 0x34, 0x16, 0x28, 0xcf, 0x6a, 0x7f, 0xd4, 0x6e,
|
||||||
0xd9, 0x3f, 0x83, 0xa3, 0xd5, 0x93, 0x91, 0xe8, 0xba, 0xc2, 0xba, 0x8c, 0xa1, 0x77, 0xcc, 0xfe,
|
0xb3, 0x7f, 0x06, 0x47, 0xab, 0x27, 0x23, 0xd1, 0x75, 0x85, 0x75, 0x19, 0x43, 0xef, 0x98, 0xfd,
|
||||||
0x19, 0x1c, 0xb1, 0x3f, 0xa2, 0xf3, 0x6a, 0xd7, 0xb6, 0xbb, 0x3d, 0xb2, 0x6a, 0x0c, 0xcc, 0x55,
|
0x33, 0x38, 0x62, 0x7f, 0x44, 0xe7, 0xd5, 0xae, 0x6d, 0x77, 0x7b, 0x64, 0xd5, 0x18, 0x98, 0xab,
|
||||||
0xc3, 0xb2, 0x6c, 0xcf, 0xf0, 0x4c, 0xdb, 0x72, 0x79, 0x2f, 0xfe, 0x5c, 0x83, 0xaa, 0x4e, 0xdc,
|
0x86, 0x65, 0xd9, 0x9e, 0xe1, 0x99, 0xb6, 0xe5, 0xf2, 0x5e, 0xfc, 0xb9, 0x06, 0x55, 0x9d, 0xb8,
|
||||||
0x81, 0x6d, 0xb9, 0x64, 0x8b, 0x18, 0x1d, 0xe2, 0xa0, 0x6b, 0x00, 0xed, 0xde, 0xd0, 0xf5, 0x88,
|
0x03, 0xdb, 0x72, 0xc9, 0x16, 0x31, 0x3a, 0xc4, 0x41, 0xd7, 0x00, 0xda, 0xbd, 0xa1, 0xeb, 0x11,
|
||||||
0xd3, 0x32, 0x3b, 0x75, 0x6d, 0x49, 0x5b, 0x9e, 0xd6, 0x8b, 0x82, 0xb2, 0xdd, 0x41, 0x57, 0xa0,
|
0xa7, 0x65, 0x76, 0xea, 0xda, 0x92, 0xb6, 0x3c, 0xad, 0x17, 0x05, 0x65, 0xbb, 0x83, 0xae, 0x40,
|
||||||
0xd8, 0x27, 0xfd, 0x23, 0xde, 0x9b, 0x61, 0xbd, 0x33, 0x9c, 0xb0, 0xdd, 0x41, 0x0d, 0x98, 0x71,
|
0xb1, 0x4f, 0xfa, 0x47, 0xbc, 0x37, 0xc3, 0x7a, 0x67, 0x38, 0x61, 0xbb, 0x83, 0x1a, 0x30, 0xe3,
|
||||||
0xc8, 0xc8, 0x74, 0x4d, 0xdb, 0xaa, 0x67, 0x97, 0xb4, 0xe5, 0xac, 0xee, 0xb7, 0xe9, 0x40, 0xc7,
|
0x90, 0x91, 0xe9, 0x9a, 0xb6, 0x55, 0xcf, 0x2e, 0x69, 0xcb, 0x59, 0xdd, 0x6f, 0xd3, 0x81, 0x8e,
|
||||||
0x78, 0xe1, 0xb5, 0x3c, 0xe2, 0xf4, 0xeb, 0xd3, 0x7c, 0x20, 0x25, 0x1c, 0x12, 0xa7, 0x8f, 0x3f,
|
0xf1, 0xc2, 0x6b, 0x79, 0xc4, 0xe9, 0xd7, 0xa7, 0xf9, 0x40, 0x4a, 0x38, 0x24, 0x4e, 0x1f, 0x7f,
|
||||||
0xcb, 0x41, 0x59, 0x37, 0xac, 0x2e, 0xd1, 0xc9, 0xc7, 0x43, 0xe2, 0x7a, 0xa8, 0x06, 0xd9, 0x13,
|
0x96, 0x83, 0xb2, 0x6e, 0x58, 0x5d, 0xa2, 0x93, 0x8f, 0x87, 0xc4, 0xf5, 0x50, 0x0d, 0xb2, 0x27,
|
||||||
0x72, 0xc6, 0xe0, 0xcb, 0x3a, 0xfd, 0xe4, 0xe3, 0xad, 0x2e, 0x69, 0x11, 0x8b, 0x03, 0x97, 0xe9,
|
0xe4, 0x8c, 0xc1, 0x97, 0x75, 0xfa, 0xc9, 0xc7, 0x5b, 0x5d, 0xd2, 0x22, 0x16, 0x07, 0x2e, 0xd3,
|
||||||
0x78, 0xab, 0x4b, 0x9a, 0x56, 0x07, 0xcd, 0x43, 0xae, 0x67, 0xf6, 0x4d, 0x4f, 0xa0, 0xf2, 0x46,
|
0xf1, 0x56, 0x97, 0x34, 0xad, 0x0e, 0x9a, 0x87, 0x5c, 0xcf, 0xec, 0x9b, 0x9e, 0x40, 0xe5, 0x8d,
|
||||||
0x48, 0x9d, 0xe9, 0x88, 0x3a, 0x1b, 0x00, 0xae, 0xed, 0x78, 0x2d, 0xdb, 0xe9, 0x10, 0xa7, 0x9e,
|
0x90, 0x3a, 0xd3, 0x11, 0x75, 0x36, 0x00, 0x5c, 0xdb, 0xf1, 0x5a, 0xb6, 0xd3, 0x21, 0x4e, 0x3d,
|
||||||
0x5b, 0xd2, 0x96, 0xab, 0x6b, 0xb7, 0x56, 0xd4, 0x85, 0x58, 0x51, 0x15, 0x5a, 0x39, 0xb0, 0x1d,
|
0xb7, 0xa4, 0x2d, 0x57, 0xd7, 0x6e, 0xad, 0xa8, 0x0b, 0xb1, 0xa2, 0x2a, 0xb4, 0x72, 0x60, 0x3b,
|
||||||
0x6f, 0x9f, 0xf2, 0xea, 0x45, 0x57, 0x7e, 0xa2, 0xf7, 0xa0, 0xc4, 0x84, 0x78, 0x86, 0xd3, 0x25,
|
0xde, 0x3e, 0xe5, 0xd5, 0x8b, 0xae, 0xfc, 0x44, 0xef, 0x41, 0x89, 0x09, 0xf1, 0x0c, 0xa7, 0x4b,
|
||||||
0x5e, 0x3d, 0xcf, 0xa4, 0xdc, 0x3e, 0x47, 0xca, 0x21, 0x63, 0xd6, 0x19, 0x3c, 0xff, 0x46, 0x18,
|
0xbc, 0x7a, 0x9e, 0x49, 0xb9, 0x7d, 0x8e, 0x94, 0x43, 0xc6, 0xac, 0x33, 0x78, 0xfe, 0x8d, 0x30,
|
||||||
0xca, 0x2e, 0x71, 0x4c, 0xa3, 0x67, 0x7e, 0x62, 0x1c, 0xf5, 0x48, 0xbd, 0xb0, 0xa4, 0x2d, 0xcf,
|
0x94, 0x5d, 0xe2, 0x98, 0x46, 0xcf, 0xfc, 0xc4, 0x38, 0xea, 0x91, 0x7a, 0x61, 0x49, 0x5b, 0x9e,
|
||||||
0xe8, 0x21, 0x1a, 0x9d, 0xff, 0x09, 0x39, 0x73, 0x5b, 0xb6, 0xd5, 0x3b, 0xab, 0xcf, 0x30, 0x86,
|
0xd1, 0x43, 0x34, 0x3a, 0xff, 0x13, 0x72, 0xe6, 0xb6, 0x6c, 0xab, 0x77, 0x56, 0x9f, 0x61, 0x0c,
|
||||||
0x19, 0x4a, 0xd8, 0xb7, 0x7a, 0x67, 0x6c, 0xd1, 0xec, 0xa1, 0xe5, 0xf1, 0xde, 0x22, 0xeb, 0x2d,
|
0x33, 0x94, 0xb0, 0x6f, 0xf5, 0xce, 0xd8, 0xa2, 0xd9, 0x43, 0xcb, 0xe3, 0xbd, 0x45, 0xd6, 0x5b,
|
||||||
0x32, 0x0a, 0xeb, 0x5e, 0x86, 0x5a, 0xdf, 0xb4, 0x5a, 0x7d, 0xbb, 0xd3, 0xf2, 0x0d, 0x02, 0xcc,
|
0x64, 0x14, 0xd6, 0xbd, 0x0c, 0xb5, 0xbe, 0x69, 0xb5, 0xfa, 0x76, 0xa7, 0xe5, 0x1b, 0x04, 0x98,
|
||||||
0x20, 0xd5, 0xbe, 0x69, 0x3d, 0xb1, 0x3b, 0xba, 0x34, 0x0b, 0xe5, 0x34, 0x4e, 0xc3, 0x9c, 0x25,
|
0x41, 0xaa, 0x7d, 0xd3, 0x7a, 0x62, 0x77, 0x74, 0x69, 0x16, 0xca, 0x69, 0x9c, 0x86, 0x39, 0x4b,
|
||||||
0xc1, 0x69, 0x9c, 0xaa, 0x9c, 0x2b, 0x30, 0x47, 0x65, 0xb6, 0x1d, 0x62, 0x78, 0x24, 0x60, 0x2e,
|
0x82, 0xd3, 0x38, 0x55, 0x39, 0x57, 0x60, 0x8e, 0xca, 0x6c, 0x3b, 0xc4, 0xf0, 0x48, 0xc0, 0x5c,
|
||||||
0x33, 0xe6, 0x0b, 0x7d, 0xd3, 0xda, 0x60, 0x3d, 0x21, 0x7e, 0xe3, 0x74, 0x8c, 0xbf, 0x22, 0xf8,
|
0x66, 0xcc, 0x17, 0xfa, 0xa6, 0xb5, 0xc1, 0x7a, 0x42, 0xfc, 0xc6, 0xe9, 0x18, 0x7f, 0x45, 0xf0,
|
||||||
0x8d, 0xd3, 0x30, 0x3f, 0x5e, 0x81, 0xa2, 0x6f, 0x73, 0x34, 0x03, 0xd3, 0x7b, 0xfb, 0x7b, 0xcd,
|
0x1b, 0xa7, 0x61, 0x7e, 0xbc, 0x02, 0x45, 0xdf, 0xe6, 0x68, 0x06, 0xa6, 0xf7, 0xf6, 0xf7, 0x9a,
|
||||||
0xda, 0x14, 0x02, 0xc8, 0xaf, 0x1f, 0x6c, 0x34, 0xf7, 0x36, 0x6b, 0x1a, 0x2a, 0x41, 0x61, 0xb3,
|
0xb5, 0x29, 0x04, 0x90, 0x5f, 0x3f, 0xd8, 0x68, 0xee, 0x6d, 0xd6, 0x34, 0x54, 0x82, 0xc2, 0x66,
|
||||||
0xc9, 0x1b, 0x19, 0xfc, 0x08, 0x20, 0xb0, 0x2e, 0x2a, 0x40, 0x76, 0xa7, 0xf9, 0xff, 0xb5, 0x29,
|
0x93, 0x37, 0x32, 0xf8, 0x11, 0x40, 0x60, 0x5d, 0x54, 0x80, 0xec, 0x4e, 0xf3, 0xff, 0x6b, 0x53,
|
||||||
0xca, 0xf3, 0xbc, 0xa9, 0x1f, 0x6c, 0xef, 0xef, 0xd5, 0x34, 0x3a, 0x78, 0x43, 0x6f, 0xae, 0x1f,
|
0x94, 0xe7, 0x79, 0x53, 0x3f, 0xd8, 0xde, 0xdf, 0xab, 0x69, 0x74, 0xf0, 0x86, 0xde, 0x5c, 0x3f,
|
||||||
0x36, 0x6b, 0x19, 0xca, 0xf1, 0x64, 0x7f, 0xb3, 0x96, 0x45, 0x45, 0xc8, 0x3d, 0x5f, 0xdf, 0x7d,
|
0x6c, 0xd6, 0x32, 0x94, 0xe3, 0xc9, 0xfe, 0x66, 0x2d, 0x8b, 0x8a, 0x90, 0x7b, 0xbe, 0xbe, 0xfb,
|
||||||
0xd6, 0xac, 0x4d, 0xe3, 0x2f, 0x34, 0xa8, 0x88, 0xf5, 0xe2, 0x3e, 0x81, 0xde, 0x86, 0xfc, 0x31,
|
0xac, 0x59, 0x9b, 0xc6, 0x5f, 0x68, 0x50, 0x11, 0xeb, 0xc5, 0x7d, 0x02, 0xbd, 0x0d, 0xf9, 0x63,
|
||||||
0xf3, 0x0b, 0xb6, 0x15, 0x4b, 0x6b, 0x57, 0x23, 0x8b, 0x1b, 0xf2, 0x1d, 0x5d, 0xf0, 0x22, 0x0c,
|
0xe6, 0x17, 0x6c, 0x2b, 0x96, 0xd6, 0xae, 0x46, 0x16, 0x37, 0xe4, 0x3b, 0xba, 0xe0, 0x45, 0x18,
|
||||||
0xd9, 0x93, 0x91, 0x5b, 0xcf, 0x2c, 0x65, 0x97, 0x4b, 0x6b, 0xb5, 0x15, 0xee, 0xb0, 0x2b, 0x3b,
|
0xb2, 0x27, 0x23, 0xb7, 0x9e, 0x59, 0xca, 0x2e, 0x97, 0xd6, 0x6a, 0x2b, 0xdc, 0x61, 0x57, 0x76,
|
||||||
0xe4, 0xec, 0xb9, 0xd1, 0x1b, 0x12, 0x9d, 0x76, 0x22, 0x04, 0xd3, 0x7d, 0xdb, 0x21, 0x6c, 0xc7,
|
0xc8, 0xd9, 0x73, 0xa3, 0x37, 0x24, 0x3a, 0xed, 0x44, 0x08, 0xa6, 0xfb, 0xb6, 0x43, 0xd8, 0x8e,
|
||||||
0xce, 0xe8, 0xec, 0x9b, 0x6e, 0x63, 0xb6, 0x68, 0x62, 0xb7, 0xf2, 0x06, 0xfe, 0x9e, 0x06, 0xf0,
|
0x9d, 0xd1, 0xd9, 0x37, 0xdd, 0xc6, 0x6c, 0xd1, 0xc4, 0x6e, 0xe5, 0x0d, 0xfc, 0xa5, 0x06, 0xf0,
|
||||||
0x74, 0xe8, 0x25, 0xbb, 0xc6, 0x3c, 0xe4, 0x46, 0x54, 0xb0, 0x70, 0x0b, 0xde, 0x60, 0x3e, 0x41,
|
0x74, 0xe8, 0x25, 0xbb, 0xc6, 0x3c, 0xe4, 0x46, 0x54, 0xb0, 0x70, 0x0b, 0xde, 0x60, 0x3e, 0x41,
|
||||||
0x0c, 0x97, 0xf8, 0x3e, 0x41, 0x1b, 0xe8, 0x32, 0x14, 0x06, 0x0e, 0x19, 0xb5, 0x4e, 0x46, 0x0c,
|
0x0c, 0x97, 0xf8, 0x3e, 0x41, 0x1b, 0xe8, 0x32, 0x14, 0x06, 0x0e, 0x19, 0xb5, 0x4e, 0x46, 0x0c,
|
||||||
0x64, 0x46, 0xcf, 0xd3, 0xe6, 0xce, 0x08, 0xdd, 0x80, 0xb2, 0xd9, 0xb5, 0x6c, 0x87, 0xb4, 0xb8,
|
0x64, 0x46, 0xcf, 0xd3, 0xe6, 0xce, 0x08, 0xdd, 0x80, 0xb2, 0xd9, 0xb5, 0x6c, 0x87, 0xb4, 0xb8,
|
||||||
0xac, 0x1c, 0xeb, 0x2d, 0x71, 0x1a, 0xd3, 0x1b, 0x5b, 0x50, 0x62, 0x7a, 0xa4, 0xb2, 0xcd, 0xbd,
|
0xac, 0x1c, 0xeb, 0x2d, 0x71, 0x1a, 0xd3, 0x5b, 0x61, 0xe1, 0x82, 0xf3, 0x2a, 0xcb, 0x2e, 0x25,
|
||||||
0x40, 0x81, 0x0c, 0x1b, 0x36, 0x6e, 0x1f, 0xa1, 0x12, 0xfe, 0x08, 0xd0, 0x26, 0xe9, 0x11, 0x8f,
|
0x61, 0x0b, 0x4a, 0x4c, 0xd5, 0x54, 0xe6, 0xbb, 0x17, 0xe8, 0x98, 0x61, 0xc3, 0xc6, 0x4d, 0x28,
|
||||||
0xa4, 0x09, 0x0d, 0xca, 0x84, 0xb3, 0xea, 0x84, 0xf1, 0x8f, 0x34, 0x98, 0x0b, 0x89, 0x4f, 0x35,
|
0xb4, 0xc6, 0x1f, 0x01, 0xda, 0x24, 0x3d, 0xe2, 0x91, 0x34, 0xd1, 0x43, 0xb1, 0x49, 0x56, 0xb5,
|
||||||
0xad, 0x3a, 0x14, 0x3a, 0x4c, 0x18, 0xd7, 0x20, 0xab, 0xcb, 0x26, 0x7a, 0x00, 0x33, 0x42, 0x01,
|
0x09, 0xfe, 0xb1, 0x06, 0x73, 0x21, 0xf1, 0xa9, 0xa6, 0x55, 0x87, 0x42, 0x87, 0x09, 0xe3, 0x1a,
|
||||||
0xb7, 0x9e, 0x4d, 0xd8, 0x11, 0x05, 0xae, 0x93, 0x8b, 0xff, 0xa6, 0x41, 0x51, 0x4c, 0x74, 0x7f,
|
0x64, 0x75, 0xd9, 0x44, 0x0f, 0x60, 0x46, 0x28, 0xe0, 0xd6, 0xb3, 0x09, 0x9b, 0xa6, 0xc0, 0x75,
|
||||||
0x80, 0xd6, 0xa1, 0xe2, 0xf0, 0x46, 0x8b, 0xcd, 0x47, 0x68, 0xd4, 0x48, 0x8e, 0x30, 0x5b, 0x53,
|
0x72, 0xf1, 0xdf, 0x34, 0x28, 0x8a, 0x89, 0xee, 0x0f, 0xd0, 0x3a, 0x54, 0x1c, 0xde, 0x68, 0xb1,
|
||||||
0x7a, 0x59, 0x0c, 0x61, 0x64, 0xf4, 0x3f, 0x50, 0x92, 0x22, 0x06, 0x43, 0x4f, 0x98, 0xbc, 0x1e,
|
0xf9, 0x08, 0x8d, 0x1a, 0xc9, 0x41, 0x68, 0x6b, 0x4a, 0x2f, 0x8b, 0x21, 0x8c, 0x8c, 0xfe, 0x07,
|
||||||
0x16, 0x10, 0x6c, 0xae, 0xad, 0x29, 0x1d, 0x04, 0xfb, 0xd3, 0xa1, 0x87, 0x0e, 0x61, 0x5e, 0x0e,
|
0x4a, 0x52, 0xc4, 0x60, 0xe8, 0x09, 0x93, 0xd7, 0xc3, 0x02, 0x82, 0xfd, 0xb7, 0x35, 0xa5, 0x83,
|
||||||
0xe6, 0xb3, 0x11, 0x6a, 0x64, 0x99, 0x94, 0xa5, 0xb0, 0x94, 0xf1, 0xa5, 0xda, 0x9a, 0xd2, 0x91,
|
0x60, 0x7f, 0x3a, 0xf4, 0xd0, 0x21, 0xcc, 0xcb, 0xc1, 0x7c, 0x36, 0x42, 0x8d, 0x2c, 0x93, 0xb2,
|
||||||
0x18, 0xaf, 0x74, 0x3e, 0x2a, 0x42, 0x41, 0x50, 0xf1, 0xdf, 0x35, 0x00, 0x69, 0xd0, 0xfd, 0x01,
|
0x14, 0x96, 0x32, 0xbe, 0x54, 0x5b, 0x53, 0x3a, 0x12, 0xe3, 0x95, 0xce, 0x47, 0x45, 0x28, 0x08,
|
||||||
0xda, 0x84, 0xaa, 0x23, 0x5a, 0xa1, 0x09, 0x5f, 0x89, 0x9d, 0xb0, 0x58, 0x87, 0x29, 0xbd, 0x22,
|
0x2a, 0xfe, 0xbb, 0x06, 0x20, 0x0d, 0xba, 0x3f, 0x40, 0x9b, 0x50, 0x75, 0x44, 0x2b, 0x34, 0xe1,
|
||||||
0x07, 0xf1, 0x29, 0xbf, 0x0b, 0x65, 0x5f, 0x4a, 0x30, 0xe7, 0x85, 0x98, 0x39, 0xfb, 0x12, 0x4a,
|
0x2b, 0xb1, 0x13, 0x16, 0xeb, 0x30, 0xa5, 0x57, 0xe4, 0x20, 0x3e, 0xe5, 0x77, 0xa1, 0xec, 0x4b,
|
||||||
0x72, 0x00, 0x9d, 0xf5, 0x07, 0x70, 0xd1, 0x1f, 0x1f, 0x33, 0xed, 0x1b, 0x13, 0xa6, 0xed, 0x0b,
|
0x09, 0xe6, 0xbc, 0x10, 0x33, 0x67, 0x5f, 0x42, 0x49, 0x0e, 0xa0, 0xb3, 0xfe, 0x00, 0x2e, 0xfa,
|
||||||
0x9c, 0x93, 0x12, 0xd4, 0x89, 0x03, 0x3d, 0x8f, 0x38, 0x19, 0xff, 0x2a, 0x0b, 0x85, 0x0d, 0xbb,
|
0xe3, 0x63, 0xa6, 0x7d, 0x63, 0xc2, 0xb4, 0x7d, 0x81, 0x73, 0x52, 0x82, 0x3a, 0x71, 0xa0, 0x47,
|
||||||
0x3f, 0x30, 0x1c, 0xba, 0x46, 0x79, 0x87, 0xb8, 0xc3, 0x9e, 0xc7, 0xa6, 0x5b, 0x5d, 0xbb, 0x19,
|
0x16, 0x27, 0xe3, 0x2f, 0xb3, 0x50, 0xd8, 0xb0, 0xfb, 0x03, 0xc3, 0xa1, 0x6b, 0x94, 0x77, 0x88,
|
||||||
0x46, 0x10, 0x6c, 0xf2, 0xaf, 0xce, 0x58, 0x75, 0x31, 0x84, 0x0e, 0x16, 0xc7, 0x4f, 0xe6, 0x15,
|
0x3b, 0xec, 0x79, 0x6c, 0xba, 0xd5, 0xb5, 0x9b, 0x61, 0x04, 0xc1, 0x26, 0xff, 0xea, 0x8c, 0x55,
|
||||||
0x06, 0x8b, 0xc3, 0x47, 0x0c, 0x91, 0xbe, 0x94, 0x0d, 0x7c, 0xa9, 0x01, 0x85, 0x11, 0x71, 0x82,
|
0x17, 0x43, 0xe8, 0x60, 0x71, 0x42, 0x65, 0x5e, 0x61, 0xb0, 0x38, 0x9f, 0xc4, 0x10, 0xe9, 0x4b,
|
||||||
0x23, 0x73, 0x6b, 0x4a, 0x97, 0x04, 0x74, 0x0f, 0x66, 0xa3, 0xe1, 0x3b, 0x27, 0x78, 0xaa, 0xed,
|
0xd9, 0xc0, 0x97, 0x1a, 0x50, 0x18, 0x11, 0x27, 0x38, 0x55, 0xb7, 0xa6, 0x74, 0x49, 0x40, 0xf7,
|
||||||
0x70, 0xb4, 0xbf, 0x09, 0xe5, 0xd0, 0x19, 0x92, 0x17, 0x7c, 0xa5, 0xbe, 0x72, 0x84, 0x5c, 0x92,
|
0x60, 0x36, 0x1a, 0xe1, 0x73, 0x82, 0xa7, 0xda, 0x0e, 0x1f, 0x08, 0x37, 0xa1, 0x1c, 0x3a, 0x66,
|
||||||
0x71, 0x8b, 0x9e, 0x77, 0xe5, 0xad, 0x29, 0x11, 0xb9, 0xf0, 0xff, 0x42, 0x25, 0x34, 0x57, 0x1a,
|
0xf2, 0x82, 0xaf, 0xd4, 0x57, 0x4e, 0x99, 0x4b, 0x32, 0xb4, 0xd1, 0x23, 0xb1, 0xbc, 0x35, 0x25,
|
||||||
0xa2, 0x9b, 0xef, 0x3f, 0x5b, 0xdf, 0xe5, 0xf1, 0xfc, 0x31, 0x0b, 0xe1, 0x7a, 0x4d, 0xa3, 0xc7,
|
0x82, 0x1b, 0xfe, 0x5f, 0xa8, 0x84, 0xe6, 0x4a, 0xa3, 0x78, 0xf3, 0xfd, 0x67, 0xeb, 0xbb, 0x3c,
|
||||||
0xc2, 0x6e, 0xf3, 0xe0, 0xa0, 0x96, 0x41, 0x15, 0x28, 0xee, 0xed, 0x1f, 0xb6, 0x38, 0x57, 0x16,
|
0xe4, 0x3f, 0x66, 0x51, 0x5e, 0xaf, 0x69, 0xf4, 0xe4, 0xd8, 0x6d, 0x1e, 0x1c, 0xd4, 0x32, 0xa8,
|
||||||
0xbf, 0xe3, 0x4b, 0x10, 0xe7, 0x81, 0x72, 0x0c, 0x4c, 0x29, 0xc7, 0x80, 0x26, 0x8f, 0x81, 0x4c,
|
0x02, 0xc5, 0xbd, 0xfd, 0xc3, 0x16, 0xe7, 0xca, 0xe2, 0x77, 0x7c, 0x09, 0xe2, 0xc8, 0x50, 0x4e,
|
||||||
0x70, 0x0c, 0x64, 0x1f, 0x55, 0xa1, 0xcc, 0xed, 0xd3, 0x1a, 0x5a, 0xf4, 0x28, 0xfa, 0x85, 0x06,
|
0x8a, 0x29, 0xe5, 0xa4, 0xd0, 0xe4, 0x49, 0x91, 0x09, 0x4e, 0x8a, 0xec, 0xa3, 0x2a, 0x94, 0xb9,
|
||||||
0x70, 0x78, 0x6a, 0xc9, 0x00, 0xb4, 0x0a, 0x85, 0x36, 0x17, 0x5e, 0xd7, 0x98, 0x3f, 0x5f, 0x8c,
|
0x7d, 0x5a, 0x43, 0x8b, 0x9e, 0x56, 0xbf, 0xd4, 0x00, 0x0e, 0x4f, 0x2d, 0x19, 0x80, 0x56, 0xa1,
|
||||||
0x35, 0xb9, 0x2e, 0xb9, 0xd0, 0x9b, 0x50, 0x70, 0x87, 0xed, 0x36, 0x71, 0xe5, 0x91, 0x70, 0x39,
|
0xd0, 0xe6, 0xc2, 0xeb, 0x1a, 0xf3, 0xe7, 0x8b, 0xb1, 0x26, 0xd7, 0x25, 0x17, 0x7a, 0x13, 0x0a,
|
||||||
0x1a, 0x52, 0x84, 0xc3, 0xeb, 0x92, 0x8f, 0x0e, 0x79, 0x61, 0x98, 0xbd, 0x21, 0x3b, 0x20, 0x26,
|
0xee, 0xb0, 0xdd, 0x26, 0xae, 0x3c, 0x35, 0x2e, 0x47, 0x43, 0x8a, 0x70, 0x78, 0x5d, 0xf2, 0xd1,
|
||||||
0x0f, 0x11, 0x7c, 0xf8, 0xa7, 0x1a, 0x94, 0x98, 0x96, 0xa9, 0xe2, 0xd8, 0x55, 0x28, 0x32, 0x1d,
|
0x21, 0x2f, 0x0c, 0xb3, 0x37, 0x64, 0x67, 0xc8, 0xe4, 0x21, 0x82, 0x0f, 0xff, 0x4c, 0x83, 0x12,
|
||||||
0x48, 0x47, 0x44, 0xb2, 0x19, 0x3d, 0x20, 0xa0, 0xff, 0x86, 0xa2, 0xdc, 0xc1, 0x32, 0x98, 0xd5,
|
0xd3, 0x32, 0x55, 0x1c, 0xbb, 0x0a, 0x45, 0xa6, 0x03, 0xe9, 0x88, 0x48, 0x36, 0xa3, 0x07, 0x04,
|
||||||
0xe3, 0xc5, 0xee, 0x0f, 0xf4, 0x80, 0x15, 0xef, 0xc0, 0x05, 0x66, 0x95, 0x36, 0x4d, 0x3e, 0xa5,
|
0xf4, 0xdf, 0x50, 0x94, 0x3b, 0x58, 0x06, 0xb3, 0x7a, 0xbc, 0xd8, 0xfd, 0x81, 0x1e, 0xb0, 0xe2,
|
||||||
0x1d, 0xd5, 0xf4, 0x4c, 0x8b, 0xa4, 0x67, 0x0d, 0x98, 0x19, 0x1c, 0x9f, 0xb9, 0x66, 0xdb, 0xe8,
|
0x1d, 0xb8, 0xc0, 0xac, 0xd2, 0xa6, 0xf9, 0xa9, 0xb4, 0xa3, 0x9a, 0xc1, 0x69, 0x91, 0x0c, 0xae,
|
||||||
0x09, 0x2d, 0xfc, 0x36, 0xfe, 0x3f, 0x40, 0xaa, 0xb0, 0x34, 0xd3, 0xc5, 0x15, 0x28, 0x6d, 0x19,
|
0x01, 0x33, 0x83, 0xe3, 0x33, 0xd7, 0x6c, 0x1b, 0x3d, 0xa1, 0x85, 0xdf, 0xc6, 0xff, 0x07, 0x48,
|
||||||
0xee, 0xb1, 0x50, 0x09, 0x7f, 0x08, 0x65, 0xde, 0x4c, 0x65, 0x43, 0x04, 0xd3, 0xc7, 0x86, 0x7b,
|
0x15, 0x96, 0x66, 0xba, 0xb8, 0x02, 0xa5, 0x2d, 0xc3, 0x3d, 0x16, 0x2a, 0xe1, 0x0f, 0xa1, 0xcc,
|
||||||
0xcc, 0x14, 0xaf, 0xe8, 0xec, 0x1b, 0x5f, 0x80, 0xd9, 0x03, 0xcb, 0x18, 0xb8, 0xc7, 0xb6, 0x8c,
|
0x9b, 0xa9, 0x6c, 0x88, 0x60, 0xfa, 0xd8, 0x70, 0x8f, 0x99, 0xe2, 0x15, 0x9d, 0x7d, 0xe3, 0x0b,
|
||||||
0xb5, 0x34, 0xf9, 0xae, 0x05, 0xb4, 0x54, 0x88, 0x77, 0x61, 0xd6, 0x21, 0x7d, 0xc3, 0xb4, 0x4c,
|
0x30, 0x7b, 0x60, 0x19, 0x03, 0xf7, 0xd8, 0x96, 0xb1, 0x96, 0xe6, 0xe7, 0xb5, 0x80, 0x96, 0x0a,
|
||||||
0xab, 0xdb, 0x3a, 0x3a, 0xf3, 0x88, 0x2b, 0x72, 0xf3, 0xaa, 0x4f, 0x7e, 0x44, 0xa9, 0x54, 0xb5,
|
0xf1, 0x2e, 0xcc, 0x3a, 0xa4, 0x6f, 0x98, 0x96, 0x69, 0x75, 0x5b, 0x47, 0x67, 0x1e, 0x71, 0x45,
|
||||||
0xa3, 0x9e, 0x7d, 0x24, 0x3c, 0x9e, 0x7d, 0xe3, 0x5f, 0x6b, 0x50, 0xfe, 0xc0, 0xf0, 0xda, 0xd2,
|
0xfa, 0x5e, 0xf5, 0xc9, 0x8f, 0x28, 0x95, 0xaa, 0x76, 0xd4, 0xb3, 0x8f, 0x84, 0xc7, 0xb3, 0x6f,
|
||||||
0x0a, 0x68, 0x1b, 0xaa, 0xbe, 0x9f, 0x33, 0x8a, 0xd0, 0x25, 0x12, 0xf0, 0xd9, 0x18, 0x99, 0xb5,
|
0xfc, 0x6b, 0x0d, 0xca, 0x1f, 0x18, 0x5e, 0x5b, 0x5a, 0x01, 0x6d, 0x43, 0xd5, 0xf7, 0x73, 0x46,
|
||||||
0xc9, 0x80, 0x5f, 0x69, 0xab, 0x04, 0x26, 0xca, 0xb0, 0xda, 0xa4, 0xe7, 0x8b, 0xca, 0x24, 0x8b,
|
0x11, 0xba, 0x44, 0x02, 0x3e, 0x1b, 0x23, 0x13, 0x3b, 0x19, 0xf0, 0x2b, 0x6d, 0x95, 0xc0, 0x44,
|
||||||
0x62, 0x8c, 0xaa, 0x28, 0x95, 0xf0, 0x68, 0x36, 0x38, 0x0c, 0xb9, 0x5b, 0xfe, 0x2c, 0x03, 0x68,
|
0x19, 0x56, 0x9b, 0xf4, 0x7c, 0x51, 0x99, 0x64, 0x51, 0x8c, 0x51, 0x15, 0xa5, 0x12, 0x1e, 0xcd,
|
||||||
0x5c, 0x87, 0xaf, 0x9a, 0x1f, 0xdc, 0x86, 0xaa, 0xeb, 0x19, 0x8e, 0xd7, 0x8a, 0xdc, 0x5c, 0x2a,
|
0x06, 0x87, 0x21, 0x77, 0xcb, 0x9f, 0x67, 0x00, 0x8d, 0xeb, 0xf0, 0x75, 0xf3, 0x83, 0xdb, 0x50,
|
||||||
0x8c, 0xea, 0xc7, 0xaa, 0xbb, 0x30, 0x3b, 0x70, 0xec, 0xae, 0x43, 0x5c, 0xb7, 0x65, 0xd9, 0x9e,
|
0x75, 0x3d, 0xc3, 0xf1, 0x5a, 0x91, 0xcb, 0x4d, 0x85, 0x51, 0xfd, 0x58, 0x75, 0x17, 0x66, 0x07,
|
||||||
0xf9, 0xe2, 0x4c, 0xe4, 0x4f, 0x55, 0x49, 0xde, 0x63, 0x54, 0xd4, 0x84, 0xc2, 0x0b, 0xb3, 0xe7,
|
0x8e, 0xdd, 0x75, 0x88, 0xeb, 0xb6, 0x2c, 0xdb, 0x33, 0x5f, 0x9c, 0x89, 0x14, 0xab, 0x2a, 0xc9,
|
||||||
0x11, 0xc7, 0xad, 0xe7, 0x96, 0xb2, 0xcb, 0xd5, 0xb5, 0x07, 0xe7, 0x59, 0x6d, 0xe5, 0x3d, 0xc6,
|
0x7b, 0x8c, 0x8a, 0x9a, 0x50, 0x78, 0x61, 0xf6, 0x3c, 0xe2, 0xb8, 0xf5, 0xdc, 0x52, 0x76, 0xb9,
|
||||||
0x7f, 0x78, 0x36, 0x20, 0xba, 0x1c, 0xab, 0xa6, 0x2d, 0xf9, 0x50, 0xda, 0x72, 0x1b, 0x20, 0xe0,
|
0xba, 0xf6, 0xe0, 0x3c, 0xab, 0xad, 0xbc, 0xc7, 0xf8, 0x0f, 0xcf, 0x06, 0x44, 0x97, 0x63, 0xd5,
|
||||||
0xa7, 0x51, 0x6b, 0x6f, 0xff, 0xe9, 0xb3, 0xc3, 0xda, 0x14, 0x2a, 0xc3, 0xcc, 0xde, 0xfe, 0x66,
|
0xb4, 0x25, 0x1f, 0x4a, 0x5b, 0x6e, 0x03, 0x04, 0xfc, 0x34, 0x6a, 0xed, 0xed, 0x3f, 0x7d, 0x76,
|
||||||
0x73, 0xb7, 0x49, 0xe3, 0x1a, 0x5e, 0x95, 0xb6, 0x51, 0x6d, 0x88, 0x16, 0x60, 0xe6, 0x25, 0xa5,
|
0x58, 0x9b, 0x42, 0x65, 0x98, 0xd9, 0xdb, 0xdf, 0x6c, 0xee, 0x36, 0x69, 0x5c, 0xc3, 0xab, 0xd2,
|
||||||
0xca, 0xab, 0x5d, 0x56, 0x2f, 0xb0, 0xf6, 0x76, 0x07, 0xff, 0x55, 0x83, 0x8a, 0xd8, 0x05, 0xa9,
|
0x36, 0xaa, 0x0d, 0xd1, 0x02, 0xcc, 0xbc, 0xa4, 0x54, 0x79, 0xfb, 0xcb, 0xea, 0x05, 0xd6, 0xde,
|
||||||
0xb6, 0xa2, 0x0a, 0x91, 0x09, 0x41, 0xd0, 0x1c, 0x89, 0xef, 0x8e, 0x8e, 0x48, 0xc5, 0x64, 0x93,
|
0xee, 0xe0, 0xbf, 0x6a, 0x50, 0x11, 0xbb, 0x20, 0xd5, 0x56, 0x54, 0x21, 0x32, 0x21, 0x08, 0x9a,
|
||||||
0xba, 0x3b, 0x5f, 0x6c, 0xd2, 0x11, 0x66, 0xf5, 0xdb, 0xe8, 0x1e, 0xd4, 0xda, 0xdc, 0xdd, 0x23,
|
0x23, 0xf1, 0xdd, 0xd1, 0x11, 0xa9, 0x98, 0x6c, 0x52, 0x77, 0xe7, 0x8b, 0x4d, 0x3a, 0xc2, 0xac,
|
||||||
0xc7, 0x8e, 0x3e, 0x2b, 0xe8, 0xfe, 0x22, 0xdd, 0x86, 0x3c, 0x19, 0x11, 0xcb, 0x73, 0xeb, 0x25,
|
0x7e, 0x1b, 0xdd, 0x83, 0x5a, 0x9b, 0xbb, 0x7b, 0xe4, 0xd8, 0xd1, 0x67, 0x05, 0xdd, 0x5f, 0xa4,
|
||||||
0x16, 0x9b, 0x2a, 0x32, 0xd1, 0x6a, 0x52, 0xaa, 0x2e, 0x3a, 0xf1, 0x7f, 0xc1, 0x85, 0x5d, 0x9a,
|
0xdb, 0x90, 0x27, 0x23, 0x62, 0x79, 0x6e, 0xbd, 0xc4, 0x62, 0x53, 0x45, 0x26, 0x5a, 0x4d, 0x4a,
|
||||||
0x0c, 0x3f, 0x76, 0x0c, 0x4b, 0x4d, 0xab, 0x0f, 0x0f, 0x77, 0x85, 0x55, 0xe8, 0x27, 0xaa, 0x42,
|
0xd5, 0x45, 0x27, 0xfe, 0x2f, 0xb8, 0xc0, 0x12, 0xda, 0xc7, 0x8e, 0x61, 0xa9, 0x99, 0xf7, 0xe1,
|
||||||
0x66, 0x7b, 0x53, 0xcc, 0x21, 0xb3, 0xbd, 0x89, 0x3f, 0xd5, 0x00, 0xa9, 0xe3, 0x52, 0x99, 0x29,
|
0xe1, 0xae, 0xb0, 0x0a, 0xfd, 0x44, 0x55, 0xc8, 0x6c, 0x6f, 0x8a, 0x39, 0x64, 0xb6, 0x37, 0xf1,
|
||||||
0x22, 0x5c, 0xc2, 0x67, 0x03, 0xf8, 0x79, 0xc8, 0x11, 0xc7, 0xb1, 0x1d, 0x66, 0x90, 0xa2, 0xce,
|
0xa7, 0x1a, 0x20, 0x75, 0x5c, 0x2a, 0x33, 0x45, 0x84, 0x4b, 0xf8, 0x6c, 0x00, 0x3f, 0x0f, 0x39,
|
||||||
0x1b, 0xf8, 0x96, 0xd0, 0x41, 0x27, 0x23, 0xfb, 0xc4, 0xdf, 0xf3, 0x5c, 0x9a, 0xe6, 0xab, 0xba,
|
0xe2, 0x38, 0xb6, 0xc3, 0x0c, 0x52, 0xd4, 0x79, 0x03, 0xdf, 0x12, 0x3a, 0xe8, 0x64, 0x64, 0x9f,
|
||||||
0x03, 0x73, 0x21, 0xae, 0x54, 0x31, 0xf2, 0x2e, 0x5c, 0x64, 0xc2, 0x76, 0x08, 0x19, 0xac, 0xf7,
|
0xf8, 0x7b, 0x9e, 0x4b, 0xd3, 0x7c, 0x55, 0x77, 0x60, 0x2e, 0xc4, 0x95, 0x2a, 0x46, 0xde, 0x85,
|
||||||
0xcc, 0x51, 0x22, 0xea, 0x00, 0x2e, 0x45, 0x19, 0xbf, 0x5e, 0x1b, 0xe1, 0x77, 0x04, 0xe2, 0xa1,
|
0x8b, 0x4c, 0xd8, 0x0e, 0x21, 0x83, 0xf5, 0x9e, 0x39, 0x4a, 0x44, 0x1d, 0xc0, 0xa5, 0x28, 0xe3,
|
||||||
0xd9, 0x27, 0x87, 0xf6, 0x6e, 0xb2, 0x6e, 0x34, 0xf0, 0xd1, 0xdb, 0xb2, 0x38, 0x4c, 0xd8, 0x37,
|
0x37, 0x6b, 0x23, 0xfc, 0x8e, 0x40, 0x3c, 0x34, 0xfb, 0xe4, 0xd0, 0xde, 0x4d, 0xd6, 0x8d, 0x06,
|
||||||
0xfe, 0xa5, 0x06, 0x97, 0xc7, 0x86, 0x7f, 0xcd, 0xab, 0xba, 0x08, 0xd0, 0xa5, 0xdb, 0x87, 0x74,
|
0x3e, 0x7a, 0xa1, 0x16, 0x87, 0x09, 0xfb, 0xc6, 0xbf, 0xd2, 0xe0, 0xf2, 0xd8, 0xf0, 0x6f, 0x78,
|
||||||
0x68, 0x07, 0xbf, 0xe7, 0x29, 0x14, 0x5f, 0x4f, 0x1a, 0x3b, 0xca, 0x42, 0xcf, 0x63, 0xc8, 0x3f,
|
0x55, 0x17, 0x01, 0xba, 0x74, 0xfb, 0x90, 0x0e, 0xed, 0xe0, 0x57, 0x41, 0x85, 0xe2, 0xeb, 0x49,
|
||||||
0x61, 0x25, 0x16, 0x65, 0x56, 0xd3, 0x72, 0x56, 0x96, 0xd1, 0xe7, 0x17, 0xbf, 0xa2, 0xce, 0xbe,
|
0x63, 0x47, 0x59, 0xe8, 0x79, 0x0c, 0xf9, 0x27, 0xac, 0x0a, 0xa3, 0xcc, 0x6a, 0x5a, 0xce, 0xca,
|
||||||
0xd9, 0xd1, 0x49, 0x88, 0xf3, 0x4c, 0xdf, 0xe5, 0x47, 0x74, 0x51, 0xf7, 0xdb, 0x14, 0xbd, 0xdd,
|
0x32, 0xfa, 0xfc, 0x6e, 0x58, 0xd4, 0xd9, 0x37, 0x3b, 0x3a, 0x09, 0x71, 0x9e, 0xe9, 0xbb, 0xfc,
|
||||||
0x33, 0x89, 0xe5, 0xb1, 0xde, 0x69, 0xd6, 0xab, 0x50, 0xf0, 0x0a, 0xd4, 0x38, 0xd2, 0x7a, 0xa7,
|
0x88, 0x2e, 0xea, 0x7e, 0x9b, 0xa2, 0xb7, 0x7b, 0x26, 0xb1, 0x3c, 0xd6, 0x3b, 0xcd, 0x7a, 0x15,
|
||||||
0xa3, 0x1c, 0xd3, 0xbe, 0x3c, 0x2d, 0x2c, 0x0f, 0xbf, 0x84, 0x0b, 0x0a, 0x7f, 0x2a, 0xd3, 0xbd,
|
0x0a, 0x5e, 0x81, 0x1a, 0x47, 0x5a, 0xef, 0x74, 0x94, 0x63, 0xda, 0x97, 0xa7, 0x85, 0xe5, 0xe1,
|
||||||
0x06, 0x79, 0x5e, 0x47, 0x12, 0x27, 0xc4, 0x7c, 0x78, 0x14, 0x87, 0xd1, 0x05, 0x0f, 0xbe, 0x0d,
|
0x97, 0x70, 0x41, 0xe1, 0x4f, 0x65, 0xba, 0xd7, 0x20, 0xcf, 0x4b, 0x4d, 0xe2, 0x84, 0x98, 0x0f,
|
||||||
0x73, 0x82, 0x42, 0xfa, 0x76, 0xdc, 0xaa, 0x33, 0xfb, 0xe0, 0x5d, 0x98, 0x0f, 0xb3, 0xa5, 0x72,
|
0x8f, 0xe2, 0x30, 0xba, 0xe0, 0xc1, 0xb7, 0x61, 0x4e, 0x50, 0x48, 0xdf, 0x8e, 0x5b, 0x75, 0x66,
|
||||||
0x84, 0x75, 0x09, 0xfa, 0x6c, 0xd0, 0x51, 0x0e, 0x9c, 0xe8, 0xa2, 0xa8, 0x06, 0xcb, 0x44, 0x0c,
|
0x1f, 0xbc, 0x0b, 0xf3, 0x61, 0xb6, 0x54, 0x8e, 0xb0, 0x2e, 0x41, 0x9f, 0x0d, 0x3a, 0xca, 0x81,
|
||||||
0xe6, 0x2b, 0x24, 0x45, 0xa4, 0x52, 0x68, 0x4e, 0x9a, 0x7f, 0xd7, 0x74, 0xfd, 0xb4, 0xe2, 0x13,
|
0x13, 0x5d, 0x14, 0xd5, 0x60, 0x99, 0x88, 0xc1, 0x7c, 0x85, 0xa4, 0x88, 0x54, 0x0a, 0xcd, 0x49,
|
||||||
0x40, 0x2a, 0x31, 0xd5, 0xa2, 0xac, 0x40, 0x81, 0x1b, 0x5c, 0x66, 0xae, 0xf1, 0xab, 0x22, 0x99,
|
0xf3, 0xef, 0x9a, 0xae, 0x9f, 0x56, 0x7c, 0x02, 0x48, 0x25, 0xa6, 0x5a, 0x94, 0x15, 0x28, 0x70,
|
||||||
0xa8, 0x42, 0x9b, 0xe4, 0x85, 0x63, 0x74, 0xfb, 0xc4, 0x8f, 0xac, 0x34, 0x5f, 0x53, 0x89, 0xa9,
|
0x83, 0xcb, 0xcc, 0x35, 0x7e, 0x55, 0x24, 0x13, 0x55, 0x68, 0x93, 0xbc, 0x70, 0x8c, 0x6e, 0x9f,
|
||||||
0x66, 0xfc, 0x7b, 0x0d, 0xca, 0xeb, 0x3d, 0xc3, 0xe9, 0x4b, 0xe3, 0xbf, 0x0b, 0x79, 0x9e, 0x08,
|
0xf8, 0x91, 0x95, 0xe6, 0x6b, 0x2a, 0x31, 0xd5, 0x8c, 0x7f, 0xaf, 0x41, 0x79, 0xbd, 0x67, 0x38,
|
||||||
0x8a, 0xbb, 0xd3, 0x9d, 0xb0, 0x18, 0x95, 0x97, 0x37, 0xd6, 0x79, 0xda, 0x28, 0x46, 0xd1, 0xc5,
|
0x7d, 0x69, 0xfc, 0x77, 0x21, 0xcf, 0x13, 0x41, 0x71, 0x77, 0xba, 0x13, 0x16, 0xa3, 0xf2, 0xf2,
|
||||||
0x12, 0xe5, 0xcb, 0xcd, 0x48, 0x39, 0x73, 0x13, 0xbd, 0x0e, 0x39, 0x83, 0x0e, 0x61, 0xfe, 0x5b,
|
0xc6, 0x3a, 0x4f, 0x1b, 0xc5, 0x28, 0xba, 0x58, 0xa2, 0xc2, 0xb9, 0x19, 0xa9, 0x78, 0x6e, 0xa2,
|
||||||
0x8d, 0xa6, 0xe0, 0x4c, 0x1a, 0x3b, 0xb4, 0x39, 0x17, 0x7e, 0x1b, 0x4a, 0x0a, 0x02, 0xbd, 0x59,
|
0xd7, 0x21, 0x67, 0xd0, 0x21, 0xcc, 0x7f, 0xab, 0xd1, 0x14, 0x9c, 0x49, 0x63, 0x87, 0x36, 0xe7,
|
||||||
0x3c, 0x6e, 0x8a, 0x83, 0x79, 0x7d, 0xe3, 0x70, 0xfb, 0x39, 0xbf, 0x70, 0x54, 0x01, 0x36, 0x9b,
|
0xc2, 0x6f, 0x43, 0x49, 0x41, 0xa0, 0x37, 0x8b, 0xc7, 0x4d, 0x71, 0x30, 0xaf, 0x6f, 0x1c, 0x6e,
|
||||||
0x7e, 0x3b, 0x83, 0x3f, 0x14, 0xa3, 0x84, 0x87, 0xab, 0xfa, 0x68, 0x49, 0xfa, 0x64, 0x5e, 0x49,
|
0x3f, 0xe7, 0x17, 0x8e, 0x2a, 0xc0, 0x66, 0xd3, 0x6f, 0x67, 0xf0, 0x87, 0x62, 0x94, 0xf0, 0x70,
|
||||||
0x9f, 0x53, 0xa8, 0x88, 0xe9, 0xa7, 0xda, 0x03, 0x6f, 0x42, 0x9e, 0xc9, 0x93, 0x5b, 0x60, 0x21,
|
0x55, 0x1f, 0x2d, 0x49, 0x9f, 0xcc, 0x2b, 0xe9, 0x73, 0x0a, 0x15, 0x31, 0xfd, 0x54, 0x7b, 0xe0,
|
||||||
0x06, 0x56, 0x7a, 0x27, 0x67, 0xc4, 0xb3, 0x50, 0x39, 0xf0, 0x0c, 0x6f, 0xe8, 0xca, 0x2d, 0xf0,
|
0x4d, 0xc8, 0x33, 0x79, 0x72, 0x0b, 0x2c, 0xc4, 0xc0, 0x4a, 0xef, 0xe4, 0x8c, 0x78, 0x16, 0x2a,
|
||||||
0x3b, 0x0d, 0xaa, 0x92, 0x92, 0xb6, 0xcc, 0x22, 0xaf, 0xa7, 0x3c, 0xe6, 0xf9, 0x97, 0xd3, 0x4b,
|
0x07, 0x9e, 0xe1, 0x0d, 0x5d, 0xb9, 0x05, 0x7e, 0xa7, 0x41, 0x55, 0x52, 0xd2, 0x96, 0x59, 0xe4,
|
||||||
0x90, 0xef, 0x1c, 0x1d, 0x98, 0x9f, 0xc8, 0x7a, 0x97, 0x68, 0x51, 0x7a, 0x8f, 0xe3, 0xf0, 0xa2,
|
0xf5, 0x94, 0xc7, 0x3c, 0xff, 0x72, 0x7a, 0x09, 0xf2, 0x9d, 0xa3, 0x03, 0xf3, 0x13, 0x59, 0x12,
|
||||||
0xb3, 0x68, 0xd1, 0x8b, 0x8e, 0x63, 0xbc, 0xf0, 0xb6, 0xad, 0x0e, 0x39, 0x65, 0xf9, 0xc4, 0xb4,
|
0x13, 0x2d, 0x4a, 0xef, 0x71, 0x1c, 0x5e, 0x97, 0x16, 0x2d, 0x7a, 0xd1, 0x71, 0x8c, 0x17, 0xde,
|
||||||
0x1e, 0x10, 0xd8, 0xdd, 0x44, 0x14, 0xa7, 0x59, 0xfe, 0xa5, 0x16, 0xab, 0xe7, 0xe0, 0xc2, 0xfa,
|
0xb6, 0xd5, 0x21, 0xa7, 0x2c, 0x9f, 0x98, 0xd6, 0x03, 0x02, 0xbb, 0x9b, 0x88, 0xfa, 0x35, 0xcb,
|
||||||
0xd0, 0x3b, 0x6e, 0x5a, 0xc6, 0x51, 0x4f, 0x06, 0x01, 0x3c, 0x0f, 0x88, 0x12, 0x37, 0x4d, 0x57,
|
0xbf, 0xd4, 0x7a, 0xf6, 0x1c, 0x5c, 0x58, 0x1f, 0x7a, 0xc7, 0x4d, 0xcb, 0x38, 0xea, 0xc9, 0x20,
|
||||||
0xa5, 0x36, 0x61, 0x8e, 0x52, 0x89, 0xe5, 0x99, 0x6d, 0x25, 0x62, 0xc8, 0xb0, 0xad, 0x45, 0xc2,
|
0x80, 0xe7, 0x01, 0x51, 0xe2, 0xa6, 0xe9, 0xaa, 0xd4, 0x26, 0xcc, 0x51, 0x2a, 0xb1, 0x3c, 0xb3,
|
||||||
0xb6, 0xe1, 0xba, 0x2f, 0x6d, 0xa7, 0x23, 0xa6, 0xe6, 0xb7, 0xf1, 0x26, 0x17, 0xfe, 0xcc, 0x0d,
|
0xad, 0x44, 0x0c, 0x19, 0xb6, 0xb5, 0x48, 0xd8, 0x36, 0x5c, 0xf7, 0xa5, 0xed, 0x74, 0xc4, 0xd4,
|
||||||
0x05, 0xe6, 0xaf, 0x2a, 0x65, 0x39, 0x90, 0xf2, 0x98, 0x78, 0x13, 0xa4, 0xe0, 0x07, 0x70, 0x51,
|
0xfc, 0x36, 0xde, 0xe4, 0xc2, 0x9f, 0xb9, 0xa1, 0xc0, 0xfc, 0x75, 0xa5, 0x2c, 0x07, 0x52, 0x1e,
|
||||||
0x72, 0x8a, 0xfa, 0xc5, 0x04, 0xe6, 0x7d, 0xb8, 0x26, 0x99, 0x37, 0x8e, 0x69, 0x56, 0xfd, 0x54,
|
0x13, 0x6f, 0x82, 0x14, 0xfc, 0x00, 0x2e, 0x4a, 0x4e, 0x51, 0xbf, 0x98, 0xc0, 0xbc, 0x0f, 0xd7,
|
||||||
0x00, 0xfe, 0xbb, 0x7a, 0x3e, 0x82, 0xba, 0xaf, 0x27, 0xcb, 0xb4, 0xec, 0x9e, 0xaa, 0xc0, 0xd0,
|
0x24, 0xf3, 0xc6, 0x31, 0xcd, 0xaa, 0x9f, 0x0a, 0xc0, 0x7f, 0x57, 0xcf, 0x47, 0x50, 0xf7, 0xf5,
|
||||||
0x15, 0x7b, 0xa6, 0xa8, 0xb3, 0x6f, 0x4a, 0x73, 0xec, 0x9e, 0x7f, 0x08, 0xd2, 0x6f, 0xbc, 0x01,
|
0x64, 0x99, 0x96, 0xdd, 0x53, 0x15, 0x18, 0xba, 0x62, 0xcf, 0x14, 0x75, 0xf6, 0x4d, 0x69, 0x8e,
|
||||||
0x0b, 0x52, 0x86, 0xc8, 0x81, 0xc2, 0x42, 0xc6, 0x14, 0x8a, 0x13, 0x22, 0x0c, 0x46, 0x87, 0x4e,
|
0xdd, 0xf3, 0x0f, 0x41, 0xfa, 0x8d, 0x37, 0x60, 0x41, 0xca, 0x10, 0x39, 0x50, 0x58, 0xc8, 0x98,
|
||||||
0x36, 0xbb, 0xca, 0x19, 0x36, 0x2d, 0x93, 0xa9, 0x29, 0x32, 0x2f, 0xf2, 0x1d, 0x41, 0x15, 0x53,
|
0x42, 0x71, 0x42, 0x84, 0xc1, 0xe8, 0xd0, 0xc9, 0x66, 0x57, 0x39, 0xc3, 0xa6, 0x65, 0x32, 0x35,
|
||||||
0x83, 0xb6, 0x20, 0x53, 0x01, 0x2a, 0x59, 0x2c, 0x04, 0x25, 0x8f, 0x2d, 0xc4, 0x98, 0xe8, 0x8f,
|
0x45, 0xe6, 0x45, 0xbe, 0x23, 0xa8, 0x62, 0x6a, 0xd0, 0x16, 0x64, 0x2a, 0x40, 0x25, 0x8b, 0x85,
|
||||||
0x60, 0xd1, 0x57, 0x82, 0xda, 0xed, 0x29, 0x71, 0xfa, 0xa6, 0xeb, 0x2a, 0x37, 0xee, 0xb8, 0x89,
|
0xa0, 0xe4, 0xb1, 0x85, 0x18, 0x13, 0xfd, 0x11, 0x2c, 0xfa, 0x4a, 0x50, 0xbb, 0x3d, 0x25, 0x4e,
|
||||||
0xdf, 0x81, 0xe9, 0x01, 0x11, 0x31, 0xa5, 0xb4, 0x86, 0x56, 0xf8, 0x13, 0xd2, 0x8a, 0x32, 0x98,
|
0xdf, 0x74, 0x5d, 0xe5, 0xc6, 0x1d, 0x37, 0xf1, 0x3b, 0x30, 0x3d, 0x20, 0x22, 0xa6, 0x94, 0xd6,
|
||||||
0xf5, 0xe3, 0x0e, 0x5c, 0x97, 0xd2, 0xb9, 0x45, 0x63, 0xc5, 0x47, 0x95, 0x92, 0xb7, 0x31, 0x6e,
|
0xd0, 0x0a, 0x7f, 0x65, 0x5a, 0x51, 0x06, 0xb3, 0x7e, 0xdc, 0x81, 0xeb, 0x52, 0x3a, 0xb7, 0x68,
|
||||||
0xd6, 0xf1, 0xdb, 0x58, 0x96, 0xaf, 0xbd, 0xbc, 0x8d, 0xd1, 0xb3, 0x42, 0xf5, 0xad, 0x54, 0x67,
|
0xac, 0xf8, 0xa8, 0x52, 0xf2, 0x36, 0xc6, 0xcd, 0x3a, 0x7e, 0x1b, 0xcb, 0xf2, 0xb5, 0x97, 0xb7,
|
||||||
0xc5, 0x0e, 0xb7, 0xa9, 0xef, 0x92, 0xa9, 0x84, 0x1d, 0xc1, 0x7c, 0xd8, 0x93, 0x53, 0x85, 0xb1,
|
0x31, 0x7a, 0x56, 0xa8, 0xbe, 0x95, 0xea, 0xac, 0xd8, 0xe1, 0x36, 0xf5, 0x5d, 0x32, 0x95, 0xb0,
|
||||||
0x79, 0xc8, 0x79, 0xf6, 0x09, 0x91, 0x41, 0x8c, 0x37, 0xa4, 0xc2, 0xbe, 0x9b, 0xa7, 0x52, 0xd8,
|
0x23, 0x98, 0x0f, 0x7b, 0x72, 0xaa, 0x30, 0x36, 0x0f, 0x39, 0xcf, 0x3e, 0x21, 0x32, 0x88, 0xf1,
|
||||||
0x08, 0x84, 0xb1, 0x2d, 0x99, 0x56, 0x5f, 0xba, 0x9a, 0x32, 0x9f, 0xe1, 0x0d, 0xbc, 0x07, 0x97,
|
0x86, 0x54, 0xd8, 0x77, 0xf3, 0x54, 0x0a, 0x1b, 0x81, 0x30, 0xb6, 0x25, 0xd3, 0xea, 0x4b, 0x57,
|
||||||
0xa2, 0x61, 0x22, 0x95, 0xca, 0xcf, 0xf9, 0x06, 0x8e, 0x8b, 0x24, 0xa9, 0xe4, 0xbe, 0x1f, 0x04,
|
0x53, 0xe6, 0x33, 0xbc, 0x81, 0xf7, 0xe0, 0x52, 0x34, 0x4c, 0xa4, 0x52, 0xf9, 0x39, 0xdf, 0xc0,
|
||||||
0x03, 0x25, 0xa0, 0xa4, 0x12, 0xa9, 0x43, 0x23, 0x2e, 0xbe, 0xfc, 0x27, 0xf6, 0xab, 0x1f, 0x6e,
|
0x71, 0x91, 0x24, 0x95, 0xdc, 0xf7, 0x83, 0x60, 0xa0, 0x04, 0x94, 0x54, 0x22, 0x75, 0x68, 0xc4,
|
||||||
0x52, 0x09, 0x73, 0x03, 0x61, 0xe9, 0x97, 0x3f, 0x88, 0x11, 0xd9, 0x89, 0x31, 0x42, 0x38, 0x49,
|
0xc5, 0x97, 0xff, 0xc4, 0x7e, 0xf5, 0xc3, 0x4d, 0x2a, 0x61, 0x6e, 0x20, 0x2c, 0xfd, 0xf2, 0x07,
|
||||||
0x10, 0xc5, 0xbe, 0x86, 0x4d, 0x27, 0x30, 0x82, 0x00, 0x9a, 0x16, 0x83, 0x9e, 0x21, 0x3e, 0x06,
|
0x31, 0x22, 0x3b, 0x31, 0x46, 0x08, 0x27, 0x09, 0xa2, 0xd8, 0x37, 0xb0, 0xe9, 0x04, 0x46, 0x10,
|
||||||
0x6b, 0xc8, 0x8d, 0xad, 0x86, 0xdd, 0x54, 0x8b, 0xf1, 0x41, 0x10, 0x3b, 0xc7, 0x22, 0x73, 0x2a,
|
0x40, 0xd3, 0x62, 0xd0, 0x33, 0xc4, 0xc7, 0x60, 0x0d, 0xb9, 0xb1, 0xd5, 0xb0, 0x9b, 0x6a, 0x31,
|
||||||
0xc1, 0x1f, 0xc2, 0x52, 0x72, 0x50, 0x4e, 0x23, 0xf9, 0x3e, 0x86, 0xa2, 0x9f, 0x50, 0x2a, 0xcf,
|
0x3e, 0x08, 0x62, 0xe7, 0x58, 0x64, 0x4e, 0x25, 0xf8, 0x43, 0x58, 0x4a, 0x0e, 0xca, 0x69, 0x24,
|
||||||
0xaf, 0x25, 0x28, 0xec, 0xed, 0x1f, 0x3c, 0x5d, 0xdf, 0x68, 0xd6, 0xb4, 0xb5, 0x7f, 0x64, 0x21,
|
0xdf, 0xc7, 0x50, 0xf4, 0x13, 0x4a, 0xe5, 0x85, 0xb6, 0x04, 0x85, 0xbd, 0xfd, 0x83, 0xa7, 0xeb,
|
||||||
0xb3, 0xf3, 0x1c, 0x7d, 0x03, 0x72, 0xfc, 0xe1, 0x65, 0xc2, 0xbb, 0x54, 0x63, 0xd2, 0x13, 0x0e,
|
0x1b, 0xcd, 0x9a, 0xb6, 0xf6, 0x8f, 0x2c, 0x64, 0x76, 0x9e, 0xa3, 0x6f, 0x41, 0x8e, 0x3f, 0xbc,
|
||||||
0xbe, 0xfa, 0xe9, 0x1f, 0xff, 0xf2, 0x45, 0xe6, 0x12, 0xbe, 0xb0, 0x3a, 0x7a, 0xcb, 0xe8, 0x0d,
|
0x4c, 0x78, 0x97, 0x6a, 0x4c, 0x7a, 0xc2, 0xc1, 0x57, 0x3f, 0xfd, 0xe3, 0x5f, 0xbe, 0xc8, 0x5c,
|
||||||
0x8e, 0x8d, 0xd5, 0x93, 0xd1, 0x2a, 0x3b, 0x13, 0x1e, 0x6a, 0xf7, 0xd1, 0x73, 0xc8, 0x3e, 0x1d,
|
0xc2, 0x17, 0x56, 0x47, 0x6f, 0x19, 0xbd, 0xc1, 0xb1, 0xb1, 0x7a, 0x32, 0x5a, 0x65, 0x67, 0xc2,
|
||||||
0x7a, 0x28, 0xf1, 0xd1, 0xaa, 0x91, 0xfc, 0xb4, 0x83, 0x1b, 0x4c, 0xf2, 0x3c, 0x9e, 0x55, 0x25,
|
0x43, 0xed, 0x3e, 0x7a, 0x0e, 0xd9, 0xa7, 0x43, 0x0f, 0x25, 0x3e, 0x5a, 0x35, 0x92, 0x9f, 0x76,
|
||||||
0x0f, 0x86, 0x1e, 0x95, 0x3b, 0x82, 0x92, 0xf2, 0x3a, 0x83, 0xce, 0x7d, 0xce, 0x6a, 0x9c, 0xff,
|
0x70, 0x83, 0x49, 0x9e, 0xc7, 0xb3, 0xaa, 0xe4, 0xc1, 0xd0, 0xa3, 0x72, 0x47, 0x50, 0x52, 0x5e,
|
||||||
0xf2, 0x83, 0x31, 0xc3, 0xbb, 0x8a, 0x2f, 0xab, 0x78, 0xfc, 0x11, 0x49, 0x9d, 0xcf, 0xe1, 0xa9,
|
0x67, 0xd0, 0xb9, 0xcf, 0x59, 0x8d, 0xf3, 0x5f, 0x7e, 0x30, 0x66, 0x78, 0x57, 0xf1, 0x65, 0x15,
|
||||||
0x15, 0x9d, 0x4f, 0xf0, 0xc0, 0x10, 0x9d, 0x8f, 0x52, 0xd4, 0x8f, 0x9f, 0x8f, 0x77, 0x6a, 0x51,
|
0x8f, 0x3f, 0x22, 0xa9, 0xf3, 0x39, 0x3c, 0xb5, 0xa2, 0xf3, 0x09, 0x1e, 0x18, 0xa2, 0xf3, 0x51,
|
||||||
0xb9, 0xb6, 0x78, 0x51, 0x6a, 0x7b, 0xe8, 0x7a, 0xcc, 0x8b, 0x84, 0x5a, 0x7b, 0x6f, 0x2c, 0x25,
|
0x8a, 0xfa, 0xf1, 0xf3, 0xf1, 0x4e, 0x2d, 0x2a, 0xd7, 0x16, 0x2f, 0x4a, 0x6d, 0x0f, 0x5d, 0x8f,
|
||||||
0x33, 0x08, 0xa4, 0x1b, 0x0c, 0xe9, 0x0a, 0xbe, 0xa4, 0x22, 0xb5, 0x7d, 0xbe, 0x87, 0xda, 0xfd,
|
0x79, 0x91, 0x50, 0x6b, 0xef, 0x8d, 0xa5, 0x64, 0x06, 0x81, 0x74, 0x83, 0x21, 0x5d, 0xc1, 0x97,
|
||||||
0xb5, 0x63, 0xc8, 0xb1, 0x8a, 0x21, 0x6a, 0xc9, 0x8f, 0x46, 0x4c, 0xad, 0x33, 0x61, 0x07, 0x84,
|
0x54, 0xa4, 0xb6, 0xcf, 0xf7, 0x50, 0xbb, 0xbf, 0x76, 0x0c, 0x39, 0x56, 0x31, 0x44, 0x2d, 0xf9,
|
||||||
0x6a, 0x8d, 0x78, 0x81, 0xa1, 0xcd, 0xe1, 0xaa, 0x8f, 0xc6, 0x8a, 0x86, 0x0f, 0xb5, 0xfb, 0xcb,
|
0xd1, 0x88, 0xa9, 0x75, 0x26, 0xec, 0x80, 0x50, 0xad, 0x11, 0x2f, 0x30, 0xb4, 0x39, 0x5c, 0xf5,
|
||||||
0xda, 0x1b, 0xda, 0xda, 0x77, 0xa7, 0x21, 0xc7, 0x2a, 0x35, 0x68, 0x00, 0x10, 0xd4, 0xe0, 0xa2,
|
0xd1, 0x58, 0xd1, 0xf0, 0xa1, 0x76, 0x7f, 0x59, 0x7b, 0x43, 0x5b, 0xfb, 0xfe, 0x34, 0xe4, 0x58,
|
||||||
0xf3, 0x1c, 0xab, 0xea, 0x45, 0xe7, 0x39, 0x5e, 0xbe, 0xc3, 0xd7, 0x19, 0xf2, 0x02, 0x9e, 0xf7,
|
0xa5, 0x06, 0x0d, 0x00, 0x82, 0x1a, 0x5c, 0x74, 0x9e, 0x63, 0x55, 0xbd, 0xe8, 0x3c, 0xc7, 0xcb,
|
||||||
0x91, 0xd9, 0x43, 0xf9, 0x2a, 0xab, 0xc9, 0x50, 0xb3, 0xbe, 0x84, 0x92, 0x52, 0x4b, 0x43, 0x71,
|
0x77, 0xf8, 0x3a, 0x43, 0x5e, 0xc0, 0xf3, 0x3e, 0x32, 0x7b, 0xff, 0x5e, 0x65, 0x35, 0x19, 0x6a,
|
||||||
0x12, 0x43, 0xc5, 0xb8, 0xe8, 0x36, 0x89, 0x29, 0xc4, 0xe1, 0x9b, 0x0c, 0xf4, 0x1a, 0xae, 0xab,
|
0xd6, 0x97, 0x50, 0x52, 0x6a, 0x69, 0x28, 0x4e, 0x62, 0xa8, 0x18, 0x17, 0xdd, 0x26, 0x31, 0x85,
|
||||||
0xc6, 0xe5, 0xb8, 0x0e, 0xe3, 0xa4, 0xc0, 0x9f, 0x69, 0x50, 0x0d, 0xd7, 0xd3, 0xd0, 0xcd, 0x18,
|
0x38, 0x7c, 0x93, 0x81, 0x5e, 0xc3, 0x75, 0xd5, 0xb8, 0x1c, 0xd7, 0x61, 0x9c, 0x14, 0xf8, 0x33,
|
||||||
0xd1, 0xd1, 0xb2, 0x5c, 0xe3, 0xd6, 0x64, 0xa6, 0x44, 0x15, 0x38, 0xfe, 0x09, 0x21, 0x03, 0x83,
|
0x0d, 0xaa, 0xe1, 0x7a, 0x1a, 0xba, 0x19, 0x23, 0x3a, 0x5a, 0x96, 0x6b, 0xdc, 0x9a, 0xcc, 0x94,
|
||||||
0x72, 0x0a, 0xdb, 0xa3, 0xef, 0x6b, 0x30, 0x1b, 0xa9, 0x92, 0xa1, 0x38, 0x88, 0xb1, 0x1a, 0x5c,
|
0xa8, 0x02, 0xc7, 0x3f, 0x21, 0x64, 0x60, 0x50, 0x4e, 0x61, 0x7b, 0xf4, 0x03, 0x0d, 0x66, 0x23,
|
||||||
0xe3, 0xf6, 0x39, 0x5c, 0x42, 0x93, 0xbb, 0x4c, 0x93, 0x1b, 0xf8, 0xea, 0xb8, 0x31, 0x3c, 0xb3,
|
0x55, 0x32, 0x14, 0x07, 0x31, 0x56, 0x83, 0x6b, 0xdc, 0x3e, 0x87, 0x4b, 0x68, 0x72, 0x97, 0x69,
|
||||||
0x4f, 0x3c, 0x5b, 0x68, 0xb3, 0xf6, 0xcf, 0x2c, 0x14, 0x36, 0xf8, 0x2f, 0x91, 0x90, 0x07, 0x45,
|
0x72, 0x03, 0x5f, 0x1d, 0x37, 0x86, 0x67, 0xf6, 0x89, 0x67, 0x0b, 0x6d, 0xd6, 0xfe, 0x99, 0x85,
|
||||||
0xbf, 0xf2, 0x84, 0x16, 0xe3, 0xaa, 0x12, 0x41, 0xca, 0xde, 0xb8, 0x9e, 0xd8, 0x2f, 0x54, 0xb8,
|
0xc2, 0x06, 0xff, 0xb1, 0x12, 0xf2, 0xa0, 0xe8, 0x57, 0x9e, 0xd0, 0x62, 0x5c, 0x55, 0x22, 0x48,
|
||||||
0xc3, 0x54, 0x58, 0xc2, 0x57, 0x7c, 0x15, 0xc4, 0x2f, 0x9e, 0x56, 0xf9, 0xe5, 0x7b, 0xd5, 0xe8,
|
0xd9, 0x1b, 0xd7, 0x13, 0xfb, 0x85, 0x0a, 0x77, 0x98, 0x0a, 0x4b, 0xf8, 0x8a, 0xaf, 0x82, 0xf8,
|
||||||
0x74, 0xe8, 0x92, 0x7c, 0x47, 0x83, 0xb2, 0x5a, 0x50, 0x42, 0x37, 0x62, 0xeb, 0x21, 0x6a, 0x4d,
|
0x51, 0xd4, 0x2a, 0xbf, 0x7c, 0xaf, 0x1a, 0x9d, 0x0e, 0x5d, 0x92, 0xef, 0x69, 0x50, 0x56, 0x0b,
|
||||||
0xaa, 0x81, 0x27, 0xb1, 0x08, 0xfc, 0x7b, 0x0c, 0xff, 0x26, 0x5e, 0x4c, 0xc2, 0x77, 0x18, 0x7f,
|
0x4a, 0xe8, 0x46, 0x6c, 0x3d, 0x44, 0xad, 0x49, 0x35, 0xf0, 0x24, 0x16, 0x81, 0x7f, 0x8f, 0xe1,
|
||||||
0x58, 0x05, 0x5e, 0x42, 0x8a, 0x57, 0x21, 0x54, 0xa1, 0x8a, 0x57, 0x21, 0x5c, 0x81, 0x3a, 0x5f,
|
0xdf, 0xc4, 0x8b, 0x49, 0xf8, 0x0e, 0xe3, 0x0f, 0xab, 0xc0, 0x4b, 0x48, 0xf1, 0x2a, 0x84, 0x2a,
|
||||||
0x85, 0x21, 0xe3, 0xa7, 0x2a, 0x9c, 0x02, 0x04, 0x15, 0x26, 0x14, 0x6b, 0x5c, 0xe5, 0x12, 0x13,
|
0x54, 0xf1, 0x2a, 0x84, 0x2b, 0x50, 0xe7, 0xab, 0x30, 0x64, 0xfc, 0x54, 0x85, 0x53, 0x80, 0xa0,
|
||||||
0xf5, 0xc1, 0xf1, 0xe2, 0x54, 0xcc, 0x0e, 0x88, 0x60, 0xf7, 0x4c, 0x97, 0xfa, 0xe2, 0xda, 0x6f,
|
0xc2, 0x84, 0x62, 0x8d, 0xab, 0x5c, 0x62, 0xa2, 0x3e, 0x38, 0x5e, 0x9c, 0x8a, 0xd9, 0x01, 0x11,
|
||||||
0xa6, 0xa1, 0xf4, 0xc4, 0x30, 0x2d, 0x8f, 0x58, 0x86, 0xd5, 0x26, 0xa8, 0x0b, 0x39, 0x76, 0x4a,
|
0xec, 0x9e, 0xe9, 0x52, 0x5f, 0x5c, 0xfb, 0xcd, 0x34, 0x94, 0x9e, 0x18, 0xa6, 0xe5, 0x11, 0xcb,
|
||||||
0x45, 0x03, 0x8f, 0x5a, 0xf6, 0x89, 0x06, 0x9e, 0x50, 0x4d, 0x04, 0xdf, 0x66, 0xd0, 0xd7, 0x71,
|
0xb0, 0xda, 0x04, 0x75, 0x21, 0xc7, 0x4e, 0xa9, 0x68, 0xe0, 0x51, 0xcb, 0x3e, 0xd1, 0xc0, 0x13,
|
||||||
0xc3, 0x87, 0xee, 0x07, 0xf2, 0x57, 0x59, 0x3d, 0x83, 0x4e, 0xf9, 0x04, 0xf2, 0xbc, 0x7e, 0x81,
|
0xaa, 0x89, 0xe0, 0xdb, 0x0c, 0xfa, 0x3a, 0x6e, 0xf8, 0xd0, 0xfd, 0x40, 0xfe, 0x2a, 0xab, 0x67,
|
||||||
0x22, 0xd2, 0x42, 0x75, 0x8e, 0xc6, 0xd5, 0xf8, 0xce, 0xc4, 0x5d, 0xa6, 0x62, 0xb9, 0x8c, 0x99,
|
0xd0, 0x29, 0x9f, 0x40, 0x9e, 0xd7, 0x2f, 0x50, 0x44, 0x5a, 0xa8, 0xce, 0xd1, 0xb8, 0x1a, 0xdf,
|
||||||
0x82, 0x7d, 0x13, 0x20, 0x28, 0x98, 0x45, 0xed, 0x3b, 0x56, 0x5f, 0x6b, 0x2c, 0x25, 0x33, 0x08,
|
0x99, 0xb8, 0xcb, 0x54, 0x2c, 0x97, 0x31, 0x53, 0xb0, 0x6f, 0x03, 0x04, 0x05, 0xb3, 0xa8, 0x7d,
|
||||||
0xe0, 0xfb, 0x0c, 0xf8, 0x16, 0xbe, 0x1e, 0x0b, 0xdc, 0xf1, 0x07, 0x50, 0xf0, 0x36, 0x4c, 0x6f,
|
0xc7, 0xea, 0x6b, 0x8d, 0xa5, 0x64, 0x06, 0x01, 0x7c, 0x9f, 0x01, 0xdf, 0xc2, 0xd7, 0x63, 0x81,
|
||||||
0x19, 0xee, 0x31, 0x8a, 0x1c, 0x42, 0xca, 0x2b, 0x69, 0xa3, 0x11, 0xd7, 0x25, 0xa0, 0x6e, 0x31,
|
0x3b, 0xfe, 0x00, 0x0a, 0xde, 0x86, 0xe9, 0x2d, 0xc3, 0x3d, 0x46, 0x91, 0x43, 0x48, 0x79, 0x25,
|
||||||
0xa8, 0x45, 0xbc, 0x10, 0x0b, 0x75, 0x6c, 0xb8, 0x34, 0xa6, 0xa3, 0x21, 0xcc, 0xc8, 0x97, 0x4f,
|
0x6d, 0x34, 0xe2, 0xba, 0x04, 0xd4, 0x2d, 0x06, 0xb5, 0x88, 0x17, 0x62, 0xa1, 0x8e, 0x0d, 0x97,
|
||||||
0x74, 0x2d, 0x62, 0xb3, 0xf0, 0x2b, 0x69, 0x63, 0x31, 0xa9, 0x5b, 0x00, 0x2e, 0x33, 0x40, 0x8c,
|
0xc6, 0x74, 0x34, 0x84, 0x19, 0xf9, 0xf2, 0x89, 0xae, 0x45, 0x6c, 0x16, 0x7e, 0x25, 0x6d, 0x2c,
|
||||||
0xaf, 0xc5, 0x1b, 0x55, 0xb0, 0x3f, 0xd4, 0xee, 0xbf, 0xa1, 0xad, 0xfd, 0xb0, 0x06, 0xd3, 0x34,
|
0x26, 0x75, 0x0b, 0xc0, 0x65, 0x06, 0x88, 0xf1, 0xb5, 0x78, 0xa3, 0x0a, 0xf6, 0x87, 0xda, 0xfd,
|
||||||
0x5f, 0xa2, 0xa7, 0x48, 0x70, 0xcd, 0x8c, 0x5a, 0x78, 0xac, 0xb8, 0x13, 0xb5, 0xf0, 0xf8, 0x0d,
|
0x37, 0xb4, 0xb5, 0x1f, 0xd5, 0x60, 0x9a, 0xe6, 0x4b, 0xf4, 0x14, 0x09, 0xae, 0x99, 0x51, 0x0b,
|
||||||
0x35, 0xe6, 0x14, 0x61, 0xbf, 0xc7, 0x24, 0x8c, 0x8b, 0xce, 0xd8, 0x83, 0x92, 0x72, 0x19, 0x45,
|
0x8f, 0x15, 0x77, 0xa2, 0x16, 0x1e, 0xbf, 0xa1, 0xc6, 0x9c, 0x22, 0xec, 0x27, 0x9b, 0x84, 0x71,
|
||||||
0x31, 0x12, 0xc3, 0xa5, 0xa3, 0xe8, 0x29, 0x12, 0x73, 0x93, 0xc5, 0x4b, 0x0c, 0xb4, 0x81, 0x2f,
|
0xd1, 0x19, 0x7b, 0x50, 0x52, 0x2e, 0xa3, 0x28, 0x46, 0x62, 0xb8, 0x74, 0x14, 0x3d, 0x45, 0x62,
|
||||||
0x86, 0x41, 0x3b, 0x9c, 0x8d, 0xa2, 0x7e, 0x0b, 0xca, 0xea, 0xad, 0x15, 0xc5, 0x08, 0x8d, 0xd4,
|
0x6e, 0xb2, 0x78, 0x89, 0x81, 0x36, 0xf0, 0xc5, 0x30, 0x68, 0x87, 0xb3, 0x51, 0xd4, 0xef, 0x40,
|
||||||
0xa6, 0xa2, 0xb1, 0x22, 0xee, 0xd2, 0x1b, 0xe3, 0x34, 0xfe, 0xaf, 0x4f, 0x25, 0x2f, 0x45, 0xff,
|
0x59, 0xbd, 0xb5, 0xa2, 0x18, 0xa1, 0x91, 0xda, 0x54, 0x34, 0x56, 0xc4, 0x5d, 0x7a, 0x63, 0x9c,
|
||||||
0x18, 0x0a, 0xe2, 0x2e, 0x1b, 0x37, 0xdf, 0x70, 0x35, 0x2b, 0x6e, 0xbe, 0x91, 0x8b, 0x70, 0x4c,
|
0xc6, 0xff, 0x81, 0xaa, 0xe4, 0xa5, 0xe8, 0x1f, 0x43, 0x41, 0xdc, 0x65, 0xe3, 0xe6, 0x1b, 0xae,
|
||||||
0x4a, 0xc2, 0x60, 0x69, 0xce, 0x2e, 0x03, 0xb4, 0x80, 0x7c, 0x4c, 0xbc, 0x24, 0xc8, 0xa0, 0x3e,
|
0x66, 0xc5, 0xcd, 0x37, 0x72, 0x11, 0x8e, 0x49, 0x49, 0x18, 0x2c, 0xcd, 0xd9, 0x65, 0x80, 0x16,
|
||||||
0x93, 0x04, 0xa9, 0xdc, 0x97, 0x26, 0x42, 0x76, 0x89, 0x27, 0xf6, 0xb2, 0xbc, 0x8c, 0xa0, 0x04,
|
0x90, 0x8f, 0x89, 0x97, 0x04, 0x19, 0xd4, 0x67, 0x92, 0x20, 0x95, 0xfb, 0xd2, 0x44, 0xc8, 0x2e,
|
||||||
0x89, 0x6a, 0x34, 0xc4, 0x93, 0x58, 0x12, 0xb3, 0xc8, 0x00, 0x55, 0x84, 0x42, 0xf4, 0x6d, 0x80,
|
0xf1, 0xc4, 0x5e, 0x96, 0x97, 0x11, 0x94, 0x20, 0x51, 0x8d, 0x86, 0x78, 0x12, 0x4b, 0x62, 0x16,
|
||||||
0xe0, 0xe2, 0x1d, 0x4d, 0x0c, 0x62, 0xab, 0x77, 0xd1, 0xc4, 0x20, 0xfe, 0xee, 0x1e, 0xe3, 0xc1,
|
0x19, 0xa0, 0x8a, 0x50, 0x88, 0xbe, 0x0b, 0x10, 0x5c, 0xbc, 0xa3, 0x89, 0x41, 0x6c, 0xf5, 0x2e,
|
||||||
0x01, 0x38, 0xcf, 0x64, 0x29, 0xfc, 0x8f, 0x35, 0x40, 0xe3, 0x17, 0x75, 0xf4, 0x20, 0x1e, 0x22,
|
0x9a, 0x18, 0xc4, 0xdf, 0xdd, 0x63, 0x3c, 0x38, 0x00, 0xe7, 0x99, 0x2c, 0x85, 0xff, 0x89, 0x06,
|
||||||
0xb6, 0x30, 0xd8, 0x78, 0xed, 0xd5, 0x98, 0x13, 0xa3, 0x67, 0xa0, 0x57, 0x9b, 0x0d, 0x19, 0xbc,
|
0x68, 0xfc, 0xa2, 0x8e, 0x1e, 0xc4, 0x43, 0xc4, 0x16, 0x06, 0x1b, 0xaf, 0xbd, 0x1a, 0x73, 0x62,
|
||||||
0xa4, 0x9a, 0x7d, 0xae, 0x41, 0x25, 0x74, 0xd5, 0x47, 0x77, 0x12, 0xd6, 0x39, 0x52, 0x5c, 0x6c,
|
0xf4, 0x0c, 0xf4, 0x6a, 0xb3, 0x21, 0x83, 0x97, 0x54, 0xb3, 0xcf, 0x35, 0xa8, 0x84, 0xae, 0xfa,
|
||||||
0xdc, 0x3d, 0x97, 0x2f, 0x31, 0x77, 0x52, 0x76, 0x85, 0xcc, 0x1b, 0x7f, 0xa0, 0x41, 0x35, 0x5c,
|
0xe8, 0x4e, 0xc2, 0x3a, 0x47, 0x8a, 0x8b, 0x8d, 0xbb, 0xe7, 0xf2, 0x25, 0xe6, 0x4e, 0xca, 0xae,
|
||||||
0x1f, 0x40, 0x09, 0x00, 0x63, 0x15, 0xca, 0xc6, 0xf2, 0xf9, 0x8c, 0xaf, 0xb0, 0x5a, 0x41, 0x2a,
|
0x90, 0x79, 0xe3, 0x0f, 0x35, 0xa8, 0x86, 0xeb, 0x03, 0x28, 0x01, 0x60, 0xac, 0x42, 0xd9, 0x58,
|
||||||
0xf9, 0x31, 0x14, 0x44, 0x59, 0x21, 0xce, 0x2d, 0xc2, 0x05, 0xce, 0x38, 0xb7, 0x88, 0xd4, 0x24,
|
0x3e, 0x9f, 0xf1, 0x15, 0x56, 0x2b, 0x48, 0x25, 0x3f, 0x86, 0x82, 0x28, 0x2b, 0xc4, 0xb9, 0x45,
|
||||||
0x92, 0xdc, 0x82, 0xde, 0xd0, 0x15, 0x4f, 0x14, 0xc5, 0x87, 0x24, 0xc8, 0xc9, 0x9e, 0x18, 0xa9,
|
0xb8, 0xc0, 0x19, 0xe7, 0x16, 0x91, 0x9a, 0x44, 0x92, 0x5b, 0xd0, 0x1b, 0xba, 0xe2, 0x89, 0xa2,
|
||||||
0x5c, 0x4c, 0x84, 0x0c, 0x3c, 0x51, 0x96, 0x1e, 0x50, 0x82, 0xc4, 0x73, 0x3c, 0x31, 0x5a, 0xb9,
|
0xf8, 0x90, 0x04, 0x39, 0xd9, 0x13, 0x23, 0x95, 0x8b, 0x89, 0x90, 0x81, 0x27, 0xca, 0xd2, 0x03,
|
||||||
0x48, 0xf2, 0x44, 0x86, 0xaa, 0x78, 0x62, 0x50, 0x29, 0x88, 0xf3, 0xc4, 0xb1, 0xf2, 0x6d, 0x9c,
|
0x4a, 0x90, 0x78, 0x8e, 0x27, 0x46, 0x2b, 0x17, 0x49, 0x9e, 0xc8, 0x50, 0x15, 0x4f, 0x0c, 0x2a,
|
||||||
0x27, 0x8e, 0x17, 0x1b, 0x92, 0xd6, 0x96, 0x81, 0x87, 0x3c, 0x71, 0x2e, 0xa6, 0xb2, 0x80, 0x5e,
|
0x05, 0x71, 0x9e, 0x38, 0x56, 0xbe, 0x8d, 0xf3, 0xc4, 0xf1, 0x62, 0x43, 0xd2, 0xda, 0x32, 0xf0,
|
||||||
0x4b, 0xb0, 0x69, 0x6c, 0x69, 0xb8, 0xf1, 0xfa, 0x2b, 0x72, 0x4f, 0xf6, 0x00, 0xbe, 0x1a, 0xd2,
|
0x90, 0x27, 0xce, 0xc5, 0x54, 0x16, 0xd0, 0x6b, 0x09, 0x36, 0x8d, 0x2d, 0x0d, 0x37, 0x5e, 0x7f,
|
||||||
0x03, 0x7e, 0xae, 0xc1, 0x7c, 0x5c, 0x69, 0x02, 0x25, 0x80, 0x25, 0xd4, 0x95, 0x1b, 0x2b, 0xaf,
|
0x45, 0xee, 0xc9, 0x1e, 0xc0, 0x57, 0x43, 0x7a, 0xc0, 0x2f, 0x34, 0x98, 0x8f, 0x2b, 0x4d, 0xa0,
|
||||||
0xca, 0xfe, 0x0a, 0x76, 0xf3, 0x7d, 0xe2, 0x51, 0xed, 0xb7, 0x5f, 0x2e, 0x6a, 0x7f, 0xf8, 0x72,
|
0x04, 0xb0, 0x84, 0xba, 0x72, 0x63, 0xe5, 0x55, 0xd9, 0x5f, 0xc1, 0x6e, 0xbe, 0x4f, 0x3c, 0xaa,
|
||||||
0x51, 0xfb, 0xd3, 0x97, 0x8b, 0xda, 0x4f, 0xfe, 0xbc, 0x38, 0x75, 0x94, 0x67, 0xff, 0x29, 0xe2,
|
0xfd, 0xf6, 0xab, 0x45, 0xed, 0x0f, 0x5f, 0x2d, 0x6a, 0x7f, 0xfa, 0x6a, 0x51, 0xfb, 0xe9, 0x9f,
|
||||||
0xad, 0x7f, 0x05, 0x00, 0x00, 0xff, 0xff, 0x89, 0x96, 0x81, 0x80, 0x9b, 0x31, 0x00, 0x00,
|
0x17, 0xa7, 0x8e, 0xf2, 0xec, 0xff, 0x4d, 0xbc, 0xf5, 0xaf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x10,
|
||||||
|
0xb3, 0xfb, 0x25, 0xbe, 0x31, 0x00, 0x00,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ import (
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
|
|
||||||
"github.com/coreos/dbtester/pkg/types"
|
"github.com/coreos/etcd/pkg/types"
|
||||||
"github.com/coreos/pkg/capnslog"
|
"github.com/coreos/pkg/capnslog"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -27,7 +27,7 @@ func GetDefaultHost() (string, error) {
|
||||||
return "", fmt.Errorf("default host not supported on %s_%s", runtime.GOOS, runtime.GOARCH)
|
return "", fmt.Errorf("default host not supported on %s_%s", runtime.GOOS, runtime.GOARCH)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDefaultInterface fetches the device name of default routable interface.
|
// GetDefaultInterfaces fetches the device name of default routable interface.
|
||||||
func GetDefaultInterface() (map[string]uint8, error) {
|
func GetDefaultInterfaces() (map[string]uint8, error) {
|
||||||
return "", fmt.Errorf("default host not supported on %s_%s", runtime.GOOS, runtime.GOARCH)
|
return nil, fmt.Errorf("default host not supported on %s_%s", runtime.GOOS, runtime.GOARCH)
|
||||||
}
|
}
|
||||||
|
|
@ -23,7 +23,7 @@ import (
|
||||||
"net"
|
"net"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/coreos/dbtester/pkg/cpuutil"
|
"github.com/coreos/etcd/pkg/cpuutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
var errNoDefaultRoute = fmt.Errorf("could not find default route")
|
var errNoDefaultRoute = fmt.Errorf("could not find default route")
|
||||||
0
pkg/report/timeseries.go → vendor/github.com/coreos/etcd/pkg/report/timeseries.go
generated
vendored
0
pkg/report/timeseries.go → vendor/github.com/coreos/etcd/pkg/report/timeseries.go
generated
vendored
|
|
@ -1,16 +0,0 @@
|
||||||
// Copyright 2016 The etcd 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 tlsutil provides utility functions for handling TLS.
|
|
||||||
package tlsutil
|
|
||||||
|
|
@ -1,72 +0,0 @@
|
||||||
// Copyright 2016 The etcd 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 tlsutil
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/tls"
|
|
||||||
"crypto/x509"
|
|
||||||
"encoding/pem"
|
|
||||||
"io/ioutil"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewCertPool creates x509 certPool with provided CA files.
|
|
||||||
func NewCertPool(CAFiles []string) (*x509.CertPool, error) {
|
|
||||||
certPool := x509.NewCertPool()
|
|
||||||
|
|
||||||
for _, CAFile := range CAFiles {
|
|
||||||
pemByte, err := ioutil.ReadFile(CAFile)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
var block *pem.Block
|
|
||||||
block, pemByte = pem.Decode(pemByte)
|
|
||||||
if block == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
cert, err := x509.ParseCertificate(block.Bytes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
certPool.AddCert(cert)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return certPool, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewCert generates TLS cert by using the given cert,key and parse function.
|
|
||||||
func NewCert(certfile, keyfile string, parseFunc func([]byte, []byte) (tls.Certificate, error)) (*tls.Certificate, error) {
|
|
||||||
cert, err := ioutil.ReadFile(certfile)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
key, err := ioutil.ReadFile(keyfile)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if parseFunc == nil {
|
|
||||||
parseFunc = tls.X509KeyPair
|
|
||||||
}
|
|
||||||
|
|
||||||
tlsCert, err := parseFunc(cert, key)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &tlsCert, nil
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
// Copyright 2015 The etcd 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 version implements etcd version parsing and contains latest version
|
||||||
|
// information.
|
||||||
|
package version
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/coreos/go-semver/semver"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// MinClusterVersion is the min cluster version this etcd binary is compatible with.
|
||||||
|
MinClusterVersion = "3.0.0"
|
||||||
|
Version = "3.2.0+git"
|
||||||
|
APIVersion = "unknown"
|
||||||
|
|
||||||
|
// Git SHA Value will be set during build
|
||||||
|
GitSHA = "Not provided (use ./build instead of go build)"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
ver, err := semver.NewVersion(Version)
|
||||||
|
if err == nil {
|
||||||
|
APIVersion = fmt.Sprintf("%d.%d", ver.Major, ver.Minor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Versions struct {
|
||||||
|
Server string `json:"etcdserver"`
|
||||||
|
Cluster string `json:"etcdcluster"`
|
||||||
|
// TODO: raft state machine version
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cluster only keeps the major.minor.
|
||||||
|
func Cluster(v string) string {
|
||||||
|
vs := strings.Split(v, ".")
|
||||||
|
if len(vs) <= 2 {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s.%s", vs[0], vs[1])
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
|
||||||
Apache License
|
Apache License
|
||||||
Version 2.0, January 2004
|
Version 2.0, January 2004
|
||||||
http://www.apache.org/licenses/
|
http://www.apache.org/licenses/
|
||||||
|
|
@ -0,0 +1,209 @@
|
||||||
|
package semver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Version struct {
|
||||||
|
Major int64
|
||||||
|
Minor int64
|
||||||
|
Patch int64
|
||||||
|
PreRelease PreRelease
|
||||||
|
Metadata string
|
||||||
|
}
|
||||||
|
|
||||||
|
type PreRelease string
|
||||||
|
|
||||||
|
func splitOff(input *string, delim string) (val string) {
|
||||||
|
parts := strings.SplitN(*input, delim, 2)
|
||||||
|
|
||||||
|
if len(parts) == 2 {
|
||||||
|
*input = parts[0]
|
||||||
|
val = parts[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewVersion(version string) (*Version, error) {
|
||||||
|
v := Version{}
|
||||||
|
|
||||||
|
dotParts := strings.SplitN(version, ".", 3)
|
||||||
|
|
||||||
|
if len(dotParts) != 3 {
|
||||||
|
return nil, errors.New(fmt.Sprintf("%s is not in dotted-tri format", version))
|
||||||
|
}
|
||||||
|
|
||||||
|
v.Metadata = splitOff(&dotParts[2], "+")
|
||||||
|
v.PreRelease = PreRelease(splitOff(&dotParts[2], "-"))
|
||||||
|
|
||||||
|
parsed := make([]int64, 3, 3)
|
||||||
|
|
||||||
|
for i, v := range dotParts[:3] {
|
||||||
|
val, err := strconv.ParseInt(v, 10, 64)
|
||||||
|
parsed[i] = val
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
v.Major = parsed[0]
|
||||||
|
v.Minor = parsed[1]
|
||||||
|
v.Patch = parsed[2]
|
||||||
|
|
||||||
|
return &v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Must(v *Version, err error) *Version {
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Version) String() string {
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
|
||||||
|
base := fmt.Sprintf("%d.%d.%d", v.Major, v.Minor, v.Patch)
|
||||||
|
buffer.WriteString(base)
|
||||||
|
|
||||||
|
if v.PreRelease != "" {
|
||||||
|
buffer.WriteString(fmt.Sprintf("-%s", v.PreRelease))
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.Metadata != "" {
|
||||||
|
buffer.WriteString(fmt.Sprintf("+%s", v.Metadata))
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Version) LessThan(versionB Version) bool {
|
||||||
|
versionA := *v
|
||||||
|
cmp := recursiveCompare(versionA.Slice(), versionB.Slice())
|
||||||
|
|
||||||
|
if cmp == 0 {
|
||||||
|
cmp = preReleaseCompare(versionA, versionB)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cmp == -1 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Slice converts the comparable parts of the semver into a slice of strings */
|
||||||
|
func (v *Version) Slice() []int64 {
|
||||||
|
return []int64{v.Major, v.Minor, v.Patch}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PreRelease) Slice() []string {
|
||||||
|
preRelease := string(*p)
|
||||||
|
return strings.Split(preRelease, ".")
|
||||||
|
}
|
||||||
|
|
||||||
|
func preReleaseCompare(versionA Version, versionB Version) int {
|
||||||
|
a := versionA.PreRelease
|
||||||
|
b := versionB.PreRelease
|
||||||
|
|
||||||
|
/* Handle the case where if two versions are otherwise equal it is the
|
||||||
|
* one without a PreRelease that is greater */
|
||||||
|
if len(a) == 0 && (len(b) > 0) {
|
||||||
|
return 1
|
||||||
|
} else if len(b) == 0 && (len(a) > 0) {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is a prelease, check and compare each part.
|
||||||
|
return recursivePreReleaseCompare(a.Slice(), b.Slice())
|
||||||
|
}
|
||||||
|
|
||||||
|
func recursiveCompare(versionA []int64, versionB []int64) int {
|
||||||
|
if len(versionA) == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
a := versionA[0]
|
||||||
|
b := versionB[0]
|
||||||
|
|
||||||
|
if a > b {
|
||||||
|
return 1
|
||||||
|
} else if a < b {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
return recursiveCompare(versionA[1:], versionB[1:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func recursivePreReleaseCompare(versionA []string, versionB []string) int {
|
||||||
|
// Handle slice length disparity.
|
||||||
|
if len(versionA) == 0 {
|
||||||
|
// Nothing to compare too, so we return 0
|
||||||
|
return 0
|
||||||
|
} else if len(versionB) == 0 {
|
||||||
|
// We're longer than versionB so return 1.
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
a := versionA[0]
|
||||||
|
b := versionB[0]
|
||||||
|
|
||||||
|
aInt := false; bInt := false
|
||||||
|
|
||||||
|
aI, err := strconv.Atoi(versionA[0])
|
||||||
|
if err == nil {
|
||||||
|
aInt = true
|
||||||
|
}
|
||||||
|
|
||||||
|
bI, err := strconv.Atoi(versionB[0])
|
||||||
|
if err == nil {
|
||||||
|
bInt = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle Integer Comparison
|
||||||
|
if aInt && bInt {
|
||||||
|
if aI > bI {
|
||||||
|
return 1
|
||||||
|
} else if aI < bI {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle String Comparison
|
||||||
|
if a > b {
|
||||||
|
return 1
|
||||||
|
} else if a < b {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
return recursivePreReleaseCompare(versionA[1:], versionB[1:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// BumpMajor increments the Major field by 1 and resets all other fields to their default values
|
||||||
|
func (v *Version) BumpMajor() {
|
||||||
|
v.Major += 1
|
||||||
|
v.Minor = 0
|
||||||
|
v.Patch = 0
|
||||||
|
v.PreRelease = PreRelease("")
|
||||||
|
v.Metadata = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// BumpMinor increments the Minor field by 1 and resets all other fields to their default values
|
||||||
|
func (v *Version) BumpMinor() {
|
||||||
|
v.Minor += 1
|
||||||
|
v.Patch = 0
|
||||||
|
v.PreRelease = PreRelease("")
|
||||||
|
v.Metadata = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// BumpPatch increments the Patch field by 1 and resets all other fields to their default values
|
||||||
|
func (v *Version) BumpPatch() {
|
||||||
|
v.Patch += 1
|
||||||
|
v.PreRelease = PreRelease("")
|
||||||
|
v.Metadata = ""
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
package semver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Versions []*Version
|
||||||
|
|
||||||
|
func (s Versions) Len() int {
|
||||||
|
return len(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Versions) Swap(i, j int) {
|
||||||
|
s[i], s[j] = s[j], s[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Versions) Less(i, j int) bool {
|
||||||
|
return s[i].LessThan(*s[j])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort sorts the given slice of Version
|
||||||
|
func Sort(versions []*Version) {
|
||||||
|
sort.Sort(Versions(versions))
|
||||||
|
}
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2014 Sam Ghods
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
|
|
||||||
|
|
||||||
Copyright (c) 2012 The 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.
|
|
||||||
|
|
@ -1,497 +0,0 @@
|
||||||
// Copyright 2013 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.
|
|
||||||
package yaml
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding"
|
|
||||||
"encoding/json"
|
|
||||||
"reflect"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"unicode"
|
|
||||||
"unicode/utf8"
|
|
||||||
)
|
|
||||||
|
|
||||||
// indirect walks down v allocating pointers as needed,
|
|
||||||
// until it gets to a non-pointer.
|
|
||||||
// if it encounters an Unmarshaler, indirect stops and returns that.
|
|
||||||
// if decodingNull is true, indirect stops at the last pointer so it can be set to nil.
|
|
||||||
func indirect(v reflect.Value, decodingNull bool) (json.Unmarshaler, encoding.TextUnmarshaler, reflect.Value) {
|
|
||||||
// If v is a named type and is addressable,
|
|
||||||
// start with its address, so that if the type has pointer methods,
|
|
||||||
// we find them.
|
|
||||||
if v.Kind() != reflect.Ptr && v.Type().Name() != "" && v.CanAddr() {
|
|
||||||
v = v.Addr()
|
|
||||||
}
|
|
||||||
for {
|
|
||||||
// Load value from interface, but only if the result will be
|
|
||||||
// usefully addressable.
|
|
||||||
if v.Kind() == reflect.Interface && !v.IsNil() {
|
|
||||||
e := v.Elem()
|
|
||||||
if e.Kind() == reflect.Ptr && !e.IsNil() && (!decodingNull || e.Elem().Kind() == reflect.Ptr) {
|
|
||||||
v = e
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if v.Kind() != reflect.Ptr {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if v.Elem().Kind() != reflect.Ptr && decodingNull && v.CanSet() {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if v.IsNil() {
|
|
||||||
v.Set(reflect.New(v.Type().Elem()))
|
|
||||||
}
|
|
||||||
if v.Type().NumMethod() > 0 {
|
|
||||||
if u, ok := v.Interface().(json.Unmarshaler); ok {
|
|
||||||
return u, nil, reflect.Value{}
|
|
||||||
}
|
|
||||||
if u, ok := v.Interface().(encoding.TextUnmarshaler); ok {
|
|
||||||
return nil, u, reflect.Value{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
v = v.Elem()
|
|
||||||
}
|
|
||||||
return nil, nil, v
|
|
||||||
}
|
|
||||||
|
|
||||||
// A field represents a single field found in a struct.
|
|
||||||
type field struct {
|
|
||||||
name string
|
|
||||||
nameBytes []byte // []byte(name)
|
|
||||||
equalFold func(s, t []byte) bool // bytes.EqualFold or equivalent
|
|
||||||
|
|
||||||
tag bool
|
|
||||||
index []int
|
|
||||||
typ reflect.Type
|
|
||||||
omitEmpty bool
|
|
||||||
quoted bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func fillField(f field) field {
|
|
||||||
f.nameBytes = []byte(f.name)
|
|
||||||
f.equalFold = foldFunc(f.nameBytes)
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
|
|
||||||
// byName sorts field by name, breaking ties with depth,
|
|
||||||
// then breaking ties with "name came from json tag", then
|
|
||||||
// breaking ties with index sequence.
|
|
||||||
type byName []field
|
|
||||||
|
|
||||||
func (x byName) Len() int { return len(x) }
|
|
||||||
|
|
||||||
func (x byName) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
|
|
||||||
|
|
||||||
func (x byName) Less(i, j int) bool {
|
|
||||||
if x[i].name != x[j].name {
|
|
||||||
return x[i].name < x[j].name
|
|
||||||
}
|
|
||||||
if len(x[i].index) != len(x[j].index) {
|
|
||||||
return len(x[i].index) < len(x[j].index)
|
|
||||||
}
|
|
||||||
if x[i].tag != x[j].tag {
|
|
||||||
return x[i].tag
|
|
||||||
}
|
|
||||||
return byIndex(x).Less(i, j)
|
|
||||||
}
|
|
||||||
|
|
||||||
// byIndex sorts field by index sequence.
|
|
||||||
type byIndex []field
|
|
||||||
|
|
||||||
func (x byIndex) Len() int { return len(x) }
|
|
||||||
|
|
||||||
func (x byIndex) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
|
|
||||||
|
|
||||||
func (x byIndex) Less(i, j int) bool {
|
|
||||||
for k, xik := range x[i].index {
|
|
||||||
if k >= len(x[j].index) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if xik != x[j].index[k] {
|
|
||||||
return xik < x[j].index[k]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return len(x[i].index) < len(x[j].index)
|
|
||||||
}
|
|
||||||
|
|
||||||
// typeFields returns a list of fields that JSON should recognize for the given type.
|
|
||||||
// The algorithm is breadth-first search over the set of structs to include - the top struct
|
|
||||||
// and then any reachable anonymous structs.
|
|
||||||
func typeFields(t reflect.Type) []field {
|
|
||||||
// Anonymous fields to explore at the current level and the next.
|
|
||||||
current := []field{}
|
|
||||||
next := []field{{typ: t}}
|
|
||||||
|
|
||||||
// Count of queued names for current level and the next.
|
|
||||||
count := map[reflect.Type]int{}
|
|
||||||
nextCount := map[reflect.Type]int{}
|
|
||||||
|
|
||||||
// Types already visited at an earlier level.
|
|
||||||
visited := map[reflect.Type]bool{}
|
|
||||||
|
|
||||||
// Fields found.
|
|
||||||
var fields []field
|
|
||||||
|
|
||||||
for len(next) > 0 {
|
|
||||||
current, next = next, current[:0]
|
|
||||||
count, nextCount = nextCount, map[reflect.Type]int{}
|
|
||||||
|
|
||||||
for _, f := range current {
|
|
||||||
if visited[f.typ] {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
visited[f.typ] = true
|
|
||||||
|
|
||||||
// Scan f.typ for fields to include.
|
|
||||||
for i := 0; i < f.typ.NumField(); i++ {
|
|
||||||
sf := f.typ.Field(i)
|
|
||||||
if sf.PkgPath != "" { // unexported
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
tag := sf.Tag.Get("json")
|
|
||||||
if tag == "-" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
name, opts := parseTag(tag)
|
|
||||||
if !isValidTag(name) {
|
|
||||||
name = ""
|
|
||||||
}
|
|
||||||
index := make([]int, len(f.index)+1)
|
|
||||||
copy(index, f.index)
|
|
||||||
index[len(f.index)] = i
|
|
||||||
|
|
||||||
ft := sf.Type
|
|
||||||
if ft.Name() == "" && ft.Kind() == reflect.Ptr {
|
|
||||||
// Follow pointer.
|
|
||||||
ft = ft.Elem()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Record found field and index sequence.
|
|
||||||
if name != "" || !sf.Anonymous || ft.Kind() != reflect.Struct {
|
|
||||||
tagged := name != ""
|
|
||||||
if name == "" {
|
|
||||||
name = sf.Name
|
|
||||||
}
|
|
||||||
fields = append(fields, fillField(field{
|
|
||||||
name: name,
|
|
||||||
tag: tagged,
|
|
||||||
index: index,
|
|
||||||
typ: ft,
|
|
||||||
omitEmpty: opts.Contains("omitempty"),
|
|
||||||
quoted: opts.Contains("string"),
|
|
||||||
}))
|
|
||||||
if count[f.typ] > 1 {
|
|
||||||
// If there were multiple instances, add a second,
|
|
||||||
// so that the annihilation code will see a duplicate.
|
|
||||||
// It only cares about the distinction between 1 or 2,
|
|
||||||
// so don't bother generating any more copies.
|
|
||||||
fields = append(fields, fields[len(fields)-1])
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Record new anonymous struct to explore in next round.
|
|
||||||
nextCount[ft]++
|
|
||||||
if nextCount[ft] == 1 {
|
|
||||||
next = append(next, fillField(field{name: ft.Name(), index: index, typ: ft}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Sort(byName(fields))
|
|
||||||
|
|
||||||
// Delete all fields that are hidden by the Go rules for embedded fields,
|
|
||||||
// except that fields with JSON tags are promoted.
|
|
||||||
|
|
||||||
// The fields are sorted in primary order of name, secondary order
|
|
||||||
// of field index length. Loop over names; for each name, delete
|
|
||||||
// hidden fields by choosing the one dominant field that survives.
|
|
||||||
out := fields[:0]
|
|
||||||
for advance, i := 0, 0; i < len(fields); i += advance {
|
|
||||||
// One iteration per name.
|
|
||||||
// Find the sequence of fields with the name of this first field.
|
|
||||||
fi := fields[i]
|
|
||||||
name := fi.name
|
|
||||||
for advance = 1; i+advance < len(fields); advance++ {
|
|
||||||
fj := fields[i+advance]
|
|
||||||
if fj.name != name {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if advance == 1 { // Only one field with this name
|
|
||||||
out = append(out, fi)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
dominant, ok := dominantField(fields[i : i+advance])
|
|
||||||
if ok {
|
|
||||||
out = append(out, dominant)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fields = out
|
|
||||||
sort.Sort(byIndex(fields))
|
|
||||||
|
|
||||||
return fields
|
|
||||||
}
|
|
||||||
|
|
||||||
// dominantField looks through the fields, all of which are known to
|
|
||||||
// have the same name, to find the single field that dominates the
|
|
||||||
// others using Go's embedding rules, modified by the presence of
|
|
||||||
// JSON tags. If there are multiple top-level fields, the boolean
|
|
||||||
// will be false: This condition is an error in Go and we skip all
|
|
||||||
// the fields.
|
|
||||||
func dominantField(fields []field) (field, bool) {
|
|
||||||
// The fields are sorted in increasing index-length order. The winner
|
|
||||||
// must therefore be one with the shortest index length. Drop all
|
|
||||||
// longer entries, which is easy: just truncate the slice.
|
|
||||||
length := len(fields[0].index)
|
|
||||||
tagged := -1 // Index of first tagged field.
|
|
||||||
for i, f := range fields {
|
|
||||||
if len(f.index) > length {
|
|
||||||
fields = fields[:i]
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if f.tag {
|
|
||||||
if tagged >= 0 {
|
|
||||||
// Multiple tagged fields at the same level: conflict.
|
|
||||||
// Return no field.
|
|
||||||
return field{}, false
|
|
||||||
}
|
|
||||||
tagged = i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if tagged >= 0 {
|
|
||||||
return fields[tagged], true
|
|
||||||
}
|
|
||||||
// All remaining fields have the same length. If there's more than one,
|
|
||||||
// we have a conflict (two fields named "X" at the same level) and we
|
|
||||||
// return no field.
|
|
||||||
if len(fields) > 1 {
|
|
||||||
return field{}, false
|
|
||||||
}
|
|
||||||
return fields[0], true
|
|
||||||
}
|
|
||||||
|
|
||||||
var fieldCache struct {
|
|
||||||
sync.RWMutex
|
|
||||||
m map[reflect.Type][]field
|
|
||||||
}
|
|
||||||
|
|
||||||
// cachedTypeFields is like typeFields but uses a cache to avoid repeated work.
|
|
||||||
func cachedTypeFields(t reflect.Type) []field {
|
|
||||||
fieldCache.RLock()
|
|
||||||
f := fieldCache.m[t]
|
|
||||||
fieldCache.RUnlock()
|
|
||||||
if f != nil {
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compute fields without lock.
|
|
||||||
// Might duplicate effort but won't hold other computations back.
|
|
||||||
f = typeFields(t)
|
|
||||||
if f == nil {
|
|
||||||
f = []field{}
|
|
||||||
}
|
|
||||||
|
|
||||||
fieldCache.Lock()
|
|
||||||
if fieldCache.m == nil {
|
|
||||||
fieldCache.m = map[reflect.Type][]field{}
|
|
||||||
}
|
|
||||||
fieldCache.m[t] = f
|
|
||||||
fieldCache.Unlock()
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
|
|
||||||
func isValidTag(s string) bool {
|
|
||||||
if s == "" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for _, c := range s {
|
|
||||||
switch {
|
|
||||||
case strings.ContainsRune("!#$%&()*+-./:<=>?@[]^_{|}~ ", c):
|
|
||||||
// Backslash and quote chars are reserved, but
|
|
||||||
// otherwise any punctuation chars are allowed
|
|
||||||
// in a tag name.
|
|
||||||
default:
|
|
||||||
if !unicode.IsLetter(c) && !unicode.IsDigit(c) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
caseMask = ^byte(0x20) // Mask to ignore case in ASCII.
|
|
||||||
kelvin = '\u212a'
|
|
||||||
smallLongEss = '\u017f'
|
|
||||||
)
|
|
||||||
|
|
||||||
// foldFunc returns one of four different case folding equivalence
|
|
||||||
// functions, from most general (and slow) to fastest:
|
|
||||||
//
|
|
||||||
// 1) bytes.EqualFold, if the key s contains any non-ASCII UTF-8
|
|
||||||
// 2) equalFoldRight, if s contains special folding ASCII ('k', 'K', 's', 'S')
|
|
||||||
// 3) asciiEqualFold, no special, but includes non-letters (including _)
|
|
||||||
// 4) simpleLetterEqualFold, no specials, no non-letters.
|
|
||||||
//
|
|
||||||
// The letters S and K are special because they map to 3 runes, not just 2:
|
|
||||||
// * S maps to s and to U+017F 'ſ' Latin small letter long s
|
|
||||||
// * k maps to K and to U+212A 'K' Kelvin sign
|
|
||||||
// See http://play.golang.org/p/tTxjOc0OGo
|
|
||||||
//
|
|
||||||
// The returned function is specialized for matching against s and
|
|
||||||
// should only be given s. It's not curried for performance reasons.
|
|
||||||
func foldFunc(s []byte) func(s, t []byte) bool {
|
|
||||||
nonLetter := false
|
|
||||||
special := false // special letter
|
|
||||||
for _, b := range s {
|
|
||||||
if b >= utf8.RuneSelf {
|
|
||||||
return bytes.EqualFold
|
|
||||||
}
|
|
||||||
upper := b & caseMask
|
|
||||||
if upper < 'A' || upper > 'Z' {
|
|
||||||
nonLetter = true
|
|
||||||
} else if upper == 'K' || upper == 'S' {
|
|
||||||
// See above for why these letters are special.
|
|
||||||
special = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if special {
|
|
||||||
return equalFoldRight
|
|
||||||
}
|
|
||||||
if nonLetter {
|
|
||||||
return asciiEqualFold
|
|
||||||
}
|
|
||||||
return simpleLetterEqualFold
|
|
||||||
}
|
|
||||||
|
|
||||||
// equalFoldRight is a specialization of bytes.EqualFold when s is
|
|
||||||
// known to be all ASCII (including punctuation), but contains an 's',
|
|
||||||
// 'S', 'k', or 'K', requiring a Unicode fold on the bytes in t.
|
|
||||||
// See comments on foldFunc.
|
|
||||||
func equalFoldRight(s, t []byte) bool {
|
|
||||||
for _, sb := range s {
|
|
||||||
if len(t) == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
tb := t[0]
|
|
||||||
if tb < utf8.RuneSelf {
|
|
||||||
if sb != tb {
|
|
||||||
sbUpper := sb & caseMask
|
|
||||||
if 'A' <= sbUpper && sbUpper <= 'Z' {
|
|
||||||
if sbUpper != tb&caseMask {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
t = t[1:]
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// sb is ASCII and t is not. t must be either kelvin
|
|
||||||
// sign or long s; sb must be s, S, k, or K.
|
|
||||||
tr, size := utf8.DecodeRune(t)
|
|
||||||
switch sb {
|
|
||||||
case 's', 'S':
|
|
||||||
if tr != smallLongEss {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
case 'k', 'K':
|
|
||||||
if tr != kelvin {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
t = t[size:]
|
|
||||||
|
|
||||||
}
|
|
||||||
if len(t) > 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// asciiEqualFold is a specialization of bytes.EqualFold for use when
|
|
||||||
// s is all ASCII (but may contain non-letters) and contains no
|
|
||||||
// special-folding letters.
|
|
||||||
// See comments on foldFunc.
|
|
||||||
func asciiEqualFold(s, t []byte) bool {
|
|
||||||
if len(s) != len(t) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for i, sb := range s {
|
|
||||||
tb := t[i]
|
|
||||||
if sb == tb {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if ('a' <= sb && sb <= 'z') || ('A' <= sb && sb <= 'Z') {
|
|
||||||
if sb&caseMask != tb&caseMask {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// simpleLetterEqualFold is a specialization of bytes.EqualFold for
|
|
||||||
// use when s is all ASCII letters (no underscores, etc) and also
|
|
||||||
// doesn't contain 'k', 'K', 's', or 'S'.
|
|
||||||
// See comments on foldFunc.
|
|
||||||
func simpleLetterEqualFold(s, t []byte) bool {
|
|
||||||
if len(s) != len(t) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for i, b := range s {
|
|
||||||
if b&caseMask != t[i]&caseMask {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// tagOptions is the string following a comma in a struct field's "json"
|
|
||||||
// tag, or the empty string. It does not include the leading comma.
|
|
||||||
type tagOptions string
|
|
||||||
|
|
||||||
// parseTag splits a struct field's json tag into its name and
|
|
||||||
// comma-separated options.
|
|
||||||
func parseTag(tag string) (string, tagOptions) {
|
|
||||||
if idx := strings.Index(tag, ","); idx != -1 {
|
|
||||||
return tag[:idx], tagOptions(tag[idx+1:])
|
|
||||||
}
|
|
||||||
return tag, tagOptions("")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Contains reports whether a comma-separated list of options
|
|
||||||
// contains a particular substr flag. substr must be surrounded by a
|
|
||||||
// string boundary or commas.
|
|
||||||
func (o tagOptions) Contains(optionName string) bool {
|
|
||||||
if len(o) == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
s := string(o)
|
|
||||||
for s != "" {
|
|
||||||
var next string
|
|
||||||
i := strings.Index(s, ",")
|
|
||||||
if i >= 0 {
|
|
||||||
s, next = s[:i], s[i+1:]
|
|
||||||
}
|
|
||||||
if s == optionName {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
s = next
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
@ -1,277 +0,0 @@
|
||||||
package yaml
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"gopkg.in/yaml.v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Marshals the object into JSON then converts JSON to YAML and returns the
|
|
||||||
// YAML.
|
|
||||||
func Marshal(o interface{}) ([]byte, error) {
|
|
||||||
j, err := json.Marshal(o)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error marshaling into JSON: ", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
y, err := JSONToYAML(j)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error converting JSON to YAML: ", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return y, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Converts YAML to JSON then uses JSON to unmarshal into an object.
|
|
||||||
func Unmarshal(y []byte, o interface{}) error {
|
|
||||||
vo := reflect.ValueOf(o)
|
|
||||||
j, err := yamlToJSON(y, &vo)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error converting YAML to JSON: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = json.Unmarshal(j, o)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error unmarshaling JSON: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert JSON to YAML.
|
|
||||||
func JSONToYAML(j []byte) ([]byte, error) {
|
|
||||||
// Convert the JSON to an object.
|
|
||||||
var jsonObj interface{}
|
|
||||||
// We are using yaml.Unmarshal here (instead of json.Unmarshal) because the
|
|
||||||
// Go JSON library doesn't try to pick the right number type (int, float,
|
|
||||||
// etc.) when unmarshling to interface{}, it just picks float64
|
|
||||||
// universally. go-yaml does go through the effort of picking the right
|
|
||||||
// number type, so we can preserve number type throughout this process.
|
|
||||||
err := yaml.Unmarshal(j, &jsonObj)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Marshal this object into YAML.
|
|
||||||
return yaml.Marshal(jsonObj)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert YAML to JSON. Since JSON is a subset of YAML, passing JSON through
|
|
||||||
// this method should be a no-op.
|
|
||||||
//
|
|
||||||
// Things YAML can do that are not supported by JSON:
|
|
||||||
// * In YAML you can have binary and null keys in your maps. These are invalid
|
|
||||||
// in JSON. (int and float keys are converted to strings.)
|
|
||||||
// * Binary data in YAML with the !!binary tag is not supported. If you want to
|
|
||||||
// use binary data with this library, encode the data as base64 as usual but do
|
|
||||||
// not use the !!binary tag in your YAML. This will ensure the original base64
|
|
||||||
// encoded data makes it all the way through to the JSON.
|
|
||||||
func YAMLToJSON(y []byte) ([]byte, error) {
|
|
||||||
return yamlToJSON(y, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func yamlToJSON(y []byte, jsonTarget *reflect.Value) ([]byte, error) {
|
|
||||||
// Convert the YAML to an object.
|
|
||||||
var yamlObj interface{}
|
|
||||||
err := yaml.Unmarshal(y, &yamlObj)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// YAML objects are not completely compatible with JSON objects (e.g. you
|
|
||||||
// can have non-string keys in YAML). So, convert the YAML-compatible object
|
|
||||||
// to a JSON-compatible object, failing with an error if irrecoverable
|
|
||||||
// incompatibilties happen along the way.
|
|
||||||
jsonObj, err := convertToJSONableObject(yamlObj, jsonTarget)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert this object to JSON and return the data.
|
|
||||||
return json.Marshal(jsonObj)
|
|
||||||
}
|
|
||||||
|
|
||||||
func convertToJSONableObject(yamlObj interface{}, jsonTarget *reflect.Value) (interface{}, error) {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
// Resolve jsonTarget to a concrete value (i.e. not a pointer or an
|
|
||||||
// interface). We pass decodingNull as false because we're not actually
|
|
||||||
// decoding into the value, we're just checking if the ultimate target is a
|
|
||||||
// string.
|
|
||||||
if jsonTarget != nil {
|
|
||||||
ju, tu, pv := indirect(*jsonTarget, false)
|
|
||||||
// We have a JSON or Text Umarshaler at this level, so we can't be trying
|
|
||||||
// to decode into a string.
|
|
||||||
if ju != nil || tu != nil {
|
|
||||||
jsonTarget = nil
|
|
||||||
} else {
|
|
||||||
jsonTarget = &pv
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If yamlObj is a number or a boolean, check if jsonTarget is a string -
|
|
||||||
// if so, coerce. Else return normal.
|
|
||||||
// If yamlObj is a map or array, find the field that each key is
|
|
||||||
// unmarshaling to, and when you recurse pass the reflect.Value for that
|
|
||||||
// field back into this function.
|
|
||||||
switch typedYAMLObj := yamlObj.(type) {
|
|
||||||
case map[interface{}]interface{}:
|
|
||||||
// JSON does not support arbitrary keys in a map, so we must convert
|
|
||||||
// these keys to strings.
|
|
||||||
//
|
|
||||||
// From my reading of go-yaml v2 (specifically the resolve function),
|
|
||||||
// keys can only have the types string, int, int64, float64, binary
|
|
||||||
// (unsupported), or null (unsupported).
|
|
||||||
strMap := make(map[string]interface{})
|
|
||||||
for k, v := range typedYAMLObj {
|
|
||||||
// Resolve the key to a string first.
|
|
||||||
var keyString string
|
|
||||||
switch typedKey := k.(type) {
|
|
||||||
case string:
|
|
||||||
keyString = typedKey
|
|
||||||
case int:
|
|
||||||
keyString = strconv.Itoa(typedKey)
|
|
||||||
case int64:
|
|
||||||
// go-yaml will only return an int64 as a key if the system
|
|
||||||
// architecture is 32-bit and the key's value is between 32-bit
|
|
||||||
// and 64-bit. Otherwise the key type will simply be int.
|
|
||||||
keyString = strconv.FormatInt(typedKey, 10)
|
|
||||||
case float64:
|
|
||||||
// Stolen from go-yaml to use the same conversion to string as
|
|
||||||
// the go-yaml library uses to convert float to string when
|
|
||||||
// Marshaling.
|
|
||||||
s := strconv.FormatFloat(typedKey, 'g', -1, 32)
|
|
||||||
switch s {
|
|
||||||
case "+Inf":
|
|
||||||
s = ".inf"
|
|
||||||
case "-Inf":
|
|
||||||
s = "-.inf"
|
|
||||||
case "NaN":
|
|
||||||
s = ".nan"
|
|
||||||
}
|
|
||||||
keyString = s
|
|
||||||
case bool:
|
|
||||||
if typedKey {
|
|
||||||
keyString = "true"
|
|
||||||
} else {
|
|
||||||
keyString = "false"
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("Unsupported map key of type: %s, key: %+#v, value: %+#v",
|
|
||||||
reflect.TypeOf(k), k, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// jsonTarget should be a struct or a map. If it's a struct, find
|
|
||||||
// the field it's going to map to and pass its reflect.Value. If
|
|
||||||
// it's a map, find the element type of the map and pass the
|
|
||||||
// reflect.Value created from that type. If it's neither, just pass
|
|
||||||
// nil - JSON conversion will error for us if it's a real issue.
|
|
||||||
if jsonTarget != nil {
|
|
||||||
t := *jsonTarget
|
|
||||||
if t.Kind() == reflect.Struct {
|
|
||||||
keyBytes := []byte(keyString)
|
|
||||||
// Find the field that the JSON library would use.
|
|
||||||
var f *field
|
|
||||||
fields := cachedTypeFields(t.Type())
|
|
||||||
for i := range fields {
|
|
||||||
ff := &fields[i]
|
|
||||||
if bytes.Equal(ff.nameBytes, keyBytes) {
|
|
||||||
f = ff
|
|
||||||
break
|
|
||||||
}
|
|
||||||
// Do case-insensitive comparison.
|
|
||||||
if f == nil && ff.equalFold(ff.nameBytes, keyBytes) {
|
|
||||||
f = ff
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if f != nil {
|
|
||||||
// Find the reflect.Value of the most preferential
|
|
||||||
// struct field.
|
|
||||||
jtf := t.Field(f.index[0])
|
|
||||||
strMap[keyString], err = convertToJSONableObject(v, &jtf)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
} else if t.Kind() == reflect.Map {
|
|
||||||
// Create a zero value of the map's element type to use as
|
|
||||||
// the JSON target.
|
|
||||||
jtv := reflect.Zero(t.Type().Elem())
|
|
||||||
strMap[keyString], err = convertToJSONableObject(v, &jtv)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
strMap[keyString], err = convertToJSONableObject(v, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return strMap, nil
|
|
||||||
case []interface{}:
|
|
||||||
// We need to recurse into arrays in case there are any
|
|
||||||
// map[interface{}]interface{}'s inside and to convert any
|
|
||||||
// numbers to strings.
|
|
||||||
|
|
||||||
// If jsonTarget is a slice (which it really should be), find the
|
|
||||||
// thing it's going to map to. If it's not a slice, just pass nil
|
|
||||||
// - JSON conversion will error for us if it's a real issue.
|
|
||||||
var jsonSliceElemValue *reflect.Value
|
|
||||||
if jsonTarget != nil {
|
|
||||||
t := *jsonTarget
|
|
||||||
if t.Kind() == reflect.Slice {
|
|
||||||
// By default slices point to nil, but we need a reflect.Value
|
|
||||||
// pointing to a value of the slice type, so we create one here.
|
|
||||||
ev := reflect.Indirect(reflect.New(t.Type().Elem()))
|
|
||||||
jsonSliceElemValue = &ev
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make and use a new array.
|
|
||||||
arr := make([]interface{}, len(typedYAMLObj))
|
|
||||||
for i, v := range typedYAMLObj {
|
|
||||||
arr[i], err = convertToJSONableObject(v, jsonSliceElemValue)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return arr, nil
|
|
||||||
default:
|
|
||||||
// If the target type is a string and the YAML type is a number,
|
|
||||||
// convert the YAML type to a string.
|
|
||||||
if jsonTarget != nil && (*jsonTarget).Kind() == reflect.String {
|
|
||||||
// Based on my reading of go-yaml, it may return int, int64,
|
|
||||||
// float64, or uint64.
|
|
||||||
var s string
|
|
||||||
switch typedVal := typedYAMLObj.(type) {
|
|
||||||
case int:
|
|
||||||
s = strconv.FormatInt(int64(typedVal), 10)
|
|
||||||
case int64:
|
|
||||||
s = strconv.FormatInt(typedVal, 10)
|
|
||||||
case float64:
|
|
||||||
s = strconv.FormatFloat(typedVal, 'g', -1, 32)
|
|
||||||
case uint64:
|
|
||||||
s = strconv.FormatUint(typedVal, 10)
|
|
||||||
case bool:
|
|
||||||
if typedVal {
|
|
||||||
s = "true"
|
|
||||||
} else {
|
|
||||||
s = "false"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(s) > 0 {
|
|
||||||
yamlObj = interface{}(s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return yamlObj, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,201 +0,0 @@
|
||||||
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.
|
|
||||||
|
|
@ -1,72 +0,0 @@
|
||||||
// Copyright 2016 Michal Witkowski. All Rights Reserved.
|
|
||||||
// See LICENSE for licensing terms.
|
|
||||||
|
|
||||||
// gRPC Prometheus monitoring interceptors for client-side gRPC.
|
|
||||||
|
|
||||||
package grpc_prometheus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
"google.golang.org/grpc"
|
|
||||||
"google.golang.org/grpc/codes"
|
|
||||||
)
|
|
||||||
|
|
||||||
// UnaryClientInterceptor is a gRPC client-side interceptor that provides Prometheus monitoring for Unary RPCs.
|
|
||||||
func UnaryClientInterceptor(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
|
|
||||||
monitor := newClientReporter(Unary, method)
|
|
||||||
monitor.SentMessage()
|
|
||||||
err := invoker(ctx, method, req, reply, cc, opts...)
|
|
||||||
if err != nil {
|
|
||||||
monitor.ReceivedMessage()
|
|
||||||
}
|
|
||||||
monitor.Handled(grpc.Code(err))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// StreamServerInterceptor is a gRPC client-side interceptor that provides Prometheus monitoring for Streaming RPCs.
|
|
||||||
func StreamClientInterceptor(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) {
|
|
||||||
monitor := newClientReporter(clientStreamType(desc), method)
|
|
||||||
clientStream, err := streamer(ctx, desc, cc, method, opts...)
|
|
||||||
if err != nil {
|
|
||||||
monitor.Handled(grpc.Code(err))
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &monitoredClientStream{clientStream, monitor}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func clientStreamType(desc *grpc.StreamDesc) grpcType {
|
|
||||||
if desc.ClientStreams && !desc.ServerStreams {
|
|
||||||
return ClientStream
|
|
||||||
} else if !desc.ClientStreams && desc.ServerStreams {
|
|
||||||
return ServerStream
|
|
||||||
}
|
|
||||||
return BidiStream
|
|
||||||
}
|
|
||||||
|
|
||||||
// monitoredClientStream wraps grpc.ClientStream allowing each Sent/Recv of message to increment counters.
|
|
||||||
type monitoredClientStream struct {
|
|
||||||
grpc.ClientStream
|
|
||||||
monitor *clientReporter
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *monitoredClientStream) SendMsg(m interface{}) error {
|
|
||||||
err := s.ClientStream.SendMsg(m)
|
|
||||||
if err == nil {
|
|
||||||
s.monitor.SentMessage()
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *monitoredClientStream) RecvMsg(m interface{}) error {
|
|
||||||
err := s.ClientStream.RecvMsg(m)
|
|
||||||
if err == nil {
|
|
||||||
s.monitor.ReceivedMessage()
|
|
||||||
} else if err == io.EOF {
|
|
||||||
s.monitor.Handled(codes.OK)
|
|
||||||
} else {
|
|
||||||
s.monitor.Handled(grpc.Code(err))
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
@ -1,111 +0,0 @@
|
||||||
// Copyright 2016 Michal Witkowski. All Rights Reserved.
|
|
||||||
// See LICENSE for licensing terms.
|
|
||||||
|
|
||||||
package grpc_prometheus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"google.golang.org/grpc/codes"
|
|
||||||
|
|
||||||
prom "github.com/prometheus/client_golang/prometheus"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
clientStartedCounter = prom.NewCounterVec(
|
|
||||||
prom.CounterOpts{
|
|
||||||
Namespace: "grpc",
|
|
||||||
Subsystem: "client",
|
|
||||||
Name: "started_total",
|
|
||||||
Help: "Total number of RPCs started on the client.",
|
|
||||||
}, []string{"grpc_type", "grpc_service", "grpc_method"})
|
|
||||||
|
|
||||||
clientHandledCounter = prom.NewCounterVec(
|
|
||||||
prom.CounterOpts{
|
|
||||||
Namespace: "grpc",
|
|
||||||
Subsystem: "client",
|
|
||||||
Name: "handled_total",
|
|
||||||
Help: "Total number of RPCs completed by the client, regardless of success or failure.",
|
|
||||||
}, []string{"grpc_type", "grpc_service", "grpc_method", "grpc_code"})
|
|
||||||
|
|
||||||
clientStreamMsgReceived = prom.NewCounterVec(
|
|
||||||
prom.CounterOpts{
|
|
||||||
Namespace: "grpc",
|
|
||||||
Subsystem: "client",
|
|
||||||
Name: "msg_received_total",
|
|
||||||
Help: "Total number of RPC stream messages received by the client.",
|
|
||||||
}, []string{"grpc_type", "grpc_service", "grpc_method"})
|
|
||||||
|
|
||||||
clientStreamMsgSent = prom.NewCounterVec(
|
|
||||||
prom.CounterOpts{
|
|
||||||
Namespace: "grpc",
|
|
||||||
Subsystem: "client",
|
|
||||||
Name: "msg_sent_total",
|
|
||||||
Help: "Total number of gRPC stream messages sent by the client.",
|
|
||||||
}, []string{"grpc_type", "grpc_service", "grpc_method"})
|
|
||||||
|
|
||||||
clientHandledHistogramEnabled = false
|
|
||||||
clientHandledHistogramOpts = prom.HistogramOpts{
|
|
||||||
Namespace: "grpc",
|
|
||||||
Subsystem: "client",
|
|
||||||
Name: "handling_seconds",
|
|
||||||
Help: "Histogram of response latency (seconds) of the gRPC until it is finished by the application.",
|
|
||||||
Buckets: prom.DefBuckets,
|
|
||||||
}
|
|
||||||
clientHandledHistogram *prom.HistogramVec
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
prom.MustRegister(clientStartedCounter)
|
|
||||||
prom.MustRegister(clientHandledCounter)
|
|
||||||
prom.MustRegister(clientStreamMsgReceived)
|
|
||||||
prom.MustRegister(clientStreamMsgSent)
|
|
||||||
}
|
|
||||||
|
|
||||||
// EnableClientHandlingTimeHistogram turns on recording of handling time of RPCs.
|
|
||||||
// Histogram metrics can be very expensive for Prometheus to retain and query.
|
|
||||||
func EnableClientHandlingTimeHistogram(opts ...HistogramOption) {
|
|
||||||
for _, o := range opts {
|
|
||||||
o(&clientHandledHistogramOpts)
|
|
||||||
}
|
|
||||||
if !clientHandledHistogramEnabled {
|
|
||||||
clientHandledHistogram = prom.NewHistogramVec(
|
|
||||||
clientHandledHistogramOpts,
|
|
||||||
[]string{"grpc_type", "grpc_service", "grpc_method"},
|
|
||||||
)
|
|
||||||
prom.Register(clientHandledHistogram)
|
|
||||||
}
|
|
||||||
clientHandledHistogramEnabled = true
|
|
||||||
}
|
|
||||||
|
|
||||||
type clientReporter struct {
|
|
||||||
rpcType grpcType
|
|
||||||
serviceName string
|
|
||||||
methodName string
|
|
||||||
startTime time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
func newClientReporter(rpcType grpcType, fullMethod string) *clientReporter {
|
|
||||||
r := &clientReporter{rpcType: rpcType}
|
|
||||||
if clientHandledHistogramEnabled {
|
|
||||||
r.startTime = time.Now()
|
|
||||||
}
|
|
||||||
r.serviceName, r.methodName = splitMethodName(fullMethod)
|
|
||||||
clientStartedCounter.WithLabelValues(string(r.rpcType), r.serviceName, r.methodName).Inc()
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *clientReporter) ReceivedMessage() {
|
|
||||||
clientStreamMsgReceived.WithLabelValues(string(r.rpcType), r.serviceName, r.methodName).Inc()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *clientReporter) SentMessage() {
|
|
||||||
clientStreamMsgSent.WithLabelValues(string(r.rpcType), r.serviceName, r.methodName).Inc()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *clientReporter) Handled(code codes.Code) {
|
|
||||||
clientHandledCounter.WithLabelValues(string(r.rpcType), r.serviceName, r.methodName, code.String()).Inc()
|
|
||||||
if clientHandledHistogramEnabled {
|
|
||||||
clientHandledHistogram.WithLabelValues(string(r.rpcType), r.serviceName, r.methodName).Observe(time.Since(r.startTime).Seconds())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,74 +0,0 @@
|
||||||
// Copyright 2016 Michal Witkowski. All Rights Reserved.
|
|
||||||
// See LICENSE for licensing terms.
|
|
||||||
|
|
||||||
// gRPC Prometheus monitoring interceptors for server-side gRPC.
|
|
||||||
|
|
||||||
package grpc_prometheus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
"google.golang.org/grpc"
|
|
||||||
)
|
|
||||||
|
|
||||||
// PreregisterServices takes a gRPC server and pre-initializes all counters to 0.
|
|
||||||
// This allows for easier monitoring in Prometheus (no missing metrics), and should be called *after* all services have
|
|
||||||
// been registered with the server.
|
|
||||||
func Register(server *grpc.Server) {
|
|
||||||
serviceInfo := server.GetServiceInfo()
|
|
||||||
for serviceName, info := range serviceInfo {
|
|
||||||
for _, mInfo := range info.Methods {
|
|
||||||
preRegisterMethod(serviceName, &mInfo)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnaryServerInterceptor is a gRPC server-side interceptor that provides Prometheus monitoring for Unary RPCs.
|
|
||||||
func UnaryServerInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
|
|
||||||
monitor := newServerReporter(Unary, info.FullMethod)
|
|
||||||
monitor.ReceivedMessage()
|
|
||||||
resp, err := handler(ctx, req)
|
|
||||||
monitor.Handled(grpc.Code(err))
|
|
||||||
if err == nil {
|
|
||||||
monitor.SentMessage()
|
|
||||||
}
|
|
||||||
return resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// StreamServerInterceptor is a gRPC server-side interceptor that provides Prometheus monitoring for Streaming RPCs.
|
|
||||||
func StreamServerInterceptor(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
|
|
||||||
monitor := newServerReporter(streamRpcType(info), info.FullMethod)
|
|
||||||
err := handler(srv, &monitoredServerStream{ss, monitor})
|
|
||||||
monitor.Handled(grpc.Code(err))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func streamRpcType(info *grpc.StreamServerInfo) grpcType {
|
|
||||||
if info.IsClientStream && !info.IsServerStream {
|
|
||||||
return ClientStream
|
|
||||||
} else if !info.IsClientStream && info.IsServerStream {
|
|
||||||
return ServerStream
|
|
||||||
}
|
|
||||||
return BidiStream
|
|
||||||
}
|
|
||||||
|
|
||||||
// monitoredStream wraps grpc.ServerStream allowing each Sent/Recv of message to increment counters.
|
|
||||||
type monitoredServerStream struct {
|
|
||||||
grpc.ServerStream
|
|
||||||
monitor *serverReporter
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *monitoredServerStream) SendMsg(m interface{}) error {
|
|
||||||
err := s.ServerStream.SendMsg(m)
|
|
||||||
if err == nil {
|
|
||||||
s.monitor.SentMessage()
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *monitoredServerStream) RecvMsg(m interface{}) error {
|
|
||||||
err := s.ServerStream.RecvMsg(m)
|
|
||||||
if err == nil {
|
|
||||||
s.monitor.ReceivedMessage()
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
@ -1,157 +0,0 @@
|
||||||
// Copyright 2016 Michal Witkowski. All Rights Reserved.
|
|
||||||
// See LICENSE for licensing terms.
|
|
||||||
|
|
||||||
package grpc_prometheus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"google.golang.org/grpc/codes"
|
|
||||||
|
|
||||||
prom "github.com/prometheus/client_golang/prometheus"
|
|
||||||
"google.golang.org/grpc"
|
|
||||||
)
|
|
||||||
|
|
||||||
type grpcType string
|
|
||||||
|
|
||||||
const (
|
|
||||||
Unary grpcType = "unary"
|
|
||||||
ClientStream grpcType = "client_stream"
|
|
||||||
ServerStream grpcType = "server_stream"
|
|
||||||
BidiStream grpcType = "bidi_stream"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
serverStartedCounter = prom.NewCounterVec(
|
|
||||||
prom.CounterOpts{
|
|
||||||
Namespace: "grpc",
|
|
||||||
Subsystem: "server",
|
|
||||||
Name: "started_total",
|
|
||||||
Help: "Total number of RPCs started on the server.",
|
|
||||||
}, []string{"grpc_type", "grpc_service", "grpc_method"})
|
|
||||||
|
|
||||||
serverHandledCounter = prom.NewCounterVec(
|
|
||||||
prom.CounterOpts{
|
|
||||||
Namespace: "grpc",
|
|
||||||
Subsystem: "server",
|
|
||||||
Name: "handled_total",
|
|
||||||
Help: "Total number of RPCs completed on the server, regardless of success or failure.",
|
|
||||||
}, []string{"grpc_type", "grpc_service", "grpc_method", "grpc_code"})
|
|
||||||
|
|
||||||
serverStreamMsgReceived = prom.NewCounterVec(
|
|
||||||
prom.CounterOpts{
|
|
||||||
Namespace: "grpc",
|
|
||||||
Subsystem: "server",
|
|
||||||
Name: "msg_received_total",
|
|
||||||
Help: "Total number of RPC stream messages received on the server.",
|
|
||||||
}, []string{"grpc_type", "grpc_service", "grpc_method"})
|
|
||||||
|
|
||||||
serverStreamMsgSent = prom.NewCounterVec(
|
|
||||||
prom.CounterOpts{
|
|
||||||
Namespace: "grpc",
|
|
||||||
Subsystem: "server",
|
|
||||||
Name: "msg_sent_total",
|
|
||||||
Help: "Total number of gRPC stream messages sent by the server.",
|
|
||||||
}, []string{"grpc_type", "grpc_service", "grpc_method"})
|
|
||||||
|
|
||||||
serverHandledHistogramEnabled = false
|
|
||||||
serverHandledHistogramOpts = prom.HistogramOpts{
|
|
||||||
Namespace: "grpc",
|
|
||||||
Subsystem: "server",
|
|
||||||
Name: "handling_seconds",
|
|
||||||
Help: "Histogram of response latency (seconds) of gRPC that had been application-level handled by the server.",
|
|
||||||
Buckets: prom.DefBuckets,
|
|
||||||
}
|
|
||||||
serverHandledHistogram *prom.HistogramVec
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
prom.MustRegister(serverStartedCounter)
|
|
||||||
prom.MustRegister(serverHandledCounter)
|
|
||||||
prom.MustRegister(serverStreamMsgReceived)
|
|
||||||
prom.MustRegister(serverStreamMsgSent)
|
|
||||||
}
|
|
||||||
|
|
||||||
type HistogramOption func(*prom.HistogramOpts)
|
|
||||||
|
|
||||||
// WithHistogramBuckets allows you to specify custom bucket ranges for histograms if EnableHandlingTimeHistogram is on.
|
|
||||||
func WithHistogramBuckets(buckets []float64) HistogramOption {
|
|
||||||
return func(o *prom.HistogramOpts) { o.Buckets = buckets }
|
|
||||||
}
|
|
||||||
|
|
||||||
// EnableHandlingTimeHistogram turns on recording of handling time of RPCs for server-side interceptors.
|
|
||||||
// Histogram metrics can be very expensive for Prometheus to retain and query.
|
|
||||||
func EnableHandlingTimeHistogram(opts ...HistogramOption) {
|
|
||||||
for _, o := range opts {
|
|
||||||
o(&serverHandledHistogramOpts)
|
|
||||||
}
|
|
||||||
if !serverHandledHistogramEnabled {
|
|
||||||
serverHandledHistogram = prom.NewHistogramVec(
|
|
||||||
serverHandledHistogramOpts,
|
|
||||||
[]string{"grpc_type", "grpc_service", "grpc_method"},
|
|
||||||
)
|
|
||||||
prom.Register(serverHandledHistogram)
|
|
||||||
}
|
|
||||||
serverHandledHistogramEnabled = true
|
|
||||||
}
|
|
||||||
|
|
||||||
type serverReporter struct {
|
|
||||||
rpcType grpcType
|
|
||||||
serviceName string
|
|
||||||
methodName string
|
|
||||||
startTime time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
func newServerReporter(rpcType grpcType, fullMethod string) *serverReporter {
|
|
||||||
r := &serverReporter{rpcType: rpcType}
|
|
||||||
if serverHandledHistogramEnabled {
|
|
||||||
r.startTime = time.Now()
|
|
||||||
}
|
|
||||||
r.serviceName, r.methodName = splitMethodName(fullMethod)
|
|
||||||
serverStartedCounter.WithLabelValues(string(r.rpcType), r.serviceName, r.methodName).Inc()
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *serverReporter) ReceivedMessage() {
|
|
||||||
serverStreamMsgReceived.WithLabelValues(string(r.rpcType), r.serviceName, r.methodName).Inc()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *serverReporter) SentMessage() {
|
|
||||||
serverStreamMsgSent.WithLabelValues(string(r.rpcType), r.serviceName, r.methodName).Inc()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *serverReporter) Handled(code codes.Code) {
|
|
||||||
serverHandledCounter.WithLabelValues(string(r.rpcType), r.serviceName, r.methodName, code.String()).Inc()
|
|
||||||
if serverHandledHistogramEnabled {
|
|
||||||
serverHandledHistogram.WithLabelValues(string(r.rpcType), r.serviceName, r.methodName).Observe(time.Since(r.startTime).Seconds())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// preRegisterMethod is invoked on Register of a Server, allowing all gRPC services labels to be pre-populated.
|
|
||||||
func preRegisterMethod(serviceName string, mInfo *grpc.MethodInfo) {
|
|
||||||
methodName := mInfo.Name
|
|
||||||
methodType := string(typeFromMethodInfo(mInfo))
|
|
||||||
// These are just references (no increments), as just referencing will create the labels but not set values.
|
|
||||||
serverStartedCounter.GetMetricWithLabelValues(methodType, serviceName, methodName)
|
|
||||||
serverStreamMsgReceived.GetMetricWithLabelValues(methodType, serviceName, methodName)
|
|
||||||
serverStreamMsgSent.GetMetricWithLabelValues(methodType, serviceName, methodName)
|
|
||||||
if serverHandledHistogramEnabled {
|
|
||||||
serverHandledHistogram.GetMetricWithLabelValues(methodType, serviceName, methodName)
|
|
||||||
}
|
|
||||||
for _, code := range allCodes {
|
|
||||||
serverHandledCounter.GetMetricWithLabelValues(methodType, serviceName, methodName, code.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func typeFromMethodInfo(mInfo *grpc.MethodInfo) grpcType {
|
|
||||||
if mInfo.IsClientStream == false && mInfo.IsServerStream == false {
|
|
||||||
return Unary
|
|
||||||
}
|
|
||||||
if mInfo.IsClientStream == true && mInfo.IsServerStream == false {
|
|
||||||
return ClientStream
|
|
||||||
}
|
|
||||||
if mInfo.IsClientStream == false && mInfo.IsServerStream == true {
|
|
||||||
return ServerStream
|
|
||||||
}
|
|
||||||
return BidiStream
|
|
||||||
}
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
// Copyright 2016 Michal Witkowski. All Rights Reserved.
|
|
||||||
// See LICENSE for licensing terms.
|
|
||||||
|
|
||||||
package grpc_prometheus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"google.golang.org/grpc/codes"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
allCodes = []codes.Code{
|
|
||||||
codes.OK, codes.Canceled, codes.Unknown, codes.InvalidArgument, codes.DeadlineExceeded, codes.NotFound,
|
|
||||||
codes.AlreadyExists, codes.PermissionDenied, codes.Unauthenticated, codes.ResourceExhausted,
|
|
||||||
codes.FailedPrecondition, codes.Aborted, codes.OutOfRange, codes.Unimplemented, codes.Internal,
|
|
||||||
codes.Unavailable, codes.DataLoss,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func splitMethodName(fullMethodName string) (string, string) {
|
|
||||||
fullMethodName = strings.TrimPrefix(fullMethodName, "/") // remove leading slash
|
|
||||||
if i := strings.Index(fullMethodName, "/"); i >= 0 {
|
|
||||||
return fullMethodName[:i], fullMethodName[i+1:]
|
|
||||||
}
|
|
||||||
return "unknown", "unknown"
|
|
||||||
}
|
|
||||||
|
|
@ -1,201 +0,0 @@
|
||||||
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.
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
Copyright 2012 Matt T. Proud (matt.proud@gmail.com)
|
|
||||||
|
|
@ -1,75 +0,0 @@
|
||||||
// Copyright 2013 Matt T. Proud
|
|
||||||
//
|
|
||||||
// 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 pbutil
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/golang/protobuf/proto"
|
|
||||||
)
|
|
||||||
|
|
||||||
var errInvalidVarint = errors.New("invalid varint32 encountered")
|
|
||||||
|
|
||||||
// ReadDelimited decodes a message from the provided length-delimited stream,
|
|
||||||
// where the length is encoded as 32-bit varint prefix to the message body.
|
|
||||||
// It returns the total number of bytes read and any applicable error. This is
|
|
||||||
// roughly equivalent to the companion Java API's
|
|
||||||
// MessageLite#parseDelimitedFrom. As per the reader contract, this function
|
|
||||||
// calls r.Read repeatedly as required until exactly one message including its
|
|
||||||
// prefix is read and decoded (or an error has occurred). The function never
|
|
||||||
// reads more bytes from the stream than required. The function never returns
|
|
||||||
// an error if a message has been read and decoded correctly, even if the end
|
|
||||||
// of the stream has been reached in doing so. In that case, any subsequent
|
|
||||||
// calls return (0, io.EOF).
|
|
||||||
func ReadDelimited(r io.Reader, m proto.Message) (n int, err error) {
|
|
||||||
// Per AbstractParser#parsePartialDelimitedFrom with
|
|
||||||
// CodedInputStream#readRawVarint32.
|
|
||||||
var headerBuf [binary.MaxVarintLen32]byte
|
|
||||||
var bytesRead, varIntBytes int
|
|
||||||
var messageLength uint64
|
|
||||||
for varIntBytes == 0 { // i.e. no varint has been decoded yet.
|
|
||||||
if bytesRead >= len(headerBuf) {
|
|
||||||
return bytesRead, errInvalidVarint
|
|
||||||
}
|
|
||||||
// We have to read byte by byte here to avoid reading more bytes
|
|
||||||
// than required. Each read byte is appended to what we have
|
|
||||||
// read before.
|
|
||||||
newBytesRead, err := r.Read(headerBuf[bytesRead : bytesRead+1])
|
|
||||||
if newBytesRead == 0 {
|
|
||||||
if err != nil {
|
|
||||||
return bytesRead, err
|
|
||||||
}
|
|
||||||
// A Reader should not return (0, nil), but if it does,
|
|
||||||
// it should be treated as no-op (according to the
|
|
||||||
// Reader contract). So let's go on...
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
bytesRead += newBytesRead
|
|
||||||
// Now present everything read so far to the varint decoder and
|
|
||||||
// see if a varint can be decoded already.
|
|
||||||
messageLength, varIntBytes = proto.DecodeVarint(headerBuf[:bytesRead])
|
|
||||||
}
|
|
||||||
|
|
||||||
messageBuf := make([]byte, messageLength)
|
|
||||||
newBytesRead, err := io.ReadFull(r, messageBuf)
|
|
||||||
bytesRead += newBytesRead
|
|
||||||
if err != nil {
|
|
||||||
return bytesRead, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return bytesRead, proto.Unmarshal(messageBuf, m)
|
|
||||||
}
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
// Copyright 2013 Matt T. Proud
|
|
||||||
//
|
|
||||||
// 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 pbutil provides record length-delimited Protocol Buffer streaming.
|
|
||||||
package pbutil
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
// Copyright 2013 Matt T. Proud
|
|
||||||
//
|
|
||||||
// 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 pbutil
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/golang/protobuf/proto"
|
|
||||||
)
|
|
||||||
|
|
||||||
// WriteDelimited encodes and dumps a message to the provided writer prefixed
|
|
||||||
// with a 32-bit varint indicating the length of the encoded message, producing
|
|
||||||
// a length-delimited record stream, which can be used to chain together
|
|
||||||
// encoded messages of the same type together in a file. It returns the total
|
|
||||||
// number of bytes written and any applicable error. This is roughly
|
|
||||||
// equivalent to the companion Java API's MessageLite#writeDelimitedTo.
|
|
||||||
func WriteDelimited(w io.Writer, m proto.Message) (n int, err error) {
|
|
||||||
buffer, err := proto.Marshal(m)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var buf [binary.MaxVarintLen32]byte
|
|
||||||
encodedLength := binary.PutUvarint(buf[:], uint64(len(buffer)))
|
|
||||||
|
|
||||||
sync, err := w.Write(buf[:encodedLength])
|
|
||||||
if err != nil {
|
|
||||||
return sync, err
|
|
||||||
}
|
|
||||||
|
|
||||||
n, err = w.Write(buffer)
|
|
||||||
return n + sync, err
|
|
||||||
}
|
|
||||||
|
|
@ -1,201 +0,0 @@
|
||||||
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.
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
Prometheus instrumentation library for Go applications
|
|
||||||
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:
|
|
||||||
|
|
||||||
perks - a fork of https://github.com/bmizerany/perks
|
|
||||||
https://github.com/beorn7/perks
|
|
||||||
Copyright 2013-2015 Blake Mizerany, Björn Rabenstein
|
|
||||||
See https://github.com/beorn7/perks/blob/master/README.md for license details.
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
@ -1,75 +0,0 @@
|
||||||
// Copyright 2014 The Prometheus Authors
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package prometheus
|
|
||||||
|
|
||||||
// Collector is the interface implemented by anything that can be used by
|
|
||||||
// Prometheus to collect metrics. A Collector has to be registered for
|
|
||||||
// collection. See Registerer.Register.
|
|
||||||
//
|
|
||||||
// The stock metrics provided by this package (Gauge, Counter, Summary,
|
|
||||||
// Histogram, Untyped) are also Collectors (which only ever collect one metric,
|
|
||||||
// namely itself). An implementer of Collector may, however, collect multiple
|
|
||||||
// metrics in a coordinated fashion and/or create metrics on the fly. Examples
|
|
||||||
// for collectors already implemented in this library are the metric vectors
|
|
||||||
// (i.e. collection of multiple instances of the same Metric but with different
|
|
||||||
// label values) like GaugeVec or SummaryVec, and the ExpvarCollector.
|
|
||||||
type Collector interface {
|
|
||||||
// Describe sends the super-set of all possible descriptors of metrics
|
|
||||||
// collected by this Collector to the provided channel and returns once
|
|
||||||
// the last descriptor has been sent. The sent descriptors fulfill the
|
|
||||||
// consistency and uniqueness requirements described in the Desc
|
|
||||||
// documentation. (It is valid if one and the same Collector sends
|
|
||||||
// duplicate descriptors. Those duplicates are simply ignored. However,
|
|
||||||
// two different Collectors must not send duplicate descriptors.) This
|
|
||||||
// method idempotently sends the same descriptors throughout the
|
|
||||||
// lifetime of the Collector. If a Collector encounters an error while
|
|
||||||
// executing this method, it must send an invalid descriptor (created
|
|
||||||
// with NewInvalidDesc) to signal the error to the registry.
|
|
||||||
Describe(chan<- *Desc)
|
|
||||||
// Collect is called by the Prometheus registry when collecting
|
|
||||||
// metrics. The implementation sends each collected metric via the
|
|
||||||
// provided channel and returns once the last metric has been sent. The
|
|
||||||
// descriptor of each sent metric is one of those returned by
|
|
||||||
// Describe. Returned metrics that share the same descriptor must differ
|
|
||||||
// in their variable label values. This method may be called
|
|
||||||
// concurrently and must therefore be implemented in a concurrency safe
|
|
||||||
// way. Blocking occurs at the expense of total performance of rendering
|
|
||||||
// all registered metrics. Ideally, Collector implementations support
|
|
||||||
// concurrent readers.
|
|
||||||
Collect(chan<- Metric)
|
|
||||||
}
|
|
||||||
|
|
||||||
// selfCollector implements Collector for a single Metric so that the Metric
|
|
||||||
// collects itself. Add it as an anonymous field to a struct that implements
|
|
||||||
// Metric, and call init with the Metric itself as an argument.
|
|
||||||
type selfCollector struct {
|
|
||||||
self Metric
|
|
||||||
}
|
|
||||||
|
|
||||||
// init provides the selfCollector with a reference to the metric it is supposed
|
|
||||||
// to collect. It is usually called within the factory function to create a
|
|
||||||
// metric. See example.
|
|
||||||
func (c *selfCollector) init(self Metric) {
|
|
||||||
c.self = self
|
|
||||||
}
|
|
||||||
|
|
||||||
// Describe implements Collector.
|
|
||||||
func (c *selfCollector) Describe(ch chan<- *Desc) {
|
|
||||||
ch <- c.self.Desc()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Collect implements Collector.
|
|
||||||
func (c *selfCollector) Collect(ch chan<- Metric) {
|
|
||||||
ch <- c.self
|
|
||||||
}
|
|
||||||
|
|
@ -1,172 +0,0 @@
|
||||||
// Copyright 2014 The Prometheus Authors
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package prometheus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Counter is a Metric that represents a single numerical value that only ever
|
|
||||||
// goes up. That implies that it cannot be used to count items whose number can
|
|
||||||
// also go down, e.g. the number of currently running goroutines. Those
|
|
||||||
// "counters" are represented by Gauges.
|
|
||||||
//
|
|
||||||
// A Counter is typically used to count requests served, tasks completed, errors
|
|
||||||
// occurred, etc.
|
|
||||||
//
|
|
||||||
// To create Counter instances, use NewCounter.
|
|
||||||
type Counter interface {
|
|
||||||
Metric
|
|
||||||
Collector
|
|
||||||
|
|
||||||
// Set is used to set the Counter to an arbitrary value. It is only used
|
|
||||||
// if you have to transfer a value from an external counter into this
|
|
||||||
// Prometheus metric. Do not use it for regular handling of a
|
|
||||||
// Prometheus counter (as it can be used to break the contract of
|
|
||||||
// monotonically increasing values).
|
|
||||||
//
|
|
||||||
// Deprecated: Use NewConstMetric to create a counter for an external
|
|
||||||
// value. A Counter should never be set.
|
|
||||||
Set(float64)
|
|
||||||
// Inc increments the counter by 1.
|
|
||||||
Inc()
|
|
||||||
// Add adds the given value to the counter. It panics if the value is <
|
|
||||||
// 0.
|
|
||||||
Add(float64)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CounterOpts is an alias for Opts. See there for doc comments.
|
|
||||||
type CounterOpts Opts
|
|
||||||
|
|
||||||
// NewCounter creates a new Counter based on the provided CounterOpts.
|
|
||||||
func NewCounter(opts CounterOpts) Counter {
|
|
||||||
desc := NewDesc(
|
|
||||||
BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
|
|
||||||
opts.Help,
|
|
||||||
nil,
|
|
||||||
opts.ConstLabels,
|
|
||||||
)
|
|
||||||
result := &counter{value: value{desc: desc, valType: CounterValue, labelPairs: desc.constLabelPairs}}
|
|
||||||
result.init(result) // Init self-collection.
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
type counter struct {
|
|
||||||
value
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *counter) Add(v float64) {
|
|
||||||
if v < 0 {
|
|
||||||
panic(errors.New("counter cannot decrease in value"))
|
|
||||||
}
|
|
||||||
c.value.Add(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CounterVec is a Collector that bundles a set of Counters that all share the
|
|
||||||
// same Desc, but have different values for their variable labels. This is used
|
|
||||||
// if you want to count the same thing partitioned by various dimensions
|
|
||||||
// (e.g. number of HTTP requests, partitioned by response code and
|
|
||||||
// method). Create instances with NewCounterVec.
|
|
||||||
//
|
|
||||||
// CounterVec embeds MetricVec. See there for a full list of methods with
|
|
||||||
// detailed documentation.
|
|
||||||
type CounterVec struct {
|
|
||||||
*MetricVec
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewCounterVec creates a new CounterVec based on the provided CounterOpts and
|
|
||||||
// partitioned by the given label names. At least one label name must be
|
|
||||||
// provided.
|
|
||||||
func NewCounterVec(opts CounterOpts, labelNames []string) *CounterVec {
|
|
||||||
desc := NewDesc(
|
|
||||||
BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
|
|
||||||
opts.Help,
|
|
||||||
labelNames,
|
|
||||||
opts.ConstLabels,
|
|
||||||
)
|
|
||||||
return &CounterVec{
|
|
||||||
MetricVec: newMetricVec(desc, func(lvs ...string) Metric {
|
|
||||||
result := &counter{value: value{
|
|
||||||
desc: desc,
|
|
||||||
valType: CounterValue,
|
|
||||||
labelPairs: makeLabelPairs(desc, lvs),
|
|
||||||
}}
|
|
||||||
result.init(result) // Init self-collection.
|
|
||||||
return result
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMetricWithLabelValues replaces the method of the same name in
|
|
||||||
// MetricVec. The difference is that this method returns a Counter and not a
|
|
||||||
// Metric so that no type conversion is required.
|
|
||||||
func (m *CounterVec) GetMetricWithLabelValues(lvs ...string) (Counter, error) {
|
|
||||||
metric, err := m.MetricVec.GetMetricWithLabelValues(lvs...)
|
|
||||||
if metric != nil {
|
|
||||||
return metric.(Counter), err
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMetricWith replaces the method of the same name in MetricVec. The
|
|
||||||
// difference is that this method returns a Counter and not a Metric so that no
|
|
||||||
// type conversion is required.
|
|
||||||
func (m *CounterVec) GetMetricWith(labels Labels) (Counter, error) {
|
|
||||||
metric, err := m.MetricVec.GetMetricWith(labels)
|
|
||||||
if metric != nil {
|
|
||||||
return metric.(Counter), err
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithLabelValues works as GetMetricWithLabelValues, but panics where
|
|
||||||
// GetMetricWithLabelValues would have returned an error. By not returning an
|
|
||||||
// error, WithLabelValues allows shortcuts like
|
|
||||||
// myVec.WithLabelValues("404", "GET").Add(42)
|
|
||||||
func (m *CounterVec) WithLabelValues(lvs ...string) Counter {
|
|
||||||
return m.MetricVec.WithLabelValues(lvs...).(Counter)
|
|
||||||
}
|
|
||||||
|
|
||||||
// With works as GetMetricWith, but panics where GetMetricWithLabels would have
|
|
||||||
// returned an error. By not returning an error, With allows shortcuts like
|
|
||||||
// myVec.With(Labels{"code": "404", "method": "GET"}).Add(42)
|
|
||||||
func (m *CounterVec) With(labels Labels) Counter {
|
|
||||||
return m.MetricVec.With(labels).(Counter)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CounterFunc is a Counter whose value is determined at collect time by calling a
|
|
||||||
// provided function.
|
|
||||||
//
|
|
||||||
// To create CounterFunc instances, use NewCounterFunc.
|
|
||||||
type CounterFunc interface {
|
|
||||||
Metric
|
|
||||||
Collector
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewCounterFunc creates a new CounterFunc based on the provided
|
|
||||||
// CounterOpts. The value reported is determined by calling the given function
|
|
||||||
// from within the Write method. Take into account that metric collection may
|
|
||||||
// happen concurrently. If that results in concurrent calls to Write, like in
|
|
||||||
// the case where a CounterFunc is directly registered with Prometheus, the
|
|
||||||
// provided function must be concurrency-safe. The function should also honor
|
|
||||||
// the contract for a Counter (values only go up, not down), but compliance will
|
|
||||||
// not be checked.
|
|
||||||
func NewCounterFunc(opts CounterOpts, function func() float64) CounterFunc {
|
|
||||||
return newValueFunc(NewDesc(
|
|
||||||
BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
|
|
||||||
opts.Help,
|
|
||||||
nil,
|
|
||||||
opts.ConstLabels,
|
|
||||||
), CounterValue, function)
|
|
||||||
}
|
|
||||||
|
|
@ -1,205 +0,0 @@
|
||||||
// Copyright 2016 The Prometheus Authors
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package prometheus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"regexp"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/golang/protobuf/proto"
|
|
||||||
|
|
||||||
dto "github.com/prometheus/client_model/go"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
metricNameRE = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_:]*$`)
|
|
||||||
labelNameRE = regexp.MustCompile("^[a-zA-Z_][a-zA-Z0-9_]*$")
|
|
||||||
)
|
|
||||||
|
|
||||||
// reservedLabelPrefix is a prefix which is not legal in user-supplied
|
|
||||||
// label names.
|
|
||||||
const reservedLabelPrefix = "__"
|
|
||||||
|
|
||||||
// Labels represents a collection of label name -> value mappings. This type is
|
|
||||||
// commonly used with the With(Labels) and GetMetricWith(Labels) methods of
|
|
||||||
// metric vector Collectors, e.g.:
|
|
||||||
// myVec.With(Labels{"code": "404", "method": "GET"}).Add(42)
|
|
||||||
//
|
|
||||||
// The other use-case is the specification of constant label pairs in Opts or to
|
|
||||||
// create a Desc.
|
|
||||||
type Labels map[string]string
|
|
||||||
|
|
||||||
// Desc is the descriptor used by every Prometheus Metric. It is essentially
|
|
||||||
// the immutable meta-data of a Metric. The normal Metric implementations
|
|
||||||
// included in this package manage their Desc under the hood. Users only have to
|
|
||||||
// deal with Desc if they use advanced features like the ExpvarCollector or
|
|
||||||
// custom Collectors and Metrics.
|
|
||||||
//
|
|
||||||
// Descriptors registered with the same registry have to fulfill certain
|
|
||||||
// consistency and uniqueness criteria if they share the same fully-qualified
|
|
||||||
// name: They must have the same help string and the same label names (aka label
|
|
||||||
// dimensions) in each, constLabels and variableLabels, but they must differ in
|
|
||||||
// the values of the constLabels.
|
|
||||||
//
|
|
||||||
// Descriptors that share the same fully-qualified names and the same label
|
|
||||||
// values of their constLabels are considered equal.
|
|
||||||
//
|
|
||||||
// Use NewDesc to create new Desc instances.
|
|
||||||
type Desc struct {
|
|
||||||
// fqName has been built from Namespace, Subsystem, and Name.
|
|
||||||
fqName string
|
|
||||||
// help provides some helpful information about this metric.
|
|
||||||
help string
|
|
||||||
// constLabelPairs contains precalculated DTO label pairs based on
|
|
||||||
// the constant labels.
|
|
||||||
constLabelPairs []*dto.LabelPair
|
|
||||||
// VariableLabels contains names of labels for which the metric
|
|
||||||
// maintains variable values.
|
|
||||||
variableLabels []string
|
|
||||||
// id is a hash of the values of the ConstLabels and fqName. This
|
|
||||||
// must be unique among all registered descriptors and can therefore be
|
|
||||||
// used as an identifier of the descriptor.
|
|
||||||
id uint64
|
|
||||||
// dimHash is a hash of the label names (preset and variable) and the
|
|
||||||
// Help string. Each Desc with the same fqName must have the same
|
|
||||||
// dimHash.
|
|
||||||
dimHash uint64
|
|
||||||
// err is an error that occured during construction. It is reported on
|
|
||||||
// registration time.
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewDesc allocates and initializes a new Desc. Errors are recorded in the Desc
|
|
||||||
// and will be reported on registration time. variableLabels and constLabels can
|
|
||||||
// be nil if no such labels should be set. fqName and help must not be empty.
|
|
||||||
//
|
|
||||||
// variableLabels only contain the label names. Their label values are variable
|
|
||||||
// and therefore not part of the Desc. (They are managed within the Metric.)
|
|
||||||
//
|
|
||||||
// For constLabels, the label values are constant. Therefore, they are fully
|
|
||||||
// specified in the Desc. See the Opts documentation for the implications of
|
|
||||||
// constant labels.
|
|
||||||
func NewDesc(fqName, help string, variableLabels []string, constLabels Labels) *Desc {
|
|
||||||
d := &Desc{
|
|
||||||
fqName: fqName,
|
|
||||||
help: help,
|
|
||||||
variableLabels: variableLabels,
|
|
||||||
}
|
|
||||||
if help == "" {
|
|
||||||
d.err = errors.New("empty help string")
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
if !metricNameRE.MatchString(fqName) {
|
|
||||||
d.err = fmt.Errorf("%q is not a valid metric name", fqName)
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
// labelValues contains the label values of const labels (in order of
|
|
||||||
// their sorted label names) plus the fqName (at position 0).
|
|
||||||
labelValues := make([]string, 1, len(constLabels)+1)
|
|
||||||
labelValues[0] = fqName
|
|
||||||
labelNames := make([]string, 0, len(constLabels)+len(variableLabels))
|
|
||||||
labelNameSet := map[string]struct{}{}
|
|
||||||
// First add only the const label names and sort them...
|
|
||||||
for labelName := range constLabels {
|
|
||||||
if !checkLabelName(labelName) {
|
|
||||||
d.err = fmt.Errorf("%q is not a valid label name", labelName)
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
labelNames = append(labelNames, labelName)
|
|
||||||
labelNameSet[labelName] = struct{}{}
|
|
||||||
}
|
|
||||||
sort.Strings(labelNames)
|
|
||||||
// ... so that we can now add const label values in the order of their names.
|
|
||||||
for _, labelName := range labelNames {
|
|
||||||
labelValues = append(labelValues, constLabels[labelName])
|
|
||||||
}
|
|
||||||
// Now add the variable label names, but prefix them with something that
|
|
||||||
// cannot be in a regular label name. That prevents matching the label
|
|
||||||
// dimension with a different mix between preset and variable labels.
|
|
||||||
for _, labelName := range variableLabels {
|
|
||||||
if !checkLabelName(labelName) {
|
|
||||||
d.err = fmt.Errorf("%q is not a valid label name", labelName)
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
labelNames = append(labelNames, "$"+labelName)
|
|
||||||
labelNameSet[labelName] = struct{}{}
|
|
||||||
}
|
|
||||||
if len(labelNames) != len(labelNameSet) {
|
|
||||||
d.err = errors.New("duplicate label names")
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
vh := hashNew()
|
|
||||||
for _, val := range labelValues {
|
|
||||||
vh = hashAdd(vh, val)
|
|
||||||
vh = hashAddByte(vh, separatorByte)
|
|
||||||
}
|
|
||||||
d.id = vh
|
|
||||||
// Sort labelNames so that order doesn't matter for the hash.
|
|
||||||
sort.Strings(labelNames)
|
|
||||||
// Now hash together (in this order) the help string and the sorted
|
|
||||||
// label names.
|
|
||||||
lh := hashNew()
|
|
||||||
lh = hashAdd(lh, help)
|
|
||||||
lh = hashAddByte(lh, separatorByte)
|
|
||||||
for _, labelName := range labelNames {
|
|
||||||
lh = hashAdd(lh, labelName)
|
|
||||||
lh = hashAddByte(lh, separatorByte)
|
|
||||||
}
|
|
||||||
d.dimHash = lh
|
|
||||||
|
|
||||||
d.constLabelPairs = make([]*dto.LabelPair, 0, len(constLabels))
|
|
||||||
for n, v := range constLabels {
|
|
||||||
d.constLabelPairs = append(d.constLabelPairs, &dto.LabelPair{
|
|
||||||
Name: proto.String(n),
|
|
||||||
Value: proto.String(v),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
sort.Sort(LabelPairSorter(d.constLabelPairs))
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewInvalidDesc returns an invalid descriptor, i.e. a descriptor with the
|
|
||||||
// provided error set. If a collector returning such a descriptor is registered,
|
|
||||||
// registration will fail with the provided error. NewInvalidDesc can be used by
|
|
||||||
// a Collector to signal inability to describe itself.
|
|
||||||
func NewInvalidDesc(err error) *Desc {
|
|
||||||
return &Desc{
|
|
||||||
err: err,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Desc) String() string {
|
|
||||||
lpStrings := make([]string, 0, len(d.constLabelPairs))
|
|
||||||
for _, lp := range d.constLabelPairs {
|
|
||||||
lpStrings = append(
|
|
||||||
lpStrings,
|
|
||||||
fmt.Sprintf("%s=%q", lp.GetName(), lp.GetValue()),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return fmt.Sprintf(
|
|
||||||
"Desc{fqName: %q, help: %q, constLabels: {%s}, variableLabels: %v}",
|
|
||||||
d.fqName,
|
|
||||||
d.help,
|
|
||||||
strings.Join(lpStrings, ","),
|
|
||||||
d.variableLabels,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkLabelName(l string) bool {
|
|
||||||
return labelNameRE.MatchString(l) &&
|
|
||||||
!strings.HasPrefix(l, reservedLabelPrefix)
|
|
||||||
}
|
|
||||||
|
|
@ -1,181 +0,0 @@
|
||||||
// Copyright 2014 The Prometheus Authors
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
// Package prometheus provides metrics primitives to instrument code for
|
|
||||||
// monitoring. It also offers a registry for metrics. Sub-packages allow to
|
|
||||||
// expose the registered metrics via HTTP (package promhttp) or push them to a
|
|
||||||
// Pushgateway (package push).
|
|
||||||
//
|
|
||||||
// All exported functions and methods are safe to be used concurrently unless
|
|
||||||
//specified otherwise.
|
|
||||||
//
|
|
||||||
// A Basic Example
|
|
||||||
//
|
|
||||||
// As a starting point, a very basic usage example:
|
|
||||||
//
|
|
||||||
// package main
|
|
||||||
//
|
|
||||||
// import (
|
|
||||||
// "net/http"
|
|
||||||
//
|
|
||||||
// "github.com/prometheus/client_golang/prometheus"
|
|
||||||
// "github.com/prometheus/client_golang/prometheus/promhttp"
|
|
||||||
// )
|
|
||||||
//
|
|
||||||
// var (
|
|
||||||
// cpuTemp = prometheus.NewGauge(prometheus.GaugeOpts{
|
|
||||||
// Name: "cpu_temperature_celsius",
|
|
||||||
// Help: "Current temperature of the CPU.",
|
|
||||||
// })
|
|
||||||
// hdFailures = prometheus.NewCounterVec(
|
|
||||||
// prometheus.CounterOpts{
|
|
||||||
// Name: "hd_errors_total",
|
|
||||||
// Help: "Number of hard-disk errors.",
|
|
||||||
// },
|
|
||||||
// []string{"device"},
|
|
||||||
// )
|
|
||||||
// )
|
|
||||||
//
|
|
||||||
// func init() {
|
|
||||||
// // Metrics have to be registered to be exposed:
|
|
||||||
// prometheus.MustRegister(cpuTemp)
|
|
||||||
// prometheus.MustRegister(hdFailures)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// func main() {
|
|
||||||
// cpuTemp.Set(65.3)
|
|
||||||
// hdFailures.With(prometheus.Labels{"device":"/dev/sda"}).Inc()
|
|
||||||
//
|
|
||||||
// // The Handler function provides a default handler to expose metrics
|
|
||||||
// // via an HTTP server. "/metrics" is the usual endpoint for that.
|
|
||||||
// http.Handle("/metrics", promhttp.Handler())
|
|
||||||
// http.ListenAndServe(":8080", nil)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// This is a complete program that exports two metrics, a Gauge and a Counter,
|
|
||||||
// the latter with a label attached to turn it into a (one-dimensional) vector.
|
|
||||||
//
|
|
||||||
// Metrics
|
|
||||||
//
|
|
||||||
// The number of exported identifiers in this package might appear a bit
|
|
||||||
// overwhelming. Hovever, in addition to the basic plumbing shown in the example
|
|
||||||
// above, you only need to understand the different metric types and their
|
|
||||||
// vector versions for basic usage.
|
|
||||||
//
|
|
||||||
// Above, you have already touched the Counter and the Gauge. There are two more
|
|
||||||
// advanced metric types: the Summary and Histogram. A more thorough description
|
|
||||||
// of those four metric types can be found in the Prometheus docs:
|
|
||||||
// https://prometheus.io/docs/concepts/metric_types/
|
|
||||||
//
|
|
||||||
// A fifth "type" of metric is Untyped. It behaves like a Gauge, but signals the
|
|
||||||
// Prometheus server not to assume anything about its type.
|
|
||||||
//
|
|
||||||
// In addition to the fundamental metric types Gauge, Counter, Summary,
|
|
||||||
// Histogram, and Untyped, a very important part of the Prometheus data model is
|
|
||||||
// the partitioning of samples along dimensions called labels, which results in
|
|
||||||
// metric vectors. The fundamental types are GaugeVec, CounterVec, SummaryVec,
|
|
||||||
// HistogramVec, and UntypedVec.
|
|
||||||
//
|
|
||||||
// While only the fundamental metric types implement the Metric interface, both
|
|
||||||
// the metrics and their vector versions implement the Collector interface. A
|
|
||||||
// Collector manages the collection of a number of Metrics, but for convenience,
|
|
||||||
// a Metric can also “collect itself”. Note that Gauge, Counter, Summary,
|
|
||||||
// Histogram, and Untyped are interfaces themselves while GaugeVec, CounterVec,
|
|
||||||
// SummaryVec, HistogramVec, and UntypedVec are not.
|
|
||||||
//
|
|
||||||
// To create instances of Metrics and their vector versions, you need a suitable
|
|
||||||
// …Opts struct, i.e. GaugeOpts, CounterOpts, SummaryOpts,
|
|
||||||
// HistogramOpts, or UntypedOpts.
|
|
||||||
//
|
|
||||||
// Custom Collectors and constant Metrics
|
|
||||||
//
|
|
||||||
// While you could create your own implementations of Metric, most likely you
|
|
||||||
// will only ever implement the Collector interface on your own. At a first
|
|
||||||
// glance, a custom Collector seems handy to bundle Metrics for common
|
|
||||||
// registration (with the prime example of the different metric vectors above,
|
|
||||||
// which bundle all the metrics of the same name but with different labels).
|
|
||||||
//
|
|
||||||
// There is a more involved use case, too: If you already have metrics
|
|
||||||
// available, created outside of the Prometheus context, you don't need the
|
|
||||||
// interface of the various Metric types. You essentially want to mirror the
|
|
||||||
// existing numbers into Prometheus Metrics during collection. An own
|
|
||||||
// implementation of the Collector interface is perfect for that. You can create
|
|
||||||
// Metric instances “on the fly” using NewConstMetric, NewConstHistogram, and
|
|
||||||
// NewConstSummary (and their respective Must… versions). That will happen in
|
|
||||||
// the Collect method. The Describe method has to return separate Desc
|
|
||||||
// instances, representative of the “throw-away” metrics to be created
|
|
||||||
// later. NewDesc comes in handy to create those Desc instances.
|
|
||||||
//
|
|
||||||
// The Collector example illustrates the use case. You can also look at the
|
|
||||||
// source code of the processCollector (mirroring process metrics), the
|
|
||||||
// goCollector (mirroring Go metrics), or the expvarCollector (mirroring expvar
|
|
||||||
// metrics) as examples that are used in this package itself.
|
|
||||||
//
|
|
||||||
// If you just need to call a function to get a single float value to collect as
|
|
||||||
// a metric, GaugeFunc, CounterFunc, or UntypedFunc might be interesting
|
|
||||||
// shortcuts.
|
|
||||||
//
|
|
||||||
// Advanced Uses of the Registry
|
|
||||||
//
|
|
||||||
// While MustRegister is the by far most common way of registering a Collector,
|
|
||||||
// sometimes you might want to handle the errors the registration might
|
|
||||||
// cause. As suggested by the name, MustRegister panics if an error occurs. With
|
|
||||||
// the Register function, the error is returned and can be handled.
|
|
||||||
//
|
|
||||||
// An error is returned if the registered Collector is incompatible or
|
|
||||||
// inconsistent with already registered metrics. The registry aims for
|
|
||||||
// consistency of the collected metrics according to the Prometheus data
|
|
||||||
// model. Inconsistencies are ideally detected at registration time, not at
|
|
||||||
// collect time. The former will usually be detected at start-up time of a
|
|
||||||
// program, while the latter will only happen at scrape time, possibly not even
|
|
||||||
// on the first scrape if the inconsistency only becomes relevant later. That is
|
|
||||||
// the main reason why a Collector and a Metric have to describe themselves to
|
|
||||||
// the registry.
|
|
||||||
//
|
|
||||||
// So far, everything we did operated on the so-called default registry, as it
|
|
||||||
// can be found in the global DefaultRegistry variable. With NewRegistry, you
|
|
||||||
// can create a custom registry, or you can even implement the Registerer or
|
|
||||||
// Gatherer interfaces yourself. The methods Register and Unregister work in
|
|
||||||
// the same way on a custom registry as the global functions Register and
|
|
||||||
// Unregister on the default registry.
|
|
||||||
//
|
|
||||||
// There are a number of uses for custom registries: You can use registries
|
|
||||||
// with special properties, see NewPedanticRegistry. You can avoid global state,
|
|
||||||
// as it is imposed by the DefaultRegistry. You can use multiple registries at
|
|
||||||
// the same time to expose different metrics in different ways. You can use
|
|
||||||
// separate registries for testing purposes.
|
|
||||||
//
|
|
||||||
// Also note that the DefaultRegistry comes registered with a Collector for Go
|
|
||||||
// runtime metrics (via NewGoCollector) and a Collector for process metrics (via
|
|
||||||
// NewProcessCollector). With a custom registry, you are in control and decide
|
|
||||||
// yourself about the Collectors to register.
|
|
||||||
//
|
|
||||||
// HTTP Exposition
|
|
||||||
//
|
|
||||||
// The Registry implements the Gatherer interface. The caller of the Gather
|
|
||||||
// method can then expose the gathered metrics in some way. Usually, the metrics
|
|
||||||
// are served via HTTP on the /metrics endpoint. That's happening in the example
|
|
||||||
// above. The tools to expose metrics via HTTP are in the promhttp
|
|
||||||
// sub-package. (The top-level functions in the prometheus package are
|
|
||||||
// deprecated.)
|
|
||||||
//
|
|
||||||
// Pushing to the Pushgateway
|
|
||||||
//
|
|
||||||
// Function for pushing to the Pushgateway can be found in the push sub-package.
|
|
||||||
//
|
|
||||||
// Other Means of Exposition
|
|
||||||
//
|
|
||||||
// More ways of exposing metrics can easily be added. Sending metrics to
|
|
||||||
// Graphite would be an example that will soon be implemented.
|
|
||||||
package prometheus
|
|
||||||
|
|
@ -1,119 +0,0 @@
|
||||||
// Copyright 2014 The Prometheus Authors
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package prometheus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"expvar"
|
|
||||||
)
|
|
||||||
|
|
||||||
type expvarCollector struct {
|
|
||||||
exports map[string]*Desc
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewExpvarCollector returns a newly allocated expvar Collector that still has
|
|
||||||
// to be registered with a Prometheus registry.
|
|
||||||
//
|
|
||||||
// An expvar Collector collects metrics from the expvar interface. It provides a
|
|
||||||
// quick way to expose numeric values that are already exported via expvar as
|
|
||||||
// Prometheus metrics. Note that the data models of expvar and Prometheus are
|
|
||||||
// fundamentally different, and that the expvar Collector is inherently slower
|
|
||||||
// than native Prometheus metrics. Thus, the expvar Collector is probably great
|
|
||||||
// for experiments and prototying, but you should seriously consider a more
|
|
||||||
// direct implementation of Prometheus metrics for monitoring production
|
|
||||||
// systems.
|
|
||||||
//
|
|
||||||
// The exports map has the following meaning:
|
|
||||||
//
|
|
||||||
// The keys in the map correspond to expvar keys, i.e. for every expvar key you
|
|
||||||
// want to export as Prometheus metric, you need an entry in the exports
|
|
||||||
// map. The descriptor mapped to each key describes how to export the expvar
|
|
||||||
// value. It defines the name and the help string of the Prometheus metric
|
|
||||||
// proxying the expvar value. The type will always be Untyped.
|
|
||||||
//
|
|
||||||
// For descriptors without variable labels, the expvar value must be a number or
|
|
||||||
// a bool. The number is then directly exported as the Prometheus sample
|
|
||||||
// value. (For a bool, 'false' translates to 0 and 'true' to 1). Expvar values
|
|
||||||
// that are not numbers or bools are silently ignored.
|
|
||||||
//
|
|
||||||
// If the descriptor has one variable label, the expvar value must be an expvar
|
|
||||||
// map. The keys in the expvar map become the various values of the one
|
|
||||||
// Prometheus label. The values in the expvar map must be numbers or bools again
|
|
||||||
// as above.
|
|
||||||
//
|
|
||||||
// For descriptors with more than one variable label, the expvar must be a
|
|
||||||
// nested expvar map, i.e. where the values of the topmost map are maps again
|
|
||||||
// etc. until a depth is reached that corresponds to the number of labels. The
|
|
||||||
// leaves of that structure must be numbers or bools as above to serve as the
|
|
||||||
// sample values.
|
|
||||||
//
|
|
||||||
// Anything that does not fit into the scheme above is silently ignored.
|
|
||||||
func NewExpvarCollector(exports map[string]*Desc) Collector {
|
|
||||||
return &expvarCollector{
|
|
||||||
exports: exports,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Describe implements Collector.
|
|
||||||
func (e *expvarCollector) Describe(ch chan<- *Desc) {
|
|
||||||
for _, desc := range e.exports {
|
|
||||||
ch <- desc
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Collect implements Collector.
|
|
||||||
func (e *expvarCollector) Collect(ch chan<- Metric) {
|
|
||||||
for name, desc := range e.exports {
|
|
||||||
var m Metric
|
|
||||||
expVar := expvar.Get(name)
|
|
||||||
if expVar == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
var v interface{}
|
|
||||||
labels := make([]string, len(desc.variableLabels))
|
|
||||||
if err := json.Unmarshal([]byte(expVar.String()), &v); err != nil {
|
|
||||||
ch <- NewInvalidMetric(desc, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
var processValue func(v interface{}, i int)
|
|
||||||
processValue = func(v interface{}, i int) {
|
|
||||||
if i >= len(labels) {
|
|
||||||
copiedLabels := append(make([]string, 0, len(labels)), labels...)
|
|
||||||
switch v := v.(type) {
|
|
||||||
case float64:
|
|
||||||
m = MustNewConstMetric(desc, UntypedValue, v, copiedLabels...)
|
|
||||||
case bool:
|
|
||||||
if v {
|
|
||||||
m = MustNewConstMetric(desc, UntypedValue, 1, copiedLabels...)
|
|
||||||
} else {
|
|
||||||
m = MustNewConstMetric(desc, UntypedValue, 0, copiedLabels...)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ch <- m
|
|
||||||
return
|
|
||||||
}
|
|
||||||
vm, ok := v.(map[string]interface{})
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for lv, val := range vm {
|
|
||||||
labels[i] = lv
|
|
||||||
processValue(val, i+1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
processValue(v, 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
package prometheus
|
|
||||||
|
|
||||||
// Inline and byte-free variant of hash/fnv's fnv64a.
|
|
||||||
|
|
||||||
const (
|
|
||||||
offset64 = 14695981039346656037
|
|
||||||
prime64 = 1099511628211
|
|
||||||
)
|
|
||||||
|
|
||||||
// hashNew initializies a new fnv64a hash value.
|
|
||||||
func hashNew() uint64 {
|
|
||||||
return offset64
|
|
||||||
}
|
|
||||||
|
|
||||||
// hashAdd adds a string to a fnv64a hash value, returning the updated hash.
|
|
||||||
func hashAdd(h uint64, s string) uint64 {
|
|
||||||
for i := 0; i < len(s); i++ {
|
|
||||||
h ^= uint64(s[i])
|
|
||||||
h *= prime64
|
|
||||||
}
|
|
||||||
return h
|
|
||||||
}
|
|
||||||
|
|
||||||
// hashAddByte adds a byte to a fnv64a hash value, returning the updated hash.
|
|
||||||
func hashAddByte(h uint64, b byte) uint64 {
|
|
||||||
h ^= uint64(b)
|
|
||||||
h *= prime64
|
|
||||||
return h
|
|
||||||
}
|
|
||||||
|
|
@ -1,140 +0,0 @@
|
||||||
// Copyright 2014 The Prometheus Authors
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package prometheus
|
|
||||||
|
|
||||||
// Gauge is a Metric that represents a single numerical value that can
|
|
||||||
// arbitrarily go up and down.
|
|
||||||
//
|
|
||||||
// A Gauge is typically used for measured values like temperatures or current
|
|
||||||
// memory usage, but also "counts" that can go up and down, like the number of
|
|
||||||
// running goroutines.
|
|
||||||
//
|
|
||||||
// To create Gauge instances, use NewGauge.
|
|
||||||
type Gauge interface {
|
|
||||||
Metric
|
|
||||||
Collector
|
|
||||||
|
|
||||||
// Set sets the Gauge to an arbitrary value.
|
|
||||||
Set(float64)
|
|
||||||
// Inc increments the Gauge by 1.
|
|
||||||
Inc()
|
|
||||||
// Dec decrements the Gauge by 1.
|
|
||||||
Dec()
|
|
||||||
// Add adds the given value to the Gauge. (The value can be
|
|
||||||
// negative, resulting in a decrease of the Gauge.)
|
|
||||||
Add(float64)
|
|
||||||
// Sub subtracts the given value from the Gauge. (The value can be
|
|
||||||
// negative, resulting in an increase of the Gauge.)
|
|
||||||
Sub(float64)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GaugeOpts is an alias for Opts. See there for doc comments.
|
|
||||||
type GaugeOpts Opts
|
|
||||||
|
|
||||||
// NewGauge creates a new Gauge based on the provided GaugeOpts.
|
|
||||||
func NewGauge(opts GaugeOpts) Gauge {
|
|
||||||
return newValue(NewDesc(
|
|
||||||
BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
|
|
||||||
opts.Help,
|
|
||||||
nil,
|
|
||||||
opts.ConstLabels,
|
|
||||||
), GaugeValue, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GaugeVec is a Collector that bundles a set of Gauges that all share the same
|
|
||||||
// Desc, but have different values for their variable labels. This is used if
|
|
||||||
// you want to count the same thing partitioned by various dimensions
|
|
||||||
// (e.g. number of operations queued, partitioned by user and operation
|
|
||||||
// type). Create instances with NewGaugeVec.
|
|
||||||
type GaugeVec struct {
|
|
||||||
*MetricVec
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewGaugeVec creates a new GaugeVec based on the provided GaugeOpts and
|
|
||||||
// partitioned by the given label names. At least one label name must be
|
|
||||||
// provided.
|
|
||||||
func NewGaugeVec(opts GaugeOpts, labelNames []string) *GaugeVec {
|
|
||||||
desc := NewDesc(
|
|
||||||
BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
|
|
||||||
opts.Help,
|
|
||||||
labelNames,
|
|
||||||
opts.ConstLabels,
|
|
||||||
)
|
|
||||||
return &GaugeVec{
|
|
||||||
MetricVec: newMetricVec(desc, func(lvs ...string) Metric {
|
|
||||||
return newValue(desc, GaugeValue, 0, lvs...)
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMetricWithLabelValues replaces the method of the same name in
|
|
||||||
// MetricVec. The difference is that this method returns a Gauge and not a
|
|
||||||
// Metric so that no type conversion is required.
|
|
||||||
func (m *GaugeVec) GetMetricWithLabelValues(lvs ...string) (Gauge, error) {
|
|
||||||
metric, err := m.MetricVec.GetMetricWithLabelValues(lvs...)
|
|
||||||
if metric != nil {
|
|
||||||
return metric.(Gauge), err
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMetricWith replaces the method of the same name in MetricVec. The
|
|
||||||
// difference is that this method returns a Gauge and not a Metric so that no
|
|
||||||
// type conversion is required.
|
|
||||||
func (m *GaugeVec) GetMetricWith(labels Labels) (Gauge, error) {
|
|
||||||
metric, err := m.MetricVec.GetMetricWith(labels)
|
|
||||||
if metric != nil {
|
|
||||||
return metric.(Gauge), err
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithLabelValues works as GetMetricWithLabelValues, but panics where
|
|
||||||
// GetMetricWithLabelValues would have returned an error. By not returning an
|
|
||||||
// error, WithLabelValues allows shortcuts like
|
|
||||||
// myVec.WithLabelValues("404", "GET").Add(42)
|
|
||||||
func (m *GaugeVec) WithLabelValues(lvs ...string) Gauge {
|
|
||||||
return m.MetricVec.WithLabelValues(lvs...).(Gauge)
|
|
||||||
}
|
|
||||||
|
|
||||||
// With works as GetMetricWith, but panics where GetMetricWithLabels would have
|
|
||||||
// returned an error. By not returning an error, With allows shortcuts like
|
|
||||||
// myVec.With(Labels{"code": "404", "method": "GET"}).Add(42)
|
|
||||||
func (m *GaugeVec) With(labels Labels) Gauge {
|
|
||||||
return m.MetricVec.With(labels).(Gauge)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GaugeFunc is a Gauge whose value is determined at collect time by calling a
|
|
||||||
// provided function.
|
|
||||||
//
|
|
||||||
// To create GaugeFunc instances, use NewGaugeFunc.
|
|
||||||
type GaugeFunc interface {
|
|
||||||
Metric
|
|
||||||
Collector
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewGaugeFunc creates a new GaugeFunc based on the provided GaugeOpts. The
|
|
||||||
// value reported is determined by calling the given function from within the
|
|
||||||
// Write method. Take into account that metric collection may happen
|
|
||||||
// concurrently. If that results in concurrent calls to Write, like in the case
|
|
||||||
// where a GaugeFunc is directly registered with Prometheus, the provided
|
|
||||||
// function must be concurrency-safe.
|
|
||||||
func NewGaugeFunc(opts GaugeOpts, function func() float64) GaugeFunc {
|
|
||||||
return newValueFunc(NewDesc(
|
|
||||||
BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
|
|
||||||
opts.Help,
|
|
||||||
nil,
|
|
||||||
opts.ConstLabels,
|
|
||||||
), GaugeValue, function)
|
|
||||||
}
|
|
||||||
|
|
@ -1,263 +0,0 @@
|
||||||
package prometheus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"runtime"
|
|
||||||
"runtime/debug"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type goCollector struct {
|
|
||||||
goroutines Gauge
|
|
||||||
gcDesc *Desc
|
|
||||||
|
|
||||||
// metrics to describe and collect
|
|
||||||
metrics memStatsMetrics
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewGoCollector returns a collector which exports metrics about the current
|
|
||||||
// go process.
|
|
||||||
func NewGoCollector() Collector {
|
|
||||||
return &goCollector{
|
|
||||||
goroutines: NewGauge(GaugeOpts{
|
|
||||||
Namespace: "go",
|
|
||||||
Name: "goroutines",
|
|
||||||
Help: "Number of goroutines that currently exist.",
|
|
||||||
}),
|
|
||||||
gcDesc: NewDesc(
|
|
||||||
"go_gc_duration_seconds",
|
|
||||||
"A summary of the GC invocation durations.",
|
|
||||||
nil, nil),
|
|
||||||
metrics: memStatsMetrics{
|
|
||||||
{
|
|
||||||
desc: NewDesc(
|
|
||||||
memstatNamespace("alloc_bytes"),
|
|
||||||
"Number of bytes allocated and still in use.",
|
|
||||||
nil, nil,
|
|
||||||
),
|
|
||||||
eval: func(ms *runtime.MemStats) float64 { return float64(ms.Alloc) },
|
|
||||||
valType: GaugeValue,
|
|
||||||
}, {
|
|
||||||
desc: NewDesc(
|
|
||||||
memstatNamespace("alloc_bytes_total"),
|
|
||||||
"Total number of bytes allocated, even if freed.",
|
|
||||||
nil, nil,
|
|
||||||
),
|
|
||||||
eval: func(ms *runtime.MemStats) float64 { return float64(ms.TotalAlloc) },
|
|
||||||
valType: CounterValue,
|
|
||||||
}, {
|
|
||||||
desc: NewDesc(
|
|
||||||
memstatNamespace("sys_bytes"),
|
|
||||||
"Number of bytes obtained by system. Sum of all system allocations.",
|
|
||||||
nil, nil,
|
|
||||||
),
|
|
||||||
eval: func(ms *runtime.MemStats) float64 { return float64(ms.Sys) },
|
|
||||||
valType: GaugeValue,
|
|
||||||
}, {
|
|
||||||
desc: NewDesc(
|
|
||||||
memstatNamespace("lookups_total"),
|
|
||||||
"Total number of pointer lookups.",
|
|
||||||
nil, nil,
|
|
||||||
),
|
|
||||||
eval: func(ms *runtime.MemStats) float64 { return float64(ms.Lookups) },
|
|
||||||
valType: CounterValue,
|
|
||||||
}, {
|
|
||||||
desc: NewDesc(
|
|
||||||
memstatNamespace("mallocs_total"),
|
|
||||||
"Total number of mallocs.",
|
|
||||||
nil, nil,
|
|
||||||
),
|
|
||||||
eval: func(ms *runtime.MemStats) float64 { return float64(ms.Mallocs) },
|
|
||||||
valType: CounterValue,
|
|
||||||
}, {
|
|
||||||
desc: NewDesc(
|
|
||||||
memstatNamespace("frees_total"),
|
|
||||||
"Total number of frees.",
|
|
||||||
nil, nil,
|
|
||||||
),
|
|
||||||
eval: func(ms *runtime.MemStats) float64 { return float64(ms.Frees) },
|
|
||||||
valType: CounterValue,
|
|
||||||
}, {
|
|
||||||
desc: NewDesc(
|
|
||||||
memstatNamespace("heap_alloc_bytes"),
|
|
||||||
"Number of heap bytes allocated and still in use.",
|
|
||||||
nil, nil,
|
|
||||||
),
|
|
||||||
eval: func(ms *runtime.MemStats) float64 { return float64(ms.HeapAlloc) },
|
|
||||||
valType: GaugeValue,
|
|
||||||
}, {
|
|
||||||
desc: NewDesc(
|
|
||||||
memstatNamespace("heap_sys_bytes"),
|
|
||||||
"Number of heap bytes obtained from system.",
|
|
||||||
nil, nil,
|
|
||||||
),
|
|
||||||
eval: func(ms *runtime.MemStats) float64 { return float64(ms.HeapSys) },
|
|
||||||
valType: GaugeValue,
|
|
||||||
}, {
|
|
||||||
desc: NewDesc(
|
|
||||||
memstatNamespace("heap_idle_bytes"),
|
|
||||||
"Number of heap bytes waiting to be used.",
|
|
||||||
nil, nil,
|
|
||||||
),
|
|
||||||
eval: func(ms *runtime.MemStats) float64 { return float64(ms.HeapIdle) },
|
|
||||||
valType: GaugeValue,
|
|
||||||
}, {
|
|
||||||
desc: NewDesc(
|
|
||||||
memstatNamespace("heap_inuse_bytes"),
|
|
||||||
"Number of heap bytes that are in use.",
|
|
||||||
nil, nil,
|
|
||||||
),
|
|
||||||
eval: func(ms *runtime.MemStats) float64 { return float64(ms.HeapInuse) },
|
|
||||||
valType: GaugeValue,
|
|
||||||
}, {
|
|
||||||
desc: NewDesc(
|
|
||||||
memstatNamespace("heap_released_bytes_total"),
|
|
||||||
"Total number of heap bytes released to OS.",
|
|
||||||
nil, nil,
|
|
||||||
),
|
|
||||||
eval: func(ms *runtime.MemStats) float64 { return float64(ms.HeapReleased) },
|
|
||||||
valType: CounterValue,
|
|
||||||
}, {
|
|
||||||
desc: NewDesc(
|
|
||||||
memstatNamespace("heap_objects"),
|
|
||||||
"Number of allocated objects.",
|
|
||||||
nil, nil,
|
|
||||||
),
|
|
||||||
eval: func(ms *runtime.MemStats) float64 { return float64(ms.HeapObjects) },
|
|
||||||
valType: GaugeValue,
|
|
||||||
}, {
|
|
||||||
desc: NewDesc(
|
|
||||||
memstatNamespace("stack_inuse_bytes"),
|
|
||||||
"Number of bytes in use by the stack allocator.",
|
|
||||||
nil, nil,
|
|
||||||
),
|
|
||||||
eval: func(ms *runtime.MemStats) float64 { return float64(ms.StackInuse) },
|
|
||||||
valType: GaugeValue,
|
|
||||||
}, {
|
|
||||||
desc: NewDesc(
|
|
||||||
memstatNamespace("stack_sys_bytes"),
|
|
||||||
"Number of bytes obtained from system for stack allocator.",
|
|
||||||
nil, nil,
|
|
||||||
),
|
|
||||||
eval: func(ms *runtime.MemStats) float64 { return float64(ms.StackSys) },
|
|
||||||
valType: GaugeValue,
|
|
||||||
}, {
|
|
||||||
desc: NewDesc(
|
|
||||||
memstatNamespace("mspan_inuse_bytes"),
|
|
||||||
"Number of bytes in use by mspan structures.",
|
|
||||||
nil, nil,
|
|
||||||
),
|
|
||||||
eval: func(ms *runtime.MemStats) float64 { return float64(ms.MSpanInuse) },
|
|
||||||
valType: GaugeValue,
|
|
||||||
}, {
|
|
||||||
desc: NewDesc(
|
|
||||||
memstatNamespace("mspan_sys_bytes"),
|
|
||||||
"Number of bytes used for mspan structures obtained from system.",
|
|
||||||
nil, nil,
|
|
||||||
),
|
|
||||||
eval: func(ms *runtime.MemStats) float64 { return float64(ms.MSpanSys) },
|
|
||||||
valType: GaugeValue,
|
|
||||||
}, {
|
|
||||||
desc: NewDesc(
|
|
||||||
memstatNamespace("mcache_inuse_bytes"),
|
|
||||||
"Number of bytes in use by mcache structures.",
|
|
||||||
nil, nil,
|
|
||||||
),
|
|
||||||
eval: func(ms *runtime.MemStats) float64 { return float64(ms.MCacheInuse) },
|
|
||||||
valType: GaugeValue,
|
|
||||||
}, {
|
|
||||||
desc: NewDesc(
|
|
||||||
memstatNamespace("mcache_sys_bytes"),
|
|
||||||
"Number of bytes used for mcache structures obtained from system.",
|
|
||||||
nil, nil,
|
|
||||||
),
|
|
||||||
eval: func(ms *runtime.MemStats) float64 { return float64(ms.MCacheSys) },
|
|
||||||
valType: GaugeValue,
|
|
||||||
}, {
|
|
||||||
desc: NewDesc(
|
|
||||||
memstatNamespace("buck_hash_sys_bytes"),
|
|
||||||
"Number of bytes used by the profiling bucket hash table.",
|
|
||||||
nil, nil,
|
|
||||||
),
|
|
||||||
eval: func(ms *runtime.MemStats) float64 { return float64(ms.BuckHashSys) },
|
|
||||||
valType: GaugeValue,
|
|
||||||
}, {
|
|
||||||
desc: NewDesc(
|
|
||||||
memstatNamespace("gc_sys_bytes"),
|
|
||||||
"Number of bytes used for garbage collection system metadata.",
|
|
||||||
nil, nil,
|
|
||||||
),
|
|
||||||
eval: func(ms *runtime.MemStats) float64 { return float64(ms.GCSys) },
|
|
||||||
valType: GaugeValue,
|
|
||||||
}, {
|
|
||||||
desc: NewDesc(
|
|
||||||
memstatNamespace("other_sys_bytes"),
|
|
||||||
"Number of bytes used for other system allocations.",
|
|
||||||
nil, nil,
|
|
||||||
),
|
|
||||||
eval: func(ms *runtime.MemStats) float64 { return float64(ms.OtherSys) },
|
|
||||||
valType: GaugeValue,
|
|
||||||
}, {
|
|
||||||
desc: NewDesc(
|
|
||||||
memstatNamespace("next_gc_bytes"),
|
|
||||||
"Number of heap bytes when next garbage collection will take place.",
|
|
||||||
nil, nil,
|
|
||||||
),
|
|
||||||
eval: func(ms *runtime.MemStats) float64 { return float64(ms.NextGC) },
|
|
||||||
valType: GaugeValue,
|
|
||||||
}, {
|
|
||||||
desc: NewDesc(
|
|
||||||
memstatNamespace("last_gc_time_seconds"),
|
|
||||||
"Number of seconds since 1970 of last garbage collection.",
|
|
||||||
nil, nil,
|
|
||||||
),
|
|
||||||
eval: func(ms *runtime.MemStats) float64 { return float64(ms.LastGC) / 1e9 },
|
|
||||||
valType: GaugeValue,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func memstatNamespace(s string) string {
|
|
||||||
return fmt.Sprintf("go_memstats_%s", s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Describe returns all descriptions of the collector.
|
|
||||||
func (c *goCollector) Describe(ch chan<- *Desc) {
|
|
||||||
ch <- c.goroutines.Desc()
|
|
||||||
ch <- c.gcDesc
|
|
||||||
|
|
||||||
for _, i := range c.metrics {
|
|
||||||
ch <- i.desc
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Collect returns the current state of all metrics of the collector.
|
|
||||||
func (c *goCollector) Collect(ch chan<- Metric) {
|
|
||||||
c.goroutines.Set(float64(runtime.NumGoroutine()))
|
|
||||||
ch <- c.goroutines
|
|
||||||
|
|
||||||
var stats debug.GCStats
|
|
||||||
stats.PauseQuantiles = make([]time.Duration, 5)
|
|
||||||
debug.ReadGCStats(&stats)
|
|
||||||
|
|
||||||
quantiles := make(map[float64]float64)
|
|
||||||
for idx, pq := range stats.PauseQuantiles[1:] {
|
|
||||||
quantiles[float64(idx+1)/float64(len(stats.PauseQuantiles)-1)] = pq.Seconds()
|
|
||||||
}
|
|
||||||
quantiles[0.0] = stats.PauseQuantiles[0].Seconds()
|
|
||||||
ch <- MustNewConstSummary(c.gcDesc, uint64(stats.NumGC), float64(stats.PauseTotal.Seconds()), quantiles)
|
|
||||||
|
|
||||||
ms := &runtime.MemStats{}
|
|
||||||
runtime.ReadMemStats(ms)
|
|
||||||
for _, i := range c.metrics {
|
|
||||||
ch <- MustNewConstMetric(i.desc, i.valType, i.eval(ms))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// memStatsMetrics provide description, value, and value type for memstat metrics.
|
|
||||||
type memStatsMetrics []struct {
|
|
||||||
desc *Desc
|
|
||||||
eval func(*runtime.MemStats) float64
|
|
||||||
valType ValueType
|
|
||||||
}
|
|
||||||
|
|
@ -1,444 +0,0 @@
|
||||||
// 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 prometheus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"math"
|
|
||||||
"sort"
|
|
||||||
"sync/atomic"
|
|
||||||
|
|
||||||
"github.com/golang/protobuf/proto"
|
|
||||||
|
|
||||||
dto "github.com/prometheus/client_model/go"
|
|
||||||
)
|
|
||||||
|
|
||||||
// A Histogram counts individual observations from an event or sample stream in
|
|
||||||
// configurable buckets. Similar to a summary, it also provides a sum of
|
|
||||||
// observations and an observation count.
|
|
||||||
//
|
|
||||||
// On the Prometheus server, quantiles can be calculated from a Histogram using
|
|
||||||
// the histogram_quantile function in the query language.
|
|
||||||
//
|
|
||||||
// Note that Histograms, in contrast to Summaries, can be aggregated with the
|
|
||||||
// Prometheus query language (see the documentation for detailed
|
|
||||||
// procedures). However, Histograms require the user to pre-define suitable
|
|
||||||
// buckets, and they are in general less accurate. The Observe method of a
|
|
||||||
// Histogram has a very low performance overhead in comparison with the Observe
|
|
||||||
// method of a Summary.
|
|
||||||
//
|
|
||||||
// To create Histogram instances, use NewHistogram.
|
|
||||||
type Histogram interface {
|
|
||||||
Metric
|
|
||||||
Collector
|
|
||||||
|
|
||||||
// Observe adds a single observation to the histogram.
|
|
||||||
Observe(float64)
|
|
||||||
}
|
|
||||||
|
|
||||||
// bucketLabel is used for the label that defines the upper bound of a
|
|
||||||
// bucket of a histogram ("le" -> "less or equal").
|
|
||||||
const bucketLabel = "le"
|
|
||||||
|
|
||||||
// DefBuckets are the default Histogram buckets. The default buckets are
|
|
||||||
// tailored to broadly measure the response time (in seconds) of a network
|
|
||||||
// service. Most likely, however, you will be required to define buckets
|
|
||||||
// customized to your use case.
|
|
||||||
var (
|
|
||||||
DefBuckets = []float64{.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10}
|
|
||||||
|
|
||||||
errBucketLabelNotAllowed = fmt.Errorf(
|
|
||||||
"%q is not allowed as label name in histograms", bucketLabel,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
// LinearBuckets creates 'count' buckets, each 'width' wide, where the lowest
|
|
||||||
// bucket has an upper bound of 'start'. The final +Inf bucket is not counted
|
|
||||||
// and not included in the returned slice. The returned slice is meant to be
|
|
||||||
// used for the Buckets field of HistogramOpts.
|
|
||||||
//
|
|
||||||
// The function panics if 'count' is zero or negative.
|
|
||||||
func LinearBuckets(start, width float64, count int) []float64 {
|
|
||||||
if count < 1 {
|
|
||||||
panic("LinearBuckets needs a positive count")
|
|
||||||
}
|
|
||||||
buckets := make([]float64, count)
|
|
||||||
for i := range buckets {
|
|
||||||
buckets[i] = start
|
|
||||||
start += width
|
|
||||||
}
|
|
||||||
return buckets
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExponentialBuckets creates 'count' buckets, where the lowest bucket has an
|
|
||||||
// upper bound of 'start' and each following bucket's upper bound is 'factor'
|
|
||||||
// times the previous bucket's upper bound. The final +Inf bucket is not counted
|
|
||||||
// and not included in the returned slice. The returned slice is meant to be
|
|
||||||
// used for the Buckets field of HistogramOpts.
|
|
||||||
//
|
|
||||||
// The function panics if 'count' is 0 or negative, if 'start' is 0 or negative,
|
|
||||||
// or if 'factor' is less than or equal 1.
|
|
||||||
func ExponentialBuckets(start, factor float64, count int) []float64 {
|
|
||||||
if count < 1 {
|
|
||||||
panic("ExponentialBuckets needs a positive count")
|
|
||||||
}
|
|
||||||
if start <= 0 {
|
|
||||||
panic("ExponentialBuckets needs a positive start value")
|
|
||||||
}
|
|
||||||
if factor <= 1 {
|
|
||||||
panic("ExponentialBuckets needs a factor greater than 1")
|
|
||||||
}
|
|
||||||
buckets := make([]float64, count)
|
|
||||||
for i := range buckets {
|
|
||||||
buckets[i] = start
|
|
||||||
start *= factor
|
|
||||||
}
|
|
||||||
return buckets
|
|
||||||
}
|
|
||||||
|
|
||||||
// HistogramOpts bundles the options for creating a Histogram metric. It is
|
|
||||||
// mandatory to set Name and Help to a non-empty string. All other fields are
|
|
||||||
// optional and can safely be left at their zero value.
|
|
||||||
type HistogramOpts struct {
|
|
||||||
// Namespace, Subsystem, and Name are components of the fully-qualified
|
|
||||||
// name of the Histogram (created by joining these components with
|
|
||||||
// "_"). Only Name is mandatory, the others merely help structuring the
|
|
||||||
// name. Note that the fully-qualified name of the Histogram must be a
|
|
||||||
// valid Prometheus metric name.
|
|
||||||
Namespace string
|
|
||||||
Subsystem string
|
|
||||||
Name string
|
|
||||||
|
|
||||||
// Help provides information about this Histogram. Mandatory!
|
|
||||||
//
|
|
||||||
// Metrics with the same fully-qualified name must have the same Help
|
|
||||||
// string.
|
|
||||||
Help string
|
|
||||||
|
|
||||||
// ConstLabels are used to attach fixed labels to this
|
|
||||||
// Histogram. Histograms with the same fully-qualified name must have the
|
|
||||||
// same label names in their ConstLabels.
|
|
||||||
//
|
|
||||||
// Note that in most cases, labels have a value that varies during the
|
|
||||||
// lifetime of a process. Those labels are usually managed with a
|
|
||||||
// HistogramVec. ConstLabels serve only special purposes. One is for the
|
|
||||||
// special case where the value of a label does not change during the
|
|
||||||
// lifetime of a process, e.g. if the revision of the running binary is
|
|
||||||
// put into a label. Another, more advanced purpose is if more than one
|
|
||||||
// Collector needs to collect Histograms with the same fully-qualified
|
|
||||||
// name. In that case, those Summaries must differ in the values of
|
|
||||||
// their ConstLabels. See the Collector examples.
|
|
||||||
//
|
|
||||||
// If the value of a label never changes (not even between binaries),
|
|
||||||
// that label most likely should not be a label at all (but part of the
|
|
||||||
// metric name).
|
|
||||||
ConstLabels Labels
|
|
||||||
|
|
||||||
// Buckets defines the buckets into which observations are counted. Each
|
|
||||||
// element in the slice is the upper inclusive bound of a bucket. The
|
|
||||||
// values must be sorted in strictly increasing order. There is no need
|
|
||||||
// to add a highest bucket with +Inf bound, it will be added
|
|
||||||
// implicitly. The default value is DefBuckets.
|
|
||||||
Buckets []float64
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewHistogram creates a new Histogram based on the provided HistogramOpts. It
|
|
||||||
// panics if the buckets in HistogramOpts are not in strictly increasing order.
|
|
||||||
func NewHistogram(opts HistogramOpts) Histogram {
|
|
||||||
return newHistogram(
|
|
||||||
NewDesc(
|
|
||||||
BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
|
|
||||||
opts.Help,
|
|
||||||
nil,
|
|
||||||
opts.ConstLabels,
|
|
||||||
),
|
|
||||||
opts,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newHistogram(desc *Desc, opts HistogramOpts, labelValues ...string) Histogram {
|
|
||||||
if len(desc.variableLabels) != len(labelValues) {
|
|
||||||
panic(errInconsistentCardinality)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, n := range desc.variableLabels {
|
|
||||||
if n == bucketLabel {
|
|
||||||
panic(errBucketLabelNotAllowed)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, lp := range desc.constLabelPairs {
|
|
||||||
if lp.GetName() == bucketLabel {
|
|
||||||
panic(errBucketLabelNotAllowed)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(opts.Buckets) == 0 {
|
|
||||||
opts.Buckets = DefBuckets
|
|
||||||
}
|
|
||||||
|
|
||||||
h := &histogram{
|
|
||||||
desc: desc,
|
|
||||||
upperBounds: opts.Buckets,
|
|
||||||
labelPairs: makeLabelPairs(desc, labelValues),
|
|
||||||
}
|
|
||||||
for i, upperBound := range h.upperBounds {
|
|
||||||
if i < len(h.upperBounds)-1 {
|
|
||||||
if upperBound >= h.upperBounds[i+1] {
|
|
||||||
panic(fmt.Errorf(
|
|
||||||
"histogram buckets must be in increasing order: %f >= %f",
|
|
||||||
upperBound, h.upperBounds[i+1],
|
|
||||||
))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if math.IsInf(upperBound, +1) {
|
|
||||||
// The +Inf bucket is implicit. Remove it here.
|
|
||||||
h.upperBounds = h.upperBounds[:i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Finally we know the final length of h.upperBounds and can make counts.
|
|
||||||
h.counts = make([]uint64, len(h.upperBounds))
|
|
||||||
|
|
||||||
h.init(h) // Init self-collection.
|
|
||||||
return h
|
|
||||||
}
|
|
||||||
|
|
||||||
type histogram struct {
|
|
||||||
// sumBits contains the bits of the float64 representing the sum of all
|
|
||||||
// observations. sumBits and count have to go first in the struct to
|
|
||||||
// guarantee alignment for atomic operations.
|
|
||||||
// http://golang.org/pkg/sync/atomic/#pkg-note-BUG
|
|
||||||
sumBits uint64
|
|
||||||
count uint64
|
|
||||||
|
|
||||||
selfCollector
|
|
||||||
// Note that there is no mutex required.
|
|
||||||
|
|
||||||
desc *Desc
|
|
||||||
|
|
||||||
upperBounds []float64
|
|
||||||
counts []uint64
|
|
||||||
|
|
||||||
labelPairs []*dto.LabelPair
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *histogram) Desc() *Desc {
|
|
||||||
return h.desc
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *histogram) Observe(v float64) {
|
|
||||||
// TODO(beorn7): For small numbers of buckets (<30), a linear search is
|
|
||||||
// slightly faster than the binary search. If we really care, we could
|
|
||||||
// switch from one search strategy to the other depending on the number
|
|
||||||
// of buckets.
|
|
||||||
//
|
|
||||||
// Microbenchmarks (BenchmarkHistogramNoLabels):
|
|
||||||
// 11 buckets: 38.3 ns/op linear - binary 48.7 ns/op
|
|
||||||
// 100 buckets: 78.1 ns/op linear - binary 54.9 ns/op
|
|
||||||
// 300 buckets: 154 ns/op linear - binary 61.6 ns/op
|
|
||||||
i := sort.SearchFloat64s(h.upperBounds, v)
|
|
||||||
if i < len(h.counts) {
|
|
||||||
atomic.AddUint64(&h.counts[i], 1)
|
|
||||||
}
|
|
||||||
atomic.AddUint64(&h.count, 1)
|
|
||||||
for {
|
|
||||||
oldBits := atomic.LoadUint64(&h.sumBits)
|
|
||||||
newBits := math.Float64bits(math.Float64frombits(oldBits) + v)
|
|
||||||
if atomic.CompareAndSwapUint64(&h.sumBits, oldBits, newBits) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *histogram) Write(out *dto.Metric) error {
|
|
||||||
his := &dto.Histogram{}
|
|
||||||
buckets := make([]*dto.Bucket, len(h.upperBounds))
|
|
||||||
|
|
||||||
his.SampleSum = proto.Float64(math.Float64frombits(atomic.LoadUint64(&h.sumBits)))
|
|
||||||
his.SampleCount = proto.Uint64(atomic.LoadUint64(&h.count))
|
|
||||||
var count uint64
|
|
||||||
for i, upperBound := range h.upperBounds {
|
|
||||||
count += atomic.LoadUint64(&h.counts[i])
|
|
||||||
buckets[i] = &dto.Bucket{
|
|
||||||
CumulativeCount: proto.Uint64(count),
|
|
||||||
UpperBound: proto.Float64(upperBound),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
his.Bucket = buckets
|
|
||||||
out.Histogram = his
|
|
||||||
out.Label = h.labelPairs
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// HistogramVec is a Collector that bundles a set of Histograms that all share the
|
|
||||||
// same Desc, but have different values for their variable labels. This is used
|
|
||||||
// if you want to count the same thing partitioned by various dimensions
|
|
||||||
// (e.g. HTTP request latencies, partitioned by status code and method). Create
|
|
||||||
// instances with NewHistogramVec.
|
|
||||||
type HistogramVec struct {
|
|
||||||
*MetricVec
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewHistogramVec creates a new HistogramVec based on the provided HistogramOpts and
|
|
||||||
// partitioned by the given label names. At least one label name must be
|
|
||||||
// provided.
|
|
||||||
func NewHistogramVec(opts HistogramOpts, labelNames []string) *HistogramVec {
|
|
||||||
desc := NewDesc(
|
|
||||||
BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
|
|
||||||
opts.Help,
|
|
||||||
labelNames,
|
|
||||||
opts.ConstLabels,
|
|
||||||
)
|
|
||||||
return &HistogramVec{
|
|
||||||
MetricVec: newMetricVec(desc, func(lvs ...string) Metric {
|
|
||||||
return newHistogram(desc, opts, lvs...)
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMetricWithLabelValues replaces the method of the same name in
|
|
||||||
// MetricVec. The difference is that this method returns a Histogram and not a
|
|
||||||
// Metric so that no type conversion is required.
|
|
||||||
func (m *HistogramVec) GetMetricWithLabelValues(lvs ...string) (Histogram, error) {
|
|
||||||
metric, err := m.MetricVec.GetMetricWithLabelValues(lvs...)
|
|
||||||
if metric != nil {
|
|
||||||
return metric.(Histogram), err
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMetricWith replaces the method of the same name in MetricVec. The
|
|
||||||
// difference is that this method returns a Histogram and not a Metric so that no
|
|
||||||
// type conversion is required.
|
|
||||||
func (m *HistogramVec) GetMetricWith(labels Labels) (Histogram, error) {
|
|
||||||
metric, err := m.MetricVec.GetMetricWith(labels)
|
|
||||||
if metric != nil {
|
|
||||||
return metric.(Histogram), err
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithLabelValues works as GetMetricWithLabelValues, but panics where
|
|
||||||
// GetMetricWithLabelValues would have returned an error. By not returning an
|
|
||||||
// error, WithLabelValues allows shortcuts like
|
|
||||||
// myVec.WithLabelValues("404", "GET").Observe(42.21)
|
|
||||||
func (m *HistogramVec) WithLabelValues(lvs ...string) Histogram {
|
|
||||||
return m.MetricVec.WithLabelValues(lvs...).(Histogram)
|
|
||||||
}
|
|
||||||
|
|
||||||
// With works as GetMetricWith, but panics where GetMetricWithLabels would have
|
|
||||||
// returned an error. By not returning an error, With allows shortcuts like
|
|
||||||
// myVec.With(Labels{"code": "404", "method": "GET"}).Observe(42.21)
|
|
||||||
func (m *HistogramVec) With(labels Labels) Histogram {
|
|
||||||
return m.MetricVec.With(labels).(Histogram)
|
|
||||||
}
|
|
||||||
|
|
||||||
type constHistogram struct {
|
|
||||||
desc *Desc
|
|
||||||
count uint64
|
|
||||||
sum float64
|
|
||||||
buckets map[float64]uint64
|
|
||||||
labelPairs []*dto.LabelPair
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *constHistogram) Desc() *Desc {
|
|
||||||
return h.desc
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *constHistogram) Write(out *dto.Metric) error {
|
|
||||||
his := &dto.Histogram{}
|
|
||||||
buckets := make([]*dto.Bucket, 0, len(h.buckets))
|
|
||||||
|
|
||||||
his.SampleCount = proto.Uint64(h.count)
|
|
||||||
his.SampleSum = proto.Float64(h.sum)
|
|
||||||
|
|
||||||
for upperBound, count := range h.buckets {
|
|
||||||
buckets = append(buckets, &dto.Bucket{
|
|
||||||
CumulativeCount: proto.Uint64(count),
|
|
||||||
UpperBound: proto.Float64(upperBound),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(buckets) > 0 {
|
|
||||||
sort.Sort(buckSort(buckets))
|
|
||||||
}
|
|
||||||
his.Bucket = buckets
|
|
||||||
|
|
||||||
out.Histogram = his
|
|
||||||
out.Label = h.labelPairs
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewConstHistogram returns a metric representing a Prometheus histogram with
|
|
||||||
// fixed values for the count, sum, and bucket counts. As those parameters
|
|
||||||
// cannot be changed, the returned value does not implement the Histogram
|
|
||||||
// interface (but only the Metric interface). Users of this package will not
|
|
||||||
// have much use for it in regular operations. However, when implementing custom
|
|
||||||
// Collectors, it is useful as a throw-away metric that is generated on the fly
|
|
||||||
// to send it to Prometheus in the Collect method.
|
|
||||||
//
|
|
||||||
// buckets is a map of upper bounds to cumulative counts, excluding the +Inf
|
|
||||||
// bucket.
|
|
||||||
//
|
|
||||||
// NewConstHistogram returns an error if the length of labelValues is not
|
|
||||||
// consistent with the variable labels in Desc.
|
|
||||||
func NewConstHistogram(
|
|
||||||
desc *Desc,
|
|
||||||
count uint64,
|
|
||||||
sum float64,
|
|
||||||
buckets map[float64]uint64,
|
|
||||||
labelValues ...string,
|
|
||||||
) (Metric, error) {
|
|
||||||
if len(desc.variableLabels) != len(labelValues) {
|
|
||||||
return nil, errInconsistentCardinality
|
|
||||||
}
|
|
||||||
return &constHistogram{
|
|
||||||
desc: desc,
|
|
||||||
count: count,
|
|
||||||
sum: sum,
|
|
||||||
buckets: buckets,
|
|
||||||
labelPairs: makeLabelPairs(desc, labelValues),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MustNewConstHistogram is a version of NewConstHistogram that panics where
|
|
||||||
// NewConstMetric would have returned an error.
|
|
||||||
func MustNewConstHistogram(
|
|
||||||
desc *Desc,
|
|
||||||
count uint64,
|
|
||||||
sum float64,
|
|
||||||
buckets map[float64]uint64,
|
|
||||||
labelValues ...string,
|
|
||||||
) Metric {
|
|
||||||
m, err := NewConstHistogram(desc, count, sum, buckets, labelValues...)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
type buckSort []*dto.Bucket
|
|
||||||
|
|
||||||
func (s buckSort) Len() int {
|
|
||||||
return len(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s buckSort) Swap(i, j int) {
|
|
||||||
s[i], s[j] = s[j], s[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s buckSort) Less(i, j int) bool {
|
|
||||||
return s[i].GetUpperBound() < s[j].GetUpperBound()
|
|
||||||
}
|
|
||||||
|
|
@ -1,490 +0,0 @@
|
||||||
// Copyright 2014 The Prometheus Authors
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package prometheus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"bytes"
|
|
||||||
"compress/gzip"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/prometheus/common/expfmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TODO(beorn7): Remove this whole file. It is a partial mirror of
|
|
||||||
// promhttp/http.go (to avoid circular import chains) where everything HTTP
|
|
||||||
// related should live. The functions here are just for avoiding
|
|
||||||
// breakage. Everything is deprecated.
|
|
||||||
|
|
||||||
const (
|
|
||||||
contentTypeHeader = "Content-Type"
|
|
||||||
contentLengthHeader = "Content-Length"
|
|
||||||
contentEncodingHeader = "Content-Encoding"
|
|
||||||
acceptEncodingHeader = "Accept-Encoding"
|
|
||||||
)
|
|
||||||
|
|
||||||
var bufPool sync.Pool
|
|
||||||
|
|
||||||
func getBuf() *bytes.Buffer {
|
|
||||||
buf := bufPool.Get()
|
|
||||||
if buf == nil {
|
|
||||||
return &bytes.Buffer{}
|
|
||||||
}
|
|
||||||
return buf.(*bytes.Buffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
func giveBuf(buf *bytes.Buffer) {
|
|
||||||
buf.Reset()
|
|
||||||
bufPool.Put(buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handler returns an HTTP handler for the DefaultGatherer. It is
|
|
||||||
// already instrumented with InstrumentHandler (using "prometheus" as handler
|
|
||||||
// name).
|
|
||||||
//
|
|
||||||
// Deprecated: Please note the issues described in the doc comment of
|
|
||||||
// InstrumentHandler. You might want to consider using promhttp.Handler instead
|
|
||||||
// (which is non instrumented).
|
|
||||||
func Handler() http.Handler {
|
|
||||||
return InstrumentHandler("prometheus", UninstrumentedHandler())
|
|
||||||
}
|
|
||||||
|
|
||||||
// UninstrumentedHandler returns an HTTP handler for the DefaultGatherer.
|
|
||||||
//
|
|
||||||
// Deprecated: Use promhttp.Handler instead. See there for further documentation.
|
|
||||||
func UninstrumentedHandler() http.Handler {
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
|
||||||
mfs, err := DefaultGatherer.Gather()
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, "An error has occurred during metrics collection:\n\n"+err.Error(), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
contentType := expfmt.Negotiate(req.Header)
|
|
||||||
buf := getBuf()
|
|
||||||
defer giveBuf(buf)
|
|
||||||
writer, encoding := decorateWriter(req, buf)
|
|
||||||
enc := expfmt.NewEncoder(writer, contentType)
|
|
||||||
var lastErr error
|
|
||||||
for _, mf := range mfs {
|
|
||||||
if err := enc.Encode(mf); err != nil {
|
|
||||||
lastErr = err
|
|
||||||
http.Error(w, "An error has occurred during metrics encoding:\n\n"+err.Error(), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if closer, ok := writer.(io.Closer); ok {
|
|
||||||
closer.Close()
|
|
||||||
}
|
|
||||||
if lastErr != nil && buf.Len() == 0 {
|
|
||||||
http.Error(w, "No metrics encoded, last error:\n\n"+err.Error(), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
header := w.Header()
|
|
||||||
header.Set(contentTypeHeader, string(contentType))
|
|
||||||
header.Set(contentLengthHeader, fmt.Sprint(buf.Len()))
|
|
||||||
if encoding != "" {
|
|
||||||
header.Set(contentEncodingHeader, encoding)
|
|
||||||
}
|
|
||||||
w.Write(buf.Bytes())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// decorateWriter wraps a writer to handle gzip compression if requested. It
|
|
||||||
// returns the decorated writer and the appropriate "Content-Encoding" header
|
|
||||||
// (which is empty if no compression is enabled).
|
|
||||||
func decorateWriter(request *http.Request, writer io.Writer) (io.Writer, string) {
|
|
||||||
header := request.Header.Get(acceptEncodingHeader)
|
|
||||||
parts := strings.Split(header, ",")
|
|
||||||
for _, part := range parts {
|
|
||||||
part := strings.TrimSpace(part)
|
|
||||||
if part == "gzip" || strings.HasPrefix(part, "gzip;") {
|
|
||||||
return gzip.NewWriter(writer), "gzip"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return writer, ""
|
|
||||||
}
|
|
||||||
|
|
||||||
var instLabels = []string{"method", "code"}
|
|
||||||
|
|
||||||
type nower interface {
|
|
||||||
Now() time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
type nowFunc func() time.Time
|
|
||||||
|
|
||||||
func (n nowFunc) Now() time.Time {
|
|
||||||
return n()
|
|
||||||
}
|
|
||||||
|
|
||||||
var now nower = nowFunc(func() time.Time {
|
|
||||||
return time.Now()
|
|
||||||
})
|
|
||||||
|
|
||||||
func nowSeries(t ...time.Time) nower {
|
|
||||||
return nowFunc(func() time.Time {
|
|
||||||
defer func() {
|
|
||||||
t = t[1:]
|
|
||||||
}()
|
|
||||||
|
|
||||||
return t[0]
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// InstrumentHandler wraps the given HTTP handler for instrumentation. It
|
|
||||||
// registers four metric collectors (if not already done) and reports HTTP
|
|
||||||
// metrics to the (newly or already) registered collectors: http_requests_total
|
|
||||||
// (CounterVec), http_request_duration_microseconds (Summary),
|
|
||||||
// http_request_size_bytes (Summary), http_response_size_bytes (Summary). Each
|
|
||||||
// has a constant label named "handler" with the provided handlerName as
|
|
||||||
// value. http_requests_total is a metric vector partitioned by HTTP method
|
|
||||||
// (label name "method") and HTTP status code (label name "code").
|
|
||||||
//
|
|
||||||
// Deprecated: InstrumentHandler has several issues:
|
|
||||||
//
|
|
||||||
// - It uses Summaries rather than Histograms. Summaries are not useful if
|
|
||||||
// aggregation across multiple instances is required.
|
|
||||||
//
|
|
||||||
// - It uses microseconds as unit, which is deprecated and should be replaced by
|
|
||||||
// seconds.
|
|
||||||
//
|
|
||||||
// - The size of the request is calculated in a separate goroutine. Since this
|
|
||||||
// calculator requires access to the request header, it creates a race with
|
|
||||||
// any writes to the header performed during request handling.
|
|
||||||
// httputil.ReverseProxy is a prominent example for a handler
|
|
||||||
// performing such writes.
|
|
||||||
//
|
|
||||||
// Upcoming versions of this package will provide ways of instrumenting HTTP
|
|
||||||
// handlers that are more flexible and have fewer issues. Please prefer direct
|
|
||||||
// instrumentation in the meantime.
|
|
||||||
func InstrumentHandler(handlerName string, handler http.Handler) http.HandlerFunc {
|
|
||||||
return InstrumentHandlerFunc(handlerName, handler.ServeHTTP)
|
|
||||||
}
|
|
||||||
|
|
||||||
// InstrumentHandlerFunc wraps the given function for instrumentation. It
|
|
||||||
// otherwise works in the same way as InstrumentHandler (and shares the same
|
|
||||||
// issues).
|
|
||||||
//
|
|
||||||
// Deprecated: InstrumentHandlerFunc is deprecated for the same reasons as
|
|
||||||
// InstrumentHandler is.
|
|
||||||
func InstrumentHandlerFunc(handlerName string, handlerFunc func(http.ResponseWriter, *http.Request)) http.HandlerFunc {
|
|
||||||
return InstrumentHandlerFuncWithOpts(
|
|
||||||
SummaryOpts{
|
|
||||||
Subsystem: "http",
|
|
||||||
ConstLabels: Labels{"handler": handlerName},
|
|
||||||
},
|
|
||||||
handlerFunc,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// InstrumentHandlerWithOpts works like InstrumentHandler (and shares the same
|
|
||||||
// issues) but provides more flexibility (at the cost of a more complex call
|
|
||||||
// syntax). As InstrumentHandler, this function registers four metric
|
|
||||||
// collectors, but it uses the provided SummaryOpts to create them. However, the
|
|
||||||
// fields "Name" and "Help" in the SummaryOpts are ignored. "Name" is replaced
|
|
||||||
// by "requests_total", "request_duration_microseconds", "request_size_bytes",
|
|
||||||
// and "response_size_bytes", respectively. "Help" is replaced by an appropriate
|
|
||||||
// help string. The names of the variable labels of the http_requests_total
|
|
||||||
// CounterVec are "method" (get, post, etc.), and "code" (HTTP status code).
|
|
||||||
//
|
|
||||||
// If InstrumentHandlerWithOpts is called as follows, it mimics exactly the
|
|
||||||
// behavior of InstrumentHandler:
|
|
||||||
//
|
|
||||||
// prometheus.InstrumentHandlerWithOpts(
|
|
||||||
// prometheus.SummaryOpts{
|
|
||||||
// Subsystem: "http",
|
|
||||||
// ConstLabels: prometheus.Labels{"handler": handlerName},
|
|
||||||
// },
|
|
||||||
// handler,
|
|
||||||
// )
|
|
||||||
//
|
|
||||||
// Technical detail: "requests_total" is a CounterVec, not a SummaryVec, so it
|
|
||||||
// cannot use SummaryOpts. Instead, a CounterOpts struct is created internally,
|
|
||||||
// and all its fields are set to the equally named fields in the provided
|
|
||||||
// SummaryOpts.
|
|
||||||
//
|
|
||||||
// Deprecated: InstrumentHandlerWithOpts is deprecated for the same reasons as
|
|
||||||
// InstrumentHandler is.
|
|
||||||
func InstrumentHandlerWithOpts(opts SummaryOpts, handler http.Handler) http.HandlerFunc {
|
|
||||||
return InstrumentHandlerFuncWithOpts(opts, handler.ServeHTTP)
|
|
||||||
}
|
|
||||||
|
|
||||||
// InstrumentHandlerFuncWithOpts works like InstrumentHandlerFunc (and shares
|
|
||||||
// the same issues) but provides more flexibility (at the cost of a more complex
|
|
||||||
// call syntax). See InstrumentHandlerWithOpts for details how the provided
|
|
||||||
// SummaryOpts are used.
|
|
||||||
//
|
|
||||||
// Deprecated: InstrumentHandlerFuncWithOpts is deprecated for the same reasons
|
|
||||||
// as InstrumentHandler is.
|
|
||||||
func InstrumentHandlerFuncWithOpts(opts SummaryOpts, handlerFunc func(http.ResponseWriter, *http.Request)) http.HandlerFunc {
|
|
||||||
reqCnt := NewCounterVec(
|
|
||||||
CounterOpts{
|
|
||||||
Namespace: opts.Namespace,
|
|
||||||
Subsystem: opts.Subsystem,
|
|
||||||
Name: "requests_total",
|
|
||||||
Help: "Total number of HTTP requests made.",
|
|
||||||
ConstLabels: opts.ConstLabels,
|
|
||||||
},
|
|
||||||
instLabels,
|
|
||||||
)
|
|
||||||
|
|
||||||
opts.Name = "request_duration_microseconds"
|
|
||||||
opts.Help = "The HTTP request latencies in microseconds."
|
|
||||||
reqDur := NewSummary(opts)
|
|
||||||
|
|
||||||
opts.Name = "request_size_bytes"
|
|
||||||
opts.Help = "The HTTP request sizes in bytes."
|
|
||||||
reqSz := NewSummary(opts)
|
|
||||||
|
|
||||||
opts.Name = "response_size_bytes"
|
|
||||||
opts.Help = "The HTTP response sizes in bytes."
|
|
||||||
resSz := NewSummary(opts)
|
|
||||||
|
|
||||||
regReqCnt := MustRegisterOrGet(reqCnt).(*CounterVec)
|
|
||||||
regReqDur := MustRegisterOrGet(reqDur).(Summary)
|
|
||||||
regReqSz := MustRegisterOrGet(reqSz).(Summary)
|
|
||||||
regResSz := MustRegisterOrGet(resSz).(Summary)
|
|
||||||
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
now := time.Now()
|
|
||||||
|
|
||||||
delegate := &responseWriterDelegator{ResponseWriter: w}
|
|
||||||
out := make(chan int)
|
|
||||||
urlLen := 0
|
|
||||||
if r.URL != nil {
|
|
||||||
urlLen = len(r.URL.String())
|
|
||||||
}
|
|
||||||
go computeApproximateRequestSize(r, out, urlLen)
|
|
||||||
|
|
||||||
_, cn := w.(http.CloseNotifier)
|
|
||||||
_, fl := w.(http.Flusher)
|
|
||||||
_, hj := w.(http.Hijacker)
|
|
||||||
_, rf := w.(io.ReaderFrom)
|
|
||||||
var rw http.ResponseWriter
|
|
||||||
if cn && fl && hj && rf {
|
|
||||||
rw = &fancyResponseWriterDelegator{delegate}
|
|
||||||
} else {
|
|
||||||
rw = delegate
|
|
||||||
}
|
|
||||||
handlerFunc(rw, r)
|
|
||||||
|
|
||||||
elapsed := float64(time.Since(now)) / float64(time.Microsecond)
|
|
||||||
|
|
||||||
method := sanitizeMethod(r.Method)
|
|
||||||
code := sanitizeCode(delegate.status)
|
|
||||||
regReqCnt.WithLabelValues(method, code).Inc()
|
|
||||||
regReqDur.Observe(elapsed)
|
|
||||||
regResSz.Observe(float64(delegate.written))
|
|
||||||
regReqSz.Observe(float64(<-out))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func computeApproximateRequestSize(r *http.Request, out chan int, s int) {
|
|
||||||
s += len(r.Method)
|
|
||||||
s += len(r.Proto)
|
|
||||||
for name, values := range r.Header {
|
|
||||||
s += len(name)
|
|
||||||
for _, value := range values {
|
|
||||||
s += len(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
s += len(r.Host)
|
|
||||||
|
|
||||||
// N.B. r.Form and r.MultipartForm are assumed to be included in r.URL.
|
|
||||||
|
|
||||||
if r.ContentLength != -1 {
|
|
||||||
s += int(r.ContentLength)
|
|
||||||
}
|
|
||||||
out <- s
|
|
||||||
}
|
|
||||||
|
|
||||||
type responseWriterDelegator struct {
|
|
||||||
http.ResponseWriter
|
|
||||||
|
|
||||||
handler, method string
|
|
||||||
status int
|
|
||||||
written int64
|
|
||||||
wroteHeader bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *responseWriterDelegator) WriteHeader(code int) {
|
|
||||||
r.status = code
|
|
||||||
r.wroteHeader = true
|
|
||||||
r.ResponseWriter.WriteHeader(code)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *responseWriterDelegator) Write(b []byte) (int, error) {
|
|
||||||
if !r.wroteHeader {
|
|
||||||
r.WriteHeader(http.StatusOK)
|
|
||||||
}
|
|
||||||
n, err := r.ResponseWriter.Write(b)
|
|
||||||
r.written += int64(n)
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
|
|
||||||
type fancyResponseWriterDelegator struct {
|
|
||||||
*responseWriterDelegator
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *fancyResponseWriterDelegator) CloseNotify() <-chan bool {
|
|
||||||
return f.ResponseWriter.(http.CloseNotifier).CloseNotify()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *fancyResponseWriterDelegator) Flush() {
|
|
||||||
f.ResponseWriter.(http.Flusher).Flush()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *fancyResponseWriterDelegator) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
|
||||||
return f.ResponseWriter.(http.Hijacker).Hijack()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *fancyResponseWriterDelegator) ReadFrom(r io.Reader) (int64, error) {
|
|
||||||
if !f.wroteHeader {
|
|
||||||
f.WriteHeader(http.StatusOK)
|
|
||||||
}
|
|
||||||
n, err := f.ResponseWriter.(io.ReaderFrom).ReadFrom(r)
|
|
||||||
f.written += n
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func sanitizeMethod(m string) string {
|
|
||||||
switch m {
|
|
||||||
case "GET", "get":
|
|
||||||
return "get"
|
|
||||||
case "PUT", "put":
|
|
||||||
return "put"
|
|
||||||
case "HEAD", "head":
|
|
||||||
return "head"
|
|
||||||
case "POST", "post":
|
|
||||||
return "post"
|
|
||||||
case "DELETE", "delete":
|
|
||||||
return "delete"
|
|
||||||
case "CONNECT", "connect":
|
|
||||||
return "connect"
|
|
||||||
case "OPTIONS", "options":
|
|
||||||
return "options"
|
|
||||||
case "NOTIFY", "notify":
|
|
||||||
return "notify"
|
|
||||||
default:
|
|
||||||
return strings.ToLower(m)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func sanitizeCode(s int) string {
|
|
||||||
switch s {
|
|
||||||
case 100:
|
|
||||||
return "100"
|
|
||||||
case 101:
|
|
||||||
return "101"
|
|
||||||
|
|
||||||
case 200:
|
|
||||||
return "200"
|
|
||||||
case 201:
|
|
||||||
return "201"
|
|
||||||
case 202:
|
|
||||||
return "202"
|
|
||||||
case 203:
|
|
||||||
return "203"
|
|
||||||
case 204:
|
|
||||||
return "204"
|
|
||||||
case 205:
|
|
||||||
return "205"
|
|
||||||
case 206:
|
|
||||||
return "206"
|
|
||||||
|
|
||||||
case 300:
|
|
||||||
return "300"
|
|
||||||
case 301:
|
|
||||||
return "301"
|
|
||||||
case 302:
|
|
||||||
return "302"
|
|
||||||
case 304:
|
|
||||||
return "304"
|
|
||||||
case 305:
|
|
||||||
return "305"
|
|
||||||
case 307:
|
|
||||||
return "307"
|
|
||||||
|
|
||||||
case 400:
|
|
||||||
return "400"
|
|
||||||
case 401:
|
|
||||||
return "401"
|
|
||||||
case 402:
|
|
||||||
return "402"
|
|
||||||
case 403:
|
|
||||||
return "403"
|
|
||||||
case 404:
|
|
||||||
return "404"
|
|
||||||
case 405:
|
|
||||||
return "405"
|
|
||||||
case 406:
|
|
||||||
return "406"
|
|
||||||
case 407:
|
|
||||||
return "407"
|
|
||||||
case 408:
|
|
||||||
return "408"
|
|
||||||
case 409:
|
|
||||||
return "409"
|
|
||||||
case 410:
|
|
||||||
return "410"
|
|
||||||
case 411:
|
|
||||||
return "411"
|
|
||||||
case 412:
|
|
||||||
return "412"
|
|
||||||
case 413:
|
|
||||||
return "413"
|
|
||||||
case 414:
|
|
||||||
return "414"
|
|
||||||
case 415:
|
|
||||||
return "415"
|
|
||||||
case 416:
|
|
||||||
return "416"
|
|
||||||
case 417:
|
|
||||||
return "417"
|
|
||||||
case 418:
|
|
||||||
return "418"
|
|
||||||
|
|
||||||
case 500:
|
|
||||||
return "500"
|
|
||||||
case 501:
|
|
||||||
return "501"
|
|
||||||
case 502:
|
|
||||||
return "502"
|
|
||||||
case 503:
|
|
||||||
return "503"
|
|
||||||
case 504:
|
|
||||||
return "504"
|
|
||||||
case 505:
|
|
||||||
return "505"
|
|
||||||
|
|
||||||
case 428:
|
|
||||||
return "428"
|
|
||||||
case 429:
|
|
||||||
return "429"
|
|
||||||
case 431:
|
|
||||||
return "431"
|
|
||||||
case 511:
|
|
||||||
return "511"
|
|
||||||
|
|
||||||
default:
|
|
||||||
return strconv.Itoa(s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,166 +0,0 @@
|
||||||
// Copyright 2014 The Prometheus Authors
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package prometheus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
dto "github.com/prometheus/client_model/go"
|
|
||||||
)
|
|
||||||
|
|
||||||
const separatorByte byte = 255
|
|
||||||
|
|
||||||
// A Metric models a single sample value with its meta data being exported to
|
|
||||||
// Prometheus. Implementations of Metric in this package are Gauge, Counter,
|
|
||||||
// Histogram, Summary, and Untyped.
|
|
||||||
type Metric interface {
|
|
||||||
// Desc returns the descriptor for the Metric. This method idempotently
|
|
||||||
// returns the same descriptor throughout the lifetime of the
|
|
||||||
// Metric. The returned descriptor is immutable by contract. A Metric
|
|
||||||
// unable to describe itself must return an invalid descriptor (created
|
|
||||||
// with NewInvalidDesc).
|
|
||||||
Desc() *Desc
|
|
||||||
// Write encodes the Metric into a "Metric" Protocol Buffer data
|
|
||||||
// transmission object.
|
|
||||||
//
|
|
||||||
// Metric implementations must observe concurrency safety as reads of
|
|
||||||
// this metric may occur at any time, and any blocking occurs at the
|
|
||||||
// expense of total performance of rendering all registered
|
|
||||||
// metrics. Ideally, Metric implementations should support concurrent
|
|
||||||
// readers.
|
|
||||||
//
|
|
||||||
// While populating dto.Metric, it is the responsibility of the
|
|
||||||
// implementation to ensure validity of the Metric protobuf (like valid
|
|
||||||
// UTF-8 strings or syntactically valid metric and label names). It is
|
|
||||||
// recommended to sort labels lexicographically. (Implementers may find
|
|
||||||
// LabelPairSorter useful for that.) Callers of Write should still make
|
|
||||||
// sure of sorting if they depend on it.
|
|
||||||
Write(*dto.Metric) error
|
|
||||||
// TODO(beorn7): The original rationale of passing in a pre-allocated
|
|
||||||
// dto.Metric protobuf to save allocations has disappeared. The
|
|
||||||
// signature of this method should be changed to "Write() (*dto.Metric,
|
|
||||||
// error)".
|
|
||||||
}
|
|
||||||
|
|
||||||
// Opts bundles the options for creating most Metric types. Each metric
|
|
||||||
// implementation XXX has its own XXXOpts type, but in most cases, it is just be
|
|
||||||
// an alias of this type (which might change when the requirement arises.)
|
|
||||||
//
|
|
||||||
// It is mandatory to set Name and Help to a non-empty string. All other fields
|
|
||||||
// are optional and can safely be left at their zero value.
|
|
||||||
type Opts struct {
|
|
||||||
// Namespace, Subsystem, and Name are components of the fully-qualified
|
|
||||||
// name of the Metric (created by joining these components with
|
|
||||||
// "_"). Only Name is mandatory, the others merely help structuring the
|
|
||||||
// name. Note that the fully-qualified name of the metric must be a
|
|
||||||
// valid Prometheus metric name.
|
|
||||||
Namespace string
|
|
||||||
Subsystem string
|
|
||||||
Name string
|
|
||||||
|
|
||||||
// Help provides information about this metric. Mandatory!
|
|
||||||
//
|
|
||||||
// Metrics with the same fully-qualified name must have the same Help
|
|
||||||
// string.
|
|
||||||
Help string
|
|
||||||
|
|
||||||
// ConstLabels are used to attach fixed labels to this metric. Metrics
|
|
||||||
// with the same fully-qualified name must have the same label names in
|
|
||||||
// their ConstLabels.
|
|
||||||
//
|
|
||||||
// Note that in most cases, labels have a value that varies during the
|
|
||||||
// lifetime of a process. Those labels are usually managed with a metric
|
|
||||||
// vector collector (like CounterVec, GaugeVec, UntypedVec). ConstLabels
|
|
||||||
// serve only special purposes. One is for the special case where the
|
|
||||||
// value of a label does not change during the lifetime of a process,
|
|
||||||
// e.g. if the revision of the running binary is put into a
|
|
||||||
// label. Another, more advanced purpose is if more than one Collector
|
|
||||||
// needs to collect Metrics with the same fully-qualified name. In that
|
|
||||||
// case, those Metrics must differ in the values of their
|
|
||||||
// ConstLabels. See the Collector examples.
|
|
||||||
//
|
|
||||||
// If the value of a label never changes (not even between binaries),
|
|
||||||
// that label most likely should not be a label at all (but part of the
|
|
||||||
// metric name).
|
|
||||||
ConstLabels Labels
|
|
||||||
}
|
|
||||||
|
|
||||||
// BuildFQName joins the given three name components by "_". Empty name
|
|
||||||
// components are ignored. If the name parameter itself is empty, an empty
|
|
||||||
// string is returned, no matter what. Metric implementations included in this
|
|
||||||
// library use this function internally to generate the fully-qualified metric
|
|
||||||
// name from the name component in their Opts. Users of the library will only
|
|
||||||
// need this function if they implement their own Metric or instantiate a Desc
|
|
||||||
// (with NewDesc) directly.
|
|
||||||
func BuildFQName(namespace, subsystem, name string) string {
|
|
||||||
if name == "" {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
switch {
|
|
||||||
case namespace != "" && subsystem != "":
|
|
||||||
return strings.Join([]string{namespace, subsystem, name}, "_")
|
|
||||||
case namespace != "":
|
|
||||||
return strings.Join([]string{namespace, name}, "_")
|
|
||||||
case subsystem != "":
|
|
||||||
return strings.Join([]string{subsystem, name}, "_")
|
|
||||||
}
|
|
||||||
return name
|
|
||||||
}
|
|
||||||
|
|
||||||
// LabelPairSorter implements sort.Interface. It is used to sort a slice of
|
|
||||||
// dto.LabelPair pointers. This is useful for implementing the Write method of
|
|
||||||
// custom metrics.
|
|
||||||
type LabelPairSorter []*dto.LabelPair
|
|
||||||
|
|
||||||
func (s LabelPairSorter) Len() int {
|
|
||||||
return len(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s LabelPairSorter) Swap(i, j int) {
|
|
||||||
s[i], s[j] = s[j], s[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s LabelPairSorter) Less(i, j int) bool {
|
|
||||||
return s[i].GetName() < s[j].GetName()
|
|
||||||
}
|
|
||||||
|
|
||||||
type hashSorter []uint64
|
|
||||||
|
|
||||||
func (s hashSorter) Len() int {
|
|
||||||
return len(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s hashSorter) Swap(i, j int) {
|
|
||||||
s[i], s[j] = s[j], s[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s hashSorter) Less(i, j int) bool {
|
|
||||||
return s[i] < s[j]
|
|
||||||
}
|
|
||||||
|
|
||||||
type invalidMetric struct {
|
|
||||||
desc *Desc
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewInvalidMetric returns a metric whose Write method always returns the
|
|
||||||
// provided error. It is useful if a Collector finds itself unable to collect
|
|
||||||
// a metric and wishes to report an error to the registry.
|
|
||||||
func NewInvalidMetric(desc *Desc, err error) Metric {
|
|
||||||
return &invalidMetric{desc, err}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *invalidMetric) Desc() *Desc { return m.desc }
|
|
||||||
|
|
||||||
func (m *invalidMetric) Write(*dto.Metric) error { return m.err }
|
|
||||||
|
|
@ -1,142 +0,0 @@
|
||||||
// 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 prometheus
|
|
||||||
|
|
||||||
import "github.com/prometheus/procfs"
|
|
||||||
|
|
||||||
type processCollector struct {
|
|
||||||
pid int
|
|
||||||
collectFn func(chan<- Metric)
|
|
||||||
pidFn func() (int, error)
|
|
||||||
cpuTotal Counter
|
|
||||||
openFDs, maxFDs Gauge
|
|
||||||
vsize, rss Gauge
|
|
||||||
startTime Gauge
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewProcessCollector returns a collector which exports the current state of
|
|
||||||
// process metrics including cpu, memory and file descriptor usage as well as
|
|
||||||
// the process start time for the given process id under the given namespace.
|
|
||||||
func NewProcessCollector(pid int, namespace string) Collector {
|
|
||||||
return NewProcessCollectorPIDFn(
|
|
||||||
func() (int, error) { return pid, nil },
|
|
||||||
namespace,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewProcessCollectorPIDFn returns a collector which exports the current state
|
|
||||||
// of process metrics including cpu, memory and file descriptor usage as well
|
|
||||||
// as the process start time under the given namespace. The given pidFn is
|
|
||||||
// called on each collect and is used to determine the process to export
|
|
||||||
// metrics for.
|
|
||||||
func NewProcessCollectorPIDFn(
|
|
||||||
pidFn func() (int, error),
|
|
||||||
namespace string,
|
|
||||||
) Collector {
|
|
||||||
c := processCollector{
|
|
||||||
pidFn: pidFn,
|
|
||||||
collectFn: func(chan<- Metric) {},
|
|
||||||
|
|
||||||
cpuTotal: NewCounter(CounterOpts{
|
|
||||||
Namespace: namespace,
|
|
||||||
Name: "process_cpu_seconds_total",
|
|
||||||
Help: "Total user and system CPU time spent in seconds.",
|
|
||||||
}),
|
|
||||||
openFDs: NewGauge(GaugeOpts{
|
|
||||||
Namespace: namespace,
|
|
||||||
Name: "process_open_fds",
|
|
||||||
Help: "Number of open file descriptors.",
|
|
||||||
}),
|
|
||||||
maxFDs: NewGauge(GaugeOpts{
|
|
||||||
Namespace: namespace,
|
|
||||||
Name: "process_max_fds",
|
|
||||||
Help: "Maximum number of open file descriptors.",
|
|
||||||
}),
|
|
||||||
vsize: NewGauge(GaugeOpts{
|
|
||||||
Namespace: namespace,
|
|
||||||
Name: "process_virtual_memory_bytes",
|
|
||||||
Help: "Virtual memory size in bytes.",
|
|
||||||
}),
|
|
||||||
rss: NewGauge(GaugeOpts{
|
|
||||||
Namespace: namespace,
|
|
||||||
Name: "process_resident_memory_bytes",
|
|
||||||
Help: "Resident memory size in bytes.",
|
|
||||||
}),
|
|
||||||
startTime: NewGauge(GaugeOpts{
|
|
||||||
Namespace: namespace,
|
|
||||||
Name: "process_start_time_seconds",
|
|
||||||
Help: "Start time of the process since unix epoch in seconds.",
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set up process metric collection if supported by the runtime.
|
|
||||||
if _, err := procfs.NewStat(); err == nil {
|
|
||||||
c.collectFn = c.processCollect
|
|
||||||
}
|
|
||||||
|
|
||||||
return &c
|
|
||||||
}
|
|
||||||
|
|
||||||
// Describe returns all descriptions of the collector.
|
|
||||||
func (c *processCollector) Describe(ch chan<- *Desc) {
|
|
||||||
ch <- c.cpuTotal.Desc()
|
|
||||||
ch <- c.openFDs.Desc()
|
|
||||||
ch <- c.maxFDs.Desc()
|
|
||||||
ch <- c.vsize.Desc()
|
|
||||||
ch <- c.rss.Desc()
|
|
||||||
ch <- c.startTime.Desc()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Collect returns the current state of all metrics of the collector.
|
|
||||||
func (c *processCollector) Collect(ch chan<- Metric) {
|
|
||||||
c.collectFn(ch)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(ts): Bring back error reporting by reverting 7faf9e7 as soon as the
|
|
||||||
// client allows users to configure the error behavior.
|
|
||||||
func (c *processCollector) processCollect(ch chan<- Metric) {
|
|
||||||
pid, err := c.pidFn()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
p, err := procfs.NewProc(pid)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if stat, err := p.NewStat(); err == nil {
|
|
||||||
c.cpuTotal.Set(stat.CPUTime())
|
|
||||||
ch <- c.cpuTotal
|
|
||||||
c.vsize.Set(float64(stat.VirtualMemory()))
|
|
||||||
ch <- c.vsize
|
|
||||||
c.rss.Set(float64(stat.ResidentMemory()))
|
|
||||||
ch <- c.rss
|
|
||||||
|
|
||||||
if startTime, err := stat.StartTime(); err == nil {
|
|
||||||
c.startTime.Set(startTime)
|
|
||||||
ch <- c.startTime
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if fds, err := p.FileDescriptorsLen(); err == nil {
|
|
||||||
c.openFDs.Set(float64(fds))
|
|
||||||
ch <- c.openFDs
|
|
||||||
}
|
|
||||||
|
|
||||||
if limits, err := p.NewLimits(); err == nil {
|
|
||||||
c.maxFDs.Set(float64(limits.OpenFiles))
|
|
||||||
ch <- c.maxFDs
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,806 +0,0 @@
|
||||||
// Copyright 2014 The Prometheus Authors
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package prometheus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"sort"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/golang/protobuf/proto"
|
|
||||||
|
|
||||||
dto "github.com/prometheus/client_model/go"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// Capacity for the channel to collect metrics and descriptors.
|
|
||||||
capMetricChan = 1000
|
|
||||||
capDescChan = 10
|
|
||||||
)
|
|
||||||
|
|
||||||
// DefaultRegisterer and DefaultGatherer are the implementations of the
|
|
||||||
// Registerer and Gatherer interface a number of convenience functions in this
|
|
||||||
// package act on. Initially, both variables point to the same Registry, which
|
|
||||||
// has a process collector (see NewProcessCollector) and a Go collector (see
|
|
||||||
// NewGoCollector) already registered. This approach to keep default instances
|
|
||||||
// as global state mirrors the approach of other packages in the Go standard
|
|
||||||
// library. Note that there are caveats. Change the variables with caution and
|
|
||||||
// only if you understand the consequences. Users who want to avoid global state
|
|
||||||
// altogether should not use the convenience function and act on custom
|
|
||||||
// instances instead.
|
|
||||||
var (
|
|
||||||
defaultRegistry = NewRegistry()
|
|
||||||
DefaultRegisterer Registerer = defaultRegistry
|
|
||||||
DefaultGatherer Gatherer = defaultRegistry
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
MustRegister(NewProcessCollector(os.Getpid(), ""))
|
|
||||||
MustRegister(NewGoCollector())
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewRegistry creates a new vanilla Registry without any Collectors
|
|
||||||
// pre-registered.
|
|
||||||
func NewRegistry() *Registry {
|
|
||||||
return &Registry{
|
|
||||||
collectorsByID: map[uint64]Collector{},
|
|
||||||
descIDs: map[uint64]struct{}{},
|
|
||||||
dimHashesByName: map[string]uint64{},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewPedanticRegistry returns a registry that checks during collection if each
|
|
||||||
// collected Metric is consistent with its reported Desc, and if the Desc has
|
|
||||||
// actually been registered with the registry.
|
|
||||||
//
|
|
||||||
// Usually, a Registry will be happy as long as the union of all collected
|
|
||||||
// Metrics is consistent and valid even if some metrics are not consistent with
|
|
||||||
// their own Desc or a Desc provided by their registered Collector. Well-behaved
|
|
||||||
// Collectors and Metrics will only provide consistent Descs. This Registry is
|
|
||||||
// useful to test the implementation of Collectors and Metrics.
|
|
||||||
func NewPedanticRegistry() *Registry {
|
|
||||||
r := NewRegistry()
|
|
||||||
r.pedanticChecksEnabled = true
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// Registerer is the interface for the part of a registry in charge of
|
|
||||||
// registering and unregistering. Users of custom registries should use
|
|
||||||
// Registerer as type for registration purposes (rather then the Registry type
|
|
||||||
// directly). In that way, they are free to use custom Registerer implementation
|
|
||||||
// (e.g. for testing purposes).
|
|
||||||
type Registerer interface {
|
|
||||||
// Register registers a new Collector to be included in metrics
|
|
||||||
// collection. It returns an error if the descriptors provided by the
|
|
||||||
// Collector are invalid or if they — in combination with descriptors of
|
|
||||||
// already registered Collectors — do not fulfill the consistency and
|
|
||||||
// uniqueness criteria described in the documentation of metric.Desc.
|
|
||||||
//
|
|
||||||
// If the provided Collector is equal to a Collector already registered
|
|
||||||
// (which includes the case of re-registering the same Collector), the
|
|
||||||
// returned error is an instance of AlreadyRegisteredError, which
|
|
||||||
// contains the previously registered Collector.
|
|
||||||
//
|
|
||||||
// It is in general not safe to register the same Collector multiple
|
|
||||||
// times concurrently.
|
|
||||||
Register(Collector) error
|
|
||||||
// MustRegister works like Register but registers any number of
|
|
||||||
// Collectors and panics upon the first registration that causes an
|
|
||||||
// error.
|
|
||||||
MustRegister(...Collector)
|
|
||||||
// Unregister unregisters the Collector that equals the Collector passed
|
|
||||||
// in as an argument. (Two Collectors are considered equal if their
|
|
||||||
// Describe method yields the same set of descriptors.) The function
|
|
||||||
// returns whether a Collector was unregistered.
|
|
||||||
//
|
|
||||||
// Note that even after unregistering, it will not be possible to
|
|
||||||
// register a new Collector that is inconsistent with the unregistered
|
|
||||||
// Collector, e.g. a Collector collecting metrics with the same name but
|
|
||||||
// a different help string. The rationale here is that the same registry
|
|
||||||
// instance must only collect consistent metrics throughout its
|
|
||||||
// lifetime.
|
|
||||||
Unregister(Collector) bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gatherer is the interface for the part of a registry in charge of gathering
|
|
||||||
// the collected metrics into a number of MetricFamilies. The Gatherer interface
|
|
||||||
// comes with the same general implication as described for the Registerer
|
|
||||||
// interface.
|
|
||||||
type Gatherer interface {
|
|
||||||
// Gather calls the Collect method of the registered Collectors and then
|
|
||||||
// gathers the collected metrics into a lexicographically sorted slice
|
|
||||||
// of MetricFamily protobufs. Even if an error occurs, Gather attempts
|
|
||||||
// to gather as many metrics as possible. Hence, if a non-nil error is
|
|
||||||
// returned, the returned MetricFamily slice could be nil (in case of a
|
|
||||||
// fatal error that prevented any meaningful metric collection) or
|
|
||||||
// contain a number of MetricFamily protobufs, some of which might be
|
|
||||||
// incomplete, and some might be missing altogether. The returned error
|
|
||||||
// (which might be a MultiError) explains the details. In scenarios
|
|
||||||
// where complete collection is critical, the returned MetricFamily
|
|
||||||
// protobufs should be disregarded if the returned error is non-nil.
|
|
||||||
Gather() ([]*dto.MetricFamily, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register registers the provided Collector with the DefaultRegisterer.
|
|
||||||
//
|
|
||||||
// Register is a shortcut for DefaultRegisterer.Register(c). See there for more
|
|
||||||
// details.
|
|
||||||
func Register(c Collector) error {
|
|
||||||
return DefaultRegisterer.Register(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MustRegister registers the provided Collectors with the DefaultRegisterer and
|
|
||||||
// panics if any error occurs.
|
|
||||||
//
|
|
||||||
// MustRegister is a shortcut for DefaultRegisterer.MustRegister(cs...). See
|
|
||||||
// there for more details.
|
|
||||||
func MustRegister(cs ...Collector) {
|
|
||||||
DefaultRegisterer.MustRegister(cs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RegisterOrGet registers the provided Collector with the DefaultRegisterer and
|
|
||||||
// returns the Collector, unless an equal Collector was registered before, in
|
|
||||||
// which case that Collector is returned.
|
|
||||||
//
|
|
||||||
// Deprecated: RegisterOrGet is merely a convenience function for the
|
|
||||||
// implementation as described in the documentation for
|
|
||||||
// AlreadyRegisteredError. As the use case is relatively rare, this function
|
|
||||||
// will be removed in a future version of this package to clean up the
|
|
||||||
// namespace.
|
|
||||||
func RegisterOrGet(c Collector) (Collector, error) {
|
|
||||||
if err := Register(c); err != nil {
|
|
||||||
if are, ok := err.(AlreadyRegisteredError); ok {
|
|
||||||
return are.ExistingCollector, nil
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MustRegisterOrGet behaves like RegisterOrGet but panics instead of returning
|
|
||||||
// an error.
|
|
||||||
//
|
|
||||||
// Deprecated: This is deprecated for the same reason RegisterOrGet is. See
|
|
||||||
// there for details.
|
|
||||||
func MustRegisterOrGet(c Collector) Collector {
|
|
||||||
c, err := RegisterOrGet(c)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unregister removes the registration of the provided Collector from the
|
|
||||||
// DefaultRegisterer.
|
|
||||||
//
|
|
||||||
// Unregister is a shortcut for DefaultRegisterer.Unregister(c). See there for
|
|
||||||
// more details.
|
|
||||||
func Unregister(c Collector) bool {
|
|
||||||
return DefaultRegisterer.Unregister(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GathererFunc turns a function into a Gatherer.
|
|
||||||
type GathererFunc func() ([]*dto.MetricFamily, error)
|
|
||||||
|
|
||||||
// Gather implements Gatherer.
|
|
||||||
func (gf GathererFunc) Gather() ([]*dto.MetricFamily, error) {
|
|
||||||
return gf()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetMetricFamilyInjectionHook replaces the DefaultGatherer with one that
|
|
||||||
// gathers from the previous DefaultGatherers but then merges the MetricFamily
|
|
||||||
// protobufs returned from the provided hook function with the MetricFamily
|
|
||||||
// protobufs returned from the original DefaultGatherer.
|
|
||||||
//
|
|
||||||
// Deprecated: This function manipulates the DefaultGatherer variable. Consider
|
|
||||||
// the implications, i.e. don't do this concurrently with any uses of the
|
|
||||||
// DefaultGatherer. In the rare cases where you need to inject MetricFamily
|
|
||||||
// protobufs directly, it is recommended to use a custom Registry and combine it
|
|
||||||
// with a custom Gatherer using the Gatherers type (see
|
|
||||||
// there). SetMetricFamilyInjectionHook only exists for compatibility reasons
|
|
||||||
// with previous versions of this package.
|
|
||||||
func SetMetricFamilyInjectionHook(hook func() []*dto.MetricFamily) {
|
|
||||||
DefaultGatherer = Gatherers{
|
|
||||||
DefaultGatherer,
|
|
||||||
GathererFunc(func() ([]*dto.MetricFamily, error) { return hook(), nil }),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// AlreadyRegisteredError is returned by the Register method if the Collector to
|
|
||||||
// be registered has already been registered before, or a different Collector
|
|
||||||
// that collects the same metrics has been registered before. Registration fails
|
|
||||||
// in that case, but you can detect from the kind of error what has
|
|
||||||
// happened. The error contains fields for the existing Collector and the
|
|
||||||
// (rejected) new Collector that equals the existing one. This can be used to
|
|
||||||
// find out if an equal Collector has been registered before and switch over to
|
|
||||||
// using the old one, as demonstrated in the example.
|
|
||||||
type AlreadyRegisteredError struct {
|
|
||||||
ExistingCollector, NewCollector Collector
|
|
||||||
}
|
|
||||||
|
|
||||||
func (err AlreadyRegisteredError) Error() string {
|
|
||||||
return "duplicate metrics collector registration attempted"
|
|
||||||
}
|
|
||||||
|
|
||||||
// MultiError is a slice of errors implementing the error interface. It is used
|
|
||||||
// by a Gatherer to report multiple errors during MetricFamily gathering.
|
|
||||||
type MultiError []error
|
|
||||||
|
|
||||||
func (errs MultiError) Error() string {
|
|
||||||
if len(errs) == 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
buf := &bytes.Buffer{}
|
|
||||||
fmt.Fprintf(buf, "%d error(s) occurred:", len(errs))
|
|
||||||
for _, err := range errs {
|
|
||||||
fmt.Fprintf(buf, "\n* %s", err)
|
|
||||||
}
|
|
||||||
return buf.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// MaybeUnwrap returns nil if len(errs) is 0. It returns the first and only
|
|
||||||
// contained error as error if len(errs is 1). In all other cases, it returns
|
|
||||||
// the MultiError directly. This is helpful for returning a MultiError in a way
|
|
||||||
// that only uses the MultiError if needed.
|
|
||||||
func (errs MultiError) MaybeUnwrap() error {
|
|
||||||
switch len(errs) {
|
|
||||||
case 0:
|
|
||||||
return nil
|
|
||||||
case 1:
|
|
||||||
return errs[0]
|
|
||||||
default:
|
|
||||||
return errs
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Registry registers Prometheus collectors, collects their metrics, and gathers
|
|
||||||
// them into MetricFamilies for exposition. It implements both Registerer and
|
|
||||||
// Gatherer. The zero value is not usable. Create instances with NewRegistry or
|
|
||||||
// NewPedanticRegistry.
|
|
||||||
type Registry struct {
|
|
||||||
mtx sync.RWMutex
|
|
||||||
collectorsByID map[uint64]Collector // ID is a hash of the descIDs.
|
|
||||||
descIDs map[uint64]struct{}
|
|
||||||
dimHashesByName map[string]uint64
|
|
||||||
pedanticChecksEnabled bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register implements Registerer.
|
|
||||||
func (r *Registry) Register(c Collector) error {
|
|
||||||
var (
|
|
||||||
descChan = make(chan *Desc, capDescChan)
|
|
||||||
newDescIDs = map[uint64]struct{}{}
|
|
||||||
newDimHashesByName = map[string]uint64{}
|
|
||||||
collectorID uint64 // Just a sum of all desc IDs.
|
|
||||||
duplicateDescErr error
|
|
||||||
)
|
|
||||||
go func() {
|
|
||||||
c.Describe(descChan)
|
|
||||||
close(descChan)
|
|
||||||
}()
|
|
||||||
r.mtx.Lock()
|
|
||||||
defer r.mtx.Unlock()
|
|
||||||
// Coduct various tests...
|
|
||||||
for desc := range descChan {
|
|
||||||
|
|
||||||
// Is the descriptor valid at all?
|
|
||||||
if desc.err != nil {
|
|
||||||
return fmt.Errorf("descriptor %s is invalid: %s", desc, desc.err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Is the descID unique?
|
|
||||||
// (In other words: Is the fqName + constLabel combination unique?)
|
|
||||||
if _, exists := r.descIDs[desc.id]; exists {
|
|
||||||
duplicateDescErr = fmt.Errorf("descriptor %s already exists with the same fully-qualified name and const label values", desc)
|
|
||||||
}
|
|
||||||
// If it is not a duplicate desc in this collector, add it to
|
|
||||||
// the collectorID. (We allow duplicate descs within the same
|
|
||||||
// collector, but their existence must be a no-op.)
|
|
||||||
if _, exists := newDescIDs[desc.id]; !exists {
|
|
||||||
newDescIDs[desc.id] = struct{}{}
|
|
||||||
collectorID += desc.id
|
|
||||||
}
|
|
||||||
|
|
||||||
// Are all the label names and the help string consistent with
|
|
||||||
// previous descriptors of the same name?
|
|
||||||
// First check existing descriptors...
|
|
||||||
if dimHash, exists := r.dimHashesByName[desc.fqName]; exists {
|
|
||||||
if dimHash != desc.dimHash {
|
|
||||||
return fmt.Errorf("a previously registered descriptor with the same fully-qualified name as %s has different label names or a different help string", desc)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// ...then check the new descriptors already seen.
|
|
||||||
if dimHash, exists := newDimHashesByName[desc.fqName]; exists {
|
|
||||||
if dimHash != desc.dimHash {
|
|
||||||
return fmt.Errorf("descriptors reported by collector have inconsistent label names or help strings for the same fully-qualified name, offender is %s", desc)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
newDimHashesByName[desc.fqName] = desc.dimHash
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Did anything happen at all?
|
|
||||||
if len(newDescIDs) == 0 {
|
|
||||||
return errors.New("collector has no descriptors")
|
|
||||||
}
|
|
||||||
if existing, exists := r.collectorsByID[collectorID]; exists {
|
|
||||||
return AlreadyRegisteredError{
|
|
||||||
ExistingCollector: existing,
|
|
||||||
NewCollector: c,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// If the collectorID is new, but at least one of the descs existed
|
|
||||||
// before, we are in trouble.
|
|
||||||
if duplicateDescErr != nil {
|
|
||||||
return duplicateDescErr
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only after all tests have passed, actually register.
|
|
||||||
r.collectorsByID[collectorID] = c
|
|
||||||
for hash := range newDescIDs {
|
|
||||||
r.descIDs[hash] = struct{}{}
|
|
||||||
}
|
|
||||||
for name, dimHash := range newDimHashesByName {
|
|
||||||
r.dimHashesByName[name] = dimHash
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unregister implements Registerer.
|
|
||||||
func (r *Registry) Unregister(c Collector) bool {
|
|
||||||
var (
|
|
||||||
descChan = make(chan *Desc, capDescChan)
|
|
||||||
descIDs = map[uint64]struct{}{}
|
|
||||||
collectorID uint64 // Just a sum of the desc IDs.
|
|
||||||
)
|
|
||||||
go func() {
|
|
||||||
c.Describe(descChan)
|
|
||||||
close(descChan)
|
|
||||||
}()
|
|
||||||
for desc := range descChan {
|
|
||||||
if _, exists := descIDs[desc.id]; !exists {
|
|
||||||
collectorID += desc.id
|
|
||||||
descIDs[desc.id] = struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
r.mtx.RLock()
|
|
||||||
if _, exists := r.collectorsByID[collectorID]; !exists {
|
|
||||||
r.mtx.RUnlock()
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
r.mtx.RUnlock()
|
|
||||||
|
|
||||||
r.mtx.Lock()
|
|
||||||
defer r.mtx.Unlock()
|
|
||||||
|
|
||||||
delete(r.collectorsByID, collectorID)
|
|
||||||
for id := range descIDs {
|
|
||||||
delete(r.descIDs, id)
|
|
||||||
}
|
|
||||||
// dimHashesByName is left untouched as those must be consistent
|
|
||||||
// throughout the lifetime of a program.
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// MustRegister implements Registerer.
|
|
||||||
func (r *Registry) MustRegister(cs ...Collector) {
|
|
||||||
for _, c := range cs {
|
|
||||||
if err := r.Register(c); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gather implements Gatherer.
|
|
||||||
func (r *Registry) Gather() ([]*dto.MetricFamily, error) {
|
|
||||||
var (
|
|
||||||
metricChan = make(chan Metric, capMetricChan)
|
|
||||||
metricHashes = map[uint64]struct{}{}
|
|
||||||
dimHashes = map[string]uint64{}
|
|
||||||
wg sync.WaitGroup
|
|
||||||
errs MultiError // The collected errors to return in the end.
|
|
||||||
registeredDescIDs map[uint64]struct{} // Only used for pedantic checks
|
|
||||||
)
|
|
||||||
|
|
||||||
r.mtx.RLock()
|
|
||||||
metricFamiliesByName := make(map[string]*dto.MetricFamily, len(r.dimHashesByName))
|
|
||||||
|
|
||||||
// Scatter.
|
|
||||||
// (Collectors could be complex and slow, so we call them all at once.)
|
|
||||||
wg.Add(len(r.collectorsByID))
|
|
||||||
go func() {
|
|
||||||
wg.Wait()
|
|
||||||
close(metricChan)
|
|
||||||
}()
|
|
||||||
for _, collector := range r.collectorsByID {
|
|
||||||
go func(collector Collector) {
|
|
||||||
defer wg.Done()
|
|
||||||
collector.Collect(metricChan)
|
|
||||||
}(collector)
|
|
||||||
}
|
|
||||||
|
|
||||||
// In case pedantic checks are enabled, we have to copy the map before
|
|
||||||
// giving up the RLock.
|
|
||||||
if r.pedanticChecksEnabled {
|
|
||||||
registeredDescIDs = make(map[uint64]struct{}, len(r.descIDs))
|
|
||||||
for id := range r.descIDs {
|
|
||||||
registeredDescIDs[id] = struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
r.mtx.RUnlock()
|
|
||||||
|
|
||||||
// Drain metricChan in case of premature return.
|
|
||||||
defer func() {
|
|
||||||
for _ = range metricChan {
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Gather.
|
|
||||||
for metric := range metricChan {
|
|
||||||
// This could be done concurrently, too, but it required locking
|
|
||||||
// of metricFamiliesByName (and of metricHashes if checks are
|
|
||||||
// enabled). Most likely not worth it.
|
|
||||||
desc := metric.Desc()
|
|
||||||
dtoMetric := &dto.Metric{}
|
|
||||||
if err := metric.Write(dtoMetric); err != nil {
|
|
||||||
errs = append(errs, fmt.Errorf(
|
|
||||||
"error collecting metric %v: %s", desc, err,
|
|
||||||
))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
metricFamily, ok := metricFamiliesByName[desc.fqName]
|
|
||||||
if ok {
|
|
||||||
if metricFamily.GetHelp() != desc.help {
|
|
||||||
errs = append(errs, fmt.Errorf(
|
|
||||||
"collected metric %s %s has help %q but should have %q",
|
|
||||||
desc.fqName, dtoMetric, desc.help, metricFamily.GetHelp(),
|
|
||||||
))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// TODO(beorn7): Simplify switch once Desc has type.
|
|
||||||
switch metricFamily.GetType() {
|
|
||||||
case dto.MetricType_COUNTER:
|
|
||||||
if dtoMetric.Counter == nil {
|
|
||||||
errs = append(errs, fmt.Errorf(
|
|
||||||
"collected metric %s %s should be a Counter",
|
|
||||||
desc.fqName, dtoMetric,
|
|
||||||
))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
case dto.MetricType_GAUGE:
|
|
||||||
if dtoMetric.Gauge == nil {
|
|
||||||
errs = append(errs, fmt.Errorf(
|
|
||||||
"collected metric %s %s should be a Gauge",
|
|
||||||
desc.fqName, dtoMetric,
|
|
||||||
))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
case dto.MetricType_SUMMARY:
|
|
||||||
if dtoMetric.Summary == nil {
|
|
||||||
errs = append(errs, fmt.Errorf(
|
|
||||||
"collected metric %s %s should be a Summary",
|
|
||||||
desc.fqName, dtoMetric,
|
|
||||||
))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
case dto.MetricType_UNTYPED:
|
|
||||||
if dtoMetric.Untyped == nil {
|
|
||||||
errs = append(errs, fmt.Errorf(
|
|
||||||
"collected metric %s %s should be Untyped",
|
|
||||||
desc.fqName, dtoMetric,
|
|
||||||
))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
case dto.MetricType_HISTOGRAM:
|
|
||||||
if dtoMetric.Histogram == nil {
|
|
||||||
errs = append(errs, fmt.Errorf(
|
|
||||||
"collected metric %s %s should be a Histogram",
|
|
||||||
desc.fqName, dtoMetric,
|
|
||||||
))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
panic("encountered MetricFamily with invalid type")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
metricFamily = &dto.MetricFamily{}
|
|
||||||
metricFamily.Name = proto.String(desc.fqName)
|
|
||||||
metricFamily.Help = proto.String(desc.help)
|
|
||||||
// TODO(beorn7): Simplify switch once Desc has type.
|
|
||||||
switch {
|
|
||||||
case dtoMetric.Gauge != nil:
|
|
||||||
metricFamily.Type = dto.MetricType_GAUGE.Enum()
|
|
||||||
case dtoMetric.Counter != nil:
|
|
||||||
metricFamily.Type = dto.MetricType_COUNTER.Enum()
|
|
||||||
case dtoMetric.Summary != nil:
|
|
||||||
metricFamily.Type = dto.MetricType_SUMMARY.Enum()
|
|
||||||
case dtoMetric.Untyped != nil:
|
|
||||||
metricFamily.Type = dto.MetricType_UNTYPED.Enum()
|
|
||||||
case dtoMetric.Histogram != nil:
|
|
||||||
metricFamily.Type = dto.MetricType_HISTOGRAM.Enum()
|
|
||||||
default:
|
|
||||||
errs = append(errs, fmt.Errorf(
|
|
||||||
"empty metric collected: %s", dtoMetric,
|
|
||||||
))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
metricFamiliesByName[desc.fqName] = metricFamily
|
|
||||||
}
|
|
||||||
if err := checkMetricConsistency(metricFamily, dtoMetric, metricHashes, dimHashes); err != nil {
|
|
||||||
errs = append(errs, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if r.pedanticChecksEnabled {
|
|
||||||
// Is the desc registered at all?
|
|
||||||
if _, exist := registeredDescIDs[desc.id]; !exist {
|
|
||||||
errs = append(errs, fmt.Errorf(
|
|
||||||
"collected metric %s %s with unregistered descriptor %s",
|
|
||||||
metricFamily.GetName(), dtoMetric, desc,
|
|
||||||
))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err := checkDescConsistency(metricFamily, dtoMetric, desc); err != nil {
|
|
||||||
errs = append(errs, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
metricFamily.Metric = append(metricFamily.Metric, dtoMetric)
|
|
||||||
}
|
|
||||||
return normalizeMetricFamilies(metricFamiliesByName), errs.MaybeUnwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gatherers is a slice of Gatherer instances that implements the Gatherer
|
|
||||||
// interface itself. Its Gather method calls Gather on all Gatherers in the
|
|
||||||
// slice in order and returns the merged results. Errors returned from the
|
|
||||||
// Gather calles are all returned in a flattened MultiError. Duplicate and
|
|
||||||
// inconsistent Metrics are skipped (first occurrence in slice order wins) and
|
|
||||||
// reported in the returned error.
|
|
||||||
//
|
|
||||||
// Gatherers can be used to merge the Gather results from multiple
|
|
||||||
// Registries. It also provides a way to directly inject existing MetricFamily
|
|
||||||
// protobufs into the gathering by creating a custom Gatherer with a Gather
|
|
||||||
// method that simply returns the existing MetricFamily protobufs. Note that no
|
|
||||||
// registration is involved (in contrast to Collector registration), so
|
|
||||||
// obviously registration-time checks cannot happen. Any inconsistencies between
|
|
||||||
// the gathered MetricFamilies are reported as errors by the Gather method, and
|
|
||||||
// inconsistent Metrics are dropped. Invalid parts of the MetricFamilies
|
|
||||||
// (e.g. syntactically invalid metric or label names) will go undetected.
|
|
||||||
type Gatherers []Gatherer
|
|
||||||
|
|
||||||
// Gather implements Gatherer.
|
|
||||||
func (gs Gatherers) Gather() ([]*dto.MetricFamily, error) {
|
|
||||||
var (
|
|
||||||
metricFamiliesByName = map[string]*dto.MetricFamily{}
|
|
||||||
metricHashes = map[uint64]struct{}{}
|
|
||||||
dimHashes = map[string]uint64{}
|
|
||||||
errs MultiError // The collected errors to return in the end.
|
|
||||||
)
|
|
||||||
|
|
||||||
for i, g := range gs {
|
|
||||||
mfs, err := g.Gather()
|
|
||||||
if err != nil {
|
|
||||||
if multiErr, ok := err.(MultiError); ok {
|
|
||||||
for _, err := range multiErr {
|
|
||||||
errs = append(errs, fmt.Errorf("[from Gatherer #%d] %s", i+1, err))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
errs = append(errs, fmt.Errorf("[from Gatherer #%d] %s", i+1, err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, mf := range mfs {
|
|
||||||
existingMF, exists := metricFamiliesByName[mf.GetName()]
|
|
||||||
if exists {
|
|
||||||
if existingMF.GetHelp() != mf.GetHelp() {
|
|
||||||
errs = append(errs, fmt.Errorf(
|
|
||||||
"gathered metric family %s has help %q but should have %q",
|
|
||||||
mf.GetName(), mf.GetHelp(), existingMF.GetHelp(),
|
|
||||||
))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if existingMF.GetType() != mf.GetType() {
|
|
||||||
errs = append(errs, fmt.Errorf(
|
|
||||||
"gathered metric family %s has type %s but should have %s",
|
|
||||||
mf.GetName(), mf.GetType(), existingMF.GetType(),
|
|
||||||
))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
existingMF = &dto.MetricFamily{}
|
|
||||||
existingMF.Name = mf.Name
|
|
||||||
existingMF.Help = mf.Help
|
|
||||||
existingMF.Type = mf.Type
|
|
||||||
metricFamiliesByName[mf.GetName()] = existingMF
|
|
||||||
}
|
|
||||||
for _, m := range mf.Metric {
|
|
||||||
if err := checkMetricConsistency(existingMF, m, metricHashes, dimHashes); err != nil {
|
|
||||||
errs = append(errs, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
existingMF.Metric = append(existingMF.Metric, m)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return normalizeMetricFamilies(metricFamiliesByName), errs.MaybeUnwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
// metricSorter is a sortable slice of *dto.Metric.
|
|
||||||
type metricSorter []*dto.Metric
|
|
||||||
|
|
||||||
func (s metricSorter) Len() int {
|
|
||||||
return len(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s metricSorter) Swap(i, j int) {
|
|
||||||
s[i], s[j] = s[j], s[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s metricSorter) Less(i, j int) bool {
|
|
||||||
if len(s[i].Label) != len(s[j].Label) {
|
|
||||||
// This should not happen. The metrics are
|
|
||||||
// inconsistent. However, we have to deal with the fact, as
|
|
||||||
// people might use custom collectors or metric family injection
|
|
||||||
// to create inconsistent metrics. So let's simply compare the
|
|
||||||
// number of labels in this case. That will still yield
|
|
||||||
// reproducible sorting.
|
|
||||||
return len(s[i].Label) < len(s[j].Label)
|
|
||||||
}
|
|
||||||
for n, lp := range s[i].Label {
|
|
||||||
vi := lp.GetValue()
|
|
||||||
vj := s[j].Label[n].GetValue()
|
|
||||||
if vi != vj {
|
|
||||||
return vi < vj
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We should never arrive here. Multiple metrics with the same
|
|
||||||
// label set in the same scrape will lead to undefined ingestion
|
|
||||||
// behavior. However, as above, we have to provide stable sorting
|
|
||||||
// here, even for inconsistent metrics. So sort equal metrics
|
|
||||||
// by their timestamp, with missing timestamps (implying "now")
|
|
||||||
// coming last.
|
|
||||||
if s[i].TimestampMs == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if s[j].TimestampMs == nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return s[i].GetTimestampMs() < s[j].GetTimestampMs()
|
|
||||||
}
|
|
||||||
|
|
||||||
// normalizeMetricFamilies returns a MetricFamily slice whith empty
|
|
||||||
// MetricFamilies pruned and the remaining MetricFamilies sorted by name within
|
|
||||||
// the slice, with the contained Metrics sorted within each MetricFamily.
|
|
||||||
func normalizeMetricFamilies(metricFamiliesByName map[string]*dto.MetricFamily) []*dto.MetricFamily {
|
|
||||||
for _, mf := range metricFamiliesByName {
|
|
||||||
sort.Sort(metricSorter(mf.Metric))
|
|
||||||
}
|
|
||||||
names := make([]string, 0, len(metricFamiliesByName))
|
|
||||||
for name, mf := range metricFamiliesByName {
|
|
||||||
if len(mf.Metric) > 0 {
|
|
||||||
names = append(names, name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sort.Strings(names)
|
|
||||||
result := make([]*dto.MetricFamily, 0, len(names))
|
|
||||||
for _, name := range names {
|
|
||||||
result = append(result, metricFamiliesByName[name])
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkMetricConsistency checks if the provided Metric is consistent with the
|
|
||||||
// provided MetricFamily. It also hashed the Metric labels and the MetricFamily
|
|
||||||
// name. If the resulting hash is alread in the provided metricHashes, an error
|
|
||||||
// is returned. If not, it is added to metricHashes. The provided dimHashes maps
|
|
||||||
// MetricFamily names to their dimHash (hashed sorted label names). If dimHashes
|
|
||||||
// doesn't yet contain a hash for the provided MetricFamily, it is
|
|
||||||
// added. Otherwise, an error is returned if the existing dimHashes in not equal
|
|
||||||
// the calculated dimHash.
|
|
||||||
func checkMetricConsistency(
|
|
||||||
metricFamily *dto.MetricFamily,
|
|
||||||
dtoMetric *dto.Metric,
|
|
||||||
metricHashes map[uint64]struct{},
|
|
||||||
dimHashes map[string]uint64,
|
|
||||||
) error {
|
|
||||||
// Type consistency with metric family.
|
|
||||||
if metricFamily.GetType() == dto.MetricType_GAUGE && dtoMetric.Gauge == nil ||
|
|
||||||
metricFamily.GetType() == dto.MetricType_COUNTER && dtoMetric.Counter == nil ||
|
|
||||||
metricFamily.GetType() == dto.MetricType_SUMMARY && dtoMetric.Summary == nil ||
|
|
||||||
metricFamily.GetType() == dto.MetricType_HISTOGRAM && dtoMetric.Histogram == nil ||
|
|
||||||
metricFamily.GetType() == dto.MetricType_UNTYPED && dtoMetric.Untyped == nil {
|
|
||||||
return fmt.Errorf(
|
|
||||||
"collected metric %s %s is not a %s",
|
|
||||||
metricFamily.GetName(), dtoMetric, metricFamily.GetType(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Is the metric unique (i.e. no other metric with the same name and the same label values)?
|
|
||||||
h := hashNew()
|
|
||||||
h = hashAdd(h, metricFamily.GetName())
|
|
||||||
h = hashAddByte(h, separatorByte)
|
|
||||||
dh := hashNew()
|
|
||||||
// Make sure label pairs are sorted. We depend on it for the consistency
|
|
||||||
// check.
|
|
||||||
sort.Sort(LabelPairSorter(dtoMetric.Label))
|
|
||||||
for _, lp := range dtoMetric.Label {
|
|
||||||
h = hashAdd(h, lp.GetValue())
|
|
||||||
h = hashAddByte(h, separatorByte)
|
|
||||||
dh = hashAdd(dh, lp.GetName())
|
|
||||||
dh = hashAddByte(dh, separatorByte)
|
|
||||||
}
|
|
||||||
if _, exists := metricHashes[h]; exists {
|
|
||||||
return fmt.Errorf(
|
|
||||||
"collected metric %s %s was collected before with the same name and label values",
|
|
||||||
metricFamily.GetName(), dtoMetric,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if dimHash, ok := dimHashes[metricFamily.GetName()]; ok {
|
|
||||||
if dimHash != dh {
|
|
||||||
return fmt.Errorf(
|
|
||||||
"collected metric %s %s has label dimensions inconsistent with previously collected metrics in the same metric family",
|
|
||||||
metricFamily.GetName(), dtoMetric,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
dimHashes[metricFamily.GetName()] = dh
|
|
||||||
}
|
|
||||||
metricHashes[h] = struct{}{}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkDescConsistency(
|
|
||||||
metricFamily *dto.MetricFamily,
|
|
||||||
dtoMetric *dto.Metric,
|
|
||||||
desc *Desc,
|
|
||||||
) error {
|
|
||||||
// Desc help consistency with metric family help.
|
|
||||||
if metricFamily.GetHelp() != desc.help {
|
|
||||||
return fmt.Errorf(
|
|
||||||
"collected metric %s %s has help %q but should have %q",
|
|
||||||
metricFamily.GetName(), dtoMetric, metricFamily.GetHelp(), desc.help,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Is the desc consistent with the content of the metric?
|
|
||||||
lpsFromDesc := make([]*dto.LabelPair, 0, len(dtoMetric.Label))
|
|
||||||
lpsFromDesc = append(lpsFromDesc, desc.constLabelPairs...)
|
|
||||||
for _, l := range desc.variableLabels {
|
|
||||||
lpsFromDesc = append(lpsFromDesc, &dto.LabelPair{
|
|
||||||
Name: proto.String(l),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if len(lpsFromDesc) != len(dtoMetric.Label) {
|
|
||||||
return fmt.Errorf(
|
|
||||||
"labels in collected metric %s %s are inconsistent with descriptor %s",
|
|
||||||
metricFamily.GetName(), dtoMetric, desc,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
sort.Sort(LabelPairSorter(lpsFromDesc))
|
|
||||||
for i, lpFromDesc := range lpsFromDesc {
|
|
||||||
lpFromMetric := dtoMetric.Label[i]
|
|
||||||
if lpFromDesc.GetName() != lpFromMetric.GetName() ||
|
|
||||||
lpFromDesc.Value != nil && lpFromDesc.GetValue() != lpFromMetric.GetValue() {
|
|
||||||
return fmt.Errorf(
|
|
||||||
"labels in collected metric %s %s are inconsistent with descriptor %s",
|
|
||||||
metricFamily.GetName(), dtoMetric, desc,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,534 +0,0 @@
|
||||||
// Copyright 2014 The Prometheus Authors
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package prometheus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"math"
|
|
||||||
"sort"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/beorn7/perks/quantile"
|
|
||||||
"github.com/golang/protobuf/proto"
|
|
||||||
|
|
||||||
dto "github.com/prometheus/client_model/go"
|
|
||||||
)
|
|
||||||
|
|
||||||
// quantileLabel is used for the label that defines the quantile in a
|
|
||||||
// summary.
|
|
||||||
const quantileLabel = "quantile"
|
|
||||||
|
|
||||||
// A Summary captures individual observations from an event or sample stream and
|
|
||||||
// summarizes them in a manner similar to traditional summary statistics: 1. sum
|
|
||||||
// of observations, 2. observation count, 3. rank estimations.
|
|
||||||
//
|
|
||||||
// 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.
|
|
||||||
//
|
|
||||||
// Note that the rank estimations cannot be aggregated in a meaningful way with
|
|
||||||
// the Prometheus query language (i.e. you cannot average or add them). If you
|
|
||||||
// need aggregatable quantiles (e.g. you want the 99th percentile latency of all
|
|
||||||
// queries served across all instances of a service), consider the Histogram
|
|
||||||
// metric type. See the Prometheus documentation for more details.
|
|
||||||
//
|
|
||||||
// To create Summary instances, use NewSummary.
|
|
||||||
type Summary interface {
|
|
||||||
Metric
|
|
||||||
Collector
|
|
||||||
|
|
||||||
// Observe adds a single observation to the summary.
|
|
||||||
Observe(float64)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefObjectives are the default Summary quantile values.
|
|
||||||
var (
|
|
||||||
DefObjectives = map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001}
|
|
||||||
|
|
||||||
errQuantileLabelNotAllowed = fmt.Errorf(
|
|
||||||
"%q is not allowed as label name in summaries", quantileLabel,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
// Default values for SummaryOpts.
|
|
||||||
const (
|
|
||||||
// DefMaxAge is the default duration for which observations stay
|
|
||||||
// relevant.
|
|
||||||
DefMaxAge time.Duration = 10 * time.Minute
|
|
||||||
// DefAgeBuckets is the default number of buckets used to calculate the
|
|
||||||
// age of observations.
|
|
||||||
DefAgeBuckets = 5
|
|
||||||
// DefBufCap is the standard buffer size for collecting Summary observations.
|
|
||||||
DefBufCap = 500
|
|
||||||
)
|
|
||||||
|
|
||||||
// SummaryOpts bundles the options for creating a Summary metric. It is
|
|
||||||
// mandatory to set Name and Help to a non-empty string. All other fields are
|
|
||||||
// optional and can safely be left at their zero value.
|
|
||||||
type SummaryOpts struct {
|
|
||||||
// Namespace, Subsystem, and Name are components of the fully-qualified
|
|
||||||
// name of the Summary (created by joining these components with
|
|
||||||
// "_"). Only Name is mandatory, the others merely help structuring the
|
|
||||||
// name. Note that the fully-qualified name of the Summary must be a
|
|
||||||
// valid Prometheus metric name.
|
|
||||||
Namespace string
|
|
||||||
Subsystem string
|
|
||||||
Name string
|
|
||||||
|
|
||||||
// Help provides information about this Summary. Mandatory!
|
|
||||||
//
|
|
||||||
// Metrics with the same fully-qualified name must have the same Help
|
|
||||||
// string.
|
|
||||||
Help string
|
|
||||||
|
|
||||||
// ConstLabels are used to attach fixed labels to this
|
|
||||||
// Summary. Summaries with the same fully-qualified name must have the
|
|
||||||
// same label names in their ConstLabels.
|
|
||||||
//
|
|
||||||
// Note that in most cases, labels have a value that varies during the
|
|
||||||
// lifetime of a process. Those labels are usually managed with a
|
|
||||||
// SummaryVec. ConstLabels serve only special purposes. One is for the
|
|
||||||
// special case where the value of a label does not change during the
|
|
||||||
// lifetime of a process, e.g. if the revision of the running binary is
|
|
||||||
// put into a label. Another, more advanced purpose is if more than one
|
|
||||||
// Collector needs to collect Summaries with the same fully-qualified
|
|
||||||
// name. In that case, those Summaries must differ in the values of
|
|
||||||
// their ConstLabels. See the Collector examples.
|
|
||||||
//
|
|
||||||
// If the value of a label never changes (not even between binaries),
|
|
||||||
// that label most likely should not be a label at all (but part of the
|
|
||||||
// metric name).
|
|
||||||
ConstLabels Labels
|
|
||||||
|
|
||||||
// Objectives defines the quantile rank estimates with their respective
|
|
||||||
// absolute error. If Objectives[q] = e, then the value reported
|
|
||||||
// for q will be the φ-quantile value for some φ between q-e and q+e.
|
|
||||||
// The default value is DefObjectives.
|
|
||||||
Objectives map[float64]float64
|
|
||||||
|
|
||||||
// MaxAge defines the duration for which an observation stays relevant
|
|
||||||
// for the summary. Must be positive. The default value is DefMaxAge.
|
|
||||||
MaxAge time.Duration
|
|
||||||
|
|
||||||
// AgeBuckets is the number of buckets used to exclude observations that
|
|
||||||
// are older than MaxAge from the summary. A higher number has a
|
|
||||||
// resource penalty, so only increase it if the higher resolution is
|
|
||||||
// really required. For very high observation rates, you might want to
|
|
||||||
// reduce the number of age buckets. With only one age bucket, you will
|
|
||||||
// effectively see a complete reset of the summary each time MaxAge has
|
|
||||||
// passed. The default value is DefAgeBuckets.
|
|
||||||
AgeBuckets uint32
|
|
||||||
|
|
||||||
// BufCap defines the default sample stream buffer size. The default
|
|
||||||
// value of DefBufCap should suffice for most uses. If there is a need
|
|
||||||
// to increase the value, a multiple of 500 is recommended (because that
|
|
||||||
// is the internal buffer size of the underlying package
|
|
||||||
// "github.com/bmizerany/perks/quantile").
|
|
||||||
BufCap uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
// Great fuck-up with the sliding-window decay algorithm... The Merge method of
|
|
||||||
// perk/quantile is actually not working as advertised - and it might be
|
|
||||||
// unfixable, as the underlying algorithm is apparently not capable of merging
|
|
||||||
// summaries in the first place. To avoid using Merge, we are currently adding
|
|
||||||
// observations to _each_ age bucket, i.e. the effort to add a sample is
|
|
||||||
// essentially multiplied by the number of age buckets. When rotating age
|
|
||||||
// buckets, we empty the previous head stream. On scrape time, we simply take
|
|
||||||
// the quantiles from the head stream (no merging required). Result: More effort
|
|
||||||
// on observation time, less effort on scrape time, which is exactly the
|
|
||||||
// opposite of what we try to accomplish, but at least the results are correct.
|
|
||||||
//
|
|
||||||
// The quite elegant previous contraption to merge the age buckets efficiently
|
|
||||||
// on scrape time (see code up commit 6b9530d72ea715f0ba612c0120e6e09fbf1d49d0)
|
|
||||||
// can't be used anymore.
|
|
||||||
|
|
||||||
// NewSummary creates a new Summary based on the provided SummaryOpts.
|
|
||||||
func NewSummary(opts SummaryOpts) Summary {
|
|
||||||
return newSummary(
|
|
||||||
NewDesc(
|
|
||||||
BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
|
|
||||||
opts.Help,
|
|
||||||
nil,
|
|
||||||
opts.ConstLabels,
|
|
||||||
),
|
|
||||||
opts,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newSummary(desc *Desc, opts SummaryOpts, labelValues ...string) Summary {
|
|
||||||
if len(desc.variableLabels) != len(labelValues) {
|
|
||||||
panic(errInconsistentCardinality)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, n := range desc.variableLabels {
|
|
||||||
if n == quantileLabel {
|
|
||||||
panic(errQuantileLabelNotAllowed)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, lp := range desc.constLabelPairs {
|
|
||||||
if lp.GetName() == quantileLabel {
|
|
||||||
panic(errQuantileLabelNotAllowed)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(opts.Objectives) == 0 {
|
|
||||||
opts.Objectives = DefObjectives
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.MaxAge < 0 {
|
|
||||||
panic(fmt.Errorf("illegal max age MaxAge=%v", opts.MaxAge))
|
|
||||||
}
|
|
||||||
if opts.MaxAge == 0 {
|
|
||||||
opts.MaxAge = DefMaxAge
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.AgeBuckets == 0 {
|
|
||||||
opts.AgeBuckets = DefAgeBuckets
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.BufCap == 0 {
|
|
||||||
opts.BufCap = DefBufCap
|
|
||||||
}
|
|
||||||
|
|
||||||
s := &summary{
|
|
||||||
desc: desc,
|
|
||||||
|
|
||||||
objectives: opts.Objectives,
|
|
||||||
sortedObjectives: make([]float64, 0, len(opts.Objectives)),
|
|
||||||
|
|
||||||
labelPairs: makeLabelPairs(desc, labelValues),
|
|
||||||
|
|
||||||
hotBuf: make([]float64, 0, opts.BufCap),
|
|
||||||
coldBuf: make([]float64, 0, opts.BufCap),
|
|
||||||
streamDuration: opts.MaxAge / time.Duration(opts.AgeBuckets),
|
|
||||||
}
|
|
||||||
s.headStreamExpTime = time.Now().Add(s.streamDuration)
|
|
||||||
s.hotBufExpTime = s.headStreamExpTime
|
|
||||||
|
|
||||||
for i := uint32(0); i < opts.AgeBuckets; i++ {
|
|
||||||
s.streams = append(s.streams, s.newStream())
|
|
||||||
}
|
|
||||||
s.headStream = s.streams[0]
|
|
||||||
|
|
||||||
for qu := range s.objectives {
|
|
||||||
s.sortedObjectives = append(s.sortedObjectives, qu)
|
|
||||||
}
|
|
||||||
sort.Float64s(s.sortedObjectives)
|
|
||||||
|
|
||||||
s.init(s) // Init self-collection.
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
type summary struct {
|
|
||||||
selfCollector
|
|
||||||
|
|
||||||
bufMtx sync.Mutex // Protects hotBuf and hotBufExpTime.
|
|
||||||
mtx sync.Mutex // Protects every other moving part.
|
|
||||||
// Lock bufMtx before mtx if both are needed.
|
|
||||||
|
|
||||||
desc *Desc
|
|
||||||
|
|
||||||
objectives map[float64]float64
|
|
||||||
sortedObjectives []float64
|
|
||||||
|
|
||||||
labelPairs []*dto.LabelPair
|
|
||||||
|
|
||||||
sum float64
|
|
||||||
cnt uint64
|
|
||||||
|
|
||||||
hotBuf, coldBuf []float64
|
|
||||||
|
|
||||||
streams []*quantile.Stream
|
|
||||||
streamDuration time.Duration
|
|
||||||
headStream *quantile.Stream
|
|
||||||
headStreamIdx int
|
|
||||||
headStreamExpTime, hotBufExpTime time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *summary) Desc() *Desc {
|
|
||||||
return s.desc
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *summary) Observe(v float64) {
|
|
||||||
s.bufMtx.Lock()
|
|
||||||
defer s.bufMtx.Unlock()
|
|
||||||
|
|
||||||
now := time.Now()
|
|
||||||
if now.After(s.hotBufExpTime) {
|
|
||||||
s.asyncFlush(now)
|
|
||||||
}
|
|
||||||
s.hotBuf = append(s.hotBuf, v)
|
|
||||||
if len(s.hotBuf) == cap(s.hotBuf) {
|
|
||||||
s.asyncFlush(now)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *summary) Write(out *dto.Metric) error {
|
|
||||||
sum := &dto.Summary{}
|
|
||||||
qs := make([]*dto.Quantile, 0, len(s.objectives))
|
|
||||||
|
|
||||||
s.bufMtx.Lock()
|
|
||||||
s.mtx.Lock()
|
|
||||||
// Swap bufs even if hotBuf is empty to set new hotBufExpTime.
|
|
||||||
s.swapBufs(time.Now())
|
|
||||||
s.bufMtx.Unlock()
|
|
||||||
|
|
||||||
s.flushColdBuf()
|
|
||||||
sum.SampleCount = proto.Uint64(s.cnt)
|
|
||||||
sum.SampleSum = proto.Float64(s.sum)
|
|
||||||
|
|
||||||
for _, rank := range s.sortedObjectives {
|
|
||||||
var q float64
|
|
||||||
if s.headStream.Count() == 0 {
|
|
||||||
q = math.NaN()
|
|
||||||
} else {
|
|
||||||
q = s.headStream.Query(rank)
|
|
||||||
}
|
|
||||||
qs = append(qs, &dto.Quantile{
|
|
||||||
Quantile: proto.Float64(rank),
|
|
||||||
Value: proto.Float64(q),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
s.mtx.Unlock()
|
|
||||||
|
|
||||||
if len(qs) > 0 {
|
|
||||||
sort.Sort(quantSort(qs))
|
|
||||||
}
|
|
||||||
sum.Quantile = qs
|
|
||||||
|
|
||||||
out.Summary = sum
|
|
||||||
out.Label = s.labelPairs
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *summary) newStream() *quantile.Stream {
|
|
||||||
return quantile.NewTargeted(s.objectives)
|
|
||||||
}
|
|
||||||
|
|
||||||
// asyncFlush needs bufMtx locked.
|
|
||||||
func (s *summary) asyncFlush(now time.Time) {
|
|
||||||
s.mtx.Lock()
|
|
||||||
s.swapBufs(now)
|
|
||||||
|
|
||||||
// Unblock the original goroutine that was responsible for the mutation
|
|
||||||
// that triggered the compaction. But hold onto the global non-buffer
|
|
||||||
// state mutex until the operation finishes.
|
|
||||||
go func() {
|
|
||||||
s.flushColdBuf()
|
|
||||||
s.mtx.Unlock()
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
// rotateStreams needs mtx AND bufMtx locked.
|
|
||||||
func (s *summary) maybeRotateStreams() {
|
|
||||||
for !s.hotBufExpTime.Equal(s.headStreamExpTime) {
|
|
||||||
s.headStream.Reset()
|
|
||||||
s.headStreamIdx++
|
|
||||||
if s.headStreamIdx >= len(s.streams) {
|
|
||||||
s.headStreamIdx = 0
|
|
||||||
}
|
|
||||||
s.headStream = s.streams[s.headStreamIdx]
|
|
||||||
s.headStreamExpTime = s.headStreamExpTime.Add(s.streamDuration)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// flushColdBuf needs mtx locked.
|
|
||||||
func (s *summary) flushColdBuf() {
|
|
||||||
for _, v := range s.coldBuf {
|
|
||||||
for _, stream := range s.streams {
|
|
||||||
stream.Insert(v)
|
|
||||||
}
|
|
||||||
s.cnt++
|
|
||||||
s.sum += v
|
|
||||||
}
|
|
||||||
s.coldBuf = s.coldBuf[0:0]
|
|
||||||
s.maybeRotateStreams()
|
|
||||||
}
|
|
||||||
|
|
||||||
// swapBufs needs mtx AND bufMtx locked, coldBuf must be empty.
|
|
||||||
func (s *summary) swapBufs(now time.Time) {
|
|
||||||
if len(s.coldBuf) != 0 {
|
|
||||||
panic("coldBuf is not empty")
|
|
||||||
}
|
|
||||||
s.hotBuf, s.coldBuf = s.coldBuf, s.hotBuf
|
|
||||||
// hotBuf is now empty and gets new expiration set.
|
|
||||||
for now.After(s.hotBufExpTime) {
|
|
||||||
s.hotBufExpTime = s.hotBufExpTime.Add(s.streamDuration)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type quantSort []*dto.Quantile
|
|
||||||
|
|
||||||
func (s quantSort) Len() int {
|
|
||||||
return len(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s quantSort) Swap(i, j int) {
|
|
||||||
s[i], s[j] = s[j], s[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s quantSort) Less(i, j int) bool {
|
|
||||||
return s[i].GetQuantile() < s[j].GetQuantile()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SummaryVec is a Collector that bundles a set of Summaries that all share the
|
|
||||||
// same Desc, but have different values for their variable labels. This is used
|
|
||||||
// if you want to count the same thing partitioned by various dimensions
|
|
||||||
// (e.g. HTTP request latencies, partitioned by status code and method). Create
|
|
||||||
// instances with NewSummaryVec.
|
|
||||||
type SummaryVec struct {
|
|
||||||
*MetricVec
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSummaryVec creates a new SummaryVec based on the provided SummaryOpts and
|
|
||||||
// partitioned by the given label names. At least one label name must be
|
|
||||||
// provided.
|
|
||||||
func NewSummaryVec(opts SummaryOpts, labelNames []string) *SummaryVec {
|
|
||||||
desc := NewDesc(
|
|
||||||
BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
|
|
||||||
opts.Help,
|
|
||||||
labelNames,
|
|
||||||
opts.ConstLabels,
|
|
||||||
)
|
|
||||||
return &SummaryVec{
|
|
||||||
MetricVec: newMetricVec(desc, func(lvs ...string) Metric {
|
|
||||||
return newSummary(desc, opts, lvs...)
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMetricWithLabelValues replaces the method of the same name in
|
|
||||||
// MetricVec. The difference is that this method returns a Summary and not a
|
|
||||||
// Metric so that no type conversion is required.
|
|
||||||
func (m *SummaryVec) GetMetricWithLabelValues(lvs ...string) (Summary, error) {
|
|
||||||
metric, err := m.MetricVec.GetMetricWithLabelValues(lvs...)
|
|
||||||
if metric != nil {
|
|
||||||
return metric.(Summary), err
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMetricWith replaces the method of the same name in MetricVec. The
|
|
||||||
// difference is that this method returns a Summary and not a Metric so that no
|
|
||||||
// type conversion is required.
|
|
||||||
func (m *SummaryVec) GetMetricWith(labels Labels) (Summary, error) {
|
|
||||||
metric, err := m.MetricVec.GetMetricWith(labels)
|
|
||||||
if metric != nil {
|
|
||||||
return metric.(Summary), err
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithLabelValues works as GetMetricWithLabelValues, but panics where
|
|
||||||
// GetMetricWithLabelValues would have returned an error. By not returning an
|
|
||||||
// error, WithLabelValues allows shortcuts like
|
|
||||||
// myVec.WithLabelValues("404", "GET").Observe(42.21)
|
|
||||||
func (m *SummaryVec) WithLabelValues(lvs ...string) Summary {
|
|
||||||
return m.MetricVec.WithLabelValues(lvs...).(Summary)
|
|
||||||
}
|
|
||||||
|
|
||||||
// With works as GetMetricWith, but panics where GetMetricWithLabels would have
|
|
||||||
// returned an error. By not returning an error, With allows shortcuts like
|
|
||||||
// myVec.With(Labels{"code": "404", "method": "GET"}).Observe(42.21)
|
|
||||||
func (m *SummaryVec) With(labels Labels) Summary {
|
|
||||||
return m.MetricVec.With(labels).(Summary)
|
|
||||||
}
|
|
||||||
|
|
||||||
type constSummary struct {
|
|
||||||
desc *Desc
|
|
||||||
count uint64
|
|
||||||
sum float64
|
|
||||||
quantiles map[float64]float64
|
|
||||||
labelPairs []*dto.LabelPair
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *constSummary) Desc() *Desc {
|
|
||||||
return s.desc
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *constSummary) Write(out *dto.Metric) error {
|
|
||||||
sum := &dto.Summary{}
|
|
||||||
qs := make([]*dto.Quantile, 0, len(s.quantiles))
|
|
||||||
|
|
||||||
sum.SampleCount = proto.Uint64(s.count)
|
|
||||||
sum.SampleSum = proto.Float64(s.sum)
|
|
||||||
|
|
||||||
for rank, q := range s.quantiles {
|
|
||||||
qs = append(qs, &dto.Quantile{
|
|
||||||
Quantile: proto.Float64(rank),
|
|
||||||
Value: proto.Float64(q),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(qs) > 0 {
|
|
||||||
sort.Sort(quantSort(qs))
|
|
||||||
}
|
|
||||||
sum.Quantile = qs
|
|
||||||
|
|
||||||
out.Summary = sum
|
|
||||||
out.Label = s.labelPairs
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewConstSummary returns a metric representing a Prometheus summary with fixed
|
|
||||||
// values for the count, sum, and quantiles. As those parameters cannot be
|
|
||||||
// changed, the returned value does not implement the Summary interface (but
|
|
||||||
// only the Metric interface). Users of this package will not have much use for
|
|
||||||
// it in regular operations. However, when implementing custom Collectors, it is
|
|
||||||
// useful as a throw-away metric that is generated on the fly to send it to
|
|
||||||
// Prometheus in the Collect method.
|
|
||||||
//
|
|
||||||
// quantiles maps ranks to quantile values. For example, a median latency of
|
|
||||||
// 0.23s and a 99th percentile latency of 0.56s would be expressed as:
|
|
||||||
// map[float64]float64{0.5: 0.23, 0.99: 0.56}
|
|
||||||
//
|
|
||||||
// NewConstSummary returns an error if the length of labelValues is not
|
|
||||||
// consistent with the variable labels in Desc.
|
|
||||||
func NewConstSummary(
|
|
||||||
desc *Desc,
|
|
||||||
count uint64,
|
|
||||||
sum float64,
|
|
||||||
quantiles map[float64]float64,
|
|
||||||
labelValues ...string,
|
|
||||||
) (Metric, error) {
|
|
||||||
if len(desc.variableLabels) != len(labelValues) {
|
|
||||||
return nil, errInconsistentCardinality
|
|
||||||
}
|
|
||||||
return &constSummary{
|
|
||||||
desc: desc,
|
|
||||||
count: count,
|
|
||||||
sum: sum,
|
|
||||||
quantiles: quantiles,
|
|
||||||
labelPairs: makeLabelPairs(desc, labelValues),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MustNewConstSummary is a version of NewConstSummary that panics where
|
|
||||||
// NewConstMetric would have returned an error.
|
|
||||||
func MustNewConstSummary(
|
|
||||||
desc *Desc,
|
|
||||||
count uint64,
|
|
||||||
sum float64,
|
|
||||||
quantiles map[float64]float64,
|
|
||||||
labelValues ...string,
|
|
||||||
) Metric {
|
|
||||||
m, err := NewConstSummary(desc, count, sum, quantiles, labelValues...)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
@ -1,138 +0,0 @@
|
||||||
// Copyright 2014 The Prometheus Authors
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package prometheus
|
|
||||||
|
|
||||||
// Untyped is a Metric that represents a single numerical value that can
|
|
||||||
// arbitrarily go up and down.
|
|
||||||
//
|
|
||||||
// An Untyped metric works the same as a Gauge. The only difference is that to
|
|
||||||
// no type information is implied.
|
|
||||||
//
|
|
||||||
// To create Untyped instances, use NewUntyped.
|
|
||||||
type Untyped interface {
|
|
||||||
Metric
|
|
||||||
Collector
|
|
||||||
|
|
||||||
// Set sets the Untyped metric to an arbitrary value.
|
|
||||||
Set(float64)
|
|
||||||
// Inc increments the Untyped metric by 1.
|
|
||||||
Inc()
|
|
||||||
// Dec decrements the Untyped metric by 1.
|
|
||||||
Dec()
|
|
||||||
// Add adds the given value to the Untyped metric. (The value can be
|
|
||||||
// negative, resulting in a decrease.)
|
|
||||||
Add(float64)
|
|
||||||
// Sub subtracts the given value from the Untyped metric. (The value can
|
|
||||||
// be negative, resulting in an increase.)
|
|
||||||
Sub(float64)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UntypedOpts is an alias for Opts. See there for doc comments.
|
|
||||||
type UntypedOpts Opts
|
|
||||||
|
|
||||||
// NewUntyped creates a new Untyped metric from the provided UntypedOpts.
|
|
||||||
func NewUntyped(opts UntypedOpts) Untyped {
|
|
||||||
return newValue(NewDesc(
|
|
||||||
BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
|
|
||||||
opts.Help,
|
|
||||||
nil,
|
|
||||||
opts.ConstLabels,
|
|
||||||
), UntypedValue, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UntypedVec is a Collector that bundles a set of Untyped metrics that all
|
|
||||||
// share the same Desc, but have different values for their variable
|
|
||||||
// labels. This is used if you want to count the same thing partitioned by
|
|
||||||
// various dimensions. Create instances with NewUntypedVec.
|
|
||||||
type UntypedVec struct {
|
|
||||||
*MetricVec
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewUntypedVec creates a new UntypedVec based on the provided UntypedOpts and
|
|
||||||
// partitioned by the given label names. At least one label name must be
|
|
||||||
// provided.
|
|
||||||
func NewUntypedVec(opts UntypedOpts, labelNames []string) *UntypedVec {
|
|
||||||
desc := NewDesc(
|
|
||||||
BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
|
|
||||||
opts.Help,
|
|
||||||
labelNames,
|
|
||||||
opts.ConstLabels,
|
|
||||||
)
|
|
||||||
return &UntypedVec{
|
|
||||||
MetricVec: newMetricVec(desc, func(lvs ...string) Metric {
|
|
||||||
return newValue(desc, UntypedValue, 0, lvs...)
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMetricWithLabelValues replaces the method of the same name in
|
|
||||||
// MetricVec. The difference is that this method returns an Untyped and not a
|
|
||||||
// Metric so that no type conversion is required.
|
|
||||||
func (m *UntypedVec) GetMetricWithLabelValues(lvs ...string) (Untyped, error) {
|
|
||||||
metric, err := m.MetricVec.GetMetricWithLabelValues(lvs...)
|
|
||||||
if metric != nil {
|
|
||||||
return metric.(Untyped), err
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMetricWith replaces the method of the same name in MetricVec. The
|
|
||||||
// difference is that this method returns an Untyped and not a Metric so that no
|
|
||||||
// type conversion is required.
|
|
||||||
func (m *UntypedVec) GetMetricWith(labels Labels) (Untyped, error) {
|
|
||||||
metric, err := m.MetricVec.GetMetricWith(labels)
|
|
||||||
if metric != nil {
|
|
||||||
return metric.(Untyped), err
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithLabelValues works as GetMetricWithLabelValues, but panics where
|
|
||||||
// GetMetricWithLabelValues would have returned an error. By not returning an
|
|
||||||
// error, WithLabelValues allows shortcuts like
|
|
||||||
// myVec.WithLabelValues("404", "GET").Add(42)
|
|
||||||
func (m *UntypedVec) WithLabelValues(lvs ...string) Untyped {
|
|
||||||
return m.MetricVec.WithLabelValues(lvs...).(Untyped)
|
|
||||||
}
|
|
||||||
|
|
||||||
// With works as GetMetricWith, but panics where GetMetricWithLabels would have
|
|
||||||
// returned an error. By not returning an error, With allows shortcuts like
|
|
||||||
// myVec.With(Labels{"code": "404", "method": "GET"}).Add(42)
|
|
||||||
func (m *UntypedVec) With(labels Labels) Untyped {
|
|
||||||
return m.MetricVec.With(labels).(Untyped)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UntypedFunc is an Untyped whose value is determined at collect time by
|
|
||||||
// calling a provided function.
|
|
||||||
//
|
|
||||||
// To create UntypedFunc instances, use NewUntypedFunc.
|
|
||||||
type UntypedFunc interface {
|
|
||||||
Metric
|
|
||||||
Collector
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewUntypedFunc creates a new UntypedFunc based on the provided
|
|
||||||
// UntypedOpts. The value reported is determined by calling the given function
|
|
||||||
// from within the Write method. Take into account that metric collection may
|
|
||||||
// happen concurrently. If that results in concurrent calls to Write, like in
|
|
||||||
// the case where an UntypedFunc is directly registered with Prometheus, the
|
|
||||||
// provided function must be concurrency-safe.
|
|
||||||
func NewUntypedFunc(opts UntypedOpts, function func() float64) UntypedFunc {
|
|
||||||
return newValueFunc(NewDesc(
|
|
||||||
BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
|
|
||||||
opts.Help,
|
|
||||||
nil,
|
|
||||||
opts.ConstLabels,
|
|
||||||
), UntypedValue, function)
|
|
||||||
}
|
|
||||||
|
|
@ -1,234 +0,0 @@
|
||||||
// Copyright 2014 The Prometheus Authors
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package prometheus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"math"
|
|
||||||
"sort"
|
|
||||||
"sync/atomic"
|
|
||||||
|
|
||||||
dto "github.com/prometheus/client_model/go"
|
|
||||||
|
|
||||||
"github.com/golang/protobuf/proto"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ValueType is an enumeration of metric types that represent a simple value.
|
|
||||||
type ValueType int
|
|
||||||
|
|
||||||
// Possible values for the ValueType enum.
|
|
||||||
const (
|
|
||||||
_ ValueType = iota
|
|
||||||
CounterValue
|
|
||||||
GaugeValue
|
|
||||||
UntypedValue
|
|
||||||
)
|
|
||||||
|
|
||||||
var errInconsistentCardinality = errors.New("inconsistent label cardinality")
|
|
||||||
|
|
||||||
// value is a generic metric for simple values. It implements Metric, Collector,
|
|
||||||
// Counter, Gauge, and Untyped. Its effective type is determined by
|
|
||||||
// ValueType. This is a low-level building block used by the library to back the
|
|
||||||
// implementations of Counter, Gauge, and Untyped.
|
|
||||||
type value struct {
|
|
||||||
// valBits containst the bits of the represented float64 value. It has
|
|
||||||
// to go first in the struct to guarantee alignment for atomic
|
|
||||||
// operations. http://golang.org/pkg/sync/atomic/#pkg-note-BUG
|
|
||||||
valBits uint64
|
|
||||||
|
|
||||||
selfCollector
|
|
||||||
|
|
||||||
desc *Desc
|
|
||||||
valType ValueType
|
|
||||||
labelPairs []*dto.LabelPair
|
|
||||||
}
|
|
||||||
|
|
||||||
// newValue returns a newly allocated value with the given Desc, ValueType,
|
|
||||||
// sample value and label values. It panics if the number of label
|
|
||||||
// values is different from the number of variable labels in Desc.
|
|
||||||
func newValue(desc *Desc, valueType ValueType, val float64, labelValues ...string) *value {
|
|
||||||
if len(labelValues) != len(desc.variableLabels) {
|
|
||||||
panic(errInconsistentCardinality)
|
|
||||||
}
|
|
||||||
result := &value{
|
|
||||||
desc: desc,
|
|
||||||
valType: valueType,
|
|
||||||
valBits: math.Float64bits(val),
|
|
||||||
labelPairs: makeLabelPairs(desc, labelValues),
|
|
||||||
}
|
|
||||||
result.init(result)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *value) Desc() *Desc {
|
|
||||||
return v.desc
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *value) Set(val float64) {
|
|
||||||
atomic.StoreUint64(&v.valBits, math.Float64bits(val))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *value) Inc() {
|
|
||||||
v.Add(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *value) Dec() {
|
|
||||||
v.Add(-1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *value) Add(val float64) {
|
|
||||||
for {
|
|
||||||
oldBits := atomic.LoadUint64(&v.valBits)
|
|
||||||
newBits := math.Float64bits(math.Float64frombits(oldBits) + val)
|
|
||||||
if atomic.CompareAndSwapUint64(&v.valBits, oldBits, newBits) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *value) Sub(val float64) {
|
|
||||||
v.Add(val * -1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *value) Write(out *dto.Metric) error {
|
|
||||||
val := math.Float64frombits(atomic.LoadUint64(&v.valBits))
|
|
||||||
return populateMetric(v.valType, val, v.labelPairs, out)
|
|
||||||
}
|
|
||||||
|
|
||||||
// valueFunc is a generic metric for simple values retrieved on collect time
|
|
||||||
// from a function. It implements Metric and Collector. Its effective type is
|
|
||||||
// determined by ValueType. This is a low-level building block used by the
|
|
||||||
// library to back the implementations of CounterFunc, GaugeFunc, and
|
|
||||||
// UntypedFunc.
|
|
||||||
type valueFunc struct {
|
|
||||||
selfCollector
|
|
||||||
|
|
||||||
desc *Desc
|
|
||||||
valType ValueType
|
|
||||||
function func() float64
|
|
||||||
labelPairs []*dto.LabelPair
|
|
||||||
}
|
|
||||||
|
|
||||||
// newValueFunc returns a newly allocated valueFunc with the given Desc and
|
|
||||||
// ValueType. The value reported is determined by calling the given function
|
|
||||||
// from within the Write method. Take into account that metric collection may
|
|
||||||
// happen concurrently. If that results in concurrent calls to Write, like in
|
|
||||||
// the case where a valueFunc is directly registered with Prometheus, the
|
|
||||||
// provided function must be concurrency-safe.
|
|
||||||
func newValueFunc(desc *Desc, valueType ValueType, function func() float64) *valueFunc {
|
|
||||||
result := &valueFunc{
|
|
||||||
desc: desc,
|
|
||||||
valType: valueType,
|
|
||||||
function: function,
|
|
||||||
labelPairs: makeLabelPairs(desc, nil),
|
|
||||||
}
|
|
||||||
result.init(result)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *valueFunc) Desc() *Desc {
|
|
||||||
return v.desc
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *valueFunc) Write(out *dto.Metric) error {
|
|
||||||
return populateMetric(v.valType, v.function(), v.labelPairs, out)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewConstMetric returns a metric with one fixed value that cannot be
|
|
||||||
// changed. Users of this package will not have much use for it in regular
|
|
||||||
// operations. However, when implementing custom Collectors, it is useful as a
|
|
||||||
// throw-away metric that is generated on the fly to send it to Prometheus in
|
|
||||||
// the Collect method. NewConstMetric returns an error if the length of
|
|
||||||
// labelValues is not consistent with the variable labels in Desc.
|
|
||||||
func NewConstMetric(desc *Desc, valueType ValueType, value float64, labelValues ...string) (Metric, error) {
|
|
||||||
if len(desc.variableLabels) != len(labelValues) {
|
|
||||||
return nil, errInconsistentCardinality
|
|
||||||
}
|
|
||||||
return &constMetric{
|
|
||||||
desc: desc,
|
|
||||||
valType: valueType,
|
|
||||||
val: value,
|
|
||||||
labelPairs: makeLabelPairs(desc, labelValues),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MustNewConstMetric is a version of NewConstMetric that panics where
|
|
||||||
// NewConstMetric would have returned an error.
|
|
||||||
func MustNewConstMetric(desc *Desc, valueType ValueType, value float64, labelValues ...string) Metric {
|
|
||||||
m, err := NewConstMetric(desc, valueType, value, labelValues...)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
type constMetric struct {
|
|
||||||
desc *Desc
|
|
||||||
valType ValueType
|
|
||||||
val float64
|
|
||||||
labelPairs []*dto.LabelPair
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *constMetric) Desc() *Desc {
|
|
||||||
return m.desc
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *constMetric) Write(out *dto.Metric) error {
|
|
||||||
return populateMetric(m.valType, m.val, m.labelPairs, out)
|
|
||||||
}
|
|
||||||
|
|
||||||
func populateMetric(
|
|
||||||
t ValueType,
|
|
||||||
v float64,
|
|
||||||
labelPairs []*dto.LabelPair,
|
|
||||||
m *dto.Metric,
|
|
||||||
) error {
|
|
||||||
m.Label = labelPairs
|
|
||||||
switch t {
|
|
||||||
case CounterValue:
|
|
||||||
m.Counter = &dto.Counter{Value: proto.Float64(v)}
|
|
||||||
case GaugeValue:
|
|
||||||
m.Gauge = &dto.Gauge{Value: proto.Float64(v)}
|
|
||||||
case UntypedValue:
|
|
||||||
m.Untyped = &dto.Untyped{Value: proto.Float64(v)}
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("encountered unknown type %v", t)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeLabelPairs(desc *Desc, labelValues []string) []*dto.LabelPair {
|
|
||||||
totalLen := len(desc.variableLabels) + len(desc.constLabelPairs)
|
|
||||||
if totalLen == 0 {
|
|
||||||
// Super fast path.
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if len(desc.variableLabels) == 0 {
|
|
||||||
// Moderately fast path.
|
|
||||||
return desc.constLabelPairs
|
|
||||||
}
|
|
||||||
labelPairs := make([]*dto.LabelPair, 0, totalLen)
|
|
||||||
for i, n := range desc.variableLabels {
|
|
||||||
labelPairs = append(labelPairs, &dto.LabelPair{
|
|
||||||
Name: proto.String(n),
|
|
||||||
Value: proto.String(labelValues[i]),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
for _, lp := range desc.constLabelPairs {
|
|
||||||
labelPairs = append(labelPairs, lp)
|
|
||||||
}
|
|
||||||
sort.Sort(LabelPairSorter(labelPairs))
|
|
||||||
return labelPairs
|
|
||||||
}
|
|
||||||
|
|
@ -1,404 +0,0 @@
|
||||||
// Copyright 2014 The Prometheus Authors
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package prometheus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/prometheus/common/model"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MetricVec is a Collector to bundle metrics of the same name that
|
|
||||||
// differ in their label values. MetricVec is usually not used directly but as a
|
|
||||||
// building block for implementations of vectors of a given metric
|
|
||||||
// type. GaugeVec, CounterVec, SummaryVec, and UntypedVec are examples already
|
|
||||||
// provided in this package.
|
|
||||||
type MetricVec struct {
|
|
||||||
mtx sync.RWMutex // Protects the children.
|
|
||||||
children map[uint64][]metricWithLabelValues
|
|
||||||
desc *Desc
|
|
||||||
|
|
||||||
newMetric func(labelValues ...string) Metric
|
|
||||||
hashAdd func(h uint64, s string) uint64 // replace hash function for testing collision handling
|
|
||||||
hashAddByte func(h uint64, b byte) uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
// newMetricVec returns an initialized MetricVec. The concrete value is
|
|
||||||
// returned for embedding into another struct.
|
|
||||||
func newMetricVec(desc *Desc, newMetric func(lvs ...string) Metric) *MetricVec {
|
|
||||||
return &MetricVec{
|
|
||||||
children: map[uint64][]metricWithLabelValues{},
|
|
||||||
desc: desc,
|
|
||||||
newMetric: newMetric,
|
|
||||||
hashAdd: hashAdd,
|
|
||||||
hashAddByte: hashAddByte,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// metricWithLabelValues provides the metric and its label values for
|
|
||||||
// disambiguation on hash collision.
|
|
||||||
type metricWithLabelValues struct {
|
|
||||||
values []string
|
|
||||||
metric Metric
|
|
||||||
}
|
|
||||||
|
|
||||||
// Describe implements Collector. The length of the returned slice
|
|
||||||
// is always one.
|
|
||||||
func (m *MetricVec) Describe(ch chan<- *Desc) {
|
|
||||||
ch <- m.desc
|
|
||||||
}
|
|
||||||
|
|
||||||
// Collect implements Collector.
|
|
||||||
func (m *MetricVec) Collect(ch chan<- Metric) {
|
|
||||||
m.mtx.RLock()
|
|
||||||
defer m.mtx.RUnlock()
|
|
||||||
|
|
||||||
for _, metrics := range m.children {
|
|
||||||
for _, metric := range metrics {
|
|
||||||
ch <- metric.metric
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMetricWithLabelValues returns the Metric for the given slice of label
|
|
||||||
// values (same order as the VariableLabels in Desc). If that combination of
|
|
||||||
// label values is accessed for the first time, a new Metric is created.
|
|
||||||
//
|
|
||||||
// It is possible to call this method without using the returned Metric to only
|
|
||||||
// create the new Metric but leave it at its start value (e.g. a Summary or
|
|
||||||
// Histogram without any observations). See also the SummaryVec example.
|
|
||||||
//
|
|
||||||
// Keeping the Metric for later use is possible (and should be considered if
|
|
||||||
// performance is critical), but keep in mind that Reset, DeleteLabelValues and
|
|
||||||
// Delete can be used to delete the Metric from the MetricVec. In that case, the
|
|
||||||
// Metric will still exist, but it will not be exported anymore, even if a
|
|
||||||
// Metric with the same label values is created later. See also the CounterVec
|
|
||||||
// example.
|
|
||||||
//
|
|
||||||
// An error is returned if the number of label values is not the same as the
|
|
||||||
// number of VariableLabels in Desc.
|
|
||||||
//
|
|
||||||
// Note that for more than one label value, this method is prone to mistakes
|
|
||||||
// caused by an incorrect order of arguments. Consider GetMetricWith(Labels) as
|
|
||||||
// an alternative to avoid that type of mistake. For higher label numbers, the
|
|
||||||
// latter has a much more readable (albeit more verbose) syntax, but it comes
|
|
||||||
// with a performance overhead (for creating and processing the Labels map).
|
|
||||||
// See also the GaugeVec example.
|
|
||||||
func (m *MetricVec) GetMetricWithLabelValues(lvs ...string) (Metric, error) {
|
|
||||||
h, err := m.hashLabelValues(lvs)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return m.getOrCreateMetricWithLabelValues(h, lvs), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMetricWith returns the Metric for the given Labels map (the label names
|
|
||||||
// must match those of the VariableLabels in Desc). If that label map is
|
|
||||||
// accessed for the first time, a new Metric is created. Implications of
|
|
||||||
// creating a Metric without using it and keeping the Metric for later use are
|
|
||||||
// the same as for GetMetricWithLabelValues.
|
|
||||||
//
|
|
||||||
// An error is returned if the number and names of the Labels are inconsistent
|
|
||||||
// with those of the VariableLabels in Desc.
|
|
||||||
//
|
|
||||||
// This method is used for the same purpose as
|
|
||||||
// GetMetricWithLabelValues(...string). See there for pros and cons of the two
|
|
||||||
// methods.
|
|
||||||
func (m *MetricVec) GetMetricWith(labels Labels) (Metric, error) {
|
|
||||||
h, err := m.hashLabels(labels)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return m.getOrCreateMetricWithLabels(h, labels), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithLabelValues works as GetMetricWithLabelValues, but panics if an error
|
|
||||||
// occurs. The method allows neat syntax like:
|
|
||||||
// httpReqs.WithLabelValues("404", "POST").Inc()
|
|
||||||
func (m *MetricVec) WithLabelValues(lvs ...string) Metric {
|
|
||||||
metric, err := m.GetMetricWithLabelValues(lvs...)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return metric
|
|
||||||
}
|
|
||||||
|
|
||||||
// With works as GetMetricWith, but panics if an error occurs. The method allows
|
|
||||||
// neat syntax like:
|
|
||||||
// httpReqs.With(Labels{"status":"404", "method":"POST"}).Inc()
|
|
||||||
func (m *MetricVec) With(labels Labels) Metric {
|
|
||||||
metric, err := m.GetMetricWith(labels)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return metric
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteLabelValues removes the metric where the variable labels are the same
|
|
||||||
// as those passed in as labels (same order as the VariableLabels in Desc). It
|
|
||||||
// returns true if a metric was deleted.
|
|
||||||
//
|
|
||||||
// It is not an error if the number of label values is not the same as the
|
|
||||||
// number of VariableLabels in Desc. However, such inconsistent label count can
|
|
||||||
// never match an actual Metric, so the method will always return false in that
|
|
||||||
// case.
|
|
||||||
//
|
|
||||||
// Note that for more than one label value, this method is prone to mistakes
|
|
||||||
// caused by an incorrect order of arguments. Consider Delete(Labels) as an
|
|
||||||
// alternative to avoid that type of mistake. For higher label numbers, the
|
|
||||||
// latter has a much more readable (albeit more verbose) syntax, but it comes
|
|
||||||
// with a performance overhead (for creating and processing the Labels map).
|
|
||||||
// See also the CounterVec example.
|
|
||||||
func (m *MetricVec) DeleteLabelValues(lvs ...string) bool {
|
|
||||||
m.mtx.Lock()
|
|
||||||
defer m.mtx.Unlock()
|
|
||||||
|
|
||||||
h, err := m.hashLabelValues(lvs)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return m.deleteByHashWithLabelValues(h, lvs)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete deletes the metric where the variable labels are the same as those
|
|
||||||
// passed in as labels. It returns true if a metric was deleted.
|
|
||||||
//
|
|
||||||
// It is not an error if the number and names of the Labels are inconsistent
|
|
||||||
// with those of the VariableLabels in the Desc of the MetricVec. However, such
|
|
||||||
// inconsistent Labels can never match an actual Metric, so the method will
|
|
||||||
// always return false in that case.
|
|
||||||
//
|
|
||||||
// This method is used for the same purpose as DeleteLabelValues(...string). See
|
|
||||||
// there for pros and cons of the two methods.
|
|
||||||
func (m *MetricVec) Delete(labels Labels) bool {
|
|
||||||
m.mtx.Lock()
|
|
||||||
defer m.mtx.Unlock()
|
|
||||||
|
|
||||||
h, err := m.hashLabels(labels)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return m.deleteByHashWithLabels(h, labels)
|
|
||||||
}
|
|
||||||
|
|
||||||
// deleteByHashWithLabelValues removes the metric from the hash bucket h. If
|
|
||||||
// there are multiple matches in the bucket, use lvs to select a metric and
|
|
||||||
// remove only that metric.
|
|
||||||
func (m *MetricVec) deleteByHashWithLabelValues(h uint64, lvs []string) bool {
|
|
||||||
metrics, ok := m.children[h]
|
|
||||||
if !ok {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
i := m.findMetricWithLabelValues(metrics, lvs)
|
|
||||||
if i >= len(metrics) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(metrics) > 1 {
|
|
||||||
m.children[h] = append(metrics[:i], metrics[i+1:]...)
|
|
||||||
} else {
|
|
||||||
delete(m.children, h)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// deleteByHashWithLabels removes the metric from the hash bucket h. If there
|
|
||||||
// are multiple matches in the bucket, use lvs to select a metric and remove
|
|
||||||
// only that metric.
|
|
||||||
func (m *MetricVec) deleteByHashWithLabels(h uint64, labels Labels) bool {
|
|
||||||
metrics, ok := m.children[h]
|
|
||||||
if !ok {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
i := m.findMetricWithLabels(metrics, labels)
|
|
||||||
if i >= len(metrics) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(metrics) > 1 {
|
|
||||||
m.children[h] = append(metrics[:i], metrics[i+1:]...)
|
|
||||||
} else {
|
|
||||||
delete(m.children, h)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset deletes all metrics in this vector.
|
|
||||||
func (m *MetricVec) Reset() {
|
|
||||||
m.mtx.Lock()
|
|
||||||
defer m.mtx.Unlock()
|
|
||||||
|
|
||||||
for h := range m.children {
|
|
||||||
delete(m.children, h)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MetricVec) hashLabelValues(vals []string) (uint64, error) {
|
|
||||||
if len(vals) != len(m.desc.variableLabels) {
|
|
||||||
return 0, errInconsistentCardinality
|
|
||||||
}
|
|
||||||
h := hashNew()
|
|
||||||
for _, val := range vals {
|
|
||||||
h = m.hashAdd(h, val)
|
|
||||||
h = m.hashAddByte(h, model.SeparatorByte)
|
|
||||||
}
|
|
||||||
return h, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MetricVec) hashLabels(labels Labels) (uint64, error) {
|
|
||||||
if len(labels) != len(m.desc.variableLabels) {
|
|
||||||
return 0, errInconsistentCardinality
|
|
||||||
}
|
|
||||||
h := hashNew()
|
|
||||||
for _, label := range m.desc.variableLabels {
|
|
||||||
val, ok := labels[label]
|
|
||||||
if !ok {
|
|
||||||
return 0, fmt.Errorf("label name %q missing in label map", label)
|
|
||||||
}
|
|
||||||
h = m.hashAdd(h, val)
|
|
||||||
h = m.hashAddByte(h, model.SeparatorByte)
|
|
||||||
}
|
|
||||||
return h, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// getOrCreateMetricWithLabelValues retrieves the metric by hash and label value
|
|
||||||
// or creates it and returns the new one.
|
|
||||||
//
|
|
||||||
// This function holds the mutex.
|
|
||||||
func (m *MetricVec) getOrCreateMetricWithLabelValues(hash uint64, lvs []string) Metric {
|
|
||||||
m.mtx.RLock()
|
|
||||||
metric, ok := m.getMetricWithLabelValues(hash, lvs)
|
|
||||||
m.mtx.RUnlock()
|
|
||||||
if ok {
|
|
||||||
return metric
|
|
||||||
}
|
|
||||||
|
|
||||||
m.mtx.Lock()
|
|
||||||
defer m.mtx.Unlock()
|
|
||||||
metric, ok = m.getMetricWithLabelValues(hash, lvs)
|
|
||||||
if !ok {
|
|
||||||
// Copy to avoid allocation in case wo don't go down this code path.
|
|
||||||
copiedLVs := make([]string, len(lvs))
|
|
||||||
copy(copiedLVs, lvs)
|
|
||||||
metric = m.newMetric(copiedLVs...)
|
|
||||||
m.children[hash] = append(m.children[hash], metricWithLabelValues{values: copiedLVs, metric: metric})
|
|
||||||
}
|
|
||||||
return metric
|
|
||||||
}
|
|
||||||
|
|
||||||
// getOrCreateMetricWithLabelValues retrieves the metric by hash and label value
|
|
||||||
// or creates it and returns the new one.
|
|
||||||
//
|
|
||||||
// This function holds the mutex.
|
|
||||||
func (m *MetricVec) getOrCreateMetricWithLabels(hash uint64, labels Labels) Metric {
|
|
||||||
m.mtx.RLock()
|
|
||||||
metric, ok := m.getMetricWithLabels(hash, labels)
|
|
||||||
m.mtx.RUnlock()
|
|
||||||
if ok {
|
|
||||||
return metric
|
|
||||||
}
|
|
||||||
|
|
||||||
m.mtx.Lock()
|
|
||||||
defer m.mtx.Unlock()
|
|
||||||
metric, ok = m.getMetricWithLabels(hash, labels)
|
|
||||||
if !ok {
|
|
||||||
lvs := m.extractLabelValues(labels)
|
|
||||||
metric = m.newMetric(lvs...)
|
|
||||||
m.children[hash] = append(m.children[hash], metricWithLabelValues{values: lvs, metric: metric})
|
|
||||||
}
|
|
||||||
return metric
|
|
||||||
}
|
|
||||||
|
|
||||||
// getMetricWithLabelValues gets a metric while handling possible collisions in
|
|
||||||
// the hash space. Must be called while holding read mutex.
|
|
||||||
func (m *MetricVec) getMetricWithLabelValues(h uint64, lvs []string) (Metric, bool) {
|
|
||||||
metrics, ok := m.children[h]
|
|
||||||
if ok {
|
|
||||||
if i := m.findMetricWithLabelValues(metrics, lvs); i < len(metrics) {
|
|
||||||
return metrics[i].metric, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// getMetricWithLabels gets a metric while handling possible collisions in
|
|
||||||
// the hash space. Must be called while holding read mutex.
|
|
||||||
func (m *MetricVec) getMetricWithLabels(h uint64, labels Labels) (Metric, bool) {
|
|
||||||
metrics, ok := m.children[h]
|
|
||||||
if ok {
|
|
||||||
if i := m.findMetricWithLabels(metrics, labels); i < len(metrics) {
|
|
||||||
return metrics[i].metric, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// findMetricWithLabelValues returns the index of the matching metric or
|
|
||||||
// len(metrics) if not found.
|
|
||||||
func (m *MetricVec) findMetricWithLabelValues(metrics []metricWithLabelValues, lvs []string) int {
|
|
||||||
for i, metric := range metrics {
|
|
||||||
if m.matchLabelValues(metric.values, lvs) {
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return len(metrics)
|
|
||||||
}
|
|
||||||
|
|
||||||
// findMetricWithLabels returns the index of the matching metric or len(metrics)
|
|
||||||
// if not found.
|
|
||||||
func (m *MetricVec) findMetricWithLabels(metrics []metricWithLabelValues, labels Labels) int {
|
|
||||||
for i, metric := range metrics {
|
|
||||||
if m.matchLabels(metric.values, labels) {
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return len(metrics)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MetricVec) matchLabelValues(values []string, lvs []string) bool {
|
|
||||||
if len(values) != len(lvs) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for i, v := range values {
|
|
||||||
if v != lvs[i] {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MetricVec) matchLabels(values []string, labels Labels) bool {
|
|
||||||
if len(labels) != len(values) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for i, k := range m.desc.variableLabels {
|
|
||||||
if values[i] != labels[k] {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MetricVec) extractLabelValues(labels Labels) []string {
|
|
||||||
labelValues := make([]string, len(labels))
|
|
||||||
for i, k := range m.desc.variableLabels {
|
|
||||||
labelValues[i] = labels[k]
|
|
||||||
}
|
|
||||||
return labelValues
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
Data model artifacts for Prometheus.
|
|
||||||
Copyright 2012-2015 The Prometheus Authors
|
|
||||||
|
|
||||||
This product includes software developed at
|
|
||||||
SoundCloud Ltd. (http://soundcloud.com/).
|
|
||||||
|
|
@ -1,364 +0,0 @@
|
||||||
// Code generated by protoc-gen-go.
|
|
||||||
// source: metrics.proto
|
|
||||||
// DO NOT EDIT!
|
|
||||||
|
|
||||||
/*
|
|
||||||
Package io_prometheus_client is a generated protocol buffer package.
|
|
||||||
|
|
||||||
It is generated from these files:
|
|
||||||
metrics.proto
|
|
||||||
|
|
||||||
It has these top-level messages:
|
|
||||||
LabelPair
|
|
||||||
Gauge
|
|
||||||
Counter
|
|
||||||
Quantile
|
|
||||||
Summary
|
|
||||||
Untyped
|
|
||||||
Histogram
|
|
||||||
Bucket
|
|
||||||
Metric
|
|
||||||
MetricFamily
|
|
||||||
*/
|
|
||||||
package io_prometheus_client
|
|
||||||
|
|
||||||
import proto "github.com/golang/protobuf/proto"
|
|
||||||
import math "math"
|
|
||||||
|
|
||||||
// Reference imports to suppress errors if they are not otherwise used.
|
|
||||||
var _ = proto.Marshal
|
|
||||||
var _ = math.Inf
|
|
||||||
|
|
||||||
type MetricType int32
|
|
||||||
|
|
||||||
const (
|
|
||||||
MetricType_COUNTER MetricType = 0
|
|
||||||
MetricType_GAUGE MetricType = 1
|
|
||||||
MetricType_SUMMARY MetricType = 2
|
|
||||||
MetricType_UNTYPED MetricType = 3
|
|
||||||
MetricType_HISTOGRAM MetricType = 4
|
|
||||||
)
|
|
||||||
|
|
||||||
var MetricType_name = map[int32]string{
|
|
||||||
0: "COUNTER",
|
|
||||||
1: "GAUGE",
|
|
||||||
2: "SUMMARY",
|
|
||||||
3: "UNTYPED",
|
|
||||||
4: "HISTOGRAM",
|
|
||||||
}
|
|
||||||
var MetricType_value = map[string]int32{
|
|
||||||
"COUNTER": 0,
|
|
||||||
"GAUGE": 1,
|
|
||||||
"SUMMARY": 2,
|
|
||||||
"UNTYPED": 3,
|
|
||||||
"HISTOGRAM": 4,
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x MetricType) Enum() *MetricType {
|
|
||||||
p := new(MetricType)
|
|
||||||
*p = x
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
func (x MetricType) String() string {
|
|
||||||
return proto.EnumName(MetricType_name, int32(x))
|
|
||||||
}
|
|
||||||
func (x *MetricType) UnmarshalJSON(data []byte) error {
|
|
||||||
value, err := proto.UnmarshalJSONEnum(MetricType_value, data, "MetricType")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
*x = MetricType(value)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type LabelPair struct {
|
|
||||||
Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
|
|
||||||
Value *string `protobuf:"bytes,2,opt,name=value" json:"value,omitempty"`
|
|
||||||
XXX_unrecognized []byte `json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *LabelPair) Reset() { *m = LabelPair{} }
|
|
||||||
func (m *LabelPair) String() string { return proto.CompactTextString(m) }
|
|
||||||
func (*LabelPair) ProtoMessage() {}
|
|
||||||
|
|
||||||
func (m *LabelPair) GetName() string {
|
|
||||||
if m != nil && m.Name != nil {
|
|
||||||
return *m.Name
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *LabelPair) GetValue() string {
|
|
||||||
if m != nil && m.Value != nil {
|
|
||||||
return *m.Value
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
type Gauge struct {
|
|
||||||
Value *float64 `protobuf:"fixed64,1,opt,name=value" json:"value,omitempty"`
|
|
||||||
XXX_unrecognized []byte `json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Gauge) Reset() { *m = Gauge{} }
|
|
||||||
func (m *Gauge) String() string { return proto.CompactTextString(m) }
|
|
||||||
func (*Gauge) ProtoMessage() {}
|
|
||||||
|
|
||||||
func (m *Gauge) GetValue() float64 {
|
|
||||||
if m != nil && m.Value != nil {
|
|
||||||
return *m.Value
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
type Counter struct {
|
|
||||||
Value *float64 `protobuf:"fixed64,1,opt,name=value" json:"value,omitempty"`
|
|
||||||
XXX_unrecognized []byte `json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Counter) Reset() { *m = Counter{} }
|
|
||||||
func (m *Counter) String() string { return proto.CompactTextString(m) }
|
|
||||||
func (*Counter) ProtoMessage() {}
|
|
||||||
|
|
||||||
func (m *Counter) GetValue() float64 {
|
|
||||||
if m != nil && m.Value != nil {
|
|
||||||
return *m.Value
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
type Quantile struct {
|
|
||||||
Quantile *float64 `protobuf:"fixed64,1,opt,name=quantile" json:"quantile,omitempty"`
|
|
||||||
Value *float64 `protobuf:"fixed64,2,opt,name=value" json:"value,omitempty"`
|
|
||||||
XXX_unrecognized []byte `json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Quantile) Reset() { *m = Quantile{} }
|
|
||||||
func (m *Quantile) String() string { return proto.CompactTextString(m) }
|
|
||||||
func (*Quantile) ProtoMessage() {}
|
|
||||||
|
|
||||||
func (m *Quantile) GetQuantile() float64 {
|
|
||||||
if m != nil && m.Quantile != nil {
|
|
||||||
return *m.Quantile
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Quantile) GetValue() float64 {
|
|
||||||
if m != nil && m.Value != nil {
|
|
||||||
return *m.Value
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
type Summary struct {
|
|
||||||
SampleCount *uint64 `protobuf:"varint,1,opt,name=sample_count" json:"sample_count,omitempty"`
|
|
||||||
SampleSum *float64 `protobuf:"fixed64,2,opt,name=sample_sum" json:"sample_sum,omitempty"`
|
|
||||||
Quantile []*Quantile `protobuf:"bytes,3,rep,name=quantile" json:"quantile,omitempty"`
|
|
||||||
XXX_unrecognized []byte `json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Summary) Reset() { *m = Summary{} }
|
|
||||||
func (m *Summary) String() string { return proto.CompactTextString(m) }
|
|
||||||
func (*Summary) ProtoMessage() {}
|
|
||||||
|
|
||||||
func (m *Summary) GetSampleCount() uint64 {
|
|
||||||
if m != nil && m.SampleCount != nil {
|
|
||||||
return *m.SampleCount
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Summary) GetSampleSum() float64 {
|
|
||||||
if m != nil && m.SampleSum != nil {
|
|
||||||
return *m.SampleSum
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Summary) GetQuantile() []*Quantile {
|
|
||||||
if m != nil {
|
|
||||||
return m.Quantile
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type Untyped struct {
|
|
||||||
Value *float64 `protobuf:"fixed64,1,opt,name=value" json:"value,omitempty"`
|
|
||||||
XXX_unrecognized []byte `json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Untyped) Reset() { *m = Untyped{} }
|
|
||||||
func (m *Untyped) String() string { return proto.CompactTextString(m) }
|
|
||||||
func (*Untyped) ProtoMessage() {}
|
|
||||||
|
|
||||||
func (m *Untyped) GetValue() float64 {
|
|
||||||
if m != nil && m.Value != nil {
|
|
||||||
return *m.Value
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
type Histogram struct {
|
|
||||||
SampleCount *uint64 `protobuf:"varint,1,opt,name=sample_count" json:"sample_count,omitempty"`
|
|
||||||
SampleSum *float64 `protobuf:"fixed64,2,opt,name=sample_sum" json:"sample_sum,omitempty"`
|
|
||||||
Bucket []*Bucket `protobuf:"bytes,3,rep,name=bucket" json:"bucket,omitempty"`
|
|
||||||
XXX_unrecognized []byte `json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Histogram) Reset() { *m = Histogram{} }
|
|
||||||
func (m *Histogram) String() string { return proto.CompactTextString(m) }
|
|
||||||
func (*Histogram) ProtoMessage() {}
|
|
||||||
|
|
||||||
func (m *Histogram) GetSampleCount() uint64 {
|
|
||||||
if m != nil && m.SampleCount != nil {
|
|
||||||
return *m.SampleCount
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Histogram) GetSampleSum() float64 {
|
|
||||||
if m != nil && m.SampleSum != nil {
|
|
||||||
return *m.SampleSum
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Histogram) GetBucket() []*Bucket {
|
|
||||||
if m != nil {
|
|
||||||
return m.Bucket
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type Bucket struct {
|
|
||||||
CumulativeCount *uint64 `protobuf:"varint,1,opt,name=cumulative_count" json:"cumulative_count,omitempty"`
|
|
||||||
UpperBound *float64 `protobuf:"fixed64,2,opt,name=upper_bound" json:"upper_bound,omitempty"`
|
|
||||||
XXX_unrecognized []byte `json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Bucket) Reset() { *m = Bucket{} }
|
|
||||||
func (m *Bucket) String() string { return proto.CompactTextString(m) }
|
|
||||||
func (*Bucket) ProtoMessage() {}
|
|
||||||
|
|
||||||
func (m *Bucket) GetCumulativeCount() uint64 {
|
|
||||||
if m != nil && m.CumulativeCount != nil {
|
|
||||||
return *m.CumulativeCount
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Bucket) GetUpperBound() float64 {
|
|
||||||
if m != nil && m.UpperBound != nil {
|
|
||||||
return *m.UpperBound
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
type Metric struct {
|
|
||||||
Label []*LabelPair `protobuf:"bytes,1,rep,name=label" json:"label,omitempty"`
|
|
||||||
Gauge *Gauge `protobuf:"bytes,2,opt,name=gauge" json:"gauge,omitempty"`
|
|
||||||
Counter *Counter `protobuf:"bytes,3,opt,name=counter" json:"counter,omitempty"`
|
|
||||||
Summary *Summary `protobuf:"bytes,4,opt,name=summary" json:"summary,omitempty"`
|
|
||||||
Untyped *Untyped `protobuf:"bytes,5,opt,name=untyped" json:"untyped,omitempty"`
|
|
||||||
Histogram *Histogram `protobuf:"bytes,7,opt,name=histogram" json:"histogram,omitempty"`
|
|
||||||
TimestampMs *int64 `protobuf:"varint,6,opt,name=timestamp_ms" json:"timestamp_ms,omitempty"`
|
|
||||||
XXX_unrecognized []byte `json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Metric) Reset() { *m = Metric{} }
|
|
||||||
func (m *Metric) String() string { return proto.CompactTextString(m) }
|
|
||||||
func (*Metric) ProtoMessage() {}
|
|
||||||
|
|
||||||
func (m *Metric) GetLabel() []*LabelPair {
|
|
||||||
if m != nil {
|
|
||||||
return m.Label
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Metric) GetGauge() *Gauge {
|
|
||||||
if m != nil {
|
|
||||||
return m.Gauge
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Metric) GetCounter() *Counter {
|
|
||||||
if m != nil {
|
|
||||||
return m.Counter
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Metric) GetSummary() *Summary {
|
|
||||||
if m != nil {
|
|
||||||
return m.Summary
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Metric) GetUntyped() *Untyped {
|
|
||||||
if m != nil {
|
|
||||||
return m.Untyped
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Metric) GetHistogram() *Histogram {
|
|
||||||
if m != nil {
|
|
||||||
return m.Histogram
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Metric) GetTimestampMs() int64 {
|
|
||||||
if m != nil && m.TimestampMs != nil {
|
|
||||||
return *m.TimestampMs
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
type MetricFamily struct {
|
|
||||||
Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
|
|
||||||
Help *string `protobuf:"bytes,2,opt,name=help" json:"help,omitempty"`
|
|
||||||
Type *MetricType `protobuf:"varint,3,opt,name=type,enum=io.prometheus.client.MetricType" json:"type,omitempty"`
|
|
||||||
Metric []*Metric `protobuf:"bytes,4,rep,name=metric" json:"metric,omitempty"`
|
|
||||||
XXX_unrecognized []byte `json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MetricFamily) Reset() { *m = MetricFamily{} }
|
|
||||||
func (m *MetricFamily) String() string { return proto.CompactTextString(m) }
|
|
||||||
func (*MetricFamily) ProtoMessage() {}
|
|
||||||
|
|
||||||
func (m *MetricFamily) GetName() string {
|
|
||||||
if m != nil && m.Name != nil {
|
|
||||||
return *m.Name
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MetricFamily) GetHelp() string {
|
|
||||||
if m != nil && m.Help != nil {
|
|
||||||
return *m.Help
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MetricFamily) GetType() MetricType {
|
|
||||||
if m != nil && m.Type != nil {
|
|
||||||
return *m.Type
|
|
||||||
}
|
|
||||||
return MetricType_COUNTER
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MetricFamily) GetMetric() []*Metric {
|
|
||||||
if m != nil {
|
|
||||||
return m.Metric
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
proto.RegisterEnum("io.prometheus.client.MetricType", MetricType_name, MetricType_value)
|
|
||||||
}
|
|
||||||
|
|
@ -1,201 +0,0 @@
|
||||||
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.
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
Common libraries shared by Prometheus Go components.
|
|
||||||
Copyright 2015 The Prometheus Authors
|
|
||||||
|
|
||||||
This product includes software developed at
|
|
||||||
SoundCloud Ltd. (http://soundcloud.com/).
|
|
||||||
|
|
@ -1,429 +0,0 @@
|
||||||
// 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 expfmt
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"math"
|
|
||||||
"mime"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
dto "github.com/prometheus/client_model/go"
|
|
||||||
|
|
||||||
"github.com/matttproud/golang_protobuf_extensions/pbutil"
|
|
||||||
"github.com/prometheus/common/model"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Decoder types decode an input stream into metric families.
|
|
||||||
type Decoder interface {
|
|
||||||
Decode(*dto.MetricFamily) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecodeOptions contains options used by the Decoder and in sample extraction.
|
|
||||||
type DecodeOptions struct {
|
|
||||||
// Timestamp is added to each value from the stream that has no explicit timestamp set.
|
|
||||||
Timestamp model.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResponseFormat extracts the correct format from a HTTP response header.
|
|
||||||
// If no matching format can be found FormatUnknown is returned.
|
|
||||||
func ResponseFormat(h http.Header) Format {
|
|
||||||
ct := h.Get(hdrContentType)
|
|
||||||
|
|
||||||
mediatype, params, err := mime.ParseMediaType(ct)
|
|
||||||
if err != nil {
|
|
||||||
return FmtUnknown
|
|
||||||
}
|
|
||||||
|
|
||||||
const textType = "text/plain"
|
|
||||||
|
|
||||||
switch mediatype {
|
|
||||||
case ProtoType:
|
|
||||||
if p, ok := params["proto"]; ok && p != ProtoProtocol {
|
|
||||||
return FmtUnknown
|
|
||||||
}
|
|
||||||
if e, ok := params["encoding"]; ok && e != "delimited" {
|
|
||||||
return FmtUnknown
|
|
||||||
}
|
|
||||||
return FmtProtoDelim
|
|
||||||
|
|
||||||
case textType:
|
|
||||||
if v, ok := params["version"]; ok && v != TextVersion {
|
|
||||||
return FmtUnknown
|
|
||||||
}
|
|
||||||
return FmtText
|
|
||||||
}
|
|
||||||
|
|
||||||
return FmtUnknown
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewDecoder returns a new decoder based on the given input format.
|
|
||||||
// If the input format does not imply otherwise, a text format decoder is returned.
|
|
||||||
func NewDecoder(r io.Reader, format Format) Decoder {
|
|
||||||
switch format {
|
|
||||||
case FmtProtoDelim:
|
|
||||||
return &protoDecoder{r: r}
|
|
||||||
}
|
|
||||||
return &textDecoder{r: r}
|
|
||||||
}
|
|
||||||
|
|
||||||
// protoDecoder implements the Decoder interface for protocol buffers.
|
|
||||||
type protoDecoder struct {
|
|
||||||
r io.Reader
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decode implements the Decoder interface.
|
|
||||||
func (d *protoDecoder) Decode(v *dto.MetricFamily) error {
|
|
||||||
_, err := pbutil.ReadDelimited(d.r, v)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !model.IsValidMetricName(model.LabelValue(v.GetName())) {
|
|
||||||
return fmt.Errorf("invalid metric name %q", v.GetName())
|
|
||||||
}
|
|
||||||
for _, m := range v.GetMetric() {
|
|
||||||
if m == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for _, l := range m.GetLabel() {
|
|
||||||
if l == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if !model.LabelValue(l.GetValue()).IsValid() {
|
|
||||||
return fmt.Errorf("invalid label value %q", l.GetValue())
|
|
||||||
}
|
|
||||||
if !model.LabelName(l.GetName()).IsValid() {
|
|
||||||
return fmt.Errorf("invalid label name %q", l.GetName())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// textDecoder implements the Decoder interface for the text protocol.
|
|
||||||
type textDecoder struct {
|
|
||||||
r io.Reader
|
|
||||||
p TextParser
|
|
||||||
fams []*dto.MetricFamily
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decode implements the Decoder interface.
|
|
||||||
func (d *textDecoder) Decode(v *dto.MetricFamily) error {
|
|
||||||
// TODO(fabxc): Wrap this as a line reader to make streaming safer.
|
|
||||||
if len(d.fams) == 0 {
|
|
||||||
// No cached metric families, read everything and parse metrics.
|
|
||||||
fams, err := d.p.TextToMetricFamilies(d.r)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if len(fams) == 0 {
|
|
||||||
return io.EOF
|
|
||||||
}
|
|
||||||
d.fams = make([]*dto.MetricFamily, 0, len(fams))
|
|
||||||
for _, f := range fams {
|
|
||||||
d.fams = append(d.fams, f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
*v = *d.fams[0]
|
|
||||||
d.fams = d.fams[1:]
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SampleDecoder wraps a Decoder to extract samples from the metric families
|
|
||||||
// decoded by the wrapped Decoder.
|
|
||||||
type SampleDecoder struct {
|
|
||||||
Dec Decoder
|
|
||||||
Opts *DecodeOptions
|
|
||||||
|
|
||||||
f dto.MetricFamily
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decode calls the Decode method of the wrapped Decoder and then extracts the
|
|
||||||
// samples from the decoded MetricFamily into the provided model.Vector.
|
|
||||||
func (sd *SampleDecoder) Decode(s *model.Vector) error {
|
|
||||||
err := sd.Dec.Decode(&sd.f)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
*s, err = extractSamples(&sd.f, sd.Opts)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExtractSamples builds a slice of samples from the provided metric
|
|
||||||
// families. If an error occurs during sample extraction, it continues to
|
|
||||||
// extract from the remaining metric families. The returned error is the last
|
|
||||||
// error that has occured.
|
|
||||||
func ExtractSamples(o *DecodeOptions, fams ...*dto.MetricFamily) (model.Vector, error) {
|
|
||||||
var (
|
|
||||||
all model.Vector
|
|
||||||
lastErr error
|
|
||||||
)
|
|
||||||
for _, f := range fams {
|
|
||||||
some, err := extractSamples(f, o)
|
|
||||||
if err != nil {
|
|
||||||
lastErr = err
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
all = append(all, some...)
|
|
||||||
}
|
|
||||||
return all, lastErr
|
|
||||||
}
|
|
||||||
|
|
||||||
func extractSamples(f *dto.MetricFamily, o *DecodeOptions) (model.Vector, error) {
|
|
||||||
switch f.GetType() {
|
|
||||||
case dto.MetricType_COUNTER:
|
|
||||||
return extractCounter(o, f), nil
|
|
||||||
case dto.MetricType_GAUGE:
|
|
||||||
return extractGauge(o, f), nil
|
|
||||||
case dto.MetricType_SUMMARY:
|
|
||||||
return extractSummary(o, f), nil
|
|
||||||
case dto.MetricType_UNTYPED:
|
|
||||||
return extractUntyped(o, f), nil
|
|
||||||
case dto.MetricType_HISTOGRAM:
|
|
||||||
return extractHistogram(o, f), nil
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("expfmt.extractSamples: unknown metric family type %v", f.GetType())
|
|
||||||
}
|
|
||||||
|
|
||||||
func extractCounter(o *DecodeOptions, f *dto.MetricFamily) model.Vector {
|
|
||||||
samples := make(model.Vector, 0, len(f.Metric))
|
|
||||||
|
|
||||||
for _, m := range f.Metric {
|
|
||||||
if m.Counter == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
lset := make(model.LabelSet, len(m.Label)+1)
|
|
||||||
for _, p := range m.Label {
|
|
||||||
lset[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue())
|
|
||||||
}
|
|
||||||
lset[model.MetricNameLabel] = model.LabelValue(f.GetName())
|
|
||||||
|
|
||||||
smpl := &model.Sample{
|
|
||||||
Metric: model.Metric(lset),
|
|
||||||
Value: model.SampleValue(m.Counter.GetValue()),
|
|
||||||
}
|
|
||||||
|
|
||||||
if m.TimestampMs != nil {
|
|
||||||
smpl.Timestamp = model.TimeFromUnixNano(*m.TimestampMs * 1000000)
|
|
||||||
} else {
|
|
||||||
smpl.Timestamp = o.Timestamp
|
|
||||||
}
|
|
||||||
|
|
||||||
samples = append(samples, smpl)
|
|
||||||
}
|
|
||||||
|
|
||||||
return samples
|
|
||||||
}
|
|
||||||
|
|
||||||
func extractGauge(o *DecodeOptions, f *dto.MetricFamily) model.Vector {
|
|
||||||
samples := make(model.Vector, 0, len(f.Metric))
|
|
||||||
|
|
||||||
for _, m := range f.Metric {
|
|
||||||
if m.Gauge == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
lset := make(model.LabelSet, len(m.Label)+1)
|
|
||||||
for _, p := range m.Label {
|
|
||||||
lset[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue())
|
|
||||||
}
|
|
||||||
lset[model.MetricNameLabel] = model.LabelValue(f.GetName())
|
|
||||||
|
|
||||||
smpl := &model.Sample{
|
|
||||||
Metric: model.Metric(lset),
|
|
||||||
Value: model.SampleValue(m.Gauge.GetValue()),
|
|
||||||
}
|
|
||||||
|
|
||||||
if m.TimestampMs != nil {
|
|
||||||
smpl.Timestamp = model.TimeFromUnixNano(*m.TimestampMs * 1000000)
|
|
||||||
} else {
|
|
||||||
smpl.Timestamp = o.Timestamp
|
|
||||||
}
|
|
||||||
|
|
||||||
samples = append(samples, smpl)
|
|
||||||
}
|
|
||||||
|
|
||||||
return samples
|
|
||||||
}
|
|
||||||
|
|
||||||
func extractUntyped(o *DecodeOptions, f *dto.MetricFamily) model.Vector {
|
|
||||||
samples := make(model.Vector, 0, len(f.Metric))
|
|
||||||
|
|
||||||
for _, m := range f.Metric {
|
|
||||||
if m.Untyped == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
lset := make(model.LabelSet, len(m.Label)+1)
|
|
||||||
for _, p := range m.Label {
|
|
||||||
lset[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue())
|
|
||||||
}
|
|
||||||
lset[model.MetricNameLabel] = model.LabelValue(f.GetName())
|
|
||||||
|
|
||||||
smpl := &model.Sample{
|
|
||||||
Metric: model.Metric(lset),
|
|
||||||
Value: model.SampleValue(m.Untyped.GetValue()),
|
|
||||||
}
|
|
||||||
|
|
||||||
if m.TimestampMs != nil {
|
|
||||||
smpl.Timestamp = model.TimeFromUnixNano(*m.TimestampMs * 1000000)
|
|
||||||
} else {
|
|
||||||
smpl.Timestamp = o.Timestamp
|
|
||||||
}
|
|
||||||
|
|
||||||
samples = append(samples, smpl)
|
|
||||||
}
|
|
||||||
|
|
||||||
return samples
|
|
||||||
}
|
|
||||||
|
|
||||||
func extractSummary(o *DecodeOptions, f *dto.MetricFamily) model.Vector {
|
|
||||||
samples := make(model.Vector, 0, len(f.Metric))
|
|
||||||
|
|
||||||
for _, m := range f.Metric {
|
|
||||||
if m.Summary == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
timestamp := o.Timestamp
|
|
||||||
if m.TimestampMs != nil {
|
|
||||||
timestamp = model.TimeFromUnixNano(*m.TimestampMs * 1000000)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, q := range m.Summary.Quantile {
|
|
||||||
lset := make(model.LabelSet, len(m.Label)+2)
|
|
||||||
for _, p := range m.Label {
|
|
||||||
lset[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue())
|
|
||||||
}
|
|
||||||
// BUG(matt): Update other names to "quantile".
|
|
||||||
lset[model.LabelName(model.QuantileLabel)] = model.LabelValue(fmt.Sprint(q.GetQuantile()))
|
|
||||||
lset[model.MetricNameLabel] = model.LabelValue(f.GetName())
|
|
||||||
|
|
||||||
samples = append(samples, &model.Sample{
|
|
||||||
Metric: model.Metric(lset),
|
|
||||||
Value: model.SampleValue(q.GetValue()),
|
|
||||||
Timestamp: timestamp,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
lset := make(model.LabelSet, len(m.Label)+1)
|
|
||||||
for _, p := range m.Label {
|
|
||||||
lset[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue())
|
|
||||||
}
|
|
||||||
lset[model.MetricNameLabel] = model.LabelValue(f.GetName() + "_sum")
|
|
||||||
|
|
||||||
samples = append(samples, &model.Sample{
|
|
||||||
Metric: model.Metric(lset),
|
|
||||||
Value: model.SampleValue(m.Summary.GetSampleSum()),
|
|
||||||
Timestamp: timestamp,
|
|
||||||
})
|
|
||||||
|
|
||||||
lset = make(model.LabelSet, len(m.Label)+1)
|
|
||||||
for _, p := range m.Label {
|
|
||||||
lset[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue())
|
|
||||||
}
|
|
||||||
lset[model.MetricNameLabel] = model.LabelValue(f.GetName() + "_count")
|
|
||||||
|
|
||||||
samples = append(samples, &model.Sample{
|
|
||||||
Metric: model.Metric(lset),
|
|
||||||
Value: model.SampleValue(m.Summary.GetSampleCount()),
|
|
||||||
Timestamp: timestamp,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return samples
|
|
||||||
}
|
|
||||||
|
|
||||||
func extractHistogram(o *DecodeOptions, f *dto.MetricFamily) model.Vector {
|
|
||||||
samples := make(model.Vector, 0, len(f.Metric))
|
|
||||||
|
|
||||||
for _, m := range f.Metric {
|
|
||||||
if m.Histogram == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
timestamp := o.Timestamp
|
|
||||||
if m.TimestampMs != nil {
|
|
||||||
timestamp = model.TimeFromUnixNano(*m.TimestampMs * 1000000)
|
|
||||||
}
|
|
||||||
|
|
||||||
infSeen := false
|
|
||||||
|
|
||||||
for _, q := range m.Histogram.Bucket {
|
|
||||||
lset := make(model.LabelSet, len(m.Label)+2)
|
|
||||||
for _, p := range m.Label {
|
|
||||||
lset[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue())
|
|
||||||
}
|
|
||||||
lset[model.LabelName(model.BucketLabel)] = model.LabelValue(fmt.Sprint(q.GetUpperBound()))
|
|
||||||
lset[model.MetricNameLabel] = model.LabelValue(f.GetName() + "_bucket")
|
|
||||||
|
|
||||||
if math.IsInf(q.GetUpperBound(), +1) {
|
|
||||||
infSeen = true
|
|
||||||
}
|
|
||||||
|
|
||||||
samples = append(samples, &model.Sample{
|
|
||||||
Metric: model.Metric(lset),
|
|
||||||
Value: model.SampleValue(q.GetCumulativeCount()),
|
|
||||||
Timestamp: timestamp,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
lset := make(model.LabelSet, len(m.Label)+1)
|
|
||||||
for _, p := range m.Label {
|
|
||||||
lset[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue())
|
|
||||||
}
|
|
||||||
lset[model.MetricNameLabel] = model.LabelValue(f.GetName() + "_sum")
|
|
||||||
|
|
||||||
samples = append(samples, &model.Sample{
|
|
||||||
Metric: model.Metric(lset),
|
|
||||||
Value: model.SampleValue(m.Histogram.GetSampleSum()),
|
|
||||||
Timestamp: timestamp,
|
|
||||||
})
|
|
||||||
|
|
||||||
lset = make(model.LabelSet, len(m.Label)+1)
|
|
||||||
for _, p := range m.Label {
|
|
||||||
lset[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue())
|
|
||||||
}
|
|
||||||
lset[model.MetricNameLabel] = model.LabelValue(f.GetName() + "_count")
|
|
||||||
|
|
||||||
count := &model.Sample{
|
|
||||||
Metric: model.Metric(lset),
|
|
||||||
Value: model.SampleValue(m.Histogram.GetSampleCount()),
|
|
||||||
Timestamp: timestamp,
|
|
||||||
}
|
|
||||||
samples = append(samples, count)
|
|
||||||
|
|
||||||
if !infSeen {
|
|
||||||
// Append an infinity bucket sample.
|
|
||||||
lset := make(model.LabelSet, len(m.Label)+2)
|
|
||||||
for _, p := range m.Label {
|
|
||||||
lset[model.LabelName(p.GetName())] = model.LabelValue(p.GetValue())
|
|
||||||
}
|
|
||||||
lset[model.LabelName(model.BucketLabel)] = model.LabelValue("+Inf")
|
|
||||||
lset[model.MetricNameLabel] = model.LabelValue(f.GetName() + "_bucket")
|
|
||||||
|
|
||||||
samples = append(samples, &model.Sample{
|
|
||||||
Metric: model.Metric(lset),
|
|
||||||
Value: count.Value,
|
|
||||||
Timestamp: timestamp,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return samples
|
|
||||||
}
|
|
||||||
|
|
@ -1,88 +0,0 @@
|
||||||
// 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 expfmt
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/golang/protobuf/proto"
|
|
||||||
"github.com/matttproud/golang_protobuf_extensions/pbutil"
|
|
||||||
"github.com/prometheus/common/internal/bitbucket.org/ww/goautoneg"
|
|
||||||
|
|
||||||
dto "github.com/prometheus/client_model/go"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Encoder types encode metric families into an underlying wire protocol.
|
|
||||||
type Encoder interface {
|
|
||||||
Encode(*dto.MetricFamily) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type encoder func(*dto.MetricFamily) error
|
|
||||||
|
|
||||||
func (e encoder) Encode(v *dto.MetricFamily) error {
|
|
||||||
return e(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Negotiate returns the Content-Type based on the given Accept header.
|
|
||||||
// If no appropriate accepted type is found, FmtText is returned.
|
|
||||||
func Negotiate(h http.Header) Format {
|
|
||||||
for _, ac := range goautoneg.ParseAccept(h.Get(hdrAccept)) {
|
|
||||||
// Check for protocol buffer
|
|
||||||
if ac.Type+"/"+ac.SubType == ProtoType && ac.Params["proto"] == ProtoProtocol {
|
|
||||||
switch ac.Params["encoding"] {
|
|
||||||
case "delimited":
|
|
||||||
return FmtProtoDelim
|
|
||||||
case "text":
|
|
||||||
return FmtProtoText
|
|
||||||
case "compact-text":
|
|
||||||
return FmtProtoCompact
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Check for text format.
|
|
||||||
ver := ac.Params["version"]
|
|
||||||
if ac.Type == "text" && ac.SubType == "plain" && (ver == TextVersion || ver == "") {
|
|
||||||
return FmtText
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return FmtText
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewEncoder returns a new encoder based on content type negotiation.
|
|
||||||
func NewEncoder(w io.Writer, format Format) Encoder {
|
|
||||||
switch format {
|
|
||||||
case FmtProtoDelim:
|
|
||||||
return encoder(func(v *dto.MetricFamily) error {
|
|
||||||
_, err := pbutil.WriteDelimited(w, v)
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
case FmtProtoCompact:
|
|
||||||
return encoder(func(v *dto.MetricFamily) error {
|
|
||||||
_, err := fmt.Fprintln(w, v.String())
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
case FmtProtoText:
|
|
||||||
return encoder(func(v *dto.MetricFamily) error {
|
|
||||||
_, err := fmt.Fprintln(w, proto.MarshalTextString(v))
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
case FmtText:
|
|
||||||
return encoder(func(v *dto.MetricFamily) error {
|
|
||||||
_, err := MetricFamilyToText(w, v)
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
}
|
|
||||||
panic("expfmt.NewEncoder: unknown format")
|
|
||||||
}
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
// 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 expfmt contains tools for reading and writing Prometheus metrics.
|
|
||||||
package expfmt
|
|
||||||
|
|
||||||
// Format specifies the HTTP content type of the different wire protocols.
|
|
||||||
type Format string
|
|
||||||
|
|
||||||
// Constants to assemble the Content-Type values for the different wire protocols.
|
|
||||||
const (
|
|
||||||
TextVersion = "0.0.4"
|
|
||||||
ProtoType = `application/vnd.google.protobuf`
|
|
||||||
ProtoProtocol = `io.prometheus.client.MetricFamily`
|
|
||||||
ProtoFmt = ProtoType + "; proto=" + ProtoProtocol + ";"
|
|
||||||
|
|
||||||
// The Content-Type values for the different wire protocols.
|
|
||||||
FmtUnknown Format = `<unknown>`
|
|
||||||
FmtText Format = `text/plain; version=` + TextVersion
|
|
||||||
FmtProtoDelim Format = ProtoFmt + ` encoding=delimited`
|
|
||||||
FmtProtoText Format = ProtoFmt + ` encoding=text`
|
|
||||||
FmtProtoCompact Format = ProtoFmt + ` encoding=compact-text`
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
hdrContentType = "Content-Type"
|
|
||||||
hdrAccept = "Accept"
|
|
||||||
)
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
// Copyright 2014 The Prometheus Authors
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
// Build only when actually fuzzing
|
|
||||||
// +build gofuzz
|
|
||||||
|
|
||||||
package expfmt
|
|
||||||
|
|
||||||
import "bytes"
|
|
||||||
|
|
||||||
// Fuzz text metric parser with with github.com/dvyukov/go-fuzz:
|
|
||||||
//
|
|
||||||
// go-fuzz-build github.com/prometheus/common/expfmt
|
|
||||||
// go-fuzz -bin expfmt-fuzz.zip -workdir fuzz
|
|
||||||
//
|
|
||||||
// Further input samples should go in the folder fuzz/corpus.
|
|
||||||
func Fuzz(in []byte) int {
|
|
||||||
parser := TextParser{}
|
|
||||||
_, err := parser.TextToMetricFamilies(bytes.NewReader(in))
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
@ -1,303 +0,0 @@
|
||||||
// Copyright 2014 The Prometheus Authors
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package expfmt
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"math"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
dto "github.com/prometheus/client_model/go"
|
|
||||||
"github.com/prometheus/common/model"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MetricFamilyToText converts a MetricFamily proto message into text format and
|
|
||||||
// writes the resulting lines to 'out'. It returns the number of bytes written
|
|
||||||
// and any error encountered. The output will have the same order as the input,
|
|
||||||
// no further sorting is performed. Furthermore, this function assumes the input
|
|
||||||
// is already sanitized and does not perform any sanity checks. If the input
|
|
||||||
// contains duplicate metrics or invalid metric or label names, the conversion
|
|
||||||
// will result in invalid text format output.
|
|
||||||
//
|
|
||||||
// This method fulfills the type 'prometheus.encoder'.
|
|
||||||
func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (int, error) {
|
|
||||||
var written int
|
|
||||||
|
|
||||||
// Fail-fast checks.
|
|
||||||
if len(in.Metric) == 0 {
|
|
||||||
return written, fmt.Errorf("MetricFamily has no metrics: %s", in)
|
|
||||||
}
|
|
||||||
name := in.GetName()
|
|
||||||
if name == "" {
|
|
||||||
return written, fmt.Errorf("MetricFamily has no name: %s", in)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Comments, first HELP, then TYPE.
|
|
||||||
if in.Help != nil {
|
|
||||||
n, err := fmt.Fprintf(
|
|
||||||
out, "# HELP %s %s\n",
|
|
||||||
name, escapeString(*in.Help, false),
|
|
||||||
)
|
|
||||||
written += n
|
|
||||||
if err != nil {
|
|
||||||
return written, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
metricType := in.GetType()
|
|
||||||
n, err := fmt.Fprintf(
|
|
||||||
out, "# TYPE %s %s\n",
|
|
||||||
name, strings.ToLower(metricType.String()),
|
|
||||||
)
|
|
||||||
written += n
|
|
||||||
if err != nil {
|
|
||||||
return written, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finally the samples, one line for each.
|
|
||||||
for _, metric := range in.Metric {
|
|
||||||
switch metricType {
|
|
||||||
case dto.MetricType_COUNTER:
|
|
||||||
if metric.Counter == nil {
|
|
||||||
return written, fmt.Errorf(
|
|
||||||
"expected counter in metric %s %s", name, metric,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
n, err = writeSample(
|
|
||||||
name, metric, "", "",
|
|
||||||
metric.Counter.GetValue(),
|
|
||||||
out,
|
|
||||||
)
|
|
||||||
case dto.MetricType_GAUGE:
|
|
||||||
if metric.Gauge == nil {
|
|
||||||
return written, fmt.Errorf(
|
|
||||||
"expected gauge in metric %s %s", name, metric,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
n, err = writeSample(
|
|
||||||
name, metric, "", "",
|
|
||||||
metric.Gauge.GetValue(),
|
|
||||||
out,
|
|
||||||
)
|
|
||||||
case dto.MetricType_UNTYPED:
|
|
||||||
if metric.Untyped == nil {
|
|
||||||
return written, fmt.Errorf(
|
|
||||||
"expected untyped in metric %s %s", name, metric,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
n, err = writeSample(
|
|
||||||
name, metric, "", "",
|
|
||||||
metric.Untyped.GetValue(),
|
|
||||||
out,
|
|
||||||
)
|
|
||||||
case dto.MetricType_SUMMARY:
|
|
||||||
if metric.Summary == nil {
|
|
||||||
return written, fmt.Errorf(
|
|
||||||
"expected summary in metric %s %s", name, metric,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
for _, q := range metric.Summary.Quantile {
|
|
||||||
n, err = writeSample(
|
|
||||||
name, metric,
|
|
||||||
model.QuantileLabel, fmt.Sprint(q.GetQuantile()),
|
|
||||||
q.GetValue(),
|
|
||||||
out,
|
|
||||||
)
|
|
||||||
written += n
|
|
||||||
if err != nil {
|
|
||||||
return written, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
n, err = writeSample(
|
|
||||||
name+"_sum", metric, "", "",
|
|
||||||
metric.Summary.GetSampleSum(),
|
|
||||||
out,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return written, err
|
|
||||||
}
|
|
||||||
written += n
|
|
||||||
n, err = writeSample(
|
|
||||||
name+"_count", metric, "", "",
|
|
||||||
float64(metric.Summary.GetSampleCount()),
|
|
||||||
out,
|
|
||||||
)
|
|
||||||
case dto.MetricType_HISTOGRAM:
|
|
||||||
if metric.Histogram == nil {
|
|
||||||
return written, fmt.Errorf(
|
|
||||||
"expected histogram in metric %s %s", name, metric,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
infSeen := false
|
|
||||||
for _, q := range metric.Histogram.Bucket {
|
|
||||||
n, err = writeSample(
|
|
||||||
name+"_bucket", metric,
|
|
||||||
model.BucketLabel, fmt.Sprint(q.GetUpperBound()),
|
|
||||||
float64(q.GetCumulativeCount()),
|
|
||||||
out,
|
|
||||||
)
|
|
||||||
written += n
|
|
||||||
if err != nil {
|
|
||||||
return written, err
|
|
||||||
}
|
|
||||||
if math.IsInf(q.GetUpperBound(), +1) {
|
|
||||||
infSeen = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !infSeen {
|
|
||||||
n, err = writeSample(
|
|
||||||
name+"_bucket", metric,
|
|
||||||
model.BucketLabel, "+Inf",
|
|
||||||
float64(metric.Histogram.GetSampleCount()),
|
|
||||||
out,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return written, err
|
|
||||||
}
|
|
||||||
written += n
|
|
||||||
}
|
|
||||||
n, err = writeSample(
|
|
||||||
name+"_sum", metric, "", "",
|
|
||||||
metric.Histogram.GetSampleSum(),
|
|
||||||
out,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return written, err
|
|
||||||
}
|
|
||||||
written += n
|
|
||||||
n, err = writeSample(
|
|
||||||
name+"_count", metric, "", "",
|
|
||||||
float64(metric.Histogram.GetSampleCount()),
|
|
||||||
out,
|
|
||||||
)
|
|
||||||
default:
|
|
||||||
return written, fmt.Errorf(
|
|
||||||
"unexpected type in metric %s %s", name, metric,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
written += n
|
|
||||||
if err != nil {
|
|
||||||
return written, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return written, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// writeSample writes a single sample in text format to out, given the metric
|
|
||||||
// name, the metric proto message itself, optionally an additional label name
|
|
||||||
// and value (use empty strings if not required), and the value. The function
|
|
||||||
// returns the number of bytes written and any error encountered.
|
|
||||||
func writeSample(
|
|
||||||
name string,
|
|
||||||
metric *dto.Metric,
|
|
||||||
additionalLabelName, additionalLabelValue string,
|
|
||||||
value float64,
|
|
||||||
out io.Writer,
|
|
||||||
) (int, error) {
|
|
||||||
var written int
|
|
||||||
n, err := fmt.Fprint(out, name)
|
|
||||||
written += n
|
|
||||||
if err != nil {
|
|
||||||
return written, err
|
|
||||||
}
|
|
||||||
n, err = labelPairsToText(
|
|
||||||
metric.Label,
|
|
||||||
additionalLabelName, additionalLabelValue,
|
|
||||||
out,
|
|
||||||
)
|
|
||||||
written += n
|
|
||||||
if err != nil {
|
|
||||||
return written, err
|
|
||||||
}
|
|
||||||
n, err = fmt.Fprintf(out, " %v", value)
|
|
||||||
written += n
|
|
||||||
if err != nil {
|
|
||||||
return written, err
|
|
||||||
}
|
|
||||||
if metric.TimestampMs != nil {
|
|
||||||
n, err = fmt.Fprintf(out, " %v", *metric.TimestampMs)
|
|
||||||
written += n
|
|
||||||
if err != nil {
|
|
||||||
return written, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
n, err = out.Write([]byte{'\n'})
|
|
||||||
written += n
|
|
||||||
if err != nil {
|
|
||||||
return written, err
|
|
||||||
}
|
|
||||||
return written, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// labelPairsToText converts a slice of LabelPair proto messages plus the
|
|
||||||
// explicitly given additional label pair into text formatted as required by the
|
|
||||||
// text format and writes it to 'out'. An empty slice in combination with an
|
|
||||||
// empty string 'additionalLabelName' results in nothing being
|
|
||||||
// written. Otherwise, the label pairs are written, escaped as required by the
|
|
||||||
// text format, and enclosed in '{...}'. The function returns the number of
|
|
||||||
// bytes written and any error encountered.
|
|
||||||
func labelPairsToText(
|
|
||||||
in []*dto.LabelPair,
|
|
||||||
additionalLabelName, additionalLabelValue string,
|
|
||||||
out io.Writer,
|
|
||||||
) (int, error) {
|
|
||||||
if len(in) == 0 && additionalLabelName == "" {
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
var written int
|
|
||||||
separator := '{'
|
|
||||||
for _, lp := range in {
|
|
||||||
n, err := fmt.Fprintf(
|
|
||||||
out, `%c%s="%s"`,
|
|
||||||
separator, lp.GetName(), escapeString(lp.GetValue(), true),
|
|
||||||
)
|
|
||||||
written += n
|
|
||||||
if err != nil {
|
|
||||||
return written, err
|
|
||||||
}
|
|
||||||
separator = ','
|
|
||||||
}
|
|
||||||
if additionalLabelName != "" {
|
|
||||||
n, err := fmt.Fprintf(
|
|
||||||
out, `%c%s="%s"`,
|
|
||||||
separator, additionalLabelName,
|
|
||||||
escapeString(additionalLabelValue, true),
|
|
||||||
)
|
|
||||||
written += n
|
|
||||||
if err != nil {
|
|
||||||
return written, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
n, err := out.Write([]byte{'}'})
|
|
||||||
written += n
|
|
||||||
if err != nil {
|
|
||||||
return written, err
|
|
||||||
}
|
|
||||||
return written, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
escape = strings.NewReplacer("\\", `\\`, "\n", `\n`)
|
|
||||||
escapeWithDoubleQuote = strings.NewReplacer("\\", `\\`, "\n", `\n`, "\"", `\"`)
|
|
||||||
)
|
|
||||||
|
|
||||||
// escapeString replaces '\' by '\\', new line character by '\n', and - if
|
|
||||||
// includeDoubleQuote is true - '"' by '\"'.
|
|
||||||
func escapeString(v string, includeDoubleQuote bool) string {
|
|
||||||
if includeDoubleQuote {
|
|
||||||
return escapeWithDoubleQuote.Replace(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
return escape.Replace(v)
|
|
||||||
}
|
|
||||||
|
|
@ -1,753 +0,0 @@
|
||||||
// Copyright 2014 The Prometheus Authors
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package expfmt
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"math"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
dto "github.com/prometheus/client_model/go"
|
|
||||||
|
|
||||||
"github.com/golang/protobuf/proto"
|
|
||||||
"github.com/prometheus/common/model"
|
|
||||||
)
|
|
||||||
|
|
||||||
// A stateFn is a function that represents a state in a state machine. By
|
|
||||||
// executing it, the state is progressed to the next state. The stateFn returns
|
|
||||||
// another stateFn, which represents the new state. The end state is represented
|
|
||||||
// by nil.
|
|
||||||
type stateFn func() stateFn
|
|
||||||
|
|
||||||
// ParseError signals errors while parsing the simple and flat text-based
|
|
||||||
// exchange format.
|
|
||||||
type ParseError struct {
|
|
||||||
Line int
|
|
||||||
Msg string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error implements the error interface.
|
|
||||||
func (e ParseError) Error() string {
|
|
||||||
return fmt.Sprintf("text format parsing error in line %d: %s", e.Line, e.Msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TextParser is used to parse the simple and flat text-based exchange format. Its
|
|
||||||
// zero value is ready to use.
|
|
||||||
type TextParser struct {
|
|
||||||
metricFamiliesByName map[string]*dto.MetricFamily
|
|
||||||
buf *bufio.Reader // Where the parsed input is read through.
|
|
||||||
err error // Most recent error.
|
|
||||||
lineCount int // Tracks the line count for error messages.
|
|
||||||
currentByte byte // The most recent byte read.
|
|
||||||
currentToken bytes.Buffer // Re-used each time a token has to be gathered from multiple bytes.
|
|
||||||
currentMF *dto.MetricFamily
|
|
||||||
currentMetric *dto.Metric
|
|
||||||
currentLabelPair *dto.LabelPair
|
|
||||||
|
|
||||||
// The remaining member variables are only used for summaries/histograms.
|
|
||||||
currentLabels map[string]string // All labels including '__name__' but excluding 'quantile'/'le'
|
|
||||||
// Summary specific.
|
|
||||||
summaries map[uint64]*dto.Metric // Key is created with LabelsToSignature.
|
|
||||||
currentQuantile float64
|
|
||||||
// Histogram specific.
|
|
||||||
histograms map[uint64]*dto.Metric // Key is created with LabelsToSignature.
|
|
||||||
currentBucket float64
|
|
||||||
// These tell us if the currently processed line ends on '_count' or
|
|
||||||
// '_sum' respectively and belong to a summary/histogram, representing the sample
|
|
||||||
// count and sum of that summary/histogram.
|
|
||||||
currentIsSummaryCount, currentIsSummarySum bool
|
|
||||||
currentIsHistogramCount, currentIsHistogramSum bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// TextToMetricFamilies reads 'in' as the simple and flat text-based exchange
|
|
||||||
// format and creates MetricFamily proto messages. It returns the MetricFamily
|
|
||||||
// proto messages in a map where the metric names are the keys, along with any
|
|
||||||
// error encountered.
|
|
||||||
//
|
|
||||||
// If the input contains duplicate metrics (i.e. lines with the same metric name
|
|
||||||
// and exactly the same label set), the resulting MetricFamily will contain
|
|
||||||
// duplicate Metric proto messages. Similar is true for duplicate label
|
|
||||||
// names. Checks for duplicates have to be performed separately, if required.
|
|
||||||
// Also note that neither the metrics within each MetricFamily are sorted nor
|
|
||||||
// the label pairs within each Metric. Sorting is not required for the most
|
|
||||||
// frequent use of this method, which is sample ingestion in the Prometheus
|
|
||||||
// server. However, for presentation purposes, you might want to sort the
|
|
||||||
// metrics, and in some cases, you must sort the labels, e.g. for consumption by
|
|
||||||
// the metric family injection hook of the Prometheus registry.
|
|
||||||
//
|
|
||||||
// Summaries and histograms are rather special beasts. You would probably not
|
|
||||||
// use them in the simple text format anyway. This method can deal with
|
|
||||||
// summaries and histograms if they are presented in exactly the way the
|
|
||||||
// text.Create function creates them.
|
|
||||||
//
|
|
||||||
// This method must not be called concurrently. If you want to parse different
|
|
||||||
// input concurrently, instantiate a separate Parser for each goroutine.
|
|
||||||
func (p *TextParser) TextToMetricFamilies(in io.Reader) (map[string]*dto.MetricFamily, error) {
|
|
||||||
p.reset(in)
|
|
||||||
for nextState := p.startOfLine; nextState != nil; nextState = nextState() {
|
|
||||||
// Magic happens here...
|
|
||||||
}
|
|
||||||
// Get rid of empty metric families.
|
|
||||||
for k, mf := range p.metricFamiliesByName {
|
|
||||||
if len(mf.GetMetric()) == 0 {
|
|
||||||
delete(p.metricFamiliesByName, k)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// If p.err is io.EOF now, we have run into a premature end of the input
|
|
||||||
// stream. Turn this error into something nicer and more
|
|
||||||
// meaningful. (io.EOF is often used as a signal for the legitimate end
|
|
||||||
// of an input stream.)
|
|
||||||
if p.err == io.EOF {
|
|
||||||
p.parseError("unexpected end of input stream")
|
|
||||||
}
|
|
||||||
return p.metricFamiliesByName, p.err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *TextParser) reset(in io.Reader) {
|
|
||||||
p.metricFamiliesByName = map[string]*dto.MetricFamily{}
|
|
||||||
if p.buf == nil {
|
|
||||||
p.buf = bufio.NewReader(in)
|
|
||||||
} else {
|
|
||||||
p.buf.Reset(in)
|
|
||||||
}
|
|
||||||
p.err = nil
|
|
||||||
p.lineCount = 0
|
|
||||||
if p.summaries == nil || len(p.summaries) > 0 {
|
|
||||||
p.summaries = map[uint64]*dto.Metric{}
|
|
||||||
}
|
|
||||||
if p.histograms == nil || len(p.histograms) > 0 {
|
|
||||||
p.histograms = map[uint64]*dto.Metric{}
|
|
||||||
}
|
|
||||||
p.currentQuantile = math.NaN()
|
|
||||||
p.currentBucket = math.NaN()
|
|
||||||
}
|
|
||||||
|
|
||||||
// startOfLine represents the state where the next byte read from p.buf is the
|
|
||||||
// start of a line (or whitespace leading up to it).
|
|
||||||
func (p *TextParser) startOfLine() stateFn {
|
|
||||||
p.lineCount++
|
|
||||||
if p.skipBlankTab(); p.err != nil {
|
|
||||||
// End of input reached. This is the only case where
|
|
||||||
// that is not an error but a signal that we are done.
|
|
||||||
p.err = nil
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
switch p.currentByte {
|
|
||||||
case '#':
|
|
||||||
return p.startComment
|
|
||||||
case '\n':
|
|
||||||
return p.startOfLine // Empty line, start the next one.
|
|
||||||
}
|
|
||||||
return p.readingMetricName
|
|
||||||
}
|
|
||||||
|
|
||||||
// startComment represents the state where the next byte read from p.buf is the
|
|
||||||
// start of a comment (or whitespace leading up to it).
|
|
||||||
func (p *TextParser) startComment() stateFn {
|
|
||||||
if p.skipBlankTab(); p.err != nil {
|
|
||||||
return nil // Unexpected end of input.
|
|
||||||
}
|
|
||||||
if p.currentByte == '\n' {
|
|
||||||
return p.startOfLine
|
|
||||||
}
|
|
||||||
if p.readTokenUntilWhitespace(); p.err != nil {
|
|
||||||
return nil // Unexpected end of input.
|
|
||||||
}
|
|
||||||
// If we have hit the end of line already, there is nothing left
|
|
||||||
// to do. This is not considered a syntax error.
|
|
||||||
if p.currentByte == '\n' {
|
|
||||||
return p.startOfLine
|
|
||||||
}
|
|
||||||
keyword := p.currentToken.String()
|
|
||||||
if keyword != "HELP" && keyword != "TYPE" {
|
|
||||||
// Generic comment, ignore by fast forwarding to end of line.
|
|
||||||
for p.currentByte != '\n' {
|
|
||||||
if p.currentByte, p.err = p.buf.ReadByte(); p.err != nil {
|
|
||||||
return nil // Unexpected end of input.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return p.startOfLine
|
|
||||||
}
|
|
||||||
// There is something. Next has to be a metric name.
|
|
||||||
if p.skipBlankTab(); p.err != nil {
|
|
||||||
return nil // Unexpected end of input.
|
|
||||||
}
|
|
||||||
if p.readTokenAsMetricName(); p.err != nil {
|
|
||||||
return nil // Unexpected end of input.
|
|
||||||
}
|
|
||||||
if p.currentByte == '\n' {
|
|
||||||
// At the end of the line already.
|
|
||||||
// Again, this is not considered a syntax error.
|
|
||||||
return p.startOfLine
|
|
||||||
}
|
|
||||||
if !isBlankOrTab(p.currentByte) {
|
|
||||||
p.parseError("invalid metric name in comment")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
p.setOrCreateCurrentMF()
|
|
||||||
if p.skipBlankTab(); p.err != nil {
|
|
||||||
return nil // Unexpected end of input.
|
|
||||||
}
|
|
||||||
if p.currentByte == '\n' {
|
|
||||||
// At the end of the line already.
|
|
||||||
// Again, this is not considered a syntax error.
|
|
||||||
return p.startOfLine
|
|
||||||
}
|
|
||||||
switch keyword {
|
|
||||||
case "HELP":
|
|
||||||
return p.readingHelp
|
|
||||||
case "TYPE":
|
|
||||||
return p.readingType
|
|
||||||
}
|
|
||||||
panic(fmt.Sprintf("code error: unexpected keyword %q", keyword))
|
|
||||||
}
|
|
||||||
|
|
||||||
// readingMetricName represents the state where the last byte read (now in
|
|
||||||
// p.currentByte) is the first byte of a metric name.
|
|
||||||
func (p *TextParser) readingMetricName() stateFn {
|
|
||||||
if p.readTokenAsMetricName(); p.err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if p.currentToken.Len() == 0 {
|
|
||||||
p.parseError("invalid metric name")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
p.setOrCreateCurrentMF()
|
|
||||||
// Now is the time to fix the type if it hasn't happened yet.
|
|
||||||
if p.currentMF.Type == nil {
|
|
||||||
p.currentMF.Type = dto.MetricType_UNTYPED.Enum()
|
|
||||||
}
|
|
||||||
p.currentMetric = &dto.Metric{}
|
|
||||||
// Do not append the newly created currentMetric to
|
|
||||||
// currentMF.Metric right now. First wait if this is a summary,
|
|
||||||
// and the metric exists already, which we can only know after
|
|
||||||
// having read all the labels.
|
|
||||||
if p.skipBlankTabIfCurrentBlankTab(); p.err != nil {
|
|
||||||
return nil // Unexpected end of input.
|
|
||||||
}
|
|
||||||
return p.readingLabels
|
|
||||||
}
|
|
||||||
|
|
||||||
// readingLabels represents the state where the last byte read (now in
|
|
||||||
// p.currentByte) is either the first byte of the label set (i.e. a '{'), or the
|
|
||||||
// first byte of the value (otherwise).
|
|
||||||
func (p *TextParser) readingLabels() stateFn {
|
|
||||||
// Summaries/histograms are special. We have to reset the
|
|
||||||
// currentLabels map, currentQuantile and currentBucket before starting to
|
|
||||||
// read labels.
|
|
||||||
if p.currentMF.GetType() == dto.MetricType_SUMMARY || p.currentMF.GetType() == dto.MetricType_HISTOGRAM {
|
|
||||||
p.currentLabels = map[string]string{}
|
|
||||||
p.currentLabels[string(model.MetricNameLabel)] = p.currentMF.GetName()
|
|
||||||
p.currentQuantile = math.NaN()
|
|
||||||
p.currentBucket = math.NaN()
|
|
||||||
}
|
|
||||||
if p.currentByte != '{' {
|
|
||||||
return p.readingValue
|
|
||||||
}
|
|
||||||
return p.startLabelName
|
|
||||||
}
|
|
||||||
|
|
||||||
// startLabelName represents the state where the next byte read from p.buf is
|
|
||||||
// the start of a label name (or whitespace leading up to it).
|
|
||||||
func (p *TextParser) startLabelName() stateFn {
|
|
||||||
if p.skipBlankTab(); p.err != nil {
|
|
||||||
return nil // Unexpected end of input.
|
|
||||||
}
|
|
||||||
if p.currentByte == '}' {
|
|
||||||
if p.skipBlankTab(); p.err != nil {
|
|
||||||
return nil // Unexpected end of input.
|
|
||||||
}
|
|
||||||
return p.readingValue
|
|
||||||
}
|
|
||||||
if p.readTokenAsLabelName(); p.err != nil {
|
|
||||||
return nil // Unexpected end of input.
|
|
||||||
}
|
|
||||||
if p.currentToken.Len() == 0 {
|
|
||||||
p.parseError(fmt.Sprintf("invalid label name for metric %q", p.currentMF.GetName()))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
p.currentLabelPair = &dto.LabelPair{Name: proto.String(p.currentToken.String())}
|
|
||||||
if p.currentLabelPair.GetName() == string(model.MetricNameLabel) {
|
|
||||||
p.parseError(fmt.Sprintf("label name %q is reserved", model.MetricNameLabel))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// Special summary/histogram treatment. Don't add 'quantile' and 'le'
|
|
||||||
// labels to 'real' labels.
|
|
||||||
if !(p.currentMF.GetType() == dto.MetricType_SUMMARY && p.currentLabelPair.GetName() == model.QuantileLabel) &&
|
|
||||||
!(p.currentMF.GetType() == dto.MetricType_HISTOGRAM && p.currentLabelPair.GetName() == model.BucketLabel) {
|
|
||||||
p.currentMetric.Label = append(p.currentMetric.Label, p.currentLabelPair)
|
|
||||||
}
|
|
||||||
if p.skipBlankTabIfCurrentBlankTab(); p.err != nil {
|
|
||||||
return nil // Unexpected end of input.
|
|
||||||
}
|
|
||||||
if p.currentByte != '=' {
|
|
||||||
p.parseError(fmt.Sprintf("expected '=' after label name, found %q", p.currentByte))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return p.startLabelValue
|
|
||||||
}
|
|
||||||
|
|
||||||
// startLabelValue represents the state where the next byte read from p.buf is
|
|
||||||
// the start of a (quoted) label value (or whitespace leading up to it).
|
|
||||||
func (p *TextParser) startLabelValue() stateFn {
|
|
||||||
if p.skipBlankTab(); p.err != nil {
|
|
||||||
return nil // Unexpected end of input.
|
|
||||||
}
|
|
||||||
if p.currentByte != '"' {
|
|
||||||
p.parseError(fmt.Sprintf("expected '\"' at start of label value, found %q", p.currentByte))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if p.readTokenAsLabelValue(); p.err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
p.currentLabelPair.Value = proto.String(p.currentToken.String())
|
|
||||||
// Special treatment of summaries:
|
|
||||||
// - Quantile labels are special, will result in dto.Quantile later.
|
|
||||||
// - Other labels have to be added to currentLabels for signature calculation.
|
|
||||||
if p.currentMF.GetType() == dto.MetricType_SUMMARY {
|
|
||||||
if p.currentLabelPair.GetName() == model.QuantileLabel {
|
|
||||||
if p.currentQuantile, p.err = strconv.ParseFloat(p.currentLabelPair.GetValue(), 64); p.err != nil {
|
|
||||||
// Create a more helpful error message.
|
|
||||||
p.parseError(fmt.Sprintf("expected float as value for 'quantile' label, got %q", p.currentLabelPair.GetValue()))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
p.currentLabels[p.currentLabelPair.GetName()] = p.currentLabelPair.GetValue()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Similar special treatment of histograms.
|
|
||||||
if p.currentMF.GetType() == dto.MetricType_HISTOGRAM {
|
|
||||||
if p.currentLabelPair.GetName() == model.BucketLabel {
|
|
||||||
if p.currentBucket, p.err = strconv.ParseFloat(p.currentLabelPair.GetValue(), 64); p.err != nil {
|
|
||||||
// Create a more helpful error message.
|
|
||||||
p.parseError(fmt.Sprintf("expected float as value for 'le' label, got %q", p.currentLabelPair.GetValue()))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
p.currentLabels[p.currentLabelPair.GetName()] = p.currentLabelPair.GetValue()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if p.skipBlankTab(); p.err != nil {
|
|
||||||
return nil // Unexpected end of input.
|
|
||||||
}
|
|
||||||
switch p.currentByte {
|
|
||||||
case ',':
|
|
||||||
return p.startLabelName
|
|
||||||
|
|
||||||
case '}':
|
|
||||||
if p.skipBlankTab(); p.err != nil {
|
|
||||||
return nil // Unexpected end of input.
|
|
||||||
}
|
|
||||||
return p.readingValue
|
|
||||||
default:
|
|
||||||
p.parseError(fmt.Sprintf("unexpected end of label value %q", p.currentLabelPair.Value))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// readingValue represents the state where the last byte read (now in
|
|
||||||
// p.currentByte) is the first byte of the sample value (i.e. a float).
|
|
||||||
func (p *TextParser) readingValue() stateFn {
|
|
||||||
// When we are here, we have read all the labels, so for the
|
|
||||||
// special case of a summary/histogram, we can finally find out
|
|
||||||
// if the metric already exists.
|
|
||||||
if p.currentMF.GetType() == dto.MetricType_SUMMARY {
|
|
||||||
signature := model.LabelsToSignature(p.currentLabels)
|
|
||||||
if summary := p.summaries[signature]; summary != nil {
|
|
||||||
p.currentMetric = summary
|
|
||||||
} else {
|
|
||||||
p.summaries[signature] = p.currentMetric
|
|
||||||
p.currentMF.Metric = append(p.currentMF.Metric, p.currentMetric)
|
|
||||||
}
|
|
||||||
} else if p.currentMF.GetType() == dto.MetricType_HISTOGRAM {
|
|
||||||
signature := model.LabelsToSignature(p.currentLabels)
|
|
||||||
if histogram := p.histograms[signature]; histogram != nil {
|
|
||||||
p.currentMetric = histogram
|
|
||||||
} else {
|
|
||||||
p.histograms[signature] = p.currentMetric
|
|
||||||
p.currentMF.Metric = append(p.currentMF.Metric, p.currentMetric)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
p.currentMF.Metric = append(p.currentMF.Metric, p.currentMetric)
|
|
||||||
}
|
|
||||||
if p.readTokenUntilWhitespace(); p.err != nil {
|
|
||||||
return nil // Unexpected end of input.
|
|
||||||
}
|
|
||||||
value, err := strconv.ParseFloat(p.currentToken.String(), 64)
|
|
||||||
if err != nil {
|
|
||||||
// Create a more helpful error message.
|
|
||||||
p.parseError(fmt.Sprintf("expected float as value, got %q", p.currentToken.String()))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
switch p.currentMF.GetType() {
|
|
||||||
case dto.MetricType_COUNTER:
|
|
||||||
p.currentMetric.Counter = &dto.Counter{Value: proto.Float64(value)}
|
|
||||||
case dto.MetricType_GAUGE:
|
|
||||||
p.currentMetric.Gauge = &dto.Gauge{Value: proto.Float64(value)}
|
|
||||||
case dto.MetricType_UNTYPED:
|
|
||||||
p.currentMetric.Untyped = &dto.Untyped{Value: proto.Float64(value)}
|
|
||||||
case dto.MetricType_SUMMARY:
|
|
||||||
// *sigh*
|
|
||||||
if p.currentMetric.Summary == nil {
|
|
||||||
p.currentMetric.Summary = &dto.Summary{}
|
|
||||||
}
|
|
||||||
switch {
|
|
||||||
case p.currentIsSummaryCount:
|
|
||||||
p.currentMetric.Summary.SampleCount = proto.Uint64(uint64(value))
|
|
||||||
case p.currentIsSummarySum:
|
|
||||||
p.currentMetric.Summary.SampleSum = proto.Float64(value)
|
|
||||||
case !math.IsNaN(p.currentQuantile):
|
|
||||||
p.currentMetric.Summary.Quantile = append(
|
|
||||||
p.currentMetric.Summary.Quantile,
|
|
||||||
&dto.Quantile{
|
|
||||||
Quantile: proto.Float64(p.currentQuantile),
|
|
||||||
Value: proto.Float64(value),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
case dto.MetricType_HISTOGRAM:
|
|
||||||
// *sigh*
|
|
||||||
if p.currentMetric.Histogram == nil {
|
|
||||||
p.currentMetric.Histogram = &dto.Histogram{}
|
|
||||||
}
|
|
||||||
switch {
|
|
||||||
case p.currentIsHistogramCount:
|
|
||||||
p.currentMetric.Histogram.SampleCount = proto.Uint64(uint64(value))
|
|
||||||
case p.currentIsHistogramSum:
|
|
||||||
p.currentMetric.Histogram.SampleSum = proto.Float64(value)
|
|
||||||
case !math.IsNaN(p.currentBucket):
|
|
||||||
p.currentMetric.Histogram.Bucket = append(
|
|
||||||
p.currentMetric.Histogram.Bucket,
|
|
||||||
&dto.Bucket{
|
|
||||||
UpperBound: proto.Float64(p.currentBucket),
|
|
||||||
CumulativeCount: proto.Uint64(uint64(value)),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
p.err = fmt.Errorf("unexpected type for metric name %q", p.currentMF.GetName())
|
|
||||||
}
|
|
||||||
if p.currentByte == '\n' {
|
|
||||||
return p.startOfLine
|
|
||||||
}
|
|
||||||
return p.startTimestamp
|
|
||||||
}
|
|
||||||
|
|
||||||
// startTimestamp represents the state where the next byte read from p.buf is
|
|
||||||
// the start of the timestamp (or whitespace leading up to it).
|
|
||||||
func (p *TextParser) startTimestamp() stateFn {
|
|
||||||
if p.skipBlankTab(); p.err != nil {
|
|
||||||
return nil // Unexpected end of input.
|
|
||||||
}
|
|
||||||
if p.readTokenUntilWhitespace(); p.err != nil {
|
|
||||||
return nil // Unexpected end of input.
|
|
||||||
}
|
|
||||||
timestamp, err := strconv.ParseInt(p.currentToken.String(), 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
// Create a more helpful error message.
|
|
||||||
p.parseError(fmt.Sprintf("expected integer as timestamp, got %q", p.currentToken.String()))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
p.currentMetric.TimestampMs = proto.Int64(timestamp)
|
|
||||||
if p.readTokenUntilNewline(false); p.err != nil {
|
|
||||||
return nil // Unexpected end of input.
|
|
||||||
}
|
|
||||||
if p.currentToken.Len() > 0 {
|
|
||||||
p.parseError(fmt.Sprintf("spurious string after timestamp: %q", p.currentToken.String()))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return p.startOfLine
|
|
||||||
}
|
|
||||||
|
|
||||||
// readingHelp represents the state where the last byte read (now in
|
|
||||||
// p.currentByte) is the first byte of the docstring after 'HELP'.
|
|
||||||
func (p *TextParser) readingHelp() stateFn {
|
|
||||||
if p.currentMF.Help != nil {
|
|
||||||
p.parseError(fmt.Sprintf("second HELP line for metric name %q", p.currentMF.GetName()))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// Rest of line is the docstring.
|
|
||||||
if p.readTokenUntilNewline(true); p.err != nil {
|
|
||||||
return nil // Unexpected end of input.
|
|
||||||
}
|
|
||||||
p.currentMF.Help = proto.String(p.currentToken.String())
|
|
||||||
return p.startOfLine
|
|
||||||
}
|
|
||||||
|
|
||||||
// readingType represents the state where the last byte read (now in
|
|
||||||
// p.currentByte) is the first byte of the type hint after 'HELP'.
|
|
||||||
func (p *TextParser) readingType() stateFn {
|
|
||||||
if p.currentMF.Type != nil {
|
|
||||||
p.parseError(fmt.Sprintf("second TYPE line for metric name %q, or TYPE reported after samples", p.currentMF.GetName()))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// Rest of line is the type.
|
|
||||||
if p.readTokenUntilNewline(false); p.err != nil {
|
|
||||||
return nil // Unexpected end of input.
|
|
||||||
}
|
|
||||||
metricType, ok := dto.MetricType_value[strings.ToUpper(p.currentToken.String())]
|
|
||||||
if !ok {
|
|
||||||
p.parseError(fmt.Sprintf("unknown metric type %q", p.currentToken.String()))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
p.currentMF.Type = dto.MetricType(metricType).Enum()
|
|
||||||
return p.startOfLine
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseError sets p.err to a ParseError at the current line with the given
|
|
||||||
// message.
|
|
||||||
func (p *TextParser) parseError(msg string) {
|
|
||||||
p.err = ParseError{
|
|
||||||
Line: p.lineCount,
|
|
||||||
Msg: msg,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// skipBlankTab reads (and discards) bytes from p.buf until it encounters a byte
|
|
||||||
// that is neither ' ' nor '\t'. That byte is left in p.currentByte.
|
|
||||||
func (p *TextParser) skipBlankTab() {
|
|
||||||
for {
|
|
||||||
if p.currentByte, p.err = p.buf.ReadByte(); p.err != nil || !isBlankOrTab(p.currentByte) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// skipBlankTabIfCurrentBlankTab works exactly as skipBlankTab but doesn't do
|
|
||||||
// anything if p.currentByte is neither ' ' nor '\t'.
|
|
||||||
func (p *TextParser) skipBlankTabIfCurrentBlankTab() {
|
|
||||||
if isBlankOrTab(p.currentByte) {
|
|
||||||
p.skipBlankTab()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// readTokenUntilWhitespace copies bytes from p.buf into p.currentToken. The
|
|
||||||
// first byte considered is the byte already read (now in p.currentByte). The
|
|
||||||
// first whitespace byte encountered is still copied into p.currentByte, but not
|
|
||||||
// into p.currentToken.
|
|
||||||
func (p *TextParser) readTokenUntilWhitespace() {
|
|
||||||
p.currentToken.Reset()
|
|
||||||
for p.err == nil && !isBlankOrTab(p.currentByte) && p.currentByte != '\n' {
|
|
||||||
p.currentToken.WriteByte(p.currentByte)
|
|
||||||
p.currentByte, p.err = p.buf.ReadByte()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// readTokenUntilNewline copies bytes from p.buf into p.currentToken. The first
|
|
||||||
// byte considered is the byte already read (now in p.currentByte). The first
|
|
||||||
// newline byte encountered is still copied into p.currentByte, but not into
|
|
||||||
// p.currentToken. If recognizeEscapeSequence is true, two escape sequences are
|
|
||||||
// recognized: '\\' tranlates into '\', and '\n' into a line-feed character. All
|
|
||||||
// other escape sequences are invalid and cause an error.
|
|
||||||
func (p *TextParser) readTokenUntilNewline(recognizeEscapeSequence bool) {
|
|
||||||
p.currentToken.Reset()
|
|
||||||
escaped := false
|
|
||||||
for p.err == nil {
|
|
||||||
if recognizeEscapeSequence && escaped {
|
|
||||||
switch p.currentByte {
|
|
||||||
case '\\':
|
|
||||||
p.currentToken.WriteByte(p.currentByte)
|
|
||||||
case 'n':
|
|
||||||
p.currentToken.WriteByte('\n')
|
|
||||||
default:
|
|
||||||
p.parseError(fmt.Sprintf("invalid escape sequence '\\%c'", p.currentByte))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
escaped = false
|
|
||||||
} else {
|
|
||||||
switch p.currentByte {
|
|
||||||
case '\n':
|
|
||||||
return
|
|
||||||
case '\\':
|
|
||||||
escaped = true
|
|
||||||
default:
|
|
||||||
p.currentToken.WriteByte(p.currentByte)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
p.currentByte, p.err = p.buf.ReadByte()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// readTokenAsMetricName copies a metric name from p.buf into p.currentToken.
|
|
||||||
// The first byte considered is the byte already read (now in p.currentByte).
|
|
||||||
// The first byte not part of a metric name is still copied into p.currentByte,
|
|
||||||
// but not into p.currentToken.
|
|
||||||
func (p *TextParser) readTokenAsMetricName() {
|
|
||||||
p.currentToken.Reset()
|
|
||||||
if !isValidMetricNameStart(p.currentByte) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for {
|
|
||||||
p.currentToken.WriteByte(p.currentByte)
|
|
||||||
p.currentByte, p.err = p.buf.ReadByte()
|
|
||||||
if p.err != nil || !isValidMetricNameContinuation(p.currentByte) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// readTokenAsLabelName copies a label name from p.buf into p.currentToken.
|
|
||||||
// The first byte considered is the byte already read (now in p.currentByte).
|
|
||||||
// The first byte not part of a label name is still copied into p.currentByte,
|
|
||||||
// but not into p.currentToken.
|
|
||||||
func (p *TextParser) readTokenAsLabelName() {
|
|
||||||
p.currentToken.Reset()
|
|
||||||
if !isValidLabelNameStart(p.currentByte) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for {
|
|
||||||
p.currentToken.WriteByte(p.currentByte)
|
|
||||||
p.currentByte, p.err = p.buf.ReadByte()
|
|
||||||
if p.err != nil || !isValidLabelNameContinuation(p.currentByte) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// readTokenAsLabelValue copies a label value from p.buf into p.currentToken.
|
|
||||||
// In contrast to the other 'readTokenAs...' functions, which start with the
|
|
||||||
// last read byte in p.currentByte, this method ignores p.currentByte and starts
|
|
||||||
// with reading a new byte from p.buf. The first byte not part of a label value
|
|
||||||
// is still copied into p.currentByte, but not into p.currentToken.
|
|
||||||
func (p *TextParser) readTokenAsLabelValue() {
|
|
||||||
p.currentToken.Reset()
|
|
||||||
escaped := false
|
|
||||||
for {
|
|
||||||
if p.currentByte, p.err = p.buf.ReadByte(); p.err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if escaped {
|
|
||||||
switch p.currentByte {
|
|
||||||
case '"', '\\':
|
|
||||||
p.currentToken.WriteByte(p.currentByte)
|
|
||||||
case 'n':
|
|
||||||
p.currentToken.WriteByte('\n')
|
|
||||||
default:
|
|
||||||
p.parseError(fmt.Sprintf("invalid escape sequence '\\%c'", p.currentByte))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
escaped = false
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
switch p.currentByte {
|
|
||||||
case '"':
|
|
||||||
return
|
|
||||||
case '\n':
|
|
||||||
p.parseError(fmt.Sprintf("label value %q contains unescaped new-line", p.currentToken.String()))
|
|
||||||
return
|
|
||||||
case '\\':
|
|
||||||
escaped = true
|
|
||||||
default:
|
|
||||||
p.currentToken.WriteByte(p.currentByte)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *TextParser) setOrCreateCurrentMF() {
|
|
||||||
p.currentIsSummaryCount = false
|
|
||||||
p.currentIsSummarySum = false
|
|
||||||
p.currentIsHistogramCount = false
|
|
||||||
p.currentIsHistogramSum = false
|
|
||||||
name := p.currentToken.String()
|
|
||||||
if p.currentMF = p.metricFamiliesByName[name]; p.currentMF != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Try out if this is a _sum or _count for a summary/histogram.
|
|
||||||
summaryName := summaryMetricName(name)
|
|
||||||
if p.currentMF = p.metricFamiliesByName[summaryName]; p.currentMF != nil {
|
|
||||||
if p.currentMF.GetType() == dto.MetricType_SUMMARY {
|
|
||||||
if isCount(name) {
|
|
||||||
p.currentIsSummaryCount = true
|
|
||||||
}
|
|
||||||
if isSum(name) {
|
|
||||||
p.currentIsSummarySum = true
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
histogramName := histogramMetricName(name)
|
|
||||||
if p.currentMF = p.metricFamiliesByName[histogramName]; p.currentMF != nil {
|
|
||||||
if p.currentMF.GetType() == dto.MetricType_HISTOGRAM {
|
|
||||||
if isCount(name) {
|
|
||||||
p.currentIsHistogramCount = true
|
|
||||||
}
|
|
||||||
if isSum(name) {
|
|
||||||
p.currentIsHistogramSum = true
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
p.currentMF = &dto.MetricFamily{Name: proto.String(name)}
|
|
||||||
p.metricFamiliesByName[name] = p.currentMF
|
|
||||||
}
|
|
||||||
|
|
||||||
func isValidLabelNameStart(b byte) bool {
|
|
||||||
return (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || b == '_'
|
|
||||||
}
|
|
||||||
|
|
||||||
func isValidLabelNameContinuation(b byte) bool {
|
|
||||||
return isValidLabelNameStart(b) || (b >= '0' && b <= '9')
|
|
||||||
}
|
|
||||||
|
|
||||||
func isValidMetricNameStart(b byte) bool {
|
|
||||||
return isValidLabelNameStart(b) || b == ':'
|
|
||||||
}
|
|
||||||
|
|
||||||
func isValidMetricNameContinuation(b byte) bool {
|
|
||||||
return isValidLabelNameContinuation(b) || b == ':'
|
|
||||||
}
|
|
||||||
|
|
||||||
func isBlankOrTab(b byte) bool {
|
|
||||||
return b == ' ' || b == '\t'
|
|
||||||
}
|
|
||||||
|
|
||||||
func isCount(name string) bool {
|
|
||||||
return len(name) > 6 && name[len(name)-6:] == "_count"
|
|
||||||
}
|
|
||||||
|
|
||||||
func isSum(name string) bool {
|
|
||||||
return len(name) > 4 && name[len(name)-4:] == "_sum"
|
|
||||||
}
|
|
||||||
|
|
||||||
func isBucket(name string) bool {
|
|
||||||
return len(name) > 7 && name[len(name)-7:] == "_bucket"
|
|
||||||
}
|
|
||||||
|
|
||||||
func summaryMetricName(name string) string {
|
|
||||||
switch {
|
|
||||||
case isCount(name):
|
|
||||||
return name[:len(name)-6]
|
|
||||||
case isSum(name):
|
|
||||||
return name[:len(name)-4]
|
|
||||||
default:
|
|
||||||
return name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func histogramMetricName(name string) string {
|
|
||||||
switch {
|
|
||||||
case isCount(name):
|
|
||||||
return name[:len(name)-6]
|
|
||||||
case isSum(name):
|
|
||||||
return name[:len(name)-4]
|
|
||||||
case isBucket(name):
|
|
||||||
return name[:len(name)-7]
|
|
||||||
default:
|
|
||||||
return name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
162
vendor/github.com/prometheus/common/internal/bitbucket.org/ww/goautoneg/autoneg.go
generated
vendored
162
vendor/github.com/prometheus/common/internal/bitbucket.org/ww/goautoneg/autoneg.go
generated
vendored
|
|
@ -1,162 +0,0 @@
|
||||||
/*
|
|
||||||
HTTP Content-Type Autonegotiation.
|
|
||||||
|
|
||||||
The functions in this package implement the behaviour specified in
|
|
||||||
http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
|
|
||||||
|
|
||||||
Copyright (c) 2011, Open Knowledge Foundation Ltd.
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
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 the Open Knowledge Foundation Ltd. 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
|
|
||||||
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 goautoneg
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sort"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Structure to represent a clause in an HTTP Accept Header
|
|
||||||
type Accept struct {
|
|
||||||
Type, SubType string
|
|
||||||
Q float64
|
|
||||||
Params map[string]string
|
|
||||||
}
|
|
||||||
|
|
||||||
// For internal use, so that we can use the sort interface
|
|
||||||
type accept_slice []Accept
|
|
||||||
|
|
||||||
func (accept accept_slice) Len() int {
|
|
||||||
slice := []Accept(accept)
|
|
||||||
return len(slice)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (accept accept_slice) Less(i, j int) bool {
|
|
||||||
slice := []Accept(accept)
|
|
||||||
ai, aj := slice[i], slice[j]
|
|
||||||
if ai.Q > aj.Q {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if ai.Type != "*" && aj.Type == "*" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if ai.SubType != "*" && aj.SubType == "*" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (accept accept_slice) Swap(i, j int) {
|
|
||||||
slice := []Accept(accept)
|
|
||||||
slice[i], slice[j] = slice[j], slice[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse an Accept Header string returning a sorted list
|
|
||||||
// of clauses
|
|
||||||
func ParseAccept(header string) (accept []Accept) {
|
|
||||||
parts := strings.Split(header, ",")
|
|
||||||
accept = make([]Accept, 0, len(parts))
|
|
||||||
for _, part := range parts {
|
|
||||||
part := strings.Trim(part, " ")
|
|
||||||
|
|
||||||
a := Accept{}
|
|
||||||
a.Params = make(map[string]string)
|
|
||||||
a.Q = 1.0
|
|
||||||
|
|
||||||
mrp := strings.Split(part, ";")
|
|
||||||
|
|
||||||
media_range := mrp[0]
|
|
||||||
sp := strings.Split(media_range, "/")
|
|
||||||
a.Type = strings.Trim(sp[0], " ")
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case len(sp) == 1 && a.Type == "*":
|
|
||||||
a.SubType = "*"
|
|
||||||
case len(sp) == 2:
|
|
||||||
a.SubType = strings.Trim(sp[1], " ")
|
|
||||||
default:
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(mrp) == 1 {
|
|
||||||
accept = append(accept, a)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, param := range mrp[1:] {
|
|
||||||
sp := strings.SplitN(param, "=", 2)
|
|
||||||
if len(sp) != 2 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
token := strings.Trim(sp[0], " ")
|
|
||||||
if token == "q" {
|
|
||||||
a.Q, _ = strconv.ParseFloat(sp[1], 32)
|
|
||||||
} else {
|
|
||||||
a.Params[token] = strings.Trim(sp[1], " ")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
accept = append(accept, a)
|
|
||||||
}
|
|
||||||
|
|
||||||
slice := accept_slice(accept)
|
|
||||||
sort.Sort(slice)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Negotiate the most appropriate content_type given the accept header
|
|
||||||
// and a list of alternatives.
|
|
||||||
func Negotiate(header string, alternatives []string) (content_type string) {
|
|
||||||
asp := make([][]string, 0, len(alternatives))
|
|
||||||
for _, ctype := range alternatives {
|
|
||||||
asp = append(asp, strings.SplitN(ctype, "/", 2))
|
|
||||||
}
|
|
||||||
for _, clause := range ParseAccept(header) {
|
|
||||||
for i, ctsp := range asp {
|
|
||||||
if clause.Type == ctsp[0] && clause.SubType == ctsp[1] {
|
|
||||||
content_type = alternatives[i]
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if clause.Type == ctsp[0] && clause.SubType == "*" {
|
|
||||||
content_type = alternatives[i]
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if clause.Type == "*" && clause.SubType == "*" {
|
|
||||||
content_type = alternatives[i]
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
@ -1,136 +0,0 @@
|
||||||
// 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 model
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type AlertStatus string
|
|
||||||
|
|
||||||
const (
|
|
||||||
AlertFiring AlertStatus = "firing"
|
|
||||||
AlertResolved AlertStatus = "resolved"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Alert is a generic representation of an alert in the Prometheus eco-system.
|
|
||||||
type Alert struct {
|
|
||||||
// Label value pairs for purpose of aggregation, matching, and disposition
|
|
||||||
// dispatching. This must minimally include an "alertname" label.
|
|
||||||
Labels LabelSet `json:"labels"`
|
|
||||||
|
|
||||||
// Extra key/value information which does not define alert identity.
|
|
||||||
Annotations LabelSet `json:"annotations"`
|
|
||||||
|
|
||||||
// The known time range for this alert. Both ends are optional.
|
|
||||||
StartsAt time.Time `json:"startsAt,omitempty"`
|
|
||||||
EndsAt time.Time `json:"endsAt,omitempty"`
|
|
||||||
GeneratorURL string `json:"generatorURL"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name returns the name of the alert. It is equivalent to the "alertname" label.
|
|
||||||
func (a *Alert) Name() string {
|
|
||||||
return string(a.Labels[AlertNameLabel])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fingerprint returns a unique hash for the alert. It is equivalent to
|
|
||||||
// the fingerprint of the alert's label set.
|
|
||||||
func (a *Alert) Fingerprint() Fingerprint {
|
|
||||||
return a.Labels.Fingerprint()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Alert) String() string {
|
|
||||||
s := fmt.Sprintf("%s[%s]", a.Name(), a.Fingerprint().String()[:7])
|
|
||||||
if a.Resolved() {
|
|
||||||
return s + "[resolved]"
|
|
||||||
}
|
|
||||||
return s + "[active]"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resolved returns true iff the activity interval ended in the past.
|
|
||||||
func (a *Alert) Resolved() bool {
|
|
||||||
return a.ResolvedAt(time.Now())
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResolvedAt returns true off the activity interval ended before
|
|
||||||
// the given timestamp.
|
|
||||||
func (a *Alert) ResolvedAt(ts time.Time) bool {
|
|
||||||
if a.EndsAt.IsZero() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return !a.EndsAt.After(ts)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Status returns the status of the alert.
|
|
||||||
func (a *Alert) Status() AlertStatus {
|
|
||||||
if a.Resolved() {
|
|
||||||
return AlertResolved
|
|
||||||
}
|
|
||||||
return AlertFiring
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate checks whether the alert data is inconsistent.
|
|
||||||
func (a *Alert) Validate() error {
|
|
||||||
if a.StartsAt.IsZero() {
|
|
||||||
return fmt.Errorf("start time missing")
|
|
||||||
}
|
|
||||||
if !a.EndsAt.IsZero() && a.EndsAt.Before(a.StartsAt) {
|
|
||||||
return fmt.Errorf("start time must be before end time")
|
|
||||||
}
|
|
||||||
if err := a.Labels.Validate(); err != nil {
|
|
||||||
return fmt.Errorf("invalid label set: %s", err)
|
|
||||||
}
|
|
||||||
if len(a.Labels) == 0 {
|
|
||||||
return fmt.Errorf("at least one label pair required")
|
|
||||||
}
|
|
||||||
if err := a.Annotations.Validate(); err != nil {
|
|
||||||
return fmt.Errorf("invalid annotations: %s", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Alert is a list of alerts that can be sorted in chronological order.
|
|
||||||
type Alerts []*Alert
|
|
||||||
|
|
||||||
func (as Alerts) Len() int { return len(as) }
|
|
||||||
func (as Alerts) Swap(i, j int) { as[i], as[j] = as[j], as[i] }
|
|
||||||
|
|
||||||
func (as Alerts) Less(i, j int) bool {
|
|
||||||
if as[i].StartsAt.Before(as[j].StartsAt) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if as[i].EndsAt.Before(as[j].EndsAt) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return as[i].Fingerprint() < as[j].Fingerprint()
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasFiring returns true iff one of the alerts is not resolved.
|
|
||||||
func (as Alerts) HasFiring() bool {
|
|
||||||
for _, a := range as {
|
|
||||||
if !a.Resolved() {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Status returns StatusFiring iff at least one of the alerts is firing.
|
|
||||||
func (as Alerts) Status() AlertStatus {
|
|
||||||
if as.HasFiring() {
|
|
||||||
return AlertFiring
|
|
||||||
}
|
|
||||||
return AlertResolved
|
|
||||||
}
|
|
||||||
|
|
@ -1,105 +0,0 @@
|
||||||
// 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 model
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Fingerprint provides a hash-capable representation of a Metric.
|
|
||||||
// For our purposes, FNV-1A 64-bit is used.
|
|
||||||
type Fingerprint uint64
|
|
||||||
|
|
||||||
// FingerprintFromString transforms a string representation into a Fingerprint.
|
|
||||||
func FingerprintFromString(s string) (Fingerprint, error) {
|
|
||||||
num, err := strconv.ParseUint(s, 16, 64)
|
|
||||||
return Fingerprint(num), err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseFingerprint parses the input string into a fingerprint.
|
|
||||||
func ParseFingerprint(s string) (Fingerprint, error) {
|
|
||||||
num, err := strconv.ParseUint(s, 16, 64)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return Fingerprint(num), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f Fingerprint) String() string {
|
|
||||||
return fmt.Sprintf("%016x", uint64(f))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fingerprints represents a collection of Fingerprint subject to a given
|
|
||||||
// natural sorting scheme. It implements sort.Interface.
|
|
||||||
type Fingerprints []Fingerprint
|
|
||||||
|
|
||||||
// Len implements sort.Interface.
|
|
||||||
func (f Fingerprints) Len() int {
|
|
||||||
return len(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Less implements sort.Interface.
|
|
||||||
func (f Fingerprints) Less(i, j int) bool {
|
|
||||||
return f[i] < f[j]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Swap implements sort.Interface.
|
|
||||||
func (f Fingerprints) Swap(i, j int) {
|
|
||||||
f[i], f[j] = f[j], f[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
// FingerprintSet is a set of Fingerprints.
|
|
||||||
type FingerprintSet map[Fingerprint]struct{}
|
|
||||||
|
|
||||||
// Equal returns true if both sets contain the same elements (and not more).
|
|
||||||
func (s FingerprintSet) Equal(o FingerprintSet) bool {
|
|
||||||
if len(s) != len(o) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
for k := range s {
|
|
||||||
if _, ok := o[k]; !ok {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Intersection returns the elements contained in both sets.
|
|
||||||
func (s FingerprintSet) Intersection(o FingerprintSet) FingerprintSet {
|
|
||||||
myLength, otherLength := len(s), len(o)
|
|
||||||
if myLength == 0 || otherLength == 0 {
|
|
||||||
return FingerprintSet{}
|
|
||||||
}
|
|
||||||
|
|
||||||
subSet := s
|
|
||||||
superSet := o
|
|
||||||
|
|
||||||
if otherLength < myLength {
|
|
||||||
subSet = o
|
|
||||||
superSet = s
|
|
||||||
}
|
|
||||||
|
|
||||||
out := FingerprintSet{}
|
|
||||||
|
|
||||||
for k := range subSet {
|
|
||||||
if _, ok := superSet[k]; ok {
|
|
||||||
out[k] = struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
// 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 model
|
|
||||||
|
|
||||||
// Inline and byte-free variant of hash/fnv's fnv64a.
|
|
||||||
|
|
||||||
const (
|
|
||||||
offset64 = 14695981039346656037
|
|
||||||
prime64 = 1099511628211
|
|
||||||
)
|
|
||||||
|
|
||||||
// hashNew initializies a new fnv64a hash value.
|
|
||||||
func hashNew() uint64 {
|
|
||||||
return offset64
|
|
||||||
}
|
|
||||||
|
|
||||||
// hashAdd adds a string to a fnv64a hash value, returning the updated hash.
|
|
||||||
func hashAdd(h uint64, s string) uint64 {
|
|
||||||
for i := 0; i < len(s); i++ {
|
|
||||||
h ^= uint64(s[i])
|
|
||||||
h *= prime64
|
|
||||||
}
|
|
||||||
return h
|
|
||||||
}
|
|
||||||
|
|
||||||
// hashAddByte adds a byte to a fnv64a hash value, returning the updated hash.
|
|
||||||
func hashAddByte(h uint64, b byte) uint64 {
|
|
||||||
h ^= uint64(b)
|
|
||||||
h *= prime64
|
|
||||||
return h
|
|
||||||
}
|
|
||||||
|
|
@ -1,210 +0,0 @@
|
||||||
// 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 model
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
"unicode/utf8"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// AlertNameLabel is the name of the label containing the an alert's name.
|
|
||||||
AlertNameLabel = "alertname"
|
|
||||||
|
|
||||||
// ExportedLabelPrefix is the prefix to prepend to the label names present in
|
|
||||||
// exported metrics if a label of the same name is added by the server.
|
|
||||||
ExportedLabelPrefix = "exported_"
|
|
||||||
|
|
||||||
// MetricNameLabel is the label name indicating the metric name of a
|
|
||||||
// timeseries.
|
|
||||||
MetricNameLabel = "__name__"
|
|
||||||
|
|
||||||
// SchemeLabel is the name of the label that holds the scheme on which to
|
|
||||||
// scrape a target.
|
|
||||||
SchemeLabel = "__scheme__"
|
|
||||||
|
|
||||||
// AddressLabel is the name of the label that holds the address of
|
|
||||||
// a scrape target.
|
|
||||||
AddressLabel = "__address__"
|
|
||||||
|
|
||||||
// MetricsPathLabel is the name of the label that holds the path on which to
|
|
||||||
// scrape a target.
|
|
||||||
MetricsPathLabel = "__metrics_path__"
|
|
||||||
|
|
||||||
// ReservedLabelPrefix is a prefix which is not legal in user-supplied
|
|
||||||
// label names.
|
|
||||||
ReservedLabelPrefix = "__"
|
|
||||||
|
|
||||||
// MetaLabelPrefix is a prefix for labels that provide meta information.
|
|
||||||
// Labels with this prefix are used for intermediate label processing and
|
|
||||||
// will not be attached to time series.
|
|
||||||
MetaLabelPrefix = "__meta_"
|
|
||||||
|
|
||||||
// TmpLabelPrefix is a prefix for temporary labels as part of relabelling.
|
|
||||||
// Labels with this prefix are used for intermediate label processing and
|
|
||||||
// will not be attached to time series. This is reserved for use in
|
|
||||||
// Prometheus configuration files by users.
|
|
||||||
TmpLabelPrefix = "__tmp_"
|
|
||||||
|
|
||||||
// ParamLabelPrefix is a prefix for labels that provide URL parameters
|
|
||||||
// used to scrape a target.
|
|
||||||
ParamLabelPrefix = "__param_"
|
|
||||||
|
|
||||||
// JobLabel is the label name indicating the job from which a timeseries
|
|
||||||
// was scraped.
|
|
||||||
JobLabel = "job"
|
|
||||||
|
|
||||||
// InstanceLabel is the label name used for the instance label.
|
|
||||||
InstanceLabel = "instance"
|
|
||||||
|
|
||||||
// BucketLabel is used for the label that defines the upper bound of a
|
|
||||||
// bucket of a histogram ("le" -> "less or equal").
|
|
||||||
BucketLabel = "le"
|
|
||||||
|
|
||||||
// QuantileLabel is used for the label that defines the quantile in a
|
|
||||||
// summary.
|
|
||||||
QuantileLabel = "quantile"
|
|
||||||
)
|
|
||||||
|
|
||||||
// LabelNameRE is a regular expression matching valid label names. Note that the
|
|
||||||
// IsValid method of LabelName performs the same check but faster than a match
|
|
||||||
// with this regular expression.
|
|
||||||
var LabelNameRE = regexp.MustCompile("^[a-zA-Z_][a-zA-Z0-9_]*$")
|
|
||||||
|
|
||||||
// A LabelName is a key for a LabelSet or Metric. It has a value associated
|
|
||||||
// therewith.
|
|
||||||
type LabelName string
|
|
||||||
|
|
||||||
// IsValid is true iff the label name matches the pattern of LabelNameRE. This
|
|
||||||
// method, however, does not use LabelNameRE for the check but a much faster
|
|
||||||
// hardcoded implementation.
|
|
||||||
func (ln LabelName) IsValid() bool {
|
|
||||||
if len(ln) == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for i, b := range ln {
|
|
||||||
if !((b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || b == '_' || (b >= '0' && b <= '9' && i > 0)) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalYAML implements the yaml.Unmarshaler interface.
|
|
||||||
func (ln *LabelName) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|
||||||
var s string
|
|
||||||
if err := unmarshal(&s); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !LabelName(s).IsValid() {
|
|
||||||
return fmt.Errorf("%q is not a valid label name", s)
|
|
||||||
}
|
|
||||||
*ln = LabelName(s)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON implements the json.Unmarshaler interface.
|
|
||||||
func (ln *LabelName) UnmarshalJSON(b []byte) error {
|
|
||||||
var s string
|
|
||||||
if err := json.Unmarshal(b, &s); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !LabelName(s).IsValid() {
|
|
||||||
return fmt.Errorf("%q is not a valid label name", s)
|
|
||||||
}
|
|
||||||
*ln = LabelName(s)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// LabelNames is a sortable LabelName slice. In implements sort.Interface.
|
|
||||||
type LabelNames []LabelName
|
|
||||||
|
|
||||||
func (l LabelNames) Len() int {
|
|
||||||
return len(l)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l LabelNames) Less(i, j int) bool {
|
|
||||||
return l[i] < l[j]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l LabelNames) Swap(i, j int) {
|
|
||||||
l[i], l[j] = l[j], l[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l LabelNames) String() string {
|
|
||||||
labelStrings := make([]string, 0, len(l))
|
|
||||||
for _, label := range l {
|
|
||||||
labelStrings = append(labelStrings, string(label))
|
|
||||||
}
|
|
||||||
return strings.Join(labelStrings, ", ")
|
|
||||||
}
|
|
||||||
|
|
||||||
// A LabelValue is an associated value for a LabelName.
|
|
||||||
type LabelValue string
|
|
||||||
|
|
||||||
// IsValid returns true iff the string is a valid UTF8.
|
|
||||||
func (lv LabelValue) IsValid() bool {
|
|
||||||
return utf8.ValidString(string(lv))
|
|
||||||
}
|
|
||||||
|
|
||||||
// LabelValues is a sortable LabelValue slice. It implements sort.Interface.
|
|
||||||
type LabelValues []LabelValue
|
|
||||||
|
|
||||||
func (l LabelValues) Len() int {
|
|
||||||
return len(l)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l LabelValues) Less(i, j int) bool {
|
|
||||||
return string(l[i]) < string(l[j])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l LabelValues) Swap(i, j int) {
|
|
||||||
l[i], l[j] = l[j], l[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
// LabelPair pairs a name with a value.
|
|
||||||
type LabelPair struct {
|
|
||||||
Name LabelName
|
|
||||||
Value LabelValue
|
|
||||||
}
|
|
||||||
|
|
||||||
// LabelPairs is a sortable slice of LabelPair pointers. It implements
|
|
||||||
// sort.Interface.
|
|
||||||
type LabelPairs []*LabelPair
|
|
||||||
|
|
||||||
func (l LabelPairs) Len() int {
|
|
||||||
return len(l)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l LabelPairs) Less(i, j int) bool {
|
|
||||||
switch {
|
|
||||||
case l[i].Name > l[j].Name:
|
|
||||||
return false
|
|
||||||
case l[i].Name < l[j].Name:
|
|
||||||
return true
|
|
||||||
case l[i].Value > l[j].Value:
|
|
||||||
return false
|
|
||||||
case l[i].Value < l[j].Value:
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l LabelPairs) Swap(i, j int) {
|
|
||||||
l[i], l[j] = l[j], l[i]
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue