From 5d650c71e77f48d7a47484b9b1ae1a19418d6806 Mon Sep 17 00:00:00 2001 From: Dan Trapp Date: Sat, 10 Oct 2026 14:04:38 -0700 Subject: [PATCH 1/6] Add an optional Rust engine for SRX address-book insertion --- .github/workflows/native.yml | 51 +++++++++ capirca/lib/junipersrx.py | 42 +++++++- native/Cargo.lock | 132 +++++++++++++++++++++++ native/Cargo.toml | 14 +++ native/LICENSE | 202 +++++++++++++++++++++++++++++++++++ native/README.md | 60 +++++++++++ native/pyproject.toml | 13 +++ native/src/lib.rs | 89 +++++++++++++++ tests/lib/srx_native_test.py | 132 +++++++++++++++++++++++ tools/benchmark_srx.py | 175 ++++++++++++++++++++++++++++++ 10 files changed, 906 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/native.yml create mode 100644 native/Cargo.lock create mode 100644 native/Cargo.toml create mode 100644 native/LICENSE create mode 100644 native/README.md create mode 100644 native/pyproject.toml create mode 100644 native/src/lib.rs create mode 100644 tests/lib/srx_native_test.py create mode 100644 tools/benchmark_srx.py diff --git a/.github/workflows/native.yml b/.github/workflows/native.yml new file mode 100644 index 00000000..6b5c95bc --- /dev/null +++ b/.github/workflows/native.yml @@ -0,0 +1,51 @@ +name: Native SRX address book + +on: + push: + paths: + - native/** + - capirca/lib/junipersrx.py + - tests/lib/srx_native_test.py + - tools/benchmark_srx.py + - .github/workflows/native.yml + pull_request: + paths: + - native/** + - capirca/lib/junipersrx.py + - tests/lib/srx_native_test.py + - tools/benchmark_srx.py + - .github/workflows/native.yml + +permissions: + contents: read + +jobs: + test: + timeout-minutes: 15 + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest + python: '3.8' + - os: ubuntu-latest + python: '3.14' + - os: macos-latest + python: '3.14' + - os: windows-latest + python: '3.14' + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python }} + - run: rustup toolchain install stable --profile minimal --component rustfmt --component clippy + - run: rustup default stable + - run: python -m pip install -e . -r requirements.txt pytest ./native + - run: cargo fmt --manifest-path native/Cargo.toml --check + - run: cargo clippy --locked --manifest-path native/Cargo.toml --all-targets -- -D warnings + - run: python -m pytest -q + - run: python tools/benchmark_srx.py --rounds 1 --output benchmark.json + - run: python -m pip uninstall -y capirca-native + - run: python -m pytest -q diff --git a/capirca/lib/junipersrx.py b/capirca/lib/junipersrx.py index bf91dbd2..cd568bea 100644 --- a/capirca/lib/junipersrx.py +++ b/capirca/lib/junipersrx.py @@ -27,6 +27,13 @@ from capirca.lib import nacaddr import six +try: + import _capirca_native +except ModuleNotFoundError as error: + if error.name != '_capirca_native': + raise + _capirca_native = None + ICMP_TERM_LIMIT = 8 @@ -569,8 +576,7 @@ def _TranslatePolicy(self, pol, exp_info): 'WARNING: Term %s has 0 valid source IPs, skipping.', term.name) continue term.source_address = valid_addrs - for addr in term.source_address: - self._BuildAddressBook(self.from_zone, addr) + self._BuildAddressBookFromList(self.from_zone, term.source_address) # Filter destination_address based on filter_type & add to address book if term.destination_address: @@ -584,8 +590,7 @@ def _TranslatePolicy(self, pol, exp_info): term.name) continue term.destination_address = valid_addrs - for addr in term.destination_address: - self._BuildAddressBook(self.to_zone, addr) + self._BuildAddressBookFromList(self.to_zone, term.destination_address) new_term = Term(term, self.from_zone, self.to_zone, self.expresspath, verbose) @@ -696,6 +701,35 @@ def Chunks(l): del terms[:] terms.extend(expanded_terms) + def _BuildAddressBookFromList(self, zone, addresses): + """Insert addresses in order, retaining the selected Python objects.""" + if _capirca_native is None: + for address in addresses: + self._BuildAddressBook(zone, address) + return + + groups = collections.OrderedDict() + for address in addresses: + groups.setdefault(address.parent_token, []).append(address) + if not groups: + return + if zone not in self.addressbook: + self.addressbook[zone] = collections.defaultdict(list) + for name, incoming in groups.items(): + current = self.addressbook[zone][name] + candidates = current + incoming + # Scoped IPv6 equality and custom subnet methods are Python contracts. + if any(type(ip) not in (nacaddr.IPv4, nacaddr.IPv6) or + getattr(ip.network_address, 'scope_id', None) is not None + for ip in candidates): + for address in incoming: + self._BuildAddressBook(zone, address) + continue + networks = [(ip.version, int(ip.network_address), ip.prefixlen) + for ip in candidates] + selected = _capirca_native.address_book_indices(networks, len(current)) + current[:] = [candidates[index] for index in selected] + def _BuildAddressBook(self, zone, address): """Create the address book configuration entries. diff --git a/native/Cargo.lock b/native/Cargo.lock new file mode 100644 index 00000000..01f3ae05 --- /dev/null +++ b/native/Cargo.lock @@ -0,0 +1,132 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "capirca-native" +version = "0.1.0" +dependencies = [ + "pyo3", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "libc" +version = "0.2.190" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce5d3ddc6d3fa000eb1536d85e147bfe31aacaba692ed6a876f95cb7c855be78" + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "portable-atomic" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "pyo3" +version = "0.29.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4688ddedf473e32662b9b067670129a8afb8c18e351482c70d62ba4a88171e8b" +dependencies = [ + "libc", + "once_cell", + "portable-atomic", + "pyo3-build-config", + "pyo3-ffi", + "pyo3-macros", +] + +[[package]] +name = "pyo3-build-config" +version = "0.29.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f41027e41b4bd03f6e60f9f417fe24a6341a6bb744edd62b6f709f2a52ea30e9" +dependencies = [ + "target-lexicon", +] + +[[package]] +name = "pyo3-ffi" +version = "0.29.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e591a95526fead067432c3b3a33fc74770b87b1e04e73671090d9c2055a2b327" +dependencies = [ + "libc", + "pyo3-build-config", +] + +[[package]] +name = "pyo3-macros" +version = "0.29.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73225868fc1cd84eef2c3c230ddb91273bf1de46aeb8a4248da76d32a0924a1c" +dependencies = [ + "proc-macro2", + "pyo3-macros-backend", + "quote", + "syn", +] + +[[package]] +name = "pyo3-macros-backend" +version = "0.29.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "571575aa3749fa6216757dd47d2a3e7ef360f329a40f0666a9fbd14889024952" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "target-lexicon" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca" + +[[package]] +name = "unicode-ident" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954" diff --git a/native/Cargo.toml b/native/Cargo.toml new file mode 100644 index 00000000..fd1c2353 --- /dev/null +++ b/native/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "capirca-native" +version = "0.1.0" +edition = "2021" +rust-version = "1.83" +license = "Apache-2.0" +publish = false + +[lib] +name = "_capirca_native" +crate-type = ["cdylib"] + +[dependencies] +pyo3 = "0.29.2" diff --git a/native/LICENSE b/native/LICENSE new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/native/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/native/README.md b/native/README.md new file mode 100644 index 00000000..74111b6c --- /dev/null +++ b/native/README.md @@ -0,0 +1,60 @@ +# SRX address-book extension + +The optional Rust extension batches the subnet checks used to construct Juniper +SRX address books. Capirca uses it when installed and otherwise uses its existing +Python implementation. + +## Installation + +Building the extension requires Python 3.8 or later and Rust 1.83 or later: + +```sh +python -m pip install ./native +``` + +The extension has a separate wheel. Installing Capirca itself does not require +Rust or change its Python version requirement. To use the Python implementation +again: + +```sh +python -m pip uninstall capirca-native +``` + +## Behavior + +Each batch updates one zone and network group. The extension returns indices +into the existing and incoming addresses; Python retains the original objects +and their comments, tokens, and identities. Existing address-book lists are +updated in place. + +Insertion stops at the first overlapping network, as in the Python implementation. +A containing incoming network replaces the first matching network and any equal +entries, leaving later contained networks in place. It does not collapse the +whole group. IPv4 and IPv6 remain separate. Scoped IPv6 addresses and custom +network subclasses use the Python path. + +## Verification + +From the repository root, with the extension installed: + +```sh +python -m pip install -r requirements.txt pytest +python -m pytest +cargo fmt --manifest-path native/Cargo.toml --check +cargo clippy --locked --manifest-path native/Cargo.toml --all-targets -- -D warnings +python tools/benchmark_srx.py --rounds 5 --output benchmark.json +python tools/benchmark_srx.py --rounds 5 --no-optimize --output benchmark-default.json +``` + +The benchmark rotates between the Python fallback, Rust, and a Python integer +comparison control. It checks exact output hashes, includes definition loading, +parsing, rendering, and writing, and records every run. Imports and process +startup are excluded. Each backend receives one warm-up per workload; all renders +use one worker. The fixtures are the repository's existing policies and network +definitions. Small policies and the complete sample set are included alongside +the large SRX sample. + +Pass `--baseline /path/to/unmodified/capirca` to compare separate CLI processes +against an upstream source tree instead. This mode includes imports, process +startup, and the multiprocessing manager, and verifies that both trees contain +identical fixture files. diff --git a/native/pyproject.toml b/native/pyproject.toml new file mode 100644 index 00000000..5d614e15 --- /dev/null +++ b/native/pyproject.toml @@ -0,0 +1,13 @@ +[build-system] +requires = ["maturin>=1.9,<2"] +build-backend = "maturin" + +[project] +name = "capirca-native" +version = "0.1.0" +description = "Native address-book operations for Capirca" +requires-python = ">=3.8" +license = "Apache-2.0" + +[tool.maturin] +module-name = "_capirca_native" diff --git a/native/src/lib.rs b/native/src/lib.rs new file mode 100644 index 00000000..9c4855f7 --- /dev/null +++ b/native/src/lib.rs @@ -0,0 +1,89 @@ +use pyo3::exceptions::PyValueError; +use pyo3::prelude::*; + +#[derive(Clone, Copy, PartialEq)] +struct Network { + version: u8, + start: u128, + end: u128, +} + +impl Network { + fn new(version: u8, start: u128, prefix: u8) -> PyResult { + let bits = match version { + 4 => 32, + 6 => 128, + _ => return Err(PyValueError::new_err("expected IPv4 or IPv6")), + }; + if prefix > bits || (version == 4 && start > u32::MAX.into()) { + return Err(PyValueError::new_err("invalid network address or prefix")); + } + let host_bits = bits - prefix; + let host_mask = if host_bits == 128 { + u128::MAX + } else { + (1_u128 << host_bits) - 1 + }; + if start & host_mask != 0 { + return Err(PyValueError::new_err("network address has host bits set")); + } + Ok(Self { + version, + start, + end: start | host_mask, + }) + } + + fn contains(self, other: Self) -> bool { + self.version == other.version && self.start <= other.start && self.end >= other.end + } +} + +fn select(networks: &[Network], existing: usize) -> Vec { + let mut selected: Vec = (0..existing).collect(); + for next in existing..networks.len() { + let address = networks[next]; + let mut matched = false; + for &index in &selected { + let previous = networks[index]; + if previous.contains(address) { + matched = true; + break; + } + if address.contains(previous) { + // SRX replaces only the first overlapping network (and its + // duplicates). Later contained networks keep their positions. + for index in &mut selected { + if networks[*index] == previous { + *index = next; + } + } + matched = true; + break; + } + } + if !matched { + selected.push(next); + } + } + selected +} + +#[pyfunction] +fn address_book_indices(networks: Vec<(u8, u128, u8)>, existing: usize) -> PyResult> { + if existing > networks.len() { + return Err(PyValueError::new_err( + "existing count exceeds network count", + )); + } + let networks: Vec = networks + .into_iter() + .map(|(version, start, prefix)| Network::new(version, start, prefix)) + .collect::>()?; + Ok(select(&networks, existing)) +} + +#[pymodule] +fn _capirca_native(module: &Bound<'_, PyModule>) -> PyResult<()> { + module.add_function(wrap_pyfunction!(address_book_indices, module)?) +} diff --git a/tests/lib/srx_native_test.py b/tests/lib/srx_native_test.py new file mode 100644 index 00000000..ddc4cd9a --- /dev/null +++ b/tests/lib/srx_native_test.py @@ -0,0 +1,132 @@ +"""Differential coverage for ordered SRX address-book insertion.""" + +import collections +import pathlib +import random +import unittest +from unittest import mock + +from capirca.lib import junipersrx +from capirca.lib import nacaddr +from capirca.lib import naming +from capirca.lib import policy + + +def book(): + result = object.__new__(junipersrx.JuniperSRX) + result.addressbook = collections.OrderedDict() + return result + + +@unittest.skipIf(junipersrx._capirca_native is None, 'native extension not installed') +class NativeAddressBookTest(unittest.TestCase): + + def compare(self, batches, initial=None): + reference, native = book(), book() + if initial: + for target in (reference, native): + target.addressbook['trust'] = collections.defaultdict(list) + target.addressbook['trust']['net'] = list(initial) + for zone, addresses in batches: + for address in addresses: + reference._BuildAddressBook(zone, address) + native._BuildAddressBookFromList(zone, addresses) + self.assertEqual(list(reference.addressbook), list(native.addressbook)) + for key in reference.addressbook: + expected, actual = reference.addressbook[key], native.addressbook[key] + self.assertEqual(list(expected), list(actual)) + for name in expected: + self.assertEqual([id(ip) for ip in expected[name]], + [id(ip) for ip in actual[name]]) + + def test_first_overlap_and_duplicate_replacement(self): + left = nacaddr.IP('10.0.0.0/25', comment='left', token='net') + right = nacaddr.IP('10.0.0.128/25', comment='right', token='net') + parent = nacaddr.IP('10.0.0.0/24', comment='parent', token='net') + self.compare([('trust', [parent, left])], [left, left, right]) + result = book() + result._BuildAddressBookFromList('trust', [left, right, parent]) + self.assertEqual([id(ip) for ip in result.addressbook['trust']['net']], + [id(parent), id(right)]) + + def test_boundaries_and_aliases(self): + addresses = [nacaddr.IP(value, token='net') for value in ( + '255.255.255.255/32', '0.0.0.0/32', '::/128', + 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128', + '::ffff:192.0.2.1/128', '0.0.0.0/0', '::/0')] + self.compare([('trust', []), ('trust', addresses), + ('untrust', addresses), ('trust', addresses)]) + result = book() + result._BuildAddressBookFromList('trust', addresses[:1]) + original_list = result.addressbook['trust']['net'] + result._BuildAddressBookFromList('trust', addresses[-2:]) + self.assertIs(original_list, result.addressbook['trust']['net']) + + def test_seeded_mixed_networks(self): + rng = random.Random(71205) + for _ in range(120): + batches = [] + for _ in range(5): + addresses = [] + for _ in range(60): + version = rng.choice((4, 6)) + bits = 32 if version == 4 else 128 + prefix = rng.randrange(bits + 1) + start = (rng.getrandbits(bits) >> (bits - prefix)) << (bits - prefix) + cls = nacaddr.IPv4 if version == 4 else nacaddr.IPv6 + address = cls((start, prefix), comment=str(rng.randrange(10)), + token='token-%d' % rng.randrange(3)) + address.parent_token = 'group-%d' % rng.randrange(4) + addresses.append(address) + batches.append((rng.choice(('trust', 'untrust')), addresses)) + self.compare(batches) + + def test_scoped_ipv6_uses_python_semantics(self): + addresses = [nacaddr.IP(value, token='net') for value in ( + 'fe80::1%eth0/128', 'fe80::1%eth1/128', 'fe80::1/128')] + self.compare([('trust', addresses[:1]), ('trust', addresses[1:])]) + + def test_subclasses_keep_their_subnet_methods(self): + class SeparateNetwork(nacaddr.IPv4): + def supernet_of(self, other): + return False + + addresses = [SeparateNetwork('10.0.0.0/8', token='net'), + SeparateNetwork('10.0.0.0/16', token='net')] + self.compare([('trust', addresses)]) + + def test_invalid_native_inputs(self): + function = junipersrx._capirca_native.address_book_indices + for values, count in (([], 1), ([(5, 0, 0)], 0), + ([(4, 0, 33)], 0), ([(6, 0, 129)], 0), + ([(4, 1 << 32, 32)], 0), ([(4, 1, 24)], 0)): + with self.subTest(values=values, count=count): + with self.assertRaises(ValueError): + function(values, count) + + def test_renderer_reaches_native_extension(self): + root = pathlib.Path(__file__).resolve().parents[2] + text = (root / 'policies/pol/sample_srx.pol').read_text() + definitions = naming.Naming(str(root / 'def')) + original = junipersrx._capirca_native.address_book_indices + with mock.patch.object(junipersrx._capirca_native, 'address_book_indices', + wraps=original) as operation: + native = str(junipersrx.JuniperSRX(policy.ParsePolicy(text, definitions), 2)) + self.assertGreater(operation.call_count, 0) + with mock.patch.object(junipersrx, '_capirca_native', None): + reference = str(junipersrx.JuniperSRX(policy.ParsePolicy(text, definitions), 2)) + self.assertEqual(reference, native) + + +class AddressBookFallbackTest(unittest.TestCase): + + def test_optional_extension(self): + result = book() + addresses = [nacaddr.IP('192.0.2.0/24', token='net')] + with mock.patch.object(junipersrx, '_capirca_native', None): + result._BuildAddressBookFromList('trust', addresses) + self.assertIs(result.addressbook['trust']['net'][0], addresses[0]) + + +if __name__ == '__main__': + unittest.main() diff --git a/tools/benchmark_srx.py b/tools/benchmark_srx.py new file mode 100644 index 00000000..56e4568e --- /dev/null +++ b/tools/benchmark_srx.py @@ -0,0 +1,175 @@ +"""Compare complete sample-policy renders with and without the SRX extension.""" + +import argparse +import hashlib +import json +import logging as python_logging +import os +import pathlib +import platform +import statistics +import subprocess +import sys +import tempfile +import time +from types import SimpleNamespace + +from absl import logging +from capirca import aclgen +from capirca.lib import junipersrx +from capirca.lib import naming + + +def integer_indices(networks, existing): + """Control using the same integer comparisons as the Rust implementation.""" + ranges = [(version, start, start | ((1 << ((32 if version == 4 else 128) + - prefix)) - 1)) + for version, start, prefix in networks] + selected = list(range(existing)) + for next_index in range(existing, len(ranges)): + version, start, end = ranges[next_index] + for index in selected: + old_version, old_start, old_end = ranges[index] + if version != old_version: + continue + if old_start <= start and old_end >= end: + break + if start <= old_start and end >= old_end: + selected = [next_index if ranges[i] == ranges[index] else i + for i in selected] + break + else: + selected.append(next_index) + return selected + + +def render(root, policies, optimize): + with tempfile.TemporaryDirectory() as directory: + output = pathlib.Path(directory) + start = time.perf_counter() + definitions = naming.Naming(str(root / 'def')) + writes = [] + for path in policies: + aclgen.RenderFile(str(root / 'policies'), path, output, definitions, + 2, optimize, False, writes) + aclgen.WriteFiles(writes) + seconds = time.perf_counter() - start + digest = hashlib.sha256() + for path in sorted(output.rglob('*')): + if path.is_file(): + digest.update(str(path.relative_to(output)).encode()) + digest.update(b'\0') + digest.update(path.read_bytes()) + return {'seconds': seconds, 'sha256': digest.hexdigest(), + 'files': len(writes)} + + +def render_cli(root, policy_paths, optimize): + with tempfile.TemporaryDirectory() as directory: + command = [sys.executable, '-m', 'capirca.aclgen', '--max_renderers=1', + '--base_directory=' + str(root / 'policies'), + '--definitions_directory=' + str(root / 'def'), + '--output_directory=' + directory] + if len(policy_paths) == 1: + command.append('--policy_file=' + str(root / policy_paths[0])) + if optimize: + command.append('--optimize') + environment = os.environ.copy() + environment['PYTHONPATH'] = str(root) + start = time.perf_counter() + subprocess.run(command, cwd=root, env=environment, check=True, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + seconds = time.perf_counter() - start + digest = hashlib.sha256() + paths = sorted(path for path in pathlib.Path(directory).rglob('*') + if path.is_file()) + for path in paths: + digest.update(str(path.relative_to(directory)).encode()) + digest.update(b'\0') + digest.update(path.read_bytes()) + return {'seconds': seconds, 'sha256': digest.hexdigest(), 'files': len(paths)} + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--rounds', type=int, default=5) + parser.add_argument('--output', type=pathlib.Path, required=True) + parser.add_argument('--no-optimize', action='store_true') + parser.add_argument('--baseline', type=pathlib.Path, + help='compare CLI processes against an unmodified source tree') + args = parser.parse_args() + if args.rounds < 1: + parser.error('--rounds must be positive') + root = pathlib.Path(__file__).resolve().parents[1] + native = junipersrx._capirca_native + if native is None: + parser.error('install the native extension first') + logging.set_verbosity(logging.ERROR) + python_logging.disable(python_logging.WARNING) + backends = {'python': None, 'rust': native, + 'python-integers': SimpleNamespace( + address_book_indices=integer_indices)} + if args.baseline: + backends = {'upstream': None, 'rust': native} + workloads = { + 'all-samples': sorted(aclgen.DescendDirectory( + str(root / 'policies'), ['DEPRECATED', 'def'])), + 'srx-large': [root / 'policies/pol/sample_stateful_multitarget_complex.pol'], + 'srx-small': [root / 'policies/pol/sample_srx.pol'], + } + results = {'revision': subprocess.check_output( + ['git', 'rev-parse', 'HEAD'], cwd=root, text=True).strip(), + 'dirty': bool(subprocess.check_output( + ['git', 'status', '--porcelain'], cwd=root, text=True).strip()), + 'python': platform.python_version(), + 'platform': platform.platform(), + 'optimize': not args.no_optimize, + 'cli': bool(args.baseline), + 'workers': 1, 'runs': [], 'summary': {}, + 'inputs': {str(path.relative_to(root)): hashlib.sha256( + path.read_bytes()).hexdigest() + for directory in ('def', 'policies') + for path in sorted((root / directory).rglob('*')) + if path.is_file()}} + names = list(backends) + if args.baseline: + args.baseline = args.baseline.resolve() + results['baseline_revision'] = subprocess.check_output( + ['git', 'rev-parse', 'HEAD'], cwd=args.baseline, text=True).strip() + if subprocess.check_output(['git', 'status', '--porcelain'], + cwd=args.baseline, text=True).strip(): + parser.error('baseline checkout must be clean') + for path, digest in results['inputs'].items(): + if hashlib.sha256((args.baseline / path).read_bytes()).hexdigest() != digest: + parser.error('baseline fixture mismatch: ' + path) + for workload, policies in workloads.items(): + expected = None + for round_number in range(-1, args.rounds): + offset = max(round_number, 0) % len(names) + order = names[offset:] + names[:offset] + for backend in order: + junipersrx._capirca_native = backends[backend] + if args.baseline: + source = args.baseline if backend == 'upstream' else root + result = render_cli(source, [path.relative_to(root) for path in policies], + not args.no_optimize) + else: + result = render(root, policies, not args.no_optimize) + if expected is None: + expected = result['sha256'] + if result['sha256'] != expected: + raise RuntimeError('output mismatch: %s/%s' % (workload, backend)) + result.update(workload=workload, backend=backend, round=round_number) + results['runs'].append(result) + print(json.dumps(result), flush=True) + results['summary'][workload] = { + backend: statistics.median(run['seconds'] for run in results['runs'] + if run['workload'] == workload and + run['backend'] == backend and run['round'] >= 0) + for backend in names} + args.output.write_text(json.dumps(results, indent=2) + '\n') + junipersrx._capirca_native = native + + +if __name__ == '__main__': + main() From 333b24286088fd5558b7df79f945bfc624fdb5d9 Mon Sep 17 00:00:00 2001 From: Dan Trapp Date: Sat, 10 Oct 2026 14:06:27 -0700 Subject: [PATCH 2/6] Pin subprocess hash seeds for reproducible output comparisons --- .github/workflows/native.yml | 12 ++++++++---- native/README.md | 3 ++- tools/benchmark_srx.py | 2 ++ 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/.github/workflows/native.yml b/.github/workflows/native.yml index 6b5c95bc..e818ea6a 100644 --- a/.github/workflows/native.yml +++ b/.github/workflows/native.yml @@ -4,15 +4,19 @@ on: push: paths: - native/** - - capirca/lib/junipersrx.py - - tests/lib/srx_native_test.py + - capirca/** + - tests/** + - requirements.txt + - setup.py - tools/benchmark_srx.py - .github/workflows/native.yml pull_request: paths: - native/** - - capirca/lib/junipersrx.py - - tests/lib/srx_native_test.py + - capirca/** + - tests/** + - requirements.txt + - setup.py - tools/benchmark_srx.py - .github/workflows/native.yml diff --git a/native/README.md b/native/README.md index 74111b6c..e92c1348 100644 --- a/native/README.md +++ b/native/README.md @@ -57,4 +57,5 @@ the large SRX sample. Pass `--baseline /path/to/unmodified/capirca` to compare separate CLI processes against an upstream source tree instead. This mode includes imports, process startup, and the multiprocessing manager, and verifies that both trees contain -identical fixture files. +identical fixture files. Both subprocesses use `PYTHONHASHSEED=0` because the +Arista generator emits counters in set iteration order. diff --git a/tools/benchmark_srx.py b/tools/benchmark_srx.py index 56e4568e..d818a82b 100644 --- a/tools/benchmark_srx.py +++ b/tools/benchmark_srx.py @@ -76,6 +76,7 @@ def render_cli(root, policy_paths, optimize): command.append('--optimize') environment = os.environ.copy() environment['PYTHONPATH'] = str(root) + environment['PYTHONHASHSEED'] = '0' start = time.perf_counter() subprocess.run(command, cwd=root, env=environment, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) @@ -125,6 +126,7 @@ def main(): 'platform': platform.platform(), 'optimize': not args.no_optimize, 'cli': bool(args.baseline), + 'hash_seed': '0' if args.baseline else os.environ.get('PYTHONHASHSEED'), 'workers': 1, 'runs': [], 'summary': {}, 'inputs': {str(path.relative_to(root)): hashlib.sha256( path.read_bytes()).hexdigest() From 14505d94c9fdff7c936a46bb16c3d73442e5da1a Mon Sep 17 00:00:00 2001 From: Dan Trapp Date: Sat, 10 Oct 2026 14:07:22 -0700 Subject: [PATCH 3/6] Count distinct output files in render benchmark results --- tools/benchmark_srx.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tools/benchmark_srx.py b/tools/benchmark_srx.py index d818a82b..697b9c4c 100644 --- a/tools/benchmark_srx.py +++ b/tools/benchmark_srx.py @@ -55,13 +55,15 @@ def render(root, policies, optimize): aclgen.WriteFiles(writes) seconds = time.perf_counter() - start digest = hashlib.sha256() + files = 0 for path in sorted(output.rglob('*')): if path.is_file(): + files += 1 digest.update(str(path.relative_to(output)).encode()) digest.update(b'\0') digest.update(path.read_bytes()) return {'seconds': seconds, 'sha256': digest.hexdigest(), - 'files': len(writes)} + 'files': files, 'writes': len(writes)} def render_cli(root, policy_paths, optimize): From d08f27a15074f7035afe87dc909ee074c4f3e8ad Mon Sep 17 00:00:00 2001 From: Dan Trapp Date: Sat, 10 Oct 2026 14:09:34 -0700 Subject: [PATCH 4/6] Test the optional extension on Python 3.9 and newer --- .github/workflows/native.yml | 2 +- native/README.md | 2 +- native/pyproject.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/native.yml b/.github/workflows/native.yml index e818ea6a..cc6a4f28 100644 --- a/.github/workflows/native.yml +++ b/.github/workflows/native.yml @@ -31,7 +31,7 @@ jobs: matrix: include: - os: ubuntu-latest - python: '3.8' + python: '3.9' - os: ubuntu-latest python: '3.14' - os: macos-latest diff --git a/native/README.md b/native/README.md index e92c1348..611e4682 100644 --- a/native/README.md +++ b/native/README.md @@ -6,7 +6,7 @@ Python implementation. ## Installation -Building the extension requires Python 3.8 or later and Rust 1.83 or later: +Building the extension requires Python 3.9 or later and Rust 1.83 or later: ```sh python -m pip install ./native diff --git a/native/pyproject.toml b/native/pyproject.toml index 5d614e15..9a3d5f75 100644 --- a/native/pyproject.toml +++ b/native/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "maturin" name = "capirca-native" version = "0.1.0" description = "Native address-book operations for Capirca" -requires-python = ">=3.8" +requires-python = ">=3.9" license = "Apache-2.0" [tool.maturin] From 904ea9417412dc9de479b4b5637f88f8d74d9fdc Mon Sep 17 00:00:00 2001 From: Dan Trapp Date: Sat, 10 Oct 2026 14:12:19 -0700 Subject: [PATCH 5/6] Record end-to-end benchmarks and verify supported SRX runtimes --- .github/workflows/native.yml | 12 +- README.md | 4 + native/README.md | 5 + native/benchmarks/README.md | 82 ++++ native/benchmarks/cli-default.json | 261 +++++++++++ native/benchmarks/python-environment.txt | 9 + native/benchmarks/render-optimized.json | 557 +++++++++++++++++++++++ 7 files changed, 927 insertions(+), 3 deletions(-) create mode 100644 native/benchmarks/README.md create mode 100644 native/benchmarks/cli-default.json create mode 100644 native/benchmarks/python-environment.txt create mode 100644 native/benchmarks/render-optimized.json diff --git a/.github/workflows/native.yml b/.github/workflows/native.yml index cc6a4f28..195afd18 100644 --- a/.github/workflows/native.yml +++ b/.github/workflows/native.yml @@ -32,12 +32,16 @@ jobs: include: - os: ubuntu-latest python: '3.9' + tests: tests/lib/junipersrx_test.py tests/lib/srx_native_test.py - os: ubuntu-latest python: '3.14' + tests: tests - os: macos-latest python: '3.14' + tests: tests - os: windows-latest python: '3.14' + tests: tests runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -49,7 +53,9 @@ jobs: - run: python -m pip install -e . -r requirements.txt pytest ./native - run: cargo fmt --manifest-path native/Cargo.toml --check - run: cargo clippy --locked --manifest-path native/Cargo.toml --all-targets -- -D warnings - - run: python -m pytest -q - - run: python tools/benchmark_srx.py --rounds 1 --output benchmark.json + # Upstream OpenConfig uses datetime.UTC, which requires Python 3.11. + - run: python -m pytest -q ${{ matrix.tests }} + - if: matrix.python != '3.9' + run: python tools/benchmark_srx.py --rounds 1 --output benchmark.json - run: python -m pip uninstall -y capirca-native - - run: python -m pytest -q + - run: python -m pytest -q ${{ matrix.tests }} diff --git a/README.md b/README.md index cf2f542f..a1b0cfde 100644 --- a/README.md +++ b/README.md @@ -638,6 +638,10 @@ pip install capirca --user ### Basic Usage +An optional [Rust extension](./native/README.md) accelerates Juniper SRX +address-book construction. Install it separately with `pip install ./native` +from a source checkout. + There are a number of command-line arguments that can be used with `aclgen`. ``` diff --git a/native/README.md b/native/README.md index 611e4682..27c56aa5 100644 --- a/native/README.md +++ b/native/README.md @@ -4,6 +4,8 @@ The optional Rust extension batches the subnet checks used to construct Juniper SRX address books. Capirca uses it when installed and otherwise uses its existing Python implementation. +[Benchmark results and reproduction commands](./benchmarks/README.md). + ## Installation Building the extension requires Python 3.9 or later and Rust 1.83 or later: @@ -20,6 +22,9 @@ again: python -m pip uninstall capirca-native ``` +CI runs the SRX tests on Python 3.9 and the full project suite on Python 3.14 +across Linux, macOS, and Windows. + ## Behavior Each batch updates one zone and network group. The extension returns indices diff --git a/native/benchmarks/README.md b/native/benchmarks/README.md new file mode 100644 index 00000000..5909c0e8 --- /dev/null +++ b/native/benchmarks/README.md @@ -0,0 +1,82 @@ +# SRX address-book benchmark + +Measured October 10, 2026 on an Apple M4 with 16 GiB RAM, macOS 15.6.1, +CPython 3.14.4, and rustc 1.94.1 (`aarch64-apple-darwin`). The extension used +the Cargo release profile, the checked-in lockfile, and no target-specific flags. + +## Default CLI + +Complete CLI invocations against unmodified upstream commit +`fcca84f5df6e8262d55ecca20635c166009c2c09`, with `--max_renderers=1` and +otherwise default options. Imports, process startup, the multiprocessing manager, +definition loading, parsing, rendering, and file writes are included. + +| Workload | Upstream median | Rust median | Speedup | +| --- | ---: | ---: | ---: | +| All bundled samples | 15.8686 s | 1.7551 s | 9.04x | +| Large SRX/Palo Alto sample | 14.6689 s | 0.6051 s | 24.24x | +| Small SRX sample | 0.2501 s | 0.2510 s | 1.00x | + +All 49 distinct files in the complete sample run matched byte-for-byte. The +large mixed-target sample produced two matching files, and the small sample +produced one. The CLI timings contain three measured runs per backend and +workload, preceded by one warm-up. Run order alternates, filesystem caches are +warm, and each invocation uses a fresh Python process. `PYTHONHASHSEED=0` is +set in both processes because the unrelated Arista generator emits counters +in set iteration order. + +## Optimized rendering and Python control + +These runs enable address optimization and execute in one Python process. +They include definition loading through file writing, but exclude imports and +process startup. The Python column uses the unchanged insertion routine through +the fallback; the integer control uses the same range comparisons and batching +as Rust, implemented in Python. Five measured runs per backend follow one warm-up; +backend order rotates. + +| Workload | Python median | Integer control median | Rust median | +| --- | ---: | ---: | ---: | +| All bundled samples | 5.752011 s | 1.635033 s | 1.341342 s | +| Large SRX/Palo Alto sample | 4.671460 s | 0.554711 s | 0.252233 s | +| Small SRX sample | 0.002287 s | 0.002282 s | 0.002289 s | + +The integer control accounts for a substantial part of the improvement. +Rust is 1.22x faster than that control over the complete optimized sample run +and 2.20x faster on the large mixed-target policy. The small policy shows no +meaningful improvement. Every backend produced identical output hashes. + +These are bundled sample fixtures, including the synthetic large networks in +`def/LARGE_NETWORK.net`; they do not establish performance on a production +policy corpus or with multiple rendering workers. + +## Correctness and build checks + +- Full local suite with Rust: 1,725 passed, plus 19 subtests. +- Full local suite with the extension uninstalled: 1,718 passed, seven native-only tests skipped, plus 13 subtests. +- Differential tests compare selected object identities after each batch, including first-match replacement, duplicate existing entries, mixed families, full address ranges, and 36,000 seeded incoming networks. +- Scoped IPv6 and custom network subclasses use the Python path. +- An integration test verifies that policy rendering invokes the native function and matches fallback output. +- Release wheel build, Rust formatting, and Clippy with warnings denied passed. +- Changed Python files pass the repository CI lint selections. Repository-wide lint finds the same pre-existing W391 and two F824 findings on unchanged upstream. + +## Reproduction + +The raw files retain individual timings, output digests, source revisions, and +input-file SHA-256 hashes: [CLI](./cli-default.json) and +[optimized rendering](./render-optimized.json). The application implementation +is commit `5d650c71e77f48d7a47484b9b1ae1a19418d6806`; subsequent changes before +measurement adjust benchmark reporting and test configuration. + +From the repository root, in an activated Python 3.14 environment: + +```sh +python -m pip install -r native/benchmarks/python-environment.txt +python -m pip install --no-deps -e . +python -m pip install --no-build-isolation ./native +git worktree add --detach ../capirca-baseline fcca84f5df6e8262d55ecca20635c166009c2c09 +PYTHONHASHSEED=0 python tools/benchmark_srx.py --rounds 3 --no-optimize --baseline ../capirca-baseline --output cli-default.json +PYTHONHASHSEED=0 python tools/benchmark_srx.py --rounds 5 --output render-optimized.json +``` + +Use rustc 1.94.1 for the recorded build configuration. Absolute runtimes depend +on the host, background activity, Python version, and policy contents. diff --git a/native/benchmarks/cli-default.json b/native/benchmarks/cli-default.json new file mode 100644 index 00000000..7ad670e7 --- /dev/null +++ b/native/benchmarks/cli-default.json @@ -0,0 +1,261 @@ +{ + "revision": "333b24286088fd5558b7df79f945bfc624fdb5d9", + "dirty": false, + "python": "3.14.4", + "platform": "macOS-15.6.1-arm64-arm-64bit-Mach-O", + "optimize": false, + "cli": true, + "hash_seed": "0", + "workers": 1, + "runs": [ + { + "seconds": 15.701102125021862, + "sha256": "6eb1ad3e47e81d75034bc1d115296c43ec5dc4cb5999e029a1f6c06799eb0461", + "files": 49, + "workload": "all-samples", + "backend": "upstream", + "round": -1 + }, + { + "seconds": 1.7138414999935776, + "sha256": "6eb1ad3e47e81d75034bc1d115296c43ec5dc4cb5999e029a1f6c06799eb0461", + "files": 49, + "workload": "all-samples", + "backend": "rust", + "round": -1 + }, + { + "seconds": 15.917016167019028, + "sha256": "6eb1ad3e47e81d75034bc1d115296c43ec5dc4cb5999e029a1f6c06799eb0461", + "files": 49, + "workload": "all-samples", + "backend": "upstream", + "round": 0 + }, + { + "seconds": 1.755138167005498, + "sha256": "6eb1ad3e47e81d75034bc1d115296c43ec5dc4cb5999e029a1f6c06799eb0461", + "files": 49, + "workload": "all-samples", + "backend": "rust", + "round": 0 + }, + { + "seconds": 1.7033659169974271, + "sha256": "6eb1ad3e47e81d75034bc1d115296c43ec5dc4cb5999e029a1f6c06799eb0461", + "files": 49, + "workload": "all-samples", + "backend": "rust", + "round": 1 + }, + { + "seconds": 15.868583583011059, + "sha256": "6eb1ad3e47e81d75034bc1d115296c43ec5dc4cb5999e029a1f6c06799eb0461", + "files": 49, + "workload": "all-samples", + "backend": "upstream", + "round": 1 + }, + { + "seconds": 15.837631542002782, + "sha256": "6eb1ad3e47e81d75034bc1d115296c43ec5dc4cb5999e029a1f6c06799eb0461", + "files": 49, + "workload": "all-samples", + "backend": "upstream", + "round": 2 + }, + { + "seconds": 1.7606482499977574, + "sha256": "6eb1ad3e47e81d75034bc1d115296c43ec5dc4cb5999e029a1f6c06799eb0461", + "files": 49, + "workload": "all-samples", + "backend": "rust", + "round": 2 + }, + { + "seconds": 14.769737958995393, + "sha256": "1e1f403497ea89efda8fa01b8dc2262629213061016d8de531973f524b954a9c", + "files": 2, + "workload": "srx-large", + "backend": "upstream", + "round": -1 + }, + { + "seconds": 0.6158439579885453, + "sha256": "1e1f403497ea89efda8fa01b8dc2262629213061016d8de531973f524b954a9c", + "files": 2, + "workload": "srx-large", + "backend": "rust", + "round": -1 + }, + { + "seconds": 14.668906666018302, + "sha256": "1e1f403497ea89efda8fa01b8dc2262629213061016d8de531973f524b954a9c", + "files": 2, + "workload": "srx-large", + "backend": "upstream", + "round": 0 + }, + { + "seconds": 0.6204440419969615, + "sha256": "1e1f403497ea89efda8fa01b8dc2262629213061016d8de531973f524b954a9c", + "files": 2, + "workload": "srx-large", + "backend": "rust", + "round": 0 + }, + { + "seconds": 0.6050867080048192, + "sha256": "1e1f403497ea89efda8fa01b8dc2262629213061016d8de531973f524b954a9c", + "files": 2, + "workload": "srx-large", + "backend": "rust", + "round": 1 + }, + { + "seconds": 15.557485666999128, + "sha256": "1e1f403497ea89efda8fa01b8dc2262629213061016d8de531973f524b954a9c", + "files": 2, + "workload": "srx-large", + "backend": "upstream", + "round": 1 + }, + { + "seconds": 14.593839208013378, + "sha256": "1e1f403497ea89efda8fa01b8dc2262629213061016d8de531973f524b954a9c", + "files": 2, + "workload": "srx-large", + "backend": "upstream", + "round": 2 + }, + { + "seconds": 0.5921664589841384, + "sha256": "1e1f403497ea89efda8fa01b8dc2262629213061016d8de531973f524b954a9c", + "files": 2, + "workload": "srx-large", + "backend": "rust", + "round": 2 + }, + { + "seconds": 0.2543911660031881, + "sha256": "2e9c8d41a331b9932d0891fd7c0328b17da32573c0ad174d25d682388daa0871", + "files": 1, + "workload": "srx-small", + "backend": "upstream", + "round": -1 + }, + { + "seconds": 0.257438667002134, + "sha256": "2e9c8d41a331b9932d0891fd7c0328b17da32573c0ad174d25d682388daa0871", + "files": 1, + "workload": "srx-small", + "backend": "rust", + "round": -1 + }, + { + "seconds": 0.2522032919805497, + "sha256": "2e9c8d41a331b9932d0891fd7c0328b17da32573c0ad174d25d682388daa0871", + "files": 1, + "workload": "srx-small", + "backend": "upstream", + "round": 0 + }, + { + "seconds": 0.2534834159887396, + "sha256": "2e9c8d41a331b9932d0891fd7c0328b17da32573c0ad174d25d682388daa0871", + "files": 1, + "workload": "srx-small", + "backend": "rust", + "round": 0 + }, + { + "seconds": 0.25098220800282434, + "sha256": "2e9c8d41a331b9932d0891fd7c0328b17da32573c0ad174d25d682388daa0871", + "files": 1, + "workload": "srx-small", + "backend": "rust", + "round": 1 + }, + { + "seconds": 0.25009449999197386, + "sha256": "2e9c8d41a331b9932d0891fd7c0328b17da32573c0ad174d25d682388daa0871", + "files": 1, + "workload": "srx-small", + "backend": "upstream", + "round": 1 + }, + { + "seconds": 0.24966604099608958, + "sha256": "2e9c8d41a331b9932d0891fd7c0328b17da32573c0ad174d25d682388daa0871", + "files": 1, + "workload": "srx-small", + "backend": "upstream", + "round": 2 + }, + { + "seconds": 0.2493300840142183, + "sha256": "2e9c8d41a331b9932d0891fd7c0328b17da32573c0ad174d25d682388daa0871", + "files": 1, + "workload": "srx-small", + "backend": "rust", + "round": 2 + } + ], + "summary": { + "all-samples": { + "upstream": 15.868583583011059, + "rust": 1.755138167005498 + }, + "srx-large": { + "upstream": 14.668906666018302, + "rust": 0.6050867080048192 + }, + "srx-small": { + "upstream": 0.25009449999197386, + "rust": 0.25098220800282434 + } + }, + "inputs": { + "def/LARGE_NETWORK.net": "e10c3238c28f61667fd650b0d497c959d3173aba3e44643ebaded1cbfc6846a5", + "def/NETWORK.net": "2f5e87e975a5daf821bf89e8f55a78ab450f31023f8c79abb3c76462f1361f5c", + "def/SERVICES.svc": "3c46ae6c6f6eea634929923d70d06510e1b4547364d72e47330a2b3bbbc82251", + "policies/includes/untrusted-networks-blocking.inc": "cb88a2b91e6bfe6c47f1dd7de046e6f3bee2502e1270ecc5fbd7db26eabea7c7", + "policies/pol/sample_arista_tp.pol": "d2b3062f9795ece58fade5fb53c78715444967459c52ccd399e8a0fdb6c79e1b", + "policies/pol/sample_cisco_lab.pol": "752b81130347b65924471a171e6030bdced1578f400dd3eef8bb5b328973e0c8", + "policies/pol/sample_cisconx.pol": "ce78e9ca1459f264f67945980e2a4cb51acfba1e47a419298a3507ec79539cb4", + "policies/pol/sample_cloudarmor.pol": "0081a9e26644f1738e9357a63d1f71a9677d4cc1c694f1513cb9d7dea51e53e4", + "policies/pol/sample_fortigate.pol": "af0e7e9aa82a9178984cacb92febdba860d5bca3ca2b5dedc7b9c3a754a4584c", + "policies/pol/sample_fortigate_localin.pol": "7e1028c3d431ae044c75f82cf2d5263ca2d7963da6dd009bf9624056f8de7900", + "policies/pol/sample_fortigate_localin_ipv4.pol": "8b2374f11dd2d2e8b3e97ab7002efc275cfcd4590b785f7165c94b93832836da", + "policies/pol/sample_fortigate_localin_ipv6.pol": "f97621a4765319887d37a4d5a6e6afcfad5d39a9c0d88f7f131ad4d35dc56a7c", + "policies/pol/sample_gce.pol": "11e55dbe62eaf7fe533d8e8623a1cf75ebe2307c42a87e37ec9e23e6e4379878", + "policies/pol/sample_gce_vpc_tf.pol": "b60f73740a9d18f363789a0579b87192fbbfc0453ea5b3532982189652e6a055", + "policies/pol/sample_gcp_hf.pol": "144f0decdcf24b410e00d9ba345304624f891d2bcebc9b63566aebb022debe7f", + "policies/pol/sample_inet6_gce.pol": "9f4aa6c0a99440b45f0d8e4d4c80c308d92f4d3fab20919084cfb2632bc3a4a5", + "policies/pol/sample_inet6_gcp_hf.pol": "e3e87c6013352997c33b77b07d374362352e30eb6feafe9b430b0dc16eae38d0", + "policies/pol/sample_ipset.pol": "6a7d7838a12896d1c323c09a2ebbee5af2d291a2b9b0a79ee44f174086779fa7", + "policies/pol/sample_juniper_loopback.pol": "699930d34bf9404a8dcbe8fbdb7a4e7a52fb58f32d3952e732ccde9265dd6bff", + "policies/pol/sample_juniperevo_loopback.pol": "a1e29dbc54b2ec34989c1c2687067e0df2876f9e4bb81e4a99c21fbe26d83e4e", + "policies/pol/sample_k8s.pol": "7c0cefb881cc7ff8c2e79e32172a2a3c4d4b0e0b88fd70201aaa32588459da62", + "policies/pol/sample_mixed_gce.pol": "80469d2883d913f50731ebf203531972c3b4936136303bfe3f3a18dbe14d6679", + "policies/pol/sample_mixed_gcp_hf.pol": "1cdb52ba11aaeb1c853be4232e7af82b8cf9e25a6ef0d1969bfdd3b7cf03d146", + "policies/pol/sample_msmpc.pol": "bc89d9d0c7386d6529f15e5774b81a0b26164d54f30ee749a8806efd79e3f87a", + "policies/pol/sample_multitarget.pol": "19a225dd00332ec6a7a151efa0b2afaf2ce9e430be7299df11254f0b1084b226", + "policies/pol/sample_nftables-dev.pol": "4888ed8def4051fd8faa338445cafebce05073912f3a87741adf9f435badc77a", + "policies/pol/sample_nftables-mixed-icmp.pol": "ef5454f17b73acdf33f7d25db631a6272724c985e10451ed81aa2834162958c5", + "policies/pol/sample_nftables-mixed-multiple-headers-combo.pol": "3b86dd6ca880786eea4824f28cbc34b6897a08a95c62be9976ae2f017f359937", + "policies/pol/sample_nftables.pol": "2725dc17768dcdb844bf7c1b14bc839231f11b0854664dc6bcccb4daf8238607", + "policies/pol/sample_nsxt.pol": "bdb439aa323af76ccd5e6649a85872b8757bdc7b83c4578f99269ec96d144038", + "policies/pol/sample_nsxv.pol": "d8b60961724926b035b312a338ca9f852d42ca4378c63a6bafd2850edb2c7793", + "policies/pol/sample_openconfig.pol": "f8a8b909b3ac7dd4bdeb998fa70c9032c22766c822bd9c83152bcbd36d97db35", + "policies/pol/sample_packetfilter.pol": "6e6fb1bd78f022c817097200f7d52634c29239f96045916bffa0a8285ad4ae11", + "policies/pol/sample_paloalto.pol": "d2a13c1e8c6c6720d332fb637843779351aadcaaa87206d99125aa30af7e1537", + "policies/pol/sample_sonic.pol": "a51a0cc23c41e040be5d6141778f26496f59ef5c3787fd20eb0b6102a7cd366e", + "policies/pol/sample_speedway.pol": "ae581b15454e1da114733aea8de73ffae901f3a8e3f7854434a5100e334e64cc", + "policies/pol/sample_srx.pol": "e81c9f64fa46053d1953a38b4eaeadcee767a99b564b6b7f9db0a67ae55ce73a", + "policies/pol/sample_stateful_multitarget_complex.pol": "b05bdc3a722aa7ec0b8659866ef4d4ced7339168356f1312e699365664fac0b1", + "policies/pol/sample_stateful_multitarget_simple.pol": "bccced6b3736a020b9071a798fa78139767f8ecacb21538bb5a86bcb6956e6d8", + "policies/pol/sample_versa.pol": "6abc769a29ef1e103686629ceb492bca2b2ad75e29abc0f3118b33b6b67e7272" + }, + "baseline_revision": "fcca84f5df6e8262d55ecca20635c166009c2c09" +} diff --git a/native/benchmarks/python-environment.txt b/native/benchmarks/python-environment.txt new file mode 100644 index 00000000..8b29157d --- /dev/null +++ b/native/benchmarks/python-environment.txt @@ -0,0 +1,9 @@ +absl-py==2.5.1 +ply==3.11 +PyYAML==6.0.3 +six==1.17.0 +typing_extensions==4.16.0 +mock==5.2.0 +pytest==9.1.1 +maturin==1.15.0 +flake8==7.4.1 diff --git a/native/benchmarks/render-optimized.json b/native/benchmarks/render-optimized.json new file mode 100644 index 00000000..9a959815 --- /dev/null +++ b/native/benchmarks/render-optimized.json @@ -0,0 +1,557 @@ +{ + "revision": "14505d94c9fdff7c936a46bb16c3d73442e5da1a", + "dirty": false, + "python": "3.14.4", + "platform": "macOS-15.6.1-arm64-arm-64bit-Mach-O", + "optimize": true, + "cli": false, + "hash_seed": "0", + "workers": 1, + "runs": [ + { + "seconds": 5.724550958024338, + "sha256": "1a23508bab19d03187d87a37550b0b4849a720ebc194f4127cce70a29fa5f95a", + "files": 49, + "writes": 50, + "workload": "all-samples", + "backend": "python", + "round": -1 + }, + { + "seconds": 1.344236083998112, + "sha256": "1a23508bab19d03187d87a37550b0b4849a720ebc194f4127cce70a29fa5f95a", + "files": 49, + "writes": 50, + "workload": "all-samples", + "backend": "rust", + "round": -1 + }, + { + "seconds": 1.6430715000024065, + "sha256": "1a23508bab19d03187d87a37550b0b4849a720ebc194f4127cce70a29fa5f95a", + "files": 49, + "writes": 50, + "workload": "all-samples", + "backend": "python-integers", + "round": -1 + }, + { + "seconds": 5.825220625003567, + "sha256": "1a23508bab19d03187d87a37550b0b4849a720ebc194f4127cce70a29fa5f95a", + "files": 49, + "writes": 50, + "workload": "all-samples", + "backend": "python", + "round": 0 + }, + { + "seconds": 1.341341833001934, + "sha256": "1a23508bab19d03187d87a37550b0b4849a720ebc194f4127cce70a29fa5f95a", + "files": 49, + "writes": 50, + "workload": "all-samples", + "backend": "rust", + "round": 0 + }, + { + "seconds": 1.6279674579855055, + "sha256": "1a23508bab19d03187d87a37550b0b4849a720ebc194f4127cce70a29fa5f95a", + "files": 49, + "writes": 50, + "workload": "all-samples", + "backend": "python-integers", + "round": 0 + }, + { + "seconds": 1.3409354169853032, + "sha256": "1a23508bab19d03187d87a37550b0b4849a720ebc194f4127cce70a29fa5f95a", + "files": 49, + "writes": 50, + "workload": "all-samples", + "backend": "rust", + "round": 1 + }, + { + "seconds": 1.634163584007183, + "sha256": "1a23508bab19d03187d87a37550b0b4849a720ebc194f4127cce70a29fa5f95a", + "files": 49, + "writes": 50, + "workload": "all-samples", + "backend": "python-integers", + "round": 1 + }, + { + "seconds": 5.713826041988796, + "sha256": "1a23508bab19d03187d87a37550b0b4849a720ebc194f4127cce70a29fa5f95a", + "files": 49, + "writes": 50, + "workload": "all-samples", + "backend": "python", + "round": 1 + }, + { + "seconds": 1.646377458004281, + "sha256": "1a23508bab19d03187d87a37550b0b4849a720ebc194f4127cce70a29fa5f95a", + "files": 49, + "writes": 50, + "workload": "all-samples", + "backend": "python-integers", + "round": 2 + }, + { + "seconds": 5.753897999995388, + "sha256": "1a23508bab19d03187d87a37550b0b4849a720ebc194f4127cce70a29fa5f95a", + "files": 49, + "writes": 50, + "workload": "all-samples", + "backend": "python", + "round": 2 + }, + { + "seconds": 1.3426247500174213, + "sha256": "1a23508bab19d03187d87a37550b0b4849a720ebc194f4127cce70a29fa5f95a", + "files": 49, + "writes": 50, + "workload": "all-samples", + "backend": "rust", + "round": 2 + }, + { + "seconds": 5.752011375006987, + "sha256": "1a23508bab19d03187d87a37550b0b4849a720ebc194f4127cce70a29fa5f95a", + "files": 49, + "writes": 50, + "workload": "all-samples", + "backend": "python", + "round": 3 + }, + { + "seconds": 1.3647291249944828, + "sha256": "1a23508bab19d03187d87a37550b0b4849a720ebc194f4127cce70a29fa5f95a", + "files": 49, + "writes": 50, + "workload": "all-samples", + "backend": "rust", + "round": 3 + }, + { + "seconds": 1.6350330840214156, + "sha256": "1a23508bab19d03187d87a37550b0b4849a720ebc194f4127cce70a29fa5f95a", + "files": 49, + "writes": 50, + "workload": "all-samples", + "backend": "python-integers", + "round": 3 + }, + { + "seconds": 1.3244826250011101, + "sha256": "1a23508bab19d03187d87a37550b0b4849a720ebc194f4127cce70a29fa5f95a", + "files": 49, + "writes": 50, + "workload": "all-samples", + "backend": "rust", + "round": 4 + }, + { + "seconds": 1.6578428329958115, + "sha256": "1a23508bab19d03187d87a37550b0b4849a720ebc194f4127cce70a29fa5f95a", + "files": 49, + "writes": 50, + "workload": "all-samples", + "backend": "python-integers", + "round": 4 + }, + { + "seconds": 5.751644125004532, + "sha256": "1a23508bab19d03187d87a37550b0b4849a720ebc194f4127cce70a29fa5f95a", + "files": 49, + "writes": 50, + "workload": "all-samples", + "backend": "python", + "round": 4 + }, + { + "seconds": 4.665097041986883, + "sha256": "c52317bcbdabaab959d4b62afd99247bf8c0c175320af0fbf9b7a8cdc3b80c0c", + "files": 2, + "writes": 2, + "workload": "srx-large", + "backend": "python", + "round": -1 + }, + { + "seconds": 0.2506572080019396, + "sha256": "c52317bcbdabaab959d4b62afd99247bf8c0c175320af0fbf9b7a8cdc3b80c0c", + "files": 2, + "writes": 2, + "workload": "srx-large", + "backend": "rust", + "round": -1 + }, + { + "seconds": 0.5434397910139523, + "sha256": "c52317bcbdabaab959d4b62afd99247bf8c0c175320af0fbf9b7a8cdc3b80c0c", + "files": 2, + "writes": 2, + "workload": "srx-large", + "backend": "python-integers", + "round": -1 + }, + { + "seconds": 4.671460000012303, + "sha256": "c52317bcbdabaab959d4b62afd99247bf8c0c175320af0fbf9b7a8cdc3b80c0c", + "files": 2, + "writes": 2, + "workload": "srx-large", + "backend": "python", + "round": 0 + }, + { + "seconds": 0.25402091699652374, + "sha256": "c52317bcbdabaab959d4b62afd99247bf8c0c175320af0fbf9b7a8cdc3b80c0c", + "files": 2, + "writes": 2, + "workload": "srx-large", + "backend": "rust", + "round": 0 + }, + { + "seconds": 0.5567012499959674, + "sha256": "c52317bcbdabaab959d4b62afd99247bf8c0c175320af0fbf9b7a8cdc3b80c0c", + "files": 2, + "writes": 2, + "workload": "srx-large", + "backend": "python-integers", + "round": 0 + }, + { + "seconds": 0.25203291699290276, + "sha256": "c52317bcbdabaab959d4b62afd99247bf8c0c175320af0fbf9b7a8cdc3b80c0c", + "files": 2, + "writes": 2, + "workload": "srx-large", + "backend": "rust", + "round": 1 + }, + { + "seconds": 0.5515678330266383, + "sha256": "c52317bcbdabaab959d4b62afd99247bf8c0c175320af0fbf9b7a8cdc3b80c0c", + "files": 2, + "writes": 2, + "workload": "srx-large", + "backend": "python-integers", + "round": 1 + }, + { + "seconds": 4.714428499981295, + "sha256": "c52317bcbdabaab959d4b62afd99247bf8c0c175320af0fbf9b7a8cdc3b80c0c", + "files": 2, + "writes": 2, + "workload": "srx-large", + "backend": "python", + "round": 1 + }, + { + "seconds": 0.6121616660093423, + "sha256": "c52317bcbdabaab959d4b62afd99247bf8c0c175320af0fbf9b7a8cdc3b80c0c", + "files": 2, + "writes": 2, + "workload": "srx-large", + "backend": "python-integers", + "round": 2 + }, + { + "seconds": 4.822139124997193, + "sha256": "c52317bcbdabaab959d4b62afd99247bf8c0c175320af0fbf9b7a8cdc3b80c0c", + "files": 2, + "writes": 2, + "workload": "srx-large", + "backend": "python", + "round": 2 + }, + { + "seconds": 0.24796229100320488, + "sha256": "c52317bcbdabaab959d4b62afd99247bf8c0c175320af0fbf9b7a8cdc3b80c0c", + "files": 2, + "writes": 2, + "workload": "srx-large", + "backend": "rust", + "round": 2 + }, + { + "seconds": 4.659038832993247, + "sha256": "c52317bcbdabaab959d4b62afd99247bf8c0c175320af0fbf9b7a8cdc3b80c0c", + "files": 2, + "writes": 2, + "workload": "srx-large", + "backend": "python", + "round": 3 + }, + { + "seconds": 0.25223337500938214, + "sha256": "c52317bcbdabaab959d4b62afd99247bf8c0c175320af0fbf9b7a8cdc3b80c0c", + "files": 2, + "writes": 2, + "workload": "srx-large", + "backend": "rust", + "round": 3 + }, + { + "seconds": 0.5547111250052694, + "sha256": "c52317bcbdabaab959d4b62afd99247bf8c0c175320af0fbf9b7a8cdc3b80c0c", + "files": 2, + "writes": 2, + "workload": "srx-large", + "backend": "python-integers", + "round": 3 + }, + { + "seconds": 0.25265862501692027, + "sha256": "c52317bcbdabaab959d4b62afd99247bf8c0c175320af0fbf9b7a8cdc3b80c0c", + "files": 2, + "writes": 2, + "workload": "srx-large", + "backend": "rust", + "round": 4 + }, + { + "seconds": 0.5525469169951975, + "sha256": "c52317bcbdabaab959d4b62afd99247bf8c0c175320af0fbf9b7a8cdc3b80c0c", + "files": 2, + "writes": 2, + "workload": "srx-large", + "backend": "python-integers", + "round": 4 + }, + { + "seconds": 4.658293500018772, + "sha256": "c52317bcbdabaab959d4b62afd99247bf8c0c175320af0fbf9b7a8cdc3b80c0c", + "files": 2, + "writes": 2, + "workload": "srx-large", + "backend": "python", + "round": 4 + }, + { + "seconds": 0.004604958987329155, + "sha256": "2e9c8d41a331b9932d0891fd7c0328b17da32573c0ad174d25d682388daa0871", + "files": 1, + "writes": 1, + "workload": "srx-small", + "backend": "python", + "round": -1 + }, + { + "seconds": 0.0024384580028709024, + "sha256": "2e9c8d41a331b9932d0891fd7c0328b17da32573c0ad174d25d682388daa0871", + "files": 1, + "writes": 1, + "workload": "srx-small", + "backend": "rust", + "round": -1 + }, + { + "seconds": 0.002514416992198676, + "sha256": "2e9c8d41a331b9932d0891fd7c0328b17da32573c0ad174d25d682388daa0871", + "files": 1, + "writes": 1, + "workload": "srx-small", + "backend": "python-integers", + "round": -1 + }, + { + "seconds": 0.0025867079966701567, + "sha256": "2e9c8d41a331b9932d0891fd7c0328b17da32573c0ad174d25d682388daa0871", + "files": 1, + "writes": 1, + "workload": "srx-small", + "backend": "python", + "round": 0 + }, + { + "seconds": 0.0023496249923482537, + "sha256": "2e9c8d41a331b9932d0891fd7c0328b17da32573c0ad174d25d682388daa0871", + "files": 1, + "writes": 1, + "workload": "srx-small", + "backend": "rust", + "round": 0 + }, + { + "seconds": 0.002285624999785796, + "sha256": "2e9c8d41a331b9932d0891fd7c0328b17da32573c0ad174d25d682388daa0871", + "files": 1, + "writes": 1, + "workload": "srx-small", + "backend": "python-integers", + "round": 0 + }, + { + "seconds": 0.002291540993610397, + "sha256": "2e9c8d41a331b9932d0891fd7c0328b17da32573c0ad174d25d682388daa0871", + "files": 1, + "writes": 1, + "workload": "srx-small", + "backend": "rust", + "round": 1 + }, + { + "seconds": 0.0022726249881088734, + "sha256": "2e9c8d41a331b9932d0891fd7c0328b17da32573c0ad174d25d682388daa0871", + "files": 1, + "writes": 1, + "workload": "srx-small", + "backend": "python-integers", + "round": 1 + }, + { + "seconds": 0.0022867499792482704, + "sha256": "2e9c8d41a331b9932d0891fd7c0328b17da32573c0ad174d25d682388daa0871", + "files": 1, + "writes": 1, + "workload": "srx-small", + "backend": "python", + "round": 1 + }, + { + "seconds": 0.0024984999909065664, + "sha256": "2e9c8d41a331b9932d0891fd7c0328b17da32573c0ad174d25d682388daa0871", + "files": 1, + "writes": 1, + "workload": "srx-small", + "backend": "python-integers", + "round": 2 + }, + { + "seconds": 0.0023624160094186664, + "sha256": "2e9c8d41a331b9932d0891fd7c0328b17da32573c0ad174d25d682388daa0871", + "files": 1, + "writes": 1, + "workload": "srx-small", + "backend": "python", + "round": 2 + }, + { + "seconds": 0.0022875419817864895, + "sha256": "2e9c8d41a331b9932d0891fd7c0328b17da32573c0ad174d25d682388daa0871", + "files": 1, + "writes": 1, + "workload": "srx-small", + "backend": "rust", + "round": 2 + }, + { + "seconds": 0.0022729999909643084, + "sha256": "2e9c8d41a331b9932d0891fd7c0328b17da32573c0ad174d25d682388daa0871", + "files": 1, + "writes": 1, + "workload": "srx-small", + "backend": "python", + "round": 3 + }, + { + "seconds": 0.0022790830116719007, + "sha256": "2e9c8d41a331b9932d0891fd7c0328b17da32573c0ad174d25d682388daa0871", + "files": 1, + "writes": 1, + "workload": "srx-small", + "backend": "rust", + "round": 3 + }, + { + "seconds": 0.0022659999958705157, + "sha256": "2e9c8d41a331b9932d0891fd7c0328b17da32573c0ad174d25d682388daa0871", + "files": 1, + "writes": 1, + "workload": "srx-small", + "backend": "python-integers", + "round": 3 + }, + { + "seconds": 0.0022893330024089664, + "sha256": "2e9c8d41a331b9932d0891fd7c0328b17da32573c0ad174d25d682388daa0871", + "files": 1, + "writes": 1, + "workload": "srx-small", + "backend": "rust", + "round": 4 + }, + { + "seconds": 0.002281624998431653, + "sha256": "2e9c8d41a331b9932d0891fd7c0328b17da32573c0ad174d25d682388daa0871", + "files": 1, + "writes": 1, + "workload": "srx-small", + "backend": "python-integers", + "round": 4 + }, + { + "seconds": 0.0022606250131502748, + "sha256": "2e9c8d41a331b9932d0891fd7c0328b17da32573c0ad174d25d682388daa0871", + "files": 1, + "writes": 1, + "workload": "srx-small", + "backend": "python", + "round": 4 + } + ], + "summary": { + "all-samples": { + "python": 5.752011375006987, + "rust": 1.341341833001934, + "python-integers": 1.6350330840214156 + }, + "srx-large": { + "python": 4.671460000012303, + "rust": 0.25223337500938214, + "python-integers": 0.5547111250052694 + }, + "srx-small": { + "python": 0.0022867499792482704, + "rust": 0.0022893330024089664, + "python-integers": 0.002281624998431653 + } + }, + "inputs": { + "def/LARGE_NETWORK.net": "e10c3238c28f61667fd650b0d497c959d3173aba3e44643ebaded1cbfc6846a5", + "def/NETWORK.net": "2f5e87e975a5daf821bf89e8f55a78ab450f31023f8c79abb3c76462f1361f5c", + "def/SERVICES.svc": "3c46ae6c6f6eea634929923d70d06510e1b4547364d72e47330a2b3bbbc82251", + "policies/includes/untrusted-networks-blocking.inc": "cb88a2b91e6bfe6c47f1dd7de046e6f3bee2502e1270ecc5fbd7db26eabea7c7", + "policies/pol/sample_arista_tp.pol": "d2b3062f9795ece58fade5fb53c78715444967459c52ccd399e8a0fdb6c79e1b", + "policies/pol/sample_cisco_lab.pol": "752b81130347b65924471a171e6030bdced1578f400dd3eef8bb5b328973e0c8", + "policies/pol/sample_cisconx.pol": "ce78e9ca1459f264f67945980e2a4cb51acfba1e47a419298a3507ec79539cb4", + "policies/pol/sample_cloudarmor.pol": "0081a9e26644f1738e9357a63d1f71a9677d4cc1c694f1513cb9d7dea51e53e4", + "policies/pol/sample_fortigate.pol": "af0e7e9aa82a9178984cacb92febdba860d5bca3ca2b5dedc7b9c3a754a4584c", + "policies/pol/sample_fortigate_localin.pol": "7e1028c3d431ae044c75f82cf2d5263ca2d7963da6dd009bf9624056f8de7900", + "policies/pol/sample_fortigate_localin_ipv4.pol": "8b2374f11dd2d2e8b3e97ab7002efc275cfcd4590b785f7165c94b93832836da", + "policies/pol/sample_fortigate_localin_ipv6.pol": "f97621a4765319887d37a4d5a6e6afcfad5d39a9c0d88f7f131ad4d35dc56a7c", + "policies/pol/sample_gce.pol": "11e55dbe62eaf7fe533d8e8623a1cf75ebe2307c42a87e37ec9e23e6e4379878", + "policies/pol/sample_gce_vpc_tf.pol": "b60f73740a9d18f363789a0579b87192fbbfc0453ea5b3532982189652e6a055", + "policies/pol/sample_gcp_hf.pol": "144f0decdcf24b410e00d9ba345304624f891d2bcebc9b63566aebb022debe7f", + "policies/pol/sample_inet6_gce.pol": "9f4aa6c0a99440b45f0d8e4d4c80c308d92f4d3fab20919084cfb2632bc3a4a5", + "policies/pol/sample_inet6_gcp_hf.pol": "e3e87c6013352997c33b77b07d374362352e30eb6feafe9b430b0dc16eae38d0", + "policies/pol/sample_ipset.pol": "6a7d7838a12896d1c323c09a2ebbee5af2d291a2b9b0a79ee44f174086779fa7", + "policies/pol/sample_juniper_loopback.pol": "699930d34bf9404a8dcbe8fbdb7a4e7a52fb58f32d3952e732ccde9265dd6bff", + "policies/pol/sample_juniperevo_loopback.pol": "a1e29dbc54b2ec34989c1c2687067e0df2876f9e4bb81e4a99c21fbe26d83e4e", + "policies/pol/sample_k8s.pol": "7c0cefb881cc7ff8c2e79e32172a2a3c4d4b0e0b88fd70201aaa32588459da62", + "policies/pol/sample_mixed_gce.pol": "80469d2883d913f50731ebf203531972c3b4936136303bfe3f3a18dbe14d6679", + "policies/pol/sample_mixed_gcp_hf.pol": "1cdb52ba11aaeb1c853be4232e7af82b8cf9e25a6ef0d1969bfdd3b7cf03d146", + "policies/pol/sample_msmpc.pol": "bc89d9d0c7386d6529f15e5774b81a0b26164d54f30ee749a8806efd79e3f87a", + "policies/pol/sample_multitarget.pol": "19a225dd00332ec6a7a151efa0b2afaf2ce9e430be7299df11254f0b1084b226", + "policies/pol/sample_nftables-dev.pol": "4888ed8def4051fd8faa338445cafebce05073912f3a87741adf9f435badc77a", + "policies/pol/sample_nftables-mixed-icmp.pol": "ef5454f17b73acdf33f7d25db631a6272724c985e10451ed81aa2834162958c5", + "policies/pol/sample_nftables-mixed-multiple-headers-combo.pol": "3b86dd6ca880786eea4824f28cbc34b6897a08a95c62be9976ae2f017f359937", + "policies/pol/sample_nftables.pol": "2725dc17768dcdb844bf7c1b14bc839231f11b0854664dc6bcccb4daf8238607", + "policies/pol/sample_nsxt.pol": "bdb439aa323af76ccd5e6649a85872b8757bdc7b83c4578f99269ec96d144038", + "policies/pol/sample_nsxv.pol": "d8b60961724926b035b312a338ca9f852d42ca4378c63a6bafd2850edb2c7793", + "policies/pol/sample_openconfig.pol": "f8a8b909b3ac7dd4bdeb998fa70c9032c22766c822bd9c83152bcbd36d97db35", + "policies/pol/sample_packetfilter.pol": "6e6fb1bd78f022c817097200f7d52634c29239f96045916bffa0a8285ad4ae11", + "policies/pol/sample_paloalto.pol": "d2a13c1e8c6c6720d332fb637843779351aadcaaa87206d99125aa30af7e1537", + "policies/pol/sample_sonic.pol": "a51a0cc23c41e040be5d6141778f26496f59ef5c3787fd20eb0b6102a7cd366e", + "policies/pol/sample_speedway.pol": "ae581b15454e1da114733aea8de73ffae901f3a8e3f7854434a5100e334e64cc", + "policies/pol/sample_srx.pol": "e81c9f64fa46053d1953a38b4eaeadcee767a99b564b6b7f9db0a67ae55ce73a", + "policies/pol/sample_stateful_multitarget_complex.pol": "b05bdc3a722aa7ec0b8659866ef4d4ced7339168356f1312e699365664fac0b1", + "policies/pol/sample_stateful_multitarget_simple.pol": "bccced6b3736a020b9071a798fa78139767f8ecacb21538bb5a86bcb6956e6d8", + "policies/pol/sample_versa.pol": "6abc769a29ef1e103686629ceb492bca2b2ad75e29abc0f3118b33b6b67e7272" + } +} From dd2e82019450614a43b9d5ad1702641c415aaf26 Mon Sep 17 00:00:00 2001 From: Dan Trapp Date: Sat, 10 Oct 2026 14:30:32 -0700 Subject: [PATCH 6/6] Pin native workflow actions to immutable revisions --- .github/workflows/native.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/native.yml b/.github/workflows/native.yml index 195afd18..b826f490 100644 --- a/.github/workflows/native.yml +++ b/.github/workflows/native.yml @@ -44,8 +44,8 @@ jobs: tests: tests runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 with: python-version: ${{ matrix.python }} - run: rustup toolchain install stable --profile minimal --component rustfmt --component clippy