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 }