OPA进阶-测试、性能分析和基准测试

OPA的测试(test)、性能分析(profile)和(benchmark

掌握他们,对于保证策略代码的质量和决策效率有很大的帮助

1
mkdir quick-start

data.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
"roles": [
{
"operation": "read",
"resource": "widgets",
"name": "widget-reader"
},
{
"operation": "write",
"resource": "widgets",
"name": "widget-writer"
}
],
"bindings": [
{
"user": "inspector-alice",
"role": "widget-reader"
},
{
"user": "maker-bob",
"role": "widget-writer"
}
]
}

example.rego

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package example_rbac

default allow = false

# allow will be true when user has role and role has permission
allow {
# opa eval -f pretty -d quick-start -i quick-start/input.json "data.example_rbac.allow" --explain=notes
# trace(role_name)
some role_name
user_has_role[role_name]
role_has_permission[role_name]
}

# check user role binding exist
user_has_role[role_name] {
role_binding = data.bindings[_]
role_binding.role = role_name
role_binding.user == input.subject.user
}

# check role permission exist
role_has_permission[role_name] {
role = data.roles[_]
role.name = role_name
role.operation == input.action.operation
role.resource == input.action.resource
}

example_test.rego

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package example_rbac_test

import data.example_rbac

# tests to coverage 100%

test_not_allow {
not example_rbac.allow with input as {}
}

test_allow {
example_rbac.allow with input as {
"action": {
"operation": "read",
"resource": "widgets",
},
"subject": {"user": "inspector-alice"},
}
}

input.json

1
2
3
4
5
6
7
8
9
{
"action":{
"operation":"read",
"resource":"widgets"
},
"subject":{
"user":"inspector-alice"
}
}

test

OPA的测试很简单。

所有test_前缀的的规则(rule)都是测试。

Tips: 虽然是测试,但其本质上其仍是规则,任然可以被查询。另外建议文件名遵循_test.rego后缀加以区分

这里拿quick-start的测试例子来看下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package example_rbac_test

import data.example_rbac

test_not_allow {
not example_rbac.allow with input as {}
}

test_allow {
example_rbac.allow with input as {
"action": {
"operation": "read",
"resource": "widgets",
},
"subject": {"user": "inspector-alice"},
}
}

就这样即覆盖了allow规则的全部测试用例。

