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

     1  package hitcount
     2  
     3  import (
     4  	"context"
     5  	"math"
     6  	"strconv"
     7  	"strings"
     8  
     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 hitcount 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 := &hitcount{}
    25  	functions := []string{"hitcount"}
    26  	for _, n := range functions {
    27  		res = append(res, interfaces.FunctionMetadata{Name: n, F: f})
    28  	}
    29  	return res
    30  }
    31  
    32  // hitcount(seriesList, intervalString, alignToInterval=False)
    33  func (f *hitcount) 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  	if e.ArgsLen() < 2 {
    36  		return nil, parser.ErrMissingArgument
    37  	}
    38  
    39  	bucketSizeInt32, err := e.GetIntervalArg(1, 1)
    40  	if err != nil {
    41  		return nil, err
    42  	}
    43  	interval := int64(bucketSizeInt32)
    44  
    45  	alignToInterval, err := e.GetBoolNamedOrPosArgDefault("alignToInterval", 2, false)
    46  	if err != nil {
    47  		return nil, err
    48  	}
    49  
    50  	if alignToInterval {
    51  		// from needs to be adjusted before grabbing the series arg as it has been adjusted in the metric request
    52  		from = helper.AlignStartToInterval(from, until, interval)
    53  	}
    54  
    55  	args, err := helper.GetSeriesArg(ctx, eval, e.Arg(0), from, until, values)
    56  	if err != nil {
    57  		return nil, err
    58  	}
    59  
    60  	if len(args) == 0 {
    61  		return []*types.MetricData{}, nil
    62  	}
    63  
    64  	start := args[0].StartTime
    65  	stop := args[0].StopTime
    66  
    67  	// Note: the start time for the fetch request is adjusted in expr.Metrics() so that the fetched
    68  	// data is already aligned by interval if this parameter is set to true
    69  	if alignToInterval {
    70  		intervalCount := (stop - start) / interval
    71  		stop = start + (intervalCount * interval) + interval
    72  	}
    73  
    74  	results := make([]*types.MetricData, 0, len(args))
    75  	for _, arg := range args {
    76  		var nameBuf strings.Builder
    77  		bucketSizeStr := e.Arg(1).StringValue()
    78  		nameBuf.Grow(len(arg.Name) + 13 + len(bucketSizeStr))
    79  		nameBuf.WriteString("hitcount(")
    80  		nameBuf.WriteString(arg.Name)
    81  		nameBuf.WriteString(",'")
    82  		nameBuf.WriteString(bucketSizeStr)
    83  		nameBuf.WriteString("'")
    84  		if alignToInterval {
    85  			nameBuf.WriteString(",")
    86  			nameBuf.WriteString(strconv.FormatBool(alignToInterval))
    87  		}
    88  		nameBuf.WriteString(")")
    89  
    90  		bucketCount := helper.GetBuckets(start, stop, interval)
    91  
    92  		r := &types.MetricData{
    93  			FetchResponse: pb.FetchResponse{
    94  				Name:      nameBuf.String(),
    95  				StepTime:  interval,
    96  				StartTime: start,
    97  				StopTime:  stop,
    98  			},
    99  			Tags: helper.CopyTags(arg),
   100  		}
   101  		r.Tags["hitcount"] = strconv.FormatInt(int64(bucketSizeInt32), 10)
   102  
   103  		step := arg.StepTime
   104  		buckets := make([][]float64, bucketCount)
   105  		newStart := stop - bucketCount*interval
   106  		r.StartTime = newStart
   107  
   108  		for i, v := range arg.Values {
   109  			if math.IsNaN(v) {
   110  				continue
   111  			}
   112  
   113  			start_time := arg.StartTime + int64(i)*step
   114  			startBucket, startMod := helper.Divmod(start_time-newStart, interval)
   115  			end_time := start_time + step
   116  			endBucket, endMod := helper.Divmod(end_time-newStart, interval)
   117  
   118  			if endBucket >= bucketCount {
   119  				endBucket = bucketCount - 1
   120  				endMod = interval
   121  			}
   122  
   123  			if startBucket == endBucket {
   124  				// All hits go into a single bucket
   125  				if startBucket >= 0 {
   126  					buckets[startBucket] = append(buckets[startBucket], v*float64(endMod-startMod))
   127  				}
   128  			} else {
   129  				// Spread the hits amongst 2 or more buckets
   130  				if startBucket >= 0 {
   131  					buckets[startBucket] = append(buckets[startBucket], v*float64(interval-startMod))
   132  				}
   133  				hitsPerBucket := v * float64(interval)
   134  				for j := startBucket + 1; j < endBucket; j++ {
   135  					buckets[j] = append(buckets[j], hitsPerBucket)
   136  				}
   137  				if endMod > 0 {
   138  					buckets[endBucket] = append(buckets[endBucket], v*float64(endMod))
   139  				}
   140  			}
   141  		}
   142  		r.Values = make([]float64, len(buckets))
   143  		for i, bucket := range buckets {
   144  			if len(bucket) != 0 {
   145  				var sum float64
   146  				for _, v := range bucket {
   147  					sum += v
   148  				}
   149  				r.Values[i] = sum
   150  			} else {
   151  				r.Values[i] = math.NaN()
   152  			}
   153  		}
   154  
   155  		results = append(results, r)
   156  	}
   157  	return results, nil
   158  }
   159  
   160  // Description is auto-generated description, based on output of https://github.com/graphite-project/graphite-web
   161  func (f *hitcount) Description() map[string]types.FunctionDescription {
   162  	return map[string]types.FunctionDescription{
   163  		"hitcount": {
   164  			Description: "Estimate hit counts from a list of time series.\n\nThis function assumes the values in each time series represent\nhits per second.  It calculates hits per some larger interval\nsuch as per day or per hour.  This function is like summarize(),\nexcept that it compensates automatically for different time scales\n(so that a similar graph results from using either fine-grained\nor coarse-grained records) and handles rarely-occurring events\ngracefully.",
   165  			Function:    "hitcount(seriesList, intervalString, alignToInterval=False)",
   166  			Group:       "Transform",
   167  			Module:      "graphite.render.functions",
   168  			Name:        "hitcount",
   169  			Params: []types.FunctionParam{
   170  				{
   171  					Name:     "seriesList",
   172  					Required: true,
   173  					Type:     types.SeriesList,
   174  				},
   175  				{
   176  					Name:     "intervalString",
   177  					Required: true,
   178  					Suggestions: types.NewSuggestions(
   179  						"10min",
   180  						"1h",
   181  						"1d",
   182  					),
   183  					Type: types.Interval,
   184  				},
   185  				{
   186  					Default: types.NewSuggestion(false),
   187  					Name:    "alignToInterval",
   188  					Type:    types.Boolean,
   189  				},
   190  			},
   191  			NameChange:   true, // name changed
   192  			ValuesChange: true, // values changed
   193  		},
   194  	}
   195  }