mirror of https://github.com/prometheus/prometheus
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