diff --git a/app/vmalert/rule/alerting.go b/app/vmalert/rule/alerting.go index ae10f4fe3b..93584930d5 100644 --- a/app/vmalert/rule/alerting.go +++ b/app/vmalert/rule/alerting.go @@ -30,6 +30,7 @@ type AlertingRule struct { Annotations map[string]string GroupID uint64 GroupName string + File string EvalInterval time.Duration Debug bool @@ -67,6 +68,7 @@ func NewAlertingRule(qb datasource.QuerierBuilder, group *Group, cfg config.Rule Annotations: cfg.Annotations, GroupID: group.ID(), GroupName: group.Name, + File: group.File, EvalInterval: group.Interval, Debug: cfg.Debug, q: qb.BuildWithParams(datasource.QuerierParams{ diff --git a/app/vmalert/rule/recording.go b/app/vmalert/rule/recording.go index e74d1b8309..196f51456a 100644 --- a/app/vmalert/rule/recording.go +++ b/app/vmalert/rule/recording.go @@ -17,12 +17,14 @@ import ( // to evaluate configured Expression and // return TimeSeries as result. type RecordingRule struct { - Type config.Type - RuleID uint64 - Name string - Expr string - Labels map[string]string - GroupID uint64 + Type config.Type + RuleID uint64 + Name string + Expr string + Labels map[string]string + GroupID uint64 + GroupName string + File string q datasource.Querier @@ -52,13 +54,15 @@ func (rr *RecordingRule) ID() uint64 { // NewRecordingRule creates a new RecordingRule func NewRecordingRule(qb datasource.QuerierBuilder, group *Group, cfg config.Rule) *RecordingRule { rr := &RecordingRule{ - Type: group.Type, - RuleID: cfg.ID, - Name: cfg.Record, - Expr: cfg.Expr, - Labels: cfg.Labels, - GroupID: group.ID(), - metrics: &recordingRuleMetrics{}, + Type: group.Type, + RuleID: cfg.ID, + Name: cfg.Record, + Expr: cfg.Expr, + Labels: cfg.Labels, + GroupID: group.ID(), + GroupName: group.Name, + File: group.File, + metrics: &recordingRuleMetrics{}, q: qb.BuildWithParams(datasource.QuerierParams{ DataSourceType: group.Type.String(), EvaluationInterval: group.Interval, diff --git a/app/vmalert/rule/rule.go b/app/vmalert/rule/rule.go index 10728cff65..0bad2ff65f 100644 --- a/app/vmalert/rule/rule.go +++ b/app/vmalert/rule/rule.go @@ -43,26 +43,26 @@ type ruleState struct { // StateEntry stores rule's execution states type StateEntry struct { // stores last moment of time rule.Exec was called - Time time.Time + Time time.Time `json:"time"` // stores the timesteamp with which rule.Exec was called - At time.Time + At time.Time `json:"at"` // stores the duration of the last rule.Exec call - Duration time.Duration + Duration time.Duration `json:"duration"` // stores last error that happened in Exec func // resets on every successful Exec // may be used as Health ruleState - Err error + Err error `json:"error"` // stores the number of samples returned during // the last evaluation - Samples int + Samples int `json:"samples"` // stores the number of time series fetched during // the last evaluation. // Is supported by VictoriaMetrics only, starting from v1.90.0 // If seriesFetched == nil, then this attribute was missing in // datasource response (unsupported). - SeriesFetched *int + SeriesFetched *int `json:"series_fetched"` // stores the curl command reflecting the HTTP request used during rule.Exec - Curl string + Curl string `json:"curl"` } // GetLastEntry returns latest stateEntry of rule diff --git a/app/vmalert/web.go b/app/vmalert/web.go index f34bad1971..04004a4467 100644 --- a/app/vmalert/web.go +++ b/app/vmalert/web.go @@ -132,6 +132,24 @@ func (rh *requestHandler) handler(w http.ResponseWriter, r *http.Request) bool { w.Header().Set("Content-Type", "application/json") w.Write(data) return true + case "/vmalert/api/v1/rule", "/api/v1/rule": + rule, err := rh.getRule(r) + if err != nil { + httpserver.Errorf(w, r, "%s", err) + return true + } + rwu := apiRuleWithUpdates{ + apiRule: rule, + StateUpdates: rule.Updates, + } + data, err := json.Marshal(rwu) + if err != nil { + httpserver.Errorf(w, r, "failed to marshal rule: %s", err) + return true + } + w.Header().Set("Content-Type", "application/json") + w.Write(data) + return true case "/-/reload": logger.Infof("api config reload was called, sending sighup") procutil.SelfSIGHUP() diff --git a/app/vmalert/web_test.go b/app/vmalert/web_test.go index c55ef5cbc5..12beb63fab 100644 --- a/app/vmalert/web_test.go +++ b/app/vmalert/web_test.go @@ -143,6 +143,28 @@ func TestHandler(t *testing.T) { t.Errorf("expected 1 group got %d", length) } }) + t.Run("/api/v1/rule?ruleID&groupID", func(t *testing.T) { + expRule := ruleToAPI(ar) + gotRule := apiRule{} + getResp(ts.URL+"/"+expRule.APILink(), &gotRule, 200) + + if expRule.ID != gotRule.ID { + t.Errorf("expected to get Rule %q; got %q instead", expRule.ID, gotRule.ID) + } + + gotRule = apiRule{} + getResp(ts.URL+"/vmalert/"+expRule.APILink(), &gotRule, 200) + + if expRule.ID != gotRule.ID { + t.Errorf("expected to get Rule %q; got %q instead", expRule.ID, gotRule.ID) + } + + gotRuleWithUpdates := apiRuleWithUpdates{} + getResp(ts.URL+"/"+expRule.APILink(), &gotRuleWithUpdates, 200) + if gotRuleWithUpdates.StateUpdates == nil || len(gotRuleWithUpdates.StateUpdates) < 1 { + t.Fatalf("expected %+v to have state updates field not empty", gotRuleWithUpdates.StateUpdates) + } + }) } func TestEmptyResponse(t *testing.T) { diff --git a/app/vmalert/web_types.go b/app/vmalert/web_types.go index 62d17f379b..61c0d21912 100644 --- a/app/vmalert/web_types.go +++ b/app/vmalert/web_types.go @@ -151,6 +151,10 @@ type apiRule struct { ID string `json:"id"` // GroupID is an unique Group's ID GroupID string `json:"group_id"` + // GroupName is Group name rule belong to + GroupName string `json:"group_name"` + // File is file name where rule is defined + File string `json:"file"` // Debug shows whether debug mode is enabled Debug bool `json:"debug"` @@ -160,6 +164,19 @@ type apiRule struct { Updates []rule.StateEntry `json:"-"` } +// apiRuleWithUpdates represents apiRule but with extra fields for marshalling +type apiRuleWithUpdates struct { + apiRule + // Updates contains the ordered list of recorded ruleStateEntry objects + StateUpdates []rule.StateEntry `json:"updates,omitempty"` +} + +// APILink returns a link to the rule's JSON representation. +func (ar apiRule) APILink() string { + return fmt.Sprintf("api/v1/rule?%s=%s&%s=%s", + paramGroupID, ar.GroupID, paramRuleID, ar.ID) +} + // WebLink returns a link to the alert which can be used in UI. func (ar apiRule) WebLink() string { return fmt.Sprintf("rule?%s=%s&%s=%s", @@ -227,8 +244,10 @@ func alertingToAPI(ar *rule.AlertingRule) apiRule { Debug: ar.Debug, // encode as strings to avoid rounding in JSON - ID: fmt.Sprintf("%d", ar.ID()), - GroupID: fmt.Sprintf("%d", ar.GroupID), + ID: fmt.Sprintf("%d", ar.ID()), + GroupID: fmt.Sprintf("%d", ar.GroupID), + GroupName: ar.GroupName, + File: ar.File, } if lastState.Err != nil { r.LastError = lastState.Err.Error() diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 63cd4c3b0c..a77acfb099 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -37,6 +37,7 @@ The sandbox cluster installation is running under the constant load generated by * FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent.html): show all the dropped targets together with the reason why they are dropped at `http://vmagent:8429/service-discovery` page. Previously targets, which were dropped because of [target sharding](https://docs.victoriametrics.com/vmagent.html#scraping-big-number-of-targets) weren't displayed on this page. This could complicate service discovery debugging. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/5389). * FEATURE: reduce the default value for `-import.maxLineLen` command-line flag from 100MB to 10MB in order to prevent excessive memory usage during data import via [/api/v1/import](https://docs.victoriametrics.com/#how-to-import-data-in-json-line-format). * FEATURE: [vmagent](https://docs.victoriametrics.com/vmagent.html): add `keep_if_contains` and `drop_if_contains` relabeling actions. See [these docs](https://docs.victoriametrics.com/vmagent.html#relabeling-enhancements) for details. +* FEATURE: [vmalert](https://docs.victoriametrics.com/vmalert.html): provide `/vmalert/api/v1/rule` and `/api/v1/rule` API endpoints to get the rule object in JSON format. See [these docs](https://docs.victoriametrics.com/vmalert.html#web) for details. * FEATURE: [MetricsQL](https://docs.victoriametrics.com/MetricsQL.html): add [day_of_year()](https://docs.victoriametrics.com/MetricsQL.html#day_of_year) function, which returns the day of the year for each of the given unix timestamps. See [this issue](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/5345) for details. Thanks to @luckyxiaoqiang for the [pull request](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/5368/). * FEATURE: all VictoriaMetrics binaries: expose additional metrics at `/metrics` page, which may simplify debugging of VictoriaMetrics components (see [this feature request](https://github.com/VictoriaMetrics/metrics/issues/54)): * `go_sched_latencies_seconds` - the [histogram](https://docs.victoriametrics.com/keyConcepts.html#histogram), which shows the time goroutines have spent in runnable state before actually running. Big values point to the lack of CPU time for the current workload. diff --git a/docs/vmalert.md b/docs/vmalert.md index 4ebf795506..ce932df417 100644 --- a/docs/vmalert.md +++ b/docs/vmalert.md @@ -659,6 +659,7 @@ or time series modification via [relabeling](https://docs.victoriametrics.com/vm Used as alert source in AlertManager. * `http://<vmalert-addr>/vmalert/alert?group_id=<group_id>&alert_id=<alert_id>` - get alert status in web UI. * `http://<vmalert-addr>/vmalert/rule?group_id=<group_id>&rule_id=<rule_id>` - get rule status in web UI. +* `http://<vmalert-addr>/vmalert/api/v1/rule?group_id=<group_id>&alert_id=<alert_id>` - get rule status in JSON format. * `http://<vmalert-addr>/metrics` - application metrics. * `http://<vmalert-addr>/-/reload` - hot configuration reload.