注意这里with起到了数据模拟的作用(data mocking

Tips: 函数不可用with替换

对应的命令行是:

1
2
3
4
5
6
7
cd quick-start
opa test . -v
# 输出如下
data.example_rbac_test.test_not_allow: PASS (693.201µs)
data.example_rbac_test.test_allow: PASS (562.02µs)
--------------------------------------------------------------------------------
PASS: 2/2

也可以查看测试覆盖率:

1
opa test . -c

设定测试覆盖率标准:

1
opa test . -c --threshold 100

(这里提示下别忘了vscode-opa支持可视化覆盖率展示哦)

也支持选择测试用例执行:

1
opa test -r test_allow . -v

profile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
cd quick-start
opa eval --profile -d example.rego -d data.json -i input.json -f pretty "data.example_rbac.allow"

true
+------------------------------+---------+
| METRIC | VALUE |
+------------------------------+---------+
| timer_rego_data_parse_ns | 18332 |
| timer_rego_load_files_ns | 5506307 |
| timer_rego_module_compile_ns | 573408 |
| timer_rego_module_parse_ns | 5226456 |
| timer_rego_query_compile_ns | 87390 |
| timer_rego_query_eval_ns | 292198 |
| timer_rego_query_parse_ns | 277932 |
+------------------------------+---------+
+----------+----------+----------+-------------------------+
| TIME | NUM EVAL | NUM REDO | LOCATION |
+----------+----------+----------+-------------------------+
| 54.121µs | 1 | 1 | data.example_rbac.allow |
| 42.776µs | 1 | 2 | example.rego:15 |
| 37.861µs | 1 | 1 | example.rego:9 |
| 35.81µs | 1 | 1 | example.rego:25 |
| 22.469µs | 1 | 1 | example.rego:10 |
| 21.353µs | 2 | 2 | example.rego:16 |
| 16.888µs | 2 | 1 | example.rego:17 |
| 15.923µs | 2 | 1 | example.rego:23 |
| 14.736µs | 1 | 2 | example.rego:22 |
| 8.798µs | 1 | 1 | example.rego:24 |
+----------+----------+----------+-------------------------+

Tips: --profile 还支持结果排序和限制显示条数

  • --profile-sort:对性能分析结果排序,默认按total_time_ns => num_eval => num_redo => file => line排序, 详见profile-sort 文档[1]
  • --profile-limit:显示几条分析结果,默认 10 条

benchmark

opa 也支持benchmark,基本实现了gobenchmark的使用方式, 甚至有更详细的结果(毕竟一直标榜性能么)

默认benchmark会展示内存(--benchmem)和查询(--metrics)的基准测试结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ opa bench --count 1 -d example.rego -d data.json -i input.json -f pretty "data.example_rbac.allow"
+-------------------------------------------+------------+
| samples | 14162 |
| ns/op | 93655 |
| B/op | 15117 |
| allocs/op | 311 |
| histogram_timer_rego_query_eval_ns_75% | 89900 |
| histogram_timer_rego_query_eval_ns_90% | 112253 |
| histogram_timer_rego_query_eval_ns_95% | 125465 |
| histogram_timer_rego_query_eval_ns_99% | 222404 |
| histogram_timer_rego_query_eval_ns_99.9% | 549291 |
| histogram_timer_rego_query_eval_ns_99.99% | 550611 |
| histogram_timer_rego_query_eval_ns_count | 14162 |
| histogram_timer_rego_query_eval_ns_max | 550611 |
| histogram_timer_rego_query_eval_ns_mean | 76735 |
| histogram_timer_rego_query_eval_ns_median | 68336 |
| histogram_timer_rego_query_eval_ns_min | 38896 |
| histogram_timer_rego_query_eval_ns_stddev | 38828 |
+-------------------------------------------+------------+

当然也集成到了opa test

1
2
3
4
5
$ opa test -v --bench  example.rego example_test.rego data.json
data.example_rbac_test.test_not_allow 15139 74172 ns/op 62044 timer_rego_query_eval_ns/op 14666 B/op 270 allocs/op
data.example_rbac_test.test_allow 10000 102658 ns/op 90779 timer_rego_query_eval_ns/op 17825 B/op 367 allocs/op
--------------------------------------------------------------------------------
PASS: 2/2

而且--format指定gobench的话,还支持了benchstat[2], 是Go亲生的,无疑了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
opa test -v --bench --count 5 --format gobench   example.rego example_test.rego data.json| tee b.txt

BenchmarkDataExampleRbacTestTestNotAllow 15186 87349 ns/op 73644 timer_rego_query_eval_ns/op 14663 B/op 270 allocs/op
BenchmarkDataExampleRbacTestTestAllow 10000 115857 ns/op 101565 timer_rego_query_eval_ns/op 17828 B/op 367 allocs/op
--------------------------------------------------------------------------------
PASS: 2/2
...

benchstat b.txt

name time/op
DataExampleRbacTestTestNotAllow 92.9µs ±40%
DataExampleRbacTestTestAllow 123µs ± 8%

name timer_rego_query_eval_ns/op
DataExampleRbacTestTestNotAllow 79.0k ±41%
DataExampleRbacTestTestAllow 109k ± 9%

name alloc/op
DataExampleRbacTestTestNotAllow 14.7kB ± 0%
DataExampleRbacTestTestAllow 17.8kB ± 0%

name allocs/op
DataExampleRbacTestTestNotAllow 270 ± 0%
DataExampleRbacTestTestAllow 367 ± 0%

基准测试前后对比自然也是支持,如benchstat old.txt new.txt,就不详述了。

这些工具固然好,高性能还是需要 follow 一些经验法则的

这里引用官方对于性能优化的建议如下(key-takeaways[3]):

  • 编写策略(policy)要最大程度地减少迭代和搜索。

    • 使用的数组元素有唯一标识符时,改为唯一标识作为 key 的对象。
    • 考虑利用部分评估(partial-evaluation[4])以将非线性策略编译为线性策略。
  • 利用规则索引优化(rule-indexing[5])编写策略,以便规则索引有效。

  • 使用探查器(profiler)可帮助确定策略的哪些部分将从提高的性能中受益最大。

  • 使用基准测试工具(benchmark tools)来帮助获取真实世界的时序数据并检测策略性能变化。

里边提到的partial-evaluationrule-indexing是保证OPA高性能的两个重要特性,感兴趣的同学可以自行查看下。

下一篇,我们来谈谈OPA的一个重磅功能 - bundle, 一种将策略及数据进行包组织的方式,也支持丰富的管理restful api,适合分布式决策服务的构建。