github.com/go-graphite/carbonapi@v0.17.0/expr/functions/baselines/function.go (about) 1 package baselines 2 3 import ( 4 "context" 5 "math" 6 7 "github.com/go-graphite/carbonapi/expr/consolidations" 8 "github.com/go-graphite/carbonapi/expr/helper" 9 "github.com/go-graphite/carbonapi/expr/interfaces" 10 "github.com/go-graphite/carbonapi/expr/types" 11 "github.com/go-graphite/carbonapi/pkg/parser" 12 ) 13 14 type baselines struct{} 15 16 func GetOrder() interfaces.Order { 17 return interfaces.Any 18 } 19 20 func New(configFile string) []interfaces.FunctionMetadata { 21 res := make([]interfaces.FunctionMetadata, 0) 22 f := &baselines{} 23 functions := []string{"baseline", "baselineAberration"} 24 for _, n := range functions { 25 res = append(res, interfaces.FunctionMetadata{Name: n, F: f}) 26 } 27 return res 28 } 29 30 func (f *baselines) Do(ctx context.Context, eval interfaces.Evaluator, e parser.Expr, from, until int64, values map[parser.MetricRequest][]*types.MetricData) ([]*types.MetricData, error) { 31 unit, err := e.GetIntervalArg(1, -1) 32 if err != nil { 33 return nil, err 34 } 35 start, err := e.GetIntArg(2) 36 if err != nil { 37 return nil, err 38 } 39 end, err := e.GetIntArg(3) 40 if err != nil { 41 return nil, err 42 } 43 maxAbsentPercent, err := e.GetFloatArgDefault(4, math.NaN()) 44 if err != nil { 45 return nil, err 46 } 47 minAvgLimit, err := e.GetFloatArgDefault(5, math.NaN()) 48 if err != nil { 49 return nil, err 50 } 51 52 isAberration := false 53 if e.Target() == "baselineAberration" { 54 isAberration = true 55 } 56 57 current := make(map[string]*types.MetricData) 58 arg, _ := helper.GetSeriesArg(ctx, eval, e.Arg(0), from, until, values) 59 for _, a := range arg { 60 current[a.Name] = a 61 } 62 63 groups := make(map[string][]*types.MetricData) 64 for i := int32(start); i < int32(end); i++ { 65 if i == 0 { 66 continue 67 } 68 offs := int64(i * unit) 69 arg, _ := helper.GetSeriesArg(ctx, eval, e.Arg(0), from+offs, until+offs, values) 70 for _, a := range arg { 71 r := a.CopyLinkTags() 72 if _, ok := current[r.Name]; ok || !isAberration { 73 r.StartTime = a.StartTime - offs 74 r.StopTime = a.StopTime - offs 75 groups[r.Name] = append(groups[r.Name], r) 76 } 77 } 78 } 79 80 results := make([]*types.MetricData, 0, len(groups)) 81 for name, args := range groups { 82 var newName string 83 if isAberration { 84 newName = "baselineAberration(" + name + ")" 85 } else { 86 newName = "baseline(" + name + ")" 87 } 88 r := args[0].CopyName(newName) 89 r.Values = make([]float64, len(args[0].Values)) 90 91 tmp := make([][]float64, len(args[0].Values)) // number of points 92 lengths := make([]int, len(args[0].Values)) // number of points with data 93 atLeastOne := make([]bool, len(args[0].Values)) 94 for _, arg := range args { 95 for i, v := range arg.Values { 96 if math.IsNaN(arg.Values[i]) { 97 continue 98 } 99 atLeastOne[i] = true 100 tmp[i] = append(tmp[i], v) 101 lengths[i]++ 102 } 103 } 104 105 totalSum := 0.0 106 totalNotAbsent := 0 107 totalCnt := len(r.Values) 108 109 for i, v := range atLeastOne { 110 if v { 111 r.Values[i] = consolidations.Percentile(tmp[i][0:lengths[i]], 50, true) 112 totalSum += r.Values[i] 113 totalNotAbsent++ 114 if isAberration { 115 if math.IsNaN(current[name].Values[i]) { 116 r.Values[i] = math.NaN() 117 } else if r.Values[i] != 0 { 118 r.Values[i] = current[name].Values[i] / r.Values[i] 119 } 120 } 121 } else { 122 r.Values[i] = math.NaN() 123 } 124 } 125 126 if !math.IsNaN(maxAbsentPercent) { 127 absentPercent := float64(100*(totalCnt-totalNotAbsent)) / float64(totalCnt) 128 if absentPercent > maxAbsentPercent { 129 continue 130 } 131 } 132 133 if !math.IsNaN(minAvgLimit) && (totalNotAbsent != 0) { 134 avg := totalSum / float64(totalNotAbsent) 135 if avg < minAvgLimit { 136 continue 137 } 138 } 139 140 results = append(results, r) 141 } 142 143 return results, nil 144 } 145 146 const baselineDescription = `Produce a baseline for the seriesList. Arguments are similar to timestack function. 147 148 For each series takes an array of shifted points and computes a median for that. 149 150 Example: 151 .. code-block:: none 152 153 baseline(metric, "1w", 1, 4) 154 155 This would take 4 points of a metric, with 1-week interval and for each point will compute a median. 156 157 Optional arguments: 158 * maxAbsentPercent - do not compute a baseline is percentage of absent points is higher than this value. 159 * minAvg - do not compute a baseline if average is lower than this value 160 ` 161 162 const baselineAberrationDescription = `Deviation from baseline, in fractions. E.x. if value is over baseline by 10% result will be 1.1` 163 164 func (f *baselines) Description() map[string]types.FunctionDescription { 165 return map[string]types.FunctionDescription{ 166 "baseline": { 167 Description: baselineDescription, 168 Function: "baseline(seriesList, timeShiftUnit, timeShiftStart, timeShiftEnd, [maxAbsentPercent, minAvg])", 169 Group: "Calculate", 170 Module: "graphite.render.functions", 171 Name: "baseline", 172 Params: []types.FunctionParam{ 173 { 174 Name: "seriesList", 175 Required: true, 176 Type: types.SeriesList, 177 }, 178 { 179 Default: types.NewSuggestion("1d"), 180 Name: "timeShiftUnit", 181 Suggestions: types.NewSuggestions( 182 "1h", 183 "6h", 184 "12h", 185 "1d", 186 "2d", 187 "7d", 188 "14d", 189 "30d", 190 ), 191 Type: types.Interval, 192 }, 193 { 194 Default: types.NewSuggestion(0), 195 Name: "timeShiftStart", 196 Type: types.Integer, 197 }, 198 { 199 Default: types.NewSuggestion(7), 200 Name: "timeShiftEnd", 201 Type: types.Integer, 202 }, 203 }, 204 SeriesChange: true, // function aggregate metrics or change series items count 205 NameChange: true, // name changed 206 TagsChange: true, // name tag changed 207 ValuesChange: true, // values changed 208 }, 209 "baselineAberration": { 210 Description: baselineAberrationDescription, 211 Function: "baselineAberration(seriesList, timeShiftUnit, timeShiftStart, timeShiftEnd, [maxAbsentPercent, minAvg])", 212 Group: "Calculate", 213 Module: "graphite.render.functions", 214 Name: "baselineAberration", 215 Params: []types.FunctionParam{ 216 { 217 Default: types.NewSuggestion("1d"), 218 Name: "timeShiftUnit", 219 Suggestions: types.NewSuggestions( 220 "1h", 221 "6h", 222 "12h", 223 "1d", 224 "2d", 225 "7d", 226 "14d", 227 "30d", 228 ), 229 Type: types.Interval, 230 }, 231 { 232 Default: types.NewSuggestion(0), 233 Name: "timeShiftStart", 234 Type: types.Integer, 235 }, 236 { 237 Default: types.NewSuggestion(7), 238 Name: "timeShiftEnd", 239 Type: types.Integer, 240 }, 241 { 242 Default: types.NewSuggestion(0.0), 243 Name: "maxAbsentPercent", 244 Type: types.Float, 245 }, 246 { 247 Default: types.NewSuggestion(0.0), 248 Name: "minAvg", 249 Type: types.Float, 250 }, 251 }, 252 SeriesChange: true, // function aggregate metrics or change series items count 253 NameChange: true, // name changed 254 TagsChange: true, // name tag changed 255 ValuesChange: true, // values changed 256 }, 257 } 258 }