github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/metrics/rules/mapping.go (about) 1 // Copyright (c) 2017 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 "bytes" 25 "errors" 26 "fmt" 27 "time" 28 29 "github.com/m3db/m3/src/metrics/aggregation" 30 merrors "github.com/m3db/m3/src/metrics/errors" 31 "github.com/m3db/m3/src/metrics/filters" 32 "github.com/m3db/m3/src/metrics/generated/proto/metricpb" 33 "github.com/m3db/m3/src/metrics/generated/proto/policypb" 34 "github.com/m3db/m3/src/metrics/generated/proto/rulepb" 35 "github.com/m3db/m3/src/metrics/metric" 36 "github.com/m3db/m3/src/metrics/policy" 37 "github.com/m3db/m3/src/metrics/rules/view" 38 "github.com/m3db/m3/src/query/models" 39 40 "github.com/pborman/uuid" 41 ) 42 43 const ( 44 nanosPerMilli = int64(time.Millisecond / time.Nanosecond) 45 ) 46 47 var ( 48 errNoStoragePoliciesAndDropPolicyInMappingRuleSnapshot = errors.New("no storage policies and no drop policy in mapping rule snapshot") 49 errInvalidDropPolicyInMappRuleSnapshot = errors.New("invalid drop policy in mapping rule snapshot") 50 errStoragePoliciesAndDropPolicyInMappingRuleSnapshot = errors.New("storage policies and a drop policy specified in mapping rule snapshot") 51 errMappingRuleSnapshotIndexOutOfRange = errors.New("mapping rule snapshot index out of range") 52 errNilMappingRuleSnapshotProto = errors.New("nil mapping rule snapshot proto") 53 errNilMappingRuleProto = errors.New("nil mapping rule proto") 54 55 pathSeparator = []byte(".") 56 ) 57 58 // mappingRuleSnapshot defines a rule snapshot such that if a metric matches the 59 // provided filters, it is aggregated and retained under the provided set of policies. 60 type mappingRuleSnapshot struct { 61 name string 62 tombstoned bool 63 cutoverNanos int64 64 filter filters.TagsFilter 65 rawFilter string 66 aggregationID aggregation.ID 67 storagePolicies policy.StoragePolicies 68 dropPolicy policy.DropPolicy 69 tags []models.Tag 70 graphitePrefix [][]byte 71 lastUpdatedAtNanos int64 72 lastUpdatedBy string 73 } 74 75 func newMappingRuleSnapshotFromProto( 76 r *rulepb.MappingRuleSnapshot, 77 opts filters.TagsFilterOptions, 78 ) (*mappingRuleSnapshot, error) { 79 if r == nil { 80 return nil, errNilMappingRuleSnapshotProto 81 } 82 var ( 83 aggregationID aggregation.ID 84 storagePolicies policy.StoragePolicies 85 dropPolicy policy.DropPolicy 86 err error 87 ) 88 if len(r.Policies) > 0 { 89 // Extract the aggregation ID and storage policies from v1 proto (i.e., policies list). 90 aggregationID, storagePolicies, err = toAggregationIDAndStoragePolicies(r.Policies) 91 if err != nil { 92 return nil, err 93 } 94 } else if len(r.StoragePolicies) > 0 { 95 // Unmarshal aggregation ID and storage policies directly from v2 proto. 96 aggregationID, err = aggregation.NewIDFromProto(r.AggregationTypes) 97 if err != nil { 98 return nil, err 99 } 100 storagePolicies, err = policy.NewStoragePoliciesFromProto(r.StoragePolicies) 101 if err != nil { 102 return nil, err 103 } 104 } 105 106 if r.DropPolicy != policypb.DropPolicy_NONE { 107 dropPolicy = policy.DropPolicy(r.DropPolicy) 108 if !dropPolicy.IsValid() { 109 return nil, errInvalidDropPolicyInMappRuleSnapshot 110 } 111 } 112 113 if !r.Tombstoned && len(storagePolicies) == 0 && dropPolicy == policy.DropNone { 114 return nil, errNoStoragePoliciesAndDropPolicyInMappingRuleSnapshot 115 } 116 117 if len(storagePolicies) > 0 && dropPolicy != policy.DropNone { 118 return nil, errStoragePoliciesAndDropPolicyInMappingRuleSnapshot 119 } 120 121 filterValues, err := filters.ParseTagFilterValueMap(r.Filter) 122 if err != nil { 123 return nil, err 124 } 125 filter, err := filters.NewTagsFilter(filterValues, filters.Conjunction, opts) 126 if err != nil { 127 return nil, err 128 } 129 130 return newMappingRuleSnapshotFromFieldsInternal( 131 r.Name, 132 r.Tombstoned, 133 r.CutoverNanos, 134 filter, 135 r.Filter, 136 aggregationID, 137 storagePolicies, 138 policy.DropPolicy(r.DropPolicy), 139 models.TagsFromProto(r.Tags), 140 r.LastUpdatedAtNanos, 141 r.LastUpdatedBy, 142 ), nil 143 } 144 145 func newMappingRuleSnapshotFromFields( 146 name string, 147 cutoverNanos int64, 148 filter filters.TagsFilter, 149 rawFilter string, 150 aggregationID aggregation.ID, 151 storagePolicies policy.StoragePolicies, 152 dropPolicy policy.DropPolicy, 153 tags []models.Tag, 154 lastUpdatedAtNanos int64, 155 lastUpdatedBy string, 156 ) (*mappingRuleSnapshot, error) { 157 if _, err := filters.ValidateTagsFilter(rawFilter); err != nil { 158 return nil, err 159 } 160 return newMappingRuleSnapshotFromFieldsInternal( 161 name, 162 false, 163 cutoverNanos, 164 filter, 165 rawFilter, 166 aggregationID, 167 storagePolicies, 168 dropPolicy, 169 tags, 170 lastUpdatedAtNanos, 171 lastUpdatedBy, 172 ), nil 173 } 174 175 // newMappingRuleSnapshotFromFieldsInternal creates a new mapping rule snapshot 176 // from various given fields assuming the filter has already been validated. 177 func newMappingRuleSnapshotFromFieldsInternal( 178 name string, 179 tombstoned bool, 180 cutoverNanos int64, 181 filter filters.TagsFilter, 182 rawFilter string, 183 aggregationID aggregation.ID, 184 storagePolicies policy.StoragePolicies, 185 dropPolicy policy.DropPolicy, 186 tags []models.Tag, 187 lastUpdatedAtNanos int64, 188 lastUpdatedBy string, 189 ) *mappingRuleSnapshot { 190 // If we have a graphite prefix tag, then parse that out here so that it 191 // can be used later. 192 var graphitePrefix [][]byte 193 for _, tag := range tags { 194 if bytes.Equal(tag.Name, metric.M3MetricsGraphitePrefix) { 195 graphitePrefix = bytes.Split(tag.Value, pathSeparator) 196 } 197 } 198 199 return &mappingRuleSnapshot{ 200 name: name, 201 tombstoned: tombstoned, 202 cutoverNanos: cutoverNanos, 203 filter: filter, 204 rawFilter: rawFilter, 205 aggregationID: aggregationID, 206 storagePolicies: storagePolicies, 207 dropPolicy: dropPolicy, 208 tags: tags, 209 graphitePrefix: graphitePrefix, 210 lastUpdatedAtNanos: lastUpdatedAtNanos, 211 lastUpdatedBy: lastUpdatedBy, 212 } 213 } 214 215 func (mrs *mappingRuleSnapshot) clone() mappingRuleSnapshot { 216 tags := make([]models.Tag, len(mrs.tags)) 217 copy(tags, mrs.tags) 218 return mappingRuleSnapshot{ 219 name: mrs.name, 220 tombstoned: mrs.tombstoned, 221 cutoverNanos: mrs.cutoverNanos, 222 filter: mrs.filter, 223 rawFilter: mrs.rawFilter, 224 aggregationID: mrs.aggregationID, 225 storagePolicies: mrs.storagePolicies.Clone(), 226 dropPolicy: mrs.dropPolicy, 227 tags: mrs.tags, 228 lastUpdatedAtNanos: mrs.lastUpdatedAtNanos, 229 lastUpdatedBy: mrs.lastUpdatedBy, 230 } 231 } 232 233 // proto returns the given MappingRuleSnapshot in protobuf form. 234 func (mrs *mappingRuleSnapshot) proto() (*rulepb.MappingRuleSnapshot, error) { 235 aggTypes, err := mrs.aggregationID.Types() 236 if err != nil { 237 return nil, err 238 } 239 pbAggTypes, err := aggTypes.Proto() 240 if err != nil { 241 return nil, err 242 } 243 storagePolicies, err := mrs.storagePolicies.Proto() 244 if err != nil { 245 return nil, err 246 } 247 tags := make([]*metricpb.Tag, 0, len(mrs.tags)) 248 for _, tag := range mrs.tags { 249 tags = append(tags, tag.ToProto()) 250 } 251 return &rulepb.MappingRuleSnapshot{ 252 Name: mrs.name, 253 Tombstoned: mrs.tombstoned, 254 CutoverNanos: mrs.cutoverNanos, 255 Filter: mrs.rawFilter, 256 LastUpdatedAtNanos: mrs.lastUpdatedAtNanos, 257 LastUpdatedBy: mrs.lastUpdatedBy, 258 AggregationTypes: pbAggTypes, 259 StoragePolicies: storagePolicies, 260 DropPolicy: policypb.DropPolicy(mrs.dropPolicy), 261 Tags: tags, 262 }, nil 263 } 264 265 // mappingRule stores mapping rule snapshots. 266 type mappingRule struct { 267 uuid string 268 snapshots []*mappingRuleSnapshot 269 } 270 271 func newEmptyMappingRule() *mappingRule { 272 return &mappingRule{uuid: uuid.New()} 273 } 274 275 func newMappingRuleFromProto( 276 mc *rulepb.MappingRule, 277 opts filters.TagsFilterOptions, 278 ) (*mappingRule, error) { 279 if mc == nil { 280 return nil, errNilMappingRuleProto 281 } 282 snapshots := make([]*mappingRuleSnapshot, 0, len(mc.Snapshots)) 283 for i := 0; i < len(mc.Snapshots); i++ { 284 mr, err := newMappingRuleSnapshotFromProto(mc.Snapshots[i], opts) 285 if err != nil { 286 return nil, err 287 } 288 snapshots = append(snapshots, mr) 289 } 290 return &mappingRule{ 291 uuid: mc.Uuid, 292 snapshots: snapshots, 293 }, nil 294 } 295 296 func (mc *mappingRule) clone() mappingRule { 297 snapshots := make([]*mappingRuleSnapshot, len(mc.snapshots)) 298 for i, s := range mc.snapshots { 299 c := s.clone() 300 snapshots[i] = &c 301 } 302 return mappingRule{ 303 uuid: mc.uuid, 304 snapshots: snapshots, 305 } 306 } 307 308 // proto returns the given MappingRule in protobuf form. 309 func (mc *mappingRule) proto() (*rulepb.MappingRule, error) { 310 snapshots := make([]*rulepb.MappingRuleSnapshot, len(mc.snapshots)) 311 for i, s := range mc.snapshots { 312 snapshot, err := s.proto() 313 if err != nil { 314 return nil, err 315 } 316 snapshots[i] = snapshot 317 } 318 319 return &rulepb.MappingRule{ 320 Uuid: mc.uuid, 321 Snapshots: snapshots, 322 }, nil 323 } 324 325 // activeSnapshot returns the active rule snapshot whose cutover time is no later than 326 // the time passed in, or nil if no such rule snapshot exists. 327 func (mc *mappingRule) activeSnapshot(timeNanos int64) *mappingRuleSnapshot { 328 idx := mc.activeIndex(timeNanos) 329 if idx < 0 { 330 return nil 331 } 332 return mc.snapshots[idx] 333 } 334 335 // activeRule returns the rule containing snapshots that's in effect at time timeNanos 336 // and all future snapshots after time timeNanos. 337 func (mc *mappingRule) activeRule(timeNanos int64) *mappingRule { 338 idx := mc.activeIndex(timeNanos) 339 // If there are no snapshots that are currently in effect, it means either all 340 // snapshots are in the future, or there are no snapshots. 341 if idx < 0 { 342 return mc 343 } 344 return &mappingRule{ 345 uuid: mc.uuid, 346 snapshots: mc.snapshots[idx:], 347 } 348 } 349 350 func (mc *mappingRule) name() (string, error) { 351 if len(mc.snapshots) == 0 { 352 return "", errNoRuleSnapshots 353 } 354 latest := mc.snapshots[len(mc.snapshots)-1] 355 return latest.name, nil 356 } 357 358 func (mc *mappingRule) tombstoned() bool { 359 if len(mc.snapshots) == 0 { 360 return true 361 } 362 latest := mc.snapshots[len(mc.snapshots)-1] 363 return latest.tombstoned 364 } 365 366 func (mc *mappingRule) addSnapshot( 367 name string, 368 rawFilter string, 369 aggregationID aggregation.ID, 370 storagePolicies policy.StoragePolicies, 371 dropPolicy policy.DropPolicy, 372 tags []models.Tag, 373 meta UpdateMetadata, 374 ) error { 375 snapshot, err := newMappingRuleSnapshotFromFields( 376 name, 377 meta.cutoverNanos, 378 nil, 379 rawFilter, 380 aggregationID, 381 storagePolicies, 382 dropPolicy, 383 tags, 384 meta.updatedAtNanos, 385 meta.updatedBy, 386 ) 387 if err != nil { 388 return err 389 } 390 mc.snapshots = append(mc.snapshots, snapshot) 391 return nil 392 } 393 394 func (mc *mappingRule) markTombstoned(meta UpdateMetadata) error { 395 n, err := mc.name() 396 if err != nil { 397 return err 398 } 399 400 if mc.tombstoned() { 401 return merrors.NewInvalidInputError(fmt.Sprintf("%s is already tombstoned", n)) 402 } 403 if len(mc.snapshots) == 0 { 404 return errNoRuleSnapshots 405 } 406 snapshot := mc.snapshots[len(mc.snapshots)-1].clone() 407 snapshot.tombstoned = true 408 snapshot.cutoverNanos = meta.cutoverNanos 409 snapshot.lastUpdatedAtNanos = meta.updatedAtNanos 410 snapshot.lastUpdatedBy = meta.updatedBy 411 snapshot.aggregationID = aggregation.DefaultID 412 snapshot.storagePolicies = nil 413 snapshot.dropPolicy = 0 414 mc.snapshots = append(mc.snapshots, &snapshot) 415 return nil 416 } 417 418 func (mc *mappingRule) revive( 419 name string, 420 rawFilter string, 421 aggregationID aggregation.ID, 422 storagePolicies policy.StoragePolicies, 423 dropPolicy policy.DropPolicy, 424 tags []models.Tag, 425 meta UpdateMetadata, 426 ) error { 427 n, err := mc.name() 428 if err != nil { 429 return err 430 } 431 if !mc.tombstoned() { 432 return merrors.NewInvalidInputError(fmt.Sprintf("%s is not tombstoned", n)) 433 } 434 return mc.addSnapshot(name, rawFilter, aggregationID, storagePolicies, 435 dropPolicy, tags, meta) 436 } 437 438 func (mc *mappingRule) activeIndex(timeNanos int64) int { 439 idx := len(mc.snapshots) - 1 440 for idx >= 0 && mc.snapshots[idx].cutoverNanos > timeNanos { 441 idx-- 442 } 443 return idx 444 } 445 446 func (mc *mappingRule) history() ([]view.MappingRule, error) { 447 lastIdx := len(mc.snapshots) - 1 448 views := make([]view.MappingRule, len(mc.snapshots)) 449 // Snapshots are stored oldest -> newest. History should start with newest. 450 for i := 0; i < len(mc.snapshots); i++ { 451 mrs, err := mc.mappingRuleView(lastIdx - i) 452 if err != nil { 453 return nil, err 454 } 455 views[i] = mrs 456 } 457 return views, nil 458 } 459 460 func (mc *mappingRule) mappingRuleView(snapshotIdx int) (view.MappingRule, error) { 461 if snapshotIdx < 0 || snapshotIdx >= len(mc.snapshots) { 462 return view.MappingRule{}, errMappingRuleSnapshotIndexOutOfRange 463 } 464 465 mrs := mc.snapshots[snapshotIdx].clone() 466 return view.MappingRule{ 467 ID: mc.uuid, 468 Name: mrs.name, 469 Tombstoned: mrs.tombstoned, 470 CutoverMillis: mrs.cutoverNanos / nanosPerMilli, 471 DropPolicy: mrs.dropPolicy, 472 Filter: mrs.rawFilter, 473 AggregationID: mrs.aggregationID, 474 StoragePolicies: mrs.storagePolicies, 475 LastUpdatedBy: mrs.lastUpdatedBy, 476 LastUpdatedAtMillis: mrs.lastUpdatedAtNanos / nanosPerMilli, 477 Tags: mrs.tags, 478 }, nil 479 }