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  }