diff --git a/.codeclimate.yml b/.codeclimate.yml new file mode 100644 index 000000000..6db7af080 --- /dev/null +++ b/.codeclimate.yml @@ -0,0 +1,15 @@ +engines: + gofmt: + enabled: true + golint: + enabled: true + govet: + enabled: true +ratings: + paths: + - "**.go" +exclude_paths: +- "/storage/remote/remote.pb.go" +- vendor/ +- web/ui/static/vendor/ +- "/web/ui/bindata.go" diff --git a/CHANGELOG.md b/CHANGELOG.md index d9552f0ec..86b4370b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,20 @@ +## 1.5.0 / 2017-01-23 + +* [CHANGE] Use lexicographic order to sort alerts by name. +* [FEATURE] Add Joyent Triton discovery. +* [FEATURE] Add scrape targets and alertmanager targets API. +* [FEATURE] Add various persistence related metrics. +* [FEATURE] Add various query engine related metrics. +* [FEATURE] Add ability to limit scrape samples, and related metrics. +* [FEATURE] Add labeldrop and labelkeep relabelling actions. +* [FEATURE] Display current working directory on status-page. +* [ENHANCEMENT] Strictly use ServiceAccount for in cluster configuration on Kubernetes. +* [ENHANCEMENT] Various performance and memory-management improvements. +* [BUGFIX] Fix basic auth for alertmanagers configured via flag. +* [BUGFIX] Don't panic on decoding corrupt data. +* [BUGFIX] Ignore dotfiles in data directory. +* [BUGFIX] Abort on intermediate federation errors. + ## 1.4.1 / 2016-11-28 * [BUGFIX] Fix Consul service discovery diff --git a/README.md b/README.md index 308a253fb..cb0b7a0a3 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ [![Docker Repository on Quay](https://quay.io/repository/prometheus/prometheus/status)][quay] [![Docker Pulls](https://img.shields.io/docker/pulls/prom/prometheus.svg?maxAge=604800)][hub] [![Go Report Card](https://goreportcard.com/badge/github.com/prometheus/prometheus)](https://goreportcard.com/report/github.com/prometheus/prometheus) +[![Code Climate](https://codeclimate.com/github/prometheus/prometheus/badges/gpa.svg)](https://codeclimate.com/github/prometheus/prometheus) +[![Issue Count](https://codeclimate.com/github/prometheus/prometheus/badges/issue_count.svg)](https://codeclimate.com/github/prometheus/prometheus) Visit [prometheus.io](https://prometheus.io) for the full documentation, examples and guides. @@ -82,15 +84,15 @@ The Makefile provides several targets: * The source code is periodically indexed: [Prometheus Core](http://godoc.org/github.com/prometheus/prometheus). * You will find a Travis CI configuration in `.travis.yml`. - * All of the core developers are accessible via the [Prometheus Developers Mailinglist](https://groups.google.com/forum/?fromgroups#!forum/prometheus-developers) and the `#prometheus` channel on `irc.freenode.net`. + * See the [Community page](https://prometheus.io/community) for how to reach the Prometheus developers and users on various communication channels. ## Contributing -Refer to [CONTRIBUTING.md](CONTRIBUTING.md) +Refer to [CONTRIBUTING.md](https://github.com/prometheus/prometheus/blob/master/CONTRIBUTING.md) ## License -Apache License 2.0, see [LICENSE](LICENSE). +Apache License 2.0, see [LICENSE](https://github.com/prometheus/prometheus/blob/master/LICENSE). [travis]: https://travis-ci.org/prometheus/prometheus diff --git a/VERSION b/VERSION index 347f5833e..bc80560fa 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.4.1 +1.5.0 diff --git a/cmd/prometheus/config.go b/cmd/prometheus/config.go index 926df34e1..f5c001512 100644 --- a/cmd/prometheus/config.go +++ b/cmd/prometheus/config.go @@ -27,6 +27,8 @@ import ( "github.com/asaskevich/govalidator" "github.com/prometheus/common/log" + "github.com/prometheus/common/model" + "github.com/prometheus/prometheus/config" "github.com/prometheus/prometheus/notifier" "github.com/prometheus/prometheus/promql" "github.com/prometheus/prometheus/web" @@ -226,6 +228,43 @@ func validateAlertmanagerURL(u string) error { return nil } +func parseAlertmanagerURLToConfig(us string) (*config.AlertmanagerConfig, error) { + u, err := url.Parse(us) + if err != nil { + return nil, err + } + acfg := &config.AlertmanagerConfig{ + Scheme: u.Scheme, + PathPrefix: u.Path, + Timeout: cfg.notifierTimeout, + ServiceDiscoveryConfig: config.ServiceDiscoveryConfig{ + StaticConfigs: []*config.TargetGroup{ + { + Targets: []model.LabelSet{ + { + model.AddressLabel: model.LabelValue(u.Host), + }, + }, + }, + }, + }, + } + + if u.User != nil { + acfg.HTTPClientConfig = config.HTTPClientConfig{ + BasicAuth: &config.BasicAuth{ + Username: u.User.Username(), + }, + } + + if password, isSet := u.User.Password(); isSet { + acfg.HTTPClientConfig.BasicAuth.Password = password + } + } + + return acfg, nil +} + var helpTmpl = ` usage: prometheus [] {{ range $cat, $flags := . }}{{ if ne $cat "." }} == {{ $cat | upper }} =={{ end }} diff --git a/cmd/prometheus/config_test.go b/cmd/prometheus/config_test.go index 392087eb7..5edb7179c 100644 --- a/cmd/prometheus/config_test.go +++ b/cmd/prometheus/config_test.go @@ -52,3 +52,46 @@ 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) + } + } +} diff --git a/cmd/prometheus/main.go b/cmd/prometheus/main.go index 62c8728a9..5df30c2c5 100644 --- a/cmd/prometheus/main.go +++ b/cmd/prometheus/main.go @@ -20,7 +20,6 @@ import ( _ "net/http/pprof" // Comment this line to disable pprof endpoint. "os" "os/signal" - "runtime" "runtime/trace" "syscall" "time" @@ -58,8 +57,6 @@ var ( func init() { prometheus.MustRegister(version.NewCollector("prometheus")) - runtime.SetMutexProfileFraction(20) - runtime.SetBlockProfileRate(20) } // Main manages the stup and shutdown lifecycle of the entire Prometheus server. diff --git a/config/config.go b/config/config.go index 510de3136..ae0046329 100644 --- a/config/config.go +++ b/config/config.go @@ -156,6 +156,13 @@ var ( RefreshInterval: model.Duration(5 * time.Minute), } + // DefaultTritonSDConfig is the default Triton SD configuration. + DefaultTritonSDConfig = TritonSDConfig{ + Port: 9163, + RefreshInterval: model.Duration(60 * time.Second), + Version: 1, + } + // DefaultRemoteWriteConfig is the default remote write configuration. DefaultRemoteWriteConfig = RemoteWriteConfig{ RemoteTimeout: model.Duration(30 * time.Second), @@ -437,6 +444,8 @@ type ServiceDiscoveryConfig struct { EC2SDConfigs []*EC2SDConfig `yaml:"ec2_sd_configs,omitempty"` // List of Azure service discovery configurations. AzureSDConfigs []*AzureSDConfig `yaml:"azure_sd_configs,omitempty"` + // List of Triton service discovery configurations. + TritonSDConfigs []*TritonSDConfig `yaml:"triton_sd_configs,omitempty"` // Catches all undefined fields and must be empty after parsing. XXX map[string]interface{} `yaml:",inline"` @@ -497,6 +506,8 @@ type ScrapeConfig struct { MetricsPath string `yaml:"metrics_path,omitempty"` // The URL scheme with which to fetch metrics from targets. Scheme string `yaml:"scheme,omitempty"` + // More than this many samples post metric-relabelling will cause the scrape to fail. + SampleLimit uint `yaml:"sample_limit,omitempty"` // We cannot do proper Go type embedding below as the parser will then parse // values arbitrarily into the overflow maps of further-down types. @@ -986,6 +997,11 @@ func (c *KubernetesSDConfig) UnmarshalYAML(unmarshal func(interface{}) error) er if c.BasicAuth != nil && (len(c.BearerToken) > 0 || len(c.BearerTokenFile) > 0) { return fmt.Errorf("at most one of basic_auth, bearer_token & bearer_token_file must be configured") } + if c.APIServer.URL == nil && + (c.BasicAuth != nil || c.BearerToken != "" || c.BearerTokenFile != "" || + c.TLSConfig.CAFile != "" || c.TLSConfig.CertFile != "" || c.TLSConfig.KeyFile != "") { + return fmt.Errorf("to use custom authentication please provide the 'api_server' URL explicitly") + } return nil } @@ -1086,6 +1102,42 @@ func (c *AzureSDConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { return checkOverflow(c.XXX, "azure_sd_config") } +// TritonSDConfig is the configuration for Triton based service discovery. +type TritonSDConfig struct { + Account string `yaml:"account"` + DNSSuffix string `yaml:"dns_suffix"` + Endpoint string `yaml:"endpoint"` + Port int `yaml:"port"` + RefreshInterval model.Duration `yaml:"refresh_interval,omitempty"` + TLSConfig TLSConfig `yaml:"tls_config,omitempty"` + Version int `yaml:"version"` + // Catches all undefined fields and must be empty after parsing. + XXX map[string]interface{} `yaml:",inline"` +} + +// UnmarshalYAML implements the yaml.Unmarshaler interface. +func (c *TritonSDConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { + *c = DefaultTritonSDConfig + type plain TritonSDConfig + err := unmarshal((*plain)(c)) + if err != nil { + return err + } + if c.Account == "" { + return fmt.Errorf("Triton SD configuration requires an account") + } + if c.DNSSuffix == "" { + return fmt.Errorf("Triton SD configuration requires a dns_suffix") + } + if c.Endpoint == "" { + return fmt.Errorf("Triton SD configuration requires an endpoint") + } + if c.RefreshInterval <= 0 { + return fmt.Errorf("Triton SD configuration requires RefreshInterval to be a positive integer") + } + return checkOverflow(c.XXX, "triton_sd_config") +} + // RelabelAction is the action to be performed on relabeling. type RelabelAction string diff --git a/config/config_test.go b/config/config_test.go index 31d678622..a8c1f32e7 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -133,6 +133,7 @@ var expectedConf = &Config{ ScrapeInterval: model.Duration(50 * time.Second), ScrapeTimeout: model.Duration(5 * time.Second), + SampleLimit: 1000, HTTPClientConfig: HTTPClientConfig{ BasicAuth: &BasicAuth{ @@ -413,6 +414,33 @@ var expectedConf = &Config{ }, }, }, + { + JobName: "service-triton", + + ScrapeInterval: model.Duration(15 * time.Second), + ScrapeTimeout: DefaultGlobalConfig.ScrapeTimeout, + + MetricsPath: DefaultScrapeConfig.MetricsPath, + Scheme: DefaultScrapeConfig.Scheme, + + ServiceDiscoveryConfig: ServiceDiscoveryConfig{ + TritonSDConfigs: []*TritonSDConfig{ + { + + Account: "testAccount", + DNSSuffix: "triton.example.com", + Endpoint: "triton.example.com", + Port: 9163, + RefreshInterval: model.Duration(60 * time.Second), + Version: 1, + TLSConfig: TLSConfig{ + CertFile: "testdata/valid_cert_file", + KeyFile: "testdata/valid_key_file", + }, + }, + }, + }, + }, }, AlertingConfig: AlertingConfig{ AlertmanagerConfigs: []*AlertmanagerConfig{ diff --git a/config/testdata/conf.good.yml b/config/testdata/conf.good.yml index 9f62cb4ca..a3aca858a 100644 --- a/config/testdata/conf.good.yml +++ b/config/testdata/conf.good.yml @@ -70,6 +70,8 @@ scrape_configs: scrape_interval: 50s scrape_timeout: 5s + sample_limit: 1000 + metrics_path: /my_path scheme: https @@ -179,6 +181,18 @@ scrape_configs: - targets: - localhost:9090 +- job_name: service-triton + triton_sd_configs: + - account: 'testAccount' + dns_suffix: 'triton.example.com' + endpoint: 'triton.example.com' + port: 9163 + refresh_interval: 1m + version: 1 + tls_config: + cert_file: testdata/valid_cert_file + key_file: testdata/valid_key_file + alerting: alertmanagers: - scheme: https diff --git a/consoles/prometheus-overview.html b/consoles/prometheus-overview.html index f1f111e9b..d85c9310f 100644 --- a/consoles/prometheus-overview.html +++ b/consoles/prometheus-overview.html @@ -46,7 +46,7 @@ Checkpoint Duration - {{ template "prom_query_drilldown" (args (printf "prometheus_local_storage_checkpoint_duration_seconds{job='prometheus',instance='%s'}" .Params.instance) "" "humanizeDuration") }} + {{ template "prom_query_drilldown" (args (printf "prometheus_local_storage_checkpoint_last_duration_seconds{job='prometheus',instance='%s'}" .Params.instance) "" "humanizeDuration") }} diff --git a/discovery/discovery.go b/discovery/discovery.go index d81ae6391..883b27b86 100644 --- a/discovery/discovery.go +++ b/discovery/discovery.go @@ -28,6 +28,7 @@ import ( "github.com/prometheus/prometheus/discovery/gce" "github.com/prometheus/prometheus/discovery/kubernetes" "github.com/prometheus/prometheus/discovery/marathon" + "github.com/prometheus/prometheus/discovery/triton" "github.com/prometheus/prometheus/discovery/zookeeper" "golang.org/x/net/context" ) @@ -106,6 +107,14 @@ func ProvidersFromConfig(cfg config.ServiceDiscoveryConfig) map[string]TargetPro for i, c := range cfg.AzureSDConfigs { app("azure", i, azure.NewDiscovery(c)) } + for i, c := range cfg.TritonSDConfigs { + t, err := triton.New(log.With("sd", "triton"), c) + if err != nil { + log.Errorf("Cannot create Triton discovery: %s", err) + continue + } + app("triton", i, t) + } if len(cfg.StaticConfigs) > 0 { app("static", 0, NewStaticProvider(cfg.StaticConfigs)) } diff --git a/discovery/kubernetes/kubernetes.go b/discovery/kubernetes/kubernetes.go index 40d899f89..768e64978 100644 --- a/discovery/kubernetes/kubernetes.go +++ b/discovery/kubernetes/kubernetes.go @@ -82,11 +82,38 @@ func New(l log.Logger, conf *config.KubernetesSDConfig) (*Kubernetes, error) { err error ) if conf.APIServer.URL == nil { + // Use the Kubernetes provided pod service account + // as described in https://kubernetes.io/docs/admin/service-accounts-admin/ kcfg, err = rest.InClusterConfig() if err != nil { return nil, err } + // Because the handling of configuration parameters changes + // we should inform the user when their currently configured values + // will be ignored due to precedence of InClusterConfig + l.Info("Using pod service account via in-cluster config") + if conf.TLSConfig.CAFile != "" { + l.Warn("Configured TLS CA file is ignored when using pod service account") + } + if conf.TLSConfig.CertFile != "" || conf.TLSConfig.KeyFile != "" { + l.Warn("Configured TLS client certificate is ignored when using pod service account") + } + if conf.BearerToken != "" { + l.Warn("Configured auth token is ignored when using pod service account") + } + if conf.BasicAuth != nil { + l.Warn("Configured basic authentication credentials are ignored when using pod service account") + } } else { + kcfg = &rest.Config{ + Host: conf.APIServer.String(), + TLSClientConfig: rest.TLSClientConfig{ + CAFile: conf.TLSConfig.CAFile, + CertFile: conf.TLSConfig.CertFile, + KeyFile: conf.TLSConfig.KeyFile, + }, + Insecure: conf.TLSConfig.InsecureSkipVerify, + } token := conf.BearerToken if conf.BearerTokenFile != "" { bf, err := ioutil.ReadFile(conf.BearerTokenFile) @@ -95,24 +122,15 @@ func New(l log.Logger, conf *config.KubernetesSDConfig) (*Kubernetes, error) { } token = string(bf) } + kcfg.BearerToken = token - kcfg = &rest.Config{ - Host: conf.APIServer.String(), - BearerToken: token, - TLSClientConfig: rest.TLSClientConfig{ - CAFile: conf.TLSConfig.CAFile, - }, + if conf.BasicAuth != nil { + kcfg.Username = conf.BasicAuth.Username + kcfg.Password = conf.BasicAuth.Password } } - kcfg.UserAgent = "prometheus/discovery" - if conf.BasicAuth != nil { - kcfg.Username = conf.BasicAuth.Username - kcfg.Password = conf.BasicAuth.Password - } - kcfg.TLSClientConfig.CertFile = conf.TLSConfig.CertFile - kcfg.TLSClientConfig.KeyFile = conf.TLSConfig.KeyFile - kcfg.Insecure = conf.TLSConfig.InsecureSkipVerify + kcfg.UserAgent = "prometheus/discovery" c, err := kubernetes.NewForConfig(kcfg) if err != nil { diff --git a/discovery/triton/triton.go b/discovery/triton/triton.go new file mode 100644 index 000000000..b2c0bd916 --- /dev/null +++ b/discovery/triton/triton.go @@ -0,0 +1,169 @@ +// 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. + +package triton + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "time" + + "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/util/httputil" + "golang.org/x/net/context" +) + +const ( + tritonLabel = model.MetaLabelPrefix + "triton_" + tritonLabelMachineId = tritonLabel + "machine_id" + tritonLabelMachineAlias = tritonLabel + "machine_alias" + tritonLabelMachineImage = tritonLabel + "machine_image" + tritonLabelServerId = tritonLabel + "server_id" + namespace = "prometheus" +) + +var ( + refreshFailuresCount = prometheus.NewCounter( + prometheus.CounterOpts{ + Name: "prometheus_sd_triton_refresh_failures_total", + Help: "The number of Triton-SD scrape failures.", + }) + refreshDuration = prometheus.NewSummary( + prometheus.SummaryOpts{ + Name: "prometheus_sd_triton_refresh_duration_seconds", + Help: "The duration of a Triton-SD refresh in seconds.", + }) +) + +func init() { + prometheus.MustRegister(refreshFailuresCount) + prometheus.MustRegister(refreshDuration) +} + +type DiscoveryResponse struct { + Containers []struct { + ServerUUID string `json:"server_uuid"` + VMAlias string `json:"vm_alias"` + VMImageUUID string `json:"vm_image_uuid"` + VMUUID string `json:"vm_uuid"` + } `json:"containers"` +} + +// Discovery periodically performs Triton-SD requests. It implements +// the TargetProvider interface. +type Discovery struct { + client *http.Client + interval time.Duration + logger log.Logger + sdConfig *config.TritonSDConfig +} + +// New returns a new Discovery which periodically refreshes its targets. +func New(logger log.Logger, conf *config.TritonSDConfig) (*Discovery, error) { + tls, err := httputil.NewTLSConfig(conf.TLSConfig) + if err != nil { + return nil, err + } + + transport := &http.Transport{TLSClientConfig: tls} + client := &http.Client{Transport: transport} + + return &Discovery{ + client: client, + interval: time.Duration(conf.RefreshInterval), + logger: logger, + sdConfig: conf, + }, nil +} + +// Run implements the TargetProvider interface. +func (d *Discovery) Run(ctx context.Context, ch chan<- []*config.TargetGroup) { + defer close(ch) + + ticker := time.NewTicker(d.interval) + defer ticker.Stop() + + // Get an initial set right away. + tg, err := d.refresh() + if err != nil { + d.logger.With("err", err).Error("Refreshing targets failed") + } else { + ch <- []*config.TargetGroup{tg} + } + + for { + select { + case <-ticker.C: + tg, err := d.refresh() + if err != nil { + d.logger.With("err", err).Error("Refreshing targets failed") + } else { + ch <- []*config.TargetGroup{tg} + } + case <-ctx.Done(): + return + } + } +} + +func (d *Discovery) refresh() (tg *config.TargetGroup, err error) { + t0 := time.Now() + defer func() { + refreshDuration.Observe(time.Since(t0).Seconds()) + if err != nil { + refreshFailuresCount.Inc() + } + }() + + var endpoint = fmt.Sprintf("https://%s:%d/v%d/discover", d.sdConfig.Endpoint, d.sdConfig.Port, d.sdConfig.Version) + tg = &config.TargetGroup{ + Source: endpoint, + } + + resp, err := d.client.Get(endpoint) + if err != nil { + return tg, fmt.Errorf("an error occurred when requesting targets from the discovery endpoint. %s", err) + } + + defer resp.Body.Close() + + data, err := ioutil.ReadAll(resp.Body) + if err != nil { + return tg, fmt.Errorf("an error occurred when reading the response body. %s", err) + } + + dr := DiscoveryResponse{} + err = json.Unmarshal(data, &dr) + if err != nil { + return tg, fmt.Errorf("an error occurred unmarshaling the disovery response json. %s", err) + } + + for _, container := range dr.Containers { + labels := model.LabelSet{ + tritonLabelMachineId: model.LabelValue(container.VMUUID), + tritonLabelMachineAlias: model.LabelValue(container.VMAlias), + tritonLabelMachineImage: model.LabelValue(container.VMImageUUID), + tritonLabelServerId: model.LabelValue(container.ServerUUID), + } + addr := fmt.Sprintf("%s.%s:%d", container.VMUUID, d.sdConfig.DNSSuffix, d.sdConfig.Port) + labels[model.AddressLabel] = model.LabelValue(addr) + tg.Targets = append(tg.Targets, labels) + } + + return tg, nil +} diff --git a/discovery/triton/triton_test.go b/discovery/triton/triton_test.go new file mode 100644 index 000000000..bf89e7806 --- /dev/null +++ b/discovery/triton/triton_test.go @@ -0,0 +1,178 @@ +// 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 triton + +import ( + "fmt" + "net" + "net/http" + "net/http/httptest" + "net/url" + "strconv" + "testing" + "time" + + "github.com/prometheus/common/log" + "github.com/prometheus/common/model" + "github.com/prometheus/prometheus/config" + "github.com/stretchr/testify/assert" + "golang.org/x/net/context" +) + +var ( + conf = config.TritonSDConfig{ + Account: "testAccount", + DNSSuffix: "triton.example.com", + Endpoint: "127.0.0.1", + Port: 443, + Version: 1, + RefreshInterval: 1, + TLSConfig: config.TLSConfig{InsecureSkipVerify: true}, + } + badconf = config.TritonSDConfig{ + Account: "badTestAccount", + DNSSuffix: "bad.triton.example.com", + Endpoint: "127.0.0.1", + Port: 443, + Version: 1, + RefreshInterval: 1, + TLSConfig: config.TLSConfig{ + InsecureSkipVerify: false, + KeyFile: "shouldnotexist.key", + CAFile: "shouldnotexist.ca", + CertFile: "shouldnotexist.cert", + }, + } + logger = log.Base() +) + +func TestTritonSDNew(t *testing.T) { + td, err := New(logger, &conf) + assert.Nil(t, err) + assert.NotNil(t, td) + assert.NotNil(t, td.client) + assert.NotNil(t, td.interval) + assert.NotNil(t, td.logger) + assert.Equal(t, logger, td.logger, "td.logger equals logger") + assert.NotNil(t, td.sdConfig) + assert.Equal(t, conf.Account, td.sdConfig.Account) + assert.Equal(t, conf.DNSSuffix, td.sdConfig.DNSSuffix) + assert.Equal(t, conf.Endpoint, td.sdConfig.Endpoint) + assert.Equal(t, conf.Port, td.sdConfig.Port) +} + +func TestTritonSDNewBadConfig(t *testing.T) { + td, err := New(logger, &badconf) + assert.NotNil(t, err) + assert.Nil(t, td) +} + +func TestTritonSDRun(t *testing.T) { + var ( + td, err = New(logger, &conf) + ch = make(chan []*config.TargetGroup) + ctx, cancel = context.WithCancel(context.Background()) + ) + + assert.Nil(t, err) + assert.NotNil(t, td) + + go td.Run(ctx, ch) + + select { + case <-time.After(60 * time.Millisecond): + // Expected. + case tgs := <-ch: + t.Fatalf("Unexpected target groups in triton discovery: %s", tgs) + } + + cancel() +} + +func TestTritonSDRefreshNoTargets(t *testing.T) { + tgts := testTritonSDRefresh(t, "{\"containers\":[]}") + assert.Nil(t, tgts) +} + +func TestTritonSDRefreshMultipleTargets(t *testing.T) { + var ( + dstr = `{"containers":[ + { + "server_uuid":"44454c4c-5000-104d-8037-b7c04f5a5131", + "vm_alias":"server01", + "vm_image_uuid":"7b27a514-89d7-11e6-bee6-3f96f367bee7", + "vm_uuid":"ad466fbf-46a2-4027-9b64-8d3cdb7e9072" + }, + { + "server_uuid":"a5894692-bd32-4ca1-908a-e2dda3c3a5e6", + "vm_alias":"server02", + "vm_image_uuid":"a5894692-bd32-4ca1-908a-e2dda3c3a5e6", + "vm_uuid":"7b27a514-89d7-11e6-bee6-3f96f367bee7" + }] + }` + ) + + tgts := testTritonSDRefresh(t, dstr) + assert.NotNil(t, tgts) + assert.Equal(t, 2, len(tgts)) +} + +func TestTritonSDRefreshNoServer(t *testing.T) { + var ( + td, err = New(logger, &conf) + ) + assert.Nil(t, err) + assert.NotNil(t, td) + + tg, rerr := td.refresh() + assert.NotNil(t, rerr) + assert.Contains(t, rerr.Error(), "an error occurred when requesting targets from the discovery endpoint.") + assert.NotNil(t, tg) + assert.Nil(t, tg.Targets) +} + +func testTritonSDRefresh(t *testing.T, dstr string) []model.LabelSet { + var ( + td, err = New(logger, &conf) + s = httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintln(w, dstr) + })) + ) + + defer s.Close() + + u, uperr := url.Parse(s.URL) + assert.Nil(t, uperr) + assert.NotNil(t, u) + + host, strport, sherr := net.SplitHostPort(u.Host) + assert.Nil(t, sherr) + assert.NotNil(t, host) + assert.NotNil(t, strport) + + port, atoierr := strconv.Atoi(strport) + assert.Nil(t, atoierr) + assert.NotNil(t, port) + + td.sdConfig.Port = port + + assert.Nil(t, err) + assert.NotNil(t, td) + + tg, err := td.refresh() + assert.Nil(t, err) + assert.NotNil(t, tg) + + return tg.Targets +} diff --git a/notifier/notifier.go b/notifier/notifier.go index b589dedb0..4329559a6 100644 --- a/notifier/notifier.go +++ b/notifier/notifier.go @@ -166,7 +166,7 @@ func New(o *Options) *Notifier { Namespace: namespace, Subsystem: subsystem, Name: "dropped_total", - Help: "Total number of alerts dropped due to alert manager missing in configuration.", + Help: "Total number of alerts dropped due to errors when sending to Alertmanager.", }), queueLength: prometheus.NewGauge(prometheus.GaugeOpts{ Namespace: namespace, diff --git a/promql/engine.go b/promql/engine.go index 7a404dd33..abee650b1 100644 --- a/promql/engine.go +++ b/promql/engine.go @@ -22,6 +22,7 @@ import ( "strconv" "time" + "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/common/log" "github.com/prometheus/prometheus/pkg/labels" "github.com/prometheus/prometheus/pkg/timestamp" @@ -32,12 +33,35 @@ import ( ) const ( + namespace = "prometheus" + subsystem = "engine" + // The largest SampleValue that can be converted to an int64 without overflow. maxInt64 = 9223372036854774784 // The smallest SampleValue that can be converted to an int64 without underflow. minInt64 = -9223372036854775808 ) +var ( + currentQueries = prometheus.NewGauge(prometheus.GaugeOpts{ + Namespace: namespace, + Subsystem: subsystem, + Name: "queries", + Help: "The current number of queries being executed or waiting.", + }) + maxConcurrentQueries = prometheus.NewGauge(prometheus.GaugeOpts{ + Namespace: namespace, + Subsystem: subsystem, + Name: "queries_concurrent_max", + Help: "The max number of concurrent queries.", + }) +) + +func init() { + prometheus.MustRegister(currentQueries) + prometheus.MustRegister(maxConcurrentQueries) +} + // convertibleToInt64 returns true if v does not over-/underflow an int64. func convertibleToInt64(v float64) bool { return v <= maxInt64 && v >= minInt64 @@ -142,6 +166,7 @@ func NewEngine(queryable Queryable, o *EngineOptions) *Engine { if o == nil { o = DefaultEngineOptions } + maxConcurrentQueries.Set(float64(o.MaxConcurrentQueries)) return &Engine{ queryable: queryable, gate: newQueryGate(o.MaxConcurrentQueries), @@ -226,6 +251,9 @@ func (ng *Engine) newTestQuery(f func(context.Context) error) Query { // At this point per query only one EvalStmt is evaluated. Alert and record // statements are not handled by the Engine. func (ng *Engine) exec(ctx context.Context, q *query) (Value, error) { + currentQueries.Inc() + defer currentQueries.Dec() + ctx, cancel := context.WithTimeout(ctx, ng.options.Timeout) q.cancel = cancel diff --git a/retrieval/scrape.go b/retrieval/scrape.go index 2bfde21b5..81421d585 100644 --- a/retrieval/scrape.go +++ b/retrieval/scrape.go @@ -34,9 +34,10 @@ import ( ) const ( - scrapeHealthMetricName = "up" - scrapeDurationMetricName = "scrape_duration_seconds" - scrapeSamplesMetricName = "scrape_samples_scraped" + scrapeHealthMetricName = "up" + scrapeDurationMetricName = "scrape_duration_seconds" + scrapeSamplesMetricName = "scrape_samples_scraped" + samplesPostRelabelMetricName = "scrape_samples_post_metric_relabeling" ) var ( @@ -77,6 +78,12 @@ var ( }, []string{"scrape_job"}, ) + targetScrapeSampleLimit = prometheus.NewCounter( + prometheus.CounterOpts{ + Name: "prometheus_target_scrapes_exceeded_sample_limit_total", + Help: "Total number of scrapes that hit the sample limit and were rejected.", + }, + ) ) func init() { @@ -85,6 +92,7 @@ func init() { prometheus.MustRegister(targetReloadIntervalLength) prometheus.MustRegister(targetSyncIntervalLength) prometheus.MustRegister(targetScrapePoolSyncsCounter) + prometheus.MustRegister(targetScrapeSampleLimit) } // scrapePool manages scrapes for sets of targets. @@ -285,6 +293,15 @@ func (sp *scrapePool) sampleAppender(target *Target) storage.Appender { if err != nil { panic(err) } + + // The limit is applied after metrics are potentially dropped via relabeling. + if sp.config.SampleLimit > 0 { + app = &limitAppender{ + Appender: app, + limit: int(sp.config.SampleLimit), + } + } + // The relabelAppender has to be inside the label-modifying appenders // so the relabeling rules are applied to the correct label set. if mrc := sp.config.MetricRelabelConfigs; len(mrc) > 0 { @@ -427,6 +444,7 @@ func (sl *scrapeLoop) run(interval, timeout time.Duration, errc chan<- error) { } var ( + total, added int start = time.Now() scrapeCtx, _ = context.WithTimeout(sl.ctx, timeout) ) @@ -438,14 +456,13 @@ func (sl *scrapeLoop) run(interval, timeout time.Duration, errc chan<- error) { ) } - n := 0 buf := bytes.NewBuffer(getScrapeBuf()) err := sl.scraper.scrape(scrapeCtx, buf) if err == nil { b := buf.Bytes() - if n, err = sl.append(b, start); err != nil { + if total, added, err = sl.append(b, start); err != nil { log.With("err", err).Error("append failed") } putScrapeBuf(b) @@ -453,7 +470,7 @@ func (sl *scrapeLoop) run(interval, timeout time.Duration, errc chan<- error) { errc <- err } - sl.report(start, time.Since(start), n, err) + sl.report(start, time.Since(start), total, added, err) last = start select { @@ -490,7 +507,7 @@ func (s samples) Less(i, j int) bool { return s[i].t < s[j].t } -func (sl *scrapeLoop) append(b []byte, ts time.Time) (n int, err error) { +func (sl *scrapeLoop) append(b []byte, ts time.Time) (total, added int, err error) { var ( app = sl.appender() p = textparse.New(b) @@ -498,6 +515,8 @@ func (sl *scrapeLoop) append(b []byte, ts time.Time) (n int, err error) { ) for p.Next() { + total++ + t := defTime met, tp, v := p.At() if tp != nil { @@ -508,6 +527,7 @@ func (sl *scrapeLoop) append(b []byte, ts time.Time) (n int, err error) { ref, ok := sl.cache[mets] if ok { if err = app.Add(ref, t, v); err == nil { + added++ continue } else if err != storage.ErrNotFound { break @@ -517,31 +537,36 @@ func (sl *scrapeLoop) append(b []byte, ts time.Time) (n int, err error) { if !ok { var lset labels.Labels p.Metric(&lset) + ref, err = app.SetSeries(lset) + // TODO(fabxc): also add a dropped-cache? + if err == errSeriesDropped { + continue + } if err != nil { break } if err = app.Add(ref, t, v); err != nil { break } + added++ } sl.cache[mets] = ref - n++ } if err == nil { err = p.Err() } if err != nil { app.Rollback() - return 0, err + return total, 0, err } if err := app.Commit(); err != nil { - return 0, err + return total, 0, err } - return n, nil + return total, added, nil } -func (sl *scrapeLoop) report(start time.Time, duration time.Duration, scrapedSamples int, err error) error { +func (sl *scrapeLoop) report(start time.Time, duration time.Duration, scraped, appended int, err error) error { sl.scraper.report(start, duration, err) ts := timestamp.FromTime(start) @@ -561,7 +586,11 @@ func (sl *scrapeLoop) report(start time.Time, duration time.Duration, scrapedSam app.Rollback() return err } - if err := sl.addReportSample(app, scrapeSamplesMetricName, ts, float64(scrapedSamples)); err != nil { + if err := sl.addReportSample(app, scrapeSamplesMetricName, ts, float64(scraped)); err != nil { + app.Rollback() + return err + } + if err := sl.addReportSample(app, samplesPostRelabelMetricName, ts, float64(appended)); err != nil { app.Rollback() return err } diff --git a/retrieval/scrape_test.go b/retrieval/scrape_test.go index a4f33b797..94e3d4e44 100644 --- a/retrieval/scrape_test.go +++ b/retrieval/scrape_test.go @@ -285,6 +285,7 @@ func TestScrapePoolSampleAppender(t *testing.T) { } cfg.HonorLabels = true + cfg.SampleLimit = 100 wrapped = sp.sampleAppender(target) hl, ok := wrapped.(honorLabelsAppender) @@ -295,7 +296,11 @@ func TestScrapePoolSampleAppender(t *testing.T) { if !ok { t.Fatalf("Expected relabelAppender but got %T", hl.Appender) } - if _, ok := re.Appender.(nopAppender); !ok { + lm, ok := re.Appender.(*limitAppender) + if !ok { + t.Fatalf("Expected limitAppender but got %T", lm.Appender) + } + if _, ok := lm.Appender.(nopAppender); !ok { t.Fatalf("Expected base appender but got %T", re.Appender) } } @@ -305,7 +310,7 @@ func TestScrapeLoopStop(t *testing.T) { sl := newScrapeLoop(context.Background(), scraper, nil, nil) // The scrape pool synchronizes on stopping scrape loops. However, new scrape - // loops are syarted asynchronously. Thus it's possible, that a loop is stopped + // loops are started asynchronously. Thus it's possible, that a loop is stopped // again before having started properly. // Stopping not-yet-started loops must block until the run method was called and exited. // The run method must exit immediately. diff --git a/retrieval/target.go b/retrieval/target.go index 2f31af787..c32a12aab 100644 --- a/retrieval/target.go +++ b/retrieval/target.go @@ -227,6 +227,26 @@ func (ts Targets) Len() int { return len(ts) } func (ts Targets) Less(i, j int) bool { return ts[i].URL().String() < ts[j].URL().String() } func (ts Targets) Swap(i, j int) { ts[i], ts[j] = ts[j], ts[i] } +// limitAppender limits the number of total appended samples in a batch. +type limitAppender struct { + storage.Appender + + limit int + i int +} + +func (app *limitAppender) Add(ref uint64, t int64, v float64) error { + if app.i+1 > app.limit { + return errors.New("sample limit exceeded") + } + + if err := app.Appender.Add(ref, t, v); err != nil { + return fmt.Errorf("sample limit of %d exceeded", app.limit) + } + app.i++ + return nil +} + // Merges the ingested sample's metric with the label set. On a collision the // value of the ingested label is stored in a label prefixed with 'exported_'. type ruleLabelsAppender struct { diff --git a/retrieval/targetmanager.go b/retrieval/targetmanager.go index 693c26a03..6fa89c2a4 100644 --- a/retrieval/targetmanager.go +++ b/retrieval/targetmanager.go @@ -136,16 +136,16 @@ func (tm *TargetManager) reload() { } // Targets returns the targets currently being scraped bucketed by their job name. -func (tm *TargetManager) Targets() []Target { +func (tm *TargetManager) Targets() []*Target { tm.mtx.RLock() defer tm.mtx.RUnlock() - targets := []Target{} + targets := []*Target{} for _, ps := range tm.targetSets { ps.sp.mtx.RLock() for _, t := range ps.sp.targets { - targets = append(targets, *t) + targets = append(targets, t) } ps.sp.mtx.RUnlock() diff --git a/vendor/github.com/prometheus/common/expfmt/decode.go b/vendor/github.com/prometheus/common/expfmt/decode.go index 487fdc6cc..a7a42d5ef 100644 --- a/vendor/github.com/prometheus/common/expfmt/decode.go +++ b/vendor/github.com/prometheus/common/expfmt/decode.go @@ -31,6 +31,7 @@ type Decoder interface { Decode(*dto.MetricFamily) error } +// DecodeOptions contains options used by the Decoder and in sample extraction. type DecodeOptions struct { // Timestamp is added to each value from the stream that has no explicit timestamp set. Timestamp model.Time @@ -142,6 +143,8 @@ func (d *textDecoder) Decode(v *dto.MetricFamily) error { return nil } +// SampleDecoder wraps a Decoder to extract samples from the metric families +// decoded by the wrapped Decoder. type SampleDecoder struct { Dec Decoder Opts *DecodeOptions @@ -149,37 +152,51 @@ type SampleDecoder struct { f dto.MetricFamily } +// Decode calls the Decode method of the wrapped Decoder and then extracts the +// samples from the decoded MetricFamily into the provided model.Vector. func (sd *SampleDecoder) Decode(s *model.Vector) error { - if err := sd.Dec.Decode(&sd.f); err != nil { + err := sd.Dec.Decode(&sd.f) + if err != nil { return err } - *s = extractSamples(&sd.f, sd.Opts) - return nil + *s, err = extractSamples(&sd.f, sd.Opts) + return err } -// Extract samples builds a slice of samples from the provided metric families. -func ExtractSamples(o *DecodeOptions, fams ...*dto.MetricFamily) model.Vector { - var all model.Vector +// ExtractSamples builds a slice of samples from the provided metric +// families. If an error occurs during sample extraction, it continues to +// extract from the remaining metric families. The returned error is the last +// error that has occured. +func ExtractSamples(o *DecodeOptions, fams ...*dto.MetricFamily) (model.Vector, error) { + var ( + all model.Vector + lastErr error + ) for _, f := range fams { - all = append(all, extractSamples(f, o)...) + some, err := extractSamples(f, o) + if err != nil { + lastErr = err + continue + } + all = append(all, some...) } - return all + return all, lastErr } -func extractSamples(f *dto.MetricFamily, o *DecodeOptions) model.Vector { +func extractSamples(f *dto.MetricFamily, o *DecodeOptions) (model.Vector, error) { switch f.GetType() { case dto.MetricType_COUNTER: - return extractCounter(o, f) + return extractCounter(o, f), nil case dto.MetricType_GAUGE: - return extractGauge(o, f) + return extractGauge(o, f), nil case dto.MetricType_SUMMARY: - return extractSummary(o, f) + return extractSummary(o, f), nil case dto.MetricType_UNTYPED: - return extractUntyped(o, f) + return extractUntyped(o, f), nil case dto.MetricType_HISTOGRAM: - return extractHistogram(o, f) + return extractHistogram(o, f), nil } - panic("expfmt.extractSamples: unknown metric family type") + return nil, fmt.Errorf("expfmt.extractSamples: unknown metric family type %v", f.GetType()) } func extractCounter(o *DecodeOptions, f *dto.MetricFamily) model.Vector { diff --git a/vendor/github.com/prometheus/common/expfmt/expfmt.go b/vendor/github.com/prometheus/common/expfmt/expfmt.go index fae10f6eb..371ac7503 100644 --- a/vendor/github.com/prometheus/common/expfmt/expfmt.go +++ b/vendor/github.com/prometheus/common/expfmt/expfmt.go @@ -11,14 +11,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -// A package for reading and writing Prometheus metrics. +// Package expfmt contains tools for reading and writing Prometheus metrics. package expfmt +// Format specifies the HTTP content type of the different wire protocols. type Format string +// Constants to assemble the Content-Type values for the different wire protocols. const ( - TextVersion = "0.0.4" - + TextVersion = "0.0.4" ProtoType = `application/vnd.google.protobuf` ProtoProtocol = `io.prometheus.client.MetricFamily` ProtoFmt = ProtoType + "; proto=" + ProtoProtocol + ";" diff --git a/vendor/github.com/prometheus/common/log/syslog_formatter.go b/vendor/github.com/prometheus/common/log/syslog_formatter.go index fd8c6fbee..64f5fdac9 100644 --- a/vendor/github.com/prometheus/common/log/syslog_formatter.go +++ b/vendor/github.com/prometheus/common/log/syslog_formatter.go @@ -23,6 +23,8 @@ import ( "github.com/Sirupsen/logrus" ) +var _ logrus.Formatter = (*syslogger)(nil) + func init() { setSyslogFormatter = func(appname, local string) error { if appname == "" { @@ -43,7 +45,7 @@ func init() { } } -var ceeTag = []byte("@cee:") +var prefixTag []byte type syslogger struct { wrap logrus.Formatter @@ -56,6 +58,11 @@ func newSyslogger(appname string, facility string, fmter logrus.Formatter) (*sys return nil, err } out, err := syslog.New(priority, appname) + _, isJSON := fmter.(*logrus.JSONFormatter) + if isJSON { + // add cee tag to json formatted syslogs + prefixTag = []byte("@cee:") + } return &syslogger{ out: out, wrap: fmter, @@ -92,7 +99,7 @@ func (s *syslogger) Format(e *logrus.Entry) ([]byte, error) { } // only append tag to data sent to syslog (line), not to what // is returned - line := string(append(ceeTag, data...)) + line := string(append(prefixTag, data...)) switch e.Level { case logrus.PanicLevel: diff --git a/vendor/github.com/prometheus/common/model/labels.go b/vendor/github.com/prometheus/common/model/labels.go index 3b72e7ff8..41051a01a 100644 --- a/vendor/github.com/prometheus/common/model/labels.go +++ b/vendor/github.com/prometheus/common/model/labels.go @@ -80,14 +80,18 @@ const ( QuantileLabel = "quantile" ) -// LabelNameRE is a regular expression matching valid label names. +// LabelNameRE is a regular expression matching valid label names. Note that the +// IsValid method of LabelName performs the same check but faster than a match +// with this regular expression. var LabelNameRE = regexp.MustCompile("^[a-zA-Z_][a-zA-Z0-9_]*$") // A LabelName is a key for a LabelSet or Metric. It has a value associated // therewith. type LabelName string -// IsValid is true iff the label name matches the pattern of LabelNameRE. +// IsValid is true iff the label name matches the pattern of LabelNameRE. This +// method, however, does not use LabelNameRE for the check but a much faster +// hardcoded implementation. func (ln LabelName) IsValid() bool { if len(ln) == 0 { return false @@ -106,7 +110,7 @@ func (ln *LabelName) UnmarshalYAML(unmarshal func(interface{}) error) error { if err := unmarshal(&s); err != nil { return err } - if !LabelNameRE.MatchString(s) { + if !LabelName(s).IsValid() { return fmt.Errorf("%q is not a valid label name", s) } *ln = LabelName(s) @@ -119,7 +123,7 @@ func (ln *LabelName) UnmarshalJSON(b []byte) error { if err := json.Unmarshal(b, &s); err != nil { return err } - if !LabelNameRE.MatchString(s) { + if !LabelName(s).IsValid() { return fmt.Errorf("%q is not a valid label name", s) } *ln = LabelName(s) diff --git a/vendor/github.com/prometheus/common/model/labelset.go b/vendor/github.com/prometheus/common/model/labelset.go index 5f931cdb9..6eda08a73 100644 --- a/vendor/github.com/prometheus/common/model/labelset.go +++ b/vendor/github.com/prometheus/common/model/labelset.go @@ -160,7 +160,7 @@ func (l *LabelSet) UnmarshalJSON(b []byte) error { // LabelName as a string and does not call its UnmarshalJSON method. // Thus, we have to replicate the behavior here. for ln := range m { - if !LabelNameRE.MatchString(string(ln)) { + if !ln.IsValid() { return fmt.Errorf("%q is not a valid label name", ln) } } diff --git a/vendor/github.com/prometheus/common/model/metric.go b/vendor/github.com/prometheus/common/model/metric.go index a5da59a50..f7250909b 100644 --- a/vendor/github.com/prometheus/common/model/metric.go +++ b/vendor/github.com/prometheus/common/model/metric.go @@ -21,8 +21,11 @@ import ( ) var ( - separator = []byte{0} - MetricNameRE = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_:]*$`) + separator = []byte{0} + // MetricNameRE is a regular expression matching valid metric + // names. Note that the IsValidMetricName function performs the same + // check but faster than a match with this regular expression. + MetricNameRE = regexp.MustCompile(`^[a-zA-Z_:][a-zA-Z0-9_:]*$`) ) // A Metric is similar to a LabelSet, but the key difference is that a Metric is @@ -41,7 +44,7 @@ func (m Metric) Before(o Metric) bool { // Clone returns a copy of the Metric. func (m Metric) Clone() Metric { - clone := Metric{} + clone := make(Metric, len(m)) for k, v := range m { clone[k] = v } @@ -85,6 +88,8 @@ func (m Metric) FastFingerprint() Fingerprint { } // IsValidMetricName returns true iff name matches the pattern of MetricNameRE. +// This function, however, does not use MetricNameRE for the check but a much +// faster hardcoded implementation. func IsValidMetricName(n LabelValue) bool { if len(n) == 0 { return false diff --git a/vendor/github.com/prometheus/common/route/route.go b/vendor/github.com/prometheus/common/route/route.go index 930b52d4f..1e5638ed9 100644 --- a/vendor/github.com/prometheus/common/route/route.go +++ b/vendor/github.com/prometheus/common/route/route.go @@ -33,18 +33,19 @@ func WithParam(ctx context.Context, p, v string) context.Context { return context.WithValue(ctx, param(p), v) } -type contextFn func(r *http.Request) (context.Context, error) +// ContextFunc returns a new context for a request. +type ContextFunc func(r *http.Request) (context.Context, error) // Router wraps httprouter.Router and adds support for prefixed sub-routers // and per-request context injections. type Router struct { rtr *httprouter.Router prefix string - ctxFn contextFn + ctxFn ContextFunc } // New returns a new Router. -func New(ctxFn contextFn) *Router { +func New(ctxFn ContextFunc) *Router { if ctxFn == nil { ctxFn = func(r *http.Request) (context.Context, error) { return context.Background(), nil diff --git a/vendor/vendor.json b/vendor/vendor.json index b58b59dfe..b3f424b38 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -516,40 +516,40 @@ "revisionTime": "2015-02-12T10:17:44Z" }, { - "checksumSHA1": "mHyjbJ3BWOfUV6q9f5PBt0gaY1k=", + "checksumSHA1": "jG8qYuDUuaZeflt4JxBBdyQBsXw=", "path": "github.com/prometheus/common/expfmt", - "revision": "85637ea67b04b5c3bb25e671dacded2977f8f9f6", - "revisionTime": "2016-10-02T21:02:34Z" + "revision": "dd2f054febf4a6c00f2343686efb775948a8bff4", + "revisionTime": "2017-01-08T23:12:12Z" }, { "checksumSHA1": "GWlM3d2vPYyNATtTFgftS10/A9w=", "path": "github.com/prometheus/common/internal/bitbucket.org/ww/goautoneg", - "revision": "85637ea67b04b5c3bb25e671dacded2977f8f9f6", - "revisionTime": "2016-10-02T21:02:34Z" + "revision": "dd2f054febf4a6c00f2343686efb775948a8bff4", + "revisionTime": "2017-01-08T23:12:12Z" }, { - "checksumSHA1": "UU6hIfhVjnAYDADQEfE/3T7Ddm8=", + "checksumSHA1": "ZA4MLHNAP905WiAOLy4BBzmcuxM=", "path": "github.com/prometheus/common/log", - "revision": "85637ea67b04b5c3bb25e671dacded2977f8f9f6", - "revisionTime": "2016-10-02T21:02:34Z" + "revision": "dd2f054febf4a6c00f2343686efb775948a8bff4", + "revisionTime": "2017-01-08T23:12:12Z" }, { - "checksumSHA1": "nFie+rxcX5WdIv1diZ+fu3aj6lE=", + "checksumSHA1": "vopCLXHzYm+3l5fPKOf4/fQwrCM=", "path": "github.com/prometheus/common/model", - "revision": "85637ea67b04b5c3bb25e671dacded2977f8f9f6", - "revisionTime": "2016-10-02T21:02:34Z" + "revision": "dd2f054febf4a6c00f2343686efb775948a8bff4", + "revisionTime": "2017-01-08T23:12:12Z" }, { - "checksumSHA1": "QQKJYoGcY10nIHxhBEHwjwUZQzk=", + "checksumSHA1": "ZbbESWBHHcPUJ/A5yrzKhTHuPc8=", "path": "github.com/prometheus/common/route", - "revision": "85637ea67b04b5c3bb25e671dacded2977f8f9f6", - "revisionTime": "2016-10-02T21:02:34Z" + "revision": "dd2f054febf4a6c00f2343686efb775948a8bff4", + "revisionTime": "2017-01-08T23:12:12Z" }, { "checksumSHA1": "91KYK0SpvkaMJJA2+BcxbVnyRO0=", "path": "github.com/prometheus/common/version", - "revision": "85637ea67b04b5c3bb25e671dacded2977f8f9f6", - "revisionTime": "2016-10-02T21:02:34Z" + "revision": "dd2f054febf4a6c00f2343686efb775948a8bff4", + "revisionTime": "2017-01-08T23:12:12Z" }, { "checksumSHA1": "W218eJZPXJG783fUr/z6IaAZyes=", diff --git a/web/api/v1/api.go b/web/api/v1/api.go index 6e2e66da0..4a8bb6d1b 100644 --- a/web/api/v1/api.go +++ b/web/api/v1/api.go @@ -69,7 +69,11 @@ func (e *apiError) Error() string { } type targetRetriever interface { - Targets() []retrieval.Target + Targets() []*retrieval.Target +} + +type alertmanagerRetriever interface { + Alertmanagers() []string } type response struct { @@ -94,20 +98,22 @@ type API struct { Storage storage.Storage QueryEngine *promql.Engine - targetRetriever targetRetriever + targetRetriever targetRetriever + alertmanagerRetriever alertmanagerRetriever context func(r *http.Request) context.Context now func() time.Time } // NewAPI returns an initialized API type. -func NewAPI(qe *promql.Engine, st storage.Storage, tr targetRetriever) *API { +func NewAPI(qe *promql.Engine, st storage.Storage, tr targetRetriever, ar alertmanagerRetriever) *API { return &API{ - QueryEngine: qe, - Storage: st, - targetRetriever: tr, - context: route.Context, - now: time.Now, + QueryEngine: qe, + Storage: st, + targetRetriever: tr, + alertmanagerRetriever: ar, + context: route.Context, + now: time.Now, } } @@ -140,6 +146,7 @@ func (api *API) Register(r *route.Router) { r.Del("/series", instr("drop_series", api.dropSeries)) r.Get("/targets", instr("targets", api.targets)) + r.Get("/alertmanagers", instr("alertmanagers", api.alertmanagers)) } type queryData struct { @@ -290,7 +297,7 @@ func (api *API) series(r *http.Request) (interface{}, *apiError) { } else { end = maxTime } - + var matcherSets [][]*labels.Matcher for _, s := range r.Form["match[]"] { matchers, err := promql.ParseMetricSelector(s) @@ -360,16 +367,20 @@ type Target struct { // Any labels that are added to this target and its metrics. Labels map[string]string `json:"labels"` - ScrapeUrl string `json:"scrapeUrl"` + ScrapeURL string `json:"scrapeUrl"` LastError string `json:"lastError"` LastScrape time.Time `json:"lastScrape"` Health retrieval.TargetHealth `json:"health"` } +type TargetDiscovery struct { + ActiveTargets []*Target `json:"activeTargets"` +} + func (api *API) targets(r *http.Request) (interface{}, *apiError) { targets := api.targetRetriever.Targets() - res := make([]*Target, len(targets)) + res := &TargetDiscovery{ActiveTargets: make([]*Target, len(targets))} for i, t := range targets { lastErrStr := "" @@ -378,10 +389,10 @@ func (api *API) targets(r *http.Request) (interface{}, *apiError) { lastErrStr = lastErr.Error() } - res[i] = &Target{ + res.ActiveTargets[i] = &Target{ DiscoveredLabels: t.DiscoveredLabels().Map(), Labels: t.Labels().Map(), - ScrapeUrl: t.URL().String(), + ScrapeURL: t.URL().String(), LastError: lastErrStr, LastScrape: t.LastScrape(), Health: t.Health(), @@ -391,6 +402,25 @@ func (api *API) targets(r *http.Request) (interface{}, *apiError) { return res, nil } +type AlertmanagerDiscovery struct { + ActiveAlertmanagers []*AlertmanagerTarget `json:"activeAlertmanagers"` +} + +type AlertmanagerTarget struct { + URL string `json:"url"` +} + +func (api *API) alertmanagers(r *http.Request) (interface{}, *apiError) { + urls := api.alertmanagerRetriever.Alertmanagers() + ams := &AlertmanagerDiscovery{ActiveAlertmanagers: make([]*AlertmanagerTarget, len(urls))} + + for i := range urls { + ams.ActiveAlertmanagers[i] = &AlertmanagerTarget{URL: urls[i]} + } + + return ams, nil +} + func respond(w http.ResponseWriter, data interface{}) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) diff --git a/web/api/v1/api_test.go b/web/api/v1/api_test.go index 490f649d2..2a4ed7da9 100644 --- a/web/api/v1/api_test.go +++ b/web/api/v1/api_test.go @@ -35,9 +35,15 @@ import ( "github.com/prometheus/prometheus/retrieval" ) -type targetRetrieverFunc func() []retrieval.Target +type targetRetrieverFunc func() []*retrieval.Target -func (f targetRetrieverFunc) Targets() []retrieval.Target { +func (f targetRetrieverFunc) Targets() []*retrieval.Target { + return f() +} + +type alertmanagerRetrieverFunc func() []string + +func (f alertmanagerRetrieverFunc) Alertmanagers() []string { return f() } @@ -59,9 +65,9 @@ func TestEndpoints(t *testing.T) { now := time.Now() - tr := targetRetrieverFunc(func() []retrieval.Target { - return []retrieval.Target{ - *retrieval.NewTarget( + tr := targetRetrieverFunc(func() []*retrieval.Target { + return []*retrieval.Target{ + retrieval.NewTarget( labels.FromMap(map[string]string{ model.SchemeLabel: "http", model.AddressLabel: "example.com:8080", @@ -73,11 +79,16 @@ func TestEndpoints(t *testing.T) { } }) + ar := alertmanagerRetrieverFunc(func() []string { + return []string{"http://alertmanager.example.com:8080/api/v1/alerts"} + }) + api := &API{ - Storage: suite.Storage(), - QueryEngine: suite.QueryEngine(), - targetRetriever: tr, - now: func() time.Time { return now }, + Storage: suite.Storage(), + QueryEngine: suite.QueryEngine(), + targetRetriever: tr, + alertmanagerRetriever: ar, + now: func() time.Time { return now }, } start := time.Unix(0, 0) diff --git a/web/ui/bindata.go b/web/ui/bindata.go index 45d3505db..49687ca2d 100644 --- a/web/ui/bindata.go +++ b/web/ui/bindata.go @@ -13,6 +13,7 @@ // web/ui/static/css/prom_console.css // web/ui/static/css/prometheus.css // web/ui/static/img/ajax-loader.gif +// web/ui/static/img/favicon.ico // web/ui/static/js/alerts.js // web/ui/static/js/graph.js // web/ui/static/js/graph_template.handlebar @@ -105,7 +106,7 @@ func (fi bindataFileInfo) Sys() interface{} { return nil } -var _webUiTemplates_baseHtml = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xbc\x56\x51\x6f\xdc\x36\x0c\x7e\xef\xaf\xe0\xd4\x62\x6d\x1e\x7c\xc6\xd0\x97\xa1\xb1\x0d\x74\x59\xbb\x16\x18\xb0\x43\x7b\x0f\x1b\x86\xa1\x60\x6c\xfa\xac\x54\x96\x54\x89\xf6\x72\x38\xe4\xbf\x0f\x3a\xd9\x9e\xcf\xb9\x4b\xd0\x6d\xd8\x93\x28\x9a\xfc\x48\x91\x1f\x25\x67\xdf\xfc\xf8\xcb\xd5\xe6\xb7\xf5\x1b\x68\xb8\x55\xc5\x93\x2c\x2c\xa0\x50\x6f\x73\x41\x5a\x14\x4f\x00\xb2\x86\xb0\x0a\x02\x40\xd6\x12\x23\x34\xcc\x36\xa1\x2f\x9d\xec\x73\x71\x65\x34\x93\xe6\x64\xb3\xb3\x24\xa0\x8c\xbb\x5c\x30\xdd\x72\x1a\xa0\x2e\xa1\x6c\xd0\x79\xe2\xbc\xe3\x3a\xf9\x5e\x0c\x38\x2c\x59\x51\xb1\x76\xa6\x25\x6e\xa8\xf3\xb0\x91\x2d\xc1\x47\x72\x92\x3c\x5c\x19\xa5\xa8\x64\x69\x34\xa0\xae\x60\xed\x4c\x49\xde\x4b\xbd\x0d\x06\x3d\xb9\x2c\x8d\xee\x11\xca\x97\x4e\x5a\x06\xef\xca\x5c\xec\xf7\x60\x91\x9b\xb5\xa3\x5a\xde\xc2\xdd\x5d\xea\x19\x59\x96\x69\x4f\xba\x32\x2e\xbd\xf1\xe9\xcd\x97\x8e\xdc\x6e\xd5\x4a\xbd\xba\xf1\xa2\xc8\xd2\xe8\xfe\xf5\x58\xd7\xc6\xb0\x67\x87\x36\x79\xb9\x7a\xb9\xfa\x2e\x60\x4f\xaa\x13\xf0\x11\x5f\x49\xfd\x19\x78\x67\x69\xa8\x50\xe9\xbd\x00\x47\x2a\x17\x9e\x77\x8a\x7c\x43\xc4\x02\x1a\x47\xf5\x57\xc7\x2f\xfd\x32\x81\x00\x5e\xfc\x77\x71\x43\x00\x3b\x35\x6c\x40\x9f\x97\x2d\xc6\x02\xe8\xd1\xc1\xfa\xf5\xe6\xdd\xa7\xf5\x87\x37\x6f\xdf\xff\x0a\x39\xdc\xc3\x14\x97\x83\xed\xb3\x17\x75\xa7\x63\xab\x5f\x5c\xc0\x7e\xd0\x06\xfd\xf3\xdf\x2b\x64\x4c\xd8\x6c\xb7\x2a\xa4\x6d\x8c\x62\x69\xc5\x1f\xcf\x2f\x56\x83\xfc\xe2\x62\x30\xbf\x8b\xc2\xa2\xd8\xfb\x3d\x53\x6b\x15\x32\x81\x08\x0c\x16\xb0\xba\xbb\x0b\x74\x4e\x23\x9f\x83\x78\x6d\xaa\xdd\x50\x22\x8d\x3d\x94\x0a\xbd\xcf\x85\xc6\xfe\x1a\x1d\xc4\x25\x91\xba\x27\xe7\x69\xdc\xd6\xf2\x96\xaa\x84\x8d\x15\xe3\x79\xb3\x4a\x4e\xae\x61\x00\x50\x6a\x72\x49\xad\x3a\x59\x4d\x36\xc7\x56\x03\x54\xc8\x83\xdc\xcc\x26\x64\xd4\x31\x1b\x3d\xf4\x2a\x6e\xc4\xc2\x2d\x96\x04\x4a\xa3\x14\x5a\x4f\x95\x80\xa3\x4a\x8d\xfa\x51\x8d\x6e\x4b\x9c\x8b\xa7\xd1\x5b\x00\x3a\x89\x09\xdd\x5a\xd4\x15\x55\xb9\xa8\x51\x05\xdb\x83\x36\x64\xef\x8c\x9a\x42\x1d\xa5\x16\xfa\x6c\x51\x8f\xc9\x78\x97\x18\xad\x76\xa2\xd8\xc4\x74\x34\xf6\x72\x8b\xa1\x93\x59\x1a\xec\x1e\x70\x95\xa5\xd1\xc9\x01\xfe\xff\x32\xcd\xd2\x58\xca\x23\x1d\x2e\xea\x7a\xed\x50\x57\x67\xa7\x40\xcc\x6e\xab\x2c\xc5\x59\x63\xd3\x4a\xf6\x8b\x3e\xcb\x6a\x2a\xe1\x22\xc8\xd8\x9d\xa9\x7d\xc7\xed\xef\xd4\xcc\x7e\xa4\xdc\x4c\x54\x54\xf3\xa2\x2b\xfb\xfd\xb3\xd2\x68\x6f\x14\x79\x78\x95\xc3\x28\xaf\x91\x9b\x03\xdf\xe7\x96\xb2\x86\xc9\x78\xf1\x31\x53\xb2\xc8\x70\x3a\xfd\xcc\x4c\x14\x57\x83\x1c\xce\x9d\xa5\x4a\x2e\x13\x00\xd2\x15\x3c\x8c\xb7\xa8\x26\x2a\x72\xec\x45\xf1\xfa\xb0\x9e\xc6\x7d\x18\x61\xeb\xd0\x36\xa2\xf8\x29\x2c\x67\xfd\xc7\x62\x56\xce\xd8\xca\xfc\xa9\x17\xa5\x3b\x90\x20\xe2\x3f\x15\x4b\xdb\x61\xa0\x16\xd3\x35\x21\x81\x33\x6a\x36\xa2\x87\xf9\x69\xd0\x5b\x63\x3b\x9b\x0b\x76\x1d\x9d\x19\xb5\xe2\x23\x23\x77\xfe\x98\xbc\x25\x3a\xe2\x89\xb9\x47\xfc\xba\xc7\x8c\x29\xc1\x96\x74\x77\xef\x44\x8f\xd5\xcd\x1f\xa2\x8b\xe2\x43\xa7\x39\xbc\xb9\xdf\x62\x6b\x2f\xe1\x87\x4e\xaa\x0a\xde\xeb\xda\xb8\x76\x18\xe2\x53\x25\x7d\x1c\xbe\x56\xb8\xf5\x81\x31\x6d\x8b\xba\x4a\x7e\x96\x9a\xe0\x6d\xd0\xfd\x53\xc0\xd2\xe8\x5a\x6e\x0f\x1c\xac\xe5\xb6\x73\xff\x2a\x3b\xd7\x29\x3a\x9c\xfd\x2c\x99\x1f\xc7\x88\x17\xaa\x17\xc5\x26\x0a\xe7\x70\xb2\xb4\x53\x0b\x42\x9e\xa4\xf8\x39\x46\x86\xbf\x2c\xff\x2a\x9d\xbf\xb9\xd2\x08\x18\xef\xf3\x4f\xd7\x0a\xf5\x67\x51\xbc\x23\x65\xef\xf1\x65\x19\xe9\x38\x97\xa3\x1b\x6b\xb6\xc9\x52\x8d\xfd\x89\xd7\x73\xf8\xab\xfb\xfb\x01\x8d\xcf\x66\x96\xc6\x5f\xc6\xbf\x02\x00\x00\xff\xff\xdf\x1b\xa4\x60\x43\x0a\x00\x00") +var _webUiTemplates_baseHtml = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xbc\x56\x4d\x8f\xdc\x36\x0f\xbe\xe7\x57\xf0\x55\x82\x37\xbb\x07\x8f\x51\xe4\x52\x64\x6d\x03\xe9\x36\x69\x02\x14\xe8\x20\x99\x43\x8b\xa2\x08\x38\x36\x6d\x6b\x23\x4b\x8a\x44\xbb\x3b\x18\xcc\x7f\x2f\x34\xfe\xa8\xc7\x3b\xb3\x8b\x7e\xa0\x27\x51\x34\xf9\x90\x7a\x48\x4a\x4e\xfe\xf7\xfd\x4f\xb7\x9b\x5f\xd6\x6f\xa1\xe6\x46\x65\xcf\x92\xb0\x80\x42\x5d\xa5\x82\xb4\xc8\x9e\x01\x24\x35\x61\x11\x04\x80\xa4\x21\x46\xa8\x99\x6d\x44\x5f\x5b\xd9\xa5\xe2\xd6\x68\x26\xcd\xd1\x66\x67\x49\x40\xde\xef\x52\xc1\x74\xcf\x71\x80\xba\x81\xbc\x46\xe7\x89\xd3\x96\xcb\xe8\x5b\x31\xe0\xb0\x64\x45\xd9\xda\x99\x86\xb8\xa6\xd6\xc3\x46\x36\x04\x9f\xc8\x49\xf2\x70\x6b\x94\xa2\x9c\xa5\xd1\x80\xba\x80\xb5\x33\x39\x79\x2f\x75\x15\x0c\x3a\x72\x49\xdc\xbb\xf7\x50\x4a\xea\x2f\xe0\x48\xa5\xc2\xd7\xc6\x71\xde\x32\xc8\xdc\x68\x01\xb5\xa3\x32\x15\xfb\x3d\x58\xe4\x7a\xed\xa8\x94\xf7\x70\x38\xc4\x9e\x91\x65\x1e\xcb\xa6\x8a\x4b\xec\x82\xe9\x4a\xe6\x66\x4c\xcc\xe7\x4e\x5a\x06\xef\xf2\xcb\xae\x1d\xe9\xc2\xb8\xf8\xce\xc7\x77\x5f\x5b\x72\xbb\x55\x23\xf5\xea\xce\x8b\x2c\x89\x7b\xf7\xbf\x8e\xb5\x35\x86\x3d\x3b\xb4\xd1\xab\xd5\xab\xd5\x37\x01\x7b\x52\x9d\x81\x9f\x9d\x9c\x77\x96\x06\xbe\x73\xef\xc5\xc0\x04\xef\x14\xf9\x9a\x88\x9f\xa2\xe1\x42\xfc\xdc\x2f\x13\x08\xe0\xd9\xbf\x17\x37\x04\xb0\x53\xf9\x07\xf4\x39\x6d\x7d\x2c\x80\x0e\x1d\xac\xdf\x6c\xde\x7f\x5e\x7f\x7c\xfb\xee\xc3\xcf\x90\xc2\x03\x4c\x71\x33\xd8\xbe\xb8\x2a\x5b\xdd\x37\xce\xd5\x35\xec\x07\x6d\xd0\xbf\xfc\xb5\x40\xc6\x88\x4d\x55\xa9\x90\xb6\x31\x8a\xa5\x15\xbf\xbd\xbc\x5e\x0d\xf2\xd5\xf5\x60\x7e\xe8\x85\x05\xd9\xfb\x3d\x53\x63\x15\x32\x81\x08\xf3\x20\x60\x75\x38\x84\xe1\x88\xfb\xe9\x08\xe2\xd6\x14\xbb\x81\x22\x8d\x1d\xe4\x0a\xbd\x4f\x85\xc6\x6e\x8b\x0e\xfa\x25\x92\xba\x23\xe7\x69\xdc\x96\xf2\x9e\x8a\x88\x8d\x15\xe3\x79\x93\x42\x4e\xae\x61\x9c\x50\x6a\x72\x51\xa9\x5a\x59\x4c\x36\xa7\x56\x03\x54\xc8\x83\xdc\xcc\x26\x64\xd4\x32\x1b\x3d\xd4\xaa\xdf\x88\x85\x5b\x4f\x09\xe4\x46\x29\xb4\x9e\x0a\x01\x27\x4c\x8d\xfa\x51\x8d\xae\x22\x4e\xc5\xf3\xde\x5b\x00\x3a\x89\x11\xdd\x5b\xd4\x05\x15\xa9\x28\x51\x05\xdb\xa3\x36\x64\xef\x8c\x9a\x42\x9d\xa4\x16\xea\x6c\x51\x8f\xc9\x78\x17\x19\xad\x76\x22\xdb\xf4\xe9\x68\xec\x64\x85\xa1\x92\x49\x1c\xec\x1e\x71\x0d\x13\x1c\x1d\xe1\xff\x2b\xd3\x24\xee\xa9\x3c\xd1\xe1\x82\xd7\xad\x43\x5d\x5c\x9c\x02\x31\xbb\xfb\x92\x18\x67\x85\x8d\x0b\xd9\x2d\xea\x2c\x8b\x89\xc2\x45\x90\xb1\x3a\x53\xf9\x4e\xcb\xdf\xaa\x99\xfd\xd8\x72\x33\x51\x51\xc9\x8b\xaa\xec\xf7\x2f\x72\xa3\xbd\x51\xe4\xe1\x75\x0a\xa3\xbc\x46\xae\x8f\xfd\x3e\xb7\x94\x25\x4c\xc6\x8b\x8f\x89\x92\x59\x82\xd3\xe9\x67\x66\x22\xbb\x1d\xe4\x70\xee\x24\x56\x72\x99\x00\x90\x2e\xe0\x71\xbc\x05\x9b\xa8\xc8\xb1\x17\xd9\x9b\xe3\x7a\x1e\xf7\x71\x84\xca\xa1\xad\x45\xf6\x43\x58\x2e\xfa\x8f\x64\x16\xce\xd8\xc2\xfc\xae\x17\xd4\x1d\x9b\xa0\xc7\x7f\x2e\x96\xb6\xc3\x40\x2d\xa6\x6b\x42\x02\x67\xd4\x6c\x44\x8f\xf3\x53\xa3\xb7\xc6\xb6\x36\x15\xec\x5a\xba\x30\x6a\xd9\x27\x46\x6e\xfd\x69\xf3\xe6\xe8\x88\xa7\xce\x3d\xe9\xaf\x07\x9d\x31\x25\xd8\x90\x6e\x1f\x9c\xe8\x29\xde\xfc\x31\xba\xc8\x3e\xb6\x9a\xc3\x0b\xfe\x7f\x6c\xec\x0d\x7c\xd7\x4a\x55\xc0\x07\x5d\x1a\xd7\x0c\x43\x7c\x8e\xd2\xa7\xe1\x4b\x85\x95\x0f\x1d\xd3\x34\xa8\x8b\xe8\x47\xa9\x09\xde\x05\xdd\xdf\x05\xcc\x8d\x2e\x65\x75\xec\xc1\x52\x56\xad\xfb\x47\xd9\xb9\x56\xd1\xf1\xec\x17\x9b\xf9\x69\x8c\xfe\x42\xf5\x22\xdb\xf4\xc2\x25\x9c\x24\x6e\xd5\xa2\x21\xcf\xb6\xf8\xa5\x8e\x0c\xff\x6c\xfe\x75\x3c\x7f\x73\xa5\x11\x30\xde\xe7\x9f\xb7\x0a\xf5\x17\x91\xbd\x27\x65\x1f\xf4\xcb\x32\xd2\x69\x2e\x27\x37\xd6\x6c\x93\xc4\x1a\xbb\x33\xaf\xe7\xf0\x8f\xf8\xe7\x03\xda\x3f\x9b\x49\xdc\xff\x80\xfe\x11\x00\x00\xff\xff\xaa\x4c\x37\x29\x91\x0a\x00\x00") func webUiTemplates_baseHtmlBytes() ([]byte, error) { return bindataRead( @@ -120,7 +121,7 @@ func webUiTemplates_baseHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "web/ui/templates/_base.html", size: 2627, mode: os.FileMode(436), modTime: time.Unix(1471437131, 0)} + info := bindataFileInfo{name: "web/ui/templates/_base.html", size: 2705, mode: os.FileMode(420), modTime: time.Unix(1482843370, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -140,7 +141,7 @@ func webUiTemplatesAlertsHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "web/ui/templates/alerts.html", size: 1795, mode: os.FileMode(436), modTime: time.Unix(1471437131, 0)} + info := bindataFileInfo{name: "web/ui/templates/alerts.html", size: 1795, mode: os.FileMode(420), modTime: time.Unix(1482843351, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -160,7 +161,7 @@ func webUiTemplatesConfigHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "web/ui/templates/config.html", size: 175, mode: os.FileMode(436), modTime: time.Unix(1471437131, 0)} + info := bindataFileInfo{name: "web/ui/templates/config.html", size: 175, mode: os.FileMode(420), modTime: time.Unix(1469471413, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -180,7 +181,7 @@ func webUiTemplatesFlagsHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "web/ui/templates/flags.html", size: 433, mode: os.FileMode(436), modTime: time.Unix(1471437131, 0)} + info := bindataFileInfo{name: "web/ui/templates/flags.html", size: 433, mode: os.FileMode(420), modTime: time.Unix(1469471413, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -200,7 +201,7 @@ func webUiTemplatesGraphHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "web/ui/templates/graph.html", size: 1645, mode: os.FileMode(436), modTime: time.Unix(1480118588, 0)} + info := bindataFileInfo{name: "web/ui/templates/graph.html", size: 1645, mode: os.FileMode(420), modTime: time.Unix(1482843351, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -220,7 +221,7 @@ func webUiTemplatesRulesHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "web/ui/templates/rules.html", size: 209, mode: os.FileMode(436), modTime: time.Unix(1471437131, 0)} + info := bindataFileInfo{name: "web/ui/templates/rules.html", size: 209, mode: os.FileMode(420), modTime: time.Unix(1469471413, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -240,7 +241,7 @@ func webUiTemplatesStatusHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "web/ui/templates/status.html", size: 1615, mode: os.FileMode(436), modTime: time.Unix(1481636980, 0)} + info := bindataFileInfo{name: "web/ui/templates/status.html", size: 1615, mode: os.FileMode(420), modTime: time.Unix(1482843351, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -260,7 +261,7 @@ func webUiTemplatesTargetsHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "web/ui/templates/targets.html", size: 2271, mode: os.FileMode(436), modTime: time.Unix(1481636980, 0)} + info := bindataFileInfo{name: "web/ui/templates/targets.html", size: 2271, mode: os.FileMode(420), modTime: time.Unix(1482843351, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -280,7 +281,7 @@ func webUiStaticCssAlertsCss() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "web/ui/static/css/alerts.css", size: 74, mode: os.FileMode(420), modTime: time.Unix(1447427349, 0)} + info := bindataFileInfo{name: "web/ui/static/css/alerts.css", size: 74, mode: os.FileMode(420), modTime: time.Unix(1459323995, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -300,7 +301,7 @@ func webUiStaticCssGraphCss() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "web/ui/static/css/graph.css", size: 2668, mode: os.FileMode(420), modTime: time.Unix(1447427349, 0)} + info := bindataFileInfo{name: "web/ui/static/css/graph.css", size: 2668, mode: os.FileMode(420), modTime: time.Unix(1459323995, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -320,7 +321,7 @@ func webUiStaticCssProm_consoleCss() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "web/ui/static/css/prom_console.css", size: 2883, mode: os.FileMode(436), modTime: time.Unix(1457335964, 0)} + info := bindataFileInfo{name: "web/ui/static/css/prom_console.css", size: 2883, mode: os.FileMode(420), modTime: time.Unix(1459323995, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -340,7 +341,7 @@ func webUiStaticCssPrometheusCss() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "web/ui/static/css/prometheus.css", size: 405, mode: os.FileMode(436), modTime: time.Unix(1471437131, 0)} + info := bindataFileInfo{name: "web/ui/static/css/prometheus.css", size: 405, mode: os.FileMode(420), modTime: time.Unix(1462739419, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -360,7 +361,27 @@ func webUiStaticImgAjaxLoaderGif() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "web/ui/static/img/ajax-loader.gif", size: 847, mode: os.FileMode(420), modTime: time.Unix(1447427349, 0)} + info := bindataFileInfo{name: "web/ui/static/img/ajax-loader.gif", size: 847, mode: os.FileMode(420), modTime: time.Unix(1459323995, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _webUiStaticImgFaviconIco = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xcc\x5b\x09\x90\x54\xc5\x19\xee\x75\x51\x52\x49\x2a\x50\xb1\xd4\xc4\x4a\x0a\xa2\x52\xae\x8b\xe2\x62\x84\x12\x63\xc4\x44\x4a\xc5\x83\x63\xb9\x2f\x05\x05\x01\x21\x20\x82\xa0\x08\xb8\x01\xc1\x8b\xa0\xd1\x35\x8a\x28\x02\x1e\x1c\xde\x8b\x88\x68\x8c\x8b\x80\x1a\x4e\x2f\x64\x41\x81\x15\x99\x99\x9d\x6b\x67\x76\x76\xae\x9d\x99\x37\x5f\xea\xef\xe9\x1e\xde\xbc\x9d\xf7\x5e\xcf\xb0\x5a\xf9\xaa\xfe\x7a\xbb\x33\xaf\xbb\xff\xee\xfe\xef\xee\x61\xac\x84\x95\xb2\x8e\x1d\xe9\xd9\x99\x4d\x6a\xc7\x58\x4f\xc6\x58\xe7\xce\x99\xff\xd7\x75\x64\x6c\x65\x3b\xc6\x2a\x2a\xc4\xff\x5d\x18\xbb\xe6\x0c\xc6\xca\x18\x63\x1d\xe9\x3d\x96\xf9\x9c\xa3\x1d\xb3\x04\x80\xf6\x00\xfa\x02\x58\xa7\x69\x38\xee\x0b\x68\x49\x7f\x50\x4b\x6a\x1a\x8e\x01\x78\x09\x40\x1f\x00\xa7\x99\xb4\xfd\x2d\x80\x6a\x00\x81\x58\x4b\x1a\xcf\xbe\xd6\x8c\x01\xd3\x3d\x18\x38\xc3\x83\x55\x6f\x87\xd1\x92\x48\x03\x80\x1f\xc0\x3f\x00\x9c\x91\xa7\x6d\x0d\x04\xea\x8e\x26\x30\x66\xae\x0f\x95\x33\x3c\x9c\xc6\xce\xf7\xe1\xc8\xf1\xa4\xfc\x9a\x3a\xda\x20\xfb\x20\x7e\x00\x3c\x49\x5f\xa4\x34\x60\xe3\xd6\x28\x1e\x7f\x29\x84\x07\x9f\x6f\xca\x21\xfa\x6c\xf3\xf6\x28\xb4\x74\xb6\x8f\x87\x01\xb4\x03\x70\x3d\x80\x46\xfa\x30\x91\x4c\x63\xd1\xb3\x41\x0c\xbe\xcb\x8b\x61\x77\xe7\xd2\xa0\xbb\xbc\x78\xe4\x85\x26\x3e\x86\x80\x1b\xc0\x55\xb4\x56\xf2\x83\xe6\x48\x1a\x6b\x37\x87\xf1\xd4\xba\x10\x9e\xde\xd0\x9c\x43\xf4\xd9\xfa\x2d\x11\x44\xa2\x69\xe8\xb0\x02\xc0\x0f\xf2\x9f\xd5\x35\x61\x5c\x3a\xdc\x85\x8a\x21\x4e\x74\x1d\x98\x4b\x97\x0c\x73\xe1\x8f\xc3\x5d\x78\xe5\xdd\xb0\xbe\xfd\x41\x00\x49\xc9\xfb\xbb\xdb\xa2\x78\xf3\xc3\x08\xfa\x4d\xf3\xa0\x7c\xa0\x13\x17\x56\x66\xa8\x7c\x80\x13\x93\x16\xf9\xf9\xda\xd0\x3b\xc9\x54\xb6\x7d\x48\xb6\x97\xd8\xb5\xbf\x05\x3d\x46\xba\xd0\xb5\x32\xb7\xfd\x8d\x53\x3d\xf0\x34\xa6\x60\x00\xb5\x3f\xa6\xff\x60\xc7\xbe\x38\xba\x0f\x33\xb4\x1f\xe8\x44\x9f\xdb\xdd\x70\xb8\x5b\xb5\xaf\x03\xf0\x8a\xfe\x83\x1f\x1a\x52\xb8\x76\x92\x1b\x17\x0d\xca\xcc\xfb\xb2\xd1\x2e\x74\x1f\xea\xc2\xad\x0b\x7c\x88\xc6\xd2\xc6\xf6\xcb\x01\x5c\x0b\xc0\x27\x3f\xa0\xfd\x99\xfb\x44\x00\xe3\xab\xfc\x7c\xcd\x06\xcf\xf4\x72\xde\xdf\xf8\x30\x62\x6c\xeb\x02\x70\x25\x80\x53\x01\x3c\xa6\xff\x82\xd6\x88\x64\xe5\xea\x09\x6e\x3c\xb4\xb2\x09\x8f\xbd\x18\x82\xc3\x93\xc3\x3b\x31\xb2\x18\x40\xa9\x90\xc1\xb3\x00\xbc\x2e\x3e\x47\x24\x96\x86\xb7\x51\xc3\xcd\xf7\xf9\xb0\x7b\x7f\x0b\xbe\x77\x26\xf5\xbc\x93\x04\xbd\x0c\xe0\x74\x83\x0e\x9c\x09\x60\x99\xd0\x11\xbe\x47\xaf\xff\x3b\x02\x7f\x50\xd3\x8f\xeb\x01\xf0\xa0\xb1\xad\xae\x8f\xd3\x84\x8e\xae\x01\x70\xa4\x25\x91\x8e\xa6\x34\xd0\xc4\xbf\x03\xf0\xbc\x90\xd7\x53\xf5\x6d\xe2\x9d\x18\xab\x6f\xcf\x58\x6d\x29\x63\x55\x25\x19\x32\x83\xfc\x9e\xde\xa5\x36\xd4\xb6\x4c\xd8\x98\xab\xf4\x76\xa6\xa3\xb5\x9d\x31\x83\xd0\xc7\x72\x00\xd3\x00\xbc\x01\xe0\x90\x90\xb1\x94\xa0\x20\x80\x03\x42\xe7\x26\x02\x38\x4f\xee\xc1\xc9\x40\x8c\x7b\x19\x80\x67\x00\xd4\x8b\xb1\x38\xc8\x6e\x35\x36\x69\x08\x84\x34\x24\x93\x39\xf2\x97\x00\xf0\xad\x90\x9d\x0a\x00\xa7\x14\x39\xf6\xef\x84\x4d\xf4\xea\x3b\x8f\xc6\xd3\xa8\xa9\x8d\x62\xca\x12\x7f\xc6\x8e\xde\xe9\xc1\x8c\x47\x1a\xf1\xc1\xa7\x31\x69\x4b\xf5\x70\x00\xa8\x32\xda\x55\x85\xb1\x69\xce\x3b\xa4\xdc\x4a\x90\xfd\x26\xb9\x27\x3b\x54\xd6\x3f\xa3\xff\x44\xf4\x37\xd9\x27\xb2\x67\xc9\x56\xaa\xcc\xe5\xfa\x3d\x00\x17\x2a\x8e\xdd\x47\xac\x5f\x2b\xbc\xff\x49\x0c\xdd\x06\x67\xc6\xbb\xc0\x40\xe7\xf7\x73\xa0\xc7\x08\x17\x76\x7e\xd5\x92\xaf\x29\xe1\x73\x00\x3d\x15\xe6\x9d\x77\x6c\xc2\xab\xef\x47\x70\xcb\x3c\x1f\xb7\x39\xf9\x88\xfc\xd1\x96\x4f\x62\x66\xcd\x09\x7b\x01\x74\xb5\xd8\xef\xed\xc6\x06\xcd\xd1\x34\xdc\xfe\x14\xb7\xb1\x64\x6b\x8e\xb9\xac\xc9\xe9\xc9\xbc\xeb\xf1\xa7\xb8\xbd\xc9\x83\x8d\x00\x7e\x6d\x18\xbb\x14\xc0\xd2\x7c\x2f\x3f\xff\x66\x33\xb7\xbb\xd7\x4f\xf1\xe0\x06\x45\xa2\x77\xfb\x4e\x76\x73\x3b\x95\x07\x24\x0f\x73\x0c\xe3\x5f\x2e\x6c\x58\x2b\x90\x6f\x3d\xf7\x06\x07\xdf\xdf\x42\xa8\xcb\x8d\x0e\xce\xbb\x09\x8e\x92\x3d\xd1\xcd\x7d\xb9\xd9\x8b\x34\x87\xdb\x17\xfa\xb9\x9f\x2c\x84\x26\x2e\xf4\x73\x9d\xb4\xc0\x03\x62\xfc\xae\x82\x9f\xbc\x20\x7d\x27\x1b\x53\x0c\x91\xbe\x5a\xe0\x0b\x21\x73\xd3\x8c\xbe\x5e\xc2\xdf\xa4\x61\x5e\x75\x00\x23\xe6\x78\x31\xea\x9e\xc2\x88\xda\x2c\x5e\x11\x44\x28\xac\x99\x4e\x0d\xc0\x50\x61\xcf\x5b\x41\xd3\xc0\xfd\x2b\xf9\xfa\xac\x9e\x0f\x28\x80\xfa\x67\x62\x8c\xe5\xaf\x35\x23\x6d\xbe\x0c\xd5\x22\x56\x6a\x85\x9d\x5f\xb7\xf0\x18\x83\x64\x89\x78\xa0\x58\x83\x62\x8f\x0b\x2b\xad\xe9\xe2\x21\x4e\x5c\x3c\x38\xc3\x03\xb5\xed\x3d\xae\x01\xfb\x0f\x27\xcc\xc6\xdf\x26\xfc\x58\xee\xdc\xd3\xc0\xd2\x55\x4d\xa8\x9c\xe1\xc5\xbd\xff\x0c\xe0\xc5\x77\xc2\x98\x5f\x1d\xb0\x1d\x9f\xf8\xec\x33\xc1\x8d\x35\x1b\xc3\x78\xe2\x95\x10\xa6\x3e\xd8\x88\x9b\xfe\xe6\xc1\x33\x1b\x4c\xf5\xe0\x88\xd0\xc7\x56\x6b\x4f\xb6\x26\xd8\xac\xf1\xb5\xf3\x06\x34\x1e\xeb\x92\xad\xb7\x9b\x3f\xf1\xf8\xc2\x5b\x99\x58\x34\x99\x4a\xc3\x17\xd4\x78\x5f\x26\x7b\xe0\xd5\xfb\x54\x33\xbc\xb7\x23\xc6\xd7\xd4\x6e\x6c\x22\x5a\xf7\xd1\xf7\xfa\x78\xcc\xad\x00\x1a\xbf\xc9\xee\xa5\xea\xb5\x21\xde\xaf\xca\xf8\xb4\x07\x14\x1f\x92\x2d\x56\xc0\x61\x11\xb7\x58\x62\xd9\x9a\x50\x56\x9e\x55\xc6\xef\x7d\x6b\x83\x3e\x2f\xb2\x42\xad\xc8\x99\x2c\xf1\xf2\xa6\x30\xef\x57\x65\x7c\x92\x91\xeb\x26\xbb\xd1\xe0\x53\x9a\xff\xe3\x00\xee\x30\xb3\x3f\x12\x7b\xbe\x69\x41\xcf\x51\xae\xbc\x3c\x50\x3e\xd1\x6b\xb4\x8b\xc7\xf4\x7c\xff\xfb\x3b\x31\xe1\xef\x7e\xc4\xad\x6d\x1f\x81\x9c\x53\x25\x80\x32\x11\x07\x9b\xa2\x29\xac\x61\xe4\x3d\x5e\x2e\x03\xa4\xdf\xdd\x06\xe7\xe6\x30\x43\x67\x79\xb9\x9e\x4b\xfe\xd6\xd4\x84\x55\xe6\x4e\xb1\xc0\xd9\x14\x1f\xca\x3c\xd5\x0a\xff\x5a\x1f\x42\x59\x7f\x07\xcf\x5b\x28\x07\x96\xba\x48\xcf\x99\x4b\x33\x7a\x4e\x71\x11\xc5\x84\x4e\x8f\xd2\xda\x2f\xd0\xf9\xdf\x1e\x22\xef\x31\xc5\xbe\xba\x16\x1e\x5f\xcd\x5e\x16\xc0\xda\xcd\x11\x5c\xa4\x9b\x3f\xe5\xb0\x94\x57\x11\x2f\x64\x7b\x14\x40\x31\x56\x17\xdd\xf8\xa7\x88\xbc\xc9\x14\xe4\xcf\x28\xd6\x7d\xe1\xed\x30\xaf\x03\x5c\x7e\x73\x66\xbd\x29\xf7\xa5\xdc\x92\x7c\xcd\x5f\xc6\xbb\x79\x4e\x65\x03\x7a\xe1\xce\x3c\xf1\xd7\x6f\x00\x7c\x68\xd6\x88\xec\xc9\x7d\x4f\x06\x70\xf8\x87\x24\x7c\x01\x0d\x37\x4c\xf5\xa0\xac\x9f\x03\x43\x66\x79\xb9\x7c\x6c\xda\x16\xe5\x3e\xdf\xc6\xe7\x12\x5e\x05\xd0\xc1\x24\x06\xec\x0e\xe0\xeb\x7c\x8d\x48\x9e\xeb\x9d\x49\x1e\x90\x87\x22\x1a\x86\xde\xed\xe5\xfe\x85\x6c\x13\x81\xe2\x3d\xf2\x75\x36\x72\xff\x29\xe5\x46\x36\x31\xf0\x15\x66\x3c\x64\x79\x49\xa4\xf9\x7e\x93\x7f\xfc\xfa\xdb\x13\xbe\xcd\xe5\x4d\xe5\xcb\x41\x24\x3e\x03\x70\xb1\xd5\xd8\x3a\x1e\x28\x6f\xfa\x20\x9f\x6f\x92\x20\x9f\x46\x31\x46\x73\xd4\x76\xbd\x93\x22\xc6\xe8\xa2\x32\xb6\x8e\x87\x33\x45\xee\xe4\xc8\xd7\x29\xe9\x58\xf5\xba\x90\x9d\x9f\xa1\xb8\x6e\x26\x80\xa2\x32\x5f\xa1\x17\x15\x22\xff\xa7\x38\x25\x27\xb9\xa1\xf8\x3e\x4f\xbe\x15\x13\xfb\xb7\x58\xd8\x36\x8b\xcc\xbd\x20\x3e\xce\x01\x30\x16\xc0\x4a\x00\xbb\x85\xbd\x08\x09\xff\xe9\x10\xfb\x4b\x79\xf2\x08\x00\xbf\x57\x1d\x17\xc1\x0e\x40\x55\x09\xd2\x8c\xdd\x9f\x62\xac\x77\x9c\xb1\x4e\x41\xc6\x3a\xd4\x33\xd6\xbe\x36\x43\xa5\x55\x8c\x95\x48\xb2\xeb\x4f\xf7\x6e\x69\x6d\x86\xda\x53\x5f\xd4\x27\xf5\x4d\x63\xa4\x69\x4a\x55\x25\xa0\xb1\xcb\x18\x63\x15\x8c\xb1\xb1\xfa\x3a\x85\xa5\x86\x14\x07\x00\xbf\x02\xd0\x0b\xc0\x74\x51\xdb\xda\x43\x62\x24\xe2\x60\x59\xc7\x08\x8b\xba\xdf\x67\x62\x9d\x6f\x17\x36\xe1\xe7\x6d\xcf\x91\x12\xcf\x94\x1b\x5d\x00\x60\x16\x80\xff\xa8\xc6\x6b\x06\xdd\xa3\x39\xbe\x03\x60\x12\x80\xce\x6d\x21\x8f\x0a\x7c\x4b\xbd\x79\x42\xac\xa7\xa9\x92\x92\xbd\x24\xdf\x42\xb1\xae\x85\xed\x82\xb0\x45\x87\x00\x2c\x12\xf5\xa5\x1f\x65\x1e\xa2\xe6\xbd\xd0\xcc\xee\x40\xd4\x00\x3f\xaf\x6b\xc1\xb2\x35\x4d\xb8\xed\xfe\x4c\x3d\x7c\xd0\x5d\x5e\x9e\x77\x3e\xbd\xbe\x19\x07\xeb\x13\xb2\x9e\x9d\x0f\x69\x31\x8f\xe9\xc5\xda\x25\x13\xbe\x4b\x00\xf4\x16\x39\x8d\xa9\xdd\xa6\x75\xa6\x78\xf6\x4f\x37\x37\x64\xf3\x34\x59\x47\x92\x7f\x5f\x3d\xde\xcd\xcf\x06\x62\x71\xcb\xfd\x20\xd9\x7a\x1b\x40\xb7\x36\xe0\xfd\x54\x00\xb7\x59\xad\x39\x44\x2e\x5e\xf5\x74\x30\x1b\x67\x5b\xc5\xc0\x15\x43\x9c\xbc\x1e\x96\x32\x5d\x89\x2c\xea\x00\xdc\x58\xac\x3c\x89\xda\xee\xec\x7c\x79\xa4\x11\xef\x7c\x1c\xe5\x79\xac\x4a\x2c\x4f\xef\x50\x8c\xb5\x7b\xbf\x69\x3d\x4d\x8f\x06\x00\x63\x0a\xad\x75\x8a\x75\x9f\x2d\x62\x71\x4b\x90\xcc\x4f\x7b\xa8\x91\xd7\x75\x28\xce\x55\x21\x7a\xf7\xe1\x95\xb6\xe9\x9d\x84\x0f\xc0\xe8\x42\xf6\x01\xc0\xad\x2a\xeb\x4e\x20\x59\x7e\xec\xc5\x10\xa6\x3f\xdc\xc8\xeb\xb5\x2a\x44\xf3\xa5\x5c\x5a\xb3\x97\x21\x09\xb2\xb5\xd7\x29\xf2\x4e\xba\x7a\x5c\xb9\xe7\x9f\x0e\x5f\x91\xcf\xb1\xe1\x9d\x62\xf5\xad\x56\x9d\x50\xfe\xbb\xba\x26\xcc\xed\xc8\x6a\x49\x35\x45\x92\x68\x4f\x7d\x51\x2e\xec\xf6\xdb\xba\xc0\xb5\x00\x7e\x61\xc2\x7b\x89\x88\x2b\x2d\x8d\xdb\xbb\xdb\xa2\x59\x5b\x42\x39\x56\x9b\xd0\x00\x27\xcf\xe1\xf6\x1e\xb0\xd5\xe9\x38\x80\x71\x26\xfc\x77\x57\x91\x1b\xe2\xbf\x9b\x62\xcd\x46\x95\xc8\x26\xf5\x1c\xe5\xc2\x3e\x7b\xfe\x09\xfb\x28\x0e\x35\xf0\x7e\x8a\xa8\xf9\xd9\x62\xd3\xc7\x51\xbe\x66\xaa\xb6\x46\x8d\x1c\x3c\x5f\x55\x58\x7f\x08\xf9\x98\x65\xe0\xbf\x5c\x7f\x2e\x6c\x85\x2f\x0f\x25\xb0\xe0\xa9\x20\xe6\x57\xb7\x1d\xcd\xab\x0e\xf0\x73\x70\x85\x1c\x5b\x82\x62\xdc\xb3\x74\xfc\xdf\xad\xda\xf0\xff\x04\xb4\x51\x43\x04\xef\x1d\x44\x0c\xac\x84\x63\xae\x24\x6a\x77\xc5\x50\xbb\xbb\x0d\x69\x57\x0c\x5b\xf7\xc4\x55\xec\x8f\x1e\xab\x44\x1c\xdf\xcb\xec\x8c\xc5\x08\xca\xe3\xc7\xce\xf3\xf1\x9a\x2b\xc5\x0b\x6d\x45\x15\x43\x5d\xbc\x7e\x46\x3e\xb0\xb1\x49\xd9\xa9\x1d\x00\xd0\x09\xc0\x9d\x2a\xb9\x47\x3c\x91\x89\xd1\xca\x15\xeb\x95\x05\xdb\x20\xf1\xa4\xd8\x3b\xa5\xb6\x0d\x94\xd3\xdd\x24\xce\xf6\x6d\x41\x31\x1a\xd9\x08\xd5\x7a\x6b\xb1\x76\xb4\xd7\x68\x17\xb6\xef\x8d\xab\xb0\x44\x76\x68\x81\xd0\x65\x4b\xc8\x73\x02\x8a\xeb\xf9\xb9\x8d\xee\x1c\xa6\x58\x5f\x70\xd1\xa0\x4c\x5b\x7d\xae\x20\xcf\x84\x26\x2e\xf2\x23\x6c\x5f\xff\x81\xb8\x17\x60\x59\xdf\x24\x6c\xd8\x12\xc9\x9e\xf5\xfc\x75\xbc\x9b\x9f\xf5\x2e\x5e\x11\xe4\xb5\xa6\x6b\x26\xba\x8b\xda\x93\x4b\x86\xb9\x78\x1f\xd5\xeb\x42\x98\xba\xc4\xcf\xeb\xb4\x97\x8d\xce\x9c\x25\xd1\x38\x1f\xed\xb4\x3c\x23\x94\xf8\x44\xd4\x09\x4c\x41\x31\xe2\xfa\xf7\x22\x3c\xff\x23\x3b\x71\xd4\x91\xcc\x9e\xff\xae\x7c\xb3\x59\xf9\xfc\x24\x5f\x2e\x43\xeb\x1c\x0a\x6b\xfc\x3c\xc7\xd3\xa8\x71\xdf\x42\x72\x4a\x79\x1c\x3d\x15\x70\xd0\x2a\x27\xcc\xce\x21\xcf\x56\x92\x1d\xed\x7b\x87\x5b\xe9\xec\xc9\x8c\x28\x17\xdb\x64\xc2\xa7\x62\x6c\xfd\xbd\x0a\xff\xf9\xb0\x7e\x4b\xe4\xa4\xf5\x95\x64\x7d\xe6\xd2\x46\x55\x7b\x63\xc6\xbf\x6d\x8e\x65\x44\x3a\x0d\x7e\xfe\x58\xa6\x78\xf6\x64\x25\x43\xfd\xa6\x79\xb8\x7d\x28\x12\x75\x76\xb9\x79\x3e\xc4\x5a\xd2\x18\x37\xdf\xa7\x7c\x76\x66\x65\x2f\xaf\x1c\xd7\xc0\x6b\xff\x45\x62\x3b\x80\x9d\x85\x36\x22\xfd\x1d\x33\xd7\xa7\x7c\xf6\x68\xc5\xff\x15\xb7\x34\xf0\xba\x50\x91\x20\xdf\xb5\xba\xd0\x46\x89\x64\x1a\x93\x1f\xf0\xb7\xc9\xfa\x5f\x75\x5b\x03\xb7\x69\x45\x80\xac\xca\x5c\x00\x53\xec\xce\x19\xf3\x61\xc9\x73\x4d\x27\xcd\xff\x49\xca\x7f\x48\xdc\xbd\xbd\x54\xc5\x87\x19\x51\x53\x1b\x45\x37\x85\x7b\x0b\x92\xc8\x37\x55\x0c\x6d\x6d\x7f\xa6\x2c\xf1\xf3\xfd\x2c\x02\x5f\x89\x7b\x26\xbf\x14\xf7\xc0\x0a\x02\xed\xf9\xd5\x13\xd4\x7c\x2f\xbd\x33\x62\x8e\x17\x97\x8f\x69\xc8\x79\x9f\xfe\xa6\xdc\xbd\x48\x3c\x2b\xeb\x5a\xa2\x66\x5a\x10\xc8\x67\x92\xed\x96\x32\x44\x7e\x9f\xe2\x3b\xb3\x39\xcc\x79\x3c\xc0\x63\x0f\xc9\x7f\xb9\xb8\xd3\xf1\x5d\x71\xb6\x87\x82\x8b\xfe\xba\xfc\xab\x8b\xb8\xf3\x51\x10\x36\x6e\x8d\xf2\xf8\x81\x62\xdf\x4b\x87\xbb\x78\x1e\xd8\x63\x64\xeb\x39\xd0\xdc\xaa\xd7\x86\xd0\x7f\x9a\x27\xe7\x7c\xfa\xd1\x55\x4d\x56\x77\x70\xac\xf0\xa9\xfe\x4e\xab\xa8\x9d\x3c\x5a\x68\x27\x0e\x4f\x0a\xd7\xdf\xe1\xe6\x76\x94\xc7\xbd\xfb\xe2\x3c\xb6\x33\xda\x55\x8a\x13\x48\x5f\xc6\xce\xcf\x7c\x47\x6b\xdf\x77\xb2\x5b\xf5\x2e\x85\x11\xe4\xad\xa7\xe4\xa9\x9f\x94\x17\xba\x07\x9a\xb8\xbb\x7c\x7e\x3f\x07\xbf\x8b\xd7\xe0\x4b\xf1\x98\x54\x1f\x13\xc9\x3b\xd1\x9f\xd7\xb5\xf0\x73\x6a\xf2\xd9\xf4\x99\x3c\x1f\x2e\x02\x9f\xe9\x73\x77\xc3\x1c\x66\x17\x78\x0e\xc4\x63\x53\xd2\x01\xf2\x07\x14\xc7\xec\xd8\x17\xcf\xd1\x03\x9a\xcb\x80\xe9\x1e\x9e\x17\xae\x7c\xab\x99\xaf\xff\x9f\xc7\x36\xe0\x1b\xf3\xbb\x53\x56\xa0\x58\x67\xb8\x45\xfd\xf0\xf4\x42\x6d\xd1\xb6\xbd\x71\x1e\xaf\xbf\xbc\x29\x63\x47\xbe\x77\x26\x73\xf4\x94\xe6\xb6\x68\x79\x90\x7f\xf7\xdf\x2f\xe3\x3c\xee\x27\x19\x33\xb9\x83\x69\x87\xe7\x00\xfc\xcc\x8c\x7f\x76\xe2\xce\xc6\x61\xd5\x0e\x3f\xda\x15\xe3\x72\x2d\x6b\x07\xc1\x90\xc6\xcf\x8c\x78\x3e\x55\x99\xb1\x49\x5b\x77\x67\xf2\xc1\x80\xf8\x8e\xe4\xa8\x08\x90\xdc\xfc\xc1\x8a\x77\xdd\x1c\x86\xc8\x7b\xed\x76\x38\x70\x24\xc1\x49\x22\x1a\x4f\x67\x75\x98\x68\xe4\x3d\x5e\x7e\xb6\x24\xb1\xe2\xf5\x66\xee\xbb\x0b\x04\xe9\xe5\x15\x2a\xbc\xb3\x13\xf5\xc4\x49\x2a\x77\xc8\x8c\x20\x5f\x3a\x71\x61\x26\x36\x22\x19\x7a\xee\x8d\xdc\x3b\x7a\x2e\x5f\x8a\xff\xa6\xa3\x00\x9f\x4b\xf1\x71\x3f\x55\xde\x75\x73\x68\x27\xee\xcc\xfb\x0a\x9d\xc3\x82\xa7\x82\xd9\x3b\x8e\x75\x47\x5b\xeb\x29\xc9\x18\xf9\x3f\x05\x1c\xd5\xfb\xa9\x22\xe6\x40\xfb\x30\xc8\xee\x1e\x97\x11\x94\xbb\x92\xee\xf2\xdf\x90\x58\x9f\x35\x5a\x61\x37\x80\x2b\x8b\xe5\xdd\x30\x8f\x4b\xc4\x59\xb9\x92\xc3\x21\x5b\x39\xea\x5e\x1f\x66\x3c\xda\x58\xc8\x19\x91\x44\x54\xd4\x06\xcf\x6d\x0b\xde\x75\x73\xe8\x28\xee\x14\x1f\xb2\x3b\xe3\x80\xb8\xdb\x47\x7a\xaa\x78\x5f\x13\x22\x0f\xdf\x23\xce\xea\x7e\x94\x7b\x11\x22\xce\x38\x4f\x9c\xf5\x1f\xb4\xf3\x75\x64\x77\x14\xf4\x34\x21\x7e\x83\x30\x13\xc0\xd9\x3f\x06\xdf\x26\xf3\xe8\x04\x60\xbc\xb8\x03\x55\x2f\xce\x76\x54\x90\x16\x7e\xf4\x90\xf8\x8d\xdf\x08\xb3\x78\xe0\x27\x9a\x4b\x7b\x71\x17\x65\xa4\xf8\x5d\xcc\x66\x71\xcf\xe9\xb8\xb8\x97\xe2\x15\x67\x23\x5f\x88\x7b\x01\x4b\xc4\xfd\xce\x73\x8c\xbf\x6d\x2a\x6e\xfc\xfa\xf6\x00\x3d\x05\xc5\x19\xeb\x44\xcf\x7a\xc6\x3a\xd0\xb3\x96\xb1\x52\x7a\x56\x31\x56\x42\x4f\xde\x88\xb1\x34\x3d\xef\x67\x2c\x45\xcf\xde\x8c\xc5\xe9\xd9\x89\xb1\x20\x3d\x3b\x30\x56\xaf\x7f\xb6\x67\xac\x56\xff\x2c\x3d\xf1\xac\x62\xfc\x82\x52\xab\x27\xd3\x3f\x4f\x16\x79\xfa\x97\x4f\x23\x3f\x92\x4f\xc9\xb7\x9c\x8f\x7c\xca\x79\xca\x79\xcb\x75\x90\xeb\x92\x5d\x27\xb9\x6e\x41\xb1\x8e\x71\xc6\x7a\xeb\xd7\x59\xac\xfb\xff\x02\x00\x00\xff\xff\xeb\x12\x88\x54\xee\x3a\x00\x00") + +func webUiStaticImgFaviconIcoBytes() ([]byte, error) { + return bindataRead( + _webUiStaticImgFaviconIco, + "web/ui/static/img/favicon.ico", + ) +} + +func webUiStaticImgFaviconIco() (*asset, error) { + bytes, err := webUiStaticImgFaviconIcoBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "web/ui/static/img/favicon.ico", size: 15086, mode: os.FileMode(420), modTime: time.Unix(1482843399, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -380,7 +401,7 @@ func webUiStaticJsAlertsJs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "web/ui/static/js/alerts.js", size: 445, mode: os.FileMode(420), modTime: time.Unix(1447427349, 0)} + info := bindataFileInfo{name: "web/ui/static/js/alerts.js", size: 445, mode: os.FileMode(420), modTime: time.Unix(1459323995, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -400,7 +421,7 @@ func webUiStaticJsGraphJs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "web/ui/static/js/graph.js", size: 25570, mode: os.FileMode(436), modTime: time.Unix(1480118588, 0)} + info := bindataFileInfo{name: "web/ui/static/js/graph.js", size: 25570, mode: os.FileMode(420), modTime: time.Unix(1482843351, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -420,7 +441,7 @@ func webUiStaticJsGraph_templateHandlebar() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "web/ui/static/js/graph_template.handlebar", size: 6316, mode: os.FileMode(436), modTime: time.Unix(1471437131, 0)} + info := bindataFileInfo{name: "web/ui/static/js/graph_template.handlebar", size: 6316, mode: os.FileMode(420), modTime: time.Unix(1482843351, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -440,7 +461,7 @@ func webUiStaticJsProm_consoleJs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "web/ui/static/js/prom_console.js", size: 21323, mode: os.FileMode(436), modTime: time.Unix(1482248767, 0)} + info := bindataFileInfo{name: "web/ui/static/js/prom_console.js", size: 21323, mode: os.FileMode(420), modTime: time.Unix(1482843351, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -460,7 +481,7 @@ func webUiStaticVendorBootstrap331CssBootstrapThemeMinCss() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "web/ui/static/vendor/bootstrap-3.3.1/css/bootstrap-theme.min.css", size: 19835, mode: os.FileMode(420), modTime: time.Unix(1447427349, 0)} + info := bindataFileInfo{name: "web/ui/static/vendor/bootstrap-3.3.1/css/bootstrap-theme.min.css", size: 19835, mode: os.FileMode(420), modTime: time.Unix(1459323995, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -480,7 +501,7 @@ func webUiStaticVendorBootstrap331CssBootstrapMinCss() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "web/ui/static/vendor/bootstrap-3.3.1/css/bootstrap.min.css", size: 113498, mode: os.FileMode(420), modTime: time.Unix(1447427349, 0)} + info := bindataFileInfo{name: "web/ui/static/vendor/bootstrap-3.3.1/css/bootstrap.min.css", size: 113498, mode: os.FileMode(420), modTime: time.Unix(1459323995, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -500,7 +521,7 @@ func webUiStaticVendorBootstrap331FontsGlyphiconsHalflingsRegularEot() (*asset, return nil, err } - info := bindataFileInfo{name: "web/ui/static/vendor/bootstrap-3.3.1/fonts/glyphicons-halflings-regular.eot", size: 20335, mode: os.FileMode(420), modTime: time.Unix(1447427349, 0)} + info := bindataFileInfo{name: "web/ui/static/vendor/bootstrap-3.3.1/fonts/glyphicons-halflings-regular.eot", size: 20335, mode: os.FileMode(420), modTime: time.Unix(1459323995, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -520,7 +541,7 @@ func webUiStaticVendorBootstrap331FontsGlyphiconsHalflingsRegularSvg() (*asset, return nil, err } - info := bindataFileInfo{name: "web/ui/static/vendor/bootstrap-3.3.1/fonts/glyphicons-halflings-regular.svg", size: 62926, mode: os.FileMode(420), modTime: time.Unix(1447427349, 0)} + info := bindataFileInfo{name: "web/ui/static/vendor/bootstrap-3.3.1/fonts/glyphicons-halflings-regular.svg", size: 62926, mode: os.FileMode(420), modTime: time.Unix(1459323995, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -540,7 +561,7 @@ func webUiStaticVendorBootstrap331FontsGlyphiconsHalflingsRegularTtf() (*asset, return nil, err } - info := bindataFileInfo{name: "web/ui/static/vendor/bootstrap-3.3.1/fonts/glyphicons-halflings-regular.ttf", size: 41280, mode: os.FileMode(420), modTime: time.Unix(1447427349, 0)} + info := bindataFileInfo{name: "web/ui/static/vendor/bootstrap-3.3.1/fonts/glyphicons-halflings-regular.ttf", size: 41280, mode: os.FileMode(420), modTime: time.Unix(1459323995, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -560,7 +581,7 @@ func webUiStaticVendorBootstrap331FontsGlyphiconsHalflingsRegularWoff() (*asset, return nil, err } - info := bindataFileInfo{name: "web/ui/static/vendor/bootstrap-3.3.1/fonts/glyphicons-halflings-regular.woff", size: 23320, mode: os.FileMode(420), modTime: time.Unix(1447427349, 0)} + info := bindataFileInfo{name: "web/ui/static/vendor/bootstrap-3.3.1/fonts/glyphicons-halflings-regular.woff", size: 23320, mode: os.FileMode(420), modTime: time.Unix(1459323995, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -580,7 +601,7 @@ func webUiStaticVendorBootstrap331JsBootstrapMinJs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "web/ui/static/vendor/bootstrap-3.3.1/js/bootstrap.min.js", size: 35601, mode: os.FileMode(420), modTime: time.Unix(1447427349, 0)} + info := bindataFileInfo{name: "web/ui/static/vendor/bootstrap-3.3.1/js/bootstrap.min.js", size: 35601, mode: os.FileMode(420), modTime: time.Unix(1459323995, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -600,7 +621,7 @@ func webUiStaticVendorBootstrap331JsNpmJs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "web/ui/static/vendor/bootstrap-3.3.1/js/npm.js", size: 484, mode: os.FileMode(420), modTime: time.Unix(1447427349, 0)} + info := bindataFileInfo{name: "web/ui/static/vendor/bootstrap-3.3.1/js/npm.js", size: 484, mode: os.FileMode(420), modTime: time.Unix(1459323995, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -620,7 +641,7 @@ func webUiStaticVendorBootstrap3TypeaheadBootstrap3TypeaheadMinJs() (*asset, err return nil, err } - info := bindataFileInfo{name: "web/ui/static/vendor/bootstrap3-typeahead/bootstrap3-typeahead.min.js", size: 7856, mode: os.FileMode(420), modTime: time.Unix(1447427349, 0)} + info := bindataFileInfo{name: "web/ui/static/vendor/bootstrap3-typeahead/bootstrap3-typeahead.min.js", size: 7856, mode: os.FileMode(420), modTime: time.Unix(1459323995, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -640,7 +661,7 @@ func webUiStaticVendorEonasdanBootstrapDatetimepickerBootstrapDatetimepickerMinC return nil, err } - info := bindataFileInfo{name: "web/ui/static/vendor/eonasdan-bootstrap-datetimepicker/bootstrap-datetimepicker.min.css", size: 7771, mode: os.FileMode(436), modTime: time.Unix(1480118588, 0)} + info := bindataFileInfo{name: "web/ui/static/vendor/eonasdan-bootstrap-datetimepicker/bootstrap-datetimepicker.min.css", size: 7771, mode: os.FileMode(420), modTime: time.Unix(1482843351, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -660,7 +681,7 @@ func webUiStaticVendorEonasdanBootstrapDatetimepickerBootstrapDatetimepickerMinJ return nil, err } - info := bindataFileInfo{name: "web/ui/static/vendor/eonasdan-bootstrap-datetimepicker/bootstrap-datetimepicker.min.js", size: 48881, mode: os.FileMode(436), modTime: time.Unix(1480118588, 0)} + info := bindataFileInfo{name: "web/ui/static/vendor/eonasdan-bootstrap-datetimepicker/bootstrap-datetimepicker.min.js", size: 48881, mode: os.FileMode(420), modTime: time.Unix(1482843351, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -680,7 +701,7 @@ func webUiStaticVendorFuzzyJs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "web/ui/static/vendor/fuzzy.js", size: 2655, mode: os.FileMode(436), modTime: time.Unix(1478184469, 0)} + info := bindataFileInfo{name: "web/ui/static/vendor/fuzzy.js", size: 2655, mode: os.FileMode(420), modTime: time.Unix(1482843351, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -700,7 +721,7 @@ func webUiStaticVendorJsJqueryHotkeysJs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "web/ui/static/vendor/js/jquery.hotkeys.js", size: 3283, mode: os.FileMode(420), modTime: time.Unix(1447427349, 0)} + info := bindataFileInfo{name: "web/ui/static/vendor/js/jquery.hotkeys.js", size: 3283, mode: os.FileMode(420), modTime: time.Unix(1459323995, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -720,7 +741,7 @@ func webUiStaticVendorJsJqueryMinJs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "web/ui/static/vendor/js/jquery.min.js", size: 95935, mode: os.FileMode(420), modTime: time.Unix(1447427349, 0)} + info := bindataFileInfo{name: "web/ui/static/vendor/js/jquery.min.js", size: 95935, mode: os.FileMode(420), modTime: time.Unix(1459323995, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -740,7 +761,7 @@ func webUiStaticVendorJsJquerySelectionJs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "web/ui/static/vendor/js/jquery.selection.js", size: 13320, mode: os.FileMode(420), modTime: time.Unix(1447427349, 0)} + info := bindataFileInfo{name: "web/ui/static/vendor/js/jquery.selection.js", size: 13320, mode: os.FileMode(420), modTime: time.Unix(1459323995, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -760,7 +781,7 @@ func webUiStaticVendorMomentMomentMinJs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "web/ui/static/vendor/moment/moment.min.js", size: 61281, mode: os.FileMode(436), modTime: time.Unix(1480118588, 0)} + info := bindataFileInfo{name: "web/ui/static/vendor/moment/moment.min.js", size: 61281, mode: os.FileMode(420), modTime: time.Unix(1482843351, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -780,7 +801,7 @@ func webUiStaticVendorMustacheMustacheMinJs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "web/ui/static/vendor/mustache/mustache.min.js", size: 9528, mode: os.FileMode(436), modTime: time.Unix(1480118588, 0)} + info := bindataFileInfo{name: "web/ui/static/vendor/mustache/mustache.min.js", size: 9528, mode: os.FileMode(420), modTime: time.Unix(1482843351, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -800,7 +821,7 @@ func webUiStaticVendorRickshawRickshawMinCss() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "web/ui/static/vendor/rickshaw/rickshaw.min.css", size: 6102, mode: os.FileMode(420), modTime: time.Unix(1447427349, 0)} + info := bindataFileInfo{name: "web/ui/static/vendor/rickshaw/rickshaw.min.css", size: 6102, mode: os.FileMode(420), modTime: time.Unix(1459323995, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -820,7 +841,7 @@ func webUiStaticVendorRickshawRickshawMinJs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "web/ui/static/vendor/rickshaw/rickshaw.min.js", size: 76322, mode: os.FileMode(420), modTime: time.Unix(1447427349, 0)} + info := bindataFileInfo{name: "web/ui/static/vendor/rickshaw/rickshaw.min.js", size: 76322, mode: os.FileMode(420), modTime: time.Unix(1459323995, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -840,7 +861,7 @@ func webUiStaticVendorRickshawVendorD3LayoutMinJs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "web/ui/static/vendor/rickshaw/vendor/d3.layout.min.js", size: 17514, mode: os.FileMode(420), modTime: time.Unix(1447427349, 0)} + info := bindataFileInfo{name: "web/ui/static/vendor/rickshaw/vendor/d3.layout.min.js", size: 17514, mode: os.FileMode(420), modTime: time.Unix(1459323995, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -860,7 +881,7 @@ func webUiStaticVendorRickshawVendorD3V3Js() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "web/ui/static/vendor/rickshaw/vendor/d3.v3.js", size: 144718, mode: os.FileMode(420), modTime: time.Unix(1447427349, 0)} + info := bindataFileInfo{name: "web/ui/static/vendor/rickshaw/vendor/d3.v3.js", size: 144718, mode: os.FileMode(420), modTime: time.Unix(1459323995, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -930,6 +951,7 @@ var _bindata = map[string]func() (*asset, error){ "web/ui/static/css/prom_console.css": webUiStaticCssProm_consoleCss, "web/ui/static/css/prometheus.css": webUiStaticCssPrometheusCss, "web/ui/static/img/ajax-loader.gif": webUiStaticImgAjaxLoaderGif, + "web/ui/static/img/favicon.ico": webUiStaticImgFaviconIco, "web/ui/static/js/alerts.js": webUiStaticJsAlertsJs, "web/ui/static/js/graph.js": webUiStaticJsGraphJs, "web/ui/static/js/graph_template.handlebar": webUiStaticJsGraph_templateHandlebar, @@ -1009,6 +1031,7 @@ var _bintree = &bintree{nil, map[string]*bintree{ }}, "img": &bintree{nil, map[string]*bintree{ "ajax-loader.gif": &bintree{webUiStaticImgAjaxLoaderGif, map[string]*bintree{}}, + "favicon.ico": &bintree{webUiStaticImgFaviconIco, map[string]*bintree{}}, }}, "js": &bintree{nil, map[string]*bintree{ "alerts.js": &bintree{webUiStaticJsAlertsJs, map[string]*bintree{}}, diff --git a/web/ui/static/img/favicon.ico b/web/ui/static/img/favicon.ico new file mode 100644 index 000000000..34bd1fbf0 Binary files /dev/null and b/web/ui/static/img/favicon.ico differ diff --git a/web/ui/templates/_base.html b/web/ui/templates/_base.html index 60221c379..7e7b3d407 100644 --- a/web/ui/templates/_base.html +++ b/web/ui/templates/_base.html @@ -3,6 +3,7 @@ Prometheus Time Series Collection and Processing Server + diff --git a/web/web.go b/web/web.go index 4b6bc3dae..31c9ffba5 100644 --- a/web/web.go +++ b/web/web.go @@ -155,7 +155,7 @@ func New(o *Options) *Handler { storage: o.Storage, notifier: o.Notifier, - apiV1: api_v1.NewAPI(o.QueryEngine, o.Storage, o.TargetManager), + apiV1: api_v1.NewAPI(o.QueryEngine, o.Storage, o.TargetManager, o.Notifier), now: model.Now, } @@ -274,7 +274,7 @@ func (h *Handler) Run() { func (h *Handler) alerts(w http.ResponseWriter, r *http.Request) { alerts := h.ruleManager.AlertingRules() - alertsSorter := byAlertStateSorter{alerts: alerts} + alertsSorter := byAlertStateAndNameSorter{alerts: alerts} sort.Sort(alertsSorter) alertStatus := AlertStatus{ @@ -373,14 +373,14 @@ func (h *Handler) rules(w http.ResponseWriter, r *http.Request) { func (h *Handler) targets(w http.ResponseWriter, r *http.Request) { // Bucket targets by job label - tps := map[string][]retrieval.Target{} + tps := map[string][]*retrieval.Target{} for _, t := range h.targetManager.Targets() { job := t.Labels().Get(model.JobLabel) tps[job] = append(tps[job], t) } h.executeTemplate(w, "targets.html", struct { - TargetPools map[string][]retrieval.Target + TargetPools map[string][]*retrieval.Target }{ TargetPools: tps, }) @@ -541,18 +541,20 @@ type AlertStatus struct { AlertStateToRowClass map[rules.AlertState]string } -type byAlertStateSorter struct { +type byAlertStateAndNameSorter struct { alerts []*rules.AlertingRule } -func (s byAlertStateSorter) Len() int { +func (s byAlertStateAndNameSorter) Len() int { return len(s.alerts) } -func (s byAlertStateSorter) Less(i, j int) bool { - return s.alerts[i].State() > s.alerts[j].State() +func (s byAlertStateAndNameSorter) Less(i, j int) bool { + return s.alerts[i].State() > s.alerts[j].State() || + (s.alerts[i].State() == s.alerts[j].State() && + s.alerts[i].Name() < s.alerts[j].Name()) } -func (s byAlertStateSorter) Swap(i, j int) { +func (s byAlertStateAndNameSorter) Swap(i, j int) { s.alerts[i], s.alerts[j] = s.alerts[j], s.alerts[i] }