diff --git a/appstate/appstate.go b/appstate/appstate.go new file mode 100644 index 0000000000..543f5c4b71 --- /dev/null +++ b/appstate/appstate.go @@ -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 +} diff --git a/config/config.go b/config/config.go index 290fa2d9cf..01dfee3b26 100644 --- a/config/config.go +++ b/config/config.go @@ -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) { diff --git a/config/config_test.go b/config/config_test.go new file mode 100644 index 0000000000..477ae067f7 --- /dev/null +++ b/config/config_test.go @@ -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) + } + } + } +} diff --git a/config/fixtures/empty.conf.printed b/config/fixtures/empty.conf.printed new file mode 100644 index 0000000000..b2c9c87380 --- /dev/null +++ b/config/fixtures/empty.conf.printed @@ -0,0 +1,8 @@ +global { + scrape_interval = "0y" + evaluation_interval = "0y" + rule_files = [ + + ] +} + diff --git a/config/fixtures/minimal.conf.printed b/config/fixtures/minimal.conf.printed new file mode 100644 index 0000000000..833f635bff --- /dev/null +++ b/config/fixtures/minimal.conf.printed @@ -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" + ] + } +} diff --git a/config/fixtures/sample.conf.printed b/config/fixtures/sample.conf.printed new file mode 100644 index 0000000000..aa46fa82fc --- /dev/null +++ b/config/fixtures/sample.conf.printed @@ -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" + } + } +} diff --git a/config/helpers.go b/config/helpers.go index af72519698..8ef989345f 100644 --- a/config/helpers.go +++ b/config/helpers.go @@ -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 -} diff --git a/config/load.go b/config/load.go index f998cb3378..f0b7407975 100644 --- a/config/load.go +++ b/config/load.go @@ -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 diff --git a/config/printer.go b/config/printer.go index 26d7c6bc12..c977d945bf 100644 --- a/config/printer.go +++ b/config/printer.go @@ -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") diff --git a/main.go b/main.go index 39af0be2f2..7d256ad87d 100644 --- a/main.go +++ b/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 { diff --git a/rules/ast/printer.go b/rules/ast/printer.go index 1f38117ac1..4fd16296d8 100644 --- a/rules/ast/printer.go +++ b/rules/ast/printer.go @@ -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 } diff --git a/rules/helpers.go b/rules/helpers.go index 3008768d1b..a5a224c443 100644 --- a/rules/helpers.go +++ b/rules/helpers.go @@ -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 } diff --git a/rules/lexer.l b/rules/lexer.l index f174f0bbf5..643f52fc6d 100644 --- a/rules/lexer.l +++ b/rules/lexer.l @@ -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 } diff --git a/rules/lexer.l.go b/rules/lexer.l.go index aceeaec4ae..7ec712fd63 100644 --- a/rules/lexer.l.go +++ b/rules/lexer.l.go @@ -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} diff --git a/utility/strconv.go b/utility/strconv.go new file mode 100644 index 0000000000..5e13fcfc8c --- /dev/null +++ b/utility/strconv.go @@ -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 +} diff --git a/web/api/api.go b/web/api/api.go index 7aa4e891a6..7c7b6155af 100644 --- a/web/api/api.go +++ b/web/api/api.go @@ -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, } } diff --git a/web/static/css/graph.css b/web/static/css/graph.css new file mode 100644 index 0000000000..5a84cbaabe --- /dev/null +++ b/web/static/css/graph.css @@ -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- +} diff --git a/web/static/css/prometheus.css b/web/static/css/prometheus.css index 52979a4c46..2a563b085f 100644 --- a/web/static/css/prometheus.css +++ b/web/static/css/prometheus.css @@ -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; diff --git a/web/static/graph.html b/web/static/graph.html index 2d64d9e6a3..592f5a3c5e 100644 --- a/web/static/graph.html +++ b/web/static/graph.html @@ -6,6 +6,7 @@ + diff --git a/web/static/index.html b/web/static/index.html index ce04c7bd44..df6e32cfa1 100644 --- a/web/static/index.html +++ b/web/static/index.html @@ -4,6 +4,7 @@
+{{.Config}} ++