github.com/thanos-io/thanos@v0.32.5/pkg/rules/rulespb/custom.go (about) 1 // Copyright (c) The Thanos Authors. 2 // Licensed under the Apache License 2.0. 3 4 package rulespb 5 6 import ( 7 "encoding/json" 8 "math/big" 9 "strconv" 10 "strings" 11 "time" 12 13 "github.com/pkg/errors" 14 "github.com/prometheus/prometheus/model/labels" 15 16 "github.com/thanos-io/thanos/pkg/store/labelpb" 17 ) 18 19 const ( 20 RuleRecordingType = "recording" 21 RuleAlertingType = "alerting" 22 ) 23 24 func NewRuleGroupRulesResponse(rg *RuleGroup) *RulesResponse { 25 return &RulesResponse{ 26 Result: &RulesResponse_Group{ 27 Group: rg, 28 }, 29 } 30 } 31 32 func NewWarningRulesResponse(warning error) *RulesResponse { 33 return &RulesResponse{ 34 Result: &RulesResponse_Warning{ 35 Warning: warning.Error(), 36 }, 37 } 38 } 39 40 func NewRecordingRule(r *RecordingRule) *Rule { 41 return &Rule{ 42 Result: &Rule_Recording{Recording: r}, 43 } 44 } 45 46 // Compare compares equal recording rules r1 and r2 and returns: 47 // 48 // < 0 if r1 < r2 if rule r1 is lexically before rule r2 49 // 0 if r1 == r2 50 // > 0 if r1 > r2 if rule r1 is lexically after rule r2 51 // 52 // More formally, the ordering is determined in the following order: 53 // 54 // 1. recording rule last evaluation (earlier evaluation comes first) 55 // 56 // Note: This method assumes r1 and r2 are logically equal as per Rule#Compare. 57 func (r1 *RecordingRule) Compare(r2 *RecordingRule) int { 58 if r1.LastEvaluation.Before(r2.LastEvaluation) { 59 return 1 60 } 61 62 if r1.LastEvaluation.After(r2.LastEvaluation) { 63 return -1 64 } 65 66 return 0 67 } 68 69 func NewAlertingRule(a *Alert) *Rule { 70 return &Rule{ 71 Result: &Rule_Alert{Alert: a}, 72 } 73 } 74 75 func (r *Rule) GetLabels() labels.Labels { 76 switch { 77 case r.GetRecording() != nil: 78 return r.GetRecording().Labels.PromLabels() 79 case r.GetAlert() != nil: 80 return r.GetAlert().Labels.PromLabels() 81 default: 82 return nil 83 } 84 } 85 86 func (r *Rule) SetLabels(ls labels.Labels) { 87 var result labelpb.ZLabelSet 88 89 if len(ls) > 0 { 90 result = labelpb.ZLabelSet{Labels: labelpb.ZLabelsFromPromLabels(ls)} 91 } 92 93 switch { 94 case r.GetRecording() != nil: 95 r.GetRecording().Labels = result 96 case r.GetAlert() != nil: 97 r.GetAlert().Labels = result 98 } 99 } 100 101 func (r *Rule) GetName() string { 102 switch { 103 case r.GetRecording() != nil: 104 return r.GetRecording().Name 105 case r.GetAlert() != nil: 106 return r.GetAlert().Name 107 default: 108 return "" 109 } 110 } 111 112 func (r *Rule) GetQuery() string { 113 switch { 114 case r.GetRecording() != nil: 115 return r.GetRecording().Query 116 case r.GetAlert() != nil: 117 return r.GetAlert().Query 118 default: 119 return "" 120 } 121 } 122 123 func (r *Rule) GetLastEvaluation() time.Time { 124 switch { 125 case r.GetRecording() != nil: 126 return r.GetRecording().LastEvaluation 127 case r.GetAlert() != nil: 128 return r.GetAlert().LastEvaluation 129 default: 130 return time.Time{} 131 } 132 } 133 134 // Compare compares recording and alerting rules r1 and r2 and returns: 135 // 136 // < 0 if r1 < r2 if rule r1 is not equal and lexically before rule r2 137 // 0 if r1 == r2 if rule r1 is logically equal to r2 (r1 and r2 are the "same" rules) 138 // > 0 if r1 > r2 if rule r1 is not equal and lexically after rule r2 139 // 140 // More formally, ordering and equality is determined in the following order: 141 // 142 // 1. rule type (alerting rules come before recording rules) 143 // 2. rule name 144 // 3. rule labels 145 // 4. rule query 146 // 5. for alerting rules: duration 147 // 148 // Note: this can still leave ordering undetermined for equal rules (x == y). 149 // For determining ordering of equal rules, use Alert#Compare or RecordingRule#Compare. 150 func (r1 *Rule) Compare(r2 *Rule) int { 151 if r1.GetAlert() != nil && r2.GetRecording() != nil { 152 return -1 153 } 154 155 if r1.GetRecording() != nil && r2.GetAlert() != nil { 156 return 1 157 } 158 159 if d := strings.Compare(r1.GetName(), r2.GetName()); d != 0 { 160 return d 161 } 162 163 if d := labels.Compare(r1.GetLabels(), r2.GetLabels()); d != 0 { 164 return d 165 } 166 167 if d := strings.Compare(r1.GetQuery(), r2.GetQuery()); d != 0 { 168 return d 169 } 170 171 if r1.GetAlert() != nil && r2.GetAlert() != nil { 172 if d := big.NewFloat(r1.GetAlert().DurationSeconds).Cmp(big.NewFloat(r2.GetAlert().DurationSeconds)); d != 0 { 173 return d 174 } 175 } 176 177 return 0 178 } 179 180 func (r *RuleGroups) MarshalJSON() ([]byte, error) { 181 if r.Groups == nil { 182 // Ensure that empty slices are marshaled as '[]' and not 'null'. 183 return []byte(`{"groups":[]}`), nil 184 } 185 type plain RuleGroups 186 return json.Marshal((*plain)(r)) 187 } 188 189 // Compare compares rule group x and y and returns: 190 // 191 // < 0 if x < y if rule group r1 is not equal and lexically before rule group r2 192 // 0 if x == y if rule group r1 is logically equal to r2 (r1 and r2 are the "same" rule groups) 193 // > 0 if x > y if rule group r1 is not equal and lexically after rule group r2 194 func (r1 *RuleGroup) Compare(r2 *RuleGroup) int { 195 return strings.Compare(r1.Key(), r2.Key()) 196 } 197 198 // Key returns the group key similar resembling Prometheus logic. 199 // See https://github.com/prometheus/prometheus/blob/869f1bc587e667b79721852d5badd9f70a39fc3f/rules/manager.go#L1062-L1065 200 func (r *RuleGroup) Key() string { 201 if r == nil { 202 return "" 203 } 204 205 return r.File + ";" + r.Name 206 } 207 208 func (m *Rule) UnmarshalJSON(entry []byte) error { 209 decider := struct { 210 Type string `json:"type"` 211 }{} 212 if err := json.Unmarshal(entry, &decider); err != nil { 213 return errors.Wrapf(err, "rule: type field unmarshal: %v", string(entry)) 214 } 215 216 switch strings.ToLower(decider.Type) { 217 case "recording": 218 r := &RecordingRule{} 219 if err := json.Unmarshal(entry, r); err != nil { 220 return errors.Wrapf(err, "rule: recording rule unmarshal: %v", string(entry)) 221 } 222 223 m.Result = &Rule_Recording{Recording: r} 224 case "alerting": 225 r := &Alert{} 226 if err := json.Unmarshal(entry, r); err != nil { 227 return errors.Wrapf(err, "rule: alerting rule unmarshal: %v", string(entry)) 228 } 229 230 m.Result = &Rule_Alert{Alert: r} 231 case "": 232 return errors.Errorf("rule: no type field provided: %v", string(entry)) 233 default: 234 return errors.Errorf("rule: unknown type field provided %s; %v", decider.Type, string(entry)) 235 } 236 return nil 237 } 238 239 func (m *Rule) MarshalJSON() ([]byte, error) { 240 if r := m.GetRecording(); r != nil { 241 return json.Marshal(struct { 242 *RecordingRule 243 Type string `json:"type"` 244 }{ 245 RecordingRule: r, 246 Type: RuleRecordingType, 247 }) 248 } 249 a := m.GetAlert() 250 if a.Alerts == nil { 251 // Ensure that empty slices are marshaled as '[]' and not 'null'. 252 a.Alerts = make([]*AlertInstance, 0) 253 } 254 return json.Marshal(struct { 255 *Alert 256 Type string `json:"type"` 257 }{ 258 Alert: a, 259 Type: RuleAlertingType, 260 }) 261 } 262 263 func (r *RuleGroup) MarshalJSON() ([]byte, error) { 264 if r.Rules == nil { 265 // Ensure that empty slices are marshaled as '[]' and not 'null'. 266 r.Rules = make([]*Rule, 0) 267 } 268 type plain RuleGroup 269 return json.Marshal((*plain)(r)) 270 } 271 272 func (x *AlertState) UnmarshalJSON(entry []byte) error { 273 fieldStr, err := strconv.Unquote(string(entry)) 274 if err != nil { 275 return errors.Wrapf(err, "alertState: unquote %v", string(entry)) 276 } 277 278 if fieldStr == "" { 279 return errors.New("empty alertState") 280 } 281 282 state, ok := AlertState_value[strings.ToUpper(fieldStr)] 283 if !ok { 284 return errors.Errorf("unknown alertState: %v", string(entry)) 285 } 286 *x = AlertState(state) 287 return nil 288 } 289 290 func (x *AlertState) MarshalJSON() ([]byte, error) { 291 return []byte(strconv.Quote(strings.ToLower(x.String()))), nil 292 } 293 294 // Compare compares alert state x and y and returns: 295 // 296 // < 0 if x < y (alert state x is more critical than alert state y) 297 // 0 if x == y 298 // > 0 if x > y (alert state x is less critical than alert state y) 299 // 300 // For sorting this makes sure that more "critical" alert states come first. 301 func (x AlertState) Compare(y AlertState) int { 302 return int(y) - int(x) 303 } 304 305 // Compare compares two equal alerting rules a1 and a2 and returns: 306 // 307 // < 0 if a1 < a2 if rule a1 is lexically before rule a2 308 // 0 if a1 == a2 309 // > 0 if a1 > a2 if rule a1 is lexically after rule a2 310 // 311 // More formally, the ordering is determined in the following order: 312 // 313 // 1. alert state 314 // 2. alert last evaluation (earlier evaluation comes first) 315 // 316 // Note: This method assumes a1 and a2 are logically equal as per Rule#Compare. 317 func (a1 *Alert) Compare(a2 *Alert) int { 318 if d := a1.State.Compare(a2.State); d != 0 { 319 return d 320 } 321 322 if a1.LastEvaluation.Before(a2.LastEvaluation) { 323 return 1 324 } 325 326 if a1.LastEvaluation.After(a2.LastEvaluation) { 327 return -1 328 } 329 330 return 0 331 }