mirror of
https://github.com/prometheus/prometheus.git
synced 2025-03-05 20:59:13 -08:00
Merge pull request #70 from prometheus/julius-hud
Cleanups and beginnings of a /status endpoint
This commit is contained in:
commit
db5868f1b3
31
appstate/appstate.go
Normal file
31
appstate/appstate.go
Normal file
|
@ -0,0 +1,31 @@
|
|||
// 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 appstate
|
||||
|
||||
import (
|
||||
"github.com/prometheus/prometheus/config"
|
||||
"github.com/prometheus/prometheus/retrieval"
|
||||
"github.com/prometheus/prometheus/rules"
|
||||
"github.com/prometheus/prometheus/storage/metric"
|
||||
)
|
||||
|
||||
// ApplicationState is an encapsulation of all relevant Prometheus application
|
||||
// runtime state. It enables simpler passing of this state to components that
|
||||
// require it.
|
||||
type ApplicationState struct {
|
||||
Config *config.Config
|
||||
Persistence metric.MetricPersistence
|
||||
RuleManager rules.RuleManager
|
||||
TargetManager retrieval.TargetManager
|
||||
}
|
|
@ -17,6 +17,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"github.com/prometheus/prometheus/model"
|
||||
"github.com/prometheus/prometheus/utility"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
@ -55,7 +56,7 @@ func (config *Config) AddJob(options map[string]string, targets []Targets) error
|
|||
return errors.New("Missing job name")
|
||||
}
|
||||
if len(targets) == 0 {
|
||||
return errors.New(fmt.Sprintf("No targets configured for job '%v'", name))
|
||||
return fmt.Errorf("No targets configured for job '%v'", name)
|
||||
}
|
||||
job := &JobConfig{
|
||||
Targets: tmpJobTargets,
|
||||
|
@ -69,18 +70,18 @@ func (config *Config) AddJob(options map[string]string, targets []Targets) error
|
|||
return nil
|
||||
}
|
||||
|
||||
func (config *GlobalConfig) SetOption(option string, value string) error {
|
||||
func (config *GlobalConfig) SetOption(option string, value string) (err error) {
|
||||
switch option {
|
||||
case "scrape_interval":
|
||||
config.ScrapeInterval = stringToDuration(value)
|
||||
config.ScrapeInterval, err = utility.StringToDuration(value)
|
||||
return nil
|
||||
case "evaluation_interval":
|
||||
config.EvaluationInterval = stringToDuration(value)
|
||||
return nil
|
||||
config.EvaluationInterval, err = utility.StringToDuration(value)
|
||||
return err
|
||||
default:
|
||||
return errors.New(fmt.Sprintf("Unrecognized global configuration option '%v'", option))
|
||||
err = fmt.Errorf("Unrecognized global configuration option '%v'", option)
|
||||
}
|
||||
return nil
|
||||
return
|
||||
}
|
||||
|
||||
func (config *GlobalConfig) SetLabels(labels model.LabelSet) {
|
||||
|
@ -95,18 +96,16 @@ func (config *GlobalConfig) AddRuleFiles(ruleFiles []string) {
|
|||
}
|
||||
}
|
||||
|
||||
func (job *JobConfig) SetOption(option string, value string) error {
|
||||
func (job *JobConfig) SetOption(option string, value string) (err error) {
|
||||
switch option {
|
||||
case "name":
|
||||
job.Name = value
|
||||
return nil
|
||||
case "scrape_interval":
|
||||
job.ScrapeInterval = stringToDuration(value)
|
||||
return nil
|
||||
job.ScrapeInterval, err = utility.StringToDuration(value)
|
||||
default:
|
||||
return errors.New(fmt.Sprintf("Unrecognized job configuration option '%v'", option))
|
||||
err = fmt.Errorf("Unrecognized job configuration option '%v'", option)
|
||||
}
|
||||
return nil
|
||||
return
|
||||
}
|
||||
|
||||
func (job *JobConfig) AddTargets(endpoints []string, labels model.LabelSet) {
|
||||
|
|
86
config/config_test.go
Normal file
86
config/config_test.go
Normal file
|
@ -0,0 +1,86 @@
|
|||
// 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 config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var fixturesPath = "fixtures"
|
||||
|
||||
var configTests = []struct {
|
||||
inputFile string
|
||||
printedFile string
|
||||
shouldFail bool
|
||||
errContains string
|
||||
}{
|
||||
{
|
||||
inputFile: "minimal.conf",
|
||||
printedFile: "minimal.conf.printed",
|
||||
}, {
|
||||
inputFile: "sample.conf",
|
||||
printedFile: "sample.conf.printed",
|
||||
}, {
|
||||
// TODO: Options that are not provided should be set to sane defaults or
|
||||
// create errors during config loading (as appropriate). Right now, these
|
||||
// options remain at their zero-values, which is probably not what we want.
|
||||
inputFile: "empty.conf",
|
||||
printedFile: "empty.conf.printed",
|
||||
},
|
||||
// TODO: To enable testing of bad configs, we first need to change config
|
||||
// loading so that it doesn't exit when loading a bad config. Instead, the
|
||||
// configuration error should be passed back all the way to the caller.
|
||||
//
|
||||
//{
|
||||
// inputFile: "bad_job_option.conf",
|
||||
// shouldFail: true,
|
||||
// errContains: "Missing job name",
|
||||
//},
|
||||
}
|
||||
|
||||
func TestConfigs(t *testing.T) {
|
||||
for _, configTest := range configTests {
|
||||
testConfig, err := LoadFromFile(path.Join(fixturesPath, configTest.inputFile))
|
||||
|
||||
if err != nil {
|
||||
if !configTest.shouldFail {
|
||||
t.Errorf("Error parsing config %v: %v", configTest.inputFile, err)
|
||||
} else {
|
||||
if !strings.Contains(err.Error(), configTest.errContains) {
|
||||
t.Errorf("Expected error containing '%v', got: %v", configTest.errContains, err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
printedConfig, err := ioutil.ReadFile(path.Join(fixturesPath, configTest.printedFile))
|
||||
if err != nil {
|
||||
t.Errorf("Error reading config %v: %v", configTest.inputFile, err)
|
||||
continue
|
||||
}
|
||||
expected := string(printedConfig)
|
||||
actual := testConfig.ToString(0)
|
||||
|
||||
if actual != expected {
|
||||
t.Errorf("%v: printed config doesn't match expected output", configTest.inputFile)
|
||||
t.Errorf("Expected:\n%v\n\nActual:\n%v\n", expected, actual)
|
||||
t.Errorf("Writing expected and actual printed configs to /tmp for diffing (see test source for paths)")
|
||||
ioutil.WriteFile(fmt.Sprintf("/tmp/%s.expected", configTest.printedFile), []byte(expected), 0600)
|
||||
ioutil.WriteFile(fmt.Sprintf("/tmp/%s.actual", configTest.printedFile), []byte(actual), 0600)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
8
config/fixtures/empty.conf.printed
Normal file
8
config/fixtures/empty.conf.printed
Normal file
|
@ -0,0 +1,8 @@
|
|||
global {
|
||||
scrape_interval = "0y"
|
||||
evaluation_interval = "0y"
|
||||
rule_files = [
|
||||
|
||||
]
|
||||
}
|
||||
|
20
config/fixtures/minimal.conf.printed
Normal file
20
config/fixtures/minimal.conf.printed
Normal file
|
@ -0,0 +1,20 @@
|
|||
global {
|
||||
scrape_interval = "30s"
|
||||
evaluation_interval = "30s"
|
||||
labels {
|
||||
monitor = "test"
|
||||
}
|
||||
rule_files = [
|
||||
"prometheus.rules"
|
||||
]
|
||||
}
|
||||
|
||||
job {
|
||||
name = "prometheus"
|
||||
scrape_interval = "15s"
|
||||
targets {
|
||||
endpoints = [
|
||||
"http://localhost:9090/metrics.json"
|
||||
]
|
||||
}
|
||||
}
|
49
config/fixtures/sample.conf.printed
Normal file
49
config/fixtures/sample.conf.printed
Normal file
|
@ -0,0 +1,49 @@
|
|||
global {
|
||||
scrape_interval = "30s"
|
||||
evaluation_interval = "30s"
|
||||
labels {
|
||||
monitor = "test"
|
||||
}
|
||||
rule_files = [
|
||||
"prometheus.rules"
|
||||
]
|
||||
}
|
||||
|
||||
job {
|
||||
name = "prometheus"
|
||||
scrape_interval = "15s"
|
||||
targets {
|
||||
endpoints = [
|
||||
"http://localhost:9090/metrics.json"
|
||||
]
|
||||
labels {
|
||||
group = "canary"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
job {
|
||||
name = "random"
|
||||
scrape_interval = "30s"
|
||||
targets {
|
||||
endpoints = [
|
||||
"http://random.com:8080/metrics.json",
|
||||
"http://random.com:8081/metrics.json",
|
||||
"http://random.com:8082/metrics.json",
|
||||
"http://random.com:8083/metrics.json",
|
||||
"http://random.com:8084/metrics.json"
|
||||
]
|
||||
labels {
|
||||
group = "production"
|
||||
}
|
||||
}
|
||||
targets {
|
||||
endpoints = [
|
||||
"http://random.com:8085/metrics.json",
|
||||
"http://random.com:8086/metrics.json"
|
||||
]
|
||||
labels {
|
||||
group = "canary"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -17,9 +17,6 @@ import (
|
|||
"fmt"
|
||||
"github.com/prometheus/prometheus/model"
|
||||
"log"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Unfortunately, more global variables that are needed for parsing.
|
||||
|
@ -30,7 +27,9 @@ var tmpTargetLabels = model.LabelSet{}
|
|||
|
||||
func configError(error string, v ...interface{}) {
|
||||
message := fmt.Sprintf(error, v...)
|
||||
log.Fatal(fmt.Sprintf("Line %v, char %v: %s", yyline, yypos, message))
|
||||
// TODO: Don't just die here. Pass errors back all the way to the caller
|
||||
// instead.
|
||||
log.Fatalf("Line %v, char %v: %s", yyline, yypos, message)
|
||||
}
|
||||
|
||||
func PushJobOption(option string, value string) {
|
||||
|
@ -66,26 +65,3 @@ func PopJob() {
|
|||
tmpJobOptions = map[string]string{}
|
||||
tmpJobTargets = []Targets{}
|
||||
}
|
||||
|
||||
func stringToDuration(durationStr string) time.Duration {
|
||||
durationRE := regexp.MustCompile("^([0-9]+)([ywdhms]+)$")
|
||||
matches := durationRE.FindStringSubmatch(durationStr)
|
||||
if len(matches) != 3 {
|
||||
configError("Not a valid duration string: '%v'", durationStr)
|
||||
}
|
||||
value, _ := strconv.Atoi(matches[1])
|
||||
unit := matches[2]
|
||||
switch unit {
|
||||
case "y":
|
||||
value *= 60 * 60 * 24 * 365
|
||||
case "w":
|
||||
value *= 60 * 60 * 24 * 7
|
||||
case "d":
|
||||
value *= 60 * 60 * 24
|
||||
case "h":
|
||||
value *= 60 * 60
|
||||
case "m":
|
||||
value *= 60
|
||||
}
|
||||
return time.Duration(value) * time.Second
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ import (
|
|||
var yylval *yySymType // For storing extra token information, like the contents of a string.
|
||||
var yyline int // Line number within the current file or buffer.
|
||||
var yypos int // Character position within the current line.
|
||||
var parsedConfig = New() // Temporary variable for storing the parsed configuration.
|
||||
var parsedConfig *Config // Temporary variable for storing the parsed configuration.
|
||||
|
||||
type ConfigLexer struct {
|
||||
errors []string
|
||||
|
@ -45,6 +45,7 @@ func (lexer *ConfigLexer) Error(errorStr string) {
|
|||
}
|
||||
|
||||
func LoadFromReader(configReader io.Reader) (*Config, error) {
|
||||
parsedConfig = New()
|
||||
yyin = configReader
|
||||
yypos = 1
|
||||
yyline = 1
|
||||
|
|
|
@ -16,6 +16,7 @@ package config
|
|||
import (
|
||||
"fmt"
|
||||
"github.com/prometheus/prometheus/model"
|
||||
"github.com/prometheus/prometheus/utility"
|
||||
"strings"
|
||||
)
|
||||
|
||||
|
@ -33,45 +34,54 @@ func (config *Config) ToString(indent int) string {
|
|||
for _, job := range config.Jobs {
|
||||
jobs = append(jobs, job.ToString(indent))
|
||||
}
|
||||
return indentStr(indent, "%v\n%v\n", global, strings.Join(jobs, "\n"))
|
||||
return indentStr(indent, "%v\n%v", global, strings.Join(jobs, "\n"))
|
||||
}
|
||||
|
||||
func labelsToString(indent int, labels model.LabelSet) string {
|
||||
str := indentStr(indent, "labels {\n")
|
||||
labelStrings := []string{}
|
||||
for label, value := range labels {
|
||||
str += indentStr(indent+1, "%v = \"%v\",\n", label, value)
|
||||
labelStrings = append(labelStrings, indentStr(indent+1, "%v = \"%v\"", label, value))
|
||||
}
|
||||
str += strings.Join(labelStrings, ",\n") + "\n"
|
||||
str += indentStr(indent, "}\n")
|
||||
return str
|
||||
}
|
||||
|
||||
func stringListToString(indent int, list []string) string {
|
||||
listString := []string{}
|
||||
for _, item := range list {
|
||||
listString = append(listString, indentStr(indent, "\"%v\"", item))
|
||||
}
|
||||
return strings.Join(listString, ",\n") + "\n"
|
||||
}
|
||||
|
||||
func (global *GlobalConfig) ToString(indent int) string {
|
||||
str := indentStr(indent, "global {\n")
|
||||
str += indentStr(indent+1, "scrape_interval = \"%vs\"\n", global.ScrapeInterval)
|
||||
str += indentStr(indent+1, "evaluation_interval = \"%vs\"\n", global.EvaluationInterval)
|
||||
str += labelsToString(indent+1, global.Labels)
|
||||
str += indentStr(indent, "}\n")
|
||||
str += indentStr(indent+1, "rule_files = [\n")
|
||||
for _, ruleFile := range global.RuleFiles {
|
||||
str += indentStr(indent+2, "\"%v\",\n", ruleFile)
|
||||
str += indentStr(indent+1, "scrape_interval = \"%s\"\n", utility.DurationToString(global.ScrapeInterval))
|
||||
str += indentStr(indent+1, "evaluation_interval = \"%s\"\n", utility.DurationToString(global.EvaluationInterval))
|
||||
if len(global.Labels) > 0 {
|
||||
str += labelsToString(indent+1, global.Labels)
|
||||
}
|
||||
str += indentStr(indent+1, "rule_files = [\n")
|
||||
str += stringListToString(indent+2, global.RuleFiles)
|
||||
str += indentStr(indent+1, "]\n")
|
||||
str += indentStr(indent, "}\n")
|
||||
return str
|
||||
}
|
||||
|
||||
func (job *JobConfig) ToString(indent int) string {
|
||||
str := indentStr(indent, "job {\n")
|
||||
str += indentStr(indent+1, "job {\n")
|
||||
str += indentStr(indent+1, "name = \"%v\"\n", job.Name)
|
||||
str += indentStr(indent+1, "scrape_interval = \"%vs\"\n", job.ScrapeInterval)
|
||||
str += indentStr(indent+1, "scrape_interval = \"%s\"\n", utility.DurationToString(job.ScrapeInterval))
|
||||
for _, targets := range job.Targets {
|
||||
str += indentStr(indent+1, "targets {\n")
|
||||
str += indentStr(indent+2, "endpoints = [\n")
|
||||
for _, endpoint := range targets.Endpoints {
|
||||
str += indentStr(indent+3, "\"%v\",\n", endpoint)
|
||||
}
|
||||
str += stringListToString(indent+3, targets.Endpoints)
|
||||
str += indentStr(indent+2, "]\n")
|
||||
str += labelsToString(indent+2, targets.Labels)
|
||||
if len(targets.Labels) > 0 {
|
||||
str += labelsToString(indent+2, targets.Labels)
|
||||
}
|
||||
str += indentStr(indent+1, "}\n")
|
||||
}
|
||||
str += indentStr(indent, "}\n")
|
||||
|
|
10
main.go
10
main.go
|
@ -15,6 +15,7 @@ package main
|
|||
|
||||
import (
|
||||
"flag"
|
||||
"github.com/prometheus/prometheus/appstate"
|
||||
"github.com/prometheus/prometheus/config"
|
||||
"github.com/prometheus/prometheus/retrieval"
|
||||
"github.com/prometheus/prometheus/retrieval/format"
|
||||
|
@ -73,7 +74,14 @@ func main() {
|
|||
log.Fatalf("Error loading rule files: %v", err)
|
||||
}
|
||||
|
||||
web.StartServing(persistence)
|
||||
appState := &appstate.ApplicationState{
|
||||
Config: conf,
|
||||
Persistence: persistence,
|
||||
RuleManager: ruleManager,
|
||||
TargetManager: targetManager,
|
||||
}
|
||||
|
||||
web.StartServing(appState)
|
||||
|
||||
for {
|
||||
select {
|
||||
|
|
|
@ -16,6 +16,7 @@ package ast
|
|||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/prometheus/prometheus/utility"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -65,29 +66,6 @@ func exprTypeToString(exprType ExprType) string {
|
|||
return exprTypeMap[exprType]
|
||||
}
|
||||
|
||||
func durationToString(duration time.Duration) string {
|
||||
seconds := int64(duration / time.Second)
|
||||
factors := map[string]int64{
|
||||
"y": 60 * 60 * 24 * 365,
|
||||
"d": 60 * 60 * 24,
|
||||
"h": 60 * 60,
|
||||
"m": 60,
|
||||
"s": 1,
|
||||
}
|
||||
unit := "s"
|
||||
switch int64(0) {
|
||||
case seconds % factors["y"]:
|
||||
unit = "y"
|
||||
case seconds % factors["d"]:
|
||||
unit = "d"
|
||||
case seconds % factors["h"]:
|
||||
unit = "h"
|
||||
case seconds % factors["m"]:
|
||||
unit = "m"
|
||||
}
|
||||
return fmt.Sprintf("%v%v", seconds/factors[unit], unit)
|
||||
}
|
||||
|
||||
func (vector Vector) ToString() string {
|
||||
metricStrings := []string{}
|
||||
for _, sample := range vector {
|
||||
|
@ -228,7 +206,7 @@ func (node *VectorLiteral) ToString() string {
|
|||
|
||||
func (node *MatrixLiteral) ToString() string {
|
||||
vectorString := (&VectorLiteral{labels: node.labels}).ToString()
|
||||
intervalString := fmt.Sprintf("['%v']", durationToString(node.interval))
|
||||
intervalString := fmt.Sprintf("['%v']", utility.DurationToString(node.interval))
|
||||
return vectorString + intervalString
|
||||
}
|
||||
|
||||
|
|
|
@ -14,48 +14,15 @@
|
|||
package rules
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/prometheus/prometheus/model"
|
||||
"github.com/prometheus/prometheus/rules/ast"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"time"
|
||||
"github.com/prometheus/prometheus/utility"
|
||||
)
|
||||
|
||||
func rulesError(error string, v ...interface{}) error {
|
||||
return errors.New(fmt.Sprintf(error, v...))
|
||||
}
|
||||
|
||||
// TODO move to common place, currently duplicated in config/
|
||||
func stringToDuration(durationStr string) (time.Duration, error) {
|
||||
durationRE := regexp.MustCompile("^([0-9]+)([ywdhms]+)$")
|
||||
matches := durationRE.FindStringSubmatch(durationStr)
|
||||
if len(matches) != 3 {
|
||||
return 0, rulesError("Not a valid duration string: '%v'", durationStr)
|
||||
}
|
||||
value, _ := strconv.Atoi(matches[1])
|
||||
unit := matches[2]
|
||||
switch unit {
|
||||
case "y":
|
||||
value *= 60 * 60 * 24 * 365
|
||||
case "w":
|
||||
value *= 60 * 60 * 24
|
||||
case "d":
|
||||
value *= 60 * 60 * 24
|
||||
case "h":
|
||||
value *= 60 * 60
|
||||
case "m":
|
||||
value *= 60
|
||||
case "s":
|
||||
value *= 1
|
||||
}
|
||||
return time.Duration(value) * time.Second, nil
|
||||
}
|
||||
|
||||
func CreateRule(name string, labels model.LabelSet, root ast.Node, permanent bool) (*Rule, error) {
|
||||
if root.Type() != ast.VECTOR {
|
||||
return nil, rulesError("Rule %v does not evaluate to vector type", name)
|
||||
return nil, fmt.Errorf("Rule %v does not evaluate to vector type", name)
|
||||
}
|
||||
return NewRule(name, labels, root.(ast.VectorNode), permanent), nil
|
||||
}
|
||||
|
@ -63,18 +30,18 @@ func CreateRule(name string, labels model.LabelSet, root ast.Node, permanent boo
|
|||
func NewFunctionCall(name string, args []ast.Node) (ast.Node, error) {
|
||||
function, err := ast.GetFunction(name)
|
||||
if err != nil {
|
||||
return nil, rulesError("Unknown function \"%v\"", name)
|
||||
return nil, fmt.Errorf("Unknown function \"%v\"", name)
|
||||
}
|
||||
functionCall, err := ast.NewFunctionCall(function, args)
|
||||
if err != nil {
|
||||
return nil, rulesError(err.Error())
|
||||
return nil, fmt.Errorf(err.Error())
|
||||
}
|
||||
return functionCall, nil
|
||||
}
|
||||
|
||||
func NewVectorAggregation(aggrTypeStr string, vector ast.Node, groupBy []model.LabelName) (*ast.VectorAggregation, error) {
|
||||
if vector.Type() != ast.VECTOR {
|
||||
return nil, rulesError("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{
|
||||
"SUM": ast.SUM,
|
||||
|
@ -84,7 +51,7 @@ func NewVectorAggregation(aggrTypeStr string, vector ast.Node, groupBy []model.L
|
|||
}
|
||||
aggrType, ok := aggrTypes[aggrTypeStr]
|
||||
if !ok {
|
||||
return nil, rulesError("Unknown aggregation type '%v'", aggrTypeStr)
|
||||
return nil, fmt.Errorf("Unknown aggregation type '%v'", aggrTypeStr)
|
||||
}
|
||||
return ast.NewVectorAggregation(aggrType, vector.(ast.VectorNode), groupBy), nil
|
||||
}
|
||||
|
@ -107,11 +74,11 @@ func NewArithExpr(opTypeStr string, lhs ast.Node, rhs ast.Node) (ast.Node, error
|
|||
}
|
||||
opType, ok := opTypes[opTypeStr]
|
||||
if !ok {
|
||||
return nil, rulesError("Invalid binary operator \"%v\"", opTypeStr)
|
||||
return nil, fmt.Errorf("Invalid binary operator \"%v\"", opTypeStr)
|
||||
}
|
||||
expr, err := ast.NewArithExpr(opType, lhs, rhs)
|
||||
if err != nil {
|
||||
return nil, rulesError(err.Error())
|
||||
return nil, fmt.Errorf(err.Error())
|
||||
}
|
||||
return expr, nil
|
||||
}
|
||||
|
@ -123,9 +90,9 @@ func NewMatrix(vector ast.Node, intervalStr string) (ast.MatrixNode, error) {
|
|||
break
|
||||
}
|
||||
default:
|
||||
return nil, rulesError("Intervals are currently only supported for vector literals.")
|
||||
return nil, fmt.Errorf("Intervals are currently only supported for vector literals.")
|
||||
}
|
||||
interval, err := stringToDuration(intervalStr)
|
||||
interval, err := utility.StringToDuration(intervalStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -48,7 +48,9 @@ AVG|SUM|MAX|MIN { yylval.str = yytext; return AGGR_OP }
|
|||
{L}({L}|{D})+ { yylval.str = yytext; return IDENTIFIER }
|
||||
|
||||
\-?{D}+(\.{D}*)? { num, err := strconv.ParseFloat(yytext, 32);
|
||||
if (err != nil) { rulesError("Invalid float %v", yytext) }
|
||||
if (err != nil && err.(*strconv.NumError).Err == strconv.ErrSyntax) {
|
||||
panic("Invalid float")
|
||||
}
|
||||
yylval.num = model.SampleValue(num)
|
||||
return NUMBER }
|
||||
|
||||
|
|
|
@ -436,8 +436,8 @@ var yyrules []yyrule = []yyrule{{regexp.MustCompile("[^\\n]"), nil, []yystartcon
|
|||
}()
|
||||
{
|
||||
num, err := strconv.ParseFloat(yytext, 32)
|
||||
if err != nil {
|
||||
rulesError("Invalid float %v", yytext)
|
||||
if err != nil && err.(*strconv.NumError).Err == strconv.ErrSyntax {
|
||||
panic("Invalid float")
|
||||
}
|
||||
yylval.num = model.SampleValue(num)
|
||||
return yyactionreturn{NUMBER, yyRT_USER_RETURN}
|
||||
|
|
74
utility/strconv.go
Normal file
74
utility/strconv.go
Normal file
|
@ -0,0 +1,74 @@
|
|||
// 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"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
var durationRE = regexp.MustCompile("^([0-9]+)([ywdhms]+)$")
|
||||
|
||||
func DurationToString(duration time.Duration) string {
|
||||
seconds := int64(duration / time.Second)
|
||||
factors := map[string]int64{
|
||||
"y": 60 * 60 * 24 * 365,
|
||||
"d": 60 * 60 * 24,
|
||||
"h": 60 * 60,
|
||||
"m": 60,
|
||||
"s": 1,
|
||||
}
|
||||
unit := "s"
|
||||
switch int64(0) {
|
||||
case seconds % factors["y"]:
|
||||
unit = "y"
|
||||
case seconds % factors["d"]:
|
||||
unit = "d"
|
||||
case seconds % factors["h"]:
|
||||
unit = "h"
|
||||
case seconds % factors["m"]:
|
||||
unit = "m"
|
||||
}
|
||||
return fmt.Sprintf("%v%v", seconds/factors[unit], unit)
|
||||
}
|
||||
|
||||
func StringToDuration(durationStr string) (duration time.Duration, err error) {
|
||||
matches := durationRE.FindStringSubmatch(durationStr)
|
||||
if len(matches) != 3 {
|
||||
err = fmt.Errorf("Not a valid duration string: '%v'", durationStr)
|
||||
return
|
||||
}
|
||||
durationSeconds, _ := strconv.Atoi(matches[1])
|
||||
duration = time.Duration(durationSeconds) * time.Second
|
||||
unit := matches[2]
|
||||
switch unit {
|
||||
case "y":
|
||||
duration *= 60 * 60 * 24 * 365
|
||||
case "w":
|
||||
duration *= 60 * 60 * 24 * 7
|
||||
case "d":
|
||||
duration *= 60 * 60 * 24
|
||||
case "h":
|
||||
duration *= 60 * 60
|
||||
case "m":
|
||||
duration *= 60
|
||||
case "s":
|
||||
duration *= 1
|
||||
default:
|
||||
panic("Invalid time unit in duration string.")
|
||||
}
|
||||
return
|
||||
}
|
|
@ -17,8 +17,8 @@ type MetricsService struct {
|
|||
time utility.Time
|
||||
}
|
||||
|
||||
func NewMetricsService(p metric.MetricPersistence) *MetricsService {
|
||||
func NewMetricsService(persistence metric.MetricPersistence) *MetricsService {
|
||||
return &MetricsService{
|
||||
persistence: p,
|
||||
persistence: persistence,
|
||||
}
|
||||
}
|
||||
|
|
23
web/static/css/graph.css
Normal file
23
web/static/css/graph.css
Normal file
|
@ -0,0 +1,23 @@
|
|||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.graph_container {
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
.graph {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
svg {
|
||||
border: 1px solid #aaa;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.legend {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
margin: 0 0 0 0px;
|
||||
background-
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
body {
|
||||
margin: 0;
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
|
@ -7,26 +6,6 @@ body {
|
|||
background-color: #eee;
|
||||
}
|
||||
|
||||
.graph_container {
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
.graph {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
svg {
|
||||
border: 1px solid #aaa;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.legend {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
margin: 0 0 0 0px;
|
||||
background-
|
||||
}
|
||||
|
||||
input:not([type=submit]):not([type=file]):not([type=button]) {
|
||||
border: 1px solid #aaa;
|
||||
-webkit-border-radius: 3px;
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
|
||||
<script src="//ajax.googleapis.com/ajax/libs/jqueryui/1.8.15/jquery-ui.min.js"></script>
|
||||
<link type="text/css" rel="stylesheet" href="css/prometheus.css">
|
||||
<link type="text/css" rel="stylesheet" href="css/graph.css">
|
||||
|
||||
<!-- copy all these CSSen/JSen to our own location -->
|
||||
<link type="text/css" rel="stylesheet" href="http://code.shutterstock.com/rickshaw/rickshaw.min.css">
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<title>Prometheus Expression Browser</title>
|
||||
<link type="text/css" rel="stylesheet" href="css/prometheus.css">
|
||||
<link type="text/css" rel="stylesheet" href="css/graph.css">
|
||||
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
|
||||
<script src="js/exprBrowser.js"></script>
|
||||
</head>
|
||||
|
|
42
web/status.go
Normal file
42
web/status.go
Normal file
|
@ -0,0 +1,42 @@
|
|||
// 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 web
|
||||
|
||||
import (
|
||||
"github.com/prometheus/prometheus/appstate"
|
||||
"html/template"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type PrometheusStatus struct {
|
||||
Config string
|
||||
Rules string
|
||||
Status string
|
||||
Targets string
|
||||
}
|
||||
|
||||
type StatusHandler struct {
|
||||
appState *appstate.ApplicationState
|
||||
}
|
||||
|
||||
func (h *StatusHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
status := &PrometheusStatus{
|
||||
Config: h.appState.Config.ToString(0),
|
||||
Rules: "TODO: list rules here",
|
||||
Status: "TODO: add status information here",
|
||||
Targets: "TODO: list targets here",
|
||||
}
|
||||
t, _ := template.ParseFiles("web/templates/status.html")
|
||||
t.Execute(w, status)
|
||||
}
|
33
web/templates/status.html
Normal file
33
web/templates/status.html
Normal file
|
@ -0,0 +1,33 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<title>Prometheus Status</title>
|
||||
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
|
||||
<link type="text/css" rel="stylesheet" href="static/css/prometheus.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h2>Status</h2>
|
||||
<div class="grouping_box">
|
||||
{{.Status}}
|
||||
</div>
|
||||
|
||||
<h2>Configuration</h2>
|
||||
<div class="grouping_box">
|
||||
<pre>
|
||||
{{.Config}}
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
<h2>Rules</h2>
|
||||
<div class="grouping_box">
|
||||
{{.Rules}}
|
||||
</div>
|
||||
|
||||
<h2>Targets</h2>
|
||||
<div class="grouping_box">
|
||||
{{.Targets}}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
12
web/web.go
12
web/web.go
|
@ -17,7 +17,7 @@ import (
|
|||
"code.google.com/p/gorest"
|
||||
"flag"
|
||||
"github.com/prometheus/client_golang"
|
||||
"github.com/prometheus/prometheus/storage/metric"
|
||||
"github.com/prometheus/prometheus/appstate"
|
||||
"github.com/prometheus/prometheus/web/api"
|
||||
"net/http"
|
||||
_ "net/http/pprof"
|
||||
|
@ -28,11 +28,13 @@ var (
|
|||
listenAddress = flag.String("listenAddress", ":9090", "Address to listen on for web interface.")
|
||||
)
|
||||
|
||||
func StartServing(persistence metric.MetricPersistence) {
|
||||
gorest.RegisterService(api.NewMetricsService(persistence))
|
||||
func StartServing(appState *appstate.ApplicationState) {
|
||||
gorest.RegisterService(api.NewMetricsService(appState.Persistence))
|
||||
exporter := registry.DefaultRegistry.YieldExporter()
|
||||
|
||||
http.Handle("/", gorest.Handle())
|
||||
http.Handle("/metrics.json", registry.DefaultHandler)
|
||||
http.Handle("/status", &StatusHandler{appState: appState})
|
||||
http.Handle("/api/", gorest.Handle())
|
||||
http.Handle("/metrics.json", exporter)
|
||||
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("web/static"))))
|
||||
|
||||
go http.ListenAndServe(*listenAddress, nil)
|
||||
|
|
Loading…
Reference in a new issue