Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 55 additions & 78 deletions api/prometheus/v1/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package v1

import (
"context"
gojson "encoding/json"
"errors"
"fmt"
"math"
Expand Down Expand Up @@ -745,13 +746,13 @@ type Stat struct {

func (rg *RuleGroup) UnmarshalJSON(b []byte) error {
v := struct {
Name string `json:"name"`
File string `json:"file"`
Interval float64 `json:"interval"`
Rules []json.RawMessage `json:"rules"`
Name string `json:"name"`
File string `json:"file"`
Interval float64 `json:"interval"`
Rules []gojson.RawMessage `json:"rules"`
Comment thread
bwplotka marked this conversation as resolved.
}{}

if err := json.Unmarshal(b, &v); err != nil {
if err := gojson.Unmarshal(b, &v); err != nil {
return err
}

Expand All @@ -760,102 +761,78 @@ func (rg *RuleGroup) UnmarshalJSON(b []byte) error {
rg.Interval = v.Interval

for _, rule := range v.Rules {
alertingRule := AlertingRule{}
if err := json.Unmarshal(rule, &alertingRule); err == nil {
rg.Rules = append(rg.Rules, alertingRule)
continue
ruleType, err := unmarshalRuleType(rule)
if err != nil {
return err
}
recordingRule := RecordingRule{}
if err := json.Unmarshal(rule, &recordingRule); err == nil {
switch ruleType {
case RuleTypeAlerting:
alertingRule := AlertingRule{}
if err := alertingRule.unmarshalTypeCheckedJSON(rule); err != nil {
return err
}
rg.Rules = append(rg.Rules, alertingRule)
case RuleTypeRecording:
recordingRule := RecordingRule{}
if err := recordingRule.unmarshalTypeCheckedJSON(rule); err != nil {
return err
}
rg.Rules = append(rg.Rules, recordingRule)
continue
default:
return errors.New("failed to decode JSON into an alerting or recording rule")
}
return errors.New("failed to decode JSON into an alerting or recording rule")
}

return nil
}

func (r *AlertingRule) UnmarshalJSON(b []byte) error {
func unmarshalRuleType(b []byte) (RuleType, error) {
v := struct {
Type string `json:"type"`
}{}
if err := json.Unmarshal(b, &v); err != nil {
return err
if err := gojson.Unmarshal(b, &v); err != nil {
return RuleType(""), err
}
if v.Type == "" {
return errors.New("type field not present in rule")
}
if v.Type != string(RuleTypeAlerting) {
return fmt.Errorf("expected rule of type %s but got %s", string(RuleTypeAlerting), v.Type)
}

rule := struct {
Name string `json:"name"`
Query string `json:"query"`
Duration float64 `json:"duration"`
Labels model.LabelSet `json:"labels"`
Annotations model.LabelSet `json:"annotations"`
Alerts []*Alert `json:"alerts"`
Health RuleHealth `json:"health"`
LastError string `json:"lastError,omitempty"`
EvaluationTime float64 `json:"evaluationTime"`
LastEvaluation time.Time `json:"lastEvaluation"`
State string `json:"state"`
}{}
if err := json.Unmarshal(b, &rule); err != nil {
return RuleType(""), errors.New("type field not present in rule")
}
return RuleType(v.Type), nil
}

func (r *AlertingRule) UnmarshalJSON(b []byte) error {
ruleType, err := unmarshalRuleType(b)
if err != nil {
return err
}
r.Health = rule.Health
r.Annotations = rule.Annotations
r.Name = rule.Name
r.Query = rule.Query
r.Alerts = rule.Alerts
r.Duration = rule.Duration
r.Labels = rule.Labels
r.LastError = rule.LastError
r.EvaluationTime = rule.EvaluationTime
r.LastEvaluation = rule.LastEvaluation
r.State = rule.State
if ruleType != RuleTypeAlerting {
return fmt.Errorf("expected rule of type %s but got %s", string(RuleTypeAlerting), ruleType)
}
return r.unmarshalTypeCheckedJSON(b)
}

return nil
type alertingRuleInternal AlertingRule

// unmarshalTypeCheckedJSON unmarshals json with the type field already verified to be RuleTypeAlerting
func (r *AlertingRule) unmarshalTypeCheckedJSON(b []byte) error {
return gojson.Unmarshal(b, (*alertingRuleInternal)(r))
}

func (r *RecordingRule) UnmarshalJSON(b []byte) error {
v := struct {
Type string `json:"type"`
}{}
if err := json.Unmarshal(b, &v); err != nil {
ruleType, err := unmarshalRuleType(b)
if err != nil {
return err
}
if v.Type == "" {
return errors.New("type field not present in rule")
}
if v.Type != string(RuleTypeRecording) {
return fmt.Errorf("expected rule of type %s but got %s", string(RuleTypeRecording), v.Type)
if ruleType != RuleTypeRecording {
return fmt.Errorf("expected rule of type %s but got %s", string(RuleTypeRecording), ruleType)
}
return r.unmarshalTypeCheckedJSON(b)
}

rule := struct {
Name string `json:"name"`
Query string `json:"query"`
Labels model.LabelSet `json:"labels,omitempty"`
Health RuleHealth `json:"health"`
LastError string `json:"lastError,omitempty"`
EvaluationTime float64 `json:"evaluationTime"`
LastEvaluation time.Time `json:"lastEvaluation"`
}{}
if err := json.Unmarshal(b, &rule); err != nil {
return err
}
r.Health = rule.Health
r.Labels = rule.Labels
r.Name = rule.Name
r.LastError = rule.LastError
r.Query = rule.Query
r.EvaluationTime = rule.EvaluationTime
r.LastEvaluation = rule.LastEvaluation
type recordingRuleInternal RecordingRule

return nil
// unmarshalTypeCheckedJSON unmarshals json with the type field already verified to be RuleTypeRecording
func (r *RecordingRule) unmarshalTypeCheckedJSON(b []byte) error {
return gojson.Unmarshal(b, (*recordingRuleInternal)(r))
}

func (qr *queryResult) UnmarshalJSON(b []byte) error {
Expand Down Expand Up @@ -1296,7 +1273,7 @@ func (h *httpAPI) Rules(ctx context.Context, matches []string) (RulesResult, err
}

var res RulesResult
err = json.Unmarshal(body, &res)
err = gojson.Unmarshal(body, &res)
return res, err
}

Expand Down
74 changes: 74 additions & 0 deletions api/prometheus/v1/api_bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@
package v1

import (
"bytes"
"encoding/json"
"strconv"
"strings"
"testing"
"time"

Expand Down Expand Up @@ -197,3 +199,75 @@ func BenchmarkSamplesJsonSerialization(b *testing.B) {
})
}
}

func BenchmarkRuleGroup(b *testing.B) {
alertingRuleJSON, err := json.Marshal(struct {
Type RuleType `json:"type"`
AlertingRule `json:""`
}{
Type: RuleTypeAlerting,
AlertingRule: AlertingRule{
Name: "HighRequestLatency",
Query: "job:request_latency_seconds:mean5m{job=\"myjob\"} > 0.5",
Duration: 600,
Labels: model.LabelSet{"severity": "page"},
Annotations: model.LabelSet{"summary": "High request latency"},
Alerts: []*Alert{{
ActiveAt: time.Now().UTC(),
Annotations: model.LabelSet{"summary": "High request latency"},
Labels: model.LabelSet{"alertname": "HighRequestLatency", "severity": "page"},
State: AlertStateFiring,
Value: "1e+00",
}},
Health: RuleHealthGood,
LastError: "Unknown",
EvaluationTime: 1,
LastEvaluation: time.Now().Round(time.Millisecond).UTC(),
State: "state",
},
})
if err != nil {
b.Fatal(err)
}
b.Log("alerting:", string(alertingRuleJSON))

recordingRuleJSON, err := json.Marshal(struct {
Type RuleType `json:"type"`
RecordingRule `json:""`
}{
Type: RuleTypeRecording,
RecordingRule: RecordingRule{
Name: "job:http_inprogress_requests:sum",
Query: "sum(http_inprogress_requests) by (job)",
Labels: model.LabelSet{"severity": "page"},
Health: RuleHealthGood,
LastError: "Unknown",
EvaluationTime: 1,
LastEvaluation: time.Now().Round(time.Millisecond).UTC(),
},
})
if err != nil {
b.Fatal(err)
}
b.Log("recording:", string(recordingRuleJSON))

data := []byte(`{
"name":"myname","file":"myfile","interval":0.0000005,"rules":[` +
string(alertingRuleJSON) + strings.Repeat(","+string(alertingRuleJSON), 100) + strings.Repeat(","+string(recordingRuleJSON), 100) +
`]}`)

b.Run("streaming", func(b *testing.B) {
for i := 0; i < b.N; i++ {
if err := json.NewDecoder(bytes.NewReader(data)).Decode(&RuleGroup{}); err != nil {
b.Fatal(err)
}
}
})
b.Run("unmarshal", func(b *testing.B) {
for i := 0; i < b.N; i++ {
if err := json.Unmarshal(data, &RuleGroup{}); err != nil {
b.Fatal(err)
}
}
})
}
Loading