github.com/go-graphite/carbonapi@v0.17.0/expr/helper/helper.go (about) 1 package helper 2 3 import ( 4 "context" 5 "fmt" 6 "math" 7 "regexp" 8 "strings" 9 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 ) 14 15 // Backref is a pre-compiled expression for backref 16 var Backref = regexp.MustCompile(`\\(\d+)`) 17 18 // ErrUnknownFunction is an error message about unknown function 19 type ErrUnknownFunction string 20 21 func (e ErrUnknownFunction) Error() string { 22 return fmt.Sprintf("unknown function in evalExpr: %q", string(e)) 23 } 24 25 // GetSeriesArg returns argument from series. 26 func GetSeriesArg(ctx context.Context, eval interfaces.Evaluator, arg parser.Expr, from, until int64, values map[parser.MetricRequest][]*types.MetricData) ([]*types.MetricData, error) { 27 if !arg.IsName() && !arg.IsFunc() { 28 return nil, parser.ErrMissingTimeseries 29 } 30 31 a, err := eval.Eval(ctx, arg, from, until, values) 32 if err != nil { 33 return nil, err 34 } 35 36 return a, nil 37 } 38 39 // RemoveEmptySeriesFromName removes empty series from list of names. 40 func RemoveEmptySeriesFromName(args []*types.MetricData) string { 41 var argNames []string 42 for _, arg := range args { 43 argNames = append(argNames, arg.Name) 44 } 45 46 return strings.Join(argNames, ",") 47 } 48 49 // GetSeriesArgs returns arguments of series 50 func GetSeriesArgs(ctx context.Context, eval interfaces.Evaluator, e []parser.Expr, from, until int64, values map[parser.MetricRequest][]*types.MetricData) ([]*types.MetricData, error) { 51 var args []*types.MetricData 52 53 for _, arg := range e { 54 a, err := GetSeriesArg(ctx, eval, arg, from, until, values) 55 if err != nil { 56 return nil, err 57 } 58 args = append(args, a...) 59 } 60 61 if len(args) == 0 { 62 return nil, nil 63 } 64 65 return args, nil 66 } 67 68 // GetSeriesArgsAndRemoveNonExisting will fetch all required arguments, but will also filter out non existing Series 69 // This is needed to be graphite-web compatible in cases when you pass non-existing Series to, for example, sumSeries 70 func GetSeriesArgsAndRemoveNonExisting(ctx context.Context, eval interfaces.Evaluator, e parser.Expr, from, until int64, values map[parser.MetricRequest][]*types.MetricData) ([]*types.MetricData, error) { 71 args, err := GetSeriesArgs(ctx, eval, e.Args(), from, until, values) 72 if err != nil { 73 return nil, err 74 } 75 76 // We need to rewrite name if there are some missing metrics 77 if len(args) < e.ArgsLen() { 78 e.SetRawArgs(RemoveEmptySeriesFromName(args)) 79 } 80 81 return args, nil 82 } 83 84 // AggKey returns joined by dot nodes of tags names 85 func AggKey(arg *types.MetricData, nodesOrTags []parser.NodeOrTag) string { 86 matched := make([]string, 0, len(nodesOrTags)) 87 metricTags := arg.Tags 88 name := types.ExtractNameTag(arg.Name) 89 nodes := strings.Split(name, ".") 90 for _, nt := range nodesOrTags { 91 if nt.IsTag { 92 tagStr := nt.Value.(string) 93 matched = append(matched, metricTags[tagStr]) 94 } else { 95 f := nt.Value.(int) 96 if f < 0 { 97 f += len(nodes) 98 } 99 if f >= len(nodes) || f < 0 { 100 continue 101 } 102 matched = append(matched, nodes[f]) 103 } 104 } 105 if len(matched) > 0 { 106 return strings.Join(matched, ".") 107 } 108 return "" 109 } 110 111 type seriesFunc1 func(*types.MetricData) *types.MetricData 112 113 // ForEachSeriesDo do action for each serie in list. 114 func ForEachSeriesDo1(ctx context.Context, eval interfaces.Evaluator, e parser.Expr, from, until int64, values map[parser.MetricRequest][]*types.MetricData, function seriesFunc1) ([]*types.MetricData, error) { 115 arg, err := GetSeriesArg(ctx, eval, e.Arg(0), from, until, values) 116 if err != nil { 117 return nil, err 118 } 119 120 var results []*types.MetricData 121 122 for _, a := range arg { 123 results = append(results, function(a)) 124 } 125 return results, nil 126 } 127 128 type seriesFunc func(*types.MetricData, *types.MetricData) *types.MetricData 129 130 // ForEachSeriesDo do action for each serie in list. 131 func ForEachSeriesDo(ctx context.Context, eval interfaces.Evaluator, e parser.Expr, from, until int64, values map[parser.MetricRequest][]*types.MetricData, function seriesFunc) ([]*types.MetricData, error) { 132 arg, err := GetSeriesArg(ctx, eval, e.Arg(0), from, until, values) 133 if err != nil { 134 return nil, err 135 } 136 137 var results []*types.MetricData 138 139 for _, a := range arg { 140 r := a.CopyName(e.Target() + "(" + a.Name + ")") 141 r.Values = make([]float64, len(a.Values)) 142 results = append(results, function(a, r)) 143 } 144 return results, nil 145 } 146 147 // AggregateFunc type that defined aggregate function 148 type AggregateFunc func([]float64) float64 149 150 // AggregateSeries aggregates series 151 func AggregateSeries(e parser.Expr, args []*types.MetricData, function AggregateFunc, xFilesFactor float64, extractTagsFromArgs bool) ([]*types.MetricData, error) { 152 if len(args) == 0 { 153 // GraphiteWeb does this, no matter the function 154 // https://github.com/graphite-project/graphite-web/blob/b52987ac97f49dcfb401a21d4b92860cfcbcf074/webapp/graphite/render/functions.py#L228 155 return args, nil 156 } 157 158 var applyXFilesFactor = xFilesFactor >= 0 159 160 args = ScaleSeries(args) 161 length := len(args[0].Values) 162 r := args[0].CopyNameArg(e.Target()+"("+e.RawArgs()+")", e.Target(), GetCommonTags(args), extractTagsFromArgs) 163 r.Values = make([]float64, length) 164 165 if _, ok := r.Tags["name"]; !ok { 166 r.Tags["name"] = r.Name 167 } 168 169 values := make([]float64, len(args)) 170 for i := range args[0].Values { 171 for n, arg := range args { 172 values[n] = arg.Values[i] 173 } 174 175 r.Values[i] = math.NaN() 176 if len(values) > 0 { 177 if applyXFilesFactor && !XFilesFactorValues(values, xFilesFactor) { 178 // if an xFileFactor is specified and the ratio of NaN values to non-NaN values is not equal to 179 // or greater than the xFilesFactor, the value should be NaN 180 r.Values[i] = math.NaN() 181 } else { 182 r.Values[i] = function(values) 183 } 184 } 185 } 186 187 return []*types.MetricData{r}, nil 188 } 189 190 // Contains check if slice 'a' contains value 'i' 191 func Contains(a []int, i int) bool { 192 for _, aa := range a { 193 if aa == i { 194 return true 195 } 196 } 197 return false 198 } 199 200 // CopyTags makes a deep copy of the tags 201 func CopyTags(series *types.MetricData) map[string]string { 202 out := make(map[string]string, len(series.Tags)) 203 for k, v := range series.Tags { 204 out[k] = v 205 } 206 return out 207 } 208 209 func GetCommonTags(series []*types.MetricData) map[string]string { 210 if len(series) == 0 { 211 return make(map[string]string) 212 } 213 commonTags := CopyTags(series[0]) 214 for _, serie := range series { 215 for k, v := range commonTags { 216 if serie.Tags[k] != v { 217 delete(commonTags, k) 218 } 219 } 220 } 221 222 return commonTags 223 } 224 225 func SafeRound(x float64, precision int) float64 { 226 if math.IsNaN(x) { 227 return x 228 } 229 roundTo := math.Pow10(precision) 230 return math.RoundToEven(x*roundTo) / roundTo 231 } 232 233 func XFilesFactorValues(values []float64, xFilesFactor float64) bool { 234 if math.IsNaN(xFilesFactor) || xFilesFactor == 0 { 235 return true 236 } 237 nonNull := 0 238 for _, val := range values { 239 if !math.IsNaN(val) { 240 nonNull++ 241 } 242 } 243 return XFilesFactor(nonNull, len(values), xFilesFactor) 244 } 245 246 func XFilesFactor(nonNull int, total int, xFilesFactor float64) bool { 247 if nonNull < 0 || total <= 0 { 248 return false 249 } 250 return float64(nonNull)/float64(total) >= xFilesFactor 251 } 252 253 type unitPrefix struct { 254 prefix string 255 size uint64 256 } 257 258 const floatEpsilon = 0.00000000001 259 260 const ( 261 unitSystemBinary = "binary" 262 unitSystemSI = "si" 263 ) 264 265 var UnitSystems = map[string][]unitPrefix{ 266 unitSystemBinary: { 267 {"Pi", 1125899906842624}, // 1024^5 268 {"Ti", 1099511627776}, // 1024^4 269 {"Gi", 1073741824}, // 1024^3 270 {"Mi", 1048576}, // 1024^2 271 {"Ki", 1024}, 272 }, 273 unitSystemSI: { 274 {"P", 1000000000000000}, // 1000^5 275 {"T", 1000000000000}, // 1000^4 276 {"G", 1000000000}, // 1000^3 277 {"M", 1000000}, // 1000^2 278 {"K", 1000}, 279 }, 280 } 281 282 // formatUnits formats the given value according to the given unit prefix system 283 func FormatUnits(v float64, system string) (float64, string) { 284 unitsystem := UnitSystems[system] 285 for _, p := range unitsystem { 286 fsize := float64(p.size) 287 if math.Abs(v) >= fsize { 288 v2 := v / fsize 289 if (v2-math.Floor(v2)) < floatEpsilon && v > 1 { 290 v2 = math.Floor(v2) 291 } 292 return v2, p.prefix 293 } 294 } 295 if (v-math.Floor(v)) < floatEpsilon && v > 1 { 296 v = math.Floor(v) 297 } 298 return v, "" 299 }