From fe6122f43eaa714341371a0080446905ac46add7 Mon Sep 17 00:00:00 2001 From: Lukas Hering Date: Thu, 18 Jun 2026 22:10:23 -0400 Subject: [PATCH 01/16] add package scaffolding --- .../opentelemetry-profiles-context/LICENSE | 201 +++++++++ .../opentelemetry-profiles-context/README.rst | 15 + .../pyproject.toml | 43 ++ .../rust/Cargo.lock | 410 ++++++++++++++++++ .../rust/Cargo.toml | 18 + .../rust/src/lib.rs | 16 + .../profiles/context/__init__.py | 2 + .../profiles/context/_rs/__init__.pyi | 4 + .../opentelemetry/profiles/context/py.typed | 0 .../opentelemetry/profiles/context/version.py | 4 + .../test-requirements.in | 5 + .../test-requirements.txt | 26 ++ .../tests/__init__.py | 0 .../tests/test_profiles_context.py | 8 + pyproject.toml | 6 +- tox.ini | 10 + uv.lock | 8 + 17 files changed, 775 insertions(+), 1 deletion(-) create mode 100644 profiles/opentelemetry-profiles-context/LICENSE create mode 100644 profiles/opentelemetry-profiles-context/README.rst create mode 100644 profiles/opentelemetry-profiles-context/pyproject.toml create mode 100644 profiles/opentelemetry-profiles-context/rust/Cargo.lock create mode 100644 profiles/opentelemetry-profiles-context/rust/Cargo.toml create mode 100644 profiles/opentelemetry-profiles-context/rust/src/lib.rs create mode 100644 profiles/opentelemetry-profiles-context/src/opentelemetry/profiles/context/__init__.py create mode 100644 profiles/opentelemetry-profiles-context/src/opentelemetry/profiles/context/_rs/__init__.pyi create mode 100644 profiles/opentelemetry-profiles-context/src/opentelemetry/profiles/context/py.typed create mode 100644 profiles/opentelemetry-profiles-context/src/opentelemetry/profiles/context/version.py create mode 100644 profiles/opentelemetry-profiles-context/test-requirements.in create mode 100644 profiles/opentelemetry-profiles-context/test-requirements.txt create mode 100644 profiles/opentelemetry-profiles-context/tests/__init__.py create mode 100644 profiles/opentelemetry-profiles-context/tests/test_profiles_context.py diff --git a/profiles/opentelemetry-profiles-context/LICENSE b/profiles/opentelemetry-profiles-context/LICENSE new file mode 100644 index 00000000000..261eeb9e9f8 --- /dev/null +++ b/profiles/opentelemetry-profiles-context/LICENSE @@ -0,0 +1,201 @@ + 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/profiles/opentelemetry-profiles-context/README.rst b/profiles/opentelemetry-profiles-context/README.rst new file mode 100644 index 00000000000..4fe8837deb4 --- /dev/null +++ b/profiles/opentelemetry-profiles-context/README.rst @@ -0,0 +1,15 @@ +OpenTelemetry Profiles Context +====================================== + +|pypi| + +.. |pypi| image:: https://badge.fury.io/py/opentelemetry-profiles-context.svg + :target: https://pypi.org/project/opentelemetry-profiles-context/ + +TODO: Update README + +References +---------- + +* `OpenTelemetry `_ +* `OpenTelemetry Protocol Specification `_ diff --git a/profiles/opentelemetry-profiles-context/pyproject.toml b/profiles/opentelemetry-profiles-context/pyproject.toml new file mode 100644 index 00000000000..f964a117d46 --- /dev/null +++ b/profiles/opentelemetry-profiles-context/pyproject.toml @@ -0,0 +1,43 @@ +[build-system] +requires = ["maturin>=1.0,<2.0"] +build-backend = "maturin" + +[project] +name = "opentelemetry-profiles-context" +version = "0.64b0.dev" +description = "OpenTelemetry Profiles Context" +readme = "README.rst" +license = "Apache-2.0" +requires-python = ">=3.10" +authors = [ + { name = "OpenTelemetry Authors", email = "cncf-opentelemetry-contributors@lists.cncf.io" }, +] +classifiers = [ + "Development Status :: 4 - Beta", + "Framework :: OpenTelemetry", + "Framework :: OpenTelemetry :: Profiles", + "Intended Audience :: Developers", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", +] +dependencies = [] + +[project.urls] +Homepage = "https://github.com/open-telemetry/opentelemetry-python/tree/main/profiles/opentelemetry-profiles-context" +Repository = "https://github.com/open-telemetry/opentelemetry-python" + +[tool.maturin] +python-source = "src" +module-name = "opentelemetry.profiles.context._rs" + +[tool.uv] +cache-keys = [ + { file = "rust/Cargo.toml" }, + { file = "rust/Cargo.lock" }, + { file = "rust/src/**" }, +] diff --git a/profiles/opentelemetry-profiles-context/rust/Cargo.lock b/profiles/opentelemetry-profiles-context/rust/Cargo.lock new file mode 100644 index 00000000000..27ad7d26c0d --- /dev/null +++ b/profiles/opentelemetry-profiles-context/rust/Cargo.lock @@ -0,0 +1,410 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "bitflags" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" + +[[package]] +name = "bytes" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "either" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "fastrand" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "libc", + "r-efi", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "log" +version = "0.4.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a" + +[[package]] +name = "memchr" +version = "2.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" + +[[package]] +name = "multimap" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "opentelemetry-profiles-context" +version = "0.1.0" +dependencies = [ + "prost", + "prost-build", + "pyo3", +] + +[[package]] +name = "petgraph" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prost" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-build" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be769465445e8c1474e9c5dac2018218498557af32d9ed057325ec9a41ae81bf" +dependencies = [ + "heck", + "itertools", + "log", + "multimap", + "once_cell", + "petgraph", + "prettyplease", + "prost", + "prost-types", + "regex", + "syn", + "tempfile", +] + +[[package]] +name = "prost-derive" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "prost-types" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52c2c1bf36ddb1a1c396b3601a3cec27c2462e45f07c386894ec3ccf5332bd16" +dependencies = [ + "prost", +] + +[[package]] +name = "pyo3" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91fd8e38a3b50ed1167fb981cd6fd60147e091784c427b8f7183a7ee32c31c12" +dependencies = [ + "libc", + "once_cell", + "portable-atomic", + "pyo3-build-config", + "pyo3-ffi", + "pyo3-macros", +] + +[[package]] +name = "pyo3-build-config" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e368e7ddfdeb98c9bca7f8383be1648fd84ab466bf2bc015e94008db6d35611e" +dependencies = [ + "target-lexicon", +] + +[[package]] +name = "pyo3-ffi" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f29e10af80b1f7ccaf7f69eace800a03ecd13e883acfacc1e5d0988605f651e" +dependencies = [ + "libc", + "pyo3-build-config", +] + +[[package]] +name = "pyo3-macros" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df6e520eff47c45997d2fc7dd8214b25dd1310918bbb2642156ef66a67f29813" +dependencies = [ + "proc-macro2", + "pyo3-macros-backend", + "quote", + "syn", +] + +[[package]] +name = "pyo3-macros-backend" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4cdc218d835738f81c2338f822078af45b4afdf8b2e33cbb5916f108b813acb" +dependencies = [ + "heck", + "proc-macro2", + "pyo3-build-config", + "quote", + "syn", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "regex" +version = "1.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "syn" +version = "2.0.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" +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 = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom", + "once_cell", + "rustix", + "windows-sys", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] diff --git a/profiles/opentelemetry-profiles-context/rust/Cargo.toml b/profiles/opentelemetry-profiles-context/rust/Cargo.toml new file mode 100644 index 00000000000..91c8d17c969 --- /dev/null +++ b/profiles/opentelemetry-profiles-context/rust/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "opentelemetry-profiles-context" +version = "0.1.0" +edition = "2024" + +[lib] +name = "opentelemetry_profiles_context" +crate-type = ["cdylib"] + +[dependencies] +prost = "0.13" + +[dependencies.pyo3] +version = "0.28.2" +features = ["abi3-py39"] + +[build-dependencies] +prost-build = "0.13" diff --git a/profiles/opentelemetry-profiles-context/rust/src/lib.rs b/profiles/opentelemetry-profiles-context/rust/src/lib.rs new file mode 100644 index 00000000000..439607b31a0 --- /dev/null +++ b/profiles/opentelemetry-profiles-context/rust/src/lib.rs @@ -0,0 +1,16 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +use pyo3::prelude::*; + +#[pyfunction] +fn sum_as_string(a: usize, b: usize) -> PyResult { + Ok((a + b).to_string()) +} + +#[pymodule] +#[pyo3(name = "_rs")] +fn init(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> { + m.add_wrapped(wrap_pyfunction!(sum_as_string))?; + Ok(()) +} diff --git a/profiles/opentelemetry-profiles-context/src/opentelemetry/profiles/context/__init__.py b/profiles/opentelemetry-profiles-context/src/opentelemetry/profiles/context/__init__.py new file mode 100644 index 00000000000..e57cf4aba95 --- /dev/null +++ b/profiles/opentelemetry-profiles-context/src/opentelemetry/profiles/context/__init__.py @@ -0,0 +1,2 @@ +# Copyright The OpenTelemetry Authors +# SPDX-License-Identifier: Apache-2.0 diff --git a/profiles/opentelemetry-profiles-context/src/opentelemetry/profiles/context/_rs/__init__.pyi b/profiles/opentelemetry-profiles-context/src/opentelemetry/profiles/context/_rs/__init__.pyi new file mode 100644 index 00000000000..8d680d717d9 --- /dev/null +++ b/profiles/opentelemetry-profiles-context/src/opentelemetry/profiles/context/_rs/__init__.pyi @@ -0,0 +1,4 @@ +# Copyright The OpenTelemetry Authors +# SPDX-License-Identifier: Apache-2.0 + +def sum_as_string(a: int, b: int) -> str: ... diff --git a/profiles/opentelemetry-profiles-context/src/opentelemetry/profiles/context/py.typed b/profiles/opentelemetry-profiles-context/src/opentelemetry/profiles/context/py.typed new file mode 100644 index 00000000000..e69de29bb2d diff --git a/profiles/opentelemetry-profiles-context/src/opentelemetry/profiles/context/version.py b/profiles/opentelemetry-profiles-context/src/opentelemetry/profiles/context/version.py new file mode 100644 index 00000000000..13e069be44f --- /dev/null +++ b/profiles/opentelemetry-profiles-context/src/opentelemetry/profiles/context/version.py @@ -0,0 +1,4 @@ +# Copyright The OpenTelemetry Authors +# SPDX-License-Identifier: Apache-2.0 + +__version__ = "0.64b0.dev" diff --git a/profiles/opentelemetry-profiles-context/test-requirements.in b/profiles/opentelemetry-profiles-context/test-requirements.in new file mode 100644 index 00000000000..fc282afaa4d --- /dev/null +++ b/profiles/opentelemetry-profiles-context/test-requirements.in @@ -0,0 +1,5 @@ +iniconfig==2.3.0 +packaging==26.2 +pluggy==1.6.0 +pytest==7.4.4 +-e profiles/opentelemetry-profiles-context diff --git a/profiles/opentelemetry-profiles-context/test-requirements.txt b/profiles/opentelemetry-profiles-context/test-requirements.txt new file mode 100644 index 00000000000..f0f697e9594 --- /dev/null +++ b/profiles/opentelemetry-profiles-context/test-requirements.txt @@ -0,0 +1,26 @@ +# This file was autogenerated by uv via the following command: +# uv pip compile --python 3.10 --universal --resolution highest profiles/opentelemetry-profiles-context/test-requirements.in -o profiles/opentelemetry-profiles-context/test-requirements.txt +-e profiles/opentelemetry-profiles-context + # via -r profiles/opentelemetry-profiles-context/test-requirements.in +colorama==0.4.6 ; sys_platform == 'win32' + # via pytest +exceptiongroup==1.3.1 ; python_full_version < '3.11' + # via pytest +iniconfig==2.3.0 + # via + # -r profiles/opentelemetry-profiles-context/test-requirements.in + # pytest +packaging==26.2 + # via + # -r profiles/opentelemetry-profiles-context/test-requirements.in + # pytest +pluggy==1.6.0 + # via + # -r profiles/opentelemetry-profiles-context/test-requirements.in + # pytest +pytest==7.4.4 + # via -r profiles/opentelemetry-profiles-context/test-requirements.in +tomli==2.4.1 ; python_full_version < '3.11' + # via pytest +typing-extensions==4.15.0 ; python_full_version < '3.11' + # via exceptiongroup diff --git a/profiles/opentelemetry-profiles-context/tests/__init__.py b/profiles/opentelemetry-profiles-context/tests/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/profiles/opentelemetry-profiles-context/tests/test_profiles_context.py b/profiles/opentelemetry-profiles-context/tests/test_profiles_context.py new file mode 100644 index 00000000000..89865073ef4 --- /dev/null +++ b/profiles/opentelemetry-profiles-context/tests/test_profiles_context.py @@ -0,0 +1,8 @@ +# Copyright The OpenTelemetry Authors +# SPDX-License-Identifier: Apache-2.0 + +from opentelemetry.profiles.context._rs import sum_as_string + + +def test_sum_as_string(): + assert sum_as_string(1, 2) == "3" diff --git a/pyproject.toml b/pyproject.toml index 95c826e01b6..dfe7b153d31 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,6 +20,7 @@ dependencies = [ "opentelemetry-propagator-jaeger", "opentelemetry-propagator-b3", "opentelemetry-codegen-json", + "opentelemetry-profiles-context", ] # https://docs.astral.sh/uv/reference/settings/ @@ -45,6 +46,7 @@ opentelemetry-exporter-prometheus = {workspace = true } opentelemetry-propagator-jaeger = { workspace = true } opentelemetry-propagator-b3 = { workspace = true } opentelemetry-codegen-json = { workspace = true } +opentelemetry-profiles-context = { workspace = true } [tool.uv.workspace] members = [ @@ -56,6 +58,7 @@ members = [ "exporter/*", "propagator/*", "codegen/*", + "profiles/*", "tests/opentelemetry-test-utils", ] @@ -128,7 +131,8 @@ include = [ "exporter/opentelemetry-exporter-otlp-proto-grpc", "exporter/opentelemetry-exporter-otlp-proto-http", "exporter/opentelemetry-exporter-otlp-json-common", - "codegen/opentelemetry-codegen-json" + "codegen/opentelemetry-codegen-json", + "profiles/opentelemetry-profiles-context" ] exclude = [ diff --git a/tox.ini b/tox.ini index 5476de0cde1..eee91a6868d 100644 --- a/tox.ini +++ b/tox.ini @@ -96,6 +96,10 @@ envlist = pypy3-test-opentelemetry-exporter-zipkin-json lint-opentelemetry-exporter-zipkin-json + py3{10,11,12,13,14,14t}-test-opentelemetry-profiles-context + pypy3-test-opentelemetry-profiles-context + lint-opentelemetry-profiles-context + py3{10,11,12,13,14,14t}-test-opentelemetry-propagator-b3 pypy3-test-opentelemetry-propagator-b3 lint-opentelemetry-propagator-b3 @@ -176,6 +180,8 @@ deps = exporter-zipkin-json: -r {toxinidir}/exporter/opentelemetry-exporter-zipkin-json/test-requirements.txt + profiles-context: -r {toxinidir}/profiles/opentelemetry-profiles-context/test-requirements.txt + propagator-b3: -r {toxinidir}/propagator/opentelemetry-propagator-b3/test-requirements.txt benchmark-opentelemetry-propagator-b3: -r {toxinidir}/propagator/opentelemetry-propagator-b3/benchmark-requirements.txt @@ -281,6 +287,9 @@ commands = test-opentelemetry-exporter-zipkin-json: pytest {toxinidir}/exporter/opentelemetry-exporter-zipkin-json/tests {posargs} lint-opentelemetry-exporter-zipkin-json: sh -c "cd exporter && pylint --rcfile ../.pylintrc {toxinidir}/exporter/opentelemetry-exporter-zipkin-json" + test-opentelemetry-profiles-context: pytest {toxinidir}/profiles/opentelemetry-profiles-context/tests {posargs} + lint-opentelemetry-profiles-context: sh -c "cd profiles && pylint --rcfile ../.pylintrc {toxinidir}/profiles/opentelemetry-profiles-context" + test-opentelemetry-propagator-b3: pytest {toxinidir}/propagator/opentelemetry-propagator-b3/tests {posargs} lint-opentelemetry-propagator-b3: sh -c "cd propagator && pylint --rcfile ../.pylintrc {toxinidir}/propagator/opentelemetry-propagator-b3" benchmark-opentelemetry-propagator-b3: pytest {toxinidir}/propagator/opentelemetry-propagator-b3/benchmarks --benchmark-json=propagator-b3-benchmark.json {posargs} @@ -430,6 +439,7 @@ deps = -e {toxinidir}/exporter/opentelemetry-exporter-otlp-proto-http -e {toxinidir}/opentelemetry-proto -e {toxinidir}/opentelemetry-proto-json + -e {toxinidir}/opentelemetry-profiles-context -e {toxinidir}/codegen/opentelemetry-codegen-json commands = diff --git a/uv.lock b/uv.lock index e307a066394..27e13cf3c0e 100644 --- a/uv.lock +++ b/uv.lock @@ -20,6 +20,7 @@ members = [ "opentelemetry-exporter-otlp-proto-http", "opentelemetry-exporter-prometheus", "opentelemetry-exporter-zipkin-json", + "opentelemetry-profiles-context", "opentelemetry-propagator-b3", "opentelemetry-propagator-jaeger", "opentelemetry-proto", @@ -1009,6 +1010,11 @@ requires-dist = [ { name = "requests", specifier = "~=2.7" }, ] +[[package]] +name = "opentelemetry-profiles-context" +version = "0.64b0.dev0" +source = { editable = "profiles/opentelemetry-profiles-context" } + [[package]] name = "opentelemetry-propagator-b3" source = { editable = "propagator/opentelemetry-propagator-b3" } @@ -1062,6 +1068,7 @@ dependencies = [ { name = "opentelemetry-exporter-otlp-proto-http" }, { name = "opentelemetry-exporter-prometheus" }, { name = "opentelemetry-exporter-zipkin-json" }, + { name = "opentelemetry-profiles-context" }, { name = "opentelemetry-propagator-b3" }, { name = "opentelemetry-propagator-jaeger" }, { name = "opentelemetry-proto" }, @@ -1092,6 +1099,7 @@ requires-dist = [ { name = "opentelemetry-exporter-otlp-proto-http", editable = "exporter/opentelemetry-exporter-otlp-proto-http" }, { name = "opentelemetry-exporter-prometheus", editable = "exporter/opentelemetry-exporter-prometheus" }, { name = "opentelemetry-exporter-zipkin-json", editable = "exporter/opentelemetry-exporter-zipkin-json" }, + { name = "opentelemetry-profiles-context", editable = "profiles/opentelemetry-profiles-context" }, { name = "opentelemetry-propagator-b3", editable = "propagator/opentelemetry-propagator-b3" }, { name = "opentelemetry-propagator-jaeger", editable = "propagator/opentelemetry-propagator-jaeger" }, { name = "opentelemetry-proto", editable = "opentelemetry-proto" }, From 05a0a20447f1deb52fccf909c17375c4cc989e03 Mon Sep 17 00:00:00 2001 From: Lukas Hering Date: Thu, 18 Jun 2026 23:33:57 -0400 Subject: [PATCH 02/16] add conversion logic --- .../pyproject.toml | 5 +- .../rust/Cargo.lock | 722 +++++++++++++++++- .../rust/Cargo.toml | 2 + .../rust/build.rs | 43 ++ .../rust/src/convert.rs | 92 +++ .../rust/src/lib.rs | 10 +- .../rust/src/proto.rs | 20 + .../profiles/context/_rs/__init__.pyi | 4 +- .../test-requirements.in | 2 + .../test-requirements.txt | 20 +- .../tests/test_profiles_context.py | 13 +- uv.lock | 10 + 12 files changed, 929 insertions(+), 14 deletions(-) create mode 100644 profiles/opentelemetry-profiles-context/rust/build.rs create mode 100644 profiles/opentelemetry-profiles-context/rust/src/convert.rs create mode 100644 profiles/opentelemetry-profiles-context/rust/src/proto.rs diff --git a/profiles/opentelemetry-profiles-context/pyproject.toml b/profiles/opentelemetry-profiles-context/pyproject.toml index f964a117d46..314ac1ceca1 100644 --- a/profiles/opentelemetry-profiles-context/pyproject.toml +++ b/profiles/opentelemetry-profiles-context/pyproject.toml @@ -25,7 +25,10 @@ classifiers = [ "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", ] -dependencies = [] +dependencies = [ + "opentelemetry-api ~= 1.15", + "opentelemetry-sdk ~= 1.15", +] [project.urls] Homepage = "https://github.com/open-telemetry/opentelemetry-python/tree/main/profiles/opentelemetry-profiles-context" diff --git a/profiles/opentelemetry-profiles-context/rust/Cargo.lock b/profiles/opentelemetry-profiles-context/rust/Cargo.lock index 27ad7d26c0d..1dd532e4405 100644 --- a/profiles/opentelemetry-profiles-context/rust/Cargo.lock +++ b/profiles/opentelemetry-profiles-context/rust/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + [[package]] name = "aho-corasick" version = "1.1.4" @@ -17,24 +23,77 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +[[package]] +name = "backon" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cffb0e931875b666fc4fcb20fee52e9bbd1ef836fd9e9e04ec21555f9f85f7ef" +dependencies = [ + "fastrand", + "gloo-timers", + "tokio", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "bitflags" version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + [[package]] name = "bytes" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593" +[[package]] +name = "cc" +version = "1.2.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dad887fd958be91b5098c0248def011f4523ab786cd411be668777e55063501f" +dependencies = [ + "find-msvc-tools", + "shlex", +] + [[package]] name = "cfg-if" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "displaydoc" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "either" version = "1.16.0" @@ -54,7 +113,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -63,12 +122,81 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + [[package]] name = "fixedbitset" version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "getrandom" version = "0.4.3" @@ -80,6 +208,18 @@ dependencies = [ "r-efi", ] +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "hashbrown" version = "0.17.1" @@ -92,6 +232,109 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "icu_collections" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" + +[[package]] +name = "icu_properties" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" + +[[package]] +name = "icu_provider" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + [[package]] name = "indexmap" version = "2.14.0" @@ -111,6 +354,17 @@ dependencies = [ "either", ] +[[package]] +name = "js-sys" +version = "0.3.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03d04c30968dffe80775bd4d7fb676131cd04a1fb46d2686dbffbaec2d9dfd31" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + [[package]] name = "libc" version = "0.2.186" @@ -123,6 +377,12 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + [[package]] name = "log" version = "0.4.32" @@ -135,6 +395,16 @@ version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + [[package]] name = "multimap" version = "0.10.1" @@ -151,11 +421,19 @@ checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" name = "opentelemetry-profiles-context" version = "0.1.0" dependencies = [ + "backon", "prost", "prost-build", "pyo3", + "ureq", ] +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + [[package]] name = "petgraph" version = "0.7.1" @@ -166,12 +444,27 @@ dependencies = [ "indexmap", ] +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + [[package]] name = "portable-atomic" version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + [[package]] name = "prettyplease" version = "0.2.37" @@ -345,6 +638,20 @@ version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + [[package]] name = "rustix" version = "1.1.4" @@ -355,9 +662,115 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" +dependencies = [ + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" +dependencies = [ + "zeroize", ] +[[package]] +name = "rustls-webpki" +version = "0.103.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "simd-adler32" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "syn" version = "2.0.118" @@ -369,6 +782,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "target-lexicon" version = "0.13.5" @@ -382,10 +806,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom", + "getrandom 0.4.3", "once_cell", "rustix", - "windows-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" +dependencies = [ + "pin-project-lite", ] [[package]] @@ -394,12 +837,130 @@ version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "ureq" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d" +dependencies = [ + "base64", + "flate2", + "log", + "once_cell", + "rustls", + "rustls-pki-types", + "url", + "webpki-roots 0.26.11", +] + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.125" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ddb3f79143bced6de84270411622a2699cee572fc0875aeaf1e7867cf9fca1a" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.125" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e21a184b13fb19e157296e2c46056aec9092264fab83e4ba59e68c61b323c3d" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.125" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fecefd9c35bd935a20fc3fc344b5f29138961e4f47fb03297d88f2587afb5ebd" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.125" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23939e44bb9a5d7576fa2b563dc2e136628f1224e88a8deed09e04858b77871f" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "webpki-roots" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.8", +] + +[[package]] +name = "webpki-roots" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf85cb06032201fa7c6f829d7db5a7e5aa45bcc0655327713065f6f0576731bf" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-sys" version = "0.61.2" @@ -408,3 +969,156 @@ checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ "windows-link", ] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] +name = "yoke" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/profiles/opentelemetry-profiles-context/rust/Cargo.toml b/profiles/opentelemetry-profiles-context/rust/Cargo.toml index 91c8d17c969..b5c5d4c9882 100644 --- a/profiles/opentelemetry-profiles-context/rust/Cargo.toml +++ b/profiles/opentelemetry-profiles-context/rust/Cargo.toml @@ -16,3 +16,5 @@ features = ["abi3-py39"] [build-dependencies] prost-build = "0.13" +ureq = { version = "2", features = ["tls"] } +backon = "1" diff --git a/profiles/opentelemetry-profiles-context/rust/build.rs b/profiles/opentelemetry-profiles-context/rust/build.rs new file mode 100644 index 00000000000..931d179824f --- /dev/null +++ b/profiles/opentelemetry-profiles-context/rust/build.rs @@ -0,0 +1,43 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +use std::fs; +use std::path::PathBuf; + +use backon::{BlockingRetryable, ExponentialBuilder}; + +const COMMIT_HASH: &str = "023f8cd36cc946617caa9a9e2e9868186f6d22dd"; + +const UPSTREAM_PROTOS: &[&str] = &[ + "opentelemetry/proto/common/v1/common.proto", + "opentelemetry/proto/resource/v1/resource.proto", + "opentelemetry/proto/processcontext/v1development/process_context.proto", +]; + +const RAW_BASE: &str = + "https://raw.githubusercontent.com/open-telemetry/opentelemetry-proto"; + +fn main() { + let proto_root = PathBuf::from(std::env::var("OUT_DIR").unwrap()).join("proto"); + + for proto_path in UPSTREAM_PROTOS { + let dest = proto_root.join(proto_path); + if dest.exists() { + continue; + } + fs::create_dir_all(dest.parent().unwrap()).unwrap(); + let url = format!("{RAW_BASE}/{COMMIT_HASH}/{proto_path}"); + let content = (|| -> Result> { + Ok(ureq::get(&url).call()?.into_string()?) + }) + .retry(ExponentialBuilder::default()) + .call() + .unwrap(); + fs::write(&dest, content).unwrap(); + } + + let process_context_proto = proto_root + .join("opentelemetry/proto/processcontext/v1development/process_context.proto"); + + prost_build::compile_protos(&[process_context_proto], &[proto_root]).unwrap(); +} diff --git a/profiles/opentelemetry-profiles-context/rust/src/convert.rs b/profiles/opentelemetry-profiles-context/rust/src/convert.rs new file mode 100644 index 00000000000..0a9d702118f --- /dev/null +++ b/profiles/opentelemetry-profiles-context/rust/src/convert.rs @@ -0,0 +1,92 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +use std::collections::HashMap; + +use pyo3::exceptions::PyTypeError; +use pyo3::prelude::*; +use pyo3::types::{PyBool, PyDict, PyFloat, PyInt, PyString}; + +#[derive(Debug)] +pub enum AttributeValue { + String(String), + Bool(bool), + Int(i64), + Float(f64), + StringArray(Vec), + BoolArray(Vec), + IntArray(Vec), + FloatArray(Vec), +} + +#[derive(Debug)] +pub struct Resource { + pub attributes: HashMap, + pub schema_url: String, +} + +fn attribute_value_from_py(val: &Bound<'_, PyAny>) -> PyResult { + // PyBool must be checked before PyInt: bool is a subclass of int in Python. + if val.is_instance_of::() { + return Ok(AttributeValue::Bool(val.extract()?)); + } + if val.is_instance_of::() { + return Ok(AttributeValue::Int(val.extract()?)); + } + if val.is_instance_of::() { + return Ok(AttributeValue::Float(val.extract()?)); + } + if val.is_instance_of::() { + return Ok(AttributeValue::String(val.extract()?)); + } + // Sequence fallback: collect all elements, dispatch by type of the first. + let elements: Vec> = val.try_iter()?.collect::>()?; + if elements.is_empty() { + return Ok(AttributeValue::StringArray(Vec::new())); + } + let first = &elements[0]; + if first.is_instance_of::() { + return Ok(AttributeValue::BoolArray( + elements.iter().map(|e| e.extract()).collect::>()?, + )); + } + if first.is_instance_of::() { + return Ok(AttributeValue::IntArray( + elements.iter().map(|e| e.extract()).collect::>()?, + )); + } + if first.is_instance_of::() { + return Ok(AttributeValue::FloatArray( + elements.iter().map(|e| e.extract()).collect::>()?, + )); + } + if first.is_instance_of::() { + return Ok(AttributeValue::StringArray( + elements.iter().map(|e| e.extract()).collect::>()?, + )); + } + Err(PyTypeError::new_err(format!( + "unsupported attribute value type: {}", + val.get_type().name()? + ))) +} + +pub fn resource_from_py(resource: &Bound<'_, PyAny>) -> PyResult { + let py = resource.py(); + let attrs_obj = resource.getattr("attributes")?; + + // Convert BoundedAttributes Mapping → plain dict so PyO3 can unpack it cleanly. + let py_dict = py + .import("builtins")? + .getattr("dict")? + .call1((&attrs_obj,))? + .cast_into::()?; + + let mut attributes = HashMap::new(); + for (key, val) in py_dict.iter() { + attributes.insert(key.extract::()?, attribute_value_from_py(&val)?); + } + + let schema_url: String = resource.getattr("schema_url")?.extract()?; + Ok(Resource { attributes, schema_url }) +} diff --git a/profiles/opentelemetry-profiles-context/rust/src/lib.rs b/profiles/opentelemetry-profiles-context/rust/src/lib.rs index 439607b31a0..ea97dd09266 100644 --- a/profiles/opentelemetry-profiles-context/rust/src/lib.rs +++ b/profiles/opentelemetry-profiles-context/rust/src/lib.rs @@ -1,16 +1,20 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 +mod convert; +pub(crate) mod proto; + use pyo3::prelude::*; #[pyfunction] -fn sum_as_string(a: usize, b: usize) -> PyResult { - Ok((a + b).to_string()) +fn publish_context(resource: &Bound<'_, PyAny>) -> PyResult<()> { + let _r = convert::resource_from_py(resource)?; + Ok(()) } #[pymodule] #[pyo3(name = "_rs")] fn init(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> { - m.add_wrapped(wrap_pyfunction!(sum_as_string))?; + m.add_wrapped(wrap_pyfunction!(publish_context))?; Ok(()) } diff --git a/profiles/opentelemetry-profiles-context/rust/src/proto.rs b/profiles/opentelemetry-profiles-context/rust/src/proto.rs new file mode 100644 index 00000000000..c9e3c0587c4 --- /dev/null +++ b/profiles/opentelemetry-profiles-context/rust/src/proto.rs @@ -0,0 +1,20 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +pub(crate) mod common { + pub(crate) mod v1 { + include!(concat!(env!("OUT_DIR"), "/opentelemetry.proto.common.v1.rs")); + } +} + +pub(crate) mod resource { + pub(crate) mod v1 { + include!(concat!(env!("OUT_DIR"), "/opentelemetry.proto.resource.v1.rs")); + } +} + +pub(crate) mod processcontext { + pub(crate) mod v1development { + include!(concat!(env!("OUT_DIR"), "/opentelemetry.proto.processcontext.v1development.rs")); + } +} diff --git a/profiles/opentelemetry-profiles-context/src/opentelemetry/profiles/context/_rs/__init__.pyi b/profiles/opentelemetry-profiles-context/src/opentelemetry/profiles/context/_rs/__init__.pyi index 8d680d717d9..ed9946d4362 100644 --- a/profiles/opentelemetry-profiles-context/src/opentelemetry/profiles/context/_rs/__init__.pyi +++ b/profiles/opentelemetry-profiles-context/src/opentelemetry/profiles/context/_rs/__init__.pyi @@ -1,4 +1,6 @@ # Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 -def sum_as_string(a: int, b: int) -> str: ... +from opentelemetry.sdk.resources import Resource + +def publish_context(resource: Resource) -> bool: ... diff --git a/profiles/opentelemetry-profiles-context/test-requirements.in b/profiles/opentelemetry-profiles-context/test-requirements.in index fc282afaa4d..120b7cc4998 100644 --- a/profiles/opentelemetry-profiles-context/test-requirements.in +++ b/profiles/opentelemetry-profiles-context/test-requirements.in @@ -2,4 +2,6 @@ iniconfig==2.3.0 packaging==26.2 pluggy==1.6.0 pytest==7.4.4 +-e opentelemetry-api +-e opentelemetry-sdk -e profiles/opentelemetry-profiles-context diff --git a/profiles/opentelemetry-profiles-context/test-requirements.txt b/profiles/opentelemetry-profiles-context/test-requirements.txt index f0f697e9594..b62334e3b3a 100644 --- a/profiles/opentelemetry-profiles-context/test-requirements.txt +++ b/profiles/opentelemetry-profiles-context/test-requirements.txt @@ -1,5 +1,17 @@ # This file was autogenerated by uv via the following command: # uv pip compile --python 3.10 --universal --resolution highest profiles/opentelemetry-profiles-context/test-requirements.in -o profiles/opentelemetry-profiles-context/test-requirements.txt +-e file:///Users/lukas/Documents/github-projects/opentelemetry-python/opentelemetry-semantic-conventions + # via opentelemetry-sdk +-e opentelemetry-api + # via + # -r profiles/opentelemetry-profiles-context/test-requirements.in + # opentelemetry-profiles-context + # opentelemetry-sdk + # opentelemetry-semantic-conventions +-e opentelemetry-sdk + # via + # -r profiles/opentelemetry-profiles-context/test-requirements.in + # opentelemetry-profiles-context -e profiles/opentelemetry-profiles-context # via -r profiles/opentelemetry-profiles-context/test-requirements.in colorama==0.4.6 ; sys_platform == 'win32' @@ -22,5 +34,9 @@ pytest==7.4.4 # via -r profiles/opentelemetry-profiles-context/test-requirements.in tomli==2.4.1 ; python_full_version < '3.11' # via pytest -typing-extensions==4.15.0 ; python_full_version < '3.11' - # via exceptiongroup +typing-extensions==4.15.0 + # via + # exceptiongroup + # opentelemetry-api + # opentelemetry-sdk + # opentelemetry-semantic-conventions diff --git a/profiles/opentelemetry-profiles-context/tests/test_profiles_context.py b/profiles/opentelemetry-profiles-context/tests/test_profiles_context.py index 89865073ef4..b07159632e8 100644 --- a/profiles/opentelemetry-profiles-context/tests/test_profiles_context.py +++ b/profiles/opentelemetry-profiles-context/tests/test_profiles_context.py @@ -1,8 +1,15 @@ # Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 -from opentelemetry.profiles.context._rs import sum_as_string +import unittest +from opentelemetry.profiles.context._rs import publish_context +from opentelemetry.sdk.resources import Resource -def test_sum_as_string(): - assert sum_as_string(1, 2) == "3" + +class TestPublishContext(unittest.TestCase): + def test_publish_context_does_not_raise(self): + resource = Resource( + {"service.name": "test", "version": 1, "pi": 3.14, "active": True} + ) + publish_context(resource) diff --git a/uv.lock b/uv.lock index 27e13cf3c0e..5bc62e3ce6f 100644 --- a/uv.lock +++ b/uv.lock @@ -1014,6 +1014,16 @@ requires-dist = [ name = "opentelemetry-profiles-context" version = "0.64b0.dev0" source = { editable = "profiles/opentelemetry-profiles-context" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-sdk" }, +] + +[package.metadata] +requires-dist = [ + { name = "opentelemetry-api", editable = "opentelemetry-api" }, + { name = "opentelemetry-sdk", editable = "opentelemetry-sdk" }, +] [[package]] name = "opentelemetry-propagator-b3" From 37f40accf46709cea23b1646fce3e92a57449dd1 Mon Sep 17 00:00:00 2001 From: Lukas Hering Date: Fri, 19 Jun 2026 14:32:00 -0400 Subject: [PATCH 03/16] rework package structure --- .../LICENSE | 0 .../README.rst | 6 +- .../pyproject.toml | 8 +- .../rust/Cargo.lock | 2 +- .../rust/Cargo.toml | 4 +- .../rust/build.rs | 0 .../rust/src/convert.rs | 86 +++++++++++++++++ .../rust/src/lib.rs | 0 .../rust/src/proto.rs | 0 .../process_context}/__init__.py | 0 .../process_context}/_rs/__init__.pyi | 0 .../opentelemetry/process_context}/py.typed | 0 .../opentelemetry/process_context}/version.py | 0 .../test-requirements.in | 2 +- .../test-requirements.txt | 22 ++--- .../tests/__init__.py | 0 .../tests/test_profiles_context.py | 3 +- .../rust/src/convert.rs | 92 ------------------- pyproject.toml | 8 +- tox.ini | 14 +-- uv.lock | 10 +- 21 files changed, 126 insertions(+), 131 deletions(-) rename {profiles/opentelemetry-profiles-context => opentelemetry-process-context}/LICENSE (100%) rename {profiles/opentelemetry-profiles-context => opentelemetry-process-context}/README.rst (60%) rename {profiles/opentelemetry-profiles-context => opentelemetry-process-context}/pyproject.toml (85%) rename {profiles/opentelemetry-profiles-context => opentelemetry-process-context}/rust/Cargo.lock (99%) rename {profiles/opentelemetry-profiles-context => opentelemetry-process-context}/rust/Cargo.toml (77%) rename {profiles/opentelemetry-profiles-context => opentelemetry-process-context}/rust/build.rs (100%) create mode 100644 opentelemetry-process-context/rust/src/convert.rs rename {profiles/opentelemetry-profiles-context => opentelemetry-process-context}/rust/src/lib.rs (100%) rename {profiles/opentelemetry-profiles-context => opentelemetry-process-context}/rust/src/proto.rs (100%) rename {profiles/opentelemetry-profiles-context/src/opentelemetry/profiles/context => opentelemetry-process-context/src/opentelemetry/process_context}/__init__.py (100%) rename {profiles/opentelemetry-profiles-context/src/opentelemetry/profiles/context => opentelemetry-process-context/src/opentelemetry/process_context}/_rs/__init__.pyi (100%) rename {profiles/opentelemetry-profiles-context/src/opentelemetry/profiles/context => opentelemetry-process-context/src/opentelemetry/process_context}/py.typed (100%) rename {profiles/opentelemetry-profiles-context/src/opentelemetry/profiles/context => opentelemetry-process-context/src/opentelemetry/process_context}/version.py (100%) rename {profiles/opentelemetry-profiles-context => opentelemetry-process-context}/test-requirements.in (70%) rename {profiles/opentelemetry-profiles-context => opentelemetry-process-context}/test-requirements.txt (53%) rename {profiles/opentelemetry-profiles-context => opentelemetry-process-context}/tests/__init__.py (100%) rename {profiles/opentelemetry-profiles-context => opentelemetry-process-context}/tests/test_profiles_context.py (79%) delete mode 100644 profiles/opentelemetry-profiles-context/rust/src/convert.rs diff --git a/profiles/opentelemetry-profiles-context/LICENSE b/opentelemetry-process-context/LICENSE similarity index 100% rename from profiles/opentelemetry-profiles-context/LICENSE rename to opentelemetry-process-context/LICENSE diff --git a/profiles/opentelemetry-profiles-context/README.rst b/opentelemetry-process-context/README.rst similarity index 60% rename from profiles/opentelemetry-profiles-context/README.rst rename to opentelemetry-process-context/README.rst index 4fe8837deb4..a4d5311c29b 100644 --- a/profiles/opentelemetry-profiles-context/README.rst +++ b/opentelemetry-process-context/README.rst @@ -1,10 +1,10 @@ -OpenTelemetry Profiles Context +OpenTelemetry Process Context ====================================== |pypi| -.. |pypi| image:: https://badge.fury.io/py/opentelemetry-profiles-context.svg - :target: https://pypi.org/project/opentelemetry-profiles-context/ +.. |pypi| image:: https://badge.fury.io/py/opentelemetry-process-context.svg + :target: https://pypi.org/project/opentelemetry-process-context/ TODO: Update README diff --git a/profiles/opentelemetry-profiles-context/pyproject.toml b/opentelemetry-process-context/pyproject.toml similarity index 85% rename from profiles/opentelemetry-profiles-context/pyproject.toml rename to opentelemetry-process-context/pyproject.toml index 314ac1ceca1..17f4c1d9ccc 100644 --- a/profiles/opentelemetry-profiles-context/pyproject.toml +++ b/opentelemetry-process-context/pyproject.toml @@ -3,9 +3,9 @@ requires = ["maturin>=1.0,<2.0"] build-backend = "maturin" [project] -name = "opentelemetry-profiles-context" +name = "opentelemetry-process-context" version = "0.64b0.dev" -description = "OpenTelemetry Profiles Context" +description = "OpenTelemetry Process Context" readme = "README.rst" license = "Apache-2.0" requires-python = ">=3.10" @@ -31,12 +31,12 @@ dependencies = [ ] [project.urls] -Homepage = "https://github.com/open-telemetry/opentelemetry-python/tree/main/profiles/opentelemetry-profiles-context" +Homepage = "https://github.com/open-telemetry/opentelemetry-python/tree/main/popentelemetry-process-context" Repository = "https://github.com/open-telemetry/opentelemetry-python" [tool.maturin] python-source = "src" -module-name = "opentelemetry.profiles.context._rs" +module-name = "opentelemetry.process_context._rs" [tool.uv] cache-keys = [ diff --git a/profiles/opentelemetry-profiles-context/rust/Cargo.lock b/opentelemetry-process-context/rust/Cargo.lock similarity index 99% rename from profiles/opentelemetry-profiles-context/rust/Cargo.lock rename to opentelemetry-process-context/rust/Cargo.lock index 1dd532e4405..8fa2e1d392a 100644 --- a/profiles/opentelemetry-profiles-context/rust/Cargo.lock +++ b/opentelemetry-process-context/rust/Cargo.lock @@ -418,7 +418,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] -name = "opentelemetry-profiles-context" +name = "opentelemetry-process-context" version = "0.1.0" dependencies = [ "backon", diff --git a/profiles/opentelemetry-profiles-context/rust/Cargo.toml b/opentelemetry-process-context/rust/Cargo.toml similarity index 77% rename from profiles/opentelemetry-profiles-context/rust/Cargo.toml rename to opentelemetry-process-context/rust/Cargo.toml index b5c5d4c9882..48b7b9dbbe8 100644 --- a/profiles/opentelemetry-profiles-context/rust/Cargo.toml +++ b/opentelemetry-process-context/rust/Cargo.toml @@ -1,10 +1,10 @@ [package] -name = "opentelemetry-profiles-context" +name = "opentelemetry-process-context" version = "0.1.0" edition = "2024" [lib] -name = "opentelemetry_profiles_context" +name = "opentelemetry_process_context" crate-type = ["cdylib"] [dependencies] diff --git a/profiles/opentelemetry-profiles-context/rust/build.rs b/opentelemetry-process-context/rust/build.rs similarity index 100% rename from profiles/opentelemetry-profiles-context/rust/build.rs rename to opentelemetry-process-context/rust/build.rs diff --git a/opentelemetry-process-context/rust/src/convert.rs b/opentelemetry-process-context/rust/src/convert.rs new file mode 100644 index 00000000000..fe80eff8e8f --- /dev/null +++ b/opentelemetry-process-context/rust/src/convert.rs @@ -0,0 +1,86 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +use crate::proto::common::v1::{any_value, AnyValue, ArrayValue, KeyValue, KeyValueList}; +use pyo3::prelude::*; +use pyo3::types::{PyBool, PyDict, PyFloat, PyInt, PyString}; + +fn any_value_from_py(val: &Bound<'_, PyAny>) -> PyResult { + // None -> empty AnyValue (value field absent). + if val.is_none() { + return Ok(AnyValue::default()); + } + // PyBool must be checked before PyInt: bool is a subclass of int in Python. + if val.is_instance_of::() { + return Ok(AnyValue { value: Some(any_value::Value::BoolValue(val.extract()?)) }); + } + if val.is_instance_of::() { + return Ok(AnyValue { value: Some(any_value::Value::IntValue(val.extract()?)) }); + } + if val.is_instance_of::() { + return Ok(AnyValue { value: Some(any_value::Value::DoubleValue(val.extract()?)) }); + } + if val.is_instance_of::() { + return Ok(AnyValue { value: Some(any_value::Value::StringValue(val.extract()?)) }); + } + // Mapping -> KvlistValue. Must come before the sequence fallback because Mappings are iterable. + let py = val.py(); + let collections_abc = py.import("collections.abc")?; + if val.is_instance(collections_abc.getattr("Mapping")?.as_ref())? { + // Normalise arbitrary Mapping to a plain dict first. + let py_dict = py + .import("builtins")? + .getattr("dict")? + .call1((val,))? + .cast_into::()?; + let values = py_dict + .iter() + .map(|(k, v)| { + Ok(KeyValue { + key: k.str()?.extract()?, + value: Some(any_value_from_py(&v)?), + ..Default::default() + }) + }) + .collect::>()?; + return Ok(AnyValue { + value: Some(any_value::Value::KvlistValue(KeyValueList { values })), + }); + } + // Sequence fallback: recurse per element into ArrayValue. + let values = val + .try_iter()? + .map(|item| any_value_from_py(&item?)) + .collect::>()?; + Ok(AnyValue { value: Some(any_value::Value::ArrayValue(ArrayValue { values })) }) +} + +pub fn resource_from_py( + resource: &Bound<'_, PyAny>, +) -> PyResult { + let py = resource.py(); + let attrs_obj = resource.getattr("attributes")?; + + // Convert BoundedAttributes Mapping → plain dict so PyO3 can unpack it cleanly. + let py_dict = py + .import("builtins")? + .getattr("dict")? + .call1((&attrs_obj,))? + .cast_into::()?; + + let attributes = py_dict + .iter() + .map(|(key, val)| { + Ok(KeyValue { + key: key.extract()?, + value: Some(any_value_from_py(&val)?), + ..Default::default() + }) + }) + .collect::>()?; + + Ok(crate::proto::resource::v1::Resource { + attributes, + ..Default::default() + }) +} diff --git a/profiles/opentelemetry-profiles-context/rust/src/lib.rs b/opentelemetry-process-context/rust/src/lib.rs similarity index 100% rename from profiles/opentelemetry-profiles-context/rust/src/lib.rs rename to opentelemetry-process-context/rust/src/lib.rs diff --git a/profiles/opentelemetry-profiles-context/rust/src/proto.rs b/opentelemetry-process-context/rust/src/proto.rs similarity index 100% rename from profiles/opentelemetry-profiles-context/rust/src/proto.rs rename to opentelemetry-process-context/rust/src/proto.rs diff --git a/profiles/opentelemetry-profiles-context/src/opentelemetry/profiles/context/__init__.py b/opentelemetry-process-context/src/opentelemetry/process_context/__init__.py similarity index 100% rename from profiles/opentelemetry-profiles-context/src/opentelemetry/profiles/context/__init__.py rename to opentelemetry-process-context/src/opentelemetry/process_context/__init__.py diff --git a/profiles/opentelemetry-profiles-context/src/opentelemetry/profiles/context/_rs/__init__.pyi b/opentelemetry-process-context/src/opentelemetry/process_context/_rs/__init__.pyi similarity index 100% rename from profiles/opentelemetry-profiles-context/src/opentelemetry/profiles/context/_rs/__init__.pyi rename to opentelemetry-process-context/src/opentelemetry/process_context/_rs/__init__.pyi diff --git a/profiles/opentelemetry-profiles-context/src/opentelemetry/profiles/context/py.typed b/opentelemetry-process-context/src/opentelemetry/process_context/py.typed similarity index 100% rename from profiles/opentelemetry-profiles-context/src/opentelemetry/profiles/context/py.typed rename to opentelemetry-process-context/src/opentelemetry/process_context/py.typed diff --git a/profiles/opentelemetry-profiles-context/src/opentelemetry/profiles/context/version.py b/opentelemetry-process-context/src/opentelemetry/process_context/version.py similarity index 100% rename from profiles/opentelemetry-profiles-context/src/opentelemetry/profiles/context/version.py rename to opentelemetry-process-context/src/opentelemetry/process_context/version.py diff --git a/profiles/opentelemetry-profiles-context/test-requirements.in b/opentelemetry-process-context/test-requirements.in similarity index 70% rename from profiles/opentelemetry-profiles-context/test-requirements.in rename to opentelemetry-process-context/test-requirements.in index 120b7cc4998..0a42b62d9ec 100644 --- a/profiles/opentelemetry-profiles-context/test-requirements.in +++ b/opentelemetry-process-context/test-requirements.in @@ -4,4 +4,4 @@ pluggy==1.6.0 pytest==7.4.4 -e opentelemetry-api -e opentelemetry-sdk --e profiles/opentelemetry-profiles-context +-e opentelemetry-process-context diff --git a/profiles/opentelemetry-profiles-context/test-requirements.txt b/opentelemetry-process-context/test-requirements.txt similarity index 53% rename from profiles/opentelemetry-profiles-context/test-requirements.txt rename to opentelemetry-process-context/test-requirements.txt index b62334e3b3a..1d551511d46 100644 --- a/profiles/opentelemetry-profiles-context/test-requirements.txt +++ b/opentelemetry-process-context/test-requirements.txt @@ -1,37 +1,37 @@ # This file was autogenerated by uv via the following command: -# uv pip compile --python 3.10 --universal --resolution highest profiles/opentelemetry-profiles-context/test-requirements.in -o profiles/opentelemetry-profiles-context/test-requirements.txt +# uv pip compile --python 3.10 --universal --resolution highest opentelemetry-process-context/test-requirements.in -o opentelemetry-process-context/test-requirements.txt -e file:///Users/lukas/Documents/github-projects/opentelemetry-python/opentelemetry-semantic-conventions # via opentelemetry-sdk -e opentelemetry-api # via - # -r profiles/opentelemetry-profiles-context/test-requirements.in - # opentelemetry-profiles-context + # -r opentelemetry-process-context/test-requirements.in + # opentelemetry-process-context # opentelemetry-sdk # opentelemetry-semantic-conventions +-e opentelemetry-process-context + # via -r opentelemetry-process-context/test-requirements.in -e opentelemetry-sdk # via - # -r profiles/opentelemetry-profiles-context/test-requirements.in - # opentelemetry-profiles-context --e profiles/opentelemetry-profiles-context - # via -r profiles/opentelemetry-profiles-context/test-requirements.in + # -r opentelemetry-process-context/test-requirements.in + # opentelemetry-process-context colorama==0.4.6 ; sys_platform == 'win32' # via pytest exceptiongroup==1.3.1 ; python_full_version < '3.11' # via pytest iniconfig==2.3.0 # via - # -r profiles/opentelemetry-profiles-context/test-requirements.in + # -r opentelemetry-process-context/test-requirements.in # pytest packaging==26.2 # via - # -r profiles/opentelemetry-profiles-context/test-requirements.in + # -r opentelemetry-process-context/test-requirements.in # pytest pluggy==1.6.0 # via - # -r profiles/opentelemetry-profiles-context/test-requirements.in + # -r opentelemetry-process-context/test-requirements.in # pytest pytest==7.4.4 - # via -r profiles/opentelemetry-profiles-context/test-requirements.in + # via -r opentelemetry-process-context/test-requirements.in tomli==2.4.1 ; python_full_version < '3.11' # via pytest typing-extensions==4.15.0 diff --git a/profiles/opentelemetry-profiles-context/tests/__init__.py b/opentelemetry-process-context/tests/__init__.py similarity index 100% rename from profiles/opentelemetry-profiles-context/tests/__init__.py rename to opentelemetry-process-context/tests/__init__.py diff --git a/profiles/opentelemetry-profiles-context/tests/test_profiles_context.py b/opentelemetry-process-context/tests/test_profiles_context.py similarity index 79% rename from profiles/opentelemetry-profiles-context/tests/test_profiles_context.py rename to opentelemetry-process-context/tests/test_profiles_context.py index b07159632e8..416b3dd75a0 100644 --- a/profiles/opentelemetry-profiles-context/tests/test_profiles_context.py +++ b/opentelemetry-process-context/tests/test_profiles_context.py @@ -3,11 +3,12 @@ import unittest -from opentelemetry.profiles.context._rs import publish_context +from opentelemetry.process_context._rs import publish_context from opentelemetry.sdk.resources import Resource class TestPublishContext(unittest.TestCase): + # pylint: disable-next=no-self-use def test_publish_context_does_not_raise(self): resource = Resource( {"service.name": "test", "version": 1, "pi": 3.14, "active": True} diff --git a/profiles/opentelemetry-profiles-context/rust/src/convert.rs b/profiles/opentelemetry-profiles-context/rust/src/convert.rs deleted file mode 100644 index 0a9d702118f..00000000000 --- a/profiles/opentelemetry-profiles-context/rust/src/convert.rs +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -use std::collections::HashMap; - -use pyo3::exceptions::PyTypeError; -use pyo3::prelude::*; -use pyo3::types::{PyBool, PyDict, PyFloat, PyInt, PyString}; - -#[derive(Debug)] -pub enum AttributeValue { - String(String), - Bool(bool), - Int(i64), - Float(f64), - StringArray(Vec), - BoolArray(Vec), - IntArray(Vec), - FloatArray(Vec), -} - -#[derive(Debug)] -pub struct Resource { - pub attributes: HashMap, - pub schema_url: String, -} - -fn attribute_value_from_py(val: &Bound<'_, PyAny>) -> PyResult { - // PyBool must be checked before PyInt: bool is a subclass of int in Python. - if val.is_instance_of::() { - return Ok(AttributeValue::Bool(val.extract()?)); - } - if val.is_instance_of::() { - return Ok(AttributeValue::Int(val.extract()?)); - } - if val.is_instance_of::() { - return Ok(AttributeValue::Float(val.extract()?)); - } - if val.is_instance_of::() { - return Ok(AttributeValue::String(val.extract()?)); - } - // Sequence fallback: collect all elements, dispatch by type of the first. - let elements: Vec> = val.try_iter()?.collect::>()?; - if elements.is_empty() { - return Ok(AttributeValue::StringArray(Vec::new())); - } - let first = &elements[0]; - if first.is_instance_of::() { - return Ok(AttributeValue::BoolArray( - elements.iter().map(|e| e.extract()).collect::>()?, - )); - } - if first.is_instance_of::() { - return Ok(AttributeValue::IntArray( - elements.iter().map(|e| e.extract()).collect::>()?, - )); - } - if first.is_instance_of::() { - return Ok(AttributeValue::FloatArray( - elements.iter().map(|e| e.extract()).collect::>()?, - )); - } - if first.is_instance_of::() { - return Ok(AttributeValue::StringArray( - elements.iter().map(|e| e.extract()).collect::>()?, - )); - } - Err(PyTypeError::new_err(format!( - "unsupported attribute value type: {}", - val.get_type().name()? - ))) -} - -pub fn resource_from_py(resource: &Bound<'_, PyAny>) -> PyResult { - let py = resource.py(); - let attrs_obj = resource.getattr("attributes")?; - - // Convert BoundedAttributes Mapping → plain dict so PyO3 can unpack it cleanly. - let py_dict = py - .import("builtins")? - .getattr("dict")? - .call1((&attrs_obj,))? - .cast_into::()?; - - let mut attributes = HashMap::new(); - for (key, val) in py_dict.iter() { - attributes.insert(key.extract::()?, attribute_value_from_py(&val)?); - } - - let schema_url: String = resource.getattr("schema_url")?.extract()?; - Ok(Resource { attributes, schema_url }) -} diff --git a/pyproject.toml b/pyproject.toml index dfe7b153d31..2c2b7e9bd9c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,7 @@ dependencies = [ "opentelemetry-propagator-jaeger", "opentelemetry-propagator-b3", "opentelemetry-codegen-json", - "opentelemetry-profiles-context", + "opentelemetry-process-context", ] # https://docs.astral.sh/uv/reference/settings/ @@ -46,7 +46,7 @@ opentelemetry-exporter-prometheus = {workspace = true } opentelemetry-propagator-jaeger = { workspace = true } opentelemetry-propagator-b3 = { workspace = true } opentelemetry-codegen-json = { workspace = true } -opentelemetry-profiles-context = { workspace = true } +opentelemetry-process-context = { workspace = true } [tool.uv.workspace] members = [ @@ -58,8 +58,8 @@ members = [ "exporter/*", "propagator/*", "codegen/*", - "profiles/*", "tests/opentelemetry-test-utils", + "opentelemetry-process-context", ] exclude = [ @@ -132,7 +132,7 @@ include = [ "exporter/opentelemetry-exporter-otlp-proto-http", "exporter/opentelemetry-exporter-otlp-json-common", "codegen/opentelemetry-codegen-json", - "profiles/opentelemetry-profiles-context" + "opentelemetry-process-context" ] exclude = [ diff --git a/tox.ini b/tox.ini index eee91a6868d..c6c405547a3 100644 --- a/tox.ini +++ b/tox.ini @@ -96,9 +96,9 @@ envlist = pypy3-test-opentelemetry-exporter-zipkin-json lint-opentelemetry-exporter-zipkin-json - py3{10,11,12,13,14,14t}-test-opentelemetry-profiles-context - pypy3-test-opentelemetry-profiles-context - lint-opentelemetry-profiles-context + py3{10,11,12,13,14,14t}-test-opentelemetry-process-context + pypy3-test-opentelemetry-process-context + lint-opentelemetry-process-context py3{10,11,12,13,14,14t}-test-opentelemetry-propagator-b3 pypy3-test-opentelemetry-propagator-b3 @@ -180,7 +180,7 @@ deps = exporter-zipkin-json: -r {toxinidir}/exporter/opentelemetry-exporter-zipkin-json/test-requirements.txt - profiles-context: -r {toxinidir}/profiles/opentelemetry-profiles-context/test-requirements.txt + process-context: -r {toxinidir}/opentelemetry-process-context/test-requirements.txt propagator-b3: -r {toxinidir}/propagator/opentelemetry-propagator-b3/test-requirements.txt benchmark-opentelemetry-propagator-b3: -r {toxinidir}/propagator/opentelemetry-propagator-b3/benchmark-requirements.txt @@ -287,8 +287,8 @@ commands = test-opentelemetry-exporter-zipkin-json: pytest {toxinidir}/exporter/opentelemetry-exporter-zipkin-json/tests {posargs} lint-opentelemetry-exporter-zipkin-json: sh -c "cd exporter && pylint --rcfile ../.pylintrc {toxinidir}/exporter/opentelemetry-exporter-zipkin-json" - test-opentelemetry-profiles-context: pytest {toxinidir}/profiles/opentelemetry-profiles-context/tests {posargs} - lint-opentelemetry-profiles-context: sh -c "cd profiles && pylint --rcfile ../.pylintrc {toxinidir}/profiles/opentelemetry-profiles-context" + test-opentelemetry-process-context: pytest {toxinidir}/opentelemetry-process-context/tests {posargs} + lint-opentelemetry-process-context: pylint {toxinidir}/opentelemetry-process-context test-opentelemetry-propagator-b3: pytest {toxinidir}/propagator/opentelemetry-propagator-b3/tests {posargs} lint-opentelemetry-propagator-b3: sh -c "cd propagator && pylint --rcfile ../.pylintrc {toxinidir}/propagator/opentelemetry-propagator-b3" @@ -439,7 +439,7 @@ deps = -e {toxinidir}/exporter/opentelemetry-exporter-otlp-proto-http -e {toxinidir}/opentelemetry-proto -e {toxinidir}/opentelemetry-proto-json - -e {toxinidir}/opentelemetry-profiles-context + -e {toxinidir}/opentelemetry-process-context -e {toxinidir}/codegen/opentelemetry-codegen-json commands = diff --git a/uv.lock b/uv.lock index 5bc62e3ce6f..490e284afb7 100644 --- a/uv.lock +++ b/uv.lock @@ -20,7 +20,7 @@ members = [ "opentelemetry-exporter-otlp-proto-http", "opentelemetry-exporter-prometheus", "opentelemetry-exporter-zipkin-json", - "opentelemetry-profiles-context", + "opentelemetry-process-context", "opentelemetry-propagator-b3", "opentelemetry-propagator-jaeger", "opentelemetry-proto", @@ -1011,9 +1011,9 @@ requires-dist = [ ] [[package]] -name = "opentelemetry-profiles-context" +name = "opentelemetry-process-context" version = "0.64b0.dev0" -source = { editable = "profiles/opentelemetry-profiles-context" } +source = { editable = "opentelemetry-process-context" } dependencies = [ { name = "opentelemetry-api" }, { name = "opentelemetry-sdk" }, @@ -1078,7 +1078,7 @@ dependencies = [ { name = "opentelemetry-exporter-otlp-proto-http" }, { name = "opentelemetry-exporter-prometheus" }, { name = "opentelemetry-exporter-zipkin-json" }, - { name = "opentelemetry-profiles-context" }, + { name = "opentelemetry-process-context" }, { name = "opentelemetry-propagator-b3" }, { name = "opentelemetry-propagator-jaeger" }, { name = "opentelemetry-proto" }, @@ -1109,7 +1109,7 @@ requires-dist = [ { name = "opentelemetry-exporter-otlp-proto-http", editable = "exporter/opentelemetry-exporter-otlp-proto-http" }, { name = "opentelemetry-exporter-prometheus", editable = "exporter/opentelemetry-exporter-prometheus" }, { name = "opentelemetry-exporter-zipkin-json", editable = "exporter/opentelemetry-exporter-zipkin-json" }, - { name = "opentelemetry-profiles-context", editable = "profiles/opentelemetry-profiles-context" }, + { name = "opentelemetry-process-context", editable = "opentelemetry-process-context" }, { name = "opentelemetry-propagator-b3", editable = "propagator/opentelemetry-propagator-b3" }, { name = "opentelemetry-propagator-jaeger", editable = "propagator/opentelemetry-propagator-jaeger" }, { name = "opentelemetry-proto", editable = "opentelemetry-proto" }, From b40994709b44972804c763c00af2d18c99639ab6 Mon Sep 17 00:00:00 2001 From: Lukas Hering Date: Fri, 19 Jun 2026 17:07:49 -0400 Subject: [PATCH 04/16] publish/unpublish/update logic --- opentelemetry-process-context/rust/Cargo.lock | 19 ++ opentelemetry-process-context/rust/Cargo.toml | 1 + .../rust/src/convert.rs | 32 +- opentelemetry-process-context/rust/src/lib.rs | 22 +- .../rust/src/publish.rs | 319 ++++++++++++++++++ .../opentelemetry/process_context/__init__.py | 4 + .../process_context/_rs/__init__.pyi | 4 +- .../tests/test_profiles_context.py | 34 +- 8 files changed, 416 insertions(+), 19 deletions(-) create mode 100644 opentelemetry-process-context/rust/src/publish.rs diff --git a/opentelemetry-process-context/rust/Cargo.lock b/opentelemetry-process-context/rust/Cargo.lock index 8fa2e1d392a..110a6c8ea78 100644 --- a/opentelemetry-process-context/rust/Cargo.lock +++ b/opentelemetry-process-context/rust/Cargo.lock @@ -74,6 +74,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "crc32fast" version = "1.5.0" @@ -411,6 +417,18 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" +[[package]] +name = "nix" +version = "0.31.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", +] + [[package]] name = "once_cell" version = "1.21.4" @@ -422,6 +440,7 @@ name = "opentelemetry-process-context" version = "0.1.0" dependencies = [ "backon", + "nix", "prost", "prost-build", "pyo3", diff --git a/opentelemetry-process-context/rust/Cargo.toml b/opentelemetry-process-context/rust/Cargo.toml index 48b7b9dbbe8..71fddaf202f 100644 --- a/opentelemetry-process-context/rust/Cargo.toml +++ b/opentelemetry-process-context/rust/Cargo.toml @@ -9,6 +9,7 @@ crate-type = ["cdylib"] [dependencies] prost = "0.13" +nix = { version = "0.31", features = ["mman", "fs", "time"] } [dependencies.pyo3] version = "0.28.2" diff --git a/opentelemetry-process-context/rust/src/convert.rs b/opentelemetry-process-context/rust/src/convert.rs index fe80eff8e8f..74b2c7cde4b 100644 --- a/opentelemetry-process-context/rust/src/convert.rs +++ b/opentelemetry-process-context/rust/src/convert.rs @@ -2,15 +2,15 @@ // SPDX-License-Identifier: Apache-2.0 use crate::proto::common::v1::{any_value, AnyValue, ArrayValue, KeyValue, KeyValueList}; +use pyo3::exceptions::PyTypeError; use pyo3::prelude::*; use pyo3::types::{PyBool, PyDict, PyFloat, PyInt, PyString}; +use crate::proto::resource::v1::Resource; fn any_value_from_py(val: &Bound<'_, PyAny>) -> PyResult { - // None -> empty AnyValue (value field absent). if val.is_none() { return Ok(AnyValue::default()); } - // PyBool must be checked before PyInt: bool is a subclass of int in Python. if val.is_instance_of::() { return Ok(AnyValue { value: Some(any_value::Value::BoolValue(val.extract()?)) }); } @@ -23,11 +23,11 @@ fn any_value_from_py(val: &Bound<'_, PyAny>) -> PyResult { if val.is_instance_of::() { return Ok(AnyValue { value: Some(any_value::Value::StringValue(val.extract()?)) }); } - // Mapping -> KvlistValue. Must come before the sequence fallback because Mappings are iterable. + let py = val.py(); let collections_abc = py.import("collections.abc")?; + if val.is_instance(collections_abc.getattr("Mapping")?.as_ref())? { - // Normalise arbitrary Mapping to a plain dict first. let py_dict = py .import("builtins")? .getattr("dict")? @@ -47,21 +47,27 @@ fn any_value_from_py(val: &Bound<'_, PyAny>) -> PyResult { value: Some(any_value::Value::KvlistValue(KeyValueList { values })), }); } - // Sequence fallback: recurse per element into ArrayValue. - let values = val - .try_iter()? - .map(|item| any_value_from_py(&item?)) - .collect::>()?; - Ok(AnyValue { value: Some(any_value::Value::ArrayValue(ArrayValue { values })) }) + + if val.is_instance(collections_abc.getattr("Sequence")?.as_ref())? { + let values = val + .try_iter()? + .map(|item| any_value_from_py(&item?)) + .collect::>()?; + return Ok(AnyValue { value: Some(any_value::Value::ArrayValue(ArrayValue { values })) }); + } + + let type_name: String = val.get_type().qualname()?.extract()?; + Err(PyTypeError::new_err(format!( + "unsupported attribute value type: {type_name}" + ))) } pub fn resource_from_py( resource: &Bound<'_, PyAny>, -) -> PyResult { +) -> PyResult { let py = resource.py(); let attrs_obj = resource.getattr("attributes")?; - // Convert BoundedAttributes Mapping → plain dict so PyO3 can unpack it cleanly. let py_dict = py .import("builtins")? .getattr("dict")? @@ -79,7 +85,7 @@ pub fn resource_from_py( }) .collect::>()?; - Ok(crate::proto::resource::v1::Resource { + Ok(Resource { attributes, ..Default::default() }) diff --git a/opentelemetry-process-context/rust/src/lib.rs b/opentelemetry-process-context/rust/src/lib.rs index ea97dd09266..cfe2d097847 100644 --- a/opentelemetry-process-context/rust/src/lib.rs +++ b/opentelemetry-process-context/rust/src/lib.rs @@ -3,12 +3,30 @@ mod convert; pub(crate) mod proto; +mod publish; use pyo3::prelude::*; #[pyfunction] fn publish_context(resource: &Bound<'_, PyAny>) -> PyResult<()> { - let _r = convert::resource_from_py(resource)?; + let _resource = convert::resource_from_py(resource)?; + // TODO(phase 2): encode the ProcessContext { resource, attributes } payload + // and publish the real bytes instead of an empty payload. + publish::publish(&[])?; + Ok(()) +} + +#[pyfunction] +fn update_context(resource: &Bound<'_, PyAny>) -> PyResult<()> { + let _resource = convert::resource_from_py(resource)?; + // TODO(phase 2): encode the ProcessContext { resource, attributes } payload. + publish::update(&[])?; + Ok(()) +} + +#[pyfunction] +fn unpublish_context() -> PyResult<()> { + publish::unpublish()?; Ok(()) } @@ -16,5 +34,7 @@ fn publish_context(resource: &Bound<'_, PyAny>) -> PyResult<()> { #[pyo3(name = "_rs")] fn init(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_wrapped(wrap_pyfunction!(publish_context))?; + m.add_wrapped(wrap_pyfunction!(update_context))?; + m.add_wrapped(wrap_pyfunction!(unpublish_context))?; Ok(()) } diff --git a/opentelemetry-process-context/rust/src/publish.rs b/opentelemetry-process-context/rust/src/publish.rs new file mode 100644 index 00000000000..96e873c746b --- /dev/null +++ b/opentelemetry-process-context/rust/src/publish.rs @@ -0,0 +1,319 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +//! Publication of the process context to a memory region that out-of-process +//! readers (e.g. the OpenTelemetry eBPF Profiler) can discover and read. +//! +//! The layout follows the OpenTelemetry "Process Context" specification: a fixed +//! 32-byte header mapping named `OTEL_CTX` and backed by a `memfd` on Linux +//! (visible in `/proc//maps`), with the payload living out-of-band in a +//! heap-allocated buffer. This means the header mapping never needs resizing: +//! updates only swap the payload buffer and rewrite the header pointer fields. + +use std::ffi::c_void; +use std::num::NonZeroUsize; +use std::ptr::NonNull; +use std::sync::atomic::{AtomicU64, Ordering}; +use std::sync::Mutex; + +use nix::sys::mman::{mmap_anonymous, MapFlags, ProtFlags}; +use nix::time::{clock_gettime, ClockId}; +use pyo3::exceptions::{PyOSError, PyRuntimeError}; +use pyo3::PyErr; + +/// 8 byte signature stamped at the start of the header. +const SIGNATURE: [u8; 8] = *b"OTEL_CTX"; +/// Format version. `2` is the first stable version (`1` is for development). +const VERSION: u32 = 2; +/// Size of the header mapping in bytes. The payload lives on the heap. +const HEADER_SIZE: usize = 32; + +/// The process context header. +#[repr(C)] +struct Header { + signature: [u8; 8], + version: u32, + payload_size: u32, + monotonic_published_at_ns: AtomicU64, + payload: u64, +} + +const _: () = assert!(std::mem::size_of::
() == HEADER_SIZE); + +/// A published mapping. +#[allow(dead_code)] +struct Region { + ptr: NonNull, + payload: Vec, +} + +// SAFETY: all access goes through `MAPPING` (a `Mutex`), which serializes +// reads and writes. The pointer lives for the process lifetime. +unsafe impl Send for Region {} + +/// The single active process context for this process, if any. +static MAPPING: Mutex> = Mutex::new(None); + +#[derive(Debug)] +pub enum PublishError { + /// A process context has already been published for this process. + AlreadyPublished, + /// `update()` was called before any context was published. + NotPublished, + /// The backing memory region could not be allocated. + Alloc, + /// `madvise(MADV_DONTFORK)` failed. + Madvise, + /// The monotonic clock could not be read for the publish timestamp. + Clock, + /// `munmap` of the header mapping failed during unpublish. + Munmap, +} + +impl From for PyErr { + fn from(err: PublishError) -> Self { + match err { + PublishError::AlreadyPublished => { + PyRuntimeError::new_err("a process context has already been published") + } + PublishError::NotPublished => { + PyRuntimeError::new_err("no process context has been published yet") + } + PublishError::Alloc => { + PyOSError::new_err("failed to allocate the process context mapping") + } + PublishError::Madvise => { + PyOSError::new_err("madvise(MADV_DONTFORK) failed for the process context mapping") + } + PublishError::Clock => { + PyOSError::new_err("failed to read the monotonic clock for the process context") + } + PublishError::Munmap => { + PyOSError::new_err("munmap of the process context mapping failed") + } + } + } +} + +/// Publish `payload` as the process context. +/// +/// The context is a per-process singleton: calling this a second time without a +/// prior teardown returns [`PublishError::AlreadyPublished`]. +pub fn publish(payload: &[u8]) -> Result<(), PublishError> { + let mut guard = MAPPING.lock().expect("process context mutex poisoned"); + if guard.is_some() { + return Err(PublishError::AlreadyPublished); + } + + let ptr = alloc_region()?; + advise_dontfork(ptr, HEADER_SIZE)?; + + let timestamp = get_boottime_ns()?; + + let payload_buf = payload.to_vec(); + + // SAFETY: `ptr` points to a freshly mapped, zero-initialized, page-aligned + // region of exactly `HEADER_SIZE` bytes. The payload lives in `payload_buf` + // on the heap; the header's `payload` field stores a pointer into it. + unsafe { + let header = ptr.as_ptr().cast::
(); + + std::ptr::addr_of_mut!((*header).signature).write(SIGNATURE); + std::ptr::addr_of_mut!((*header).version).write(VERSION); + std::ptr::addr_of_mut!((*header).payload_size).write(payload_buf.len() as u32); + std::ptr::addr_of_mut!((*header).payload).write(payload_buf.as_ptr() as u64); + + // Write the timestamp last with release ordering. This publishes every + // store above to any reader that observes the (non-zero) timestamp. + let published_at = &*std::ptr::addr_of!((*header).monotonic_published_at_ns); + published_at.store(timestamp, Ordering::Release); + } + + // Best effort naming so readers can find the mapping even without a memfd + // path, failures are ignored per the spec. + name_mapping(ptr, HEADER_SIZE); + + *guard = Some(Region { ptr, payload: payload_buf }); + Ok(()) +} + +/// Update the published process context with a new payload. +/// +/// Follows the spec's Updating Protocol: zeros the timestamp (Release) to signal +/// readers that an update is in progress, rewrites the payload fields, then +/// publishes the new timestamp (Release) to signal completion. The old payload +/// buffer is dropped after the new timestamp is live. +pub fn update(payload: &[u8]) -> Result<(), PublishError> { + let mut guard = MAPPING.lock().expect("process context mutex poisoned"); + let region = guard.as_mut().ok_or(PublishError::NotPublished)?; + + let new_buf = payload.to_vec(); + let timestamp = get_boottime_ns()?; + + // SAFETY: `region.ptr` points to the live header mapping with exactly + // `HEADER_SIZE` bytes, writable for the process lifetime. + unsafe { + let header = region.ptr.as_ptr().cast::
(); + let published_at = &*std::ptr::addr_of!((*header).monotonic_published_at_ns); + + // Zero timestamp with Release ensuring the previous payload state is + // visible to readers that observe the "update in progress" signal. + published_at.store(0, Ordering::Release); + + // Rewrite payload fields between the two Release stores. + std::ptr::addr_of_mut!((*header).payload_size).write(new_buf.len() as u32); + std::ptr::addr_of_mut!((*header).payload).write(new_buf.as_ptr() as u64); + + // Publish new timestamp with Release ensuring the new payload fields + // are visible to readers that observe the "update complete" signal. + published_at.store(timestamp, Ordering::Release); + } + + // Rename the mapping unconditionally (failures ignored per spec). + name_mapping(region.ptr, HEADER_SIZE); + + // Drop the old payload only after the new timestamp is live + region.payload = new_buf; + Ok(()) +} + +/// Remove the published process context. +/// +/// Zeros the timestamp (Release) so readers still observing the mapping see an +/// invalid state, then `munmap`s the header and drops the payload buffer. +/// After a successful call, `publish()` may be called again. +pub fn unpublish() -> Result<(), PublishError> { + let mut guard = MAPPING.lock().expect("process context mutex poisoned"); + let region = guard.take().ok_or(PublishError::NotPublished)?; + + // Zero the timestamp with Release before removing the mapping so any + // reader still observing it sees an invalid (zero) state. + unsafe { + let header = region.ptr.as_ptr().cast::
(); + let published_at = &*std::ptr::addr_of!((*header).monotonic_published_at_ns); + published_at.store(0, Ordering::Release); + } + + // Remove the mapping. The memfd fd was already closed in try_memfd_mapping() + // so nothing extra to close here; munmap releases the kernel's last reference + // to the backing store. The payload Vec drops with `region` at end of scope. + unsafe { nix::sys::mman::munmap(region.ptr, HEADER_SIZE) } + .map_err(|_| PublishError::Munmap) +} + +/// Allocate the 32-byte header mapping: a `memfd`-backed mapping on Linux (so +/// it shows up in `/proc//maps`), falling back to an anonymous mapping. +fn alloc_region() -> Result, PublishError> { + let len = NonZeroUsize::new(HEADER_SIZE).unwrap(); + + #[cfg(target_os = "linux")] + if let Some(ptr) = linux::try_memfd_mapping(len) { + return Ok(ptr); + } + + // SAFETY: a fresh anonymous mapping with a valid, non-zero length. + unsafe { + mmap_anonymous( + None, + len, + ProtFlags::PROT_READ | ProtFlags::PROT_WRITE, + MapFlags::MAP_PRIVATE | MapFlags::MAP_ANONYMOUS, + ) + } + .map_err(|_| PublishError::Alloc) +} + +/// Read the publish timestamp. Uses `CLOCK_BOOTTIME` on Linux (as the spec +/// requires) and `CLOCK_MONOTONIC` elsewhere. The value is forced non-zero, as a +/// zero timestamp is reserved to mean "being mutated, not ready". +fn get_boottime_ns() -> Result { + #[cfg(target_os = "linux")] + let clock = ClockId::CLOCK_BOOTTIME; + #[cfg(not(target_os = "linux"))] + let clock = ClockId::CLOCK_MONOTONIC; + + let ts = clock_gettime(clock).map_err(|_| PublishError::Clock)?; + let ns = (ts.tv_sec() as u64) + .saturating_mul(1_000_000_000) + .saturating_add(ts.tv_nsec() as u64); + Ok(ns.max(1)) +} + +/// Prevent child processes from inheriting (stale) context memory. No-op off +/// Linux, where `MADV_DONTFORK` does not exist. +#[cfg(target_os = "linux")] +fn advise_dontfork(ptr: NonNull, len: usize) -> Result<(), PublishError> { + // SAFETY: `ptr`/`len` describe the mapping we just created. + unsafe { nix::sys::mman::madvise(ptr, len, nix::sys::mman::MmapAdvise::MADV_DONTFORK) } + .map_err(|_| PublishError::Madvise) +} + +#[cfg(not(target_os = "linux"))] +fn advise_dontfork(_ptr: NonNull, _len: usize) -> Result<(), PublishError> { + Ok(()) +} + +/// Name the mapping `OTEL_CTX` via `prctl(PR_SET_VMA_ANON_NAME)`. No-op off +/// Linux. Failures (e.g. kernels without `CONFIG_ANON_VMA_NAME`) are ignored. +#[cfg(target_os = "linux")] +fn name_mapping(ptr: NonNull, len: usize) { + linux::name_mapping(ptr, len); +} + +#[cfg(not(target_os = "linux"))] +fn name_mapping(_ptr: NonNull, _len: usize) {} + +#[cfg(target_os = "linux")] +mod linux { + use std::ffi::c_void; + use std::num::NonZeroUsize; + use std::ptr::NonNull; + + use nix::sys::memfd::{memfd_create, MFdFlags}; + use nix::sys::mman::{mmap, MapFlags, ProtFlags}; + + // `prctl` options for naming an anonymous VMA. + const PR_SET_VMA: nix::libc::c_int = 0x53564d41; + const PR_SET_VMA_ANON_NAME: nix::libc::c_ulong = 0; + + /// Create a `memfd`, size it, and map it `MAP_PRIVATE`. Returns `None` if any + /// step fails so the caller can fall back to an anonymous mapping. + pub(super) fn try_memfd_mapping(len: NonZeroUsize) -> Option> { + let base = MFdFlags::MFD_CLOEXEC | MFdFlags::MFD_ALLOW_SEALING; + // `MFD_NOEXEC_SEAL` is a newer flag not exposed by `nix`, request it but + // fall back without it on kernels/libc that lack it. + let noexec_seal = MFdFlags::from_bits_retain(nix::libc::MFD_NOEXEC_SEAL as _); + let fd = memfd_create(c"OTEL_CTX", base | noexec_seal) + .or_else(|_| memfd_create(c"OTEL_CTX", base)) + .ok()?; + + nix::unistd::ftruncate(&fd, len.get() as nix::libc::off_t).ok()?; + + // SAFETY: `fd` is a valid, sized memfd and `len` is non-zero. + let ptr = unsafe { + mmap( + None, + len, + ProtFlags::PROT_READ | ProtFlags::PROT_WRITE, + MapFlags::MAP_PRIVATE, + &fd, + 0, + ) + } + .ok()?; + Some(ptr) + } + + pub(super) fn name_mapping(ptr: NonNull, len: usize) { + const NAME: &core::ffi::CStr = c"OTEL_CTX"; + unsafe { + let _ = nix::libc::prctl( + PR_SET_VMA, + PR_SET_VMA_ANON_NAME, + ptr.as_ptr() as nix::libc::c_ulong, + len as nix::libc::c_ulong, + NAME.as_ptr() as nix::libc::c_ulong, + ); + } + } +} diff --git a/opentelemetry-process-context/src/opentelemetry/process_context/__init__.py b/opentelemetry-process-context/src/opentelemetry/process_context/__init__.py index e57cf4aba95..6932ef75864 100644 --- a/opentelemetry-process-context/src/opentelemetry/process_context/__init__.py +++ b/opentelemetry-process-context/src/opentelemetry/process_context/__init__.py @@ -1,2 +1,6 @@ # Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 + +from opentelemetry.process_context._rs import publish_context + +__all__ = ["publish_context"] diff --git a/opentelemetry-process-context/src/opentelemetry/process_context/_rs/__init__.pyi b/opentelemetry-process-context/src/opentelemetry/process_context/_rs/__init__.pyi index ed9946d4362..e94289db917 100644 --- a/opentelemetry-process-context/src/opentelemetry/process_context/_rs/__init__.pyi +++ b/opentelemetry-process-context/src/opentelemetry/process_context/_rs/__init__.pyi @@ -3,4 +3,6 @@ from opentelemetry.sdk.resources import Resource -def publish_context(resource: Resource) -> bool: ... +def publish_context(resource: Resource) -> None: ... +def update_context(resource: Resource) -> None: ... +def unpublish_context() -> None: ... diff --git a/opentelemetry-process-context/tests/test_profiles_context.py b/opentelemetry-process-context/tests/test_profiles_context.py index 416b3dd75a0..0a1346781de 100644 --- a/opentelemetry-process-context/tests/test_profiles_context.py +++ b/opentelemetry-process-context/tests/test_profiles_context.py @@ -3,14 +3,40 @@ import unittest -from opentelemetry.process_context._rs import publish_context +from opentelemetry.process_context._rs import ( + publish_context, + unpublish_context, + update_context, +) from opentelemetry.sdk.resources import Resource class TestPublishContext(unittest.TestCase): - # pylint: disable-next=no-self-use - def test_publish_context_does_not_raise(self): + def tearDown(self): + try: + unpublish_context() + except RuntimeError: + pass + + def test_publish_context_lifecycle(self): resource = Resource( {"service.name": "test", "version": 1, "pi": 3.14, "active": True} ) - publish_context(resource) + self.assertIsNone(publish_context(resource)) + + with self.assertRaises(RuntimeError): + publish_context(resource) + + self.assertIsNone(update_context(resource)) + self.assertIsNone(update_context(resource)) + + self.assertIsNone(unpublish_context()) + self.assertIsNone(publish_context(resource)) + + def test_update_before_publish_raises(self): + with self.assertRaises(RuntimeError): + update_context(Resource({})) + + def test_unpublish_before_publish_raises(self): + with self.assertRaises(RuntimeError): + unpublish_context() From ed84540dd8862fb2b96f2c7a4e567c80178cfa3e Mon Sep 17 00:00:00 2001 From: Lukas Hering Date: Fri, 19 Jun 2026 17:23:58 -0400 Subject: [PATCH 05/16] add encoding logic --- .../rust/src/convert.rs | 12 ++++++++- opentelemetry-process-context/rust/src/lib.rs | 12 ++++----- .../rust/src/publish.rs | 26 +++++++------------ 3 files changed, 26 insertions(+), 24 deletions(-) diff --git a/opentelemetry-process-context/rust/src/convert.rs b/opentelemetry-process-context/rust/src/convert.rs index 74b2c7cde4b..0279d47a899 100644 --- a/opentelemetry-process-context/rust/src/convert.rs +++ b/opentelemetry-process-context/rust/src/convert.rs @@ -1,10 +1,12 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -use crate::proto::common::v1::{any_value, AnyValue, ArrayValue, KeyValue, KeyValueList}; +use prost::Message; use pyo3::exceptions::PyTypeError; use pyo3::prelude::*; use pyo3::types::{PyBool, PyDict, PyFloat, PyInt, PyString}; +use crate::proto::common::v1::{any_value, AnyValue, ArrayValue, KeyValue, KeyValueList}; +use crate::proto::processcontext::v1development::ProcessContext; use crate::proto::resource::v1::Resource; fn any_value_from_py(val: &Bound<'_, PyAny>) -> PyResult { @@ -90,3 +92,11 @@ pub fn resource_from_py( ..Default::default() }) } + +pub fn encode_process_context(resource: Resource) -> Vec { + ProcessContext { + resource: Some(resource), + attributes: vec![], + } + .encode_to_vec() +} diff --git a/opentelemetry-process-context/rust/src/lib.rs b/opentelemetry-process-context/rust/src/lib.rs index cfe2d097847..c4eaba1a248 100644 --- a/opentelemetry-process-context/rust/src/lib.rs +++ b/opentelemetry-process-context/rust/src/lib.rs @@ -6,21 +6,19 @@ pub(crate) mod proto; mod publish; use pyo3::prelude::*; +use crate::convert::{encode_process_context, resource_from_py}; #[pyfunction] fn publish_context(resource: &Bound<'_, PyAny>) -> PyResult<()> { - let _resource = convert::resource_from_py(resource)?; - // TODO(phase 2): encode the ProcessContext { resource, attributes } payload - // and publish the real bytes instead of an empty payload. - publish::publish(&[])?; + let resource = resource_from_py(resource)?; + publish::publish(encode_process_context(resource))?; Ok(()) } #[pyfunction] fn update_context(resource: &Bound<'_, PyAny>) -> PyResult<()> { - let _resource = convert::resource_from_py(resource)?; - // TODO(phase 2): encode the ProcessContext { resource, attributes } payload. - publish::update(&[])?; + let resource = resource_from_py(resource)?; + publish::update(encode_process_context(resource))?; Ok(()) } diff --git a/opentelemetry-process-context/rust/src/publish.rs b/opentelemetry-process-context/rust/src/publish.rs index 96e873c746b..550c3b86292 100644 --- a/opentelemetry-process-context/rust/src/publish.rs +++ b/opentelemetry-process-context/rust/src/publish.rs @@ -99,7 +99,7 @@ impl From for PyErr { /// /// The context is a per-process singleton: calling this a second time without a /// prior teardown returns [`PublishError::AlreadyPublished`]. -pub fn publish(payload: &[u8]) -> Result<(), PublishError> { +pub fn publish(payload: Vec) -> Result<(), PublishError> { let mut guard = MAPPING.lock().expect("process context mutex poisoned"); if guard.is_some() { return Err(PublishError::AlreadyPublished); @@ -110,11 +110,11 @@ pub fn publish(payload: &[u8]) -> Result<(), PublishError> { let timestamp = get_boottime_ns()?; - let payload_buf = payload.to_vec(); + let payload_buf = payload; - // SAFETY: `ptr` points to a freshly mapped, zero-initialized, page-aligned + // SAFETY: `ptr` points to a freshly mapped, zero initialized, page aligned // region of exactly `HEADER_SIZE` bytes. The payload lives in `payload_buf` - // on the heap; the header's `payload` field stores a pointer into it. + // on the heap and the header's `payload` field stores a pointer into it. unsafe { let header = ptr.as_ptr().cast::
(); @@ -123,8 +123,7 @@ pub fn publish(payload: &[u8]) -> Result<(), PublishError> { std::ptr::addr_of_mut!((*header).payload_size).write(payload_buf.len() as u32); std::ptr::addr_of_mut!((*header).payload).write(payload_buf.as_ptr() as u64); - // Write the timestamp last with release ordering. This publishes every - // store above to any reader that observes the (non-zero) timestamp. + // Write the timestamp last with release ordering. let published_at = &*std::ptr::addr_of!((*header).monotonic_published_at_ns); published_at.store(timestamp, Ordering::Release); } @@ -140,14 +139,14 @@ pub fn publish(payload: &[u8]) -> Result<(), PublishError> { /// Update the published process context with a new payload. /// /// Follows the spec's Updating Protocol: zeros the timestamp (Release) to signal -/// readers that an update is in progress, rewrites the payload fields, then +/// readers that an update is in progress, rewrites the payload fields and then /// publishes the new timestamp (Release) to signal completion. The old payload /// buffer is dropped after the new timestamp is live. -pub fn update(payload: &[u8]) -> Result<(), PublishError> { +pub fn update(payload: Vec) -> Result<(), PublishError> { let mut guard = MAPPING.lock().expect("process context mutex poisoned"); let region = guard.as_mut().ok_or(PublishError::NotPublished)?; - let new_buf = payload.to_vec(); + let new_buf = payload; let timestamp = get_boottime_ns()?; // SAFETY: `region.ptr` points to the live header mapping with exactly @@ -194,9 +193,6 @@ pub fn unpublish() -> Result<(), PublishError> { published_at.store(0, Ordering::Release); } - // Remove the mapping. The memfd fd was already closed in try_memfd_mapping() - // so nothing extra to close here; munmap releases the kernel's last reference - // to the backing store. The payload Vec drops with `region` at end of scope. unsafe { nix::sys::mman::munmap(region.ptr, HEADER_SIZE) } .map_err(|_| PublishError::Munmap) } @@ -239,8 +235,7 @@ fn get_boottime_ns() -> Result { Ok(ns.max(1)) } -/// Prevent child processes from inheriting (stale) context memory. No-op off -/// Linux, where `MADV_DONTFORK` does not exist. +/// Prevent child processes from inheriting (stale) context memory. #[cfg(target_os = "linux")] fn advise_dontfork(ptr: NonNull, len: usize) -> Result<(), PublishError> { // SAFETY: `ptr`/`len` describe the mapping we just created. @@ -253,8 +248,7 @@ fn advise_dontfork(_ptr: NonNull, _len: usize) -> Result<(), PublishErro Ok(()) } -/// Name the mapping `OTEL_CTX` via `prctl(PR_SET_VMA_ANON_NAME)`. No-op off -/// Linux. Failures (e.g. kernels without `CONFIG_ANON_VMA_NAME`) are ignored. +/// Name the mapping `OTEL_CTX` via `prctl(PR_SET_VMA_ANON_NAME)`. #[cfg(target_os = "linux")] fn name_mapping(ptr: NonNull, len: usize) { linux::name_mapping(ptr, len); From c384f9f04a30ff7e1f50b86bd07da0e8f8babba8 Mon Sep 17 00:00:00 2001 From: Lukas Hering Date: Sun, 21 Jun 2026 22:32:26 -0400 Subject: [PATCH 06/16] update generate-workflows.py --- .changelog/5337.added | 1 + .github/workflows/generate_workflows.py | 47 ++++++- .github/workflows/lint.yml | 19 +++ .github/workflows/test.yml | 133 ++++++++++++++++++ opentelemetry-process-context/pyproject.toml | 1 + .../opentelemetry/process_context/__init__.py | 8 +- .../process_context/_rs/__init__.pyi | 37 ++++- .../test-requirements.in | 2 + .../test-requirements.txt | 10 +- .../tests/test_process_context.py | 119 ++++++++++++++++ .../tests/test_profiles_context.py | 42 ------ tox.ini | 3 + 12 files changed, 369 insertions(+), 53 deletions(-) create mode 100644 .changelog/5337.added create mode 100644 opentelemetry-process-context/tests/test_process_context.py delete mode 100644 opentelemetry-process-context/tests/test_profiles_context.py diff --git a/.changelog/5337.added b/.changelog/5337.added new file mode 100644 index 00000000000..64b5a11e728 --- /dev/null +++ b/.changelog/5337.added @@ -0,0 +1 @@ +`opentelemetry-process-context`: implement process context publishing (OTEP-4719) diff --git a/.github/workflows/generate_workflows.py b/.github/workflows/generate_workflows.py index 5b53c211ebf..1f314daacf1 100644 --- a/.github/workflows/generate_workflows.py +++ b/.github/workflows/generate_workflows.py @@ -1,9 +1,11 @@ from collections import defaultdict from pathlib import Path from re import compile as re_compile +from re import fullmatch from jinja2 import Environment, FileSystemLoader from tox.config.cli.parse import get_options +from tox.config.main import Config from tox.config.sets import CoreConfigSet from tox.config.source.tox_ini import ToxIni from tox.session.state import State @@ -18,7 +20,7 @@ ) -def get_tox_envs(tox_ini_path: Path) -> list: +def get_core_config_set(tox_ini_path: Path) -> tuple[Config, CoreConfigSet]: tox_ini = ToxIni(tox_ini_path) conf = State(get_options(), []).conf @@ -40,11 +42,39 @@ def get_tox_envs(tox_ini_path: Path) -> list: ) ) - return core_config_set.load("env_list") + return conf, core_config_set + + +def get_tox_envs(tox_ini_path: Path) -> list: + return get_core_config_set(tox_ini_path)[1].load("env_list") + + +def get_env_platforms(tox_ini_path: Path) -> dict[str, str]: + conf, core_config_set = get_core_config_set(tox_ini_path) + + platforms = {} + for env_name in core_config_set.load("env_list"): + env_config_set = conf.get_env(env_name) + env_config_set.add_config( + keys=["platform"], + of_type=str, + default="", + desc="platform constraint regex", + ) + platforms[env_name] = env_config_set.load("platform") + + return platforms -def get_test_job_datas(tox_envs: list, operating_systems: list) -> list: + +def get_test_job_datas( + tox_envs: list, operating_systems: list, env_platforms: dict[str, str] +) -> list: os_alias = {"ubuntu-latest": "Ubuntu", "windows-latest": "Windows"} + os_sys_platform = { + "ubuntu-latest": "linux", + "windows-latest": "win32", + } python_version_alias = { "pypy3": "pypy-3.10", @@ -59,12 +89,17 @@ def get_test_job_datas(tox_envs: list, operating_systems: list) -> list: test_job_datas = [] for operating_system in operating_systems: + sys_platform = os_sys_platform[operating_system] + for tox_env in tox_envs: tox_test_env_match = _tox_test_env_regex.match(tox_env) if tox_test_env_match is None: continue + if (platform := env_platforms.get(tox_env, "")) and not fullmatch(platform, sys_platform): + continue + groups = tox_test_env_match.groupdict() aliased_python_version = python_version_alias[ @@ -156,7 +191,11 @@ def generate_test_workflow( tox_ini_path: Path, workflow_directory_path: Path, operating_systems ) -> None: _generate_workflow( - get_test_job_datas(get_tox_envs(tox_ini_path), operating_systems), + get_test_job_datas( + get_tox_envs(tox_ini_path), + operating_systems, + get_env_platforms(tox_ini_path), + ), "test", workflow_directory_path, ) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 27033640dc1..b24172de37b 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -423,6 +423,25 @@ jobs: - name: Run tests run: tox -e lint-opentelemetry-exporter-zipkin-json + lint-opentelemetry-process-context: + name: opentelemetry-process-context + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - name: Checkout repo @ SHA - ${{ github.sha }} + uses: actions/checkout@v4 + + - name: Set up Python 3.14 + uses: actions/setup-python@v5 + with: + python-version: "3.14" + + - name: Install tox + run: pip install tox-uv + + - name: Run tests + run: tox -e lint-opentelemetry-process-context + lint-opentelemetry-propagator-b3: name: opentelemetry-propagator-b3 runs-on: ubuntu-latest diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d562b9d8745..6cd98d119c5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -3086,6 +3086,139 @@ jobs: - name: Run tests run: tox -e pypy3-test-opentelemetry-exporter-zipkin-json -- -ra + py310-test-opentelemetry-process-context_ubuntu-latest: + name: opentelemetry-process-context 3.10 Ubuntu + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - name: Checkout repo @ SHA - ${{ github.sha }} + uses: actions/checkout@v4 + + - name: Set up Python 3.10 + uses: actions/setup-python@v5 + with: + python-version: "3.10" + + - name: Install tox + run: pip install tox-uv + + - name: Run tests + run: tox -e py310-test-opentelemetry-process-context -- -ra + + py311-test-opentelemetry-process-context_ubuntu-latest: + name: opentelemetry-process-context 3.11 Ubuntu + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - name: Checkout repo @ SHA - ${{ github.sha }} + uses: actions/checkout@v4 + + - name: Set up Python 3.11 + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install tox + run: pip install tox-uv + + - name: Run tests + run: tox -e py311-test-opentelemetry-process-context -- -ra + + py312-test-opentelemetry-process-context_ubuntu-latest: + name: opentelemetry-process-context 3.12 Ubuntu + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - name: Checkout repo @ SHA - ${{ github.sha }} + uses: actions/checkout@v4 + + - name: Set up Python 3.12 + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install tox + run: pip install tox-uv + + - name: Run tests + run: tox -e py312-test-opentelemetry-process-context -- -ra + + py313-test-opentelemetry-process-context_ubuntu-latest: + name: opentelemetry-process-context 3.13 Ubuntu + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - name: Checkout repo @ SHA - ${{ github.sha }} + uses: actions/checkout@v4 + + - name: Set up Python 3.13 + uses: actions/setup-python@v5 + with: + python-version: "3.13" + + - name: Install tox + run: pip install tox-uv + + - name: Run tests + run: tox -e py313-test-opentelemetry-process-context -- -ra + + py314-test-opentelemetry-process-context_ubuntu-latest: + name: opentelemetry-process-context 3.14 Ubuntu + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - name: Checkout repo @ SHA - ${{ github.sha }} + uses: actions/checkout@v4 + + - name: Set up Python 3.14 + uses: actions/setup-python@v5 + with: + python-version: "3.14" + + - name: Install tox + run: pip install tox-uv + + - name: Run tests + run: tox -e py314-test-opentelemetry-process-context -- -ra + + py314t-test-opentelemetry-process-context_ubuntu-latest: + name: opentelemetry-process-context 3.14t Ubuntu + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - name: Checkout repo @ SHA - ${{ github.sha }} + uses: actions/checkout@v4 + + - name: Set up Python 3.14t + uses: actions/setup-python@v5 + with: + python-version: "3.14t" + + - name: Install tox + run: pip install tox-uv + + - name: Run tests + run: tox -e py314t-test-opentelemetry-process-context -- -ra + + pypy3-test-opentelemetry-process-context_ubuntu-latest: + name: opentelemetry-process-context pypy-3.10 Ubuntu + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - name: Checkout repo @ SHA - ${{ github.sha }} + uses: actions/checkout@v4 + + - name: Set up Python pypy-3.10 + uses: actions/setup-python@v5 + with: + python-version: "pypy-3.10" + + - name: Install tox + run: pip install tox-uv + + - name: Run tests + run: tox -e pypy3-test-opentelemetry-process-context -- -ra + py310-test-opentelemetry-propagator-b3_ubuntu-latest: name: opentelemetry-propagator-b3 3.10 Ubuntu runs-on: ubuntu-latest diff --git a/opentelemetry-process-context/pyproject.toml b/opentelemetry-process-context/pyproject.toml index 17f4c1d9ccc..5935689004a 100644 --- a/opentelemetry-process-context/pyproject.toml +++ b/opentelemetry-process-context/pyproject.toml @@ -17,6 +17,7 @@ classifiers = [ "Framework :: OpenTelemetry", "Framework :: OpenTelemetry :: Profiles", "Intended Audience :: Developers", + "Operating System :: POSIX :: Linux", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", diff --git a/opentelemetry-process-context/src/opentelemetry/process_context/__init__.py b/opentelemetry-process-context/src/opentelemetry/process_context/__init__.py index 6932ef75864..41c288f8c69 100644 --- a/opentelemetry-process-context/src/opentelemetry/process_context/__init__.py +++ b/opentelemetry-process-context/src/opentelemetry/process_context/__init__.py @@ -1,6 +1,10 @@ # Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 -from opentelemetry.process_context._rs import publish_context +from opentelemetry.process_context._rs import ( + publish_context, + unpublish_context, + update_context, +) -__all__ = ["publish_context"] +__all__ = ["publish_context", "update_context", "unpublish_context"] diff --git a/opentelemetry-process-context/src/opentelemetry/process_context/_rs/__init__.pyi b/opentelemetry-process-context/src/opentelemetry/process_context/_rs/__init__.pyi index e94289db917..026ba1c1dcd 100644 --- a/opentelemetry-process-context/src/opentelemetry/process_context/_rs/__init__.pyi +++ b/opentelemetry-process-context/src/opentelemetry/process_context/_rs/__init__.pyi @@ -3,6 +3,37 @@ from opentelemetry.sdk.resources import Resource -def publish_context(resource: Resource) -> None: ... -def update_context(resource: Resource) -> None: ... -def unpublish_context() -> None: ... +def publish_context(resource: Resource) -> None: + """Publish the process context for the given resource. + + Encodes ``resource`` as a protobuf ``ProcessContext`` message and writes it + to a named memory mapping (``OTEL_CTX``) that out of process readers such + as the OpenTelemetry eBPF Profiler can discover via ``/proc//maps``. + + The context is a per-process singleton. Calling this function a second time + without an intervening :func:`unpublish_context` raises :exc:`RuntimeError`. + + :param resource: The SDK resource whose attributes are to be published. + :raises RuntimeError: If a context has already been published. + :raises OSError: If the memory mapping or clock could not be initialized. + """ + +def update_context(resource: Resource) -> None: + """Update the published process context with a new resource. + + :func:`publish_context` must be called before this function. + + :param resource: The updated SDK resource to publish. + :raises RuntimeError: If no context has been published yet. + :raises OSError: If the clock could not be read. + """ + +def unpublish_context() -> None: + """Remove the published process context. + + Zeros the publish timestamp and unmaps the ``OTEL_CTX`` memory region. + After this call returns, :func:`publish_context` may be called again. + + :raises RuntimeError: If no context has been published yet. + :raises OSError: If unmapping the memory region failed. + """ diff --git a/opentelemetry-process-context/test-requirements.in b/opentelemetry-process-context/test-requirements.in index 0a42b62d9ec..00297963bf6 100644 --- a/opentelemetry-process-context/test-requirements.in +++ b/opentelemetry-process-context/test-requirements.in @@ -4,4 +4,6 @@ pluggy==1.6.0 pytest==7.4.4 -e opentelemetry-api -e opentelemetry-sdk +-e opentelemetry-semantic-conventions +-e opentelemetry-proto -e opentelemetry-process-context diff --git a/opentelemetry-process-context/test-requirements.txt b/opentelemetry-process-context/test-requirements.txt index 1d551511d46..e67e85ce843 100644 --- a/opentelemetry-process-context/test-requirements.txt +++ b/opentelemetry-process-context/test-requirements.txt @@ -1,7 +1,5 @@ # This file was autogenerated by uv via the following command: # uv pip compile --python 3.10 --universal --resolution highest opentelemetry-process-context/test-requirements.in -o opentelemetry-process-context/test-requirements.txt --e file:///Users/lukas/Documents/github-projects/opentelemetry-python/opentelemetry-semantic-conventions - # via opentelemetry-sdk -e opentelemetry-api # via # -r opentelemetry-process-context/test-requirements.in @@ -10,10 +8,16 @@ # opentelemetry-semantic-conventions -e opentelemetry-process-context # via -r opentelemetry-process-context/test-requirements.in +-e opentelemetry-proto + # via -r opentelemetry-process-context/test-requirements.in -e opentelemetry-sdk # via # -r opentelemetry-process-context/test-requirements.in # opentelemetry-process-context +-e opentelemetry-semantic-conventions + # via + # -r opentelemetry-process-context/test-requirements.in + # opentelemetry-sdk colorama==0.4.6 ; sys_platform == 'win32' # via pytest exceptiongroup==1.3.1 ; python_full_version < '3.11' @@ -30,6 +34,8 @@ pluggy==1.6.0 # via # -r opentelemetry-process-context/test-requirements.in # pytest +protobuf==7.35.1 + # via opentelemetry-proto pytest==7.4.4 # via -r opentelemetry-process-context/test-requirements.in tomli==2.4.1 ; python_full_version < '3.11' diff --git a/opentelemetry-process-context/tests/test_process_context.py b/opentelemetry-process-context/tests/test_process_context.py new file mode 100644 index 00000000000..a96318ca52c --- /dev/null +++ b/opentelemetry-process-context/tests/test_process_context.py @@ -0,0 +1,119 @@ +# Copyright The OpenTelemetry Authors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +import struct +import subprocess +import sys +import textwrap +import unittest + +from opentelemetry.process_context import ( + publish_context, + unpublish_context, + update_context, +) +from opentelemetry.sdk.resources import Resource + + +class TestPublishContext(unittest.TestCase): + def tearDown(self): + try: + unpublish_context() + except RuntimeError: + pass + + def test_publish_context_lifecycle(self): + resource = Resource( + {"service.name": "test", "version": 1, "pi": 3.14, "active": True} + ) + self.assertIsNone(publish_context(resource)) + + with self.assertRaises(RuntimeError): + publish_context(resource) + + self.assertIsNone(update_context(resource)) + self.assertIsNone(update_context(resource)) + + self.assertIsNone(unpublish_context()) + self.assertIsNone(publish_context(resource)) + + def test_update_before_publish_raises(self): + with self.assertRaises(RuntimeError): + update_context(Resource({})) + + def test_unpublish_before_publish_raises(self): + with self.assertRaises(RuntimeError): + unpublish_context() + + def test_cross_process_memory_region(self): + """Spawn a child that publishes a fixed context; read and validate its memory region.""" + child_script = textwrap.dedent("""\ + import sys + from opentelemetry.sdk.resources import Resource + from opentelemetry.process_context._rs import publish_context + + resource = Resource({"service.name": "otel-test-service", "version": 42}) + publish_context(resource) + + sys.stdout.write("ready\\n") + sys.stdout.flush() + sys.stdin.readline() + """) + + # pylint: disable-next=consider-using-with + proc = subprocess.Popen( + [sys.executable, "-c", child_script], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + ) + try: + self.assertEqual(proc.stdout.readline(), b"ready\n") + + pid = proc.pid + + # Locate the OTEL_CTX mapping and verify the memfd name. + start_addr = None + with open(f"/proc/{pid}/maps", encoding="utf-8") as maps_file: + for maps_line in maps_file: + if "OTEL_CTX" in maps_line: + start_addr = int(maps_line.split("-")[0], 16) + self.assertIn("/memfd:OTEL_CTX", maps_line) + break + + self.assertIsNotNone( + start_addr, + f"OTEL_CTX mapping not found in /proc/{pid}/maps", + ) + + # Read the 32-byte header and the variable-length payload in one + # open so there is no TOCTOU window between the two reads. + with open(f"/proc/{pid}/mem", "rb") as mem: + mem.seek(start_addr) + header_bytes = mem.read(32) + + signature = header_bytes[0:8] + version = struct.unpack_from(" Date: Sun, 21 Jun 2026 22:33:20 -0400 Subject: [PATCH 07/16] fix formatting --- .github/workflows/generate_workflows.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/generate_workflows.py b/.github/workflows/generate_workflows.py index 1f314daacf1..549f3b1d2e2 100644 --- a/.github/workflows/generate_workflows.py +++ b/.github/workflows/generate_workflows.py @@ -97,7 +97,9 @@ def get_test_job_datas( if tox_test_env_match is None: continue - if (platform := env_platforms.get(tox_env, "")) and not fullmatch(platform, sys_platform): + if (platform := env_platforms.get(tox_env, "")) and not fullmatch( + platform, sys_platform + ): continue groups = tox_test_env_match.groupdict() From 35564391f114104f405d552156765dfe5a102674 Mon Sep 17 00:00:00 2001 From: Lukas Hering Date: Sun, 21 Jun 2026 22:52:53 -0400 Subject: [PATCH 08/16] fix codespell issues --- .codespellrc | 4 +- opentelemetry-process-context/rust/Cargo.lock | 65 +++++++++++++++++++ opentelemetry-process-context/rust/Cargo.toml | 1 + opentelemetry-process-context/rust/build.rs | 5 +- 4 files changed, 72 insertions(+), 3 deletions(-) diff --git a/.codespellrc b/.codespellrc index 788c648bc5b..981b4e224f2 100644 --- a/.codespellrc +++ b/.codespellrc @@ -1,4 +1,4 @@ [codespell] # skipping auto generated folders -skip = ./.tox,./.mypy_cache,./docs/_build,./target,*/LICENSE,./venv,.git,./opentelemetry-semantic-conventions,*-requirements*.txt -ignore-words-list = ans,ue,ot,hist,ro,astroid +skip = ./.tox,./.mypy_cache,./docs/_build,./target,*/LICENSE,./venv,.git,./opentelemetry-semantic-conventions,*-requirements*.txt,./opentelemetry-process-context/rust/target +ignore-words-list = ans,ue,ot,hist,ro,astroid,crate diff --git a/opentelemetry-process-context/rust/Cargo.lock b/opentelemetry-process-context/rust/Cargo.lock index 110a6c8ea78..2891db83fd2 100644 --- a/opentelemetry-process-context/rust/Cargo.lock +++ b/opentelemetry-process-context/rust/Cargo.lock @@ -443,6 +443,7 @@ dependencies = [ "nix", "prost", "prost-build", + "protoc-bin-vendored", "pyo3", "ureq", ] @@ -555,6 +556,70 @@ dependencies = [ "prost", ] +[[package]] +name = "protoc-bin-vendored" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1c381df33c98266b5f08186583660090a4ffa0889e76c7e9a5e175f645a67fa" +dependencies = [ + "protoc-bin-vendored-linux-aarch_64", + "protoc-bin-vendored-linux-ppcle_64", + "protoc-bin-vendored-linux-s390_64", + "protoc-bin-vendored-linux-x86_32", + "protoc-bin-vendored-linux-x86_64", + "protoc-bin-vendored-macos-aarch_64", + "protoc-bin-vendored-macos-x86_64", + "protoc-bin-vendored-win32", +] + +[[package]] +name = "protoc-bin-vendored-linux-aarch_64" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c350df4d49b5b9e3ca79f7e646fde2377b199e13cfa87320308397e1f37e1a4c" + +[[package]] +name = "protoc-bin-vendored-linux-ppcle_64" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a55a63e6c7244f19b5c6393f025017eb5d793fd5467823a099740a7a4222440c" + +[[package]] +name = "protoc-bin-vendored-linux-s390_64" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dba5565db4288e935d5330a07c264a4ee8e4a5b4a4e6f4e83fad824cc32f3b0" + +[[package]] +name = "protoc-bin-vendored-linux-x86_32" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8854774b24ee28b7868cd71dccaae8e02a2365e67a4a87a6cd11ee6cdbdf9cf5" + +[[package]] +name = "protoc-bin-vendored-linux-x86_64" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b38b07546580df720fa464ce124c4b03630a6fb83e05c336fea2a241df7e5d78" + +[[package]] +name = "protoc-bin-vendored-macos-aarch_64" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89278a9926ce312e51f1d999fee8825d324d603213344a9a706daa009f1d8092" + +[[package]] +name = "protoc-bin-vendored-macos-x86_64" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81745feda7ccfb9471d7a4de888f0652e806d5795b61480605d4943176299756" + +[[package]] +name = "protoc-bin-vendored-win32" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95067976aca6421a523e491fce939a3e65249bac4b977adee0ee9771568e8aa3" + [[package]] name = "pyo3" version = "0.28.3" diff --git a/opentelemetry-process-context/rust/Cargo.toml b/opentelemetry-process-context/rust/Cargo.toml index 71fddaf202f..765621b34b0 100644 --- a/opentelemetry-process-context/rust/Cargo.toml +++ b/opentelemetry-process-context/rust/Cargo.toml @@ -17,5 +17,6 @@ features = ["abi3-py39"] [build-dependencies] prost-build = "0.13" +protoc-bin-vendored = "3" ureq = { version = "2", features = ["tls"] } backon = "1" diff --git a/opentelemetry-process-context/rust/build.rs b/opentelemetry-process-context/rust/build.rs index 931d179824f..7747b717758 100644 --- a/opentelemetry-process-context/rust/build.rs +++ b/opentelemetry-process-context/rust/build.rs @@ -39,5 +39,8 @@ fn main() { let process_context_proto = proto_root .join("opentelemetry/proto/processcontext/v1development/process_context.proto"); - prost_build::compile_protos(&[process_context_proto], &[proto_root]).unwrap(); + prost_build::Config::new() + .protoc_executable(protoc_bin_vendored::protoc_bin_path().unwrap()) + .compile_protos(&[process_context_proto], &[proto_root]) + .unwrap(); } From 9e50fed5a63fef5f233e4ae4ed2f879525f021a4 Mon Sep 17 00:00:00 2001 From: Lukas Hering Date: Sun, 21 Jun 2026 23:10:23 -0400 Subject: [PATCH 09/16] fix test assertions and remove pypy3 --- opentelemetry-process-context/tests/test_process_context.py | 2 +- tox.ini | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/opentelemetry-process-context/tests/test_process_context.py b/opentelemetry-process-context/tests/test_process_context.py index a96318ca52c..d3dd1b49829 100644 --- a/opentelemetry-process-context/tests/test_process_context.py +++ b/opentelemetry-process-context/tests/test_process_context.py @@ -79,7 +79,7 @@ def test_cross_process_memory_region(self): for maps_line in maps_file: if "OTEL_CTX" in maps_line: start_addr = int(maps_line.split("-")[0], 16) - self.assertIn("/memfd:OTEL_CTX", maps_line) + self.assertIn(":OTEL_CTX", maps_line) break self.assertIsNotNone( diff --git a/tox.ini b/tox.ini index 5a2022f62ea..911bb9d0a89 100644 --- a/tox.ini +++ b/tox.ini @@ -97,7 +97,7 @@ envlist = lint-opentelemetry-exporter-zipkin-json py3{10,11,12,13,14,14t}-test-opentelemetry-process-context - pypy3-test-opentelemetry-process-context + ; intentionally excluded from pypy3 (can't build) lint-opentelemetry-process-context py3{10,11,12,13,14,14t}-test-opentelemetry-propagator-b3 From f2031c30794405ee49794c6a33a289cc77a115a2 Mon Sep 17 00:00:00 2001 From: Lukas Hering Date: Sun, 21 Jun 2026 23:24:22 -0400 Subject: [PATCH 10/16] rerun generate-workflows --- .github/workflows/test.yml | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6cd98d119c5..4219292b92c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -3200,25 +3200,6 @@ jobs: - name: Run tests run: tox -e py314t-test-opentelemetry-process-context -- -ra - pypy3-test-opentelemetry-process-context_ubuntu-latest: - name: opentelemetry-process-context pypy-3.10 Ubuntu - runs-on: ubuntu-latest - timeout-minutes: 30 - steps: - - name: Checkout repo @ SHA - ${{ github.sha }} - uses: actions/checkout@v4 - - - name: Set up Python pypy-3.10 - uses: actions/setup-python@v5 - with: - python-version: "pypy-3.10" - - - name: Install tox - run: pip install tox-uv - - - name: Run tests - run: tox -e pypy3-test-opentelemetry-process-context -- -ra - py310-test-opentelemetry-propagator-b3_ubuntu-latest: name: opentelemetry-propagator-b3 3.10 Ubuntu runs-on: ubuntu-latest From 5d169fbd20f7fc1b13ff553d1e3d6d2827c6aebb Mon Sep 17 00:00:00 2001 From: Lukas Hering Date: Sun, 21 Jun 2026 23:25:44 -0400 Subject: [PATCH 11/16] remove process context tests from typecheck --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 2c2b7e9bd9c..070a8b825ff 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -152,6 +152,7 @@ exclude = [ "exporter/opentelemetry-exporter-otlp-proto-http/tests", "exporter/opentelemetry-exporter-otlp-json-common/tests", "exporter/opentelemetry-exporter-otlp-json-common/benchmarks", + "opentelemetry-process-context/tests" ] # When packages are correct typed add them to the strict list From 74a1c4079fd1f3e414a1dbf92ae03aa0caa614b0 Mon Sep 17 00:00:00 2001 From: Lukas Hering Date: Sun, 21 Jun 2026 23:35:20 -0400 Subject: [PATCH 12/16] update release workflow --- .github/workflows/release.yml | 75 +++++++++++++++++++++++++++++++++-- 1 file changed, 71 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3179fc3486e..1b739f19462 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,7 +6,64 @@ permissions: contents: read jobs: + build-process-context-wheels: + name: process-context wheels (${{ matrix.platform.target }} ${{ matrix.platform.manylinux }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + platform: + # manylinux + - { target: x86_64, manylinux: auto } + - { target: x86, manylinux: auto } + - { target: aarch64, manylinux: auto } + - { target: armv7, manylinux: auto } + - { target: ppc64le, manylinux: auto } + - { target: s390x, manylinux: auto } + # musllinux + - { target: x86_64, manylinux: musllinux_1_2 } + - { target: x86, manylinux: musllinux_1_2 } + - { target: aarch64, manylinux: musllinux_1_2 } + - { target: armv7, manylinux: musllinux_1_2 } + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + # opentelemetry-process-context is a maturin/PyO3 abi3 extension, its Cargo + # manifest lives in the rust/ subdirectory, so --manifest-path is required. + - name: Build wheels + uses: PyO3/maturin-action@fdd104694b233054d6e985e29b3af9f126af7ac6 # v1.9.0 + with: + working-directory: opentelemetry-process-context + target: ${{ matrix.platform.target }} + manylinux: ${{ matrix.platform.manylinux }} + args: --release --out dist --manifest-path rust/Cargo.toml + sccache: 'true' + + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: process-context-wheels-${{ matrix.platform.target }}-${{ matrix.platform.manylinux }} + path: opentelemetry-process-context/dist + + build-process-context-sdist: + name: process-context sdist + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Build sdist + uses: PyO3/maturin-action@fdd104694b233054d6e985e29b3af9f126af7ac6 # v1.9.0 + with: + working-directory: opentelemetry-process-context + command: sdist + args: --out dist --manifest-path rust/Cargo.toml + + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: process-context-sdist + path: opentelemetry-process-context/dist + release: + needs: [build-process-context-wheels, build-process-context-sdist] permissions: contents: write # required for creating GitHub releases runs-on: ubuntu-latest @@ -17,7 +74,7 @@ jobs: exit 1 fi - - uses: actions/checkout@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Install toml run: pip install toml @@ -64,21 +121,31 @@ jobs: # check out main branch to verify there won't be problems with merging the change log # at the end of this workflow - - uses: actions/checkout@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: ref: main # back to the release branch - - uses: actions/checkout@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 # next few steps publish to pypi - - uses: actions/setup-python@v5 + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 with: python-version: '3.10' - name: Build wheels run: ./scripts/build.sh + # Native wheels + sdist for opentelemetry-process-context, built by the + # build-process-context-* jobs above, are merged into dist/ so the twine + # uploads below publish them alongside the pure-Python packages. + - name: Download process-context wheels and sdist + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + pattern: process-context-* + merge-multiple: true + path: dist + - name: Install twine run: | pip install twine From 23f4267533ecd00349b269516f0c5ea18e5c9ed4 Mon Sep 17 00:00:00 2001 From: Lukas Hering Date: Sun, 21 Jun 2026 23:38:12 -0400 Subject: [PATCH 13/16] fix workflow bugs --- .github/workflows/release.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1b739f19462..5aa23c37e65 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -29,20 +29,21 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 # opentelemetry-process-context is a maturin/PyO3 abi3 extension, its Cargo - # manifest lives in the rust/ subdirectory, so --manifest-path is required. + # manifest lives in the rust/ subdirectory. maturin-action runs from the + # repo root and has no working-directory input, so point --manifest-path at + # the full path; maturin locates pyproject.toml by walking up from there. - name: Build wheels uses: PyO3/maturin-action@fdd104694b233054d6e985e29b3af9f126af7ac6 # v1.9.0 with: - working-directory: opentelemetry-process-context target: ${{ matrix.platform.target }} manylinux: ${{ matrix.platform.manylinux }} - args: --release --out dist --manifest-path rust/Cargo.toml + args: --release --out dist --manifest-path opentelemetry-process-context/rust/Cargo.toml sccache: 'true' - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: process-context-wheels-${{ matrix.platform.target }}-${{ matrix.platform.manylinux }} - path: opentelemetry-process-context/dist + path: dist build-process-context-sdist: name: process-context sdist @@ -53,14 +54,13 @@ jobs: - name: Build sdist uses: PyO3/maturin-action@fdd104694b233054d6e985e29b3af9f126af7ac6 # v1.9.0 with: - working-directory: opentelemetry-process-context command: sdist - args: --out dist --manifest-path rust/Cargo.toml + args: --out dist --manifest-path opentelemetry-process-context/rust/Cargo.toml - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: process-context-sdist - path: opentelemetry-process-context/dist + path: dist release: needs: [build-process-context-wheels, build-process-context-sdist] From bd309c268f36163470fe034bcfd89082ac2ded72 Mon Sep 17 00:00:00 2001 From: Lukas Hering Date: Sun, 21 Jun 2026 23:41:16 -0400 Subject: [PATCH 14/16] update maturin action commit pin --- .github/workflows/release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5aa23c37e65..4bb1193eb24 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -33,7 +33,7 @@ jobs: # repo root and has no working-directory input, so point --manifest-path at # the full path; maturin locates pyproject.toml by walking up from there. - name: Build wheels - uses: PyO3/maturin-action@fdd104694b233054d6e985e29b3af9f126af7ac6 # v1.9.0 + uses: PyO3/maturin-action@3e2bdf6ba6453a61e649744019b8a2d906c7eb38 # v1.51.0 with: target: ${{ matrix.platform.target }} manylinux: ${{ matrix.platform.manylinux }} @@ -52,7 +52,7 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Build sdist - uses: PyO3/maturin-action@fdd104694b233054d6e985e29b3af9f126af7ac6 # v1.9.0 + uses: PyO3/maturin-action@3e2bdf6ba6453a61e649744019b8a2d906c7eb38 # v1.51.0 with: command: sdist args: --out dist --manifest-path opentelemetry-process-context/rust/Cargo.toml From 8b4e7b9a23cdf1592fa3e0dca821c7194eaa7f78 Mon Sep 17 00:00:00 2001 From: Lukas Hering Date: Sun, 21 Jun 2026 23:53:33 -0400 Subject: [PATCH 15/16] small tweak --- .github/workflows/release.yml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4bb1193eb24..82001a9aac0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -28,10 +28,6 @@ jobs: steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - # opentelemetry-process-context is a maturin/PyO3 abi3 extension, its Cargo - # manifest lives in the rust/ subdirectory. maturin-action runs from the - # repo root and has no working-directory input, so point --manifest-path at - # the full path; maturin locates pyproject.toml by walking up from there. - name: Build wheels uses: PyO3/maturin-action@3e2bdf6ba6453a61e649744019b8a2d906c7eb38 # v1.51.0 with: @@ -137,8 +133,8 @@ jobs: run: ./scripts/build.sh # Native wheels + sdist for opentelemetry-process-context, built by the - # build-process-context-* jobs above, are merged into dist/ so the twine - # uploads below publish them alongside the pure-Python packages. + # build-process-context-* jobs above are merged into dist/ so the twine + # uploads below publish them alongside the pure Python packages. - name: Download process-context wheels and sdist uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: From 1f4e4522e721ca788342f407252098627fe8b529 Mon Sep 17 00:00:00 2001 From: Lukas Hering Date: Sun, 21 Jun 2026 23:57:31 -0400 Subject: [PATCH 16/16] small tweak to tests --- .../tests/test_process_context.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/opentelemetry-process-context/tests/test_process_context.py b/opentelemetry-process-context/tests/test_process_context.py index d3dd1b49829..a6c137fc579 100644 --- a/opentelemetry-process-context/tests/test_process_context.py +++ b/opentelemetry-process-context/tests/test_process_context.py @@ -48,7 +48,7 @@ def test_unpublish_before_publish_raises(self): unpublish_context() def test_cross_process_memory_region(self): - """Spawn a child that publishes a fixed context; read and validate its memory region.""" + """Spawn a child that publishes a fixed context and read/validate its memory region.""" child_script = textwrap.dedent("""\ import sys from opentelemetry.sdk.resources import Resource @@ -73,7 +73,7 @@ def test_cross_process_memory_region(self): pid = proc.pid - # Locate the OTEL_CTX mapping and verify the memfd name. + # Locate the OTEL_CTX mapping and verify the name. start_addr = None with open(f"/proc/{pid}/maps", encoding="utf-8") as maps_file: for maps_line in maps_file: @@ -87,8 +87,6 @@ def test_cross_process_memory_region(self): f"OTEL_CTX mapping not found in /proc/{pid}/maps", ) - # Read the 32-byte header and the variable-length payload in one - # open so there is no TOCTOU window between the two reads. with open(f"/proc/{pid}/mem", "rb") as mem: mem.seek(start_addr) header_bytes = mem.read(32) @@ -107,9 +105,6 @@ def test_cross_process_memory_region(self): self.assertGreater(payload_size, 0) self.assertGreater(timestamp_ns, 0) self.assertNotEqual(payload_ptr, 0) - - # Protobuf string fields are length-prefixed raw UTF-8, so the - # attribute key and value appear verbatim in the serialised payload. self.assertIn(b"service.name", payload_bytes) self.assertIn(b"otel-test-service", payload_bytes)