diff --git a/ThirdPartyNotices.txt b/ThirdPartyNotices.txt index fbd9f9a95f601..9f9f3515717f0 100644 --- a/ThirdPartyNotices.txt +++ b/ThirdPartyNotices.txt @@ -6119,3 +6119,213 @@ Redistribution and use in source and binary forms, with or without modification, 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +_____ + +microsoft/cpp_client_telemetry, https://github.com/microsoft/cpp_client_telemetry/ + +Apache License + +Copyright (c) Microsoft Corporation. All rights reserved. + + 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. \ No newline at end of file diff --git a/build.sh b/build.sh index bf799ac8b7211..37873d0b76b7e 100755 --- a/build.sh +++ b/build.sh @@ -18,4 +18,12 @@ elif [[ "$*" == *"--android"* ]]; then DIR_OS="Android" fi -python3 $DIR/tools/ci_build/build.py --build_dir $DIR/build/$DIR_OS "$@" +# Telemetry uses the 1DS SDK, which is not supported for WebAssembly/Emscripten builds. +# Only request it for native builds so that `./build.sh --build_wasm` keeps working without +# the user having to override the wrapper's default. +TELEMETRY_ARG="--use_telemetry" +if [[ "$*" == *"--build_wasm"* ]]; then + TELEMETRY_ARG="" +fi + +python3 $DIR/tools/ci_build/build.py --build_dir $DIR/build/$DIR_OS $TELEMETRY_ARG "$@" diff --git a/cgmanifests/cgmanifest.json b/cgmanifests/cgmanifest.json index bf889e9fb61a8..c030d5cb66447 100644 --- a/cgmanifests/cgmanifest.json +++ b/cgmanifests/cgmanifest.json @@ -345,6 +345,16 @@ }, "comments": "python-pillow. Implementation logic for anti-aliasing copied by Resize CPU kernel." } + }, + { + "component": { + "type": "git", + "git": { + "commitHash": "edf33f80035575b82f1fafd5f9bd0dc0d2064e94", + "repositoryUrl": "https://github.com/microsoft/cpp_client_telemetry.git" + }, + "comments": "1DS SDK (cpp_client_telemetry) for cross-platform telemetry on non-Windows platforms (macOS, Linux, Android, iOS)." + } } ], "Version": 1 diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt index 28d6aa83c5343..8520b8a0b9432 100644 --- a/cmake/CMakeLists.txt +++ b/cmake/CMakeLists.txt @@ -143,6 +143,19 @@ option(onnxruntime_USE_WINML "Build with WinML support" OFF) option(onnxruntime_USE_ACL "Build with ACL support" OFF) option(onnxruntime_ENABLE_INSTRUMENT "Enable Instrument with Event Tracing for Windows (ETW)" OFF) option(onnxruntime_USE_TELEMETRY "Build with Telemetry" OFF) +# Optional 1DS ingestion token for non-Windows telemetry. Leave empty to compile in the default +# throwaway token from core/platform/posix/telemetry.cc; official pipelines override it at compile +# time by setting this to the real token (injected from a pipeline secret). +set(onnxruntime_1DS_TENANT_TOKEN "" CACHE STRING "Override the compiled-in 1DS telemetry ingestion token (non-Windows)") +# When building non-Windows telemetry, optionally build the 1DS SDK (cpp-client-telemetry) as a shared +# library (libmat.so) instead of statically linking it into libonnxruntime. This lets several binaries +# that link the same SDK (for example onnxruntime and onnxruntime-genai shipped together) share a single +# copy of the SDK and its transitive TLS/HTTP stack, paying its footprint once instead of per-binary. Each +# binary still runs its own named 1DS LogManager (onnxruntime and onnxruntime-genai use distinct factory +# hosts), so the shared library shares only the SDK code, not telemetry state. The SDK's own dependencies +# (OpenSSL/curl/sqlite3/zlib) stay static inside libmat.so so it remains self-contained. Requires the +# vcpkg cpp-client-telemetry port. Off by default: a standalone onnxruntime is smaller/simpler fully static. +cmake_dependent_option(onnxruntime_TELEMETRY_SHARED_SDK "Build the non-Windows 1DS telemetry SDK as a shared library so multiple binaries can share one copy" OFF "onnxruntime_USE_TELEMETRY;NOT WIN32" OFF) cmake_dependent_option(onnxruntime_USE_MIMALLOC "Override new/delete and arena allocator with mimalloc" OFF "WIN32;NOT onnxruntime_USE_CUDA;NOT onnxruntime_USE_OPENVINO" OFF) option(onnxruntime_USE_CANN "Build with CANN support" OFF) option(onnxruntime_USE_XNNPACK "Build with XNNPACK support. Provides an alternative math library on ARM, WebAssembly and x86." OFF) @@ -589,6 +602,9 @@ set(ONNXRUNTIME_INCLUDE_DIR ${REPO_ROOT}/include/onnxruntime) list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/external) include(external/onnxruntime_external_deps.cmake) +# 1DS telemetry integration for non-Windows platforms (must come after external deps) +include(onnxruntime_1ds_telemetry.cmake) + set(ORT_WARNING_FLAGS) if (WIN32) # class needs to have dll-interface to be used by clients diff --git a/cmake/deps.txt b/cmake/deps.txt index e303ccd9f8a98..0388c09d3fa7f 100644 --- a/cmake/deps.txt +++ b/cmake/deps.txt @@ -64,3 +64,5 @@ kleidiai;https://github.com/ARM-software/kleidiai/archive/refs/tags/v1.20.0.tar. # this entry will be updated to use refs/tags/ instead of the raw commit hash. kleidiai-qmx;https://github.com/qualcomm/kleidiai/archive/2f10c9a8d32f81ffeeb6d4885a29cc35d2b0da87.zip;5e855730a2d69057a569f43dd7532db3b2d2a05c vulkan_headers;https://codeload.github.com/KhronosGroup/Vulkan-Headers/tar.gz/refs/tags/v1.4.344;57bc528ef7c4a3f7bfbb59e64a187e3734bd29d8 +# cpp_client_telemetry (1DS SDK) for cross-platform telemetry on non-Windows platforms +cpp_client_telemetry;https://github.com/microsoft/cpp_client_telemetry/archive/refs/tags/v3.10.173.1.zip;d35a0f0595114304ed8c308e720671032f9586d1 diff --git a/cmake/external/onnxruntime_external_deps.cmake b/cmake/external/onnxruntime_external_deps.cmake index 1a1e4921a41e6..01c60dcd3dfaf 100644 --- a/cmake/external/onnxruntime_external_deps.cmake +++ b/cmake/external/onnxruntime_external_deps.cmake @@ -7,8 +7,9 @@ include(external/helper_functions.cmake) file(STRINGS deps.txt ONNXRUNTIME_DEPS_LIST) foreach(ONNXRUNTIME_DEP IN LISTS ONNXRUNTIME_DEPS_LIST) - # Lines start with "#" are comments - if(NOT ONNXRUNTIME_DEP MATCHES "^#") + # Lines start with "#" are comments, so skip them. + # cpp_client_telemetry is only needed for telemetry on non-Windows platforms, so skip if telemetry is not enabled or it's Windows platform. + if((NOT ONNXRUNTIME_DEP MATCHES "^#") AND ((NOT ONNXRUNTIME_DEP MATCHES "^cpp_client_telemetry") OR (onnxruntime_USE_TELEMETRY AND NOT WIN32))) # The first column is name list(POP_FRONT ONNXRUNTIME_DEP ONNXRUNTIME_DEP_NAME) # The second column is URL @@ -900,6 +901,174 @@ if(onnxruntime_USE_SNPE) list(APPEND onnxruntime_EXTERNAL_LIBRARIES ${SNPE_NN_LIBS}) endif() +# 1DS SDK (cpp_client_telemetry) for cross-platform telemetry on non-Windows platforms +if(onnxruntime_USE_TELEMETRY AND NOT WIN32) + if(CMAKE_SYSTEM_NAME STREQUAL "Emscripten") + message(FATAL_ERROR "onnxruntime_USE_TELEMETRY is not supported for WebAssembly/Emscripten builds: " + "the 1DS telemetry SDK is excluded on Emscripten. Disable telemetry for WASM builds.") + endif() + if(onnxruntime_USE_VCPKG) + # Consume the 1DS SDK from the vcpkg port "cpp-client-telemetry", which exposes the + # MSTelemetry::mat target with its include directories and transitive dependencies + # (curl/nlohmann-json/sqlite3/zlib) already wired up via vcpkg. None of the FetchContent + # workarounds below are needed on this path. + find_package(MSTelemetry CONFIG REQUIRED) + else() + set(BUILD_UNIT_TESTS_SAVED "${BUILD_UNIT_TESTS}") + set(BUILD_FUNC_TESTS_SAVED "${BUILD_FUNC_TESTS}") + set(BUILD_SAMPLES_SAVED "${BUILD_SAMPLES}") + set(BUILD_SHARED_LIBS_SAVED "${BUILD_SHARED_LIBS}") + set(BUILD_UNIT_TESTS OFF CACHE BOOL "Disable 1DS SDK unit tests" FORCE) + set(BUILD_FUNC_TESTS OFF CACHE BOOL "Disable 1DS SDK functional tests" FORCE) + set(BUILD_SAMPLES OFF CACHE BOOL "Disable 1DS SDK samples" FORCE) + # Build 1DS SDK as static library + set(BUILD_SHARED_LIBS OFF CACHE BOOL "Build 1DS SDK as static library" FORCE) + # Disable optional 1DS modules that may not have source in the release archive + set(BUILD_PRIVACYGUARD OFF CACHE BOOL "Disable 1DS privacy guard module" FORCE) + set(BUILD_SANITIZER OFF CACHE BOOL "Disable 1DS sanitizer module" FORCE) + # Disable ObjC and Swift wrappers - we use the C++ API directly + set(BUILD_OBJC_WRAPPER OFF CACHE BOOL "Disable 1DS ObjC wrapper" FORCE) + set(BUILD_SWIFT_WRAPPER OFF CACHE BOOL "Disable 1DS Swift wrapper" FORCE) + + # The 1DS SDK CMakeLists.txt expects specific variables on Apple platforms. + # For iOS: We set BUILD_IOS=YES so the 1DS SDK skips its CURL dependency + # (iOS uses NSURLSession instead). We disable FORCE_RESET_OSX_DEPLOYMENT_TARGET + # and provide IOS_DEPLOYMENT_TARGET to prevent the SDK from clearing cmake's + # deployment target or adding empty -miphoneos-version-min flags. + # The SDK's internal xcodebuild call may fail (license issues), but cmake's + # toolchain already provides the correct sysroot via CMAKE_OSX_SYSROOT. + if(APPLE) + if(CMAKE_SYSTEM_NAME STREQUAL "iOS") + set(BUILD_IOS YES CACHE BOOL "Tell 1DS SDK this is an iOS build" FORCE) + set(FORCE_RESET_OSX_DEPLOYMENT_TARGET NO CACHE BOOL "Don't let 1DS SDK clear deployment target" FORCE) + if(NOT DEFINED IOS_DEPLOYMENT_TARGET) + set(IOS_DEPLOYMENT_TARGET "${CMAKE_OSX_DEPLOYMENT_TARGET}" CACHE STRING "iOS deployment target for 1DS SDK" FORCE) + endif() + if(NOT DEFINED IOS_ARCH) + set(IOS_ARCH "${CMAKE_OSX_ARCHITECTURES}" CACHE STRING "iOS architecture for 1DS SDK" FORCE) + if(NOT IOS_ARCH) + set(IOS_ARCH "arm64" CACHE STRING "iOS architecture for 1DS SDK" FORCE) + endif() + endif() + if(NOT DEFINED IOS_PLAT) + string(TOLOWER "${CMAKE_OSX_SYSROOT}" IOS_SYSROOT_LOWER) + if(IOS_SYSROOT_LOWER MATCHES "iphonesimulator") + set(IOS_PLAT "iphonesimulator" CACHE STRING "iOS platform for 1DS SDK" FORCE) + else() + set(IOS_PLAT "iphoneos" CACHE STRING "iOS platform for 1DS SDK" FORCE) + endif() + endif() + else() + if(NOT DEFINED MAC_ARCH) + set(MAC_ARCH "${CMAKE_OSX_ARCHITECTURES}" CACHE STRING "Architecture for 1DS SDK on macOS" FORCE) + if(NOT MAC_ARCH) + set(MAC_ARCH "${CMAKE_SYSTEM_PROCESSOR}" CACHE STRING "Architecture for 1DS SDK on macOS" FORCE) + endif() + endif() + endif() + endif() + + onnxruntime_fetchcontent_declare( + cpp_client_telemetry + URL ${DEP_URL_cpp_client_telemetry} + URL_HASH SHA1=${DEP_SHA1_cpp_client_telemetry} + EXCLUDE_FROM_ALL + ) + onnxruntime_fetchcontent_makeavailable(cpp_client_telemetry) + + # cpp_client_telemetry's CMakeLists.txt uses include_directories(${CMAKE_SOURCE_DIR}) to find + # its bundled nlohmann/, sqlite/, and zlib/ headers. When built via FetchContent, CMAKE_SOURCE_DIR + # points to ORT's root instead. Fix by adding the actual source dir as an include path. + if(TARGET mat) + target_include_directories(mat PRIVATE ${cpp_client_telemetry_SOURCE_DIR}) + # On iOS we ship the SDK's bundled sqlite3/zlib headers and pair them with a bundled + # zlib target below, so the vendored symbol-renaming `act_z_*` ABI is consistent. + # On macOS the system / (resolved via /usr/local/include from + # lib/CMakeLists.txt) is the right header to pair with the system `z` / `sqlite3` + # targets that the SDK imports; adding the vendored headers there would produce + # an `act_z_*` compile/link mismatch against system libz. + if(CMAKE_SYSTEM_NAME STREQUAL "iOS") + target_include_directories(mat PRIVATE ${cpp_client_telemetry_SOURCE_DIR}/sqlite) + target_include_directories(mat PRIVATE ${cpp_client_telemetry_SOURCE_DIR}/zlib) + endif() + # ORT enables -ffast-math globally, which conflicts with + # std::numeric_limits::infinity() in the 1DS SDK's bundled nlohmann/json.hpp. + # Also suppress warnings in the 1DS SDK code that ORT treats as errors. + target_compile_options(mat PRIVATE + -fno-finite-math-only + -Wno-unused-const-variable + $<$:-Wno-reorder> + $<$:-Wno-reorder-ctor> + ) + # The vendored zlib headers always prefix exported symbols via names.h (act_z_*), + # so iOS cannot link mat against the system zlib. Mirror the SDK's Android build + # and provide a bundled zlib target for ORT's FetchContent build. + if(CMAKE_SYSTEM_NAME STREQUAL "iOS" AND NOT TARGET onnxruntime_mat_zlib_bundled) + add_library(onnxruntime_mat_zlib_bundled STATIC + "${cpp_client_telemetry_SOURCE_DIR}/zlib/adler32.c" + "${cpp_client_telemetry_SOURCE_DIR}/zlib/compress.c" + "${cpp_client_telemetry_SOURCE_DIR}/zlib/crc32.c" + "${cpp_client_telemetry_SOURCE_DIR}/zlib/deflate.c" + "${cpp_client_telemetry_SOURCE_DIR}/zlib/gzclose.c" + "${cpp_client_telemetry_SOURCE_DIR}/zlib/gzlib.c" + "${cpp_client_telemetry_SOURCE_DIR}/zlib/gzread.c" + "${cpp_client_telemetry_SOURCE_DIR}/zlib/gzwrite.c" + "${cpp_client_telemetry_SOURCE_DIR}/zlib/infback.c" + "${cpp_client_telemetry_SOURCE_DIR}/zlib/inffast.c" + "${cpp_client_telemetry_SOURCE_DIR}/zlib/inflate.c" + "${cpp_client_telemetry_SOURCE_DIR}/zlib/inftrees.c" + "${cpp_client_telemetry_SOURCE_DIR}/zlib/trees.c" + "${cpp_client_telemetry_SOURCE_DIR}/zlib/uncompr.c" + "${cpp_client_telemetry_SOURCE_DIR}/zlib/zutil.c" + ) + target_include_directories(onnxruntime_mat_zlib_bundled PUBLIC "${cpp_client_telemetry_SOURCE_DIR}/zlib") + target_compile_options(onnxruntime_mat_zlib_bundled PRIVATE + -Wno-strict-prototypes + -Wno-deprecated-non-prototype + -Wno-implicit-function-declaration + ) + target_link_libraries(mat PUBLIC onnxruntime_mat_zlib_bundled) + endif() + # The 1DS SDK's iOS path calls xcodebuild to find the sysroot, which can + # fail (license not accepted, missing tools) and leave CMAKE_OSX_SYSROOT + # empty in its scope. Force the correct sysroot via compile options. + if(CMAKE_SYSTEM_NAME STREQUAL "iOS" AND CMAKE_OSX_SYSROOT) + target_compile_options(mat PRIVATE "-isysroot" "${CMAKE_OSX_SYSROOT}") + endif() + endif() + + # The 1DS SDK creates GLOBAL imported targets 'z' and 'sqlite3' without setting + # IMPORTED_LOCATION, which causes link errors on cross-compile. For Android, + # the 1DS CMake now builds from bundled source. For other platforms, resolve + # the imported targets if possible. + if(NOT ANDROID) + if(TARGET z) + get_target_property(_z_loc z IMPORTED_LOCATION) + if(NOT _z_loc OR _z_loc STREQUAL "_z_loc-NOTFOUND") + find_library(_z_lib z) + if(_z_lib) + set_target_properties(z PROPERTIES IMPORTED_LOCATION "${_z_lib}") + endif() + endif() + endif() + if(TARGET sqlite3) + get_target_property(_sqlite3_loc sqlite3 IMPORTED_LOCATION) + if(NOT _sqlite3_loc OR _sqlite3_loc STREQUAL "_sqlite3_loc-NOTFOUND") + find_library(_sqlite3_lib sqlite3) + if(_sqlite3_lib) + set_target_properties(sqlite3 PROPERTIES IMPORTED_LOCATION "${_sqlite3_lib}") + endif() + endif() + endif() + endif() + + set(BUILD_UNIT_TESTS "${BUILD_UNIT_TESTS_SAVED}" CACHE BOOL "" FORCE) + set(BUILD_FUNC_TESTS "${BUILD_FUNC_TESTS_SAVED}" CACHE BOOL "" FORCE) + set(BUILD_SAMPLES "${BUILD_SAMPLES_SAVED}" CACHE BOOL "" FORCE) + set(BUILD_SHARED_LIBS "${BUILD_SHARED_LIBS_SAVED}" CACHE BOOL "" FORCE) + endif() +endif() + FILE(TO_NATIVE_PATH ${CMAKE_BINARY_DIR} ORT_BINARY_DIR) FILE(TO_NATIVE_PATH ${PROJECT_SOURCE_DIR} ORT_SOURCE_DIR) diff --git a/cmake/onnxruntime_1ds_telemetry.cmake b/cmake/onnxruntime_1ds_telemetry.cmake new file mode 100644 index 0000000000000..bde7956d8396c --- /dev/null +++ b/cmake/onnxruntime_1ds_telemetry.cmake @@ -0,0 +1,34 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +# This file handles telemetry integration for non-Windows platforms +# (macOS, Linux, Android, iOS) using the 1DS SDK (cpp_client_telemetry). +# The SDK is provided either by the vcpkg port "cpp-client-telemetry" (target +# MSTelemetry::mat) or fetched via FetchContent (target mat) in +# onnxruntime_external_deps.cmake. + +if(onnxruntime_USE_TELEMETRY AND NOT WIN32) + if(NOT TARGET mat AND NOT TARGET MSTelemetry::mat) + message(FATAL_ERROR "Telemetry enabled for non-Windows but no 1DS SDK target " + "('mat' or 'MSTelemetry::mat') was found. Ensure cpp_client_telemetry " + "is provided via the vcpkg port or fetched in onnxruntime_external_deps.cmake.") + endif() + + message(STATUS "Enabling 1DS telemetry for non-Windows platforms") + + # USE_1DS_TELEMETRY is defined on the onnxruntime_common target in onnxruntime_common.cmake + # (its only consumer is core/platform/posix/env.cc), so it is not added globally here. + + # Platform-specific status messages + if(APPLE) + if(CMAKE_SYSTEM_NAME STREQUAL "iOS") + message(STATUS " Platform: iOS") + else() + message(STATUS " Platform: macOS") + endif() + elseif(ANDROID) + message(STATUS " Platform: Android") + elseif(UNIX) + message(STATUS " Platform: Linux") + endif() +endif() diff --git a/cmake/onnxruntime_common.cmake b/cmake/onnxruntime_common.cmake index b081e22e8b3f4..000757761d03f 100644 --- a/cmake/onnxruntime_common.cmake +++ b/cmake/onnxruntime_common.cmake @@ -55,6 +55,16 @@ else() "${ONNXRUNTIME_ROOT}/core/platform/posix/stacktrace.cc" ) + # Telemetry for non-Windows platforms (enabled by USE_TELEMETRY) + if (onnxruntime_USE_TELEMETRY) + list(APPEND onnxruntime_common_src_patterns + "${ONNXRUNTIME_ROOT}/core/platform/posix/device_id.h" + "${ONNXRUNTIME_ROOT}/core/platform/posix/device_id.cc" + "${ONNXRUNTIME_ROOT}/core/platform/posix/telemetry.h" + "${ONNXRUNTIME_ROOT}/core/platform/posix/telemetry.cc" + ) + endif() + # logging files if (onnxruntime_USE_SYSLOG) list(APPEND onnxruntime_common_src_patterns @@ -129,6 +139,13 @@ if(WIN32) set_property(TARGET onnxruntime_common PROPERTY CXX_STANDARD 23) target_compile_options(onnxruntime_common PRIVATE "/Zc:char8_t-") endif() + # windows/telemetry.cc's svchost service-name fallback uses CommandLineToArgvW (shell32), which is + # only compiled on the desktop partition (guarded with WINAPI_PARTITION_DESKTOP there). Restrict the + # explicit shell32 link to desktop Windows: GDK lists shell32.lib in nodefault_libs (excluded via + # /NODEFAULTLIB), and non-desktop partitions (UWP/WindowsStore) neither use nor ship it. + if(NOT GDK_PLATFORM AND NOT CMAKE_SYSTEM_NAME STREQUAL "WindowsStore") + target_link_libraries(onnxruntime_common PRIVATE shell32) + endif() endif() if(NOT WIN32 AND NOT APPLE AND NOT ANDROID AND CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64") @@ -139,7 +156,26 @@ if(NOT WIN32 AND NOT APPLE AND NOT ANDROID AND CMAKE_SYSTEM_PROCESSOR MATCHES "x endif() if (onnxruntime_USE_TELEMETRY) - set_target_properties(onnxruntime_common PROPERTIES COMPILE_FLAGS "/FI${ONNXRUNTIME_INCLUDE_DIR}/core/platform/windows/TraceLoggingConfigPrivate.h") + if(WIN32) + set_target_properties(onnxruntime_common PROPERTIES COMPILE_FLAGS "/FI${ONNXRUNTIME_INCLUDE_DIR}/core/platform/windows/TraceLoggingConfigPrivate.h") + else() + target_compile_definitions(onnxruntime_common PRIVATE USE_1DS_TELEMETRY) + # The optional tenant-token override is emitted into a generated header in the build tree rather + # than onto the compiler command line, so an injected token (sourced from a CI secret) does not + # leak into compile_commands.json or build logs. DIY builds leave onnxruntime_1DS_TENANT_TOKEN + # empty, so the header defines nothing and telemetry.cc uses its throwaway default. + if(onnxruntime_1DS_TENANT_TOKEN) + set(ONNXRUNTIME_1DS_TENANT_TOKEN_DEFINE "#define ORT_1DS_TENANT_TOKEN \"${onnxruntime_1DS_TENANT_TOKEN}\"") + else() + set(ONNXRUNTIME_1DS_TENANT_TOKEN_DEFINE "") + endif() + set(_ort_telemetry_gen_dir "${CMAKE_CURRENT_BINARY_DIR}/onnxruntime_telemetry") + configure_file( + "${REPO_ROOT}/cmake/onnxruntime_telemetry_tenant_token.h.in" + "${_ort_telemetry_gen_dir}/onnxruntime_telemetry_tenant_token.h" + @ONLY) + target_include_directories(onnxruntime_common PRIVATE "${_ort_telemetry_gen_dir}") + endif() endif() if (onnxruntime_USE_MIMALLOC) list(APPEND onnxruntime_EXTERNAL_LIBRARIES mimalloc-static) @@ -201,6 +237,67 @@ if(CPUINFO_SUPPORTED) list(APPEND onnxruntime_EXTERNAL_LIBRARIES cpuinfo::cpuinfo) endif() +# Link telemetry library (1DS SDK) for non-Windows platforms +if(onnxruntime_USE_TELEMETRY AND NOT WIN32) + if(TARGET MSTelemetry::mat) + # vcpkg port (cpp-client-telemetry): the imported target propagates its include + # directories and transitive dependencies (curl/sqlite3/zlib/nlohmann-json), so no + # manual include paths or system libraries are required here. + target_link_libraries(onnxruntime_common PRIVATE MSTelemetry::mat) + if(onnxruntime_TELEMETRY_SHARED_SDK AND onnxruntime_BUILD_SHARED_LIB) + # The vcpkg triplet built the SDK as a shared library (libmat.so) so it can be shared by + # several binaries (e.g. onnxruntime and onnxruntime-genai) instead of being statically + # embedded in each. Ship it next to libonnxruntime; the $ORIGIN/@loader_path RPATH set on the + # onnxruntime target (see onnxruntime.cmake) resolves it at load time. IMPORTED_RUNTIME_ARTIFACTS + # installs the resolved soname and its symlinks. + install(IMPORTED_RUNTIME_ARTIFACTS MSTelemetry::mat LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) + endif() + elseif(TARGET mat) + if(onnxruntime_TELEMETRY_SHARED_SDK) + message(FATAL_ERROR "onnxruntime_TELEMETRY_SHARED_SDK requires the vcpkg cpp-client-telemetry port (build with --use_vcpkg); it is not supported with the FetchContent fallback.") + endif() + # mat is a FetchContent target that is not installed/exported. Scope the link to the build + # interface so it is still absorbed into libonnxruntime (shared build) and in-tree executables, + # but is excluded from the installed onnxruntimeTargets export of the static onnxruntime_common + # (where mat cannot be shipped). Without this, install(EXPORT) fails for static telemetry builds + # with "target onnxruntime_common requires target mat that is not in any export set". The vcpkg + # path links the imported MSTelemetry::mat target, which is exportable and unaffected. + target_link_libraries(onnxruntime_common PRIVATE $) + # cpp_client_telemetry uses include_directories() (directory-scoped) rather than + # target_include_directories(), so include paths don't propagate via target_link_libraries. + # Add them explicitly for onnxruntime_common. Mark them SYSTEM so the SDK's public headers are + # exempt from onnxruntime_common's -Wall -Wextra -Werror (they trip -Werror=unused-parameter in + # NullObjects.hpp / LogManagerProvider.hpp). The vcpkg MSTelemetry::mat path already propagates + # its includes as SYSTEM via the imported target. + if(DEFINED cpp_client_telemetry_SOURCE_DIR) + target_include_directories(onnxruntime_common SYSTEM PRIVATE + ${cpp_client_telemetry_SOURCE_DIR}/lib/include/public + ${cpp_client_telemetry_SOURCE_DIR}/lib/include/mat + ${cpp_client_telemetry_SOURCE_DIR}/lib + ) + endif() + # Platform-specific system libraries required by the 1DS SDK + if(APPLE) + target_link_libraries(onnxruntime_common PRIVATE + "-framework CoreFoundation" + "-framework Security" + z + sqlite3 + ) + elseif(ANDROID) + target_link_libraries(onnxruntime_common PRIVATE z log) + elseif(UNIX) + target_link_libraries(onnxruntime_common PRIVATE + curl + z + sqlite3 + ) + endif() + else() + message(FATAL_ERROR "Telemetry enabled but no 1DS SDK target ('MSTelemetry::mat' or 'mat') was found") + endif() +endif() + if (NOT onnxruntime_BUILD_SHARED_LIB) install(DIRECTORY ${PROJECT_SOURCE_DIR}/../include/onnxruntime/core/common DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/onnxruntime/core) install(TARGETS onnxruntime_common EXPORT ${PROJECT_NAME}Targets diff --git a/cmake/onnxruntime_telemetry_tenant_token.h.in b/cmake/onnxruntime_telemetry_tenant_token.h.in new file mode 100644 index 0000000000000..3facccece89b1 --- /dev/null +++ b/cmake/onnxruntime_telemetry_tenant_token.h.in @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// +// Generated by CMake from cmake/onnxruntime_telemetry_tenant_token.h.in. Do not edit. +// +// When the official build sets onnxruntime_1DS_TENANT_TOKEN (sourced from a CI secret), CMake emits +// the corresponding ORT_1DS_TENANT_TOKEN #define here, in the build tree, instead of passing the +// token on the compiler command line. This keeps the token out of compile_commands.json and build +// logs. DIY/OSS builds leave the variable empty, so this header defines nothing and +// core/platform/posix/telemetry.cc falls back to its in-source throwaway default. +#pragma once +@ONNXRUNTIME_1DS_TENANT_TOKEN_DEFINE@ diff --git a/cmake/vcpkg-configuration.json b/cmake/vcpkg-configuration.json index ad4be7d57c220..8fa750286f705 100644 --- a/cmake/vcpkg-configuration.json +++ b/cmake/vcpkg-configuration.json @@ -1,8 +1,9 @@ { + "$comment": "Baseline pinned to the microsoft/vcpkg commit that ships the cpp-client-telemetry 3.10.173.1 port (PR #52568) for POSIX 1DS telemetry. ONNX Runtime's heavy dependencies are pinned via overlay-ports/overrides (see cmake/vcpkg.json), so this only floats benign minor versions; verified with 'vcpkg install --dry-run'.", "default-registry": { "kind": "git", "repository": "https://github.com/Microsoft/vcpkg", - "baseline": "120deac3062162151622ca4860575a33844ba10b" + "baseline": "18a4723aeb7adbbae84bcff0edf510883800f32f" }, "overlay-ports": [ "./vcpkg-ports" diff --git a/cmake/vcpkg.json b/cmake/vcpkg.json index e9ea52e4b5248..5529e2f70bf3c 100644 --- a/cmake/vcpkg.json +++ b/cmake/vcpkg.json @@ -58,7 +58,6 @@ "pybind11", "re2", "safeint", - "utf8-range", { "name": "vcpkg-cmake", "host": true @@ -96,6 +95,20 @@ "webgpu-ep": { "description": "Build with WebGPU EP", "dependencies": [] + }, + "telemetry": { + "description": "Build with 1DS telemetry support (cpp-client-telemetry) on non-Windows platforms", + "dependencies": [ + { + "name": "cpp-client-telemetry", + "platform": "!windows & !emscripten & !uwp" + }, + { + "name": "sqlite3", + "default-features": false, + "platform": "!windows & !emscripten & !uwp" + } + ] } }, "overrides": [ @@ -106,6 +119,10 @@ { "name": "flatbuffers", "version": "23.5.26" + }, + { + "name": "mimalloc", + "version": "2.1.1" } ] } diff --git a/docs/Privacy.md b/docs/Privacy.md index fcc8468b7fa9f..eca47bed82b9b 100644 --- a/docs/Privacy.md +++ b/docs/Privacy.md @@ -11,11 +11,19 @@ No data collection is performed when using your private builds built from source ### Official Builds ONNX Runtime does not maintain any independent telemetry collection mechanisms outside of what is provided by the platforms it supports. However, where applicable, ONNX Runtime will take advantage of platform-supported telemetry systems to collect trace events with the goal of improving product quality. -Currently telemetry is only implemented for Windows builds and is turned **ON** by default in the official builds distributed in their respective package management repositories ([see here](../README.md#binaries)). This may be expanded to cover other platforms in the future. Data collection is implemented via 'Platform Telemetry' per vendor platform providers (see [telemetry.h](../onnxruntime/core/platform/telemetry.h)). +Telemetry is turned **ON** by default in the official Windows builds distributed in their respective package management repositories ([see here](../README.md#binaries)), where it is implemented with the platform ETW provider. Builds for other platforms can additionally be compiled with the cross-platform 1DS telemetry provider by configuring with `--use_telemetry`; this is **not** enabled in the default builds. Data collection is implemented via 'Platform Telemetry' per vendor platform providers (see [telemetry.h](../onnxruntime/core/platform/telemetry.h)). #### Technical Details The Windows provider uses the [TraceLogging](https://docs.microsoft.com/en-us/windows/win32/tracelogging/trace-logging-about) API for its implementation. This enables ONNX Runtime trace events to be collected by the operating system, and based on user consent, this data may be periodically sent to Microsoft servers following GDPR and privacy regulations for anonymity and data access controls. Windows ML and onnxruntime C APIs allow Trace Logging to be turned on/off (see [API pages](../README.md#api-documentation) for details). -For information on how to enable and disable telemetry, see [C API: Telemetry](./C_API.md#telemetry). +For the ways to disable telemetry, see the [Disabling Telemetry](#disabling-telemetry) section below. There are equivalent APIs in the C#, Python, and Java language bindings as well. + +### Disabling Telemetry + +Telemetry can be disabled in any of these ways: + +- **Don't build it in.** Telemetry is only compiled when configuring with `--use_telemetry` (`onnxruntime_USE_TELEMETRY=OFF` is the default), so a build without that flag collects no data. +- **At runtime, via environment variable.** Set `ORT_TELEMETRY_DISABLED=1` (also accepts `true`/`yes`/`on`/`y`, case-insensitive) before ONNX Runtime initializes. On the non-Windows 1DS provider this prevents the telemetry uploader from being created. The same variable is also honored by ONNX Runtime GenAI. +- **At runtime, via the API.** The C API (and the C#, Python, and Java bindings) expose calls to turn telemetry on/off. On Windows, ETW is passive — events are only emitted when an external trace session is collecting. diff --git a/onnxruntime/core/platform/posix/device_id.cc b/onnxruntime/core/platform/posix/device_id.cc new file mode 100644 index 0000000000000..af4bb9513e140 --- /dev/null +++ b/onnxruntime/core/platform/posix/device_id.cc @@ -0,0 +1,240 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#include "core/platform/posix/device_id.h" + +#include "core/common/common.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#ifdef __APPLE__ +#include +#endif + +namespace onnxruntime { + +DeviceId& DeviceId::Instance() { + static DeviceId instance; + return instance; +} + +std::string DeviceId::GetValue() { + std::lock_guard lock(mutex_); + InitializeInternal(); + return device_id_; +} + +DeviceIdStatus DeviceId::GetStatus() { + std::lock_guard lock(mutex_); + InitializeInternal(); + return status_; +} + +std::string DeviceId::GetStatusString() { + switch (GetStatus()) { + case DeviceIdStatus::New: + return "New"; + case DeviceIdStatus::Existing: + return "Existing"; + case DeviceIdStatus::Corrupted: + return "Corrupted"; + case DeviceIdStatus::Failed: + return "Failed"; + default: + return "Unknown"; + } +} + +std::string DeviceId::GenerateUUID() { + // Draw the UUID fields directly from std::random_device -- a non-deterministic, + // CSPRNG-backed source on the POSIX platforms this file targets (glibc/bionic/ + // libc++ draw from getrandom or /dev/urandom). Seeding a std::mt19937 from a + // single random_device value would cap the entropy at 32 bits and make device + // ids collide across a large fleet (birthday-bound ~77k devices), so each field + // is sourced straight from the device. random_device::operator() spans the full + // unsigned int (>= 32-bit) range. + std::random_device rd; + + uint32_t data1 = rd(); + uint16_t data2 = static_cast(rd() & 0xFFFF); + uint16_t data3 = static_cast((rd() & 0x0FFF) | 0x4000); // Version 4 + uint16_t data4 = static_cast((rd() & 0x3FFF) | 0x8000); // Variant 1 + uint16_t data5a = static_cast(rd() & 0xFFFF); + uint32_t data5b = rd(); + + std::ostringstream oss; + oss << std::hex << std::setfill('0') + << std::setw(8) << data1 << '-' + << std::setw(4) << data2 << '-' + << std::setw(4) << data3 << '-' + << std::setw(4) << data4 << '-' + << std::setw(4) << data5a + << std::setw(8) << data5b; + return oss.str(); +} + +bool DeviceId::IsValidGUID(const std::string& str) { + if (str.length() != 36) return false; + + for (size_t i = 0; i < str.length(); ++i) { + char c = str[i]; + if (i == 8 || i == 13 || i == 18 || i == 23) { + if (c != '-') return false; + } else { + if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))) { + return false; + } + } + } + return true; +} + +std::string DeviceId::GetStorageDirectory(bool mobile) { + // Prefer $HOME; fall back to the password database (getpwuid) for contexts where HOME is unset, + // e.g. system services/daemons under systemd/launchd. + std::string home; + if (const char* h = std::getenv("HOME"); h != nullptr && h[0] != '\0') { + home = h; + } else { + // getpwuid() returns a pointer to shared static storage and is not thread-safe; use the + // reentrant getpwuid_r() with a caller-provided buffer so concurrent callers don't race. + struct passwd pwd; + struct passwd* result = nullptr; + const long sc = ::sysconf(_SC_GETPW_R_SIZE_MAX); + std::vector buf(sc > 0 ? static_cast(sc) : 16384); + if (::getpwuid_r(::getuid(), &pwd, buf.data(), buf.size(), &result) == 0 && + result != nullptr && result->pw_dir != nullptr && result->pw_dir[0] != '\0') { + home = result->pw_dir; + } + } + if (home.empty()) return ""; + + if (mobile) { + return home + "/.onnxruntime"; + } + +#if defined(__APPLE__) + return home + "/Library/Application Support/" + kDeviceIdDir; +#else + return home + "/" + kDeviceIdDir; +#endif +} + +std::string DeviceId::EnsureStorageDirectory(bool mobile) { + std::string dir = GetStorageDirectory(mobile); + if (!dir.empty()) { + CreateDirectoryTree(dir); + } + return dir; +} + +void DeviceId::CreateDirectoryTree(const std::string& path) { + if (path.empty()) return; + + size_t pos = path.find_last_of('/'); + if (pos != std::string::npos && pos > 0) { + CreateDirectoryTree(path.substr(0, pos)); + } + + // Owner-only (0700): this tree holds the persistent device id and the telemetry offline cache, so + // it should not be listable/traversable by other users. mkdir only sets the mode for directories + // it actually creates; pre-existing directories are left untouched. + mkdir(path.c_str(), 0700); +} + +void DeviceId::InitializeInternal() { + if (initialized_) return; + initialized_ = true; + + ORT_TRY { + // Use compile-time platform detection to select the appropriate storage path. + // This matches the mobile/desktop selection in posix/env.cc. +#if defined(__ANDROID__) || (defined(__APPLE__) && TARGET_OS_IOS) + constexpr bool is_mobile = true; +#else + constexpr bool is_mobile = false; +#endif + std::string dir_path = GetStorageDirectory(is_mobile); + if (dir_path.empty()) { + status_ = DeviceIdStatus::Failed; + return; + } + + std::string file_path = dir_path + "/" + kFileName; + + // Try to read existing device ID + { + std::ifstream infile(file_path); + if (infile.good()) { + infile.seekg(0, std::ios::end); + auto size = infile.tellg(); + infile.seekg(0, std::ios::beg); + + if (size > static_cast(kMaxFileSize)) { + status_ = DeviceIdStatus::Corrupted; + } else { + std::string content; + std::getline(infile, content); + + // Trim whitespace + while (!content.empty() && + (content.back() == '\n' || content.back() == '\r' || content.back() == ' ')) { + content.pop_back(); + } + + if (IsValidGUID(content)) { + device_id_ = content; + status_ = DeviceIdStatus::Existing; + return; + } + status_ = DeviceIdStatus::Corrupted; + } + } + } + + // Generate new device ID + device_id_ = GenerateUUID(); + + // Create directory tree + CreateDirectoryTree(dir_path); + + // Persist with owner-only (0600) permissions from creation. Using open() with mode 0600 (and + // fchmod to also tighten a pre-existing file) avoids the window where std::ofstream would create + // the file using the process umask and only chmod it afterwards — during which the device id + // could briefly be world-readable. fchmod runs before any write, so content is never exposed. + const bool regenerated_from_corruption = (status_ == DeviceIdStatus::Corrupted); + const int fd = ::open(file_path.c_str(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); + if (fd >= 0) { + ::fchmod(fd, S_IRUSR | S_IWUSR); + const ssize_t written = ::write(fd, device_id_.data(), device_id_.size()); + ::close(fd); + if (written == static_cast(device_id_.size())) { + // Preserve Corrupted (defined as "invalid and regenerated") instead of overwriting it with + // New, so callers/telemetry can still observe that the persisted id had to be regenerated. + status_ = regenerated_from_corruption ? DeviceIdStatus::Corrupted : DeviceIdStatus::New; + } else { + status_ = DeviceIdStatus::Failed; + } + } else { + status_ = DeviceIdStatus::Failed; + } + } + ORT_CATCH(...) { + status_ = DeviceIdStatus::Failed; + // Keep device_id_ if generated — it's still valid for this session (in-memory only). + } +} + +} // namespace onnxruntime diff --git a/onnxruntime/core/platform/posix/device_id.h b/onnxruntime/core/platform/posix/device_id.h new file mode 100644 index 0000000000000..687b6c45fcaab --- /dev/null +++ b/onnxruntime/core/platform/posix/device_id.h @@ -0,0 +1,76 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#pragma once + +#include +#include +#include "core/common/common.h" + +namespace onnxruntime { + +enum class DeviceIdStatus { + New, // Device ID was newly generated + Existing, // Device ID was loaded from persistent storage + Corrupted, // Stored device ID was invalid and regenerated + Failed // Failed to persist device ID (in-memory only) +}; + +/** + * Manages a persistent device identifier for telemetry purposes. + * The device ID is stored in a platform-appropriate location: + * - macOS: ~/Library/Application Support/Microsoft/DeveloperTools/.onnxruntime/deviceid + * - Linux: ~/Microsoft/DeveloperTools/.onnxruntime/deviceid + * - iOS/Android: ~/.onnxruntime/deviceid (shorter path, avoids iCloud backup on iOS) + * + * Thread-safe singleton - use DeviceId::Instance() to access. + */ +class DeviceId { + public: + static DeviceId& Instance(); + + // Get the device ID value (generates/loads on first call) + std::string GetValue(); + + // Get the status of the device ID + DeviceIdStatus GetStatus(); + + // Get human-readable status string + std::string GetStatusString(); + + // Get the directory path for device ID / telemetry cache storage + // Desktop: ~/Microsoft/DeveloperTools/.onnxruntime (or platform equivalent) + // Mobile: ~/.onnxruntime + static std::string GetStorageDirectory(bool mobile = false); + + // Same as GetStorageDirectory(), but also creates the directory tree (0700) if it does not exist. + // Returns "" if no suitable location is available. Use before writing into the directory (e.g. the + // telemetry offline cache, which the 1DS SDK opens during initialization). + static std::string EnsureStorageDirectory(bool mobile = false); + + private: + DeviceId() = default; + ~DeviceId() = default; + ORT_DISALLOW_COPY_ASSIGNMENT_AND_MOVE(DeviceId); + + void InitializeInternal(); + + // Generate a random UUID v4 + static std::string GenerateUUID(); + + // Validate GUID format (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) + static bool IsValidGUID(const std::string& str); + + // Create directory tree recursively using platform APIs + static void CreateDirectoryTree(const std::string& path); + + static constexpr const char* kDeviceIdDir = "Microsoft/DeveloperTools/.onnxruntime"; + static constexpr const char* kFileName = "deviceid"; + static constexpr size_t kMaxFileSize = 256; + + std::string device_id_; + DeviceIdStatus status_ = DeviceIdStatus::New; + bool initialized_ = false; + std::mutex mutex_; +}; +} // namespace onnxruntime diff --git a/onnxruntime/core/platform/posix/env.cc b/onnxruntime/core/platform/posix/env.cc index 0270bf9d4d79c..4c75abab50d2d 100644 --- a/onnxruntime/core/platform/posix/env.cc +++ b/onnxruntime/core/platform/posix/env.cc @@ -16,6 +16,10 @@ limitations under the License. #include "core/platform/env.h" +#ifdef USE_1DS_TELEMETRY +#include "core/platform/posix/telemetry.h" +#endif + #include #include #include @@ -639,7 +643,11 @@ class PosixEnv : public Env { } private: +#ifdef USE_1DS_TELEMETRY + PosixTelemetry telemetry_provider_; +#else Telemetry telemetry_provider_; +#endif #ifdef ORT_USE_CPUINFO PosixEnv() { cpuinfo_available_ = cpuinfo_initialize(); diff --git a/onnxruntime/core/platform/posix/telemetry.cc b/onnxruntime/core/platform/posix/telemetry.cc new file mode 100644 index 0000000000000..cb31c40ead60e --- /dev/null +++ b/onnxruntime/core/platform/posix/telemetry.cc @@ -0,0 +1,1095 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#include "core/platform/posix/telemetry.h" +#include "core/platform/posix/device_id.h" +#include "core/platform/telemetry_redaction.h" + +#ifdef __APPLE__ +#include +#endif + +// 1DS SDK +#include +#include +// ContextFieldsProvider is an internal SDK header (not part of the vcpkg-installed public headers); +// it is only used on mobile to read the SDK's auto-generated device id. +#if defined(__ANDROID__) || (defined(__APPLE__) && TARGET_OS_IOS) +#include +#endif + +#include +#include + +#ifdef __APPLE__ +#include +#endif + +#if defined(__linux__) || defined(__ANDROID__) +#include +#endif + +#include +#include +#include +#include +#include +#include + +#include "core/common/common.h" +#include "core/common/logging/logging.h" +#include "core/common/status.h" +#include "onnxruntime_config.h" +// Optional compile-time tenant-token override, generated by CMake into the build tree (keeps an +// injected token off the compiler command line). See TENANT_TOKEN below. +#include "onnxruntime_telemetry_tenant_token.h" + +using namespace Microsoft::Applications::Events; + +namespace onnxruntime { + +// PosixTelemetry can be constructed during early Env initialization (before logging registers a +// default logger) and destroyed late at process exit, so only emit warnings when a default logger +// exists, to avoid touching a missing/destroyed LoggingManager. +#define ORT_TELEMETRY_WARN(stream_expr) \ + do { \ + if (::onnxruntime::logging::LoggingManager::HasDefaultLogger()) { \ + LOGS_DEFAULT(WARNING) << stream_expr; \ + } \ + } while (0) + +// Static member initialization +std::atomic PosixTelemetry::global_register_count_{0}; +std::mutex PosixTelemetry::global_mutex_; +std::shared_mutex PosixTelemetry::mutex_; +::Microsoft::Applications::Events::ILogManager* PosixTelemetry::log_manager_ = nullptr; +std::atomic<::Microsoft::Applications::Events::ILogger*> PosixTelemetry::logger_{nullptr}; +std::unique_ptr<::Microsoft::Applications::Events::ILogConfiguration> PosixTelemetry::config_; +std::atomic PosixTelemetry::enabled_{true}; +std::atomic PosixTelemetry::projection_{0}; +std::atomic PosixTelemetry::level_{0}; +std::atomic PosixTelemetry::keyword_{0}; +std::atomic PosixTelemetry::process_info_logged_{false}; +std::atomic PosixTelemetry::system_metrics_sample_counter_{0}; + +// Tenant token for 1DS telemetry ingestion. +// +// The default below is a throwaway ingestion key so that anyone building ONNX Runtime themselves +// gets working telemetry by default; it carries no secret and can simply be revoked if abused. +// Official builds override it via the onnxruntime_1DS_TENANT_TOKEN CMake variable (sourced from a CI +// secret), which CMake writes into the generated onnxruntime_telemetry_tenant_token.h included above +// — in the build tree, not on the compiler command line — so the production token is never committed +// to source and stays out of compile_commands.json / build logs. +#ifndef ORT_1DS_TENANT_TOKEN +#define ORT_1DS_TENANT_TOKEN "5ad963bd4b3a4118a481401cc0211875-da8e8657-47d4-4ed7-ab39-7886e136f53b-6988" +#endif +constexpr const char* TENANT_TOKEN = ORT_1DS_TENANT_TOKEN; + +// Event priority mapping (1DS priorities) +enum class EventPriority { + NORMAL = EventLatency_Normal, // Most events + HIGH = EventLatency_RealTime, // RuntimeError + CRITICAL = EventLatency_RealTime // ProcessInfo, SessionCreation +}; + +// SystemMetrics is emitted from LogEvaluationStop, i.e. once per inference Run(), which is a hot +// path for small/high-frequency models. Sample it to bound the per-run getrusage() + event cost: +// the event is emitted on the first run and then once every kSystemMetricsSampleInterval runs. +constexpr uint32_t kSystemMetricsSampleInterval = 100; + +// Helper class to build events with common properties +class EventBuilder { + private: + EventProperties props_; + + public: + explicit EventBuilder(std::string event_name, EventPriority priority, + uint64_t privacy_tags = PDT_ProductAndServicePerformance) + : props_(std::move(event_name)) { + // Set latency/priority + props_.SetLatency(static_cast(priority)); + + // Default schemaVersion is 0; events that have evolved call SetSchemaVersion() to match the + // Windows provider's per-event versions. + props_.SetProperty("schemaVersion", static_cast(0)); + + // All ORT telemetry is required system metadata (no PII) + props_.SetLevel(DIAG_LEVEL_REQUIRED); + + // Privacy data tags for GDPR compliance classification + props_.SetProperty(COMMONFIELDS_EVENT_PRIVTAGS, static_cast(privacy_tags)); + } + + // Override the default schemaVersion (0) to match the Windows provider's per-event versions. + EventBuilder& SetSchemaVersion(uint8_t schema_version) { + props_.SetProperty("schemaVersion", static_cast(schema_version)); + return *this; + } + + EventBuilder& AddString(const char* key, const std::string& value) { + if (!value.empty()) { + props_.SetProperty(key, value); + } + return *this; + } + + EventBuilder& AddInt32(const char* key, int32_t value) { + props_.SetProperty(key, static_cast(value)); + return *this; + } + + EventBuilder& AddInt64(const char* key, int64_t value) { + props_.SetProperty(key, value); + return *this; + } + + EventBuilder& AddBool(const char* key, bool value) { + props_.SetProperty(key, value); + return *this; + } + + EventBuilder& AddUInt32(const char* key, uint32_t value) { + props_.SetProperty(key, static_cast(value)); + return *this; + } + + EventBuilder& AddDouble(const char* key, double value) { + props_.SetProperty(key, value); + return *this; + } + + // Helper for vector to comma-separated string + EventBuilder& AddStringList(const char* key, const std::vector& vec) { + if (!vec.empty()) { + std::string result; + for (size_t i = 0; i < vec.size(); ++i) { + if (i > 0) result += ','; + result += vec[i]; + } + props_.SetProperty(key, result); + } + return *this; + } + + // Helper for map to key=value,key=value format + EventBuilder& AddIntMap(const char* key, const std::unordered_map& map) { + if (!map.empty()) { + std::string result; + bool first = true; + for (const auto& [k, v] : map) { + if (!first) result += ','; + result += k + '=' + std::to_string(v); + first = false; + } + props_.SetProperty(key, result); + } + return *this; + } + + // Helper for string map + EventBuilder& AddStringMap(const char* key, const std::unordered_map& map) { + if (!map.empty()) { + std::string result; + bool first = true; + for (const auto& [k, v] : map) { + if (!first) result += ','; + result += k + '=' + v; + first = false; + } + props_.SetProperty(key, result); + } + return *this; + } + + // Helper for batch size duration map + EventBuilder& AddBatchSizeDurations(const std::unordered_map& durations) { + for (const auto& [batch_size, duration] : durations) { + std::string key = "batchSize_" + std::to_string(batch_size); + props_.SetProperty(key, static_cast(duration)); + } + return *this; + } + + // Add common platform/device context + EventBuilder& AddCommonContext(const PosixTelemetry* telemetry) { + props_.SetProperty("projection", static_cast(telemetry->projection_.load())); + return *this; + } + + EventProperties Build() { return std::move(props_); } +}; + +// Hash a device ID with a stable, platform-independent algorithm (FNV-1a 64-bit) and format as +// fixed-width hex, so the same device maps to the same anonymized id across runs and platforms. +// std::hash is implementation-defined (and may be process-salted), so it is unsuitable here. +// Ensures raw device identifiers are never sent over the wire. +static std::string HashDeviceId(const std::string& id) { + uint64_t hash = 14695981039346656037ULL; // FNV-1a offset basis + for (unsigned char c : id) { + hash ^= static_cast(c); + hash *= 1099511628211ULL; // FNV-1a prime + } + std::ostringstream oss; + oss << std::hex << std::setfill('0') << std::setw(16) << hash; + return oss.str(); +} + +PosixTelemetry::PosixTelemetry() { + std::lock_guard lock(global_mutex_); + + // Always increment so destructor pairing is symmetric + global_register_count_++; + + if (global_register_count_ == 1) { + ORT_TRY { + Initialize(); + } + ORT_CATCH(const std::exception& ex) { + // Telemetry failures should not break application functionality. + ORT_HANDLE_EXCEPTION([&]() { + ORT_TELEMETRY_WARN("Failed to initialize telemetry: " << ex.what()); + }); + } + } +} + +PosixTelemetry::~PosixTelemetry() { + std::lock_guard lock(global_mutex_); + + global_register_count_--; + if (global_register_count_ == 0) { + ORT_TRY { + Shutdown(); + } + ORT_CATCH(const std::exception& ex) { + // Don't throw from a destructor. + ORT_HANDLE_EXCEPTION([&]() { + ORT_TELEMETRY_WARN("Error during telemetry shutdown: " << ex.what()); + }); + } + } +} + +void PosixTelemetry::LogEventAsync(Microsoft::Applications::Events::EventProperties&& props) const { + // Hold a shared (reader) lock for the duration of the LogEvent call so the logger and its owning + // log manager cannot be torn down underneath us: Initialize()/Shutdown() take this lock + // exclusively. The shared lock still allows multiple threads to log concurrently. + std::shared_lock lock(mutex_); + auto* logger = logger_.load(std::memory_order_acquire); + if (logger == nullptr) { + return; + } + ORT_TRY { + logger->LogEvent(std::move(props)); + } + ORT_CATCH(const std::exception& ex) { + ORT_HANDLE_EXCEPTION([&]() { + ORT_TELEMETRY_WARN("[Telemetry] Failed to log event: " << ex.what()); + }); + } +} + +void PosixTelemetry::Initialize() { + std::unique_lock lock(mutex_); + + // Environment opt-out: ORT_TELEMETRY_DISABLED set to a truthy value (1/true/yes/on/y, + // case-insensitive) disables telemetry at runtime without recompiling and skips creating the 1DS + // uploader entirely. A single opt-out variable honored by both ONNX Runtime and onnxruntime-genai. + if (const char* env = std::getenv("ORT_TELEMETRY_DISABLED"); env != nullptr) { + std::string value(env); + for (char& ch : value) { + ch = static_cast(std::tolower(static_cast(ch))); + } + if (value == "1" || value == "true" || value == "yes" || value == "on" || value == "y") { + enabled_.store(false, std::memory_order_release); + return; + } + } + + // NOTE: On Android, the Java layer must be initialized before calling this: + // System.loadLibrary("maesdk"); + // new HttpClient(getApplicationContext()); + // OfflineRoom.connectContext(getApplicationContext()); // if using Room DB + // See cpp_client_telemetry/docs/cpp-start-android.md for details. + + // Create SDK configuration — stored as member because LogManagerImpl holds a reference + // and the configuration must remain valid for the lifetime of the log manager. + config_ = std::make_unique(); + auto& config = *config_; + + config[CFG_STR_COLLECTOR_URL] = "https://mobile.events.data.microsoft.com/OneCollector/1.0"; + config[CFG_INT_TRACE_LEVEL_MASK] = 0; // Disable SDK internal logging + config[CFG_INT_SDK_MODE] = SdkModeTypes::SdkModeTypes_CS; // Common Schema 4.0 mode + // Do not block process teardown to upload; persisted events are sent on the next run. 0 keeps + // Shutdown non-blocking and avoids adding exit latency to host apps (matches onnxruntime-genai). + config[CFG_INT_MAX_TEARDOWN_TIME] = 0; + + // Configure cache for offline scenarios — use same directory as device ID storage + { +#if defined(__ANDROID__) || (defined(__APPLE__) && TARGET_OS_IOS) + constexpr bool is_mobile = true; +#else + constexpr bool is_mobile = false; +#endif + std::string cache_dir = DeviceId::EnsureStorageDirectory(is_mobile); + if (!cache_dir.empty()) { + std::string cache_path = cache_dir + "/telemetry_cache.db"; + config[CFG_STR_CACHE_FILE_PATH] = cache_path; + } + } + + // Configure RAM queue for async batching + config[CFG_INT_RAM_QUEUE_SIZE] = 512 * 1024; // 512KB RAM queue + + // Create log manager via LogManagerProvider (recommended for production use, + // per LogManager_Creation_and_Lifecycle_Management.md). + status_t status; + log_manager_ = LogManagerProvider::CreateLogManager("OnnxRuntime", true, *config_, status); + if (status != STATUS_SUCCESS || !log_manager_) { + ORT_TELEMETRY_WARN("Failed to create telemetry LogManager, status: " << status); + config_.reset(); + return; + } + + // Get logger for our tenant + auto* logger = log_manager_->GetLogger(TENANT_TOKEN); + if (logger == nullptr) { + ORT_TELEMETRY_WARN("Failed to get telemetry logger"); + LogManagerProvider::Release(*config_); + log_manager_ = nullptr; + config_.reset(); + return; + } + + // Use BEST_EFFORT transmit profile to minimize battery and network impact. + // Events are batched and uploaded at a lower cadence. + log_manager_->SetTransmitProfile(TransmitProfile_BestEffort); + + // Override device ID with hashed version for privacy. + // The "c:" prefix tells the backend it's a caller-supplied identifier. + auto& ctx = log_manager_->GetSemanticContext(); + std::string raw_device_id; +#if defined(__ANDROID__) || (defined(__APPLE__) && TARGET_OS_IOS) + // Mobile: read SDK's auto-generated platform device ID (e.g., identifierForVendor + // on iOS, ANDROID_ID on Android) and hash it before sending. + auto* provider = static_cast(&ctx); + auto& fields = provider->GetCommonFields(); + auto it = fields.find(COMMONFIELDS_DEVICE_ID); + if (it != fields.end()) { + raw_device_id = it->second.to_string(); + } +#else + // Desktop: use our custom persistent UUID. + raw_device_id = DeviceId::Instance().GetValue(); +#endif + if (!raw_device_id.empty()) { + ctx.SetDeviceId("c:" + HashDeviceId(raw_device_id)); + } + + // Set application information as logger context (attached to all events) + logger->SetContext("AppName", "ONNXRuntime"); + logger->SetContext("AppVersion", ORT_VERSION); + logger->SetContext("Platform", GetPlatformInfo()); + + // Publish the fully-configured logger atomically; concurrent readers observe it only now. + // enabled_ is left to its default / the runtime EnableTelemetryEvents()/DisableTelemetryEvents() + // opt-in state rather than being force-set here. + logger_.store(logger, std::memory_order_release); +} + +void PosixTelemetry::Shutdown() { + std::unique_lock lock(mutex_); + + // Clear the logger so concurrent LogEventAsync() readers (which take the shared lock) observe + // nullptr and skip. enabled_ is intentionally left untouched: it reflects the user's + // EnableTelemetryEvents()/DisableTelemetryEvents() opt-in state, so a later Initialize() (e.g. in + // tests or dynamic load/unload of the last instance) can resume telemetry without a forced re-enable. + logger_ = nullptr; // Owned by log_manager_, will be destroyed with it + + if (log_manager_ && config_) { + // Per SDK use-after-free docs (use-after-free.md): + // Flush() must be called before FlushAndTeardown() to ensure all pending + // events are persisted to offline storage. FlushAndTeardown() internally + // calls PauseActivity() + WaitPause() to quiesce the SDK. + log_manager_->Flush(); + log_manager_->FlushAndTeardown(); + + // Release the log manager instance via LogManagerProvider + LogManagerProvider::Release(*config_); + log_manager_ = nullptr; + config_.reset(); + } +} + +std::string PosixTelemetry::GetPlatformInfo() const { +#if defined(__APPLE__) +#if TARGET_OS_IOS + return "iOS"; +#elif TARGET_OS_MAC + return "macOS"; +#else + return "Apple"; +#endif +#elif defined(__ANDROID__) + return "Android"; +#elif defined(__linux__) + return "Linux"; +#else + return "Unknown"; +#endif +} + +// --------------------------------------------------------------------------- +// Process / system info helpers for LogProcessInfo +// --------------------------------------------------------------------------- + +// Get detailed OS version string (e.g., "macOS 15.2", "Ubuntu 22.04 LTS") +std::string PosixTelemetry::GetOsDescription() const { +#if defined(__APPLE__) + char version[64] = {}; + size_t len = sizeof(version); + if (sysctlbyname("kern.osproductversion", version, &len, nullptr, 0) == 0) { +#if TARGET_OS_IOS + return std::string("iOS ") + version; +#else + return std::string("macOS ") + version; +#endif + } + return GetPlatformInfo(); + +#elif defined(__ANDROID__) + // Read Android system properties via /system/build.prop + std::string release, sdk; + std::ifstream prop("/system/build.prop"); + if (prop.is_open()) { + std::string line; + while (std::getline(prop, line)) { + if (line.rfind("ro.build.version.release=", 0) == 0) + release = line.substr(25); + else if (line.rfind("ro.build.version.sdk=", 0) == 0) + sdk = line.substr(21); + } + } + if (!release.empty()) { + std::string result = "Android " + release; + if (!sdk.empty()) result += " (API " + sdk + ")"; + return result; + } + return "Android"; + +#elif defined(__linux__) + // Parse /etc/os-release for PRETTY_NAME (e.g., "Ubuntu 22.04.3 LTS") + std::ifstream os_release("/etc/os-release"); + if (os_release.is_open()) { + std::string line; + while (std::getline(os_release, line)) { + if (line.rfind("PRETTY_NAME=", 0) == 0) { + std::string value = line.substr(12); + if (value.size() >= 2 && value.front() == '"' && value.back() == '"') { + value = value.substr(1, value.size() - 2); + } + return value; + } + } + } + return "Linux"; + +#else + return "Unknown"; +#endif +} + +// Get the name of the current process +std::string PosixTelemetry::GetProcessName() const { +#if defined(__APPLE__) || defined(__FreeBSD__) + const char* name = getprogname(); + return name ? name : ""; + +#elif defined(__linux__) || defined(__ANDROID__) + // /proc/self/comm contains the process name (up to 15 chars) + std::ifstream comm("/proc/self/comm"); + if (comm.is_open()) { + std::string name; + std::getline(comm, name); + while (!name.empty() && (name.back() == '\n' || name.back() == '\r')) + name.pop_back(); + return name; + } + return ""; + +#else + return ""; +#endif +} + +// Get the CPU architecture the binary was compiled for +std::string PosixTelemetry::GetArchitecture() { +#if defined(__x86_64__) + return "x86_64"; +#elif defined(__i386__) + return "x86"; +#elif defined(__aarch64__) + return "arm64"; +#elif defined(__arm__) + return "arm"; +#elif defined(__riscv) + return "riscv"; +#elif defined(__wasm__) + return "wasm"; +#else + return "unknown"; +#endif +} + +// Get total physical memory in MB +int64_t PosixTelemetry::GetTotalMemoryMB() { +#if defined(__APPLE__) + int64_t mem = 0; + size_t len = sizeof(mem); + if (sysctlbyname("hw.memsize", &mem, &len, nullptr, 0) == 0) { + return mem / (1024 * 1024); + } + return -1; + +#elif defined(__linux__) || defined(__ANDROID__) + long pages = sysconf(_SC_PHYS_PAGES); + long page_size = sysconf(_SC_PAGE_SIZE); + if (pages > 0 && page_size > 0) { + return static_cast(pages) * page_size / (1024 * 1024); + } + return -1; + +#else + return -1; +#endif +} + +// Get system locale (e.g., "en-US", "ja-JP") +std::string PosixTelemetry::GetLocale() { + const char* lang = std::getenv("LANG"); + if (lang && lang[0]) { + std::string loc(lang); + // Strip encoding suffix (e.g., "en_US.UTF-8" → "en_US") + auto dot = loc.find('.'); + if (dot != std::string::npos) loc = loc.substr(0, dot); + // Normalize separator: "en_US" → "en-US" + for (auto& c : loc) { + if (c == '_') c = '-'; + } + return loc; + } + return ""; +} + +void PosixTelemetry::EnableTelemetryEvents() const { + enabled_ = true; +} + +void PosixTelemetry::DisableTelemetryEvents() const { + enabled_ = false; +} + +void PosixTelemetry::SetLanguageProjection(uint32_t projection) const { + projection_ = projection; +} + +bool PosixTelemetry::IsEnabled() const { + // Reflect actual readiness: the opt-out flag AND a successfully-initialized logger. + return enabled_.load(std::memory_order_acquire) && logger_.load(std::memory_order_acquire) != nullptr; +} + +unsigned char PosixTelemetry::Level() const { + return level_; +} + +uint64_t PosixTelemetry::Keyword() const { + return keyword_; +} + +void PosixTelemetry::LogProcessInfo() const { + // LogProcessInfo only collects system metadata, but it must still honor the + // runtime opt-out (DisableTelemetryEvents) like every other event. + if (!IsEnabled()) { + return; + } + + // Log process info only once + if (process_info_logged_.exchange(true)) { + return; + } + + auto builder = EventBuilder("ProcessInfo", EventPriority::CRITICAL, + PDT_DeviceConnectivityAndConfiguration | PDT_SoftwareSetupAndInventory) + .AddCommonContext(this) + .AddString("runtimeVersion", ORT_VERSION) +#if defined(__ANDROID__) || (defined(__APPLE__) && TARGET_OS_IOS) + .AddString("DeviceInfo.Status", "Mobile") +#else + .AddString("DeviceInfo.Status", DeviceId::Instance().GetStatusString()) +#endif + .AddString("osDescription", GetOsDescription()) + .AddString("processName", GetProcessName()) + .AddString("architecture", GetArchitecture()) + .AddInt32("cpuCount", static_cast(std::thread::hardware_concurrency())) + .AddInt64("totalMemoryMB", GetTotalMemoryMB()) + .AddString("locale", GetLocale()); + + LogEventAsync(builder.Build()); +} + +void PosixTelemetry::LogSessionCreationStart(uint32_t session_id) const { + if (!IsEnabled()) { + return; + } + + auto event = EventBuilder("SessionCreationStart", EventPriority::CRITICAL, + PDT_SoftwareSetupAndInventory | PDT_ProductAndServicePerformance) + .SetSchemaVersion(2) + .AddCommonContext(this) + .AddUInt32("sessionId", session_id) + .Build(); + + LogEventAsync(std::move(event)); +} + +void PosixTelemetry::LogEvaluationStop(uint32_t session_id) const { + if (!IsEnabled()) { + return; + } + + auto event = EventBuilder("EvaluationStop", EventPriority::NORMAL, + PDT_ProductAndServicePerformance) + .AddCommonContext(this) + .AddUInt32("sessionId", session_id) + .Build(); + + LogEventAsync(std::move(event)); + + // Capture system metrics after each inference run to observe impact + LogSystemMetrics(session_id); +} + +void PosixTelemetry::LogEvaluationStart(uint32_t session_id) const { + if (!IsEnabled()) { + return; + } + + auto event = EventBuilder("EvaluationStart", EventPriority::NORMAL, + PDT_ProductAndServicePerformance) + .AddCommonContext(this) + .AddUInt32("sessionId", session_id) + .Build(); + + LogEventAsync(std::move(event)); +} + +void PosixTelemetry::LogSessionCreation( + uint32_t session_id, int64_t ir_version, + const std::string& model_producer_name, + const std::string& model_producer_version, + const std::string& model_domain, + const std::unordered_map& domain_to_version_map, + const std::string& model_file_name, + const std::string& model_graph_name, + const std::string& model_weight_type, + const std::string& model_graph_hash, + const std::string& model_weight_hash, + const std::unordered_map& model_metadata, + const std::string& loadedFrom, + const std::vector& execution_provider_ids, + const std::string& hardware_device_types, + const std::string& hardware_vendor_ids, + const std::string& ep_versions, + bool use_fp16, bool captureState) const { + if (!IsEnabled()) { + return; + } + + // captureState is currently only triggered on Windows via ETW's EVENT_CONTROL_CODE_CAPTURE_STATE callback + // (LogAllSessions). Kept here for future compatibility if a similar mechanism is added for POSIX. + std::string event_name = captureState ? "SessionCreation_CaptureState" : "SessionCreation"; + + auto builder = EventBuilder(std::move(event_name), EventPriority::CRITICAL, + PDT_SoftwareSetupAndInventory | PDT_ProductAndServicePerformance) + .AddCommonContext(this) + .AddUInt32("sessionId", session_id) + .AddInt64("irVersion", ir_version) + .AddString("modelProducerName", model_producer_name) + .AddString("modelProducerVersion", model_producer_version) + .AddString("modelDomain", model_domain) + .AddIntMap("domainToVersionMap", domain_to_version_map) + .AddString("modelFileName", model_file_name) + .AddString("modelGraphName", model_graph_name) + .AddString("modelWeightType", model_weight_type) + .AddString("modelGraphHash", model_graph_hash) + .AddString("modelWeightHash", model_weight_hash) + .AddStringMap("modelMetadata", model_metadata) + .AddString("loadedFrom", loadedFrom) + .AddStringList("executionProviderIds", execution_provider_ids) + .AddString("hardwareDeviceTypes", hardware_device_types) + .AddString("hardwareVendorIds", hardware_vendor_ids) + .AddString("executionProviderVersions", ep_versions) + .AddBool("useFp16", use_fp16); + + LogEventAsync(builder.Build()); +} + +void PosixTelemetry::LogCompileModelStart( + uint32_t session_id, + const std::string& input_source, + const std::string& output_target, + uint32_t flags, + int graph_optimization_level, + bool embed_ep_context, + bool has_external_initializers_file, + const std::vector& execution_provider_ids) const { + if (!IsEnabled()) { + return; + } + + auto event = EventBuilder("CompileModelStart", EventPriority::NORMAL, + PDT_SoftwareSetupAndInventory | PDT_ProductAndServicePerformance) + .SetSchemaVersion(1) + .AddCommonContext(this) + .AddUInt32("sessionId", session_id) + .AddString("inputSource", input_source) + .AddString("outputTarget", output_target) + .AddUInt32("flags", flags) + .AddInt32("graphOptimizationLevel", graph_optimization_level) + .AddBool("embedEpContext", embed_ep_context) + .AddBool("hasExternalInitializersFile", has_external_initializers_file) + .AddStringList("executionProviderIds", execution_provider_ids) + .Build(); + + LogEventAsync(std::move(event)); +} + +void PosixTelemetry::LogCompileModelComplete( + uint32_t session_id, + bool success, + uint32_t error_code, + uint32_t error_category, + const std::string& error_message) const { + if (!IsEnabled()) { + return; + } + + auto event = EventBuilder("CompileModelComplete", EventPriority::NORMAL, + PDT_SoftwareSetupAndInventory | PDT_ProductAndServicePerformance) + .AddCommonContext(this) + .AddUInt32("sessionId", session_id) + .AddBool("success", success) + .AddUInt32("errorCode", error_code) + .AddUInt32("errorCategory", error_category) + .AddString("errorMessage", ScrubErrorMessage(error_message)) + .Build(); + + LogEventAsync(std::move(event)); +} + +void PosixTelemetry::LogRuntimeError( + uint32_t session_id, const common::Status& status, + const char* file, const char* function, uint32_t line) const { + if (!IsEnabled()) { + return; + } + + // __FILE__ may be an absolute build path that embeds developer/build directory names; emit only + // the basename so remote telemetry doesn't leak usernames or local paths. + std::string_view file_view = file ? std::string_view{file} : std::string_view{}; + if (const size_t slash = file_view.find_last_of('/'); slash != std::string_view::npos) { + file_view.remove_prefix(slash + 1); + } + + auto event = EventBuilder("RuntimeError", EventPriority::HIGH, + PDT_ProductAndServicePerformance) + .SetSchemaVersion(1) + .AddCommonContext(this) + .AddUInt32("sessionId", session_id) + .AddInt32("errorCode", static_cast(status.Code())) + .AddInt32("errorCategory", static_cast(status.Category())) + .AddString("errorMessage", ScrubErrorMessage(status.ErrorMessage())) + .AddString("file", std::string(file_view)) + .AddString("function", function ? function : "") + .AddUInt32("line", line) + .Build(); + + LogEventAsync(std::move(event)); +} + +void PosixTelemetry::LogRuntimeInferenceError(uint32_t session_id, const common::Status& status, + const std::string& ep_versions, + const std::string& ep_device_types) const { + if (!IsEnabled()) { + return; + } + + auto event = EventBuilder("RuntimeInferenceError", EventPriority::HIGH, + PDT_ProductAndServicePerformance) + .AddCommonContext(this) + .AddUInt32("sessionId", session_id) + .AddInt32("errorCode", static_cast(status.Code())) + .AddInt32("errorCategory", static_cast(status.Category())) + .AddString("errorMessage", ScrubErrorMessage(status.ErrorMessage())) + .AddString("executionProviderVersions", ep_versions) + .AddString("executionProviderDeviceTypes", ep_device_types) + .AddString("runtimeVersion", ORT_VERSION) + .Build(); + + LogEventAsync(std::move(event)); +} + +void PosixTelemetry::LogRuntimePerf( + uint32_t session_id, uint32_t total_runs_since_last, + int64_t total_run_duration_since_last, + const std::unordered_map& duration_per_batch_size) const { + if (!IsEnabled()) { + return; + } + + auto event = EventBuilder("RuntimePerf", EventPriority::NORMAL, + PDT_ProductAndServicePerformance) + .SetSchemaVersion(1) + .AddCommonContext(this) + .AddUInt32("sessionId", session_id) + .AddUInt32("totalRunsSinceLast", total_runs_since_last) + .AddInt64("totalRunDurationSinceLast", total_run_duration_since_last) + .AddBatchSizeDurations(duration_per_batch_size) + .Build(); + + LogEventAsync(std::move(event)); +} + +void PosixTelemetry::LogExecutionProviderEvent(LUID* adapterLuid) const { + // Not applicable for non-Windows platforms (LUID is Windows-specific) + (void)adapterLuid; +} + +void PosixTelemetry::LogDriverInfoEvent( + const std::string_view device_class, + const std::wstring_view& driver_names, + const std::wstring_view& driver_versions) const { + // Not applicable for non-Windows platforms + (void)device_class; + (void)driver_names; + (void)driver_versions; +} + +void PosixTelemetry::LogAutoEpSelection( + uint32_t session_id, const std::string& selection_policy, + const std::vector& requested_execution_provider_ids, + const std::vector& available_execution_provider_ids) const { + if (!IsEnabled()) { + return; + } + + auto event = EventBuilder("EpAutoSelection", EventPriority::NORMAL, + PDT_SoftwareSetupAndInventory) + .AddCommonContext(this) + .AddUInt32("sessionId", session_id) + .AddString("selectionPolicy", selection_policy) + .AddStringList("requestedExecutionProviderIds", requested_execution_provider_ids) + .AddStringList("availableExecutionProviderIds", available_execution_provider_ids) + .Build(); + + LogEventAsync(std::move(event)); +} + +void PosixTelemetry::LogProviderOptions( + const std::string& provider_id, + const std::string& provider_options_string, + bool captureState) const { + if (!IsEnabled()) { + return; + } + + std::string event_name = captureState ? "ProviderOptions_CaptureState" : "ProviderOptions"; + + auto event = EventBuilder(std::move(event_name), EventPriority::NORMAL, + PDT_SoftwareSetupAndInventory) + .AddCommonContext(this) + .AddString("providerId", provider_id) + .AddString("providerOptions", provider_options_string) + .Build(); + + LogEventAsync(std::move(event)); +} + +void PosixTelemetry::LogModelLoadStart(uint32_t session_id) const { + if (!IsEnabled()) { + return; + } + + auto event = EventBuilder("ModelLoadStart", EventPriority::NORMAL, + PDT_ProductAndServiceUsage) + .SetSchemaVersion(1) + .AddCommonContext(this) + .AddUInt32("sessionId", session_id) + .Build(); + + LogEventAsync(std::move(event)); +} + +void PosixTelemetry::LogModelLoadEnd(uint32_t session_id, const common::Status& status) const { + if (!IsEnabled()) { + return; + } + + auto event = EventBuilder("ModelLoadEnd", EventPriority::NORMAL, + PDT_ProductAndServicePerformance) + .AddCommonContext(this) + .AddUInt32("sessionId", session_id) + .AddBool("isSuccess", status.IsOK()) + .AddInt32("errorCode", static_cast(status.Code())) + .AddInt32("errorCategory", static_cast(status.Category())) + .AddString("errorMessage", ScrubErrorMessage(status.ErrorMessage())) + .Build(); + + LogEventAsync(std::move(event)); +} + +void PosixTelemetry::LogSessionCreationEnd(uint32_t session_id, const common::Status& status) const { + if (!IsEnabled()) { + return; + } + + auto event = EventBuilder("SessionCreationEnd", EventPriority::CRITICAL, + PDT_ProductAndServicePerformance) + .AddCommonContext(this) + .AddUInt32("sessionId", session_id) + .AddBool("isSuccess", status.IsOK()) + .AddInt32("errorCode", static_cast(status.Code())) + .AddInt32("errorCategory", static_cast(status.Category())) + .AddString("errorMessage", ScrubErrorMessage(status.ErrorMessage())) + .Build(); + + LogEventAsync(std::move(event)); +} + +void PosixTelemetry::LogEpDeviceUsage( + uint32_t session_id, + const std::string& ep_type, + const std::string& hardware_device_type, + uint32_t hardware_vendor_id, + uint32_t hardware_device_id, + const std::string& hardware_vendor, + const std::string& ep_vendor, + const std::string& ep_version, + int assigned_node_count, + uint32_t total_runs_since_last, + int64_t total_run_duration_since_last) const { + if (!IsEnabled()) { + return; + } + + auto event = EventBuilder("EpDeviceUsage", EventPriority::NORMAL, + PDT_ProductAndServiceUsage) + .SetSchemaVersion(1) + .AddCommonContext(this) + .AddUInt32("sessionId", session_id) + .AddString("executionProviderType", ep_type) + .AddString("hardwareDeviceType", hardware_device_type) + .AddUInt32("hardwareVendorId", hardware_vendor_id) + .AddUInt32("hardwareDeviceId", hardware_device_id) + .AddString("hardwareVendor", hardware_vendor) + .AddString("epVendor", ep_vendor) + .AddString("epVersion", ep_version) + .AddInt32("assignedNodeCount", assigned_node_count) + .AddUInt32("totalRunsSinceLast", total_runs_since_last) + .AddInt64("totalRunDurationSinceLast", total_run_duration_since_last) + .Build(); + + LogEventAsync(std::move(event)); +} + +void PosixTelemetry::LogRegisterEpLibraryStart(const std::string& registration_name) const { + if (!IsEnabled()) { + return; + } + + auto event = EventBuilder("RegisterEpLibraryStart", EventPriority::NORMAL, + PDT_ProductAndServiceUsage) + .SetSchemaVersion(1) + .AddCommonContext(this) + .AddString("registrationName", registration_name) + .Build(); + + LogEventAsync(std::move(event)); +} + +void PosixTelemetry::LogRegisterEpLibraryEnd(const std::string& registration_name, + const common::Status& status) const { + if (!IsEnabled()) { + return; + } + + auto event = EventBuilder("RegisterEpLibraryEnd", EventPriority::NORMAL, + PDT_ProductAndServicePerformance) + .AddCommonContext(this) + .AddString("registrationName", registration_name) + .AddBool("isSuccess", status.IsOK()) + .AddInt32("errorCode", static_cast(status.Code())) + .AddInt32("errorCategory", static_cast(status.Category())) + .AddString("errorMessage", ScrubErrorMessage(status.ErrorMessage())) + .Build(); + + LogEventAsync(std::move(event)); +} + +void PosixTelemetry::LogRegisterEpLibraryWithLibPath(const std::string& registration_name, + const std::string& lib_path) const { + if (!IsEnabled()) { + return; + } + + auto event = EventBuilder("RegisterEpLibraryWithLibPath", EventPriority::NORMAL, + PDT_ProductAndServiceUsage) + .AddCommonContext(this) + .AddString("registrationName", registration_name) + .AddString("libPath", lib_path) + .Build(); + + LogEventAsync(std::move(event)); +} + +void PosixTelemetry::LogSystemMetrics(uint32_t session_id) const { + if (!IsEnabled()) { + return; + } + + // Sample to bound per-inference overhead: emit on the first run and every + // kSystemMetricsSampleInterval-th run thereafter. fetch_add returns the previous value, so the + // first call (0) passes and the getrusage() syscall below is skipped on non-sampled runs. + if ((system_metrics_sample_counter_.fetch_add(1, std::memory_order_relaxed) % kSystemMetricsSampleInterval) != 0) { + return; + } + + struct rusage usage; + if (getrusage(RUSAGE_SELF, &usage) == 0) { + // ru_maxrss is in KB on Linux, bytes on macOS +#ifdef __APPLE__ + int64_t max_rss_kb = usage.ru_maxrss / 1024; +#else + int64_t max_rss_kb = usage.ru_maxrss; +#endif + + auto event = EventBuilder("SystemMetrics", EventPriority::NORMAL, + PDT_ProductAndServicePerformance | PDT_DeviceConnectivityAndConfiguration) + .AddCommonContext(this) + .AddUInt32("sessionId", session_id) + .AddInt64("maxRssKb", max_rss_kb) + .AddInt64("userCpuTimeSec", usage.ru_utime.tv_sec) + .AddInt64("userCpuTimeUsec", usage.ru_utime.tv_usec) + .AddInt64("systemCpuTimeSec", usage.ru_stime.tv_sec) + .AddInt64("systemCpuTimeUsec", usage.ru_stime.tv_usec) + .AddInt64("minorPageFaults", usage.ru_minflt) + .AddInt64("majorPageFaults", usage.ru_majflt) + .AddInt64("voluntaryContextSwitches", usage.ru_nvcsw) + .AddInt64("involuntaryContextSwitches", usage.ru_nivcsw) + .Build(); + + LogEventAsync(std::move(event)); + } +} + +} // namespace onnxruntime diff --git a/onnxruntime/core/platform/posix/telemetry.h b/onnxruntime/core/platform/posix/telemetry.h new file mode 100644 index 0000000000000..c68eebf83fa26 --- /dev/null +++ b/onnxruntime/core/platform/posix/telemetry.h @@ -0,0 +1,193 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#pragma once + +#include "core/platform/telemetry.h" +#include +#include +#include +#include +#include +#include +#include +#include + +// Forward declarations of 1DS SDK types +namespace Microsoft::Applications::Events { +class ILogger; +class ILogManager; +class ILogConfiguration; +class EventProperties; +} // namespace Microsoft::Applications::Events + +namespace onnxruntime { + +/** + * @brief Cross-platform telemetry implementation using 1DS SDK (cpp_client_telemetry). + * + * This class provides telemetry logging capabilities for all platforms + * using the cpp_client_telemetry library (1DS SDK). It implements the same interface + * as the original WindowsTelemetry to provide consistent telemetry across all platforms. + * + * Configuration: + * - Telemetry is opt-in via build flags + */ +class PosixTelemetry : public Telemetry { + public: + PosixTelemetry(); + ~PosixTelemetry() override; + + void EnableTelemetryEvents() const override; + void DisableTelemetryEvents() const override; + void SetLanguageProjection(uint32_t projection) const override; + + bool IsEnabled() const override; + unsigned char Level() const override; + uint64_t Keyword() const override; + + void LogProcessInfo() const override; + void LogSessionCreationStart(uint32_t session_id) const override; + void LogEvaluationStop(uint32_t session_id) const override; + void LogEvaluationStart(uint32_t session_id) const override; + + void LogSessionCreation(uint32_t session_id, int64_t ir_version, + const std::string& model_producer_name, + const std::string& model_producer_version, + const std::string& model_domain, + const std::unordered_map& domain_to_version_map, + const std::string& model_file_name, + const std::string& model_graph_name, + const std::string& model_weight_type, + const std::string& model_graph_hash, + const std::string& model_weight_hash, + const std::unordered_map& model_metadata, + const std::string& loadedFrom, + const std::vector& execution_provider_ids, + const std::string& hardware_device_types, + const std::string& hardware_vendor_ids, + const std::string& ep_versions, + bool use_fp16, bool captureState) const override; + + void LogCompileModelStart(uint32_t session_id, + const std::string& input_source, + const std::string& output_target, + uint32_t flags, + int graph_optimization_level, + bool embed_ep_context, + bool has_external_initializers_file, + const std::vector& execution_provider_ids) const override; + + void LogCompileModelComplete(uint32_t session_id, + bool success, + uint32_t error_code, + uint32_t error_category, + const std::string& error_message) const override; + + void LogRuntimeError(uint32_t session_id, const common::Status& status, + const char* file, const char* function, uint32_t line) const override; + + void LogRuntimeInferenceError(uint32_t session_id, const common::Status& status, + const std::string& ep_versions, + const std::string& ep_device_types) const override; + + void LogRuntimePerf(uint32_t session_id, uint32_t total_runs_since_last, + int64_t total_run_duration_since_last, + const std::unordered_map& duration_per_batch_size) const override; + + void LogExecutionProviderEvent(LUID* adapterLuid) const override; + void LogDriverInfoEvent(const std::string_view device_class, + const std::wstring_view& driver_names, + const std::wstring_view& driver_versions) const override; + + void LogAutoEpSelection(uint32_t session_id, const std::string& selection_policy, + const std::vector& requested_execution_provider_ids, + const std::vector& available_execution_provider_ids) const override; + + void LogProviderOptions(const std::string& provider_id, + const std::string& provider_options_string, + bool captureState) const override; + + void LogModelLoadStart(uint32_t session_id) const override; + void LogModelLoadEnd(uint32_t session_id, const common::Status& status) const override; + + void LogSessionCreationEnd(uint32_t session_id, const common::Status& status) const override; + + void LogEpDeviceUsage(uint32_t session_id, + const std::string& ep_type, + const std::string& hardware_device_type, + uint32_t hardware_vendor_id, + uint32_t hardware_device_id, + const std::string& hardware_vendor, + const std::string& ep_vendor, + const std::string& ep_version, + int assigned_node_count, + uint32_t total_runs_since_last, + int64_t total_run_duration_since_last) const override; + + void LogRegisterEpLibraryStart(const std::string& registration_name) const override; + void LogRegisterEpLibraryEnd(const std::string& registration_name, + const common::Status& status) const override; + void LogRegisterEpLibraryWithLibPath(const std::string& registration_name, + const std::string& lib_path) const override; + + private: + // Initialize telemetry SDK logger + void Initialize(); + + // Shutdown telemetry SDK logger + void Shutdown(); + + // Helper to get platform name + std::string GetPlatformInfo() const; + + // Process/system info helpers for LogProcessInfo + std::string GetOsDescription() const; + std::string GetProcessName() const; + static std::string GetArchitecture(); + static int64_t GetTotalMemoryMB(); + static std::string GetLocale(); + + // Safe async event logging. + void LogEventAsync(::Microsoft::Applications::Events::EventProperties&& props) const; + + // Log system resource metrics + void LogSystemMetrics(uint32_t session_id) const; + + // All shared telemetry state below is static: PosixTelemetry is a process-wide singleton whose + // lifetime is gated by global_register_count_ (the first instance initializes the SDK, the last + // tears it down), matching WindowsTelemetry. Keeping the SDK handles and state static ensures a + // single owner regardless of how many PosixTelemetry objects exist. + + // Mutex for thread-safe init/shutdown of the shared SDK state. + static std::shared_mutex mutex_; + + // Telemetry SDK instances. + // log_manager_ is owned by LogManagerProvider; logger_ is owned by log_manager_. + static ::Microsoft::Applications::Events::ILogManager* log_manager_; + static std::atomic<::Microsoft::Applications::Events::ILogger*> logger_; + + // SDK configuration — must outlive log_manager_ (LogManagerImpl holds a reference). + static std::unique_ptr<::Microsoft::Applications::Events::ILogConfiguration> config_; + + // State tracking + static std::atomic enabled_; + static std::atomic projection_; + static std::atomic level_; + static std::atomic keyword_; + + // Process info tracking + static std::atomic process_info_logged_; + + // Sampling counter for the per-run SystemMetrics event (see LogSystemMetrics). + static std::atomic system_metrics_sample_counter_; + + // Global registration count for singleton behavior + static std::atomic global_register_count_; + static std::mutex global_mutex_; + + // Make EventBuilder a friend so it can access projection_ + friend class EventBuilder; +}; + +} // namespace onnxruntime diff --git a/onnxruntime/core/platform/telemetry_redaction.h b/onnxruntime/core/platform/telemetry_redaction.h new file mode 100644 index 0000000000000..a46c0e362d083 --- /dev/null +++ b/onnxruntime/core/platform/telemetry_redaction.h @@ -0,0 +1,78 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#pragma once + +#include +#include +#include + +namespace onnxruntime { +namespace telemetry_detail { + +// Returns true if a whitespace-delimited token looks like a filesystem path. Used to redact paths +// (which embed usernames / directory layout) from error text. Mirrors onnxruntime-genai's +// Generators::LooksLikePath so both telemetry pipelines scrub error messages identically. +inline bool LooksLikePath(std::string_view token) { + if (token.find('\\') != std::string_view::npos) { + return true; // any backslash: Windows path / UNC + } + if (token.size() >= 3 && std::isalpha(static_cast(token[0])) && token[1] == ':' && + (token[2] == '\\' || token[2] == '/')) { + return true; // drive-letter prefix: C:\ or C:/ + } + if (token.size() >= 2 && token[0] == '~' && (token[1] == '/' || token[1] == '\\')) { + return true; // home-relative: ~/ or ~\. + } + int segments = 0; // count "/x" runs; 2+ indicates a multi-segment POSIX path + for (size_t k = 0; k + 1 < token.size(); ++k) { + if (token[k] == '/' && token[k + 1] != '/') { + ++segments; + } + } + return segments >= 2; +} + +} // namespace telemetry_detail + +// Maximum transmitted error-message length, applied after scrubbing to bound telemetry payload size. +inline constexpr size_t kMaxTelemetryErrorMessageLength = 256; + +// Scrub filesystem paths out of a free-text error string before transmission and cap its length. +// Each whitespace-delimited token that looks like a path is replaced with a "[path]" placeholder, so +// load/runtime exceptions don't ship the user's config/model path (e.g. C:\Users\\... or +// /home//...) and thereby the username and directory layout. Mirrors onnxruntime-genai's +// Generators::ScrubErrorMessage so both pipelines redact identically; the trailing length cap matches +// the 256-byte guard genai applies at its call sites. +inline std::string ScrubErrorMessage(std::string_view msg) { + using telemetry_detail::LooksLikePath; + + std::string out; + out.reserve(msg.size()); + + size_t i = 0; + while (i < msg.size()) { + if (std::isspace(static_cast(msg[i]))) { + out.push_back(msg[i]); + ++i; + continue; + } + const size_t start = i; + while (i < msg.size() && !std::isspace(static_cast(msg[i]))) { + ++i; + } + const std::string_view token = msg.substr(start, i - start); + if (LooksLikePath(token)) { + out += "[path]"; + } else { + out.append(token.data(), token.size()); + } + } + + if (out.size() > kMaxTelemetryErrorMessageLength) { + out.resize(kMaxTelemetryErrorMessageLength); + } + return out; +} + +} // namespace onnxruntime diff --git a/onnxruntime/core/platform/windows/telemetry.cc b/onnxruntime/core/platform/windows/telemetry.cc index 342b937ffb656..2080ee0a56b82 100644 --- a/onnxruntime/core/platform/windows/telemetry.cc +++ b/onnxruntime/core/platform/windows/telemetry.cc @@ -2,13 +2,18 @@ // Licensed under the MIT License. #include "core/platform/windows/telemetry.h" +#include +#include +#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) +#include +#endif +#include #include #include #include -#include -#include #include "core/common/logging/logging.h" #include "onnxruntime_config.h" +#include "core/platform/telemetry_redaction.h" // ETW includes // need space after Windows.h to prevent clang-format re-ordering breaking the build. @@ -75,14 +80,51 @@ std::string ConvertWideStringToUtf8(const std::wstring& wide) { return utf8; } +// Parse the command line for -s (service name) and -k (service group) arguments. +// These are svchost.exe conventions and may not be present for all services. +std::string GetServiceNamesFromCommandLine() { +#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) + LPCWSTR cmd_line = ::GetCommandLineW(); + if (cmd_line == nullptr) + return {}; + + int argc = 0; + LPWSTR* argv = ::CommandLineToArgvW(cmd_line, &argc); + if (argv == nullptr) + return {}; + + std::wstring aggregated; + bool first = true; + for (int i = 0; i < argc - 1; ++i) { + if ((_wcsicmp(argv[i], L"-s") == 0 || _wcsicmp(argv[i], L"-k") == 0)) { + if (!first) { + aggregated.push_back(L','); + } + aggregated.append(argv[i + 1]); + first = false; + ++i; // skip the value we just consumed + } + } + + ::LocalFree(argv); + return ConvertWideStringToUtf8(aggregated); +#else + // CommandLineToArgvW lives in shell32 and is only available on the desktop partition; the + // svchost -s/-k service-name convention does not apply on non-desktop Windows (UWP/GDK). + return {}; +#endif +} + std::string GetServiceNamesForCurrentProcess() { static std::once_flag once_flag; static std::string service_names; std::call_once(once_flag, [] { SC_HANDLE service_manager = ::OpenSCManagerW(nullptr, nullptr, SC_MANAGER_ENUMERATE_SERVICE); - if (service_manager == nullptr) + if (service_manager == nullptr) { + service_names = GetServiceNamesFromCommandLine(); return; + } DWORD bytes_needed = 0; DWORD services_returned = 0; @@ -91,11 +133,13 @@ std::string GetServiceNamesForCurrentProcess() { &services_returned, &resume_handle, nullptr) && ::GetLastError() != ERROR_MORE_DATA) { ::CloseServiceHandle(service_manager); + service_names = GetServiceNamesFromCommandLine(); return; } if (bytes_needed == 0) { ::CloseServiceHandle(service_manager); + service_names = GetServiceNamesFromCommandLine(); return; } @@ -106,6 +150,7 @@ std::string GetServiceNamesForCurrentProcess() { if (!::EnumServicesStatusExW(service_manager, SC_ENUM_PROCESS_INFO, SERVICE_WIN32, SERVICE_ACTIVE, reinterpret_cast(services), bytes_needed, &bytes_needed, &services_returned, &resume_handle, nullptr)) { ::CloseServiceHandle(service_manager); + service_names = GetServiceNamesFromCommandLine(); return; } @@ -125,6 +170,9 @@ std::string GetServiceNamesForCurrentProcess() { ::CloseServiceHandle(service_manager); service_names = ConvertWideStringToUtf8(aggregated); + if (service_names.empty()) { + service_names = GetServiceNamesFromCommandLine(); + } }); return service_names; @@ -496,7 +544,7 @@ void WindowsTelemetry::LogCompileModelComplete(uint32_t session_id, TraceLoggingBool(success, "success"), TraceLoggingUInt32(error_code, "errorCode"), TraceLoggingUInt32(error_category, "errorCategory"), - TraceLoggingString(error_message.c_str(), "errorMessage"), + TraceLoggingString(ScrubErrorMessage(error_message).c_str(), "errorMessage"), TraceLoggingString(ORT_CALLER_FRAMEWORK, "frameworkName")); } @@ -519,7 +567,7 @@ void WindowsTelemetry::LogRuntimeError(uint32_t session_id, const common::Status TraceLoggingUInt32(session_id, "sessionId"), TraceLoggingUInt32(status.Code(), "errorCode"), TraceLoggingUInt32(status.Category(), "errorCategory"), - TraceLoggingString(status.ErrorMessage().c_str(), "errorMessage"), + TraceLoggingString(ScrubErrorMessage(status.ErrorMessage()).c_str(), "errorMessage"), TraceLoggingString(file, "file"), TraceLoggingString(function, "function"), TraceLoggingInt32(line, "line"), @@ -537,7 +585,7 @@ void WindowsTelemetry::LogRuntimeError(uint32_t session_id, const common::Status TraceLoggingUInt32(session_id, "sessionId"), TraceLoggingUInt32(status.Code(), "errorCode"), TraceLoggingUInt32(status.Category(), "errorCategory"), - TraceLoggingString(status.ErrorMessage().c_str(), "errorMessage"), + TraceLoggingString(ScrubErrorMessage(status.ErrorMessage()).c_str(), "errorMessage"), TraceLoggingString(file, "file"), TraceLoggingString(function, "function"), TraceLoggingInt32(line, "line"), @@ -563,7 +611,7 @@ void WindowsTelemetry::LogRuntimeInferenceError(uint32_t session_id, const commo TraceLoggingUInt32(session_id, "sessionId"), TraceLoggingUInt32(status.Code(), "errorCode"), TraceLoggingUInt32(status.Category(), "errorCategory"), - TraceLoggingString(status.ErrorMessage().c_str(), "errorMessage"), + TraceLoggingString(ScrubErrorMessage(status.ErrorMessage()).c_str(), "errorMessage"), TraceLoggingString(ep_versions.c_str(), "executionProviderVersions"), TraceLoggingString(ep_device_types.c_str(), "executionProviderDeviceTypes"), TraceLoggingString(ORT_VERSION, "runtimeVersion"), @@ -784,7 +832,7 @@ void WindowsTelemetry::LogModelLoadEnd(uint32_t session_id, const common::Status TraceLoggingBool(status.IsOK(), "isSuccess"), TraceLoggingUInt32(status.Code(), "errorCode"), TraceLoggingUInt32(status.Category(), "errorCategory"), - TraceLoggingString(status.IsOK() ? "" : status.ErrorMessage().c_str(), "errorMessage"), + TraceLoggingString((status.IsOK() ? std::string() : ScrubErrorMessage(status.ErrorMessage())).c_str(), "errorMessage"), TraceLoggingString(ORT_CALLER_FRAMEWORK, "frameworkName")); } @@ -805,7 +853,7 @@ void WindowsTelemetry::LogSessionCreationEnd(uint32_t session_id, TraceLoggingBool(status.IsOK(), "isSuccess"), TraceLoggingUInt32(status.Code(), "errorCode"), TraceLoggingUInt32(status.Category(), "errorCategory"), - TraceLoggingString(status.IsOK() ? "" : status.ErrorMessage().c_str(), "errorMessage"), + TraceLoggingString((status.IsOK() ? std::string() : ScrubErrorMessage(status.ErrorMessage())).c_str(), "errorMessage"), TraceLoggingString(ORT_CALLER_FRAMEWORK, "frameworkName")); } @@ -861,7 +909,7 @@ void WindowsTelemetry::LogRegisterEpLibraryEnd(const std::string& registration_n TraceLoggingBool(status.IsOK(), "isSuccess"), TraceLoggingUInt32(status.Code(), "errorCode"), TraceLoggingUInt32(status.Category(), "errorCategory"), - TraceLoggingString(status.IsOK() ? "" : status.ErrorMessage().c_str(), "errorMessage"), + TraceLoggingString((status.IsOK() ? std::string() : ScrubErrorMessage(status.ErrorMessage())).c_str(), "errorMessage"), TraceLoggingString(ORT_CALLER_FRAMEWORK, "frameworkName")); } diff --git a/onnxruntime/test/platform/telemetry_redaction_test.cc b/onnxruntime/test/platform/telemetry_redaction_test.cc new file mode 100644 index 0000000000000..54ff94a369824 --- /dev/null +++ b/onnxruntime/test/platform/telemetry_redaction_test.cc @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#include "core/platform/telemetry_redaction.h" + +#include + +#include "gtest/gtest.h" + +namespace onnxruntime { +namespace test { + +TEST(TelemetryRedactionTest, EmptyAndNoPath) { + EXPECT_EQ(ScrubErrorMessage(""), ""); + EXPECT_EQ(ScrubErrorMessage("no path here"), "no path here"); + EXPECT_EQ(ScrubErrorMessage("error code 13"), "error code 13"); +} + +TEST(TelemetryRedactionTest, PosixPathReplacedWithPlaceholder) { + EXPECT_EQ(ScrubErrorMessage("Load model from /home/alice/models/foo.onnx failed"), + "Load model from [path] failed"); + // The username must not survive. + EXPECT_EQ(ScrubErrorMessage("/home/alice/models/foo.onnx").find("alice"), std::string::npos); +} + +TEST(TelemetryRedactionTest, WindowsDriveAndUncReplaced) { + EXPECT_EQ(ScrubErrorMessage("Load C:\\Users\\bob\\m.onnx failed"), "Load [path] failed"); + EXPECT_EQ(ScrubErrorMessage("open D:/data/secret/model.onnx"), "open [path]"); + EXPECT_EQ(ScrubErrorMessage("from \\\\server\\share\\dir\\weights.bin done"), "from [path] done"); + EXPECT_EQ(ScrubErrorMessage("Load C:\\Users\\bob\\m.onnx failed").find("bob"), std::string::npos); +} + +TEST(TelemetryRedactionTest, PathsWithSpacesDoNotLeakUsername) { + // Both halves of a spaced path contain a backslash, so each is replaced; no username leaks. + EXPECT_EQ(ScrubErrorMessage("Load C:\\Users\\First Last\\model.onnx failed"), + "Load [path] [path] failed"); + EXPECT_EQ(ScrubErrorMessage("Load C:\\Users\\First Last\\model.onnx failed").find("First"), + std::string::npos); +} + +TEST(TelemetryRedactionTest, MultiSegmentRelativeAndUrlReplaced) { + // Matches onnxruntime-genai: a token with 2+ "/x" segments (incl. URLs) is treated as a path. + EXPECT_EQ(ScrubErrorMessage("a/b/c"), "[path]"); + EXPECT_EQ(ScrubErrorMessage("see https://example.com/a/b/c for details"), "see [path] for details"); + EXPECT_EQ(ScrubErrorMessage("input:/home/alice/secret/m.onnx"), "[path]"); + EXPECT_EQ(ScrubErrorMessage("file:///home/alice/secret/model.onnx"), "[path]"); + EXPECT_EQ(ScrubErrorMessage("~/.config/app/x"), "[path]"); +} + +TEST(TelemetryRedactionTest, SingleSegmentAndNonPathSlashesKept) { + EXPECT_EQ(ScrubErrorMessage("models/foo.onnx"), "models/foo.onnx"); + EXPECT_EQ(ScrubErrorMessage("ratio 3/4 and and/or"), "ratio 3/4 and and/or"); +} + +TEST(TelemetryRedactionTest, LengthIsCappedAfterScrub) { + const std::string long_msg(300, 'x'); + EXPECT_EQ(ScrubErrorMessage(long_msg).size(), kMaxTelemetryErrorMessageLength); + EXPECT_LE(ScrubErrorMessage("short").size(), kMaxTelemetryErrorMessageLength); +} + +} // namespace test +} // namespace onnxruntime diff --git a/tools/ci_build/build.py b/tools/ci_build/build.py index 231888f2204a8..8a8f9a649c27d 100644 --- a/tools/ci_build/build.py +++ b/tools/ci_build/build.py @@ -267,6 +267,8 @@ def generate_vcpkg_install_options(build_dir, args): vcpkg_install_options.append("--x-feature=webnn-ep") if args.use_xnnpack: vcpkg_install_options.append("--x-feature=xnnpack-ep") + if args.use_telemetry: + vcpkg_install_options.append("--x-feature=telemetry") overlay_triplets_dir = None @@ -354,12 +356,33 @@ def generate_build_tree( disable_float4_types = args.android or ("float4" in types_to_disable) disable_optional_type = "optional" in types_to_disable disable_sparse_tensors = "sparsetensor" in types_to_disable + # Telemetry: On Windows uses ETW, on non-Windows uses 1DS. Telemetry is unsupported on + # WebAssembly/Emscripten (the 1DS vcpkg feature excludes it), so fail fast on that combination. + if args.use_telemetry and args.build_wasm: + raise BuildError( + "Telemetry is not supported for WebAssembly (Emscripten) builds; omit --use_telemetry when using --build_wasm." + ) + # The shared 1DS SDK (libmat.so) is provided by the vcpkg cpp-client-telemetry port and is only + # meaningful for the non-Windows 1DS path. Fail fast on unsupported combinations. + if args.telemetry_shared_sdk: + if not args.use_telemetry: + raise BuildError("--telemetry_shared_sdk requires --use_telemetry.") + if not args.use_vcpkg: + raise BuildError( + "--telemetry_shared_sdk requires --use_vcpkg; the shared 1DS SDK is built by the vcpkg cpp-client-telemetry port." + ) + if args.android: + raise BuildError("--telemetry_shared_sdk is not supported for Android builds.") + cmake_args += [ + "-Donnxruntime_USE_TELEMETRY=" + ("ON" if args.use_telemetry else "OFF"), + "-Donnxruntime_TELEMETRY_SHARED_SDK=" + ("ON" if args.telemetry_shared_sdk else "OFF"), + ] + disable_string_type = "string" in types_to_disable if is_windows(): cmake_args += [ "-Donnxruntime_USE_DML=" + ("ON" if args.use_dml else "OFF"), "-Donnxruntime_USE_WINML=" + ("ON" if args.use_winml else "OFF"), - "-Donnxruntime_USE_TELEMETRY=" + ("ON" if args.use_telemetry else "OFF"), "-Donnxruntime_ENABLE_PIX_FOR_WEBGPU_EP=" + ("ON" if args.enable_pix_capture else "OFF"), ] @@ -618,10 +641,14 @@ def generate_build_tree( osx_target = os.environ.get("MACOSX_DEPLOYMENT_TARGET") if osx_target is not None: log.info(f"Setting VCPKG_OSX_DEPLOYMENT_TARGET to {osx_target}") - generate_macos_triplets(build_dir, configs, osx_target, args.use_full_protobuf) + generate_macos_triplets( + build_dir, configs, osx_target, args.use_full_protobuf, args.telemetry_shared_sdk, args.use_telemetry + ) else: # Linux, *BSD, AIX or other platforms - generate_linux_triplets(build_dir, configs, args.use_full_protobuf) + generate_linux_triplets( + build_dir, configs, args.use_full_protobuf, args.telemetry_shared_sdk, args.use_telemetry + ) add_default_definition(cmake_extra_defines, "CMAKE_TOOLCHAIN_FILE", str(vcpkg_toolchain_path)) # Choose the cmake triplet diff --git a/tools/ci_build/build_args.py b/tools/ci_build/build_args.py index abc9a8d91779c..0230174656700 100644 --- a/tools/ci_build/build_args.py +++ b/tools/ci_build/build_args.py @@ -435,7 +435,6 @@ def add_windows_specific_args(parser: argparse.ArgumentParser) -> None: parser.add_argument("--msvc_toolset", help="MSVC toolset version (e.g., 14.11). Must be >=14.40") parser.add_argument("--windows_sdk_version", help="Windows SDK version (e.g., 10.0.19041.0).") parser.add_argument("--enable_msvc_static_runtime", action="store_true", help="Statically link MSVC runtimes.") - parser.add_argument("--use_telemetry", action="store_true", help="Enable telemetry (official builds only).") parser.add_argument("--caller_framework", type=str, help="Name of the framework calling ONNX Runtime.") # Cross-compilation targets hosted on Windows @@ -869,6 +868,25 @@ def add_other_feature_args(parser: argparse.ArgumentParser) -> None: action="store_true", help="Build ORT shared lib with compatible bridge for primary EPs (TRT, OV, QNN, VitisAI), excludes tests.", ) + # Telemetry arguments (cross-platform) + parser.add_argument( + "--use_telemetry", + action="store_true", + help="Enable telemetry (ETW on Windows; 1DS on Linux, macOS, Android, and iOS).", + ) + parser.add_argument( + "--telemetry_shared_sdk", + action="store_true", + help=( + "Build the non-Windows 1DS telemetry SDK (cpp-client-telemetry) as a shared library " + "(libmat.so) instead of statically linking it into libonnxruntime. This lets several " + "binaries that link the same SDK (for example onnxruntime and onnxruntime-genai shipped " + "together) share a single copy of the SDK and its TLS/HTTP stack instead of embedding it " + "in each. The SDK's own dependencies (OpenSSL/curl/sqlite3/zlib) stay static inside " + "libmat.so so it remains self-contained. Requires --use_telemetry and --use_vcpkg; " + "Linux/macOS only." + ), + ) def is_cross_compiling(args: argparse.Namespace) -> bool: diff --git a/tools/python/util/vcpkg_helpers.py b/tools/python/util/vcpkg_helpers.py index 8d1c665f631d9..248fc1e6d78fd 100644 --- a/tools/python/util/vcpkg_helpers.py +++ b/tools/python/util/vcpkg_helpers.py @@ -1,6 +1,32 @@ import os from pathlib import Path +# Compile-time options that shrink the sqlite3 build that the 1DS telemetry SDK +# (cpp-client-telemetry) pulls in. sqlite3 is only ever built here as the SDK's offline-event +# store, and the SDK uses it very narrowly: a single event table with parameter-bound +# INSERT/SELECT/DELETE, a handful of PRAGMAs and VACUUM, UTF-8 only (no triggers, views, FTS, +# JSON, ALTER, ATTACH, foreign keys, UTF-16, or extension loading). These feature reductions are +# therefore safe for that usage, also harden it (no runtime extension loading), and all take +# effect with the prebuilt sqlite amalgamation. Deeper grammar omits (triggers/views/window +# functions) only shrink a build from canonical sources, so they are intentionally not applied here. +# Some omits are deliberately excluded because they conflict with options the vcpkg sqlite3 port +# enables: SQLITE_OMIT_DECLTYPE clashes with SQLITE_ENABLE_COLUMN_METADATA (all configs), and +# SQLITE_OMIT_TRACE / SQLITE_UNTESTABLE clash with SQLITE_DEBUG / SELECTTRACE in Debug-config builds. +# Verified: the SDK references none of the omitted APIs, so the trimmed sqlite still links cleanly. +_SQLITE_TELEMETRY_MINIMAL_DEFINES = [ + "-DSQLITE_OMIT_LOAD_EXTENSION", + "-DSQLITE_OMIT_DEPRECATED", + "-DSQLITE_OMIT_UTF16", + "-DSQLITE_OMIT_PROGRESS_CALLBACK", + "-DSQLITE_OMIT_SHARED_CACHE", + "-DSQLITE_OMIT_GET_TABLE", + "-DSQLITE_OMIT_COMPLETE", + "-DSQLITE_OMIT_TCL_VARIABLE", + "-DSQLITE_DQS=0", + "-DSQLITE_DEFAULT_MEMSTATUS=0", + "-DSQLITE_DEFAULT_FOREIGN_KEYS=0", +] + # The official vcpkg repository has about 80 different triplets. But ONNX Runtime has many more build variants. For example, in general, for each platform, we need to support builds with C++ exceptions, builds without C++ exceptions, builds with C++ RTTI, builds without C++ RTTI, linking to static C++ runtime, linking to dynamic (shared) C++ runtime, builds with address sanitizer, builds without address sanitizer, etc. Therefore, this script file was created to dynamically generate the triplet files on-the-fly. # Originally, we tried to check in all the generated files into our repository so that people could build onnxruntime without using build.py or any other Python scripts in the "/tools" directory. However, we encountered an issue when adding support for WASM builds. VCPKG has a limitation that when doing cross-compiling, the triplet file must specify the full path of the chain-loaded toolchain file. The file needs to be located either via environment variables (like ANDROID_NDK_HOME) or via an absolute path. Since environment variables are hard to track, we chose the latter approach. So the generated triplet files may contain absolute file paths that are only valid on the current build machine. @@ -349,6 +375,8 @@ def generate_triplet_for_posix_platform( target_abi: str, osx_deployment_target: str, use_full_protobuf: bool, + telemetry_shared_sdk: bool = False, + use_telemetry: bool = False, ) -> None: """ Generate triplet file for POSIX platforms (Linux, macOS). @@ -365,6 +393,8 @@ def generate_triplet_for_posix_platform( target_abi (str): The target ABI, which maps to the VCPKG_TARGET_ARCHITECTURE variable. Valid options include x86, x64, arm, arm64, arm64ec, s390x, ppc64le, riscv32, riscv64, loongarch32, loongarch64, mips64. osx_deployment_target (str, optional): The macOS deployment target version. The parameter sets the minimum macOS version for compiled binaries. It also changes what versions of the macOS platform SDK CMake will search for. See the CMake documentation for CMAKE_OSX_DEPLOYMENT_TARGET for more information. use_full_protobuf (bool): Flag indicating if full Protobuf is used. + telemetry_shared_sdk (bool): Flag indicating if the 1DS telemetry SDK (cpp-client-telemetry) should be built as a shared library (libmat.so) while its dependencies remain static. + use_telemetry (bool): Flag indicating if telemetry is enabled; when set, sqlite3 (the telemetry SDK's offline store) is compiled with size-reducing feature omits. """ folder_name_parts = [] if enable_asan: @@ -409,6 +439,14 @@ def generate_triplet_for_posix_platform( # Valid options are dynamic and static. Libraries can ignore this setting if they do not support the preferred linkage type. f.write("set(VCPKG_LIBRARY_LINKAGE static)\n") + if telemetry_shared_sdk: + # Build only the 1DS telemetry SDK (cpp-client-telemetry) as a shared library while + # keeping every other port (including the SDK's own OpenSSL/curl/sqlite3/zlib + # dependencies) static. This produces a self-contained libmat.so that several binaries + # (for example onnxruntime and onnxruntime-genai shipped together) can share instead of + # statically embedding the SDK and its TLS/HTTP stack into each one. + f.write('if(PORT STREQUAL "cpp-client-telemetry")\n set(VCPKG_LIBRARY_LINKAGE dynamic)\nendif()\n') + ldflags = [] if enable_binskim and os_name == "linux": @@ -465,6 +503,16 @@ def generate_triplet_for_posix_platform( f.write(f'set(VCPKG_C_FLAGS_RELWITHDEBINFO "{" ".join(cflags_release)}")\n') f.write(f'set(VCPKG_CXX_FLAGS_RELWITHDEBINFO "{" ".join(cflags_release)}")\n') + if use_telemetry: + # sqlite3 is pulled in only as the 1DS telemetry SDK's offline store; compile it with + # the matching feature reductions (see _SQLITE_TELEMETRY_MINIMAL_DEFINES) to shrink the + # offline-storage footprint and drop runtime extension loading. Scoped per-PORT so it + # only affects sqlite3, and gated on telemetry so non-telemetry builds keep their cache. + sqlite_min_defines = " ".join(_SQLITE_TELEMETRY_MINIMAL_DEFINES) + f.write('if(PORT STREQUAL "sqlite3")\n') + f.write(f' string(APPEND VCPKG_C_FLAGS " {sqlite_min_defines}")\n') + f.write("endif()\n") + # Set target platform # VCPKG_CMAKE_SYSTEM_NAME specifies the target platform. if os_name == "linux": @@ -764,7 +812,13 @@ def generate_windows_triplets(build_dir: str, configs: set[str], toolset_version ) # Pass enable_minimal_build -def generate_linux_triplets(build_dir: str, configs: set[str], use_full_protobuf: bool) -> None: +def generate_linux_triplets( + build_dir: str, + configs: set[str], + use_full_protobuf: bool, + telemetry_shared_sdk: bool = False, + use_telemetry: bool = False, +) -> None: """ Generate triplet files for Linux platforms. @@ -772,6 +826,8 @@ def generate_linux_triplets(build_dir: str, configs: set[str], use_full_protobuf build_dir (str): The directory to save the generated triplet files. configs (set[str]): The set of build configurations. use_full_protobuf (bool): Flag indicating if full Protobuf is used. + telemetry_shared_sdk (bool): Flag indicating if the 1DS telemetry SDK should be built as a shared library. + use_telemetry (bool): Flag indicating if telemetry is enabled (triggers a size-reduced sqlite3 build). """ target_abis = ["x86", "x64", "arm", "arm64", "s390x", "ppc64le", "riscv64", "loongarch64", "mips64"] for enable_rtti in [True, False]: @@ -797,11 +853,18 @@ def generate_linux_triplets(build_dir: str, configs: set[str], use_full_protobuf target_abi, None, use_full_protobuf=use_full_protobuf, + telemetry_shared_sdk=telemetry_shared_sdk, + use_telemetry=use_telemetry, ) def generate_macos_triplets( - build_dir: str, configs: set[str], osx_deployment_target: str, use_full_protobuf: bool + build_dir: str, + configs: set[str], + osx_deployment_target: str, + use_full_protobuf: bool, + telemetry_shared_sdk: bool = False, + use_telemetry: bool = False, ) -> None: """ Generate triplet files for macOS platforms. @@ -810,6 +873,8 @@ def generate_macos_triplets( build_dir (str): The directory to save the generated triplet files. osx_deployment_target (str, optional): The macOS deployment target version. use_full_protobuf (bool): Flag indicating if full Protobuf is used. + telemetry_shared_sdk (bool): Flag indicating if the 1DS telemetry SDK should be built as a shared library. + use_telemetry (bool): Flag indicating if telemetry is enabled (triggers a size-reduced sqlite3 build). """ target_abis = ["x64", "arm64", "universal2"] for enable_rtti in [True, False]: @@ -836,4 +901,6 @@ def generate_macos_triplets( target_abi, osx_deployment_target, use_full_protobuf=use_full_protobuf, + telemetry_shared_sdk=telemetry_shared_sdk, + use_telemetry=use_telemetry, )