github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/metrics/rules/rollup.go (about) 1 // Copyright (c) 2020 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package rules 22 23 import ( 24 "errors" 25 "fmt" 26 27 merrors "github.com/m3db/m3/src/metrics/errors" 28 "github.com/m3db/m3/src/metrics/filters" 29 "github.com/m3db/m3/src/metrics/generated/proto/metricpb" 30 "github.com/m3db/m3/src/metrics/generated/proto/rulepb" 31 "github.com/m3db/m3/src/metrics/rules/view" 32 "github.com/m3db/m3/src/query/models" 33 34 "github.com/pborman/uuid" 35 ) 36 37 var ( 38 errNoRollupTargetsInRollupRuleSnapshot = errors.New("no rollup targets in rollup rule snapshot") 39 errRollupRuleSnapshotIndexOutOfRange = errors.New("rollup rule snapshot index out of range") 40 errNilRollupRuleSnapshotProto = errors.New("nil rollup rule snapshot proto") 41 errNilRollupRuleProto = errors.New("nil rollup rule proto") 42 ) 43 44 // rollupRuleSnapshot defines a rule snapshot such that if a metric matches the 45 // provided filters, it is rolled up using the provided list of rollup targets. 46 type rollupRuleSnapshot struct { 47 name string 48 tombstoned bool 49 cutoverNanos int64 50 filter filters.TagsFilter 51 targets []rollupTarget 52 rawFilter string 53 lastUpdatedAtNanos int64 54 lastUpdatedBy string 55 keepOriginal bool 56 tags []models.Tag 57 } 58 59 func newRollupRuleSnapshotFromProto( 60 r *rulepb.RollupRuleSnapshot, 61 opts filters.TagsFilterOptions, 62 ) (*rollupRuleSnapshot, error) { 63 if r == nil { 64 return nil, errNilRollupRuleSnapshotProto 65 } 66 var targets []rollupTarget 67 if len(r.Targets) > 0 { 68 // Convert v1 (i.e., legacy) rollup targets proto to rollup targets v2. 69 targets = make([]rollupTarget, 0, len(r.Targets)) 70 for _, t := range r.Targets { 71 target, err := newRollupTargetFromV1Proto(t) 72 if err != nil { 73 return nil, err 74 } 75 targets = append(targets, target) 76 } 77 } else if len(r.TargetsV2) > 0 { 78 // Convert v2 rollup targets proto to rollup targest v2. 79 targets = make([]rollupTarget, 0, len(r.TargetsV2)) 80 for _, t := range r.TargetsV2 { 81 target, err := newRollupTargetFromV2Proto(t) 82 if err != nil { 83 return nil, err 84 } 85 targets = append(targets, target) 86 } 87 } else if !r.Tombstoned { 88 return nil, errNoRollupTargetsInRollupRuleSnapshot 89 } 90 91 filterValues, err := filters.ParseTagFilterValueMap(r.Filter) 92 if err != nil { 93 return nil, err 94 } 95 filter, err := filters.NewTagsFilter(filterValues, filters.Conjunction, opts) 96 if err != nil { 97 return nil, err 98 } 99 100 return newRollupRuleSnapshotFromFieldsInternal( 101 r.Name, 102 r.Tombstoned, 103 r.CutoverNanos, 104 r.Filter, 105 targets, 106 filter, 107 r.LastUpdatedAtNanos, 108 r.LastUpdatedBy, 109 r.KeepOriginal, 110 models.TagsFromProto(r.Tags), 111 ), nil 112 } 113 114 func newRollupRuleSnapshotFromFields( 115 name string, 116 cutoverNanos int64, 117 rawFilter string, 118 targets []rollupTarget, 119 filter filters.TagsFilter, 120 lastUpdatedAtNanos int64, 121 lastUpdatedBy string, 122 keepOriginal bool, 123 tags []models.Tag, 124 ) (*rollupRuleSnapshot, error) { 125 if _, err := filters.ValidateTagsFilter(rawFilter); err != nil { 126 return nil, err 127 } 128 return newRollupRuleSnapshotFromFieldsInternal( 129 name, 130 false, 131 cutoverNanos, 132 rawFilter, 133 targets, 134 filter, 135 lastUpdatedAtNanos, 136 lastUpdatedBy, 137 keepOriginal, 138 tags, 139 ), nil 140 } 141 142 // newRollupRuleSnapshotFromFieldsInternal creates a new rollup rule snapshot 143 // from various given fields assuming the filter has already been validated. 144 func newRollupRuleSnapshotFromFieldsInternal( 145 name string, 146 tombstoned bool, 147 cutoverNanos int64, 148 rawFilter string, 149 targets []rollupTarget, 150 filter filters.TagsFilter, 151 lastUpdatedAtNanos int64, 152 lastUpdatedBy string, 153 keepOriginal bool, 154 tags []models.Tag, 155 ) *rollupRuleSnapshot { 156 return &rollupRuleSnapshot{ 157 name: name, 158 tombstoned: tombstoned, 159 cutoverNanos: cutoverNanos, 160 filter: filter, 161 targets: targets, 162 rawFilter: rawFilter, 163 lastUpdatedAtNanos: lastUpdatedAtNanos, 164 lastUpdatedBy: lastUpdatedBy, 165 keepOriginal: keepOriginal, 166 tags: tags, 167 } 168 } 169 170 func (rrs *rollupRuleSnapshot) clone() rollupRuleSnapshot { 171 targets := make([]rollupTarget, len(rrs.targets)) 172 for i, t := range rrs.targets { 173 targets[i] = t.clone() 174 } 175 tags := make([]models.Tag, len(rrs.tags)) 176 copy(tags, rrs.tags) 177 return rollupRuleSnapshot{ 178 name: rrs.name, 179 tombstoned: rrs.tombstoned, 180 cutoverNanos: rrs.cutoverNanos, 181 filter: rrs.filter, 182 targets: targets, 183 rawFilter: rrs.rawFilter, 184 lastUpdatedAtNanos: rrs.lastUpdatedAtNanos, 185 lastUpdatedBy: rrs.lastUpdatedBy, 186 keepOriginal: rrs.keepOriginal, 187 tags: tags, 188 } 189 } 190 191 // proto returns the given MappingRuleSnapshot in protobuf form. 192 func (rrs *rollupRuleSnapshot) proto() (*rulepb.RollupRuleSnapshot, error) { 193 tags := make([]*metricpb.Tag, 0, len(rrs.tags)) 194 for _, tag := range rrs.tags { 195 tags = append(tags, tag.ToProto()) 196 } 197 res := &rulepb.RollupRuleSnapshot{ 198 Name: rrs.name, 199 Tombstoned: rrs.tombstoned, 200 CutoverNanos: rrs.cutoverNanos, 201 Filter: rrs.rawFilter, 202 LastUpdatedAtNanos: rrs.lastUpdatedAtNanos, 203 LastUpdatedBy: rrs.lastUpdatedBy, 204 KeepOriginal: rrs.keepOriginal, 205 Tags: tags, 206 } 207 208 targets := make([]*rulepb.RollupTargetV2, len(rrs.targets)) 209 for i, t := range rrs.targets { 210 target, err := t.proto() 211 if err != nil { 212 return nil, err 213 } 214 targets[i] = target 215 } 216 res.TargetsV2 = targets 217 218 return res, nil 219 } 220 221 // rollupRule stores rollup rule snapshots. 222 type rollupRule struct { 223 uuid string 224 snapshots []*rollupRuleSnapshot 225 } 226 227 func newEmptyRollupRule() *rollupRule { 228 return &rollupRule{uuid: uuid.New()} 229 } 230 231 func newRollupRuleFromProto( 232 rc *rulepb.RollupRule, 233 opts filters.TagsFilterOptions, 234 ) (*rollupRule, error) { 235 if rc == nil { 236 return nil, errNilRollupRuleProto 237 } 238 snapshots := make([]*rollupRuleSnapshot, 0, len(rc.Snapshots)) 239 for i := 0; i < len(rc.Snapshots); i++ { 240 rr, err := newRollupRuleSnapshotFromProto(rc.Snapshots[i], opts) 241 if err != nil { 242 return nil, err 243 } 244 snapshots = append(snapshots, rr) 245 } 246 return &rollupRule{ 247 uuid: rc.Uuid, 248 snapshots: snapshots, 249 }, nil 250 } 251 252 func (rc rollupRule) clone() rollupRule { 253 snapshots := make([]*rollupRuleSnapshot, len(rc.snapshots)) 254 for i, s := range rc.snapshots { 255 c := s.clone() 256 snapshots[i] = &c 257 } 258 return rollupRule{ 259 uuid: rc.uuid, 260 snapshots: snapshots, 261 } 262 } 263 264 // proto returns the proto message for the given rollup rule. 265 func (rc *rollupRule) proto() (*rulepb.RollupRule, error) { 266 snapshots := make([]*rulepb.RollupRuleSnapshot, len(rc.snapshots)) 267 for i, s := range rc.snapshots { 268 snapshot, err := s.proto() 269 if err != nil { 270 return nil, err 271 } 272 snapshots[i] = snapshot 273 } 274 275 return &rulepb.RollupRule{ 276 Uuid: rc.uuid, 277 Snapshots: snapshots, 278 }, nil 279 } 280 281 // activeSnapshot returns the latest rule snapshot whose cutover time is earlier 282 // than or equal to timeNanos, or nil if not found. 283 func (rc *rollupRule) activeSnapshot(timeNanos int64) *rollupRuleSnapshot { 284 idx := rc.activeIndex(timeNanos) 285 if idx < 0 { 286 return nil 287 } 288 return rc.snapshots[idx] 289 } 290 291 // activeRule returns the rule containing snapshots that's in effect at time timeNanos 292 // and all future rules after time timeNanos. 293 func (rc *rollupRule) activeRule(timeNanos int64) *rollupRule { 294 idx := rc.activeIndex(timeNanos) 295 if idx < 0 { 296 return rc 297 } 298 return &rollupRule{ 299 uuid: rc.uuid, 300 snapshots: rc.snapshots[idx:]} 301 } 302 303 func (rc *rollupRule) activeIndex(timeNanos int64) int { 304 idx := len(rc.snapshots) - 1 305 for idx >= 0 && rc.snapshots[idx].cutoverNanos > timeNanos { 306 idx-- 307 } 308 return idx 309 } 310 311 func (rc *rollupRule) name() (string, error) { 312 if len(rc.snapshots) == 0 { 313 return "", errNoRuleSnapshots 314 } 315 latest := rc.snapshots[len(rc.snapshots)-1] 316 return latest.name, nil 317 } 318 319 func (rc *rollupRule) tombstoned() bool { 320 if len(rc.snapshots) == 0 { 321 return true 322 } 323 324 latest := rc.snapshots[len(rc.snapshots)-1] 325 return latest.tombstoned 326 } 327 328 func (rc *rollupRule) addSnapshot( 329 name string, 330 rawFilter string, 331 rollupTargets []rollupTarget, 332 meta UpdateMetadata, 333 keepOriginal bool, 334 tags []models.Tag, 335 ) error { 336 snapshot, err := newRollupRuleSnapshotFromFields( 337 name, 338 meta.cutoverNanos, 339 rawFilter, 340 rollupTargets, 341 nil, 342 meta.updatedAtNanos, 343 meta.updatedBy, 344 keepOriginal, 345 tags, 346 ) 347 if err != nil { 348 return err 349 } 350 rc.snapshots = append(rc.snapshots, snapshot) 351 return nil 352 } 353 354 func (rc *rollupRule) markTombstoned(meta UpdateMetadata) error { 355 n, err := rc.name() 356 if err != nil { 357 return err 358 } 359 360 if rc.tombstoned() { 361 return merrors.NewInvalidInputError(fmt.Sprintf("%s is already tombstoned", n)) 362 } 363 364 if len(rc.snapshots) == 0 { 365 return errNoRuleSnapshots 366 } 367 368 snapshot := rc.snapshots[len(rc.snapshots)-1].clone() 369 snapshot.tombstoned = true 370 snapshot.cutoverNanos = meta.cutoverNanos 371 snapshot.lastUpdatedAtNanos = meta.updatedAtNanos 372 snapshot.lastUpdatedBy = meta.updatedBy 373 snapshot.targets = nil 374 snapshot.keepOriginal = false 375 rc.snapshots = append(rc.snapshots, &snapshot) 376 return nil 377 } 378 379 func (rc *rollupRule) revive( 380 name string, 381 rawFilter string, 382 targets []rollupTarget, 383 meta UpdateMetadata, 384 keepOriginal bool, 385 tags []models.Tag, 386 ) error { 387 n, err := rc.name() 388 if err != nil { 389 return err 390 } 391 if !rc.tombstoned() { 392 return merrors.NewInvalidInputError(fmt.Sprintf("%s is not tombstoned", n)) 393 } 394 return rc.addSnapshot(name, rawFilter, targets, meta, keepOriginal, tags) 395 } 396 397 func (rc *rollupRule) history() ([]view.RollupRule, error) { 398 lastIdx := len(rc.snapshots) - 1 399 views := make([]view.RollupRule, len(rc.snapshots)) 400 // Snapshots are stored oldest -> newest. History should start with newest. 401 for i := 0; i < len(rc.snapshots); i++ { 402 rrs, err := rc.rollupRuleView(lastIdx - i) 403 if err != nil { 404 return nil, err 405 } 406 views[i] = rrs 407 } 408 return views, nil 409 } 410 411 func (rc *rollupRule) rollupRuleView(snapshotIdx int) (view.RollupRule, error) { 412 if snapshotIdx < 0 || snapshotIdx >= len(rc.snapshots) { 413 return view.RollupRule{}, errRollupRuleSnapshotIndexOutOfRange 414 } 415 416 rrs := rc.snapshots[snapshotIdx].clone() 417 targets := make([]view.RollupTarget, len(rrs.targets)) 418 for i, t := range rrs.targets { 419 targets[i] = t.rollupTargetView() 420 } 421 422 return view.RollupRule{ 423 ID: rc.uuid, 424 Name: rrs.name, 425 Tombstoned: rrs.tombstoned, 426 CutoverMillis: rrs.cutoverNanos / nanosPerMilli, 427 Filter: rrs.rawFilter, 428 Targets: targets, 429 LastUpdatedBy: rrs.lastUpdatedBy, 430 LastUpdatedAtMillis: rrs.lastUpdatedAtNanos / nanosPerMilli, 431 KeepOriginal: rrs.keepOriginal, 432 Tags: rrs.tags, 433 }, nil 434 }