diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 659c1d6..ca588ce 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -39,6 +39,30 @@ 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 }} + refcol_smoke: + name: Reference collector smoke test + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest + lib: build_linux/64/bin/libittnotify_refcol.so + exe: build_linux/64/bin/refcol_smoke_test + - os: windows-latest + lib: build_win/64/bin/libittnotify_refcol.dll + exe: build_win/64/bin/refcol_smoke_test.exe + defaults: + run: + shell: bash + steps: + - name: Checkout sources + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Build reference collector library and smoke test + run: python buildall.py --force_bits 64 --refcol -DITT_API_REFCOL_SMOKE_TESTS=ON ${{ runner.os == 'Windows' && '--cmake_gen ninja' || '' }} + - name: Run smoke test + run: python src/ittnotify_refcol/tests/run_smoke_test.py --lib ${{ matrix.lib }} --exe ${{ matrix.exe }} + cpp_wrapper: name: Check C++ wrapper runs-on: ${{ matrix.os }} diff --git a/CMakeLists.txt b/CMakeLists.txt index 8c97cf9..bf9dd45 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,6 +27,7 @@ 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_REFERENCE_COLLECTOR "Build reference collector shared library" OFF) option(ITT_API_INSTALL "Enable ITT API installation rules" ON) if(FORCE_32 AND UNIX) @@ -141,6 +142,10 @@ add_library(ittapi::ittnotify ALIAS ittnotify) set(JITPROFILING_SRC "src/ittnotify/jitprofiling.c") add_library(jitprofiling STATIC ${JITPROFILING_SRC}) +if(ITT_API_REFERENCE_COLLECTOR) + add_subdirectory(src/ittnotify_refcol) +endif() + if(WIN32) set_target_properties(ittnotify PROPERTIES OUTPUT_NAME libittnotify) set_target_properties(jitprofiling PROPERTIES OUTPUT_NAME libjitprofiling) diff --git a/buildall.py b/buildall.py index 180f7fe..96a5f99 100755 --- a/buildall.py +++ b/buildall.py @@ -119,6 +119,11 @@ def main(): "-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( + "--refcol", help="enable reference collector build", action="store_true") + parser.add_argument( + "-D", dest="cmake_defines", metavar="VAR=VALUE", action="append", default=[], + help="pass extra -D defines to cmake (may be specified multiple times)") parser.add_argument( "--force_bits", choices=["32", "64"], help="specify bit version for the target") if sys.platform == 'win32' and vs_versions: @@ -180,8 +185,9 @@ def main(): ('-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_CPP_SUPPORT=ON" if args.cpp else "") - ]))) + ("-DITT_API_CPP_SUPPORT=ON" if args.cpp else ""), + ("-DITT_API_REFERENCE_COLLECTOR=ON" if args.refcol else ""), + ] + ["-D" + d for d in args.cmake_defines]))) if sys.platform == 'win32': target_project = 'ALL_BUILD' if not use_ninja else 'all' diff --git a/src/ittnotify_refcol/CMakeLists.txt b/src/ittnotify_refcol/CMakeLists.txt new file mode 100644 index 0000000..9cc93fa --- /dev/null +++ b/src/ittnotify_refcol/CMakeLists.txt @@ -0,0 +1,33 @@ +add_library(ittnotify_refcol SHARED itt_refcol_impl.c) + +target_include_directories(ittnotify_refcol + PUBLIC $ + PRIVATE ${CMAKE_SOURCE_DIR}/src/ittnotify +) + +target_link_libraries(ittnotify_refcol PRIVATE ${CMAKE_DL_LIBS}) +set_target_properties(ittnotify_refcol PROPERTIES + LINKER_LANGUAGE C + RUNTIME_OUTPUT_DIRECTORY ${LIBRARY_OUTPUT_PATH} + ARCHIVE_OUTPUT_DIRECTORY ${LIBRARY_OUTPUT_PATH}) + +if(WIN32) + set_target_properties(ittnotify_refcol PROPERTIES + OUTPUT_NAME libittnotify_refcol + PREFIX "" + WINDOWS_EXPORT_ALL_SYMBOLS ON) +else() + set_target_properties(ittnotify_refcol PROPERTIES OUTPUT_NAME ittnotify_refcol) +endif() + +option(ITT_API_REFCOL_SMOKE_TESTS "Build reference collector smoke tests" OFF) +if(ITT_API_REFCOL_SMOKE_TESTS) + add_executable(refcol_smoke_test tests/smoke_test.c) + target_include_directories(refcol_smoke_test + PRIVATE ${CMAKE_SOURCE_DIR}/include + ${CMAKE_SOURCE_DIR}/src/ittnotify + ) + target_link_libraries(refcol_smoke_test PRIVATE ittnotify ${CMAKE_DL_LIBS}) + set_target_properties(refcol_smoke_test PROPERTIES + RUNTIME_OUTPUT_DIRECTORY ${LIBRARY_OUTPUT_PATH}) +endif() diff --git a/src/ittnotify_refcol/README.md b/src/ittnotify_refcol/README.md index ee30fc5..9b68c9b 100644 --- a/src/ittnotify_refcol/README.md +++ b/src/ittnotify_refcol/README.md @@ -5,24 +5,52 @@ performs tracing data from ITT API function calls to log files. To use this solution, build the collector as a shared library and point the full library path to the `INTEL_LIBITTNOTIFY64` or `INTEL_LIBITTNOTIFY32` -environment variable: +environment variable. -**On Linux** +## Building + +### CMake (all platforms, recommended) + +``` +cmake -DITT_API_REFERENCE_COLLECTOR=ON +cmake --build . +``` + +The shared library is placed in the `bin/` subdirectory of the CMake build +directory. Alternatively, use the provided `buildall.py` script: + +``` +python buildall.py --refcol +``` + +### Make (Linux / FreeBSD) ``` make -export INTEL_LIBITTNOTIFY64=/libittnotify_refcol.so +``` + +## Usage + +**On Linux** + +``` +export INTEL_LIBITTNOTIFY64=/bin/libittnotify_refcol.so ``` **On FreeBSD** ``` -make -setenv INTEL_LIBITTNOTIFY64 /libittnotify_refcol.so +setenv INTEL_LIBITTNOTIFY64 /bin/libittnotify_refcol.so +``` + +**On Windows** + +``` +set INTEL_LIBITTNOTIFY64=\bin\libittnotify_refcol.dll ``` -By default, log files save in the `tmp` directory. To change the location, -use the `INTEL_LIBITTNOTIFY_LOG_DIR` environment variable: +By default, log files are saved in the system temporary directory. To change +the location, use the `INTEL_LIBITTNOTIFY_LOG_DIR` environment variable: **On Linux** @@ -35,6 +63,11 @@ export INTEL_LIBITTNOTIFY_LOG_DIR= setenv INTEL_LIBITTNOTIFY_LOG_DIR ``` +**On Windows** +``` +set INTEL_LIBITTNOTIFY_LOG_DIR= +``` + This implementation adds logging of some of the ITT API function calls. Adding logging of other ITT API function calls is welcome. The solution provides 4 functions with different log levels that take `printf` format for logging: diff --git a/src/ittnotify_refcol/itt_refcol_impl.c b/src/ittnotify_refcol/itt_refcol_impl.c index 42571e8..3e4829a 100644 --- a/src/ittnotify_refcol/itt_refcol_impl.c +++ b/src/ittnotify_refcol/itt_refcol_impl.c @@ -4,8 +4,10 @@ SPDX-License-Identifier: GPL-2.0-only OR BSD-3-Clause */ +#include #include #include +#include #include #include #include @@ -45,15 +47,34 @@ static struct ref_collector_global { __itt_histogram* histogram_list; } g_ref_collector_global = {MUTEX_INITIALIZER, 0, NULL, NULL, NULL, NULL}; +#ifdef _WIN32 + #define REFCOL_LOCALTIME(out_tm, in_time) (localtime_s((out_tm), (in_time)) == 0) +#else + #define REFCOL_LOCALTIME(out_tm, in_time) (localtime_r((in_time), (out_tm)) != NULL) +#endif + static char* log_file_name_generate() { time_t time_now = time(NULL); - struct tm* time_info = localtime(&time_now); + struct tm time_info; char* log_file_name = malloc(sizeof(char) * (LOG_BUFFER_MAX_SIZE/2)); + if (log_file_name == NULL) + { + printf("ERROR: Failed to allocate memory for log file name\n"); + return NULL; + } + + if (!REFCOL_LOCALTIME(&time_info, &time_now)) + { + printf("ERROR: Failed to get local time for log file name\n"); + free(log_file_name); + return NULL; + } + sprintf(log_file_name,"libittnotify_refcol_%d%d%d%d%d%d.log", - time_info->tm_year+1900, time_info->tm_mon+1, time_info->tm_mday, - time_info->tm_hour, time_info->tm_min, time_info->tm_sec); + time_info.tm_year+1900, time_info.tm_mon+1, time_info.tm_mday, + time_info.tm_hour, time_info.tm_min, time_info.tm_sec); return log_file_name; } @@ -85,6 +106,10 @@ static void ref_collector_init() { sprintf(file_name_buffer,"%s\\%s", temp_dir, log_file); } + else + { + sprintf(file_name_buffer,"%s", log_file); + } #else sprintf(file_name_buffer,"/tmp/%s", log_file); #endif @@ -364,7 +389,48 @@ static char* get_context_metadata_element(__itt_context_type type, void* metadat return metadata_str; } +#ifdef _WIN32 +static char* wchar2char(const wchar_t* wide_str) +{ + if (wide_str == NULL) + { + return NULL; + } + + int narrow_size = WideCharToMultiByte(CP_UTF8, 0, wide_str, -1, NULL, 0, NULL, NULL); + if (narrow_size <= 0) + { + return NULL; + } + + char* narrow_str = (char*)malloc((size_t)narrow_size); + if (narrow_str == NULL) + { + return NULL; + } + + if (!WideCharToMultiByte(CP_UTF8, 0, wide_str, -1, narrow_str, narrow_size, NULL, NULL)) + { + free(narrow_str); + return NULL; + } + + return narrow_str; +} +#endif + +#ifdef _WIN32 +ITT_EXTERN_C __itt_domain* ITTAPI __itt_domain_createW(const wchar_t *name) +{ + char* name_a = wchar2char(name); + __itt_domain* result = __itt_domain_createA(name_a); + free(name_a); + return result; +} +ITT_EXTERN_C __itt_domain* ITTAPI __itt_domain_createA(const char *name) +#else ITT_EXTERN_C __itt_domain* ITTAPI __itt_domain_create(const char *name) +#endif { if (!g_ref_collector_global.mutex_initialized || name == NULL) { @@ -396,7 +462,18 @@ ITT_EXTERN_C __itt_domain* ITTAPI __itt_domain_create(const char *name) return h; } +#ifdef _WIN32 +ITT_EXTERN_C __itt_string_handle* ITTAPI __itt_string_handle_createW(const wchar_t* name) +{ + char* name_a = wchar2char(name); + __itt_string_handle* result = __itt_string_handle_createA(name_a); + free(name_a); + return result; +} +ITT_EXTERN_C __itt_string_handle* ITTAPI __itt_string_handle_createA(const char* name) +#else ITT_EXTERN_C __itt_string_handle* ITTAPI __itt_string_handle_create(const char* name) +#endif { if (!g_ref_collector_global.mutex_initialized || name == NULL) { @@ -428,21 +505,62 @@ ITT_EXTERN_C __itt_string_handle* ITTAPI __itt_string_handle_create(const char* return h; } +#ifdef _WIN32 +ITT_EXTERN_C __itt_counter ITTAPI __itt_counter_createW(const wchar_t *name, const wchar_t *domain) +{ + char* name_a = wchar2char(name); + char* domain_a = wchar2char(domain); + __itt_counter result = __itt_counter_createA(name_a, domain_a); + free(name_a); + free(domain_a); + return result; +} +ITT_EXTERN_C __itt_counter ITTAPI __itt_counter_createA(const char *name, const char *domain) +#else ITT_EXTERN_C __itt_counter ITTAPI __itt_counter_create(const char *name, const char *domain) +#endif { LOG_FUNC_CALL_INFO("function call"); return __itt_counter_create_typed(name, domain, __itt_metadata_u64); } +#ifdef _WIN32 +ITT_EXTERN_C __itt_counter ITTAPI __itt_counter_createW_v3( + const __itt_domain* domain, const wchar_t* name, __itt_metadata_type type) +{ + char* name_a = wchar2char(name); + __itt_counter result = __itt_counter_create_typed(name_a, domain->nameA, type); + free(name_a); + return result; +} +ITT_EXTERN_C __itt_counter ITTAPI __itt_counter_createA_v3( + const __itt_domain* domain, const char* name, __itt_metadata_type type) +#else ITT_EXTERN_C __itt_counter ITTAPI __itt_counter_create_v3( const __itt_domain* domain, const char* name, __itt_metadata_type type) +#endif { LOG_FUNC_CALL_INFO("function call"); return __itt_counter_create_typed(name, domain->nameA, type); } +#ifdef _WIN32 +ITT_EXTERN_C __itt_counter ITTAPI __itt_counter_create_typedW( + const wchar_t *name, const wchar_t *domain, __itt_metadata_type type) +{ + char* name_a = wchar2char(name); + char* domain_a = wchar2char(domain); + __itt_counter result = __itt_counter_create_typedA(name_a, domain_a, type); + free(name_a); + free(domain_a); + return result; +} +ITT_EXTERN_C __itt_counter ITTAPI __itt_counter_create_typedA( + const char *name, const char *domain, __itt_metadata_type type) +#else ITT_EXTERN_C __itt_counter ITTAPI __itt_counter_create_typed( const char *name, const char *domain, __itt_metadata_type type) +#endif { if (!g_ref_collector_global.mutex_initialized || name == NULL || domain == NULL) { @@ -478,8 +596,21 @@ ITT_EXTERN_C __itt_counter ITTAPI __itt_counter_create_typed( return (__itt_counter)h; } +#ifdef _WIN32 +ITT_EXTERN_C __itt_histogram* ITTAPI __itt_histogram_createW( + const __itt_domain* domain, const wchar_t* name, __itt_metadata_type x_type, __itt_metadata_type y_type) +{ + char* name_a = wchar2char(name); + __itt_histogram* result = __itt_histogram_createA(domain, name_a, x_type, y_type); + free(name_a); + return result; +} +ITT_EXTERN_C __itt_histogram* ITTAPI __itt_histogram_createA( + const __itt_domain* domain, const char* name, __itt_metadata_type x_type, __itt_metadata_type y_type) +#else ITT_EXTERN_C __itt_histogram* ITTAPI __itt_histogram_create( const __itt_domain* domain, const char* name, __itt_metadata_type x_type, __itt_metadata_type y_type) +#endif { if (!g_ref_collector_global.mutex_initialized || name == NULL || domain == NULL) { diff --git a/src/ittnotify_refcol/tests/run_smoke_test.py b/src/ittnotify_refcol/tests/run_smoke_test.py new file mode 100644 index 0000000..cea0c65 --- /dev/null +++ b/src/ittnotify_refcol/tests/run_smoke_test.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python +# +# Copyright (C) 2026 Intel Corporation +# SPDX-License-Identifier: GPL-2.0-only OR BSD-3-Clause +# + +import argparse +import glob +import os +import subprocess +import sys +import tempfile + + +EXPECTED_SYMBOLS = [ + "__itt_task_begin", + "__itt_task_end", + "__itt_metadata_add", +] + + +def main(): + parser = argparse.ArgumentParser(description="Run reference collector smoke test") + parser.add_argument("--lib", required=True, help="Path to libittnotify_refcol shared library") + parser.add_argument("--exe", required=True, help="Path to refcol_smoke_test executable") + parser.add_argument("--log-dir", help="Directory for log files (default: temp dir)") + args = parser.parse_args() + + lib = os.path.abspath(args.lib) + exe = os.path.abspath(args.exe) + + if not os.path.isfile(lib): + print(f"ERROR: library not found: {lib}") + return 1 + if not os.path.isfile(exe): + print(f"ERROR: executable not found: {exe}") + return 1 + + log_dir = os.path.abspath(args.log_dir) if args.log_dir else tempfile.mkdtemp(prefix="refcol_logs_") + os.makedirs(log_dir, exist_ok=True) + + env = os.environ.copy() + env["INTEL_LIBITTNOTIFY64"] = lib + env["INTEL_LIBITTNOTIFY_LOG_DIR"] = log_dir + + print(f"Library: {lib}") + print(f"Exe: {exe}") + print(f"Log dir: {log_dir}") + + result = subprocess.run([exe], env=env) + if result.returncode != 0: + print(f"ERROR: smoke test executable exited with code {result.returncode}") + return 1 + + logs = glob.glob(os.path.join(log_dir, "libittnotify_refcol_*.log")) + if not logs: + print("ERROR: no log file found in", log_dir) + return 1 + + log_path = logs[0] + print(f"Log file: {log_path}") + + with open(log_path, encoding="utf-8", errors="replace") as f: + content = f.read() + + missing = [sym for sym in EXPECTED_SYMBOLS if sym not in content] + if missing: + for sym in missing: + print(f"ERROR: '{sym}' not found in log") + return 1 + + print("Smoke test passed.") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/src/ittnotify_refcol/tests/smoke_test.c b/src/ittnotify_refcol/tests/smoke_test.c new file mode 100644 index 0000000..4c2a5bf --- /dev/null +++ b/src/ittnotify_refcol/tests/smoke_test.c @@ -0,0 +1,43 @@ +/* + Copyright (C) 2026 Intel Corporation + + SPDX-License-Identifier: GPL-2.0-only OR BSD-3-Clause +*/ + +#include "ittnotify.h" +#include "ittnotify_types.h" + +int main(void) +{ + __itt_domain* domain = __itt_domain_create("smoke_test_domain"); + __itt_string_handle* handle = __itt_string_handle_create("smoke_test_handler"); + __itt_string_handle* handle_work = __itt_string_handle_create("smoke_test_worker"); + + const int n = 10; + int a[10][10], b[10][10], mul[10][10], i, j, k, count = 0; + + for (i = 0; i < n; i++) + for (j = 0; j < n; j++) + { + a[i][j] = i; + b[i][j] = j; + mul[i][j] = 0; + } + + for (i = 0; i < n; i++) + for (j = 0; j < n; j++) + for (k = 0; k < n; k++) + { + mul[i][j] += a[i][k] * b[k][j]; + count++; + if (count % 100 == 0) + { + __itt_task_begin(domain, __itt_null, __itt_null, handle_work); + unsigned long long data[5] = { i, j, k, count, mul[i][j] }; + __itt_metadata_add(domain, __itt_null, handle, __itt_metadata_u64, 5, data); + __itt_task_end(domain); + } + } + + return 0; +}