github.com/go-graphite/carbonapi@v0.17.0/expr/functions/summarize/function.go (about)

     1  package summarize
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"math"
     7  
     8  	"github.com/go-graphite/carbonapi/expr/consolidations"
     9  	"github.com/go-graphite/carbonapi/expr/helper"
    10  	"github.com/go-graphite/carbonapi/expr/interfaces"
    11  	"github.com/go-graphite/carbonapi/expr/types"
    12  	"github.com/go-graphite/carbonapi/pkg/parser"
    13  	pb "github.com/go-graphite/protocol/carbonapi_v3_pb"
    14  )
    15  
    16  type summarize struct{}
    17  
    18  func GetOrder() interfaces.Order {
    19  	return interfaces.Any
    20  }
    21  
    22  func New(configFile string) []interfaces.FunctionMetadata {
    23  	res := make([]interfaces.FunctionMetadata, 0)
    24  	f := &summarize{}
    25  	functions := []string{"summarize"}
    26  	for _, n := range functions {
    27  		res = append(res, interfaces.FunctionMetadata{Name: n, F: f})
    28  	}
    29  	return res
    30  }
    31  
    32  // summarize(seriesList, intervalString, func='sum', alignToFrom=False)
    33  func (f *summarize) Do(ctx context.Context, eval interfaces.Evaluator, e parser.Expr, from, until int64, values map[parser.MetricRequest][]*types.MetricData) ([]*types.MetricData, error) {
    34  	// TODO(dgryski): make sure the arrays are all the same 'size'
    35  	args, err := helper.GetSeriesArg(ctx, eval, e.Arg(0), from, until, values)
    36  	if err != nil {
    37  		return nil, err
    38  	}
    39  	if len(args) == 0 {
    40  		return nil, nil
    41  	}
    42  
    43  	bucketSizeInt32, err := e.GetIntervalArg(1, 1)
    44  	if err != nil {
    45  		return nil, err
    46  	}
    47  	bucketSize := int64(bucketSizeInt32)
    48  
    49  	summarizeFunction, err := e.GetStringNamedOrPosArgDefault("func", 2, "sum")
    50  	if err != nil {
    51  		return nil, err
    52  	}
    53  	_, funcOk := e.NamedArg("func")
    54  	if !funcOk {
    55  		funcOk = e.ArgsLen() > 2
    56  	}
    57  	if err := consolidations.CheckValidConsolidationFunc(summarizeFunction); err != nil {
    58  		return nil, err
    59  	}
    60  
    61  	alignToFrom, err := e.GetBoolNamedOrPosArgDefault("alignToFrom", 3, false)
    62  	if err != nil {
    63  		return nil, err
    64  	}
    65  	_, alignOk := e.NamedArgs()["alignToFrom"]
    66  	if !alignOk {
    67  		alignOk = e.ArgsLen() > 3
    68  	}
    69  
    70  	newStart := args[0].StartTime
    71  	newStop := args[0].StopTime
    72  	if !alignToFrom {
    73  		newStart, newStop = helper.AlignToBucketSize(newStart, newStop, bucketSize)
    74  		newStop += bucketSize
    75  	}
    76  
    77  	results := make([]*types.MetricData, len(args))
    78  	for n, arg := range args {
    79  
    80  		name := fmt.Sprintf("summarize(%s,'%s'", arg.Name, e.Arg(1).StringValue())
    81  		if funcOk || alignOk {
    82  			// we include the "func" argument in the presence of
    83  			// "alignToFrom", even if the former was omitted
    84  			// this is so that a call like "summarize(foo, '5min', alignToFrom=true)"
    85  			// doesn't produce a metric name that has a boolean value
    86  			// where a function name should be
    87  			// so we show "summarize(foo,'5min','sum',true)" instead of "summarize(foo,'5min',true)"
    88  			//
    89  			// this does not match graphite's behaviour but seems more correct
    90  			name += fmt.Sprintf(",'%s'", summarizeFunction)
    91  		}
    92  		if alignOk {
    93  			name += fmt.Sprintf(",%v", alignToFrom)
    94  		}
    95  		name += ")"
    96  
    97  		r := types.MetricData{
    98  			FetchResponse: pb.FetchResponse{
    99  				Name:              name,
   100  				StepTime:          bucketSize,
   101  				StartTime:         newStart,
   102  				StopTime:          newStop,
   103  				XFilesFactor:      arg.XFilesFactor,
   104  				PathExpression:    name,
   105  				ConsolidationFunc: arg.ConsolidationFunc,
   106  			},
   107  			Tags: helper.CopyTags(arg),
   108  		}
   109  		r.Tags["summarize"] = e.Arg(1).StringValue()
   110  		r.Tags["summarizeFunction"] = summarizeFunction
   111  
   112  		ts := newStart
   113  		var bucketStart int64 = 0
   114  		for ts < newStop {
   115  			bucketUpperBound := ts + bucketSize
   116  
   117  			// If alignToFrom is not set, and the start time is adjusted to a value that is earlier than the serie's StartTime,
   118  			// then the bucketStart ends up being set to a negative number. Therefore, we check here to make sure that the ts is
   119  			// equal to or after the data's start time to avoid a panic.
   120  			if ts >= arg.StartTime {
   121  				bucketStart = (ts - arg.StartTime + arg.StepTime - 1) / arg.StepTime // equivalent to ceil((ts-arg.StartTime) / arg.StepTime)
   122  
   123  				if bucketStart > int64(len(arg.Values)) {
   124  					// It is possible for the stop time to not be reached, but all of the values in the series have already been assigned
   125  					// to buckets and aggregated. In this case, the final bucket will have a value of NaN.
   126  					ts = bucketUpperBound
   127  					r.Values = append(r.Values, math.NaN())
   128  					break
   129  				}
   130  			}
   131  			bucketEnd := (bucketUpperBound - arg.StartTime + arg.StepTime - 1) / arg.StepTime // equivalent to ceil((until-arg.StartTime) / arg.StepTime)
   132  			if bucketEnd > int64(len(arg.Values)) {
   133  				bucketEnd = int64(len(arg.Values))
   134  			}
   135  
   136  			rv := consolidations.SummarizeValues(summarizeFunction, arg.Values[bucketStart:bucketEnd], arg.XFilesFactor)
   137  			r.Values = append(r.Values, rv)
   138  			ts = bucketUpperBound
   139  		}
   140  		r.StopTime = ts
   141  		results[n] = &r
   142  	}
   143  
   144  	return results, nil
   145  }
   146  
   147  // Description is auto-generated description, based on output of https://github.com/graphite-project/graphite-web
   148  func (f *summarize) Description() map[string]types.FunctionDescription {
   149  	return map[string]types.FunctionDescription{
   150  		"summarize": {
   151  			Description: "Summarize the data into interval buckets of a certain size.\n\nBy default, the contents of each interval bucket are summed together. This is\nuseful for counters where each increment represents a discrete event and\nretrieving a \"per X\" value requires summing all the events in that interval.\n\nSpecifying 'average' instead will return the mean for each bucket, which can be more\nuseful when the value is a gauge that represents a certain value in time.\n\nThis function can be used with aggregation functions ``average``, ``median``, ``sum``, ``min``,\n``max``, ``diff``, ``stddev``, ``count``, ``range``, ``multiply`` & ``last``.\n\nBy default, buckets are calculated by rounding to the nearest interval. This\nworks well for intervals smaller than a day. For example, 22:32 will end up\nin the bucket 22:00-23:00 when the interval=1hour.\n\nPassing alignToFrom=true will instead create buckets starting at the from\ntime. In this case, the bucket for 22:32 depends on the from time. If\nfrom=6:30 then the 1hour bucket for 22:32 is 22:30-23:30.\n\nExample:\n\n.. code-block:: none\n\n  &target=summarize(counter.errors, \"1hour\") # total errors per hour\n  &target=summarize(nonNegativeDerivative(gauge.num_users), \"1week\") # new users per week\n  &target=summarize(queue.size, \"1hour\", \"avg\") # average queue size per hour\n  &target=summarize(queue.size, \"1hour\", \"max\") # maximum queue size during each hour\n  &target=summarize(metric, \"13week\", \"avg\", true)&from=midnight+20100101 # 2010 Q1-4",
   152  			Function:    "summarize(seriesList, intervalString, func='sum', alignToFrom=False)",
   153  			Group:       "Transform",
   154  			Module:      "graphite.render.functions",
   155  			Name:        "summarize",
   156  			Params: []types.FunctionParam{
   157  				{
   158  					Name:     "seriesList",
   159  					Required: true,
   160  					Type:     types.SeriesList,
   161  				},
   162  				{
   163  					Name:     "intervalString",
   164  					Required: true,
   165  					Suggestions: types.NewSuggestions(
   166  						"10min",
   167  						"1h",
   168  						"1d",
   169  					),
   170  					Type: types.Interval,
   171  				},
   172  				{
   173  					Default: types.NewSuggestion("sum"),
   174  					Name:    "func",
   175  					Options: types.StringsToSuggestionList(consolidations.AvailableSummarizers),
   176  					Type:    types.AggFunc,
   177  				},
   178  				{
   179  					Default: types.NewSuggestion(false),
   180  					Name:    "alignToFrom",
   181  					Type:    types.Boolean,
   182  				},
   183  			},
   184  		},
   185  	}
   186  }