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  }