Merge branch 'master' into dev-2.0

This commit is contained in:
Fabian Reinartz 2017-02-28 09:43:16 +01:00
commit 9304179ef7
67 changed files with 2260 additions and 443 deletions

View file

@ -3,7 +3,7 @@ sudo: false
language: go
go:
- 1.7.3
- 1.7.4
go_import_path: github.com/prometheus/prometheus

View file

@ -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

View file

@ -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.

View file

@ -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
View 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.

View file

@ -1 +1 @@
1.5.0
1.5.2

View file

@ -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(

View file

@ -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)
}
}
}

View file

@ -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)

View file

@ -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"`

View file

@ -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{

View file

@ -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

View file

@ -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

View file

@ -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_(.+)

View file

@ -7,7 +7,7 @@ To use it:
```
go build
./remote_storage
./example_receiver
```
...and then add the following to your `prometheus.yml`:

View file

@ -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"
```

View file

@ -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"
}

View file

@ -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)
}
}

View file

@ -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()
}

View file

@ -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
}

View file

@ -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)
}
}

View file

@ -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)
}

View file

@ -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"
}

View file

@ -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,
)
}
}

View file

@ -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
}

View file

@ -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,
)
}
}
}
}

View file

@ -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
}

View file

@ -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,

View file

@ -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
}

View file

@ -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)
}

View file

@ -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)
}

View file

@ -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 {

View file

@ -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)
}
}

View file

@ -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
}

View file

@ -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) {

View file

@ -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
View file

@ -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

View file

@ -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

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}
}

View file

@ -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)

View file

@ -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
}

View file

@ -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
View file

@ -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
View file

@ -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))

View file

@ -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"`:

View file

@ -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
View file

@ -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)
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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:])

View file

@ -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
View file

@ -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
View file

@ -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) {

View file

@ -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()))
}

View file

@ -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 }

View file

@ -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
}

View file

@ -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
View file

@ -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
}

View file

@ -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
View 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
View 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
View 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
View file

@ -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",

View file

@ -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 = [];