mirror of
https://github.com/prometheus/prometheus.git
synced 2024-12-25 13:44:05 -08:00
Merge branch 'master' into dev-2.0-rebase
This commit is contained in:
commit
1d3cdd0d67
15
.codeclimate.yml
Normal file
15
.codeclimate.yml
Normal file
|
@ -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"
|
17
CHANGELOG.md
17
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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 [<args>]
|
||||
{{ range $cat, $flags := . }}{{ if ne $cat "." }} == {{ $cat | upper }} =={{ end }}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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{
|
||||
|
|
14
config/testdata/conf.good.yml
vendored
14
config/testdata/conf.good.yml
vendored
|
@ -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
|
||||
|
|
|
@ -46,7 +46,7 @@
|
|||
</tr>
|
||||
<tr>
|
||||
<td>Checkpoint Duration</td>
|
||||
<td>{{ template "prom_query_drilldown" (args (printf "prometheus_local_storage_checkpoint_duration_seconds{job='prometheus',instance='%s'}" .Params.instance) "" "humanizeDuration") }}</td>
|
||||
<td>{{ template "prom_query_drilldown" (args (printf "prometheus_local_storage_checkpoint_last_duration_seconds{job='prometheus',instance='%s'}" .Params.instance) "" "humanizeDuration") }}</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
169
discovery/triton/triton.go
Normal file
169
discovery/triton/triton.go
Normal file
|
@ -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
|
||||
}
|
178
discovery/triton/triton_test.go
Normal file
178
discovery/triton/triton_test.go
Normal file
|
@ -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
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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()
|
||||
|
|
47
vendor/github.com/prometheus/common/expfmt/decode.go
generated
vendored
47
vendor/github.com/prometheus/common/expfmt/decode.go
generated
vendored
|
@ -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 {
|
||||
|
|
7
vendor/github.com/prometheus/common/expfmt/expfmt.go
generated
vendored
7
vendor/github.com/prometheus/common/expfmt/expfmt.go
generated
vendored
|
@ -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 + ";"
|
||||
|
|
11
vendor/github.com/prometheus/common/log/syslog_formatter.go
generated
vendored
11
vendor/github.com/prometheus/common/log/syslog_formatter.go
generated
vendored
|
@ -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:
|
||||
|
|
12
vendor/github.com/prometheus/common/model/labels.go
generated
vendored
12
vendor/github.com/prometheus/common/model/labels.go
generated
vendored
|
@ -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)
|
||||
|
|
2
vendor/github.com/prometheus/common/model/labelset.go
generated
vendored
2
vendor/github.com/prometheus/common/model/labelset.go
generated
vendored
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
11
vendor/github.com/prometheus/common/model/metric.go
generated
vendored
11
vendor/github.com/prometheus/common/model/metric.go
generated
vendored
|
@ -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
|
||||
|
|
7
vendor/github.com/prometheus/common/route/route.go
generated
vendored
7
vendor/github.com/prometheus/common/route/route.go
generated
vendored
|
@ -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
|
||||
|
|
32
vendor/vendor.json
vendored
32
vendor/vendor.json
vendored
|
@ -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=",
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
File diff suppressed because one or more lines are too long
BIN
web/ui/static/img/favicon.ico
Normal file
BIN
web/ui/static/img/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
|
@ -3,6 +3,7 @@
|
|||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<title>Prometheus Time Series Collection and Processing Server</title>
|
||||
<link rel="shortcut icon" href="{{ pathPrefix }}/static/img/favicon.ico">
|
||||
<script src="{{ pathPrefix }}/static/vendor/js/jquery.min.js"></script>
|
||||
<script src="{{ pathPrefix }}/static/vendor/bootstrap-3.3.1/js/bootstrap.min.js"></script>
|
||||
|
||||
|
|
20
web/web.go
20
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]
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue