github.com/go-graphite/carbonapi@v0.17.0/expr/functions/tukey/function.go (about) 1 package tukey 2 3 import ( 4 "container/heap" 5 "context" 6 "errors" 7 "math" 8 "sort" 9 "strings" 10 11 "github.com/go-graphite/carbonapi/expr/helper" 12 "github.com/go-graphite/carbonapi/expr/interfaces" 13 "github.com/go-graphite/carbonapi/expr/types" 14 "github.com/go-graphite/carbonapi/pkg/parser" 15 ) 16 17 type tukey 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 := &tukey{} 26 functions := []string{"tukeyAbove", "tukeyBelow"} 27 for _, n := range functions { 28 res = append(res, interfaces.FunctionMetadata{Name: n, F: f}) 29 } 30 return res 31 } 32 33 // tukeyAbove(seriesList,basis,n,interval=0) , tukeyBelow(seriesList,basis,n,interval=0) 34 func (f *tukey) Do(ctx context.Context, eval interfaces.Evaluator, e parser.Expr, from, until int64, values map[parser.MetricRequest][]*types.MetricData) ([]*types.MetricData, error) { 35 arg, err := helper.GetSeriesArg(ctx, eval, e.Arg(0), from, until, values) 36 if err != nil { 37 return nil, err 38 } 39 40 basis, err := e.GetFloatArg(1) 41 if err != nil || basis <= 0 { 42 return nil, err 43 } 44 45 n, err := e.GetIntArg(2) 46 if err != nil { 47 return nil, err 48 } 49 if n < 1 { 50 return nil, errors.New("n must be larger or equal to 1") 51 } 52 53 var beginInterval int 54 endInterval := len(arg[0].Values) 55 if e.ArgsLen() >= 4 { 56 switch e.Arg(3).Type() { 57 case parser.EtConst: 58 beginInterval, err = e.GetIntArg(3) 59 case parser.EtString: 60 var i32 int32 61 i32, err = e.GetIntervalArg(3, 1) 62 beginInterval = int(i32) 63 beginInterval /= int(arg[0].StepTime) 64 // TODO(nnuss): make sure the arrays are all the same 'size' 65 default: 66 err = parser.ErrBadType 67 } 68 if err != nil { 69 return nil, err 70 } 71 if beginInterval < 0 && (-1*beginInterval) < endInterval { 72 // negative intervals are everything preceding the last 'interval' points 73 endInterval += beginInterval 74 beginInterval = 0 75 } else if beginInterval > 0 && beginInterval < endInterval { 76 // positive intervals are the last 'interval' points 77 beginInterval = endInterval - beginInterval 78 //endInterval = len(arg[0].Values) 79 } else { 80 // zero -or- beyond the len() of the series ; will revert to whole range 81 beginInterval = 0 82 //endInterval = len(arg[0].Values) 83 } 84 } 85 86 // gather all the valid points 87 var points []float64 88 for _, a := range arg { 89 for i, m := range a.Values[beginInterval:endInterval] { 90 if math.IsNaN(a.Values[beginInterval+i]) { 91 continue 92 } 93 points = append(points, m) 94 } 95 } 96 97 sort.Float64s(points) 98 99 first := int(0.25 * float64(len(points))) 100 third := int(0.75 * float64(len(points))) 101 102 iqr := points[third] - points[first] 103 104 max := points[third] + basis*iqr 105 min := points[first] - basis*iqr 106 107 isAbove := strings.HasSuffix(e.Target(), "Above") 108 109 var mh types.MetricHeap 110 111 // count how many points are above the threshold 112 for i, a := range arg { 113 var outlier int 114 for i, m := range a.Values[beginInterval:endInterval] { 115 if math.IsNaN(a.Values[beginInterval+i]) { 116 continue 117 } 118 if isAbove { 119 if m >= max { 120 outlier++ 121 } 122 } else { 123 if m <= min { 124 outlier++ 125 } 126 } 127 } 128 129 // not even a single anomalous point -- ignore this metric 130 if outlier == 0 { 131 continue 132 } 133 134 if len(mh) < n { 135 heap.Push(&mh, types.MetricHeapElement{Idx: i, Val: float64(outlier)}) 136 continue 137 } 138 // current outlier count is is bigger than smallest max found so far 139 foutlier := float64(outlier) 140 if mh[0].Val < foutlier { 141 mh[0].Val = foutlier 142 mh[0].Idx = i 143 heap.Fix(&mh, 0) 144 } 145 } 146 147 if len(mh) < n { 148 n = len(mh) 149 } 150 results := make([]*types.MetricData, n) 151 // results should be ordered ascending 152 for len(mh) > 0 { 153 v := heap.Pop(&mh).(types.MetricHeapElement) 154 results[len(mh)] = arg[v.Idx] 155 } 156 157 return results, nil 158 } 159 160 // Description is auto-generated description, based on output of https://github.com/graphite-project/graphite-web 161 func (f *tukey) Description() map[string]types.FunctionDescription { 162 return map[string]types.FunctionDescription{ 163 "tukeyAbove": { 164 Description: "Tukey's range test, also known as the Tukey's test, Tukey method, Tukey's honest significance test, Tukey's HSD (honest significant difference) test,[1] or the Tukey–Kramer method, is a single-step multiple comparison procedure and statistical test. https://en.wikipedia.org/wiki/Tukey%27s_range_test", 165 Function: "tukeyAbove(seriesList, basis, n, interval=0)", 166 Group: "Transform", 167 Module: "graphite.render.functions.custom", 168 Name: "tukeyAbove", 169 Params: []types.FunctionParam{ 170 { 171 Name: "seriesList", 172 Required: true, 173 Type: types.SeriesList, 174 }, 175 { 176 Required: true, 177 Name: "basis", 178 Type: types.Float, 179 }, 180 { 181 Required: true, 182 Name: "n", 183 Type: types.Integer, 184 }, 185 { 186 Default: types.NewSuggestion(0), 187 Name: "interval", 188 Type: types.IntOrInterval, 189 }, 190 }, 191 }, 192 "tukeyBelow": { 193 Description: "Tukey's range test, also known as the Tukey's test, Tukey method, Tukey's honest significance test, Tukey's HSD (honest significant difference) test,[1] or the Tukey–Kramer method, is a single-step multiple comparison procedure and statistical test. https://en.wikipedia.org/wiki/Tukey%27s_range_test", 194 Function: "tukeyBelow(seriesList, basis, n, interval=0)", 195 Group: "Transform", 196 Module: "graphite.render.functions.custom", 197 Name: "tukeyBelow", 198 Params: []types.FunctionParam{ 199 { 200 Name: "seriesList", 201 Required: true, 202 Type: types.SeriesList, 203 }, 204 { 205 Required: true, 206 Name: "basis", 207 Type: types.Float, 208 }, 209 { 210 Required: true, 211 Name: "n", 212 Type: types.Integer, 213 }, 214 { 215 Default: types.NewSuggestion(0), 216 Name: "interval", 217 Type: types.IntOrInterval, 218 }, 219 }, 220 SeriesChange: true, // function aggregate metrics or change series items count 221 NameChange: true, // name changed 222 TagsChange: true, // name tag changed 223 ValuesChange: true, // values changed 224 }, 225 } 226 }