Alerting: reduce database calls in prometheus-comptible rules API (#47080)

* move validation at the beginning of method
* remove usage of GetOrgRuleGroups because it is not necessary. All information is already available in memory.
* remove unused method
This commit is contained in:
Yuriy Tseretyan
2022-04-11 10:54:29 -04:00
committed by GitHub
parent 7be8fe027f
commit 48519f9ebb
4 changed files with 45 additions and 134 deletions
+27 -43
View File
@@ -101,6 +101,15 @@ func getPanelIDFromRequest(r *http.Request) (int64, error) {
}
func (srv PrometheusSrv) RouteGetRuleStatuses(c *models.ReqContext) response.Response {
dashboardUID := c.Query("dashboard_uid")
panelID, err := getPanelIDFromRequest(c.Req)
if err != nil {
return ErrResp(http.StatusBadRequest, err, "invalid panel_id")
}
if dashboardUID == "" && panelID != 0 {
return ErrResp(http.StatusBadRequest, errors.New("panel_id must be set with dashboard_uid"), "")
}
ruleResponse := apimodels.RuleResponse{
DiscoveryBase: apimodels.DiscoveryBase{
Status: "success",
@@ -130,33 +139,12 @@ func (srv PrometheusSrv) RouteGetRuleStatuses(c *models.ReqContext) response.Res
namespaceUIDs = append(namespaceUIDs, k)
}
dashboardUID := c.Query("dashboard_uid")
panelID, err := getPanelIDFromRequest(c.Req)
if err != nil {
return ErrResp(http.StatusBadRequest, err, "invalid panel_id")
}
if dashboardUID == "" && panelID != 0 {
return ErrResp(http.StatusBadRequest, errors.New("panel_id must be set with dashboard_uid"), "")
}
ruleGroupQuery := ngmodels.ListOrgRuleGroupsQuery{
alertRuleQuery := ngmodels.ListAlertRulesQuery{
OrgID: c.SignedInUser.OrgId,
NamespaceUIDs: namespaceUIDs,
DashboardUID: dashboardUID,
PanelID: panelID,
}
if err := srv.store.GetOrgRuleGroups(c.Req.Context(), &ruleGroupQuery); err != nil {
ruleResponse.DiscoveryBase.Status = "error"
ruleResponse.DiscoveryBase.Error = fmt.Sprintf("failure getting rule groups: %s", err.Error())
ruleResponse.DiscoveryBase.ErrorType = apiv1.ErrServer
return response.JSON(http.StatusInternalServerError, ruleResponse)
}
alertRuleQuery := ngmodels.ListAlertRulesQuery{
OrgID: c.SignedInUser.OrgId,
DashboardUID: dashboardUID,
PanelID: panelID,
}
if err := srv.store.GetOrgAlertRules(c.Req.Context(), &alertRuleQuery); err != nil {
ruleResponse.DiscoveryBase.Status = "error"
ruleResponse.DiscoveryBase.Error = fmt.Sprintf("failure getting rules: %s", err.Error())
@@ -166,29 +154,23 @@ func (srv PrometheusSrv) RouteGetRuleStatuses(c *models.ReqContext) response.Res
groupMap := make(map[string]*apimodels.RuleGroup)
for _, r := range ruleGroupQuery.Result {
if len(r) < 3 {
continue
}
groupId, namespaceUID, namespace := r[0], r[1], r[2]
newGroup := &apimodels.RuleGroup{
Name: groupId,
File: namespace, // file is what Prometheus uses for provisioning, we replace it with namespace.
LastEvaluation: time.Time{},
EvaluationTime: 0, // TODO: see if we are able to pass this along with evaluation results
}
groupMap[groupId+"-"+namespaceUID] = newGroup
ruleResponse.Data.RuleGroups = append(ruleResponse.Data.RuleGroups, newGroup)
}
for _, rule := range alertRuleQuery.Result {
newGroup, ok := groupMap[rule.RuleGroup+"-"+rule.NamespaceUID]
groupKey := rule.RuleGroup + "-" + rule.NamespaceUID
newGroup, ok := groupMap[groupKey]
if !ok {
continue
folder := namespaceMap[rule.NamespaceUID]
if folder == nil {
srv.log.Warn("query returned rules that belong to folder the user does not have access to. The rule will not be added to the response", "folder_uid", rule.NamespaceUID, "rule_uid", rule.UID)
continue
}
newGroup = &apimodels.RuleGroup{
Name: rule.RuleGroup,
File: folder.Title, // file is what Prometheus uses for provisioning, we replace it with namespace.
}
groupMap[groupKey] = newGroup
ruleResponse.Data.RuleGroups = append(ruleResponse.Data.RuleGroups, newGroup)
}
alertingRule := apimodels.AlertingRule{
State: "inactive",
Name: rule.Title,
@@ -222,7 +204,6 @@ func (srv PrometheusSrv) RouteGetRuleStatuses(c *models.ReqContext) response.Res
if alertState.LastEvaluationTime.After(newRule.LastEvaluation) {
newRule.LastEvaluation = alertState.LastEvaluationTime
newGroup.LastEvaluation = alertState.LastEvaluationTime
}
newRule.EvaluationTime = alertState.EvaluationDuration.Seconds()
@@ -252,7 +233,10 @@ func (srv PrometheusSrv) RouteGetRuleStatuses(c *models.ReqContext) response.Res
alertingRule.Rule = newRule
newGroup.Rules = append(newGroup.Rules, alertingRule)
newGroup.Interval = float64(rule.IntervalSeconds)
newGroup.EvaluationTime = newRule.EvaluationTime
newGroup.LastEvaluation = newRule.LastEvaluation
}
return response.JSON(http.StatusOK, ruleResponse)
}
+15 -12
View File
@@ -270,16 +270,17 @@ func TestRouteGetRuleStatuses(t *testing.T) {
t.Run("with a rule that only has one query", func(t *testing.T) {
fakeStore, fakeAIM, api := setupAPI(t)
generateRuleAndInstanceWithQuery(t, orgID, fakeAIM, fakeStore, withClassicConditionSingleQuery())
folder := fakeStore.Folders[orgID][0]
r := api.RouteGetRuleStatuses(c)
require.Equal(t, http.StatusOK, r.Status())
require.JSONEq(t, `
require.JSONEq(t, fmt.Sprintf(`
{
"status": "success",
"data": {
"groups": [{
"name": "rule-group",
"file": "namespaceUID",
"file": "%s",
"rules": [{
"state": "inactive",
"name": "AlwaysFiring",
@@ -306,16 +307,17 @@ func TestRouteGetRuleStatuses(t *testing.T) {
}],
"interval": 60,
"lastEvaluation": "2022-03-10T14:01:00Z",
"evaluationTime": 0
"evaluationTime": 60
}]
}
}
`, string(r.Body()))
`, folder.Title), string(r.Body()))
})
t.Run("with the inclusion of internal Labels", func(t *testing.T) {
fakeStore, fakeAIM, api := setupAPI(t)
generateRuleAndInstanceWithQuery(t, orgID, fakeAIM, fakeStore, withClassicConditionSingleQuery())
folder := fakeStore.Folders[orgID][0]
req, err := http.NewRequest("GET", "/api/v1/rules?includeInternalLabels=true", nil)
require.NoError(t, err)
@@ -323,13 +325,13 @@ func TestRouteGetRuleStatuses(t *testing.T) {
r := api.RouteGetRuleStatuses(c)
require.Equal(t, http.StatusOK, r.Status())
require.JSONEq(t, `
require.JSONEq(t, fmt.Sprintf(`
{
"status": "success",
"data": {
"groups": [{
"name": "rule-group",
"file": "namespaceUID",
"file": "%s",
"rules": [{
"state": "inactive",
"name": "AlwaysFiring",
@@ -359,26 +361,27 @@ func TestRouteGetRuleStatuses(t *testing.T) {
}],
"interval": 60,
"lastEvaluation": "2022-03-10T14:01:00Z",
"evaluationTime": 0
"evaluationTime": 60
}]
}
}
`, string(r.Body()))
`, folder.Title), string(r.Body()))
})
t.Run("with a rule that has multiple queries", func(t *testing.T) {
fakeStore, fakeAIM, api := setupAPI(t)
generateRuleAndInstanceWithQuery(t, orgID, fakeAIM, fakeStore, withExpressionsMultiQuery())
folder := fakeStore.Folders[orgID][0]
r := api.RouteGetRuleStatuses(c)
require.Equal(t, http.StatusOK, r.Status())
require.JSONEq(t, `
require.JSONEq(t, fmt.Sprintf(`
{
"status": "success",
"data": {
"groups": [{
"name": "rule-group",
"file": "namespaceUID",
"file": "%s",
"rules": [{
"state": "inactive",
"name": "AlwaysFiring",
@@ -405,11 +408,11 @@ func TestRouteGetRuleStatuses(t *testing.T) {
}],
"interval": 60,
"lastEvaluation": "2022-03-10T14:01:00Z",
"evaluationTime": 0
"evaluationTime": 60
}]
}
}
`, string(r.Body()))
`, folder.Title), string(r.Body()))
})
}