mirror of
https://github.com/prometheus/prometheus.git
synced 2024-12-24 21:24:05 -08:00
api v1 alerts/rules json endpoint
Signed-off-by: mg03 <mgeng03@gmail.com>
This commit is contained in:
parent
ffb7836c14
commit
31f8ca0dfb
|
@ -131,6 +131,41 @@ func (r *AlertingRule) Name() string {
|
|||
return r.name
|
||||
}
|
||||
|
||||
// Query returns the query expression of the alert.
|
||||
func (r *AlertingRule) Query() promql.Expr {
|
||||
return r.vector
|
||||
}
|
||||
|
||||
// Duration returns the hold duration of the alert.
|
||||
func (r *AlertingRule) Duration() time.Duration {
|
||||
return r.holdDuration
|
||||
}
|
||||
|
||||
// Labels returns the labels of the alert.
|
||||
func (r *AlertingRule) Labels() labels.Labels {
|
||||
return r.labels
|
||||
}
|
||||
|
||||
// Annotations returns the annotations of the alert.
|
||||
func (r *AlertingRule) Annotations() labels.Labels {
|
||||
return r.annotations
|
||||
}
|
||||
|
||||
// Alertinfo return an array of alerts
|
||||
func (r *AlertingRule) Alertinfo() []*Alert {
|
||||
activealerts := &r.active
|
||||
alertsarr := make([]*Alert, 0)
|
||||
if len(*activealerts) > 0 {
|
||||
for _, a := range *activealerts {
|
||||
if a.ResolvedAt.IsZero() {
|
||||
alertsarr = append(alertsarr, a)
|
||||
}
|
||||
}
|
||||
return alertsarr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *AlertingRule) equal(o *AlertingRule) bool {
|
||||
return r.name == o.name && labels.Equal(r.labels, o.labels)
|
||||
}
|
||||
|
|
|
@ -41,6 +41,7 @@ import (
|
|||
"github.com/prometheus/prometheus/pkg/timestamp"
|
||||
"github.com/prometheus/prometheus/prompb"
|
||||
"github.com/prometheus/prometheus/promql"
|
||||
"github.com/prometheus/prometheus/rules"
|
||||
"github.com/prometheus/prometheus/scrape"
|
||||
"github.com/prometheus/prometheus/storage"
|
||||
"github.com/prometheus/prometheus/storage/remote"
|
||||
|
@ -95,6 +96,19 @@ type alertmanagerRetriever interface {
|
|||
DroppedAlertmanagers() []*url.URL
|
||||
}
|
||||
|
||||
type alertRetreiver interface {
|
||||
AlertingRules() []*rules.AlertingRule
|
||||
}
|
||||
|
||||
type rulesRetreiver interface {
|
||||
RuleGroups() []*rules.Group
|
||||
}
|
||||
|
||||
type alertsrulesRetreiver interface {
|
||||
alertRetreiver
|
||||
rulesRetreiver
|
||||
}
|
||||
|
||||
type response struct {
|
||||
Status status `json:"status"`
|
||||
Data interface{} `json:"data,omitempty"`
|
||||
|
@ -119,7 +133,7 @@ type API struct {
|
|||
|
||||
targetRetriever targetRetriever
|
||||
alertmanagerRetriever alertmanagerRetriever
|
||||
|
||||
alertsrulesRetreiver alertsrulesRetreiver
|
||||
now func() time.Time
|
||||
config func() config.Config
|
||||
flagsMap map[string]string
|
||||
|
@ -142,6 +156,7 @@ func NewAPI(
|
|||
db func() *tsdb.DB,
|
||||
enableAdmin bool,
|
||||
logger log.Logger,
|
||||
al alertsrulesRetreiver,
|
||||
) *API {
|
||||
return &API{
|
||||
QueryEngine: qe,
|
||||
|
@ -154,6 +169,7 @@ func NewAPI(
|
|||
ready: readyFunc,
|
||||
db: db,
|
||||
enableAdmin: enableAdmin,
|
||||
alertsrulesRetreiver: al,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -199,6 +215,9 @@ func (api *API) Register(r *route.Router) {
|
|||
r.Get("/status/flags", wrap(api.serveFlags))
|
||||
r.Post("/read", api.ready(http.HandlerFunc(api.remoteRead)))
|
||||
|
||||
r.Get("/alerts", wrap(api.alerts))
|
||||
r.Get("/rules", wrap(api.rules))
|
||||
|
||||
// Admin APIs
|
||||
r.Post("/admin/tsdb/delete_series", wrap(api.deleteSeries))
|
||||
r.Post("/admin/tsdb/clean_tombstones", wrap(api.cleanTombstones))
|
||||
|
@ -578,6 +597,94 @@ func (api *API) alertmanagers(r *http.Request) (interface{}, *apiError, func())
|
|||
return ams, nil, nil
|
||||
}
|
||||
|
||||
// AlertDiscovery has info for all alerts
|
||||
type AlertDiscovery struct {
|
||||
Alertgrps []*Alertgrp `json:"alertgrp"`
|
||||
}
|
||||
|
||||
// Alert has info for a alert
|
||||
type Alert struct {
|
||||
Labels labels.Labels `json:"labels"`
|
||||
Status string `json:"status"`
|
||||
Activesince *time.Time `json:"activesince,omitempty"`
|
||||
}
|
||||
|
||||
// Alertgrp has info for alerts part of a group
|
||||
type Alertgrp struct {
|
||||
Name string `json:"name"`
|
||||
Query string `json:"query"`
|
||||
Duration string `json:"duration"`
|
||||
Annotations labels.Labels `json:"annotations,omitempty"`
|
||||
Alerts []*Alert `json:"alerts"`
|
||||
}
|
||||
|
||||
func (api *API) alerts(r *http.Request) (interface{}, *apiError) {
|
||||
alertingrules := api.alertsrulesRetreiver.AlertingRules()
|
||||
var alertgrps []*Alertgrp
|
||||
res := &AlertDiscovery{Alertgrps: alertgrps}
|
||||
for _, activerule := range alertingrules {
|
||||
t := &Alertgrp{
|
||||
Name: activerule.Name(),
|
||||
Query: fmt.Sprintf("%v", activerule.Query()),
|
||||
Duration: activerule.Duration().String(),
|
||||
Annotations: activerule.Annotations(),
|
||||
}
|
||||
alerts := activerule.Alertinfo()
|
||||
var activealerts []*Alert
|
||||
for _, alert := range alerts {
|
||||
q := &Alert{
|
||||
Labels: alert.Labels,
|
||||
Status: alert.State.String(),
|
||||
Activesince: &alert.ActiveAt,
|
||||
}
|
||||
|
||||
activealerts = append(activealerts, q)
|
||||
}
|
||||
t.Alerts = activealerts
|
||||
res.Alertgrps = append(res.Alertgrps, t)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// GroupDiscovery has info for all rules
|
||||
type GroupDiscovery struct {
|
||||
Rulegrps []*Rulegrp `json:"groups"`
|
||||
}
|
||||
|
||||
// Rulegrp has info for rules which are part of a group
|
||||
type Rulegrp struct {
|
||||
Name string `json:"name"`
|
||||
File string `json:"file"`
|
||||
Rules []*Ruleinfo `json:"rules"`
|
||||
}
|
||||
|
||||
// Ruleinfo has rule in human readable format using \n as line separators
|
||||
type Ruleinfo struct {
|
||||
Rule string `json:"rule"`
|
||||
}
|
||||
|
||||
func (api *API) rules(r *http.Request) (interface{}, *apiError) {
|
||||
grps := api.alertsrulesRetreiver.RuleGroups()
|
||||
res := &GroupDiscovery{Rulegrps: make([]*Rulegrp, len(grps))}
|
||||
for i, grp := range grps {
|
||||
t := &Rulegrp{
|
||||
Name: grp.Name(),
|
||||
File: grp.File(),
|
||||
}
|
||||
var rulearr []*Ruleinfo
|
||||
for _, rule := range grp.Rules() {
|
||||
q := &Ruleinfo{
|
||||
Rule: rule.String(),
|
||||
}
|
||||
rulearr = append(rulearr, q)
|
||||
}
|
||||
t.Rules = rulearr
|
||||
res.Rulegrps[i] = t
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
type prometheusConfig struct {
|
||||
YAML string `json:"yaml"`
|
||||
}
|
||||
|
|
|
@ -19,7 +19,9 @@ import (
|
|||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/go-kit/kit/log"
|
||||
"io/ioutil"
|
||||
stdlog "log"
|
||||
"math"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
|
@ -41,9 +43,11 @@ import (
|
|||
"github.com/prometheus/prometheus/pkg/timestamp"
|
||||
"github.com/prometheus/prometheus/prompb"
|
||||
"github.com/prometheus/prometheus/promql"
|
||||
"github.com/prometheus/prometheus/rules"
|
||||
"github.com/prometheus/prometheus/scrape"
|
||||
"github.com/prometheus/prometheus/storage"
|
||||
"github.com/prometheus/prometheus/storage/remote"
|
||||
"github.com/prometheus/prometheus/util/testutil"
|
||||
)
|
||||
|
||||
type testTargetRetriever struct{}
|
||||
|
@ -98,6 +102,68 @@ func (t testAlertmanagerRetriever) DroppedAlertmanagers() []*url.URL {
|
|||
}
|
||||
}
|
||||
|
||||
type testalertsrulesfunc struct {
|
||||
test *testing.T
|
||||
}
|
||||
|
||||
func (t testalertsrulesfunc) AlertingRules() []*rules.AlertingRule {
|
||||
expr1, err := promql.ParseExpr(`absent(test_metric3) != 1`)
|
||||
if err != nil {
|
||||
stdlog.Fatalf("Unable to parse alert expression: %s", err)
|
||||
}
|
||||
expr2, err := promql.ParseExpr(`up == 1`)
|
||||
if err != nil {
|
||||
stdlog.Fatalf("Unable to parse alert expression: %s", err)
|
||||
}
|
||||
|
||||
rule1 := rules.NewAlertingRule(
|
||||
"test_metric3",
|
||||
expr1,
|
||||
time.Second,
|
||||
labels.Labels{},
|
||||
labels.Labels{},
|
||||
log.NewNopLogger(),
|
||||
)
|
||||
rule2 := rules.NewAlertingRule(
|
||||
"test_metric4",
|
||||
expr2,
|
||||
time.Second,
|
||||
labels.Labels{},
|
||||
labels.Labels{},
|
||||
log.NewNopLogger(),
|
||||
)
|
||||
var r []*rules.AlertingRule
|
||||
r = append(r, rule1)
|
||||
r = append(r, rule2)
|
||||
return r
|
||||
}
|
||||
|
||||
func (t testalertsrulesfunc) RuleGroups() []*rules.Group {
|
||||
var ar testalertsrulesfunc
|
||||
arules := ar.AlertingRules()
|
||||
storage := testutil.NewStorage(t.test)
|
||||
defer storage.Close()
|
||||
|
||||
engine := promql.NewEngine(nil, nil, 10, 10*time.Second)
|
||||
opts := &rules.ManagerOptions{
|
||||
QueryFunc: rules.EngineQueryFunc(engine, storage),
|
||||
Appendable: storage,
|
||||
Context: context.Background(),
|
||||
Logger: log.NewNopLogger(),
|
||||
}
|
||||
|
||||
var r []rules.Rule
|
||||
|
||||
for _, alertrule := range arules {
|
||||
r = append(r, alertrule)
|
||||
}
|
||||
|
||||
group := rules.NewGroup("grp", "/path/to/file", time.Second, r, opts)
|
||||
fmt.Println(group)
|
||||
return []*rules.Group{group}
|
||||
|
||||
}
|
||||
|
||||
var samplePrometheusCfg = config.Config{
|
||||
GlobalConfig: config.GlobalConfig{},
|
||||
AlertingConfig: config.AlertingConfig{},
|
||||
|
@ -131,6 +197,14 @@ func TestEndpoints(t *testing.T) {
|
|||
now := time.Now()
|
||||
|
||||
t.Run("local", func(t *testing.T) {
|
||||
|
||||
var algr testalertsrulesfunc
|
||||
algr.test = t
|
||||
|
||||
algr.AlertingRules()
|
||||
|
||||
algr.RuleGroups()
|
||||
|
||||
api := &API{
|
||||
Queryable: suite.Storage(),
|
||||
QueryEngine: suite.QueryEngine(),
|
||||
|
@ -140,6 +214,7 @@ func TestEndpoints(t *testing.T) {
|
|||
config: func() config.Config { return samplePrometheusCfg },
|
||||
flagsMap: sampleFlagMap,
|
||||
ready: func(f http.HandlerFunc) http.HandlerFunc { return f },
|
||||
alertsrulesRetreiver: algr,
|
||||
}
|
||||
|
||||
testEndpoints(t, api, true)
|
||||
|
@ -176,6 +251,13 @@ func TestEndpoints(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var algr testalertsrulesfunc
|
||||
algr.test = t
|
||||
|
||||
algr.AlertingRules()
|
||||
|
||||
algr.RuleGroups()
|
||||
|
||||
api := &API{
|
||||
Queryable: remote,
|
||||
QueryEngine: suite.QueryEngine(),
|
||||
|
@ -185,6 +267,7 @@ func TestEndpoints(t *testing.T) {
|
|||
config: func() config.Config { return samplePrometheusCfg },
|
||||
flagsMap: sampleFlagMap,
|
||||
ready: func(f http.HandlerFunc) http.HandlerFunc { return f },
|
||||
alertsrulesRetreiver: algr,
|
||||
}
|
||||
|
||||
testEndpoints(t, api, false)
|
||||
|
@ -237,7 +320,6 @@ func setupRemote(s storage.Storage) *httptest.Server {
|
|||
}
|
||||
|
||||
func testEndpoints(t *testing.T, api *API, testLabelAPI bool) {
|
||||
|
||||
start := time.Unix(0, 0)
|
||||
|
||||
type test struct {
|
||||
|
@ -567,6 +649,46 @@ func testEndpoints(t *testing.T, api *API, testLabelAPI bool) {
|
|||
endpoint: api.serveFlags,
|
||||
response: sampleFlagMap,
|
||||
},
|
||||
{
|
||||
endpoint: api.alerts,
|
||||
response: &AlertDiscovery{
|
||||
Alertgrps: []*Alertgrp{
|
||||
{
|
||||
Name: "test_metric3",
|
||||
Query: "absent(test_metric3) != 1",
|
||||
Duration: "1s",
|
||||
Alerts: nil,
|
||||
Annotations: labels.Labels{},
|
||||
},
|
||||
{
|
||||
Name: "test_metric4",
|
||||
Query: "up == 1",
|
||||
Duration: "1s",
|
||||
Alerts: nil,
|
||||
Annotations: labels.Labels{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
endpoint: api.rules,
|
||||
response: &GroupDiscovery{
|
||||
Rulegrps: []*Rulegrp{
|
||||
{
|
||||
Name: "grp",
|
||||
File: "/path/to/file",
|
||||
Rules: []*Ruleinfo{
|
||||
{
|
||||
Rule: "alert: test_metric3\nexpr: absent(test_metric3) != 1\nfor: 1s\n",
|
||||
},
|
||||
{
|
||||
Rule: "alert: test_metric4\nexpr: up == 1\nfor: 1s\n",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if testLabelAPI {
|
||||
|
|
|
@ -228,6 +228,7 @@ func New(logger log.Logger, o *Options) *Handler {
|
|||
h.options.TSDB,
|
||||
h.options.EnableAdminAPI,
|
||||
logger,
|
||||
h.ruleManager,
|
||||
)
|
||||
|
||||
if o.RoutePrefix != "/" {
|
||||
|
|
Loading…
Reference in a new issue