// FunctionAnomalyMaker makes anomaly-measurement functions that return simple p-values for deviations from the predicted model. // In order to make this procedure mostly automatic, it performs a join on the original tagsets to match them up with their predictions. func FunctionPeriodicAnomalyMaker(name string, model function.MetricFunction) function.MetricFunction { if model.MinArguments < 2 { panic("FunctionAnomalyMaker requires that the model argument take at least two parameters; series and period.") } return function.MetricFunction{ Name: name, MinArguments: model.MinArguments, MaxArguments: model.MaxArguments, Compute: func(context *function.EvaluationContext, arguments []function.Expression, groups function.Groups) (function.Value, error) { original, err := function.EvaluateToSeriesList(arguments[0], context) if err != nil { return nil, err } predictionValue, err := model.Compute(context, arguments, groups) if err != nil { return nil, err // TODO: add decoration to describe it's coming from the anomaly function } prediction, err := predictionValue.ToSeriesList(context.Timerange) if err != nil { return nil, err } period, err := function.EvaluateToDuration(arguments[1], context) if err != nil { return nil, err } periodSlots := int(period / context.Timerange.Resolution()) // Now we need to match up 'original' and 'prediction' // We'll use a hashmap for now. // TODO: clean this up to hog less memory lookup := map[string][]float64{} for _, series := range original.Series { lookup[series.TagSet.Serialize()] = series.Values } result := make([]api.Timeseries, len(prediction.Series)) for i, series := range prediction.Series { result[i] = series result[i].Values, err = pValueFromNormalDifferenceSlices(lookup[series.TagSet.Serialize()], series.Values, periodSlots) if err != nil { return nil, err } } prediction.Series = result return prediction, nil }, } }
"math" "github.com/square/metrics/api" "github.com/square/metrics/function" ) var FunctionDrop = function.MetricFunction{ Name: "forecast.drop", MinArguments: 2, MaxArguments: 2, Compute: func(context *function.EvaluationContext, arguments []function.Expression, groups function.Groups) (function.Value, error) { original, err := function.EvaluateToSeriesList(arguments[0], context) if err != nil { return nil, err } dropTime, err := function.EvaluateToDuration(arguments[1], context) if err != nil { return nil, err } lastValue := float64(context.Timerange.Slots()) - dropTime.Seconds()/context.Timerange.Resolution().Seconds() result := make([]api.Timeseries, len(original.Series)) for i, series := range original.Series { values := make([]float64, len(series.Values)) result[i] = series for j := range values { if float64(j) < lastValue { values[j] = series.Values[j] } else { values[j] = math.NaN() } }
"github.com/square/metrics/api" "github.com/square/metrics/function" ) // FunctionRollingMultiplicativeHoltWinters computes a rolling multiplicative Holt-Winters model for the data. // It takes in several learning rates, as well as the period that describes the periodicity of the seasonal term. // The learning rates are interpreted as being "per period." For example, a value of 0.5 means that values in // this period are effectively weighted twice as much as those in the previous. A value of 0.9 means that values in // this period are weighted 1.0/(1.0 - 0.9) = 10 times as much as the previous. var FunctionRollingMultiplicativeHoltWinters = function.MetricFunction{ Name: "forecast.rolling_multiplicative_holt_winters", MinArguments: 5, // Series, period, level learning rate, trend learning rate, seasonal learning rate MaxArguments: 6, // Series, period, level learning rate, trend learning rate, seasonal learning rate, extra training time Compute: func(context *function.EvaluationContext, arguments []function.Expression, groups function.Groups) (function.Value, error) { period, err := function.EvaluateToDuration(arguments[1], context) if err != nil { return nil, err } levelLearningRate, err := function.EvaluateToScalar(arguments[2], context) if err != nil { return nil, err } trendLearningRate, err := function.EvaluateToScalar(arguments[3], context) if err != nil { return nil, err } seasonalLearningRate, err := function.EvaluateToScalar(arguments[4], context) if err != nil { return nil, err }