github.com/m3db/m3@v1.5.0/src/metrics/aggregation/type.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 aggregation 22 23 import ( 24 "fmt" 25 "strings" 26 27 "github.com/m3db/m3/src/metrics/generated/proto/aggregationpb" 28 "github.com/m3db/m3/src/x/pool" 29 ) 30 31 // Supported aggregation types. 32 const ( 33 UnknownType Type = iota 34 Last 35 Min 36 Max 37 Mean 38 Median 39 Count 40 Sum 41 SumSq 42 Stdev 43 P10 44 P20 45 P30 46 P40 47 P50 48 P60 49 P70 50 P80 51 P90 52 P95 53 P99 54 P999 55 P9999 56 P25 57 P75 58 59 nextTypeID = iota 60 ) 61 62 const ( 63 // maxTypeID is the largest id of all the valid aggregation types. 64 // NB(cw) maxTypeID is guaranteed to be greater or equal 65 // to len(ValidTypes). 66 // Iff ids of all the valid aggregation types are consecutive, 67 // maxTypeID == len(ValidTypes). 68 maxTypeID = nextTypeID - 1 69 70 typesSeparator = "," 71 ) 72 73 var ( 74 emptyStruct struct{} 75 76 // DefaultTypes is a default list of aggregation types. 77 DefaultTypes Types 78 79 // ValidTypes is the list of all the valid aggregation types. 80 ValidTypes = map[Type]struct{}{ 81 Last: emptyStruct, 82 Min: emptyStruct, 83 Max: emptyStruct, 84 Mean: emptyStruct, 85 Median: emptyStruct, 86 Count: emptyStruct, 87 Sum: emptyStruct, 88 SumSq: emptyStruct, 89 Stdev: emptyStruct, 90 P10: emptyStruct, 91 P20: emptyStruct, 92 P25: emptyStruct, 93 P30: emptyStruct, 94 P40: emptyStruct, 95 P50: emptyStruct, 96 P60: emptyStruct, 97 P70: emptyStruct, 98 P75: emptyStruct, 99 P80: emptyStruct, 100 P90: emptyStruct, 101 P95: emptyStruct, 102 P99: emptyStruct, 103 P999: emptyStruct, 104 P9999: emptyStruct, 105 } 106 107 typeStringMap map[string]Type 108 109 typeStringNames = map[Type][]byte{ 110 Last: []byte("last"), 111 Min: []byte("lower"), 112 Max: []byte("upper"), 113 Mean: []byte("mean"), 114 Median: []byte("median"), 115 Count: []byte("count"), 116 Sum: []byte("sum"), 117 SumSq: []byte("sum_sq"), 118 Stdev: []byte("stdev"), 119 P10: []byte("p10"), 120 P20: []byte("p20"), 121 P25: []byte("p25"), 122 P30: []byte("p30"), 123 P40: []byte("p40"), 124 P50: []byte("p50"), 125 P60: []byte("p60"), 126 P70: []byte("p70"), 127 P75: []byte("p75"), 128 P80: []byte("p80"), 129 P90: []byte("p90"), 130 P95: []byte("p95"), 131 P99: []byte("p99"), 132 P999: []byte("p999"), 133 P9999: []byte("p9999"), 134 } 135 136 typeQuantileBytes = map[Type][]byte{ 137 P10: []byte("0.1"), 138 P20: []byte("0.2"), 139 P25: []byte("0.25"), 140 P30: []byte("0.3"), 141 P40: []byte("0.4"), 142 P50: []byte("0.5"), 143 P60: []byte("0.6"), 144 P70: []byte("0.7"), 145 P75: []byte("0.75"), 146 P80: []byte("0.8"), 147 P90: []byte("0.9"), 148 P95: []byte("0.95"), 149 P99: []byte("0.99"), 150 P999: []byte("0.999"), 151 P9999: []byte("0.9999"), 152 } 153 ) 154 155 // Type defines an aggregation function. 156 type Type int 157 158 // NewTypeFromProto creates an aggregation type from a proto. 159 func NewTypeFromProto(input aggregationpb.AggregationType) (Type, error) { 160 aggType := Type(input) 161 if !aggType.IsValid() { 162 return UnknownType, fmt.Errorf("invalid aggregation type from proto: %s", input) 163 } 164 return aggType, nil 165 } 166 167 // ID returns the id of the Type. 168 func (a Type) ID() int { 169 return int(a) 170 } 171 172 // IsValid checks if an Type is valid. 173 func (a Type) IsValid() bool { 174 _, ok := ValidTypes[a] 175 return ok 176 } 177 178 // IsValidForGauge if an Type is valid for Gauge. 179 func (a Type) IsValidForGauge() bool { 180 switch a { 181 case Last, Min, Max, Mean, Count, Sum, SumSq, Stdev: 182 return true 183 default: 184 return false 185 } 186 } 187 188 // IsValidForCounter if an Type is valid for Counter. 189 func (a Type) IsValidForCounter() bool { 190 switch a { 191 case Min, Max, Mean, Count, Sum, SumSq, Stdev: 192 return true 193 default: 194 return false 195 } 196 } 197 198 // IsValidForTimer if an Type is valid for Timer. 199 func (a Type) IsValidForTimer() bool { 200 switch a { 201 case Last: 202 return false 203 default: 204 return true 205 } 206 } 207 208 // Quantile returns the quantile represented by the Type. 209 func (a Type) Quantile() (float64, bool) { 210 switch a { 211 case P10: 212 return 0.1, true 213 case P20: 214 return 0.2, true 215 case P25: 216 return 0.25, true 217 case P30: 218 return 0.3, true 219 case P40: 220 return 0.4, true 221 case P50, Median: 222 return 0.5, true 223 case P60: 224 return 0.6, true 225 case P70: 226 return 0.7, true 227 case P75: 228 return 0.75, true 229 case P80: 230 return 0.8, true 231 case P90: 232 return 0.9, true 233 case P95: 234 return 0.95, true 235 case P99: 236 return 0.99, true 237 case P999: 238 return 0.999, true 239 case P9999: 240 return 0.9999, true 241 default: 242 return 0, false 243 } 244 } 245 246 // QuantileBytes returns the quantile bytes represented by the Type. 247 func (a Type) QuantileBytes() ([]byte, bool) { 248 val, ok := typeQuantileBytes[a] 249 return val, ok 250 } 251 252 // Proto returns the proto of the aggregation type. 253 func (a Type) Proto() (aggregationpb.AggregationType, error) { 254 s := aggregationpb.AggregationType(a) 255 if err := validateProtoType(s); err != nil { 256 return aggregationpb.AggregationType_UNKNOWN, err 257 } 258 return s, nil 259 } 260 261 // UnmarshalYAML unmarshals text-encoded data into an aggregation type. 262 func (a *Type) UnmarshalYAML(unmarshal func(interface{}) error) error { 263 var str string 264 if err := unmarshal(&str); err != nil { 265 return err 266 } 267 value, err := ParseType(str) 268 if err != nil { 269 return err 270 } 271 *a = value 272 return nil 273 } 274 275 // MarshalText returns the text encoding of an aggregation type. 276 func (a Type) MarshalText() ([]byte, error) { 277 if !a.IsValid() { 278 return nil, fmt.Errorf("invalid aggregation type %s", a.String()) 279 } 280 return []byte(a.String()), nil 281 } 282 283 // UnmarshalText unmarshals text-encoded data into an aggregation type. 284 func (a *Type) UnmarshalText(data []byte) error { 285 str := string(data) 286 parsed, err := ParseType(str) 287 if err != nil { 288 return err 289 } 290 *a = parsed 291 return nil 292 } 293 294 // Name returns the name of the Type. 295 func (a Type) Name() []byte { 296 name, ok := typeStringNames[a] 297 if ok { 298 return name 299 } 300 return a.Bytes() 301 } 302 303 func validateProtoType(a aggregationpb.AggregationType) error { 304 _, ok := aggregationpb.AggregationType_name[int32(a)] 305 if !ok { 306 return fmt.Errorf("invalid proto aggregation type: %v", a) 307 } 308 return nil 309 } 310 311 // ParseType parses an aggregation type. 312 func ParseType(str string) (Type, error) { 313 var ( 314 aggType Type 315 exactMatch bool 316 looseMatch bool 317 ) 318 319 aggType, exactMatch = typeStringMap[str] 320 if !exactMatch { 321 for key, val := range typeStringMap { 322 if strings.ToLower(key) == strings.ToLower(str) { 323 looseMatch = true 324 aggType = val 325 break 326 } 327 } 328 } 329 330 if !exactMatch && !looseMatch { 331 return UnknownType, fmt.Errorf("invalid aggregation type: %s", str) 332 } 333 return aggType, nil 334 } 335 336 // Types is a list of Types. 337 type Types []Type 338 339 // NewTypesFromProto creates a list of aggregation types from a proto. 340 func NewTypesFromProto(input []aggregationpb.AggregationType) (Types, error) { 341 res := make([]Type, len(input)) 342 for i, t := range input { 343 aggType, err := NewTypeFromProto(t) 344 if err != nil { 345 return DefaultTypes, err 346 } 347 res[i] = aggType 348 } 349 return res, nil 350 } 351 352 // Contains checks if the given type is contained in the aggregation types. 353 func (aggTypes Types) Contains(aggType Type) bool { 354 for _, at := range aggTypes { 355 if at == aggType { 356 return true 357 } 358 } 359 return false 360 } 361 362 // IsDefault checks if the Types is the default aggregation type. 363 func (aggTypes Types) IsDefault() bool { 364 return len(aggTypes) == 0 365 } 366 367 // String returns the string representation of the list of aggregation types. 368 func (aggTypes Types) String() string { 369 if len(aggTypes) == 0 { 370 return "" 371 } 372 373 parts := make([]string, len(aggTypes)) 374 for i, aggType := range aggTypes { 375 parts[i] = aggType.String() 376 } 377 return strings.Join(parts, typesSeparator) 378 } 379 380 // IsValidForGauge checks if the list of aggregation types is valid for Gauge. 381 func (aggTypes Types) IsValidForGauge() bool { 382 for _, aggType := range aggTypes { 383 if !aggType.IsValidForGauge() { 384 return false 385 } 386 } 387 return true 388 } 389 390 // IsValidForCounter checks if the list of aggregation types is valid for Counter. 391 func (aggTypes Types) IsValidForCounter() bool { 392 for _, aggType := range aggTypes { 393 if !aggType.IsValidForCounter() { 394 return false 395 } 396 } 397 return true 398 } 399 400 // IsValidForTimer checks if the list of aggregation types is valid for Timer. 401 func (aggTypes Types) IsValidForTimer() bool { 402 for _, aggType := range aggTypes { 403 if !aggType.IsValidForTimer() { 404 return false 405 } 406 } 407 return true 408 } 409 410 // PooledQuantiles returns all the quantiles found in the list 411 // of aggregation types. Using a floats pool if available. 412 // 413 // A boolean will also be returned to indicate whether the 414 // returned float slice is from the pool. 415 func (aggTypes Types) PooledQuantiles(p pool.FloatsPool) ([]float64, bool) { 416 var ( 417 res []float64 418 initialized bool 419 medianAdded bool 420 pooled bool 421 ) 422 for _, aggType := range aggTypes { 423 q, ok := aggType.Quantile() 424 if !ok { 425 continue 426 } 427 // Dedup P50 and Median. 428 if aggType == P50 || aggType == Median { 429 if medianAdded { 430 continue 431 } 432 medianAdded = true 433 } 434 if !initialized { 435 if p == nil { 436 res = make([]float64, 0, len(aggTypes)) 437 } else { 438 res = p.Get(len(aggTypes)) 439 pooled = true 440 } 441 initialized = true 442 } 443 res = append(res, q) 444 } 445 return res, pooled 446 } 447 448 // Proto returns the proto of the aggregation types. 449 func (aggTypes Types) Proto() ([]aggregationpb.AggregationType, error) { 450 // This is the same as returning an empty slice from the functionality perspective. 451 // It makes creating testing fixtures much simpler. 452 if aggTypes == nil { 453 return nil, nil 454 } 455 456 res := make([]aggregationpb.AggregationType, len(aggTypes)) 457 for i, aggType := range aggTypes { 458 s, err := aggType.Proto() 459 if err != nil { 460 return nil, err 461 } 462 res[i] = s 463 } 464 465 return res, nil 466 } 467 468 // ParseTypes parses a list of aggregation types in the form of type1,type2,type3. 469 func ParseTypes(str string) (Types, error) { 470 parts := strings.Split(str, typesSeparator) 471 res := make(Types, len(parts)) 472 for i := range parts { 473 aggType, err := ParseType(parts[i]) 474 if err != nil { 475 return nil, err 476 } 477 res[i] = aggType 478 } 479 return res, nil 480 } 481 482 func init() { 483 typeStringMap = make(map[string]Type, maxTypeID) 484 for aggType := range ValidTypes { 485 typeStringMap[aggType.String()] = aggType 486 } 487 }