mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: Improve performance of cache.getOrCreate (#63909)
* move expansion of labels and annotations outside of mutex lock * propagate struct but not pointer
This commit is contained in:
parent
5ca7a1a01e
commit
baffe83da6
@ -34,23 +34,54 @@ func newCache() *cache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *cache) getOrCreate(ctx context.Context, log log.Logger, alertRule *ngModels.AlertRule, result eval.Result, extraLabels data.Labels, externalURL *url.URL) *State {
|
func (c *cache) getOrCreate(ctx context.Context, log log.Logger, alertRule *ngModels.AlertRule, result eval.Result, extraLabels data.Labels, externalURL *url.URL) *State {
|
||||||
|
// Calculation of state ID involves label and annotation expansion, which may be resource intensive operations, and doing it in the context guarded by mtxStates may create a lot of contention.
|
||||||
|
// Instead of just calculating ID we create an entire state - a candidate. If rule states already hold a state with this ID, this candidate will be discarded and the existing one will be returned.
|
||||||
|
// Otherwise, this candidate will be added to the rule states and returned.
|
||||||
|
stateCandidate := calculateState(ctx, log, alertRule, result, extraLabels, externalURL)
|
||||||
|
|
||||||
c.mtxStates.Lock()
|
c.mtxStates.Lock()
|
||||||
defer c.mtxStates.Unlock()
|
defer c.mtxStates.Unlock()
|
||||||
|
|
||||||
var orgStates map[string]*ruleStates
|
var orgStates map[string]*ruleStates
|
||||||
var ok bool
|
var ok bool
|
||||||
if orgStates, ok = c.states[alertRule.OrgID]; !ok {
|
if orgStates, ok = c.states[stateCandidate.OrgID]; !ok {
|
||||||
orgStates = make(map[string]*ruleStates)
|
orgStates = make(map[string]*ruleStates)
|
||||||
c.states[alertRule.OrgID] = orgStates
|
c.states[stateCandidate.OrgID] = orgStates
|
||||||
}
|
}
|
||||||
var states *ruleStates
|
var states *ruleStates
|
||||||
if states, ok = orgStates[alertRule.UID]; !ok {
|
if states, ok = orgStates[stateCandidate.AlertRuleUID]; !ok {
|
||||||
states = &ruleStates{states: make(map[string]*State)}
|
states = &ruleStates{states: make(map[string]*State)}
|
||||||
c.states[alertRule.OrgID][alertRule.UID] = states
|
c.states[stateCandidate.OrgID][stateCandidate.AlertRuleUID] = states
|
||||||
}
|
}
|
||||||
return states.getOrCreate(ctx, log, alertRule, result, extraLabels, externalURL)
|
return states.getOrAdd(stateCandidate)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rs *ruleStates) getOrCreate(ctx context.Context, log log.Logger, alertRule *ngModels.AlertRule, result eval.Result, extraLabels data.Labels, externalURL *url.URL) *State {
|
func (rs *ruleStates) getOrAdd(stateCandidate State) *State {
|
||||||
|
state, ok := rs.states[stateCandidate.CacheID]
|
||||||
|
// Check if the state with this ID already exists.
|
||||||
|
if !ok {
|
||||||
|
rs.states[stateCandidate.CacheID] = &stateCandidate
|
||||||
|
return &stateCandidate
|
||||||
|
}
|
||||||
|
|
||||||
|
// Annotations can change over time, however we also want to maintain
|
||||||
|
// certain annotations across evaluations
|
||||||
|
for k, v := range state.Annotations {
|
||||||
|
if _, ok := ngModels.InternalAnnotationNameSet[k]; ok {
|
||||||
|
// If the annotation is not present then it should be copied from the
|
||||||
|
// previous state to the next state
|
||||||
|
if _, ok := stateCandidate.Annotations[k]; !ok {
|
||||||
|
stateCandidate.Annotations[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
state.Annotations = stateCandidate.Annotations
|
||||||
|
state.Values = stateCandidate.Values
|
||||||
|
rs.states[stateCandidate.CacheID] = state
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
|
func calculateState(ctx context.Context, log log.Logger, alertRule *ngModels.AlertRule, result eval.Result, extraLabels data.Labels, externalURL *url.URL) State {
|
||||||
// Merge both the extra labels and the labels from the evaluation into a common set
|
// Merge both the extra labels and the labels from the evaluation into a common set
|
||||||
// of labels that can be expanded in custom labels and annotations.
|
// of labels that can be expanded in custom labels and annotations.
|
||||||
templateData := template.NewData(mergeLabels(extraLabels, result.Instance), result)
|
templateData := template.NewData(mergeLabels(extraLabels, result.Instance), result)
|
||||||
@ -108,27 +139,9 @@ func (rs *ruleStates) getOrCreate(ctx context.Context, log log.Logger, alertRule
|
|||||||
log.Error("Error getting cacheId for entry", "error", err)
|
log.Error("Error getting cacheId for entry", "error", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if state, ok := rs.states[id]; ok {
|
|
||||||
// Annotations can change over time, however we also want to maintain
|
|
||||||
// certain annotations across evaluations
|
|
||||||
for k, v := range state.Annotations {
|
|
||||||
if _, ok := ngModels.InternalAnnotationNameSet[k]; ok {
|
|
||||||
// If the annotation is not present then it should be copied from the
|
|
||||||
// previous state to the next state
|
|
||||||
if _, ok := annotations[k]; !ok {
|
|
||||||
annotations[k] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
state.Annotations = annotations
|
|
||||||
state.Values = values
|
|
||||||
rs.states[id] = state
|
|
||||||
return state
|
|
||||||
}
|
|
||||||
|
|
||||||
// For new states, we set StartsAt & EndsAt to EvaluatedAt as this is the
|
// For new states, we set StartsAt & EndsAt to EvaluatedAt as this is the
|
||||||
// expected value for a Normal state during state transition.
|
// expected value for a Normal state during state transition.
|
||||||
newState := &State{
|
newState := State{
|
||||||
AlertRuleUID: alertRule.UID,
|
AlertRuleUID: alertRule.UID,
|
||||||
OrgID: alertRule.OrgID,
|
OrgID: alertRule.OrgID,
|
||||||
CacheID: id,
|
CacheID: id,
|
||||||
@ -139,7 +152,6 @@ func (rs *ruleStates) getOrCreate(ctx context.Context, log log.Logger, alertRule
|
|||||||
StartsAt: result.EvaluatedAt,
|
StartsAt: result.EvaluatedAt,
|
||||||
EndsAt: result.EvaluatedAt,
|
EndsAt: result.EvaluatedAt,
|
||||||
}
|
}
|
||||||
rs.states[id] = newState
|
|
||||||
return newState
|
return newState
|
||||||
}
|
}
|
||||||
|
|
||||||
|
49
pkg/services/ngalert/state/cache_bench_test.go
Normal file
49
pkg/services/ngalert/state/cache_bench_test.go
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
package state
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"math/rand"
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/grafana/grafana-plugin-sdk-go/data"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/infra/log/logtest"
|
||||||
|
"github.com/grafana/grafana/pkg/services/ngalert/eval"
|
||||||
|
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
func BenchmarkGetOrCreateTest(b *testing.B) {
|
||||||
|
cache := newCache()
|
||||||
|
rule := models.AlertRuleGen(func(rule *models.AlertRule) {
|
||||||
|
for i := 0; i < 2; i++ {
|
||||||
|
rule.Labels = data.Labels{
|
||||||
|
"label-1": "{{ $value }}",
|
||||||
|
"label-2": "{{ $values.A.Labels.instance }} has value {{ $values.A }}",
|
||||||
|
}
|
||||||
|
rule.Annotations = data.Labels{
|
||||||
|
"anno-1": "{{ $value }}",
|
||||||
|
"anno-2": "{{ $values.A.Labels.instance }} has value {{ $values.A }}",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
result := eval.ResultGen(func(r *eval.Result) {
|
||||||
|
r.Values = map[string]eval.NumberValueCapture{
|
||||||
|
"A": {
|
||||||
|
Var: "A",
|
||||||
|
Labels: data.Labels{"instance": uuid.New().String()},
|
||||||
|
Value: func(f float64) *float64 { return &f }(rand.Float64()),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
ctx := context.Background()
|
||||||
|
log := &logtest.Fake{}
|
||||||
|
u, _ := url.Parse("http://localhost")
|
||||||
|
// values := make([]int64, count)
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
for pb.Next() {
|
||||||
|
_ = cache.getOrCreate(ctx, log, rule, result, nil, u)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user