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 }