mirror of
https://github.com/prometheus/prometheus.git
synced 2025-01-13 23:10:12 -08:00
Merge pull request #421 from prometheus/u-c-l/code-cleanup
Apply a giant code cleanup.
This commit is contained in:
commit
cf781eff37
|
@ -14,16 +14,17 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"code.google.com/p/goprotobuf/proto"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"code.google.com/p/goprotobuf/proto"
|
||||||
|
|
||||||
clientmodel "github.com/prometheus/client_golang/model"
|
clientmodel "github.com/prometheus/client_golang/model"
|
||||||
|
|
||||||
pb "github.com/prometheus/prometheus/config/generated"
|
|
||||||
|
|
||||||
"github.com/prometheus/prometheus/utility"
|
"github.com/prometheus/prometheus/utility"
|
||||||
|
|
||||||
|
pb "github.com/prometheus/prometheus/config/generated"
|
||||||
)
|
)
|
||||||
|
|
||||||
var jobNameRE = regexp.MustCompile("^[a-zA-Z_][a-zA-Z0-9_-]*$")
|
var jobNameRE = regexp.MustCompile("^[a-zA-Z_][a-zA-Z0-9_-]*$")
|
||||||
|
@ -48,7 +49,7 @@ func (c Config) validateLabels(labels *pb.LabelPairs) error {
|
||||||
}
|
}
|
||||||
for _, label := range labels.Label {
|
for _, label := range labels.Label {
|
||||||
if !labelNameRE.MatchString(label.GetName()) {
|
if !labelNameRE.MatchString(label.GetName()) {
|
||||||
return fmt.Errorf("Invalid label name '%s'", label.GetName())
|
return fmt.Errorf("invalid label name '%s'", label.GetName())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -59,42 +60,42 @@ func (c Config) Validate() error {
|
||||||
// Check the global configuration section for validity.
|
// Check the global configuration section for validity.
|
||||||
global := c.Global
|
global := c.Global
|
||||||
if _, err := utility.StringToDuration(global.GetScrapeInterval()); err != nil {
|
if _, err := utility.StringToDuration(global.GetScrapeInterval()); err != nil {
|
||||||
return fmt.Errorf("Invalid global scrape interval: %s", err)
|
return fmt.Errorf("invalid global scrape interval: %s", err)
|
||||||
}
|
}
|
||||||
if _, err := utility.StringToDuration(global.GetEvaluationInterval()); err != nil {
|
if _, err := utility.StringToDuration(global.GetEvaluationInterval()); err != nil {
|
||||||
return fmt.Errorf("Invalid rule evaluation interval: %s", err)
|
return fmt.Errorf("invalid rule evaluation interval: %s", err)
|
||||||
}
|
}
|
||||||
if err := c.validateLabels(global.Labels); err != nil {
|
if err := c.validateLabels(global.Labels); err != nil {
|
||||||
return fmt.Errorf("Invalid global labels: %s", err)
|
return fmt.Errorf("invalid global labels: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check each job configuration for validity.
|
// Check each job configuration for validity.
|
||||||
jobNames := map[string]bool{}
|
jobNames := map[string]bool{}
|
||||||
for _, job := range c.Job {
|
for _, job := range c.Job {
|
||||||
if jobNames[job.GetName()] {
|
if jobNames[job.GetName()] {
|
||||||
return fmt.Errorf("Found multiple jobs configured with the same name: '%s'", job.GetName())
|
return fmt.Errorf("found multiple jobs configured with the same name: '%s'", job.GetName())
|
||||||
}
|
}
|
||||||
jobNames[job.GetName()] = true
|
jobNames[job.GetName()] = true
|
||||||
|
|
||||||
if !jobNameRE.MatchString(job.GetName()) {
|
if !jobNameRE.MatchString(job.GetName()) {
|
||||||
return fmt.Errorf("Invalid job name '%s'", job.GetName())
|
return fmt.Errorf("invalid job name '%s'", job.GetName())
|
||||||
}
|
}
|
||||||
if _, err := utility.StringToDuration(job.GetScrapeInterval()); err != nil {
|
if _, err := utility.StringToDuration(job.GetScrapeInterval()); err != nil {
|
||||||
return fmt.Errorf("Invalid scrape interval for job '%s': %s", job.GetName(), err)
|
return fmt.Errorf("invalid scrape interval for job '%s': %s", job.GetName(), err)
|
||||||
}
|
}
|
||||||
if _, err := utility.StringToDuration(job.GetSdRefreshInterval()); err != nil {
|
if _, err := utility.StringToDuration(job.GetSdRefreshInterval()); err != nil {
|
||||||
return fmt.Errorf("Invalid SD refresh interval for job '%s': %s", job.GetName(), err)
|
return fmt.Errorf("invalid SD refresh interval for job '%s': %s", job.GetName(), err)
|
||||||
}
|
}
|
||||||
if _, err := utility.StringToDuration(job.GetScrapeTimeout()); err != nil {
|
if _, err := utility.StringToDuration(job.GetScrapeTimeout()); err != nil {
|
||||||
return fmt.Errorf("Invalid scrape timeout for job '%s': %s", job.GetName(), err)
|
return fmt.Errorf("invalid scrape timeout for job '%s': %s", job.GetName(), err)
|
||||||
}
|
}
|
||||||
for _, targetGroup := range job.TargetGroup {
|
for _, targetGroup := range job.TargetGroup {
|
||||||
if err := c.validateLabels(targetGroup.Labels); err != nil {
|
if err := c.validateLabels(targetGroup.Labels); err != nil {
|
||||||
return fmt.Errorf("Invalid labels for job '%s': %s", job.GetName(), err)
|
return fmt.Errorf("invalid labels for job '%s': %s", job.GetName(), err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if job.SdName != nil && len(job.TargetGroup) > 0 {
|
if job.SdName != nil && len(job.TargetGroup) > 0 {
|
||||||
return fmt.Errorf("Specified both DNS-SD name and target group for job: %s", job.GetName())
|
return fmt.Errorf("specified both DNS-SD name and target group for job: %s", job.GetName())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,7 +112,7 @@ func (c Config) GetJobByName(name string) *JobConfig {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the global labels as a LabelSet.
|
// GlobalLabels returns the global labels as a LabelSet.
|
||||||
func (c Config) GlobalLabels() clientmodel.LabelSet {
|
func (c Config) GlobalLabels() clientmodel.LabelSet {
|
||||||
labels := clientmodel.LabelSet{}
|
labels := clientmodel.LabelSet{}
|
||||||
if c.Global.Labels != nil {
|
if c.Global.Labels != nil {
|
||||||
|
@ -155,12 +156,12 @@ type JobConfig struct {
|
||||||
pb.JobConfig
|
pb.JobConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
// EvaluationInterval gets the scrape interval for a job.
|
// ScrapeInterval gets the scrape interval for a job.
|
||||||
func (c JobConfig) ScrapeInterval() time.Duration {
|
func (c JobConfig) ScrapeInterval() time.Duration {
|
||||||
return stringToDuration(c.GetScrapeInterval())
|
return stringToDuration(c.GetScrapeInterval())
|
||||||
}
|
}
|
||||||
|
|
||||||
// EvaluationInterval gets the scrape interval for a job.
|
// ScrapeTimeout gets the scrape timeout for a job.
|
||||||
func (c JobConfig) ScrapeTimeout() time.Duration {
|
func (c JobConfig) ScrapeTimeout() time.Duration {
|
||||||
return stringToDuration(c.GetScrapeInterval())
|
return stringToDuration(c.GetScrapeInterval())
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,27 +43,27 @@ var configTests = []struct {
|
||||||
{
|
{
|
||||||
inputFile: "invalid_scrape_interval.conf.input",
|
inputFile: "invalid_scrape_interval.conf.input",
|
||||||
shouldFail: true,
|
shouldFail: true,
|
||||||
errContains: "Invalid global scrape interval",
|
errContains: "invalid global scrape interval",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
inputFile: "invalid_job_name.conf.input",
|
inputFile: "invalid_job_name.conf.input",
|
||||||
shouldFail: true,
|
shouldFail: true,
|
||||||
errContains: "Invalid job name",
|
errContains: "invalid job name",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
inputFile: "invalid_label_name.conf.input",
|
inputFile: "invalid_label_name.conf.input",
|
||||||
shouldFail: true,
|
shouldFail: true,
|
||||||
errContains: "Invalid label name",
|
errContains: "invalid label name",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
inputFile: "mixing_sd_and_manual_targets.conf.input",
|
inputFile: "mixing_sd_and_manual_targets.conf.input",
|
||||||
shouldFail: true,
|
shouldFail: true,
|
||||||
errContains: "Specified both DNS-SD name and target group",
|
errContains: "specified both DNS-SD name and target group",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
inputFile: "repeated_job_name.conf.input",
|
inputFile: "repeated_job_name.conf.input",
|
||||||
shouldFail: true,
|
shouldFail: true,
|
||||||
errContains: "Found multiple jobs configured with the same name: 'testjob1'",
|
errContains: "found multiple jobs configured with the same name: 'testjob1'",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,11 +14,14 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"code.google.com/p/goprotobuf/proto"
|
|
||||||
pb "github.com/prometheus/prometheus/config/generated"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
|
||||||
|
"code.google.com/p/goprotobuf/proto"
|
||||||
|
|
||||||
|
pb "github.com/prometheus/prometheus/config/generated"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// LoadFromString returns a config parsed from the provided string.
|
||||||
func LoadFromString(configStr string) (Config, error) {
|
func LoadFromString(configStr string) (Config, error) {
|
||||||
configProto := pb.PrometheusConfig{}
|
configProto := pb.PrometheusConfig{}
|
||||||
if err := proto.UnmarshalText(configStr, &configProto); err != nil {
|
if err := proto.UnmarshalText(configStr, &configProto); err != nil {
|
||||||
|
@ -39,6 +42,7 @@ func LoadFromString(configStr string) (Config, error) {
|
||||||
return config, err
|
return config, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LoadFromFile returns a config parsed from the file of the provided name.
|
||||||
func LoadFromFile(fileName string) (Config, error) {
|
func LoadFromFile(fileName string) (Config, error) {
|
||||||
configStr, err := ioutil.ReadFile(fileName)
|
configStr, err := ioutil.ReadFile(fileName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
3
main.go
3
main.go
|
@ -15,6 +15,7 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
|
_ "net/http/pprof" // Comment this line to disable pprof endpoint.
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -130,7 +131,7 @@ func NewPrometheus() *prometheus {
|
||||||
NotificationHandler: notificationHandler,
|
NotificationHandler: notificationHandler,
|
||||||
EvaluationInterval: conf.EvaluationInterval(),
|
EvaluationInterval: conf.EvaluationInterval(),
|
||||||
Storage: memStorage,
|
Storage: memStorage,
|
||||||
PrometheusUrl: web.MustBuildServerUrl(),
|
PrometheusURL: web.MustBuildServerURL(),
|
||||||
})
|
})
|
||||||
if err := ruleManager.AddRulesFromConfig(conf); err != nil {
|
if err := ruleManager.AddRulesFromConfig(conf); err != nil {
|
||||||
glog.Fatal("Error loading rule files: ", err)
|
glog.Fatal("Error loading rule files: ", err)
|
||||||
|
|
|
@ -31,8 +31,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
alertmanagerApiEventsPath = "/api/alerts"
|
alertmanagerAPIEventsPath = "/api/alerts"
|
||||||
contentTypeJson = "application/json"
|
contentTypeJSON = "application/json"
|
||||||
)
|
)
|
||||||
|
|
||||||
// String constants for instrumentation.
|
// String constants for instrumentation.
|
||||||
|
@ -50,8 +50,8 @@ var (
|
||||||
deadline = flag.Duration("alertmanager.http-deadline", 10*time.Second, "Alert manager HTTP API timeout.")
|
deadline = flag.Duration("alertmanager.http-deadline", 10*time.Second, "Alert manager HTTP API timeout.")
|
||||||
)
|
)
|
||||||
|
|
||||||
// A request for sending a notification to the alert manager for a single alert
|
// NotificationReq is a request for sending a notification to the alert manager
|
||||||
// vector element.
|
// for a single alert vector element.
|
||||||
type NotificationReq struct {
|
type NotificationReq struct {
|
||||||
// Short-form alert summary. May contain text/template-style interpolations.
|
// Short-form alert summary. May contain text/template-style interpolations.
|
||||||
Summary string
|
Summary string
|
||||||
|
@ -69,6 +69,9 @@ type NotificationReq struct {
|
||||||
GeneratorURL string
|
GeneratorURL string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NotificationReqs is just a short-hand for []*NotificationReq. No methods
|
||||||
|
// attached. Arguably, it's more confusing than helpful. Perhaps we should
|
||||||
|
// remove it...
|
||||||
type NotificationReqs []*NotificationReq
|
type NotificationReqs []*NotificationReq
|
||||||
|
|
||||||
type httpPoster interface {
|
type httpPoster interface {
|
||||||
|
@ -79,7 +82,7 @@ type httpPoster interface {
|
||||||
// alert manager service.
|
// alert manager service.
|
||||||
type NotificationHandler struct {
|
type NotificationHandler struct {
|
||||||
// The URL of the alert manager to send notifications to.
|
// The URL of the alert manager to send notifications to.
|
||||||
alertmanagerUrl string
|
alertmanagerURL string
|
||||||
// Buffer of notifications that have not yet been sent.
|
// Buffer of notifications that have not yet been sent.
|
||||||
pendingNotifications chan NotificationReqs
|
pendingNotifications chan NotificationReqs
|
||||||
// HTTP client with custom timeout settings.
|
// HTTP client with custom timeout settings.
|
||||||
|
@ -92,10 +95,10 @@ type NotificationHandler struct {
|
||||||
stopped chan struct{}
|
stopped chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Construct a new NotificationHandler.
|
// NewNotificationHandler constructs a new NotificationHandler.
|
||||||
func NewNotificationHandler(alertmanagerUrl string, notificationQueueCapacity int) *NotificationHandler {
|
func NewNotificationHandler(alertmanagerURL string, notificationQueueCapacity int) *NotificationHandler {
|
||||||
return &NotificationHandler{
|
return &NotificationHandler{
|
||||||
alertmanagerUrl: alertmanagerUrl,
|
alertmanagerURL: alertmanagerURL,
|
||||||
pendingNotifications: make(chan NotificationReqs, notificationQueueCapacity),
|
pendingNotifications: make(chan NotificationReqs, notificationQueueCapacity),
|
||||||
|
|
||||||
httpClient: utility.NewDeadlineClient(*deadline),
|
httpClient: utility.NewDeadlineClient(*deadline),
|
||||||
|
@ -150,8 +153,8 @@ func (n *NotificationHandler) sendNotifications(reqs NotificationReqs) error {
|
||||||
}
|
}
|
||||||
glog.V(1).Infoln("Sending notifications to alertmanager:", string(buf))
|
glog.V(1).Infoln("Sending notifications to alertmanager:", string(buf))
|
||||||
resp, err := n.httpClient.Post(
|
resp, err := n.httpClient.Post(
|
||||||
n.alertmanagerUrl+alertmanagerApiEventsPath,
|
n.alertmanagerURL+alertmanagerAPIEventsPath,
|
||||||
contentTypeJson,
|
contentTypeJSON,
|
||||||
bytes.NewBuffer(buf),
|
bytes.NewBuffer(buf),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -170,7 +173,7 @@ func (n *NotificationHandler) sendNotifications(reqs NotificationReqs) error {
|
||||||
// Run dispatches notifications continuously.
|
// Run dispatches notifications continuously.
|
||||||
func (n *NotificationHandler) Run() {
|
func (n *NotificationHandler) Run() {
|
||||||
for reqs := range n.pendingNotifications {
|
for reqs := range n.pendingNotifications {
|
||||||
if n.alertmanagerUrl == "" {
|
if n.alertmanagerURL == "" {
|
||||||
glog.Warning("No alert manager configured, not dispatching notification")
|
glog.Warning("No alert manager configured, not dispatching notification")
|
||||||
n.notificationLatency.WithLabelValues(dropped).Observe(0)
|
n.notificationLatency.WithLabelValues(dropped).Observe(0)
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -24,12 +24,12 @@ import (
|
||||||
clientmodel "github.com/prometheus/client_golang/model"
|
clientmodel "github.com/prometheus/client_golang/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
type testHttpPoster struct {
|
type testHTTPPoster struct {
|
||||||
message string
|
message string
|
||||||
receivedPost chan<- bool
|
receivedPost chan<- bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *testHttpPoster) Post(url string, bodyType string, body io.Reader) (*http.Response, error) {
|
func (p *testHTTPPoster) Post(url string, bodyType string, body io.Reader) (*http.Response, error) {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
buf.ReadFrom(body)
|
buf.ReadFrom(body)
|
||||||
p.message = buf.String()
|
p.message = buf.String()
|
||||||
|
@ -50,7 +50,7 @@ func (s *testNotificationScenario) test(i int, t *testing.T) {
|
||||||
defer h.Stop()
|
defer h.Stop()
|
||||||
|
|
||||||
receivedPost := make(chan bool, 1)
|
receivedPost := make(chan bool, 1)
|
||||||
poster := testHttpPoster{receivedPost: receivedPost}
|
poster := testHTTPPoster{receivedPost: receivedPost}
|
||||||
h.httpClient = &poster
|
h.httpClient = &poster
|
||||||
|
|
||||||
go h.Run()
|
go h.Run()
|
||||||
|
|
|
@ -29,6 +29,8 @@ type MergeLabelsIngester struct {
|
||||||
Ingester extraction.Ingester
|
Ingester extraction.Ingester
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ingest ingests the provided extraction result by merging in i.Labels and then
|
||||||
|
// handing it over to i.Ingester.
|
||||||
func (i *MergeLabelsIngester) Ingest(r *extraction.Result) error {
|
func (i *MergeLabelsIngester) Ingest(r *extraction.Result) error {
|
||||||
for _, s := range r.Samples {
|
for _, s := range r.Samples {
|
||||||
s.Metric.MergeFromLabelSet(i.Labels, i.CollisionPrefix)
|
s.Metric.MergeFromLabelSet(i.Labels, i.CollisionPrefix)
|
||||||
|
@ -40,6 +42,7 @@ func (i *MergeLabelsIngester) Ingest(r *extraction.Result) error {
|
||||||
// ChannelIngester feeds results into a channel without modifying them.
|
// ChannelIngester feeds results into a channel without modifying them.
|
||||||
type ChannelIngester chan<- *extraction.Result
|
type ChannelIngester chan<- *extraction.Result
|
||||||
|
|
||||||
|
// Ingest ingests the provided extraction result by sending it to i.
|
||||||
func (i ChannelIngester) Ingest(r *extraction.Result) error {
|
func (i ChannelIngester) Ingest(r *extraction.Result) error {
|
||||||
i <- r
|
i <- r
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -32,8 +32,10 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
// InstanceLabel is the label value used for the instance label.
|
||||||
InstanceLabel clientmodel.LabelName = "instance"
|
InstanceLabel clientmodel.LabelName = "instance"
|
||||||
// The metric name for the synthetic health variable.
|
// ScrapeHealthMetricName is the metric name for the synthetic health
|
||||||
|
// variable.
|
||||||
ScrapeHealthMetricName clientmodel.LabelValue = "up"
|
ScrapeHealthMetricName clientmodel.LabelValue = "up"
|
||||||
|
|
||||||
// Constants for instrumentation.
|
// Constants for instrumentation.
|
||||||
|
@ -74,16 +76,16 @@ func init() {
|
||||||
prometheus.MustRegister(targetIntervalLength)
|
prometheus.MustRegister(targetIntervalLength)
|
||||||
}
|
}
|
||||||
|
|
||||||
// The state of the given Target.
|
// TargetState describes the state of a Target.
|
||||||
type TargetState int
|
type TargetState int
|
||||||
|
|
||||||
func (t TargetState) String() string {
|
func (t TargetState) String() string {
|
||||||
switch t {
|
switch t {
|
||||||
case UNKNOWN:
|
case Unknown:
|
||||||
return "UNKNOWN"
|
return "UNKNOWN"
|
||||||
case ALIVE:
|
case Alive:
|
||||||
return "ALIVE"
|
return "ALIVE"
|
||||||
case UNREACHABLE:
|
case Unreachable:
|
||||||
return "UNREACHABLE"
|
return "UNREACHABLE"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,14 +93,16 @@ func (t TargetState) String() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// The Target has not been seen; we know nothing about it, except that it is
|
// Unknown is the state of a Target that has not been seen; we know
|
||||||
// on our docket for examination.
|
// nothing about it, except that it is on our docket for examination.
|
||||||
UNKNOWN TargetState = iota
|
Unknown TargetState = iota
|
||||||
// The Target has been found and successfully queried.
|
// Alive is the state of a Target that has been found and successfully
|
||||||
ALIVE
|
// queried.
|
||||||
// The Target was either historically found or not found and then determined
|
Alive
|
||||||
// to be unhealthy by either not responding or disappearing.
|
// Unreachable is the state of a Target that was either historically
|
||||||
UNREACHABLE
|
// found or not found and then determined to be unhealthy by either not
|
||||||
|
// responding or disappearing.
|
||||||
|
Unreachable
|
||||||
)
|
)
|
||||||
|
|
||||||
// A Target represents an endpoint that should be interrogated for metrics.
|
// A Target represents an endpoint that should be interrogated for metrics.
|
||||||
|
@ -170,7 +174,7 @@ type target struct {
|
||||||
sync.Mutex
|
sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// Furnish a reasonably configured target for querying.
|
// NewTarget creates a reasonably configured target for querying.
|
||||||
func NewTarget(address string, deadline time.Duration, baseLabels clientmodel.LabelSet) Target {
|
func NewTarget(address string, deadline time.Duration, baseLabels clientmodel.LabelSet) Target {
|
||||||
target := &target{
|
target := &target{
|
||||||
address: address,
|
address: address,
|
||||||
|
@ -296,10 +300,10 @@ func (t *target) scrape(ingester extraction.Ingester) (err error) {
|
||||||
}
|
}
|
||||||
t.Lock() // Writing t.state and t.lastError requires the lock.
|
t.Lock() // Writing t.state and t.lastError requires the lock.
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.state = ALIVE
|
t.state = Alive
|
||||||
labels[outcome] = failure
|
labels[outcome] = failure
|
||||||
} else {
|
} else {
|
||||||
t.state = UNREACHABLE
|
t.state = Unreachable
|
||||||
}
|
}
|
||||||
t.lastError = err
|
t.lastError = err
|
||||||
t.Unlock()
|
t.Unlock()
|
||||||
|
|
|
@ -62,7 +62,7 @@ type sdTargetProvider struct {
|
||||||
refreshInterval time.Duration
|
refreshInterval time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
// Constructs a new sdTargetProvider for a job.
|
// NewSdTargetProvider constructs a new sdTargetProvider for a job.
|
||||||
func NewSdTargetProvider(job config.JobConfig) *sdTargetProvider {
|
func NewSdTargetProvider(job config.JobConfig) *sdTargetProvider {
|
||||||
i, err := utility.StringToDuration(job.GetSdRefreshInterval())
|
i, err := utility.StringToDuration(job.GetSdRefreshInterval())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -125,7 +125,7 @@ func (p *sdTargetProvider) Targets() ([]Target, error) {
|
||||||
func lookupSRV(name string) (*dns.Msg, error) {
|
func lookupSRV(name string) (*dns.Msg, error) {
|
||||||
conf, err := dns.ClientConfigFromFile(resolvConf)
|
conf, err := dns.ClientConfigFromFile(resolvConf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Couldn't load resolv.conf: %s", err)
|
return nil, fmt.Errorf("couldn't load resolv.conf: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
client := &dns.Client{}
|
client := &dns.Client{}
|
||||||
|
@ -140,7 +140,7 @@ func lookupSRV(name string) (*dns.Msg, error) {
|
||||||
return response, nil
|
return response, nil
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
glog.Warningf("Resolving %s.%s failed: %s", name, suffix, err)
|
glog.Warningf("resolving %s.%s failed: %s", name, suffix, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
response, err = lookup(name, dns.TypeSRV, client, servAddr, "", false)
|
response, err = lookup(name, dns.TypeSRV, client, servAddr, "", false)
|
||||||
|
@ -148,7 +148,7 @@ func lookupSRV(name string) (*dns.Msg, error) {
|
||||||
return response, nil
|
return response, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return response, fmt.Errorf("Couldn't resolve %s: No server responded", name)
|
return response, fmt.Errorf("couldn't resolve %s: No server responded", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func lookup(name string, queryType uint16, client *dns.Client, servAddr string, suffix string, edns bool) (*dns.Msg, error) {
|
func lookup(name string, queryType uint16, client *dns.Client, servAddr string, suffix string, edns bool) (*dns.Msg, error) {
|
||||||
|
@ -178,7 +178,7 @@ func lookup(name string, queryType uint16, client *dns.Client, servAddr string,
|
||||||
|
|
||||||
if response.MsgHdr.Truncated {
|
if response.MsgHdr.Truncated {
|
||||||
if client.Net == "tcp" {
|
if client.Net == "tcp" {
|
||||||
return nil, fmt.Errorf("Got truncated message on tcp")
|
return nil, fmt.Errorf("got truncated message on tcp")
|
||||||
}
|
}
|
||||||
|
|
||||||
if edns { // Truncated even though EDNS is used
|
if edns { // Truncated even though EDNS is used
|
||||||
|
|
|
@ -38,13 +38,13 @@ func (i *collectResultIngester) Ingest(r *extraction.Result) error {
|
||||||
|
|
||||||
func TestTargetScrapeUpdatesState(t *testing.T) {
|
func TestTargetScrapeUpdatesState(t *testing.T) {
|
||||||
testTarget := target{
|
testTarget := target{
|
||||||
state: UNKNOWN,
|
state: Unknown,
|
||||||
address: "bad schema",
|
address: "bad schema",
|
||||||
httpClient: utility.NewDeadlineClient(0),
|
httpClient: utility.NewDeadlineClient(0),
|
||||||
}
|
}
|
||||||
testTarget.scrape(nopIngester{})
|
testTarget.scrape(nopIngester{})
|
||||||
if testTarget.state != UNREACHABLE {
|
if testTarget.state != Unreachable {
|
||||||
t.Errorf("Expected target state %v, actual: %v", UNREACHABLE, testTarget.state)
|
t.Errorf("Expected target state %v, actual: %v", Unreachable, testTarget.state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -146,7 +146,7 @@ func TestTargetScrape404(t *testing.T) {
|
||||||
|
|
||||||
func TestTargetRunScraperScrapes(t *testing.T) {
|
func TestTargetRunScraperScrapes(t *testing.T) {
|
||||||
testTarget := target{
|
testTarget := target{
|
||||||
state: UNKNOWN,
|
state: Unknown,
|
||||||
address: "bad schema",
|
address: "bad schema",
|
||||||
httpClient: utility.NewDeadlineClient(0),
|
httpClient: utility.NewDeadlineClient(0),
|
||||||
scraperStopping: make(chan struct{}),
|
scraperStopping: make(chan struct{}),
|
||||||
|
|
|
@ -39,6 +39,7 @@ type targetManager struct {
|
||||||
ingester extraction.Ingester
|
ingester extraction.Ingester
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewTargetManager returns a newly initialized TargetManager ready to use.
|
||||||
func NewTargetManager(ingester extraction.Ingester) TargetManager {
|
func NewTargetManager(ingester extraction.Ingester) TargetManager {
|
||||||
return &targetManager{
|
return &targetManager{
|
||||||
ingester: ingester,
|
ingester: ingester,
|
||||||
|
@ -50,7 +51,7 @@ func (m *targetManager) targetPoolForJob(job config.JobConfig) *TargetPool {
|
||||||
targetPool, ok := m.poolsByJob[job.GetName()]
|
targetPool, ok := m.poolsByJob[job.GetName()]
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
var provider TargetProvider = nil
|
var provider TargetProvider
|
||||||
if job.SdName != nil {
|
if job.SdName != nil {
|
||||||
provider = NewSdTargetProvider(job)
|
provider = NewSdTargetProvider(job)
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,7 +73,7 @@ func (t fakeTarget) StopScraper() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t fakeTarget) State() TargetState {
|
func (t fakeTarget) State() TargetState {
|
||||||
return ALIVE
|
return Alive
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *fakeTarget) SetBaseLabelsFrom(newTarget Target) {}
|
func (t *fakeTarget) SetBaseLabelsFrom(newTarget Target) {}
|
||||||
|
|
|
@ -27,10 +27,10 @@ const (
|
||||||
targetReplaceQueueSize = 1
|
targetReplaceQueueSize = 1
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TargetPool is a pool of targets for the same job.
|
||||||
type TargetPool struct {
|
type TargetPool struct {
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
|
|
||||||
done chan chan struct{}
|
|
||||||
manager TargetManager
|
manager TargetManager
|
||||||
targetsByAddress map[string]Target
|
targetsByAddress map[string]Target
|
||||||
interval time.Duration
|
interval time.Duration
|
||||||
|
@ -38,8 +38,11 @@ type TargetPool struct {
|
||||||
addTargetQueue chan Target
|
addTargetQueue chan Target
|
||||||
|
|
||||||
targetProvider TargetProvider
|
targetProvider TargetProvider
|
||||||
|
|
||||||
|
stopping, stopped chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewTargetPool creates a TargetPool, ready to be started by calling Run.
|
||||||
func NewTargetPool(m TargetManager, p TargetProvider, ing extraction.Ingester, i time.Duration) *TargetPool {
|
func NewTargetPool(m TargetManager, p TargetProvider, ing extraction.Ingester, i time.Duration) *TargetPool {
|
||||||
return &TargetPool{
|
return &TargetPool{
|
||||||
manager: m,
|
manager: m,
|
||||||
|
@ -48,10 +51,13 @@ func NewTargetPool(m TargetManager, p TargetProvider, ing extraction.Ingester, i
|
||||||
targetsByAddress: make(map[string]Target),
|
targetsByAddress: make(map[string]Target),
|
||||||
addTargetQueue: make(chan Target, targetAddQueueSize),
|
addTargetQueue: make(chan Target, targetAddQueueSize),
|
||||||
targetProvider: p,
|
targetProvider: p,
|
||||||
done: make(chan chan struct{}),
|
stopping: make(chan struct{}),
|
||||||
|
stopped: make(chan struct{}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Run starts the target pool. It returns when the target pool has stopped
|
||||||
|
// (after calling Stop). Run is usually called as a goroutine.
|
||||||
func (p *TargetPool) Run() {
|
func (p *TargetPool) Run() {
|
||||||
ticker := time.NewTicker(p.interval)
|
ticker := time.NewTicker(p.interval)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
@ -69,20 +75,21 @@ func (p *TargetPool) Run() {
|
||||||
}
|
}
|
||||||
case newTarget := <-p.addTargetQueue:
|
case newTarget := <-p.addTargetQueue:
|
||||||
p.addTarget(newTarget)
|
p.addTarget(newTarget)
|
||||||
case stopped := <-p.done:
|
case <-p.stopping:
|
||||||
p.ReplaceTargets([]Target{})
|
p.ReplaceTargets([]Target{})
|
||||||
close(stopped)
|
close(p.stopped)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Stop stops the target pool and returns once the shutdown is complete.
|
||||||
func (p *TargetPool) Stop() {
|
func (p *TargetPool) Stop() {
|
||||||
stopped := make(chan struct{})
|
close(p.stopping)
|
||||||
p.done <- stopped
|
<-p.stopped
|
||||||
<-stopped
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddTarget adds a target by queuing it in the target queue.
|
||||||
func (p *TargetPool) AddTarget(target Target) {
|
func (p *TargetPool) AddTarget(target Target) {
|
||||||
p.addTargetQueue <- target
|
p.addTargetQueue <- target
|
||||||
}
|
}
|
||||||
|
@ -95,13 +102,13 @@ func (p *TargetPool) addTarget(target Target) {
|
||||||
go target.RunScraper(p.ingester, p.interval)
|
go target.RunScraper(p.ingester, p.interval)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ReplaceTargets replaces the old targets by the provided new ones but reuses
|
||||||
|
// old targets that are also present in newTargets to preserve scheduling and
|
||||||
|
// health state. Targets no longer present are stopped.
|
||||||
func (p *TargetPool) ReplaceTargets(newTargets []Target) {
|
func (p *TargetPool) ReplaceTargets(newTargets []Target) {
|
||||||
p.Lock()
|
p.Lock()
|
||||||
defer p.Unlock()
|
defer p.Unlock()
|
||||||
|
|
||||||
// Replace old target list by new one, but reuse those targets from the old
|
|
||||||
// list of targets which are also in the new list (to preserve scheduling and
|
|
||||||
// health state).
|
|
||||||
newTargetAddresses := make(utility.Set)
|
newTargetAddresses := make(utility.Set)
|
||||||
for _, newTarget := range newTargets {
|
for _, newTarget := range newTargets {
|
||||||
newTargetAddresses.Add(newTarget.Address())
|
newTargetAddresses.Add(newTarget.Address())
|
||||||
|
@ -113,7 +120,7 @@ func (p *TargetPool) ReplaceTargets(newTargets []Target) {
|
||||||
go newTarget.RunScraper(p.ingester, p.interval)
|
go newTarget.RunScraper(p.ingester, p.interval)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Stop any targets no longer present.
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
for k, oldTarget := range p.targetsByAddress {
|
for k, oldTarget := range p.targetsByAddress {
|
||||||
if !newTargetAddresses.Has(k) {
|
if !newTargetAddresses.Has(k) {
|
||||||
|
@ -130,6 +137,7 @@ func (p *TargetPool) ReplaceTargets(newTargets []Target) {
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Targets returns a copy of the current target list.
|
||||||
func (p *TargetPool) Targets() []Target {
|
func (p *TargetPool) Targets() []Target {
|
||||||
p.RLock()
|
p.RLock()
|
||||||
defer p.RUnlock()
|
defer p.RUnlock()
|
||||||
|
|
|
@ -115,7 +115,7 @@ func TestTargetPoolReplaceTargets(t *testing.T) {
|
||||||
pool := NewTargetPool(nil, nil, nopIngester{}, time.Duration(1))
|
pool := NewTargetPool(nil, nil, nopIngester{}, time.Duration(1))
|
||||||
oldTarget1 := &target{
|
oldTarget1 := &target{
|
||||||
address: "example1",
|
address: "example1",
|
||||||
state: UNREACHABLE,
|
state: Unreachable,
|
||||||
scraperStopping: make(chan struct{}),
|
scraperStopping: make(chan struct{}),
|
||||||
scraperStopped: make(chan struct{}),
|
scraperStopped: make(chan struct{}),
|
||||||
newBaseLabels: make(chan clientmodel.LabelSet, 1),
|
newBaseLabels: make(chan clientmodel.LabelSet, 1),
|
||||||
|
@ -123,7 +123,7 @@ func TestTargetPoolReplaceTargets(t *testing.T) {
|
||||||
}
|
}
|
||||||
oldTarget2 := &target{
|
oldTarget2 := &target{
|
||||||
address: "example2",
|
address: "example2",
|
||||||
state: UNREACHABLE,
|
state: Unreachable,
|
||||||
scraperStopping: make(chan struct{}),
|
scraperStopping: make(chan struct{}),
|
||||||
scraperStopped: make(chan struct{}),
|
scraperStopped: make(chan struct{}),
|
||||||
newBaseLabels: make(chan clientmodel.LabelSet, 1),
|
newBaseLabels: make(chan clientmodel.LabelSet, 1),
|
||||||
|
@ -131,7 +131,7 @@ func TestTargetPoolReplaceTargets(t *testing.T) {
|
||||||
}
|
}
|
||||||
newTarget1 := &target{
|
newTarget1 := &target{
|
||||||
address: "example1",
|
address: "example1",
|
||||||
state: ALIVE,
|
state: Alive,
|
||||||
scraperStopping: make(chan struct{}),
|
scraperStopping: make(chan struct{}),
|
||||||
scraperStopped: make(chan struct{}),
|
scraperStopped: make(chan struct{}),
|
||||||
newBaseLabels: make(chan clientmodel.LabelSet, 1),
|
newBaseLabels: make(chan clientmodel.LabelSet, 1),
|
||||||
|
@ -139,7 +139,7 @@ func TestTargetPoolReplaceTargets(t *testing.T) {
|
||||||
}
|
}
|
||||||
newTarget2 := &target{
|
newTarget2 := &target{
|
||||||
address: "example3",
|
address: "example3",
|
||||||
state: ALIVE,
|
state: Alive,
|
||||||
scraperStopping: make(chan struct{}),
|
scraperStopping: make(chan struct{}),
|
||||||
scraperStopped: make(chan struct{}),
|
scraperStopped: make(chan struct{}),
|
||||||
newBaseLabels: make(chan clientmodel.LabelSet, 1),
|
newBaseLabels: make(chan clientmodel.LabelSet, 1),
|
||||||
|
|
|
@ -16,6 +16,7 @@ package rules
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
|
"reflect"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -28,25 +29,25 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// The metric name for synthetic alert timeseries.
|
// AlertMetricName is the metric name for synthetic alert timeseries.
|
||||||
AlertMetricName clientmodel.LabelValue = "ALERTS"
|
AlertMetricName clientmodel.LabelValue = "ALERTS"
|
||||||
|
|
||||||
// The label name indicating the name of an alert.
|
// AlertNameLabel is the label name indicating the name of an alert.
|
||||||
AlertNameLabel clientmodel.LabelName = "alertname"
|
AlertNameLabel clientmodel.LabelName = "alertname"
|
||||||
// The label name indicating the state of an alert.
|
// AlertStateLabel is the label name indicating the state of an alert.
|
||||||
AlertStateLabel clientmodel.LabelName = "alertstate"
|
AlertStateLabel clientmodel.LabelName = "alertstate"
|
||||||
)
|
)
|
||||||
|
|
||||||
// States that active alerts can be in.
|
// AlertState denotes the state of an active alert.
|
||||||
type AlertState int
|
type AlertState int
|
||||||
|
|
||||||
func (s AlertState) String() string {
|
func (s AlertState) String() string {
|
||||||
switch s {
|
switch s {
|
||||||
case INACTIVE:
|
case Inactive:
|
||||||
return "inactive"
|
return "inactive"
|
||||||
case PENDING:
|
case Pending:
|
||||||
return "pending"
|
return "pending"
|
||||||
case FIRING:
|
case Firing:
|
||||||
return "firing"
|
return "firing"
|
||||||
default:
|
default:
|
||||||
panic("undefined")
|
panic("undefined")
|
||||||
|
@ -54,9 +55,14 @@ func (s AlertState) String() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
INACTIVE AlertState = iota
|
// Inactive alerts are neither firing nor pending.
|
||||||
PENDING
|
Inactive AlertState = iota
|
||||||
FIRING
|
// Pending alerts have been active for less than the configured
|
||||||
|
// threshold duration.
|
||||||
|
Pending
|
||||||
|
// Firing alerts have been active for longer than the configured
|
||||||
|
// threshold duration.
|
||||||
|
Firing
|
||||||
)
|
)
|
||||||
|
|
||||||
// Alert is used to track active (pending/firing) alerts over time.
|
// Alert is used to track active (pending/firing) alerts over time.
|
||||||
|
@ -65,9 +71,9 @@ type Alert struct {
|
||||||
Name string
|
Name string
|
||||||
// The vector element labelset triggering this alert.
|
// The vector element labelset triggering this alert.
|
||||||
Labels clientmodel.LabelSet
|
Labels clientmodel.LabelSet
|
||||||
// The state of the alert (PENDING or FIRING).
|
// The state of the alert (Pending or Firing).
|
||||||
State AlertState
|
State AlertState
|
||||||
// The time when the alert first transitioned into PENDING state.
|
// The time when the alert first transitioned into Pending state.
|
||||||
ActiveSince clientmodel.Timestamp
|
ActiveSince clientmodel.Timestamp
|
||||||
// The value of the alert expression for this vector element.
|
// The value of the alert expression for this vector element.
|
||||||
Value clientmodel.SampleValue
|
Value clientmodel.SampleValue
|
||||||
|
@ -91,14 +97,14 @@ func (a Alert) sample(timestamp clientmodel.Timestamp, value clientmodel.SampleV
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// An alerting rule generates alerts from its vector expression.
|
// An AlertingRule generates alerts from its vector expression.
|
||||||
type AlertingRule struct {
|
type AlertingRule struct {
|
||||||
// The name of the alert.
|
// The name of the alert.
|
||||||
name string
|
name string
|
||||||
// The vector expression from which to generate alerts.
|
// The vector expression from which to generate alerts.
|
||||||
Vector ast.VectorNode
|
Vector ast.VectorNode
|
||||||
// The duration for which a labelset needs to persist in the expression
|
// The duration for which a labelset needs to persist in the expression
|
||||||
// output vector before an alert transitions from PENDING to FIRING state.
|
// output vector before an alert transitions from Pending to Firing state.
|
||||||
holdDuration time.Duration
|
holdDuration time.Duration
|
||||||
// Extra labels to attach to the resulting alert sample vectors.
|
// Extra labels to attach to the resulting alert sample vectors.
|
||||||
Labels clientmodel.LabelSet
|
Labels clientmodel.LabelSet
|
||||||
|
@ -109,21 +115,24 @@ type AlertingRule struct {
|
||||||
|
|
||||||
// Protects the below.
|
// Protects the below.
|
||||||
mutex sync.Mutex
|
mutex sync.Mutex
|
||||||
// A map of alerts which are currently active (PENDING or FIRING), keyed by
|
// A map of alerts which are currently active (Pending or Firing), keyed by
|
||||||
// the fingerprint of the labelset they correspond to.
|
// the fingerprint of the labelset they correspond to.
|
||||||
activeAlerts map[clientmodel.Fingerprint]*Alert
|
activeAlerts map[clientmodel.Fingerprint]*Alert
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Name returns the name of the alert.
|
||||||
func (rule *AlertingRule) Name() string {
|
func (rule *AlertingRule) Name() string {
|
||||||
return rule.name
|
return rule.name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EvalRaw returns the raw value of the rule expression, without creating alerts.
|
||||||
func (rule *AlertingRule) EvalRaw(timestamp clientmodel.Timestamp, storage local.Storage) (ast.Vector, error) {
|
func (rule *AlertingRule) EvalRaw(timestamp clientmodel.Timestamp, storage local.Storage) (ast.Vector, error) {
|
||||||
return ast.EvalVectorInstant(rule.Vector, timestamp, storage, stats.NewTimerGroup())
|
return ast.EvalVectorInstant(rule.Vector, timestamp, storage, stats.NewTimerGroup())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Eval evaluates the rule expression and then creates pending alerts and fires
|
||||||
|
// or removes previously pending alerts accordingly.
|
||||||
func (rule *AlertingRule) Eval(timestamp clientmodel.Timestamp, storage local.Storage) (ast.Vector, error) {
|
func (rule *AlertingRule) Eval(timestamp clientmodel.Timestamp, storage local.Storage) (ast.Vector, error) {
|
||||||
// Get the raw value of the rule expression.
|
|
||||||
exprResult, err := rule.EvalRaw(timestamp, storage)
|
exprResult, err := rule.EvalRaw(timestamp, storage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -150,7 +159,7 @@ func (rule *AlertingRule) Eval(timestamp clientmodel.Timestamp, storage local.St
|
||||||
rule.activeAlerts[*fp] = &Alert{
|
rule.activeAlerts[*fp] = &Alert{
|
||||||
Name: rule.name,
|
Name: rule.name,
|
||||||
Labels: labels,
|
Labels: labels,
|
||||||
State: PENDING,
|
State: Pending,
|
||||||
ActiveSince: timestamp,
|
ActiveSince: timestamp,
|
||||||
Value: sample.Value,
|
Value: sample.Value,
|
||||||
}
|
}
|
||||||
|
@ -169,9 +178,9 @@ func (rule *AlertingRule) Eval(timestamp clientmodel.Timestamp, storage local.St
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if activeAlert.State == PENDING && timestamp.Sub(activeAlert.ActiveSince) >= rule.holdDuration {
|
if activeAlert.State == Pending && timestamp.Sub(activeAlert.ActiveSince) >= rule.holdDuration {
|
||||||
vector = append(vector, activeAlert.sample(timestamp, 0))
|
vector = append(vector, activeAlert.sample(timestamp, 0))
|
||||||
activeAlert.State = FIRING
|
activeAlert.State = Firing
|
||||||
}
|
}
|
||||||
|
|
||||||
vector = append(vector, activeAlert.sample(timestamp, 1))
|
vector = append(vector, activeAlert.sample(timestamp, 1))
|
||||||
|
@ -180,12 +189,17 @@ func (rule *AlertingRule) Eval(timestamp clientmodel.Timestamp, storage local.St
|
||||||
return vector, nil
|
return vector, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ToDotGraph returns the text representation of a dot graph.
|
||||||
func (rule *AlertingRule) ToDotGraph() string {
|
func (rule *AlertingRule) ToDotGraph() string {
|
||||||
graph := fmt.Sprintf(`digraph "Rules" {
|
graph := fmt.Sprintf(
|
||||||
|
`digraph "Rules" {
|
||||||
%#p[shape="box",label="ALERT %s IF FOR %s"];
|
%#p[shape="box",label="ALERT %s IF FOR %s"];
|
||||||
%#p -> %#p;
|
%#p -> %x;
|
||||||
%s
|
%s
|
||||||
}`, &rule, rule.name, utility.DurationToString(rule.holdDuration), &rule, rule.Vector, rule.Vector.NodeTreeToDotGraph())
|
}`,
|
||||||
|
&rule, rule.name, utility.DurationToString(rule.holdDuration),
|
||||||
|
&rule, reflect.ValueOf(rule.Vector).Pointer(),
|
||||||
|
rule.Vector.NodeTreeToDotGraph())
|
||||||
return graph
|
return graph
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -193,6 +207,7 @@ func (rule *AlertingRule) String() string {
|
||||||
return fmt.Sprintf("ALERT %s IF %s FOR %s WITH %s", rule.name, rule.Vector, utility.DurationToString(rule.holdDuration), rule.Labels)
|
return fmt.Sprintf("ALERT %s IF %s FOR %s WITH %s", rule.name, rule.Vector, utility.DurationToString(rule.holdDuration), rule.Labels)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HTMLSnippet returns an HTML snippet representing this alerting rule.
|
||||||
func (rule *AlertingRule) HTMLSnippet() template.HTML {
|
func (rule *AlertingRule) HTMLSnippet() template.HTML {
|
||||||
alertMetric := clientmodel.Metric{
|
alertMetric := clientmodel.Metric{
|
||||||
clientmodel.MetricNameLabel: AlertMetricName,
|
clientmodel.MetricNameLabel: AlertMetricName,
|
||||||
|
@ -208,11 +223,12 @@ func (rule *AlertingRule) HTMLSnippet() template.HTML {
|
||||||
rule.Labels))
|
rule.Labels))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// State returns the "maximum" state: firing > pending > inactive.
|
||||||
func (rule *AlertingRule) State() AlertState {
|
func (rule *AlertingRule) State() AlertState {
|
||||||
rule.mutex.Lock()
|
rule.mutex.Lock()
|
||||||
defer rule.mutex.Unlock()
|
defer rule.mutex.Unlock()
|
||||||
|
|
||||||
maxState := INACTIVE
|
maxState := Inactive
|
||||||
for _, activeAlert := range rule.activeAlerts {
|
for _, activeAlert := range rule.activeAlerts {
|
||||||
if activeAlert.State > maxState {
|
if activeAlert.State > maxState {
|
||||||
maxState = activeAlert.State
|
maxState = activeAlert.State
|
||||||
|
@ -221,6 +237,7 @@ func (rule *AlertingRule) State() AlertState {
|
||||||
return maxState
|
return maxState
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ActiveAlerts returns a slice of active alerts.
|
||||||
func (rule *AlertingRule) ActiveAlerts() []Alert {
|
func (rule *AlertingRule) ActiveAlerts() []Alert {
|
||||||
rule.mutex.Lock()
|
rule.mutex.Lock()
|
||||||
defer rule.mutex.Unlock()
|
defer rule.mutex.Unlock()
|
||||||
|
@ -232,7 +249,7 @@ func (rule *AlertingRule) ActiveAlerts() []Alert {
|
||||||
return alerts
|
return alerts
|
||||||
}
|
}
|
||||||
|
|
||||||
// Construct a new AlertingRule.
|
// NewAlertingRule constructs a new AlertingRule.
|
||||||
func NewAlertingRule(name string, vector ast.VectorNode, holdDuration time.Duration, labels clientmodel.LabelSet, summary string, description string) *AlertingRule {
|
func NewAlertingRule(name string, vector ast.VectorNode, holdDuration time.Duration, labels clientmodel.LabelSet, summary string, description string) *AlertingRule {
|
||||||
return &AlertingRule{
|
return &AlertingRule{
|
||||||
name: name,
|
name: name,
|
||||||
|
|
|
@ -17,6 +17,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"reflect"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
@ -223,7 +224,7 @@ func EvalToVector(node Node, timestamp clientmodel.Timestamp, storage local.Stor
|
||||||
evalTimer.Stop()
|
evalTimer.Stop()
|
||||||
return vector, nil
|
return vector, nil
|
||||||
case MATRIX:
|
case MATRIX:
|
||||||
return nil, errors.New("Matrices not supported by EvalToVector")
|
return nil, errors.New("matrices not supported by EvalToVector")
|
||||||
case STRING:
|
case STRING:
|
||||||
str := node.(StringNode).Eval(timestamp)
|
str := node.(StringNode).Eval(timestamp)
|
||||||
evalTimer.Stop()
|
evalTimer.Stop()
|
||||||
|
@ -242,7 +243,7 @@ func (node *ScalarLiteral) NodeTreeToDotGraph() string {
|
||||||
func functionArgsToDotGraph(node Node, args []Node) string {
|
func functionArgsToDotGraph(node Node, args []Node) string {
|
||||||
graph := ""
|
graph := ""
|
||||||
for _, arg := range args {
|
for _, arg := range args {
|
||||||
graph += fmt.Sprintf("%#p -> %#p;\n", node, arg)
|
graph += fmt.Sprintf("%x -> %x;\n", reflect.ValueOf(node).Pointer(), reflect.ValueOf(arg).Pointer())
|
||||||
}
|
}
|
||||||
for _, arg := range args {
|
for _, arg := range args {
|
||||||
graph += arg.NodeTreeToDotGraph()
|
graph += arg.NodeTreeToDotGraph()
|
||||||
|
@ -260,13 +261,21 @@ func (node *ScalarFunctionCall) NodeTreeToDotGraph() string {
|
||||||
|
|
||||||
// NodeTreeToDotGraph returns a DOT representation of the expression.
|
// NodeTreeToDotGraph returns a DOT representation of the expression.
|
||||||
func (node *ScalarArithExpr) NodeTreeToDotGraph() string {
|
func (node *ScalarArithExpr) NodeTreeToDotGraph() string {
|
||||||
graph := fmt.Sprintf(`
|
nodeAddr := reflect.ValueOf(node).Pointer()
|
||||||
%#p[label="%s"];
|
graph := fmt.Sprintf(
|
||||||
%#p -> %#p;
|
`
|
||||||
%#p -> %#p;
|
%x[label="%s"];
|
||||||
|
%x -> %x;
|
||||||
|
%x -> %x;
|
||||||
%s
|
%s
|
||||||
%s
|
%s
|
||||||
}`, node, node.opType, node, node.lhs, node, node.rhs, node.lhs.NodeTreeToDotGraph(), node.rhs.NodeTreeToDotGraph())
|
}`,
|
||||||
|
nodeAddr, node.opType,
|
||||||
|
nodeAddr, reflect.ValueOf(node.lhs).Pointer(),
|
||||||
|
nodeAddr, reflect.ValueOf(node.rhs).Pointer(),
|
||||||
|
node.lhs.NodeTreeToDotGraph(),
|
||||||
|
node.rhs.NodeTreeToDotGraph(),
|
||||||
|
)
|
||||||
return graph
|
return graph
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -295,20 +304,28 @@ func (node *VectorAggregation) NodeTreeToDotGraph() string {
|
||||||
node,
|
node,
|
||||||
node.aggrType,
|
node.aggrType,
|
||||||
strings.Join(groupByStrings, ", "))
|
strings.Join(groupByStrings, ", "))
|
||||||
graph += fmt.Sprintf("%#p -> %#p;\n", node, node.vector)
|
graph += fmt.Sprintf("%#p -> %x;\n", node, reflect.ValueOf(node.vector).Pointer())
|
||||||
graph += node.vector.NodeTreeToDotGraph()
|
graph += node.vector.NodeTreeToDotGraph()
|
||||||
return graph
|
return graph
|
||||||
}
|
}
|
||||||
|
|
||||||
// NodeTreeToDotGraph returns a DOT representation of the expression.
|
// NodeTreeToDotGraph returns a DOT representation of the expression.
|
||||||
func (node *VectorArithExpr) NodeTreeToDotGraph() string {
|
func (node *VectorArithExpr) NodeTreeToDotGraph() string {
|
||||||
graph := fmt.Sprintf(`
|
nodeAddr := reflect.ValueOf(node).Pointer()
|
||||||
%#p[label="%s"];
|
graph := fmt.Sprintf(
|
||||||
%#p -> %#p;
|
`
|
||||||
%#p -> %#p;
|
%x[label="%s"];
|
||||||
|
%x -> %x;
|
||||||
|
%x -> %x;
|
||||||
%s
|
%s
|
||||||
%s
|
%s
|
||||||
`, node, node.opType, node, node.lhs, node, node.rhs, node.lhs.NodeTreeToDotGraph(), node.rhs.NodeTreeToDotGraph())
|
}`,
|
||||||
|
nodeAddr, node.opType,
|
||||||
|
nodeAddr, reflect.ValueOf(node.lhs).Pointer(),
|
||||||
|
nodeAddr, reflect.ValueOf(node.rhs).Pointer(),
|
||||||
|
node.lhs.NodeTreeToDotGraph(),
|
||||||
|
node.rhs.NodeTreeToDotGraph(),
|
||||||
|
)
|
||||||
return graph
|
return graph
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,16 +25,23 @@ import (
|
||||||
"github.com/prometheus/prometheus/utility"
|
"github.com/prometheus/prometheus/utility"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// CreateRecordingRule is a convenience function to create a recording rule.
|
||||||
func CreateRecordingRule(name string, labels clientmodel.LabelSet, expr ast.Node, permanent bool) (*RecordingRule, error) {
|
func CreateRecordingRule(name string, labels clientmodel.LabelSet, expr ast.Node, permanent bool) (*RecordingRule, error) {
|
||||||
if _, ok := expr.(ast.VectorNode); !ok {
|
if _, ok := expr.(ast.VectorNode); !ok {
|
||||||
return nil, fmt.Errorf("Recording rule expression %v does not evaluate to vector type", expr)
|
return nil, fmt.Errorf("recording rule expression %v does not evaluate to vector type", expr)
|
||||||
}
|
}
|
||||||
return NewRecordingRule(name, labels, expr.(ast.VectorNode), permanent), nil
|
return &RecordingRule{
|
||||||
|
name: name,
|
||||||
|
labels: labels,
|
||||||
|
vector: expr.(ast.VectorNode),
|
||||||
|
permanent: permanent,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateAlertingRule is a convenience function to create a new alerting rule.
|
||||||
func CreateAlertingRule(name string, expr ast.Node, holdDurationStr string, labels clientmodel.LabelSet, summary string, description string) (*AlertingRule, error) {
|
func CreateAlertingRule(name string, expr ast.Node, holdDurationStr string, labels clientmodel.LabelSet, summary string, description string) (*AlertingRule, error) {
|
||||||
if _, ok := expr.(ast.VectorNode); !ok {
|
if _, ok := expr.(ast.VectorNode); !ok {
|
||||||
return nil, fmt.Errorf("Alert rule expression %v does not evaluate to vector type", expr)
|
return nil, fmt.Errorf("alert rule expression %v does not evaluate to vector type", expr)
|
||||||
}
|
}
|
||||||
holdDuration, err := utility.StringToDuration(holdDurationStr)
|
holdDuration, err := utility.StringToDuration(holdDurationStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -43,10 +50,11 @@ func CreateAlertingRule(name string, expr ast.Node, holdDurationStr string, labe
|
||||||
return NewAlertingRule(name, expr.(ast.VectorNode), holdDuration, labels, summary, description), nil
|
return NewAlertingRule(name, expr.(ast.VectorNode), holdDuration, labels, summary, description), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewFunctionCall is a convenience function to create a new AST function-call node.
|
||||||
func NewFunctionCall(name string, args []ast.Node) (ast.Node, error) {
|
func NewFunctionCall(name string, args []ast.Node) (ast.Node, error) {
|
||||||
function, err := ast.GetFunction(name)
|
function, err := ast.GetFunction(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Unknown function \"%v\"", name)
|
return nil, fmt.Errorf("unknown function %q", name)
|
||||||
}
|
}
|
||||||
functionCall, err := ast.NewFunctionCall(function, args)
|
functionCall, err := ast.NewFunctionCall(function, args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -55,9 +63,10 @@ func NewFunctionCall(name string, args []ast.Node) (ast.Node, error) {
|
||||||
return functionCall, nil
|
return functionCall, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewVectorAggregation is a convenience function to create a new AST vector aggregation.
|
||||||
func NewVectorAggregation(aggrTypeStr string, vector ast.Node, groupBy clientmodel.LabelNames, keepExtraLabels bool) (*ast.VectorAggregation, error) {
|
func NewVectorAggregation(aggrTypeStr string, vector ast.Node, groupBy clientmodel.LabelNames, keepExtraLabels bool) (*ast.VectorAggregation, error) {
|
||||||
if _, ok := vector.(ast.VectorNode); !ok {
|
if _, ok := vector.(ast.VectorNode); !ok {
|
||||||
return nil, fmt.Errorf("Operand of %v aggregation must be of vector type", aggrTypeStr)
|
return nil, fmt.Errorf("operand of %v aggregation must be of vector type", aggrTypeStr)
|
||||||
}
|
}
|
||||||
var aggrTypes = map[string]ast.AggrType{
|
var aggrTypes = map[string]ast.AggrType{
|
||||||
"SUM": ast.SUM,
|
"SUM": ast.SUM,
|
||||||
|
@ -68,11 +77,12 @@ func NewVectorAggregation(aggrTypeStr string, vector ast.Node, groupBy clientmod
|
||||||
}
|
}
|
||||||
aggrType, ok := aggrTypes[aggrTypeStr]
|
aggrType, ok := aggrTypes[aggrTypeStr]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("Unknown aggregation type '%v'", aggrTypeStr)
|
return nil, fmt.Errorf("unknown aggregation type %q", aggrTypeStr)
|
||||||
}
|
}
|
||||||
return ast.NewVectorAggregation(aggrType, vector.(ast.VectorNode), groupBy, keepExtraLabels), nil
|
return ast.NewVectorAggregation(aggrType, vector.(ast.VectorNode), groupBy, keepExtraLabels), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewArithExpr is a convenience function to create a new AST arithmetic expression.
|
||||||
func NewArithExpr(opTypeStr string, lhs ast.Node, rhs ast.Node) (ast.Node, error) {
|
func NewArithExpr(opTypeStr string, lhs ast.Node, rhs ast.Node) (ast.Node, error) {
|
||||||
var opTypes = map[string]ast.BinOpType{
|
var opTypes = map[string]ast.BinOpType{
|
||||||
"+": ast.ADD,
|
"+": ast.ADD,
|
||||||
|
@ -91,7 +101,7 @@ func NewArithExpr(opTypeStr string, lhs ast.Node, rhs ast.Node) (ast.Node, error
|
||||||
}
|
}
|
||||||
opType, ok := opTypes[opTypeStr]
|
opType, ok := opTypes[opTypeStr]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("Invalid binary operator \"%v\"", opTypeStr)
|
return nil, fmt.Errorf("invalid binary operator %q", opTypeStr)
|
||||||
}
|
}
|
||||||
expr, err := ast.NewArithExpr(opType, lhs, rhs)
|
expr, err := ast.NewArithExpr(opType, lhs, rhs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -100,6 +110,7 @@ func NewArithExpr(opTypeStr string, lhs ast.Node, rhs ast.Node) (ast.Node, error
|
||||||
return expr, nil
|
return expr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewMatrixSelector is a convenience function to create a new AST matrix selector.
|
||||||
func NewMatrixSelector(vector ast.Node, intervalStr string) (ast.MatrixNode, error) {
|
func NewMatrixSelector(vector ast.Node, intervalStr string) (ast.MatrixNode, error) {
|
||||||
switch vector.(type) {
|
switch vector.(type) {
|
||||||
case *ast.VectorSelector:
|
case *ast.VectorSelector:
|
||||||
|
@ -107,7 +118,7 @@ func NewMatrixSelector(vector ast.Node, intervalStr string) (ast.MatrixNode, err
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("Intervals are currently only supported for vector selectors.")
|
return nil, fmt.Errorf("intervals are currently only supported for vector selectors")
|
||||||
}
|
}
|
||||||
interval, err := utility.StringToDuration(intervalStr)
|
interval, err := utility.StringToDuration(intervalStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -126,11 +137,13 @@ func newLabelMatcher(matchTypeStr string, name clientmodel.LabelName, value clie
|
||||||
}
|
}
|
||||||
matchType, ok := matchTypes[matchTypeStr]
|
matchType, ok := matchTypes[matchTypeStr]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("Invalid label matching operator \"%v\"", matchTypeStr)
|
return nil, fmt.Errorf("invalid label matching operator %q", matchTypeStr)
|
||||||
}
|
}
|
||||||
return metric.NewLabelMatcher(matchType, name, value)
|
return metric.NewLabelMatcher(matchType, name, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TableLinkForExpression creates an escaped relative link to the table view of
|
||||||
|
// the provided expression.
|
||||||
func TableLinkForExpression(expr string) string {
|
func TableLinkForExpression(expr string) string {
|
||||||
// url.QueryEscape percent-escapes everything except spaces, for which it
|
// url.QueryEscape percent-escapes everything except spaces, for which it
|
||||||
// uses "+". However, in the non-query part of a URI, only percent-escaped
|
// uses "+". However, in the non-query part of a URI, only percent-escaped
|
||||||
|
@ -143,6 +156,8 @@ func TableLinkForExpression(expr string) string {
|
||||||
return fmt.Sprintf("/graph#%s", strings.Replace(urlData, "+", "%20", -1))
|
return fmt.Sprintf("/graph#%s", strings.Replace(urlData, "+", "%20", -1))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GraphLinkForExpression creates an escaped relative link to the graph view of
|
||||||
|
// the provided expression.
|
||||||
func GraphLinkForExpression(expr string) string {
|
func GraphLinkForExpression(expr string) string {
|
||||||
urlData := url.QueryEscape(fmt.Sprintf(`[{"expr":%q}]`, expr))
|
urlData := url.QueryEscape(fmt.Sprintf(`[{"expr":%q}]`, expr))
|
||||||
return fmt.Sprintf("/graph#%s", strings.Replace(urlData, "+", "%20", -1))
|
return fmt.Sprintf("/graph#%s", strings.Replace(urlData, "+", "%20", -1))
|
||||||
|
|
|
@ -26,6 +26,7 @@ import (
|
||||||
"github.com/prometheus/prometheus/rules/ast"
|
"github.com/prometheus/prometheus/rules/ast"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// RulesLexer is the lexer for rule expressions.
|
||||||
type RulesLexer struct {
|
type RulesLexer struct {
|
||||||
// Errors encountered during parsing.
|
// Errors encountered during parsing.
|
||||||
errors []string
|
errors []string
|
||||||
|
@ -94,38 +95,37 @@ func newRulesLexer(src io.Reader, singleExpr bool) *RulesLexer {
|
||||||
return lexer
|
return lexer
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoadFromReader(rulesReader io.Reader, singleExpr bool) (interface{}, error) {
|
func lexAndParse(rulesReader io.Reader, singleExpr bool) (*RulesLexer, error) {
|
||||||
lexer := newRulesLexer(rulesReader, singleExpr)
|
lexer := newRulesLexer(rulesReader, singleExpr)
|
||||||
ret := yyParse(lexer)
|
ret := yyParse(lexer)
|
||||||
if ret != 0 && len(lexer.errors) == 0 {
|
if ret != 0 && len(lexer.errors) == 0 {
|
||||||
lexer.Error("Unknown parser error")
|
lexer.Error("unknown parser error")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(lexer.errors) > 0 {
|
if len(lexer.errors) > 0 {
|
||||||
err := errors.New(strings.Join(lexer.errors, "\n"))
|
err := errors.New(strings.Join(lexer.errors, "\n"))
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
return lexer, nil
|
||||||
if singleExpr {
|
|
||||||
return lexer.parsedExpr, nil
|
|
||||||
} else {
|
|
||||||
return lexer.parsedRules, nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LoadRulesFromReader parses rules from the provided reader and returns them.
|
||||||
func LoadRulesFromReader(rulesReader io.Reader) ([]Rule, error) {
|
func LoadRulesFromReader(rulesReader io.Reader) ([]Rule, error) {
|
||||||
expr, err := LoadFromReader(rulesReader, false)
|
lexer, err := lexAndParse(rulesReader, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return expr.([]Rule), err
|
return lexer.parsedRules, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LoadRulesFromString parses rules from the provided string returns them.
|
||||||
func LoadRulesFromString(rulesString string) ([]Rule, error) {
|
func LoadRulesFromString(rulesString string) ([]Rule, error) {
|
||||||
rulesReader := strings.NewReader(rulesString)
|
rulesReader := strings.NewReader(rulesString)
|
||||||
return LoadRulesFromReader(rulesReader)
|
return LoadRulesFromReader(rulesReader)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LoadRulesFromFile parses rules from the file of the provided name and returns
|
||||||
|
// them.
|
||||||
func LoadRulesFromFile(fileName string) ([]Rule, error) {
|
func LoadRulesFromFile(fileName string) ([]Rule, error) {
|
||||||
rulesReader, err := os.Open(fileName)
|
rulesReader, err := os.Open(fileName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -135,19 +135,25 @@ func LoadRulesFromFile(fileName string) ([]Rule, error) {
|
||||||
return LoadRulesFromReader(rulesReader)
|
return LoadRulesFromReader(rulesReader)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LoadExprFromReader parses a single expression from the provided reader and
|
||||||
|
// returns it as an AST node.
|
||||||
func LoadExprFromReader(exprReader io.Reader) (ast.Node, error) {
|
func LoadExprFromReader(exprReader io.Reader) (ast.Node, error) {
|
||||||
expr, err := LoadFromReader(exprReader, true)
|
lexer, err := lexAndParse(exprReader, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return expr.(ast.Node), err
|
return lexer.parsedExpr, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LoadExprFromString parses a single expression from the provided string and
|
||||||
|
// returns it as an AST node.
|
||||||
func LoadExprFromString(exprString string) (ast.Node, error) {
|
func LoadExprFromString(exprString string) (ast.Node, error) {
|
||||||
exprReader := strings.NewReader(exprString)
|
exprReader := strings.NewReader(exprString)
|
||||||
return LoadExprFromReader(exprReader)
|
return LoadExprFromReader(exprReader)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LoadExprFromFile parses a single expression from the file of the provided
|
||||||
|
// name and returns it as an AST node.
|
||||||
func LoadExprFromFile(fileName string) (ast.Node, error) {
|
func LoadExprFromFile(fileName string) (ast.Node, error) {
|
||||||
exprReader, err := os.Open(fileName)
|
exprReader, err := os.Open(fileName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -62,6 +62,8 @@ func init() {
|
||||||
prometheus.MustRegister(evalDuration)
|
prometheus.MustRegister(evalDuration)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A RuleManager manages recording and alerting rules. Create instances with
|
||||||
|
// NewRuleManager.
|
||||||
type RuleManager interface {
|
type RuleManager interface {
|
||||||
// Load and add rules from rule files specified in the configuration.
|
// Load and add rules from rule files specified in the configuration.
|
||||||
AddRulesFromConfig(config config.Config) error
|
AddRulesFromConfig(config config.Config) error
|
||||||
|
@ -88,9 +90,10 @@ type ruleManager struct {
|
||||||
results chan<- *extraction.Result
|
results chan<- *extraction.Result
|
||||||
notificationHandler *notification.NotificationHandler
|
notificationHandler *notification.NotificationHandler
|
||||||
|
|
||||||
prometheusUrl string
|
prometheusURL string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RuleManagerOptions bundles options for the RuleManager.
|
||||||
type RuleManagerOptions struct {
|
type RuleManagerOptions struct {
|
||||||
EvaluationInterval time.Duration
|
EvaluationInterval time.Duration
|
||||||
Storage local.Storage
|
Storage local.Storage
|
||||||
|
@ -98,9 +101,11 @@ type RuleManagerOptions struct {
|
||||||
NotificationHandler *notification.NotificationHandler
|
NotificationHandler *notification.NotificationHandler
|
||||||
Results chan<- *extraction.Result
|
Results chan<- *extraction.Result
|
||||||
|
|
||||||
PrometheusUrl string
|
PrometheusURL string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewRuleManager returns an implementation of RuleManager, ready to be started
|
||||||
|
// by calling the Run method.
|
||||||
func NewRuleManager(o *RuleManagerOptions) RuleManager {
|
func NewRuleManager(o *RuleManagerOptions) RuleManager {
|
||||||
manager := &ruleManager{
|
manager := &ruleManager{
|
||||||
rules: []rules.Rule{},
|
rules: []rules.Rule{},
|
||||||
|
@ -110,7 +115,7 @@ func NewRuleManager(o *RuleManagerOptions) RuleManager {
|
||||||
storage: o.Storage,
|
storage: o.Storage,
|
||||||
results: o.Results,
|
results: o.Results,
|
||||||
notificationHandler: o.NotificationHandler,
|
notificationHandler: o.NotificationHandler,
|
||||||
prometheusUrl: o.PrometheusUrl,
|
prometheusURL: o.PrometheusURL,
|
||||||
}
|
}
|
||||||
return manager
|
return manager
|
||||||
}
|
}
|
||||||
|
@ -120,6 +125,12 @@ func (m *ruleManager) Run() {
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
// TODO(beorn): This has the same problem as the scraper had
|
||||||
|
// before. If rule evaluation takes longer than the interval,
|
||||||
|
// there is a 50% chance per iteration that - after stopping the
|
||||||
|
// ruleManager - a new evaluation will be started rather than
|
||||||
|
// the ruleManager actually stopped. We need a similar
|
||||||
|
// contraption here as in the scraper.
|
||||||
select {
|
select {
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
@ -145,7 +156,7 @@ func (m *ruleManager) queueAlertNotifications(rule *rules.AlertingRule, timestam
|
||||||
|
|
||||||
notifications := make(notification.NotificationReqs, 0, len(activeAlerts))
|
notifications := make(notification.NotificationReqs, 0, len(activeAlerts))
|
||||||
for _, aa := range activeAlerts {
|
for _, aa := range activeAlerts {
|
||||||
if aa.State != rules.FIRING {
|
if aa.State != rules.Firing {
|
||||||
// BUG: In the future, make AlertManager support pending alerts?
|
// BUG: In the future, make AlertManager support pending alerts?
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -185,7 +196,7 @@ func (m *ruleManager) queueAlertNotifications(rule *rules.AlertingRule, timestam
|
||||||
Value: aa.Value,
|
Value: aa.Value,
|
||||||
ActiveSince: aa.ActiveSince.Time(),
|
ActiveSince: aa.ActiveSince.Time(),
|
||||||
RuleString: rule.String(),
|
RuleString: rule.String(),
|
||||||
GeneratorURL: m.prometheusUrl + rules.GraphLinkForExpression(rule.Vector.String()),
|
GeneratorURL: m.prometheusURL + rules.GraphLinkForExpression(rule.Vector.String()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
m.notificationHandler.SubmitReqs(notifications)
|
m.notificationHandler.SubmitReqs(notifications)
|
||||||
|
|
|
@ -16,6 +16,7 @@ package rules
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
clientmodel "github.com/prometheus/client_golang/model"
|
clientmodel "github.com/prometheus/client_golang/model"
|
||||||
|
|
||||||
|
@ -32,14 +33,16 @@ type RecordingRule struct {
|
||||||
permanent bool
|
permanent bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Name returns the rule name.
|
||||||
func (rule RecordingRule) Name() string { return rule.name }
|
func (rule RecordingRule) Name() string { return rule.name }
|
||||||
|
|
||||||
|
// EvalRaw returns the raw value of the rule expression.
|
||||||
func (rule RecordingRule) EvalRaw(timestamp clientmodel.Timestamp, storage local.Storage) (ast.Vector, error) {
|
func (rule RecordingRule) EvalRaw(timestamp clientmodel.Timestamp, storage local.Storage) (ast.Vector, error) {
|
||||||
return ast.EvalVectorInstant(rule.vector, timestamp, storage, stats.NewTimerGroup())
|
return ast.EvalVectorInstant(rule.vector, timestamp, storage, stats.NewTimerGroup())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Eval evaluates the rule and then overrides the metric names and labels accordingly.
|
||||||
func (rule RecordingRule) Eval(timestamp clientmodel.Timestamp, storage local.Storage) (ast.Vector, error) {
|
func (rule RecordingRule) Eval(timestamp clientmodel.Timestamp, storage local.Storage) (ast.Vector, error) {
|
||||||
// Get the raw value of the rule expression.
|
|
||||||
vector, err := rule.EvalRaw(timestamp, storage)
|
vector, err := rule.EvalRaw(timestamp, storage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -60,12 +63,18 @@ func (rule RecordingRule) Eval(timestamp clientmodel.Timestamp, storage local.St
|
||||||
return vector, nil
|
return vector, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ToDotGraph returns the text representation of a dot graph.
|
||||||
func (rule RecordingRule) ToDotGraph() string {
|
func (rule RecordingRule) ToDotGraph() string {
|
||||||
graph := fmt.Sprintf(`digraph "Rules" {
|
graph := fmt.Sprintf(
|
||||||
|
`digraph "Rules" {
|
||||||
%#p[shape="box",label="%s = "];
|
%#p[shape="box",label="%s = "];
|
||||||
%#p -> %#p;
|
%#p -> %x;
|
||||||
%s
|
%s
|
||||||
}`, &rule, rule.name, &rule, rule.vector, rule.vector.NodeTreeToDotGraph())
|
}`,
|
||||||
|
&rule, rule.name,
|
||||||
|
&rule, reflect.ValueOf(rule.vector).Pointer(),
|
||||||
|
rule.vector.NodeTreeToDotGraph(),
|
||||||
|
)
|
||||||
return graph
|
return graph
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,6 +82,7 @@ func (rule RecordingRule) String() string {
|
||||||
return fmt.Sprintf("%s%s = %s\n", rule.name, rule.labels, rule.vector)
|
return fmt.Sprintf("%s%s = %s\n", rule.name, rule.labels, rule.vector)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HTMLSnippet returns an HTML snippet representing this rule.
|
||||||
func (rule RecordingRule) HTMLSnippet() template.HTML {
|
func (rule RecordingRule) HTMLSnippet() template.HTML {
|
||||||
ruleExpr := rule.vector.String()
|
ruleExpr := rule.vector.String()
|
||||||
return template.HTML(fmt.Sprintf(
|
return template.HTML(fmt.Sprintf(
|
||||||
|
@ -83,13 +93,3 @@ func (rule RecordingRule) HTMLSnippet() template.HTML {
|
||||||
GraphLinkForExpression(ruleExpr),
|
GraphLinkForExpression(ruleExpr),
|
||||||
ruleExpr))
|
ruleExpr))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Construct a new RecordingRule.
|
|
||||||
func NewRecordingRule(name string, labels clientmodel.LabelSet, vector ast.VectorNode, permanent bool) *RecordingRule {
|
|
||||||
return &RecordingRule{
|
|
||||||
name: name,
|
|
||||||
labels: labels,
|
|
||||||
vector: vector,
|
|
||||||
permanent: permanent,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ type QueryTiming int
|
||||||
const (
|
const (
|
||||||
TotalEvalTime QueryTiming = iota
|
TotalEvalTime QueryTiming = iota
|
||||||
ResultSortTime
|
ResultSortTime
|
||||||
JsonEncodeTime
|
JSONEncodeTime
|
||||||
PreloadTime
|
PreloadTime
|
||||||
TotalQueryPreparationTime
|
TotalQueryPreparationTime
|
||||||
InnerViewBuildingTime
|
InnerViewBuildingTime
|
||||||
|
@ -44,7 +44,7 @@ func (s QueryTiming) String() string {
|
||||||
return "Total eval time"
|
return "Total eval time"
|
||||||
case ResultSortTime:
|
case ResultSortTime:
|
||||||
return "Result sorting time"
|
return "Result sorting time"
|
||||||
case JsonEncodeTime:
|
case JSONEncodeTime:
|
||||||
return "JSON encoding time"
|
return "JSON encoding time"
|
||||||
case PreloadTime:
|
case PreloadTime:
|
||||||
return "Query preloading time"
|
return "Query preloading time"
|
||||||
|
|
|
@ -20,7 +20,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// A timer that can be started and stopped and accumulates the total time it
|
// A Timer that can be started and stopped and accumulates the total time it
|
||||||
// was running (the time between Start() and Stop()).
|
// was running (the time between Start() and Stop()).
|
||||||
type Timer struct {
|
type Timer struct {
|
||||||
name fmt.Stringer
|
name fmt.Stringer
|
||||||
|
@ -51,12 +51,12 @@ type TimerGroup struct {
|
||||||
child *TimerGroup
|
child *TimerGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
// Construct a new TimerGroup.
|
// NewTimerGroup constructs a new TimerGroup.
|
||||||
func NewTimerGroup() *TimerGroup {
|
func NewTimerGroup() *TimerGroup {
|
||||||
return &TimerGroup{timers: map[fmt.Stringer]*Timer{}}
|
return &TimerGroup{timers: map[fmt.Stringer]*Timer{}}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get (and create, if necessary) the Timer for a given code section.
|
// GetTimer gets (and creates, if necessary) the Timer for a given code section.
|
||||||
func (t *TimerGroup) GetTimer(name fmt.Stringer) *Timer {
|
func (t *TimerGroup) GetTimer(name fmt.Stringer) *Timer {
|
||||||
if timer, exists := t.timers[name]; exists {
|
if timer, exists := t.timers[name]; exists {
|
||||||
return timer
|
return timer
|
||||||
|
@ -69,14 +69,18 @@ func (t *TimerGroup) GetTimer(name fmt.Stringer) *Timer {
|
||||||
return timer
|
return timer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Timers is a slice of Timer pointers that implements Len and Swap from
|
||||||
|
// sort.Interface.
|
||||||
type Timers []*Timer
|
type Timers []*Timer
|
||||||
|
|
||||||
type byCreationTimeSorter struct{ Timers }
|
type byCreationTimeSorter struct{ Timers }
|
||||||
|
|
||||||
|
// Len implements sort.Interface.
|
||||||
func (t Timers) Len() int {
|
func (t Timers) Len() int {
|
||||||
return len(t)
|
return len(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Swap implements sort.Interface.
|
||||||
func (t Timers) Swap(i, j int) {
|
func (t Timers) Swap(i, j int) {
|
||||||
t[i], t[j] = t[j], t[i]
|
t[i], t[j] = t[j], t[i]
|
||||||
}
|
}
|
||||||
|
|
|
@ -91,6 +91,7 @@ type templateExpander struct {
|
||||||
funcMap text_template.FuncMap
|
funcMap text_template.FuncMap
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewTemplateExpander returns a template expander ready to use.
|
||||||
func NewTemplateExpander(text string, name string, data interface{}, timestamp clientmodel.Timestamp, storage local.Storage) *templateExpander {
|
func NewTemplateExpander(text string, name string, data interface{}, timestamp clientmodel.Timestamp, storage local.Storage) *templateExpander {
|
||||||
return &templateExpander{
|
return &templateExpander{
|
||||||
text: text,
|
text: text,
|
||||||
|
@ -152,17 +153,16 @@ func NewTemplateExpander(text string, name string, data interface{}, timestamp c
|
||||||
v /= 1000
|
v /= 1000
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("%.4g%s", v, prefix)
|
return fmt.Sprintf("%.4g%s", v, prefix)
|
||||||
} else {
|
|
||||||
prefix := ""
|
|
||||||
for _, p := range []string{"m", "u", "n", "p", "f", "a", "z", "y"} {
|
|
||||||
if math.Abs(v) >= 1 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
prefix = p
|
|
||||||
v *= 1000
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%.4g%s", v, prefix)
|
|
||||||
}
|
}
|
||||||
|
prefix := ""
|
||||||
|
for _, p := range []string{"m", "u", "n", "p", "f", "a", "z", "y"} {
|
||||||
|
if math.Abs(v) >= 1 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
prefix = p
|
||||||
|
v *= 1000
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%.4g%s", v, prefix)
|
||||||
},
|
},
|
||||||
"humanize1024": func(v float64) string {
|
"humanize1024": func(v float64) string {
|
||||||
if math.Abs(v) <= 1 {
|
if math.Abs(v) <= 1 {
|
||||||
|
@ -204,17 +204,16 @@ func NewTemplateExpander(text string, name string, data interface{}, timestamp c
|
||||||
}
|
}
|
||||||
// For seconds, we display 4 significant digts.
|
// For seconds, we display 4 significant digts.
|
||||||
return fmt.Sprintf("%s%.4gs", sign, math.Floor(seconds*1000+.5)/1000)
|
return fmt.Sprintf("%s%.4gs", sign, math.Floor(seconds*1000+.5)/1000)
|
||||||
} else {
|
|
||||||
prefix := ""
|
|
||||||
for _, p := range []string{"m", "u", "n", "p", "f", "a", "z", "y"} {
|
|
||||||
if math.Abs(v) >= 1 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
prefix = p
|
|
||||||
v *= 1000
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%.4g%ss", v, prefix)
|
|
||||||
}
|
}
|
||||||
|
prefix := ""
|
||||||
|
for _, p := range []string{"m", "u", "n", "p", "f", "a", "z", "y"} {
|
||||||
|
if math.Abs(v) >= 1 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
prefix = p
|
||||||
|
v *= 1000
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%.4g%ss", v, prefix)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -229,7 +228,7 @@ func (te templateExpander) Expand() (result string, resultErr error) {
|
||||||
var ok bool
|
var ok bool
|
||||||
resultErr, ok = r.(error)
|
resultErr, ok = r.(error)
|
||||||
if !ok {
|
if !ok {
|
||||||
resultErr = fmt.Errorf("Panic expanding template %v: %v", te.name, r)
|
resultErr = fmt.Errorf("panic expanding template %v: %v", te.name, r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
@ -237,11 +236,11 @@ func (te templateExpander) Expand() (result string, resultErr error) {
|
||||||
var buffer bytes.Buffer
|
var buffer bytes.Buffer
|
||||||
tmpl, err := text_template.New(te.name).Funcs(te.funcMap).Parse(te.text)
|
tmpl, err := text_template.New(te.name).Funcs(te.funcMap).Parse(te.text)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("Error parsing template %v: %v", te.name, err)
|
return "", fmt.Errorf("error parsing template %v: %v", te.name, err)
|
||||||
}
|
}
|
||||||
err = tmpl.Execute(&buffer, te.data)
|
err = tmpl.Execute(&buffer, te.data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("Error executing template %v: %v", te.name, err)
|
return "", fmt.Errorf("error executing template %v: %v", te.name, err)
|
||||||
}
|
}
|
||||||
return buffer.String(), nil
|
return buffer.String(), nil
|
||||||
}
|
}
|
||||||
|
@ -253,7 +252,7 @@ func (te templateExpander) ExpandHTML(templateFiles []string) (result string, re
|
||||||
var ok bool
|
var ok bool
|
||||||
resultErr, ok = r.(error)
|
resultErr, ok = r.(error)
|
||||||
if !ok {
|
if !ok {
|
||||||
resultErr = fmt.Errorf("Panic expanding template %v: %v", te.name, r)
|
resultErr = fmt.Errorf("panic expanding template %v: %v", te.name, r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
@ -269,17 +268,17 @@ func (te templateExpander) ExpandHTML(templateFiles []string) (result string, re
|
||||||
})
|
})
|
||||||
tmpl, err := tmpl.Parse(te.text)
|
tmpl, err := tmpl.Parse(te.text)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("Error parsing template %v: %v", te.name, err)
|
return "", fmt.Errorf("error parsing template %v: %v", te.name, err)
|
||||||
}
|
}
|
||||||
if len(templateFiles) > 0 {
|
if len(templateFiles) > 0 {
|
||||||
_, err = tmpl.ParseFiles(templateFiles...)
|
_, err = tmpl.ParseFiles(templateFiles...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("Error parsing template files for %v: %v", te.name, err)
|
return "", fmt.Errorf("error parsing template files for %v: %v", te.name, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
err = tmpl.Execute(&buffer, te.data)
|
err = tmpl.Execute(&buffer, te.data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("Error executing template %v: %v", te.name, err)
|
return "", fmt.Errorf("error executing template %v: %v", te.name, err)
|
||||||
}
|
}
|
||||||
return buffer.String(), nil
|
return buffer.String(), nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,55 +0,0 @@
|
||||||
// Copyright 2013 Prometheus Team
|
|
||||||
// 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 utility
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// The canonical example: http://golang.org/doc/progs/eff_bytesize.go.
|
|
||||||
type ByteSize float64
|
|
||||||
|
|
||||||
const (
|
|
||||||
_ = iota // ignore first value by assigning to blank identifier
|
|
||||||
KB ByteSize = 1 << (10 * iota)
|
|
||||||
MB
|
|
||||||
GB
|
|
||||||
TB
|
|
||||||
PB
|
|
||||||
EB
|
|
||||||
ZB
|
|
||||||
YB
|
|
||||||
)
|
|
||||||
|
|
||||||
func (b ByteSize) String() string {
|
|
||||||
switch {
|
|
||||||
case b >= YB:
|
|
||||||
return fmt.Sprintf("%.2fYB", b/YB)
|
|
||||||
case b >= ZB:
|
|
||||||
return fmt.Sprintf("%.2fZB", b/ZB)
|
|
||||||
case b >= EB:
|
|
||||||
return fmt.Sprintf("%.2fEB", b/EB)
|
|
||||||
case b >= PB:
|
|
||||||
return fmt.Sprintf("%.2fPB", b/PB)
|
|
||||||
case b >= TB:
|
|
||||||
return fmt.Sprintf("%.2fTB", b/TB)
|
|
||||||
case b >= GB:
|
|
||||||
return fmt.Sprintf("%.2fGB", b/GB)
|
|
||||||
case b >= MB:
|
|
||||||
return fmt.Sprintf("%.2fMB", b/MB)
|
|
||||||
case b >= KB:
|
|
||||||
return fmt.Sprintf("%.2fKB", b/KB)
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%.2fB", b)
|
|
||||||
}
|
|
176
utility/cache.go
176
utility/cache.go
|
@ -1,176 +0,0 @@
|
||||||
// Copyright 2013 Prometheus Team
|
|
||||||
// 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 utility
|
|
||||||
|
|
||||||
import (
|
|
||||||
"container/list"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Cache interface {
|
|
||||||
Put(k, v interface{}) (replaced bool, err error)
|
|
||||||
PutIfAbsent(k, v interface{}) (put bool, err error)
|
|
||||||
Get(k interface{}) (v interface{}, ok bool, err error)
|
|
||||||
Has(k interface{}) (ok bool, err error)
|
|
||||||
Delete(k interface{}) (deleted bool, err error)
|
|
||||||
Clear() (cleared bool, err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type LRUCache struct {
|
|
||||||
list *list.List
|
|
||||||
table map[interface{}]*list.Element
|
|
||||||
|
|
||||||
limit uint
|
|
||||||
size uint
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewLRUCache(limit uint) *LRUCache {
|
|
||||||
return &LRUCache{
|
|
||||||
list: list.New(),
|
|
||||||
table: map[interface{}]*list.Element{},
|
|
||||||
|
|
||||||
limit: limit,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *LRUCache) Has(k interface{}) (has bool, err error) {
|
|
||||||
_, ok := c.table[k]
|
|
||||||
return ok, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *LRUCache) Get(k interface{}) (v interface{}, ok bool, err error) {
|
|
||||||
element, ok := c.table[k]
|
|
||||||
if !ok {
|
|
||||||
return nil, false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
c.moveToFront(element)
|
|
||||||
|
|
||||||
return element.Value, true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *LRUCache) Put(k, v interface{}) (replaced bool, err error) {
|
|
||||||
element, ok := c.table[k]
|
|
||||||
if ok {
|
|
||||||
c.updateInplace(element, v)
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
c.addNew(k, v)
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *LRUCache) PutIfAbsent(k, v interface{}) (put bool, err error) {
|
|
||||||
if _, ok := c.table[k]; ok {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
c.addNew(k, v)
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *LRUCache) Delete(k interface{}) (deleted bool, err error) {
|
|
||||||
element, ok := c.table[k]
|
|
||||||
if !ok {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
c.list.Remove(element)
|
|
||||||
delete(c.table, k)
|
|
||||||
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *LRUCache) Clear() (cleared bool, err error) {
|
|
||||||
c.list.Init()
|
|
||||||
c.table = map[interface{}]*list.Element{}
|
|
||||||
c.size = 0
|
|
||||||
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *LRUCache) updateInplace(e *list.Element, v interface{}) {
|
|
||||||
e.Value = v
|
|
||||||
c.moveToFront(e)
|
|
||||||
c.checkCapacity()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *LRUCache) moveToFront(e *list.Element) {
|
|
||||||
c.list.MoveToFront(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *LRUCache) addNew(k, v interface{}) {
|
|
||||||
c.table[k] = c.list.PushFront(v)
|
|
||||||
c.checkCapacity()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *LRUCache) checkCapacity() {
|
|
||||||
for c.size > c.limit {
|
|
||||||
delElem := c.list.Back()
|
|
||||||
v := delElem.Value
|
|
||||||
c.list.Remove(delElem)
|
|
||||||
delete(c.table, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type SynchronizedCache struct {
|
|
||||||
mu sync.Mutex
|
|
||||||
c Cache
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *SynchronizedCache) Put(k, v interface{}) (replaced bool, err error) {
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
|
|
||||||
return c.c.Put(k, v)
|
|
||||||
}
|
|
||||||
func (c *SynchronizedCache) PutIfAbsent(k, v interface{}) (put bool, err error) {
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
|
|
||||||
return c.PutIfAbsent(k, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *SynchronizedCache) Get(k interface{}) (v interface{}, ok bool, err error) {
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
|
|
||||||
return c.c.Get(k)
|
|
||||||
}
|
|
||||||
func (c *SynchronizedCache) Has(k interface{}) (ok bool, err error) {
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
|
|
||||||
return c.c.Has(k)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *SynchronizedCache) Delete(k interface{}) (deleted bool, err error) {
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
|
|
||||||
return c.c.Delete(k)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *SynchronizedCache) Clear() (cleared bool, err error) {
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
|
|
||||||
return c.c.Clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSynchronizedCache(c Cache) *SynchronizedCache {
|
|
||||||
return &SynchronizedCache{
|
|
||||||
c: c,
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
// Copyright 2013 Prometheus Team
|
|
||||||
// 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.
|
|
||||||
|
|
||||||
// Useful file/filesystem related functions.
|
|
||||||
|
|
||||||
package utility
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Returns true iff dirPath is a valid directory path.
|
|
||||||
func IsDir(dirPath string) (bool, error) {
|
|
||||||
finfo, err := os.Stat(dirPath)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
if !finfo.IsDir() {
|
|
||||||
return false, fmt.Errorf("%s not a directory", dirPath)
|
|
||||||
}
|
|
||||||
return true, nil
|
|
||||||
}
|
|
|
@ -1,45 +0,0 @@
|
||||||
// Copyright 2013 Prometheus Team
|
|
||||||
// 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 utility
|
|
||||||
|
|
||||||
type FreeList chan interface{}
|
|
||||||
|
|
||||||
func NewFreeList(cap int) FreeList {
|
|
||||||
return make(FreeList, cap)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l FreeList) Get() (interface{}, bool) {
|
|
||||||
select {
|
|
||||||
case v := <-l:
|
|
||||||
return v, true
|
|
||||||
default:
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l FreeList) Give(v interface{}) bool {
|
|
||||||
select {
|
|
||||||
case l <- v:
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l FreeList) Close() {
|
|
||||||
close(l)
|
|
||||||
|
|
||||||
for _ = range l {
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -22,6 +22,11 @@ import (
|
||||||
|
|
||||||
var durationRE = regexp.MustCompile("^([0-9]+)([ywdhms]+)$")
|
var durationRE = regexp.MustCompile("^([0-9]+)([ywdhms]+)$")
|
||||||
|
|
||||||
|
// DurationToString formats a time.Duration as a string with the assumption that
|
||||||
|
// a year always has 365 days and a day always has 24h. (The former doesn't work
|
||||||
|
// in leap years, the latter is broken by DST switches, not to speak about leap
|
||||||
|
// seconds, but those are not even treated properly by the duration strings in
|
||||||
|
// the standard library.)
|
||||||
func DurationToString(duration time.Duration) string {
|
func DurationToString(duration time.Duration) string {
|
||||||
seconds := int64(duration / time.Second)
|
seconds := int64(duration / time.Second)
|
||||||
factors := map[string]int64{
|
factors := map[string]int64{
|
||||||
|
@ -45,10 +50,13 @@ func DurationToString(duration time.Duration) string {
|
||||||
return fmt.Sprintf("%v%v", seconds/factors[unit], unit)
|
return fmt.Sprintf("%v%v", seconds/factors[unit], unit)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StringToDuration parses a string into a time.Duration, assuming that a year
|
||||||
|
// always has 365d, a week 7d, a day 24h. See DurationToString for problems with
|
||||||
|
// that.
|
||||||
func StringToDuration(durationStr string) (duration time.Duration, err error) {
|
func StringToDuration(durationStr string) (duration time.Duration, err error) {
|
||||||
matches := durationRE.FindStringSubmatch(durationStr)
|
matches := durationRE.FindStringSubmatch(durationStr)
|
||||||
if len(matches) != 3 {
|
if len(matches) != 3 {
|
||||||
err = fmt.Errorf("Not a valid duration string: '%v'", durationStr)
|
err = fmt.Errorf("not a valid duration string: %q", durationStr)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
durationSeconds, _ := strconv.Atoi(matches[1])
|
durationSeconds, _ := strconv.Atoi(matches[1])
|
||||||
|
|
|
@ -24,11 +24,12 @@ const (
|
||||||
// environment variable.
|
// environment variable.
|
||||||
defaultDirectory = ""
|
defaultDirectory = ""
|
||||||
|
|
||||||
// A NO-OP Closer.
|
// NilCloser is a no-op Closer.
|
||||||
NilCloser = nilCloser(true)
|
NilCloser = nilCloser(true)
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
// Closer is the interface that wraps the Close method.
|
||||||
Closer interface {
|
Closer interface {
|
||||||
// Close reaps the underlying directory and its children. The directory
|
// Close reaps the underlying directory and its children. The directory
|
||||||
// could be deleted by its users already.
|
// could be deleted by its users already.
|
||||||
|
@ -74,6 +75,8 @@ func (c callbackCloser) Close() {
|
||||||
c.fn()
|
c.fn()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewCallbackCloser returns a Closer that calls the provided function upon
|
||||||
|
// closing.
|
||||||
func NewCallbackCloser(fn func()) *callbackCloser {
|
func NewCallbackCloser(fn func()) *callbackCloser {
|
||||||
return &callbackCloser{
|
return &callbackCloser{
|
||||||
fn: fn,
|
fn: fn,
|
||||||
|
|
|
@ -17,8 +17,8 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// A basic interface only useful in testing contexts for dispensing the time
|
// InstantProvider is a basic interface only useful in testing contexts for
|
||||||
// in a controlled manner.
|
// dispensing the time in a controlled manner.
|
||||||
type InstantProvider interface {
|
type InstantProvider interface {
|
||||||
// The current instant.
|
// The current instant.
|
||||||
Now() time.Time
|
Now() time.Time
|
||||||
|
@ -35,7 +35,7 @@ type Time struct {
|
||||||
Provider InstantProvider
|
Provider InstantProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
// Emit the current instant.
|
// Now emits the current instant.
|
||||||
func (t Time) Now() time.Time {
|
func (t Time) Now() time.Time {
|
||||||
if t.Provider == nil {
|
if t.Provider == nil {
|
||||||
return time.Now()
|
return time.Now()
|
||||||
|
|
|
@ -1,162 +0,0 @@
|
||||||
// Copyright 2013 Prometheus Team
|
|
||||||
// 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 utility
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
type state int
|
|
||||||
|
|
||||||
func (s state) String() string {
|
|
||||||
switch s {
|
|
||||||
case unstarted:
|
|
||||||
return "unstarted"
|
|
||||||
case started:
|
|
||||||
return "started"
|
|
||||||
case finished:
|
|
||||||
return "finished"
|
|
||||||
}
|
|
||||||
panic("unreachable")
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
unstarted state = iota
|
|
||||||
started
|
|
||||||
finished
|
|
||||||
)
|
|
||||||
|
|
||||||
// An UncertaintyGroup models a group of operations whose result disposition is
|
|
||||||
// tenuous and needs to be validated en masse in order to make a future
|
|
||||||
// decision.
|
|
||||||
type UncertaintyGroup interface {
|
|
||||||
// Succeed makes a remark that a given action succeeded, in part.
|
|
||||||
Succeed()
|
|
||||||
// Fail makes a remark that a given action failed, in part. Nil values are
|
|
||||||
// illegal.
|
|
||||||
Fail(error)
|
|
||||||
// MayFail makes a remark that a given action either succeeded or failed. The
|
|
||||||
// determination is made by whether the error is nil.
|
|
||||||
MayFail(error)
|
|
||||||
// Wait waits for the group to have finished and emits the result of what
|
|
||||||
// occurred for the group.
|
|
||||||
Wait() (succeeded bool)
|
|
||||||
// Errors emits any errors that could have occurred.
|
|
||||||
Errors() []error
|
|
||||||
}
|
|
||||||
|
|
||||||
type uncertaintyGroup struct {
|
|
||||||
state state
|
|
||||||
remaining uint
|
|
||||||
successes uint
|
|
||||||
results chan error
|
|
||||||
anomalies []error
|
|
||||||
sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *uncertaintyGroup) Succeed() {
|
|
||||||
if g.isFinished() {
|
|
||||||
panic("cannot remark when done")
|
|
||||||
}
|
|
||||||
|
|
||||||
g.results <- nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *uncertaintyGroup) Fail(err error) {
|
|
||||||
if g.isFinished() {
|
|
||||||
panic("cannot remark when done")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
panic("expected a failure")
|
|
||||||
}
|
|
||||||
|
|
||||||
g.results <- err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *uncertaintyGroup) MayFail(err error) {
|
|
||||||
if g.isFinished() {
|
|
||||||
panic("cannot remark when done")
|
|
||||||
}
|
|
||||||
|
|
||||||
g.results <- err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *uncertaintyGroup) isFinished() bool {
|
|
||||||
g.Lock()
|
|
||||||
defer g.Unlock()
|
|
||||||
|
|
||||||
return g.state == finished
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *uncertaintyGroup) finish() {
|
|
||||||
g.Lock()
|
|
||||||
defer g.Unlock()
|
|
||||||
|
|
||||||
g.state = finished
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *uncertaintyGroup) start() {
|
|
||||||
g.Lock()
|
|
||||||
defer g.Unlock()
|
|
||||||
|
|
||||||
if g.state != unstarted {
|
|
||||||
panic("cannot restart")
|
|
||||||
}
|
|
||||||
|
|
||||||
g.state = started
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *uncertaintyGroup) Wait() bool {
|
|
||||||
defer close(g.results)
|
|
||||||
g.start()
|
|
||||||
|
|
||||||
for g.remaining > 0 {
|
|
||||||
result := <-g.results
|
|
||||||
switch result {
|
|
||||||
case nil:
|
|
||||||
g.successes++
|
|
||||||
default:
|
|
||||||
g.anomalies = append(g.anomalies, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
g.remaining--
|
|
||||||
}
|
|
||||||
|
|
||||||
g.finish()
|
|
||||||
|
|
||||||
return len(g.anomalies) == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *uncertaintyGroup) Errors() []error {
|
|
||||||
if g.state != finished {
|
|
||||||
panic("cannot provide errors until finished")
|
|
||||||
}
|
|
||||||
|
|
||||||
return g.anomalies
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *uncertaintyGroup) String() string {
|
|
||||||
return fmt.Sprintf("UncertaintyGroup %s with %s failures", g.state, g.anomalies)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewUncertaintyGroup furnishes an UncertaintyGroup for a given set of actions
|
|
||||||
// where their quantity is known a priori.
|
|
||||||
func NewUncertaintyGroup(count uint) UncertaintyGroup {
|
|
||||||
return &uncertaintyGroup{
|
|
||||||
remaining: count,
|
|
||||||
results: make(chan error),
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,77 +0,0 @@
|
||||||
package utility
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestGroupSuccess(t *testing.T) {
|
|
||||||
uncertaintyGroup := NewUncertaintyGroup(10)
|
|
||||||
|
|
||||||
for i := 0; i < 10; i++ {
|
|
||||||
go uncertaintyGroup.Succeed()
|
|
||||||
}
|
|
||||||
|
|
||||||
result := make(chan bool)
|
|
||||||
go func() {
|
|
||||||
result <- uncertaintyGroup.Wait()
|
|
||||||
}()
|
|
||||||
select {
|
|
||||||
case v := <-result:
|
|
||||||
if !v {
|
|
||||||
t.Fatal("expected success")
|
|
||||||
}
|
|
||||||
case <-time.After(time.Second):
|
|
||||||
t.Fatal("deadline exceeded")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGroupFail(t *testing.T) {
|
|
||||||
uncertaintyGroup := NewUncertaintyGroup(10)
|
|
||||||
|
|
||||||
for i := 0; i < 10; i++ {
|
|
||||||
go uncertaintyGroup.Fail(fmt.Errorf(""))
|
|
||||||
}
|
|
||||||
|
|
||||||
result := make(chan bool)
|
|
||||||
go func() {
|
|
||||||
result <- uncertaintyGroup.Wait()
|
|
||||||
}()
|
|
||||||
select {
|
|
||||||
case v := <-result:
|
|
||||||
if v {
|
|
||||||
t.Fatal("expected failure")
|
|
||||||
}
|
|
||||||
case <-time.After(time.Second):
|
|
||||||
t.Fatal("deadline exceeded")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGroupFailMix(t *testing.T) {
|
|
||||||
uncertaintyGroup := NewUncertaintyGroup(10)
|
|
||||||
|
|
||||||
for i := 0; i < 10; i++ {
|
|
||||||
go func(i int) {
|
|
||||||
switch {
|
|
||||||
case i%2 == 0:
|
|
||||||
uncertaintyGroup.Fail(fmt.Errorf(""))
|
|
||||||
default:
|
|
||||||
uncertaintyGroup.Succeed()
|
|
||||||
}
|
|
||||||
}(i)
|
|
||||||
}
|
|
||||||
|
|
||||||
result := make(chan bool)
|
|
||||||
go func() {
|
|
||||||
result <- uncertaintyGroup.Wait()
|
|
||||||
}()
|
|
||||||
select {
|
|
||||||
case v := <-result:
|
|
||||||
if v {
|
|
||||||
t.Fatal("expected failure")
|
|
||||||
}
|
|
||||||
case <-time.After(time.Second):
|
|
||||||
t.Fatal("deadline exceeded")
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -22,17 +22,13 @@ import (
|
||||||
"github.com/prometheus/prometheus/rules/manager"
|
"github.com/prometheus/prometheus/rules/manager"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// AlertStatus bundles alerting rules and the mapping of alert states to row
|
||||||
|
// classes.
|
||||||
type AlertStatus struct {
|
type AlertStatus struct {
|
||||||
AlertingRules []*rules.AlertingRule
|
AlertingRules []*rules.AlertingRule
|
||||||
AlertStateToRowClass map[rules.AlertState]string
|
AlertStateToRowClass map[rules.AlertState]string
|
||||||
}
|
}
|
||||||
|
|
||||||
type AlertsHandler struct {
|
|
||||||
RuleManager manager.RuleManager
|
|
||||||
|
|
||||||
mutex sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
type byAlertStateSorter struct {
|
type byAlertStateSorter struct {
|
||||||
alerts []*rules.AlertingRule
|
alerts []*rules.AlertingRule
|
||||||
}
|
}
|
||||||
|
@ -49,6 +45,13 @@ func (s byAlertStateSorter) Swap(i, j int) {
|
||||||
s.alerts[i], s.alerts[j] = s.alerts[j], s.alerts[i]
|
s.alerts[i], s.alerts[j] = s.alerts[j], s.alerts[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AlertsHandler implements http.Handler.
|
||||||
|
type AlertsHandler struct {
|
||||||
|
RuleManager manager.RuleManager
|
||||||
|
|
||||||
|
mutex sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
func (h *AlertsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (h *AlertsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
h.mutex.Lock()
|
h.mutex.Lock()
|
||||||
defer h.mutex.Unlock()
|
defer h.mutex.Unlock()
|
||||||
|
@ -60,9 +63,9 @@ func (h *AlertsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
alertStatus := AlertStatus{
|
alertStatus := AlertStatus{
|
||||||
AlertingRules: alertsSorter.alerts,
|
AlertingRules: alertsSorter.alerts,
|
||||||
AlertStateToRowClass: map[rules.AlertState]string{
|
AlertStateToRowClass: map[rules.AlertState]string{
|
||||||
rules.INACTIVE: "success",
|
rules.Inactive: "success",
|
||||||
rules.PENDING: "warning",
|
rules.Pending: "warning",
|
||||||
rules.FIRING: "error",
|
rules.Firing: "error",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
executeTemplate(w, "alerts", alertStatus)
|
executeTemplate(w, "alerts", alertStatus)
|
||||||
|
|
|
@ -22,9 +22,10 @@ import (
|
||||||
"github.com/prometheus/prometheus/retrieval"
|
"github.com/prometheus/prometheus/retrieval"
|
||||||
"github.com/prometheus/prometheus/storage/local"
|
"github.com/prometheus/prometheus/storage/local"
|
||||||
"github.com/prometheus/prometheus/utility"
|
"github.com/prometheus/prometheus/utility"
|
||||||
"github.com/prometheus/prometheus/web/http_utils"
|
"github.com/prometheus/prometheus/web/httputils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// MetricsService manages the /api HTTP endpoint.
|
||||||
type MetricsService struct {
|
type MetricsService struct {
|
||||||
time utility.Time
|
time utility.Time
|
||||||
Config *config.Config
|
Config *config.Config
|
||||||
|
@ -32,9 +33,10 @@ type MetricsService struct {
|
||||||
Storage local.Storage
|
Storage local.Storage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RegisterHandler registers the handler for the various endpoints below /api.
|
||||||
func (msrv *MetricsService) RegisterHandler() {
|
func (msrv *MetricsService) RegisterHandler() {
|
||||||
handler := func(h func(http.ResponseWriter, *http.Request)) http.Handler {
|
handler := func(h func(http.ResponseWriter, *http.Request)) http.Handler {
|
||||||
return http_utils.CompressionHandler{
|
return httputils.CompressionHandler{
|
||||||
Handler: http.HandlerFunc(h),
|
Handler: http.HandlerFunc(h),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ import (
|
||||||
"github.com/prometheus/prometheus/rules"
|
"github.com/prometheus/prometheus/rules"
|
||||||
"github.com/prometheus/prometheus/rules/ast"
|
"github.com/prometheus/prometheus/rules/ast"
|
||||||
"github.com/prometheus/prometheus/stats"
|
"github.com/prometheus/prometheus/stats"
|
||||||
"github.com/prometheus/prometheus/web/http_utils"
|
"github.com/prometheus/prometheus/web/httputils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Enables cross-site script calls.
|
// Enables cross-site script calls.
|
||||||
|
@ -40,10 +40,11 @@ func setAccessControlHeaders(w http.ResponseWriter) {
|
||||||
w.Header().Set("Access-Control-Expose-Headers", "Date")
|
w.Header().Set("Access-Control-Expose-Headers", "Date")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Query handles the /api/query endpoint.
|
||||||
func (serv MetricsService) Query(w http.ResponseWriter, r *http.Request) {
|
func (serv MetricsService) Query(w http.ResponseWriter, r *http.Request) {
|
||||||
setAccessControlHeaders(w)
|
setAccessControlHeaders(w)
|
||||||
|
|
||||||
params := http_utils.GetQueryParams(r)
|
params := httputils.GetQueryParams(r)
|
||||||
expr := params.Get("expr")
|
expr := params.Get("expr")
|
||||||
asText := params.Get("asText")
|
asText := params.Get("asText")
|
||||||
|
|
||||||
|
@ -71,11 +72,12 @@ func (serv MetricsService) Query(w http.ResponseWriter, r *http.Request) {
|
||||||
fmt.Fprint(w, result)
|
fmt.Fprint(w, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// QueryRange handles the /api/query_range endpoint.
|
||||||
func (serv MetricsService) QueryRange(w http.ResponseWriter, r *http.Request) {
|
func (serv MetricsService) QueryRange(w http.ResponseWriter, r *http.Request) {
|
||||||
setAccessControlHeaders(w)
|
setAccessControlHeaders(w)
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
|
||||||
params := http_utils.GetQueryParams(r)
|
params := httputils.GetQueryParams(r)
|
||||||
expr := params.Get("expr")
|
expr := params.Get("expr")
|
||||||
|
|
||||||
// Input times and durations are in seconds and get converted to nanoseconds.
|
// Input times and durations are in seconds and get converted to nanoseconds.
|
||||||
|
@ -93,7 +95,7 @@ func (serv MetricsService) QueryRange(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if exprNode.Type() != ast.VECTOR {
|
if exprNode.Type() != ast.VECTOR {
|
||||||
fmt.Fprint(w, ast.ErrorToJSON(errors.New("Expression does not evaluate to vector type")))
|
fmt.Fprint(w, ast.ErrorToJSON(errors.New("expression does not evaluate to vector type")))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,7 +114,7 @@ func (serv MetricsService) QueryRange(w http.ResponseWriter, r *http.Request) {
|
||||||
// For safety, limit the number of returned points per timeseries.
|
// For safety, limit the number of returned points per timeseries.
|
||||||
// This is sufficient for 60s resolution for a week or 1h resolution for a year.
|
// This is sufficient for 60s resolution for a week or 1h resolution for a year.
|
||||||
if duration/step > 11000 {
|
if duration/step > 11000 {
|
||||||
fmt.Fprint(w, ast.ErrorToJSON(errors.New("Exceeded maximum resolution of 11,000 points per timeseries. Try decreasing the query resolution (?step=XX).")))
|
fmt.Fprint(w, ast.ErrorToJSON(errors.New("exceeded maximum resolution of 11,000 points per timeseries. Try decreasing the query resolution (?step=XX)")))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,7 +141,7 @@ func (serv MetricsService) QueryRange(w http.ResponseWriter, r *http.Request) {
|
||||||
sort.Sort(matrix)
|
sort.Sort(matrix)
|
||||||
sortTimer.Stop()
|
sortTimer.Stop()
|
||||||
|
|
||||||
jsonTimer := queryStats.GetTimer(stats.JsonEncodeTime).Start()
|
jsonTimer := queryStats.GetTimer(stats.JSONEncodeTime).Start()
|
||||||
result := ast.TypedValueToJSON(matrix, "matrix")
|
result := ast.TypedValueToJSON(matrix, "matrix")
|
||||||
jsonTimer.Stop()
|
jsonTimer.Stop()
|
||||||
|
|
||||||
|
@ -147,6 +149,7 @@ func (serv MetricsService) QueryRange(w http.ResponseWriter, r *http.Request) {
|
||||||
fmt.Fprint(w, result)
|
fmt.Fprint(w, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Metrics handles the /api/metrics endpoint.
|
||||||
func (serv MetricsService) Metrics(w http.ResponseWriter, r *http.Request) {
|
func (serv MetricsService) Metrics(w http.ResponseWriter, r *http.Request) {
|
||||||
setAccessControlHeaders(w)
|
setAccessControlHeaders(w)
|
||||||
|
|
||||||
|
|
|
@ -20,16 +20,19 @@ import (
|
||||||
clientmodel "github.com/prometheus/client_golang/model"
|
clientmodel "github.com/prometheus/client_golang/model"
|
||||||
|
|
||||||
"github.com/prometheus/prometheus/retrieval"
|
"github.com/prometheus/prometheus/retrieval"
|
||||||
"github.com/prometheus/prometheus/web/http_utils"
|
"github.com/prometheus/prometheus/web/httputils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TargetGroup bundles endpoints and base labels with appropriate JSON
|
||||||
|
// annotations.
|
||||||
type TargetGroup struct {
|
type TargetGroup struct {
|
||||||
Endpoints []string `json:"endpoints"`
|
Endpoints []string `json:"endpoints"`
|
||||||
BaseLabels map[string]string `json:"baseLabels"`
|
BaseLabels map[string]string `json:"baseLabels"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetTargets handles the /api/targets endpoint.
|
||||||
func (serv MetricsService) SetTargets(w http.ResponseWriter, r *http.Request) {
|
func (serv MetricsService) SetTargets(w http.ResponseWriter, r *http.Request) {
|
||||||
params := http_utils.GetQueryParams(r)
|
params := httputils.GetQueryParams(r)
|
||||||
jobName := params.Get("job")
|
jobName := params.Get("job")
|
||||||
|
|
||||||
decoder := json.NewDecoder(r.Body)
|
decoder := json.NewDecoder(r.Body)
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Sub-directories for templates and static content.
|
||||||
const (
|
const (
|
||||||
TemplateFiles = "templates"
|
TemplateFiles = "templates"
|
||||||
StaticFiles = "static"
|
StaticFiles = "static"
|
||||||
|
@ -22,10 +23,11 @@ var mimeMap = map[string]string{
|
||||||
"descriptor": "application/vnd.google.protobuf;proto=google.protobuf.FileDescriptorSet",
|
"descriptor": "application/vnd.google.protobuf;proto=google.protobuf.FileDescriptorSet",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetFile retrieves the content of an embedded file.
|
||||||
func GetFile(bucket string, name string) ([]byte, error) {
|
func GetFile(bucket string, name string) ([]byte, error) {
|
||||||
blob, ok := files[bucket][name]
|
blob, ok := files[bucket][name]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("Could not find %s/%s. Missing/updated files.go?", bucket, name)
|
return nil, fmt.Errorf("could not find %s/%s (missing or updated files.go?)", bucket, name)
|
||||||
}
|
}
|
||||||
reader := bytes.NewReader(blob)
|
reader := bytes.NewReader(blob)
|
||||||
gz, err := gzip.NewReader(reader)
|
gz, err := gzip.NewReader(reader)
|
||||||
|
@ -40,6 +42,7 @@ func GetFile(bucket string, name string) ([]byte, error) {
|
||||||
return b.Bytes(), nil
|
return b.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handler implements http.Handler.
|
||||||
type Handler struct{}
|
type Handler struct{}
|
||||||
|
|
||||||
func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
|
@ -31,6 +31,7 @@ var (
|
||||||
consoleLibrariesPath = flag.String("web.console.libraries", "console_libraries", "Path to the console library directory.")
|
consoleLibrariesPath = flag.String("web.console.libraries", "console_libraries", "Path to the console library directory.")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ConsolesHandler implements http.Handler.
|
||||||
type ConsolesHandler struct {
|
type ConsolesHandler struct {
|
||||||
Storage local.Storage
|
Storage local.Storage
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package http_utils
|
package httputils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"compress/gzip"
|
"compress/gzip"
|
||||||
|
@ -78,13 +78,13 @@ func newCompressedResponseWriter(writer http.ResponseWriter, req *http.Request)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wrapper around http.Handler which adds suitable response compression based
|
// CompressionHandler is a wrapper around http.Handler which adds suitable
|
||||||
// on the client's Accept-Encoding headers.
|
// response compression based on the client's Accept-Encoding headers.
|
||||||
type CompressionHandler struct {
|
type CompressionHandler struct {
|
||||||
Handler http.Handler
|
Handler http.Handler
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adds compression to the original http.Handler's ServeHTTP() method.
|
// ServeHTTP adds compression to the original http.Handler's ServeHTTP() method.
|
||||||
func (c CompressionHandler) ServeHTTP(writer http.ResponseWriter, req *http.Request) {
|
func (c CompressionHandler) ServeHTTP(writer http.ResponseWriter, req *http.Request) {
|
||||||
compWriter := newCompressedResponseWriter(writer, req)
|
compWriter := newCompressedResponseWriter(writer, req)
|
||||||
c.Handler.ServeHTTP(compWriter, req)
|
c.Handler.ServeHTTP(compWriter, req)
|
|
@ -11,13 +11,14 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package http_utils
|
package httputils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// GetQueryParams calls r.ParseForm and returns r.Form.
|
||||||
func GetQueryParams(r *http.Request) url.Values {
|
func GetQueryParams(r *http.Request) url.Values {
|
||||||
r.ParseForm()
|
r.ParseForm()
|
||||||
return r.Form
|
return r.Form
|
|
@ -22,6 +22,7 @@ import (
|
||||||
"github.com/prometheus/prometheus/rules/manager"
|
"github.com/prometheus/prometheus/rules/manager"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// PrometheusStatusHandler implements http.Handler.
|
||||||
type PrometheusStatusHandler struct {
|
type PrometheusStatusHandler struct {
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
|
|
||||||
|
|
35
web/web.go
35
web/web.go
|
@ -20,7 +20,6 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
_ "net/http/pprof"
|
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -41,6 +40,7 @@ var (
|
||||||
enableQuit = flag.Bool("web.enable-remote-shutdown", false, "Enable remote service shutdown.")
|
enableQuit = flag.Bool("web.enable-remote-shutdown", false, "Enable remote service shutdown.")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// WebService handles the HTTP endpoints with the exception of /api.
|
||||||
type WebService struct {
|
type WebService struct {
|
||||||
StatusHandler *PrometheusStatusHandler
|
StatusHandler *PrometheusStatusHandler
|
||||||
MetricsHandler *api.MetricsService
|
MetricsHandler *api.MetricsService
|
||||||
|
@ -50,19 +50,20 @@ type WebService struct {
|
||||||
QuitDelegate func()
|
QuitDelegate func()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w WebService) ServeForever() error {
|
// ServeForever serves the HTTP endpoints and only returns upon errors.
|
||||||
|
func (ws WebService) ServeForever() error {
|
||||||
http.Handle("/favicon.ico", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
http.Handle("/favicon.ico", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
http.Error(w, "", 404)
|
http.Error(w, "", 404)
|
||||||
}))
|
}))
|
||||||
|
|
||||||
http.Handle("/", prometheus.InstrumentHandler(
|
http.Handle("/", prometheus.InstrumentHandler(
|
||||||
"/", w.StatusHandler,
|
"/", ws.StatusHandler,
|
||||||
))
|
))
|
||||||
http.Handle("/alerts", prometheus.InstrumentHandler(
|
http.Handle("/alerts", prometheus.InstrumentHandler(
|
||||||
"/alerts", w.AlertsHandler,
|
"/alerts", ws.AlertsHandler,
|
||||||
))
|
))
|
||||||
http.Handle("/consoles/", prometheus.InstrumentHandler(
|
http.Handle("/consoles/", prometheus.InstrumentHandler(
|
||||||
"/consoles/", http.StripPrefix("/consoles/", w.ConsolesHandler),
|
"/consoles/", http.StripPrefix("/consoles/", ws.ConsolesHandler),
|
||||||
))
|
))
|
||||||
http.Handle("/graph", prometheus.InstrumentHandler(
|
http.Handle("/graph", prometheus.InstrumentHandler(
|
||||||
"/graph", http.HandlerFunc(graphHandler),
|
"/graph", http.HandlerFunc(graphHandler),
|
||||||
|
@ -71,7 +72,7 @@ func (w WebService) ServeForever() error {
|
||||||
"/heap", http.HandlerFunc(dumpHeap),
|
"/heap", http.HandlerFunc(dumpHeap),
|
||||||
))
|
))
|
||||||
|
|
||||||
w.MetricsHandler.RegisterHandler()
|
ws.MetricsHandler.RegisterHandler()
|
||||||
http.Handle("/metrics", prometheus.Handler())
|
http.Handle("/metrics", prometheus.Handler())
|
||||||
if *useLocalAssets {
|
if *useLocalAssets {
|
||||||
http.Handle("/static/", prometheus.InstrumentHandler(
|
http.Handle("/static/", prometheus.InstrumentHandler(
|
||||||
|
@ -90,7 +91,7 @@ func (w WebService) ServeForever() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if *enableQuit {
|
if *enableQuit {
|
||||||
http.Handle("/-/quit", http.HandlerFunc(w.quitHandler))
|
http.Handle("/-/quit", http.HandlerFunc(ws.quitHandler))
|
||||||
}
|
}
|
||||||
|
|
||||||
glog.Info("listening on ", *listenAddress)
|
glog.Info("listening on ", *listenAddress)
|
||||||
|
@ -98,7 +99,7 @@ func (w WebService) ServeForever() error {
|
||||||
return http.ListenAndServe(*listenAddress, nil)
|
return http.ListenAndServe(*listenAddress, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s WebService) quitHandler(w http.ResponseWriter, r *http.Request) {
|
func (ws WebService) quitHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != "POST" {
|
if r.Method != "POST" {
|
||||||
w.Header().Add("Allow", "POST")
|
w.Header().Add("Allow", "POST")
|
||||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||||
|
@ -107,7 +108,7 @@ func (s WebService) quitHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
fmt.Fprintf(w, "Requesting termination... Goodbye!")
|
fmt.Fprintf(w, "Requesting termination... Goodbye!")
|
||||||
|
|
||||||
s.QuitDelegate()
|
ws.QuitDelegate()
|
||||||
}
|
}
|
||||||
|
|
||||||
func getTemplateFile(name string) (string, error) {
|
func getTemplateFile(name string) (string, error) {
|
||||||
|
@ -118,14 +119,13 @@ func getTemplateFile(name string) (string, error) {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return string(file), nil
|
return string(file), nil
|
||||||
} else {
|
|
||||||
file, err := blob.GetFile(blob.TemplateFiles, name+".html")
|
|
||||||
if err != nil {
|
|
||||||
glog.Errorf("Could not read %s template: %s", name, err)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return string(file), nil
|
|
||||||
}
|
}
|
||||||
|
file, err := blob.GetFile(blob.TemplateFiles, name+".html")
|
||||||
|
if err != nil {
|
||||||
|
glog.Errorf("Could not read %s template: %s", name, err)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(file), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getConsoles() string {
|
func getConsoles() string {
|
||||||
|
@ -186,7 +186,8 @@ func dumpHeap(w http.ResponseWriter, r *http.Request) {
|
||||||
fmt.Fprintf(w, "Done")
|
fmt.Fprintf(w, "Done")
|
||||||
}
|
}
|
||||||
|
|
||||||
func MustBuildServerUrl() string {
|
// MustBuildServerURL returns the server URL and panics in case an error occurs.
|
||||||
|
func MustBuildServerURL() string {
|
||||||
_, port, err := net.SplitHostPort(*listenAddress)
|
_, port, err := net.SplitHostPort(*listenAddress)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
|
Loading…
Reference in a new issue