mirror of
https://github.com/grafana/grafana.git
synced 2025-07-30 00:34:27 +08:00
feat(alerting): requests looks to be working again
This commit is contained in:
@ -22,7 +22,7 @@ func ValidateOrgAlert(c *middleware.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET /api/alerts/changes
|
// GET /api/alerting/changes
|
||||||
func GetAlertChanges(c *middleware.Context) Response {
|
func GetAlertChanges(c *middleware.Context) Response {
|
||||||
query := models.GetAlertChangesQuery{
|
query := models.GetAlertChangesQuery{
|
||||||
OrgId: c.OrgId,
|
OrgId: c.OrgId,
|
||||||
@ -69,7 +69,7 @@ func GetAlerts(c *middleware.Context) Response {
|
|||||||
WarnLevel: alert.WarnLevel,
|
WarnLevel: alert.WarnLevel,
|
||||||
CritLevel: alert.CritLevel,
|
CritLevel: alert.CritLevel,
|
||||||
Frequency: alert.Frequency,
|
Frequency: alert.Frequency,
|
||||||
Title: alert.Title,
|
Name: alert.Name,
|
||||||
Description: alert.Description,
|
Description: alert.Description,
|
||||||
QueryRange: alert.QueryRange,
|
QueryRange: alert.QueryRange,
|
||||||
Aggregator: alert.Aggregator,
|
Aggregator: alert.Aggregator,
|
||||||
|
@ -244,9 +244,7 @@ func Register(r *macaron.Macaron) {
|
|||||||
r.Group("/alerts", func() {
|
r.Group("/alerts", func() {
|
||||||
r.Group("/rules", func() {
|
r.Group("/rules", func() {
|
||||||
r.Get("/:alertId/states", wrap(GetAlertStates))
|
r.Get("/:alertId/states", wrap(GetAlertStates))
|
||||||
|
|
||||||
r.Put("/:alertId/state", bind(m.UpdateAlertStateCommand{}), wrap(PutAlertState))
|
r.Put("/:alertId/state", bind(m.UpdateAlertStateCommand{}), wrap(PutAlertState))
|
||||||
|
|
||||||
r.Get("/:alertId", ValidateOrgAlert, wrap(GetAlert))
|
r.Get("/:alertId", ValidateOrgAlert, wrap(GetAlert))
|
||||||
//r.Delete("/:alertId", ValidateOrgAlert, wrap(DelAlert)) disabled until we know how to handle it dashboard updates
|
//r.Delete("/:alertId", ValidateOrgAlert, wrap(DelAlert)) disabled until we know how to handle it dashboard updates
|
||||||
r.Get("/", wrap(GetAlerts))
|
r.Get("/", wrap(GetAlerts))
|
||||||
|
@ -77,7 +77,7 @@ func getDatasource(id int64, orgId int64) (*m.DataSource, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &query.Result, nil
|
return query.Result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ProxyDataSourceRequest(c *middleware.Context) {
|
func ProxyDataSourceRequest(c *middleware.Context) {
|
||||||
|
@ -123,9 +123,7 @@ func GetDataSourceByName(c *middleware.Context) Response {
|
|||||||
return ApiError(500, "Failed to query datasources", err)
|
return ApiError(500, "Failed to query datasources", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ds := query.Result
|
dtos := convertModelToDtos(query.Result)
|
||||||
dtos := convertModelToDtos(ds)
|
|
||||||
|
|
||||||
return Json(200, &dtos)
|
return Json(200, &dtos)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -148,7 +146,7 @@ func GetDataSourceIdByName(c *middleware.Context) Response {
|
|||||||
return Json(200, &dtos)
|
return Json(200, &dtos)
|
||||||
}
|
}
|
||||||
|
|
||||||
func convertModelToDtos(ds m.DataSource) dtos.DataSource {
|
func convertModelToDtos(ds *m.DataSource) dtos.DataSource {
|
||||||
return dtos.DataSource{
|
return dtos.DataSource{
|
||||||
Id: ds.Id,
|
Id: ds.Id,
|
||||||
OrgId: ds.OrgId,
|
OrgId: ds.OrgId,
|
||||||
|
@ -11,7 +11,7 @@ type AlertRuleDTO struct {
|
|||||||
WarnOperator string `json:"warnOperator"`
|
WarnOperator string `json:"warnOperator"`
|
||||||
CritOperator string `json:"critOperator"`
|
CritOperator string `json:"critOperator"`
|
||||||
Frequency int64 `json:"frequency"`
|
Frequency int64 `json:"frequency"`
|
||||||
Title string `json:"title"`
|
Name string `json:"name"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
QueryRange int `json:"queryRange"`
|
QueryRange int `json:"queryRange"`
|
||||||
Aggregator string `json:"aggregator"`
|
Aggregator string `json:"aggregator"`
|
||||||
|
@ -17,7 +17,7 @@ type AlertRule struct {
|
|||||||
WarnOperator string `json:"warnOperator"`
|
WarnOperator string `json:"warnOperator"`
|
||||||
CritOperator string `json:"critOperator"`
|
CritOperator string `json:"critOperator"`
|
||||||
Frequency int64 `json:"frequency"`
|
Frequency int64 `json:"frequency"`
|
||||||
Title string `json:"title"`
|
Name string `json:"name"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
QueryRange int `json:"queryRange"`
|
QueryRange int `json:"queryRange"`
|
||||||
Aggregator string `json:"aggregator"`
|
Aggregator string `json:"aggregator"`
|
||||||
@ -38,7 +38,7 @@ func (this *AlertRule) Equals(other *AlertRule) bool {
|
|||||||
result = result || this.Query != other.Query
|
result = result || this.Query != other.Query
|
||||||
result = result || this.QueryRefId != other.QueryRefId
|
result = result || this.QueryRefId != other.QueryRefId
|
||||||
result = result || this.Frequency != other.Frequency
|
result = result || this.Frequency != other.Frequency
|
||||||
result = result || this.Title != other.Title
|
result = result || this.Name != other.Name
|
||||||
result = result || this.Description != other.Description
|
result = result || this.Description != other.Description
|
||||||
result = result || this.QueryRange != other.QueryRange
|
result = result || this.QueryRange != other.QueryRange
|
||||||
//don't compare .State! That would be insane.
|
//don't compare .State! That would be insane.
|
||||||
|
@ -131,13 +131,13 @@ type GetDataSourcesQuery struct {
|
|||||||
type GetDataSourceByIdQuery struct {
|
type GetDataSourceByIdQuery struct {
|
||||||
Id int64
|
Id int64
|
||||||
OrgId int64
|
OrgId int64
|
||||||
Result DataSource
|
Result *DataSource
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetDataSourceByNameQuery struct {
|
type GetDataSourceByNameQuery struct {
|
||||||
Name string
|
Name string
|
||||||
OrgId int64
|
OrgId int64
|
||||||
Result DataSource
|
Result *DataSource
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------
|
// ---------------------
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/log"
|
"github.com/grafana/grafana/pkg/log"
|
||||||
m "github.com/grafana/grafana/pkg/models"
|
m "github.com/grafana/grafana/pkg/models"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
|
_ "github.com/grafana/grafana/pkg/tsdb/graphite"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -31,11 +32,11 @@ func Init() {
|
|||||||
// go scheduler.handleResponses()
|
// go scheduler.handleResponses()
|
||||||
}
|
}
|
||||||
|
|
||||||
func saveState(response *AlertResult) {
|
func saveState(result *AlertResult) {
|
||||||
cmd := &m.UpdateAlertStateCommand{
|
cmd := &m.UpdateAlertStateCommand{
|
||||||
AlertId: response.Id,
|
AlertId: result.AlertJob.Rule.Id,
|
||||||
NewState: response.State,
|
NewState: result.State,
|
||||||
Info: response.Description,
|
Info: result.Description,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := bus.Dispatch(cmd); err != nil {
|
if err := bus.Dispatch(cmd); err != nil {
|
||||||
|
@ -3,6 +3,7 @@ package alerting
|
|||||||
import (
|
import (
|
||||||
"github.com/grafana/grafana/pkg/bus"
|
"github.com/grafana/grafana/pkg/bus"
|
||||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||||
|
"github.com/grafana/grafana/pkg/log"
|
||||||
m "github.com/grafana/grafana/pkg/models"
|
m "github.com/grafana/grafana/pkg/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -27,12 +28,13 @@ func ParseAlertsFromDashboard(cmd *m.SaveDashboardCommand) []*m.AlertRule {
|
|||||||
WarnOperator: alerting.Get("warnOperator").MustString(),
|
WarnOperator: alerting.Get("warnOperator").MustString(),
|
||||||
CritOperator: alerting.Get("critOperator").MustString(),
|
CritOperator: alerting.Get("critOperator").MustString(),
|
||||||
Frequency: alerting.Get("frequency").MustInt64(),
|
Frequency: alerting.Get("frequency").MustInt64(),
|
||||||
Title: alerting.Get("title").MustString(),
|
Name: alerting.Get("name").MustString(),
|
||||||
Description: alerting.Get("description").MustString(),
|
Description: alerting.Get("description").MustString(),
|
||||||
QueryRange: alerting.Get("queryRange").MustInt(),
|
QueryRange: alerting.Get("queryRange").MustInt(),
|
||||||
Aggregator: alerting.Get("aggregator").MustString(),
|
Aggregator: alerting.Get("aggregator").MustString(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Info("Alertrule: %v", alert.Name)
|
||||||
for _, targetsObj := range panel.Get("targets").MustArray() {
|
for _, targetsObj := range panel.Get("targets").MustArray() {
|
||||||
target := simplejson.NewFromAny(targetsObj)
|
target := simplejson.NewFromAny(targetsObj)
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ func NewEngine() *Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (e *Engine) Start() {
|
func (e *Engine) Start() {
|
||||||
log.Info("Alerting: Engine.Start()")
|
log.Info("Alerting: engine.Start()")
|
||||||
|
|
||||||
go e.alertingTicker()
|
go e.alertingTicker()
|
||||||
go e.execDispatch()
|
go e.execDispatch()
|
||||||
@ -51,13 +51,12 @@ func (e *Engine) alertingTicker() {
|
|||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case tick := <-e.ticker.C:
|
case tick := <-e.ticker.C:
|
||||||
// update rules ever tenth tick
|
// TEMP SOLUTION update rules ever tenth tick
|
||||||
if tickIndex%10 == 0 {
|
if tickIndex%10 == 0 {
|
||||||
e.scheduler.Update(e.ruleReader.Fetch())
|
e.scheduler.Update(e.ruleReader.Fetch())
|
||||||
}
|
}
|
||||||
|
|
||||||
e.scheduler.Tick(tick, e.execQueue)
|
e.scheduler.Tick(tick, e.execQueue)
|
||||||
|
|
||||||
tickIndex++
|
tickIndex++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -65,7 +64,7 @@ func (e *Engine) alertingTicker() {
|
|||||||
|
|
||||||
func (e *Engine) execDispatch() {
|
func (e *Engine) execDispatch() {
|
||||||
for job := range e.execQueue {
|
for job := range e.execQueue {
|
||||||
log.Trace("Alerting: Engine:execDispatch() starting job %s", job.Rule.Title)
|
log.Trace("Alerting: engine:execDispatch() starting job %s", job.Rule.Name)
|
||||||
job.Running = true
|
job.Running = true
|
||||||
e.executeJob(job)
|
e.executeJob(job)
|
||||||
}
|
}
|
||||||
@ -80,33 +79,39 @@ func (e *Engine) executeJob(job *AlertJob) {
|
|||||||
select {
|
select {
|
||||||
case <-time.After(time.Second * 5):
|
case <-time.After(time.Second * 5):
|
||||||
e.resultQueue <- &AlertResult{
|
e.resultQueue <- &AlertResult{
|
||||||
Id: job.Rule.Id,
|
|
||||||
State: alertstates.Pending,
|
State: alertstates.Pending,
|
||||||
Duration: float64(time.Since(now).Nanoseconds()) / float64(1000000),
|
Duration: float64(time.Since(now).Nanoseconds()) / float64(1000000),
|
||||||
|
Error: fmt.Errorf("Timeout"),
|
||||||
AlertJob: job,
|
AlertJob: job,
|
||||||
}
|
}
|
||||||
|
log.Trace("Alerting: engine.executeJob(): timeout")
|
||||||
case result := <-resultChan:
|
case result := <-resultChan:
|
||||||
result.Duration = float64(time.Since(now).Nanoseconds()) / float64(1000000)
|
result.Duration = float64(time.Since(now).Nanoseconds()) / float64(1000000)
|
||||||
log.Trace("Alerting: engine.executeJob(): exeuction took %vms", result.Duration)
|
log.Trace("Alerting: engine.executeJob(): done %vms", result.Duration)
|
||||||
e.resultQueue <- result
|
e.resultQueue <- result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Engine) resultHandler() {
|
func (e *Engine) resultHandler() {
|
||||||
for result := range e.resultQueue {
|
for result := range e.resultQueue {
|
||||||
log.Debug("Alerting: engine.resultHandler(): alert(%d) status(%s) actual(%v) retry(%d)", result.Id, result.State, result.ActualValue, result.AlertJob.RetryCount)
|
log.Debug("Alerting: engine.resultHandler(): alert(%d) status(%s) actual(%v) retry(%d)", result.AlertJob.Rule.Id, result.State, result.ActualValue, result.AlertJob.RetryCount)
|
||||||
|
|
||||||
result.AlertJob.Running = false
|
result.AlertJob.Running = false
|
||||||
|
|
||||||
if result.IsResultIncomplete() {
|
// handle result error
|
||||||
|
if result.Error != nil {
|
||||||
result.AlertJob.RetryCount++
|
result.AlertJob.RetryCount++
|
||||||
|
|
||||||
if result.AlertJob.RetryCount < maxRetries {
|
if result.AlertJob.RetryCount < maxRetries {
|
||||||
|
log.Error(3, "Alerting: Rule('%s') Result Error: %v, Retrying..", result.AlertJob.Rule.Name, result.Error)
|
||||||
|
|
||||||
e.execQueue <- result.AlertJob
|
e.execQueue <- result.AlertJob
|
||||||
} else {
|
} else {
|
||||||
saveState(&AlertResult{
|
log.Error(3, "Alerting: Rule('%s') Result Error: %v, Max retries reached", result.AlertJob.Rule.Name, result.Error)
|
||||||
Id: result.Id,
|
|
||||||
State: alertstates.Critical,
|
result.State = alertstates.Critical
|
||||||
Description: fmt.Sprintf("Failed to run check after %d retires", maxRetries),
|
result.Description = fmt.Sprintf("Failed to run check after %d retires, Error: %v", maxRetries, result.Error)
|
||||||
})
|
saveState(result)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
result.AlertJob.RetryCount = 0
|
result.AlertJob.RetryCount = 0
|
||||||
|
@ -2,6 +2,7 @@ package alerting
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
@ -78,38 +79,79 @@ var aggregator = map[string]aggregationFn{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (e *ExecutorImpl) Execute(job *AlertJob, resultQueue chan *AlertResult) {
|
func (e *ExecutorImpl) Execute(job *AlertJob, resultQueue chan *AlertResult) {
|
||||||
response, err := e.GetSeries(job)
|
timeSeries, err := e.executeQuery(job)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resultQueue <- &AlertResult{State: alertstates.Pending, Id: job.Rule.Id, AlertJob: job}
|
resultQueue <- &AlertResult{
|
||||||
|
Error: err,
|
||||||
|
State: alertstates.Pending,
|
||||||
|
AlertJob: job,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result := e.validateRule(job.Rule, response)
|
result := e.evaluateRule(job.Rule, timeSeries)
|
||||||
result.AlertJob = job
|
result.AlertJob = job
|
||||||
resultQueue <- result
|
resultQueue <- result
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ExecutorImpl) GetSeries(job *AlertJob) (tsdb.TimeSeriesSlice, error) {
|
func (e *ExecutorImpl) executeQuery(job *AlertJob) (tsdb.TimeSeriesSlice, error) {
|
||||||
query := &m.GetDataSourceByIdQuery{
|
getDsInfo := &m.GetDataSourceByIdQuery{
|
||||||
Id: job.Rule.DatasourceId,
|
Id: job.Rule.DatasourceId,
|
||||||
OrgId: job.Rule.OrgId,
|
OrgId: job.Rule.OrgId,
|
||||||
}
|
}
|
||||||
|
|
||||||
err := bus.Dispatch(query)
|
if err := bus.Dispatch(getDsInfo); err != nil {
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Could not find datasource for %d", job.Rule.DatasourceId)
|
return nil, fmt.Errorf("Could not find datasource for %d", job.Rule.DatasourceId)
|
||||||
}
|
}
|
||||||
|
|
||||||
// if query.Result.Type == m.DS_GRAPHITE {
|
req := e.GetRequestForAlertRule(job.Rule, getDsInfo.Result)
|
||||||
// return GraphiteClient{}.GetSeries(*job, query.Result)
|
result := make(tsdb.TimeSeriesSlice, 0)
|
||||||
// }
|
|
||||||
|
|
||||||
return nil, fmt.Errorf("Grafana does not support alerts for %s", query.Result.Type)
|
resp, err := tsdb.HandleRequest(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Alerting: GetSeries() tsdb.HandleRequest() error %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range resp.Results {
|
||||||
|
if v.Error != nil {
|
||||||
|
return nil, fmt.Errorf("Alerting: GetSeries() tsdb.HandleRequest() response error %v", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
result = append(result, v.Series...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ExecutorImpl) validateRule(rule *AlertRule, series tsdb.TimeSeriesSlice) *AlertResult {
|
func (e *ExecutorImpl) GetRequestForAlertRule(rule *AlertRule, datasource *m.DataSource) *tsdb.Request {
|
||||||
|
|
||||||
|
req := &tsdb.Request{
|
||||||
|
TimeRange: tsdb.TimeRange{
|
||||||
|
From: "-" + strconv.Itoa(rule.QueryRange) + "s",
|
||||||
|
To: "now",
|
||||||
|
},
|
||||||
|
Queries: tsdb.QuerySlice{
|
||||||
|
&tsdb.Query{
|
||||||
|
RefId: rule.QueryRefId,
|
||||||
|
Query: rule.Query,
|
||||||
|
DataSource: &tsdb.DataSourceInfo{
|
||||||
|
Id: datasource.Id,
|
||||||
|
Name: datasource.Name,
|
||||||
|
PluginId: datasource.Type,
|
||||||
|
Url: datasource.Url,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return req
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ExecutorImpl) evaluateRule(rule *AlertRule, series tsdb.TimeSeriesSlice) *AlertResult {
|
||||||
|
log.Trace("Alerting: executor.evaluateRule: %v, query result: series: %v", rule.Name, len(series))
|
||||||
|
|
||||||
for _, serie := range series {
|
for _, serie := range series {
|
||||||
|
log.Info("Alerting: executor.validate: %v", serie.Name)
|
||||||
|
|
||||||
if aggregator[rule.Aggregator] == nil {
|
if aggregator[rule.Aggregator] == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -122,7 +164,6 @@ func (e *ExecutorImpl) validateRule(rule *AlertRule, series tsdb.TimeSeriesSlice
|
|||||||
if critResult {
|
if critResult {
|
||||||
return &AlertResult{
|
return &AlertResult{
|
||||||
State: alertstates.Critical,
|
State: alertstates.Critical,
|
||||||
Id: rule.Id,
|
|
||||||
ActualValue: aggValue,
|
ActualValue: aggValue,
|
||||||
Description: fmt.Sprintf(descriptionFmt, aggValue, serie.Name),
|
Description: fmt.Sprintf(descriptionFmt, aggValue, serie.Name),
|
||||||
}
|
}
|
||||||
@ -134,12 +175,11 @@ func (e *ExecutorImpl) validateRule(rule *AlertRule, series tsdb.TimeSeriesSlice
|
|||||||
if warnResult {
|
if warnResult {
|
||||||
return &AlertResult{
|
return &AlertResult{
|
||||||
State: alertstates.Warn,
|
State: alertstates.Warn,
|
||||||
Id: rule.Id,
|
|
||||||
Description: fmt.Sprintf(descriptionFmt, aggValue, serie.Name),
|
Description: fmt.Sprintf(descriptionFmt, aggValue, serie.Name),
|
||||||
ActualValue: aggValue,
|
ActualValue: aggValue,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &AlertResult{State: alertstates.Ok, Id: rule.Id, Description: "Alert is OK!"}
|
return &AlertResult{State: alertstates.Ok, Description: "Alert is OK!"}
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ func TestAlertingExecutor(t *testing.T) {
|
|||||||
tsdb.NewTimeSeries("test1", [][2]float64{{2, 0}}),
|
tsdb.NewTimeSeries("test1", [][2]float64{{2, 0}}),
|
||||||
}
|
}
|
||||||
|
|
||||||
result := executor.validateRule(rule, timeSeries)
|
result := executor.evaluateRule(rule, timeSeries)
|
||||||
So(result.State, ShouldEqual, alertstates.Ok)
|
So(result.State, ShouldEqual, alertstates.Ok)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -31,7 +31,7 @@ func TestAlertingExecutor(t *testing.T) {
|
|||||||
tsdb.NewTimeSeries("test1", [][2]float64{{2, 0}}),
|
tsdb.NewTimeSeries("test1", [][2]float64{{2, 0}}),
|
||||||
}
|
}
|
||||||
|
|
||||||
result := executor.validateRule(rule, timeSeries)
|
result := executor.evaluateRule(rule, timeSeries)
|
||||||
So(result.State, ShouldEqual, alertstates.Critical)
|
So(result.State, ShouldEqual, alertstates.Critical)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -42,7 +42,7 @@ func TestAlertingExecutor(t *testing.T) {
|
|||||||
tsdb.NewTimeSeries("test1", [][2]float64{{9, 0}, {9, 0}}),
|
tsdb.NewTimeSeries("test1", [][2]float64{{9, 0}, {9, 0}}),
|
||||||
}
|
}
|
||||||
|
|
||||||
result := executor.validateRule(rule, timeSeries)
|
result := executor.evaluateRule(rule, timeSeries)
|
||||||
So(result.State, ShouldEqual, alertstates.Critical)
|
So(result.State, ShouldEqual, alertstates.Critical)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -53,7 +53,7 @@ func TestAlertingExecutor(t *testing.T) {
|
|||||||
tsdb.NewTimeSeries("test1", [][2]float64{{9, 0}, {9, 0}}),
|
tsdb.NewTimeSeries("test1", [][2]float64{{9, 0}, {9, 0}}),
|
||||||
}
|
}
|
||||||
|
|
||||||
result := executor.validateRule(rule, timeSeries)
|
result := executor.evaluateRule(rule, timeSeries)
|
||||||
So(result.State, ShouldEqual, alertstates.Ok)
|
So(result.State, ShouldEqual, alertstates.Ok)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -64,7 +64,7 @@ func TestAlertingExecutor(t *testing.T) {
|
|||||||
tsdb.NewTimeSeries("test1", [][2]float64{{11, 0}, {9, 0}}),
|
tsdb.NewTimeSeries("test1", [][2]float64{{11, 0}, {9, 0}}),
|
||||||
}
|
}
|
||||||
|
|
||||||
result := executor.validateRule(rule, timeSeries)
|
result := executor.evaluateRule(rule, timeSeries)
|
||||||
So(result.State, ShouldEqual, alertstates.Ok)
|
So(result.State, ShouldEqual, alertstates.Ok)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -75,7 +75,7 @@ func TestAlertingExecutor(t *testing.T) {
|
|||||||
tsdb.NewTimeSeries("test1", [][2]float64{{1, 0}, {11, 0}}),
|
tsdb.NewTimeSeries("test1", [][2]float64{{1, 0}, {11, 0}}),
|
||||||
}
|
}
|
||||||
|
|
||||||
result := executor.validateRule(rule, timeSeries)
|
result := executor.evaluateRule(rule, timeSeries)
|
||||||
So(result.State, ShouldEqual, alertstates.Critical)
|
So(result.State, ShouldEqual, alertstates.Critical)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -89,7 +89,7 @@ func TestAlertingExecutor(t *testing.T) {
|
|||||||
tsdb.NewTimeSeries("test1", [][2]float64{{2, 0}}),
|
tsdb.NewTimeSeries("test1", [][2]float64{{2, 0}}),
|
||||||
}
|
}
|
||||||
|
|
||||||
result := executor.validateRule(rule, timeSeries)
|
result := executor.evaluateRule(rule, timeSeries)
|
||||||
So(result.State, ShouldEqual, alertstates.Ok)
|
So(result.State, ShouldEqual, alertstates.Ok)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -101,7 +101,7 @@ func TestAlertingExecutor(t *testing.T) {
|
|||||||
tsdb.NewTimeSeries("test1", [][2]float64{{11, 0}}),
|
tsdb.NewTimeSeries("test1", [][2]float64{{11, 0}}),
|
||||||
}
|
}
|
||||||
|
|
||||||
result := executor.validateRule(rule, timeSeries)
|
result := executor.evaluateRule(rule, timeSeries)
|
||||||
So(result.State, ShouldEqual, alertstates.Critical)
|
So(result.State, ShouldEqual, alertstates.Critical)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
package alerting
|
package alerting
|
||||||
|
|
||||||
import "github.com/grafana/grafana/pkg/services/alerting/alertstates"
|
|
||||||
|
|
||||||
type AlertJob struct {
|
type AlertJob struct {
|
||||||
Offset int64
|
Offset int64
|
||||||
Delay bool
|
Delay bool
|
||||||
@ -11,18 +9,14 @@ type AlertJob struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type AlertResult struct {
|
type AlertResult struct {
|
||||||
Id int64
|
|
||||||
State string
|
State string
|
||||||
ActualValue float64
|
ActualValue float64
|
||||||
Duration float64
|
Duration float64
|
||||||
Description string
|
Description string
|
||||||
|
Error error
|
||||||
AlertJob *AlertJob
|
AlertJob *AlertJob
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ar *AlertResult) IsResultIncomplete() bool {
|
|
||||||
return ar.State == alertstates.Pending
|
|
||||||
}
|
|
||||||
|
|
||||||
type AlertRule struct {
|
type AlertRule struct {
|
||||||
Id int64
|
Id int64
|
||||||
OrgId int64
|
OrgId int64
|
||||||
@ -36,7 +30,7 @@ type AlertRule struct {
|
|||||||
WarnOperator string
|
WarnOperator string
|
||||||
CritOperator string
|
CritOperator string
|
||||||
Frequency int64
|
Frequency int64
|
||||||
Title string
|
Name string
|
||||||
Description string
|
Description string
|
||||||
QueryRange int
|
QueryRange int
|
||||||
Aggregator string
|
Aggregator string
|
||||||
|
@ -60,7 +60,7 @@ func (arr *AlertRuleReader) Fetch() []*AlertRule {
|
|||||||
model.CritLevel = ruleDef.CritLevel
|
model.CritLevel = ruleDef.CritLevel
|
||||||
model.CritOperator = ruleDef.CritOperator
|
model.CritOperator = ruleDef.CritOperator
|
||||||
model.Frequency = ruleDef.Frequency
|
model.Frequency = ruleDef.Frequency
|
||||||
model.Title = ruleDef.Title
|
model.Name = ruleDef.Name
|
||||||
model.Description = ruleDef.Description
|
model.Description = ruleDef.Description
|
||||||
model.Aggregator = ruleDef.Aggregator
|
model.Aggregator = ruleDef.Aggregator
|
||||||
model.State = ruleDef.State
|
model.State = ruleDef.State
|
||||||
|
@ -47,7 +47,7 @@ func (s *SchedulerImpl) Tick(tickTime time.Time, execQueue chan *AlertJob) {
|
|||||||
|
|
||||||
for _, job := range s.jobs {
|
for _, job := range s.jobs {
|
||||||
if now%job.Rule.Frequency == 0 && job.Running == false {
|
if now%job.Rule.Frequency == 0 && job.Running == false {
|
||||||
log.Trace("Scheduler: Putting job on to exec queue: %s", job.Rule.Title)
|
log.Trace("Scheduler: Putting job on to exec queue: %s", job.Rule.Name)
|
||||||
execQueue <- job
|
execQueue <- job
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,7 @@ func TestAlertRuleChangesDataAccess(t *testing.T) {
|
|||||||
WarnOperator: ">",
|
WarnOperator: ">",
|
||||||
CritOperator: ">",
|
CritOperator: ">",
|
||||||
Frequency: 10,
|
Frequency: 10,
|
||||||
Title: "Alerting title",
|
Name: "Alerting title",
|
||||||
Description: "Alerting description",
|
Description: "Alerting description",
|
||||||
QueryRange: 3600,
|
QueryRange: 3600,
|
||||||
Aggregator: "avg",
|
Aggregator: "avg",
|
||||||
|
@ -26,7 +26,7 @@ func TestAlertingDataAccess(t *testing.T) {
|
|||||||
WarnOperator: ">",
|
WarnOperator: ">",
|
||||||
CritOperator: ">",
|
CritOperator: ">",
|
||||||
Frequency: 10,
|
Frequency: 10,
|
||||||
Title: "Alerting title",
|
Name: "Alerting title",
|
||||||
Description: "Alerting description",
|
Description: "Alerting description",
|
||||||
QueryRange: 3600,
|
QueryRange: 3600,
|
||||||
Aggregator: "avg",
|
Aggregator: "avg",
|
||||||
@ -65,7 +65,7 @@ func TestAlertingDataAccess(t *testing.T) {
|
|||||||
So(alert.CritOperator, ShouldEqual, ">")
|
So(alert.CritOperator, ShouldEqual, ">")
|
||||||
So(alert.Query, ShouldEqual, "Query")
|
So(alert.Query, ShouldEqual, "Query")
|
||||||
So(alert.QueryRefId, ShouldEqual, "A")
|
So(alert.QueryRefId, ShouldEqual, "A")
|
||||||
So(alert.Title, ShouldEqual, "Alerting title")
|
So(alert.Name, ShouldEqual, "Alerting title")
|
||||||
So(alert.Description, ShouldEqual, "Alerting description")
|
So(alert.Description, ShouldEqual, "Alerting description")
|
||||||
So(alert.QueryRange, ShouldEqual, 3600)
|
So(alert.QueryRange, ShouldEqual, 3600)
|
||||||
So(alert.Aggregator, ShouldEqual, "avg")
|
So(alert.Aggregator, ShouldEqual, "avg")
|
||||||
@ -189,7 +189,7 @@ func TestAlertingDataAccess(t *testing.T) {
|
|||||||
WarnOperator: ">",
|
WarnOperator: ">",
|
||||||
CritOperator: ">",
|
CritOperator: ">",
|
||||||
Frequency: 10,
|
Frequency: 10,
|
||||||
Title: "Alerting title",
|
Name: "Alerting title",
|
||||||
Description: "Alerting description",
|
Description: "Alerting description",
|
||||||
QueryRange: 3600,
|
QueryRange: 3600,
|
||||||
Aggregator: "avg",
|
Aggregator: "avg",
|
||||||
|
@ -25,7 +25,7 @@ func TestAlertingStateAccess(t *testing.T) {
|
|||||||
WarnOperator: ">",
|
WarnOperator: ">",
|
||||||
CritOperator: ">",
|
CritOperator: ">",
|
||||||
Frequency: 10,
|
Frequency: 10,
|
||||||
Title: "Alerting title",
|
Name: "Alerting title",
|
||||||
Description: "Alerting description",
|
Description: "Alerting description",
|
||||||
QueryRange: 3600,
|
QueryRange: 3600,
|
||||||
Aggregator: "avg",
|
Aggregator: "avg",
|
||||||
|
@ -110,7 +110,7 @@ func TestAlertModel(t *testing.T) {
|
|||||||
"aggregator": "sum",
|
"aggregator": "sum",
|
||||||
"queryRange": "10m",
|
"queryRange": "10m",
|
||||||
"frequency": 10,
|
"frequency": 10,
|
||||||
"title": "active desktop users",
|
"name": "active desktop users",
|
||||||
"description": "restart webservers"
|
"description": "restart webservers"
|
||||||
},
|
},
|
||||||
"links": []
|
"links": []
|
||||||
@ -386,7 +386,7 @@ func TestAlertModel(t *testing.T) {
|
|||||||
So(v.Query, ShouldNotBeEmpty)
|
So(v.Query, ShouldNotBeEmpty)
|
||||||
So(v.QueryRefId, ShouldNotBeEmpty)
|
So(v.QueryRefId, ShouldNotBeEmpty)
|
||||||
So(v.QueryRange, ShouldNotBeEmpty)
|
So(v.QueryRange, ShouldNotBeEmpty)
|
||||||
So(v.Title, ShouldNotBeEmpty)
|
So(v.Name, ShouldNotBeEmpty)
|
||||||
So(v.Description, ShouldNotBeEmpty)
|
So(v.Description, ShouldNotBeEmpty)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,22 +19,26 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetDataSourceById(query *m.GetDataSourceByIdQuery) error {
|
func GetDataSourceById(query *m.GetDataSourceByIdQuery) error {
|
||||||
sess := x.Limit(100, 0).Where("org_id=? AND id=?", query.OrgId, query.Id)
|
datasource := m.DataSource{OrgId: query.OrgId, Id: query.Id}
|
||||||
has, err := sess.Get(&query.Result)
|
has, err := x.Get(&datasource)
|
||||||
|
|
||||||
if !has {
|
if !has {
|
||||||
return m.ErrDataSourceNotFound
|
return m.ErrDataSourceNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
|
query.Result = &datasource
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetDataSourceByName(query *m.GetDataSourceByNameQuery) error {
|
func GetDataSourceByName(query *m.GetDataSourceByNameQuery) error {
|
||||||
sess := x.Limit(100, 0).Where("org_id=? AND name=?", query.OrgId, query.Name)
|
datasource := m.DataSource{OrgId: query.OrgId, Name: query.Name}
|
||||||
has, err := sess.Get(&query.Result)
|
has, err := x.Get(&datasource)
|
||||||
|
|
||||||
if !has {
|
if !has {
|
||||||
return m.ErrDataSourceNotFound
|
return m.ErrDataSourceNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
|
query.Result = &datasource
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ func addAlertMigrations(mg *Migrator) {
|
|||||||
{Name: "crit_level", Type: DB_Float, Nullable: false},
|
{Name: "crit_level", Type: DB_Float, Nullable: false},
|
||||||
{Name: "crit_operator", Type: DB_NVarchar, Length: 10, Nullable: false},
|
{Name: "crit_operator", Type: DB_NVarchar, Length: 10, Nullable: false},
|
||||||
{Name: "frequency", Type: DB_BigInt, Nullable: false},
|
{Name: "frequency", Type: DB_BigInt, Nullable: false},
|
||||||
{Name: "title", Type: DB_NVarchar, Length: 255, Nullable: false},
|
{Name: "name", Type: DB_NVarchar, Length: 255, Nullable: false},
|
||||||
{Name: "description", Type: DB_NVarchar, Length: 255, Nullable: false},
|
{Name: "description", Type: DB_NVarchar, Length: 255, Nullable: false},
|
||||||
{Name: "query_range", Type: DB_Int, Nullable: false},
|
{Name: "query_range", Type: DB_Int, Nullable: false},
|
||||||
{Name: "aggregator", Type: DB_NVarchar, Length: 255, Nullable: false},
|
{Name: "aggregator", Type: DB_NVarchar, Length: 255, Nullable: false},
|
||||||
@ -32,7 +32,7 @@ func addAlertMigrations(mg *Migrator) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// create table
|
// create table
|
||||||
mg.AddMigration("create alert_rule table v1", NewAddTableMigration(alertV1))
|
mg.AddMigration("create alert_rule table v2", NewAddTableMigration(alertV1))
|
||||||
|
|
||||||
alert_changes := Table{
|
alert_changes := Table{
|
||||||
Name: "alert_rule_change",
|
Name: "alert_rule_change",
|
||||||
|
@ -26,7 +26,7 @@ func (bg *Batch) process(context *QueryContext) {
|
|||||||
if executor == nil {
|
if executor == nil {
|
||||||
bg.Done = true
|
bg.Done = true
|
||||||
result := &BatchResult{
|
result := &BatchResult{
|
||||||
Error: errors.New("Could not find executor for data source type " + bg.Queries[0].DataSource.Type),
|
Error: errors.New("Could not find executor for data source type " + bg.Queries[0].DataSource.PluginId),
|
||||||
QueryResults: make(map[string]*QueryResult),
|
QueryResults: make(map[string]*QueryResult),
|
||||||
}
|
}
|
||||||
for _, query := range bg.Queries {
|
for _, query := range bg.Queries {
|
||||||
|
@ -13,12 +13,12 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getExecutorFor(dsInfo *DataSourceInfo) Executor {
|
func getExecutorFor(dsInfo *DataSourceInfo) Executor {
|
||||||
if fn, exists := registry[dsInfo.Type]; exists {
|
if fn, exists := registry[dsInfo.PluginId]; exists {
|
||||||
return fn(dsInfo)
|
return fn(dsInfo)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func RegisterExecutor(dsType string, fn GetExecutorFn) {
|
func RegisterExecutor(pluginId string, fn GetExecutorFn) {
|
||||||
registry[dsType] = fn
|
registry[pluginId] = fn
|
||||||
}
|
}
|
||||||
|
39
pkg/tsdb/fake_test.go
Normal file
39
pkg/tsdb/fake_test.go
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
package tsdb
|
||||||
|
|
||||||
|
type FakeExecutor struct {
|
||||||
|
results map[string]*QueryResult
|
||||||
|
resultsFn map[string]ResultsFn
|
||||||
|
}
|
||||||
|
|
||||||
|
type ResultsFn func(context *QueryContext) *QueryResult
|
||||||
|
|
||||||
|
func NewFakeExecutor(dsInfo *DataSourceInfo) *FakeExecutor {
|
||||||
|
return &FakeExecutor{
|
||||||
|
results: make(map[string]*QueryResult),
|
||||||
|
resultsFn: make(map[string]ResultsFn),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *FakeExecutor) Execute(queries QuerySlice, context *QueryContext) *BatchResult {
|
||||||
|
result := &BatchResult{QueryResults: make(map[string]*QueryResult)}
|
||||||
|
for _, query := range queries {
|
||||||
|
if results, has := e.results[query.RefId]; has {
|
||||||
|
result.QueryResults[query.RefId] = results
|
||||||
|
}
|
||||||
|
if testFunc, has := e.resultsFn[query.RefId]; has {
|
||||||
|
result.QueryResults[query.RefId] = testFunc(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *FakeExecutor) Return(refId string, series TimeSeriesSlice) {
|
||||||
|
e.results[refId] = &QueryResult{
|
||||||
|
RefId: refId, Series: series,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *FakeExecutor) HandleQuery(refId string, fn ResultsFn) {
|
||||||
|
e.resultsFn[refId] = fn
|
||||||
|
}
|
81
pkg/tsdb/graphite/graphite.go
Normal file
81
pkg/tsdb/graphite/graphite.go
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
package graphite
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Unknwon/log"
|
||||||
|
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||||
|
"github.com/grafana/grafana/pkg/tsdb"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GraphiteExecutor struct {
|
||||||
|
*tsdb.DataSourceInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGraphiteExecutor(dsInfo *tsdb.DataSourceInfo) tsdb.Executor {
|
||||||
|
return &GraphiteExecutor{dsInfo}
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
tsdb.RegisterExecutor("graphite", NewGraphiteExecutor)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *GraphiteExecutor) Execute(queries tsdb.QuerySlice, context *tsdb.QueryContext) *tsdb.BatchResult {
|
||||||
|
result := &tsdb.BatchResult{}
|
||||||
|
|
||||||
|
params := url.Values{
|
||||||
|
"from": []string{context.TimeRange.From},
|
||||||
|
"until": []string{context.TimeRange.To},
|
||||||
|
"format": []string{"json"},
|
||||||
|
"maxDataPoints": []string{"500"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, query := range queries {
|
||||||
|
params["target"] = []string{
|
||||||
|
getTargetFromQuery(query.Query),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
client := http.Client{Timeout: time.Duration(10 * time.Second)}
|
||||||
|
res, err := client.PostForm(e.Url+"/render?", params)
|
||||||
|
if err != nil {
|
||||||
|
result.Error = err
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
result.Error = err
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
var data []TargetResponseDTO
|
||||||
|
err = json.Unmarshal(body, &data)
|
||||||
|
if err != nil {
|
||||||
|
log.Info("Error: %v", string(body))
|
||||||
|
result.Error = err
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
result.QueryResults = make(map[string]*tsdb.QueryResult)
|
||||||
|
queryRes := &tsdb.QueryResult{}
|
||||||
|
for _, series := range data {
|
||||||
|
queryRes.Series = append(queryRes.Series, &tsdb.TimeSeries{
|
||||||
|
Name: series.Target,
|
||||||
|
Points: series.DataPoints,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
result.QueryResults["A"] = queryRes
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTargetFromQuery(query string) string {
|
||||||
|
json, _ := simplejson.NewJson([]byte(query))
|
||||||
|
return json.Get("target").MustString()
|
||||||
|
}
|
31
pkg/tsdb/graphite/graphite_test.go
Normal file
31
pkg/tsdb/graphite/graphite_test.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package graphite
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/tsdb"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGraphite(t *testing.T) {
|
||||||
|
|
||||||
|
Convey("When executing graphite query", t, func() {
|
||||||
|
executor := NewGraphiteExecutor(&tsdb.DataSourceInfo{
|
||||||
|
Url: "http://localhost:8080",
|
||||||
|
})
|
||||||
|
|
||||||
|
queries := tsdb.QuerySlice{
|
||||||
|
&tsdb.Query{Query: "apps.backend.*.counters.requests.count"},
|
||||||
|
}
|
||||||
|
context := tsdb.NewQueryContext(queries, tsdb.TimeRange{})
|
||||||
|
|
||||||
|
result := executor.Execute(queries, context)
|
||||||
|
So(result.Error, ShouldBeNil)
|
||||||
|
|
||||||
|
Convey("Should return series", func() {
|
||||||
|
So(result.QueryResults, ShouldNotBeEmpty)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
6
pkg/tsdb/graphite/types.go
Normal file
6
pkg/tsdb/graphite/types.go
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
package graphite
|
||||||
|
|
||||||
|
type TargetResponseDTO struct {
|
||||||
|
Target string `json:"target"`
|
||||||
|
DataPoints [][2]float64 `json:"datapoints"`
|
||||||
|
}
|
@ -1,10 +1,8 @@
|
|||||||
package tsdb
|
package tsdb
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
type TimeRange struct {
|
type TimeRange struct {
|
||||||
From time.Time
|
From string
|
||||||
To time.Time
|
To string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Request struct {
|
type Request struct {
|
||||||
@ -21,7 +19,7 @@ type Response struct {
|
|||||||
type DataSourceInfo struct {
|
type DataSourceInfo struct {
|
||||||
Id int64
|
Id int64
|
||||||
Name string
|
Name string
|
||||||
Type string
|
PluginId string
|
||||||
Url string
|
Url string
|
||||||
Password string
|
Password string
|
||||||
User string
|
User string
|
||||||
|
@ -27,6 +27,10 @@ func HandleRequest(req *Request) (*Response, error) {
|
|||||||
|
|
||||||
response.BatchTimings = append(response.BatchTimings, batchResult.Timings)
|
response.BatchTimings = append(response.BatchTimings, batchResult.Timings)
|
||||||
|
|
||||||
|
if batchResult.Error != nil {
|
||||||
|
return nil, batchResult.Error
|
||||||
|
}
|
||||||
|
|
||||||
for refId, result := range batchResult.QueryResults {
|
for refId, result := range batchResult.QueryResults {
|
||||||
context.Results[refId] = result
|
context.Results[refId] = result
|
||||||
}
|
}
|
||||||
|
177
pkg/tsdb/tsdb_test.go
Normal file
177
pkg/tsdb/tsdb_test.go
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
package tsdb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMetricQuery(t *testing.T) {
|
||||||
|
|
||||||
|
Convey("When batches groups for query", t, func() {
|
||||||
|
|
||||||
|
Convey("Given 3 queries for 2 data sources", func() {
|
||||||
|
request := &Request{
|
||||||
|
Queries: QuerySlice{
|
||||||
|
{RefId: "A", Query: "asd", DataSource: &DataSourceInfo{Id: 1}},
|
||||||
|
{RefId: "B", Query: "asd", DataSource: &DataSourceInfo{Id: 1}},
|
||||||
|
{RefId: "C", Query: "asd", DataSource: &DataSourceInfo{Id: 2}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
batches, err := getBatches(request)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
Convey("Should group into two batches", func() {
|
||||||
|
So(len(batches), ShouldEqual, 2)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Given query 2 depends on query 1", func() {
|
||||||
|
request := &Request{
|
||||||
|
Queries: QuerySlice{
|
||||||
|
{RefId: "A", Query: "asd", DataSource: &DataSourceInfo{Id: 1}},
|
||||||
|
{RefId: "B", Query: "asd", DataSource: &DataSourceInfo{Id: 2}},
|
||||||
|
{RefId: "C", Query: "#A / #B", DataSource: &DataSourceInfo{Id: 3}, Depends: []string{"A", "B"}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
batches, err := getBatches(request)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
Convey("Should return three batch groups", func() {
|
||||||
|
So(len(batches), ShouldEqual, 3)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Group 3 should have group 1 and 2 as dependencies", func() {
|
||||||
|
So(batches[2].Depends["A"], ShouldEqual, true)
|
||||||
|
So(batches[2].Depends["B"], ShouldEqual, true)
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("When executing request with one query", t, func() {
|
||||||
|
req := &Request{
|
||||||
|
Queries: QuerySlice{
|
||||||
|
{RefId: "A", Query: "asd", DataSource: &DataSourceInfo{Id: 1, Type: "test"}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
fakeExecutor := registerFakeExecutor()
|
||||||
|
fakeExecutor.Return("A", TimeSeriesSlice{&TimeSeries{Name: "argh"}})
|
||||||
|
|
||||||
|
res, err := HandleRequest(req)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
Convey("Should return query results", func() {
|
||||||
|
So(res.Results["A"].Series, ShouldNotBeEmpty)
|
||||||
|
So(res.Results["A"].Series[0].Name, ShouldEqual, "argh")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("When executing one request with two queries from same data source", t, func() {
|
||||||
|
req := &Request{
|
||||||
|
Queries: QuerySlice{
|
||||||
|
{RefId: "A", Query: "asd", DataSource: &DataSourceInfo{Id: 1, Type: "test"}},
|
||||||
|
{RefId: "B", Query: "asd", DataSource: &DataSourceInfo{Id: 1, Type: "test"}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
fakeExecutor := registerFakeExecutor()
|
||||||
|
fakeExecutor.Return("A", TimeSeriesSlice{&TimeSeries{Name: "argh"}})
|
||||||
|
fakeExecutor.Return("B", TimeSeriesSlice{&TimeSeries{Name: "barg"}})
|
||||||
|
|
||||||
|
res, err := HandleRequest(req)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
Convey("Should return query results", func() {
|
||||||
|
So(len(res.Results), ShouldEqual, 2)
|
||||||
|
So(res.Results["B"].Series[0].Name, ShouldEqual, "barg")
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Should have been batched in one request", func() {
|
||||||
|
So(len(res.BatchTimings), ShouldEqual, 1)
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("When executing one request with three queries from different datasources", t, func() {
|
||||||
|
req := &Request{
|
||||||
|
Queries: QuerySlice{
|
||||||
|
{RefId: "A", Query: "asd", DataSource: &DataSourceInfo{Id: 1, Type: "test"}},
|
||||||
|
{RefId: "B", Query: "asd", DataSource: &DataSourceInfo{Id: 1, Type: "test"}},
|
||||||
|
{RefId: "C", Query: "asd", DataSource: &DataSourceInfo{Id: 2, Type: "test"}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := HandleRequest(req)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
Convey("Should have been batched in two requests", func() {
|
||||||
|
So(len(res.BatchTimings), ShouldEqual, 2)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("When query uses data source of unknown type", t, func() {
|
||||||
|
req := &Request{
|
||||||
|
Queries: QuerySlice{
|
||||||
|
{RefId: "A", Query: "asd", DataSource: &DataSourceInfo{Id: 1, Type: "asdasdas"}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := HandleRequest(req)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
Convey("Should return error", func() {
|
||||||
|
So(res.Results["A"].Error.Error(), ShouldContainSubstring, "not find")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("When executing request that depend on other query", t, func() {
|
||||||
|
req := &Request{
|
||||||
|
Queries: QuerySlice{
|
||||||
|
{RefId: "A", Query: "asd", DataSource: &DataSourceInfo{Id: 1, Type: "test"}},
|
||||||
|
{RefId: "B", Query: "#A / 2", DataSource: &DataSourceInfo{Id: 2, Type: "test"},
|
||||||
|
Depends: []string{"A"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
fakeExecutor := registerFakeExecutor()
|
||||||
|
fakeExecutor.HandleQuery("A", func(c *QueryContext) *QueryResult {
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
return &QueryResult{
|
||||||
|
Series: TimeSeriesSlice{
|
||||||
|
&TimeSeries{Name: "Ares"},
|
||||||
|
}}
|
||||||
|
})
|
||||||
|
fakeExecutor.HandleQuery("B", func(c *QueryContext) *QueryResult {
|
||||||
|
return &QueryResult{
|
||||||
|
Series: TimeSeriesSlice{
|
||||||
|
&TimeSeries{Name: "Bres+" + c.Results["A"].Series[0].Name},
|
||||||
|
}}
|
||||||
|
})
|
||||||
|
|
||||||
|
res, err := HandleRequest(req)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
Convey("Should have been batched in two requests", func() {
|
||||||
|
So(len(res.BatchTimings), ShouldEqual, 2)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Query B should have access to Query A results", func() {
|
||||||
|
So(res.Results["B"].Series[0].Name, ShouldEqual, "Bres+Ares")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func registerFakeExecutor() *FakeExecutor {
|
||||||
|
executor := NewFakeExecutor(nil)
|
||||||
|
RegisterExecutor("test", func(dsInfo *DataSourceInfo) Executor {
|
||||||
|
return executor
|
||||||
|
})
|
||||||
|
|
||||||
|
return executor
|
||||||
|
}
|
@ -21,7 +21,7 @@
|
|||||||
<tr ng-repeat="alert in ctrl.alerts">
|
<tr ng-repeat="alert in ctrl.alerts">
|
||||||
<td>
|
<td>
|
||||||
<a href="alerting/{{alert.id}}/states">
|
<a href="alerting/{{alert.id}}/states">
|
||||||
{{alert.title}}
|
{{alert.name}}
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
|
@ -29,8 +29,8 @@ export class AlertTabCtrl {
|
|||||||
|
|
||||||
_.defaults(this.panel.alerting, this.defaultValues);
|
_.defaults(this.panel.alerting, this.defaultValues);
|
||||||
|
|
||||||
var defaultTitle = (this.panelCtrl.dashboard.title + ' ' + this.panel.title + ' alert');
|
var defaultName = (this.panelCtrl.dashboard.title + ' ' + this.panel.title + ' alert');
|
||||||
this.panel.alerting.title = this.panel.alerting.title || defaultTitle;
|
this.panel.alerting.name = this.panel.alerting.name || defaultName;
|
||||||
|
|
||||||
this.panel.targets.map(target => {
|
this.panel.targets.map(target => {
|
||||||
this.metricTargets.push(target);
|
this.metricTargets.push(target);
|
||||||
|
@ -60,7 +60,7 @@
|
|||||||
<h5 class="section-heading">Alert info</h5>
|
<h5 class="section-heading">Alert info</h5>
|
||||||
<div class="gf-form">
|
<div class="gf-form">
|
||||||
<span class="gf-form-label width-10">Alert name</span>
|
<span class="gf-form-label width-10">Alert name</span>
|
||||||
<input type="text" class="gf-form-input width-22" ng-model="ctrl.panel.alerting.title">
|
<input type="text" class="gf-form-input width-22" ng-model="ctrl.panel.alerting.name">
|
||||||
</div>
|
</div>
|
||||||
<div class="gf-form-inline">
|
<div class="gf-form-inline">
|
||||||
<div class="gf-form">
|
<div class="gf-form">
|
||||||
|
Reference in New Issue
Block a user