-
Notifications
You must be signed in to change notification settings - Fork 453
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[query] clean up logic of groupByNodes, implement nodes
param in asPercent
#2816
Changes from 15 commits
6e5bbdc
253ff2a
1eff3a7
447b7b8
c8c7dfc
ca2e4f3
6a94fda
1fe1a35
0335a6f
6f8a890
9ccfd78
b021795
37def27
ad3bcca
b0c0371
28f3334
9391a58
a96a3ff
46125b5
d5cff29
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -561,24 +561,54 @@ func applyByNode(ctx *common.Context, seriesList singlePathSpec, nodeNum int, te | |
// | ||
// sumSeries(foo.by-function.server1.*.cpu.load5),sumSeries(foo.by-function.server2.*.cpu.load5),... | ||
func groupByNode(ctx *common.Context, series singlePathSpec, node int, fname string) (ts.SeriesList, error) { | ||
metaSeries := make(map[string][]*ts.Series) | ||
for _, s := range series.Values { | ||
parts := strings.Split(s.Name(), ".") | ||
return groupByNodes(ctx, series, fname, []int{node}...) | ||
} | ||
|
||
func findFirstMetricExpression(seriesName string) string { | ||
idxOfRightParen := strings.Index(seriesName, ")") | ||
substring := seriesName[:idxOfRightParen] | ||
idxOfLeftParen := strings.LastIndex(substring, "(") | ||
return seriesName[idxOfLeftParen+1:idxOfRightParen] | ||
} | ||
|
||
func getParts(series *ts.Series) []string { | ||
seriesName := series.Name() | ||
if strings.Contains(seriesName, ")") { | ||
seriesName = findFirstMetricExpression(seriesName) | ||
} | ||
|
||
return strings.Split(seriesName, ".") | ||
teddywahle marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
n := node | ||
} | ||
func getAggregationKey(series *ts.Series, nodes []int) string { | ||
parts := getParts(series) | ||
|
||
var keys []string | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you pre-allocate this perhaps? keys := make([]string, 0, len(nodes))
teddywahle marked this conversation as resolved.
Show resolved
Hide resolved
|
||
for _, n := range nodes { | ||
if n < 0 { | ||
n = len(parts) + n | ||
} | ||
|
||
if n >= len(parts) || n < 0 { | ||
return aggregate(ctx, series, fname) | ||
if n < len(parts) { | ||
keys = append(keys, parts[n]) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For graphite-web just an empty string is used if n is not within the range (see IndexError simply will "pass" which then tries to get the value from graphite tags or if not found returns empty string""): To mimic same behavior instead of leaving it from the final key produced, I believe you want an else here: if n < len(parts) {
keys = append(keys, parts[n])
} else {
keys = append(keys, "")
} That way you'll get "foo...bar" for two missing in the middle which better represents the true key since that part was missing and now won't collide with other values missing a value in a different key. |
||
} | ||
} | ||
return strings.Join(keys, ".") | ||
} | ||
|
||
func getMetaSeriesGrouping(seriesList singlePathSpec, nodes []int) map[string][]*ts.Series { | ||
metaSeries := make(map[string][]*ts.Series) | ||
|
||
key := parts[n] | ||
metaSeries[key] = append(metaSeries[key], s) | ||
if len(nodes) > 0 { | ||
for _, s := range seriesList.Values { | ||
key := getAggregationKey(s, nodes) | ||
if key != "" { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
metaSeries[key] = append(metaSeries[key], s) | ||
} | ||
} | ||
} | ||
|
||
return applyFnToMetaSeries(ctx, series, metaSeries, fname) | ||
return metaSeries | ||
} | ||
|
||
// Takes a serieslist and maps a callback to subgroups within as defined by multiple nodes | ||
|
@@ -592,37 +622,16 @@ func groupByNode(ctx *common.Context, series singlePathSpec, node int, fname str | |
// sumSeries(ganglia.server2.*.cpu.load5),sumSeries(ganglia.server2.*.cpu.load10),sumSeries(ganglia.server2.*.cpu.load15),... | ||
// | ||
// NOTE: if len(nodes) = 0, aggregate all series into 1 series. | ||
func groupByNodes(ctx *common.Context, series singlePathSpec, fname string, nodes ...int) (ts.SeriesList, error) { | ||
metaSeries := make(map[string][]*ts.Series) | ||
|
||
nodeLen := len(nodes) | ||
if nodeLen == 0 { | ||
key := "*" // put into single group, not ideal, but more graphite-ish. | ||
for _, s := range series.Values { | ||
metaSeries[key] = append(metaSeries[key], s) | ||
} | ||
} else { | ||
for _, s := range series.Values { | ||
parts := strings.Split(s.Name(), ".") | ||
func groupByNodes(ctx *common.Context, seriesList singlePathSpec, fname string, nodes ...int) (ts.SeriesList, error) { | ||
metaSeries := getMetaSeriesGrouping(seriesList, nodes) | ||
|
||
var keys []string | ||
for _, n := range nodes { | ||
if n < 0 { | ||
n = len(parts) + n | ||
} | ||
|
||
if n >= len(parts) || n < 0 { | ||
return aggregate(ctx, series, fname) | ||
} | ||
|
||
keys = append(keys, parts[n]) | ||
} | ||
key := strings.Join(keys, ".") | ||
metaSeries[key] = append(metaSeries[key], s) | ||
} | ||
if len(metaSeries) == 0 { | ||
// if nodes is an empty slice or every node in nodes exceeds the number | ||
// of parts in each series, just treat it like aggregate | ||
return aggregate(ctx, seriesList, fname) | ||
} | ||
|
||
return applyFnToMetaSeries(ctx, series, metaSeries, fname) | ||
return applyFnToMetaSeries(ctx, seriesList, metaSeries, fname) | ||
} | ||
|
||
func applyFnToMetaSeries(ctx *common.Context, series singlePathSpec, metaSeries map[string][]*ts.Series, fname string) (ts.SeriesList, error) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -990,7 +990,7 @@ func exponentialMovingAverage(ctx *common.Context, input singlePathSpec, windowS | |
} | ||
|
||
// totalFunc takes an index and returns a total value for that index | ||
type totalFunc func(int) float64 | ||
type totalFunc func(int, *ts.Series) float64 | ||
|
||
func totalBySum(seriesList []*ts.Series, index int) float64 { | ||
s, hasValue := 0.0, false | ||
|
@@ -1008,7 +1008,7 @@ func totalBySum(seriesList []*ts.Series, index int) float64 { | |
} | ||
|
||
// asPercent calculates a percentage of the total of a wildcard series. | ||
func asPercent(ctx *common.Context, input singlePathSpec, total genericInterface) (ts.SeriesList, error) { | ||
func asPercent(ctx *common.Context, input singlePathSpec, total genericInterface, nodes ...int) (ts.SeriesList, error) { | ||
if len(input.Values) == 0 { | ||
return ts.SeriesList(input), nil | ||
} | ||
|
@@ -1029,24 +1029,56 @@ func asPercent(ctx *common.Context, input singlePathSpec, total genericInterface | |
if total.Len() == 0 { | ||
// normalize input and sum up input as the total series | ||
toNormalize = input.Values | ||
tf = func(idx int) float64 { return totalBySum(normalized, idx) } | ||
tf = func(idx int, _ *ts.Series) float64 { return totalBySum(normalized, idx) } | ||
} else { | ||
// check total is a single-series list and normalize all of them | ||
if total.Len() != 1 { | ||
err := errors.NewInvalidParamsError(errors.New("total must be a single series")) | ||
return ts.NewSeriesList(), err | ||
} | ||
if len(nodes) > 0 { | ||
// group the series by specified nodes and then sum those groups | ||
groupedTotal, err := groupByNodes(ctx, input, "sum", nodes...) | ||
if err != nil { | ||
return ts.NewSeriesList(), err | ||
} | ||
toNormalize = append(input.Values, groupedTotal.Values[0]) | ||
metaSeriesSumByKey := make(map[string]*ts.Series) | ||
|
||
toNormalize = append(input.Values, total.Values[0]) | ||
tf = func(idx int) float64 { return normalized[len(normalized)-1].ValueAt(idx) } | ||
totalText = total.Values[0].Name() | ||
// map the aggregation key to the aggregated series | ||
for _, series := range groupedTotal.Values { | ||
metaSeriesSumByKey[series.Name()] = series | ||
} | ||
|
||
tf = func(idx int, series *ts.Series) float64 { | ||
// find which aggregation key this series falls under | ||
// and return the sum for that aggregated group | ||
key := getAggregationKey(series, nodes) | ||
return metaSeriesSumByKey[key].ValueAt(idx) | ||
} | ||
totalText = groupedTotal.Values[0].Name() | ||
} else { | ||
toNormalize = append(input.Values, total.Values[0]) | ||
tf = func(idx int, _ *ts.Series) float64 { return normalized[len(normalized)-1].ValueAt(idx) } | ||
totalText = total.Values[0].Name() | ||
} | ||
} | ||
case float64: | ||
toNormalize = input.Values | ||
tf = func(idx int) float64 { return totalArg } | ||
tf = func(idx int, _ *ts.Series) float64 { return totalArg } | ||
totalText = fmt.Sprintf(common.FloatingPointFormat, totalArg) | ||
case nil: | ||
// if total is nil, the total is the sum of all the input series | ||
toNormalize = input.Values | ||
var err error = nil | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't need to initialize to nil, just |
||
summedSeries, err := sumSeries(ctx, multiplePathSpecs(input)) | ||
if err != nil { | ||
return ts.NewSeriesList(), err | ||
} | ||
tf = func(idx int, _ *ts.Series) float64 { return summedSeries.Values[0].ValueAt(idx) } | ||
totalText = summedSeries.Values[0].Name() | ||
default: | ||
err := errors.NewInvalidParamsError(errors.New("total is neither an int nor a series")) | ||
err := errors.NewInvalidParamsError(errors.New("total must be either an int, a series, or nil")) | ||
return ts.NewSeriesList(), err | ||
} | ||
|
||
|
@@ -1066,8 +1098,8 @@ func asPercent(ctx *common.Context, input singlePathSpec, total genericInterface | |
values = append(values, percents) | ||
} | ||
for i := 0; i < normalized[0].Len(); i++ { | ||
t := tf(i) | ||
for j := 0; j < numInputSeries; j++ { | ||
t := tf(i, normalized[j]) | ||
v := normalized[j].ValueAt(i) | ||
if !math.IsNaN(v) && !math.IsNaN(t) && t != 0 { | ||
values[j].SetValueAt(i, 100.0*v/t) | ||
|
@@ -2348,6 +2380,7 @@ func init() { | |
}) | ||
MustRegisterFunction(asPercent).WithDefaultParams(map[uint8]interface{}{ | ||
2: []*ts.Series(nil), // total | ||
3: nil, // nodes | ||
}) | ||
MustRegisterFunction(averageAbove) | ||
MustRegisterFunction(averageSeries) | ||
|
@@ -2378,7 +2411,9 @@ func init() { | |
MustRegisterFunction(groupByNode).WithDefaultParams(map[uint8]interface{}{ | ||
3: "average", // fname | ||
}) | ||
MustRegisterFunction(groupByNodes) | ||
MustRegisterFunction(groupByNodes).WithDefaultParams(map[uint8]interface{}{ | ||
3: nil, // nodes | ||
}) | ||
MustRegisterFunction(highest).WithDefaultParams(map[uint8]interface{}{ | ||
2: 1, // n, | ||
3: "average", // f | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you make
findFirstMetricExpression
potentially return an error if eitherstrings.Index
orstrings.LastIndex
return a-1
?Otherwise it will cause out of bounds panic, better to always optionally return an error rather than panic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe return
(string, bool)
and if either of them return a -1 then the second bool param value returnsfalse
.That way you can do:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done! Great idea.