|
| 1 | +// Copyright 2026 The OPA Authors. All rights reserved. |
| 2 | +// Use of this source code is governed by an Apache2 |
| 3 | +// license that can be found in the LICENSE file. |
| 4 | + |
| 5 | +package topdown |
| 6 | + |
| 7 | +import ( |
| 8 | + "context" |
| 9 | + "fmt" |
| 10 | + "math/rand" |
| 11 | + "testing" |
| 12 | + |
| 13 | + "github.com/open-policy-agent/opa/v1/ast" |
| 14 | + "github.com/open-policy-agent/opa/v1/storage" |
| 15 | + inmem "github.com/open-policy-agent/opa/v1/storage/inmem/test" |
| 16 | +) |
| 17 | + |
| 18 | +// BenchmarkEnumerateComprehensions benchmarks policy evaluation with |
| 19 | +// comprehensions over large datasets. This specifically targets the |
| 20 | +// enumerate optimization that eliminates closure allocations. |
| 21 | +func BenchmarkEnumerateComprehensions(b *testing.B) { |
| 22 | + sizes := []int{1000, 5000, 10000} |
| 23 | + |
| 24 | + for _, size := range sizes { |
| 25 | + b.Run(fmt.Sprintf("size_%d", size), func(b *testing.B) { |
| 26 | + ctx := context.Background() |
| 27 | + |
| 28 | + // Generate mock dataset with nested objects |
| 29 | + data := generateNestedDataset(size) |
| 30 | + store := inmem.NewFromObject(data) |
| 31 | + |
| 32 | + // Policy with multiple comprehensions that exercise enumerate |
| 33 | + module := `package test |
| 34 | +
|
| 35 | +import rego.v1 |
| 36 | +
|
| 37 | +# Set comprehension over users |
| 38 | +active_users contains user.id if { |
| 39 | + some user in data.users |
| 40 | + user.profile.active == true |
| 41 | +} |
| 42 | +
|
| 43 | +# Array comprehension with nested access |
| 44 | +premium_users := [user | |
| 45 | + some user in data.users |
| 46 | + user.profile.settings.subscription.tier == "premium" |
| 47 | +] |
| 48 | +
|
| 49 | +# Object comprehension with filtering |
| 50 | +users_by_age contains age_group if { |
| 51 | + age_group := "20-30" |
| 52 | + some u in data.users |
| 53 | + u.profile.age >= 20 |
| 54 | + u.profile.age < 30 |
| 55 | +} |
| 56 | +
|
| 57 | +users_by_age contains age_group if { |
| 58 | + age_group := "30-40" |
| 59 | + some u in data.users |
| 60 | + u.profile.age >= 30 |
| 61 | + u.profile.age < 40 |
| 62 | +} |
| 63 | +
|
| 64 | +# Nested comprehension |
| 65 | +high_value_users contains user.email if { |
| 66 | + some user in data.users |
| 67 | + user.profile.active == true |
| 68 | + count([p | some p in user.permissions; p.level > 5]) > 0 |
| 69 | +} |
| 70 | +
|
| 71 | +# Random access pattern |
| 72 | +user_lookup[id] := user if { |
| 73 | + some user in data.users |
| 74 | + id := user.id |
| 75 | +} |
| 76 | + ` |
| 77 | + |
| 78 | + compiler := ast.MustCompileModules(map[string]string{ |
| 79 | + "test.rego": module, |
| 80 | + }) |
| 81 | + |
| 82 | + // Query that exercises all comprehensions |
| 83 | + query := ast.MustParseBody(` |
| 84 | + data.test.active_users |
| 85 | + data.test.premium_users |
| 86 | + data.test.users_by_age |
| 87 | + data.test.high_value_users |
| 88 | + `) |
| 89 | + |
| 90 | + b.ReportAllocs() |
| 91 | + b.ResetTimer() |
| 92 | + |
| 93 | + for b.Loop() { |
| 94 | + err := storage.Txn(ctx, store, storage.TransactionParams{}, func(txn storage.Transaction) error { |
| 95 | + q := NewQuery(query). |
| 96 | + WithCompiler(compiler). |
| 97 | + WithStore(store). |
| 98 | + WithTransaction(txn) |
| 99 | + |
| 100 | + _, err := q.Run(ctx) |
| 101 | + return err |
| 102 | + }) |
| 103 | + |
| 104 | + if err != nil { |
| 105 | + b.Fatal(err) |
| 106 | + } |
| 107 | + } |
| 108 | + }) |
| 109 | + } |
| 110 | +} |
| 111 | + |
| 112 | +// generateNestedDataset creates a dataset with nested objects of varying depth |
| 113 | +func generateNestedDataset(size int) map[string]any { |
| 114 | + users := make([]any, size) |
| 115 | + rng := rand.New(rand.NewSource(42)) // Fixed seed for reproducibility |
| 116 | + |
| 117 | + tiers := []string{"free", "basic", "premium", "enterprise"} |
| 118 | + departments := []string{"engineering", "sales", "marketing", "support", "hr"} |
| 119 | + |
| 120 | + for i := range size { |
| 121 | + // Random nested object with 3-5 levels of nesting |
| 122 | + permissions := make([]any, rng.Intn(10)+1) |
| 123 | + for j := 0; j < len(permissions); j++ { |
| 124 | + permissions[j] = map[string]any{ |
| 125 | + "name": fmt.Sprintf("perm_%d", j), |
| 126 | + "level": rng.Intn(10), |
| 127 | + "scope": map[string]any{ |
| 128 | + "resource": fmt.Sprintf("res_%d", rng.Intn(100)), |
| 129 | + "actions": []string{"read", "write", "delete"}[rng.Intn(3)], |
| 130 | + }, |
| 131 | + } |
| 132 | + } |
| 133 | + |
| 134 | + users[i] = map[string]any{ |
| 135 | + "id": fmt.Sprintf("user_%d", i), |
| 136 | + "name": fmt.Sprintf("User %d", i), |
| 137 | + "email": fmt. Sprintf( "user%[email protected]", i), |
| 138 | + "profile": map[string]any{ |
| 139 | + "active": rng.Float64() > 0.3, // 70% active |
| 140 | + "age": 20 + rng.Intn(40), // Age 20-59 |
| 141 | + "settings": map[string]any{ |
| 142 | + "subscription": map[string]any{ |
| 143 | + "tier": tiers[rng.Intn(len(tiers))], |
| 144 | + "start_date": "2024-01-01", |
| 145 | + "features": map[string]any{ |
| 146 | + "api_access": rng.Float64() > 0.5, |
| 147 | + "custom_domain": rng.Float64() > 0.7, |
| 148 | + "priority_support": map[string]any{ |
| 149 | + "enabled": rng.Float64() > 0.8, |
| 150 | + "level": rng.Intn(5) + 1, |
| 151 | + }, |
| 152 | + }, |
| 153 | + }, |
| 154 | + "notifications": map[string]any{ |
| 155 | + "email": rng.Float64() > 0.4, |
| 156 | + "sms": rng.Float64() > 0.8, |
| 157 | + }, |
| 158 | + }, |
| 159 | + "department": departments[rng.Intn(len(departments))], |
| 160 | + }, |
| 161 | + "permissions": permissions, |
| 162 | + "metadata": map[string]any{ |
| 163 | + "created_at": "2024-01-01T00:00:00Z", |
| 164 | + "updated_at": "2024-01-15T00:00:00Z", |
| 165 | + "tags": map[string]any{ |
| 166 | + "region": []string{"us-west", "us-east", "eu-central"}[rng.Intn(3)], |
| 167 | + "environment": []string{"prod", "staging", "dev"}[rng.Intn(3)], |
| 168 | + "cost_center": fmt.Sprintf("CC%04d", rng.Intn(1000)), |
| 169 | + }, |
| 170 | + }, |
| 171 | + } |
| 172 | + } |
| 173 | + |
| 174 | + return map[string]any{ |
| 175 | + "users": users, |
| 176 | + } |
| 177 | +} |
| 178 | + |
| 179 | +// BenchmarkEnumerateRandomAccess benchmarks random access patterns |
| 180 | +// that exercise virtual document enumeration |
| 181 | +func BenchmarkEnumerateRandomAccess(b *testing.B) { |
| 182 | + ctx := context.Background() |
| 183 | + |
| 184 | + data := generateNestedDataset(10000) |
| 185 | + store := inmem.NewFromObject(data) |
| 186 | + |
| 187 | + module := `package test |
| 188 | +
|
| 189 | +import rego.v1 |
| 190 | +
|
| 191 | +# Virtual document with random access |
| 192 | +user_by_id[id] := user if { |
| 193 | + some user in data.users |
| 194 | + id := user.id |
| 195 | +} |
| 196 | +
|
| 197 | +# Nested virtual document access |
| 198 | +premium_by_dept[dept] := users if { |
| 199 | + dept := data.users[_].profile.department |
| 200 | + users := [u | |
| 201 | + some u in data.users |
| 202 | + u.profile.department == dept |
| 203 | + u.profile.settings.subscription.tier == "premium" |
| 204 | + ] |
| 205 | +} |
| 206 | + ` |
| 207 | + |
| 208 | + compiler := ast.MustCompileModules(map[string]string{ |
| 209 | + "test.rego": module, |
| 210 | + }) |
| 211 | + |
| 212 | + // Access random users |
| 213 | + query := ast.MustParseBody(` |
| 214 | + data.test.user_by_id["user_1234"] |
| 215 | + data.test.user_by_id["user_5678"] |
| 216 | + data.test.premium_by_dept.engineering |
| 217 | + `) |
| 218 | + |
| 219 | + b.ReportAllocs() |
| 220 | + |
| 221 | + for b.Loop() { |
| 222 | + err := storage.Txn(ctx, store, storage.TransactionParams{}, func(txn storage.Transaction) error { |
| 223 | + q := NewQuery(query). |
| 224 | + WithCompiler(compiler). |
| 225 | + WithStore(store). |
| 226 | + WithTransaction(txn) |
| 227 | + |
| 228 | + _, err := q.Run(ctx) |
| 229 | + return err |
| 230 | + }) |
| 231 | + |
| 232 | + if err != nil { |
| 233 | + b.Fatal(err) |
| 234 | + } |
| 235 | + } |
| 236 | +} |
0 commit comments