diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2fb2be5..659c1d6 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -39,6 +39,27 @@ jobs: # doesn't work in case of CMake + VS (https://github.com/fortran-lang/setup-fortran/issues/45) run: python buildall.py --force_bits 64 -ft ${{ matrix.optional_args }} + cpp_wrapper: + name: Check C++ wrapper + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest + - os: windows-latest + steps: + - name: Checkout sources + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Build C++ wrapper + run: python buildall.py --force_bits 64 -cpp + - name: Test (Linux) + run: ctest --test-dir build_linux/64/cpp --build-config Release --output-on-failure + if: runner.os == 'Linux' + - name: Test (Windows) + run: ctest --test-dir build_win/64/cpp --build-config Release --output-on-failure + if: runner.os == 'Windows' + rust_format: name: Check Rust formatting runs-on: ubuntu-latest diff --git a/CMakeLists.txt b/CMakeLists.txt index 443ad93..8c97cf9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,6 +26,7 @@ project(ittapi) option(FORCE_32 "Force a 32-bit compile on 64-bit" OFF) option(ITT_API_IPT_SUPPORT "ptmarks support" OFF) option(ITT_API_FORTRAN_SUPPORT "fortran support" OFF) +option(ITT_API_CPP_SUPPORT "C++ wrapper support" OFF) option(ITT_API_INSTALL "Enable ITT API installation rules" ON) if(FORCE_32 AND UNIX) @@ -171,6 +172,11 @@ target_include_directories(jitprofiling PRIVATE src/ittnotify ) +# C++ wrapper +if(ITT_API_CPP_SUPPORT) + add_subdirectory(cpp) +endif() + # install include(CMakePackageConfigHelpers) @@ -178,6 +184,11 @@ include(GNUInstallDirs) if(ITT_API_INSTALL) install(TARGETS ittnotify EXPORT ittapi-targets INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) + if(ITT_API_CPP_SUPPORT) + install(TARGETS ittapi-cpp EXPORT ittapi-targets) + install(DIRECTORY cpp/include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} + FILES_MATCHING PATTERN "*.hpp") + endif() install(EXPORT ittapi-targets NAMESPACE ittapi:: DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/ittapi) install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} FILES_MATCHING PATTERN "*.h" diff --git a/README.md b/README.md index 37ea32f..0176d25 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ To build the library: - To list available build options execute: `python buildall.py -h` ``` -usage: buildall.py [-h] [-d] [-c] [-v] [-pt] [-ft] [--force_bits] +usage: buildall.py [-h] [-d] [-c] [-v] [-pt] [-ft] [-cpp] [--force_bits] optional arguments: -h, --help show this help message and exit @@ -50,6 +50,7 @@ optional arguments: -v, --verbose enable verbose output from build process -pt, --ptmark enable anomaly detection support -ft, --fortran enable fortran support + -cpp, --cpp enable C++ wrapper support --force_bits specify bit version for the target --vs specify visual studio version (Windows only) --cmake_gen specify cmake build generator (Windows only) diff --git a/buildall.py b/buildall.py index a85cb7f..180f7fe 100755 --- a/buildall.py +++ b/buildall.py @@ -117,6 +117,8 @@ def main(): "-pt", "--ptmark", help="enable anomaly detection support", action="store_true") parser.add_argument( "-ft", "--fortran", help="enable fortran support", action="store_true") + parser.add_argument( + "-cpp", "--cpp", help="enable C++ wrapper support", action="store_true") parser.add_argument( "--force_bits", choices=["32", "64"], help="specify bit version for the target") if sys.platform == 'win32' and vs_versions: @@ -177,7 +179,8 @@ def main(): ("-DCMAKE_BUILD_TYPE=Debug" if args.debug else ""), ('-DCMAKE_VERBOSE_MAKEFILE:BOOL=ON' if args.verbose else ''), ("-DITT_API_IPT_SUPPORT=1" if args.ptmark else ""), - ("-DITT_API_FORTRAN_SUPPORT=1" if args.fortran else "") + ("-DITT_API_FORTRAN_SUPPORT=1" if args.fortran else ""), + ("-DITT_API_CPP_SUPPORT=ON" if args.cpp else "") ]))) if sys.platform == 'win32': diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt new file mode 100644 index 0000000..56141d5 --- /dev/null +++ b/cpp/CMakeLists.txt @@ -0,0 +1,56 @@ +# +# Copyright (C) 2026 Intel Corporation +# SPDX-License-Identifier: (BSD-3-Clause OR GPL-2.0-only) +# + +add_library(ittapi-cpp INTERFACE) +add_library(ittapi::cxx ALIAS ittapi-cpp) + +target_include_directories(ittapi-cpp + INTERFACE + $ + $ +) + +target_link_libraries(ittapi-cpp INTERFACE ittnotify) +target_compile_features(ittapi-cpp INTERFACE cxx_std_17) + +if(NOT WIN32) + find_package(Threads) +endif() + +# Sample +add_executable(task_sample samples/task_sample.cpp) +target_link_libraries(task_sample PRIVATE ittapi-cpp) +if(NOT WIN32) + target_link_libraries(task_sample PRIVATE ${CMAKE_DL_LIBS}) + if(Threads_FOUND) + target_link_libraries(task_sample PRIVATE Threads::Threads) + endif() +endif() + +# Tests +enable_testing() + +set(CPP_TESTS + test_string_handle + test_domain + test_task + test_region + test_frame + test_collection_control + test_thread_naming + test_ittapi +) + +foreach(test_name ${CPP_TESTS}) + add_executable(${test_name} tests/${test_name}.cpp) + target_link_libraries(${test_name} PRIVATE ittapi-cpp) + if(NOT WIN32) + target_link_libraries(${test_name} PRIVATE ${CMAKE_DL_LIBS}) + if(Threads_FOUND) + target_link_libraries(${test_name} PRIVATE Threads::Threads) + endif() + endif() + add_test(NAME cpp_${test_name} COMMAND ${test_name}) +endforeach() diff --git a/cpp/README.md b/cpp/README.md new file mode 100644 index 0000000..71bf5c3 --- /dev/null +++ b/cpp/README.md @@ -0,0 +1,223 @@ +# ITT API C++ Wrapper + +A modern, header-only C++ wrapper for the [ITT API](https://github.com/intel/ittapi) instrumentation library. The wrapper provides RAII-based scoped helpers and type-safe C++ abstractions over the existing C API. + +## Supported APIs + +| API | C++ Wrapper | +|-----|------------| +| String Handle | `ittapi::StringHandle` | +| Domain | `ittapi::Domain` | +| Task | `ittapi::ScopedTask`, `Domain::task_begin()` / `Domain::task_end()` | +| Region | `ittapi::ScopedRegion` | +| Frame | `ittapi::ScopedFrame` | +| Collection Control | `ittapi::pause()`, `ittapi::resume()`, `ittapi::ScopedPause` | +| Thread Naming | `ittapi::set_thread_name()` | + +## Requirements + +- C++17 or later +- The existing `ittnotify` static C-library + +## Including the Wrapper + +Use the umbrella header to get the full API: + +```cpp +#include +``` + +Or include individual headers: + +```cpp +#include +#include +#include +``` + +## Example: Task Instrumentation + +```cpp +#include + +#include +#include + +int main() { + ittapi::set_thread_name("main"); + ittapi::Domain domain{"example.task"}; + ittapi::StringHandle task_name{"process"}; + + ittapi::pause(); + ittapi::resume(); + + { + auto task = domain.task(task_name); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + + return 0; +} +``` + +## Linking + +### CMake Consumer + +```cmake +find_package(ittapi CONFIG REQUIRED) +target_link_libraries(my_app PRIVATE ittapi::cxx) +``` + +The `ittapi::cxx` target is an `INTERFACE` library that transitively links the existing `ittnotify` static library and adds the C++ wrapper include directories. + +### Manual GCC/G++ (Linux) + +```bash +g++ -std=c++17 -O2 \ + -I/include \ + app.cpp \ + /lib/libittnotify.a \ + -ldl -pthread \ + -o app +``` + +## Building with CMake + +From the repository root: + +```bash +cmake -B build -DCMAKE_BUILD_TYPE=Release -DITT_API_CPP_SUPPORT=ON +cmake --build build +``` + +The `ITT_API_CPP_SUPPORT` option is `OFF` by default. + +You can also build with the build script: + +```bash +python buildall.py --cpp +``` + +## Running Tests + +Tests are registered inside the `cpp/` subdirectory. After building: + +```bash +ctest --test-dir build/cpp --output-on-failure +``` + +## Performance Tips + +- **Pre-create `StringHandle` objects** for task/region names used in hot paths. The `StringHandle` overloads pass a raw pointer with no locking — this is the zero-overhead path. + ```cpp + // Do this once at startup: + ittapi::StringHandle name{"compute"}; + + // Then in hot code: + auto task = domain.task(name); // no allocation, no lock + ``` +- **The `string_view` overloads** are convenient but allocate a `std::string` on cache miss and acquire a lock in the C library. Use them for setup or infrequent tasks, not tight loops. +- **Create `Domain` objects once** and reuse them. Domain creation is a global lookup — store them as class members or globals, not as function locals called repeatedly. +- **Use overlapped tasks** (with IDs) only when you need tasks that end out of order. Stack-based tasks (without IDs) are simpler and carry less internal state. + +## API Reference + +### Free Functions + +- `ittapi::pause()` — Pause collection. +- `ittapi::resume()` — Resume collection. +- `ittapi::set_thread_name(std::string_view name)` — Set the current thread's name. + +### Classes + +#### `ittapi::StringHandle` + +Lightweight wrapper around `__itt_string_handle*`. + +```cpp +ittapi::StringHandle h{"my_handle"}; +h.valid(); // true if handle was created +h.get(); // underlying __itt_string_handle* +``` + +#### `ittapi::Domain` + +Lightweight wrapper around `__itt_domain*` with convenience factories. + +```cpp +ittapi::Domain d{"my.domain"}; +auto task = d.task("task_name"); // returns ScopedTask (RAII) +auto region = d.region("region_name"); // returns ScopedRegion +auto frame = d.frame(); // returns ScopedFrame + +d.task_begin("work"); // manual task begin (simple stack-based task) +d.task_end(); // manual task end + +__itt_id id = __itt_id_make(nullptr, 1); +d.task_begin("overlapped", id, __itt_null); // manual task begin (overlapped task) +d.task_end(id); // manual task end by ID +``` + +#### `ittapi::ScopedTask` + +RAII wrapper for task begin/end. Use `domain.overlapped_task()` to create an overlapped task (tasks that can end in any order). Each overlapped task instance gets a unique `__itt_id` derived from its object address. The `id()` method returns this ID. + +```cpp +// Simple task +{ + auto task = domain.task("work"); + // ... do work ... + task.end(); // optional early end (idempotent) +} // destructor ends task if still active +``` + +Overlapped tasks — use `overlapped_task()`, optionally pass a parent ID: + +```cpp +{ + auto parent = domain.overlapped_task("parent"); // overlapped, no parent + auto child = domain.overlapped_task("child", parent.id()); // overlapped, child of parent + + parent.end(); // end parent task while child is still running + child.end(); // child task ends +} +``` + +For manual (non-RAII) control: + +```cpp +domain.task_begin("work"); +// ... do work ... +domain.task_end(); + +// overlapped tasks: +__itt_id id = __itt_id_make(nullptr, 1); +domain.task_begin("overlapped_work", id, __itt_null); +// ... do work ... +domain.task_end(id); +``` + +#### `ittapi::ScopedRegion` + +RAII wrapper for region begin/end. + +#### `ittapi::ScopedFrame` + +RAII wrapper for frame begin/end. Supports explicit timestamp submission. + +```cpp +ittapi::ScopedFrame::submit(domain.get(), begin_ts, end_ts); +``` + +#### `ittapi::ScopedPause` + +RAII wrapper for pause/resume. Constructor pauses, destructor resumes. + +```cpp +{ + ittapi::ScopedPause sp; + // collection is paused + sp.resume_now(); // optional early resume +} +``` diff --git a/cpp/include/ittapi.hpp b/cpp/include/ittapi.hpp new file mode 100644 index 0000000..67d6557 --- /dev/null +++ b/cpp/include/ittapi.hpp @@ -0,0 +1,17 @@ +/* + Copyright (C) 2026 Intel Corporation + SPDX-License-Identifier: (BSD-3-Clause OR GPL-2.0-only) +*/ + +#ifndef ITTAPI_HPP +#define ITTAPI_HPP + +#include "ittapi_collection_control.hpp" +#include "ittapi_domain.hpp" +#include "ittapi_frame.hpp" +#include "ittapi_region.hpp" +#include "ittapi_string_handle.hpp" +#include "ittapi_task.hpp" +#include "ittapi_thread_naming.hpp" + +#endif // ITTAPI_HPP diff --git a/cpp/include/ittapi_collection_control.hpp b/cpp/include/ittapi_collection_control.hpp new file mode 100644 index 0000000..a6da28d --- /dev/null +++ b/cpp/include/ittapi_collection_control.hpp @@ -0,0 +1,71 @@ +/* + Copyright (C) 2026 Intel Corporation + SPDX-License-Identifier: (BSD-3-Clause OR GPL-2.0-only) +*/ + +#ifndef ITTAPI_COLLECTION_CONTROL_HPP +#define ITTAPI_COLLECTION_CONTROL_HPP + +#include + +namespace ittapi +{ + +inline void pause() noexcept +{ + __itt_pause(); +} + +inline void resume() noexcept +{ + __itt_resume(); +} + +class ScopedPause +{ +public: + ScopedPause() noexcept + : m_active(true) + { + __itt_pause(); + } + + ScopedPause(const ScopedPause&) = delete; + ScopedPause& operator=(const ScopedPause&) = delete; + ScopedPause& operator=(ScopedPause&&) = delete; + + ScopedPause(ScopedPause&& other) noexcept + : m_active(other.m_active) + { + other.m_active = false; + } + + ~ScopedPause() noexcept + { + if (m_active) + { + __itt_resume(); + } + } + + void resume_now() noexcept + { + if (m_active) + { + __itt_resume(); + m_active = false; + } + } + + bool active() const noexcept + { + return m_active; + } + +private: + bool m_active = false; +}; + +} // namespace ittapi + +#endif // ITTAPI_COLLECTION_CONTROL_HPP diff --git a/cpp/include/ittapi_domain.hpp b/cpp/include/ittapi_domain.hpp new file mode 100644 index 0000000..86d1746 --- /dev/null +++ b/cpp/include/ittapi_domain.hpp @@ -0,0 +1,219 @@ +/* + Copyright (C) 2026 Intel Corporation + SPDX-License-Identifier: (BSD-3-Clause OR GPL-2.0-only) +*/ + +#ifndef ITTAPI_DOMAIN_HPP +#define ITTAPI_DOMAIN_HPP + +#include +#include + +#include "ittapi_utils.hpp" +#include "ittapi_string_handle.hpp" +#include "ittapi_task.hpp" +#include "ittapi_region.hpp" +#include "ittapi_frame.hpp" + +namespace ittapi +{ + +class Domain +{ +public: + explicit Domain(std::string_view name) + : m_domain(detail::create_domain(std::string(name).c_str())) + { + } + +#if ITT_PLATFORM == ITT_PLATFORM_WIN + explicit Domain(std::wstring_view name) + : m_domain(detail::create_domain(std::wstring(name).c_str())) + { + } +#endif + + __itt_domain* get() const noexcept + { + return m_domain; + } + + bool valid() const noexcept + { + return m_domain != nullptr; + } + + Domain(const Domain&) = default; + Domain& operator=(const Domain&) = default; + + Domain(Domain&& other) noexcept + : m_domain(other.m_domain) + { + other.m_domain = nullptr; + } + + Domain& operator=(Domain&& other) noexcept + { + m_domain = other.m_domain; + other.m_domain = nullptr; + return *this; + } + + [[nodiscard]] ScopedTask task(std::string_view name) const + { + return ScopedTask(m_domain, name); + } + + [[nodiscard]] ScopedTask overlapped_task(std::string_view name, + __itt_id parentid = detail::get_null_id()) const + { + return ScopedTask(m_domain, name, detail::get_null_id(), parentid, true); + } + + [[nodiscard]] ScopedTask overlapped_task(std::string_view name, + __itt_id taskid, __itt_id parentid) const + { + return ScopedTask(m_domain, name, taskid, parentid, true); + } + + [[nodiscard]] ScopedTask task(std::string_view name, __itt_id taskid, __itt_id parentid) const + { + return ScopedTask(m_domain, name, taskid, parentid); + } + +#if ITT_PLATFORM == ITT_PLATFORM_WIN + [[nodiscard]] ScopedTask task(std::wstring_view name) const + { + return ScopedTask(m_domain, name); + } + + [[nodiscard]] ScopedTask overlapped_task(std::wstring_view name, + __itt_id parentid = detail::get_null_id()) const + { + return ScopedTask(m_domain, name, detail::get_null_id(), parentid, true); + } + + [[nodiscard]] ScopedTask overlapped_task(std::wstring_view name, + __itt_id taskid, __itt_id parentid) const + { + return ScopedTask(m_domain, name, taskid, parentid, true); + } + + [[nodiscard]] ScopedTask task(std::wstring_view name, __itt_id taskid, __itt_id parentid) const + { + return ScopedTask(m_domain, name, taskid, parentid); + } +#endif + + [[nodiscard]] ScopedTask task(const StringHandle& name) const noexcept + { + return ScopedTask(m_domain, name); + } + + [[nodiscard]] ScopedTask overlapped_task(const StringHandle& name, + __itt_id parentid = detail::get_null_id()) const noexcept + { + return ScopedTask(m_domain, name, detail::get_null_id(), parentid, true); + } + + [[nodiscard]] ScopedTask overlapped_task(const StringHandle& name, + __itt_id taskid, __itt_id parentid) const noexcept + { + return ScopedTask(m_domain, name, taskid, parentid, true); + } + + [[nodiscard]] ScopedTask task(const StringHandle& name, __itt_id taskid, __itt_id parentid) const noexcept + { + return ScopedTask(m_domain, name, taskid, parentid); + } + + void task_begin(std::string_view name) const + { + __itt_string_handle* h = detail::get_or_create_string_handle(name); + __itt_task_begin(m_domain, detail::get_null_id(), detail::get_null_id(), h); + } + + void task_begin(std::string_view name, __itt_id taskid, __itt_id parentid) const + { + __itt_string_handle* h = detail::get_or_create_string_handle(name); + __itt_task_begin_overlapped(m_domain, taskid, parentid, h); + } + +#if ITT_PLATFORM == ITT_PLATFORM_WIN + void task_begin(std::wstring_view name) const + { + __itt_string_handle* h = detail::get_or_create_string_handle(name); + __itt_task_begin(m_domain, detail::get_null_id(), detail::get_null_id(), h); + } + + void task_begin(std::wstring_view name, __itt_id taskid, __itt_id parentid) const + { + __itt_string_handle* h = detail::get_or_create_string_handle(name); + __itt_task_begin_overlapped(m_domain, taskid, parentid, h); + } +#endif + + void task_begin(const StringHandle& name) const noexcept + { + __itt_task_begin(m_domain, detail::get_null_id(), detail::get_null_id(), name.get()); + } + + void task_begin(const StringHandle& name, __itt_id taskid, __itt_id parentid) const noexcept + { + __itt_task_begin_overlapped(m_domain, taskid, parentid, name.get()); + } + + void task_end() const noexcept + { + __itt_task_end(m_domain); + } + + void task_end(__itt_id taskid) const noexcept + { + __itt_task_end_overlapped(m_domain, taskid); + } + + [[nodiscard]] ScopedRegion region(std::string_view name) const + { + return ScopedRegion(m_domain, name); + } + + [[nodiscard]] ScopedRegion region(std::string_view name, __itt_id id, __itt_id parentid) const + { + return ScopedRegion(m_domain, name, id, parentid); + } + +#if ITT_PLATFORM == ITT_PLATFORM_WIN + [[nodiscard]] ScopedRegion region(std::wstring_view name) const + { + return ScopedRegion(m_domain, name); + } + + [[nodiscard]] ScopedRegion region(std::wstring_view name, __itt_id id, __itt_id parentid) const + { + return ScopedRegion(m_domain, name, id, parentid); + } +#endif + + [[nodiscard]] ScopedRegion region(const StringHandle& name) const noexcept + { + return ScopedRegion(m_domain, name); + } + + [[nodiscard]] ScopedRegion region(const StringHandle& name, __itt_id id, __itt_id parentid) const noexcept + { + return ScopedRegion(m_domain, name, id, parentid); + } + + [[nodiscard]] ScopedFrame frame() const noexcept + { + return ScopedFrame(m_domain); + } + +private: + __itt_domain* m_domain = nullptr; +}; + +} // namespace ittapi + +#endif // ITTAPI_DOMAIN_HPP diff --git a/cpp/include/ittapi_frame.hpp b/cpp/include/ittapi_frame.hpp new file mode 100644 index 0000000..8fecc2f --- /dev/null +++ b/cpp/include/ittapi_frame.hpp @@ -0,0 +1,72 @@ +/* + Copyright (C) 2026 Intel Corporation + SPDX-License-Identifier: (BSD-3-Clause OR GPL-2.0-only) +*/ + +#ifndef ITTAPI_FRAME_HPP +#define ITTAPI_FRAME_HPP + +#include "ittapi_utils.hpp" + +namespace ittapi +{ + +class Domain; + +class ScopedFrame +{ +public: + explicit ScopedFrame(const __itt_domain* domain) noexcept + : m_domain(domain) + , m_id(detail::get_null_id()) + , m_active(true) + { + __itt_frame_begin_v3(m_domain, &m_id); + } + + ScopedFrame(const ScopedFrame&) = delete; + ScopedFrame& operator=(const ScopedFrame&) = delete; + ScopedFrame& operator=(ScopedFrame&&) = delete; + + ScopedFrame(ScopedFrame&& other) noexcept + : m_domain(other.m_domain) + , m_id(other.m_id) + , m_active(other.m_active) + { + other.m_active = false; + } + + ~ScopedFrame() noexcept + { + end(); + } + + void end() noexcept + { + if (m_active) + { + __itt_frame_end_v3(m_domain, &m_id); + m_active = false; + } + } + + bool active() const noexcept + { + return m_active; + } + + static void submit(const __itt_domain* domain, __itt_timestamp begin, __itt_timestamp end) noexcept + { + __itt_id id = detail::get_null_id(); + __itt_frame_submit_v3(domain, &id, begin, end); + } + +private: + const __itt_domain* m_domain = nullptr; + __itt_id m_id{}; + bool m_active = false; +}; + +} // namespace ittapi + +#endif // ITTAPI_FRAME_HPP diff --git a/cpp/include/ittapi_region.hpp b/cpp/include/ittapi_region.hpp new file mode 100644 index 0000000..41f071f --- /dev/null +++ b/cpp/include/ittapi_region.hpp @@ -0,0 +1,119 @@ +/* + Copyright (C) 2026 Intel Corporation + SPDX-License-Identifier: (BSD-3-Clause OR GPL-2.0-only) +*/ + +#ifndef ITTAPI_REGION_HPP +#define ITTAPI_REGION_HPP + +#include +#include + +#include "ittapi_utils.hpp" +#include "ittapi_string_handle.hpp" + +namespace ittapi +{ + +class Domain; + +class ScopedRegion +{ +public: + ScopedRegion(const __itt_domain* domain, std::string_view name) + : m_domain(domain) + , m_id(detail::get_null_id()) + , m_active(true) + { + __itt_string_handle* h = detail::get_or_create_string_handle(name); + __itt_region_begin(m_domain, m_id, detail::get_null_id(), h); + } + + ScopedRegion(const __itt_domain* domain, std::string_view name, + __itt_id id, __itt_id parentid) + : m_domain(domain) + , m_id(id) + , m_active(true) + { + __itt_string_handle* h = detail::get_or_create_string_handle(name); + __itt_region_begin(m_domain, m_id, parentid, h); + } + +#if ITT_PLATFORM == ITT_PLATFORM_WIN + ScopedRegion(const __itt_domain* domain, std::wstring_view name) + : m_domain(domain) + , m_id(detail::get_null_id()) + , m_active(true) + { + __itt_string_handle* h = detail::get_or_create_string_handle(name); + __itt_region_begin(m_domain, m_id, detail::get_null_id(), h); + } + + ScopedRegion(const __itt_domain* domain, std::wstring_view name, + __itt_id id, __itt_id parentid) + : m_domain(domain) + , m_id(id) + , m_active(true) + { + __itt_string_handle* h = detail::get_or_create_string_handle(name); + __itt_region_begin(m_domain, m_id, parentid, h); + } +#endif + + ScopedRegion(const __itt_domain* domain, const StringHandle& name) noexcept + : m_domain(domain) + , m_id(detail::get_null_id()) + , m_active(true) + { + __itt_region_begin(m_domain, m_id, detail::get_null_id(), name.get()); + } + + ScopedRegion(const __itt_domain* domain, const StringHandle& name, + __itt_id id, __itt_id parentid) noexcept + : m_domain(domain) + , m_id(id) + , m_active(true) + { + __itt_region_begin(m_domain, m_id, parentid, name.get()); + } + + ScopedRegion(const ScopedRegion&) = delete; + ScopedRegion& operator=(const ScopedRegion&) = delete; + ScopedRegion& operator=(ScopedRegion&&) = delete; + + ScopedRegion(ScopedRegion&& other) noexcept + : m_domain(other.m_domain) + , m_id(other.m_id) + , m_active(other.m_active) + { + other.m_active = false; + } + + ~ScopedRegion() noexcept + { + end(); + } + + void end() noexcept + { + if (m_active) + { + __itt_region_end(m_domain, m_id); + m_active = false; + } + } + + bool active() const noexcept + { + return m_active; + } + +private: + const __itt_domain* m_domain = nullptr; + __itt_id m_id{}; + bool m_active = false; +}; + +} // namespace ittapi + +#endif // ITTAPI_REGION_HPP diff --git a/cpp/include/ittapi_string_handle.hpp b/cpp/include/ittapi_string_handle.hpp new file mode 100644 index 0000000..533407a --- /dev/null +++ b/cpp/include/ittapi_string_handle.hpp @@ -0,0 +1,64 @@ +/* + Copyright (C) 2026 Intel Corporation + SPDX-License-Identifier: (BSD-3-Clause OR GPL-2.0-only) +*/ + +#ifndef ITTAPI_STRING_HANDLE_HPP +#define ITTAPI_STRING_HANDLE_HPP + +#include +#include + +#include "ittapi_utils.hpp" + +namespace ittapi +{ + +class StringHandle +{ +public: + explicit StringHandle(std::string_view name) + : m_handle(detail::create_string_handle(std::string(name).c_str())) + { + } + +#if ITT_PLATFORM == ITT_PLATFORM_WIN + explicit StringHandle(std::wstring_view name) + : m_handle(detail::create_string_handle(std::wstring(name).c_str())) + { + } +#endif + + __itt_string_handle* get() const noexcept + { + return m_handle; + } + + bool valid() const noexcept + { + return m_handle != nullptr; + } + + StringHandle(const StringHandle&) = default; + StringHandle& operator=(const StringHandle&) = default; + + StringHandle(StringHandle&& other) noexcept + : m_handle(other.m_handle) + { + other.m_handle = nullptr; + } + + StringHandle& operator=(StringHandle&& other) noexcept + { + m_handle = other.m_handle; + other.m_handle = nullptr; + return *this; + } + +private: + __itt_string_handle* m_handle = nullptr; +}; + +} // namespace ittapi + +#endif // ITTAPI_STRING_HANDLE_HPP diff --git a/cpp/include/ittapi_task.hpp b/cpp/include/ittapi_task.hpp new file mode 100644 index 0000000..7697d68 --- /dev/null +++ b/cpp/include/ittapi_task.hpp @@ -0,0 +1,172 @@ +/* + Copyright (C) 2026 Intel Corporation + SPDX-License-Identifier: (BSD-3-Clause OR GPL-2.0-only) +*/ + +#ifndef ITTAPI_TASK_HPP +#define ITTAPI_TASK_HPP + +#include +#include + +#include "ittapi_utils.hpp" +#include "ittapi_string_handle.hpp" + +namespace ittapi +{ + +class Domain; + +class ScopedTask +{ +public: + ScopedTask(const __itt_domain* domain, std::string_view name) + : m_domain(domain) + , m_taskid(detail::get_null_id()) + , m_overlapped(false) + , m_active(true) + { + __itt_string_handle* h = detail::get_or_create_string_handle(name); + __itt_task_begin(m_domain, detail::get_null_id(), detail::get_null_id(), h); + } + + ScopedTask(const __itt_domain* domain, std::string_view name, + __itt_id taskid, __itt_id parentid, bool overlapped = false) + : m_domain(domain) + , m_taskid(taskid) + , m_overlapped(overlapped) + , m_active(true) + { + if (m_overlapped && detail::is_null_id(m_taskid)) + { + m_taskid = __itt_id_make(this, 0); + } + __itt_string_handle* h = detail::get_or_create_string_handle(name); + if (m_overlapped) + { + __itt_task_begin_overlapped(m_domain, m_taskid, parentid, h); + } + else + { + __itt_task_begin(m_domain, m_taskid, parentid, h); + } + } + +#if ITT_PLATFORM == ITT_PLATFORM_WIN + ScopedTask(const __itt_domain* domain, std::wstring_view name) + : m_domain(domain) + , m_taskid(detail::get_null_id()) + , m_overlapped(false) + , m_active(true) + { + __itt_string_handle* h = detail::get_or_create_string_handle(name); + __itt_task_begin(m_domain, detail::get_null_id(), detail::get_null_id(), h); + } + + ScopedTask(const __itt_domain* domain, std::wstring_view name, + __itt_id taskid, __itt_id parentid, bool overlapped = false) + : m_domain(domain) + , m_taskid(taskid) + , m_overlapped(overlapped) + , m_active(true) + { + if (m_overlapped && detail::is_null_id(m_taskid)) + { + m_taskid = __itt_id_make(this, 0); + } + __itt_string_handle* h = detail::get_or_create_string_handle(name); + if (m_overlapped) + { + __itt_task_begin_overlapped(m_domain, m_taskid, parentid, h); + } + else + { + __itt_task_begin(m_domain, m_taskid, parentid, h); + } + } +#endif + + ScopedTask(const __itt_domain* domain, const StringHandle& name) noexcept + : m_domain(domain) + , m_taskid(detail::get_null_id()) + , m_overlapped(false) + , m_active(true) + { + __itt_task_begin(m_domain, detail::get_null_id(), detail::get_null_id(), name.get()); + } + + ScopedTask(const __itt_domain* domain, const StringHandle& name, + __itt_id taskid, __itt_id parentid, bool overlapped = false) noexcept + : m_domain(domain) + , m_taskid(taskid) + , m_overlapped(overlapped) + , m_active(true) + { + if (m_overlapped && detail::is_null_id(m_taskid)) + { + m_taskid = __itt_id_make(this, 0); + } + if (m_overlapped) + { + __itt_task_begin_overlapped(m_domain, m_taskid, parentid, name.get()); + } + else + { + __itt_task_begin(m_domain, m_taskid, parentid, name.get()); + } + } + + ScopedTask(const ScopedTask&) = delete; + ScopedTask& operator=(const ScopedTask&) = delete; + ScopedTask& operator=(ScopedTask&&) = delete; + + ScopedTask(ScopedTask&& other) noexcept + : m_domain(other.m_domain) + , m_taskid(other.m_taskid) + , m_overlapped(other.m_overlapped) + , m_active(other.m_active) + { + other.m_active = false; + } + + ~ScopedTask() noexcept + { + end(); + } + + void end() noexcept + { + if (m_active) + { + if (m_overlapped) + { + __itt_task_end_overlapped(m_domain, m_taskid); + } + else + { + __itt_task_end(m_domain); + } + m_active = false; + } + } + + bool active() const noexcept + { + return m_active; + } + + __itt_id id() const noexcept + { + return m_taskid; + } + +private: + const __itt_domain* m_domain = nullptr; + __itt_id m_taskid{}; + bool m_overlapped = false; + bool m_active = false; +}; + +} // namespace ittapi + +#endif // ITTAPI_TASK_HPP diff --git a/cpp/include/ittapi_thread_naming.hpp b/cpp/include/ittapi_thread_naming.hpp new file mode 100644 index 0000000..699c78d --- /dev/null +++ b/cpp/include/ittapi_thread_naming.hpp @@ -0,0 +1,31 @@ +/* + Copyright (C) 2026 Intel Corporation + SPDX-License-Identifier: (BSD-3-Clause OR GPL-2.0-only) +*/ + +#ifndef ITTAPI_THREAD_NAMING_HPP +#define ITTAPI_THREAD_NAMING_HPP + +#include +#include + +#include "ittapi_utils.hpp" + +namespace ittapi +{ + +inline void set_thread_name(std::string_view name) +{ + detail::thread_set_name(std::string(name).c_str()); +} + +#if ITT_PLATFORM == ITT_PLATFORM_WIN +inline void set_thread_name(std::wstring_view name) +{ + detail::thread_set_name(std::wstring(name).c_str()); +} +#endif + +} // namespace ittapi + +#endif // ITTAPI_THREAD_NAMING_HPP diff --git a/cpp/include/ittapi_utils.hpp b/cpp/include/ittapi_utils.hpp new file mode 100644 index 0000000..345239a --- /dev/null +++ b/cpp/include/ittapi_utils.hpp @@ -0,0 +1,112 @@ +/* + Copyright (C) 2026 Intel Corporation + SPDX-License-Identifier: (BSD-3-Clause OR GPL-2.0-only) +*/ + +#ifndef ITTAPI_UTILS_HPP +#define ITTAPI_UTILS_HPP + +#include + +#include +#include +#include + +namespace ittapi +{ +namespace detail +{ + +inline __itt_domain* create_domain(const char* name) noexcept +{ +#if ITT_PLATFORM == ITT_PLATFORM_WIN + return __itt_domain_createA(name); +#else + return __itt_domain_create(name); +#endif +} + +inline __itt_string_handle* create_string_handle(const char* name) noexcept +{ +#if ITT_PLATFORM == ITT_PLATFORM_WIN + return __itt_string_handle_createA(name); +#else + return __itt_string_handle_create(name); +#endif +} + +inline __itt_string_handle* get_or_create_string_handle(std::string_view name) +{ + thread_local std::unordered_map cache; + auto it = cache.find(name); + if (it != cache.end()) + { + return it->second; + } + __itt_string_handle* h = create_string_handle(std::string(name).c_str()); + if (h != nullptr) + { + cache.emplace(h->strA, h); + } + return h; +} + +inline void thread_set_name(const char* name) noexcept +{ +#if ITT_PLATFORM == ITT_PLATFORM_WIN + __itt_thread_set_nameA(name); +#else + __itt_thread_set_name(name); +#endif +} + +#if ITT_PLATFORM == ITT_PLATFORM_WIN + +inline __itt_domain* create_domain(const wchar_t* name) noexcept +{ + return __itt_domain_createW(name); +} + +inline __itt_string_handle* create_string_handle(const wchar_t* name) noexcept +{ + return __itt_string_handle_createW(name); +} + +inline __itt_string_handle* get_or_create_string_handle(std::wstring_view name) +{ + thread_local std::unordered_map cache; + auto it = cache.find(name); + if (it != cache.end()) + { + return it->second; + } + __itt_string_handle* h = create_string_handle(std::wstring(name).c_str()); + if (h != nullptr) + { + cache.emplace(static_cast(h->strW), h); + } + return h; +} + +inline void thread_set_name(const wchar_t* name) noexcept +{ + __itt_thread_set_nameW(name); +} + +#endif // ITT_PLATFORM == ITT_PLATFORM_WIN + +inline __itt_id get_null_id() noexcept +{ + __itt_id id = __itt_null; + return id; +} + +inline bool is_null_id(const __itt_id& id) noexcept +{ + return id.d1 == 0 && id.d2 == 0 && id.d3 == 0; +} + +} // namespace detail +} // namespace ittapi + +#endif // ITTAPI_UTILS_HPP diff --git a/cpp/samples/task_sample.cpp b/cpp/samples/task_sample.cpp new file mode 100644 index 0000000..4fec496 --- /dev/null +++ b/cpp/samples/task_sample.cpp @@ -0,0 +1,102 @@ +/* + Copyright (C) 2026 Intel Corporation + SPDX-License-Identifier: (BSD-3-Clause OR GPL-2.0-only) +*/ + +#include + +#include +#include + +static void simulate_work(int ms) +{ + std::this_thread::sleep_for(std::chrono::milliseconds(ms)); +} + +int main() +{ + // Thread naming + ittapi::set_thread_name("main"); + + // Domain and string handle creation + ittapi::Domain domain{"example.app"}; + ittapi::StringHandle task_name{"process"}; + + // Collection control — pause/resume + ittapi::pause(); + ittapi::resume(); + + // Scoped task with pre-created StringHandle (zero-overhead path) + { + auto task = domain.task(task_name); + simulate_work(10); + } + + // Scoped task with inline string (convenience path) + { + auto task = domain.task("startup"); + simulate_work(10); + } + + // Scoped task with early end + { + auto task = domain.task("partial_work"); + simulate_work(5); + task.end(); // end early, destructor is a no-op + } + + // Overlapped tasks — parent and child can end in any order + { + auto parent = domain.overlapped_task("parent_task"); + simulate_work(5); + + auto child = domain.overlapped_task("child_task", parent.id()); + simulate_work(5); + + parent.end(); // end parent while child is still running + simulate_work(5); + } // child ends here via destructor + + // Manual task begin/end (non-RAII, simple task) + domain.task_begin("manual_work"); + simulate_work(5); + domain.task_end(); + + // Manual overlapped task begin/end (ID-based) + { + __itt_id id = __itt_id_make(nullptr, 3); + domain.task_begin("overlapped_manual", id, __itt_null); + simulate_work(5); + domain.task_end(id); + } + + // Scoped region + { + auto region = domain.region("init_phase"); + simulate_work(10); + } + + // Scoped frame + { + auto frame = domain.frame(); + simulate_work(10); + } + + // Frame submit with explicit timestamps + { + __itt_timestamp begin = __itt_get_timestamp(); + simulate_work(10); + __itt_timestamp end = __itt_get_timestamp(); + ittapi::ScopedFrame::submit(domain.get(), begin, end); + } + + // Scoped pause — collection paused within scope + { + ittapi::ScopedPause sp; + simulate_work(10); // not collected + sp.resume_now(); // resume early + simulate_work(10); // collected + } + + return 0; +} diff --git a/cpp/tests/test_collection_control.cpp b/cpp/tests/test_collection_control.cpp new file mode 100644 index 0000000..d0abdea --- /dev/null +++ b/cpp/tests/test_collection_control.cpp @@ -0,0 +1,53 @@ +/* + Copyright (C) 2026 Intel Corporation + SPDX-License-Identifier: (BSD-3-Clause OR GPL-2.0-only) +*/ + +#include +#include "test_helpers.hpp" + +#include + +static void test_pause_resume() +{ + ittapi::pause(); + ittapi::resume(); +} + +static void test_scoped_pause_resumes_on_destruction() +{ + + { + ittapi::ScopedPause sp; + CHECK(sp.active()); + } + // destructor should have called resume() +} + +static void test_resume_now_disables_destructor() +{ + ittapi::ScopedPause sp; + CHECK(sp.active()); + sp.resume_now(); + CHECK(!sp.active()); + // destructor should not call resume() again +} + +static void test_move_construction() +{ + ittapi::ScopedPause sp1; + CHECK(sp1.active()); + + auto sp2 = std::move(sp1); + CHECK(!sp1.active()); + CHECK(sp2.active()); +} + +int main() +{ + test_pause_resume(); + test_scoped_pause_resumes_on_destruction(); + test_resume_now_disables_destructor(); + test_move_construction(); + return 0; +} diff --git a/cpp/tests/test_domain.cpp b/cpp/tests/test_domain.cpp new file mode 100644 index 0000000..66371df --- /dev/null +++ b/cpp/tests/test_domain.cpp @@ -0,0 +1,68 @@ +/* + Copyright (C) 2026 Intel Corporation + SPDX-License-Identifier: (BSD-3-Clause OR GPL-2.0-only) +*/ + +#include +#include "test_helpers.hpp" + +static void test_construct_domain() +{ + ittapi::Domain d{"test.domain"}; + ittapi::test::check_domain_name(d, "test.domain"); +} + +static void test_create_task_from_domain() +{ + ittapi::Domain d{"test.domain.task"}; + ittapi::test::check_domain_name(d, "test.domain.task"); + + { + auto task = d.task("my_task"); + CHECK(task.active()); + } +} + +static void test_create_task_with_string_handle() +{ + ittapi::Domain d{"test.domain.task_sh"}; + ittapi::StringHandle name{"my_task"}; + ittapi::test::check_string_handle_name(name, "my_task"); + + { + auto task = d.task(name); + CHECK(task.active()); + } +} + +static void test_create_region_from_domain() +{ + ittapi::Domain d{"test.domain.region"}; + ittapi::test::check_domain_name(d, "test.domain.region"); + + { + auto region = d.region("my_region"); + CHECK(region.active()); + } +} + +static void test_create_frame_from_domain() +{ + ittapi::Domain d{"test.domain.frame"}; + ittapi::test::check_domain_name(d, "test.domain.frame"); + + { + auto frame = d.frame(); + CHECK(frame.active()); + } +} + +int main() +{ + test_construct_domain(); + test_create_task_from_domain(); + test_create_task_with_string_handle(); + test_create_region_from_domain(); + test_create_frame_from_domain(); + return 0; +} diff --git a/cpp/tests/test_frame.cpp b/cpp/tests/test_frame.cpp new file mode 100644 index 0000000..2868e20 --- /dev/null +++ b/cpp/tests/test_frame.cpp @@ -0,0 +1,59 @@ +/* + Copyright (C) 2026 Intel Corporation + SPDX-License-Identifier: (BSD-3-Clause OR GPL-2.0-only) +*/ + +#include +#include +#include "test_helpers.hpp" + +#include + +static void test_scoped_frame_lifecycle() +{ + ittapi::Domain d{"test.frame.lifecycle"}; + ittapi::test::check_domain_name(d, "test.frame.lifecycle"); + + { + auto frame = d.frame(); + CHECK(frame.active()); + } +} + +static void test_explicit_end_is_idempotent() +{ + ittapi::Domain d{"test.frame.end"}; + auto frame = d.frame(); + CHECK(frame.active()); + frame.end(); + CHECK(!frame.active()); + frame.end(); + CHECK(!frame.active()); +} + +static void test_move_construction() +{ + ittapi::Domain d{"test.frame.move"}; + auto f1 = d.frame(); + CHECK(f1.active()); + + auto f2 = std::move(f1); + CHECK(!f1.active()); + CHECK(f2.active()); +} + +static void test_submit() +{ + ittapi::Domain d{"test.frame.submit"}; + // Just verify it compiles and runs without a collector + ittapi::ScopedFrame::submit(d.get(), 0, 100); +} + +int main() +{ + test_scoped_frame_lifecycle(); + test_explicit_end_is_idempotent(); + test_move_construction(); + test_submit(); + return 0; +} diff --git a/cpp/tests/test_helpers.hpp b/cpp/tests/test_helpers.hpp new file mode 100644 index 0000000..d852642 --- /dev/null +++ b/cpp/tests/test_helpers.hpp @@ -0,0 +1,91 @@ +/* + Copyright (C) 2026 Intel Corporation + SPDX-License-Identifier: (BSD-3-Clause OR GPL-2.0-only) +*/ + +#pragma once + +#include +#include +#include + +#include +#include +#include + +// --------------------------------------------------------------------------- +// Assertion macros with file/line diagnostics +// --------------------------------------------------------------------------- + +#define CHECK(expr) \ + do \ + { \ + if (!(expr)) \ + { \ + std::fprintf(stderr, "FAIL %s:%d: %s\n", \ + __FILE__, __LINE__, #expr); \ + std::abort(); \ + } \ + } while (0) + +#define CHECK_STR_EQ(actual, expected) \ + do \ + { \ + const char* a_ = (actual); \ + const char* e_ = (expected); \ + if (std::strcmp(a_, e_) != 0) \ + { \ + std::fprintf(stderr, "FAIL %s:%d: expected \"%s\", got \"%s\"\n", \ + __FILE__, __LINE__, e_, a_); \ + std::abort(); \ + } \ + } while (0) + +// --------------------------------------------------------------------------- +// Domain / StringHandle / ID verification helpers +// --------------------------------------------------------------------------- + +namespace ittapi +{ +namespace test +{ + +inline void check_domain_name(const Domain& domain, const char* expected) +{ + if (domain.get() == nullptr) + { + return; // no collector attached — nothing to verify + } + CHECK_STR_EQ(domain.get()->nameA, expected); +} + +inline void check_string_handle_name(const StringHandle& handle, const char* expected) +{ + if (handle.get() == nullptr) + { + return; // no collector attached — nothing to verify + } + CHECK_STR_EQ(handle.get()->strA, expected); +} + +inline void check_id_fields(const __itt_id& id, + unsigned long long d1, + unsigned long long d2, + unsigned long long d3) +{ + if (id.d1 != d1 || id.d2 != d2 || id.d3 != d3) + { + std::fprintf(stderr, + "FAIL: id {%llu,%llu,%llu} != expected {%llu,%llu,%llu}\n", + id.d1, id.d2, id.d3, d1, d2, d3); + std::abort(); + } +} + +inline void check_id_is_null(const __itt_id& id) +{ + check_id_fields(id, 0, 0, 0); +} + +} // namespace test +} // namespace ittapi diff --git a/cpp/tests/test_ittapi.cpp b/cpp/tests/test_ittapi.cpp new file mode 100644 index 0000000..5d1d92a --- /dev/null +++ b/cpp/tests/test_ittapi.cpp @@ -0,0 +1,51 @@ +/* + Copyright (C) 2026 Intel Corporation + SPDX-License-Identifier: (BSD-3-Clause OR GPL-2.0-only) +*/ + +#include +#include "test_helpers.hpp" + +static void test_umbrella_header_compiles() +{ + ittapi::Domain d{"test.umbrella"}; + ittapi::StringHandle sh{"umbrella_handle"}; + + ittapi::test::check_domain_name(d, "test.umbrella"); + ittapi::test::check_string_handle_name(sh, "umbrella_handle"); + + ittapi::set_thread_name("umbrella_thread"); + ittapi::pause(); + ittapi::resume(); + + { + auto task = d.task("umbrella_task"); + CHECK(task.active()); + } + + { + auto region = d.region(sh); + CHECK(region.active()); + } + + { + auto frame = d.frame(); + CHECK(frame.active()); + } + + { + ittapi::ScopedPause sp; + CHECK(sp.active()); + sp.resume_now(); + CHECK(!sp.active()); + } + + d.task_begin("manual"); + d.task_end(); +} + +int main() +{ + test_umbrella_header_compiles(); + return 0; +} diff --git a/cpp/tests/test_region.cpp b/cpp/tests/test_region.cpp new file mode 100644 index 0000000..1e6a653 --- /dev/null +++ b/cpp/tests/test_region.cpp @@ -0,0 +1,95 @@ +/* + Copyright (C) 2026 Intel Corporation + SPDX-License-Identifier: (BSD-3-Clause OR GPL-2.0-only) +*/ + +#include +#include +#include "test_helpers.hpp" + +#include + +static void test_scoped_region_lifecycle() +{ + ittapi::Domain d{"test.region.lifecycle"}; + ittapi::test::check_domain_name(d, "test.region.lifecycle"); + + { + auto region = d.region("lifecycle_region"); + CHECK(region.active()); + } +} + +static void test_explicit_end_is_idempotent() +{ + ittapi::Domain d{"test.region.end"}; + auto region = d.region("end_region"); + CHECK(region.active()); + region.end(); + CHECK(!region.active()); + region.end(); + CHECK(!region.active()); +} + +static void test_move_construction() +{ + ittapi::Domain d{"test.region.move"}; + auto r1 = d.region("move_region"); + CHECK(r1.active()); + + auto r2 = std::move(r1); + CHECK(!r1.active()); + CHECK(r2.active()); +} + +static void test_string_handle_overload() +{ + ittapi::Domain d{"test.region.sh"}; + ittapi::StringHandle name{"sh_region"}; + ittapi::test::check_string_handle_name(name, "sh_region"); + + { + auto region = d.region(name); + CHECK(region.active()); + } +} + +static void test_scoped_region_with_ids() +{ + ittapi::Domain d{"test.region.ids"}; + ittapi::test::check_domain_name(d, "test.region.ids"); + __itt_id id = __itt_id_make(nullptr, 10); + __itt_id parentid = __itt_null; + + ittapi::test::check_id_is_null(parentid); + + { + auto region = d.region("region_with_ids", id, parentid); + CHECK(region.active()); + } +} + +static void test_scoped_region_with_ids_string_handle() +{ + ittapi::Domain d{"test.region.ids_sh"}; + ittapi::StringHandle name{"sh_region_ids"}; + ittapi::test::check_string_handle_name(name, "sh_region_ids"); + __itt_id id = __itt_id_make(nullptr, 11); + __itt_id parentid = __itt_null; + + { + auto region = d.region(name, id, parentid); + CHECK(region.active()); + } +} + +int main() +{ + test_scoped_region_lifecycle(); + test_explicit_end_is_idempotent(); + test_move_construction(); + test_string_handle_overload(); + test_scoped_region_with_ids(); + test_scoped_region_with_ids_string_handle(); + return 0; +} diff --git a/cpp/tests/test_string_handle.cpp b/cpp/tests/test_string_handle.cpp new file mode 100644 index 0000000..608372a --- /dev/null +++ b/cpp/tests/test_string_handle.cpp @@ -0,0 +1,38 @@ +/* + Copyright (C) 2026 Intel Corporation + SPDX-License-Identifier: (BSD-3-Clause OR GPL-2.0-only) +*/ + +#include +#include "test_helpers.hpp" + +#include +#include + +static void test_construct_from_literal() +{ + ittapi::StringHandle h{"test_handle"}; + ittapi::test::check_string_handle_name(h, "test_handle"); +} + +static void test_construct_from_string_view() +{ + std::string_view sv = "test_sv_handle"; + ittapi::StringHandle h{sv}; + ittapi::test::check_string_handle_name(h, "test_sv_handle"); +} + +static void test_construct_from_std_string() +{ + std::string s = "test_string_handle"; + ittapi::StringHandle h{std::string_view(s)}; + ittapi::test::check_string_handle_name(h, "test_string_handle"); +} + +int main() +{ + test_construct_from_literal(); + test_construct_from_string_view(); + test_construct_from_std_string(); + return 0; +} diff --git a/cpp/tests/test_task.cpp b/cpp/tests/test_task.cpp new file mode 100644 index 0000000..0d4785b --- /dev/null +++ b/cpp/tests/test_task.cpp @@ -0,0 +1,193 @@ +/* + Copyright (C) 2026 Intel Corporation + SPDX-License-Identifier: (BSD-3-Clause OR GPL-2.0-only) +*/ + +#include +#include +#include "test_helpers.hpp" + +#include + +static void test_scoped_task_lifecycle() +{ + ittapi::Domain d{"test.task.lifecycle"}; + ittapi::test::check_domain_name(d, "test.task.lifecycle"); + + { + auto task = d.task("lifecycle_task"); + CHECK(task.active()); + } + // destructor should have ended the task +} + +static void test_explicit_end_is_idempotent() +{ + ittapi::Domain d{"test.task.end"}; + auto task = d.task("end_task"); + CHECK(task.active()); + task.end(); + CHECK(!task.active()); + task.end(); // second call should be safe + CHECK(!task.active()); +} + +static void test_move_construction() +{ + ittapi::Domain d{"test.task.move"}; + auto task1 = d.task("move_task"); + CHECK(task1.active()); + + auto task2 = std::move(task1); + CHECK(!task1.active()); + CHECK(task2.active()); +} + +static void test_string_handle_overload() +{ + ittapi::Domain d{"test.task.sh"}; + ittapi::StringHandle name{"sh_task"}; + ittapi::test::check_string_handle_name(name, "sh_task"); + + { + auto task = d.task(name); + CHECK(task.active()); + } +} + +static void test_manual_task_begin_end() +{ + ittapi::Domain d{"test.task.manual"}; + d.task_begin("manual_task"); + d.task_end(); +} + +static void test_manual_task_begin_end_string_handle() +{ + ittapi::Domain d{"test.task.manual_sh"}; + ittapi::StringHandle name{"manual_sh_task"}; + d.task_begin(name); + d.task_end(); +} + +static void test_scoped_task_with_ids() +{ + ittapi::Domain d{"test.task.ids"}; + __itt_id taskid = __itt_id_make(nullptr, 1); + __itt_id parentid = __itt_null; + + ittapi::test::check_id_is_null(parentid); + + { + auto task = d.task("task_with_ids", taskid, parentid); + CHECK(task.active()); + } +} + +static void test_scoped_task_with_ids_string_handle() +{ + ittapi::Domain d{"test.task.ids_sh"}; + ittapi::StringHandle name{"sh_task_ids"}; + ittapi::test::check_string_handle_name(name, "sh_task_ids"); + __itt_id taskid = __itt_id_make(nullptr, 2); + __itt_id parentid = __itt_null; + + { + auto task = d.task(name, taskid, parentid); + CHECK(task.active()); + } +} + +static void test_manual_task_begin_end_with_ids() +{ + ittapi::Domain d{"test.task.manual_ids"}; + __itt_id taskid = __itt_id_make(nullptr, 3); + __itt_id parentid = __itt_null; + + d.task_begin("manual_ids_task", taskid, parentid); + d.task_end(taskid); +} + +static void test_overlapped_tasks_interleaved() +{ + ittapi::Domain d{"test.task.overlapped"}; + + // Start parent, start child, end parent, end child — only valid with overlapped + auto parent = d.overlapped_task("parent"); + auto child = d.overlapped_task("child", parent.id()); + + CHECK(parent.active()); + CHECK(child.active()); + + parent.end(); + CHECK(!parent.active()); + CHECK(child.active()); + + child.end(); + CHECK(!child.active()); +} + +static void test_overlapped_manual_interleaved() +{ + ittapi::Domain d{"test.task.overlapped_manual"}; + __itt_id id1 = __itt_id_make(nullptr, 300); + __itt_id id2 = __itt_id_make(nullptr, 400); + + d.task_begin("first", id1, __itt_null); + d.task_begin("second", id2, __itt_null); + d.task_end(id1); + d.task_end(id2); +} + +static void test_overlapped_auto_id() +{ + ittapi::Domain d{"test.task.auto_id"}; + auto task = d.overlapped_task("work"); + CHECK(task.active()); + __itt_id tid = task.id(); + CHECK(tid.d1 != 0); +} + +static void test_overlapped_auto_id_parent_child() +{ + ittapi::Domain d{"test.task.auto_parent_child"}; + auto parent = d.overlapped_task("parent"); + auto child = d.overlapped_task("child", parent.id()); + + CHECK(parent.active()); + CHECK(child.active()); + + parent.end(); + CHECK(!parent.active()); + CHECK(child.active()); + + child.end(); + CHECK(!child.active()); +} + +static void test_non_overlapped_id_is_null() +{ + ittapi::Domain d{"test.task.non_overlapped_id"}; + auto task = d.task("simple"); + __itt_id tid = task.id(); + CHECK(tid.d1 == 0 && tid.d2 == 0 && tid.d3 == 0); +} + +int main() +{ + test_scoped_task_lifecycle(); + test_explicit_end_is_idempotent(); + test_move_construction(); + test_string_handle_overload(); + test_manual_task_begin_end(); + test_manual_task_begin_end_string_handle(); + test_scoped_task_with_ids(); + test_scoped_task_with_ids_string_handle(); + test_manual_task_begin_end_with_ids(); + test_overlapped_tasks_interleaved(); + test_overlapped_manual_interleaved(); + test_overlapped_auto_id(); + test_overlapped_auto_id_parent_child(); + test_non_overlapped_id_is_null(); + return 0; +} diff --git a/cpp/tests/test_thread_naming.cpp b/cpp/tests/test_thread_naming.cpp new file mode 100644 index 0000000..36a0732 --- /dev/null +++ b/cpp/tests/test_thread_naming.cpp @@ -0,0 +1,17 @@ +/* + Copyright (C) 2026 Intel Corporation + SPDX-License-Identifier: (BSD-3-Clause OR GPL-2.0-only) +*/ + +#include + +static void test_set_thread_name() +{ + ittapi::set_thread_name("test_thread"); +} + +int main() +{ + test_set_thread_name(); + return 0; +} diff --git a/docs/conf.py b/docs/conf.py index 831c7b9..0bc4225 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -12,7 +12,7 @@ # -- Project information ----------------------------------------------------- project = 'ITT/JIT APIs Documentation' -copyright = '2025 Intel Corporation' +copyright = '2026 Intel Corporation' author = 'Intel Corporation' # -- General configuration --------------------------------------------------- diff --git a/docs/src/api-support.rst b/docs/src/api-support.rst index 57c7890..3bc233b 100644 --- a/docs/src/api-support.rst +++ b/docs/src/api-support.rst @@ -28,6 +28,7 @@ Other Language API Bindings: :maxdepth: 1 + itt-api-cpp-wrapper Rust ITT API Bindings Python ITT API Bindings diff --git a/docs/src/itt-api-cpp-wrapper.rst b/docs/src/itt-api-cpp-wrapper.rst new file mode 100644 index 0000000..313fd3b --- /dev/null +++ b/docs/src/itt-api-cpp-wrapper.rst @@ -0,0 +1,284 @@ +.. _itt-api-cpp-wrapper: + +ITT API C++ Wrapper +=================== + +A modern, header-only C++ wrapper for the `ITT API `_ +instrumentation library. The wrapper provides RAII-based scoped helpers and type-safe +C++ abstractions over the existing C API. + +Supported APIs +-------------- + +.. list-table:: + :header-rows: 1 + :widths: 30 70 + + * - API + - C++ Wrapper + * - String Handle + - ``ittapi::StringHandle`` + * - Domain + - ``ittapi::Domain`` + * - Task + - ``ittapi::ScopedTask``, ``Domain::task_begin()`` / ``Domain::task_end()`` + * - Region + - ``ittapi::ScopedRegion`` + * - Frame + - ``ittapi::ScopedFrame`` + * - Collection Control + - ``ittapi::pause()``, ``ittapi::resume()``, ``ittapi::ScopedPause`` + * - Thread Naming + - ``ittapi::set_thread_name()`` + +Requirements +------------ + +- C++17 or later +- The existing ``ittnotify`` static C-library + +Including the Wrapper +--------------------- + +Use the umbrella header to get the full API: + +.. code-block:: cpp + + #include + +Or include individual headers: + +.. code-block:: cpp + + #include + #include + #include + +Example: Task Instrumentation +----------------------------- + +.. code-block:: cpp + + #include + + #include + #include + + int main() { + ittapi::set_thread_name("main"); + ittapi::Domain domain{"example.task"}; + ittapi::StringHandle task_name{"process"}; + + ittapi::pause(); + ittapi::resume(); + + { + auto task = domain.task(task_name); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + + return 0; + } + +Linking +------- + +CMake Consumer +^^^^^^^^^^^^^^ + +.. code-block:: cmake + + find_package(ittapi CONFIG REQUIRED) + target_link_libraries(my_app PRIVATE ittapi::cxx) + +The ``ittapi::cxx`` target is an ``INTERFACE`` library that transitively links the +existing ``ittnotify`` static library and adds the C++ wrapper include directories. + +Manual GCC/G++ (Linux) +^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: bash + + g++ -std=c++17 -O2 \ + -I/include \ + app.cpp \ + /lib/libittnotify.a \ + -ldl -pthread \ + -o app + +Building with CMake +------------------- + +From the repository root: + +.. code-block:: bash + + cmake -B build -DCMAKE_BUILD_TYPE=Release -DITT_API_CPP_SUPPORT=ON + cmake --build build + +The ``ITT_API_CPP_SUPPORT`` option is ``OFF`` by default. + +You can also build with the build script: + +.. code-block:: bash + + python buildall.py --cpp + +Running Tests +------------- + +Tests are registered inside the ``cpp/`` subdirectory. After building: + +.. code-block:: bash + + ctest --test-dir build/cpp --output-on-failure + +Performance Tips +---------------- + +- **Pre-create** ``StringHandle`` **objects** for task/region names used in hot paths. + The ``StringHandle`` overloads pass a raw pointer with no locking — this is the + zero-overhead path. + + .. code-block:: cpp + + // Do this once at startup: + ittapi::StringHandle name{"compute"}; + + // Then in hot code: + auto task = domain.task(name); // no allocation, no lock + +- **The** ``string_view`` **overloads** are convenient but allocate a ``std::string`` + on cache miss and acquire a lock in the C library. Use them for setup or infrequent + tasks, not tight loops. +- **Create** ``Domain`` **objects once** and reuse them. Domain creation is a global + lookup — store them as class members or globals, not as function locals called + repeatedly. +- **Use overlapped tasks** (with IDs) only when you need tasks that end out of order. + Stack-based tasks (without IDs) are simpler and carry less internal state. + +API Reference +------------- + +Free Functions +^^^^^^^^^^^^^^ + +- ``ittapi::pause()`` — Pause collection. +- ``ittapi::resume()`` — Resume collection. +- ``ittapi::set_thread_name(std::string_view name)`` — Set the current thread's name. + +Classes +^^^^^^^ + +ittapi::StringHandle +"""""""""""""""""""" + +Lightweight wrapper around ``__itt_string_handle*``. + +.. code-block:: cpp + + ittapi::StringHandle h{"my_handle"}; + h.valid(); // true if handle was created + h.get(); // underlying __itt_string_handle* + +ittapi::Domain +"""""""""""""" + +Lightweight wrapper around ``__itt_domain*`` with convenience factories. + +.. code-block:: cpp + + ittapi::Domain d{"my.domain"}; + auto task = d.task("task_name"); // returns ScopedTask (RAII) + auto region = d.region("region_name"); // returns ScopedRegion + auto frame = d.frame(); // returns ScopedFrame + + d.task_begin("work"); // manual task begin (simple stack-based) + d.task_end(); // manual task end + + __itt_id id = __itt_id_make(nullptr, 1); + d.task_begin("overlapped", id, __itt_null); // manual task begin (overlapped) + d.task_end(id); // manual task end by ID + +ittapi::ScopedTask +"""""""""""""""""" + +RAII wrapper for task begin/end. Use ``domain.overlapped_task()`` to create +an overlapped task (tasks that can end in any order). Each overlapped task +instance gets a unique ``__itt_id`` derived from its object address. The +``id()`` method returns this ID. + +.. code-block:: cpp + + // Simple task + { + auto task = domain.task("work"); + // ... do work ... + task.end(); // optional early end (idempotent) + } // destructor ends task if still active + +Overlapped tasks — use ``overlapped_task()``, optionally pass a parent ID: + +.. code-block:: cpp + + { + auto parent = domain.overlapped_task("parent"); // overlapped, no parent + auto child = domain.overlapped_task("child", parent.id()); // overlapped, child of parent + + parent.end(); // end parent while child is still running + } // child ends here via destructor + +Overlapped tasks with explicit IDs (advanced): + +.. code-block:: cpp + + { + __itt_id parent_id = __itt_id_make(nullptr, 1); + auto parent = domain.task("parent", parent_id, __itt_null); + + __itt_id child_id = __itt_id_make(nullptr, 2); + auto child = domain.task("child", child_id, parent_id); + + parent.end(); // end parent while child is still running + } // child ends here via destructor + +For manual (non-RAII) control: + +.. code-block:: cpp + + domain.task_begin("work"); + // ... do work ... + domain.task_end(); + + // Or with overlapped tasks: + __itt_id id = __itt_id_make(nullptr, 1); + domain.task_begin("overlapped_work", id, __itt_null); + // ... do work ... + domain.task_end(id); + +ittapi::ScopedRegion +"""""""""""""""""""" + +RAII wrapper for region begin/end. + +ittapi::ScopedFrame +""""""""""""""""""" + +RAII wrapper for frame begin/end. Supports explicit timestamp submission. + +.. code-block:: cpp + + ittapi::ScopedFrame::submit(domain.get(), begin_ts, end_ts); + +ittapi::ScopedPause +""""""""""""""""""" + +RAII wrapper for pause/resume. Constructor pauses, destructor resumes. + +.. code-block:: cpp + + { + ittapi::ScopedPause sp; + // collection is paused + sp.resume_now(); // optional early resume + }