prometheus/rules/recording.go
Julien Duchesne 1a27ab29b8
Some checks are pending
buf.build / lint and publish (push) Waiting to run
CI / Go tests (push) Waiting to run
CI / More Go tests (push) Waiting to run
CI / Go tests with previous Go version (push) Waiting to run
CI / UI tests (push) Waiting to run
CI / Go tests on Windows (push) Waiting to run
CI / Mixins tests (push) Waiting to run
CI / Build Prometheus for common architectures (0) (push) Waiting to run
CI / Build Prometheus for common architectures (1) (push) Waiting to run
CI / Build Prometheus for common architectures (2) (push) Waiting to run
CI / Build Prometheus for all architectures (0) (push) Waiting to run
CI / Build Prometheus for all architectures (1) (push) Waiting to run
CI / Build Prometheus for all architectures (10) (push) Waiting to run
CI / Build Prometheus for all architectures (11) (push) Waiting to run
CI / Build Prometheus for all architectures (2) (push) Waiting to run
CI / Build Prometheus for all architectures (3) (push) Waiting to run
CI / Build Prometheus for all architectures (4) (push) Waiting to run
CI / Build Prometheus for all architectures (5) (push) Waiting to run
CI / Build Prometheus for all architectures (6) (push) Waiting to run
CI / Build Prometheus for all architectures (7) (push) Waiting to run
CI / Build Prometheus for all architectures (8) (push) Waiting to run
CI / Build Prometheus for all architectures (9) (push) Waiting to run
CI / Report status of build Prometheus for all architectures (push) Blocked by required conditions
CI / Check generated parser (push) Waiting to run
CI / golangci-lint (push) Waiting to run
CI / fuzzing (push) Waiting to run
CI / codeql (push) Waiting to run
CI / Publish main branch artifacts (push) Blocked by required conditions
CI / Publish release artefacts (push) Blocked by required conditions
CI / Publish UI on npm Registry (push) Blocked by required conditions
Scorecards supply-chain security / Scorecards analysis (push) Waiting to run
Rules: Store dependencies instead of boolean (#15689)
* Rules: Store dependencies instead of boolean
To improve https://github.com/prometheus/prometheus/pull/15681 further, we'll need to store the dependencies and dependents of each

Right now, if a rule has both (at least 1) dependents and dependencies, it is not possible to determine the order to run the rules and they must all run sequentially

This PR only changes the dependents and dependencies attributes of rules, it does not implement a new topological sort algorithm

Signed-off-by: Julien Duchesne <julien.duchesne@grafana.com>

* Store a slice of Rule instead

Signed-off-by: Julien Duchesne <julien.duchesne@grafana.com>

* Add `BenchmarkRuleDependencyController_AnalyseRules` for future reference

Signed-off-by: Julien Duchesne <julien.duchesne@grafana.com>

---------

Signed-off-by: Julien Duchesne <julien.duchesne@grafana.com>
2025-01-06 20:48:38 +00:00

224 lines
6.3 KiB
Go

// Copyright 2013 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package rules
import (
"context"
"errors"
"fmt"
"net/url"
"sync"
"time"
"go.uber.org/atomic"
"gopkg.in/yaml.v2"
"github.com/prometheus/prometheus/model/labels"
"github.com/prometheus/prometheus/model/rulefmt"
"github.com/prometheus/prometheus/promql"
"github.com/prometheus/prometheus/promql/parser"
)
// A RecordingRule records its vector expression into new timeseries.
type RecordingRule struct {
name string
vector parser.Expr
labels labels.Labels
// The health of the recording rule.
health *atomic.String
// Timestamp of last evaluation of the recording rule.
evaluationTimestamp *atomic.Time
// The last error seen by the recording rule.
lastError *atomic.Error
// Duration of how long it took to evaluate the recording rule.
evaluationDuration *atomic.Duration
dependenciesMutex sync.RWMutex
dependentRules []Rule
dependencyRules []Rule
}
// NewRecordingRule returns a new recording rule.
func NewRecordingRule(name string, vector parser.Expr, lset labels.Labels) *RecordingRule {
return &RecordingRule{
name: name,
vector: vector,
labels: lset,
health: atomic.NewString(string(HealthUnknown)),
evaluationTimestamp: atomic.NewTime(time.Time{}),
evaluationDuration: atomic.NewDuration(0),
lastError: atomic.NewError(nil),
}
}
// Name returns the rule name.
func (rule *RecordingRule) Name() string {
return rule.name
}
// Query returns the rule query expression.
func (rule *RecordingRule) Query() parser.Expr {
return rule.vector
}
// Labels returns the rule labels.
func (rule *RecordingRule) Labels() labels.Labels {
return rule.labels
}
// Eval evaluates the rule and then overrides the metric names and labels accordingly.
func (rule *RecordingRule) Eval(ctx context.Context, queryOffset time.Duration, ts time.Time, query QueryFunc, _ *url.URL, limit int) (promql.Vector, error) {
ctx = NewOriginContext(ctx, NewRuleDetail(rule))
vector, err := query(ctx, rule.vector.String(), ts.Add(-queryOffset))
if err != nil {
return nil, err
}
// Override the metric name and labels.
lb := labels.NewBuilder(labels.EmptyLabels())
for i := range vector {
sample := &vector[i]
lb.Reset(sample.Metric)
lb.Set(labels.MetricName, rule.name)
rule.labels.Range(func(l labels.Label) {
lb.Set(l.Name, l.Value)
})
sample.Metric = lb.Labels()
}
// Check that the rule does not produce identical metrics after applying
// labels.
if vector.ContainsSameLabelset() {
return nil, errors.New("vector contains metrics with the same labelset after applying rule labels")
}
numSeries := len(vector)
if limit > 0 && numSeries > limit {
return nil, fmt.Errorf("exceeded limit of %d with %d series", limit, numSeries)
}
rule.SetHealth(HealthGood)
rule.SetLastError(err)
return vector, nil
}
func (rule *RecordingRule) String() string {
r := rulefmt.Rule{
Record: rule.name,
Expr: rule.vector.String(),
Labels: rule.labels.Map(),
}
byt, err := yaml.Marshal(r)
if err != nil {
return fmt.Sprintf("error marshaling recording rule: %q", err.Error())
}
return string(byt)
}
// SetEvaluationDuration updates evaluationDuration to the time in seconds it took to evaluate the rule on its last evaluation.
func (rule *RecordingRule) SetEvaluationDuration(dur time.Duration) {
rule.evaluationDuration.Store(dur)
}
// SetLastError sets the current error seen by the recording rule.
func (rule *RecordingRule) SetLastError(err error) {
rule.lastError.Store(err)
}
// LastError returns the last error seen by the recording rule.
func (rule *RecordingRule) LastError() error {
return rule.lastError.Load()
}
// SetHealth sets the current health of the recording rule.
func (rule *RecordingRule) SetHealth(health RuleHealth) {
rule.health.Store(string(health))
}
// Health returns the current health of the recording rule.
func (rule *RecordingRule) Health() RuleHealth {
return RuleHealth(rule.health.Load())
}
// GetEvaluationDuration returns the time in seconds it took to evaluate the recording rule.
func (rule *RecordingRule) GetEvaluationDuration() time.Duration {
return rule.evaluationDuration.Load()
}
// SetEvaluationTimestamp updates evaluationTimestamp to the timestamp of when the rule was last evaluated.
func (rule *RecordingRule) SetEvaluationTimestamp(ts time.Time) {
rule.evaluationTimestamp.Store(ts)
}
// GetEvaluationTimestamp returns the time the evaluation took place.
func (rule *RecordingRule) GetEvaluationTimestamp() time.Time {
return rule.evaluationTimestamp.Load()
}
func (rule *RecordingRule) SetDependentRules(dependents []Rule) {
rule.dependenciesMutex.Lock()
defer rule.dependenciesMutex.Unlock()
rule.dependentRules = make([]Rule, len(dependents))
copy(rule.dependentRules, dependents)
}
func (rule *RecordingRule) NoDependentRules() bool {
rule.dependenciesMutex.RLock()
defer rule.dependenciesMutex.RUnlock()
if rule.dependentRules == nil {
return false // We don't know if there are dependent rules.
}
return len(rule.dependentRules) == 0
}
func (rule *RecordingRule) DependentRules() []Rule {
rule.dependenciesMutex.RLock()
defer rule.dependenciesMutex.RUnlock()
return rule.dependentRules
}
func (rule *RecordingRule) SetDependencyRules(dependencies []Rule) {
rule.dependenciesMutex.Lock()
defer rule.dependenciesMutex.Unlock()
rule.dependencyRules = make([]Rule, len(dependencies))
copy(rule.dependencyRules, dependencies)
}
func (rule *RecordingRule) NoDependencyRules() bool {
rule.dependenciesMutex.RLock()
defer rule.dependenciesMutex.RUnlock()
if rule.dependencyRules == nil {
return false // We don't know if there are dependency rules.
}
return len(rule.dependencyRules) == 0
}
func (rule *RecordingRule) DependencyRules() []Rule {
rule.dependenciesMutex.RLock()
defer rule.dependenciesMutex.RUnlock()
return rule.dependencyRules
}