Merge branch 'master' of github.com:grafana/grafana

This commit is contained in:
Torkel Ödegaard
2016-11-04 12:15:08 +01:00
38 changed files with 348 additions and 162 deletions

View File

@@ -264,7 +264,7 @@ func PauseAlert(c *middleware.Context, dto dtos.PauseAlertCommand) Response {
return ApiError(500, "", err)
}
var response models.AlertStateType = models.AlertStateNoData
var response models.AlertStateType = models.AlertStatePending
pausedState := "un paused"
if cmd.Paused {
response = models.AlertStatePaused

View File

@@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"io/ioutil"
"net"
"net/http"
"net/url"
"path"
@@ -25,6 +26,16 @@ func Init(version string) {
grafanaVersion = version
tr := &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).DialContext,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
TLSClientConfig: &tls.Config{InsecureSkipVerify: false},
}

View File

@@ -41,6 +41,7 @@ var (
M_Alerting_Result_State_Paused Counter
M_Alerting_Result_State_NoData Counter
M_Alerting_Result_State_ExecError Counter
M_Alerting_Result_State_Pending Counter
M_Alerting_Active_Alerts Counter
M_Alerting_Notification_Sent_Slack Counter
M_Alerting_Notification_Sent_Email Counter
@@ -102,6 +103,7 @@ func initMetricVars(settings *MetricSettings) {
M_Alerting_Result_State_Paused = RegCounter("alerting.result", "state", "paused")
M_Alerting_Result_State_NoData = RegCounter("alerting.result", "state", "no_data")
M_Alerting_Result_State_ExecError = RegCounter("alerting.result", "state", "exec_error")
M_Alerting_Result_State_Pending = RegCounter("alerting.result", "state", "pending")
M_Alerting_Active_Alerts = RegCounter("alerting.active_alerts")
M_Alerting_Notification_Sent_Slack = RegCounter("alerting.notifications_sent", "type", "slack")

View File

@@ -11,11 +11,12 @@ type AlertSeverityType string
type NoDataOption string
const (
AlertStateNoData AlertStateType = "no_data"
AlertStateExecError AlertStateType = "execution_error"
AlertStatePaused AlertStateType = "paused"
AlertStateAlerting AlertStateType = "alerting"
AlertStateOK AlertStateType = "ok"
AlertStateNoData AlertStateType = "no_data"
AlertStateExecError AlertStateType = "execution_error"
AlertStatePaused AlertStateType = "paused"
AlertStateAlerting AlertStateType = "alerting"
AlertStateOK AlertStateType = "ok"
AlertStatePending AlertStateType = "pending"
)
const (
@@ -26,7 +27,7 @@ const (
)
func (s AlertStateType) IsValid() bool {
return s == AlertStateOK || s == AlertStateNoData || s == AlertStateExecError || s == AlertStatePaused
return s == AlertStateOK || s == AlertStateNoData || s == AlertStateExecError || s == AlertStatePaused || s == AlertStatePending
}
func (s NoDataOption) IsValid() bool {

View File

@@ -33,15 +33,17 @@ type AlertQuery struct {
To string
}
func (c *QueryCondition) Eval(context *alerting.EvalContext) {
func (c *QueryCondition) Eval(context *alerting.EvalContext) (*alerting.ConditionResult, error) {
timeRange := tsdb.NewTimeRange(c.Query.From, c.Query.To)
seriesList, err := c.executeQuery(context, timeRange)
if err != nil {
context.Error = err
return
return nil, err
}
emptySerieCount := 0
evalMatchCount := 0
var matches []*alerting.EvalMatch
for _, series := range seriesList {
reducedValue := c.Reducer.Reduce(series)
evalMatch := c.Evaluator.Eval(reducedValue)
@@ -58,15 +60,20 @@ func (c *QueryCondition) Eval(context *alerting.EvalContext) {
}
if evalMatch {
context.EvalMatches = append(context.EvalMatches, &alerting.EvalMatch{
evalMatchCount++
matches = append(matches, &alerting.EvalMatch{
Metric: series.Name,
Value: reducedValue.Float64,
})
}
}
context.NoDataFound = emptySerieCount == len(seriesList)
context.Firing = len(context.EvalMatches) > 0
return &alerting.ConditionResult{
Firing: evalMatchCount > 0,
NoDataFound: emptySerieCount == len(seriesList),
EvalMatches: matches,
}, nil
}
func (c *QueryCondition) executeQuery(context *alerting.EvalContext, timeRange *tsdb.TimeRange) (tsdb.TimeSeriesSlice, error) {

View File

@@ -46,19 +46,19 @@ func TestQueryCondition(t *testing.T) {
Convey("should fire when avg is above 100", func() {
points := tsdb.NewTimeSeriesPointsFromArgs(120, 0)
ctx.series = tsdb.TimeSeriesSlice{tsdb.NewTimeSeries("test1", points)}
ctx.exec()
cr, err := ctx.exec()
So(ctx.result.Error, ShouldBeNil)
So(ctx.result.Firing, ShouldBeTrue)
So(err, ShouldBeNil)
So(cr.Firing, ShouldBeTrue)
})
Convey("Should not fire when avg is below 100", func() {
points := tsdb.NewTimeSeriesPointsFromArgs(90, 0)
ctx.series = tsdb.TimeSeriesSlice{tsdb.NewTimeSeries("test1", points)}
ctx.exec()
cr, err := ctx.exec()
So(ctx.result.Error, ShouldBeNil)
So(ctx.result.Firing, ShouldBeFalse)
So(err, ShouldBeNil)
So(cr.Firing, ShouldBeFalse)
})
Convey("Should fire if only first serie matches", func() {
@@ -66,10 +66,10 @@ func TestQueryCondition(t *testing.T) {
tsdb.NewTimeSeries("test1", tsdb.NewTimeSeriesPointsFromArgs(120, 0)),
tsdb.NewTimeSeries("test2", tsdb.NewTimeSeriesPointsFromArgs(0, 0)),
}
ctx.exec()
cr, err := ctx.exec()
So(ctx.result.Error, ShouldBeNil)
So(ctx.result.Firing, ShouldBeTrue)
So(err, ShouldBeNil)
So(cr.Firing, ShouldBeTrue)
})
Convey("Empty series", func() {
@@ -78,10 +78,10 @@ func TestQueryCondition(t *testing.T) {
tsdb.NewTimeSeries("test1", tsdb.NewTimeSeriesPointsFromArgs()),
tsdb.NewTimeSeries("test2", tsdb.NewTimeSeriesPointsFromArgs()),
}
ctx.exec()
cr, err := ctx.exec()
So(ctx.result.Error, ShouldBeNil)
So(ctx.result.NoDataFound, ShouldBeTrue)
So(err, ShouldBeNil)
So(cr.NoDataFound, ShouldBeTrue)
})
Convey("Should set NoDataFound both series contains null", func() {
@@ -89,10 +89,10 @@ func TestQueryCondition(t *testing.T) {
tsdb.NewTimeSeries("test1", tsdb.TimeSeriesPoints{tsdb.TimePoint{null.FloatFromPtr(nil), null.FloatFrom(0)}}),
tsdb.NewTimeSeries("test2", tsdb.TimeSeriesPoints{tsdb.TimePoint{null.FloatFromPtr(nil), null.FloatFrom(0)}}),
}
ctx.exec()
cr, err := ctx.exec()
So(ctx.result.Error, ShouldBeNil)
So(ctx.result.NoDataFound, ShouldBeTrue)
So(err, ShouldBeNil)
So(cr.NoDataFound, ShouldBeTrue)
})
Convey("Should not set NoDataFound if one serie is empty", func() {
@@ -100,10 +100,10 @@ func TestQueryCondition(t *testing.T) {
tsdb.NewTimeSeries("test1", tsdb.NewTimeSeriesPointsFromArgs()),
tsdb.NewTimeSeries("test2", tsdb.NewTimeSeriesPointsFromArgs(120, 0)),
}
ctx.exec()
cr, err := ctx.exec()
So(ctx.result.Error, ShouldBeNil)
So(ctx.result.NoDataFound, ShouldBeFalse)
So(err, ShouldBeNil)
So(cr.NoDataFound, ShouldBeFalse)
})
})
})
@@ -120,7 +120,7 @@ type queryConditionTestContext struct {
type queryConditionScenarioFunc func(c *queryConditionTestContext)
func (ctx *queryConditionTestContext) exec() {
func (ctx *queryConditionTestContext) exec() (*alerting.ConditionResult, error) {
jsonModel, err := simplejson.NewJson([]byte(`{
"type": "query",
"query": {
@@ -146,7 +146,7 @@ func (ctx *queryConditionTestContext) exec() {
}, nil
}
condition.Eval(ctx.result)
return condition.Eval(ctx.result)
}
func queryConditionScenario(desc string, fn queryConditionScenarioFunc) {

View File

@@ -20,8 +20,12 @@ func NewEvalHandler() *DefaultEvalHandler {
}
func (e *DefaultEvalHandler) Eval(context *EvalContext) {
firing := true
for _, condition := range context.Rule.Conditions {
condition.Eval(context)
cr, err := condition.Eval(context)
if err != nil {
context.Error = err
}
// break if condition could not be evaluated
if context.Error != nil {
@@ -29,11 +33,15 @@ func (e *DefaultEvalHandler) Eval(context *EvalContext) {
}
// break if result has not triggered yet
if context.Firing == false {
if cr.Firing == false {
firing = false
break
}
context.EvalMatches = append(context.EvalMatches, cr.EvalMatches...)
}
context.Firing = firing
context.EndTime = time.Now()
elapsedTime := context.EndTime.Sub(context.StartTime) / time.Millisecond
metrics.M_Alerting_Exeuction_Time.Update(elapsedTime)

View File

@@ -8,11 +8,12 @@ import (
)
type conditionStub struct {
firing bool
firing bool
matches []*EvalMatch
}
func (c *conditionStub) Eval(context *EvalContext) {
context.Firing = c.firing
func (c *conditionStub) Eval(context *EvalContext) (*ConditionResult, error) {
return &ConditionResult{Firing: c.firing, EvalMatches: c.matches}, nil
}
func TestAlertingExecutor(t *testing.T) {
@@ -30,10 +31,10 @@ func TestAlertingExecutor(t *testing.T) {
So(context.Firing, ShouldEqual, true)
})
Convey("Show return false with not passing condition", func() {
Convey("Show return false with not passing asdf", func() {
context := NewEvalContext(context.TODO(), &Rule{
Conditions: []Condition{
&conditionStub{firing: true},
&conditionStub{firing: true, matches: []*EvalMatch{&EvalMatch{}, &EvalMatch{}}},
&conditionStub{firing: false},
},
})

View File

@@ -3,6 +3,8 @@ package alerting
import (
"errors"
"fmt"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/log"
@@ -104,7 +106,8 @@ func (e *DashAlertExtractor) GetAlerts() ([]*m.Alert, error) {
panelQuery := findPanelQueryByRefId(panel, queryRefId)
if panelQuery == nil {
return nil, ValidationError{Reason: "Alert refes to query that cannot be found"}
reason := fmt.Sprintf("Alert on PanelId: %v refers to query(%s) that cannot be found", alert.PanelId, queryRefId)
return nil, ValidationError{Reason: reason}
}
dsName := ""

View File

@@ -21,6 +21,12 @@ type Notifier interface {
GetIsDefault() bool
}
type Condition interface {
Eval(result *EvalContext)
type ConditionResult struct {
Firing bool
NoDataFound bool
EvalMatches []*EvalMatch
}
type Condition interface {
Eval(result *EvalContext) (*ConditionResult, error)
}

View File

@@ -49,7 +49,7 @@ func (n *RootNotifier) Notify(context *EvalContext) error {
return err
}
n.log.Info("Sending notifications for", "ruleId", context.Rule.Id, "Amount to send", len(notifiers))
n.log.Info("Sending notifications for", "ruleId", context.Rule.Id, "sent count", len(notifiers))
if len(notifiers) == 0 {
return nil

View File

@@ -22,17 +22,21 @@ func NewSlackNotifier(model *m.AlertNotification) (alerting.Notifier, error) {
return nil, alerting.ValidationError{Reason: "Could not find url property in settings"}
}
recipient := model.Settings.Get("recipient").MustString()
return &SlackNotifier{
NotifierBase: NewNotifierBase(model.Id, model.IsDefault, model.Name, model.Type, model.Settings),
Url: url,
Recipient: recipient,
log: log.New("alerting.notifier.slack"),
}, nil
}
type SlackNotifier struct {
NotifierBase
Url string
log log.Logger
Url string
Recipient string
log log.Logger
}
func (this *SlackNotifier) Notify(evalContext *alerting.EvalContext) error {
@@ -85,6 +89,12 @@ func (this *SlackNotifier) Notify(evalContext *alerting.EvalContext) error {
"ts": time.Now().Unix(),
},
},
"parse": "full", // to linkify urls, users and channels in alert message.
}
//recipient override
if this.Recipient != "" {
body["channel"] = this.Recipient
}
data, _ := json.Marshal(&body)

View File

@@ -86,7 +86,12 @@ func (handler *DefaultResultHandler) Handle(evalContext *EvalContext) error {
handler.log.Error("Failed to save annotation for new alert state", "error", err)
}
handler.notifier.Notify(evalContext)
if (oldState == m.AlertStatePending) && (evalContext.Rule.State == m.AlertStateOK) {
handler.log.Info("Notfication not sent", "oldState", oldState, "newState", evalContext.Rule.State)
} else {
handler.notifier.Notify(evalContext)
}
}
return nil
@@ -98,6 +103,8 @@ func (handler *DefaultResultHandler) shouldUpdateAlertState(evalContext *EvalCon
func countStateResult(state m.AlertStateType) {
switch state {
case m.AlertStatePending:
metrics.M_Alerting_Result_State_Pending.Inc(1)
case m.AlertStateAlerting:
metrics.M_Alerting_Result_State_Alerting.Inc(1)
case m.AlertStateOK:

View File

@@ -10,7 +10,9 @@ import (
type FakeCondition struct{}
func (f *FakeCondition) Eval(context *EvalContext) {}
func (f *FakeCondition) Eval(context *EvalContext) (*ConditionResult, error) {
return &ConditionResult{}, nil
}
func TestAlertRuleModel(t *testing.T) {
Convey("Testing alert rule", t, func() {

View File

@@ -99,7 +99,7 @@ func createDialer() (*gomail.Dialer, error) {
tlsconfig.Certificates = []tls.Certificate{cert}
}
d := gomail.NewPlainDialer(host, iPort, setting.Smtp.User, setting.Smtp.Password)
d := gomail.NewDialer(host, iPort, setting.Smtp.User, setting.Smtp.Password)
d.TLSConfig = tlsconfig
return d, nil
}

View File

@@ -173,7 +173,7 @@ func upsertAlerts(existingAlerts []*m.Alert, cmd *m.SaveAlertsCommand, sess *xor
} else {
alert.Updated = time.Now()
alert.Created = time.Now()
alert.State = m.AlertStateNoData
alert.State = m.AlertStatePending
alert.NewStateDate = time.Now()
_, err := sess.Insert(alert)
@@ -260,7 +260,7 @@ func PauseAlertRule(cmd *m.PauseAlertCommand) error {
if cmd.Paused {
newState = m.AlertStatePaused
} else {
newState = m.AlertStateNoData
newState = m.AlertStatePending
}
alert.State = newState

View File

@@ -47,7 +47,7 @@ func TestAlertingDataAccess(t *testing.T) {
So(err2, ShouldBeNil)
So(alert.Name, ShouldEqual, "Alerting title")
So(alert.Message, ShouldEqual, "Alerting message")
So(alert.State, ShouldEqual, "no_data")
So(alert.State, ShouldEqual, "pending")
So(alert.Frequency, ShouldEqual, 1)
})
@@ -77,7 +77,7 @@ func TestAlertingDataAccess(t *testing.T) {
So(query.Result[0].Name, ShouldEqual, "Name")
Convey("Alert state should not be updated", func() {
So(query.Result[0].State, ShouldEqual, "no_data")
So(query.Result[0].State, ShouldEqual, "pending")
})
})

View File

@@ -2,15 +2,14 @@ package graphite
import (
"context"
"crypto/tls"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"path"
"regexp"
"strings"
"time"
"golang.org/x/net/context/ctxhttp"
@@ -36,14 +35,7 @@ func init() {
glog = log.New("tsdb.graphite")
tsdb.RegisterExecutor("graphite", NewGraphiteExecutor)
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
HttpClient = &http.Client{
Timeout: time.Duration(15 * time.Second),
Transport: tr,
}
HttpClient = tsdb.GetDefaultClient()
}
func (e *GraphiteExecutor) Execute(ctx context.Context, queries tsdb.QuerySlice, context *tsdb.QueryContext) *tsdb.BatchResult {
@@ -58,9 +50,9 @@ func (e *GraphiteExecutor) Execute(ctx context.Context, queries tsdb.QuerySlice,
for _, query := range queries {
if fullTarget, err := query.Model.Get("targetFull").String(); err == nil {
formData["target"] = []string{fullTarget}
formData["target"] = []string{fixIntervalFormat(fullTarget)}
} else {
formData["target"] = []string{query.Model.Get("target").MustString()}
formData["target"] = []string{fixIntervalFormat(query.Model.Get("target").MustString())}
}
}
@@ -150,3 +142,17 @@ func formatTimeRange(input string) string {
}
return strings.Replace(strings.Replace(input, "m", "min", -1), "M", "mon", -1)
}
func fixIntervalFormat(target string) string {
rMinute := regexp.MustCompile(`'(\d+)m'`)
rMin := regexp.MustCompile("m")
target = rMinute.ReplaceAllStringFunc(target, func(m string) string {
return rMin.ReplaceAllString(m, "min")
})
rMonth := regexp.MustCompile(`'(\d+)M'`)
rMon := regexp.MustCompile("M")
target = rMonth.ReplaceAllStringFunc(target, func(M string) string {
return rMon.ReplaceAllString(M, "mon")
})
return target
}

View File

@@ -1 +1,61 @@
package graphite
import (
. "github.com/smartystreets/goconvey/convey"
"testing"
)
func TestGraphiteFunctions(t *testing.T) {
Convey("Testing Graphite Functions", t, func() {
Convey("formatting time range for now", func() {
timeRange := formatTimeRange("now")
So(timeRange, ShouldEqual, "now")
})
Convey("formatting time range for now-1m", func() {
timeRange := formatTimeRange("now-1m")
So(timeRange, ShouldEqual, "now-1min")
})
Convey("formatting time range for now-1M", func() {
timeRange := formatTimeRange("now-1M")
So(timeRange, ShouldEqual, "now-1mon")
})
Convey("fix interval format in query for 1m", func() {
timeRange := fixIntervalFormat("aliasByNode(hitcount(averageSeries(app.grafana.*.dashboards.views.count), '1m'), 4)")
So(timeRange, ShouldEqual, "aliasByNode(hitcount(averageSeries(app.grafana.*.dashboards.views.count), '1min'), 4)")
})
Convey("fix interval format in query for 1M", func() {
timeRange := fixIntervalFormat("aliasByNode(hitcount(averageSeries(app.grafana.*.dashboards.views.count), '1M'), 4)")
So(timeRange, ShouldEqual, "aliasByNode(hitcount(averageSeries(app.grafana.*.dashboards.views.count), '1mon'), 4)")
})
Convey("should not override query for 1M", func() {
timeRange := fixIntervalFormat("app.grafana.*.dashboards.views.1M.count")
So(timeRange, ShouldEqual, "app.grafana.*.dashboards.views.1M.count")
})
Convey("should not override query for 1m", func() {
timeRange := fixIntervalFormat("app.grafana.*.dashboards.views.1m.count")
So(timeRange, ShouldEqual, "app.grafana.*.dashboards.views.1m.count")
})
})
}

29
pkg/tsdb/http.go Normal file
View File

@@ -0,0 +1,29 @@
package tsdb
import (
"crypto/tls"
"net"
"net/http"
"time"
)
func GetDefaultClient() *http.Client {
tr := &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).DialContext,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
return &http.Client{
Timeout: time.Duration(30 * time.Second),
Transport: tr,
}
}

View File

@@ -2,13 +2,11 @@ package influxdb
import (
"context"
"crypto/tls"
"encoding/json"
"fmt"
"net/http"
"net/url"
"path"
"time"
"golang.org/x/net/context/ctxhttp"
@@ -41,14 +39,7 @@ func init() {
glog = log.New("tsdb.influxdb")
tsdb.RegisterExecutor("influxdb", NewInfluxDBExecutor)
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
HttpClient = &http.Client{
Timeout: time.Duration(15 * time.Second),
Transport: tr,
}
HttpClient = tsdb.GetDefaultClient()
}
func (e *InfluxDBExecutor) Execute(ctx context.Context, queries tsdb.QuerySlice, context *tsdb.QueryContext) *tsdb.BatchResult {

View File

@@ -2,19 +2,17 @@ package opentsdb
import (
"context"
"crypto/tls"
"fmt"
"path"
"strconv"
"strings"
"time"
"golang.org/x/net/context/ctxhttp"
"encoding/json"
"io/ioutil"
"net/http"
"net/url"
"encoding/json"
"gopkg.in/guregu/null.v3"
@@ -40,14 +38,7 @@ func init() {
plog = log.New("tsdb.opentsdb")
tsdb.RegisterExecutor("opentsdb", NewOpenTsdbExecutor)
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
HttpClient = &http.Client{
Timeout: time.Duration(15 * time.Second),
Transport: tr,
}
HttpClient = tsdb.GetDefaultClient()
}
func (e *OpenTsdbExecutor) Execute(ctx context.Context, queries tsdb.QuerySlice, queryContext *tsdb.QueryContext) *tsdb.BatchResult {
@@ -58,9 +49,9 @@ func (e *OpenTsdbExecutor) Execute(ctx context.Context, queries tsdb.QuerySlice,
tsdbQuery.Start = queryContext.TimeRange.GetFromAsMsEpoch()
tsdbQuery.End = queryContext.TimeRange.GetToAsMsEpoch()
for _ , query := range queries {
metric := e.buildMetric(query)
tsdbQuery.Queries = append(tsdbQuery.Queries, metric)
for _, query := range queries {
metric := e.buildMetric(query)
tsdbQuery.Queries = append(tsdbQuery.Queries, metric)
}
if setting.Env == setting.DEV {
@@ -104,7 +95,7 @@ func (e *OpenTsdbExecutor) createRequest(data OpenTsdbQuery) (*http.Request, err
if e.BasicAuth {
req.SetBasicAuth(e.BasicAuthUser, e.BasicAuthPassword)
}
return req, err
}
@@ -152,61 +143,61 @@ func (e *OpenTsdbExecutor) parseResponse(query OpenTsdbQuery, res *http.Response
return queryResults, nil
}
func (e *OpenTsdbExecutor) buildMetric(query *tsdb.Query) (map[string]interface{}) {
func (e *OpenTsdbExecutor) buildMetric(query *tsdb.Query) map[string]interface{} {
metric := make(map[string]interface{})
// Setting metric and aggregator
metric["metric"] = query.Model.Get("metric").MustString()
metric["aggregator"] = query.Model.Get("aggregator").MustString()
// Setting metric and aggregator
metric["metric"] = query.Model.Get("metric").MustString()
metric["aggregator"] = query.Model.Get("aggregator").MustString()
// Setting downsampling options
disableDownsampling := query.Model.Get("disableDownsampling").MustBool()
if !disableDownsampling {
downsampleInterval := query.Model.Get("downsampleInterval").MustString()
if downsampleInterval == "" {
downsampleInterval = "1m" //default value for blank
}
downsample := downsampleInterval + "-" + query.Model.Get("downsampleAggregator").MustString()
if query.Model.Get("downsampleFillPolicy").MustString() != "none" {
metric["downsample"] = downsample + "-" + query.Model.Get("downsampleFillPolicy").MustString()
} else {
metric["downsample"] = downsample
}
// Setting downsampling options
disableDownsampling := query.Model.Get("disableDownsampling").MustBool()
if !disableDownsampling {
downsampleInterval := query.Model.Get("downsampleInterval").MustString()
if downsampleInterval == "" {
downsampleInterval = "1m" //default value for blank
}
downsample := downsampleInterval + "-" + query.Model.Get("downsampleAggregator").MustString()
if query.Model.Get("downsampleFillPolicy").MustString() != "none" {
metric["downsample"] = downsample + "-" + query.Model.Get("downsampleFillPolicy").MustString()
} else {
metric["downsample"] = downsample
}
}
// Setting rate options
if query.Model.Get("shouldComputeRate").MustBool() {
metric["rate"] = true
rateOptions := make(map[string]interface{})
rateOptions["counter"] = query.Model.Get("isCounter").MustBool()
counterMax, counterMaxCheck := query.Model.CheckGet("counterMax")
if counterMaxCheck {
rateOptions["counterMax"] = counterMax.MustFloat64()
}
// Setting rate options
if query.Model.Get("shouldComputeRate").MustBool() {
metric["rate"] = true
rateOptions := make(map[string]interface{})
rateOptions["counter"] = query.Model.Get("isCounter").MustBool()
counterMax, counterMaxCheck := query.Model.CheckGet("counterMax")
if counterMaxCheck {
rateOptions["counterMax"] = counterMax.MustFloat64()
}
resetValue, resetValueCheck := query.Model.CheckGet("counterResetValue")
if resetValueCheck {
rateOptions["resetValue"] = resetValue.MustFloat64()
}
metric["rateOptions"] = rateOptions
resetValue, resetValueCheck := query.Model.CheckGet("counterResetValue")
if resetValueCheck {
rateOptions["resetValue"] = resetValue.MustFloat64()
}
// Setting tags
tags, tagsCheck := query.Model.CheckGet("tags")
if tagsCheck && len(tags.MustMap()) > 0 {
metric["tags"] = tags.MustMap()
}
metric["rateOptions"] = rateOptions
}
// Setting filters
filters, filtersCheck := query.Model.CheckGet("filters")
if filtersCheck && len(filters.MustArray()) > 0 {
metric["filters"] = filters.MustArray()
}
// Setting tags
tags, tagsCheck := query.Model.CheckGet("tags")
if tagsCheck && len(tags.MustMap()) > 0 {
metric["tags"] = tags.MustMap()
}
return metric
// Setting filters
filters, filtersCheck := query.Model.CheckGet("filters")
if filtersCheck && len(filters.MustArray()) > 0 {
metric["filters"] = filters.MustArray()
}
return metric
}

View File

@@ -3,7 +3,6 @@ package prometheus
import (
"context"
"fmt"
"net/http"
"regexp"
"strings"
"time"
@@ -25,8 +24,7 @@ func NewPrometheusExecutor(dsInfo *tsdb.DataSourceInfo) tsdb.Executor {
}
var (
plog log.Logger
HttpClient http.Client
plog log.Logger
)
func init() {
@@ -83,6 +81,10 @@ func (e *PrometheusExecutor) Execute(ctx context.Context, queries tsdb.QuerySlic
func formatLegend(metric pmodel.Metric, query *PrometheusQuery) string {
reg, _ := regexp.Compile(`\{\{\s*(.+?)\s*\}\}`)
if query.LegendFormat == "" {
return metric.String()
}
result := reg.ReplaceAllFunc([]byte(query.LegendFormat), func(in []byte) []byte {
labelName := strings.Replace(string(in), "{{", "", 1)
labelName = strings.Replace(labelName, "}}", "", 1)
@@ -110,10 +112,7 @@ func parseQuery(queries tsdb.QuerySlice, queryContext *tsdb.QueryContext) (*Prom
return nil, err
}
format, err := queryModel.Model.Get("legendFormat").String()
if err != nil {
return nil, err
}
format := queryModel.Model.Get("legendFormat").MustString("")
start, err := queryContext.TimeRange.ParseFrom()
if err != nil {
@@ -158,9 +157,3 @@ func parseResponse(value pmodel.Value, query *PrometheusQuery) (map[string]*tsdb
queryResults["A"] = queryRes
return queryResults, nil
}
/*
func resultWithError(result *tsdb.BatchResult, err error) *tsdb.BatchResult {
result.Error = err
return result
}*/

View File

@@ -22,5 +22,19 @@ func TestPrometheus(t *testing.T) {
So(formatLegend(metric, query), ShouldEqual, "legend backend mobile {{broken}}")
})
Convey("build full serie name", func() {
metric := map[p.LabelName]p.LabelValue{
p.LabelName(p.MetricNameLabel): p.LabelValue("http_request_total"),
p.LabelName("app"): p.LabelValue("backend"),
p.LabelName("device"): p.LabelValue("mobile"),
}
query := &PrometheusQuery{
LegendFormat: "",
}
So(formatLegend(metric, query), ShouldEqual, `http_request_total{app="backend", device="mobile"}`)
})
})
}