github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/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  // MarshalYAML marshals a Type.
   262  func (a Type) MarshalYAML() (interface{}, error) {
   263  	return a.String(), nil
   264  }
   265  
   266  // UnmarshalYAML unmarshals text-encoded data into an aggregation type.
   267  func (a *Type) UnmarshalYAML(unmarshal func(interface{}) error) error {
   268  	var str string
   269  	if err := unmarshal(&str); err != nil {
   270  		return err
   271  	}
   272  	value, err := ParseType(str)
   273  	if err != nil {
   274  		return err
   275  	}
   276  	*a = value
   277  	return nil
   278  }
   279  
   280  // MarshalText returns the text encoding of an aggregation type.
   281  func (a Type) MarshalText() ([]byte, error) {
   282  	if !a.IsValid() {
   283  		return nil, fmt.Errorf("invalid aggregation type %s", a.String())
   284  	}
   285  	return []byte(a.String()), nil
   286  }
   287  
   288  // UnmarshalText unmarshals text-encoded data into an aggregation type.
   289  func (a *Type) UnmarshalText(data []byte) error {
   290  	str := string(data)
   291  	parsed, err := ParseType(str)
   292  	if err != nil {
   293  		return err
   294  	}
   295  	*a = parsed
   296  	return nil
   297  }
   298  
   299  // Name returns the name of the Type.
   300  func (a Type) Name() []byte {
   301  	name, ok := typeStringNames[a]
   302  	if ok {
   303  		return name
   304  	}
   305  	return a.Bytes()
   306  }
   307  
   308  func validateProtoType(a aggregationpb.AggregationType) error {
   309  	_, ok := aggregationpb.AggregationType_name[int32(a)]
   310  	if !ok {
   311  		return fmt.Errorf("invalid proto aggregation type: %v", a)
   312  	}
   313  	return nil
   314  }
   315  
   316  // ParseType parses an aggregation type.
   317  func ParseType(str string) (Type, error) {
   318  	var (
   319  		aggType    Type
   320  		exactMatch bool
   321  		looseMatch bool
   322  	)
   323  
   324  	aggType, exactMatch = typeStringMap[str]
   325  	if !exactMatch {
   326  		for key, val := range typeStringMap {
   327  			if strings.ToLower(key) == strings.ToLower(str) {
   328  				looseMatch = true
   329  				aggType = val
   330  				break
   331  			}
   332  		}
   333  	}
   334  
   335  	if !exactMatch && !looseMatch {
   336  		return UnknownType, fmt.Errorf("invalid aggregation type: %s", str)
   337  	}
   338  	return aggType, nil
   339  }
   340  
   341  // Types is a list of Types.
   342  type Types []Type
   343  
   344  // NewTypesFromProto creates a list of aggregation types from a proto.
   345  func NewTypesFromProto(input []aggregationpb.AggregationType) (Types, error) {
   346  	res := make([]Type, len(input))
   347  	for i, t := range input {
   348  		aggType, err := NewTypeFromProto(t)
   349  		if err != nil {
   350  			return DefaultTypes, err
   351  		}
   352  		res[i] = aggType
   353  	}
   354  	return res, nil
   355  }
   356  
   357  // Contains checks if the given type is contained in the aggregation types.
   358  func (aggTypes Types) Contains(aggType Type) bool {
   359  	for _, at := range aggTypes {
   360  		if at == aggType {
   361  			return true
   362  		}
   363  	}
   364  	return false
   365  }
   366  
   367  // IsDefault checks if the Types is the default aggregation type.
   368  func (aggTypes Types) IsDefault() bool {
   369  	return len(aggTypes) == 0
   370  }
   371  
   372  // String returns the string representation of the list of aggregation types.
   373  func (aggTypes Types) String() string {
   374  	if len(aggTypes) == 0 {
   375  		return ""
   376  	}
   377  
   378  	parts := make([]string, len(aggTypes))
   379  	for i, aggType := range aggTypes {
   380  		parts[i] = aggType.String()
   381  	}
   382  	return strings.Join(parts, typesSeparator)
   383  }
   384  
   385  // IsValidForGauge checks if the list of aggregation types is valid for Gauge.
   386  func (aggTypes Types) IsValidForGauge() bool {
   387  	for _, aggType := range aggTypes {
   388  		if !aggType.IsValidForGauge() {
   389  			return false
   390  		}
   391  	}
   392  	return true
   393  }
   394  
   395  // IsValidForCounter checks if the list of aggregation types is valid for Counter.
   396  func (aggTypes Types) IsValidForCounter() bool {
   397  	for _, aggType := range aggTypes {
   398  		if !aggType.IsValidForCounter() {
   399  			return false
   400  		}
   401  	}
   402  	return true
   403  }
   404  
   405  // IsValidForTimer checks if the list of aggregation types is valid for Timer.
   406  func (aggTypes Types) IsValidForTimer() bool {
   407  	for _, aggType := range aggTypes {
   408  		if !aggType.IsValidForTimer() {
   409  			return false
   410  		}
   411  	}
   412  	return true
   413  }
   414  
   415  // PooledQuantiles returns all the quantiles found in the list
   416  // of aggregation types. Using a floats pool if available.
   417  //
   418  // A boolean will also be returned to indicate whether the
   419  // returned float slice is from the pool.
   420  func (aggTypes Types) PooledQuantiles(p pool.FloatsPool) ([]float64, bool) {
   421  	var (
   422  		res         []float64
   423  		initialized bool
   424  		medianAdded bool
   425  		pooled      bool
   426  	)
   427  	for _, aggType := range aggTypes {
   428  		q, ok := aggType.Quantile()
   429  		if !ok {
   430  			continue
   431  		}
   432  		// Dedup P50 and Median.
   433  		if aggType == P50 || aggType == Median {
   434  			if medianAdded {
   435  				continue
   436  			}
   437  			medianAdded = true
   438  		}
   439  		if !initialized {
   440  			if p == nil {
   441  				res = make([]float64, 0, len(aggTypes))
   442  			} else {
   443  				res = p.Get(len(aggTypes))
   444  				pooled = true
   445  			}
   446  			initialized = true
   447  		}
   448  		res = append(res, q)
   449  	}
   450  	return res, pooled
   451  }
   452  
   453  // Proto returns the proto of the aggregation types.
   454  func (aggTypes Types) Proto() ([]aggregationpb.AggregationType, error) {
   455  	// This is the same as returning an empty slice from the functionality perspective.
   456  	// It makes creating testing fixtures much simpler.
   457  	if aggTypes == nil {
   458  		return nil, nil
   459  	}
   460  
   461  	res := make([]aggregationpb.AggregationType, len(aggTypes))
   462  	for i, aggType := range aggTypes {
   463  		s, err := aggType.Proto()
   464  		if err != nil {
   465  			return nil, err
   466  		}
   467  		res[i] = s
   468  	}
   469  
   470  	return res, nil
   471  }
   472  
   473  // ParseTypes parses a list of aggregation types in the form of type1,type2,type3.
   474  func ParseTypes(str string) (Types, error) {
   475  	parts := strings.Split(str, typesSeparator)
   476  	res := make(Types, len(parts))
   477  	for i := range parts {
   478  		aggType, err := ParseType(parts[i])
   479  		if err != nil {
   480  			return nil, err
   481  		}
   482  		res[i] = aggType
   483  	}
   484  	return res, nil
   485  }
   486  
   487  func init() {
   488  	typeStringMap = make(map[string]Type, maxTypeID)
   489  	for aggType := range ValidTypes {
   490  		typeStringMap[aggType.String()] = aggType
   491  	}
   492  }