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

     1  package linearRegression
     2  
     3  import (
     4  	"context"
     5  	"math"
     6  	"strconv"
     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  	"gonum.org/v1/gonum/mat"
    14  )
    15  
    16  type linearRegression 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 := &linearRegression{}
    25  	functions := []string{"linearRegression"}
    26  	for _, n := range functions {
    27  		res = append(res, interfaces.FunctionMetadata{Name: n, F: f})
    28  	}
    29  	return res
    30  }
    31  
    32  // linearRegression(seriesList, startSourceAt=None, endSourceAt=None)
    33  func (f *linearRegression) Do(ctx context.Context, eval interfaces.Evaluator, e parser.Expr, from, until int64, values map[parser.MetricRequest][]*types.MetricData) ([]*types.MetricData, error) {
    34  	arg, err := helper.GetSeriesArg(ctx, eval, e.Arg(0), from, until, values)
    35  	if err != nil {
    36  		return nil, err
    37  	}
    38  
    39  	degree := 1
    40  
    41  	results := make([]*types.MetricData, 0, len(arg))
    42  
    43  	for _, a := range arg {
    44  		r := a.CopyLink()
    45  		if e.ArgsLen() > 2 {
    46  			r.Name = "linearRegression(" + a.GetName() + ",'" + e.Arg(1).StringValue() + "','" + e.Arg(2).StringValue() + "')"
    47  		} else if e.ArgsLen() > 1 {
    48  			r.Name = "linearRegression(" + a.GetName() + ",'" + e.Arg(1).StringValue() + "')"
    49  		} else {
    50  			r.Name = "linearRegression(" + a.Name + ")"
    51  		}
    52  
    53  		r.Values = make([]float64, len(a.Values))
    54  		r.StopTime = a.GetStopTime()
    55  
    56  		// Removing absent values from original dataset
    57  		nonNulls := make([]float64, 0, len(a.Values))
    58  		for i, v := range a.Values {
    59  			if !math.IsNaN(v) {
    60  				nonNulls = append(nonNulls, a.Values[i])
    61  			}
    62  		}
    63  		if len(nonNulls) < 2 {
    64  			for i := range r.Values {
    65  				r.Values[i] = math.NaN()
    66  			}
    67  			results = append(results, r)
    68  			continue
    69  		}
    70  
    71  		// STEP 1: Creating Vandermonde (X)
    72  		v := consolidations.Vandermonde(a.Values, degree)
    73  		// STEP 2: Creating (X^T * X)**-1
    74  		var t mat.Dense
    75  		t.Mul(v.T(), v)
    76  		var i mat.Dense
    77  		err := i.Inverse(&t)
    78  		if err != nil {
    79  			continue
    80  		}
    81  		// STEP 3: Creating I * X^T * y
    82  		var c mat.Dense
    83  		c.Product(&i, v.T(), mat.NewDense(len(nonNulls), 1, nonNulls))
    84  		// END OF STEPS
    85  
    86  		for i := range r.Values {
    87  			r.Values[i] = consolidations.Poly(float64(i), c.RawMatrix().Data...)
    88  		}
    89  		r.Tags["linearRegressions"] = strconv.FormatInt(a.GetStartTime(), 10) + ", " + strconv.FormatInt(a.GetStopTime(), 10)
    90  
    91  		results = append(results, r)
    92  	}
    93  	return results, nil
    94  }
    95  
    96  // Description is auto-generated description, based on output of https://github.com/graphite-project/graphite-web
    97  func (f *linearRegression) Description() map[string]types.FunctionDescription {
    98  	return map[string]types.FunctionDescription{
    99  		"linearRegression": {
   100  			Description: "Graphs the liner regression function by least squares method.\n\nTakes one metric or a wildcard seriesList, followed by a quoted string with the\ntime to start the line and another quoted string with the time to end the line.\nThe start and end times are inclusive (default range is from to until). See\n``from / until`` in the render\\_api_ for examples of time formats. Datapoints\nin the range is used to regression.\n\nExample:\n\n.. code-block:: none\n\n  &target=linearRegression(Server.instance01.threads.busy, '-1d')\n  &target=linearRegression(Server.instance*.threads.busy, \"00:00 20140101\",\"11:59 20140630\")",
   101  			Function:    "linearRegression(seriesList, startSourceAt=None, endSourceAt=None)",
   102  			Group:       "Calculate",
   103  			Module:      "graphite.render.functions",
   104  			Name:        "linearRegression",
   105  			Params: []types.FunctionParam{
   106  				{
   107  					Name:     "seriesList",
   108  					Required: true,
   109  					Type:     types.SeriesList,
   110  				},
   111  				{
   112  					Name: "startSourceAt",
   113  					Type: types.Date,
   114  				},
   115  				{
   116  					Name: "endSourceAt",
   117  					Type: types.Date,
   118  				},
   119  			},
   120  			SeriesChange: true, // function aggregate metrics or change series items count
   121  			NameChange:   true, // name changed
   122  			ValuesChange: true, // values changed
   123  		},
   124  	}
   125  }