github.com/go-graphite/carbonapi@v0.17.0/expr/functions/seriesList/function.go (about) 1 package seriesList 2 3 import ( 4 "context" 5 "math" 6 "sort" 7 "strconv" 8 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 ) 14 15 type seriesList struct{} 16 17 func GetOrder() interfaces.Order { 18 return interfaces.Any 19 } 20 21 func New(configFile string) []interfaces.FunctionMetadata { 22 res := make([]interfaces.FunctionMetadata, 0) 23 f := &seriesList{} 24 functions := []string{"divideSeriesLists", "diffSeriesLists", "multiplySeriesLists", "powSeriesLists", "sumSeriesLists"} 25 for _, n := range functions { 26 res = append(res, interfaces.FunctionMetadata{Name: n, F: f}) 27 } 28 return res 29 } 30 31 func (f *seriesList) Do(ctx context.Context, eval interfaces.Evaluator, e parser.Expr, from, until int64, values map[parser.MetricRequest][]*types.MetricData) ([]*types.MetricData, error) { 32 if e.ArgsLen() < 2 { 33 return nil, parser.ErrMissingArgument 34 } 35 36 useConstant := false 37 useDenom := false 38 39 defaultValue, err := e.GetFloatNamedOrPosArgDefault("default", 3, math.NaN()) 40 if err != nil { 41 return nil, err 42 } 43 44 numerators, err := helper.GetSeriesArg(ctx, eval, e.Arg(0), from, until, values) 45 if err != nil { 46 return nil, err 47 } 48 if len(numerators) == 0 { 49 if !math.IsNaN(defaultValue) { 50 useConstant = true 51 useDenom = true 52 } else { 53 return nil, nil 54 } 55 } 56 57 denominators, err := helper.GetSeriesArg(ctx, eval, e.Arg(1), from, until, values) 58 if err != nil { 59 return nil, err 60 } 61 if len(denominators) == 0 { 62 if !math.IsNaN(defaultValue) && !useConstant { 63 useConstant = true 64 } else { 65 return nil, nil 66 } 67 } 68 69 sizeMatch := len(denominators) == len(numerators) || len(denominators) == 1 70 useMatching, err := e.GetBoolNamedOrPosArgDefault("matching", 2, !useConstant && !sizeMatch) 71 if err != nil { 72 return nil, err 73 } 74 75 sort.Slice(numerators, func(i, j int) bool { return numerators[i].Name < numerators[j].Name }) 76 sort.Slice(denominators, func(i, j int) bool { return denominators[i].Name < denominators[j].Name }) 77 78 functionName := e.Target()[:len(e.Target())-len("Lists")] 79 80 var compute func(l, r float64) float64 81 82 switch e.Target() { 83 case "divideSeriesLists": 84 compute = func(l, r float64) float64 { return l / r } 85 case "multiplySeriesLists": 86 compute = func(l, r float64) float64 { return l * r } 87 case "diffSeriesLists": 88 compute = func(l, r float64) float64 { return l - r } 89 case "powSeriesLists": 90 compute = math.Pow 91 case "sumSeriesLists": 92 compute = func(l, r float64) float64 { return l + r } 93 } 94 95 if useConstant { 96 var single []*types.MetricData 97 if useDenom { 98 single = denominators 99 } else { 100 single = numerators 101 } 102 results := make([]*types.MetricData, len(single)) 103 for n, s := range single { 104 r := s.CopyLinkTags() 105 r.Name = functionName + "(" + s.Name + "," + s.Name + ")" 106 r.Values = make([]float64, len(s.Values)) 107 for i, v := range s.Values { 108 if math.IsNaN(v) { 109 r.Values[i] = math.NaN() 110 continue 111 } 112 113 if e.Target() == "divideSeriesLists" { 114 if (useDenom && v == 0) || (!useDenom && defaultValue == 0) { 115 r.Values[i] = math.NaN() 116 continue 117 } 118 } 119 if useDenom { 120 r.Values[i] = compute(defaultValue, v) 121 } else { 122 r.Values[i] = compute(v, defaultValue) 123 } 124 125 } 126 results[n] = r 127 } 128 return results, nil 129 } 130 131 var denomMap map[string]*types.MetricData 132 if useMatching { 133 denomMap = make(map[string]*types.MetricData, len(denominators)) 134 for _, s := range denominators { 135 denomMap[s.Name] = s 136 } 137 } 138 139 var denominator *types.MetricData 140 141 results := make([]*types.MetricData, 0, len(numerators)) 142 for n, numerator := range numerators { 143 pairFound := false 144 if useMatching { 145 denominator, pairFound = denomMap[numerator.Name] 146 if !pairFound && math.IsNaN(defaultValue) { 147 continue 148 } 149 } else { 150 pairFound = true 151 if len(denominators) == 1 { 152 denominator = denominators[0] 153 } else { 154 denominator = denominators[n] 155 } 156 } 157 if pairFound { 158 numerator, denominator = helper.ConsolidateSeriesByStep(numerator, denominator) 159 } 160 161 r := numerator.CopyLink() 162 var denomName string 163 if pairFound { 164 denomName = denominator.Name 165 } else { 166 denomName = strconv.FormatFloat(defaultValue, 'f', -1, 64) 167 } 168 r.Name = functionName + "(" + numerator.Name + "," + denomName + ")" 169 r.Values = make([]float64, len(numerator.Values)) 170 171 for i, v := range numerator.Values { 172 denomIsAbsent := pairFound && math.IsNaN(denominator.Values[i]) 173 if math.IsNaN(numerator.Values[i]) || denomIsAbsent { 174 r.Values[i] = math.NaN() 175 continue 176 } 177 178 denomValue := defaultValue 179 if pairFound { 180 denomValue = denominator.Values[i] 181 } 182 183 switch e.Target() { 184 case "divideSeriesLists": 185 if denominator.Values[i] == 0 { 186 r.Values[i] = math.NaN() 187 continue 188 } 189 r.Values[i] = compute(v, denomValue) 190 default: 191 r.Values[i] = compute(v, denomValue) 192 } 193 } 194 results = append(results, r) 195 } 196 return results, nil 197 } 198 199 // Description is auto-generated description, based on output of https://github.com/graphite-project/graphite-web 200 func (f *seriesList) Description() map[string]types.FunctionDescription { 201 return map[string]types.FunctionDescription{ 202 "divideSeriesLists": { 203 Description: "Iterates over a two lists and divides list1[0} by list2[0}, list1[1} by list2[1} and so on.\nThe lists need to be the same length\nCarbonAPI-specific extension allows to specify default value as 3rd optional argument in case series doesn't exist or value is missing", 204 Function: "divideSeriesLists(dividendSeriesList, divisorSeriesList)", 205 Group: "Combine", 206 Module: "graphite.render.functions", 207 Name: "divideSeriesLists", 208 Params: []types.FunctionParam{ 209 { 210 Name: "dividendSeriesList", 211 Required: true, 212 Type: types.SeriesList, 213 }, 214 { 215 Name: "divisorSeriesList", 216 Required: true, 217 Type: types.SeriesList, 218 }, 219 { 220 Name: "default", 221 Required: false, 222 Type: types.Float, 223 }, 224 }, 225 SeriesChange: true, // function aggregate metrics or change series items count 226 NameChange: true, // name changed 227 TagsChange: true, // name tag changed 228 ValuesChange: true, // values changed 229 }, 230 "diffSeriesLists": { 231 Description: "Iterates over a two lists and substracts list1[0} by list2[0}, list1[1} by list2[1} and so on.\nThe lists need to be the same length\nCarbonAPI-specific extension allows to specify default value as 3rd optional argument in case series doesn't exist or value is missing", 232 Function: "diffSeriesLists(firstSeriesList, secondSeriesList)", 233 Group: "Combine", 234 Module: "graphite.render.functions.custom", 235 Name: "diffSeriesLists", 236 Params: []types.FunctionParam{ 237 { 238 Name: "firstSeriesList", 239 Required: true, 240 Type: types.SeriesList, 241 }, 242 { 243 Name: "secondSeriesList", 244 Required: true, 245 Type: types.SeriesList, 246 }, 247 { 248 Name: "default", 249 Required: false, 250 Type: types.Float, 251 }, 252 }, 253 SeriesChange: true, // function aggregate metrics or change series items count 254 NameChange: true, // name changed 255 TagsChange: true, // name tag changed 256 ValuesChange: true, // values changed 257 }, 258 "multiplySeriesLists": { 259 Description: "Iterates over a two lists and multiplies list1[0} by list2[0}, list1[1} by list2[1} and so on.\nThe lists need to be the same length\nCarbonAPI-specific extension allows to specify default value as 3rd optional argument in case series doesn't exist or value is missing", 260 Function: "multiplySeriesLists(sourceSeriesList, factorSeriesList)", 261 Group: "Combine", 262 Module: "graphite.render.functions.custom", 263 Name: "multiplySeriesLists", 264 Params: []types.FunctionParam{ 265 { 266 Name: "sourceSeriesList", 267 Required: true, 268 Type: types.SeriesList, 269 }, 270 { 271 Name: "factorSeriesList", 272 Required: true, 273 Type: types.SeriesList, 274 }, 275 { 276 Name: "default", 277 Required: false, 278 Type: types.Float, 279 }, 280 }, 281 SeriesChange: true, // function aggregate metrics or change series items count 282 NameChange: true, // name changed 283 TagsChange: true, // name tag changed 284 ValuesChange: true, // values changed 285 }, 286 "powSeriesLists": { 287 Description: "Iterates over a two lists and do list1[0} in power of list2[0}, list1[1} in power of list2[1} and so on.\nThe lists need to be the same length\nCarbonAPI-specific extension allows to specify default value as 3rd optional argument in case series doesn't exist or value is missing", 288 Function: "powSeriesLists(sourceSeriesList, factorSeriesList)", 289 Group: "Combine", 290 Module: "graphite.render.functions.custom", 291 Name: "powSeriesLists", 292 Params: []types.FunctionParam{ 293 { 294 Name: "sourceSeriesList", 295 Required: true, 296 Type: types.SeriesList, 297 }, 298 { 299 Name: "factorSeriesList", 300 Required: true, 301 Type: types.SeriesList, 302 }, 303 { 304 Name: "default", 305 Required: false, 306 Type: types.Float, 307 }, 308 }, 309 SeriesChange: true, // function aggregate metrics or change series items count 310 NameChange: true, // name changed 311 TagsChange: true, // name tag changed 312 ValuesChange: true, // values changed 313 }, 314 "sumSeriesLists": { 315 Description: "Iterates over a two lists and subtracts series lists 2 through n from series 1 list1[0] to list2[0], list1[1] to list2[1] and so on. \n The lists will need to be the same length\nCarbonAPI-specific extension allows to specify default value as 3rd optional argument in case series doesn't exist or value is missing Example:\n\n.. code-block:: none\n\n &target=sumSeriesLists(mining.{carbon,graphite,diamond}.extracted,mining.{carbon,graphite,diamond}.shipped)\n\n", 316 Function: "sumSeriesLists(seriesListFirstPos, seriesListSecondPos)", 317 Group: "Combine", 318 Module: "graphite.render.functions.custom", 319 Name: "sumSeriesLists", 320 Params: []types.FunctionParam{ 321 { 322 Name: "seriesListFirstPos", 323 Required: true, 324 Type: types.SeriesList, 325 }, 326 { 327 Name: "seriesListSecondPos", 328 Required: true, 329 Type: types.SeriesList, 330 }, 331 { 332 Name: "default", 333 Required: false, 334 Type: types.Float, 335 }, 336 }, 337 SeriesChange: true, // function aggregate metrics or change series items count 338 NameChange: true, // name changed 339 TagsChange: true, // name tag changed 340 ValuesChange: true, // values changed 341 }, 342 } 343 }