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

     1  package interpolate
     2  
     3  import (
     4  	"context"
     5  	"math"
     6  
     7  	"github.com/go-graphite/carbonapi/expr/helper"
     8  	"github.com/go-graphite/carbonapi/expr/interfaces"
     9  	"github.com/go-graphite/carbonapi/expr/types"
    10  	"github.com/go-graphite/carbonapi/pkg/parser"
    11  )
    12  
    13  type interpolate struct{}
    14  
    15  func GetOrder() interfaces.Order {
    16  	return interfaces.Any
    17  }
    18  
    19  func New(configFile string) []interfaces.FunctionMetadata {
    20  	return []interfaces.FunctionMetadata{{
    21  		F:    &interpolate{},
    22  		Name: "interpolate",
    23  	}}
    24  }
    25  
    26  func (f *interpolate) Do(ctx context.Context, eval interfaces.Evaluator, e parser.Expr, from, until int64, values map[parser.MetricRequest][]*types.MetricData) (resultData []*types.MetricData, resultError error) {
    27  	seriesList, err := helper.GetSeriesArg(ctx, eval, e.Arg(0), from, until, values)
    28  	if err != nil {
    29  		return nil, err
    30  	}
    31  
    32  	limit, err := e.GetIntOrInfArgDefault(1, parser.IntOrInf{IsInf: true})
    33  	if err != nil {
    34  		return nil, err
    35  	}
    36  
    37  	resultSeriesList := make([]*types.MetricData, 0, len(seriesList))
    38  	for _, series := range seriesList {
    39  		pointsQty := len(series.Values)
    40  		resultSeries := series.CopyLinkTags()
    41  		resultSeries.Name = "interpolate(" + series.Name + ")"
    42  
    43  		resultSeries.Values = make([]float64, pointsQty)
    44  		copy(resultSeries.Values, series.Values)
    45  
    46  		consecutiveNulls := 0
    47  		for i := 0; i < pointsQty; i++ {
    48  			if i == 0 {
    49  				// no "keeping" can be done on the first value
    50  				// because we have no idea what came before it
    51  				continue
    52  			}
    53  
    54  			value := resultSeries.Values[i]
    55  
    56  			if math.IsNaN(value) {
    57  				consecutiveNulls += 1
    58  			} else if consecutiveNulls == 0 {
    59  				// have a value but no need to interpolate
    60  				continue
    61  			} else if math.IsNaN(resultSeries.Values[i-consecutiveNulls-1]) {
    62  				// # have a value but can't interpolate: reset counter
    63  				consecutiveNulls = 0
    64  				continue
    65  			} else {
    66  				// have a value and can interpolate
    67  				// if a non-null value is seen before the limit is hit
    68  				// backfill all the missing datapoints with the last known value
    69  				if consecutiveNulls > 0 && (limit.IsInf || consecutiveNulls <= limit.IntVal) {
    70  					lastNotNullIndex := i - consecutiveNulls - 1
    71  					lastNotNullValue := resultSeries.Values[lastNotNullIndex]
    72  
    73  					for j := 0; j < consecutiveNulls; j++ {
    74  						coefficient := float64(j+1) / float64(consecutiveNulls+1)
    75  						index := i - consecutiveNulls + j
    76  
    77  						resultSeries.Values[index] = lastNotNullValue + coefficient*(value-lastNotNullValue)
    78  					}
    79  				}
    80  
    81  				// reset counter
    82  				consecutiveNulls = 0
    83  			}
    84  		}
    85  
    86  		resultSeriesList = append(resultSeriesList, resultSeries)
    87  	}
    88  
    89  	return resultSeriesList, nil
    90  }
    91  
    92  func (f *interpolate) Description() map[string]types.FunctionDescription {
    93  	return map[string]types.FunctionDescription{
    94  		"interpolate": {
    95  			Description: "Takes one metric or a wildcard seriesList, and optionally a limit to the number of 'None' values to skip over." +
    96  				"\nContinues the line with the last received value when gaps ('None' values) appear in your data, rather than breaking your line." +
    97  				"\n\n.. code-block:: none\n\n  &target=interpolate(Server01.connections.handled)\n  &target=interpolate(Server01.connections.handled, 10)",
    98  			Function: "interpolate(seriesList, limit)",
    99  			Group:    "Transform",
   100  			Module:   "graphite.render.functions",
   101  			Name:     "interpolate",
   102  			Params: []types.FunctionParam{
   103  				{
   104  					Name:     "seriesList",
   105  					Required: true,
   106  					Type:     types.SeriesList,
   107  				},
   108  				{
   109  					Name:     "limit",
   110  					Required: false,
   111  					Type:     types.IntOrInf,
   112  					Default:  types.NewSuggestion(math.Inf(1)),
   113  				},
   114  			},
   115  			NameChange:   true, // name changed
   116  			ValuesChange: true, // values changed
   117  		},
   118  	}
   119  }