mirror of
https://github.com/open-policy-agent/opa.git
synced 2026-08-12 19:32:48 -06:00
wasm/sdk: add benchmarks (#3103)
This is porting some of the existing topdown tests --
but run with the Wasm SDK. I've factored out the data
generation bits into the util package; using it in both places.
I've come to believe that whatever memory usage data we're
collecting there is probably bogus: too many of my runs yield
1597 B/op 41 allocs/op
regardless of the input size. I'd think that the problem lies in
the boundary crossing to cgo-land, but I have yet to find a
source for that.
That aside, disabling the allocation reporting, and gathering
benchmark data for the run times is probably already useful,
so let's go with that.
Signed-off-by: Stephan Renatus <stephan.renatus@gmail.com>
This commit is contained in:
@@ -196,7 +196,6 @@ func (p *Pool) SetPolicyData(policy []byte, data []byte) error {
|
||||
|
||||
if bytes.Equal(policy, currentPolicy) && bytes.Equal(data, currentData) {
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
err := p.setPolicyData(policy, data)
|
||||
|
||||
@@ -4,12 +4,13 @@
|
||||
|
||||
package wasm
|
||||
|
||||
const wasmPageSize = 65535
|
||||
// PageSize represents the WASM page size in bytes.
|
||||
const PageSize = 65535
|
||||
|
||||
// Pages converts a byte size to Pages, rounding up as necessary.
|
||||
func Pages(n uint32) uint32 {
|
||||
pages := n / wasmPageSize
|
||||
if pages*wasmPageSize == n {
|
||||
pages := n / PageSize
|
||||
if pages*PageSize == n {
|
||||
return pages
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
@@ -259,7 +258,7 @@ func (i *VM) Eval(ctx context.Context, entrypoint int32, input *interface{}, met
|
||||
if e := recover(); e != nil {
|
||||
switch e := e.(type) {
|
||||
case abortError:
|
||||
err = errors.New(e.message)
|
||||
err = fmt.Errorf(e.message)
|
||||
case builtinError:
|
||||
err = e.err
|
||||
if _, ok := err.(topdown.Halt); !ok {
|
||||
|
||||
@@ -64,7 +64,7 @@ func (o *OPA) WithDataJSON(data interface{}) *OPA {
|
||||
// WithMemoryLimits configures the memory limits (in bytes) for a single policy
|
||||
// evaluation.
|
||||
func (o *OPA) WithMemoryLimits(min, max uint32) *OPA {
|
||||
if min < 2*65535 {
|
||||
if min < 2*wasm.PageSize {
|
||||
o.configErr = fmt.Errorf("too low minimum memory limit: %w", errors.ErrInvalidConfig)
|
||||
return o
|
||||
}
|
||||
|
||||
@@ -182,7 +182,7 @@ func (o *OPA) Eval(ctx context.Context, opts EvalOpts) (*Result, error) {
|
||||
return nil, fmt.Errorf("%v: %w", err, errors.ErrInternal)
|
||||
}
|
||||
|
||||
return &Result{result}, nil
|
||||
return &Result{Result: result}, nil
|
||||
}
|
||||
|
||||
// Close waits until all the pending evaluations complete and then
|
||||
|
||||
@@ -0,0 +1,194 @@
|
||||
package opa_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/open-policy-agent/opa/internal/wasm/sdk/internal/wasm"
|
||||
"github.com/open-policy-agent/opa/internal/wasm/sdk/opa"
|
||||
"github.com/open-policy-agent/opa/rego"
|
||||
"github.com/open-policy-agent/opa/util/test"
|
||||
)
|
||||
|
||||
func BenchmarkWasmRego(b *testing.B) {
|
||||
policy := compileRegoToWasm("a = true", "data.p.a = x", false)
|
||||
instance, _ := opa.New().
|
||||
WithPolicyBytes(policy).
|
||||
WithMemoryLimits(131070, 2*131070). // TODO: For some reason unlimited memory slows down the eval_ctx_new().
|
||||
WithPoolSize(1).
|
||||
Init()
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
|
||||
ctx := context.Background()
|
||||
var input interface{} = make(map[string]interface{})
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err := instance.Eval(ctx, opa.EvalOpts{Input: &input}); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkGoRego(b *testing.B) {
|
||||
pq := compileRego(`package p
|
||||
|
||||
a = true`, "data.p.a = x")
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
|
||||
ctx := context.Background()
|
||||
input := make(map[string]interface{})
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err := pq.Eval(ctx, rego.EvalInput(input)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkWASMArrayIteration(b *testing.B) {
|
||||
sizes := []int{10, 100, 1000, 10000}
|
||||
for _, n := range sizes {
|
||||
b.Run(fmt.Sprint(n), func(b *testing.B) {
|
||||
benchmarkIteration(b, test.ArrayIterationBenchmarkModule(n))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkWASMSetIteration(b *testing.B) {
|
||||
sizes := []int{10, 100, 1000, 10000}
|
||||
for _, n := range sizes {
|
||||
b.Run(fmt.Sprint(n), func(b *testing.B) {
|
||||
benchmarkIteration(b, test.SetIterationBenchmarkModule(n))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkWASMObjectIteration(b *testing.B) {
|
||||
sizes := []int{10, 100, 1000, 10000}
|
||||
for _, n := range sizes {
|
||||
b.Run(fmt.Sprint(n), func(b *testing.B) {
|
||||
benchmarkIteration(b, test.ObjectIterationBenchmarkModule(n))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var r *opa.Result
|
||||
|
||||
func benchmarkIteration(b *testing.B, module string) {
|
||||
query := "data.test.main = x"
|
||||
policy := compileRegoToWasm(module, query, false)
|
||||
|
||||
instance, err := opa.New().
|
||||
WithPolicyBytes(policy).
|
||||
WithMemoryLimits(2*wasm.PageSize, 47*wasm.PageSize).
|
||||
WithPoolSize(1).
|
||||
Init()
|
||||
if err != nil {
|
||||
b.Fatalf("init sdk: %v", err)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
ctx := context.Background()
|
||||
var input interface{} = make(map[string]interface{})
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
r, err = instance.Eval(ctx, opa.EvalOpts{Input: &input})
|
||||
if err != nil {
|
||||
b.Fatalf("Unexpected query error: %v", err)
|
||||
}
|
||||
if string(r.Result) != `{{"x":true}}` {
|
||||
b.Errorf("unexpected result: %s", string(r.Result))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkWASMLargeJSON(b *testing.B) {
|
||||
for _, kv := range []struct{ key, val int }{
|
||||
{10, 10},
|
||||
{10, 100},
|
||||
{10, 1000},
|
||||
{10, 10000},
|
||||
{100, 100},
|
||||
{100, 1000},
|
||||
} {
|
||||
b.Run(fmt.Sprintf("%dx%d", kv.key, kv.val), func(b *testing.B) {
|
||||
ctx := context.Background()
|
||||
data := test.GenerateJSONBenchmarkData(kv.key, kv.val)
|
||||
|
||||
// Read data.values N times inside query.
|
||||
query := "data.keys[_] = x; data.values = y"
|
||||
policy := compileRegoToWasm("", query, false)
|
||||
|
||||
instance, err := opa.New().
|
||||
WithPolicyBytes(policy).
|
||||
WithDataJSON(data).
|
||||
WithMemoryLimits(200*wasm.PageSize, 600*wasm.PageSize). // This is rather much
|
||||
WithPoolSize(1).
|
||||
Init()
|
||||
if err != nil {
|
||||
b.Fatalf("init sdk: %v", err)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
var input interface{} = make(map[string]interface{})
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
r, err = instance.Eval(ctx, opa.EvalOpts{Input: &input})
|
||||
if err != nil {
|
||||
b.Fatalf("Unexpected query error: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkWASMVirtualDocs(b *testing.B) {
|
||||
for _, kv := range []struct{ total, hit int }{
|
||||
{1, 1},
|
||||
{10, 1},
|
||||
{100, 1},
|
||||
{1000, 1},
|
||||
{10, 10},
|
||||
{100, 10},
|
||||
{1000, 10},
|
||||
{100, 100},
|
||||
{1000, 100},
|
||||
{1000, 1000},
|
||||
} {
|
||||
b.Run(fmt.Sprintf("total=%d/hit=%d", kv.total, kv.hit), func(b *testing.B) {
|
||||
runVirtualDocsBenchmark(b, kv.total, kv.hit)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func runVirtualDocsBenchmark(b *testing.B, numTotalRules, numHitRules int) {
|
||||
ctx := context.Background()
|
||||
module, input := test.GenerateVirtualDocsBenchmarkData(numTotalRules, numHitRules)
|
||||
query := "data.a.b.c.allow = x"
|
||||
|
||||
policy := compileRegoToWasm(module, query, false)
|
||||
|
||||
instance, err := opa.New().
|
||||
WithPolicyBytes(policy).
|
||||
WithMemoryLimits(8*wasm.PageSize, 8*wasm.PageSize).
|
||||
WithPoolSize(1).
|
||||
Init()
|
||||
if err != nil {
|
||||
b.Fatalf("init sdk: %v", err)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
var inp interface{} = input
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
r, err = instance.Eval(ctx, opa.EvalOpts{Input: &inp})
|
||||
if err != nil {
|
||||
b.Fatalf("Unexpected query error: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/open-policy-agent/opa/ast"
|
||||
@@ -17,6 +18,9 @@ import (
|
||||
"github.com/open-policy-agent/opa/util"
|
||||
)
|
||||
|
||||
// control dumping in this file
|
||||
const dump = true
|
||||
|
||||
func TestOPA(t *testing.T) {
|
||||
type Eval struct {
|
||||
NewPolicy string
|
||||
@@ -220,7 +224,7 @@ a = "c" { input > 2 }`,
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.Description, func(t *testing.T) {
|
||||
policy := compileRegoToWasm(test.Policy, test.Query)
|
||||
policy := compileRegoToWasm(test.Policy, test.Query, dump)
|
||||
data := []byte(test.Data)
|
||||
if len(data) == 0 {
|
||||
data = nil
|
||||
@@ -240,14 +244,14 @@ a = "c" { input > 2 }`,
|
||||
for _, eval := range test.Evals {
|
||||
switch {
|
||||
case eval.NewPolicy != "" && eval.NewData != "":
|
||||
policy := compileRegoToWasm(eval.NewPolicy, test.Query)
|
||||
policy := compileRegoToWasm(eval.NewPolicy, test.Query, dump)
|
||||
data := parseJSON(eval.NewData)
|
||||
if err := instance.SetPolicyData(policy, data); err != nil {
|
||||
t.Errorf(err.Error())
|
||||
}
|
||||
|
||||
case eval.NewPolicy != "":
|
||||
policy := compileRegoToWasm(eval.NewPolicy, test.Query)
|
||||
policy := compileRegoToWasm(eval.NewPolicy, test.Query, dump)
|
||||
if err := instance.SetPolicy(policy); err != nil {
|
||||
t.Errorf(err.Error())
|
||||
}
|
||||
@@ -349,51 +353,22 @@ func TestNamedEntrypoint(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkWasmRego(b *testing.B) {
|
||||
policy := compileRegoToWasm("a = true", "data.p.a = x")
|
||||
instance, _ := opa.New().
|
||||
WithPolicyBytes(policy).
|
||||
WithMemoryLimits(131070, 2*131070). // TODO: For some reason unlimited memory slows down the eval_ctx_new().
|
||||
WithPoolSize(1).
|
||||
Init()
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
|
||||
ctx := context.Background()
|
||||
var input interface{} = make(map[string]interface{})
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err := instance.Eval(ctx, opa.EvalOpts{Input: &input}); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// compileRegoToWasm is shared with the benchmarking functions in opa_bench_test.go;
|
||||
// those function use helpers shared with topdown_bench_test.go, and they all use
|
||||
// `package test` -- whereas the callers in this file don't provide the package at
|
||||
// all and assume it'll be `p`.
|
||||
func compileRegoToWasm(module string, query string, dump bool) []byte {
|
||||
if !strings.HasPrefix(module, "package") {
|
||||
module = fmt.Sprintf("package p\n%s", module)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkGoRego(b *testing.B) {
|
||||
pq := compileRego(`package p
|
||||
|
||||
a = true`, "data.p.a = x")
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
|
||||
input := make(map[string]interface{})
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
if _, err := pq.Eval(context.Background(), rego.EvalInput(input)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func compileRegoToWasm(policy string, query string) []byte {
|
||||
module := fmt.Sprintf("package p\n%s", policy)
|
||||
cr, err := rego.New(
|
||||
opts := []func(*rego.Rego){
|
||||
rego.Query(query),
|
||||
rego.Module("module.rego", module),
|
||||
rego.Dump(os.Stderr),
|
||||
).Compile(context.Background(), rego.CompilePartial(false))
|
||||
}
|
||||
if dump {
|
||||
opts = append(opts, rego.Dump(os.Stderr))
|
||||
}
|
||||
cr, err := rego.New(opts...).Compile(context.Background(), rego.CompilePartial(false))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
+1
-1
@@ -1257,7 +1257,7 @@ func (r *Rego) Compile(ctx context.Context, opts ...CompileOption) (*CompileResu
|
||||
}
|
||||
} else {
|
||||
var err error
|
||||
// If creating a new transacation it should be closed before calling the
|
||||
// If creating a new transaction it should be closed before calling the
|
||||
// planner to avoid holding open the transaction longer than needed.
|
||||
//
|
||||
// TODO(tsandall): in future, planner could make use of store, in which
|
||||
|
||||
+13
-176
@@ -16,14 +16,14 @@ import (
|
||||
"github.com/open-policy-agent/opa/metrics"
|
||||
"github.com/open-policy-agent/opa/storage"
|
||||
"github.com/open-policy-agent/opa/storage/inmem"
|
||||
"github.com/open-policy-agent/opa/util"
|
||||
"github.com/open-policy-agent/opa/util/test"
|
||||
)
|
||||
|
||||
func BenchmarkArrayIteration(b *testing.B) {
|
||||
sizes := []int{10, 100, 1000, 10000}
|
||||
for _, n := range sizes {
|
||||
b.Run(fmt.Sprint(n), func(b *testing.B) {
|
||||
benchmarkIteration(b, getArrayIterationBenchmarkModule(n))
|
||||
benchmarkIteration(b, test.ArrayIterationBenchmarkModule(n))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,7 @@ func BenchmarkSetIteration(b *testing.B) {
|
||||
sizes := []int{10, 100, 1000, 10000}
|
||||
for _, n := range sizes {
|
||||
b.Run(fmt.Sprint(n), func(b *testing.B) {
|
||||
benchmarkIteration(b, getSetIterationBenchmarkModule(n))
|
||||
benchmarkIteration(b, test.SetIterationBenchmarkModule(n))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -41,7 +41,7 @@ func BenchmarkObjectIteration(b *testing.B) {
|
||||
sizes := []int{10, 100, 1000, 10000}
|
||||
for _, n := range sizes {
|
||||
b.Run(fmt.Sprint(n), func(b *testing.B) {
|
||||
benchmarkIteration(b, getObjectIterationBenchmarkModule(n))
|
||||
benchmarkIteration(b, test.ObjectIterationBenchmarkModule(n))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -65,32 +65,8 @@ func benchmarkIteration(b *testing.B, module string) {
|
||||
}
|
||||
}
|
||||
|
||||
func getArrayIterationBenchmarkModule(n int) string {
|
||||
return fmt.Sprintf(`package test
|
||||
|
||||
fixture = [ x | x := numbers.range(1, %d)[_] ]
|
||||
|
||||
main { fixture[i] }`, n)
|
||||
}
|
||||
|
||||
func getSetIterationBenchmarkModule(n int) string {
|
||||
return fmt.Sprintf(`package test
|
||||
|
||||
fixture = { x | x := numbers.range(1, %d)[_] }
|
||||
|
||||
main { fixture[i] }`, n)
|
||||
}
|
||||
|
||||
func getObjectIterationBenchmarkModule(n int) string {
|
||||
return fmt.Sprintf(`package test
|
||||
|
||||
fixture = { x: x | x := numbers.range(1, %d)[_] }
|
||||
|
||||
main { fixture[i] }`, n)
|
||||
}
|
||||
|
||||
func BenchmarkLargeJSON(b *testing.B) {
|
||||
data := generateLargeJSONBenchmarkData()
|
||||
data := test.GenerateLargeJSONBenchmarkData()
|
||||
ctx := context.Background()
|
||||
store := inmem.NewFromObject(data)
|
||||
compiler := ast.NewCompiler()
|
||||
@@ -128,26 +104,6 @@ func BenchmarkLargeJSON(b *testing.B) {
|
||||
}
|
||||
}
|
||||
|
||||
func generateLargeJSONBenchmarkData() map[string]interface{} {
|
||||
|
||||
// create array of null values that can be iterated over
|
||||
keys := make([]interface{}, 100)
|
||||
for i := range keys {
|
||||
keys[i] = nil
|
||||
}
|
||||
|
||||
// create large JSON object value (100,000 entries is about 2MB on disk)
|
||||
values := map[string]interface{}{}
|
||||
for i := 0; i < 100*1000; i++ {
|
||||
values[fmt.Sprintf("key%d", i)] = fmt.Sprintf("value%d", i)
|
||||
}
|
||||
|
||||
return map[string]interface{}{
|
||||
"keys": keys,
|
||||
"values": values,
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkConcurrency1(b *testing.B) {
|
||||
benchmarkConcurrency(b, getParams(1, 0))
|
||||
}
|
||||
@@ -174,10 +130,10 @@ func BenchmarkConcurrency8Writers(b *testing.B) {
|
||||
|
||||
func benchmarkConcurrency(b *testing.B, params []storage.TransactionParams) {
|
||||
|
||||
mod, data := generateConcurrencyBenchmarkData()
|
||||
mod, data := test.GenerateConcurrencyBenchmarkData()
|
||||
ctx := context.Background()
|
||||
store := inmem.NewFromObject(data)
|
||||
mods := map[string]*ast.Module{"module": mod}
|
||||
mods := map[string]*ast.Module{"module": ast.MustParseModule(mod)}
|
||||
compiler := ast.NewCompiler()
|
||||
|
||||
if compiler.Compile(mods); compiler.Failed() {
|
||||
@@ -226,53 +182,6 @@ func getParams(nReaders, nWriters int) (sl []storage.TransactionParams) {
|
||||
return sl
|
||||
}
|
||||
|
||||
func generateConcurrencyBenchmarkData() (*ast.Module, map[string]interface{}) {
|
||||
obj := util.MustUnmarshalJSON([]byte(`
|
||||
{
|
||||
"objs": [
|
||||
{
|
||||
"attr1": "get",
|
||||
"path": "/foo/bar",
|
||||
"user": "bob"
|
||||
},
|
||||
{
|
||||
"attr1": "set",
|
||||
"path": "/foo/bar/baz",
|
||||
"user": "alice"
|
||||
},
|
||||
{
|
||||
"attr1": "get",
|
||||
"path": "/foo",
|
||||
"groups": [
|
||||
"admin",
|
||||
"eng"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/foo/bar",
|
||||
"user": "alice"
|
||||
}
|
||||
]
|
||||
}
|
||||
`))
|
||||
|
||||
mod := `package test
|
||||
|
||||
import data.objs
|
||||
|
||||
p {
|
||||
objs[i].attr1 = "get"
|
||||
objs[i].groups[j] = "eng"
|
||||
}
|
||||
|
||||
p {
|
||||
objs[i].user = "alice"
|
||||
}
|
||||
`
|
||||
|
||||
return ast.MustParseModule(mod), obj.(map[string]interface{})
|
||||
}
|
||||
|
||||
func BenchmarkVirtualDocs1x1(b *testing.B) {
|
||||
runVirtualDocsBenchmark(b, 1, 1)
|
||||
}
|
||||
@@ -315,24 +224,25 @@ func BenchmarkVirtualDocs1000x1000(b *testing.B) {
|
||||
|
||||
func runVirtualDocsBenchmark(b *testing.B, numTotalRules, numHitRules int) {
|
||||
|
||||
mod, input := generateVirtualDocsBenchmarkData(numTotalRules, numHitRules)
|
||||
mod, inp := test.GenerateVirtualDocsBenchmarkData(numTotalRules, numHitRules)
|
||||
ctx := context.Background()
|
||||
compiler := ast.NewCompiler()
|
||||
mods := map[string]*ast.Module{"module": mod}
|
||||
mods := map[string]*ast.Module{"module": ast.MustParseModule(mod)}
|
||||
input := ast.NewTerm(ast.MustInterfaceToValue(inp))
|
||||
store := inmem.New()
|
||||
txn := storage.NewTransactionOrDie(ctx, store)
|
||||
if compiler.Compile(mods); compiler.Failed() {
|
||||
b.Fatalf("Unexpected compiler error: %v", compiler.Errors)
|
||||
}
|
||||
|
||||
body := ast.MustParseBody("data.a.b.c.allow = x")
|
||||
query := ast.MustParseBody("data.a.b.c.allow = x")
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
b.StopTimer()
|
||||
|
||||
query := NewQuery(body).
|
||||
query := NewQuery(query).
|
||||
WithCompiler(compiler).
|
||||
WithStore(store).
|
||||
WithTransaction(txn).
|
||||
@@ -345,84 +255,11 @@ func runVirtualDocsBenchmark(b *testing.B, numTotalRules, numHitRules int) {
|
||||
b.Fatalf("Unexpected topdown query error: %v", err)
|
||||
}
|
||||
if len(rs) != 1 || !rs[0][ast.Var("x")].Equal(ast.BooleanTerm(true)) {
|
||||
b.Fatalf("Unexpecfted undefined/extra/bad result: %v", rs)
|
||||
b.Fatalf("Unexpected undefined/extra/bad result: %v", rs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func generateVirtualDocsBenchmarkData(numTotalRules, numHitRules int) (*ast.Module, *ast.Term) {
|
||||
|
||||
hitRule := `
|
||||
allow {
|
||||
input.method = "POST"
|
||||
input.path = ["accounts", account_id]
|
||||
input.user_id = account_id
|
||||
}
|
||||
`
|
||||
|
||||
missRule := `
|
||||
allow {
|
||||
input.method = "GET"
|
||||
input.path = ["salaries", account_id]
|
||||
input.user_id = account_id
|
||||
}
|
||||
`
|
||||
|
||||
testModuleTmpl := `
|
||||
package a.b.c
|
||||
|
||||
{{range .MissRules }}
|
||||
{{ . }}
|
||||
{{end}}
|
||||
|
||||
{{range .HitRules }}
|
||||
{{ . }}
|
||||
{{end}}
|
||||
`
|
||||
|
||||
tmpl, err := template.New("Test").Parse(testModuleTmpl)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
|
||||
var missRules []string
|
||||
|
||||
if numTotalRules > numHitRules {
|
||||
missRules = make([]string, numTotalRules-numHitRules)
|
||||
for i := range missRules {
|
||||
missRules[i] = missRule
|
||||
}
|
||||
}
|
||||
|
||||
hitRules := make([]string, numHitRules)
|
||||
for i := range hitRules {
|
||||
hitRules[i] = hitRule
|
||||
}
|
||||
|
||||
params := struct {
|
||||
MissRules []string
|
||||
HitRules []string
|
||||
}{
|
||||
MissRules: missRules,
|
||||
HitRules: hitRules,
|
||||
}
|
||||
|
||||
err = tmpl.Execute(&buf, params)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
input := ast.MustParseTerm(`{
|
||||
"path": ["accounts", "alice"],
|
||||
"method": "POST",
|
||||
"user_id": "alice"
|
||||
}`)
|
||||
|
||||
return ast.MustParseModule(buf.String()), input
|
||||
}
|
||||
|
||||
func BenchmarkPartialEval(b *testing.B) {
|
||||
sizes := []int{1, 10, 100, 1000}
|
||||
for _, n := range sizes {
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
package test
|
||||
|
||||
// This file collects some helpers for generating data used in
|
||||
// benchmarks,
|
||||
// - topdown/topdown_bench_test.go
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
// ArrayIterationBenchmarkModule returns a module that iterates an array
|
||||
// with `n` elements
|
||||
func ArrayIterationBenchmarkModule(n int) string {
|
||||
return fmt.Sprintf(`package test
|
||||
|
||||
fixture = [ x | x := numbers.range(1, %d)[_] ]
|
||||
|
||||
main { fixture[i] }`, n)
|
||||
}
|
||||
|
||||
// SetIterationBenchmarkModule returns a module that iterates a set
|
||||
// with `n` elements
|
||||
func SetIterationBenchmarkModule(n int) string {
|
||||
return fmt.Sprintf(`package test
|
||||
|
||||
fixture = { x | x := numbers.range(1, %d)[_] }
|
||||
|
||||
main { fixture[i] }`, n)
|
||||
}
|
||||
|
||||
// ObjectIterationBenchmarkModule returns a module that iterates an object
|
||||
// with `n` key/val pairs
|
||||
func ObjectIterationBenchmarkModule(n int) string {
|
||||
return fmt.Sprintf(`package test
|
||||
|
||||
fixture = { x: x | x := numbers.range(1, %d)[_] }
|
||||
|
||||
main { fixture[i] }`, n)
|
||||
}
|
||||
|
||||
// GenerateLargeJSONBenchmarkData returns a map of 100 keys and 100.000 key/value
|
||||
// pairs.
|
||||
func GenerateLargeJSONBenchmarkData() map[string]interface{} {
|
||||
return GenerateJSONBenchmarkData(100, 100*1000)
|
||||
}
|
||||
|
||||
// GenerateJSONBenchmarkData returns a map of `k` keys and `v` key/value pairs.
|
||||
func GenerateJSONBenchmarkData(k, v int) map[string]interface{} {
|
||||
|
||||
// create array of null values that can be iterated over
|
||||
keys := make([]interface{}, k)
|
||||
for i := range keys {
|
||||
keys[i] = nil
|
||||
}
|
||||
|
||||
// create large JSON object value (100,000 entries is about 2MB on disk)
|
||||
values := map[string]interface{}{}
|
||||
for i := 0; i < v; i++ {
|
||||
values[fmt.Sprintf("key%d", i)] = fmt.Sprintf("value%d", i)
|
||||
}
|
||||
|
||||
return map[string]interface{}{
|
||||
"keys": keys,
|
||||
"values": values,
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateConcurrencyBenchmarkData returns a module and data; the module
|
||||
// checks some input parameters against that data in a simple API authz
|
||||
// scheme.
|
||||
func GenerateConcurrencyBenchmarkData() (string, map[string]interface{}) {
|
||||
obj := []byte(`
|
||||
{
|
||||
"objs": [
|
||||
{
|
||||
"attr1": "get",
|
||||
"path": "/foo/bar",
|
||||
"user": "bob"
|
||||
},
|
||||
{
|
||||
"attr1": "set",
|
||||
"path": "/foo/bar/baz",
|
||||
"user": "alice"
|
||||
},
|
||||
{
|
||||
"attr1": "get",
|
||||
"path": "/foo",
|
||||
"groups": [
|
||||
"admin",
|
||||
"eng"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": "/foo/bar",
|
||||
"user": "alice"
|
||||
}
|
||||
]
|
||||
}
|
||||
`)
|
||||
|
||||
var data map[string]interface{}
|
||||
if err := json.Unmarshal(obj, &data); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
mod := `package test
|
||||
|
||||
import data.objs
|
||||
|
||||
p {
|
||||
objs[i].attr1 = "get"
|
||||
objs[i].groups[j] = "eng"
|
||||
}
|
||||
|
||||
p {
|
||||
objs[i].user = "alice"
|
||||
}
|
||||
`
|
||||
|
||||
return mod, data
|
||||
}
|
||||
|
||||
// GenerateVirtualDocsBenchmarkData generates a module and input; the
|
||||
// numTotalRules and numHitRules create as many rules in the module to
|
||||
// match/miss the returned input.
|
||||
func GenerateVirtualDocsBenchmarkData(numTotalRules, numHitRules int) (string, map[string]interface{}) {
|
||||
|
||||
hitRule := `
|
||||
allow {
|
||||
input.method = "POST"
|
||||
input.path = ["accounts", account_id]
|
||||
input.user_id = account_id
|
||||
}
|
||||
`
|
||||
|
||||
missRule := `
|
||||
allow {
|
||||
input.method = "GET"
|
||||
input.path = ["salaries", account_id]
|
||||
input.user_id = account_id
|
||||
}
|
||||
`
|
||||
|
||||
testModuleTmpl := `package a.b.c
|
||||
|
||||
{{range .MissRules }}
|
||||
{{ . }}
|
||||
{{end}}
|
||||
|
||||
{{range .HitRules }}
|
||||
{{ . }}
|
||||
{{end}}
|
||||
`
|
||||
|
||||
tmpl, err := template.New("Test").Parse(testModuleTmpl)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
|
||||
var missRules []string
|
||||
|
||||
if numTotalRules > numHitRules {
|
||||
missRules = make([]string, numTotalRules-numHitRules)
|
||||
for i := range missRules {
|
||||
missRules[i] = missRule
|
||||
}
|
||||
}
|
||||
|
||||
hitRules := make([]string, numHitRules)
|
||||
for i := range hitRules {
|
||||
hitRules[i] = hitRule
|
||||
}
|
||||
|
||||
params := struct {
|
||||
MissRules []string
|
||||
HitRules []string
|
||||
}{
|
||||
MissRules: missRules,
|
||||
HitRules: hitRules,
|
||||
}
|
||||
|
||||
err = tmpl.Execute(&buf, params)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
input := map[string]interface{}{
|
||||
"path": []interface{}{"accounts", "alice"},
|
||||
"method": "POST",
|
||||
"user_id": "alice",
|
||||
}
|
||||
|
||||
return buf.String(), input
|
||||
}
|
||||
Reference in New Issue
Block a user