OPA进阶-测试、性能分析和基准测试 OPA
的测试(test
)、性能分析(profile
)和(benchmark
)
掌握他们,对于保证策略代码的质量和决策效率有很大的帮助
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 --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
,基本实现了go
的benchmark
的使用方式, 甚至有更详细的结果(毕竟一直标榜性能么)
默认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-evaluation
和rule-indexing
是保证OPA
高性能的两个重要特性,感兴趣的同学可以自行查看下。
下一篇,我们来谈谈OPA
的一个重磅功能 - bundle
, 一种将策略及数据进行包组织的方式,也支持丰富的管理restful api
,适合分布式决策服务的构建。