mirror of
https://github.com/prometheus/prometheus.git
synced 2024-11-09 23:24:05 -08:00
Merge branch 'master' into dev-2.0
This commit is contained in:
commit
9304179ef7
|
@ -3,7 +3,7 @@ sudo: false
|
|||
language: go
|
||||
|
||||
go:
|
||||
- 1.7.3
|
||||
- 1.7.4
|
||||
|
||||
go_import_path: github.com/prometheus/prometheus
|
||||
|
||||
|
|
13
AUTHORS.md
13
AUTHORS.md
|
@ -1,13 +0,0 @@
|
|||
The Prometheus project was started by Matt T. Proud (emeritus) and
|
||||
Julius Volz in 2012.
|
||||
|
||||
Maintainers of this repository:
|
||||
|
||||
* Björn Rabenstein <beorn@soundcloud.com>
|
||||
* Fabian Reinartz <fabian.reinartz@coreos.com>
|
||||
* Julius Volz <julius.volz@gmail.com>
|
||||
|
||||
More than [100 individuals][1] have contributed to this repository. Please
|
||||
refer to the Git commit log for a complete list.
|
||||
|
||||
[1]: https://github.com/prometheus/prometheus/graphs/contributors
|
15
CHANGELOG.md
15
CHANGELOG.md
|
@ -1,3 +1,18 @@
|
|||
## 1.5.2 / 2017-02-10
|
||||
|
||||
* [BUGFIX] Fix series corruption in a special case of series maintenance where
|
||||
the minimum series-file-shrink-ratio kicks in.
|
||||
* [BUGFIX] Fix two panic conditions both related to processing a series
|
||||
scheduled to be quarantined.
|
||||
* [ENHANCEMENT] Binaries built with Go1.7.5.
|
||||
|
||||
## 1.5.1 / 2017-02-07
|
||||
|
||||
* [BUGFIX] Don't lose fully persisted memory series during checkpointing.
|
||||
* [BUGFIX] Fix intermittently failing relabeling.
|
||||
* [BUGFIX] Make `-storage.local.series-file-shrink-ratio` work.
|
||||
* [BUGFIX] Remove race condition from TestLoop.
|
||||
|
||||
## 1.5.0 / 2017-01-23
|
||||
|
||||
* [CHANGE] Use lexicographic order to sort alerts by name.
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
Prometheus uses GitHub to manage reviews of pull requests.
|
||||
|
||||
* If you have a trivial fix or improvement, go ahead and create a pull
|
||||
request, addressing (with `@...`) one or more of the maintainers
|
||||
(see [AUTHORS.md](AUTHORS.md)) in the description of the pull request.
|
||||
* If you have a trivial fix or improvement, go ahead and create a pull request,
|
||||
addressing (with `@...`) a suitable maintainer of this repository (see
|
||||
[MAINTAINERS.md](MAINTAINERS.md)) in the description of the pull request.
|
||||
|
||||
* If you plan to do something more involved, first discuss your ideas
|
||||
on our [mailing list](https://groups.google.com/forum/?fromgroups#!forum/prometheus-developers).
|
||||
|
|
7
MAINTAINERS.md
Normal file
7
MAINTAINERS.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
Maintainers of this repository with their focus areas:
|
||||
|
||||
* Björn Rabenstein <beorn@soundcloud.com>: Local storage; general code-level issues.
|
||||
* Brian Brazil <brian.brazil@robustperception.io>: Console templates; semantics of PromQL, service discovery, and relabeling.
|
||||
* Fabian Reinartz <fabian.reinartz@coreos.com>: PromQL parsing and evaluation; implementation of retrieval, alert notification, and service discovery.
|
||||
* Julius Volz <julius.volz@gmail.com>: Remote storage integrations; web UI.
|
||||
|
|
@ -53,7 +53,6 @@ var cfg = struct {
|
|||
|
||||
alertmanagerURLs stringset
|
||||
prometheusURL string
|
||||
influxdbURL string
|
||||
}{
|
||||
alertmanagerURLs: stringset{},
|
||||
}
|
||||
|
@ -130,6 +129,10 @@ func init() {
|
|||
&cfg.tsdb.AppendableBlocks, "storage.tsdb.AppendableBlocks", 2,
|
||||
"Number of head blocks that can be appended to.",
|
||||
)
|
||||
cfg.fs.StringVar(
|
||||
&cfg.localStorageEngine, "storage.local.engine", "persisted",
|
||||
"Local storage engine. Supported values are: 'persisted' (full local storage with on-disk persistence) and 'none' (no local storage).",
|
||||
)
|
||||
|
||||
// Alertmanager.
|
||||
cfg.fs.IntVar(
|
||||
|
|
|
@ -41,7 +41,6 @@ func TestParse(t *testing.T) {
|
|||
for i, test := range tests {
|
||||
// reset "immutable" config
|
||||
cfg.prometheusURL = ""
|
||||
cfg.influxdbURL = ""
|
||||
cfg.alertmanagerURLs = stringset{}
|
||||
|
||||
err := parse(test.input)
|
||||
|
@ -52,46 +51,3 @@ func TestParse(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseAlertmanagerURLToConfig(t *testing.T) {
|
||||
tests := []struct {
|
||||
url string
|
||||
username string
|
||||
password string
|
||||
}{
|
||||
{
|
||||
url: "http://alertmanager.company.com",
|
||||
username: "",
|
||||
password: "",
|
||||
},
|
||||
{
|
||||
url: "https://user:password@alertmanager.company.com",
|
||||
username: "user",
|
||||
password: "password",
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
acfg, err := parseAlertmanagerURLToConfig(test.url)
|
||||
if err != nil {
|
||||
t.Errorf("%d. expected alertmanager URL to be valid, got %s", i, err)
|
||||
}
|
||||
|
||||
if acfg.HTTPClientConfig.BasicAuth != nil {
|
||||
if test.username != acfg.HTTPClientConfig.BasicAuth.Username {
|
||||
t.Errorf("%d. expected alertmanagerConfig username to be %q, got %q",
|
||||
i, test.username, acfg.HTTPClientConfig.BasicAuth.Username)
|
||||
}
|
||||
|
||||
if test.password != acfg.HTTPClientConfig.BasicAuth.Password {
|
||||
t.Errorf("%d. expected alertmanagerConfig password to be %q, got %q", i,
|
||||
test.password, acfg.HTTPClientConfig.BasicAuth.Username)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if test.username != "" || test.password != "" {
|
||||
t.Errorf("%d. expected alertmanagerConfig to have basicAuth filled, but was not", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -91,9 +91,9 @@ func Main() int {
|
|||
return 1
|
||||
}
|
||||
|
||||
// reloadableRemoteStorage := remote.New()
|
||||
// sampleAppender = append(sampleAppender, reloadableRemoteStorage)
|
||||
// reloadables = append(reloadables, reloadableRemoteStorage)
|
||||
// remoteStorage := &remote.Storage{}
|
||||
// sampleAppender = append(sampleAppender, remoteStorage)
|
||||
// reloadables = append(reloadables, remoteStorage)
|
||||
|
||||
var (
|
||||
notifier = notifier.New(&cfg.notifier)
|
||||
|
@ -172,12 +172,8 @@ func Main() int {
|
|||
}
|
||||
}()
|
||||
|
||||
// defer reloadableRemoteStorage.Stop()
|
||||
// defer remoteStorage.Stop()
|
||||
|
||||
// The storage has to be fully initialized before registering.
|
||||
if instrumentedStorage, ok := localStorage.(prometheus.Collector); ok {
|
||||
prometheus.MustRegister(instrumentedStorage)
|
||||
}
|
||||
prometheus.MustRegister(notifier)
|
||||
prometheus.MustRegister(configSuccess)
|
||||
prometheus.MustRegister(configSuccessTime)
|
||||
|
|
|
@ -204,7 +204,7 @@ type Config struct {
|
|||
RuleFiles []string `yaml:"rule_files,omitempty"`
|
||||
ScrapeConfigs []*ScrapeConfig `yaml:"scrape_configs,omitempty"`
|
||||
|
||||
RemoteWriteConfig RemoteWriteConfig `yaml:"remote_write,omitempty"`
|
||||
RemoteWriteConfigs []*RemoteWriteConfig `yaml:"remote_write,omitempty"`
|
||||
|
||||
// Catches all undefined fields and must be empty after parsing.
|
||||
XXX map[string]interface{} `yaml:",inline"`
|
||||
|
|
|
@ -26,6 +26,14 @@ import (
|
|||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
func mustParseURL(u string) *URL {
|
||||
parsed, err := url.Parse(u)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return &URL{URL: parsed}
|
||||
}
|
||||
|
||||
var expectedConf = &Config{
|
||||
GlobalConfig: GlobalConfig{
|
||||
ScrapeInterval: model.Duration(15 * time.Second),
|
||||
|
@ -44,17 +52,24 @@ var expectedConf = &Config{
|
|||
"testdata/my/*.rules",
|
||||
},
|
||||
|
||||
RemoteWriteConfig: RemoteWriteConfig{
|
||||
RemoteTimeout: model.Duration(30 * time.Second),
|
||||
WriteRelabelConfigs: []*RelabelConfig{
|
||||
{
|
||||
SourceLabels: model.LabelNames{"__name__"},
|
||||
Separator: ";",
|
||||
Regex: MustNewRegexp("expensive.*"),
|
||||
Replacement: "$1",
|
||||
Action: RelabelDrop,
|
||||
RemoteWriteConfigs: []*RemoteWriteConfig{
|
||||
{
|
||||
URL: mustParseURL("http://remote1/push"),
|
||||
RemoteTimeout: model.Duration(30 * time.Second),
|
||||
WriteRelabelConfigs: []*RelabelConfig{
|
||||
{
|
||||
SourceLabels: model.LabelNames{"__name__"},
|
||||
Separator: ";",
|
||||
Regex: MustNewRegexp("expensive.*"),
|
||||
Replacement: "$1",
|
||||
Action: RelabelDrop,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
URL: mustParseURL("http://remote2/push"),
|
||||
RemoteTimeout: model.Duration(30 * time.Second),
|
||||
},
|
||||
},
|
||||
|
||||
ScrapeConfigs: []*ScrapeConfig{
|
||||
|
|
10
config/testdata/conf.good.yml
vendored
10
config/testdata/conf.good.yml
vendored
|
@ -14,10 +14,12 @@ rule_files:
|
|||
- "my/*.rules"
|
||||
|
||||
remote_write:
|
||||
write_relabel_configs:
|
||||
- source_labels: [__name__]
|
||||
regex: expensive.*
|
||||
action: drop
|
||||
- url: http://remote1/push
|
||||
write_relabel_configs:
|
||||
- source_labels: [__name__]
|
||||
regex: expensive.*
|
||||
action: drop
|
||||
- url: http://remote2/push
|
||||
|
||||
scrape_configs:
|
||||
- job_name: prometheus
|
||||
|
|
|
@ -179,13 +179,12 @@ func lookupAll(name string, qtype uint16) (*dns.Msg, error) {
|
|||
|
||||
for _, server := range conf.Servers {
|
||||
servAddr := net.JoinHostPort(server, conf.Port)
|
||||
for _, suffix := range conf.Search {
|
||||
response, err = lookup(name, qtype, client, servAddr, suffix, false)
|
||||
for _, lname := range conf.NameList(name) {
|
||||
response, err = lookup(lname, qtype, client, servAddr, false)
|
||||
if err != nil {
|
||||
log.
|
||||
With("server", server).
|
||||
With("name", name).
|
||||
With("suffix", suffix).
|
||||
With("reason", err).
|
||||
Warn("DNS resolution failed.")
|
||||
continue
|
||||
|
@ -194,22 +193,12 @@ func lookupAll(name string, qtype uint16) (*dns.Msg, error) {
|
|||
return response, nil
|
||||
}
|
||||
}
|
||||
response, err = lookup(name, qtype, client, servAddr, "", false)
|
||||
if err == nil {
|
||||
return response, nil
|
||||
}
|
||||
log.
|
||||
With("server", server).
|
||||
With("name", name).
|
||||
With("reason", err).
|
||||
Warn("DNS resolution failed.")
|
||||
}
|
||||
return response, fmt.Errorf("could not resolve %s: no server responded", name)
|
||||
}
|
||||
|
||||
func lookup(name string, queryType uint16, client *dns.Client, servAddr string, suffix string, edns bool) (*dns.Msg, error) {
|
||||
func lookup(lname string, queryType uint16, client *dns.Client, servAddr string, edns bool) (*dns.Msg, error) {
|
||||
msg := &dns.Msg{}
|
||||
lname := strings.Join([]string{name, suffix}, ".")
|
||||
msg.SetQuestion(dns.Fqdn(lname), queryType)
|
||||
|
||||
if edns {
|
||||
|
@ -224,7 +213,7 @@ func lookup(name string, queryType uint16, client *dns.Client, servAddr string,
|
|||
if edns { // Truncated even though EDNS is used
|
||||
client.Net = "tcp"
|
||||
}
|
||||
return lookup(name, queryType, client, servAddr, suffix, !edns)
|
||||
return lookup(lname, queryType, client, servAddr, !edns)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -108,7 +108,7 @@ scrape_configs:
|
|||
- source_labels: [__address__, __meta_kubernetes_service_annotation_prometheus_io_port]
|
||||
action: replace
|
||||
target_label: __address__
|
||||
regex: (.+)(?::\d+);(\d+)
|
||||
regex: ([^:]+)(?::\d+)?;(\d+)
|
||||
replacement: $1:$2
|
||||
- action: labelmap
|
||||
regex: __meta_kubernetes_service_label_(.+)
|
||||
|
@ -146,7 +146,7 @@ scrape_configs:
|
|||
target_label: instance
|
||||
- action: labelmap
|
||||
regex: __meta_kubernetes_service_label_(.+)
|
||||
- source_labels: [__meta_kubernetes_service_namespace]
|
||||
- source_labels: [__meta_kubernetes_namespace]
|
||||
target_label: kubernetes_namespace
|
||||
- source_labels: [__meta_kubernetes_service_name]
|
||||
target_label: kubernetes_name
|
||||
|
@ -174,8 +174,8 @@ scrape_configs:
|
|||
regex: (.+)
|
||||
- source_labels: [__address__, __meta_kubernetes_pod_annotation_prometheus_io_port]
|
||||
action: replace
|
||||
regex: (.+):(?:\d+);(\d+)
|
||||
replacement: ${1}:${2}
|
||||
regex: ([^:]+)(?::\d+)?;(\d+)
|
||||
replacement: $1:$2
|
||||
target_label: __address__
|
||||
- action: labelmap
|
||||
regex: __meta_kubernetes_pod_label_(.+)
|
||||
|
|
|
@ -7,7 +7,7 @@ To use it:
|
|||
|
||||
```
|
||||
go build
|
||||
./remote_storage
|
||||
./example_receiver
|
||||
```
|
||||
|
||||
...and then add the following to your `prometheus.yml`:
|
|
@ -0,0 +1,35 @@
|
|||
# Remote storage bridge
|
||||
|
||||
This is a bridge that receives samples in Prometheus's remote storage
|
||||
format and forwards them to Graphite, InfluxDB, or OpenTSDB. It is meant
|
||||
as a replacement for the built-in specific remote storage implementations
|
||||
that have been removed from Prometheus.
|
||||
|
||||
## Building
|
||||
|
||||
```
|
||||
go build
|
||||
```
|
||||
|
||||
## Running
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
./remote_storage_bridge -graphite-address=localhost:8080 -opentsdb-url=http://localhost:8081/
|
||||
```
|
||||
|
||||
To show all flags:
|
||||
|
||||
```
|
||||
./remote_storage_bridge -h
|
||||
```
|
||||
|
||||
## Configuring Prometheus
|
||||
|
||||
To configure Prometheus to send samples to this bridge, add the following to your `prometheus.yml`:
|
||||
|
||||
```yaml
|
||||
remote_write:
|
||||
url: "http://localhost:9201/receive"
|
||||
```
|
|
@ -0,0 +1,107 @@
|
|||
// 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 graphite
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"math"
|
||||
"net"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/common/log"
|
||||
"github.com/prometheus/common/model"
|
||||
)
|
||||
|
||||
// Client allows sending batches of Prometheus samples to Graphite.
|
||||
type Client struct {
|
||||
address string
|
||||
transport string
|
||||
timeout time.Duration
|
||||
prefix string
|
||||
}
|
||||
|
||||
// NewClient creates a new Client.
|
||||
func NewClient(address string, transport string, timeout time.Duration, prefix string) *Client {
|
||||
return &Client{
|
||||
address: address,
|
||||
transport: transport,
|
||||
timeout: timeout,
|
||||
prefix: prefix,
|
||||
}
|
||||
}
|
||||
|
||||
func pathFromMetric(m model.Metric, prefix string) string {
|
||||
var buffer bytes.Buffer
|
||||
|
||||
buffer.WriteString(prefix)
|
||||
buffer.WriteString(escape(m[model.MetricNameLabel]))
|
||||
|
||||
// We want to sort the labels.
|
||||
labels := make(model.LabelNames, 0, len(m))
|
||||
for l := range m {
|
||||
labels = append(labels, l)
|
||||
}
|
||||
sort.Sort(labels)
|
||||
|
||||
// For each label, in order, add ".<label>.<value>".
|
||||
for _, l := range labels {
|
||||
v := m[l]
|
||||
|
||||
if l == model.MetricNameLabel || len(l) == 0 {
|
||||
continue
|
||||
}
|
||||
// Since we use '.' instead of '=' to separate label and values
|
||||
// it means that we can't have an '.' in the metric name. Fortunately
|
||||
// this is prohibited in prometheus metrics.
|
||||
buffer.WriteString(fmt.Sprintf(
|
||||
".%s.%s", string(l), escape(v)))
|
||||
}
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
// Store sends a batch of samples to Graphite.
|
||||
func (c *Client) Store(samples model.Samples) error {
|
||||
conn, err := net.DialTimeout(c.transport, c.address, c.timeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
var buf bytes.Buffer
|
||||
for _, s := range samples {
|
||||
k := pathFromMetric(s.Metric, c.prefix)
|
||||
t := float64(s.Timestamp.UnixNano()) / 1e9
|
||||
v := float64(s.Value)
|
||||
if math.IsNaN(v) || math.IsInf(v, 0) {
|
||||
log.Warnf("cannot send value %f to Graphite,"+
|
||||
"skipping sample %#v", v, s)
|
||||
continue
|
||||
}
|
||||
fmt.Fprintf(&buf, "%s %f %f\n", k, v, t)
|
||||
}
|
||||
|
||||
_, err = conn.Write(buf.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Name identifies the client as a Graphite client.
|
||||
func (c Client) Name() string {
|
||||
return "graphite"
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
// 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 graphite
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/prometheus/common/model"
|
||||
)
|
||||
|
||||
var (
|
||||
metric = model.Metric{
|
||||
model.MetricNameLabel: "test:metric",
|
||||
"testlabel": "test:value",
|
||||
"many_chars": "abc!ABC:012-3!45ö67~89./(){},=.\"\\",
|
||||
}
|
||||
)
|
||||
|
||||
func TestEscape(t *testing.T) {
|
||||
// Can we correctly keep and escape valid chars.
|
||||
value := "abzABZ019(){},'\"\\"
|
||||
expected := "abzABZ019\\(\\)\\{\\}\\,\\'\\\"\\\\"
|
||||
actual := escape(model.LabelValue(value))
|
||||
if expected != actual {
|
||||
t.Errorf("Expected %s, got %s", expected, actual)
|
||||
}
|
||||
|
||||
// Test percent-encoding.
|
||||
value = "é/|_;:%."
|
||||
expected = "%C3%A9%2F|_;:%25%2E"
|
||||
actual = escape(model.LabelValue(value))
|
||||
if expected != actual {
|
||||
t.Errorf("Expected %s, got %s", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPathFromMetric(t *testing.T) {
|
||||
expected := ("prefix." +
|
||||
"test:metric" +
|
||||
".many_chars.abc!ABC:012-3!45%C3%B667~89%2E%2F\\(\\)\\{\\}\\,%3D%2E\\\"\\\\" +
|
||||
".testlabel.test:value")
|
||||
actual := pathFromMetric(metric, "prefix.")
|
||||
if expected != actual {
|
||||
t.Errorf("Expected %s, got %s", expected, actual)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
// 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 graphite
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/prometheus/common/model"
|
||||
)
|
||||
|
||||
const (
|
||||
// From https://github.com/graphite-project/graphite-web/blob/master/webapp/graphite/render/grammar.py#L83
|
||||
symbols = "(){},=.'\"\\"
|
||||
printables = ("0123456789abcdefghijklmnopqrstuvwxyz" +
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
|
||||
"!\"#$%&\\'()*+,-./:;<=>?@[\\]^_`{|}~")
|
||||
)
|
||||
|
||||
// Graphite doesn't support tags, so label names and values must be
|
||||
// encoded into the metric path. The list of characters that are usable
|
||||
// with Graphite is rather fuzzy. One 'source of truth' might be the grammar
|
||||
// used to parse requests in the webapp:
|
||||
// https://github.com/graphite-project/graphite-web/blob/master/webapp/graphite/render/grammar.py#L83
|
||||
// The list of valid symbols is defined as:
|
||||
// legal = printables - symbols + escaped(symbols)
|
||||
//
|
||||
// The default storage backend for Graphite (whisper) stores data
|
||||
// in filenames, so we also need to use only valid filename characters.
|
||||
// Fortunately on UNIX only '/' isn't, and Windows is completely unsupported
|
||||
// by Graphite: http://graphite.readthedocs.org/en/latest/install.html#windows-users
|
||||
|
||||
// escape escapes a model.LabelValue into runes allowed in Graphite. The runes
|
||||
// allowed in Graphite are all single-byte. This function encodes the arbitrary
|
||||
// byte sequence found in this TagValue in way very similar to the traditional
|
||||
// percent-encoding (https://en.wikipedia.org/wiki/Percent-encoding):
|
||||
//
|
||||
// - The string that underlies TagValue is scanned byte by byte.
|
||||
//
|
||||
// - If a byte represents a legal Graphite rune with the exception of '%', '/',
|
||||
// '=' and '.', that byte is directly copied to the resulting byte slice.
|
||||
// % is used for percent-encoding of other bytes.
|
||||
// / is not usable in filenames.
|
||||
// = is used when generating the path to associate values to labels.
|
||||
// . already means something for Graphite and thus can't be used in a value.
|
||||
//
|
||||
// - If the byte is any of (){},=.'"\, then a '\' will be prepended to it. We
|
||||
// do not percent-encode them since they are explicitly usable in this
|
||||
// way in Graphite.
|
||||
//
|
||||
// - All other bytes are replaced by '%' followed by two bytes containing the
|
||||
// uppercase ASCII representation of their hexadecimal value.
|
||||
//
|
||||
// This encoding allows to save arbitrary Go strings in Graphite. That's
|
||||
// required because Prometheus label values can contain anything. Using
|
||||
// percent encoding makes it easy to unescape, even in javascript.
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// "foo-bar-42" -> "foo-bar-42"
|
||||
//
|
||||
// "foo_bar%42" -> "foo_bar%2542"
|
||||
//
|
||||
// "http://example.org:8080" -> "http:%2F%2Fexample%2Eorg:8080"
|
||||
//
|
||||
// "Björn's email: bjoern@soundcloud.com" ->
|
||||
// "Bj%C3%B6rn's%20email:%20bjoern%40soundcloud.com"
|
||||
//
|
||||
// "日" -> "%E6%97%A5"
|
||||
func escape(tv model.LabelValue) string {
|
||||
length := len(tv)
|
||||
result := bytes.NewBuffer(make([]byte, 0, length))
|
||||
for i := 0; i < length; i++ {
|
||||
b := tv[i]
|
||||
switch {
|
||||
// . is reserved by graphite, % is used to escape other bytes.
|
||||
case b == '.' || b == '%' || b == '/' || b == '=':
|
||||
fmt.Fprintf(result, "%%%X", b)
|
||||
// These symbols are ok only if backslash escaped.
|
||||
case strings.IndexByte(symbols, b) != -1:
|
||||
result.WriteString("\\" + string(b))
|
||||
// These are all fine.
|
||||
case strings.IndexByte(printables, b) != -1:
|
||||
result.WriteByte(b)
|
||||
// Defaults to percent-encoding.
|
||||
default:
|
||||
fmt.Fprintf(result, "%%%X", b)
|
||||
}
|
||||
}
|
||||
return result.String()
|
||||
}
|
|
@ -0,0 +1,109 @@
|
|||
// 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 influxdb
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/common/log"
|
||||
"github.com/prometheus/common/model"
|
||||
|
||||
influx "github.com/influxdb/influxdb/client"
|
||||
)
|
||||
|
||||
// Client allows sending batches of Prometheus samples to InfluxDB.
|
||||
type Client struct {
|
||||
client *influx.Client
|
||||
database string
|
||||
retentionPolicy string
|
||||
ignoredSamples prometheus.Counter
|
||||
}
|
||||
|
||||
// NewClient creates a new Client.
|
||||
func NewClient(conf influx.Config, db string, rp string) *Client {
|
||||
c, err := influx.NewClient(conf)
|
||||
// Currently influx.NewClient() *should* never return an error.
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
return &Client{
|
||||
client: c,
|
||||
database: db,
|
||||
retentionPolicy: rp,
|
||||
ignoredSamples: prometheus.NewCounter(
|
||||
prometheus.CounterOpts{
|
||||
Name: "prometheus_influxdb_ignored_samples_total",
|
||||
Help: "The total number of samples not sent to InfluxDB due to unsupported float values (Inf, -Inf, NaN).",
|
||||
},
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
// tagsFromMetric extracts InfluxDB tags from a Prometheus metric.
|
||||
func tagsFromMetric(m model.Metric) map[string]string {
|
||||
tags := make(map[string]string, len(m)-1)
|
||||
for l, v := range m {
|
||||
if l != model.MetricNameLabel {
|
||||
tags[string(l)] = string(v)
|
||||
}
|
||||
}
|
||||
return tags
|
||||
}
|
||||
|
||||
// Store sends a batch of samples to InfluxDB via its HTTP API.
|
||||
func (c *Client) Store(samples model.Samples) error {
|
||||
points := make([]influx.Point, 0, len(samples))
|
||||
for _, s := range samples {
|
||||
v := float64(s.Value)
|
||||
if math.IsNaN(v) || math.IsInf(v, 0) {
|
||||
log.Debugf("cannot send value %f to InfluxDB, skipping sample %#v", v, s)
|
||||
c.ignoredSamples.Inc()
|
||||
continue
|
||||
}
|
||||
points = append(points, influx.Point{
|
||||
Measurement: string(s.Metric[model.MetricNameLabel]),
|
||||
Tags: tagsFromMetric(s.Metric),
|
||||
Time: s.Timestamp.Time(),
|
||||
Precision: "ms",
|
||||
Fields: map[string]interface{}{
|
||||
"value": v,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
bps := influx.BatchPoints{
|
||||
Points: points,
|
||||
Database: c.database,
|
||||
RetentionPolicy: c.retentionPolicy,
|
||||
}
|
||||
_, err := c.client.Write(bps)
|
||||
return err
|
||||
}
|
||||
|
||||
// Name identifies the client as an InfluxDB client.
|
||||
func (c Client) Name() string {
|
||||
return "influxdb"
|
||||
}
|
||||
|
||||
// Describe implements prometheus.Collector.
|
||||
func (c *Client) Describe(ch chan<- *prometheus.Desc) {
|
||||
ch <- c.ignoredSamples.Desc()
|
||||
}
|
||||
|
||||
// Collect implements prometheus.Collector.
|
||||
func (c *Client) Collect(ch chan<- prometheus.Metric) {
|
||||
ch <- c.ignoredSamples
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
// 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 influxdb
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
influx "github.com/influxdb/influxdb/client"
|
||||
|
||||
"github.com/prometheus/common/model"
|
||||
)
|
||||
|
||||
func TestClient(t *testing.T) {
|
||||
samples := model.Samples{
|
||||
{
|
||||
Metric: model.Metric{
|
||||
model.MetricNameLabel: "testmetric",
|
||||
"test_label": "test_label_value1",
|
||||
},
|
||||
Timestamp: model.Time(123456789123),
|
||||
Value: 1.23,
|
||||
},
|
||||
{
|
||||
Metric: model.Metric{
|
||||
model.MetricNameLabel: "testmetric",
|
||||
"test_label": "test_label_value2",
|
||||
},
|
||||
Timestamp: model.Time(123456789123),
|
||||
Value: 5.1234,
|
||||
},
|
||||
{
|
||||
Metric: model.Metric{
|
||||
model.MetricNameLabel: "nan_value",
|
||||
},
|
||||
Timestamp: model.Time(123456789123),
|
||||
Value: model.SampleValue(math.NaN()),
|
||||
},
|
||||
{
|
||||
Metric: model.Metric{
|
||||
model.MetricNameLabel: "pos_inf_value",
|
||||
},
|
||||
Timestamp: model.Time(123456789123),
|
||||
Value: model.SampleValue(math.Inf(1)),
|
||||
},
|
||||
{
|
||||
Metric: model.Metric{
|
||||
model.MetricNameLabel: "neg_inf_value",
|
||||
},
|
||||
Timestamp: model.Time(123456789123),
|
||||
Value: model.SampleValue(math.Inf(-1)),
|
||||
},
|
||||
}
|
||||
|
||||
expectedBody := `testmetric,test_label=test_label_value1 value=1.23 123456789123000000
|
||||
testmetric,test_label=test_label_value2 value=5.1234 123456789123000000
|
||||
`
|
||||
|
||||
server := httptest.NewServer(http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "POST" {
|
||||
t.Fatalf("Unexpected method; expected POST, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/write" {
|
||||
t.Fatalf("Unexpected path; expected %s, got %s", "/write", r.URL.Path)
|
||||
}
|
||||
b, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
t.Fatalf("Error reading body: %s", err)
|
||||
}
|
||||
|
||||
if string(b) != expectedBody {
|
||||
t.Fatalf("Unexpected request body; expected:\n\n%s\n\ngot:\n\n%s", expectedBody, string(b))
|
||||
}
|
||||
},
|
||||
))
|
||||
defer server.Close()
|
||||
|
||||
serverURL, err := url.Parse(server.URL)
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to parse server URL %s: %s", server.URL, err)
|
||||
}
|
||||
|
||||
conf := influx.Config{
|
||||
URL: *serverURL,
|
||||
Username: "testuser",
|
||||
Password: "testpass",
|
||||
Timeout: time.Minute,
|
||||
}
|
||||
c := NewClient(conf, "test_db", "default")
|
||||
|
||||
if err := c.Store(samples); err != nil {
|
||||
t.Fatalf("Error sending samples: %s", err)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,232 @@
|
|||
// Copyright 2017 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// The main package for the Prometheus server executable.
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
_ "net/http/pprof"
|
||||
"net/url"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"github.com/golang/snappy"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/common/log"
|
||||
"github.com/prometheus/common/model"
|
||||
|
||||
influx "github.com/influxdb/influxdb/client"
|
||||
|
||||
"github.com/prometheus/prometheus/documentation/examples/remote_storage/remote_storage_bridge/graphite"
|
||||
"github.com/prometheus/prometheus/documentation/examples/remote_storage/remote_storage_bridge/influxdb"
|
||||
"github.com/prometheus/prometheus/documentation/examples/remote_storage/remote_storage_bridge/opentsdb"
|
||||
"github.com/prometheus/prometheus/storage/remote"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
graphiteAddress string
|
||||
graphiteTransport string
|
||||
graphitePrefix string
|
||||
opentsdbURL string
|
||||
influxdbURL string
|
||||
influxdbRetentionPolicy string
|
||||
influxdbUsername string
|
||||
influxdbDatabase string
|
||||
influxdbPassword string
|
||||
remoteTimeout time.Duration
|
||||
listenAddr string
|
||||
telemetryPath string
|
||||
}
|
||||
|
||||
var (
|
||||
receivedSamples = prometheus.NewCounter(
|
||||
prometheus.CounterOpts{
|
||||
Name: "received_samples_total",
|
||||
Help: "Total number of received samples.",
|
||||
},
|
||||
)
|
||||
sentSamples = prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Name: "sent_samples_total",
|
||||
Help: "Total number of processed samples sent to remote storage.",
|
||||
},
|
||||
[]string{"remote"},
|
||||
)
|
||||
failedSamples = prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Name: "failed_samples_total",
|
||||
Help: "Total number of processed samples which failed on send to remote storage.",
|
||||
},
|
||||
[]string{"remote"},
|
||||
)
|
||||
sentBatchDuration = prometheus.NewHistogramVec(
|
||||
prometheus.HistogramOpts{
|
||||
Name: "sent_batch_duration_seconds",
|
||||
Help: "Duration of sample batch send calls to the remote storage.",
|
||||
Buckets: prometheus.DefBuckets,
|
||||
},
|
||||
[]string{"remote"},
|
||||
)
|
||||
)
|
||||
|
||||
func init() {
|
||||
prometheus.MustRegister(receivedSamples)
|
||||
prometheus.MustRegister(sentSamples)
|
||||
prometheus.MustRegister(failedSamples)
|
||||
prometheus.MustRegister(sentBatchDuration)
|
||||
}
|
||||
|
||||
func main() {
|
||||
cfg := parseFlags()
|
||||
http.Handle(cfg.telemetryPath, prometheus.Handler())
|
||||
|
||||
clients := buildClients(cfg)
|
||||
serve(cfg.listenAddr, clients)
|
||||
}
|
||||
|
||||
func parseFlags() *config {
|
||||
cfg := &config{
|
||||
influxdbPassword: os.Getenv("INFLUXDB_PW"),
|
||||
}
|
||||
|
||||
flag.StringVar(&cfg.graphiteAddress, "graphite-address", "",
|
||||
"The host:port of the Graphite server to send samples to. None, if empty.",
|
||||
)
|
||||
flag.StringVar(&cfg.graphiteTransport, "graphite-transport", "tcp",
|
||||
"Transport protocol to use to communicate with Graphite. 'tcp', if empty.",
|
||||
)
|
||||
flag.StringVar(&cfg.graphitePrefix, "graphite-prefix", "",
|
||||
"The prefix to prepend to all metrics exported to Graphite. None, if empty.",
|
||||
)
|
||||
flag.StringVar(&cfg.opentsdbURL, "opentsdb-url", "",
|
||||
"The URL of the remote OpenTSDB server to send samples to. None, if empty.",
|
||||
)
|
||||
flag.StringVar(&cfg.influxdbURL, "influxdb-url", "",
|
||||
"The URL of the remote InfluxDB server to send samples to. None, if empty.",
|
||||
)
|
||||
flag.StringVar(&cfg.influxdbRetentionPolicy, "influxdb.retention-policy", "default",
|
||||
"The InfluxDB retention policy to use.",
|
||||
)
|
||||
flag.StringVar(&cfg.influxdbUsername, "influxdb.username", "",
|
||||
"The username to use when sending samples to InfluxDB. The corresponding password must be provided via the INFLUXDB_PW environment variable.",
|
||||
)
|
||||
flag.StringVar(&cfg.influxdbDatabase, "influxdb.database", "prometheus",
|
||||
"The name of the database to use for storing samples in InfluxDB.",
|
||||
)
|
||||
flag.DurationVar(&cfg.remoteTimeout, "send-timeout", 30*time.Second,
|
||||
"The timeout to use when sending samples to the remote storage.",
|
||||
)
|
||||
flag.StringVar(&cfg.listenAddr, "web.listen-address", ":9201", "Address to listen on for web endpoints.")
|
||||
flag.StringVar(&cfg.telemetryPath, "web.telemetry-path", "/metrics", "Address to listen on for web endpoints.")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
return cfg
|
||||
}
|
||||
|
||||
func buildClients(cfg *config) []remote.StorageClient {
|
||||
var clients []remote.StorageClient
|
||||
if cfg.graphiteAddress != "" {
|
||||
c := graphite.NewClient(
|
||||
cfg.graphiteAddress, cfg.graphiteTransport,
|
||||
cfg.remoteTimeout, cfg.graphitePrefix)
|
||||
clients = append(clients, c)
|
||||
}
|
||||
if cfg.opentsdbURL != "" {
|
||||
c := opentsdb.NewClient(cfg.opentsdbURL, cfg.remoteTimeout)
|
||||
clients = append(clients, c)
|
||||
}
|
||||
if cfg.influxdbURL != "" {
|
||||
url, err := url.Parse(cfg.influxdbURL)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to parse InfluxDB URL %q: %v", cfg.influxdbURL, err)
|
||||
}
|
||||
conf := influx.Config{
|
||||
URL: *url,
|
||||
Username: cfg.influxdbUsername,
|
||||
Password: cfg.influxdbPassword,
|
||||
Timeout: cfg.remoteTimeout,
|
||||
}
|
||||
c := influxdb.NewClient(conf, cfg.influxdbDatabase, cfg.influxdbRetentionPolicy)
|
||||
prometheus.MustRegister(c)
|
||||
clients = append(clients, c)
|
||||
}
|
||||
return clients
|
||||
}
|
||||
|
||||
func serve(addr string, clients []remote.StorageClient) error {
|
||||
http.HandleFunc("/receive", func(w http.ResponseWriter, r *http.Request) {
|
||||
reqBuf, err := ioutil.ReadAll(snappy.NewReader(r.Body))
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var req remote.WriteRequest
|
||||
if err := proto.Unmarshal(reqBuf, &req); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
samples := protoToSamples(&req)
|
||||
receivedSamples.Add(float64(len(samples)))
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for _, c := range clients {
|
||||
wg.Add(1)
|
||||
go func(rc remote.StorageClient) {
|
||||
sendSamples(rc, samples)
|
||||
wg.Done()
|
||||
}(c)
|
||||
}
|
||||
wg.Wait()
|
||||
})
|
||||
|
||||
return http.ListenAndServe(addr, nil)
|
||||
}
|
||||
|
||||
func protoToSamples(req *remote.WriteRequest) model.Samples {
|
||||
var samples model.Samples
|
||||
for _, ts := range req.Timeseries {
|
||||
metric := make(model.Metric, len(ts.Labels))
|
||||
for _, l := range ts.Labels {
|
||||
metric[model.LabelName(l.Name)] = model.LabelValue(l.Value)
|
||||
}
|
||||
|
||||
for _, s := range ts.Samples {
|
||||
samples = append(samples, &model.Sample{
|
||||
Metric: metric,
|
||||
Value: model.SampleValue(s.Value),
|
||||
Timestamp: model.Time(s.TimestampMs),
|
||||
})
|
||||
}
|
||||
}
|
||||
return samples
|
||||
}
|
||||
|
||||
func sendSamples(c remote.StorageClient, samples model.Samples) {
|
||||
begin := time.Now()
|
||||
err := c.Store(samples)
|
||||
duration := time.Since(begin).Seconds()
|
||||
if err != nil {
|
||||
log.Warnf("Error sending %d samples to remote storage %q: %v", len(samples), c.Name(), err)
|
||||
failedSamples.WithLabelValues(c.Name()).Add(float64(len(samples)))
|
||||
}
|
||||
sentSamples.WithLabelValues(c.Name()).Add(float64(len(samples)))
|
||||
sentBatchDuration.WithLabelValues(c.Name()).Observe(duration)
|
||||
}
|
|
@ -0,0 +1,136 @@
|
|||
// 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 opentsdb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/common/log"
|
||||
|
||||
"github.com/prometheus/common/model"
|
||||
|
||||
"github.com/prometheus/prometheus/util/httputil"
|
||||
)
|
||||
|
||||
const (
|
||||
putEndpoint = "/api/put"
|
||||
contentTypeJSON = "application/json"
|
||||
)
|
||||
|
||||
// Client allows sending batches of Prometheus samples to OpenTSDB.
|
||||
type Client struct {
|
||||
url string
|
||||
httpClient *http.Client
|
||||
}
|
||||
|
||||
// NewClient creates a new Client.
|
||||
func NewClient(url string, timeout time.Duration) *Client {
|
||||
return &Client{
|
||||
url: url,
|
||||
httpClient: httputil.NewDeadlineClient(timeout, nil),
|
||||
}
|
||||
}
|
||||
|
||||
// StoreSamplesRequest is used for building a JSON request for storing samples
|
||||
// via the OpenTSDB.
|
||||
type StoreSamplesRequest struct {
|
||||
Metric TagValue `json:"metric"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
Value float64 `json:"value"`
|
||||
Tags map[string]TagValue `json:"tags"`
|
||||
}
|
||||
|
||||
// tagsFromMetric translates Prometheus metric into OpenTSDB tags.
|
||||
func tagsFromMetric(m model.Metric) map[string]TagValue {
|
||||
tags := make(map[string]TagValue, len(m)-1)
|
||||
for l, v := range m {
|
||||
if l == model.MetricNameLabel {
|
||||
continue
|
||||
}
|
||||
tags[string(l)] = TagValue(v)
|
||||
}
|
||||
return tags
|
||||
}
|
||||
|
||||
// Store sends a batch of samples to OpenTSDB via its HTTP API.
|
||||
func (c *Client) Store(samples model.Samples) error {
|
||||
reqs := make([]StoreSamplesRequest, 0, len(samples))
|
||||
for _, s := range samples {
|
||||
v := float64(s.Value)
|
||||
if math.IsNaN(v) || math.IsInf(v, 0) {
|
||||
log.Warnf("cannot send value %f to OpenTSDB, skipping sample %#v", v, s)
|
||||
continue
|
||||
}
|
||||
metric := TagValue(s.Metric[model.MetricNameLabel])
|
||||
reqs = append(reqs, StoreSamplesRequest{
|
||||
Metric: metric,
|
||||
Timestamp: s.Timestamp.Unix(),
|
||||
Value: v,
|
||||
Tags: tagsFromMetric(s.Metric),
|
||||
})
|
||||
}
|
||||
|
||||
u, err := url.Parse(c.url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
u.Path = putEndpoint
|
||||
|
||||
buf, err := json.Marshal(reqs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := c.httpClient.Post(
|
||||
u.String(),
|
||||
contentTypeJSON,
|
||||
bytes.NewBuffer(buf),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// API returns status code 204 for successful writes.
|
||||
// http://opentsdb.net/docs/build/html/api_http/put.html
|
||||
if resp.StatusCode == http.StatusNoContent {
|
||||
return nil
|
||||
}
|
||||
|
||||
// API returns status code 400 on error, encoding error details in the
|
||||
// response content in JSON.
|
||||
buf, err = ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var r map[string]int
|
||||
if err := json.Unmarshal(buf, &r); err != nil {
|
||||
return err
|
||||
}
|
||||
return fmt.Errorf("failed to write %d samples to OpenTSDB, %d succeeded", r["failed"], r["success"])
|
||||
}
|
||||
|
||||
// Name identifies the client as an OpenTSDB client.
|
||||
func (c Client) Name() string {
|
||||
return "opentsdb"
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
// 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 opentsdb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/prometheus/common/model"
|
||||
)
|
||||
|
||||
var (
|
||||
metric = model.Metric{
|
||||
model.MetricNameLabel: "test:metric",
|
||||
"testlabel": "test:value",
|
||||
"many_chars": "abc!ABC:012-3!45ö67~89./",
|
||||
}
|
||||
)
|
||||
|
||||
func TestTagsFromMetric(t *testing.T) {
|
||||
expected := map[string]TagValue{
|
||||
"testlabel": TagValue("test:value"),
|
||||
"many_chars": TagValue("abc!ABC:012-3!45ö67~89./"),
|
||||
}
|
||||
actual := tagsFromMetric(metric)
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Errorf("Expected %#v, got %#v", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarshalStoreSamplesRequest(t *testing.T) {
|
||||
request := StoreSamplesRequest{
|
||||
Metric: TagValue("test:metric"),
|
||||
Timestamp: 4711,
|
||||
Value: 3.1415,
|
||||
Tags: tagsFromMetric(metric),
|
||||
}
|
||||
expectedJSON := []byte(`{"metric":"test_.metric","timestamp":4711,"value":3.1415,"tags":{"many_chars":"abc_21ABC_.012-3_2145_C3_B667_7E89./","testlabel":"test_.value"}}`)
|
||||
|
||||
resultingJSON, err := json.Marshal(request)
|
||||
if err != nil {
|
||||
t.Fatalf("Marshal(request) resulted in err: %s", err)
|
||||
}
|
||||
if !bytes.Equal(resultingJSON, expectedJSON) {
|
||||
t.Errorf(
|
||||
"Marshal(request) => %q, want %q",
|
||||
resultingJSON, expectedJSON,
|
||||
)
|
||||
}
|
||||
|
||||
var unmarshaledRequest StoreSamplesRequest
|
||||
err = json.Unmarshal(expectedJSON, &unmarshaledRequest)
|
||||
if err != nil {
|
||||
t.Fatalf("Unmarshal(expectedJSON, &unmarshaledRequest) resulted in err: %s", err)
|
||||
}
|
||||
if !reflect.DeepEqual(unmarshaledRequest, request) {
|
||||
t.Errorf(
|
||||
"Unmarshal(expectedJSON, &unmarshaledRequest) => %#v, want %#v",
|
||||
unmarshaledRequest, request,
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,157 @@
|
|||
// 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 opentsdb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/prometheus/common/model"
|
||||
)
|
||||
|
||||
// TagValue is a model.LabelValue that implements json.Marshaler and
|
||||
// json.Unmarshaler. These implementations avoid characters illegal in
|
||||
// OpenTSDB. See the MarshalJSON for details. TagValue is used for the values of
|
||||
// OpenTSDB tags as well as for OpenTSDB metric names.
|
||||
type TagValue model.LabelValue
|
||||
|
||||
// MarshalJSON marshals this TagValue into JSON that only contains runes allowed
|
||||
// in OpenTSDB. It implements json.Marshaler. The runes allowed in OpenTSDB are
|
||||
// all single-byte. This function encodes the arbitrary byte sequence found in
|
||||
// this TagValue in the following way:
|
||||
//
|
||||
// - The string that underlies TagValue is scanned byte by byte.
|
||||
//
|
||||
// - If a byte represents a legal OpenTSDB rune with the exception of '_', that
|
||||
// byte is directly copied to the resulting JSON byte slice.
|
||||
//
|
||||
// - If '_' is encountered, it is replaced by '__'.
|
||||
//
|
||||
// - If ':' is encountered, it is replaced by '_.'.
|
||||
//
|
||||
// - All other bytes are replaced by '_' followed by two bytes containing the
|
||||
// uppercase ASCII representation of their hexadecimal value.
|
||||
//
|
||||
// This encoding allows to save arbitrary Go strings in OpenTSDB. That's
|
||||
// required because Prometheus label values can contain anything, and even
|
||||
// Prometheus metric names may (and often do) contain ':' (which is disallowed
|
||||
// in OpenTSDB strings). The encoding uses '_' as an escape character and
|
||||
// renders a ':' more or less recognizable as '_.'
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// "foo-bar-42" -> "foo-bar-42"
|
||||
//
|
||||
// "foo_bar_42" -> "foo__bar__42"
|
||||
//
|
||||
// "http://example.org:8080" -> "http_.//example.org_.8080"
|
||||
//
|
||||
// "Björn's email: bjoern@soundcloud.com" ->
|
||||
// "Bj_C3_B6rn_27s_20email_._20bjoern_40soundcloud.com"
|
||||
//
|
||||
// "日" -> "_E6_97_A5"
|
||||
func (tv TagValue) MarshalJSON() ([]byte, error) {
|
||||
length := len(tv)
|
||||
// Need at least two more bytes than in tv.
|
||||
result := bytes.NewBuffer(make([]byte, 0, length+2))
|
||||
result.WriteByte('"')
|
||||
for i := 0; i < length; i++ {
|
||||
b := tv[i]
|
||||
switch {
|
||||
case (b >= '-' && b <= '9') || // '-', '.', '/', 0-9
|
||||
(b >= 'A' && b <= 'Z') ||
|
||||
(b >= 'a' && b <= 'z'):
|
||||
result.WriteByte(b)
|
||||
case b == '_':
|
||||
result.WriteString("__")
|
||||
case b == ':':
|
||||
result.WriteString("_.")
|
||||
default:
|
||||
result.WriteString(fmt.Sprintf("_%X", b))
|
||||
}
|
||||
}
|
||||
result.WriteByte('"')
|
||||
return result.Bytes(), nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshals JSON strings coming from OpenTSDB into Go strings
|
||||
// by applying the inverse of what is described for the MarshalJSON method.
|
||||
func (tv *TagValue) UnmarshalJSON(json []byte) error {
|
||||
escapeLevel := 0 // How many bytes after '_'.
|
||||
var parsedByte byte
|
||||
|
||||
// Might need fewer bytes, but let's avoid realloc.
|
||||
result := bytes.NewBuffer(make([]byte, 0, len(json)-2))
|
||||
|
||||
for i, b := range json {
|
||||
if i == 0 {
|
||||
if b != '"' {
|
||||
return fmt.Errorf("expected '\"', got %q", b)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if i == len(json)-1 {
|
||||
if b != '"' {
|
||||
return fmt.Errorf("expected '\"', got %q", b)
|
||||
}
|
||||
break
|
||||
}
|
||||
switch escapeLevel {
|
||||
case 0:
|
||||
if b == '_' {
|
||||
escapeLevel = 1
|
||||
continue
|
||||
}
|
||||
result.WriteByte(b)
|
||||
case 1:
|
||||
switch {
|
||||
case b == '_':
|
||||
result.WriteByte('_')
|
||||
escapeLevel = 0
|
||||
case b == '.':
|
||||
result.WriteByte(':')
|
||||
escapeLevel = 0
|
||||
case b >= '0' && b <= '9':
|
||||
parsedByte = (b - 48) << 4
|
||||
escapeLevel = 2
|
||||
case b >= 'A' && b <= 'F': // A-F
|
||||
parsedByte = (b - 55) << 4
|
||||
escapeLevel = 2
|
||||
default:
|
||||
return fmt.Errorf(
|
||||
"illegal escape sequence at byte %d (%c)",
|
||||
i, b,
|
||||
)
|
||||
}
|
||||
case 2:
|
||||
switch {
|
||||
case b >= '0' && b <= '9':
|
||||
parsedByte += b - 48
|
||||
case b >= 'A' && b <= 'F': // A-F
|
||||
parsedByte += b - 55
|
||||
default:
|
||||
return fmt.Errorf(
|
||||
"illegal escape sequence at byte %d (%c)",
|
||||
i, b,
|
||||
)
|
||||
}
|
||||
result.WriteByte(parsedByte)
|
||||
escapeLevel = 0
|
||||
default:
|
||||
panic("unexpected escape level")
|
||||
}
|
||||
}
|
||||
*tv = TagValue(result.String())
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
// 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 opentsdb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var stringtests = []struct {
|
||||
tv TagValue
|
||||
json []byte
|
||||
}{
|
||||
{TagValue("foo-bar-42"), []byte(`"foo-bar-42"`)},
|
||||
{TagValue("foo_bar_42"), []byte(`"foo__bar__42"`)},
|
||||
{TagValue("http://example.org:8080"), []byte(`"http_.//example.org_.8080"`)},
|
||||
{TagValue("Björn's email: bjoern@soundcloud.com"), []byte(`"Bj_C3_B6rn_27s_20email_._20bjoern_40soundcloud.com"`)},
|
||||
{TagValue("日"), []byte(`"_E6_97_A5"`)},
|
||||
}
|
||||
|
||||
func TestTagValueMarshaling(t *testing.T) {
|
||||
for i, tt := range stringtests {
|
||||
json, err := json.Marshal(tt.tv)
|
||||
if err != nil {
|
||||
t.Errorf("%d. Marshal(%q) returned err: %s", i, tt.tv, err)
|
||||
} else {
|
||||
if !bytes.Equal(json, tt.json) {
|
||||
t.Errorf(
|
||||
"%d. Marshal(%q) => %q, want %q",
|
||||
i, tt.tv, json, tt.json,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTagValueUnMarshaling(t *testing.T) {
|
||||
for i, tt := range stringtests {
|
||||
var tv TagValue
|
||||
err := json.Unmarshal(tt.json, &tv)
|
||||
if err != nil {
|
||||
t.Errorf("%d. Unmarshal(%q, &str) returned err: %s", i, tt.json, err)
|
||||
} else {
|
||||
if tv != tt.tv {
|
||||
t.Errorf(
|
||||
"%d. Unmarshal(%q, &str) => str==%q, want %q",
|
||||
i, tt.json, tv, tt.tv,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -125,12 +125,18 @@ type Options struct {
|
|||
QueueCapacity int
|
||||
ExternalLabels model.LabelSet
|
||||
RelabelConfigs []*config.RelabelConfig
|
||||
// Used for sending HTTP requests to the Alertmanager.
|
||||
Do func(ctx context.Context, client *http.Client, req *http.Request) (*http.Response, error)
|
||||
}
|
||||
|
||||
// New constructs a new Notifier.
|
||||
func New(o *Options) *Notifier {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
if o.Do == nil {
|
||||
o.Do = ctxhttp.Do
|
||||
}
|
||||
|
||||
return &Notifier{
|
||||
queue: make([]*Alert, 0, o.QueueCapacity),
|
||||
ctx: ctx,
|
||||
|
@ -404,7 +410,12 @@ func (n *Notifier) sendAll(alerts ...*Alert) bool {
|
|||
}
|
||||
|
||||
func (n *Notifier) sendOne(ctx context.Context, c *http.Client, url string, b []byte) error {
|
||||
resp, err := ctxhttp.Post(ctx, c, url, contentTypeJSON, bytes.NewReader(b))
|
||||
req, err := http.NewRequest("POST", url, bytes.NewReader(b))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("Content-Type", contentTypeJSON)
|
||||
resp, err := n.opts.Do(ctx, c, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -16,11 +16,14 @@ package notifier
|
|||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/prometheus/common/model"
|
||||
"github.com/prometheus/prometheus/config"
|
||||
"github.com/prometheus/prometheus/pkg/labels"
|
||||
|
@ -187,6 +190,37 @@ func TestHandlerSendAll(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestCustomDo(t *testing.T) {
|
||||
const testURL = "http://testurl.com/"
|
||||
const testBody = "testbody"
|
||||
|
||||
var received bool
|
||||
h := New(&Options{
|
||||
Do: func(ctx context.Context, client *http.Client, req *http.Request) (*http.Response, error) {
|
||||
received = true
|
||||
body, err := ioutil.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to read request body: %v", err)
|
||||
}
|
||||
if string(body) != testBody {
|
||||
t.Fatalf("Unexpected body; want %v, got %v", testBody, string(body))
|
||||
}
|
||||
if req.URL.String() != testURL {
|
||||
t.Fatalf("Unexpected URL; want %v, got %v", testURL, req.URL.String())
|
||||
}
|
||||
return &http.Response{
|
||||
Body: ioutil.NopCloser(nil),
|
||||
}, nil
|
||||
},
|
||||
})
|
||||
|
||||
h.sendOne(context.Background(), nil, testURL, []byte(testBody))
|
||||
|
||||
if !received {
|
||||
t.Fatal("Expected to receive an alert, but didn't")
|
||||
}
|
||||
}
|
||||
|
||||
func TestExternalLabels(t *testing.T) {
|
||||
h := New(&Options{
|
||||
QueueCapacity: 3 * maxBatchSize,
|
||||
|
|
|
@ -55,11 +55,51 @@ var (
|
|||
Name: "queries_concurrent_max",
|
||||
Help: "The max number of concurrent queries.",
|
||||
})
|
||||
queryPrepareTime = prometheus.NewSummary(
|
||||
prometheus.SummaryOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: "query_duration_seconds",
|
||||
Help: "Query timmings",
|
||||
ConstLabels: prometheus.Labels{"slice": "prepare_time"},
|
||||
},
|
||||
)
|
||||
queryInnerEval = prometheus.NewSummary(
|
||||
prometheus.SummaryOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: "query_duration_seconds",
|
||||
Help: "Query timmings",
|
||||
ConstLabels: prometheus.Labels{"slice": "inner_eval"},
|
||||
},
|
||||
)
|
||||
queryResultAppend = prometheus.NewSummary(
|
||||
prometheus.SummaryOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: "query_duration_seconds",
|
||||
Help: "Query timmings",
|
||||
ConstLabels: prometheus.Labels{"slice": "result_append"},
|
||||
},
|
||||
)
|
||||
queryResultSort = prometheus.NewSummary(
|
||||
prometheus.SummaryOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: "query_duration_seconds",
|
||||
Help: "Query timmings",
|
||||
ConstLabels: prometheus.Labels{"slice": "result_sort"},
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
func init() {
|
||||
prometheus.MustRegister(currentQueries)
|
||||
prometheus.MustRegister(maxConcurrentQueries)
|
||||
prometheus.MustRegister(queryPrepareTime)
|
||||
prometheus.MustRegister(queryInnerEval)
|
||||
prometheus.MustRegister(queryResultAppend)
|
||||
prometheus.MustRegister(queryResultSort)
|
||||
}
|
||||
|
||||
// convertibleToInt64 returns true if v does not over-/underflow an int64.
|
||||
|
@ -303,6 +343,8 @@ func (ng *Engine) execEvalStmt(ctx context.Context, query *query, s *EvalStmt) (
|
|||
prepareTimer := query.stats.GetTimer(stats.QueryPreparationTime).Start()
|
||||
querier, err := ng.populateIterators(ctx, s)
|
||||
prepareTimer.Stop()
|
||||
queryPrepareTime.Observe(prepareTimer.ElapsedTime().Seconds())
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -321,6 +363,8 @@ func (ng *Engine) execEvalStmt(ctx context.Context, query *query, s *EvalStmt) (
|
|||
}
|
||||
|
||||
evalTimer.Stop()
|
||||
queryInnerEval.Observe(evalTimer.ElapsedTime().Seconds())
|
||||
|
||||
return val, nil
|
||||
}
|
||||
numSteps := int(s.End.Sub(s.Start) / s.Interval)
|
||||
|
@ -372,6 +416,7 @@ func (ng *Engine) execEvalStmt(ctx context.Context, query *query, s *EvalStmt) (
|
|||
}
|
||||
}
|
||||
evalTimer.Stop()
|
||||
queryInnerEval.Observe(evalTimer.ElapsedTime().Seconds())
|
||||
|
||||
if err := contextDone(ctx, "expression evaluation"); err != nil {
|
||||
return nil, err
|
||||
|
@ -383,6 +428,7 @@ func (ng *Engine) execEvalStmt(ctx context.Context, query *query, s *EvalStmt) (
|
|||
mat = append(mat, ss)
|
||||
}
|
||||
appendTimer.Stop()
|
||||
queryResultAppend.Observe(appendTimer.ElapsedTime().Seconds())
|
||||
|
||||
if err := contextDone(ctx, "expression evaluation"); err != nil {
|
||||
return nil, err
|
||||
|
@ -394,6 +440,7 @@ func (ng *Engine) execEvalStmt(ctx context.Context, query *query, s *EvalStmt) (
|
|||
sort.Sort(mat)
|
||||
sortTimer.Stop()
|
||||
|
||||
queryResultSort.Observe(sortTimer.ElapsedTime().Seconds())
|
||||
return mat, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -141,10 +141,14 @@ func TestPopulateLabels(t *testing.T) {
|
|||
},
|
||||
}
|
||||
for i, c := range cases {
|
||||
in := c.in.Clone()
|
||||
res, orig, err := populateLabels(c.in, c.cfg)
|
||||
if err != nil {
|
||||
t.Fatalf("case %d: %s", i, err)
|
||||
}
|
||||
if !reflect.DeepEqual(c.in, in) {
|
||||
t.Errorf("case %d: input lset was changed was\n\t%+v\n now\n\t%+v", i, in, c.in)
|
||||
}
|
||||
if !reflect.DeepEqual(res, c.res) {
|
||||
t.Errorf("case %d: expected res\n\t%+v\n got\n\t%+v", i, c.res, res)
|
||||
}
|
||||
|
|
|
@ -31,13 +31,14 @@ import (
|
|||
|
||||
// Client allows sending batches of Prometheus samples to an HTTP endpoint.
|
||||
type Client struct {
|
||||
index int // Used to differentiate metrics.
|
||||
url config.URL
|
||||
client *http.Client
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
// NewClient creates a new Client.
|
||||
func NewClient(conf config.RemoteWriteConfig) (*Client, error) {
|
||||
func NewClient(index int, conf *config.RemoteWriteConfig) (*Client, error) {
|
||||
tlsConfig, err := httputil.NewTLSConfig(conf.TLSConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -55,6 +56,7 @@ func NewClient(conf config.RemoteWriteConfig) (*Client, error) {
|
|||
}
|
||||
|
||||
return &Client{
|
||||
index: index,
|
||||
url: *conf.URL,
|
||||
client: httputil.NewClient(rt),
|
||||
timeout: time.Duration(conf.RemoteTimeout),
|
||||
|
@ -114,7 +116,7 @@ func (c *Client) Store(samples model.Samples) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// Name identifies the client as a generic client.
|
||||
// Name identifies the client.
|
||||
func (c Client) Name() string {
|
||||
return "generic"
|
||||
return fmt.Sprintf("%d:%s", c.index, c.url)
|
||||
}
|
||||
|
|
|
@ -17,9 +17,13 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/time/rate"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/common/log"
|
||||
"github.com/prometheus/common/model"
|
||||
"github.com/prometheus/prometheus/config"
|
||||
"github.com/prometheus/prometheus/relabel"
|
||||
)
|
||||
|
||||
// String constants for instrumentation.
|
||||
|
@ -27,6 +31,14 @@ const (
|
|||
namespace = "prometheus"
|
||||
subsystem = "remote_storage"
|
||||
queue = "queue"
|
||||
|
||||
defaultShards = 10
|
||||
defaultMaxSamplesPerSend = 100
|
||||
// The queue capacity is per shard.
|
||||
defaultQueueCapacity = 100 * 1024 / defaultShards
|
||||
defaultBatchSendDeadline = 5 * time.Second
|
||||
logRateLimit = 0.1 // Limit to 1 log event every 10s
|
||||
logBurst = 10
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -105,35 +117,41 @@ type StorageClient interface {
|
|||
Name() string
|
||||
}
|
||||
|
||||
type StorageQueueManagerConfig struct {
|
||||
// QueueManagerConfig configures a storage queue.
|
||||
type QueueManagerConfig struct {
|
||||
QueueCapacity int // Number of samples to buffer per shard before we start dropping them.
|
||||
Shards int // Number of shards, i.e. amount of concurrency.
|
||||
MaxSamplesPerSend int // Maximum number of samples per send.
|
||||
BatchSendDeadline time.Duration // Maximum time sample will wait in buffer.
|
||||
ExternalLabels model.LabelSet
|
||||
RelabelConfigs []*config.RelabelConfig
|
||||
Client StorageClient
|
||||
}
|
||||
|
||||
var defaultConfig = StorageQueueManagerConfig{
|
||||
QueueCapacity: 100 * 1024 / 10,
|
||||
Shards: 10,
|
||||
MaxSamplesPerSend: 100,
|
||||
BatchSendDeadline: 5 * time.Second,
|
||||
}
|
||||
|
||||
// StorageQueueManager manages a queue of samples to be sent to the Storage
|
||||
// QueueManager manages a queue of samples to be sent to the Storage
|
||||
// indicated by the provided StorageClient.
|
||||
type StorageQueueManager struct {
|
||||
cfg StorageQueueManagerConfig
|
||||
tsdb StorageClient
|
||||
shards []chan *model.Sample
|
||||
wg sync.WaitGroup
|
||||
done chan struct{}
|
||||
queueName string
|
||||
type QueueManager struct {
|
||||
cfg QueueManagerConfig
|
||||
shards []chan *model.Sample
|
||||
wg sync.WaitGroup
|
||||
done chan struct{}
|
||||
queueName string
|
||||
logLimiter *rate.Limiter
|
||||
}
|
||||
|
||||
// NewStorageQueueManager builds a new StorageQueueManager.
|
||||
func NewStorageQueueManager(tsdb StorageClient, cfg *StorageQueueManagerConfig) *StorageQueueManager {
|
||||
if cfg == nil {
|
||||
cfg = &defaultConfig
|
||||
// NewQueueManager builds a new QueueManager.
|
||||
func NewQueueManager(cfg QueueManagerConfig) *QueueManager {
|
||||
if cfg.QueueCapacity == 0 {
|
||||
cfg.QueueCapacity = defaultQueueCapacity
|
||||
}
|
||||
if cfg.Shards == 0 {
|
||||
cfg.Shards = defaultShards
|
||||
}
|
||||
if cfg.MaxSamplesPerSend == 0 {
|
||||
cfg.MaxSamplesPerSend = defaultMaxSamplesPerSend
|
||||
}
|
||||
if cfg.BatchSendDeadline == 0 {
|
||||
cfg.BatchSendDeadline = defaultBatchSendDeadline
|
||||
}
|
||||
|
||||
shards := make([]chan *model.Sample, cfg.Shards)
|
||||
|
@ -141,12 +159,12 @@ func NewStorageQueueManager(tsdb StorageClient, cfg *StorageQueueManagerConfig)
|
|||
shards[i] = make(chan *model.Sample, cfg.QueueCapacity)
|
||||
}
|
||||
|
||||
t := &StorageQueueManager{
|
||||
cfg: *cfg,
|
||||
tsdb: tsdb,
|
||||
shards: shards,
|
||||
done: make(chan struct{}),
|
||||
queueName: tsdb.Name(),
|
||||
t := &QueueManager{
|
||||
cfg: cfg,
|
||||
shards: shards,
|
||||
done: make(chan struct{}),
|
||||
queueName: cfg.Client.Name(),
|
||||
logLimiter: rate.NewLimiter(logRateLimit, logBurst),
|
||||
}
|
||||
|
||||
queueCapacity.WithLabelValues(t.queueName).Set(float64(t.cfg.QueueCapacity))
|
||||
|
@ -157,16 +175,35 @@ func NewStorageQueueManager(tsdb StorageClient, cfg *StorageQueueManagerConfig)
|
|||
// Append queues a sample to be sent to the remote storage. It drops the
|
||||
// sample on the floor if the queue is full.
|
||||
// Always returns nil.
|
||||
func (t *StorageQueueManager) Append(s *model.Sample) error {
|
||||
fp := s.Metric.FastFingerprint()
|
||||
func (t *QueueManager) Append(s *model.Sample) error {
|
||||
var snew model.Sample
|
||||
snew = *s
|
||||
snew.Metric = s.Metric.Clone()
|
||||
|
||||
for ln, lv := range t.cfg.ExternalLabels {
|
||||
if _, ok := s.Metric[ln]; !ok {
|
||||
snew.Metric[ln] = lv
|
||||
}
|
||||
}
|
||||
|
||||
snew.Metric = model.Metric(
|
||||
relabel.Process(model.LabelSet(snew.Metric), t.cfg.RelabelConfigs...))
|
||||
|
||||
if snew.Metric == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
fp := snew.Metric.FastFingerprint()
|
||||
shard := uint64(fp) % uint64(t.cfg.Shards)
|
||||
|
||||
select {
|
||||
case t.shards[shard] <- s:
|
||||
case t.shards[shard] <- &snew:
|
||||
queueLength.WithLabelValues(t.queueName).Inc()
|
||||
default:
|
||||
droppedSamplesTotal.WithLabelValues(t.queueName).Inc()
|
||||
log.Warn("Remote storage queue full, discarding sample.")
|
||||
if t.logLimiter.Allow() {
|
||||
log.Warn("Remote storage queue full, discarding sample. Multiple subsequent messages of this kind may be suppressed.")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -174,13 +211,13 @@ func (t *StorageQueueManager) Append(s *model.Sample) error {
|
|||
// NeedsThrottling implements storage.SampleAppender. It will always return
|
||||
// false as a remote storage drops samples on the floor if backlogging instead
|
||||
// of asking for throttling.
|
||||
func (*StorageQueueManager) NeedsThrottling() bool {
|
||||
func (*QueueManager) NeedsThrottling() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Start the queue manager sending samples to the remote storage.
|
||||
// Does not block.
|
||||
func (t *StorageQueueManager) Start() {
|
||||
func (t *QueueManager) Start() {
|
||||
for i := 0; i < t.cfg.Shards; i++ {
|
||||
go t.runShard(i)
|
||||
}
|
||||
|
@ -188,7 +225,7 @@ func (t *StorageQueueManager) Start() {
|
|||
|
||||
// Stop stops sending samples to the remote storage and waits for pending
|
||||
// sends to complete.
|
||||
func (t *StorageQueueManager) Stop() {
|
||||
func (t *QueueManager) Stop() {
|
||||
log.Infof("Stopping remote storage...")
|
||||
for _, shard := range t.shards {
|
||||
close(shard)
|
||||
|
@ -197,7 +234,7 @@ func (t *StorageQueueManager) Stop() {
|
|||
log.Info("Remote storage stopped.")
|
||||
}
|
||||
|
||||
func (t *StorageQueueManager) runShard(i int) {
|
||||
func (t *QueueManager) runShard(i int) {
|
||||
defer t.wg.Done()
|
||||
shard := t.shards[i]
|
||||
|
||||
|
@ -234,12 +271,12 @@ func (t *StorageQueueManager) runShard(i int) {
|
|||
}
|
||||
}
|
||||
|
||||
func (t *StorageQueueManager) sendSamples(s model.Samples) {
|
||||
func (t *QueueManager) sendSamples(s model.Samples) {
|
||||
// Samples are sent to the remote storage on a best-effort basis. If a
|
||||
// sample isn't sent correctly the first time, it's simply dropped on the
|
||||
// floor.
|
||||
begin := time.Now()
|
||||
err := t.tsdb.Store(s)
|
||||
err := t.cfg.Client.Store(s)
|
||||
duration := time.Since(begin).Seconds()
|
||||
|
||||
if err != nil {
|
||||
|
|
|
@ -81,9 +81,7 @@ func (c *TestStorageClient) Name() string {
|
|||
func TestSampleDelivery(t *testing.T) {
|
||||
// Let's create an even number of send batches so we don't run into the
|
||||
// batch timeout case.
|
||||
cfg := defaultConfig
|
||||
n := cfg.QueueCapacity * 2
|
||||
cfg.Shards = 1
|
||||
n := defaultQueueCapacity * 2
|
||||
|
||||
samples := make(model.Samples, 0, n)
|
||||
for i := 0; i < n; i++ {
|
||||
|
@ -98,7 +96,11 @@ func TestSampleDelivery(t *testing.T) {
|
|||
|
||||
c := NewTestStorageClient()
|
||||
c.expectSamples(samples[:len(samples)/2])
|
||||
m := NewStorageQueueManager(c, &cfg)
|
||||
|
||||
m := NewQueueManager(QueueManagerConfig{
|
||||
Client: c,
|
||||
Shards: 1,
|
||||
})
|
||||
|
||||
// These should be received by the client.
|
||||
for _, s := range samples[:len(samples)/2] {
|
||||
|
@ -115,11 +117,8 @@ func TestSampleDelivery(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestSampleDeliveryOrder(t *testing.T) {
|
||||
cfg := defaultConfig
|
||||
ts := 10
|
||||
n := cfg.MaxSamplesPerSend * ts
|
||||
// Ensure we don't drop samples in this test.
|
||||
cfg.QueueCapacity = n
|
||||
n := defaultMaxSamplesPerSend * ts
|
||||
|
||||
samples := make(model.Samples, 0, n)
|
||||
for i := 0; i < n; i++ {
|
||||
|
@ -135,7 +134,11 @@ func TestSampleDeliveryOrder(t *testing.T) {
|
|||
|
||||
c := NewTestStorageClient()
|
||||
c.expectSamples(samples)
|
||||
m := NewStorageQueueManager(c, &cfg)
|
||||
m := NewQueueManager(QueueManagerConfig{
|
||||
Client: c,
|
||||
// Ensure we don't drop samples in this test.
|
||||
QueueCapacity: n,
|
||||
})
|
||||
|
||||
// These should be received by the client.
|
||||
for _, s := range samples {
|
||||
|
@ -181,7 +184,7 @@ func (c *TestBlockingStorageClient) Name() string {
|
|||
return "testblockingstorageclient"
|
||||
}
|
||||
|
||||
func (t *StorageQueueManager) queueLen() int {
|
||||
func (t *QueueManager) queueLen() int {
|
||||
queueLength := 0
|
||||
for _, shard := range t.shards {
|
||||
queueLength += len(shard)
|
||||
|
@ -194,9 +197,7 @@ func TestSpawnNotMoreThanMaxConcurrentSendsGoroutines(t *testing.T) {
|
|||
// `MaxSamplesPerSend*Shards` samples should be consumed by the
|
||||
// per-shard goroutines, and then another `MaxSamplesPerSend`
|
||||
// should be left on the queue.
|
||||
cfg := defaultConfig
|
||||
n := cfg.MaxSamplesPerSend*cfg.Shards + cfg.MaxSamplesPerSend
|
||||
cfg.QueueCapacity = n
|
||||
n := defaultMaxSamplesPerSend*defaultShards + defaultMaxSamplesPerSend
|
||||
|
||||
samples := make(model.Samples, 0, n)
|
||||
for i := 0; i < n; i++ {
|
||||
|
@ -210,7 +211,10 @@ func TestSpawnNotMoreThanMaxConcurrentSendsGoroutines(t *testing.T) {
|
|||
}
|
||||
|
||||
c := NewTestBlockedStorageClient()
|
||||
m := NewStorageQueueManager(c, &cfg)
|
||||
m := NewQueueManager(QueueManagerConfig{
|
||||
Client: c,
|
||||
QueueCapacity: n,
|
||||
})
|
||||
|
||||
m.Start()
|
||||
|
||||
|
@ -239,14 +243,14 @@ func TestSpawnNotMoreThanMaxConcurrentSendsGoroutines(t *testing.T) {
|
|||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
|
||||
if m.queueLen() != cfg.MaxSamplesPerSend {
|
||||
t.Fatalf("Failed to drain StorageQueueManager queue, %d elements left",
|
||||
if m.queueLen() != defaultMaxSamplesPerSend {
|
||||
t.Fatalf("Failed to drain QueueManager queue, %d elements left",
|
||||
m.queueLen(),
|
||||
)
|
||||
}
|
||||
|
||||
numCalls := c.NumCalls()
|
||||
if numCalls != uint64(cfg.Shards) {
|
||||
t.Errorf("Saw %d concurrent sends, expected %d", numCalls, cfg.Shards)
|
||||
if numCalls != uint64(defaultShards) {
|
||||
t.Errorf("Saw %d concurrent sends, expected %d", numCalls, defaultShards)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
<<<<<<< HEAD
|
||||
// Copyright 2016 The Prometheus Authors
|
||||
=======
|
||||
// Copyright 2017 The Prometheus Authors
|
||||
>>>>>>> master
|
||||
// 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
|
||||
|
@ -19,11 +23,15 @@ import (
|
|||
"github.com/prometheus/common/model"
|
||||
|
||||
"github.com/prometheus/prometheus/config"
|
||||
<<<<<<< HEAD
|
||||
"github.com/prometheus/prometheus/relabel"
|
||||
=======
|
||||
>>>>>>> master
|
||||
)
|
||||
|
||||
// Storage collects multiple remote storage queues.
|
||||
// Storage allows queueing samples for remote writes.
|
||||
type Storage struct {
|
||||
<<<<<<< HEAD
|
||||
mtx sync.RWMutex
|
||||
externalLabels model.LabelSet
|
||||
conf config.RemoteWriteConfig
|
||||
|
@ -34,6 +42,10 @@ type Storage struct {
|
|||
// New returns a new remote Storage.
|
||||
func New() *Storage {
|
||||
return &Storage{}
|
||||
=======
|
||||
mtx sync.RWMutex
|
||||
queues []*QueueManager
|
||||
>>>>>>> master
|
||||
}
|
||||
|
||||
// ApplyConfig updates the state as the new config requires.
|
||||
|
@ -41,6 +53,7 @@ func (s *Storage) ApplyConfig(conf *config.Config) error {
|
|||
s.mtx.Lock()
|
||||
defer s.mtx.Unlock()
|
||||
|
||||
<<<<<<< HEAD
|
||||
// TODO: we should only stop & recreate queues which have changes,
|
||||
// as this can be quite disruptive.
|
||||
var newQueue *StorageQueueManager
|
||||
|
@ -61,6 +74,30 @@ func (s *Storage) ApplyConfig(conf *config.Config) error {
|
|||
s.externalLabels = conf.GlobalConfig.ExternalLabels
|
||||
if s.queue != nil {
|
||||
s.queue.Start()
|
||||
=======
|
||||
newQueues := []*QueueManager{}
|
||||
// TODO: we should only stop & recreate queues which have changes,
|
||||
// as this can be quite disruptive.
|
||||
for i, rwConf := range conf.RemoteWriteConfigs {
|
||||
c, err := NewClient(i, rwConf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newQueues = append(newQueues, NewQueueManager(QueueManagerConfig{
|
||||
Client: c,
|
||||
ExternalLabels: conf.GlobalConfig.ExternalLabels,
|
||||
RelabelConfigs: rwConf.WriteRelabelConfigs,
|
||||
}))
|
||||
}
|
||||
|
||||
for _, q := range s.queues {
|
||||
q.Stop()
|
||||
}
|
||||
|
||||
s.queues = newQueues
|
||||
for _, q := range s.queues {
|
||||
q.Start()
|
||||
>>>>>>> master
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -77,6 +114,7 @@ func (s *Storage) Append(smpl *model.Sample) error {
|
|||
s.mtx.RLock()
|
||||
defer s.mtx.RUnlock()
|
||||
|
||||
<<<<<<< HEAD
|
||||
if s.queue == nil {
|
||||
return nil
|
||||
}
|
||||
|
@ -97,6 +135,11 @@ func (s *Storage) Append(smpl *model.Sample) error {
|
|||
return nil
|
||||
}
|
||||
s.queue.Append(&snew)
|
||||
=======
|
||||
for _, q := range s.queues {
|
||||
q.Append(smpl)
|
||||
}
|
||||
>>>>>>> master
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
9
vendor/github.com/fabxc/tsdb/block.go
generated
vendored
9
vendor/github.com/fabxc/tsdb/block.go
generated
vendored
|
@ -94,7 +94,11 @@ func readMetaFile(dir string) (*BlockMeta, error) {
|
|||
}
|
||||
|
||||
func writeMetaFile(dir string, meta *BlockMeta) error {
|
||||
f, err := os.Create(filepath.Join(dir, metaFilename))
|
||||
// Make any changes to the file appear atomic.
|
||||
path := filepath.Join(dir, metaFilename)
|
||||
tmp := path + ".tmp"
|
||||
|
||||
f, err := os.Create(tmp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -108,8 +112,7 @@ func writeMetaFile(dir string, meta *BlockMeta) error {
|
|||
if err := f.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return renameFile(tmp, path)
|
||||
}
|
||||
|
||||
func newPersistedBlock(dir string) (*persistedBlock, error) {
|
||||
|
|
2
vendor/github.com/fabxc/tsdb/compact.go
generated
vendored
2
vendor/github.com/fabxc/tsdb/compact.go
generated
vendored
|
@ -406,7 +406,7 @@ func (c *compactionMerger) At() (labels.Labels, []ChunkMeta) {
|
|||
return c.l, c.c
|
||||
}
|
||||
|
||||
func renameDir(from, to string) error {
|
||||
func renameFile(from, to string) error {
|
||||
if err := os.RemoveAll(to); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
10
vendor/github.com/fabxc/tsdb/db.go
generated
vendored
10
vendor/github.com/fabxc/tsdb/db.go
generated
vendored
|
@ -187,8 +187,16 @@ func Open(dir string, l log.Logger, r prometheus.Registerer, opts *Options) (db
|
|||
func (db *DB) run() {
|
||||
defer close(db.donec)
|
||||
|
||||
tick := time.NewTicker(30 * time.Second)
|
||||
defer tick.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-tick.C:
|
||||
select {
|
||||
case db.compactc <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
case <-db.compactc:
|
||||
db.metrics.compactionsTriggered.Inc()
|
||||
|
||||
|
@ -292,7 +300,7 @@ func (db *DB) compact(i, j int) error {
|
|||
}
|
||||
}
|
||||
|
||||
if err := renameDir(tmpdir, dir); err != nil {
|
||||
if err := renameFile(tmpdir, dir); err != nil {
|
||||
return errors.Wrap(err, "rename dir")
|
||||
}
|
||||
pb.dir = dir
|
||||
|
|
5
vendor/github.com/miekg/dns/README.md
generated
vendored
5
vendor/github.com/miekg/dns/README.md
generated
vendored
|
@ -1,4 +1,5 @@
|
|||
[![Build Status](https://travis-ci.org/miekg/dns.svg?branch=master)](https://travis-ci.org/miekg/dns) [![](https://godoc.org/github.com/miekg/dns?status.svg)](https://godoc.org/github.com/miekg/dns)
|
||||
[![Build Status](https://travis-ci.org/miekg/dns.svg?branch=master)](https://travis-ci.org/miekg/dns)
|
||||
[![](https://godoc.org/github.com/miekg/dns?status.svg)](https://godoc.org/github.com/miekg/dns)
|
||||
|
||||
# Alternative (more granular) approach to a DNS library
|
||||
|
||||
|
@ -12,7 +13,7 @@ can build servers and resolvers with it.
|
|||
|
||||
We try to keep the "master" branch as sane as possible and at the bleeding edge
|
||||
of standards, avoiding breaking changes wherever reasonable. We support the last
|
||||
two versions of Go, currently: 1.5 and 1.6.
|
||||
two versions of Go, currently: 1.6 and 1.7.
|
||||
|
||||
# Goals
|
||||
|
||||
|
|
14
vendor/github.com/miekg/dns/client.go
generated
vendored
14
vendor/github.com/miekg/dns/client.go
generated
vendored
|
@ -300,6 +300,18 @@ func tcpMsgLen(t io.Reader) (int, error) {
|
|||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// As seen with my local router/switch, retursn 1 byte on the above read,
|
||||
// resulting a a ShortRead. Just write it out (instead of loop) and read the
|
||||
// other byte.
|
||||
if n == 1 {
|
||||
n1, err := t.Read(p[1:])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
n += n1
|
||||
}
|
||||
|
||||
if n != 2 {
|
||||
return 0, ErrShortRead
|
||||
}
|
||||
|
@ -400,7 +412,7 @@ func (co *Conn) Write(p []byte) (n int, err error) {
|
|||
n, err := io.Copy(w, bytes.NewReader(p))
|
||||
return int(n), err
|
||||
}
|
||||
n, err = co.Conn.(*net.UDPConn).Write(p)
|
||||
n, err = co.Conn.Write(p)
|
||||
return n, err
|
||||
}
|
||||
|
||||
|
|
32
vendor/github.com/miekg/dns/clientconfig.go
generated
vendored
32
vendor/github.com/miekg/dns/clientconfig.go
generated
vendored
|
@ -97,3 +97,35 @@ func ClientConfigFromFile(resolvconf string) (*ClientConfig, error) {
|
|||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// NameList returns all of the names that should be queried based on the
|
||||
// config. It is based off of go's net/dns name building, but it does not
|
||||
// check the length of the resulting names.
|
||||
func (c *ClientConfig) NameList(name string) []string {
|
||||
// if this domain is already fully qualified, no append needed.
|
||||
if IsFqdn(name) {
|
||||
return []string{name}
|
||||
}
|
||||
|
||||
// Check to see if the name has more labels than Ndots. Do this before making
|
||||
// the domain fully qualified.
|
||||
hasNdots := CountLabel(name) > c.Ndots
|
||||
// Make the domain fully qualified.
|
||||
name = Fqdn(name)
|
||||
|
||||
// Make a list of names based off search.
|
||||
names := []string{}
|
||||
|
||||
// If name has enough dots, try that first.
|
||||
if hasNdots {
|
||||
names = append(names, name)
|
||||
}
|
||||
for _, s := range c.Search {
|
||||
names = append(names, Fqdn(name+s))
|
||||
}
|
||||
// If we didn't have enough dots, try after suffixes.
|
||||
if !hasNdots {
|
||||
names = append(names, name)
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
|
9
vendor/github.com/miekg/dns/dane.go
generated
vendored
9
vendor/github.com/miekg/dns/dane.go
generated
vendored
|
@ -6,7 +6,6 @@ import (
|
|||
"crypto/x509"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
// CertificateToDANE converts a certificate to a hex string as used in the TLSA or SMIMEA records.
|
||||
|
@ -23,20 +22,20 @@ func CertificateToDANE(selector, matchingType uint8, cert *x509.Certificate) (st
|
|||
h := sha256.New()
|
||||
switch selector {
|
||||
case 0:
|
||||
io.WriteString(h, string(cert.Raw))
|
||||
h.Write(cert.Raw)
|
||||
return hex.EncodeToString(h.Sum(nil)), nil
|
||||
case 1:
|
||||
io.WriteString(h, string(cert.RawSubjectPublicKeyInfo))
|
||||
h.Write(cert.RawSubjectPublicKeyInfo)
|
||||
return hex.EncodeToString(h.Sum(nil)), nil
|
||||
}
|
||||
case 2:
|
||||
h := sha512.New()
|
||||
switch selector {
|
||||
case 0:
|
||||
io.WriteString(h, string(cert.Raw))
|
||||
h.Write(cert.Raw)
|
||||
return hex.EncodeToString(h.Sum(nil)), nil
|
||||
case 1:
|
||||
io.WriteString(h, string(cert.RawSubjectPublicKeyInfo))
|
||||
h.Write(cert.RawSubjectPublicKeyInfo)
|
||||
return hex.EncodeToString(h.Sum(nil)), nil
|
||||
}
|
||||
}
|
||||
|
|
4
vendor/github.com/miekg/dns/defaults.go
generated
vendored
4
vendor/github.com/miekg/dns/defaults.go
generated
vendored
|
@ -102,11 +102,11 @@ func (dns *Msg) SetAxfr(z string) *Msg {
|
|||
// SetTsig appends a TSIG RR to the message.
|
||||
// This is only a skeleton TSIG RR that is added as the last RR in the
|
||||
// additional section. The Tsig is calculated when the message is being send.
|
||||
func (dns *Msg) SetTsig(z, algo string, fudge, timesigned int64) *Msg {
|
||||
func (dns *Msg) SetTsig(z, algo string, fudge uint16, timesigned int64) *Msg {
|
||||
t := new(TSIG)
|
||||
t.Hdr = RR_Header{z, TypeTSIG, ClassANY, 0, 0}
|
||||
t.Algorithm = algo
|
||||
t.Fudge = 300
|
||||
t.Fudge = fudge
|
||||
t.TimeSigned = uint64(timesigned)
|
||||
t.OrigId = dns.Id
|
||||
dns.Extra = append(dns.Extra, t)
|
||||
|
|
11
vendor/github.com/miekg/dns/dnssec.go
generated
vendored
11
vendor/github.com/miekg/dns/dnssec.go
generated
vendored
|
@ -208,9 +208,6 @@ func (k *DNSKEY) ToDS(h uint8) *DS {
|
|||
// "|" denotes concatenation
|
||||
// DNSKEY RDATA = Flags | Protocol | Algorithm | Public Key.
|
||||
|
||||
// digest buffer
|
||||
digest := append(owner, wire...) // another copy
|
||||
|
||||
var hash crypto.Hash
|
||||
switch h {
|
||||
case SHA1:
|
||||
|
@ -226,7 +223,8 @@ func (k *DNSKEY) ToDS(h uint8) *DS {
|
|||
}
|
||||
|
||||
s := hash.New()
|
||||
s.Write(digest)
|
||||
s.Write(owner)
|
||||
s.Write(wire)
|
||||
ds.Digest = hex.EncodeToString(s.Sum(nil))
|
||||
return ds
|
||||
}
|
||||
|
@ -297,7 +295,6 @@ func (rr *RRSIG) Sign(k crypto.Signer, rrset []RR) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
signdata = append(signdata, wire...)
|
||||
|
||||
hash, ok := AlgorithmToHash[rr.Algorithm]
|
||||
if !ok {
|
||||
|
@ -306,6 +303,7 @@ func (rr *RRSIG) Sign(k crypto.Signer, rrset []RR) error {
|
|||
|
||||
h := hash.New()
|
||||
h.Write(signdata)
|
||||
h.Write(wire)
|
||||
|
||||
signature, err := sign(k, h.Sum(nil), hash, rr.Algorithm)
|
||||
if err != nil {
|
||||
|
@ -415,7 +413,6 @@ func (rr *RRSIG) Verify(k *DNSKEY, rrset []RR) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
signeddata = append(signeddata, wire...)
|
||||
|
||||
sigbuf := rr.sigBuf() // Get the binary signature data
|
||||
if rr.Algorithm == PRIVATEDNS { // PRIVATEOID
|
||||
|
@ -438,6 +435,7 @@ func (rr *RRSIG) Verify(k *DNSKEY, rrset []RR) error {
|
|||
|
||||
h := hash.New()
|
||||
h.Write(signeddata)
|
||||
h.Write(wire)
|
||||
return rsa.VerifyPKCS1v15(pubkey, hash, h.Sum(nil), sigbuf)
|
||||
|
||||
case ECDSAP256SHA256, ECDSAP384SHA384:
|
||||
|
@ -452,6 +450,7 @@ func (rr *RRSIG) Verify(k *DNSKEY, rrset []RR) error {
|
|||
|
||||
h := hash.New()
|
||||
h.Write(signeddata)
|
||||
h.Write(wire)
|
||||
if ecdsa.Verify(pubkey, h.Sum(nil), r, s) {
|
||||
return nil
|
||||
}
|
||||
|
|
16
vendor/github.com/miekg/dns/dnssec_keygen.go
generated
vendored
16
vendor/github.com/miekg/dns/dnssec_keygen.go
generated
vendored
|
@ -121,17 +121,17 @@ func (k *DNSKEY) setPublicKeyDSA(_Q, _P, _G, _Y *big.Int) bool {
|
|||
// RFC 3110: Section 2. RSA Public KEY Resource Records
|
||||
func exponentToBuf(_E int) []byte {
|
||||
var buf []byte
|
||||
i := big.NewInt(int64(_E))
|
||||
if len(i.Bytes()) < 256 {
|
||||
buf = make([]byte, 1)
|
||||
buf[0] = uint8(len(i.Bytes()))
|
||||
i := big.NewInt(int64(_E)).Bytes()
|
||||
if len(i) < 256 {
|
||||
buf = make([]byte, 1, 1+len(i))
|
||||
buf[0] = uint8(len(i))
|
||||
} else {
|
||||
buf = make([]byte, 3)
|
||||
buf = make([]byte, 3, 3+len(i))
|
||||
buf[0] = 0
|
||||
buf[1] = uint8(len(i.Bytes()) >> 8)
|
||||
buf[2] = uint8(len(i.Bytes()))
|
||||
buf[1] = uint8(len(i) >> 8)
|
||||
buf[2] = uint8(len(i))
|
||||
}
|
||||
buf = append(buf, i.Bytes()...)
|
||||
buf = append(buf, i...)
|
||||
return buf
|
||||
}
|
||||
|
||||
|
|
83
vendor/github.com/miekg/dns/edns.go
generated
vendored
83
vendor/github.com/miekg/dns/edns.go
generated
vendored
|
@ -4,25 +4,27 @@ import (
|
|||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// EDNS0 Option codes.
|
||||
const (
|
||||
EDNS0LLQ = 0x1 // long lived queries: http://tools.ietf.org/html/draft-sekar-dns-llq-01
|
||||
EDNS0UL = 0x2 // update lease draft: http://files.dns-sd.org/draft-sekar-dns-ul.txt
|
||||
EDNS0NSID = 0x3 // nsid (RFC5001)
|
||||
EDNS0DAU = 0x5 // DNSSEC Algorithm Understood
|
||||
EDNS0DHU = 0x6 // DS Hash Understood
|
||||
EDNS0N3U = 0x7 // NSEC3 Hash Understood
|
||||
EDNS0SUBNET = 0x8 // client-subnet (RFC6891)
|
||||
EDNS0EXPIRE = 0x9 // EDNS0 expire
|
||||
EDNS0COOKIE = 0xa // EDNS0 Cookie
|
||||
EDNS0SUBNETDRAFT = 0x50fa // Don't use! Use EDNS0SUBNET
|
||||
EDNS0LOCALSTART = 0xFDE9 // Beginning of range reserved for local/experimental use (RFC6891)
|
||||
EDNS0LOCALEND = 0xFFFE // End of range reserved for local/experimental use (RFC6891)
|
||||
_DO = 1 << 15 // dnssec ok
|
||||
EDNS0LLQ = 0x1 // long lived queries: http://tools.ietf.org/html/draft-sekar-dns-llq-01
|
||||
EDNS0UL = 0x2 // update lease draft: http://files.dns-sd.org/draft-sekar-dns-ul.txt
|
||||
EDNS0NSID = 0x3 // nsid (RFC5001)
|
||||
EDNS0DAU = 0x5 // DNSSEC Algorithm Understood
|
||||
EDNS0DHU = 0x6 // DS Hash Understood
|
||||
EDNS0N3U = 0x7 // NSEC3 Hash Understood
|
||||
EDNS0SUBNET = 0x8 // client-subnet (RFC6891)
|
||||
EDNS0EXPIRE = 0x9 // EDNS0 expire
|
||||
EDNS0COOKIE = 0xa // EDNS0 Cookie
|
||||
EDNS0TCPKEEPALIVE = 0xb // EDNS0 tcp keep alive (RFC7828)
|
||||
EDNS0SUBNETDRAFT = 0x50fa // Don't use! Use EDNS0SUBNET
|
||||
EDNS0LOCALSTART = 0xFDE9 // Beginning of range reserved for local/experimental use (RFC6891)
|
||||
EDNS0LOCALEND = 0xFFFE // End of range reserved for local/experimental use (RFC6891)
|
||||
_DO = 1 << 15 // dnssec ok
|
||||
)
|
||||
|
||||
// OPT is the EDNS0 RR appended to messages to convey extra (meta) information.
|
||||
|
@ -195,7 +197,7 @@ func (e *EDNS0_NSID) String() string { return string(e.Nsid) }
|
|||
// e := new(dns.EDNS0_SUBNET)
|
||||
// e.Code = dns.EDNS0SUBNET
|
||||
// e.Family = 1 // 1 for IPv4 source address, 2 for IPv6
|
||||
// e.NetMask = 32 // 32 for IPV4, 128 for IPv6
|
||||
// e.SourceNetMask = 32 // 32 for IPV4, 128 for IPv6
|
||||
// e.SourceScope = 0
|
||||
// e.Address = net.ParseIP("127.0.0.1").To4() // for IPv4
|
||||
// // e.Address = net.ParseIP("2001:7b8:32a::2") // for IPV6
|
||||
|
@ -540,3 +542,56 @@ func (e *EDNS0_LOCAL) unpack(b []byte) error {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type EDNS0_TCP_KEEPALIVE struct {
|
||||
Code uint16 // Always EDNSTCPKEEPALIVE
|
||||
Length uint16 // the value 0 if the TIMEOUT is omitted, the value 2 if it is present;
|
||||
Timeout uint16 // an idle timeout value for the TCP connection, specified in units of 100 milliseconds, encoded in network byte order.
|
||||
}
|
||||
|
||||
func (e *EDNS0_TCP_KEEPALIVE) Option() uint16 {
|
||||
return EDNS0TCPKEEPALIVE
|
||||
}
|
||||
|
||||
func (e *EDNS0_TCP_KEEPALIVE) pack() ([]byte, error) {
|
||||
if e.Timeout != 0 && e.Length != 2 {
|
||||
return nil, errors.New("dns: timeout specified but length is not 2")
|
||||
}
|
||||
if e.Timeout == 0 && e.Length != 0 {
|
||||
return nil, errors.New("dns: timeout not specified but length is not 0")
|
||||
}
|
||||
b := make([]byte, 4+e.Length)
|
||||
binary.BigEndian.PutUint16(b[0:], e.Code)
|
||||
binary.BigEndian.PutUint16(b[2:], e.Length)
|
||||
if e.Length == 2 {
|
||||
binary.BigEndian.PutUint16(b[4:], e.Timeout)
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (e *EDNS0_TCP_KEEPALIVE) unpack(b []byte) error {
|
||||
if len(b) < 4 {
|
||||
return ErrBuf
|
||||
}
|
||||
e.Length = binary.BigEndian.Uint16(b[2:4])
|
||||
if e.Length != 0 && e.Length != 2 {
|
||||
return errors.New("dns: length mismatch, want 0/2 but got " + strconv.FormatUint(uint64(e.Length), 10))
|
||||
}
|
||||
if e.Length == 2 {
|
||||
if len(b) < 6 {
|
||||
return ErrBuf
|
||||
}
|
||||
e.Timeout = binary.BigEndian.Uint16(b[4:6])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *EDNS0_TCP_KEEPALIVE) String() (s string) {
|
||||
s = "use tcp keep-alive"
|
||||
if e.Length == 0 {
|
||||
s += ", timeout omitted"
|
||||
} else {
|
||||
s += fmt.Sprintf(", timeout %dms", e.Timeout*100)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
82
vendor/github.com/miekg/dns/msg.go
generated
vendored
82
vendor/github.com/miekg/dns/msg.go
generated
vendored
|
@ -16,22 +16,9 @@ import (
|
|||
"math/big"
|
||||
"math/rand"
|
||||
"strconv"
|
||||
"sync"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Initialize default math/rand source using crypto/rand to provide better
|
||||
// security without the performance trade-off.
|
||||
buf := make([]byte, 8)
|
||||
_, err := crand.Read(buf)
|
||||
if err != nil {
|
||||
// Failed to read from cryptographic source, fallback to default initial
|
||||
// seed (1) by returning early
|
||||
return
|
||||
}
|
||||
seed := binary.BigEndian.Uint64(buf)
|
||||
rand.Seed(int64(seed))
|
||||
}
|
||||
|
||||
const maxCompressionOffset = 2 << 13 // We have 14 bits for the compression pointer
|
||||
|
||||
var (
|
||||
|
@ -66,11 +53,45 @@ var (
|
|||
// dns.Id = func() uint16 { return 3 }
|
||||
var Id func() uint16 = id
|
||||
|
||||
var (
|
||||
idLock sync.Mutex
|
||||
idRand *rand.Rand
|
||||
)
|
||||
|
||||
// id returns a 16 bits random number to be used as a
|
||||
// message id. The random provided should be good enough.
|
||||
func id() uint16 {
|
||||
id32 := rand.Uint32()
|
||||
return uint16(id32)
|
||||
idLock.Lock()
|
||||
|
||||
if idRand == nil {
|
||||
// This (partially) works around
|
||||
// https://github.com/golang/go/issues/11833 by only
|
||||
// seeding idRand upon the first call to id.
|
||||
|
||||
var seed int64
|
||||
var buf [8]byte
|
||||
|
||||
if _, err := crand.Read(buf[:]); err == nil {
|
||||
seed = int64(binary.LittleEndian.Uint64(buf[:]))
|
||||
} else {
|
||||
seed = rand.Int63()
|
||||
}
|
||||
|
||||
idRand = rand.New(rand.NewSource(seed))
|
||||
}
|
||||
|
||||
// The call to idRand.Uint32 must be within the
|
||||
// mutex lock because *rand.Rand is not safe for
|
||||
// concurrent use.
|
||||
//
|
||||
// There is no added performance overhead to calling
|
||||
// idRand.Uint32 inside a mutex lock over just
|
||||
// calling rand.Uint32 as the global math/rand rng
|
||||
// is internally protected by a sync.Mutex.
|
||||
id := uint16(idRand.Uint32())
|
||||
|
||||
idLock.Unlock()
|
||||
return id
|
||||
}
|
||||
|
||||
// MsgHdr is a a manually-unpacked version of (id, bits).
|
||||
|
@ -203,12 +224,6 @@ func packDomainName(s string, msg []byte, off int, compression map[string]int, c
|
|||
bs[j] = bs[j+2]
|
||||
}
|
||||
ls -= 2
|
||||
} else if bs[i] == 't' {
|
||||
bs[i] = '\t'
|
||||
} else if bs[i] == 'r' {
|
||||
bs[i] = '\r'
|
||||
} else if bs[i] == 'n' {
|
||||
bs[i] = '\n'
|
||||
}
|
||||
escapedDot = bs[i] == '.'
|
||||
bsFresh = false
|
||||
|
@ -335,10 +350,6 @@ Loop:
|
|||
fallthrough
|
||||
case '"', '\\':
|
||||
s = append(s, '\\', b)
|
||||
case '\t':
|
||||
s = append(s, '\\', 't')
|
||||
case '\r':
|
||||
s = append(s, '\\', 'r')
|
||||
default:
|
||||
if b < 32 || b >= 127 { // unprintable use \DDD
|
||||
var buf [3]byte
|
||||
|
@ -431,12 +442,6 @@ func packTxtString(s string, msg []byte, offset int, tmp []byte) (int, error) {
|
|||
if i+2 < len(bs) && isDigit(bs[i]) && isDigit(bs[i+1]) && isDigit(bs[i+2]) {
|
||||
msg[offset] = dddToByte(bs[i:])
|
||||
i += 2
|
||||
} else if bs[i] == 't' {
|
||||
msg[offset] = '\t'
|
||||
} else if bs[i] == 'r' {
|
||||
msg[offset] = '\r'
|
||||
} else if bs[i] == 'n' {
|
||||
msg[offset] = '\n'
|
||||
} else {
|
||||
msg[offset] = bs[i]
|
||||
}
|
||||
|
@ -508,12 +513,6 @@ func unpackTxtString(msg []byte, offset int) (string, int, error) {
|
|||
switch b {
|
||||
case '"', '\\':
|
||||
s = append(s, '\\', b)
|
||||
case '\t':
|
||||
s = append(s, `\t`...)
|
||||
case '\r':
|
||||
s = append(s, `\r`...)
|
||||
case '\n':
|
||||
s = append(s, `\n`...)
|
||||
default:
|
||||
if b < 32 || b > 127 { // unprintable
|
||||
var buf [3]byte
|
||||
|
@ -781,9 +780,6 @@ func (dns *Msg) Unpack(msg []byte) (err error) {
|
|||
if dh, off, err = unpackMsgHdr(msg, off); err != nil {
|
||||
return err
|
||||
}
|
||||
if off == len(msg) {
|
||||
return ErrTruncated
|
||||
}
|
||||
|
||||
dns.Id = dh.Id
|
||||
dns.Response = (dh.Bits & _QR) != 0
|
||||
|
@ -797,6 +793,10 @@ func (dns *Msg) Unpack(msg []byte) (err error) {
|
|||
dns.CheckingDisabled = (dh.Bits & _CD) != 0
|
||||
dns.Rcode = int(dh.Bits & 0xF)
|
||||
|
||||
if off == len(msg) {
|
||||
return ErrTruncated
|
||||
}
|
||||
|
||||
// Optimistically use the count given to us in the header
|
||||
dns.Question = make([]Question, 0, int(dh.Qdcount))
|
||||
|
||||
|
|
4
vendor/github.com/miekg/dns/msg_generate.go
generated
vendored
4
vendor/github.com/miekg/dns/msg_generate.go
generated
vendored
|
@ -117,9 +117,9 @@ return off, err
|
|||
switch {
|
||||
case st.Tag(i) == `dns:"-"`: // ignored
|
||||
case st.Tag(i) == `dns:"cdomain-name"`:
|
||||
fallthrough
|
||||
case st.Tag(i) == `dns:"domain-name"`:
|
||||
o("off, err = PackDomainName(rr.%s, msg, off, compression, compress)\n")
|
||||
case st.Tag(i) == `dns:"domain-name"`:
|
||||
o("off, err = PackDomainName(rr.%s, msg, off, compression, false)\n")
|
||||
case st.Tag(i) == `dns:"a"`:
|
||||
o("off, err = packDataA(rr.%s, msg, off)\n")
|
||||
case st.Tag(i) == `dns:"aaaa"`:
|
||||
|
|
2
vendor/github.com/miekg/dns/msg_helpers.go
generated
vendored
2
vendor/github.com/miekg/dns/msg_helpers.go
generated
vendored
|
@ -263,8 +263,6 @@ func unpackString(msg []byte, off int) (string, int, error) {
|
|||
switch b {
|
||||
case '"', '\\':
|
||||
s = append(s, '\\', b)
|
||||
case '\t', '\r', '\n':
|
||||
s = append(s, b)
|
||||
default:
|
||||
if b < 32 || b > 127 { // unprintable
|
||||
var buf [3]byte
|
||||
|
|
11
vendor/github.com/miekg/dns/nsecx.go
generated
vendored
11
vendor/github.com/miekg/dns/nsecx.go
generated
vendored
|
@ -3,7 +3,6 @@ package dns
|
|||
import (
|
||||
"crypto/sha1"
|
||||
"hash"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
|
@ -36,15 +35,15 @@ func HashName(label string, ha uint8, iter uint16, salt string) string {
|
|||
}
|
||||
|
||||
// k = 0
|
||||
name = append(name, wire...)
|
||||
io.WriteString(s, string(name))
|
||||
s.Write(name)
|
||||
s.Write(wire)
|
||||
nsec3 := s.Sum(nil)
|
||||
// k > 0
|
||||
for k := uint16(0); k < iter; k++ {
|
||||
s.Reset()
|
||||
nsec3 = append(nsec3, wire...)
|
||||
io.WriteString(s, string(nsec3))
|
||||
nsec3 = s.Sum(nil)
|
||||
s.Write(nsec3)
|
||||
s.Write(wire)
|
||||
nsec3 = s.Sum(nsec3[:0])
|
||||
}
|
||||
return toBase32(nsec3)
|
||||
}
|
||||
|
|
112
vendor/github.com/miekg/dns/scan_rr.go
generated
vendored
112
vendor/github.com/miekg/dns/scan_rr.go
generated
vendored
|
@ -64,74 +64,63 @@ func endingToString(c chan lex, errstr, f string) (string, *ParseError, string)
|
|||
return s, nil, l.comment
|
||||
}
|
||||
|
||||
// A remainder of the rdata with embedded spaces, return the parsed string slice (sans the spaces)
|
||||
// or an error
|
||||
// A remainder of the rdata with embedded spaces, split on unquoted whitespace
|
||||
// and return the parsed string slice or an error
|
||||
func endingToTxtSlice(c chan lex, errstr, f string) ([]string, *ParseError, string) {
|
||||
// Get the remaining data until we see a zNewline
|
||||
quote := false
|
||||
l := <-c
|
||||
var s []string
|
||||
if l.err {
|
||||
return s, &ParseError{f, errstr, l}, ""
|
||||
return nil, &ParseError{f, errstr, l}, ""
|
||||
}
|
||||
switch l.value == zQuote {
|
||||
case true: // A number of quoted string
|
||||
s = make([]string, 0)
|
||||
empty := true
|
||||
for l.value != zNewline && l.value != zEOF {
|
||||
if l.err {
|
||||
return nil, &ParseError{f, errstr, l}, ""
|
||||
}
|
||||
switch l.value {
|
||||
case zString:
|
||||
empty = false
|
||||
if len(l.token) > 255 {
|
||||
// split up tokens that are larger than 255 into 255-chunks
|
||||
sx := []string{}
|
||||
p, i := 0, 255
|
||||
for {
|
||||
if i <= len(l.token) {
|
||||
sx = append(sx, l.token[p:i])
|
||||
} else {
|
||||
sx = append(sx, l.token[p:])
|
||||
break
|
||||
|
||||
}
|
||||
p, i = p+255, i+255
|
||||
}
|
||||
s = append(s, sx...)
|
||||
break
|
||||
}
|
||||
|
||||
s = append(s, l.token)
|
||||
case zBlank:
|
||||
if quote {
|
||||
// zBlank can only be seen in between txt parts.
|
||||
return nil, &ParseError{f, errstr, l}, ""
|
||||
}
|
||||
case zQuote:
|
||||
if empty && quote {
|
||||
s = append(s, "")
|
||||
}
|
||||
quote = !quote
|
||||
empty = true
|
||||
default:
|
||||
return nil, &ParseError{f, errstr, l}, ""
|
||||
}
|
||||
l = <-c
|
||||
}
|
||||
if quote {
|
||||
// Build the slice
|
||||
s := make([]string, 0)
|
||||
quote := false
|
||||
empty := false
|
||||
for l.value != zNewline && l.value != zEOF {
|
||||
if l.err {
|
||||
return nil, &ParseError{f, errstr, l}, ""
|
||||
}
|
||||
case false: // Unquoted text record
|
||||
s = make([]string, 1)
|
||||
for l.value != zNewline && l.value != zEOF {
|
||||
if l.err {
|
||||
return s, &ParseError{f, errstr, l}, ""
|
||||
switch l.value {
|
||||
case zString:
|
||||
empty = false
|
||||
if len(l.token) > 255 {
|
||||
// split up tokens that are larger than 255 into 255-chunks
|
||||
sx := []string{}
|
||||
p, i := 0, 255
|
||||
for {
|
||||
if i <= len(l.token) {
|
||||
sx = append(sx, l.token[p:i])
|
||||
} else {
|
||||
sx = append(sx, l.token[p:])
|
||||
break
|
||||
|
||||
}
|
||||
p, i = p+255, i+255
|
||||
}
|
||||
s = append(s, sx...)
|
||||
break
|
||||
}
|
||||
s[0] += l.token
|
||||
l = <-c
|
||||
|
||||
s = append(s, l.token)
|
||||
case zBlank:
|
||||
if quote {
|
||||
// zBlank can only be seen in between txt parts.
|
||||
return nil, &ParseError{f, errstr, l}, ""
|
||||
}
|
||||
case zQuote:
|
||||
if empty && quote {
|
||||
s = append(s, "")
|
||||
}
|
||||
quote = !quote
|
||||
empty = true
|
||||
default:
|
||||
return nil, &ParseError{f, errstr, l}, ""
|
||||
}
|
||||
l = <-c
|
||||
}
|
||||
if quote {
|
||||
return nil, &ParseError{f, errstr, l}, ""
|
||||
}
|
||||
return s, nil, l.comment
|
||||
}
|
||||
|
@ -2027,9 +2016,12 @@ func setUINFO(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) {
|
|||
rr.Hdr = h
|
||||
s, e, c1 := endingToTxtSlice(c, "bad UINFO Uinfo", f)
|
||||
if e != nil {
|
||||
return nil, e, ""
|
||||
return nil, e, c1
|
||||
}
|
||||
rr.Uinfo = s[0] // silently discard anything above
|
||||
if ln := len(s); ln == 0 {
|
||||
return rr, nil, c1
|
||||
}
|
||||
rr.Uinfo = s[0] // silently discard anything after the first character-string
|
||||
return rr, nil, c1
|
||||
}
|
||||
|
||||
|
|
6
vendor/github.com/miekg/dns/server.go
generated
vendored
6
vendor/github.com/miekg/dns/server.go
generated
vendored
|
@ -339,7 +339,7 @@ func (srv *Server) ListenAndServe() error {
|
|||
network := "tcp"
|
||||
if srv.Net == "tcp4-tls" {
|
||||
network = "tcp4"
|
||||
} else if srv.Net == "tcp6" {
|
||||
} else if srv.Net == "tcp6-tls" {
|
||||
network = "tcp6"
|
||||
}
|
||||
|
||||
|
@ -389,7 +389,9 @@ func (srv *Server) ActivateAndServe() error {
|
|||
if srv.UDPSize == 0 {
|
||||
srv.UDPSize = MinMsgSize
|
||||
}
|
||||
if t, ok := pConn.(*net.UDPConn); ok {
|
||||
// Check PacketConn interface's type is valid and value
|
||||
// is not nil
|
||||
if t, ok := pConn.(*net.UDPConn); ok && t != nil {
|
||||
if e := setUDPSocketOptions(t); e != nil {
|
||||
return e
|
||||
}
|
||||
|
|
5
vendor/github.com/miekg/dns/sig0.go
generated
vendored
5
vendor/github.com/miekg/dns/sig0.go
generated
vendored
|
@ -60,16 +60,15 @@ func (rr *SIG) Sign(k crypto.Signer, m *Msg) ([]byte, error) {
|
|||
}
|
||||
|
||||
rr.Signature = toBase64(signature)
|
||||
sig := string(signature)
|
||||
|
||||
buf = append(buf, sig...)
|
||||
buf = append(buf, signature...)
|
||||
if len(buf) > int(^uint16(0)) {
|
||||
return nil, ErrBuf
|
||||
}
|
||||
// Adjust sig data length
|
||||
rdoff := len(mbuf) + 1 + 2 + 2 + 4
|
||||
rdlen := binary.BigEndian.Uint16(buf[rdoff:])
|
||||
rdlen += uint16(len(sig))
|
||||
rdlen += uint16(len(signature))
|
||||
binary.BigEndian.PutUint16(buf[rdoff:], rdlen)
|
||||
// Adjust additional count
|
||||
adc := binary.BigEndian.Uint16(buf[10:])
|
||||
|
|
3
vendor/github.com/miekg/dns/tsig.go
generated
vendored
3
vendor/github.com/miekg/dns/tsig.go
generated
vendored
|
@ -9,7 +9,6 @@ import (
|
|||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"hash"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -124,7 +123,7 @@ func TsigGenerate(m *Msg, secret, requestMAC string, timersOnly bool) ([]byte, s
|
|||
default:
|
||||
return nil, "", ErrKeyAlg
|
||||
}
|
||||
io.WriteString(h, string(buf))
|
||||
h.Write(buf)
|
||||
t.MAC = hex.EncodeToString(h.Sum(nil))
|
||||
t.MACSize = uint16(len(t.MAC) / 2) // Size is half!
|
||||
|
||||
|
|
19
vendor/github.com/miekg/dns/types.go
generated
vendored
19
vendor/github.com/miekg/dns/types.go
generated
vendored
|
@ -480,12 +480,6 @@ func appendDomainNameByte(s []byte, b byte) []byte {
|
|||
|
||||
func appendTXTStringByte(s []byte, b byte) []byte {
|
||||
switch b {
|
||||
case '\t':
|
||||
return append(s, '\\', 't')
|
||||
case '\r':
|
||||
return append(s, '\\', 'r')
|
||||
case '\n':
|
||||
return append(s, '\\', 'n')
|
||||
case '"', '\\':
|
||||
return append(s, '\\', b)
|
||||
}
|
||||
|
@ -525,17 +519,8 @@ func nextByte(b []byte, offset int) (byte, int) {
|
|||
return dddToByte(b[offset+1:]), 4
|
||||
}
|
||||
}
|
||||
// not \ddd, maybe a control char
|
||||
switch b[offset+1] {
|
||||
case 't':
|
||||
return '\t', 2
|
||||
case 'r':
|
||||
return '\r', 2
|
||||
case 'n':
|
||||
return '\n', 2
|
||||
default:
|
||||
return b[offset+1], 2
|
||||
}
|
||||
// not \ddd, just an RFC 1035 "quoted" character
|
||||
return b[offset+1], 2
|
||||
}
|
||||
|
||||
type SPF struct {
|
||||
|
|
26
vendor/github.com/miekg/dns/udp.go
generated
vendored
26
vendor/github.com/miekg/dns/udp.go
generated
vendored
|
@ -1,10 +1,9 @@
|
|||
// +build !windows,!plan9
|
||||
// +build !windows
|
||||
|
||||
package dns
|
||||
|
||||
import (
|
||||
"net"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// SessionUDP holds the remote address and the associated
|
||||
|
@ -17,29 +16,6 @@ type SessionUDP struct {
|
|||
// RemoteAddr returns the remote network address.
|
||||
func (s *SessionUDP) RemoteAddr() net.Addr { return s.raddr }
|
||||
|
||||
// setUDPSocketOptions sets the UDP socket options.
|
||||
// This function is implemented on a per platform basis. See udp_*.go for more details
|
||||
func setUDPSocketOptions(conn *net.UDPConn) error {
|
||||
sa, err := getUDPSocketName(conn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch sa.(type) {
|
||||
case *syscall.SockaddrInet6:
|
||||
v6only, err := getUDPSocketOptions6Only(conn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
setUDPSocketOptions6(conn)
|
||||
if !v6only {
|
||||
setUDPSocketOptions4(conn)
|
||||
}
|
||||
case *syscall.SockaddrInet4:
|
||||
setUDPSocketOptions4(conn)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadFromSessionUDP acts just like net.UDPConn.ReadFrom(), but returns a session object instead of a
|
||||
// net.UDPAddr.
|
||||
func ReadFromSessionUDP(conn *net.UDPConn, b []byte) (int, *SessionUDP, error) {
|
||||
|
|
34
vendor/github.com/miekg/dns/udp_linux.go
generated
vendored
34
vendor/github.com/miekg/dns/udp_linux.go
generated
vendored
|
@ -1,4 +1,4 @@
|
|||
// +build linux
|
||||
// +build linux,!appengine
|
||||
|
||||
package dns
|
||||
|
||||
|
@ -15,6 +15,29 @@ import (
|
|||
"syscall"
|
||||
)
|
||||
|
||||
// setUDPSocketOptions sets the UDP socket options.
|
||||
// This function is implemented on a per platform basis. See udp_*.go for more details
|
||||
func setUDPSocketOptions(conn *net.UDPConn) error {
|
||||
sa, err := getUDPSocketName(conn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch sa.(type) {
|
||||
case *syscall.SockaddrInet6:
|
||||
v6only, err := getUDPSocketOptions6Only(conn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
setUDPSocketOptions6(conn)
|
||||
if !v6only {
|
||||
setUDPSocketOptions4(conn)
|
||||
}
|
||||
case *syscall.SockaddrInet4:
|
||||
setUDPSocketOptions4(conn)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// setUDPSocketOptions4 prepares the v4 socket for sessions.
|
||||
func setUDPSocketOptions4(conn *net.UDPConn) error {
|
||||
file, err := conn.File()
|
||||
|
@ -22,14 +45,17 @@ func setUDPSocketOptions4(conn *net.UDPConn) error {
|
|||
return err
|
||||
}
|
||||
if err := syscall.SetsockoptInt(int(file.Fd()), syscall.IPPROTO_IP, syscall.IP_PKTINFO, 1); err != nil {
|
||||
file.Close()
|
||||
return err
|
||||
}
|
||||
// Calling File() above results in the connection becoming blocking, we must fix that.
|
||||
// See https://github.com/miekg/dns/issues/279
|
||||
err = syscall.SetNonblock(int(file.Fd()), true)
|
||||
if err != nil {
|
||||
file.Close()
|
||||
return err
|
||||
}
|
||||
file.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -40,12 +66,15 @@ func setUDPSocketOptions6(conn *net.UDPConn) error {
|
|||
return err
|
||||
}
|
||||
if err := syscall.SetsockoptInt(int(file.Fd()), syscall.IPPROTO_IPV6, syscall.IPV6_RECVPKTINFO, 1); err != nil {
|
||||
file.Close()
|
||||
return err
|
||||
}
|
||||
err = syscall.SetNonblock(int(file.Fd()), true)
|
||||
if err != nil {
|
||||
file.Close()
|
||||
return err
|
||||
}
|
||||
file.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -59,8 +88,10 @@ func getUDPSocketOptions6Only(conn *net.UDPConn) (bool, error) {
|
|||
// dual stack. See http://stackoverflow.com/questions/1618240/how-to-support-both-ipv4-and-ipv6-connections
|
||||
v6only, err := syscall.GetsockoptInt(int(file.Fd()), syscall.IPPROTO_IPV6, syscall.IPV6_V6ONLY)
|
||||
if err != nil {
|
||||
file.Close()
|
||||
return false, err
|
||||
}
|
||||
file.Close()
|
||||
return v6only == 1, nil
|
||||
}
|
||||
|
||||
|
@ -69,5 +100,6 @@ func getUDPSocketName(conn *net.UDPConn) (syscall.Sockaddr, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
return syscall.Getsockname(int(file.Fd()))
|
||||
}
|
||||
|
|
6
vendor/github.com/miekg/dns/udp_other.go
generated
vendored
6
vendor/github.com/miekg/dns/udp_other.go
generated
vendored
|
@ -1,17 +1,15 @@
|
|||
// +build !linux,!plan9
|
||||
// +build !linux appengine
|
||||
|
||||
package dns
|
||||
|
||||
import (
|
||||
"net"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// These do nothing. See udp_linux.go for an example of how to implement this.
|
||||
|
||||
// We tried to adhire to some kind of naming scheme.
|
||||
|
||||
func setUDPSocketOptions(conn *net.UDPConn) error { return nil }
|
||||
func setUDPSocketOptions4(conn *net.UDPConn) error { return nil }
|
||||
func setUDPSocketOptions6(conn *net.UDPConn) error { return nil }
|
||||
func getUDPSocketOptions6Only(conn *net.UDPConn) (bool, error) { return false, nil }
|
||||
func getUDPSocketName(conn *net.UDPConn) (syscall.Sockaddr, error) { return nil, nil }
|
||||
|
|
34
vendor/github.com/miekg/dns/udp_plan9.go
generated
vendored
34
vendor/github.com/miekg/dns/udp_plan9.go
generated
vendored
|
@ -1,34 +0,0 @@
|
|||
package dns
|
||||
|
||||
import (
|
||||
"net"
|
||||
)
|
||||
|
||||
func setUDPSocketOptions(conn *net.UDPConn) error { return nil }
|
||||
|
||||
// SessionUDP holds the remote address and the associated
|
||||
// out-of-band data.
|
||||
type SessionUDP struct {
|
||||
raddr *net.UDPAddr
|
||||
context []byte
|
||||
}
|
||||
|
||||
// RemoteAddr returns the remote network address.
|
||||
func (s *SessionUDP) RemoteAddr() net.Addr { return s.raddr }
|
||||
|
||||
// ReadFromSessionUDP acts just like net.UDPConn.ReadFrom(), but returns a session object instead of a
|
||||
// net.UDPAddr.
|
||||
func ReadFromSessionUDP(conn *net.UDPConn, b []byte) (int, *SessionUDP, error) {
|
||||
oob := make([]byte, 40)
|
||||
n, oobn, _, raddr, err := conn.ReadMsgUDP(b, oob)
|
||||
if err != nil {
|
||||
return n, nil, err
|
||||
}
|
||||
return n, &SessionUDP{raddr, oob[:oobn]}, err
|
||||
}
|
||||
|
||||
// WriteToSessionUDP acts just like net.UDPConn.WritetTo(), but uses a *SessionUDP instead of a net.Addr.
|
||||
func WriteToSessionUDP(conn *net.UDPConn, b []byte, session *SessionUDP) (int, error) {
|
||||
n, _, err := conn.WriteMsgUDP(b, session.context, session.raddr)
|
||||
return n, err
|
||||
}
|
9
vendor/github.com/miekg/dns/udp_windows.go
generated
vendored
9
vendor/github.com/miekg/dns/udp_windows.go
generated
vendored
|
@ -8,6 +8,8 @@ type SessionUDP struct {
|
|||
raddr *net.UDPAddr
|
||||
}
|
||||
|
||||
func (s *SessionUDP) RemoteAddr() net.Addr { return s.raddr }
|
||||
|
||||
// ReadFromSessionUDP acts just like net.UDPConn.ReadFrom(), but returns a session object instead of a
|
||||
// net.UDPAddr.
|
||||
func ReadFromSessionUDP(conn *net.UDPConn, b []byte) (int, *SessionUDP, error) {
|
||||
|
@ -25,10 +27,3 @@ func WriteToSessionUDP(conn *net.UDPConn, b []byte, session *SessionUDP) (int, e
|
|||
return n, err
|
||||
}
|
||||
|
||||
func (s *SessionUDP) RemoteAddr() net.Addr { return s.raddr }
|
||||
|
||||
// setUDPSocketOptions sets the UDP socket options.
|
||||
// This function is implemented on a per platform basis. See udp_*.go for more details
|
||||
func setUDPSocketOptions(conn *net.UDPConn) error {
|
||||
return nil
|
||||
}
|
||||
|
|
34
vendor/github.com/miekg/dns/zmsg.go
generated
vendored
34
vendor/github.com/miekg/dns/zmsg.go
generated
vendored
|
@ -221,7 +221,7 @@ func (rr *DNAME) pack(msg []byte, off int, compression map[string]int, compress
|
|||
return off, err
|
||||
}
|
||||
headerEnd := off
|
||||
off, err = PackDomainName(rr.Target, msg, off, compression, compress)
|
||||
off, err = PackDomainName(rr.Target, msg, off, compression, false)
|
||||
if err != nil {
|
||||
return off, err
|
||||
}
|
||||
|
@ -447,7 +447,7 @@ func (rr *KX) pack(msg []byte, off int, compression map[string]int, compress boo
|
|||
if err != nil {
|
||||
return off, err
|
||||
}
|
||||
off, err = PackDomainName(rr.Exchanger, msg, off, compression, compress)
|
||||
off, err = PackDomainName(rr.Exchanger, msg, off, compression, false)
|
||||
if err != nil {
|
||||
return off, err
|
||||
}
|
||||
|
@ -539,7 +539,7 @@ func (rr *LP) pack(msg []byte, off int, compression map[string]int, compress boo
|
|||
if err != nil {
|
||||
return off, err
|
||||
}
|
||||
off, err = PackDomainName(rr.Fqdn, msg, off, compression, compress)
|
||||
off, err = PackDomainName(rr.Fqdn, msg, off, compression, false)
|
||||
if err != nil {
|
||||
return off, err
|
||||
}
|
||||
|
@ -679,7 +679,7 @@ func (rr *NAPTR) pack(msg []byte, off int, compression map[string]int, compress
|
|||
if err != nil {
|
||||
return off, err
|
||||
}
|
||||
off, err = PackDomainName(rr.Replacement, msg, off, compression, compress)
|
||||
off, err = PackDomainName(rr.Replacement, msg, off, compression, false)
|
||||
if err != nil {
|
||||
return off, err
|
||||
}
|
||||
|
@ -753,7 +753,7 @@ func (rr *NSAPPTR) pack(msg []byte, off int, compression map[string]int, compres
|
|||
return off, err
|
||||
}
|
||||
headerEnd := off
|
||||
off, err = PackDomainName(rr.Ptr, msg, off, compression, compress)
|
||||
off, err = PackDomainName(rr.Ptr, msg, off, compression, false)
|
||||
if err != nil {
|
||||
return off, err
|
||||
}
|
||||
|
@ -767,7 +767,7 @@ func (rr *NSEC) pack(msg []byte, off int, compression map[string]int, compress b
|
|||
return off, err
|
||||
}
|
||||
headerEnd := off
|
||||
off, err = PackDomainName(rr.NextDomain, msg, off, compression, compress)
|
||||
off, err = PackDomainName(rr.NextDomain, msg, off, compression, false)
|
||||
if err != nil {
|
||||
return off, err
|
||||
}
|
||||
|
@ -905,11 +905,11 @@ func (rr *PX) pack(msg []byte, off int, compression map[string]int, compress boo
|
|||
if err != nil {
|
||||
return off, err
|
||||
}
|
||||
off, err = PackDomainName(rr.Map822, msg, off, compression, compress)
|
||||
off, err = PackDomainName(rr.Map822, msg, off, compression, false)
|
||||
if err != nil {
|
||||
return off, err
|
||||
}
|
||||
off, err = PackDomainName(rr.Mapx400, msg, off, compression, compress)
|
||||
off, err = PackDomainName(rr.Mapx400, msg, off, compression, false)
|
||||
if err != nil {
|
||||
return off, err
|
||||
}
|
||||
|
@ -963,11 +963,11 @@ func (rr *RP) pack(msg []byte, off int, compression map[string]int, compress boo
|
|||
return off, err
|
||||
}
|
||||
headerEnd := off
|
||||
off, err = PackDomainName(rr.Mbox, msg, off, compression, compress)
|
||||
off, err = PackDomainName(rr.Mbox, msg, off, compression, false)
|
||||
if err != nil {
|
||||
return off, err
|
||||
}
|
||||
off, err = PackDomainName(rr.Txt, msg, off, compression, compress)
|
||||
off, err = PackDomainName(rr.Txt, msg, off, compression, false)
|
||||
if err != nil {
|
||||
return off, err
|
||||
}
|
||||
|
@ -1009,7 +1009,7 @@ func (rr *RRSIG) pack(msg []byte, off int, compression map[string]int, compress
|
|||
if err != nil {
|
||||
return off, err
|
||||
}
|
||||
off, err = PackDomainName(rr.SignerName, msg, off, compression, compress)
|
||||
off, err = PackDomainName(rr.SignerName, msg, off, compression, false)
|
||||
if err != nil {
|
||||
return off, err
|
||||
}
|
||||
|
@ -1073,7 +1073,7 @@ func (rr *SIG) pack(msg []byte, off int, compression map[string]int, compress bo
|
|||
if err != nil {
|
||||
return off, err
|
||||
}
|
||||
off, err = PackDomainName(rr.SignerName, msg, off, compression, compress)
|
||||
off, err = PackDomainName(rr.SignerName, msg, off, compression, false)
|
||||
if err != nil {
|
||||
return off, err
|
||||
}
|
||||
|
@ -1181,7 +1181,7 @@ func (rr *SRV) pack(msg []byte, off int, compression map[string]int, compress bo
|
|||
if err != nil {
|
||||
return off, err
|
||||
}
|
||||
off, err = PackDomainName(rr.Target, msg, off, compression, compress)
|
||||
off, err = PackDomainName(rr.Target, msg, off, compression, false)
|
||||
if err != nil {
|
||||
return off, err
|
||||
}
|
||||
|
@ -1243,11 +1243,11 @@ func (rr *TALINK) pack(msg []byte, off int, compression map[string]int, compress
|
|||
return off, err
|
||||
}
|
||||
headerEnd := off
|
||||
off, err = PackDomainName(rr.PreviousName, msg, off, compression, compress)
|
||||
off, err = PackDomainName(rr.PreviousName, msg, off, compression, false)
|
||||
if err != nil {
|
||||
return off, err
|
||||
}
|
||||
off, err = PackDomainName(rr.NextName, msg, off, compression, compress)
|
||||
off, err = PackDomainName(rr.NextName, msg, off, compression, false)
|
||||
if err != nil {
|
||||
return off, err
|
||||
}
|
||||
|
@ -1261,7 +1261,7 @@ func (rr *TKEY) pack(msg []byte, off int, compression map[string]int, compress b
|
|||
return off, err
|
||||
}
|
||||
headerEnd := off
|
||||
off, err = PackDomainName(rr.Algorithm, msg, off, compression, compress)
|
||||
off, err = PackDomainName(rr.Algorithm, msg, off, compression, false)
|
||||
if err != nil {
|
||||
return off, err
|
||||
}
|
||||
|
@ -1333,7 +1333,7 @@ func (rr *TSIG) pack(msg []byte, off int, compression map[string]int, compress b
|
|||
return off, err
|
||||
}
|
||||
headerEnd := off
|
||||
off, err = PackDomainName(rr.Algorithm, msg, off, compression, compress)
|
||||
off, err = PackDomainName(rr.Algorithm, msg, off, compression, false)
|
||||
if err != nil {
|
||||
return off, err
|
||||
}
|
||||
|
|
5
vendor/github.com/prometheus/common/model/value.go
generated
vendored
5
vendor/github.com/prometheus/common/model/value.go
generated
vendored
|
@ -129,11 +129,8 @@ func (s *Sample) Equal(o *Sample) bool {
|
|||
if !s.Timestamp.Equal(o.Timestamp) {
|
||||
return false
|
||||
}
|
||||
if s.Value.Equal(o.Value) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
return s.Value.Equal(o.Value)
|
||||
}
|
||||
|
||||
func (s Sample) String() string {
|
||||
|
|
27
vendor/golang.org/x/time/LICENSE
generated
vendored
Normal file
27
vendor/golang.org/x/time/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,27 @@
|
|||
Copyright (c) 2009 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.
|
22
vendor/golang.org/x/time/PATENTS
generated
vendored
Normal file
22
vendor/golang.org/x/time/PATENTS
generated
vendored
Normal file
|
@ -0,0 +1,22 @@
|
|||
Additional IP Rights Grant (Patents)
|
||||
|
||||
"This implementation" means the copyrightable works distributed by
|
||||
Google as part of the Go project.
|
||||
|
||||
Google 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,
|
||||
transfer and otherwise run, modify and propagate the contents of this
|
||||
implementation of Go, where such license applies only to those patent
|
||||
claims, both currently owned or controlled by Google and acquired in
|
||||
the future, licensable by Google that are necessarily infringed by this
|
||||
implementation of Go. This grant does not include claims that would be
|
||||
infringed only as a consequence of further modification of this
|
||||
implementation. If you or your agent or exclusive licensee institute or
|
||||
order or agree to the institution of patent litigation against any
|
||||
entity (including a cross-claim or counterclaim in a lawsuit) alleging
|
||||
that this implementation of Go or any code incorporated within this
|
||||
implementation of Go constitutes direct or contributory patent
|
||||
infringement, or inducement of patent infringement, then any patent
|
||||
rights granted to you under this License for this implementation of Go
|
||||
shall terminate as of the date such litigation is filed.
|
371
vendor/golang.org/x/time/rate/rate.go
generated
vendored
Normal file
371
vendor/golang.org/x/time/rate/rate.go
generated
vendored
Normal file
|
@ -0,0 +1,371 @@
|
|||
// Copyright 2015 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 rate provides a rate limiter.
|
||||
package rate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// Limit defines the maximum frequency of some events.
|
||||
// Limit is represented as number of events per second.
|
||||
// A zero Limit allows no events.
|
||||
type Limit float64
|
||||
|
||||
// Inf is the infinite rate limit; it allows all events (even if burst is zero).
|
||||
const Inf = Limit(math.MaxFloat64)
|
||||
|
||||
// Every converts a minimum time interval between events to a Limit.
|
||||
func Every(interval time.Duration) Limit {
|
||||
if interval <= 0 {
|
||||
return Inf
|
||||
}
|
||||
return 1 / Limit(interval.Seconds())
|
||||
}
|
||||
|
||||
// A Limiter controls how frequently events are allowed to happen.
|
||||
// It implements a "token bucket" of size b, initially full and refilled
|
||||
// at rate r tokens per second.
|
||||
// Informally, in any large enough time interval, the Limiter limits the
|
||||
// rate to r tokens per second, with a maximum burst size of b events.
|
||||
// As a special case, if r == Inf (the infinite rate), b is ignored.
|
||||
// See https://en.wikipedia.org/wiki/Token_bucket for more about token buckets.
|
||||
//
|
||||
// The zero value is a valid Limiter, but it will reject all events.
|
||||
// Use NewLimiter to create non-zero Limiters.
|
||||
//
|
||||
// Limiter has three main methods, Allow, Reserve, and Wait.
|
||||
// Most callers should use Wait.
|
||||
//
|
||||
// Each of the three methods consumes a single token.
|
||||
// They differ in their behavior when no token is available.
|
||||
// If no token is available, Allow returns false.
|
||||
// If no token is available, Reserve returns a reservation for a future token
|
||||
// and the amount of time the caller must wait before using it.
|
||||
// If no token is available, Wait blocks until one can be obtained
|
||||
// or its associated context.Context is canceled.
|
||||
//
|
||||
// The methods AllowN, ReserveN, and WaitN consume n tokens.
|
||||
type Limiter struct {
|
||||
limit Limit
|
||||
burst int
|
||||
|
||||
mu sync.Mutex
|
||||
tokens float64
|
||||
// last is the last time the limiter's tokens field was updated
|
||||
last time.Time
|
||||
// lastEvent is the latest time of a rate-limited event (past or future)
|
||||
lastEvent time.Time
|
||||
}
|
||||
|
||||
// Limit returns the maximum overall event rate.
|
||||
func (lim *Limiter) Limit() Limit {
|
||||
lim.mu.Lock()
|
||||
defer lim.mu.Unlock()
|
||||
return lim.limit
|
||||
}
|
||||
|
||||
// Burst returns the maximum burst size. Burst is the maximum number of tokens
|
||||
// that can be consumed in a single call to Allow, Reserve, or Wait, so higher
|
||||
// Burst values allow more events to happen at once.
|
||||
// A zero Burst allows no events, unless limit == Inf.
|
||||
func (lim *Limiter) Burst() int {
|
||||
return lim.burst
|
||||
}
|
||||
|
||||
// NewLimiter returns a new Limiter that allows events up to rate r and permits
|
||||
// bursts of at most b tokens.
|
||||
func NewLimiter(r Limit, b int) *Limiter {
|
||||
return &Limiter{
|
||||
limit: r,
|
||||
burst: b,
|
||||
}
|
||||
}
|
||||
|
||||
// Allow is shorthand for AllowN(time.Now(), 1).
|
||||
func (lim *Limiter) Allow() bool {
|
||||
return lim.AllowN(time.Now(), 1)
|
||||
}
|
||||
|
||||
// AllowN reports whether n events may happen at time now.
|
||||
// Use this method if you intend to drop / skip events that exceed the rate limit.
|
||||
// Otherwise use Reserve or Wait.
|
||||
func (lim *Limiter) AllowN(now time.Time, n int) bool {
|
||||
return lim.reserveN(now, n, 0).ok
|
||||
}
|
||||
|
||||
// A Reservation holds information about events that are permitted by a Limiter to happen after a delay.
|
||||
// A Reservation may be canceled, which may enable the Limiter to permit additional events.
|
||||
type Reservation struct {
|
||||
ok bool
|
||||
lim *Limiter
|
||||
tokens int
|
||||
timeToAct time.Time
|
||||
// This is the Limit at reservation time, it can change later.
|
||||
limit Limit
|
||||
}
|
||||
|
||||
// OK returns whether the limiter can provide the requested number of tokens
|
||||
// within the maximum wait time. If OK is false, Delay returns InfDuration, and
|
||||
// Cancel does nothing.
|
||||
func (r *Reservation) OK() bool {
|
||||
return r.ok
|
||||
}
|
||||
|
||||
// Delay is shorthand for DelayFrom(time.Now()).
|
||||
func (r *Reservation) Delay() time.Duration {
|
||||
return r.DelayFrom(time.Now())
|
||||
}
|
||||
|
||||
// InfDuration is the duration returned by Delay when a Reservation is not OK.
|
||||
const InfDuration = time.Duration(1<<63 - 1)
|
||||
|
||||
// DelayFrom returns the duration for which the reservation holder must wait
|
||||
// before taking the reserved action. Zero duration means act immediately.
|
||||
// InfDuration means the limiter cannot grant the tokens requested in this
|
||||
// Reservation within the maximum wait time.
|
||||
func (r *Reservation) DelayFrom(now time.Time) time.Duration {
|
||||
if !r.ok {
|
||||
return InfDuration
|
||||
}
|
||||
delay := r.timeToAct.Sub(now)
|
||||
if delay < 0 {
|
||||
return 0
|
||||
}
|
||||
return delay
|
||||
}
|
||||
|
||||
// Cancel is shorthand for CancelAt(time.Now()).
|
||||
func (r *Reservation) Cancel() {
|
||||
r.CancelAt(time.Now())
|
||||
return
|
||||
}
|
||||
|
||||
// CancelAt indicates that the reservation holder will not perform the reserved action
|
||||
// and reverses the effects of this Reservation on the rate limit as much as possible,
|
||||
// considering that other reservations may have already been made.
|
||||
func (r *Reservation) CancelAt(now time.Time) {
|
||||
if !r.ok {
|
||||
return
|
||||
}
|
||||
|
||||
r.lim.mu.Lock()
|
||||
defer r.lim.mu.Unlock()
|
||||
|
||||
if r.lim.limit == Inf || r.tokens == 0 || r.timeToAct.Before(now) {
|
||||
return
|
||||
}
|
||||
|
||||
// calculate tokens to restore
|
||||
// The duration between lim.lastEvent and r.timeToAct tells us how many tokens were reserved
|
||||
// after r was obtained. These tokens should not be restored.
|
||||
restoreTokens := float64(r.tokens) - r.limit.tokensFromDuration(r.lim.lastEvent.Sub(r.timeToAct))
|
||||
if restoreTokens <= 0 {
|
||||
return
|
||||
}
|
||||
// advance time to now
|
||||
now, _, tokens := r.lim.advance(now)
|
||||
// calculate new number of tokens
|
||||
tokens += restoreTokens
|
||||
if burst := float64(r.lim.burst); tokens > burst {
|
||||
tokens = burst
|
||||
}
|
||||
// update state
|
||||
r.lim.last = now
|
||||
r.lim.tokens = tokens
|
||||
if r.timeToAct == r.lim.lastEvent {
|
||||
prevEvent := r.timeToAct.Add(r.limit.durationFromTokens(float64(-r.tokens)))
|
||||
if !prevEvent.Before(now) {
|
||||
r.lim.lastEvent = prevEvent
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Reserve is shorthand for ReserveN(time.Now(), 1).
|
||||
func (lim *Limiter) Reserve() *Reservation {
|
||||
return lim.ReserveN(time.Now(), 1)
|
||||
}
|
||||
|
||||
// ReserveN returns a Reservation that indicates how long the caller must wait before n events happen.
|
||||
// The Limiter takes this Reservation into account when allowing future events.
|
||||
// ReserveN returns false if n exceeds the Limiter's burst size.
|
||||
// Usage example:
|
||||
// r := lim.ReserveN(time.Now(), 1)
|
||||
// if !r.OK() {
|
||||
// // Not allowed to act! Did you remember to set lim.burst to be > 0 ?
|
||||
// return
|
||||
// }
|
||||
// time.Sleep(r.Delay())
|
||||
// Act()
|
||||
// Use this method if you wish to wait and slow down in accordance with the rate limit without dropping events.
|
||||
// If you need to respect a deadline or cancel the delay, use Wait instead.
|
||||
// To drop or skip events exceeding rate limit, use Allow instead.
|
||||
func (lim *Limiter) ReserveN(now time.Time, n int) *Reservation {
|
||||
r := lim.reserveN(now, n, InfDuration)
|
||||
return &r
|
||||
}
|
||||
|
||||
// Wait is shorthand for WaitN(ctx, 1).
|
||||
func (lim *Limiter) Wait(ctx context.Context) (err error) {
|
||||
return lim.WaitN(ctx, 1)
|
||||
}
|
||||
|
||||
// WaitN blocks until lim permits n events to happen.
|
||||
// It returns an error if n exceeds the Limiter's burst size, the Context is
|
||||
// canceled, or the expected wait time exceeds the Context's Deadline.
|
||||
// The burst limit is ignored if the rate limit is Inf.
|
||||
func (lim *Limiter) WaitN(ctx context.Context, n int) (err error) {
|
||||
if n > lim.burst && lim.limit != Inf {
|
||||
return fmt.Errorf("rate: Wait(n=%d) exceeds limiter's burst %d", n, lim.burst)
|
||||
}
|
||||
// Check if ctx is already cancelled
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
default:
|
||||
}
|
||||
// Determine wait limit
|
||||
now := time.Now()
|
||||
waitLimit := InfDuration
|
||||
if deadline, ok := ctx.Deadline(); ok {
|
||||
waitLimit = deadline.Sub(now)
|
||||
}
|
||||
// Reserve
|
||||
r := lim.reserveN(now, n, waitLimit)
|
||||
if !r.ok {
|
||||
return fmt.Errorf("rate: Wait(n=%d) would exceed context deadline", n)
|
||||
}
|
||||
// Wait
|
||||
t := time.NewTimer(r.DelayFrom(now))
|
||||
defer t.Stop()
|
||||
select {
|
||||
case <-t.C:
|
||||
// We can proceed.
|
||||
return nil
|
||||
case <-ctx.Done():
|
||||
// Context was canceled before we could proceed. Cancel the
|
||||
// reservation, which may permit other events to proceed sooner.
|
||||
r.Cancel()
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
// SetLimit is shorthand for SetLimitAt(time.Now(), newLimit).
|
||||
func (lim *Limiter) SetLimit(newLimit Limit) {
|
||||
lim.SetLimitAt(time.Now(), newLimit)
|
||||
}
|
||||
|
||||
// SetLimitAt sets a new Limit for the limiter. The new Limit, and Burst, may be violated
|
||||
// or underutilized by those which reserved (using Reserve or Wait) but did not yet act
|
||||
// before SetLimitAt was called.
|
||||
func (lim *Limiter) SetLimitAt(now time.Time, newLimit Limit) {
|
||||
lim.mu.Lock()
|
||||
defer lim.mu.Unlock()
|
||||
|
||||
now, _, tokens := lim.advance(now)
|
||||
|
||||
lim.last = now
|
||||
lim.tokens = tokens
|
||||
lim.limit = newLimit
|
||||
}
|
||||
|
||||
// reserveN is a helper method for AllowN, ReserveN, and WaitN.
|
||||
// maxFutureReserve specifies the maximum reservation wait duration allowed.
|
||||
// reserveN returns Reservation, not *Reservation, to avoid allocation in AllowN and WaitN.
|
||||
func (lim *Limiter) reserveN(now time.Time, n int, maxFutureReserve time.Duration) Reservation {
|
||||
lim.mu.Lock()
|
||||
|
||||
if lim.limit == Inf {
|
||||
lim.mu.Unlock()
|
||||
return Reservation{
|
||||
ok: true,
|
||||
lim: lim,
|
||||
tokens: n,
|
||||
timeToAct: now,
|
||||
}
|
||||
}
|
||||
|
||||
now, last, tokens := lim.advance(now)
|
||||
|
||||
// Calculate the remaining number of tokens resulting from the request.
|
||||
tokens -= float64(n)
|
||||
|
||||
// Calculate the wait duration
|
||||
var waitDuration time.Duration
|
||||
if tokens < 0 {
|
||||
waitDuration = lim.limit.durationFromTokens(-tokens)
|
||||
}
|
||||
|
||||
// Decide result
|
||||
ok := n <= lim.burst && waitDuration <= maxFutureReserve
|
||||
|
||||
// Prepare reservation
|
||||
r := Reservation{
|
||||
ok: ok,
|
||||
lim: lim,
|
||||
limit: lim.limit,
|
||||
}
|
||||
if ok {
|
||||
r.tokens = n
|
||||
r.timeToAct = now.Add(waitDuration)
|
||||
}
|
||||
|
||||
// Update state
|
||||
if ok {
|
||||
lim.last = now
|
||||
lim.tokens = tokens
|
||||
lim.lastEvent = r.timeToAct
|
||||
} else {
|
||||
lim.last = last
|
||||
}
|
||||
|
||||
lim.mu.Unlock()
|
||||
return r
|
||||
}
|
||||
|
||||
// advance calculates and returns an updated state for lim resulting from the passage of time.
|
||||
// lim is not changed.
|
||||
func (lim *Limiter) advance(now time.Time) (newNow time.Time, newLast time.Time, newTokens float64) {
|
||||
last := lim.last
|
||||
if now.Before(last) {
|
||||
last = now
|
||||
}
|
||||
|
||||
// Avoid making delta overflow below when last is very old.
|
||||
maxElapsed := lim.limit.durationFromTokens(float64(lim.burst) - lim.tokens)
|
||||
elapsed := now.Sub(last)
|
||||
if elapsed > maxElapsed {
|
||||
elapsed = maxElapsed
|
||||
}
|
||||
|
||||
// Calculate the new number of tokens, due to time that passed.
|
||||
delta := lim.limit.tokensFromDuration(elapsed)
|
||||
tokens := lim.tokens + delta
|
||||
if burst := float64(lim.burst); tokens > burst {
|
||||
tokens = burst
|
||||
}
|
||||
|
||||
return now, last, tokens
|
||||
}
|
||||
|
||||
// durationFromTokens is a unit conversion function from the number of tokens to the duration
|
||||
// of time it takes to accumulate them at a rate of limit tokens per second.
|
||||
func (limit Limit) durationFromTokens(tokens float64) time.Duration {
|
||||
seconds := tokens / float64(limit)
|
||||
return time.Nanosecond * time.Duration(1e9*seconds)
|
||||
}
|
||||
|
||||
// tokensFromDuration is a unit conversion function from a time duration to the number of tokens
|
||||
// which could be accumulated during that duration at a rate of limit tokens per second.
|
||||
func (limit Limit) tokensFromDuration(d time.Duration) float64 {
|
||||
return d.Seconds() * float64(limit)
|
||||
}
|
24
vendor/vendor.json
vendored
24
vendor/vendor.json
vendored
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"comment": "",
|
||||
"ignore": "test appengine",
|
||||
"ignore": "test",
|
||||
"package": [
|
||||
{
|
||||
"checksumSHA1": "oIt4tXgFYnZJBsCac1BQLnTWALM=",
|
||||
|
@ -368,10 +368,10 @@
|
|||
"revisionTime": "2016-09-30T00:14:02Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "UeErAjCWDt1vzaXGQGxnenFUg7w=",
|
||||
"checksumSHA1": "E5C5z6CV6JeIA2cpT3KVWeFgZdM=",
|
||||
"path": "github.com/fabxc/tsdb",
|
||||
"revision": "db5c88ea9ac9400b0e58ef18b9b272c34b98639b",
|
||||
"revisionTime": "2017-02-28T07:40:51Z"
|
||||
"revision": "2c3b56350a6d75a15484494c5a87145828cb34ef",
|
||||
"revisionTime": "2017-03-01T16:19:57Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "uVzWuLvF646YjiKomsc2CR1ua58=",
|
||||
|
@ -562,10 +562,10 @@
|
|||
"revisionTime": "2015-04-06T19:39:34+02:00"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "Wahi4g/9XiHhSLAJ+8jskg71PCU=",
|
||||
"checksumSHA1": "UoQnBcZrj1gvLAK+MGNB+E7+AIE=",
|
||||
"path": "github.com/miekg/dns",
|
||||
"revision": "58f52c57ce9df13460ac68200cef30a008b9c468",
|
||||
"revisionTime": "2016-10-18T06:08:08Z"
|
||||
"revision": "672033dedc09500ca4d340760d0b80b9c0b198bd",
|
||||
"revisionTime": "2017-02-13T20:16:50Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "aCtmlyAgau9n0UHs8Pk+3xfIaVk=",
|
||||
|
@ -626,8 +626,8 @@
|
|||
{
|
||||
"checksumSHA1": "vopCLXHzYm+3l5fPKOf4/fQwrCM=",
|
||||
"path": "github.com/prometheus/common/model",
|
||||
"revision": "dd2f054febf4a6c00f2343686efb775948a8bff4",
|
||||
"revisionTime": "2017-01-08T23:12:12Z"
|
||||
"revision": "3007b6072c17c8d985734e6e19b1dea9174e13d3",
|
||||
"revisionTime": "2017-02-19T00:35:58+01:00"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "ZbbESWBHHcPUJ/A5yrzKhTHuPc8=",
|
||||
|
@ -861,6 +861,12 @@
|
|||
"revision": "c589d0c9f0d81640c518354c7bcae77d99820aa3",
|
||||
"revisionTime": "2016-09-30T00:14:02Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "eFQDEix/mGnhwnFu/Hq63zMfrX8=",
|
||||
"path": "golang.org/x/time/rate",
|
||||
"revision": "f51c12702a4d776e4c1fa9b0fabab841babae631",
|
||||
"revisionTime": "2016-10-28T04:02:39Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "AjdmRXf0fiy6Bec9mNlsGsmZi1k=",
|
||||
"path": "google.golang.org/api/compute/v1",
|
||||
|
|
|
@ -332,6 +332,9 @@ PromConsole.Graph = function(params) {
|
|||
|
||||
this.params = params;
|
||||
this.rendered_data = null;
|
||||
// Keep a reference so that further updates (e.g. annotations) can be made
|
||||
// by the user in their templates.
|
||||
this.rickshawGraph = null;
|
||||
PromConsole._graph_registry.push(this);
|
||||
|
||||
/*
|
||||
|
@ -504,6 +507,8 @@ PromConsole.Graph.prototype._render = function(data) {
|
|||
xAxis.render();
|
||||
yAxis.render();
|
||||
graph.render();
|
||||
|
||||
this.rickshawGraph = graph;
|
||||
};
|
||||
|
||||
PromConsole.Graph.prototype._clearGraph = function() {
|
||||
|
@ -513,6 +518,7 @@ PromConsole.Graph.prototype._clearGraph = function() {
|
|||
while (this.legendDiv.lastChild) {
|
||||
this.legendDiv.removeChild(this.legendDiv.lastChild);
|
||||
}
|
||||
this.rickshawGraph = null;
|
||||
};
|
||||
|
||||
PromConsole.Graph.prototype._xhrs = [];
|
||||
|
|
Loading…
Reference in a new issue