Skip to content

Commit 7c79c8a

Browse files
committed
Add initial benchmark
Signed-off-by: Anuraag Agrawal <anuraaga@gmail.com>
1 parent 0b86454 commit 7c79c8a

4 files changed

Lines changed: 135 additions & 0 deletions

File tree

poe_tasks.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ go run github.com/bufbuild/buf/private/pkg/licenseheader/cmd/license-header@v1.6
1515
--year-range "2023-2026"
1616
"""
1717

18+
[tasks.bench]
19+
help = "Run CEL evaluation benchmarks"
20+
cmd = "pytest --benchmark-enable --benchmark-only"
21+
1822
[tasks.check]
1923
help = "Run code checks"
2024
sequence = [

pyproject.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ dev = [
4949
"google-re2-stubs==0.1.1",
5050
"poethepoet==0.46.0",
5151
"pytest==9.0.3",
52+
"pytest-benchmark==5.1.0",
5253
"ruff==0.15.16",
5354
"tombi==1.1.2",
5455
"ty==0.0.44",
@@ -64,6 +65,9 @@ source = "vcs"
6465
raw-options = { fallback_version = "0.0.0" }
6566

6667
[tool.pytest]
68+
addopts = [
69+
"--benchmark-disable",
70+
]
6771
# Turn all warnings into errors,
6872
# except DeprecationWarnings (which we knowingly tolerate due to using old `protobuf` APIs).
6973
filterwarnings = ["error", "ignore::DeprecationWarning"]

test/test_benchmark.py

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# Copyright 2023-2026 Buf Technologies, Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""Benchmarks for CEL rule evaluation, by rule category.
16+
17+
Run with `uv run poe bench`. This file only touches the public protovalidate
18+
API, so the same file produces comparable numbers on any branch (e.g. to
19+
compare CEL backends, check out the baseline branch, drop this file in, and
20+
run it there).
21+
22+
Each benchmark measures steady-state `collect_violations` on a shared
23+
`Validator`, so per-message-type compilation is warmed up by the calibration
24+
phase and the numbers reflect rule evaluation, not compilation.
25+
"""
26+
27+
import time
28+
29+
from google.protobuf import any_pb2, duration_pb2, timestamp_pb2
30+
31+
import protovalidate
32+
from buf.validate.conformance.cases import (
33+
kitchen_sink_pb2,
34+
numbers_pb2,
35+
repeated_pb2,
36+
strings_pb2,
37+
wkt_timestamp_pb2,
38+
)
39+
40+
_validator = protovalidate.Validator()
41+
42+
43+
def _run(benchmark, msg, *, expected_violations: int = 0):
44+
violations = benchmark(_validator.collect_violations, msg)
45+
assert len(violations) == expected_violations
46+
47+
48+
def test_int64_range_valid(benchmark):
49+
_run(benchmark, numbers_pb2.Int64GTELTE(val=200))
50+
51+
52+
def test_int64_range_invalid(benchmark):
53+
_run(benchmark, numbers_pb2.Int64GTELTE(val=300), expected_violations=1)
54+
55+
56+
def test_string_len_valid(benchmark):
57+
_run(benchmark, strings_pb2.StringLen(val="foo"))
58+
59+
60+
def test_string_email_valid(benchmark):
61+
_run(benchmark, strings_pb2.StringEmail(val="foo@example.com"))
62+
63+
64+
def test_string_pattern_valid(benchmark):
65+
_run(benchmark, strings_pb2.StringPattern(val="Foo123"))
66+
67+
68+
def test_repeated_unique_valid(benchmark):
69+
_run(benchmark, repeated_pb2.RepeatedUnique(val=["a", "b", "c", "d", "e"]))
70+
71+
72+
def test_timestamp_gt_now_valid(benchmark):
73+
ts = timestamp_pb2.Timestamp()
74+
ts.FromSeconds(int(time.time()) + 3600)
75+
_run(benchmark, wkt_timestamp_pb2.TimestampGTNow(val=ts))
76+
77+
78+
def _kitchen_sink_msg() -> kitchen_sink_pb2.KitchenSinkMessage:
79+
inner = duration_pb2.Duration(seconds=1)
80+
any_val = any_pb2.Any()
81+
any_val.Pack(inner)
82+
return kitchen_sink_pb2.KitchenSinkMessage(
83+
val=kitchen_sink_pb2.ComplexTestMsg(
84+
const="abcd",
85+
int_const=5,
86+
bool_const=False,
87+
float_val={"value": 1.0},
88+
dur_val=duration_pb2.Duration(seconds=3),
89+
ts_val=timestamp_pb2.Timestamp(seconds=100),
90+
float_const=7.0,
91+
double_in=123,
92+
enum_const=kitchen_sink_pb2.COMPLEX_TEST_ENUM_TWO,
93+
any_val=any_val,
94+
rep_ts_val=[timestamp_pb2.Timestamp(nanos=2000000)],
95+
map_val={-1: "a", -2: "b"},
96+
bytes_val=b"\x00\x99",
97+
x="oneof",
98+
)
99+
)
100+
101+
102+
def test_kitchen_sink_valid(benchmark):
103+
_run(benchmark, _kitchen_sink_msg())

uv.lock

Lines changed: 24 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)