-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add threshold parsing functions to the stats package
In this commit we use the parser combinator library introduced in the previous commit to build a threshold expression parser. We match the existing supported expression format, without any modifications or additions.
- Loading branch information
Showing
2 changed files
with
414 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,230 @@ | ||
/* | ||
* | ||
* k6 - a next-generation load testing tool | ||
* Copyright (C) 2021 Load Impact | ||
* | ||
* This program is free software: you can redistribute it and/or modify | ||
* it under the terms of the GNU Affero General Public License as | ||
* published by the Free Software Foundation, either version 3 of the | ||
* License, or (at your option) any later version. | ||
* | ||
* This program is distributed in the hope that it will be useful, | ||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
* GNU Affero General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU Affero General Public License | ||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
* | ||
*/ | ||
|
||
package stats | ||
|
||
import ( | ||
"fmt" | ||
|
||
c "go.k6.io/k6/pkg/combinators" | ||
) | ||
|
||
// ParseAssertion parses any aggregation method as defined in | ||
// the BNF: `aggregation_method whitespace* operator whitespace* float`. | ||
// The Result's `Payload interface{}` value will hold the | ||
// assertion expression as a `[]interface{}` slice of len 3, its content | ||
// will hold values of type `string` at position 0, `string` at position 1, | ||
// and `float64` at position 2. | ||
func ParseAssertion() c.Parser { | ||
parser := c.Sequence( | ||
ParseAggregationMethod(), | ||
c.DiscardAll(c.Whitespace()), | ||
ParseOperator(), | ||
c.DiscardAll(c.Whitespace()), | ||
ParseValue(), | ||
c.DiscardAll(c.Newline()), | ||
) | ||
|
||
return func(input []rune) c.Result { | ||
res := parser(input) | ||
if res.Err != nil { | ||
return res | ||
} | ||
|
||
return c.Success(res.Payload, res.Remaining) | ||
} | ||
} | ||
|
||
// ParseAggregationMethod parses any aggregation method as defined in | ||
// the BNF: `aggregation_method -> trend | rate | gauge | counter`. | ||
// The Result's `Payload interface{}` value will hold the | ||
// aggregation method name as a string. | ||
func ParseAggregationMethod() c.Parser { | ||
parser := c.Expect(c.Alternative( | ||
ParseCounter(), | ||
ParseGauge(), | ||
ParseRate(), | ||
ParseTrend(), | ||
ParsePercentile(), | ||
), "aggregation method") | ||
|
||
return func(input []rune) c.Result { | ||
res := parser(input) | ||
if res.Err != nil { | ||
return res | ||
} | ||
|
||
return c.Success(res.Payload, res.Remaining) | ||
} | ||
} | ||
|
||
// ParseOperator parses a threshold expression supported operator | ||
// as defined in the BNF: `operator -> ">" | ">=" | "<=" | "<" | "==" | "===" | "!="`. | ||
// The Result's `Payload interface{}` value will hold the | ||
// operator expression as a string. | ||
func ParseOperator() c.Parser { | ||
parser := c.Expect(c.Alternative( | ||
c.Tag(">="), | ||
c.Tag("<="), | ||
c.Tag(">"), | ||
c.Tag("<"), | ||
c.Tag("==="), | ||
c.Tag("=="), | ||
c.Tag("!="), | ||
), "operator") | ||
|
||
return func(input []rune) c.Result { | ||
res := parser(input) | ||
if res.Err != nil { | ||
return res | ||
} | ||
|
||
return c.Success(res.Payload.(string), res.Remaining) | ||
} | ||
} | ||
|
||
// ParseTrend parses a trend aggregation method as defined in | ||
// the BNF: `trend -> "avg" | "min" | "max" | "med" | percentile`. | ||
// The Result's `Payload interface{}` value will hold the | ||
// trend's aggregation method name as a string. | ||
func ParseTrend() c.Parser { | ||
parser := c.Expect(c.Alternative( | ||
c.Tag("mean"), | ||
c.Tag("min"), | ||
c.Tag("max"), | ||
c.Tag("avg"), | ||
c.Tag("med"), | ||
ParsePercentile(), | ||
), "trend aggregation method") | ||
|
||
return func(input []rune) c.Result { | ||
res := parser(input) | ||
if res.Err != nil { | ||
return res | ||
} | ||
|
||
return c.Success(res.Payload, res.Remaining) | ||
} | ||
} | ||
|
||
// ParsePercentile parses a percentile as defined in | ||
// the BNF: `percentile -> "p(" float ")"`. The Result's `Payload interface{}` | ||
// value will hold the percentile expression as a string. | ||
func ParsePercentile() c.Parser { | ||
parser := c.Expect(c.Sequence( | ||
c.Tag("p("), | ||
c.Float(), | ||
c.Char(')'), | ||
), "percentile") | ||
|
||
return func(input []rune) c.Result { | ||
res := parser(input) | ||
if res.Err != nil { | ||
return res | ||
} | ||
|
||
parsed, ok := res.Payload.([]interface{}) | ||
if !ok { | ||
err := fmt.Errorf("failed parsing percentile expression; " + | ||
"reason: converting ParsePercentile() parser result's payload to []interface{} failed", | ||
) | ||
res.Err = c.NewFatalError(input, err, "percentile") | ||
} | ||
|
||
percentile := fmt.Sprintf("%s%g%s", parsed[0].(string), parsed[1].(float64), parsed[2].(string)) | ||
|
||
return c.Success(percentile, res.Remaining) | ||
} | ||
} | ||
|
||
// ParseRate parses a rate aggregation method as defined in | ||
// the BNF: `rate -> "rate"`. The Result's `Payload interface{}` value | ||
// will hold the rate's aggregation method name as a string. | ||
func ParseRate() c.Parser { | ||
parser := c.Expect(c.Tag("rate"), "rate aggregation method") | ||
|
||
return func(input []rune) c.Result { | ||
res := parser(input) | ||
if res.Err != nil { | ||
return res | ||
} | ||
|
||
return c.Success(res.Payload, res.Remaining) | ||
} | ||
} | ||
|
||
// ParseGauge parses a gauge aggregation method as defined in | ||
// the BNF: `gauge -> "value"`. The Result's `Payload interface{}` value | ||
// will hold the gauge's aggregation method name as a string. | ||
func ParseGauge() c.Parser { | ||
parser := c.Expect(c.Alternative( | ||
c.Tag("value"), | ||
), "gauge aggregation method") | ||
|
||
return func(input []rune) c.Result { | ||
res := parser(input) | ||
if res.Err != nil { | ||
return res | ||
} | ||
|
||
return c.Success(res.Payload, res.Remaining) | ||
} | ||
} | ||
|
||
// ParseCounter parses a counter aggregation method as defined in | ||
// the BNF: `counter -> "count" | "rate"`. The Result's `Payload interface{}` value | ||
// will hold the counter's aggregation method name as a string. | ||
func ParseCounter() c.Parser { | ||
parser := c.Expect(c.Alternative( | ||
c.Tag("count"), | ||
c.Tag("rate"), | ||
), "counter aggregation method") | ||
|
||
return func(input []rune) c.Result { | ||
res := parser(input) | ||
if res.Err != nil { | ||
return res | ||
} | ||
|
||
return c.Success(res.Payload, res.Remaining) | ||
} | ||
} | ||
|
||
// ParseValue parses a threshold assertion value as defined in | ||
// the BNF: | ||
// ``` | ||
// float -> digit+ (. digit+)? | ||
// digit -> "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | ||
// ``` | ||
// | ||
// The Result's `Payload interface{}` value will hold the assertion's right | ||
// hand side's as a float64. | ||
func ParseValue() c.Parser { | ||
parser := c.Expect(c.Float(), "numerical value") | ||
|
||
return func(input []rune) c.Result { | ||
res := parser(input) | ||
if res.Err != nil { | ||
return res | ||
} | ||
|
||
return c.Success(res.Payload, res.Remaining) | ||
} | ||
} |
Oops, something went wrong.