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  }