diff --git a/cmd/eval_test.go b/cmd/eval_test.go
index b1e3cace64..495abc5f27 100755
--- a/cmd/eval_test.go
+++ b/cmd/eval_test.go
@@ -131,13 +131,19 @@ func TestEvalWithProfiler(t *testing.T) {
files := map[string]string{
"x.rego": `package x
-p = 1`,
+p {
+ a := 1
+ b := 2
+ c := 3
+ x = a + b * c
+}`,
}
test.WithTempFS(files, func(path string) {
params := newEvalCommandParams()
params.profile = true
+ params.profileCriteria = newrepeatedStringFlag([]string{"line"})
params.dataPaths = newrepeatedStringFlag([]string{path})
var buf bytes.Buffer
@@ -156,6 +162,29 @@ p = 1`,
if len(output.Profile) == 0 {
t.Fatal("Expected profile output to be non-empty")
}
+
+ expectedNumEval := []int{3, 1, 1, 1, 1}
+ expectedNumRedo := []int{3, 1, 1, 1, 1}
+ expectedRow := []int{7, 6, 5, 4, 1}
+ expectedNumGenExpr := []int{3, 1, 1, 1, 1}
+
+ for idx, actualExprStat := range output.Profile {
+ if actualExprStat.NumEval != expectedNumEval[idx] {
+ t.Fatalf("Index %v: Expected number of evals %v but got %v", idx, expectedNumEval[idx], actualExprStat.NumEval)
+ }
+
+ if actualExprStat.NumRedo != expectedNumRedo[idx] {
+ t.Fatalf("Index %v: Expected number of redos %v but got %v", idx, expectedNumRedo[idx], actualExprStat.NumRedo)
+ }
+
+ if actualExprStat.Location.Row != expectedRow[idx] {
+ t.Fatalf("Index %v: Expected row %v but got %v", idx, expectedRow[idx], actualExprStat.Location.Row)
+ }
+
+ if actualExprStat.NumGenExpr != expectedNumGenExpr[idx] {
+ t.Fatalf("Index %v: Expected number of generated expressions %v but got %v", idx, expectedNumGenExpr[idx], actualExprStat.NumGenExpr)
+ }
+ }
})
}
diff --git a/docs/content/policy-performance.md b/docs/content/policy-performance.md
index 025132571a..127895215c 100644
--- a/docs/content/policy-performance.md
+++ b/docs/content/policy-performance.md
@@ -387,24 +387,65 @@ why policy evaluation is slow.
The `opa eval` command provides the following profiler options:
-| Option | Detail | Default |
-| --- | --- | --- |
-| `--profile` | Enables expression profiling and outputs profiler results. | off |
-| `--profile-sort` | Criteria to sort the expression profiling results. This options implies `--profile`. | total_time_ns => num_eval => num_redo => file => line |
-| `--profile-limit` | Desired number of profiling results sorted on the given criteria. This options implies `--profile`. | 10 |
-| `--count` | Desired number of evaluations that profiling metrics are to be captured for. With `--format=pretty`, the output will contain min, max, mean and the 90th and 99th percentile. All collected percentiles can be found in the JSON output. | 1 |
+| Option | Detail | Default |
+| --- | --- |-----------------------------------------------------------------------|
+| `--profile` | Enables expression profiling and outputs profiler results. | off |
+| `--profile-sort` | Criteria to sort the expression profiling results. This options implies `--profile`. | total_time_ns => num_eval => num_redo => num_gen_expr => file => line |
+| `--profile-limit` | Desired number of profiling results sorted on the given criteria. This options implies `--profile`. | 10 |
+| `--count` | Desired number of evaluations that profiling metrics are to be captured for. With `--format=pretty`, the output will contain min, max, mean and the 90th and 99th percentile. All collected percentiles can be found in the JSON output. | 1 |
#### Sort criteria for the profile results
* `total_time_ns` - Results are displayed is decreasing order of *expression evaluation time*
* `num_eval` - Results are displayed is decreasing order of *number of times an expression is evaluated*
* `num_redo` - Results are displayed is decreasing order of *number of times an expression is re-evaluated(redo)*
+* `num_gen_expr` - Results are displayed is decreasing order of *number of generated expressions*
* `file` - Results are sorted in reverse alphabetical order based on the *rego source filename*
* `line` - Results are displayed is decreasing order of *expression line number* in the source file
When the sort criteria is not provided `total_time_ns` has the **highest** priority
while `line` has the **lowest**.
+The `num_gen_expr` represents the number of expressions generated for a given statement on a particular line. For example,
+let's take the following policy:
+
+```rego
+package test
+
+p {
+ a := 1
+ b := 2
+ c := 3
+ x = a + b * c
+}
+```
+
+If we profile the above policy we would get something like the following output:
+
+```ruby
++----------+----------+----------+--------------+-------------+
+| TIME | NUM EVAL | NUM REDO | NUM GEN EXPR | LOCATION |
++----------+----------+----------+--------------+-------------+
+| 20.291µs | 3 | 3 | 3 | test.rego:7 |
+| 1µs | 1 | 1 | 1 | test.rego:6 |
+| 2.333µs | 1 | 1 | 1 | test.rego:5 |
+| 6.333µs | 1 | 1 | 1 | test.rego:4 |
+| 84.75µs | 1 | 1 | 1 | data |
++----------+----------+----------+--------------+-------------+
+```
+
+The first entry indicates that line `test.rego:7` has a `EVAL/REDO` count of `3`. If we look at the expression on line `test.rego:7`
+ie `x = a + b * c` it's not immediately clear why this line has a `EVAL/REDO` count of `3`. But we also notice that there
+are `3` generated expressions (ie. `NUM GEN EXPR`) at line `test.rego:7`. This is because the compiler rewrites the above policy to
+something like below:
+
+`p = true { __local0__ = 1; __local1__ = 2; __local2__ = 3; mul(__local1__, __local2__, __local3__); plus(__local0__, __local3__, __local4__); x = __local4__ }`
+
+And that line `test.rego:7` is rewritten to `mul(__local1__, __local2__, __local3__); plus(__local0__, __local3__, __local4__); x = __local4__` which
+results in a `NUM GEN EXPR` count of `3`. Hence the `NUM GEN EXPR` count can help to better understand the `EVAL/REDO` counts
+for a given expression and also provide more clarity into the profile results and how policy evaluation works.
+
+
#### Example Policy
The different profiling examples shown later on this page use the below
@@ -483,20 +524,20 @@ opa eval --data rbac.rego --profile --format=pretty 'data.rbac.allow'
```ruby
false
-+----------+----------+----------+-----------------+
-| TIME | NUM EVAL | NUM REDO | LOCATION |
-+----------+----------+----------+-----------------+
-| 47.148µs | 1 | 1 | data.rbac.allow |
-| 28.965µs | 1 | 1 | rbac.rego:11 |
-| 24.384µs | 1 | 1 | rbac.rego:41 |
-| 23.064µs | 2 | 1 | rbac.rego:47 |
-| 15.525µs | 1 | 1 | rbac.rego:38 |
-| 14.137µs | 1 | 2 | rbac.rego:46 |
-| 13.927µs | 1 | 0 | rbac.rego:42 |
-| 13.568µs | 1 | 1 | rbac.rego:55 |
-| 12.982µs | 1 | 0 | rbac.rego:56 |
-| 12.763µs | 1 | 2 | rbac.rego:52 |
-+----------+----------+----------+-----------------+
++----------+----------+----------+--------------+-----------------+
+| TIME | NUM EVAL | NUM REDO | NUM GEN EXPR | LOCATION |
++----------+----------+----------+--------------+-----------------+
+| 47.148µs | 1 | 1 | 1 | data.rbac.allow |
+| 28.965µs | 1 | 1 | 1 | rbac.rego:11 |
+| 24.384µs | 1 | 1 | 1 | rbac.rego:41 |
+| 23.064µs | 2 | 1 | 1 | rbac.rego:47 |
+| 15.525µs | 1 | 1 | 1 | rbac.rego:38 |
+| 14.137µs | 1 | 2 | 1 | rbac.rego:46 |
+| 13.927µs | 1 | 0 | 1 | rbac.rego:42 |
+| 13.568µs | 1 | 1 | 1 | rbac.rego:55 |
+| 12.982µs | 1 | 0 | 1 | rbac.rego:56 |
+| 12.763µs | 1 | 2 | 1 | rbac.rego:52 |
++----------+----------+----------+--------------+-----------------+
+------------------------------+----------+
| METRIC | VALUE |
@@ -532,20 +573,20 @@ false
| timer_rego_query_eval_ns | 161812 | 1198092 | 637754 | 1.1846622e+06 | 1.198092e+06 |
| timer_rego_query_parse_ns | 6078 | 6078 | 6078 | 6078 | 6078 |
+------------------------------+---------+----------+---------------+----------------+---------------+
-+----------+-------------+-------------+-------------+-------------+----------+----------+-----------------+
-| MIN | MAX | MEAN | 90% | 99% | NUM EVAL | NUM REDO | LOCATION |
-+----------+-------------+-------------+-------------+-------------+----------+----------+-----------------+
-| 43.875µs | 26.135469ms | 11.494512ms | 25.746215ms | 26.135469ms | 1 | 1 | data.rbac.allow |
-| 21.478µs | 211.461µs | 98.102µs | 205.72µs | 211.461µs | 1 | 1 | rbac.rego:13 |
-| 19.652µs | 123.537µs | 73.161µs | 122.75µs | 123.537µs | 1 | 1 | rbac.rego:40 |
-| 12.303µs | 117.277µs | 61.59µs | 116.733µs | 117.277µs | 2 | 1 | rbac.rego:50 |
-| 12.224µs | 93.214µs | 51.289µs | 92.217µs | 93.214µs | 1 | 1 | rbac.rego:44 |
-| 5.561µs | 84.121µs | 43.002µs | 83.469µs | 84.121µs | 1 | 1 | rbac.rego:51 |
-| 5.56µs | 71.712µs | 36.545µs | 71.158µs | 71.712µs | 1 | 0 | rbac.rego:45 |
-| 4.958µs | 66.04µs | 33.161µs | 65.636µs | 66.04µs | 1 | 2 | rbac.rego:49 |
-| 4.326µs | 65.836µs | 30.461µs | 65.083µs | 65.836µs | 1 | 1 | rbac.rego:6 |
-| 3.948µs | 43.399µs | 24.167µs | 43.055µs | 43.399µs | 1 | 2 | rbac.rego:55 |
-+----------+-------------+-------------+-------------+-------------+----------+----------+-----------------+
++----------+-------------+-------------+-------------+-------------+----------+----------+--------------+------------------+
+| MIN | MAX | MEAN | 90% | 99% | NUM EVAL | NUM REDO | NUM GEN EXPR | LOCATION |
++----------+-------------+-------------+-------------+-------------+----------+----------+--------------+------------------+
+| 43.875µs | 26.135469ms | 11.494512ms | 25.746215ms | 26.135469ms | 1 | 1 | 1 | data.rbac.allow |
+| 21.478µs | 211.461µs | 98.102µs | 205.72µs | 211.461µs | 1 | 1 | 1 | rbac.rego:13 |
+| 19.652µs | 123.537µs | 73.161µs | 122.75µs | 123.537µs | 1 | 1 | 1 | rbac.rego:40 |
+| 12.303µs | 117.277µs | 61.59µs | 116.733µs | 117.277µs | 2 | 1 | 1 | rbac.rego:50 |
+| 12.224µs | 93.214µs | 51.289µs | 92.217µs | 93.214µs | 1 | 1 | 1 | rbac.rego:44 |
+| 5.561µs | 84.121µs | 43.002µs | 83.469µs | 84.121µs | 1 | 1 | 1 | rbac.rego:51 |
+| 5.56µs | 71.712µs | 36.545µs | 71.158µs | 71.712µs | 1 | 0 | 1 | rbac.rego:45 |
+| 4.958µs | 66.04µs | 33.161µs | 65.636µs | 66.04µs | 1 | 2 | 1 | rbac.rego:49 |
+| 4.326µs | 65.836µs | 30.461µs | 65.083µs | 65.836µs | 1 | 1 | 1 | rbac.rego:6 |
+| 3.948µs | 43.399µs | 24.167µs | 43.055µs | 43.399µs | 1 | 2 | 1 | rbac.rego:55 |
++----------+-------------+-------------+-------------+-------------+----------+----------+--------------+------------------+
```
##### Example: Display top `5` profile results
@@ -557,15 +598,15 @@ opa eval --data rbac.rego --profile-limit 5 --format=pretty 'data.rbac.allow'
**Sample Output**
```ruby
-+----------+----------+----------+-----------------+
-| TIME | NUM EVAL | NUM REDO | LOCATION |
-+----------+----------+----------+-----------------+
-| 46.329µs | 1 | 1 | data.rbac.allow |
-| 26.656µs | 1 | 1 | rbac.rego:11 |
-| 24.206µs | 2 | 1 | rbac.rego:47 |
-| 23.235µs | 1 | 1 | rbac.rego:41 |
-| 18.242µs | 1 | 1 | rbac.rego:38 |
-+----------+----------+----------+-----------------+
++----------+----------+----------+--------------+-----------------+
+| TIME | NUM EVAL | NUM REDO | NUM GEN EXPR | LOCATION |
++----------+----------+----------+--------------+-----------------+
+| 46.329µs | 1 | 1 | 1 | data.rbac.allow |
+| 26.656µs | 1 | 1 | 1 | rbac.rego:11 |
+| 24.206µs | 2 | 1 | 1 | rbac.rego:47 |
+| 23.235µs | 1 | 1 | 1 | rbac.rego:41 |
+| 18.242µs | 1 | 1 | 1 | rbac.rego:38 |
++----------+----------+----------+--------------+-----------------+
```
The profile results are sorted on the default sort criteria.
@@ -580,15 +621,15 @@ opa eval --data rbac.rego --profile-limit 5 --profile-sort num_eval --format=pr
**Sample Profile Output**
```ruby
-+----------+----------+----------+-----------------+
-| TIME | NUM EVAL | NUM REDO | LOCATION |
-+----------+----------+----------+-----------------+
-| 26.675µs | 2 | 1 | rbac.rego:47 |
-| 9.274µs | 2 | 1 | rbac.rego:53 |
-| 43.356µs | 1 | 1 | data.rbac.allow |
-| 22.467µs | 1 | 1 | rbac.rego:41 |
-| 22.425µs | 1 | 1 | rbac.rego:11 |
-+----------+----------+----------+-----------------+
++----------+----------+----------+--------------+-----------------+
+| TIME | NUM EVAL | NUM REDO | NUM GEN EXPR | LOCATION |
++----------+----------+----------+--------------+-----------------+
+| 26.675µs | 2 | 1 | 1 | rbac.rego:47 |
+| 9.274µs | 2 | 1 | 1 | rbac.rego:53 |
+| 43.356µs | 1 | 1 | 1 | data.rbac.allow |
+| 22.467µs | 1 | 1 | 1 | rbac.rego:41 |
+| 22.425µs | 1 | 1 | 1 | rbac.rego:11 |
++----------+----------+----------+--------------+-----------------+
```
As seen from the above table, the results are arranged first in decreasing
@@ -606,15 +647,15 @@ opa eval --data rbac.rego --profile-limit 5 --profile-sort num_eval,num_redo --f
**Sample Profile Output**
```ruby
-+----------+----------+----------+-----------------+
-| TIME | NUM EVAL | NUM REDO | LOCATION |
-+----------+----------+----------+-----------------+
-| 22.892µs | 2 | 1 | rbac.rego:47 |
-| 8.831µs | 2 | 1 | rbac.rego:53 |
-| 13.767µs | 1 | 2 | rbac.rego:46 |
-| 10.78µs | 1 | 2 | rbac.rego:52 |
-| 42.338µs | 1 | 1 | data.rbac.allow |
-+----------+----------+----------+-----------------+
++----------+----------+----------+--------------+-----------------+
+| TIME | NUM EVAL | NUM REDO | NUM GEN EXPR | LOCATION |
++----------+----------+----------+--------------+-----------------+
+| 22.892µs | 2 | 1 | 1 | rbac.rego:47 |
+| 8.831µs | 2 | 1 | 1 | rbac.rego:53 |
+| 13.767µs | 1 | 2 | 1 | rbac.rego:46 |
+| 10.78µs | 1 | 2 | 1 | rbac.rego:52 |
+| 42.338µs | 1 | 1 | 1 | data.rbac.allow |
++----------+----------+----------+--------------+-----------------+
```
As seen from the above table, result are first arranged based on *number of evaluations*,
diff --git a/internal/presentation/presentation.go b/internal/presentation/presentation.go
index 18c0e102a5..7a024d87f2 100644
--- a/internal/presentation/presentation.go
+++ b/internal/presentation/presentation.go
@@ -496,14 +496,16 @@ func prettyAggregatedMetrics(w io.Writer, ms map[string]interface{}, limit int)
func prettyProfile(w io.Writer, profile []profiler.ExprStats) error {
tableProfile := generateTableProfile(w)
+
for _, rs := range profile {
line := []string{}
timeNs := time.Duration(rs.ExprTimeNs) * time.Nanosecond
timeNsStr := timeNs.String()
numEval := strconv.FormatInt(int64(rs.NumEval), 10)
numRedo := strconv.FormatInt(int64(rs.NumRedo), 10)
+ numGenExpr := strconv.FormatInt(int64(rs.NumGenExpr), 10)
loc := rs.Location.String()
- line = append(line, timeNsStr, numEval, numRedo, loc)
+ line = append(line, timeNsStr, numEval, numRedo, numGenExpr, loc)
tableProfile.Append(line)
}
if tableProfile.NumLines() > 0 {
@@ -513,7 +515,7 @@ func prettyProfile(w io.Writer, profile []profiler.ExprStats) error {
}
func prettyAggregatedProfile(w io.Writer, profile []profiler.ExprStatsAggregated) error {
- tableProfile := generateTableWithKeys(w, append(statKeys, "num eval", "num redo", "location")...)
+ tableProfile := generateTableWithKeys(w, append(statKeys, "num eval", "num redo", "num gen expr", "location")...)
for _, rs := range profile {
line := []string{}
for _, k := range statKeys {
@@ -526,8 +528,9 @@ func prettyAggregatedProfile(w io.Writer, profile []profiler.ExprStatsAggregated
}
numEval := strconv.FormatInt(int64(rs.NumEval), 10)
numRedo := strconv.FormatInt(int64(rs.NumRedo), 10)
+ numGenExpr := strconv.FormatInt(int64(rs.NumGenExpr), 10)
loc := rs.Location.String()
- line = append(line, numEval, numRedo, loc)
+ line = append(line, numEval, numRedo, numGenExpr, loc)
tableProfile.Append(line)
}
if tableProfile.NumLines() > 0 {
@@ -611,7 +614,7 @@ func generateTableWithKeys(writer io.Writer, keys ...string) *tablewriter.Table
}
func generateTableProfile(writer io.Writer) *tablewriter.Table {
- return generateTableWithKeys(writer, "Time", "Num Eval", "Num Redo", "Location")
+ return generateTableWithKeys(writer, "Time", "Num Eval", "Num Redo", "Num Gen Expr", "Location")
}
func populateTableMetrics(m metrics.Metrics, table *tablewriter.Table, prettyLimit int) {
diff --git a/profiler/profiler.go b/profiler/profiler.go
index fc6bc21b0f..62dfd2d38d 100644
--- a/profiler/profiler.go
+++ b/profiler/profiler.go
@@ -16,13 +16,15 @@ import (
// Profiler computes and reports on the time spent on expressions.
type Profiler struct {
- hits map[string]map[int]ExprStats
- activeTimer time.Time
- prevExpr exprInfo
+ hits map[string]map[int]ExprStats
+ hitsByExprIndex map[string]map[int]map[int]ExprStats
+ activeTimer time.Time
+ prevExpr exprInfo
}
// exprInfo stores information about an expression.
type exprInfo struct {
+ index int
location *ast.Location
op topdown.Op
}
@@ -30,7 +32,8 @@ type exprInfo struct {
// New returns a new Profiler object.
func New() *Profiler {
return &Profiler{
- hits: map[string]map[int]ExprStats{},
+ hits: map[string]map[int]ExprStats{},
+ hitsByExprIndex: map[string]map[int]map[int]ExprStats{},
}
}
@@ -52,9 +55,13 @@ func (p *Profiler) ReportByFile() Report {
p.processLastExpr()
report := Report{Files: map[string]*FileReport{}}
+
for file, hits := range p.hits {
stats := []ExprStats{}
- for _, stat := range hits {
+ for row, stat := range hits {
+ if entry, ok := p.hitsByExprIndex[file][row]; ok {
+ stat.NumGenExpr = len(entry)
+ }
stats = append(stats, stat)
}
@@ -66,6 +73,7 @@ func (p *Profiler) ReportByFile() Report {
}
fr.Result = stats
}
+
return report
}
@@ -73,10 +81,14 @@ func (p *Profiler) ReportByFile() Report {
// criteria. If N <= 0, all the results based on the criteria are returned.
func (p *Profiler) ReportTopNResults(numResults int, criteria []string) []ExprStats {
p.processLastExpr()
+
stats := []ExprStats{}
- for _, hits := range p.hits {
- for _, stat := range hits {
+ for file, hits := range p.hits {
+ for row, stat := range hits {
+ if entry, ok := p.hitsByExprIndex[file][row]; ok {
+ stat.NumGenExpr = len(entry)
+ }
stats = append(stats, stat)
}
}
@@ -92,6 +104,9 @@ func (p *Profiler) ReportTopNResults(numResults int, criteria []string) []ExprSt
allowedCriteria["num_redo"] = func(stat1, stat2 *ExprStats) bool {
return stat1.NumRedo > stat2.NumRedo
}
+ allowedCriteria["num_gen_expr"] = func(stat1, stat2 *ExprStats) bool {
+ return stat1.NumGenExpr > stat2.NumGenExpr
+ }
allowedCriteria["file"] = func(stat1, stat2 *ExprStats) bool {
return stat1.Location.File > stat2.Location.File
}
@@ -156,11 +171,14 @@ func (p *Profiler) processExpr(expr *ast.Expr, eventType topdown.Op) {
p.prevExpr = exprInfo{
op: eventType,
location: expr.Location,
+ index: expr.Index,
}
return
}
// record the profiler results for the previous expression
+ p.calculateHitsByExprIndex()
+
file := p.prevExpr.location.File
hits, ok := p.hits[file]
if !ok {
@@ -190,16 +208,53 @@ func (p *Profiler) processExpr(expr *ast.Expr, eventType topdown.Op) {
p.prevExpr = exprInfo{
op: eventType,
location: expr.Location,
+ index: expr.Index,
}
}
func (p *Profiler) processLastExpr() {
expr := ast.Expr{
Location: p.prevExpr.location,
+ Index: p.prevExpr.index,
}
p.processExpr(&expr, p.prevExpr.op)
}
+func (p *Profiler) calculateHitsByExprIndex() {
+ file := p.prevExpr.location.File
+ hitsUnique, ok := p.hitsByExprIndex[file]
+
+ if !ok {
+ hitsUnique = map[int]map[int]ExprStats{}
+ hitsUnique[p.prevExpr.location.Row] = map[int]ExprStats{p.prevExpr.index: getProfilerStats(p.prevExpr, p.activeTimer)}
+ p.hitsByExprIndex[file] = hitsUnique
+ } else {
+ row := p.prevExpr.location.Row
+ idx := p.prevExpr.index
+
+ pStats, ok := hitsUnique[row]
+ if !ok {
+ hitsUnique[row] = map[int]ExprStats{idx: getProfilerStats(p.prevExpr, p.activeTimer)}
+ } else {
+ pStatsIdx, ok := pStats[idx]
+ if !ok {
+ hitsUnique[row][idx] = getProfilerStats(p.prevExpr, p.activeTimer)
+ } else {
+ pStatsIdx.ExprTimeNs += time.Since(p.activeTimer).Nanoseconds()
+
+ switch p.prevExpr.op {
+ case topdown.EvalOp:
+ pStatsIdx.NumEval++
+ case topdown.RedoOp:
+ pStatsIdx.NumRedo++
+ }
+
+ hitsUnique[row][idx] = pStatsIdx
+ }
+ }
+ }
+}
+
func getProfilerStats(expr exprInfo, timer time.Time) ExprStats {
profilerStats := ExprStats{}
profilerStats.ExprTimeNs = time.Since(timer).Nanoseconds()
@@ -219,6 +274,7 @@ type ExprStats struct {
ExprTimeNs int64 `json:"total_time_ns"`
NumEval int `json:"num_eval"`
NumRedo int `json:"num_redo"`
+ NumGenExpr int `json:"num_gen_expr"`
Location *ast.Location `json:"location"`
}
@@ -228,6 +284,7 @@ type ExprStatsAggregated struct {
ExprTimeNsStats interface{} `json:"total_time_ns_stats"`
NumEval int `json:"num_eval"`
NumRedo int `json:"num_redo"`
+ NumGenExpr int `json:"num_gen_expr"`
Location *ast.Location `json:"location"`
}
@@ -236,9 +293,10 @@ func aggregate(stats ...ExprStats) ExprStatsAggregated {
return ExprStatsAggregated{}
}
res := ExprStatsAggregated{
- NumEval: stats[0].NumEval,
- NumRedo: stats[0].NumRedo,
- Location: stats[0].Location,
+ NumEval: stats[0].NumEval,
+ NumRedo: stats[0].NumRedo,
+ NumGenExpr: stats[0].NumGenExpr,
+ Location: stats[0].Location,
}
timeNs := make([]int64, 0, len(stats))
for _, s := range stats {
diff --git a/profiler/profiler_test.go b/profiler/profiler_test.go
index a7a54e110e..a706415960 100644
--- a/profiler/profiler_test.go
+++ b/profiler/profiler_test.go
@@ -23,6 +23,7 @@ func TestProfilerLargeArray(t *testing.T) {
module := `package test
foo {
+ p
bar
not baz
bee
@@ -44,7 +45,15 @@ baz {
true
false
true
-}`
+}
+
+p {
+ a := 1
+ b := 2
+ c := 3
+ x = a + b * c
+}
+`
_, err := ast.ParseModule("test.rego", module)
if err != nil {
@@ -71,13 +80,14 @@ baz {
t.Fatal("Expected file report for test.rego")
}
- if len(fr.Result) != 11 {
- t.Fatalf("Expected file report length to be 11 instead got %v", len(fr.Result))
+ if len(fr.Result) != 16 {
+ t.Fatalf("Expected file report length to be 16 instead got %v", len(fr.Result))
}
- expectedNumEval := []int{1, 2, 1, 1, 1, 1633, 1, 1, 1, 1, 1}
- expectedNumRedo := []int{1, 0, 0, 1, 1633, 0, 1, 1, 1, 1, 0}
- expectedRow := []int{4, 5, 6, 10, 11, 12, 16, 17, 18, 22, 23}
+ expectedNumEval := []int{1, 1, 2, 1, 1, 1, 1633, 1, 1, 1, 1, 1, 1, 1, 1, 3}
+ expectedNumRedo := []int{1, 1, 0, 0, 1, 1633, 0, 1, 1, 1, 1, 0, 1, 1, 1, 3}
+ expectedRow := []int{4, 5, 6, 7, 11, 12, 13, 17, 18, 19, 23, 24, 29, 30, 31, 32}
+ expectedNumGenExpr := []int{1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3}
for idx, actualExprStat := range fr.Result {
if actualExprStat.NumEval != expectedNumEval[idx] {
@@ -92,6 +102,9 @@ baz {
t.Fatalf("Index %v: Expected row %v but got %v", idx, expectedRow[idx], actualExprStat.Location.Row)
}
+ if actualExprStat.NumGenExpr != expectedNumGenExpr[idx] {
+ t.Fatalf("Index %v: Expected number of generated expressions %v but got %v", idx, expectedNumGenExpr[idx], actualExprStat.NumGenExpr)
+ }
}
}
@@ -464,6 +477,7 @@ allowed_operations = [
expectedNumEval := []int{2, 1}
expectedNumRedo := []int{2, 1}
+ expectedNumGenExpr := []int{1, 1}
expectedLocation := []string{"???", "data.partial.__result__"}
for idx, actualExprStat := range fr.Result {
@@ -475,10 +489,13 @@ allowed_operations = [
t.Fatalf("Index %v: Expected number of redos %v but got %v", idx, expectedNumRedo[idx], actualExprStat.NumRedo)
}
+ if actualExprStat.NumGenExpr != expectedNumGenExpr[idx] {
+ t.Fatalf("Index %v: Expected number of generated expressions %v but got %v", idx, expectedNumGenExpr[idx], actualExprStat.NumGenExpr)
+ }
+
if string(actualExprStat.Location.Text) != expectedLocation[idx] {
t.Fatalf("Index %v: Expected location %v but got %v", idx, expectedLocation[idx], string(actualExprStat.Location.Text))
}
-
}
}