Move rulemanager to it's own package to break cicrular dependency. Make NewTestTieredStorage available to tests, remove duplication. Change-Id: I33b321245a44aa727bfc3614a7c9ae5005b34e03changes/32/232/5
parent
16ca35c07e
commit
e041c0cd46
@ -0,0 +1,110 @@ |
||||
// 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 templates |
||||
|
||||
import ( |
||||
"bytes" |
||||
"errors" |
||||
"fmt" |
||||
"text/template" |
||||
|
||||
clientmodel "github.com/prometheus/client_golang/model" |
||||
|
||||
"github.com/prometheus/prometheus/rules" |
||||
"github.com/prometheus/prometheus/rules/ast" |
||||
"github.com/prometheus/prometheus/stats" |
||||
"github.com/prometheus/prometheus/storage/metric" |
||||
) |
||||
|
||||
// A version of vector that's easier to use from templates.
|
||||
type sample struct { |
||||
Labels map[string]string |
||||
Value float64 |
||||
} |
||||
type queryResult []*sample |
||||
|
||||
// Expand a template, using the given data, time and storage.
|
||||
func Expand(text string, name string, data interface{}, timestamp clientmodel.Timestamp, storage metric.PreloadingPersistence) (result string, resultErr error) { |
||||
|
||||
// It'd better to have no alert description than to kill the whole process
|
||||
// if there's a bug in the template. Similarly with console templates.
|
||||
defer func() { |
||||
if r := recover(); r != nil { |
||||
var ok bool |
||||
resultErr, ok = r.(error) |
||||
if !ok { |
||||
resultErr = fmt.Errorf("Panic expanding template: %v", r) |
||||
} |
||||
} |
||||
}() |
||||
|
||||
funcMap := template.FuncMap{ |
||||
"query": func(q string) (queryResult, error) { |
||||
return query(q, timestamp, storage) |
||||
}, |
||||
"first": func(v queryResult) (*sample, error) { |
||||
if len(v) > 0 { |
||||
return v[0], nil |
||||
} |
||||
return nil, errors.New("first() called on vector with no elements") |
||||
}, |
||||
"label": func(label string, s *sample) string { |
||||
return s.Labels[label] |
||||
}, |
||||
"value": func(s *sample) float64 { |
||||
return s.Value |
||||
}, |
||||
"strvalue": func(s *sample) string { |
||||
return s.Labels["__value__"] |
||||
}, |
||||
} |
||||
|
||||
var buffer bytes.Buffer |
||||
tmpl, err := template.New(name).Funcs(funcMap).Parse(text) |
||||
if err != nil { |
||||
return "", fmt.Errorf("Error parsing template %v: %v", name, err) |
||||
} |
||||
err = tmpl.Execute(&buffer, data) |
||||
if err != nil { |
||||
return "", fmt.Errorf("Error executing template %v: %v", name, err) |
||||
} |
||||
return buffer.String(), nil |
||||
} |
||||
|
||||
func query(q string, timestamp clientmodel.Timestamp, storage metric.PreloadingPersistence) (queryResult, error) { |
||||
exprNode, err := rules.LoadExprFromString(q) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
queryStats := stats.NewTimerGroup() |
||||
vector, err := ast.EvalToVector(exprNode, timestamp, storage, queryStats) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
// ast.Vector is hard to work with in templates, so convert to
|
||||
// base data types.
|
||||
var result = make(queryResult, len(vector)) |
||||
for n, v := range vector { |
||||
s := sample{ |
||||
Value: float64(v.Value), |
||||
Labels: make(map[string]string), |
||||
} |
||||
for label, value := range v.Metric { |
||||
s.Labels[string(label)] = string(value) |
||||
} |
||||
result[n] = &s |
||||
} |
||||
return result, nil |
||||
} |
||||
@ -0,0 +1,109 @@ |
||||
// Copyright 2014 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 templates |
||||
|
||||
import ( |
||||
"testing" |
||||
|
||||
clientmodel "github.com/prometheus/client_golang/model" |
||||
|
||||
"github.com/prometheus/prometheus/storage/metric/tiered" |
||||
) |
||||
|
||||
type testTemplatesScenario struct { |
||||
text string |
||||
output string |
||||
shouldFail bool |
||||
} |
||||
|
||||
func TestTemplateExpansion(t *testing.T) { |
||||
scenarios := []testTemplatesScenario{ |
||||
{ |
||||
// No template.
|
||||
text: "plain text", |
||||
output: "plain text", |
||||
}, |
||||
{ |
||||
// Simple value.
|
||||
text: "{{ 1 }}", |
||||
output: "1", |
||||
}, |
||||
{ |
||||
// Get value from query.
|
||||
text: "{{ query \"metric{instance='a'}\" | first | value }}", |
||||
output: "11", |
||||
}, |
||||
{ |
||||
// Get label from query.
|
||||
text: "{{ query \"metric{instance='a'}\" | first | label \"instance\" }}", |
||||
output: "a", |
||||
}, |
||||
{ |
||||
// Range over query.
|
||||
text: "{{ range query \"metric\" }}{{.Labels.instance}}:{{.Value}}: {{end}}", |
||||
output: "a:11: b:21: ", |
||||
}, |
||||
{ |
||||
// Unparsable template.
|
||||
text: "{{", |
||||
shouldFail: true, |
||||
}, |
||||
{ |
||||
// Error in function.
|
||||
text: "{{ query \"missing\" | first }}", |
||||
shouldFail: true, |
||||
}, |
||||
{ |
||||
// Panic.
|
||||
text: "{{ (query \"missing\").banana }}", |
||||
shouldFail: true, |
||||
}, |
||||
} |
||||
|
||||
time := clientmodel.Timestamp(0) |
||||
|
||||
ts, _ := tiered.NewTestTieredStorage(t) |
||||
ts.AppendSamples(clientmodel.Samples{ |
||||
{ |
||||
Metric: clientmodel.Metric{ |
||||
clientmodel.MetricNameLabel: "metric", |
||||
"instance": "a"}, |
||||
Value: 11, |
||||
}, |
||||
{ |
||||
Metric: clientmodel.Metric{ |
||||
clientmodel.MetricNameLabel: "metric", |
||||
"instance": "b"}, |
||||
Value: 21, |
||||
}, |
||||
}) |
||||
|
||||
for _, s := range scenarios { |
||||
result, err := Expand(s.text, "test", nil, time, ts) |
||||
if s.shouldFail { |
||||
if err == nil { |
||||
t.Fatalf("Error not returned from %v", s.text) |
||||
} |
||||
continue |
||||
} |
||||
if err != nil { |
||||
t.Fatalf("Error returned from %v: %v", s.text, err) |
||||
continue |
||||
} |
||||
if result != s.output { |
||||
t.Fatalf("Error in result from %v: Expected '%v' Got '%v'", s.text, s.output, result) |
||||
continue |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,74 @@ |
||||
// Copyright 2014 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 ( |
||||
"flag" |
||||
"io" |
||||
"io/ioutil" |
||||
"net/http" |
||||
"net/url" |
||||
|
||||
clientmodel "github.com/prometheus/client_golang/model" |
||||
"github.com/prometheus/prometheus/storage/metric" |
||||
"github.com/prometheus/prometheus/templates" |
||||
) |
||||
|
||||
var ( |
||||
consoleTemplatesPath = flag.String("consoleTemplates", "consoles", "Path to console template directory, available at /console") |
||||
) |
||||
|
||||
type ConsolesHandler struct { |
||||
Storage metric.PreloadingPersistence |
||||
} |
||||
|
||||
func (h *ConsolesHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { |
||||
file, err := http.Dir(*consoleTemplatesPath).Open(r.URL.Path) |
||||
if err != nil { |
||||
http.Error(w, err.Error(), http.StatusNotFound) |
||||
return |
||||
} |
||||
text, err := ioutil.ReadAll(file) |
||||
if err != nil { |
||||
http.Error(w, err.Error(), http.StatusInternalServerError) |
||||
return |
||||
} |
||||
|
||||
// Provide URL parameters as a map for easy use. Advanced users may have need for
|
||||
// parameters beyond the first, so provide RawParams.
|
||||
rawParams, err := url.ParseQuery(r.URL.RawQuery) |
||||
if err != nil { |
||||
http.Error(w, err.Error(), http.StatusBadRequest) |
||||
return |
||||
} |
||||
params := map[string]string{} |
||||
for k, v := range rawParams { |
||||
params[k] = v[0] |
||||
} |
||||
data := struct { |
||||
RawParams url.Values |
||||
Params map[string]string |
||||
}{ |
||||
RawParams: rawParams, |
||||
Params: params, |
||||
} |
||||
|
||||
now := clientmodel.Now() |
||||
result, err := templates.Expand(string(text), "__console_"+r.URL.Path, data, now, h.Storage) |
||||
if err != nil { |
||||
http.Error(w, err.Error(), http.StatusInternalServerError) |
||||
return |
||||
} |
||||
io.WriteString(w, result) |
||||
} |
||||
Loading…
Reference in new issue