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 }