github.com/go-graphite/carbonapi@v0.17.0/expr/functions/polyfit/function.go (about) 1 package polyfit 2 3 import ( 4 "context" 5 "errors" 6 "math" 7 "strconv" 8 9 "github.com/go-graphite/carbonapi/expr/consolidations" 10 "github.com/go-graphite/carbonapi/expr/helper" 11 "github.com/go-graphite/carbonapi/expr/interfaces" 12 "github.com/go-graphite/carbonapi/expr/types" 13 "github.com/go-graphite/carbonapi/pkg/parser" 14 "gonum.org/v1/gonum/mat" 15 ) 16 17 type polyfit struct{} 18 19 func GetOrder() interfaces.Order { 20 return interfaces.Any 21 } 22 23 func New(configFile string) []interfaces.FunctionMetadata { 24 res := make([]interfaces.FunctionMetadata, 0) 25 f := &polyfit{} 26 functions := []string{"polyfit"} 27 for _, n := range functions { 28 res = append(res, interfaces.FunctionMetadata{Name: n, F: f}) 29 } 30 return res 31 } 32 33 // polyfit(seriesList, degree=1, offset="0d") 34 func (f *polyfit) Do(ctx context.Context, eval interfaces.Evaluator, e parser.Expr, from, until int64, values map[parser.MetricRequest][]*types.MetricData) ([]*types.MetricData, error) { 35 // Fitting Nth degree polynom to the dataset 36 // https://en.wikipedia.org/wiki/Polynomial_regression#Matrix_form_and_calculation_of_estimates 37 arg, err := helper.GetSeriesArg(ctx, eval, e.Arg(0), from, until, values) 38 if err != nil { 39 return nil, err 40 } 41 42 degree, err := e.GetIntNamedOrPosArgDefault("degree", 1, 1) 43 if err != nil { 44 return nil, err 45 } else if degree < 1 { 46 return nil, errors.New("degree must be larger or equal to 1") 47 } 48 degreeStr := strconv.Itoa(degree) 49 50 offsStr, err := e.GetStringNamedOrPosArgDefault("offset", 2, "0d") 51 if err != nil { 52 return nil, err 53 } 54 55 offs, err := parser.IntervalString(offsStr, 1) 56 if err != nil { 57 return nil, err 58 } 59 60 results := make([]*types.MetricData, 0, len(arg)) 61 for _, a := range arg { 62 r := a.CopyLinkTags() 63 if e.ArgsLen() > 2 { 64 r.Name = "polyfit(" + a.Name + "," + degreeStr + ",'" + offsStr + "')" 65 } else if e.ArgsLen() > 1 { 66 r.Name = "polyfit(" + a.Name + "," + degreeStr + ")" 67 } else { 68 r.Name = "polyfit(" + a.Name + ")" 69 } 70 // Extending slice by "offset" so our graph slides into future! 71 r.Values = make([]float64, len(a.Values)+int(offs)/int(r.StepTime)) 72 r.StopTime = a.StopTime + int64(offs) 73 74 // Removing absent values from original dataset 75 nonNulls := make([]float64, 0, len(a.Values)) 76 for _, v := range a.Values { 77 if !math.IsNaN(v) { 78 nonNulls = append(nonNulls, v) 79 } 80 } 81 if len(nonNulls) < 2 { 82 for i := range r.Values { 83 r.Values[i] = math.NaN() 84 } 85 results = append(results, r) 86 continue 87 } 88 89 // STEP 1: Creating Vandermonde (X) 90 v := consolidations.Vandermonde(a.Values, degree) 91 // STEP 2: Creating (X^T * X)**-1 92 var t mat.Dense 93 t.Mul(v.T(), v) 94 var i mat.Dense 95 err := i.Inverse(&t) 96 if err != nil { 97 continue 98 } 99 // STEP 3: Creating I * X^T * y 100 var c mat.Dense 101 c.Product(&i, v.T(), mat.NewDense(len(nonNulls), 1, nonNulls)) 102 // END OF STEPS 103 104 for i := range r.Values { 105 r.Values[i] = consolidations.Poly(float64(i), c.RawMatrix().Data...) 106 } 107 results = append(results, r) 108 } 109 return results, nil 110 } 111 112 func (f *polyfit) Description() map[string]types.FunctionDescription { 113 return map[string]types.FunctionDescription{ 114 "polyfit": { 115 Description: "Fitting Nth degree polynom to the dataset. https://en.wikipedia.org/wiki/Polynomial_regression#Matrix_form_and_calculation_of_estimates", 116 Function: "polyfit(seriesList, degree=1, offset=\"0d\")", 117 Group: "Combine", 118 Module: "graphite.render.functions.custom", 119 Name: "polyfit", 120 Params: []types.FunctionParam{ 121 { 122 Name: "seriesList", 123 Required: true, 124 Type: types.SeriesList, 125 }, 126 { 127 Name: "degree", 128 Default: types.NewSuggestion(1), 129 Required: true, 130 Type: types.Integer, 131 }, 132 { 133 Default: types.NewSuggestion("0d"), 134 Name: "offset", 135 Type: types.Interval, 136 }, 137 }, 138 SeriesChange: true, // function aggregate metrics or change series items count 139 NameChange: true, // name changed 140 TagsChange: true, // name tag changed 141 ValuesChange: true, // values changed 142 }, 143 } 144 }