github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/metrics/pipeline/applied/type.go (about)

     1  // Copyright (c) 2018 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 applied
    22  
    23  import (
    24  	"bytes"
    25  	"errors"
    26  	"fmt"
    27  	"strings"
    28  
    29  	"github.com/m3db/m3/src/metrics/aggregation"
    30  	"github.com/m3db/m3/src/metrics/generated/proto/pipelinepb"
    31  	"github.com/m3db/m3/src/metrics/generated/proto/transformationpb"
    32  	"github.com/m3db/m3/src/metrics/pipeline"
    33  	"github.com/m3db/m3/src/metrics/transformation"
    34  )
    35  
    36  var (
    37  	// DefaultPipeline is a default pipeline.
    38  	DefaultPipeline Pipeline
    39  
    40  	errNilAppliedRollupOpProto  = errors.New("nil applied rollup op proto message")
    41  	errUnknownOpType            = errors.New("unknown op type")
    42  	errOperationsLengthMismatch = errors.New("operations list length does not match proto")
    43  	errNilTransformationOpProto = errors.New("nil transformation op proto message")
    44  )
    45  
    46  // RollupOp captures the rollup metadata after the operation is applied against a metric ID.
    47  type RollupOp struct {
    48  	// Metric ID generated as a result of the rollup.
    49  	ID []byte
    50  	// Type of aggregations performed within each unique dimension combination.
    51  	AggregationID aggregation.ID
    52  }
    53  
    54  // Equal determines whether two rollup Operations are equal.
    55  func (op RollupOp) Equal(other RollupOp) bool {
    56  	return op.AggregationID == other.AggregationID && bytes.Equal(op.ID, other.ID)
    57  }
    58  
    59  // Clone clones the rollup operation.
    60  func (op RollupOp) Clone() RollupOp {
    61  	idClone := make([]byte, len(op.ID))
    62  	copy(idClone, op.ID)
    63  	return RollupOp{ID: idClone, AggregationID: op.AggregationID}
    64  }
    65  
    66  func (op RollupOp) String() string {
    67  	return fmt.Sprintf("{id: %s, aggregation: %v}", op.ID, op.AggregationID)
    68  }
    69  
    70  // ToProto converts the applied rollup op to a protobuf message in place.
    71  func (op RollupOp) ToProto(pb *pipelinepb.AppliedRollupOp) error {
    72  	op.AggregationID.ToProto(&pb.AggregationId)
    73  	pb.Id = op.ID
    74  	return nil
    75  }
    76  
    77  // FromProto converts the protobuf message to an applied rollup op in place.
    78  func (op *RollupOp) FromProto(pb pipelinepb.AppliedRollupOp) error {
    79  	op.AggregationID.FromProto(pb.AggregationId)
    80  	op.ID = pb.Id
    81  	return nil
    82  }
    83  
    84  // OpUnion is a union of different types of operation.
    85  type OpUnion struct {
    86  	Rollup         RollupOp
    87  	Type           pipeline.OpType
    88  	Transformation pipeline.TransformationOp
    89  }
    90  
    91  // Equal determines whether two operation unions are equal.
    92  func (u OpUnion) Equal(other OpUnion) bool {
    93  	// keep in sync with Pipeline.Equal as go is terrible at inlining anything with a loop
    94  	if u.Type != other.Type {
    95  		return false
    96  	}
    97  
    98  	if u.Type == pipeline.RollupOpType {
    99  		return u.Rollup.Equal(other.Rollup)
   100  	}
   101  
   102  	return u.Transformation.Type == other.Transformation.Type
   103  }
   104  
   105  // Clone clones an operation union.
   106  func (u OpUnion) Clone() OpUnion {
   107  	clone := OpUnion{
   108  		Type:           u.Type,
   109  		Transformation: u.Transformation,
   110  	}
   111  	if u.Type == pipeline.RollupOpType {
   112  		clone.Rollup = u.Rollup.Clone()
   113  	}
   114  	return clone
   115  }
   116  
   117  func (u OpUnion) String() string {
   118  	var b bytes.Buffer
   119  	b.WriteString("{")
   120  	switch u.Type {
   121  	case pipeline.TransformationOpType:
   122  		fmt.Fprintf(&b, "transformation: %s", u.Transformation.String())
   123  	case pipeline.RollupOpType:
   124  		fmt.Fprintf(&b, "rollup: %s", u.Rollup.String())
   125  	default:
   126  		fmt.Fprintf(&b, "unknown op type: %v", u.Type)
   127  	}
   128  	b.WriteString("}")
   129  	return b.String()
   130  }
   131  
   132  // ToProto converts the applied pipeline op to a protobuf message in place.
   133  func (u OpUnion) ToProto(pb *pipelinepb.AppliedPipelineOp) error {
   134  	pb.Reset()
   135  	switch u.Type {
   136  	case pipeline.TransformationOpType:
   137  		pb.Type = pipelinepb.AppliedPipelineOp_TRANSFORMATION
   138  		return u.Transformation.ToProto(&pb.Transformation)
   139  	case pipeline.RollupOpType:
   140  		pb.Type = pipelinepb.AppliedPipelineOp_ROLLUP
   141  		return u.Rollup.ToProto(&pb.Rollup)
   142  	default:
   143  		return errUnknownOpType
   144  	}
   145  }
   146  
   147  // Reset resets the operation union.
   148  func (u *OpUnion) Reset() { *u = OpUnion{} }
   149  
   150  // FromProto converts the protobuf message to an applied pipeline op in place.
   151  func (u *OpUnion) FromProto(pb pipelinepb.AppliedPipelineOp) error {
   152  	switch pb.Type {
   153  	case pipelinepb.AppliedPipelineOp_TRANSFORMATION:
   154  		u.Type = pipeline.TransformationOpType
   155  		if u.Rollup.ID != nil {
   156  			u.Rollup.ID = u.Rollup.ID[:0]
   157  		}
   158  		u.Rollup.AggregationID[0] = aggregation.DefaultID[0]
   159  		return u.Transformation.FromProto(pb.Transformation)
   160  	case pipelinepb.AppliedPipelineOp_ROLLUP:
   161  		u.Type = pipeline.RollupOpType
   162  		u.Transformation.Type = transformation.UnknownType
   163  		return u.Rollup.FromProto(pb.Rollup)
   164  	default:
   165  		return errUnknownOpType
   166  	}
   167  }
   168  
   169  // Pipeline is a pipeline of operations.
   170  type Pipeline struct {
   171  	// a list of pipeline Operations.
   172  	Operations []OpUnion
   173  }
   174  
   175  // NewPipeline creates a new pipeline.
   176  func NewPipeline(ops []OpUnion) Pipeline {
   177  	return Pipeline{Operations: ops}
   178  }
   179  
   180  // Len returns the number of steps in a pipeline.
   181  func (p Pipeline) Len() int { return len(p.Operations) }
   182  
   183  func (p Pipeline) Swap(i, j int) { p.Operations[i], p.Operations[j] = p.Operations[j], p.Operations[i] }
   184  func (p Pipeline) Less(i, j int) bool {
   185  	return strings.Compare(p.Operations[i].String(), p.Operations[j].String()) == -1
   186  }
   187  
   188  // IsEmpty determines whether a pipeline is empty.
   189  func (p Pipeline) IsEmpty() bool { return len(p.Operations) == 0 }
   190  
   191  // At returns the operation at a given step.
   192  func (p Pipeline) At(i int) OpUnion { return p.Operations[i] }
   193  
   194  // Equal determines whether two pipelines are equal.
   195  func (p Pipeline) Equal(other Pipeline) bool {
   196  	// keep in sync with OpUnion.Equal as go is terrible at inlining anything with a loop
   197  	if len(p.Operations) != len(other.Operations) {
   198  		return false
   199  	}
   200  
   201  	for i := 0; i < len(p.Operations); i++ {
   202  		if p.Operations[i].Type != other.Operations[i].Type {
   203  			return false
   204  		}
   205  		//nolint:exhaustive
   206  		switch p.Operations[i].Type {
   207  		case pipeline.RollupOpType:
   208  			if !p.Operations[i].Rollup.Equal(other.Operations[i].Rollup) {
   209  				return false
   210  			}
   211  		case pipeline.TransformationOpType:
   212  			if p.Operations[i].Transformation.Type != other.Operations[i].Transformation.Type {
   213  				return false
   214  			}
   215  		}
   216  	}
   217  
   218  	return true
   219  }
   220  
   221  // Clone clones the pipeline.
   222  func (p Pipeline) Clone() Pipeline {
   223  	clone := p
   224  	clone.Operations = make([]OpUnion, len(p.Operations))
   225  	for i := range p.Operations {
   226  		clone.Operations[i] = p.Operations[i].Clone()
   227  	}
   228  	return clone
   229  }
   230  
   231  // SubPipeline returns a sub-pipeline containing Operations between step `startInclusive`
   232  // and step `endExclusive` of the current pipeline.
   233  func (p Pipeline) SubPipeline(startInclusive int, endExclusive int) Pipeline {
   234  	sub := p
   235  	sub.Operations = p.Operations[startInclusive:endExclusive]
   236  	return sub
   237  }
   238  
   239  func (p Pipeline) String() string {
   240  	var b bytes.Buffer
   241  	b.WriteString("{operations: [")
   242  	for i, op := range p.Operations {
   243  		b.WriteString(op.String())
   244  		if i < len(p.Operations)-1 {
   245  			b.WriteString(", ")
   246  		}
   247  	}
   248  	b.WriteString("]}")
   249  	return b.String()
   250  }
   251  
   252  // ToProto converts the applied pipeline to a protobuf message in place.
   253  func (p Pipeline) ToProto(pb *pipelinepb.AppliedPipeline) error {
   254  	numOps := len(p.Operations)
   255  	if cap(pb.Ops) >= numOps {
   256  		pb.Ops = pb.Ops[:numOps]
   257  	} else {
   258  		pb.Ops = make([]pipelinepb.AppliedPipelineOp, numOps)
   259  	}
   260  	for i := 0; i < numOps; i++ {
   261  		if err := p.Operations[i].ToProto(&pb.Ops[i]); err != nil {
   262  			return err
   263  		}
   264  	}
   265  	return nil
   266  }
   267  
   268  // FromProto converts the protobuf message to an applied pipeline in place.
   269  func (p *Pipeline) FromProto(pb pipelinepb.AppliedPipeline) error {
   270  	numOps := len(pb.Ops)
   271  	if cap(p.Operations) >= numOps {
   272  		p.Operations = p.Operations[:numOps]
   273  	} else {
   274  		p.Operations = make([]OpUnion, numOps)
   275  	}
   276  	for i := 0; i < numOps; i++ {
   277  		if err := p.Operations[i].FromProto(pb.Ops[i]); err != nil {
   278  			return err
   279  		}
   280  	}
   281  	return nil
   282  }
   283  
   284  // IsMappingRule returns whether this is a mapping rule, determined by
   285  // if any rollup pipelines are included.
   286  func (p Pipeline) IsMappingRule() bool {
   287  	for _, op := range p.Operations {
   288  		if op.Rollup.ID != nil {
   289  			return false
   290  		}
   291  	}
   292  	return true
   293  }
   294  
   295  // WithResets returns a new Pipeline with Add transformations replaced with Reset transformations.
   296  // See transformReset for why Reset should be used instead of Add.
   297  func (p Pipeline) WithResets() Pipeline {
   298  	for i, o := range p.Operations {
   299  		if o.Transformation.Type == transformation.Add {
   300  			o.Transformation.Type = transformation.Reset
   301  			p.Operations[i] = o
   302  		}
   303  	}
   304  	return p
   305  }
   306  
   307  // OperationsFromProto converts a list of protobuf AppliedPipelineOps, used in optimized staged metadata methods.
   308  func OperationsFromProto(pb []pipelinepb.AppliedPipelineOp, ops []OpUnion) error {
   309  	numOps := len(pb)
   310  	if numOps != len(ops) {
   311  		return errOperationsLengthMismatch
   312  	}
   313  	for i := 0; i < numOps; i++ {
   314  		u := &ops[i]
   315  		u.Type = pipeline.OpType(pb[i].Type + 1)
   316  		switch u.Type {
   317  		case pipeline.TransformationOpType:
   318  			if u.Rollup.ID != nil {
   319  				u.Rollup.ID = u.Rollup.ID[:0]
   320  			}
   321  			u.Rollup.AggregationID[0] = aggregation.DefaultID[0]
   322  			if pb[i].Transformation.Type == transformationpb.TransformationType_UNKNOWN {
   323  				return errNilTransformationOpProto
   324  			}
   325  			if err := u.Transformation.Type.FromProto(pb[i].Transformation.Type); err != nil {
   326  				return err
   327  			}
   328  		case pipeline.RollupOpType:
   329  			u.Transformation.Type = transformation.UnknownType
   330  			if pb == nil {
   331  				return errNilAppliedRollupOpProto
   332  			}
   333  			u.Rollup.AggregationID[0] = pb[i].Rollup.AggregationId.Id
   334  			u.Rollup.ID = pb[i].Rollup.Id
   335  		default:
   336  			return errUnknownOpType
   337  		}
   338  	}
   339  	return nil
   340  }