-
Notifications
You must be signed in to change notification settings - Fork 455
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] Implemented movingSum, movingMax, movingMin (graphite functions) #2570
Changes from 14 commits
4e901d7
f42685a
f0df4ce
bef6377
8ce6dc3
30ef946
cf9288f
50b0ae9
1ebe629
ebaafce
2a0643c
53222b9
8c2b6bf
01792e7
27f5d53
b9e6a92
91b2778
c024c85
a2698c2
7c1129d
9c070b8
a0807a4
9877de0
86e9b6a
3dbc68e
2124c31
3a201d6
1847586
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 |
---|---|---|
|
@@ -71,6 +71,16 @@ func SafeSort(input []float64) int { | |
return nans | ||
} | ||
|
||
func SafeSum(input []float64) float64 { | ||
sum := 0.0 | ||
for _, v := range input { | ||
if !math.IsNaN(v) { | ||
sum += v | ||
} | ||
} | ||
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. nit: Go prefers to limit indentation wherever possible (since it's relative to complexity of the statement). Can simplify loop by following's advice of Effective Go "In the Go libraries, you'll find that when an if statement doesn't flow into the next statement—that is, the body ends in break, continue, goto, or return—the unnecessary else is omitted.": e.g. for _, v := range input {
if math.IsNaN(v) {
nans++
continue
}
if v > max {
max = v
}
} 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. done! |
||
return sum | ||
} | ||
|
||
// GetPercentile computes the percentile cut off for an array of floats | ||
func GetPercentile(input []float64, percentile float64, interpolate bool) float64 { | ||
nans := SafeSort(input) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1658,6 +1658,83 @@ func movingMedian(ctx *common.Context, _ singlePathSpec, windowSize string) (*bi | |
}, 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. nit: Remove empty line here, can check err immediately. |
||
// movingSum takes one metric or a wildcard seriesList followed by a a quoted string | ||
// with a length of time like '1hour' or '5min'. Graphs the sum of the preceding | ||
// datapoints for each point on the graph. All previous datapoints are set to None at | ||
// the beginning of the graph. | ||
func movingSum(ctx *common.Context, _ singlePathSpec, windowSize string) (*binaryContextShifter, error) { | ||
interval, err := common.ParseInterval(windowSize) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
if interval <= 0 { | ||
return nil, common.ErrInvalidIntervalFormat | ||
} | ||
|
||
contextShiftingFn := func(c *common.Context) *common.Context { | ||
opts := common.NewChildContextOptions() | ||
opts.AdjustTimeRange(0, 0, interval, 0) | ||
childCtx := c.NewChildContext(opts) | ||
return childCtx | ||
} | ||
|
||
bootstrapStartTime, bootstrapEndTime := ctx.StartTime.Add(-interval), ctx.StartTime | ||
transformerFn := func(bootstrapped, original ts.SeriesList) (ts.SeriesList, error) { | ||
bootstrapList, err := combineBootstrapWithOriginal(ctx, | ||
bootstrapStartTime, bootstrapEndTime, | ||
bootstrapped, singlePathSpec(original)) | ||
if err != nil { | ||
return ts.NewSeriesList(), err | ||
} | ||
|
||
results := make([]*ts.Series, 0, original.Len()) | ||
|
||
for i, bootstrap := range bootstrapList.Values { | ||
series := original.Values[i] | ||
windowPoints := int(interval / (time.Duration(series.MillisPerStep()) * time.Millisecond)) | ||
if windowPoints <= 0 { | ||
err := errors.NewInvalidParamsError(fmt.Errorf( | ||
"non positive window points, windowSize=%s, stepSize=%d", | ||
windowSize, series.MillisPerStep())) | ||
return ts.NewSeriesList(), err | ||
} | ||
window := make([]float64, windowPoints) | ||
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. nit: Would probably loop through all series before this loop and find the "max window points" required by a series, then reuse that in the inner loop so we don't need to allocate this intermediate large slice per series. Perhaps we can update movingAverage and movingMedian to do that too? e.g. results := make([]*ts.Series, 0, original.Len())
maxWindowPoints := 0
for i, bootstrap := range bootstrapList.Values {
series := original.Values[i]
windowPoints := windowPointsLength(series, interval)
if windowPoints > maxWindowPoints {
maxWindowPoints = windowPoints
}
}
windowPoints := make([]float64, maxWindowPoints)
for i, bootstrap := range bootstrapList.Values {
series := original.Values[i]
currWindowPoints := windowPointsLength(series, interval)
window := windowPoints[:currWindowPoints]
util.Memset(window, math.NaN())
// .. existing code
} And a common func now: // ported windowPointsLength to a dedicated function
func windowPointsLength(series *ts.Series, interval time.Duration) int {
return int(interval / (time.Duration(series.MillisPerStep()) * time.Millisecond))
} |
||
util.Memset(window, math.NaN()) | ||
numSteps := series.Len() | ||
offset := bootstrap.Len() - numSteps | ||
vals := ts.NewValues(ctx, series.MillisPerStep(), numSteps) | ||
for i := 0; i < numSteps; i++ { | ||
for j := i + offset - windowPoints; j < i+offset; j++ { | ||
if j < 0 || j >= bootstrap.Len() { | ||
continue | ||
} | ||
|
||
idx := j - i - offset + windowPoints | ||
if idx < 0 || idx > len(window)-1 { | ||
continue | ||
} | ||
|
||
window[idx] = bootstrap.ValueAt(j) | ||
} | ||
vals.SetValueAt(i, common.SafeSum(window)) | ||
} | ||
name := fmt.Sprintf("movingSum(%s,%q)", series.Name(), windowSize) | ||
newSeries := ts.NewSeries(ctx, name, series.StartTime(), vals) | ||
results = append(results, newSeries) | ||
} | ||
|
||
original.Values = results | ||
return original, nil | ||
} | ||
|
||
return &binaryContextShifter{ | ||
ContextShiftFunc: contextShiftingFn, | ||
BinaryTransformer: transformerFn, | ||
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. Now that I've read the common code, I think the only things that differ is stuff like this: // movingMedian
nans := common.SafeSort(window)
if nans < windowPoints {
index := (windowPoints - nans) / 2
median := window[nans+index]
vals.SetValueAt(i, median)
} // movingSum
vals.SetValueAt(i, common.SafeSum(window)) And the naming of the series: // movingMedian
name := fmt.Sprintf("movingMedian(%s,%q)", series.Name(), windowSize)
// movingSum
name := fmt.Sprintf("movingSum(%s,%q)", series.Name(), windowSize) Can we extract all the common into a new method? i.e. type movingImplFn func(window float64, values ts.MutableValues)
func newMovingBinaryTransform(movingFunctionName string, impl movingImplFn) *binaryContextShifter {
interval, err := common.ParseInterval(windowSize)
if err != nil {
return nil, err
}
if interval <= 0 {
return nil, common.ErrInvalidIntervalFormat
}
contextShiftingFn := func(c *common.Context) *common.Context {
opts := common.NewChildContextOptions()
opts.AdjustTimeRange(0, 0, interval, 0)
childCtx := c.NewChildContext(opts)
return childCtx
}
bootstrapStartTime, bootstrapEndTime := ctx.StartTime.Add(-interval), ctx.StartTime
return &binaryContextShifter{
ContextShiftFunc: contextShiftingFn,
BinaryTransformer: func(bootstrapped, original ts.SeriesList) (ts.SeriesList, error) {
bootstrapList, err := combineBootstrapWithOriginal(ctx,
bootstrapStartTime, bootstrapEndTime,
bootstrapped, singlePathSpec(original))
if err != nil {
return ts.NewSeriesList(), err
}
results := make([]*ts.Series, 0, original.Len())
maxWindowPoints := 0
for i, bootstrap := range bootstrapList.Values {
series := original.Values[i]
windowPoints := windowPointsLength(series, interval)
if windowPoints <= 0 {
err := errors.NewInvalidParamsError(fmt.Errorf(
"non positive window points, windowSize=%s, stepSize=%d",
windowSize, series.MillisPerStep()))
return ts.NewSeriesList(), err
}
if windowPoints > maxWindowPoints {
maxWindowPoints = windowPoints
}
}
windowPoints := make([]float64, maxWindowPoints)
for i, bootstrap := range bootstrapList.Values {
series := original.Values[i]
currWindowPoints := windowPointsLength(series, interval)
window := windowPoints[:currWindowPoints]
util.Memset(window, math.NaN())
numSteps := series.Len()
offset := bootstrap.Len() - numSteps
vals := ts.NewValues(ctx, series.MillisPerStep(), numSteps)
for i := 0; i < numSteps; i++ {
for j := i + offset - windowPoints; j < i+offset; j++ {
if j < 0 || j >= bootstrap.Len() {
continue
}
idx := j - i - offset + windowPoints
if idx < 0 || idx > len(window)-1 {
continue
}
window[idx] = bootstrap.ValueAt(j)
}
impl(window, vals)
}
name := fmt.Sprintf("%s(%s,%q)", movingFunctionName, series.Name(), windowSize)
newSeries := ts.NewSeries(ctx, name, series.StartTime(), vals)
results = append(results, newSeries)
}
original.Values = results
return original, 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. I see that movingAverage differs a fair bit, but at least we could reuse between movingMedian and movingSum, etc? 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. done, great idea! |
||
}, nil | ||
} | ||
|
||
|
||
// legendValue takes one metric or a wildcard seriesList and a string in quotes. | ||
// Appends a value to the metric name in the legend. Currently one or several of: | ||
// "last", "avg", "total", "min", "max". | ||
|
@@ -1906,6 +1983,7 @@ func init() { | |
MustRegisterFunction(mostDeviant) | ||
MustRegisterFunction(movingAverage) | ||
MustRegisterFunction(movingMedian) | ||
MustRegisterFunction(movingSum) | ||
MustRegisterFunction(multiplySeries) | ||
MustRegisterFunction(nonNegativeDerivative).WithDefaultParams(map[uint8]interface{}{ | ||
2: math.NaN(), // maxValue | ||
|
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.
nit: Need comments for exported methods.
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.
Added