From d623cfe7b8a9ad4349e16078e65bbedd108a197b Mon Sep 17 00:00:00 2001 From: Vasanth Tovinkere Date: Mon, 16 Mar 2020 13:44:55 -0700 Subject: [PATCH 01/18] [XPTIFW] Initial check-in of XPTI Framework + Implementation of the specification in llvm/xpti + Documentation on the API and the architecture of the framework + Unit tests and additional semantic and performance tests + Sample collector (subscriber) to attach to an instrumented application and print out the trace data being received Signed-off-by: Vasanth Tovinkere --- xptifw/CMakeLists.txt | 24 + xptifw/CMakeLists.txt.in | 15 + xptifw/README.md | 16 + xptifw/basic_test/CMakeLists.txt | 25 + xptifw/basic_test/README.md | 15 + xptifw/basic_test/cl_processor.hpp | 637 ++++++++++ xptifw/basic_test/main.cpp | 77 ++ xptifw/basic_test/performance_tests.cpp | 552 +++++++++ xptifw/basic_test/semantic_tests.cpp | 511 ++++++++ xptifw/doc/XPTI_Framework.md | 665 ++++++++++ xptifw/doc/xpti_arch.png | Bin 0 -> 87322 bytes xptifw/include/xpti_int64_hash_table.hpp | 148 +++ xptifw/include/xpti_string_table.hpp | 180 +++ xptifw/samples/basic_collector/CMakeLists.txt | 24 + xptifw/samples/basic_collector/README.md | 26 + .../basic_collector/basic_collector.cpp | 204 +++ xptifw/samples/include/xpti_timers.hpp | 95 ++ xptifw/src/CMakeLists.txt | 23 + xptifw/src/xpti_trace_framework.cpp | 1091 +++++++++++++++++ xptifw/unit_test/CMakeLists.txt | 42 + xptifw/unit_test/README.md | 7 + xptifw/unit_test/xpti_api_tests.cpp | 320 +++++ xptifw/unit_test/xpti_correctness_tests.cpp | 331 +++++ 23 files changed, 5028 insertions(+) create mode 100644 xptifw/CMakeLists.txt create mode 100644 xptifw/CMakeLists.txt.in create mode 100644 xptifw/README.md create mode 100644 xptifw/basic_test/CMakeLists.txt create mode 100644 xptifw/basic_test/README.md create mode 100644 xptifw/basic_test/cl_processor.hpp create mode 100644 xptifw/basic_test/main.cpp create mode 100644 xptifw/basic_test/performance_tests.cpp create mode 100644 xptifw/basic_test/semantic_tests.cpp create mode 100644 xptifw/doc/XPTI_Framework.md create mode 100644 xptifw/doc/xpti_arch.png create mode 100644 xptifw/include/xpti_int64_hash_table.hpp create mode 100644 xptifw/include/xpti_string_table.hpp create mode 100644 xptifw/samples/basic_collector/CMakeLists.txt create mode 100644 xptifw/samples/basic_collector/README.md create mode 100644 xptifw/samples/basic_collector/basic_collector.cpp create mode 100644 xptifw/samples/include/xpti_timers.hpp create mode 100644 xptifw/src/CMakeLists.txt create mode 100644 xptifw/src/xpti_trace_framework.cpp create mode 100644 xptifw/unit_test/CMakeLists.txt create mode 100644 xptifw/unit_test/README.md create mode 100644 xptifw/unit_test/xpti_api_tests.cpp create mode 100644 xptifw/unit_test/xpti_correctness_tests.cpp diff --git a/xptifw/CMakeLists.txt b/xptifw/CMakeLists.txt new file mode 100644 index 0000000000000..1952c34f600e8 --- /dev/null +++ b/xptifw/CMakeLists.txt @@ -0,0 +1,24 @@ +cmake_minimum_required(VERSION 2.8.9) + +set(XPTI_VERSION 0.4.1) +set(XPTIFW_DIR ${CMAKE_CURRENT_LIST_DIR}) +# The XPTI framework requires the includes from +# the proxy implementation of XPTI +set(XPTI_DIR ${CMAKE_CURRENT_LIST_DIR}/../xpti) + +if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + message(STATUS "No build type selected, default to Release") + set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Build type (default Release)" FORCE) +endif() + +project (xptifw) + +set(CMAKE_BINARY_DIR ${CMAKE_SOURCE_DIR}/lib/${CMAKE_BUILD_TYPE}) +set(EXECUTABLE_OUTPUT_PATH ${CMAKE_BINARY_DIR}) +set(LIBRARY_OUTPUT_PATH ${CMAKE_BINARY_DIR}) + +include_directories(${CMAKE_SOURCE_DIR}/include) +add_subdirectory(src) +add_subdirectory(unit_test) +add_subdirectory(samples/basic_collector) +add_subdirectory(basic_test) diff --git a/xptifw/CMakeLists.txt.in b/xptifw/CMakeLists.txt.in new file mode 100644 index 0000000000000..f98ccb4ac9780 --- /dev/null +++ b/xptifw/CMakeLists.txt.in @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 2.8.2) + +project(googletest-download NONE) + +include(ExternalProject) +ExternalProject_Add(googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG master + SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/googletest-src" + BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/googletest-build" + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + TEST_COMMAND "" +) \ No newline at end of file diff --git a/xptifw/README.md b/xptifw/README.md new file mode 100644 index 0000000000000..43dc3f6848ea3 --- /dev/null +++ b/xptifw/README.md @@ -0,0 +1,16 @@ +# XPTI Framework Library + +Implementation of the instrumentation framework library to support +instrumentation of arbitrary regions of code. This implementation requires the +specification header files used by the proxy library in ```xpti/```. This +library is not necessary for building the SYCL runtime library and only required +to build tools that extract the traces from instrumented code. + +To see the implementation of the basic collector and how it can be attached to +an application that has been instrumented with XPTI, see [samples/basic_collector/README.md](samples/basic_collector/README.md). + +To see how to determine the cost of the APIs, see the tests under [basic_test/](basic_test/README.md). + +Unit tests are available under [unit_test](unit_test/README.md). + +To see the complete documentation on XPTI framework API, please see [XPTI Framework library documentation](doc/XPTI_Framework.md) \ No newline at end of file diff --git a/xptifw/basic_test/CMakeLists.txt b/xptifw/basic_test/CMakeLists.txt new file mode 100644 index 0000000000000..50bcc4460c97f --- /dev/null +++ b/xptifw/basic_test/CMakeLists.txt @@ -0,0 +1,25 @@ +cmake_minimum_required(VERSION 2.8.9) +project (run_test) + +file(GLOB SOURCES *.cpp *.hpp) +include_directories(${XPTIFW_DIR}/include) +include_directories(${XPTI_DIR}/include) + +remove_definitions(-DXPTI_STATIC_LIBRARY) +add_definitions(-DXPTI_API_EXPORTS -g -O3) +add_executable(run_test ${SOURCES}) +add_dependencies(run_test xptifw) +if (MSVC) + target_link_libraries(run_test + PRIVATE xptifw + PRIVATE tbb + ) +else() + target_link_libraries(run_test + PRIVATE xptifw + PRIVATE dl + PRIVATE tbb + ) +endif() +# Set the location of the library installation +install(TARGETS run_test DESTINATION ${CMAKE_BINARY_DIR}) diff --git a/xptifw/basic_test/README.md b/xptifw/basic_test/README.md new file mode 100644 index 0000000000000..880d5237afbc7 --- /dev/null +++ b/xptifw/basic_test/README.md @@ -0,0 +1,15 @@ +# Basic tests + +In order to capture the cost of various API calls in the framework and test the +correctness of the API, a set of basic tests have been created. hey primarily fall under two categories: + +1. Sematic tests: These tests perform correctness checks on the API call to +ensure the right data is being retrieved. The sematic tests are categorized into +string table tests, trace point tests and notification tests. + +2. Performance tests: These test attempt to capture the average cost of various +operations that are a part of creating trace points in applications. The tests +are categorized into data structure tests and instrumentation tests. + +For more detail on the framework, the tests that are provided and their usage, +please consult the [XPTI Framework library documentation](doc/XPTI_Framework.md). diff --git a/xptifw/basic_test/cl_processor.hpp b/xptifw/basic_test/cl_processor.hpp new file mode 100644 index 0000000000000..9b859c2590ccb --- /dev/null +++ b/xptifw/basic_test/cl_processor.hpp @@ -0,0 +1,637 @@ +// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "xpti_trace_framework.hpp" + +namespace test { +namespace utils { +enum class OptionType { Boolean, Integer, Float, String, Range }; + +// We are using C++ 11, hence we cannot use +// std::variant or std::any +typedef std::map table_row_t; +typedef std::map table_t; +typedef std::vector titles_t; +class scoped_timer { +public: + typedef std::chrono::time_point + time_unit_t; + scoped_timer(uint64_t &ns, double &ratio, size_t count = 1) + : m_duration{ns}, m_average{ratio}, m_instances{count} { + m_before = std::chrono::high_resolution_clock::now(); + } + + ~scoped_timer() { + m_after = std::chrono::high_resolution_clock::now(); + auto duration = std::chrono::duration_cast( + m_after - m_before); + m_duration = duration.count(); + m_average = (double)m_duration / m_instances; + } + +private: + uint64_t &m_duration; + double &m_average; + size_t m_instances; + time_unit_t m_before, m_after; +}; + +class cl_option { +public: + cl_option() + : m_required(false), m_type(OptionType::String), + m_help("No help available.") {} + ~cl_option() {} + + cl_option &set_required(bool yesOrNo) { + m_required = yesOrNo; + return *this; + } + cl_option &set_type(OptionType type) { + m_type = type; + return *this; + } + cl_option &set_help(std::string help) { + m_help = help; + return *this; + } + cl_option &set_abbreviation(std::string abbr) { + m_abbrev = abbr; + return *this; + } + + std::string &abbreviation() { return m_abbrev; } + std::string &help() { return m_help; } + OptionType type() { return m_type; } + bool required() { return m_required; } + +private: + bool m_required; + OptionType m_type; + std::string m_help; + std::string m_abbrev; +}; + +class cl_parser { +public: + typedef std::unordered_map cl_options_t; + typedef std::unordered_map key_value_t; + + cl_parser() { + m_reserved_key = "--help"; + m_reserved_key_abbr = "-h"; + } + + ~cl_parser() {} + + void parse(int argc, char **argv) { + m_cl_options.resize(argc); + // Go through the command-line options + // list and build an internal + // + m_app_name = argv[0]; + for (int i = 1; i < argc; ++i) { + m_cl_options[i - 1] = argv[i]; + } + + build_abbreviation_table(); + + if (!check_options()) { + print_help(); + exit(-1); + } + } + + cl_option &add_option(std::string key) { + if (key == m_reserved_key) { + std::cout << "Option[" << key + << "] is a reserved option. Ignoring the add_option() call!\n"; + // throw an exception here; + } + if (m_option_help_lut.count(key)) { + std::cout << "Option " << key << " has already been registered!\n"; + return m_option_help_lut[key]; + } + + return m_option_help_lut[key]; + } + + std::string &query(const char *key) { + if (m_option_help_lut.count(key)) { + return m_value_lut[key]; + } else if (m_abbreviated_option_lut.count(key)) { + std::string full_key = m_abbreviated_option_lut[key]; + if (m_value_lut.count(full_key)) { + return m_value_lut[full_key]; + } + return m_empty_string; + } + } + +private: + void build_abbreviation_table() { + for (auto &o : m_option_help_lut) { + std::string &abbr = o.second.abbreviation(); + if (!abbr.empty()) { + m_abbreviated_option_lut[abbr] = o.first; + } + } + } + + void print_help() { + std::cout << "Usage:- \n"; + std::cout << " " << m_app_name << " "; + // Print all required options first + // + for (auto &op : m_option_help_lut) { + if (op.second.required()) { + // Required! + std::cout << op.first << " "; + switch (op.second.type()) { + case OptionType::Integer: + std::cout << " "; + break; + case OptionType::Float: + std::cout << " "; + break; + case OptionType::Boolean: + std::cout << " "; + break; + case OptionType::String: + std::cout << " "; + break; + case OptionType::Range: + std::cout << " "; + break; + } + } + } + // Print the optional flags next. + // + for (auto &op : m_option_help_lut) { + if (!op.second.required()) { + // Not required! + std::cout << "[" << op.first << " "; + switch (op.second.type()) { + case OptionType::Integer: + std::cout << "] "; + break; + case OptionType::Float: + std::cout << "] "; + break; + case OptionType::Boolean: + std::cout << "] "; + break; + break; + case OptionType::String: + std::cout << "] "; + break; + case OptionType::Range: + std::cout << "] "; + break; + } + } + } + std::cout << "\n Options supported:\n"; + // Print help for all of the options + // + for (auto &op : m_option_help_lut) { + std::stringstream help(op.second.help()); + std::string help_line; + bool first = true; + + while (std::getline(help, help_line, '\n')) { + if (first) { + std::string options = op.first + ", " + op.second.abbreviation(); + first = false; + std::cout << " " << std::left << std::setw(20) << options << " " + << help_line << "\n"; + } else { + std::cout << " " << std::left << std::setw(20) << " " + << " " << help_line << "\n"; + } + } + } + } + + bool check_options() { + bool pass = true; + std::string prev_key; + for (auto &op : m_cl_options) { + std::size_t pos = op.find_first_of("-"); + if (std::string::npos != pos) { + // We have an option provided; let's + // check to see if it is verbose or + // abbreviated + // + pos = op.find_first_of("-", pos + 1); + if (std::string::npos != pos) { + // We have a verbose option + // + if (op == m_reserved_key) { + print_help(); + exit(-1); + } else if (m_option_help_lut.count(op) == 0) { + std::cout << "Unknown option[" << op << "]!\n"; + pass = false; + } + m_value_lut[op] = "true"; + prev_key = op; + } else { + // We have abbreviated option + // + if (op == m_reserved_key_abbr) { + print_help(); + exit(-1); + } else if (m_abbreviated_option_lut.count(op) == 0) { + std::cout << "Unknown option[" << op << "] detected.\n"; + pass = false; + } + prev_key = m_abbreviated_option_lut[op]; + m_value_lut[prev_key] = "true"; + } + } else { + // No idea why stringstream will decode the + // last \n as a "" string; this handles that + // case + // + if (prev_key.empty() && op.empty()) + break; + // We have an option value + // + if (prev_key.empty()) { + std::cout << "Value[" << op + << "] provided without specifying an option\n"; + pass = false; + } else { + m_value_lut[prev_key] = op; + prev_key = m_empty_string; + } + } + } + + for (auto &op : m_option_help_lut) { + // Check to see if an option is required; + // If so, check to see if there's a value + // associated with it. + // + if (op.second.required()) { + // Required! + if (!m_value_lut.count(op.first)) { + std::cout << "Option[" << op.first + << "] is required and not provided.\n"; + pass = false; + } + } + } + + return pass; + } + + std::vector m_cl_options; + cl_options_t m_option_help_lut; + key_value_t m_abbreviated_option_lut; + key_value_t m_value_lut; + std::string m_empty_string; + std::string m_reserved_key; + std::string m_reserved_key_abbr; + std::string m_app_name; +}; + +class table_model { +public: + typedef std::map row_titles_t; + table_model() {} + + void set_headers(titles_t &titles) { m_column_titles = titles; } + + table_row_t &add_row(int row, std::string &row_name) { + if (m_row_titles.count(row)) { + std::cout << "Warning: Row title already specified!\n"; + } + m_row_titles[row] = row_name; + return m_table[row]; + } + table_row_t &add_row(int row, const char *row_name) { + if (m_row_titles.count(row)) { + std::cout << "Warning: Row title already specified!\n"; + } + m_row_titles[row] = row_name; + return m_table[row]; + } + + table_row_t &operator[](int row) { return m_table[row]; } + + void print() { + std::cout << std::setw(14) << " "; + for (auto &title : m_column_titles) { + std::cout << std::setw(14) << title; // Column headers + } + std::cout << "\n"; + + for (auto &row : m_table) { + std::cout << std::setw(14) << m_row_titles[row.first]; + int prev_col = 0; + for (auto &data : row.second) { + std::cout << std::fixed << std::setw(14) << std::setprecision(0) + << data.second; + } + std::cout << "\n"; + } + std::cout << "\n"; + } + +private: + titles_t m_column_titles; + row_titles_t m_row_titles; + table_t m_table; +}; + +class range_decoder { +public: + range_decoder(std::string &range_str) : m_range(range_str) { + // Split by commas first + // followed by : for begin,end, step + // + std::stringstream elements(range_str); + std::string element; + while (std::getline(elements, element, ',')) { + if (element.find_first_of("-:") == std::string::npos) { + m_elements.insert(std::stol(element)); + } else { + std::stringstream r(element); + std::vector range_tokens; + std::string e, b; + // Now split by : + // + while (std::getline(r, e, ':')) { + range_tokens.push_back(e); + } + // range_tokens should have three entries + // Second entry is the step + std::cout << range_tokens[0] << ";" << range_tokens[1] << std::endl; + long step = std::stol(range_tokens[2]); + for (long i = std::stol(range_tokens[0]); + i <= std::stol(range_tokens[1]); i += step) { + m_elements.insert(i); + } + } + } + } + + std::set &decode() { return m_elements; } + +private: + std::string m_range; + std::set m_elements; +}; +} // namespace utils + +namespace semantic { +class test_correctness { +public: + enum class SemanticTests { + StringTableTest = 1, + TracePointTest, + NotificationTest + }; + + test_correctness(test::utils::cl_parser &parser) : m_parser(parser) { + xptiInitialize("xpti", 20, 0, "xptiTests"); + } + + void run() { + auto &v = m_parser.query("--type"); + if (v != "semantic") + return; + + test::utils::range_decoder td(m_parser.query("--num-threads")); + m_threads = td.decode(); + test::utils::range_decoder rd(m_parser.query("--test-id")); + m_tests = rd.decode(); + + run_tests(); + } + + void run_tests() { + for (auto test : m_tests) { + switch ((SemanticTests)test) { + case SemanticTests::StringTableTest: + run_string_table_tests(); + break; + case SemanticTests::TracePointTest: + run_tracepoint_tests(); + break; + case SemanticTests::NotificationTest: + run_notification_tests(); + break; + default: + std::cout << "Unknown test type [" << test << "]: use 1,2,3 or 1:3:1\n"; + break; + } + } + m_table.print(); + } + +private: + void run_string_table_tests(); + void run_string_table_test_threads(int run_no, int nt, + test::utils::table_model &t); + void run_tracepoint_tests(); + void run_tracepoint_test_threads(int run_no, int nt, + test::utils::table_model &t); + void run_notification_tests(); + void run_notification_test_threads(int run_no, int nt, + test::utils::table_model &t); + + test::utils::cl_parser &m_parser; + test::utils::table_model m_table; + std::set m_threads, m_tests; + long m_tracepoints; + const char *m_source = "foo.cpp"; + uint64_t m_instance_id = 0; +}; +} // namespace semantic + +namespace performance { +constexpr long MaxTracepoints = 100000; +constexpr long MinTracepoints = 10; +class test_performance { +public: + struct record { + std::string fn; + uint64_t lookup; + }; + enum class PerformanceTests { DataStructureTest = 1, InstrumentationTest }; + + test_performance(test::utils::cl_parser &parser) : m_parser(parser) { + xptiInitialize("xpti", 20, 0, "xptiTests"); + } + + std::string make_random_string(uint8_t length, std::mt19937_64 &gen) { + if (length > 25) { + length = 25; + } + // A=65, a=97 + std::string s(length, '\0'); + for (int i = 0; i < length; ++i) { + int ascii = m_case(gen); + int value = m_char(gen); + s[i] = (ascii ? value + 97 : value + 65); + } + return s; + } + + void run() { + auto &v = m_parser.query("--type"); + if (v != "performance") + return; + + test::utils::range_decoder td(m_parser.query("--num-threads")); + m_threads = td.decode(); + m_tracepoints = std::stol(m_parser.query("--trace-points")); + if (m_tracepoints > MaxTracepoints) { + std::cout << "Reducing trace points to " << MaxTracepoints << "!\n"; + m_tracepoints = MaxTracepoints; + } + if (m_tracepoints < 0) { + std::cout << "Setting trace points to " << MinTracepoints << "!\n"; + m_tracepoints = MinTracepoints; + } + + test::utils::range_decoder rd(m_parser.query("--test-id")); + m_tests = rd.decode(); + + std::string dist = m_parser.query("--tp-frequency"); + if (dist.empty()) { + // By default, we assume that for every trace point that is created, we + // will visit it NINE more times. + m_tp_instances = m_tracepoints * 10; + } else { + float value = std::stof(dist); + if (value > 100) { + std::cout << "Trace point creation frequency limited to 100%!\n"; + value = 100; + } + if (value < 0) { + std::cout << "Trace point creation frequency set to 1%!\n"; + value = 1; + } + // If not, we compute the number of trace point instances based on the + // trace point frequency value; If the frequency is 10%, then every 10th + // trace point create will be creating a new trace point. If it is 2%, + // then every 50th trace point will create call will result in a new + // trace point. + m_tp_instances = (long)((1.0 / (std::stof(dist) / 100)) * m_tracepoints); + } + // Check to see if overheads to model are set; if not assume 1.0% + dist = m_parser.query("--overhead"); + if (!dist.empty()) { + m_overhead = std::stof(dist); + if (m_overhead < 0.1) { + std::cout << "Overheads to be modeled clamped to range - 0.1%!\n"; + m_overhead = 0.1; + } else if (m_overhead > 15) { + std::cout << "Overheads to be modeled clamped to range - 15%!\n"; + m_overhead = 15; + } + } + + // If the number of trace points(TP) required to run tests on is 1000, then + // we will run our string table tests on the number of TPs we compute. For a + // TP frequency of 10%, we will have TP instances be 1000x10 + m_st_entries = m_tp_instances; + // Mersenne twister RNG engine that is uniform distribution + std::random_device q_rd; + std::mt19937_64 gen(q_rd()); + // Generate the pseudo-random numbers for trace points and string table + // random lookup + m_tp = std::uniform_int_distribution(0, m_tracepoints - 1); + m_st = std::uniform_int_distribution(0, m_st_entries - 1); + m_char = std::uniform_int_distribution(0, 25); + m_case = std::uniform_int_distribution(0, 1); + + m_rnd_st.resize(m_st_entries); + m_rnd_tp.resize(m_st_entries); + for (int i = 0; i < m_st_entries; ++i) { + m_rnd_st[i] = m_st(gen); + } + for (int i = 0; i < m_st_entries; ++i) { + m_rnd_tp[i] = m_tp(gen); + } + // Generate the strings we will be registering with the string table and + // also the random lookup table for trace points + for (int i = 0; i < m_tp_instances; ++i) { + record r; + r.lookup = m_rnd_tp[i]; // 0-999999999 + std::string str = make_random_string(5, gen); + r.fn = str + std::to_string(r.lookup); + m_records.push_back(r); + str = make_random_string(8, gen) + std::to_string(i); + m_functions.push_back(str); + str = make_random_string(8, gen) + std::to_string(i); + m_functions2.push_back(str); + } + // Done with the setup; now run the tests + run_tests(); + } + + void run_tests() { + for (auto test : m_tests) { + switch ((PerformanceTests)test) { + case PerformanceTests::DataStructureTest: + run_data_structure_tests(); + break; + case PerformanceTests::InstrumentationTest: + run_instrumentation_tests(); + break; + default: + std::cout << "Unknown test type [" << test << "]: use 1,2 or 1:2:1\n"; + break; + } + } + m_table.print(); + } + +private: + void run_data_structure_tests(); + void run_data_structure_tests_threads(int run_no, int nt, + test::utils::table_model &t); + void run_instrumentation_tests(); + void run_instrumentation_tests_threads(int run_no, int nt, + test::utils::table_model &t); + + test::utils::cl_parser &m_parser; + test::utils::table_model m_table; + std::set m_threads, m_tests; + long m_tracepoints; + long m_tp_instances; + long m_st_entries; + const char *m_source = "foo.cpp"; + uint64_t m_instance_id = 0; + std::uniform_int_distribution m_tp, m_st, m_char, m_case; + std::vector m_rnd_tp, m_rnd_st; + std::vector m_records; + std::vector m_functions, m_functions2; + double m_overhead = 1.0; +}; +} // namespace performance +} // namespace test diff --git a/xptifw/basic_test/main.cpp b/xptifw/basic_test/main.cpp new file mode 100644 index 0000000000000..8409cf7a9dd90 --- /dev/null +++ b/xptifw/basic_test/main.cpp @@ -0,0 +1,77 @@ +// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// +#include "cl_processor.hpp" + +// This test will expose the correctness and performance tests +// through the command-line options. +int main(int argc, char **argv) { + test::utils::cl_parser options; + + options.add_option("--verbose") + .set_abbreviation("-v") + .set_help("Run the tests in verbose mode. Running in this mode " + "may\naffect performance test metrics.\n") + .set_required(false) + .set_type(test::utils::OptionType::String); + + options.add_option("--trace-points") + .set_abbreviation("-t") + .set_help( + "Number of trace points to use in the tests - Range [10-100000]\n") + .set_required(true) + .set_type(test::utils::OptionType::Integer); + + options.add_option("--type") + .set_abbreviation("-y") + .set_help("Takes in the type of test to run. The options are:\n\n o " + "semantic\n o performance\n\nSemantic tests will ignore all " + "flags that are meant\nfor performance tests.\n") + .set_required(true) + .set_type(test::utils::OptionType::String); + + options.add_option("--test-id") + .set_abbreviation("-i") + .set_help( + "Takes in the test identifier to run a specific test. These\ntests " + "will be identifiers within the semantic or performance tests.\n") + .set_required(true) + .set_type(test::utils::OptionType::Range); + + options.add_option("--num-threads") + .set_abbreviation("-n") + .set_help("Number of threads to use to run the tests.\n") + .set_required(true) + .set_type(test::utils::OptionType::Range); + + options.add_option("--overhead") + .set_abbreviation("-o") + .set_help("Overhead limit in percentage - Range[0.1-15]\n") + .set_required(false) + .set_type(test::utils::OptionType::Float); + + options.add_option("--report") + .set_abbreviation("-r") + .set_help("Print the results in tabular form.\n") + .set_required(false) + .set_type(test::utils::OptionType::String); + + options.add_option("--tp-frequency") + .set_abbreviation("-f") + .set_help("Trace point creation frequency as a percentage of tracepoint " + "instances-Range [1-100]\n") + .set_required(false) + .set_type(test::utils::OptionType::Float); + + options.parse(argc, argv); + + test::semantic::test_correctness ct(options); + test::performance::test_performance pt(options); + + ct.run(); + pt.run(); +} \ No newline at end of file diff --git a/xptifw/basic_test/performance_tests.cpp b/xptifw/basic_test/performance_tests.cpp new file mode 100644 index 0000000000000..d804cf665cc36 --- /dev/null +++ b/xptifw/basic_test/performance_tests.cpp @@ -0,0 +1,552 @@ +// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// +//----------------------- performance_tests.cpp ----------------------------- +// Tests the performance of the API and framework by running real world +// scenarios and computing the average costs and maximum events/sec that can +// be serviced by the framework at a given max. overhead constraint. +//--------------------------------------------------------------------------- +#include +#include +#include + +#include "tbb/concurrent_vector.h" +#include "tbb/parallel_for.h" +#include "tbb/spin_mutex.h" +#include "tbb/task_arena.h" +#include "tbb/task_group.h" + +#include "cl_processor.hpp" +#include "xpti_trace_framework.h" + +namespace test { +void register_callbacks(uint8_t sid); +namespace performance { +enum class DSColumns { + Threads, ///< Slot used to record the number of threads + STInsert, ///< Used to capture the average string insert costs + STLookup, ///< Used to capture the average string lookup costs + STInsertLookup, ///< Avg. string insert+2 lookups cost + TPCreate, ///< Average trace point creation costs + TPUncachedLookup, ///< Average trace point recration costs using payload + TPFWCache, ///< Average trace event lookup using unique_id + TPLocalCache, ///< Average costs to look up locally cached event (0) + Notify ///< Average notification costs +}; + +enum class FWColumns { + Threads, ///< Slot used to record the number of threads + TPLookupAndNotify, ///< Average cost to create a trace event and notify based + ///< on the average frequency of a new tracepoint being + ///< created as a function of total number of trace point + ///< lookup/notifications + TPCreate, ///< Average trace point event creation cost + EPS10, ///< Events/sec @ given overhead with CB handler cost of 10ns + EPS100, ///< Events/sec @ given overhead with CB handler cost of 100ns + EPS500, ///< Events/sec @ given overhead with CB handler cost of 500ns + EPS1000, ///< Events/sec @ given overhead with CB handler cost of 1000ns + EPS2000 ///< Events/sec @ given overhead with CB handler cost of 2000ns +}; + +void test_performance::run_data_structure_tests_threads( + int run_no, int num_threads, test::utils::table_model &t) { + xptiReset(); + uint64_t ns; + double ratio; + + // If the num-threads specification includes 0, then a true serial version + // outside of TBB is run + if (!num_threads) { + auto &row = t.add_row(run_no, "Serial"); + row[(int)DSColumns::Threads] = num_threads; + // Hold the string ids for measuring lookup later + std::vector ids; + ids.resize(m_tracepoints); + // Columns 1, 2: Insert, 2 Lookups + // Perform measurement tests to determine the cost of insertions into the + // string table, the lookup costs and a composite measurement of insertion + // and 2 lookups for strings added to the string table + { + // Create 'm_tracepoint' number of strings and measure the cost of serial + // insertions into a concurrent container. Here, using an unordered_map + // will be faster, but we rely on TBB concurrent containers to ensure they + // are thread safe + { + test::utils::scoped_timer timer(ns, ratio, m_tracepoints); + for (int i = 0; i < m_tracepoints; ++i) { + char *table_str = nullptr; + // Assume that the string has already been created as it is normally + // provided to the Payload constructors + std::string &str = m_functions[i]; + ids[i] = xptiRegisterString(str.c_str(), &table_str); + } + } + row[(int)DSColumns::STInsert] = ratio; + + { // lookup the created strings "m_tracepoints" randomly + test::utils::scoped_timer timer(ns, ratio, m_tracepoints * 2); + for (int i = 0; i < m_tracepoints * 2; ++i) { + int lookup = m_rnd_tp[i % m_st_entries]; + const char *lut_string = xptiLookupString(ids[lookup]); + } + } + row[(int)DSColumns::STLookup] = ratio; + } + + // Column 3: Insert+ 2 Lookups + // Perform measurement tests to determine the cost of insertion and 2 + // lookups for strings added to the string table + { // Create NEW "m_tracepoint" strings + std::vector new_ids; + new_ids.resize(m_tracepoints); + long no_of_operations = m_tracepoints * 3; + test::utils::scoped_timer timer(ns, ratio, no_of_operations); + for (int i = 0; i < m_tracepoints; ++i) { + char *table_str = nullptr; + std::string &str = m_functions2[i]; + new_ids.push_back(xptiRegisterString(str.c_str(), &table_str)); + } + for (int i = 0; i < m_tracepoints * 2; ++i) { + int lookup = m_rnd_tp[i % m_st_entries]; // Generates a value between + // 0-m_tracepoints-1 + const char *lut_string = xptiLookupString(ids[lookup]); + } + } + row[(int)DSColumns::STInsertLookup] = ratio; + + std::vector uids; + std::vector events; + uids.resize(m_tracepoints); + events.resize(m_tracepoints); + // Column 4: Measure the cost of trace point creation and cache the returned + // event and event ids + { + // Create "m_tracepoints" number of trace point events + test::utils::scoped_timer timer(ns, ratio, m_tracepoints); + for (int i = 0; i < m_tracepoints; ++i) { + record &r = m_records[i]; + int lookup = r.lookup; + std::string &fn = r.fn; + xpti::payload_t p = xpti::payload_t(fn.c_str(), m_source, lookup, + lookup % 80, (void *)r.lookup); + xpti::trace_event_data_t *e = xptiMakeEvent( + fn.c_str(), &p, (uint16_t)xpti::trace_event_type_t::algorithm, + xpti::trace_activity_type_t::active, &m_instance_id); + if (e) { + uids[lookup] = e->unique_id; + events[lookup] = e; + } + } + } + row[(int)DSColumns::TPCreate] = ratio; + + // Column 5: Measure the cost of trace point creation of previously created + // trace points in an un-cached manner + { // Lookup "m_tracepoints" instances, uncached where we create the payload + // each time + test::utils::scoped_timer timer(ns, ratio, m_tracepoints); + for (int i = 0; i < m_tracepoints; ++i) { + record &r = m_records[i]; + uint64_t lookup = r.lookup; + std::string &fn = r.fn; + xpti::payload_t p = xpti::payload_t(fn.c_str(), m_source, (int)lookup, + (int)lookup % 80, (void *)lookup); + xpti::trace_event_data_t *e = xptiMakeEvent( + fn.c_str(), &p, (uint16_t)xpti::trace_event_type_t::algorithm, + xpti::trace_activity_type_t::active, &m_instance_id); + } + } + row[(int)DSColumns::TPUncachedLookup] = ratio; + + // Column 6: Measure the cost of trace point creation of previously created + // trace points in an framework-cached manner + { // Lookup "m_tracepoints" instances, framework-cached + test::utils::scoped_timer timer(ns, ratio, m_tracepoints); + for (int i = 0; i < m_tracepoints; ++i) { + record &r = m_records[i]; + uint64_t lookup = r.lookup; + xpti::trace_event_data_t *e = + const_cast(xptiFindEvent(uids[lookup])); + } + } + row[(int)DSColumns::TPFWCache] = ratio; + + // Column 7: Measure the cost of trace point creation of previously created + // and cached trace points + { // Lookup "m_tracepoints" instances, locally-cached or locally visible + test::utils::scoped_timer timer(ns, ratio, m_tp_instances); + for (int i = 0; i < m_tp_instances; ++i) { + record &r = m_records[i % m_tracepoints]; + uint64_t lookup = r.lookup; // get the random id to lookup + xpti::trace_event_data_t *e = events[lookup]; + } + } + row[(int)DSColumns::TPLocalCache] = ratio; + + { // Notify "m_tracepoints" number tps, locally cached + test::utils::scoped_timer timer(ns, ratio, m_tp_instances); + for (int i = 0; i < m_tp_instances; ++i) { + record &r = m_records[i % m_tracepoints]; + uint64_t lookup = r.lookup; + xpti::trace_event_data_t *e = events[lookup]; + xpti::framework::scoped_notify ev( + "xpti", (uint16_t)xpti::trace_point_type_t::region_begin, nullptr, + e, m_instance_id, nullptr); + } + } + row[(int)DSColumns::Notify] = ratio; + + } else { + // Now run the same performance tests in multi-threaded mode to accommodate + // lock contention costs + + std::string row_title = "Threads " + std::to_string(num_threads); + auto &row = t.add_row(run_no, row_title); + row[(int)DSColumns::Threads] = num_threads; + + // Limit TBB to use the number of threads for this run + tbb::task_arena a(num_threads); + a.execute([&]() { + std::vector ids; + ids.resize(m_tracepoints); + // Columns 1, 2: Insert, 2 Lookups + // Perform measurement tests to determine the cost of insertions into the + // string table, the lookup costs and a composite measurement of insertion + // and 2 lookups for strings added to the string table + { + { // Create "m_tracepoints" strings + test::utils::scoped_timer timer(ns, ratio, m_tracepoints); + tbb::parallel_for(tbb::blocked_range(0, m_tracepoints), + [&](tbb::blocked_range &r) { + for (int i = r.begin(); i != r.end(); ++i) { + char *table_str = nullptr; + std::string &str = m_functions[i]; + ids[i] = + xptiRegisterString(str.c_str(), &table_str); + } + }); + } + row[(int)DSColumns::STInsert] = ratio; + + { // lookup the created strings "m_tracepoints*2" linearly + test::utils::scoped_timer timer(ns, ratio, m_tracepoints * 2); + tbb::parallel_for(tbb::blocked_range(0, m_tracepoints * 2), + [&](tbb::blocked_range &r) { + for (int i = r.begin(); i != r.end(); ++i) { + int lookup = m_rnd_tp[i % m_st_entries]; + const char *lut_string = + xptiLookupString(ids[lookup]); + } + }); + } + row[(int)DSColumns::STLookup] = ratio; + } + // Column 3: Insert+ 2 Lookups + // Perform measurement tests to determine the cost of insertion and 2 + // lookups for strings added to the string table + { // insert and lookup at the same time "m_st_entries*10" + std::vector new_ids; + new_ids.resize(m_tracepoints); + tbb::task_group g; + // 2 lookups + 1 insert of m_tracepoints elements that occurs + // simultaneously + long no_of_operations = m_tracepoints * 3; + test::utils::scoped_timer timer(ns, ratio, no_of_operations); + g.run([&] { + // Add new strings + tbb::parallel_for(tbb::blocked_range(0, m_tracepoints), + [&](tbb::blocked_range &r) { + for (int i = r.begin(); i != r.end(); ++i) { + char *table_str = nullptr; + std::string &str = m_functions2[i]; + new_ids[i] = + xptiRegisterString(str.c_str(), &table_str); + } + }); + }); + g.run([&] { + // And read previously added strings + tbb::parallel_for( + tbb::blocked_range(0, m_st_entries), + [&](tbb::blocked_range &r) { + for (int i = r.begin(); i != r.end(); ++i) { + int lookup = + m_rnd_tp[i % m_st_entries]; // Generates a value between + // 0-m_tracepoints-1 + // Read from previously added strings by looking + // up the old IDs stored in 'ids' + const char *lut_string = xptiLookupString(ids[lookup]); + } + }); + }); + g.wait(); + } + row[(int)DSColumns::STInsertLookup] = ratio; + + std::vector uids; + std::vector events; + uids.resize(m_tracepoints); + events.resize(m_tracepoints); + // Column 4: Measure the cost of trace point creation and cache the + // returned event and event ids + { // Create "m_tracepoints" number of trace point events + test::utils::scoped_timer timer(ns, ratio, m_tracepoints); + tbb::parallel_for( + tbb::blocked_range(0, m_tracepoints), + [&](tbb::blocked_range &r) { + for (int i = r.begin(); i != r.end(); ++i) { + record &r = m_records[i]; + int lookup = r.lookup; + std::string &fn = r.fn; + xpti::payload_t p = + xpti::payload_t(fn.c_str(), m_source, lookup, lookup % 80, + (void *)r.lookup); + xpti::trace_event_data_t *e = xptiMakeEvent( + fn.c_str(), &p, + (uint16_t)xpti::trace_event_type_t::algorithm, + xpti::trace_activity_type_t::active, &m_instance_id); + if (e) { + uids[lookup] = e->unique_id; + events[lookup] = e; + } + } + }); + } + row[(int)DSColumns::TPCreate] = ratio; + + // Column 5: Measure the cost of trace point creation of previously + // created trace points in an un-cached manner + { // Lookup "m_tracepoints" number of trace point events, uncached + test::utils::scoped_timer timer(ns, ratio, m_tracepoints); + tbb::parallel_for( + tbb::blocked_range(0, m_tracepoints), + [&](tbb::blocked_range &r) { + for (int i = r.begin(); i != r.end(); ++i) { + record &r = m_records[i]; + int lookup = r.lookup; + std::string &fn = r.fn; + xpti::payload_t p = + xpti::payload_t(fn.c_str(), m_source, lookup, lookup % 80, + (void *)r.lookup); + xpti::trace_event_data_t *e = xptiMakeEvent( + fn.c_str(), &p, + (uint16_t)xpti::trace_event_type_t::algorithm, + xpti::trace_activity_type_t::active, &m_instance_id); + } + }); + } + row[(int)DSColumns::TPUncachedLookup] = ratio; + + // Column 6: Measure the cost of trace point creation of previously + // created trace points in an framework-cached manner + { // Lookup "m_tp_instances" number of trace point events, + // framework-cached + test::utils::scoped_timer timer(ns, ratio, m_tracepoints); + tbb::parallel_for(tbb::blocked_range(0, m_tracepoints), + [&](tbb::blocked_range &r) { + for (int i = r.begin(); i != r.end(); ++i) { + record &r = m_records[i]; + uint64_t lookup = r.lookup; + xpti::trace_event_data_t *e = + const_cast( + xptiFindEvent(uids[lookup])); + } + }); + } + row[(int)DSColumns::TPFWCache] = ratio; + + // Column 7: Measure the cost of trace point creation of previously + // created and cached trace points + { // Lookup "m_tracepoints" number of trace point events, locally-cached + test::utils::scoped_timer timer(ns, ratio, m_tp_instances); + tbb::parallel_for(tbb::blocked_range(0, m_tp_instances), + [&](tbb::blocked_range &r) { + for (int i = r.begin(); i != r.end(); ++i) { + record &r = m_records[i % m_tracepoints]; + uint64_t lookup = + r.lookup; // get the random id to lookup + xpti::trace_event_data_t *e = events[lookup]; + } + }); + } + row[(int)DSColumns::TPLocalCache] = ratio; + + { // Notify "m_tracepoints" number tps, locally cached + test::utils::scoped_timer timer(ns, ratio, m_tp_instances); + tbb::parallel_for( + tbb::blocked_range(0, m_tp_instances), + [&](tbb::blocked_range &r) { + for (int i = r.begin(); i != r.end(); ++i) { + record &r = m_records[i % m_tracepoints]; + uint64_t lookup = r.lookup; + xpti::trace_event_data_t *e = events[lookup]; + xpti::framework::scoped_notify ev( + "xpti", (uint16_t)xpti::trace_point_type_t::region_begin, + nullptr, e, m_instance_id, nullptr); + } + }); + } + row[(int)DSColumns::Notify] = ratio; + }); + } +} + +void test_performance::run_data_structure_tests() { + test::utils::table_model table; + + test::utils::titles_t columns{"Threads", "Str.Insert", "Str.Lookup", + "St.Ins/Lu", "TP Create", "TP Un-Cached", + "TP FW-Cached", "TP Local", "Notify"}; + std::cout << std::setw(columns.size() * 15 / 2) + << "Data Structure Tests [FW=framework, Lu=lookup, " + "TP=Tracepoint, Time=ns\n"; + table.set_headers(columns); + + uint8_t sid = xptiRegisterStream("xpti"); + test::register_callbacks(sid); + + if (m_threads.size()) { + int run_no = 0; + for (auto thread : m_threads) { + run_data_structure_tests_threads(run_no++, thread, table); + } + } + + table.print(); +} + +void test_performance::run_instrumentation_tests_threads( + int run_no, int num_threads, test::utils::table_model &t) { + xptiReset(); + uint64_t ns; + double ratio; + + std::vector tp_ids; + tp_ids.resize(m_tracepoints); + std::vector events; + events.resize(m_tracepoints); + // Variables used to compute events/sec + uint64_t events_per_sec, overhead_based_cost; + std::vector> cb_handler_cost = { + {FWColumns::EPS10, 10}, + {FWColumns::EPS100, 100}, + {FWColumns::EPS500, 500}, + {FWColumns::EPS1000, 1000}, + {FWColumns::EPS2000, 2000}}; + + if (!num_threads) { + auto &row = t.add_row(run_no, "Serial"); + row[(int)FWColumns::Threads] = num_threads; + { + test::utils::scoped_timer timer(ns, ratio, m_tp_instances * 2); + { + test::utils::scoped_timer timer(ns, ratio, m_tracepoints); + for (int i = 0; i < m_tracepoints; ++i) { + std::string &fn = m_functions[i]; + xpti::payload_t p(fn.c_str(), m_source, i, i % 80, (void *)i); + xpti::trace_event_data_t *e = xptiMakeEvent( + fn.c_str(), &p, (uint16_t)xpti::trace_event_type_t::algorithm, + xpti::trace_activity_type_t::active, &m_instance_id); + if (e) { + tp_ids[i] = e->unique_id; + events[i] = e; + } + } + } + row[(int)FWColumns::TPCreate] = ratio; + for (int i = 0; i < m_tp_instances; ++i) { + int lookup = m_rnd_tp[i % m_st_entries]; + xpti::trace_event_data_t *e = events[lookup]; + xpti::framework::scoped_notify ev( + "xpti", (uint16_t)xpti::trace_point_type_t::region_begin, nullptr, + e, m_instance_id, nullptr); + } + } + row[(int)FWColumns::TPLookupAndNotify] = ratio; + for (auto cost : cb_handler_cost) { + // Amount of non-instrumentation based work that needs to be present for + // it to meet the overhead constraints requested + overhead_based_cost = (ratio + cost.second) * (100.0 / m_overhead); + row[(int)cost.first] = 1000000000 / overhead_based_cost; + } + + } else { + tbb::task_arena a(num_threads); + + std::string row_title = "Threads " + std::to_string(num_threads); + auto &row = t.add_row(run_no, row_title); + row[(int)FWColumns::Threads] = num_threads; + { + test::utils::scoped_timer timer(ns, ratio, m_tp_instances * 2); + a.execute([&]() { + { + test::utils::scoped_timer timer(ns, ratio, m_tracepoints); + tbb::parallel_for( + tbb::blocked_range(0, m_tracepoints), + [&](tbb::blocked_range &r) { + for (int i = r.begin(); i != r.end(); ++i) { + std::string &fn = m_functions[i]; + xpti::payload_t p(fn.c_str(), m_source, i, i % 80, (void *)i); + xpti::trace_event_data_t *e = xptiMakeEvent( + fn.c_str(), &p, + (uint16_t)xpti::trace_event_type_t::algorithm, + xpti::trace_activity_type_t::active, &m_instance_id); + if (e) { + tp_ids[i] = e->unique_id; + events[i] = e; + } + } + }); + } + row[(int)FWColumns::TPCreate] = ratio; + tbb::parallel_for( + tbb::blocked_range(0, m_tp_instances), + [&](tbb::blocked_range &r) { + for (int i = r.begin(); i != r.end(); ++i) { + record &r = m_records[i % m_tracepoints]; + uint64_t lookup = r.lookup; + xpti::trace_event_data_t *e = events[lookup]; + xpti::framework::scoped_notify ev( + "xpti", (uint16_t)xpti::trace_point_type_t::region_begin, + nullptr, e, m_instance_id, nullptr); + } + }); + }); + } + row[(int)FWColumns::TPLookupAndNotify] = ratio; + for (auto cost : cb_handler_cost) { + // Amount of non-instrumentation based work that needs to be present for + // it to meet the overhead constraints requested + overhead_based_cost = (ratio + cost.second) * (100.0 / m_overhead); + row[(int)cost.first] = 1000000000 / overhead_based_cost; + } + } +} + +void test_performance::run_instrumentation_tests() { + test::utils::table_model table; + + test::utils::titles_t columns{ + "Threads", "TP LU+Notify(ns)", "TP Create(ns)", "Ev/s,cb=10", + "Ev/s,cb=100", "Ev/s,cb=500", "Ev/s,cb=1000", "Ev/s,cb=2000"}; + std::cout << std::setw(columns.size() * 15 / 2) << "Framework Tests\n"; + table.set_headers(columns); + uint8_t sid = xptiRegisterStream("xpti"); + test::register_callbacks(sid); + + if (m_threads.size()) { + int run_no = 0; + for (auto thread : m_threads) { + run_instrumentation_tests_threads(run_no++, thread, table); + } + } + + table.print(); +} + +} // namespace performance +} // namespace test \ No newline at end of file diff --git a/xptifw/basic_test/semantic_tests.cpp b/xptifw/basic_test/semantic_tests.cpp new file mode 100644 index 0000000000000..69a92dddc3432 --- /dev/null +++ b/xptifw/basic_test/semantic_tests.cpp @@ -0,0 +1,511 @@ +// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// +//----------------------- semantic_tests.cpp ------------------------------- +// Tests the correctness of the API by comparing it agains the spec and +// expected results. +//-------------------------------------------------------------------------- +#include +#include +#include + +#include "tbb/concurrent_vector.h" +#include "tbb/parallel_for.h" +#include "tbb/spin_mutex.h" +#include "tbb/task_arena.h" +#include "tbb/task_group.h" + +#include "cl_processor.hpp" +#include "xpti_trace_framework.h" + +static void tp_cb(uint16_t trace_type, xpti::trace_event_data_t *parent, + xpti::trace_event_data_t *event, uint64_t instance, + const void *ud) {} + +namespace test { +void register_callbacks(uint8_t sid) { + xptiRegisterCallback(sid, (uint16_t)xpti::trace_point_type_t::graph_create, + tp_cb); + xptiRegisterCallback(sid, (uint16_t)xpti::trace_point_type_t::node_create, + tp_cb); + xptiRegisterCallback(sid, (uint16_t)xpti::trace_point_type_t::edge_create, + tp_cb); + xptiRegisterCallback(sid, (uint16_t)xpti::trace_point_type_t::region_begin, + tp_cb); + xptiRegisterCallback(sid, (uint16_t)xpti::trace_point_type_t::region_end, + tp_cb); + xptiRegisterCallback(sid, (uint16_t)xpti::trace_point_type_t::task_begin, + tp_cb); + xptiRegisterCallback(sid, (uint16_t)xpti::trace_point_type_t::task_end, + tp_cb); + xptiRegisterCallback(sid, (uint16_t)xpti::trace_point_type_t::barrier_begin, + tp_cb); + xptiRegisterCallback(sid, (uint16_t)xpti::trace_point_type_t::barrier_end, + tp_cb); + xptiRegisterCallback(sid, (uint16_t)xpti::trace_point_type_t::lock_begin, + tp_cb); + xptiRegisterCallback(sid, (uint16_t)xpti::trace_point_type_t::lock_end, + tp_cb); + xptiRegisterCallback(sid, (uint16_t)xpti::trace_point_type_t::transfer_begin, + tp_cb); + xptiRegisterCallback(sid, (uint16_t)xpti::trace_point_type_t::transfer_end, + tp_cb); + xptiRegisterCallback(sid, (uint16_t)xpti::trace_point_type_t::thread_begin, + tp_cb); + xptiRegisterCallback(sid, (uint16_t)xpti::trace_point_type_t::thread_end, + tp_cb); + xptiRegisterCallback(sid, (uint16_t)xpti::trace_point_type_t::wait_begin, + tp_cb); + xptiRegisterCallback(sid, (uint16_t)xpti::trace_point_type_t::wait_end, + tp_cb); + xptiRegisterCallback(sid, (uint16_t)xpti::trace_point_type_t::signal, tp_cb); +} +// The semantic namespace contains tests to determine the correctness of the +// implementation. The test ensure that the framework is robust under serial and +// multi-threaded conditions. +namespace semantic { +enum class STColumns { + Threads, + Insertions, + Lookups, + DuplicateInserts, + PassRate +}; + +enum class TPColumns { + Threads, + Insertions, + Lookups, + DuplicateInserts, + PayloadLookup, + PassRate +}; + +enum class NColumns { Threads, Notifications, PassRate }; + +void test_correctness::run_string_table_test_threads( + int run_no, int num_threads, test::utils::table_model &t) { + xptiReset(); + constexpr long num_strings = 1000; + + if (!num_threads) { + std::vector strings; + std::vector ids; + ids.resize(num_strings); + strings.resize(num_strings); + for (int i = 0; i < num_strings; ++i) { + char *table_str = nullptr; + std::string str = "Function" + std::to_string(i); + ids[i] = xptiRegisterString(str.c_str(), &table_str); + strings[i] = table_str; + } + auto &row = t.add_row(run_no, "Serial"); + row[(int)STColumns::Threads] = num_threads; + row[(int)STColumns::Insertions] = (long double)strings.size(); + int lookup_count = 0; + for (int i = 0; i < strings.size(); ++i) { + const char *table_str = xptiLookupString(ids[i]); + if (table_str == strings[i]) + ++lookup_count; + } + row[(int)STColumns::Lookups] = lookup_count; + int duplicate_count = 0; + for (int i = 0; i < strings.size(); ++i) { + char *table_str = nullptr; + std::string str = "Function" + std::to_string(i); + xpti::string_id_t id = xptiRegisterString(str.c_str(), &table_str); + if (str == table_str && id == ids[i] && table_str == strings[i]) + ++duplicate_count; + } + row[(int)STColumns::DuplicateInserts] = duplicate_count; + row[(int)STColumns::PassRate] = + (double)(strings.size() + lookup_count + duplicate_count) / + (num_strings * 3) * 100; + } else { + tbb::task_arena a(num_threads); + + a.execute([&]() { + std::vector strings; + std::vector ids; + strings.resize(num_strings); + ids.resize(num_strings); + tbb::parallel_for(tbb::blocked_range(0, num_strings), + [&](tbb::blocked_range &r) { + for (int i = r.begin(); i != r.end(); ++i) { + char *table_str = nullptr; + std::string str = "Function" + std::to_string(i); + ids[i] = + xptiRegisterString(str.c_str(), &table_str); + strings[i] = table_str; + } + }); + + std::string row_title = "Threads " + std::to_string(num_threads); + auto &row = t.add_row(run_no, row_title); + row[(int)STColumns::Threads] = num_threads; + row[(int)STColumns::Insertions] = (long double)strings.size(); + std::atomic lookup_count = {0}, duplicate_count = {0}; + tbb::parallel_for(tbb::blocked_range(0, num_strings), + [&](tbb::blocked_range &r) { + for (int i = r.begin(); i != r.end(); ++i) { + const char *table_str = xptiLookupString(ids[i]); + if (table_str == strings[i]) + ++lookup_count; + } + }); + row[(int)STColumns::Lookups] = lookup_count; + tbb::parallel_for(tbb::blocked_range(0, num_strings), + [&](tbb::blocked_range &r) { + for (int i = r.begin(); i != r.end(); ++i) { + char *table_str = nullptr; + std::string str = "Function" + std::to_string(i); + xpti::string_id_t id = + xptiRegisterString(str.c_str(), &table_str); + if (str == table_str && id == ids[i] && + table_str == strings[i]) + ++duplicate_count; + } + }); + row[(int)STColumns::DuplicateInserts] = duplicate_count; + + row[(int)STColumns::PassRate] = + (double)(strings.size() + lookup_count + duplicate_count) / + (num_strings * 3) * 100; + }); + } +} + +void test_correctness::run_string_table_tests() { + test::utils::table_model table; + + test::utils::titles_t columns{"Threads", "Insert", "Lookup", "Duplicate", + "Pass rate"}; + std::cout << std::setw(25) << "String Table Tests\n"; + table.set_headers(columns); + + if (m_threads.size()) { + int run_no = 0; + for (auto thread : m_threads) { + run_string_table_test_threads(run_no++, thread, table); + } + } + + table.print(); +} + +void test_correctness::run_tracepoint_test_threads( + int run_no, int num_threads, test::utils::table_model &t) { + xptiReset(); + constexpr long tracepoint_count = 1000; + + if (!num_threads) { + std::vector payloads; + std::vector uids; + std::vector events; + payloads.resize(tracepoint_count); + uids.resize(tracepoint_count); + events.resize(tracepoint_count); + + for (uint64_t i = 0; i < tracepoint_count; ++i) { + std::string fn = "Function" + std::to_string(i); + xpti::payload_t p = xpti::payload_t(fn.c_str(), m_source, (int)i, + (int)(i % 80), (void *)i); + xpti::trace_event_data_t *e = xptiMakeEvent( + fn.c_str(), &p, (uint16_t)xpti::trace_event_type_t::algorithm, + xpti::trace_activity_type_t::active, &m_instance_id); + if (e) { + uids[i] = e->unique_id; + payloads[i] = e->reserved.payload; + events[i] = e; + } + } + auto &row = t.add_row(run_no, "Serial"); + row[(int)TPColumns::Threads] = num_threads; + row[(int)TPColumns::Insertions] = (long double)events.size(); + + std::atomic lookup_count = {0}; + for (int i = 0; i < events.size(); ++i) { + const xpti::trace_event_data_t *e = xptiFindEvent(uids[i]); + if (e && e->unique_id == uids[i]) + ++lookup_count; + } + row[(int)TPColumns::Lookups] = lookup_count; + std::atomic duplicate_count = {0}; + std::atomic payload_count = {0}; + for (uint64_t i = 0; i < events.size(); ++i) { + std::string fn = "Function" + std::to_string(i); + xpti::payload_t p = + xpti::payload_t(fn.c_str(), m_source, (int)i, (int)i % 80, (void *)i); + xpti::trace_event_data_t *e = xptiMakeEvent( + fn.c_str(), &p, (uint16_t)xpti::trace_event_type_t::algorithm, + xpti::trace_activity_type_t::active, &m_instance_id); + if (e) { + if (e->unique_id == uids[i]) { + ++duplicate_count; + } + xpti::payload_t *rp = e->reserved.payload; + if (e->unique_id == uids[i] && rp && + std::string(rp->name) == std::string(p.name) && + std::string(rp->source_file) == std::string(p.source_file) && + rp->line_no == p.line_no && rp->column_no == p.column_no) + ++payload_count; + } + } + row[(int)TPColumns::DuplicateInserts] = duplicate_count; + row[(int)TPColumns::PayloadLookup] = payload_count; + row[(int)TPColumns::PassRate] = (double)(events.size() + lookup_count + + duplicate_count + payload_count) / + (tracepoint_count * 4) * 100; + } else { + tbb::task_arena a(num_threads); + + a.execute([&]() { + std::vector payloads; + std::vector uids; + std::vector events; + payloads.resize(tracepoint_count); + uids.resize(tracepoint_count); + events.resize(tracepoint_count); + + tbb::spin_mutex m_lock; + tbb::parallel_for( + tbb::blocked_range(0, tracepoint_count), + [&](tbb::blocked_range &r) { + for (uint64_t i = r.begin(); i != r.end(); ++i) { + std::string fn = "Function" + std::to_string(i); + xpti::payload_t p = xpti::payload_t(fn.c_str(), m_source, (int)i, + (int)i % 80, (void *)i); + xpti::trace_event_data_t *e = xptiMakeEvent( + fn.c_str(), &p, (uint16_t)xpti::trace_event_type_t::algorithm, + xpti::trace_activity_type_t::active, &m_instance_id); + if (e) { + uids[i] = e->unique_id; + payloads[i] = e->reserved.payload; + events[i] = e; + } + } + }); + + std::string row_title = "Threads " + std::to_string(num_threads); + auto &row = t.add_row(run_no, row_title); + row[(int)TPColumns::Threads] = num_threads; + row[(int)TPColumns::Insertions] = (long double)events.size(); + std::atomic lookup_count = {0}, duplicate_count = {0}, + payload_count = {0}; + tbb::parallel_for(tbb::blocked_range(0, tracepoint_count), + [&](tbb::blocked_range &r) { + for (int i = r.begin(); i != r.end(); ++i) { + const xpti::trace_event_data_t *e = + xptiFindEvent(uids[i]); + if (e && e->unique_id == uids[i]) + lookup_count++; + } + }); + + row[(int)TPColumns::Lookups] = lookup_count; + tbb::parallel_for( + tbb::blocked_range(0, tracepoint_count), + [&](tbb::blocked_range &r) { + for (uint64_t i = r.begin(); i != r.end(); ++i) { + std::string fn = "Function" + std::to_string(i); + xpti::payload_t p = xpti::payload_t(fn.c_str(), m_source, (int)i, + (int)i % 80, (void *)i); + xpti::trace_event_data_t *e = xptiMakeEvent( + fn.c_str(), &p, (uint16_t)xpti::trace_event_type_t::algorithm, + xpti::trace_activity_type_t::active, &m_instance_id); + if (e) { + if (e->unique_id == uids[i]) { + ++duplicate_count; + } + xpti::payload_t *rp = e->reserved.payload; + if (e->unique_id == uids[i] && rp && + std::string(rp->name) == std::string(p.name) && + std::string(rp->source_file) == + std::string(p.source_file) && + rp->line_no == p.line_no && rp->column_no == p.column_no) + ++payload_count; + } + } + }); + row[(int)TPColumns::DuplicateInserts] = duplicate_count; + row[(int)TPColumns::PayloadLookup] = payload_count; + row[(int)TPColumns::PassRate] = + (double)(events.size() + lookup_count + duplicate_count + + payload_count) / + (tracepoint_count * 4) * 100; + }); + } +} + +void test_correctness::run_tracepoint_tests() { + test::utils::table_model table; + + test::utils::titles_t columns{"Threads", "Create", "Lookup", + "Duplicate", "Payload", "Pass rate"}; + std::cout << std::setw(25) << "Tracepoint Tests\n"; + table.set_headers(columns); + + if (m_threads.size()) { + int run_no = 0; + for (auto thread : m_threads) { + run_tracepoint_test_threads(run_no++, thread, table); + } + } + + table.print(); +} + +void test_correctness::run_notification_test_threads( + int run_no, int num_threads, test::utils::table_model &t) { + xptiReset(); + constexpr long tp_count = 30, callback_count = tp_count * 30; + std::vector payloads; + std::vector uids; + std::vector events; + payloads.resize(tp_count); + uids.resize(tp_count); + events.resize(tp_count); + + if (!num_threads) { + + // assumes tp creation is thread safe + std::atomic notify_count = {0}; + for (uint64_t i = 0; i < tp_count; ++i) { + int index = (int)i; + std::string fn = "Function" + std::to_string(i); + xpti::payload_t p = xpti::payload_t(fn.c_str(), m_source, index, + index % 80, (void *)(i % 10)); + xpti::trace_event_data_t *e = xptiMakeEvent( + fn.c_str(), &p, (uint16_t)xpti::trace_event_type_t::algorithm, + xpti::trace_activity_type_t::active, &m_instance_id); + if (e) { + uids[index] = e->unique_id; + payloads[index] = e->reserved.payload; + events[index] = e; + } + notify_count++; + } + + auto &row = t.add_row(run_no, "Serial"); + row[(int)NColumns::Threads] = num_threads; + + for (int i = tp_count; i < callback_count; ++i) { + int index = (int)i % tp_count; + void *addr = (void *)(index % 10); + std::string fn = "Function" + std::to_string(index); + xpti::payload_t p = xpti::payload_t(fn.c_str(), m_source, (int)index, + (int)index % 80, addr); + xpti::trace_event_data_t *e = xptiMakeEvent( + fn.c_str(), &p, (uint16_t)xpti::trace_event_type_t::algorithm, + xpti::trace_activity_type_t::active, &m_instance_id); + if (e && e->unique_id == uids[index]) { + uint8_t tp = (index % 10) + 1; + uint16_t tp_type = (uint16_t)(tp << 1); + xpti::framework::scoped_notify ev("xpti", tp_type, nullptr, e, + m_instance_id, nullptr); + notify_count++; + } + } + uint64_t acc = 0; + for (int i = 0; i < tp_count; ++i) { + acc += events[i]->instance_id; + } + + // Accumulator contains 'callback_count' number of + // instances that are invoked after creation, so + // each event has 101 instances * tp_count = 1010 + // + // total instances = callback_count + tp_count; + + row[(int)NColumns::Notifications] = (long double)acc; + row[(int)NColumns::PassRate] = (long double)(acc) / (notify_count)*100; + } else { + tbb::task_arena a(num_threads); + + a.execute([&]() { + std::atomic notify_count = {0}; + tbb::spin_mutex m_lock; + tbb::parallel_for( + tbb::blocked_range(0, tp_count), + [&](tbb::blocked_range &r) { + for (uint64_t i = r.begin(); i != r.end(); ++i) { + int index = (int)i; + std::string fn = "Function" + std::to_string(i); + xpti::payload_t p = + xpti::payload_t(fn.c_str(), m_source, (int)index, + (int)index % 80, (void *)(i % 10)); + xpti::trace_event_data_t *e = xptiMakeEvent( + fn.c_str(), &p, (uint16_t)xpti::trace_event_type_t::algorithm, + xpti::trace_activity_type_t::active, &m_instance_id); + if (e) { + uids[index] = e->unique_id; + payloads[index] = e->reserved.payload; + events[index] = e; + } + ++notify_count; + } + }); + + std::string row_title = "Threads " + std::to_string(num_threads); + auto &row = t.add_row(run_no, row_title); + row[(int)NColumns::Threads] = num_threads; + + tbb::parallel_for( + tbb::blocked_range(tp_count, callback_count), + [&](tbb::blocked_range &r) { + for (int i = r.begin(); i != r.end(); ++i) { + int index = (int)i % tp_count; + void *addr = (void *)(index % 10); + std::string fn = "Function" + std::to_string(index); + xpti::payload_t p = xpti::payload_t( + fn.c_str(), m_source, (int)index, (int)index % 80, addr); + xpti::trace_event_data_t *e = xptiMakeEvent( + fn.c_str(), &p, (uint16_t)xpti::trace_event_type_t::algorithm, + xpti::trace_activity_type_t::active, &m_instance_id); + if (e && e->unique_id == uids[index]) { + uint8_t tp = (index % 10) + 1; + uint16_t tp_type = (uint16_t)(tp << 1); + xpti::framework::scoped_notify ev("xpti", tp_type, nullptr, e, + m_instance_id, nullptr); + notify_count++; + } + } + }); + + uint64_t acc = 0; + for (int i = 0; i < tp_count; ++i) { + acc += events[i]->instance_id; + } + + row[(int)NColumns::Notifications] = (long double)acc; + row[(int)NColumns::PassRate] = (double)(acc) / (notify_count)*100; + }); + } +} + +void test_correctness::run_notification_tests() { + test::utils::table_model table; + + test::utils::titles_t columns{"Threads", "Notify", "Pass rate"}; + std::cout << std::setw(25) << "Notification Tests\n"; + table.set_headers(columns); + + uint8_t sid = xptiRegisterStream("xpti"); + // We do not need to register callback for correctness tests + + if (m_threads.size()) { + int run_no = 0; + for (auto thread : m_threads) { + run_notification_test_threads(run_no++, thread, table); + } + } + + table.print(); +} + +} // namespace semantic +} // namespace test \ No newline at end of file diff --git a/xptifw/doc/XPTI_Framework.md b/xptifw/doc/XPTI_Framework.md new file mode 100644 index 0000000000000..ad7069ebbce0f --- /dev/null +++ b/xptifw/doc/XPTI_Framework.md @@ -0,0 +1,665 @@ +# **XPTI Tracing Framework** + +- [**XPTI Tracing Framework**](#xpti-tracing-framework) + - [**Overview**](#overview) + - [**Architecture**](#architecture) + - [**The Dispatcher**](#the-dispatcher) + - [**The Subscriber**](#the-subscriber) + - [**Tracing Framework and Callback APIs**](#tracing-framework-and-callback-apis) + - [**Brief API Concepts**](#brief-api-concepts) + - [**`xptiInitialize`**](#xptiinitialize) + - [**`xptiFinalize`**](#xptifinalize) + - [**APIs and Data Structures Exported by the Tracing Framework**](#apis-and-data-structures-exported-by-the-tracing-framework) + +## **Overview** + +In order to understand different parts of an application or library, the +ability to capture information about the application or library is needed. +Using such information, one can create meaningful representations such as +call-graphs, execution trace views etc. XPTI tracing framework is once such +framework that allows developers to instrument their code with XPTI API and +forward interesting or useful events during the application's execution, as +determined by the developer. + +The XPTI tracing framework is a lightweight tracing framework that provides a +simple API for instrumenting code, which allows developers to capture +relationships through nodes and edges and track the execution of the +aforementioned nodes and other functions that may be of interest. The API also +provides the ability to associate each trace point with end-user source code +information such as source file, function name and line number, for example. +The goal of this framework is to provide a low overhead solution that tools +can use to build performance analytical models. This document describes the +different components of this framework and a testing methodology to determine +the cost of using this framework in your applications. + +In order to provide efficient and performant thread-safe behavior, the +framework relies on the concurrent containers in [Threading Building Blocks](github.com/intel/tbb). + +> **NOTE:** This document is best viewed with [Markdown Reader](https://chrome.google.com/webstore/detail/markdown-reader/gpoigdifkoadgajcincpilkjmejcaanc) +> plugin for Chrome or the [Markdown Preview Extension]() for Visual Studio Code. + +## **Architecture** + +The framework consists of a proxy library that is a static library for use +within your applications. However, the proxy library consists of stubs that +forward calls to a dynamic component, if available and if tracing has been +enabled. If tracing has not been enabled or the dynamic component not found at +run time, the stub implementations return immediately. + +The framework currently employs environment variables to determine if tracing +has been enabled and where to find the dynamic component. Both of these must be set for it to successfully dispatch event streams. + +1. The environment variable that indicates tracing has been enabled for the + run is defined by `XPTI_TRACE_ENABLE`. + + To enable tracing, the possible values are `XPTI_TRACE_ENABLE=1` or + `XPTI_TRACE_ENABLE=true` and to disable, the possible values are + `XPTI_TRACE_ENABLE=0` or `XPTI_TRACE_ENABLE=false`. + +2. The environment variable `XPTI_FRAMEWORK_DISPATCHER` points to the XPTI + dispatcher or the dynamic component that allows the static library to load + the shared object into memory and dispatch the event streams to subscribers + of the event streams. The dispatcher or the dynamic component manages the + subscribers to the event streams through an environment variable + `XPTI_SUBSCRIBERS`, but this variable has no bearing on the functionality + supported by the static library portion of the framework. + +![XPTI Architecture](xpti_arch.png) + +In the above diagram, the interactions between a sample application and library +that have been instrumented with XPTI API, the XPTI dispatcher and the +subscriber loaded by the dispatcher. All API calls made by the application and +the library go to the static library in this diagram and if `XPTI_TRACE_ENABLE` +is not enabled or if the path to the dispatcher is not provided in the +environment variable `XPTI_FRAMEWORK_DISPATCHER`, then the calls to the static +library return immediately. + +In the case both these variables are enabled, then the calls will be forwarded +to the dynamic library which will attempt to load the subscribers pointed to by +the environment variable `XPTI_SUBSCRIBERS`. The hypothetical trace data +captured by the subscriber is shown as well under the `Resulting trace data` +part of the diagram. + +### **The Dispatcher** + +The dispatcher is a dynamic library that implements the XPTI API and is +dynamically loaded by the static proxy library, if the static library is used to +link with the application or library. Linking with the dynamic library instead +of the static library is also an option, however the dynamic library will now +have to be shipped with the application. Using the static library allows +instrumented applications or libraries to get around this problem. + +### **The Subscriber** + +A subscriber in XPTI is a shared object that is dynamically loaded by the +dispatcher. Events generated by an instrumented application or library are +forwarded to the subscriber by the dispatcher through a callback mechanism. The +ownership of the subscriber is controlled by tools or applications that consume +the generated event streams and **must** follow the protocol or handshake +defined for an event stream. + +There are three important things that a subscriber must implement to be +functional: (1) `xptiTraceInit`, (2) `xptiTraceFinish` and (3) callback handlers. +The `xptiTraceInit` and `xptiTraceFinish` API calls are used by the dispatcher +loading the subscriber dynamically to determine if the subscriber is a valid +subscriber. If these entry points are not present, then the subscriber is not +loaded. + +The `xptiTraceInit` callback is called by the dispatcher when the generator of a +new stream of data makes a call to `xptiInitialize` for the new stream. The +implementation of the `xptiTraceInit` function is where the subscriber would +follow the specification or protocol defined for the stream to subscribe to +events from various trace point types. The code snippet below shows an example +of such an implementation for the stream `"foo"`. + +```cpp +#include "xpti_data_types.h" + +XPTI_CALLBACK_API void xptiTraceInit +( + unsigned int major_version, ///< Major version + unsigned int minor_version, ///< Minor version + const char *version_str, ///< Version as a string + const char *stream_name ///< Stream name +) { + if (stream_name) { + // Only register callbacks if the major version is the + // expected version and the stream is the stream type + // you care about + if(std::string("foo") != stream_name && + major_version > 0 && major_version < 3) + return; + g_stream_id = xptiRegisterStream(stream_name); + xptiRegisterCallback(g_stream_id, graph_create, graph_create); + xptiRegisterCallback(g_stream_id, node_create, node_create); + xptiRegisterCallback(g_stream_id, edge_create, edge_create); + xptiRegisterCallback(g_stream_id, region_begin, algorithm_begin); + xptiRegisterCallback(g_stream_id, region_end, algorithm_end); + xptiRegisterCallback(g_stream_id, task_begin, trace_point_begin); + xptiRegisterCallback(g_stream_id, task_end, trace_point_end); + ... + } + else { + // handle the case when a bad stream name has been provided + } +} + +XPTI_CALLBACK_API void xptiTraceFinish(const char *stream_name) { + // Free any dynamically allocated memory for the stream + // and any additional cleanup operation + ... +} +``` + +The above code snippet shows the `xptiTraceFinish` call as well and this +function is used to clean up memory and any other data structures that were +allocated to handle the stream. The `xptiTraceFinish` call is made by the +dispatcher when the instrumented code is winding down a data stream by calling + `xptiFinalize` for the stream. + +The implementation of the callbacks is where attention needs to be given to the +handshake protocol or specification for a given stream the subscriber wants to +attach to and consume the data. The instrumented library may send additional +user data during the notification of each trace point and this data could be of +a different type for each trace point notification. + +```cpp +XPTI_CALLBACK_API void trace_point_begin( + xpti_trace_point_type_t trace_type, + xpti_trace_event_data_t *parent, + xpti_trace_event_data_t *event, + uint64_t instance, + const void *user_data) +{ + /// Capture time here + my_time_t begin = get_my_time(); + // Capture thread id or cpu or whatever else + my_device_t dev = get_my_device_instance(); + + /// If access to the payload is required, it can be done like this. + auto p = xptiQueryPayload(event); + + // Send the data off to some serializing buffer asynchronously + emit_data(begin, dev,...); +} + +``` + +For example, the specification for a given event stream, the trace point type +`graph_create` might send a data structure of a certain type as user_data and +`task_begin` might send a character string. Resolving the `user_data` parameter +requires strict conformance to the specification for the stream. + +In addition to the `user_data`, the unique id that describes the event is +available under `event->unique_id`. For most cases, this should be sufficient to +resolve a given event. However, in many cases, a particular event may be +exercised within a loop. Since a trace point event is based on the +instrumentation at a specific location in the code, the `unique_id` of this will +always remain the same. However, with each instance of this event, and instance +ID may be emitted that keeps track of the instance of this event. The combined +value of the `unique_id` and `instance_id` should always be unique. + +> **NOTE:** A subscriber **must** implement the `xptiTraceInit` and +> `xptiTraceFinish` APIs for the dispatcher to successfully load the subscriber. + +> **NOTE:** The specification for a given event stream **must** be consulted +> before implementing the callback handlers for various trace types. + +## **Tracing Framework and Callback APIs** +The current version of the instrumentation API adopts a model where traces are +generated in pairs for a give trace scope and a scoped class is made available +that assists developers instrumenting their code. The APIs are divided into two parts: (1) the public API exported by the tracing framework which are +implemented by the static library and the dispatcher and (2) the callback API +that tools will implement to create a subscriber. + +### **Brief API Concepts** + +The XPTI framework exports a small set of API functions that are a part of the +static library and dispatcher exports and deemed sufficient for the uses-cases +that have been considered so far. Since the primary goal is to gather execution +traces of compute elements in an application, the APIs address this scope for +now. Currently, they allow developers to capture relationship information as +nodes and edges in a graph, where the nodes represent a compute element or an +action with a latency associated with it. The edges represent the dependencies +between the compute elements which be events or memory objects. In addition to +such relationship events, the API allows to you trace arbitrary regions of code +similar to conventional tracing. + +For each interesting trace point in an application, a notification can be sent +out by the framework. However, if there are no subscribers to consume this +notification event, the framework returns immediately. This allows developers +that want to instrument applications or run-times to limit the overheads +considerably. + +The API is documented in the file `xpti_trace_framework.h` that can be located +under `xpti/doc`. Some of the API functions and concepts that warrant additional +insight are discussed further. + +### **`xptiInitialize`** + +When instrumenting an application, developers can decide how many streams they +want to generate. In some cases, organizing trace data by streams may be +preferable. The `xptiInitialize` function facilitates the organization of data +streams by allowing each component that generates a stream of data to make a +call to `xptiInitialize`. The types of events captured by the instrumentation +and the protocol for the handshake between the trace generation for a given +stream must be defined for a given stream as a contract or specification. +This allows subscribers to rely on this specification to implement a tool that +can consume this data and do something useful with it. + +The `xptiIntialize` function reports to all the subscribers that a new stream of +data is about to be generated and the name of the stream along with some version +information of the stream is sent to the subscriber. + +The version information is primarily provided to ensure that subscribers to the +event stream can choose not to handle an event stream if it is an unsupported +version. The up-to-date documentation of the XPTI API is always maintained in +the `xpti_trace_framework.h` header file. The application that is instrumented +**must** attempt to send the initialization only once, but the subscriber must +be prepared to handle the case when multiple initialization callbacks occur for +a given stream. + +### **`xptiFinalize`** + +The application or library being instrumented to generate a stream of data must +attempt to finalize the stream by making this call. This allows the dispatcher +to notify all the subscribers that a stream is about to end. + + +### **APIs and Data Structures Exported by the Tracing Framework** + +We will begin our discussion by detailing the various public APIs that are +exported by the framework and when they are meant to be used. The framework API +is what will be used by developers instrumenting their code. The primary goal of +the API is to support the instrumentation of code that may or may not fall into +function boundaries. + +* First, the places in the code where instrumentation is warranted should be + identified. Each trace point is unique and will be associated with a + ```payload``` data structure that encapsulates: (1) a unique name, such as a + function or kernel name or something meaningful if the trace point marks a + section of code, (2) the source file it is located in, (3) the line number + where this interesting event occurs and (4) the column number of the + interesting event. Compilers such as `gcc`, `clang` and the Intel compilers + can generate all of this information easily through builtin functions. +* Secondly, an event must be created for this trace point region and this + process of creating an event will use the ```payload``` information and create + an event. If the payload has already been registered, then the previously + registered and associated event will be returned. This process will also + create a ```unique_id``` for the event. +* Thirdly, the scope of this trace point must be determined and for a given + scope, as in a related pair of events, the ```unique_id``` created at the + begin trace point **must** be preserved and used for the end trace point as + well. +* Finally, the callbacks registered for these types of events must be notified. + +### **Trace Point Event** + +The trace point event describes the event used to notify the subscriber and is +usually associated with a payload that describes the event. Since application +code is being instrumented with XPTI, the payload may consist of a `function` +`name`, `source file name` and `line number`, which forms a unique combination +of strings and numbers that is used to create the `unique_id` associated with an +event. Using the `event` or the `unique_id`, one should be able to query the +`payload` information. When a notification occurs for a trace point, the trace +point event and trace point type information is sent to the subscriber. A given +event may be used to notify subscribers as multiple trace point types. For +example, a node may represent a computational entity and an event created for +the node may be emitted as `node_create`, `task_begin` and `task_end` +notifications to record the creation, the beginning of the execution of an +instance of the node and when the execution of that instance has completed. + +#### **Creating the Payload** + +We will first look at the ```xpti::trace_point_payload_t``` data structure that +is defined in `xpti_data_types.h`. + +```cpp + +xpti::payload_t p("function1", "main.cpp", 104, 5, function1); + +``` + +The payload data structure can be created with a set of unique descriptors for +the region of code being instrumented, such as a function name, source file name +and line number, for example. However, it can also take in a function name and +pointer to the function or just a pointer to the function that uniquely +describes the payload that will be used to create a trace point event. This +information is used by the `xptiMakeEvent` function to create a `unique_id` for +the trace point event. + +The next section looks at using the payload information to create a trace point +event. Each trace point is unique, from a language or code section standpoint. A +trace point maybe visited multiple times, but the payload and the event +describing the trace point will always be the same. The tracing framework must +guarantee that when a trace point is visited, the same ```unique_id``` is +retrieved for it. For frequent visits to the same trace point site, we must be +able to look up the `unique_id` of the payload efficiently or we cache the +information at the trace point location. + +#### **Creating an Event that Represents the Trace Point** + +Once a payload structure has been created, it is used to associate the trace +point that this payload represents to an event that captures additional +information about the trace point. The framework has a list of predefined trace +point types that may be used to mark various trace points with an appropriate +type. They are declared in the header file `xpti_data_types.h` and are used to +describe the creation of a graph, node, edges or the instantiation of a node as +`task_begin` and `task_end` pair of trace point notifications. +These trace points represent the types of actions commonly associated with +instrumenting a library or application. However, in cases where there is no +direct mapping from the predefined trace point types to a language structure or +if they need to describe an action that is orthogonal to code structure such as +diagnostic information for executing code, the framework allows each +instrumentation stream to extend the available trace point types with new trace +point types that map to these unsupported constructs. + +The basic pre-defined types may be extended as follows: + +```cpp +typedef enum { + my_read_begin = XPTI_TRACE_POINT_BEGIN(0), + my_read_end = XPTI_TRACE_POINT_END(0), + my_allocate_begin = XPTI_TRACE_POINT_BEGIN(1), + my_allocate_end = XPTI_TRACE_POINT_END(1) +}tp_extension_t; +... +uint16_t tp1_start = xptiRegisterUserDefinedTracePoint("myTest", + my_read_begin); +uint16_t tp1_end = xptiRegisterUserDefinedTracePoint("myTest", + my_read_end); +uint16_t tp2_start = xptiRegisterUserDefinedTracePoint("myTest", + my_allocate_begin); +uint16_t tp2_end = xptiRegisterUserDefinedTracePoint("myTest", + my_allocate_end); +... +xptiNotifySubscribers(stream_id, tp1_start, parent, event, instance, + nullptr); +``` + +If the callback handler for this stream needs to know if this is an extension or +a predefined type, they can use the following macros to decipher the trace point +type. + +```cpp +uint8_t tool_vendor_id = XPTI_TOOL_ID(tp1_start); +uint8_t tp_type = XPTI_EXTRACT_USER_DEFINED_ID(tp1_start); + +if(tool_vendor_id == 0) { + // Default pre-defined type +} +else { + // User-defined trace type + // Here: tp_type will be tp_extension_t::my_read_begin +} +``` + +This mechanism allows different kinds of information to be captured and the +trace point type describes the type of information expected by the notification. +The trace point type is only used when notifying the subscribers of an event +with the trace point type acting as a qualifier for the event. + +In a similar manner, the `xpti::trace_event_type_t` can also be extended. +The events that are predefined by the framework fall under `{graph, algorithm,` +`barrier, scheduler, async, lock, offload_read, offload_write, user_defined}`. +If a particular library or application needs to extend these event types, the +framework provides APIs to extend this set to meet the need of the specific +tracing activity using the `xptiRegisterUserDefinedEventType` API function. + +The code sample below shows a sample code snippet that creates such an trace +point event using a payload and uses the created event to notify all subscribers +of the event qualified by a trace point type. + +```cpp +if ( xptiTraceEnabled() ) { + // example + auto stream_id = xptiRegisterStream("myStream"); + xptiInitialize("myStream", 1, 0, "myStream 1.0"); + xpti::payload_t p("application_graph"); + auto event = xptiMakeEvent( "app", &p, + xpti::trace_event_type_t::graph, + xpti::trace_activity_type_t::active); +} +... +if (event && xptiTraceEnabled()) { + // If the event has been created, then notify + // all subscribers about the graph creation + xptiNotifySubscribers(stream_id, + xpti::trace_point_type_t::graph_create, + nullptr, // no parent + event, + nullptr // no user data); +} + +``` + +#### **Notifying the registered listeners** + +The code example below shows an example 'C' code that is instrumented with the +framework API and this will generate traces for the functions in the program. +However, in this example, we use the helper scoped class provided by the +framework to emit notifications for `xpti::trace_point_type_t::task_begin` and +`xpti::trace_point_type_t::task_begin` automatically. + +```cpp +void function1() { + uint64_t instance_id = 0; + xpt::trace_event_data_t event; + if (xptiTraceEnabled()) { + xpti::payload_t p("function1","main.cpp",104, 2,function1); + event = xptiMakeEvent("function1",&p, xpti::trace_event_type_t::algorithm, + xpti::trace_activity_type_t::active, &instance_id); + } + xpti::framework::scoped_notify ev("myStream", + xpti::trace_point_type_t::region_begin, nullptr, &event,instance_id); + for(int i = 0; i < 5; ++i ) { + function2(); + } +} +``` + +## **Performance of the Framework** + +In order to estimate the overheads one could experience by using the framework, +this section will outline a couple of scenarios. Some of the key operations +that would result in overheads are listed below. For each of these operations, +we will construct scenarios that will provide us with measurements to determine +how many events/sec we can process before the overheads start to become a +problem. We will use an overhead limit of 1% as this data will be used to build an analytical model in the future. + +| Data structure | Operation | Description | +| -------------- | ------------- |------------ | +| String table | String insert | Every string that is exposed to the framework is registered with the string table and only the string IDs used | +|| String lookup | The lookup of a string in the string table can be implicit or explicit. For example, when a previously defined payload is submitted again, the strings present will be registered. However, there is an early exit if the string already exists in the table due to an implicit lookup. | +| Payload| Creation| A payload constructor may take in `function name`, `source file name`, line number, column number and an address or a `function name` and an address or just an address. Whenever strings are presented, they are first registered with the string table and the string IDs are used in the creation of a hash that is used to generate a `unique_id` for the event. | +| Trace point event | Metadata add| One can add arbitrary number of pairs to an event as strings. These strings will be registered with the string table and the metadata is maintained as a map of two string IDs. | +|| Metadata lookup | The metadata associated with a trace point event is looked up explicitly in the string table. | +|| Creation | Includes string table registration for the strings contained in the payload, the hash generation for a payload and insertion of the payload and event into a map.| +|| Framework lookup | If a trace point has already been created, the event can be looked up using its `unique_id`. | +|| Notification | Events that are created are notified to all subscribers| +|Subscriber | Callback handler | When a notification is received by the subscriber, the event is handled. The callback handlers must be efficient to minimize the overheads.| + +Using the operations described in the table above, a set of tests are designed +to evaluate the performance of these operations in near real-world scenarios. +The tests are present under `xptifw/basic_test` and report these results in +tabular form. The performance test in particular accepts various configuration +parameters from the command line. An example command is shown in the section +below. + +```bash +run_test --trace-points 1000 --type performance --overhead 1.5 --num-threads 0,1,2,3 --test-id 1,2 --tp-frequency 50 +``` + +The above command will run the test for 1000 trace points and compile the +performance measurements for the set of threads given as input with a maximum +overhead at 1.5% and for every trace point created, it will be visited twice. +A description of the command line arguments is provided in detail below: + +> **--type, -y [`required`]** +> - This flag takes in the type of tests that need to be run. The allowed options are **[semantic, performance]**. +> - **semantic**: Runs semantic tests that test the correctness of the framework operations and they are split into three separate tests. +> 1. Performs string table tests on a 1000 strings +> 2. Performs tests on trace point events by checking to see if the same event is returned for the same payload and so on. +> 3. Performs notification tests to see if trace events are notified correctly and record the instances of notifications per events. +> - **performance**: Runs performance tests on the provided input configuration and these tests measure the cost of various operations used in the framework. These tests are split into two separate tests. +> 1. Data structure tests that capture the average cost of string table inserts, lookups, trace point event creation and lookup using the same payload or `unique_id` for the event and notification. +> 2. Runs instrumentation tests and projects the number of events that can be serviced per second using the configuration provided on the command line. These tests are where the **--overhead** and **--tp-frequency** arguments are used. + +> **--trace-points, -t [`required`]** +> - Number of trace point events to create and use for the test. The expected range is **[10-100000]**. + +> **--test-id, -i [`required`]** +> - Takes in a list of tests that are comma separated and runs the requested tests. This command line argument takes in a range as well and the format is described below: +> 1. Comma separated sequence: --test-id 1,2,3 +> 2. Range description: --test-id 1:3:1 + +> **--num-threads, -n [`required`]** +> - Takes in a list of thread counts to use for the requested tests. +> 1. Comma separated sequence: --num-threads 0,1,2,4,8,12,16 +> 2. Range description: --num-threads 0:2:1,4:16:4 + +> **--tp-frequency, --f** +> - The trace point creation frequency basically allows the test to determine the total number of trace point visits to perform for every trace point event that is created. If the trace point creation frequency is 10%, then every trace point event that is created must be visited 10 times. Since we know how many trace point events were requested from the command line (**--trace-points N**), we multiply this value (N) by 100/f where f = trace point frequency in percent to get the total number of trace point visits. +> - So, if number of trace points is 5000 and trace point frequency is 10%, the total number of trace point visits the test is going to perform is 5000 x 1/0.1 = 50000 + +>**--overhead** +> - The overhead input allows the test framework to use the measured performance of the trace point creation and notification to come up with an estimate of how many events can be serviced per second with the given configuration. +> - The default overheads for which the events/sec are computed is **1%** +> - If the overheads desired is 1%, then the following formula is used to compute the events/sec: +>

total cost of instrumentation (I) = (cost of trace point creation + cost of notification)

+>

So, if --trace-points 5000 --tp-frequency 10, this will be:

+>

I = 5000xCost(TP Create) + 50000xCost(Notify)

+>

Average cost (A) = I/50000, for 50000 events notified

+>

This cost A does not take into account the cost of the callback handler. In our projections, we use a handler cost of 10ns, 100ns and 500ns to get the events/sec that can be serviced. On an average, the handler costs for real-world cases will be somewhere between 80ns-400ns. +>

So, if the average cost is A and this is 1% overhead, the total run time must be 100xA ns

+>

Events/second E = 1000,000,000 ns/(100xA)ns

+> + +Using the metrics described above, we run the tests with varying overheads and +trace point creation frequencies to determine the maximum number of events +that can be serviced for that configuration. Some sample configurations are shown below: + +- Configuration where each trace point event created is only visited **once** + ```bash + run_test --trace-points 5000 --type performance --num-threads 0,1,2,4 --test-id 1,2 --tp-frequency 100 + ``` +- Configuration where each trace point event is visited **twice** + ```bash + run_test --trace-points 5000 --type performance --num-threads 0,1,2,4 --test-id 1,2 --tp-frequency 50 + ``` +- Configuration where each trace point event is visited **ten** times + ```bash + run_test --trace-points 5000 --type performance --num-threads 0,1,2,4 --test-id 1,2 --tp-frequency 10 + ``` + + +## **Modeling and projection** + +In order to determine the number of events that the framework can service in a +second, the performance tests use the following approach. If the total instrumentation cost is 1µs and for this cost to be under 1% total overhead, the amount of work that needs to be accomplished for every trace event would be 1µs x 100 = 100µs. In this case, the maximum number of events that can be notified/serviced would be: + + 1 sec/100µs = 1000000µs/100µs = 10000 events/sec + +The total instrumentation cost would include *some of the time in the infrastructure in the framework* and the *cost of handling each notification through callbacks* in the subscriber. + +### **Computing the cost incurred in the framework** + +On an average, some trace points are visited only once and others 10s-100s of +times. We assume that each trace point created will be visited at least 10 +times. The command line arguments that would test such a configuration is +shown below. + + ```bash + run_test --trace-points 10000 --type performance --num-threads 0,1,2,4 --test-id 1,2 --tp-frequency 10 + +We take average cost of a trace point event creation and the cost of 10 notifications for each such event as it is visited 10 times to form the basis of the cost incurred within the framework. This information is reported by the performance test. The total instrumentation cost as discussed in the previous section comprises of a framework cost and a callback handler cost in the subscriber. + + Framework cost **FW*****cost*** = Avg{TP*create* + 10 x TP*notify*} + + Subscriber cost **Callback*****cost*** = **C*t*** which could be anywhere in the range [10-10000]ns + + Total cost **Cost*****total*** = **FW*****cost*** + **C*t*** + +Using the information from the report or one such instance captured in the table above, we know that: + +**FW*****cost*** = ~55ns + +Using different values for **C*t*** = [10, 100, 500, 1000]ns, we get the table that shows the events/sec that can be serviced for total instrumentation cost for the configuration. It can be noticed that as the callback handler costs increase, the events/sec is inversely proportional to the callback handler costs. The work unit cost for determining the number of events/sec is given by: + +**W*****cost*** = **100** x [**FW*****cost*** + **C*t***] for the configuration that limits overheads to 1%. + +The more times a trace point event is visited, the more events per second can +be serviced by the framework as the cost of a trace point event creation can +be amortized over all the visits to the same trace point. However, +**C*t*** will eventually limit the events/sec when they get to be +significantly larger than **FW*****cost***. + +> **NOTE:** All measurements reported in this document were measured on an NUC +form-factor machine with Intel® Core™ i7-8559U @ 2.7 GHz processor +running Ubuntu 18.04. + + +| Operation | Statistic | Scenario |Count| Framework Cost(ns) | +|-----------|-----------|----------|-----|------| +| String table insertion| Cost/insertion| Create a large number of strings (>1000) and insert them into the table. Measure the average cost of multi-threaded insertion.|10000|~**250**ns| +|String table lookup| Cost/lookup| Look up the strings added in the insertion test in random order and measure the average cost of the lookup.|20000| ~**40**ns| +|String table insert/lookup| Cost of insert/lookup | Strings that are added to the string table may be looked up multiple times. On an average, we assume that ever string added to the string table is looked up twice. If strings are looked up more often than the twice assumed in this test, then the average cost of insertion/lookup will be lower.|30000|~**130**ns| +| Trace point creation | Cost/creation| Create unique trace points and measure the average cost for each creation. |10000|~**1100**ns| +| Trace point creation | Cost/creation| Attempt to create previously created trace points again and measure the average cost for each creation. Since the payloads will be the same, there should be an early exit after it is determined that they are the same.|100000|~**275**ns| +| Trace point lookup using `unique_id`(FW lookup)| Cost/lookup| This time will purely be the cost of lookup of finding the trace point event, given its `unique_id`. |100000|~**35**ns| +|Trace point event caching| Cost/lookup| If the trace point event is cached at the event site, then this cost is 0. This is the most efficient mode of using it and amortizes the cost od trace point event creation the best.|100000|~**0**ns| +| Trace event notification| Cost/notification| Measure the average cost of notification. Here the the callback handler registered will return immediately. The callback handler overheads are modeled separately when the maximum number of events that can be serviced per sec are computed.|100000|~**10**ns| +|Trace event composite cost [**FW*****cost***]|Average cost/trace point| Create N unique trace points and MxN trace point lookups + MxN notifications. Measure the total time and get the average using MxN as the denominator.|100000|~**55**ns| + +> **NOTE:** The trace point, as implemented in the code block in the previous +> section can lead to significant runtime overheads as the framework has to +> lookup the `unique_id` of the payload provided with the trace point and this +> lookup can be costly. It is recommended that events created for each trace +> point are cached locally for subsequent use. An example implementation of +> this optimization is is shown in the code block below using a static +> variable, for example. If the instrumentation is a part of a class, then the +> event can be saved as a member variable. + +```c++ +void function1() { + uint64_t instance_id = 0; + static xpti::trace_event_data_t *f1_event = nullptr; + // Only create the event if it hasn't already been created. + // When the data structure f1_event is initialized, the + // unique id is set to invalid_id. + if (xptiTraceEnabled() && !f1_event) { + xpti::payload_t p("function1","main.cpp",104, 2,function1); + f1_event = xptiMakeEvent("function1", &p, + xpti::trace_event_type_t::algorithm, + xpti::trace_activity_type_t::active, + &instance_id); + } + xpti::framework::scoped_notify ev("myStream", + xpti::trace_point_type_t::region_begin, + nullptr, f1_event, instance_id); + for(int i = 0; i < 5; ++i ) { + function2(); + } +} +``` + +> **NOTE:** Using the framework cost, **FW*****cost*** = ~55ns, and +> a set of callback handler costs, we get the following table that shows the +> events/sec that can be handled by XPTI for a given configuration. + +| Trace points | Threads | Overhead|Events/sec @**C*t***=10ns | Events/sec @ **C*t***=100ns | Events/sec @ **C*t***=500ns| Events/sec @ **C*t***=1000 +| ------------- | -------- | ------- | ------------ |------| -----|---| +|10000 | Serial | 1% | ~150K | ~64K | ~18K | ~9.5K| +| | 4 | 1% | ~150K | ~64K | ~18K |~9.5K| +| | Serial | 2% | ~300K | ~127K | ~36K|~19K| +| | 4 | 2% | ~290K | ~125K | ~36K|~19K| +|1000 | Serial | 1% | ~165K | ~66K | ~18K | ~9.5K| +| | 4 | 1% | ~165K | ~66K | ~18K |~9.5K| +| | Serial | 2% | ~360K | ~137K | ~37K|~19K| +| | 4 | 2% | ~345K | ~135K | ~37K|~19K| + +The above data from the table is only provided to provide a guideline on what +to expect with the instrumentation. The events/second can be quite high if the +callback handlers are written efficiently. So the range of events that can be +serviced can be ~300,000 to 10000 per second depending on the cost of handling +the callbacks. diff --git a/xptifw/doc/xpti_arch.png b/xptifw/doc/xpti_arch.png new file mode 100644 index 0000000000000000000000000000000000000000..9855a33b4b6ba06f1c7cfe4b2f86be3fc54c3027 GIT binary patch literal 87322 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 ~n(oa(6|~lqzKyPm!3AfEI-R##;m7 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 literal 0 HcmV?d00001 diff --git a/xptifw/include/xpti_int64_hash_table.hpp b/xptifw/include/xpti_int64_hash_table.hpp new file mode 100644 index 0000000000000..58a5df5432af8 --- /dev/null +++ b/xptifw/include/xpti_int64_hash_table.hpp @@ -0,0 +1,148 @@ +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +#pragma once +#include "xpti_data_types.h" + +#include + +#ifdef XPTI_STATISTICS +#include +#endif + +#include +#include + +namespace xpti { +/// @brief A class for mapping one 64-bit value to another 64-bit value +/// @details With each payload, a kernel/function name and the +/// source file name may be passed and we need to ensure that the +/// payload can be cached in a hash map that maps a unique value +/// from the payload to a universal ID. We could use the payload +/// hash for this purpose, but the numbers are non -monotonic and +/// can be harder to debug. +/// +class Hash64x64Table { +public: + typedef tbb::concurrent_hash_map ht_lut_t; + + Hash64x64Table(int size = 1024) + : m_forward(size), m_reverse(size), m_table_size(size) { +#ifdef XPTI_STATISTICS + m_insert = 0; + m_lookup = 0; +#endif + } + + ~Hash64x64Table() { + m_forward.clear(); + m_reverse.clear(); + } + + // Clear all the contents of this hash table and get it ready for re-use + void clear() { + m_forward.clear(); + m_reverse.clear(); + m_forward.rehash(m_table_size); + m_reverse.rehash(m_table_size); +#ifdef XPTI_STATISTICS + m_insert = 0; + m_lookup = 0; +#endif + } + + // Check to see if a particular key is already present in the table; + // + // On success, the value for the key will be returned. If not, + // xpti::invalid_id will be returned. + int64_t find(int64_t key) { + // Try to read it, if already present + ht_lut_t::const_accessor e; + if (m_forward.find(e, key)) { +#ifdef XPTI_STATISTICS + m_lookup++; +#endif + return e->second; // We found it, so we return the value + } else + return xpti::invalid_id; + } + + // Add a pair to the hash table. If the key already exists, this + // call returns even if the value happens to be different this time. + // + // If the key does not exists, then the key is inserted into the hash map and + // the reverse lookup populated with the pair. + void add(int64_t key, int64_t value) { + // Try to read it, if already present + ht_lut_t::const_accessor e; + if (m_forward.find(e, key)) { +#ifdef XPTI_STATISTICS + m_lookup++; +#endif + } else { // Multiple threads could fall through here + // Release the reader lock held; + e.release(); + { + // Employ a double-check pattern here + tbb::spin_mutex::scoped_lock dc(m_mutex); + ht_lut_t::accessor f; + if (m_forward.insert(f, key)) { + // The key does not exist, so we will add the key-value pair to the + // hash map + f->second = value; +#ifdef XPTI_STATISTICS + m_insert++; +#endif + // When we insert a new entry into the table, we also need to build + // the reverse lookup; + { + ht_lut_t::accessor r; + if (m_reverse.insert(r, value)) { + // An entry does not exist, so we will add it to the reverse + // lookup. + r->second = key; + f.release(); + r.release(); + } + } + } + // else, we do not add the key-value pair as the key already exists in + // the table! + } + } + } + + // The reverse query allows one to get the value from the key that may have + // been cached somewhere. + int64_t reverseFind(int64_t value) { + ht_lut_t::const_accessor e; + if (m_reverse.find(e, value)) { +#ifdef XPTI_STATISTICS + m_lookup++; +#endif + return e->second; + } else + return xpti::invalid_id; + } + + void printStatistics() { +#ifdef XPTI_STATISTICS + printf("Hash table inserts : [%llu]\n", m_insert.load()); + printf("Hash table lookups : [%llu]\n", m_lookup.load()); +#endif + } + +private: + ht_lut_t m_forward; ///< Forward lookup hash map + ht_lut_t m_reverse; ///< Reverse lookup hash map + int32_t m_table_size; ///< Initial size of the hash map + tbb::spin_mutex + m_mutex; ///< Mutex required to implement a double-check pattern +#ifdef XPTI_STATISTICS + safe_uint64_t m_insert, ///< Thread-safe tracking of insertions + m_lookup; ///< Thread-safe tracking of lookups +#endif +}; +} // namespace xpti \ No newline at end of file diff --git a/xptifw/include/xpti_string_table.hpp b/xptifw/include/xpti_string_table.hpp new file mode 100644 index 0000000000000..aa2c02f95b851 --- /dev/null +++ b/xptifw/include/xpti_string_table.hpp @@ -0,0 +1,180 @@ +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +#pragma once +#include "xpti_data_types.h" + +#include +#include + +#ifdef XPTI_STATISTICS +#include +#endif + +#include +#include + +namespace xpti { +/// @brief A string table class to support the payload handling +/// @details With each payload, a kernel/function name and the source file name +/// may be passed and we need to ensure that the incoming strings are copied and +/// represented in a string table as the incoming strings are guaranteed to be +/// valid only for the duration of the call that handles the payload. +class StringTable { +public: + using st_forward_t = tbb::concurrent_hash_map; + using st_reverse_t = tbb::concurrent_hash_map; + + StringTable(int size = 4096) + : m_str2id(size), m_id2str(size), m_table_size(size) { + m_ids = 1; +#ifdef XPTI_STATISTICS + m_insert = 0; + m_lookup = 0; +#endif + } + + // Clear all the contents of this string table and get it ready for re-use + void clear() { + m_ids = {1}; + m_id2str.clear(); + m_str2id.clear(); + + m_id2str.rehash(m_table_size); + m_str2id.rehash(m_table_size); +#ifdef XPTI_STATISTICS + m_insert = 0; + m_lookup = 0; +#endif + } + + // If the string being added to the string table is empty or invalid, then + // the returned string id = invalid_id; + // + // On success, the string will be inserted into two tables - one that maps + // string to string ID and another that maps from string ID to string. If a + // reference string pointer is made available, then the address of the string + // in the string table is returned through the default argument + xpti::string_id_t add(const char *str, const char **ref_str = nullptr) { + if (!str) + return xpti::invalid_id; + + std::string local_str = str; + return add(local_str, ref_str); + } + + xpti::string_id_t add(std::string str, const char **ref_str = nullptr) { + if (str.empty()) + return xpti::invalid_id; + + // Try to see if the string is already + // present in the string table + // + st_forward_t::const_accessor e; + if (m_str2id.find(e, str)) { +#ifdef XPTI_STATISTICS + m_lookup++; +#endif + if (ref_str) + *ref_str = e->first.c_str(); + + // We found it, so we return the string ID + return e->second; + } else { + // Multiple threads could fall through here + // Release the reader lock held + e.release(); + string_id_t id; + { + // Employ a double-check pattern here + tbb::spin_mutex::scoped_lock dc(m_mutex); + st_forward_t::accessor f; + if (m_str2id.insert(f, str)) { + // If the string does not exist, then insert() returns true. Here we + // create an ID for it + id = m_ids++; + f->second = id; +#ifdef XPTI_STATISTICS + m_insert++; +#endif + // When we insert a new entry into the table, we also need to build + // the reverse lookup; + { + st_reverse_t::accessor r; + if (m_id2str.insert(r, id)) { + // An entry does not exist, so we will add it to the reverse + // lookup. + r->second = f->first.c_str(); + // Cache the saved string address and send it to the caller + if (ref_str) + *ref_str = r->second; + f.release(); + r.release(); + m_strings++; + return id; + } else { + // We cannot have a case where a string is not present in the + // forward lookup and present in the reverse lookup + m_str2id.erase(f); + if (ref_str) + *ref_str = nullptr; + + return xpti::invalid_id; + } + } + + } else { + // The string has already been added, so we return the stored ID + id = f->second; +#ifdef XPTI_STATISTICS + m_lookup++; +#endif + if (ref_str) + *ref_str = f->first.c_str(); + return id; + } + // Both the accessor and m_mutex will be released here! + } + } + return xpti::invalid_id; + } + + // The reverse query allows one to get the string from the string_id_t that + // may have been cached somewhere. + const char *query(xpti::string_id_t id) { + st_reverse_t::const_accessor e; + if (m_id2str.find(e, id)) { +#ifdef XPTI_STATISTICS + m_lookup++; +#endif + return e->second; + } else + return nullptr; + } + + int32_t count() { return (int32_t)m_strings; } + + const st_reverse_t &table() { return m_id2str; } + + void printStatistics() { +#ifdef XPTI_STATISTICS + printf("String table inserts: [%llu]\n", m_insert.load()); + printf("String table lookups: [%llu]\n", m_lookup.load()); +#endif + } + +private: + safe_int32_t m_ids; ///< Thread-safe ID generator + st_forward_t m_str2id; ///< Forward lookup hash map + st_reverse_t m_id2str; ///< Reverse lookup hash map + int32_t m_table_size; ///< Initial table size of the hash-map + tbb::spin_mutex m_mutex; ///< Mutex required for double-check pattern + safe_int32_t m_strings; ///< The count of strings in the table +#ifdef XPTI_STATISTICS + safe_uint64_t m_insert, ///< Thread-safe tracking of insertions + m_lookup; ///< Thread-safe tracking of lookups +#endif +}; +} // namespace xpti diff --git a/xptifw/samples/basic_collector/CMakeLists.txt b/xptifw/samples/basic_collector/CMakeLists.txt new file mode 100644 index 0000000000000..f164a06ad6ae2 --- /dev/null +++ b/xptifw/samples/basic_collector/CMakeLists.txt @@ -0,0 +1,24 @@ +cmake_minimum_required(VERSION 2.8.9) +project (basic_collector) + +file(GLOB SOURCES *.cpp) +include_directories(${XPTIFW_DIR}/include) +include_directories(${XPTI_DIR}/include) +include_directories(${XPTIFW_DIR}/samples/include) + +remove_definitions(-DXPTI_STATIC_LIBRARY) +add_definitions(-DXPTI_API_EXPORTS) +add_library(basic_collector SHARED ${SOURCES}) +add_dependencies(basic_collector xptifw) +if (MSVC) + target_link_libraries(basic_collector + PRIVATE xptifw + ) +else() + target_link_libraries(basic_collector + PRIVATE xptifw + PRIVATE dl + ) +endif() +# Set the location of the library installation +install(TARGETS basic_collector DESTINATION ${CMAKE_BINARY_DIR}) diff --git a/xptifw/samples/basic_collector/README.md b/xptifw/samples/basic_collector/README.md new file mode 100644 index 0000000000000..2040db580b640 --- /dev/null +++ b/xptifw/samples/basic_collector/README.md @@ -0,0 +1,26 @@ +# Basic collector + +The basic collector demonstrates the creation of a subscriber and prints of the +data received from various streams. In order to obtain the data from an application instrumented with XPTI, the following steps must be performed. + +1. Set the environment variable that indicates that tracing has been enabled. + + This is defined by the variable `XPTI_TRACE_ENABLE`. The possible + values taken by this environment variable are: + + To enable: `XPTI_TRACE_ENABLE=1` or `XPTI_TRACE_ENABLE=true` + + To disable: `XPTI_TRACE_ENABLE=0` or `XPTI_TRACE_ENABLE=false` + +2. Set the environment variable that points to the XPTI framework dispatcher so + the stub library can dynamically load it and dispatch the calls to the + dispatcher. + `XPTI_FRAMEWORK_DISPATCHER=/path/to/libxptifw.[so,dll,dylib]` + +3. Set the environment variable that points to the subscriber, which in this + case is `libbasic_collector.[so,dll,dylib]`. + + `XPTI_SUBSCRIBERS=/path/to/libbasic_collector.[so,dll,dylib]` + +For more detail on the framework, the tests that are provided and their usage, +please consult the [XPTI Framework library documentation](doc/XPTI_Framework.md). diff --git a/xptifw/samples/basic_collector/basic_collector.cpp b/xptifw/samples/basic_collector/basic_collector.cpp new file mode 100644 index 0000000000000..db1d40e9434f8 --- /dev/null +++ b/xptifw/samples/basic_collector/basic_collector.cpp @@ -0,0 +1,204 @@ +// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// +#include +#include +#include +#include +#include +#include +#include +#include + +#include "xpti_timers.hpp" +#include "xpti_trace_framework.h" + +static uint8_t g_stream_id = 0; +std::mutex g_io_mutex; +xpti::thread_id g_tid; + +static const char *tp_types[] = { + "unknown", "graph_create", "node_create", "edge_create", + "region_", "task_", "barrier_", "lock_", + "signal ", "transfer_", "thread_", "wait_", + 0}; + +// The lone callback function we are going to use to demonstrate how to attach +// the collector to the running executable +XPTI_CALLBACK_API void tp_callback(uint16_t trace_type, + xpti::trace_event_data_t *parent, + xpti::trace_event_data_t *event, + uint64_t instance, const void *user_data); + +// Based on the documentation, every subscriber MUST implement the +// xptiTraceInit() and xptiTraceFinish() APIs for their subscriber collector to +// be loaded successfully. +XPTI_CALLBACK_API void xptiTraceInit(unsigned int major_version, + unsigned int minor_version, + const char *version_str, + const char *stream_name) { + // The basic collector will take in streams from anyone as we are just + // printing out the stream data + if (stream_name) { + char *tstr; + // Register this stream to get the stream ID; This stream may already have + // been registered by the framework and will return the previously + // registered stream ID + g_stream_id = xptiRegisterStream(stream_name); + xpti::string_id_t dev_id = xptiRegisterString("sycl_device", &tstr); + + // Register our lone callback to all pre-defined trace point types + xptiRegisterCallback(g_stream_id, + (uint16_t)xpti::trace_point_type_t::graph_create, + tp_callback); + xptiRegisterCallback(g_stream_id, + (uint16_t)xpti::trace_point_type_t::node_create, + tp_callback); + xptiRegisterCallback(g_stream_id, + (uint16_t)xpti::trace_point_type_t::edge_create, + tp_callback); + xptiRegisterCallback(g_stream_id, + (uint16_t)xpti::trace_point_type_t::region_begin, + tp_callback); + xptiRegisterCallback(g_stream_id, + (uint16_t)xpti::trace_point_type_t::region_end, + tp_callback); + xptiRegisterCallback(g_stream_id, + (uint16_t)xpti::trace_point_type_t::task_begin, + tp_callback); + xptiRegisterCallback( + g_stream_id, (uint16_t)xpti::trace_point_type_t::task_end, tp_callback); + xptiRegisterCallback(g_stream_id, + (uint16_t)xpti::trace_point_type_t::barrier_begin, + tp_callback); + xptiRegisterCallback(g_stream_id, + (uint16_t)xpti::trace_point_type_t::barrier_end, + tp_callback); + xptiRegisterCallback(g_stream_id, + (uint16_t)xpti::trace_point_type_t::lock_begin, + tp_callback); + xptiRegisterCallback( + g_stream_id, (uint16_t)xpti::trace_point_type_t::lock_end, tp_callback); + xptiRegisterCallback(g_stream_id, + (uint16_t)xpti::trace_point_type_t::transfer_begin, + tp_callback); + xptiRegisterCallback(g_stream_id, + (uint16_t)xpti::trace_point_type_t::transfer_end, + tp_callback); + xptiRegisterCallback(g_stream_id, + (uint16_t)xpti::trace_point_type_t::thread_begin, + tp_callback); + xptiRegisterCallback(g_stream_id, + (uint16_t)xpti::trace_point_type_t::thread_end, + tp_callback); + xptiRegisterCallback(g_stream_id, + (uint16_t)xpti::trace_point_type_t::wait_begin, + tp_callback); + xptiRegisterCallback( + g_stream_id, (uint16_t)xpti::trace_point_type_t::wait_end, tp_callback); + xptiRegisterCallback( + g_stream_id, (uint16_t)xpti::trace_point_type_t::signal, tp_callback); + printf("Registered all callbacks\n"); + } else { + // handle the case when a bad stream name has been provided + std::cerr << "Invalid stream - no callbacks registered!\n"; + } +} + +// +std::string truncate(std::string name) { + size_t pos = name.find_last_of(":"); + if (pos != std::string::npos) { + return name.substr(pos + 1); + } else { + return name; + } +} + +const char *extract_value(xpti::trace_event_data_t *event) { + auto data = xptiQueryMetadata(event); + char *str_ptr; + xpti::string_id_t kernel_id = xptiRegisterString("kernel_name", &str_ptr); + xpti::string_id_t memory_id = xptiRegisterString("memory_object", &str_ptr); + if (data->count(kernel_id)) { + return xptiLookupString((*data)[kernel_id]); + } else if (data->count(memory_id)) { + return xptiLookupString((*data)[memory_id]); + } + return event->reserved.payload->name; +} + +XPTI_CALLBACK_API void xptiTraceFinish(const char *stream_name) { + // We do nothing here +} + +XPTI_CALLBACK_API void tp_callback(uint16_t trace_type, + xpti::trace_event_data_t *parent, + xpti::trace_event_data_t *event, + uint64_t instance, const void *user_data) { + auto p = xptiQueryPayload(event); + xpti::timer::tick_t time = xpti::timer::rdtsc(); + auto tid = xpti::timer::get_thread_id(); + uint32_t cpu = g_tid.enum_id(tid); + std::string name; + + if (p->name_sid != xpti::invalid_id) { + name = truncate(p->name); + } else { + name = ""; + } + + uint64_t id = event ? event->unique_id : 0; + // Lock while we print information + std::lock_guard lock(g_io_mutex); + // Print the record information + printf("%-25lu: name=%-35s cpu=%3d event_id=%10lu\n", time, name.c_str(), cpu, + id); + // Go through all available meta-data for an event and print it out + xpti::metadata_t *metadata = xptiQueryMetadata(event); + for (auto &item : *metadata) { + printf(" %-25s:%s\n", xptiLookupString(item.first), + xptiLookupString(item.second)); + } + + if (p->source_file_sid != xpti::invalid_id && p->line_no > 0) { + printf("---[Source file:line no] %s:%d\n", p->source_file, p->line_no); + } +} + +#if (defined(_WIN32) || defined(_WIN64)) + +#include +#include + +BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fwdReason, LPVOID lpvReserved) { + switch (fwdReason) { + case DLL_PROCESS_ATTACH: + // printf("Framework initialization\n"); + break; + case DLL_PROCESS_DETACH: + // + // We cannot unload all subscribers here... + // + // printf("Framework finalization\n"); + break; + } + + return TRUE; +} + +#else // Linux (possibly macOS?) + +__attribute__((constructor)) static void framework_init() { + // printf("Framework initialization\n"); +} + +__attribute__((destructor)) static void framework_fini() { + // printf("Framework finalization\n"); +} + +#endif \ No newline at end of file diff --git a/xptifw/samples/include/xpti_timers.hpp b/xptifw/samples/include/xpti_timers.hpp new file mode 100644 index 0000000000000..ad80b0962aa3d --- /dev/null +++ b/xptifw/samples/include/xpti_timers.hpp @@ -0,0 +1,95 @@ +// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// +#pragma once + +#include +#include +#include +#include + +namespace xpti { +class thread_id { +public: + typedef std::unordered_map thread_lut_t; + + thread_id() : m_tid(0) {} + ~thread_id() {} + + inline uint32_t enum_id(std::thread::id &curr) { + std::stringstream s; + s << curr; + std::string str(s.str()); + + if (m_thread_lookup.count(str)) { + return m_thread_lookup[str]; + } else { + uint32_t enum_id = m_tid++; + m_thread_lookup[str] = enum_id; + return enum_id; + } + } + + inline uint32_t enum_id(const std::string &curr) { + if (m_thread_lookup.count(curr)) { + return m_thread_lookup[curr]; + } else { + uint32_t enum_id = m_tid++; + m_thread_lookup[curr] = enum_id; + return enum_id; + } + } + +private: + std::atomic m_tid; + thread_lut_t m_thread_lookup; +}; + +namespace timer { +#include +typedef uint64_t tick_t; +#if defined(_WIN32) || defined(_WIN64) +#include "windows.h" +inline xpti::timer::tick_t rdtsc() { + LARGE_INTEGER qpcnt; + int rval = QueryPerformanceCounter(&qpcnt); + return qpcnt.QuadPart; +} +inline uint64_t get_ts_frequency() { + LARGE_INTEGER freq; + QueryPerformanceFrequency(&freq); + return freq.QuadPart * 1000; +} +inline uint64_t get_cpu() { return GetCurrentProcessorNumber(); } +#else +#include +#include +#if __x86_64__ || __i386__ || __i386 +inline xpti::timer::tick_t rdtsc() { + struct timespec ts; + int status = clock_gettime(CLOCK_REALTIME, &ts); + return (static_cast(1000000000UL) * static_cast(ts.tv_sec) + + static_cast(ts.tv_nsec)); +} + +inline uint64_t get_ts_frequency() { return static_cast(1E9); } + +inline uint64_t get_cpu() { +#ifdef __linux__ + return sched_getcpu(); +#else + return 0; +#endif +} +#else +#error Unsupported ISA +#endif + +inline std::thread::id get_thread_id() { return std::this_thread::get_id(); } +#endif +} // namespace timer +} // namespace xpti \ No newline at end of file diff --git a/xptifw/src/CMakeLists.txt b/xptifw/src/CMakeLists.txt new file mode 100644 index 0000000000000..85b02eee0a0ea --- /dev/null +++ b/xptifw/src/CMakeLists.txt @@ -0,0 +1,23 @@ +cmake_minimum_required(VERSION 2.8.9) +project (xptifw) + +file(GLOB SOURCES *.cpp) +include_directories(${XPTIFW_DIR}/include) +include_directories(${XPTI_DIR}/include) + +remove_definitions(-DXPTI_STATIC_LIBRARY) +add_definitions(-DXPTI_API_EXPORTS) +add_library(xptifw SHARED ${SOURCES}) +add_dependencies(xptifw tbb) +if (MSVC) + target_link_libraries(xptifw + PRIVATE tbb + ) +else() + target_link_libraries(xptifw + PRIVATE tbb + PRIVATE dl + ) +endif() +# Set the location of the library installation +install(TARGETS xptifw DESTINATION ${CMAKE_BINARY_DIR}) diff --git a/xptifw/src/xpti_trace_framework.cpp b/xptifw/src/xpti_trace_framework.cpp new file mode 100644 index 0000000000000..9416d62355811 --- /dev/null +++ b/xptifw/src/xpti_trace_framework.cpp @@ -0,0 +1,1091 @@ +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +#include "xpti_trace_framework.hpp" +#include "xpti_int64_hash_table.hpp" +#include "xpti_string_table.hpp" + +#include +#include +#include +#include +#include +#include + +#ifdef XPTI_STATISTICS +#include +#endif + +#include +#include +#include +#include + +#define XPTI_USER_DEFINED_TRACE_TYPE16(value) \ + ((uint16_t)xpti::trace_point_type_t::user_defined | (uint16_t)value) +#define XPTI_USER_DEFINED_EVENT_TYPE16(value) \ + ((uint16_t)xpti::trace_event_type_t::user_defined | (uint16_t)value) +#define XPTI_EXTRACT_MSB16(val) (val >> 16) +#define XPTI_EXTRACT_LSB16(val) (val & 0x0000ffff) + +#define XPTI_VENDOR_DEFINED_TRACE_TYPE16(vendor_id, trace_type) \ + ((uint16_t)vendor_id << 8 | XPTI_USER_DEFINED_TRACE_TYPE16(trace_type)) +#define XPTI_VENDOR_DEFINED_EVENT_TYPE16(vendor_id, event_type) \ + ((uint16_t)vendor_id << 8 | XPTI_USER_DEFINED_EVENT_TYPE16(event_type)) + +namespace xpti { +constexpr const char *env_subscribers = "XPTI_SUBSCRIBERS"; +xpti::utils::PlatformHelper g_helper; +// This class is a helper class to load all the listed subscribers provided by +// the user in XPTI_SUBSCRIBERS environment variable. +class Subscribers { +public: + // Data structure to hold the plugin related information, including the + // initialization and finalization functions + struct plugin_data_t { + ///< The handle of the loaded shared object + xpti_plugin_handle_t handle = nullptr; + ///< The initialization entry point + xpti::plugin_init_t init = nullptr; + ///< The finalization entry point + xpti::plugin_fini_t fini = nullptr; + ///< The name of the shared object (in UTF8?)) + std::string name; + ///< indicates whether the data structure is valid + bool valid = false; + }; + // Data structures defined to hold the plugin data that can be looked up by + // plugin name or the handle + // + using plugin_handle_lut_t = std::map; + using plugin_name_lut_t = std::map; + + // We unload all loaded shared objects in the destructor; Must not be invoked + // in the DLLMain() function and possibly the __fini() function in Linux + ~Subscribers() { unloadAllPlugins(); } + // Method to query the plugin data information using the handle. If there's no + // information present for the handle provided, the method returns a structure + // with the valid attribute set to 'false' + plugin_data_t queryPlugin(xpti_plugin_handle_t h) { + plugin_data_t p; + tbb::spin_mutex::scoped_lock my_lock(m_mutex); + if (m_handle_lut.count(h)) + return m_handle_lut[h]; + else + return p; // return invalid plugin data + } + + // Load the provided shared object file name using the explicit load API. If + // the load is successful, a test is performed to see if the shared object has + // the required entry points for it to be considered a trace plugin + // subscriber. If so, the internal data structures are updated and a valid + // handle is returned. + // + // If not, the shared object is unloaded and a NULL handle is returned. + xpti_plugin_handle_t loadPlugin(const char *path) { + xpti_plugin_handle_t handle = 0; + std::string error; + // Check to see if the subscriber has already been loaded; if so, return the + // handle from the previously loaded library + if (m_name_lut.count(path)) { + tbb::spin_mutex::scoped_lock my_lock(m_mutex); + // This plugin has already been loaded, so let's return previously + // recorded handle + printf("Plugin (%s) has already been loaded..\n", path); + plugin_data_t &d = m_name_lut[path]; + assert(d.valid && "Lookup is invalid!"); + if (d.valid) + return d.handle; + } + + handle = g_helper.loadLibrary(path, error); + if (handle) { + // The tracing framework requires the tool plugins to implement the + // xptiTraceInit() and xptiTraceFinish() functions. If these are not + // present, then the plugin will be ruled an invalid plugin and unloaded + // from the process. + xpti::plugin_init_t init = + (xpti::plugin_init_t)g_helper.findFunction(handle, "xptiTraceInit"); + xpti::plugin_fini_t fini = + (xpti::plugin_fini_t)g_helper.findFunction(handle, "xptiTraceFinish"); + if (init && fini) { + // We appear to have loaded a valid plugin, so we will insert the + // plugin information into the two maps guarded by a lock + plugin_data_t d; + d.valid = true; + d.handle = handle; + d.name = path; + d.init = init; + d.fini = fini; + + tbb::spin_mutex::scoped_lock my_lock(m_mutex); + m_name_lut[path] = d; + m_handle_lut[handle] = d; + } else { + // We may have loaded another shared object that is not a tool plugin + // for the tracing framework, so we'll unload it now + unloadPlugin(handle); + handle = nullptr; + } + } else { + // Get error from errno + if (!error.empty()) + printf("[%s]: %s\n", path, error.c_str()); + } + return handle; + } + + // Unloads the shared object identified by the handle provided. If + // successful, returns a success code, else a failure code. + xpti::result_t unloadPlugin(xpti_plugin_handle_t h) { + xpti::result_t res = g_helper.unloadLibrary(h); + if (xpti::result_t::XPTI_RESULT_SUCCESS == res) { + auto it = m_handle_lut.find(h); + if (it != m_handle_lut.end()) { + m_handle_lut.erase(h); + } + } + return res; + } + + // Quick test to see if there are registered subscribers + bool hasValidSubscribers() { return (m_handle_lut.size() > 0); } + + void initializeForStream(const char *stream, uint32_t major_revision, + uint32_t minor_revision, + const char *version_string) { + // If there are subscribers registered, then initialize the subscribers + // with the new stream information. + if (m_handle_lut.size()) { + for (auto &handle : m_handle_lut) { + handle.second.init(major_revision, minor_revision, version_string, + stream); + } + } + } + + void finalizeForStream(const char *stream) { + // If there are subscribers registered, then finalize the subscribers for + // the stream + if (m_handle_lut.size()) { + for (auto &handle : m_handle_lut) { + handle.second.fini(stream); + } + } + } + + void loadFromEnvironmentVariable() { + if (!g_helper.checkTraceEnv()) + return; + // Load all registered listeners by scanning the environment variable in + // "env"; The environment variable, if set, extract the comma separated + // tokens into a vector. + std::string token, env = g_helper.getEnvironmentVariable(env_subscribers); + std::vector listeners; + std::stringstream stream(env); + + // Split the environment variable value by ',' and build a vector of the + // tokens (subscribers) + while (std::getline(stream, token, ',')) { + listeners.push_back(token); + } + + size_t valid_subscribers = listeners.size(); + if (valid_subscribers) { + // Let's go through the subscribers and load these plugins; + for (auto &path : listeners) { + // Load the plugins listed in the environment variable + tbb::spin_mutex::scoped_lock my_lock(m_loader); + auto subs_handle = loadPlugin(path.c_str()); + if (!subs_handle) { + valid_subscribers--; + printf("Failed to load %s successfully...\n", path.c_str()); + } + } + } + } + + void unloadAllPlugins() { + for (auto &item : m_name_lut) { + unloadPlugin(item.second.handle); + } + + m_handle_lut.clear(); + m_name_lut.clear(); + } + +private: + ///< Hash map that maps shared object name to the plugin data + plugin_name_lut_t m_name_lut; + ///< Hash map that maps shared object handle to the plugin data + plugin_handle_lut_t m_handle_lut; + ///< Lock to ensure the operation on these maps are safe + tbb::spin_mutex m_mutex; + ///< Lock to ensure that only one load happens at a time + tbb::spin_mutex m_loader; +}; + +/// @brief Helper class to create and manage tracepoints +/// @details The class uses the global string table to register the strings it +/// encounters in various payloads and builds internal hash maps to manage them. +/// This is a single point for managing tracepoints. +class Tracepoints { +public: + using va_uid_t = tbb::concurrent_unordered_map; + using uid_payload_t = tbb::concurrent_unordered_map; + using uid_event_t = + tbb::concurrent_unordered_map; + + Tracepoints(xpti::StringTable &st) + : m_uid(1), m_insert(0), m_lookup(0), m_string_table(st) { + // Nothing requires to be done at construction time + } + + ~Tracepoints() { clear(); } + + void clear() { + m_string_table.clear(); + // We will always start our ID + // stream from 1. 0 is null_id + // and -1 is invalid_id + m_uid = {1}; + m_payload_lut.clear(); + m_insert = m_lookup = {0}; + m_payloads.clear(); + m_events.clear(); + m_va_lut.clear(); + } + + inline uint64_t makeUniqueID() { return m_uid++; } + + // Create an event with the payload information. If one already exists, the + // retrieve the previously added event. If not, we register the provided + // payload as we are seeing it for the first time. We will register all of + // the strings in the payload and used the string ids for generating a hash + // for the payload. + // + // In the case the event already exists, the instance_no will return the + // instance ID of the event. If the event is created for the first time, the + // instance_id will always be 1. + // + // If the string information like the name, source file etc is not available, + // we will use the code pointer to generate an universal id. + // + // At the end of the function, the following tasks will be complete: + // 1. Create a hash for the payload and cache it + // 2. Create a mapping from hash <--> Universal ID + // 3. Create a mapping from code_ptr <--> Universal ID + // 4. Create a mapping from Universal ID <--> Payload + // 5. Create a mapping from Universal ID <--> Event + xpti::trace_event_data_t *create(const xpti::payload_t *p, + uint64_t *instance_no) { + return register_event(p, instance_no); + } + // Method to get the payload information from the event structure. This method + // uses the Universal ID in the event structure to lookup the payload + // information and returns the payload if available. + // + // This method is thread-safe + const xpti::payload_t *payloadData(xpti::trace_event_data_t *e) { + if (!e || e->unique_id == xpti::invalid_id) + return nullptr; + + if (e->reserved.payload) + return e->reserved.payload; + else { + // Cache it in case it is not already cached + e->reserved.payload = &m_payloads[e->unique_id]; + return e->reserved.payload; + } + } + + const xpti::trace_event_data_t *eventData(int64_t uid) { + if (uid == xpti::invalid_id) + return nullptr; + + auto ev = m_events.find(uid); + if (ev != m_events.end()) + return &(ev->second); + else + return nullptr; + } + + // Sometimes, the user may want to add key-value pairs as metadata associated + // with an event; this would be in addition to the source_file, line_no and + // column_no fields that may already be present. Since we are not sure of the + // data types, we will allow them to add these pairs as strings. Internally, + // we will store key-value pairs as a map of string ids. + xpti::result_t addMetadata(xpti::trace_event_data_t *e, const char *key, + const char *value) { + if (!e || !key || !value) + return xpti::result_t::XPTI_RESULT_INVALIDARG; + + string_id_t key_id = m_string_table.add(key); + if (key_id == xpti::invalid_id) { + return xpti::result_t::XPTI_RESULT_INVALIDARG; + } + string_id_t val_id = m_string_table.add(value); + if (val_id == xpti::invalid_id) { + return xpti::result_t::XPTI_RESULT_INVALIDARG; + } + // Protect simultaneous insert operations on the metadata tables + tbb::spin_mutex::scoped_lock hl(m_metadata_mutex); + if (e->reserved.metadata.count(key_id)) { + return xpti::result_t::XPTI_RESULT_DUPLICATE; + } + e->reserved.metadata[key_id] = val_id; + return xpti::result_t::XPTI_RESULT_SUCCESS; + } + + // Method to get the access statistics of the tracepoints. + // It will print the number of insertions vs lookups that were + // performed. + // + void printStatistics() { + printf("Tracepoint inserts : [%lu] \n", m_insert.load()); + printf("Tracepoint lookups : [%lu]\n", m_lookup.load()); + printf("Tracepoint Hashmap :\n"); + m_payload_lut.printStatistics(); + } + +private: + /// Goals: To create a hash value from payload + /// 1. Check the payload structure to see if it is valid. If valid, then + /// check to see if any strings are provided and add them to the string + /// table. + /// 2. Generate a payload reference using the string information, if + /// present or the code pointer information, otherwise + /// 3. Add the payload and generate a unique ID + /// 4. Cache the computed hash in the payload + int64_t make_hash(xpti::payload_t *p) { + // Initialize to invalid hash value + int64_t hash = xpti::invalid_id; + // If no flags are set, then the payload is not valid + if (p->flags == 0) + return hash; + // If the hash value has been cached, return and bail early + if (p->flags & (uint64_t)payload_flag_t::HashAvailable) + return p->internal; + + // Add the string information to the string table and use the string IDs + // (in addition to any unique addresses) to create a hash value + if ((p->flags & (uint64_t)payload_flag_t::NameAvailable)) { + // Add the kernel name to the string table; if the add() returns the + // address to the string in the string table, we can avoid a query [TBD] + p->name_sid = m_string_table.add(p->name, &p->name); + // p->name = m_string_table.query(p->name_sid); + if (p->flags & (uint64_t)payload_flag_t::SourceFileAvailable) { + // Add source file information ot string table + p->source_file_sid = + m_string_table.add(p->source_file, &p->source_file); + // p->source_file = m_string_table.query(p->source_file_sid); + if (p->flags & (uint64_t)payload_flag_t::CodePointerAvailable) { + // We have source file, kernel name info and kernel address; + // so we combine all of them to make it unique: + // + // <32-bits of address bits 5-36><16-bit source_file_sid><16-bit + // kernel name sid> + // + // Using the code pointer address works better than using the line + // number and column number as the column numbers are not set in all + // compilers that support builtin functions. If two objects are + // declared on the same line, then the line numbers, function name, + // source file are all the same and it would be hard to disambiguate + // them. However, if we use the address, which would be the object + // address, they both will have different addresses even if they + // happen to be on the same line. + uint16_t sname_pack = (uint16_t)(p->name_sid & 0x0000ffff); + uint16_t sfile_pack = (uint16_t)(p->source_file_sid & 0x0000ffff); + uint32_t kernel_sid_pack = XPTI_PACK16_RET32(sfile_pack, sname_pack); + uint32_t addr = + (uint32_t)(((uint64_t)p->code_ptr_va & 0x0000000ffffffff0) >> 4); + hash = XPTI_PACK32_RET64(addr, kernel_sid_pack); + // Cache the hash once it is computed + p->flags |= (uint64_t)payload_flag_t::HashAvailable; + p->internal = hash; + return hash; + } else { + // We have both source file and kernel name info + // + // If we happen to have the line number, then we will combine all + // three integer values (22-bits) to form a 64-bit hash. If not, we + // will use 22 bits of the source file and kernel name ids and form a + // 64-bit value with the middle 22-bits being zero representing the + // line number. + uint64_t left = 0, middle = 0, right = 0, mask22 = 0x00000000003fffff; + // If line number info is available, extract 22-bits of it + if (p->flags & (uint64_t)payload_flag_t::LineInfoAvailable) { + middle = p->line_no & mask22; + middle = middle << 22; + } + // The leftmost 22-bits will represent the file name string id + left = p->source_file_sid & mask22; + left = left << 44; + // The rightmost 22-bits will represent the kernel name string id + right = p->name_sid & mask22; + hash = left | middle | right; + p->flags |= (uint64_t)payload_flag_t::HashAvailable; + p->internal = hash; + return hash; + } + } else if (p->flags & (uint64_t)payload_flag_t::CodePointerAvailable) { + // We have both kernel name and kernel address; we use bits 5-36 from + // the address and combine it with the kernel name string ID + uint32_t addr = + (uint32_t)(((uint64_t)p->code_ptr_va & 0x0000000ffffffff0) >> 4); + hash = XPTI_PACK32_RET64(addr, p->name_sid); + p->flags |= (uint64_t)payload_flag_t::HashAvailable; + p->internal = hash; + return hash; + } else { + // We only have kernel name and this is suspect if the kernel names are + // not unique and will replace any previously stored payload information + if (p->name_sid != xpti::invalid_id) { + hash = XPTI_PACK32_RET64(0, p->name_sid); + p->flags |= (uint64_t)payload_flag_t::HashAvailable; + p->internal = hash; + return hash; + } + } + } else if (p->flags & (uint64_t)payload_flag_t::CodePointerAvailable) { + // We are only going to look at Kernel address when kernel name is not + // available. + hash = (uint64_t)p->code_ptr_va; + p->flags |= (uint64_t)payload_flag_t::HashAvailable; + p->internal = hash; + return hash; + } + return hash; + } + + // Register the payload and generate a universal ID for it. + // Once registered, the payload is accessible through the + // Universal ID that corresponds to the payload. + // + // This method is thread-safe + // + xpti::trace_event_data_t *register_event(const xpti::payload_t *payload, + uint64_t *instance_no) { + xpti::payload_t ptemp = *payload; + // Initialize to invalid + // We need an explicit lock for the rest of the operations as the same + // payload could be registered from multiple-threads. + // + // 1. make_hash(p) is invariant, although the hash may be created twice and + // written to the same field in the structure. If we have a lock guard, we + // may be spinning and wasting time instead. We will just compute this in + // parallel. + // 2. m_payload_lut is queried by two threads and and both queries return + // "not found" + // 3. This takes both threads to the else clause both threads will create a + // unique_id for the payload being registered and add them to the hash table + // [with DIFFERENT IDs] and m_payloads[unique_id] gets updated twice for the + // same payload with different IDs + // 4. ev.unique_id is undefined as it could be one of the two IDs generated + // for the payload + // + int64_t uid = xpti::invalid_id; + // Make a hash value from the payload. If the hash value created is + // invalid, return immediately + int64_t hash = make_hash(&ptemp); + if (hash == xpti::invalid_id) + return nullptr; + // If it's valid, we check to see if we can retrieve the previously added + // event structure; we do this as a critical section + tbb::speculative_spin_mutex::scoped_lock hl(m_hash_lock); + uid = m_payload_lut.find(hash); + if (uid != xpti::invalid_id) { +#ifdef XPTI_STATISTICS + m_lookup++; +#endif + auto ev = m_events.find(uid); + if (ev != m_events.end()) { + ev->second.instance_id++; + // Guarantees that the returned instance ID will be accurate as + // it is on the stack + if (instance_no) + *instance_no = ev->second.instance_id; + return &(ev->second); + } else + return nullptr; // we have a problem! + } else { +#ifdef XPTI_STATISTICS + m_insert++; +#endif + // Create a new unique ID + // + uid = m_uid++; + // And add it as a pair + // + m_payload_lut.add(hash, uid); + // The API allows you to query a Universal ID from the kernel address; so + // build the necessary data structures for this. + if (ptemp.flags & (uint64_t)payload_flag_t::HashAvailable) { + m_va_lut[(uint64_t)ptemp.code_ptr_va] = uid; + } + // We also want to query the payload by universal ID that has been + // generated + m_payloads[uid] = ptemp; // uses tbb, should be thread-safe + { + xpti::trace_event_data_t *ev = &m_events[uid]; + // We are seeing this unique ID for the first time, so we will + // initialize the event structure with defaults and set the unique_id to + // the newly generated unique id (uid) + ev->unique_id = uid; + ev->unused = 0; + ev->reserved.payload = &m_payloads[uid]; + ev->data_id = ev->source_id = ev->target_id = 0; + ev->instance_id = 1; + ev->user_data = nullptr; + ev->event_type = (uint16_t)xpti::trace_event_type_t::unknown_event; + ev->activity_type = + (uint16_t)xpti::trace_activity_type_t::unknown_activity; + *instance_no = ev->instance_id; + return ev; + } + } + return nullptr; + } + + xpti::safe_int64_t m_uid; + xpti::Hash64x64Table m_payload_lut; + xpti::StringTable &m_string_table; + xpti::safe_uint64_t m_insert, m_lookup; + uid_payload_t m_payloads; + uid_event_t m_events; + va_uid_t m_va_lut; + tbb::spin_mutex m_metadata_mutex; + tbb::speculative_spin_mutex m_hash_lock; +}; + +/// @brief Helper class to manage subscriber callbacks for a given tracepoint +/// @details This class provides a thread-safe way to register and unregister +/// callbacks for a given stream. This will be used by tool plugins. +/// +/// The class also provided a way to notify registered callbacks for a given +/// stream and trace point type. This will be used by framework to trigger +/// notifications are instrumentation points. +/// +class Notifications { +public: + using cb_entry_t = std::pair; + using cb_entries_t = tbb::concurrent_vector; + using cb_t = tbb::concurrent_hash_map; + using stream_cb_t = tbb::concurrent_unordered_map; + using statistics_t = tbb::concurrent_unordered_map; + + Notifications() = default; + ~Notifications() = default; + + xpti::result_t registerCallback(uint8_t stream_id, uint16_t trace_type, + xpti::tracepoint_callback_api_t cb) { + if (!cb) + return xpti::result_t::XPTI_RESULT_INVALIDARG; + +#ifdef XPTI_STATISTICS + // Initialize first encountered trace + // type statistics counters + { + tbb::spin_mutex::scoped_lock sl(m_stats_lock); + auto instance = m_stats.find(trace_type); + if (instance == m_stats.end()) { + m_stats[trace_type] = 0; + } + } +#endif + auto &stream_cbs = m_cbs[stream_id]; // thread-safe + // What we get is a concurrent_hash_map + // of vectors holding the callbacks we + // need access to; + cb_t::accessor a; + stream_cbs.insert(a, trace_type); + // If the key does not exist, a new entry is created and an accessor to it + // is returned. If it exists, we have access to the previous entry. + // + // Before we add this element, we scan all existing elements to see if it + // has already been registered. If so, we return XPTI_RESULT_DUPLICATE. + // + // If not, we set the first element of new entry to 'true' indicating that + // it is valid. Unregister will just set this flag to false, indicating that + // it is no longer valid and is unregistered. + for (auto &e : a->second) { + if (e.second == cb) { + if (e.first) // Already here and active + return xpti::result_t::XPTI_RESULT_DUPLICATE; + else { // it has been unregistered before, re-enable + e.first = true; + return xpti::result_t::XPTI_RESULT_UNDELETE; + } + } + } + // If we come here, then we did not find the callback being registered + // already in the framework. So, we insert it. + a->second.push_back(std::make_pair(true, cb)); + return xpti::result_t::XPTI_RESULT_SUCCESS; + } + + xpti::result_t unregisterCallback(uint8_t stream_id, uint16_t trace_type, + xpti::tracepoint_callback_api_t cb) { + if (!cb) + return xpti::result_t::XPTI_RESULT_INVALIDARG; + + auto &stream_cbs = + m_cbs[stream_id]; // thread-safe + // What we get is a concurrent_hash_map of + // vectors holding the callbacks we need + // access to; + cb_t::accessor a; + if (stream_cbs.find(a, trace_type)) { + for (auto &e : a->second) { + if (e.second == cb) { + if (e.first) { // Already here and active + // unregister, since delete and simultaneous + // iterations by other threads are unsafe + e.first = false; + // releases the accessor + return xpti::result_t::XPTI_RESULT_SUCCESS; + } else { + // releases the accessor + return xpti::result_t::XPTI_RESULT_DUPLICATE; + } + } + } + } + // Not here, so nothing to unregister + return xpti::result_t::XPTI_RESULT_NOTFOUND; + } + + xpti::result_t unregisterStream(uint8_t stream_id) { + // If there are no callbacks registered for the requested stream ID, we + // return not found + if (m_cbs.count(stream_id) == 0) + return xpti::result_t::XPTI_RESULT_NOTFOUND; + + auto &stream_cbs = m_cbs[stream_id]; // thread-safe + // Disable all callbacks registered for the stream represented by stream_id + for (auto &it : stream_cbs) { + for (auto &ele : it.second) { + ele.first = false; + } + } + // Return success + return xpti::result_t::XPTI_RESULT_SUCCESS; + } + + xpti::result_t notifySubscribers(uint16_t stream_id, uint16_t trace_type, + xpti::trace_event_data_t *parent, + xpti::trace_event_data_t *object, + uint64_t instance, const void *user_data) { + cb_t &stream = m_cbs[stream_id]; // Thread-safe + cb_t::const_accessor a; // read-only accessor + if (stream.find(a, trace_type)) { + // Go through all registered callbacks and invoke them + for (auto &e : a->second) { + if (e.first) + (e.second)(trace_type, parent, object, instance, user_data); + } + } +#ifdef XPTI_STATISTICS + auto &counter = m_stats[trace_type]; + { + tbb::spin_mutex::scoped_lock sl(m_stats_lock); + counter++; + } +#endif + return xpti::result_t::XPTI_RESULT_SUCCESS; + } + + void printStatistics() { +#ifdef XPTI_STATISTICS + printf("Notification statistics:\n"); + for (auto &s : m_stats) { + printf("%19s: [%llu] \n", + stringify_trace_type((xpti_trace_point_type_t)s.first).c_str(), + s.second); + } +#endif + } + +private: +#ifdef XPTI_STATISTICS + std::string stringify_trace_type(xpti_trace_point_type_t trace_type) { + switch (trace_type) { + case graph_create: + return "graph_create"; + case node_create: + return "node_create"; + case edge_create: + return "edge_create"; + case region_begin: + return "region_begin"; + case region_end: + return "region_end"; + case task_begin: + return "task_begin"; + case task_end: + return "task_end"; + case barrier_begin: + return "barrier_begin"; + case barrier_end: + return "barrier_end"; + case lock_begin: + return "lock_begin"; + case lock_end: + return "lock_end"; + case signal: + return "signal"; + case transfer_begin: + return "transfer_begin"; + case transfer_end: + return "transfer_end"; + case thread_begin: + return "thread_begin"; + case thread_end: + return "thread_end"; + case wait_begin: + return "wait_begin"; + case wait_end: + return "wait_end"; + break; + default: + if (trace_type & user_defined_trace_point) { + std::string str = + "user_defined[" + + std::to_string(XPTI_EXTRACT_USER_DEFINED_ID(trace_type)) + "]"; + return str; + } else { + std::string str = + "unknown[" + + std::to_string(XPTI_EXTRACT_USER_DEFINED_ID(trace_type)) + "]"; + return str; + } + } + } + tbb::spin_mutex m_stats_lock; +#endif + stream_cb_t m_cbs; + tbb::spin_mutex m_cb_lock; + statistics_t m_stats; +}; + +class Framework { +public: + Framework() + : m_tracepoints(m_string_table), m_universal_ids(0), + m_trace_enabled(false) { + // Load all subscribers on construction + m_subscribers.loadFromEnvironmentVariable(); + m_trace_enabled = + (g_helper.checkTraceEnv() && m_subscribers.hasValidSubscribers()); + } + ~Framework() = default; + + void clear() { + m_universal_ids = {1}; + m_tracepoints.clear(); + m_string_table.clear(); + } + + inline void setTraceEnabled(bool yesOrNo = true) { + m_trace_enabled = yesOrNo; + } + + inline bool traceEnabled() { return m_trace_enabled; } + + inline uint64_t makeUniqueID() { return m_tracepoints.makeUniqueID(); } + + xpti::result_t addMetadata(xpti::trace_event_data_t *e, const char *key, + const char *value) { + return m_tracepoints.addMetadata(e, key, value); + } + + xpti::trace_event_data_t * + createEvent(const xpti::payload_t *payload, uint16_t event_type, + xpti::trace_activity_type_t activity_type, + uint64_t *instance_no) { + if (!payload || !instance_no) + return nullptr; + + if (payload->flags == 0) + return nullptr; + + xpti::trace_event_data_t *e = m_tracepoints.create(payload, instance_no); + + // Event is not managed by anyone. The unique_id that is a part of the event + // structure can be used to determine the payload that forms the event. The + // attribute 'ev.user_data' and 'ev.reserved' can be used to store user + // defined and system defined data respectively. Currently the 'reserved' + // field is not used, but object lifetime management must be employed once + // this is active. + // + // On the other hand, the 'user_data' field is for user data and should be + // managed by the user code. The framework will NOT free any memory + // allocated to this pointer + e->event_type = event_type; + e->activity_type = (uint16_t)activity_type; + return e; + } + + inline const xpti::trace_event_data_t *findEvent(int64_t universal_id) { + return m_tracepoints.eventData(universal_id); + } + + xpti::result_t initializeStream(const char *stream, uint32_t major_revision, + uint32_t minor_revision, + const char *version_string) { + if (!stream || !version_string) + return xpti::result_t::XPTI_RESULT_INVALIDARG; + + m_subscribers.initializeForStream(stream, major_revision, minor_revision, + version_string); + return xpti::result_t::XPTI_RESULT_SUCCESS; + } + + uint8_t registerStream(const char *stream_name) { + return (uint8_t)m_stream_string_table.add(stream_name); + } + + void closeAllStreams() { + auto table = m_stream_string_table.table(); + StringTable::st_reverse_t::iterator it; + for (it = table.begin(); it != table.end(); ++it) { + xptiFinalize(it->second); + } + } + + xpti::result_t unregisterStream(const char *stream_name) { + return finalizeStream(stream_name); + } + + uint8_t registerVendor(const char *stream_name) { + return (uint8_t)m_vendor_string_table.add(stream_name); + } + + string_id_t registerString(const char *string, char **table_string) { + if (!table_string || !string) + return xpti::invalid_id; + + *table_string = 0; + + const char *ref_str; + auto id = m_string_table.add(string, &ref_str); + *table_string = const_cast(ref_str); + + return id; + } + + const char *lookupString(string_id_t id) { + if (id < 0) + return nullptr; + return m_string_table.query(id); + } + + xpti::result_t registerCallback(uint8_t stream_id, uint16_t trace_type, + xpti::tracepoint_callback_api_t cb) { + return m_notifier.registerCallback(stream_id, trace_type, cb); + } + + xpti::result_t unregisterCallback(uint8_t stream_id, uint16_t trace_type, + xpti::tracepoint_callback_api_t cb) { + return m_notifier.unregisterCallback(stream_id, trace_type, cb); + } + + xpti::result_t notifySubscribers(uint8_t stream_id, uint16_t trace_type, + xpti::trace_event_data_t *parent, + xpti::trace_event_data_t *object, + uint64_t instance, const void *user_data) { + if (!m_trace_enabled) + return xpti::result_t::XPTI_RESULT_FALSE; + if (!object) + return xpti::result_t::XPTI_RESULT_INVALIDARG; + // + // Notify all subscribers for the stream 'stream_id' + // + return m_notifier.notifySubscribers(stream_id, trace_type, parent, object, + instance, user_data); + } + + bool hasSubscribers() { return m_subscribers.hasValidSubscribers(); } + + xpti::result_t finalizeStream(const char *stream) { + if (!stream) + return xpti::result_t::XPTI_RESULT_INVALIDARG; + m_subscribers.finalizeForStream(stream); + return m_notifier.unregisterStream(m_stream_string_table.add(stream)); + } + + const xpti::payload_t *queryPayload(xpti::trace_event_data_t *e) { + return m_tracepoints.payloadData(e); + } + + void printStatistics() { + m_notifier.printStatistics(); + m_string_table.printStatistics(); + m_tracepoints.printStatistics(); + } + +private: + ///< Thread-safe counter used for generating universal IDs + xpti::safe_uint64_t m_universal_ids; + ///< Manages loading the subscribers and calling their init() functions + xpti::Subscribers m_subscribers; + ///< Used to send event notification to subscribers + xpti::Notifications m_notifier; + ///< Thread-safe string table + xpti::StringTable m_string_table; + ///< Thread-safe string table, used for stream IDs + xpti::StringTable m_stream_string_table; + ///< Thread-safe string table, used for vendor IDs + xpti::StringTable m_vendor_string_table; + ///< Manages the tracepoints - framework caching + xpti::Tracepoints m_tracepoints; + ///< Flag indicates whether tracing should be enabled + bool m_trace_enabled; + ///< Mutes used for double-check pattern + tbb::spin_mutex m_framework_mutex; +}; + +static Framework g_framework; +} // namespace xpti + +extern "C" { +XPTI_EXPORT_API uint16_t xptiRegisterUserDefinedTracePoint( + const char *tool_name, uint8_t user_defined_tp) { + uint8_t tool_id = xpti::g_framework.registerVendor(tool_name); + user_defined_tp |= (uint8_t)xpti::trace_point_type_t::user_defined; + uint16_t usr_def_tp = XPTI_PACK08_RET16(tool_id, user_defined_tp); + + return usr_def_tp; +} + +XPTI_EXPORT_API uint16_t xptiRegisterUserDefinedEventType( + const char *tool_name, uint8_t user_defined_event) { + uint8_t tool_id = xpti::g_framework.registerVendor(tool_name); + user_defined_event |= (uint8_t)xpti::trace_event_type_t::user_defined; + uint16_t usr_def_ev = XPTI_PACK08_RET16(tool_id, user_defined_event); + return usr_def_ev; +} + +XPTI_EXPORT_API xpti::result_t xptiInitialize(const char *stream, uint32_t maj, + uint32_t min, + const char *version) { + return xpti::g_framework.initializeStream(stream, maj, min, version); +} + +XPTI_EXPORT_API void xptiFinalize(const char *stream) { + xpti::g_framework.finalizeStream(stream); +} + +XPTI_EXPORT_API uint64_t xptiGetUniqueId() { + return xpti::g_framework.makeUniqueID(); +} + +XPTI_EXPORT_API xpti::string_id_t xptiRegisterString(const char *string, + char **table_string) { + return xpti::g_framework.registerString(string, table_string); +} + +XPTI_EXPORT_API const char *xptiLookupString(xpti::string_id_t id) { + return xpti::g_framework.lookupString(id); +} + +XPTI_EXPORT_API uint8_t xptiRegisterStream(const char *stream_name) { + return xpti::g_framework.registerStream(stream_name); +} + +XPTI_EXPORT_API xpti::result_t xptiUnregisterStream(const char *stream_name) { + return xpti::g_framework.unregisterStream(stream_name); +} +XPTI_EXPORT_API xpti::trace_event_data_t * +xptiMakeEvent(const char *name, xpti::payload_t *payload, uint16_t event, + xpti::trace_activity_type_t activity, uint64_t *instance_no) { + return xpti::g_framework.createEvent(payload, event, activity, instance_no); +} + +XPTI_EXPORT_API void xptiReset() { xpti::g_framework.clear(); } + +XPTI_EXPORT_API const xpti::trace_event_data_t *xptiFindEvent(int64_t uid) { + return xpti::g_framework.findEvent(uid); +} + +XPTI_EXPORT_API const xpti::payload_t * +xptiQueryPayload(xpti::trace_event_data_t *lookup_object) { + return xpti::g_framework.queryPayload(lookup_object); +} + +XPTI_EXPORT_API xpti::result_t +xptiRegisterCallback(uint8_t stream_id, uint16_t trace_type, + xpti::tracepoint_callback_api_t cb) { + return xpti::g_framework.registerCallback(stream_id, trace_type, cb); +} + +XPTI_EXPORT_API xpti::result_t +xptiUnregisterCallback(uint8_t stream_id, uint16_t trace_type, + xpti::tracepoint_callback_api_t cb) { + return xpti::g_framework.unregisterCallback(stream_id, trace_type, cb); +} + +XPTI_EXPORT_API xpti::result_t +xptiNotifySubscribers(uint8_t stream_id, uint16_t trace_type, + xpti::trace_event_data_t *parent, + xpti::trace_event_data_t *object, uint64_t instance, + const void *temporal_user_data) { + return xpti::g_framework.notifySubscribers( + stream_id, trace_type, parent, object, instance, temporal_user_data); +} + +XPTI_EXPORT_API bool xptiTraceEnabled() { + return xpti::g_framework.traceEnabled(); +} + +XPTI_EXPORT_API xpti::result_t xptiAddMetadata(xpti::trace_event_data_t *e, + const char *key, + const char *value) { + return xpti::g_framework.addMetadata(e, key, value); +} + +XPTI_EXPORT_API xpti::metadata_t * +xptiQueryMetadata(xpti::trace_event_data_t *e) { + return &e->reserved.metadata; +} + +XPTI_EXPORT_API void xptiForceSetTraceEnabled(bool yesOrNo) { + xpti::g_framework.setTraceEnabled(yesOrNo); +} +} // extern "C" + +#if (defined(_WIN32) || defined(_WIN64)) + +#include +#include + +BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fwdReason, LPVOID lpvReserved) { + switch (fwdReason) { + case DLL_PROCESS_ATTACH: + break; + case DLL_PROCESS_DETACH: + // + // We cannot unload all subscribers here... + // +#ifdef XPTI_STATISTICS + __g_framework.printStatistics(); +#endif + break; + } + + return TRUE; +} + +#else // Linux (possibly macOS?) + +__attribute__((constructor)) static void framework_init() {} + +__attribute__((destructor)) static void framework_fini() { +#ifdef XPTI_STATISTICS + __g_framework.printStatistics(); +#endif +} + +#endif diff --git a/xptifw/unit_test/CMakeLists.txt b/xptifw/unit_test/CMakeLists.txt new file mode 100644 index 0000000000000..aab3f0b247eff --- /dev/null +++ b/xptifw/unit_test/CMakeLists.txt @@ -0,0 +1,42 @@ +if (NOT EXISTS ${XPTI_SOURCE_DIR}) + message (FATAL_ERROR "Undefined XPTI_SOURCE_DIR variable: Must be set for tests to work!") +endif() +include_directories(${XPTI_SOURCE_DIR}/include) + +# Download and unpack googletest at configure time +configure_file(../CMakeLists.txt.in googletest-download/CMakeLists.txt) +execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" . + RESULT_VARIABLE result + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/googletest-download ) +if(result) + message(FATAL_ERROR "CMake step for googletest failed: ${result}") +endif() +execute_process(COMMAND ${CMAKE_COMMAND} --build . + RESULT_VARIABLE result + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/googletest-download ) +if(result) + message(FATAL_ERROR "Build step for googletest failed: ${result}") +endif() + +# Prevent overriding the parent project's compiler/linker +# settings on Windows +set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) + +# Add googletest directly to our build. This defines +# the gtest and gtest_main targets. +add_subdirectory(${CMAKE_CURRENT_BINARY_DIR}/googletest-src + ${CMAKE_CURRENT_BINARY_DIR}/googletest-build + EXCLUDE_FROM_ALL) + +# The gtest/gtest_main targets carry header search path +# dependencies automatically when using CMake 2.8.11 or +# later. Otherwise we have to add them here ourselves. +if (CMAKE_VERSION VERSION_LESS 2.8.11) + include_directories("${gtest_SOURCE_DIR}/include") +endif() + +# Now simply link against gtest or gtest_main as needed. Eg +add_executable(xpti_tests xpti_api_tests.cpp xpti_correctness_tests.cpp) +target_link_libraries(xpti_tests gtest) +target_link_libraries(xpti_tests gtest_main xptifw) +add_test(NAME example_test COMMAND xpti_tests) diff --git a/xptifw/unit_test/README.md b/xptifw/unit_test/README.md new file mode 100644 index 0000000000000..d843d195cdd64 --- /dev/null +++ b/xptifw/unit_test/README.md @@ -0,0 +1,7 @@ +# Unit tests + +Unit tests in XPTI use the googletest framework and test the APIs for +correctness. + +For more detail on the framework, the tests that are provided and their usage, +please consult the [XPTI Framework library documentation](doc/XPTI_Framework.md). diff --git a/xptifw/unit_test/xpti_api_tests.cpp b/xptifw/unit_test/xpti_api_tests.cpp new file mode 100644 index 0000000000000..ae048a7b646a8 --- /dev/null +++ b/xptifw/unit_test/xpti_api_tests.cpp @@ -0,0 +1,320 @@ +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +#include "xpti_trace_framework.hpp" + +#include +#include + +#include + +TEST(xptiApiTest, xptiInitializeBadInput) { + auto result = xptiInitialize(nullptr, 0, 0, nullptr); + EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_INVALIDARG); +} + +TEST(xptiApiTest, xptiRegisterStringBadInput) { + char *tstr; + + auto id = xptiRegisterString(nullptr, nullptr); + EXPECT_EQ(id, xpti::invalid_id); + id = xptiRegisterString(nullptr, &tstr); + EXPECT_EQ(id, xpti::invalid_id); + id = xptiRegisterString("foo", nullptr); + EXPECT_EQ(id, xpti::invalid_id); +} + +TEST(xptiApiTest, xptiRegisterStringGoodInput) { + char *tstr = nullptr; + + auto id = xptiRegisterString("foo", &tstr); + EXPECT_NE(id, xpti::invalid_id); + EXPECT_NE(tstr, nullptr); + EXPECT_STREQ("foo", tstr); +} + +TEST(xptiApiTest, xptiLookupStringBadInput) { + const char *tstr; + xptiReset(); + tstr = xptiLookupString(-1); + EXPECT_EQ(tstr, nullptr); +} + +TEST(xptiApiTest, xptiLookupStringGoodInput) { + char *tstr = nullptr; + + auto id = xptiRegisterString("foo", &tstr); + EXPECT_NE(id, xpti::invalid_id); + EXPECT_NE(tstr, nullptr); + EXPECT_STREQ("foo", tstr); + + const char *lstr = xptiLookupString(id); + EXPECT_EQ(lstr, tstr); + EXPECT_STREQ(lstr, tstr); + EXPECT_STREQ("foo", lstr); +} + +TEST(xptiApiTest, xptiGetUniqueId) { + std::set ids; + for (int i = 0; i < 10; ++i) { + auto id = xptiGetUniqueId(); + auto loc = ids.find(id); + EXPECT_EQ(loc, ids.end()); + ids.insert(id); + } +} + +TEST(xptiApiTest, xptiRegisterStreamBadInput) { + auto id = xptiRegisterStream(nullptr); + EXPECT_EQ(id, (uint8_t)xpti::invalid_id); +} + +TEST(xptiApiTest, xptiRegisterStreamGoodInput) { + auto id = xptiRegisterStream("foo"); + EXPECT_NE(id, xpti::invalid_id); + auto new_id = xptiRegisterStream("foo"); + EXPECT_EQ(id, new_id); +} + +TEST(xptiApiTest, xptiUnregisterStreamBadInput) { + auto result = xptiUnregisterStream(nullptr); + EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_INVALIDARG); +} + +TEST(xptiApiTest, xptiUnregisterStreamGoodInput) { + auto id = xptiRegisterStream("foo"); + EXPECT_NE(id, xpti::invalid_id); + auto res = xptiUnregisterStream("NoSuchStream"); + EXPECT_EQ(res, xpti::result_t::XPTI_RESULT_NOTFOUND); + // Event though stream exists, no callbacks registered + auto new_res = xptiUnregisterStream("foo"); + EXPECT_EQ(new_res, xpti::result_t::XPTI_RESULT_NOTFOUND); +} + +TEST(xptiApiTest, xptiMakeEventBadInput) { + xpti::payload_t p; + auto result = + xptiMakeEvent(nullptr, &p, 0, (xpti::trace_activity_type_t)1, nullptr); + EXPECT_EQ(result, nullptr); + p = xpti::payload_t("foo", "foo.cpp", 1, 0, (void *)13); + EXPECT_NE(p.flags, 0); + result = + xptiMakeEvent(nullptr, &p, 0, (xpti::trace_activity_type_t)1, nullptr); + EXPECT_EQ(result, nullptr); + result = xptiMakeEvent("foo", &p, 0, (xpti::trace_activity_type_t)1, nullptr); + EXPECT_EQ(result, nullptr); +} + +TEST(xptiApiTest, xptiMakeEventGoodInput) { + uint64_t instance; + xpti::payload_t p("foo", "foo.cpp", 1, 0, (void *)13); + auto result = + xptiMakeEvent("foo", &p, 0, (xpti::trace_activity_type_t)1, &instance); + EXPECT_NE(result, nullptr); + EXPECT_EQ(instance, 1); + p = xpti::payload_t("foo", "foo.cpp", 1, 0, (void *)13); + auto new_result = + xptiMakeEvent("foo", &p, 0, (xpti::trace_activity_type_t)1, &instance); + EXPECT_EQ(result, new_result); + EXPECT_EQ(instance, 2); +} + +TEST(xptiApiTest, xptiFindEventBadInput) { + auto result = xptiFindEvent(0); + EXPECT_EQ(result, nullptr); + result = xptiFindEvent(1000000); + EXPECT_EQ(result, nullptr); +} + +TEST(xptiApiTest, xptiFindEventGoodInput) { + uint64_t instance; + xpti::payload_t p("foo", "foo.cpp", 1, 0, (void *)13); + + auto result = + xptiMakeEvent("foo", &p, 0, (xpti::trace_activity_type_t)1, &instance); + EXPECT_NE(result, nullptr); + EXPECT_GT(instance, 1); + auto new_result = xptiFindEvent(result->unique_id); + EXPECT_EQ(result, new_result); +} + +TEST(xptiApiTest, xptiQueryPayloadBadInput) { + auto result = xptiQueryPayload(nullptr); + EXPECT_EQ(result, nullptr); +} + +TEST(xptiApiTest, xptiQueryPayloadGoodInput) { + uint64_t instance; + xpti::payload_t p("foo", "foo.cpp", 1, 0, (void *)13); + ; + auto result = + xptiMakeEvent("foo", &p, 0, (xpti::trace_activity_type_t)1, &instance); + EXPECT_NE(result, nullptr); + EXPECT_GT(instance, 1); + auto new_result = xptiQueryPayload(result); + EXPECT_STREQ(p.name, new_result->name); + EXPECT_STREQ(p.source_file, new_result->source_file); + // new_result->name_sid will have a string id whereas 'p' will not + EXPECT_NE(p.name_sid, new_result->name_sid); + EXPECT_NE(p.source_file_sid, new_result->source_file_sid); + EXPECT_EQ(p.line_no, new_result->line_no); +} + +TEST(xptiApiTest, xptiTraceEnabled) { + // If no env is set, this should be false + // The state is determined at app startup + // XPTI_TRACE_ENABLE=1 or 0 and XPTI_FRAMEWORK_DISPATCHER= + // result false + auto result = xptiTraceEnabled(); + EXPECT_EQ(result, false); +} + +XPTI_CALLBACK_API void trace_point_callback( + uint16_t trace_type, + xpti::trace_event_data_t *parent, + xpti::trace_event_data_t *event, + uint64_t instance, + const void *user_data) { + + if(user_data) + (*(int *)user_data) = 1; +} + +XPTI_CALLBACK_API void trace_point_callback2( + uint16_t trace_type, + xpti::trace_event_data_t *parent, + xpti::trace_event_data_t *event, + uint64_t instance, + const void *user_data) { + if(user_data) + (*(int *)user_data) = 1; +} + +TEST(xptiApiTest, xptiRegisterCallbackBadInput) { + uint8_t stream_id = xptiRegisterStream("foo"); + auto result = xptiRegisterCallback(stream_id, 1, nullptr); + EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_INVALIDARG); +} + +TEST(xptiApiTest, xptiRegisterCallbackGoodInput) { + uint64_t instance; + xpti::payload_t p("foo", "foo.cpp", 1, 0, (void *)13); + + auto event = + xptiMakeEvent("foo", &p, 0, (xpti::trace_activity_type_t)1, &instance); + EXPECT_NE(event, nullptr); + + uint8_t stream_id = xptiRegisterStream("foo"); + auto result = xptiRegisterCallback(stream_id, 1, trace_point_callback); + EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_SUCCESS); + result = xptiRegisterCallback(stream_id, 1, trace_point_callback); + EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_DUPLICATE); +} + +TEST(xptiApiTest, xptiUnregisterCallbackBadInput) { + uint8_t stream_id = xptiRegisterStream("foo"); + auto result = xptiUnregisterCallback(stream_id, 1, nullptr); + EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_INVALIDARG); +} + +TEST(xptiApiTest, xptiUnregisterCallbackGoodInput) { + uint64_t instance; + xpti::payload_t p("foo", "foo.cpp", 1, 0, (void *)13); + + auto event = + xptiMakeEvent("foo", &p, 0, (xpti::trace_activity_type_t)1, &instance); + EXPECT_NE(event, nullptr); + + uint8_t stream_id = xptiRegisterStream("foo"); + auto result = xptiUnregisterCallback(stream_id, 1, trace_point_callback2); + EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_NOTFOUND); + result = xptiRegisterCallback(stream_id, 1, trace_point_callback2); + EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_SUCCESS); + result = xptiUnregisterCallback(stream_id, 1, trace_point_callback2); + EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_SUCCESS); + result = xptiUnregisterCallback(stream_id, 1, trace_point_callback2); + EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_DUPLICATE); + result = xptiRegisterCallback(stream_id, 1, trace_point_callback2); + EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_UNDELETE); +} + +TEST(xptiApiTest, xptiNotifySubscribersBadInput) { + uint8_t stream_id = xptiRegisterStream("foo"); + auto result = xptiNotifySubscribers(stream_id, 1, nullptr, nullptr, 0, nullptr); + EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_FALSE); + xptiForceSetTraceEnabled(true); + result = xptiNotifySubscribers(stream_id, 1, nullptr, nullptr, 0, nullptr); + EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_INVALIDARG); +} + +TEST(xptiApiTest, xptiNotifySubscribersGoodInput) { + uint64_t instance; + xpti::payload_t p("foo", "foo.cpp", 1, 0, (void *)13); + + auto event = + xptiMakeEvent("foo", &p, 0, (xpti::trace_activity_type_t)1, &instance); + EXPECT_NE(event, nullptr); + + uint8_t stream_id = xptiRegisterStream("foo"); + xptiForceSetTraceEnabled(true); + int foo_return = 0; + auto result = xptiRegisterCallback(stream_id, 1, trace_point_callback2); + result = xptiNotifySubscribers(stream_id, 1, nullptr, event, 0, (void *)(&foo_return)); + EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_SUCCESS); + EXPECT_EQ(foo_return, 1); +} + +TEST(xptiApiTest, xptiAddMetadataBadInput) { + uint64_t instance; + xpti::payload_t p("foo", "foo.cpp", 1, 0, (void *)13); + + auto event = + xptiMakeEvent("foo", &p, 0, (xpti::trace_activity_type_t)1, &instance); + EXPECT_NE(event, nullptr); + + auto result = xptiAddMetadata(nullptr, nullptr, nullptr); + EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_INVALIDARG); + result = xptiAddMetadata(event, nullptr, nullptr); + EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_INVALIDARG); + result = xptiAddMetadata(event, "foo", nullptr); + EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_INVALIDARG); + result = xptiAddMetadata(event, nullptr, "bar"); + EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_INVALIDARG); +} + +TEST(xptiApiTest, xptiAddMetadataGoodInput) { + uint64_t instance; + xpti::payload_t p("foo", "foo.cpp", 1, 0, (void *)13); + + auto event = + xptiMakeEvent("foo", &p, 0, (xpti::trace_activity_type_t)1, &instance); + EXPECT_NE(event, nullptr); + + auto result = xptiAddMetadata(event, "foo", "bar"); + EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_SUCCESS); + result = xptiAddMetadata(event, "foo", "bar"); + EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_DUPLICATE); +} + +TEST(xptiApiTest, xptiQueryMetadata) { + uint64_t instance; + xpti::payload_t p("foo", "foo.cpp", 1, 0, (void *)13); + + auto event = + xptiMakeEvent("foo", &p, 0, (xpti::trace_activity_type_t)1, &instance); + EXPECT_NE(event, nullptr); + + auto md = xptiQueryMetadata(event); + EXPECT_NE(md, nullptr); + + auto result = xptiAddMetadata(event, "foo1", "bar1"); + EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_SUCCESS); + + char *ts; + EXPECT_TRUE(md->size() > 1); + auto id = (*md)[xptiRegisterString("foo1", &ts)]; + auto str = xptiLookupString(id); + EXPECT_STREQ(str, "bar1"); +} diff --git a/xptifw/unit_test/xpti_correctness_tests.cpp b/xptifw/unit_test/xpti_correctness_tests.cpp new file mode 100644 index 0000000000000..d2675f82dce45 --- /dev/null +++ b/xptifw/unit_test/xpti_correctness_tests.cpp @@ -0,0 +1,331 @@ +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +#include "xpti_trace_framework.h" +#include "xpti_trace_framework.hpp" + +#include +#include + +#include + +XPTI_CALLBACK_API void tp_callback(uint16_t trace_type, + xpti::trace_event_data_t *parent, + xpti::trace_event_data_t *event, + uint64_t instance, const void *user_data) { + + if (user_data) + (*(int *)user_data) = trace_type; +} + +#define NOTIFY(stream, tt, event, retval) \ + { \ + xpti::result_t result = xptiNotifySubscribers(stream, tt, nullptr, event, \ + 0, (void *)(&retval)); \ + EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_SUCCESS); \ + EXPECT_EQ(retval, tt); \ + } + +TEST(xptiCorrectnessTest, xptiMakeEvent) { + uint64_t instance = 0; + xpti::payload_t p("foo", "foo.cpp", 1, 0, (void *)13); + auto result = + xptiMakeEvent("foo", &p, 0, (xpti::trace_activity_type_t)1, &instance); + EXPECT_NE(result, nullptr); + p = xpti::payload_t("foo", "foo.cpp", 1, 0, (void *)13); + auto new_result = + xptiMakeEvent("foo", &p, 0, (xpti::trace_activity_type_t)1, &instance); + EXPECT_EQ(result, new_result); + EXPECT_EQ(result->unique_id, new_result->unique_id); + EXPECT_EQ(result->reserved.payload, new_result->reserved.payload); + EXPECT_STREQ(result->reserved.payload->name, "foo"); + EXPECT_STREQ(result->reserved.payload->source_file, "foo.cpp"); + EXPECT_EQ(result->reserved.payload->line_no, 1); +} + +TEST(xptiCorrectnessTest, xptiRegisterString) { + char *tstr = nullptr; + auto id = xptiRegisterString("foo", &tstr); + EXPECT_NE(id, xpti::invalid_id); + EXPECT_NE(tstr, nullptr); + EXPECT_STREQ("foo", tstr); + + const char *lutstr = xptiLookupString(id); + EXPECT_EQ(tstr, lutstr); + EXPECT_STREQ(lutstr, tstr); +} + +TEST(xptiCorrectnessTest, xptiInitializeForDefaultTracePointTypes) { + // We will test functionality of a subscriber + // without actually creating a plugin + uint8_t stream_id = xptiRegisterStream("test_foo"); + auto result = xptiRegisterCallback( + stream_id, (uint16_t)xpti::trace_point_type_t::graph_create, tp_callback); + EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_SUCCESS); + result = xptiRegisterCallback( + stream_id, (uint16_t)xpti::trace_point_type_t::node_create, tp_callback); + EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_SUCCESS); + result = xptiRegisterCallback( + stream_id, (uint16_t)xpti::trace_point_type_t::edge_create, tp_callback); + EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_SUCCESS); + result = xptiRegisterCallback( + stream_id, (uint16_t)xpti::trace_point_type_t::region_begin, tp_callback); + EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_SUCCESS); + result = xptiRegisterCallback( + stream_id, (uint16_t)xpti::trace_point_type_t::region_end, tp_callback); + EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_SUCCESS); + result = xptiRegisterCallback( + stream_id, (uint16_t)xpti::trace_point_type_t::task_begin, tp_callback); + EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_SUCCESS); + result = xptiRegisterCallback( + stream_id, (uint16_t)xpti::trace_point_type_t::task_end, tp_callback); + EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_SUCCESS); + result = xptiRegisterCallback( + stream_id, (uint16_t)xpti::trace_point_type_t::barrier_begin, + tp_callback); + EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_SUCCESS); + result = xptiRegisterCallback( + stream_id, (uint16_t)xpti::trace_point_type_t::barrier_end, tp_callback); + EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_SUCCESS); + result = xptiRegisterCallback( + stream_id, (uint16_t)xpti::trace_point_type_t::lock_begin, tp_callback); + EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_SUCCESS); + result = xptiRegisterCallback( + stream_id, (uint16_t)xpti::trace_point_type_t::lock_end, tp_callback); + EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_SUCCESS); + result = xptiRegisterCallback( + stream_id, (uint16_t)xpti::trace_point_type_t::transfer_begin, + tp_callback); + EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_SUCCESS); + result = xptiRegisterCallback( + stream_id, (uint16_t)xpti::trace_point_type_t::transfer_end, tp_callback); + EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_SUCCESS); + result = xptiRegisterCallback( + stream_id, (uint16_t)xpti::trace_point_type_t::thread_begin, tp_callback); + EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_SUCCESS); + result = xptiRegisterCallback( + stream_id, (uint16_t)xpti::trace_point_type_t::thread_end, tp_callback); + EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_SUCCESS); + result = xptiRegisterCallback( + stream_id, (uint16_t)xpti::trace_point_type_t::wait_begin, tp_callback); + EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_SUCCESS); + result = xptiRegisterCallback( + stream_id, (uint16_t)xpti::trace_point_type_t::wait_end, tp_callback); + EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_SUCCESS); + result = xptiRegisterCallback( + stream_id, (uint16_t)xpti::trace_point_type_t::signal, tp_callback); + EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_SUCCESS); +} + +TEST(xptiCorrectnessTest, xptiNotifySubscribersForDefaultTracePointTypes) { + uint64_t instance; + xpti::payload_t p("foo", "foo.cpp", 1, 0, (void *)13); + xptiForceSetTraceEnabled(true); + + uint8_t stream_id = xptiRegisterStream("test_foo"); + auto result = xptiRegisterCallback( + stream_id, (uint16_t)xpti::trace_point_type_t::graph_create, tp_callback); + result = xptiRegisterCallback( + stream_id, (uint16_t)xpti::trace_point_type_t::node_create, tp_callback); + result = xptiRegisterCallback( + stream_id, (uint16_t)xpti::trace_point_type_t::edge_create, tp_callback); + result = xptiRegisterCallback( + stream_id, (uint16_t)xpti::trace_point_type_t::region_begin, tp_callback); + result = xptiRegisterCallback( + stream_id, (uint16_t)xpti::trace_point_type_t::region_end, tp_callback); + result = xptiRegisterCallback( + stream_id, (uint16_t)xpti::trace_point_type_t::task_begin, tp_callback); + result = xptiRegisterCallback( + stream_id, (uint16_t)xpti::trace_point_type_t::task_end, tp_callback); + result = xptiRegisterCallback( + stream_id, (uint16_t)xpti::trace_point_type_t::barrier_begin, + tp_callback); + result = xptiRegisterCallback( + stream_id, (uint16_t)xpti::trace_point_type_t::barrier_end, tp_callback); + result = xptiRegisterCallback( + stream_id, (uint16_t)xpti::trace_point_type_t::lock_begin, tp_callback); + result = xptiRegisterCallback( + stream_id, (uint16_t)xpti::trace_point_type_t::lock_end, tp_callback); + result = xptiRegisterCallback( + stream_id, (uint16_t)xpti::trace_point_type_t::transfer_begin, + tp_callback); + result = xptiRegisterCallback( + stream_id, (uint16_t)xpti::trace_point_type_t::transfer_end, tp_callback); + result = xptiRegisterCallback( + stream_id, (uint16_t)xpti::trace_point_type_t::thread_begin, tp_callback); + result = xptiRegisterCallback( + stream_id, (uint16_t)xpti::trace_point_type_t::thread_end, tp_callback); + result = xptiRegisterCallback( + stream_id, (uint16_t)xpti::trace_point_type_t::wait_begin, tp_callback); + result = xptiRegisterCallback( + stream_id, (uint16_t)xpti::trace_point_type_t::wait_end, tp_callback); + result = xptiRegisterCallback( + stream_id, (uint16_t)xpti::trace_point_type_t::signal, tp_callback); + + auto ge = + xptiMakeEvent("foo", &p, 0, (xpti::trace_activity_type_t)1, &instance); + EXPECT_NE(ge, nullptr); + + int foo_return = 0; + NOTIFY(stream_id, (uint16_t)xpti::trace_point_type_t::graph_create, ge, + foo_return); + NOTIFY(stream_id, (uint16_t)xpti::trace_point_type_t::node_create, ge, + foo_return); + NOTIFY(stream_id, (uint16_t)xpti::trace_point_type_t::edge_create, ge, + foo_return); + NOTIFY(stream_id, (uint16_t)xpti::trace_point_type_t::region_begin, ge, + foo_return); + NOTIFY(stream_id, (uint16_t)xpti::trace_point_type_t::region_end, ge, + foo_return); + NOTIFY(stream_id, (uint16_t)xpti::trace_point_type_t::task_begin, ge, + foo_return); + NOTIFY(stream_id, (uint16_t)xpti::trace_point_type_t::task_end, ge, + foo_return); + NOTIFY(stream_id, (uint16_t)xpti::trace_point_type_t::barrier_begin, ge, + foo_return); + NOTIFY(stream_id, (uint16_t)xpti::trace_point_type_t::barrier_end, ge, + foo_return); + NOTIFY(stream_id, (uint16_t)xpti::trace_point_type_t::lock_begin, ge, + foo_return); + NOTIFY(stream_id, (uint16_t)xpti::trace_point_type_t::lock_end, ge, + foo_return); + NOTIFY(stream_id, (uint16_t)xpti::trace_point_type_t::transfer_begin, ge, + foo_return); + NOTIFY(stream_id, (uint16_t)xpti::trace_point_type_t::transfer_end, ge, + foo_return); + NOTIFY(stream_id, (uint16_t)xpti::trace_point_type_t::thread_begin, ge, + foo_return); + NOTIFY(stream_id, (uint16_t)xpti::trace_point_type_t::thread_end, ge, + foo_return); + NOTIFY(stream_id, (uint16_t)xpti::trace_point_type_t::wait_begin, ge, + foo_return); + NOTIFY(stream_id, (uint16_t)xpti::trace_point_type_t::wait_end, ge, + foo_return); + NOTIFY(stream_id, (uint16_t)xpti::trace_point_type_t::signal, ge, foo_return); +} + +TEST(xptiCorrectnessTest, xptiInitializeForUserDefinedTracePointTypes) { + // We will test functionality of a subscriber + // without actually creating a plugin + uint8_t stream_id = xptiRegisterStream("test_foo"); + typedef enum { + extn1_begin = XPTI_TRACE_POINT_BEGIN(0), + extn1_end = XPTI_TRACE_POINT_END(0), + extn2_begin = XPTI_TRACE_POINT_BEGIN(1), + extn2_end = XPTI_TRACE_POINT_END(1) + } tp_extension_t; + + auto tt_type = + xptiRegisterUserDefinedTracePoint("test_foo_tool", extn1_begin); + auto result = xptiRegisterCallback(stream_id, tt_type, tp_callback); + EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_SUCCESS); + tt_type = xptiRegisterUserDefinedTracePoint("test_foo_tool", extn1_end); + result = xptiRegisterCallback(stream_id, tt_type, tp_callback); + EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_SUCCESS); + tt_type = xptiRegisterUserDefinedTracePoint("test_foo_tool", extn2_begin); + result = xptiRegisterCallback(stream_id, tt_type, tp_callback); + EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_SUCCESS); + tt_type = xptiRegisterUserDefinedTracePoint("test_foo_tool", extn2_end); + result = xptiRegisterCallback(stream_id, tt_type, tp_callback); + EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_SUCCESS); +} + +TEST(xptiCorrectnessTest, xptiNotifySubscribersForUserDefinedTracePointTypes) { + uint64_t instance; + xpti::payload_t p("foo", "foo.cpp", 1, 0, (void *)13); + xptiForceSetTraceEnabled(true); + + uint8_t stream_id = xptiRegisterStream("test_foo"); + typedef enum { + extn1_begin = XPTI_TRACE_POINT_BEGIN(0), + extn1_end = XPTI_TRACE_POINT_END(0), + extn2_begin = XPTI_TRACE_POINT_BEGIN(1), + extn2_end = XPTI_TRACE_POINT_END(1) + } tp_extension_t; + + auto tt_type1 = + xptiRegisterUserDefinedTracePoint("test_foo_tool", extn1_begin); + auto result = xptiRegisterCallback(stream_id, tt_type1, tp_callback); + EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_DUPLICATE); + auto tt_type2 = xptiRegisterUserDefinedTracePoint("test_foo_tool", extn1_end); + result = xptiRegisterCallback(stream_id, tt_type2, tp_callback); + EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_DUPLICATE); + auto tt_type3 = + xptiRegisterUserDefinedTracePoint("test_foo_tool", extn2_begin); + result = xptiRegisterCallback(stream_id, tt_type3, tp_callback); + EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_DUPLICATE); + auto tt_type4 = xptiRegisterUserDefinedTracePoint("test_foo_tool", extn2_end); + result = xptiRegisterCallback(stream_id, tt_type4, tp_callback); + EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_DUPLICATE); + + auto ge = + xptiMakeEvent("foo", &p, 0, (xpti::trace_activity_type_t)1, &instance); + EXPECT_NE(ge, nullptr); + + int foo_return = 0; + NOTIFY(stream_id, tt_type1, ge, foo_return); + NOTIFY(stream_id, tt_type2, ge, foo_return); + NOTIFY(stream_id, tt_type3, ge, foo_return); + NOTIFY(stream_id, tt_type4, ge, foo_return); + + auto tool_id1 = XPTI_TOOL_ID(tt_type1); + auto tool_id2 = XPTI_TOOL_ID(tt_type2); + auto tool_id3 = XPTI_TOOL_ID(tt_type3); + auto tool_id4 = XPTI_TOOL_ID(tt_type4); + EXPECT_EQ(tool_id1, tool_id2); + EXPECT_EQ(tool_id2, tool_id3); + EXPECT_EQ(tool_id3, tool_id4); + EXPECT_EQ(tool_id4, tool_id1); + + auto tp_id1 = XPTI_EXTRACT_USER_DEFINED_ID(tt_type1); + auto tp_id2 = XPTI_EXTRACT_USER_DEFINED_ID(tt_type2); + auto tp_id3 = XPTI_EXTRACT_USER_DEFINED_ID(tt_type3); + auto tp_id4 = XPTI_EXTRACT_USER_DEFINED_ID(tt_type4); + EXPECT_NE(tp_id1, tp_id2); + EXPECT_NE(tp_id2, tp_id3); + EXPECT_NE(tp_id3, tp_id4); + EXPECT_NE(tp_id4, tp_id1); +} + +TEST(xptiCorrectnessTest, xptiUserDefinedEventTypes) { + uint64_t instance; + xpti::payload_t p("foo", "foo.cpp", 1, 0, (void *)13); + xptiForceSetTraceEnabled(true); + + uint8_t stream_id = xptiRegisterStream("test_foo"); + typedef enum { + extn_ev1 = XPTI_EVENT(0), + extn_ev2 = XPTI_EVENT(1), + extn_ev3 = XPTI_EVENT(2), + extn_ev4 = XPTI_EVENT(3) + } event_extension_t; + + auto ev_type1 = xptiRegisterUserDefinedEventType("test_foo_tool", extn_ev1); + auto ev_type2 = xptiRegisterUserDefinedEventType("test_foo_tool", extn_ev2); + auto ev_type3 = xptiRegisterUserDefinedEventType("test_foo_tool", extn_ev3); + auto ev_type4 = xptiRegisterUserDefinedEventType("test_foo_tool", extn_ev4); + EXPECT_NE(ev_type1, ev_type2); + EXPECT_NE(ev_type2, ev_type3); + EXPECT_NE(ev_type3, ev_type4); + EXPECT_NE(ev_type4, ev_type1); + + auto tool_id1 = XPTI_TOOL_ID(ev_type1); + auto tool_id2 = XPTI_TOOL_ID(ev_type2); + auto tool_id3 = XPTI_TOOL_ID(ev_type3); + auto tool_id4 = XPTI_TOOL_ID(ev_type4); + EXPECT_EQ(tool_id1, tool_id2); + EXPECT_EQ(tool_id2, tool_id3); + EXPECT_EQ(tool_id3, tool_id4); + EXPECT_EQ(tool_id4, tool_id1); + + auto tp_id1 = XPTI_EXTRACT_USER_DEFINED_ID(ev_type1); + auto tp_id2 = XPTI_EXTRACT_USER_DEFINED_ID(ev_type2); + auto tp_id3 = XPTI_EXTRACT_USER_DEFINED_ID(ev_type3); + auto tp_id4 = XPTI_EXTRACT_USER_DEFINED_ID(ev_type4); + EXPECT_NE(tp_id1, tp_id2); + EXPECT_NE(tp_id2, tp_id3); + EXPECT_NE(tp_id3, tp_id4); + EXPECT_NE(tp_id4, tp_id1); +} \ No newline at end of file From 4078330eb8c3a9d9e59539b49ca6b37566fef361 Mon Sep 17 00:00:00 2001 From: Vasanth Tovinkere Date: Mon, 16 Mar 2020 13:51:03 -0700 Subject: [PATCH 02/18] [XPTIFW] Added .gitignore to ignore the generated files Signed-off-by: Vasanth Tovinkere --- xptifw/.gitignore | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 xptifw/.gitignore diff --git a/xptifw/.gitignore b/xptifw/.gitignore new file mode 100644 index 0000000000000..aaf47ec18df35 --- /dev/null +++ b/xptifw/.gitignore @@ -0,0 +1,22 @@ +CMakeCache.txt +CMakeFiles/ +Makefile +basic_test/CMakeFiles/ +basic_test/Makefile +basic_test/cmake_install.cmake +cmake_install.cmake +lib/ +samples/basic_collector/CMakeFiles/ +samples/basic_collector/Makefile +samples/basic_collector/cmake_install.cmake +samples/basic_collector/xpti_timers.hpp +src/CMakeFiles/ +src/Makefile +src/cmake_install.cmake +unit_test/CMakeFiles/ +unit_test/Makefile +unit_test/cmake_install.cmake +unit_test/googletest-build/ +unit_test/googletest-download/ +unit_test/googletest-src/ + From 02e5610382d52a87609d5d8c669f5336229161f9 Mon Sep 17 00:00:00 2001 From: Vasanth Tovinkere Date: Mon, 16 Mar 2020 14:42:07 -0700 Subject: [PATCH 03/18] [XPTIFW] Fixed comments and newlines Signed-off-by: Vasanth Tovinkere --- xptifw/CMakeLists.txt.in | 2 +- xptifw/basic_test/main.cpp | 2 +- xptifw/basic_test/performance_tests.cpp | 2 +- xptifw/basic_test/semantic_tests.cpp | 2 +- xptifw/include/xpti_int64_hash_table.hpp | 2 +- .../basic_collector/basic_collector.cpp | 2 +- xptifw/src/xpti_trace_framework.cpp | 36 +++++++++---------- xptifw/unit_test/xpti_correctness_tests.cpp | 2 +- 8 files changed, 25 insertions(+), 25 deletions(-) diff --git a/xptifw/CMakeLists.txt.in b/xptifw/CMakeLists.txt.in index f98ccb4ac9780..c6247af53cf5f 100644 --- a/xptifw/CMakeLists.txt.in +++ b/xptifw/CMakeLists.txt.in @@ -12,4 +12,4 @@ ExternalProject_Add(googletest BUILD_COMMAND "" INSTALL_COMMAND "" TEST_COMMAND "" -) \ No newline at end of file +) diff --git a/xptifw/basic_test/main.cpp b/xptifw/basic_test/main.cpp index 8409cf7a9dd90..e0215ba1c0e77 100644 --- a/xptifw/basic_test/main.cpp +++ b/xptifw/basic_test/main.cpp @@ -74,4 +74,4 @@ int main(int argc, char **argv) { ct.run(); pt.run(); -} \ No newline at end of file +} diff --git a/xptifw/basic_test/performance_tests.cpp b/xptifw/basic_test/performance_tests.cpp index d804cf665cc36..da3630b199db9 100644 --- a/xptifw/basic_test/performance_tests.cpp +++ b/xptifw/basic_test/performance_tests.cpp @@ -549,4 +549,4 @@ void test_performance::run_instrumentation_tests() { } } // namespace performance -} // namespace test \ No newline at end of file +} // namespace test diff --git a/xptifw/basic_test/semantic_tests.cpp b/xptifw/basic_test/semantic_tests.cpp index 69a92dddc3432..267f6dc1c963c 100644 --- a/xptifw/basic_test/semantic_tests.cpp +++ b/xptifw/basic_test/semantic_tests.cpp @@ -508,4 +508,4 @@ void test_correctness::run_notification_tests() { } } // namespace semantic -} // namespace test \ No newline at end of file +} // namespace test diff --git a/xptifw/include/xpti_int64_hash_table.hpp b/xptifw/include/xpti_int64_hash_table.hpp index 58a5df5432af8..ec16022369fa2 100644 --- a/xptifw/include/xpti_int64_hash_table.hpp +++ b/xptifw/include/xpti_int64_hash_table.hpp @@ -145,4 +145,4 @@ class Hash64x64Table { m_lookup; ///< Thread-safe tracking of lookups #endif }; -} // namespace xpti \ No newline at end of file +} // namespace xpti diff --git a/xptifw/samples/basic_collector/basic_collector.cpp b/xptifw/samples/basic_collector/basic_collector.cpp index db1d40e9434f8..857dce843f67e 100644 --- a/xptifw/samples/basic_collector/basic_collector.cpp +++ b/xptifw/samples/basic_collector/basic_collector.cpp @@ -201,4 +201,4 @@ __attribute__((destructor)) static void framework_fini() { // printf("Framework finalization\n"); } -#endif \ No newline at end of file +#endif diff --git a/xptifw/src/xpti_trace_framework.cpp b/xptifw/src/xpti_trace_framework.cpp index 9416d62355811..7cbdbce16f3df 100644 --- a/xptifw/src/xpti_trace_framework.cpp +++ b/xptifw/src/xpti_trace_framework.cpp @@ -45,15 +45,15 @@ class Subscribers { // Data structure to hold the plugin related information, including the // initialization and finalization functions struct plugin_data_t { - ///< The handle of the loaded shared object + /// The handle of the loaded shared object xpti_plugin_handle_t handle = nullptr; - ///< The initialization entry point + /// The initialization entry point xpti::plugin_init_t init = nullptr; - ///< The finalization entry point + /// The finalization entry point xpti::plugin_fini_t fini = nullptr; - ///< The name of the shared object (in UTF8?)) + /// The name of the shared object (in UTF8?)) std::string name; - ///< indicates whether the data structure is valid + /// indicates whether the data structure is valid bool valid = false; }; // Data structures defined to hold the plugin data that can be looked up by @@ -217,13 +217,13 @@ class Subscribers { } private: - ///< Hash map that maps shared object name to the plugin data + /// Hash map that maps shared object name to the plugin data plugin_name_lut_t m_name_lut; - ///< Hash map that maps shared object handle to the plugin data + /// Hash map that maps shared object handle to the plugin data plugin_handle_lut_t m_handle_lut; - ///< Lock to ensure the operation on these maps are safe + /// Lock to ensure the operation on these maps are safe tbb::spin_mutex m_mutex; - ///< Lock to ensure that only one load happens at a time + /// Lock to ensure that only one load happens at a time tbb::spin_mutex m_loader; }; @@ -927,23 +927,23 @@ class Framework { } private: - ///< Thread-safe counter used for generating universal IDs + /// Thread-safe counter used for generating universal IDs xpti::safe_uint64_t m_universal_ids; - ///< Manages loading the subscribers and calling their init() functions + /// Manages loading the subscribers and calling their init() functions xpti::Subscribers m_subscribers; - ///< Used to send event notification to subscribers + /// Used to send event notification to subscribers xpti::Notifications m_notifier; - ///< Thread-safe string table + /// Thread-safe string table xpti::StringTable m_string_table; - ///< Thread-safe string table, used for stream IDs + /// Thread-safe string table, used for stream IDs xpti::StringTable m_stream_string_table; - ///< Thread-safe string table, used for vendor IDs + /// Thread-safe string table, used for vendor IDs xpti::StringTable m_vendor_string_table; - ///< Manages the tracepoints - framework caching + /// Manages the tracepoints - framework caching xpti::Tracepoints m_tracepoints; - ///< Flag indicates whether tracing should be enabled + /// Flag indicates whether tracing should be enabled bool m_trace_enabled; - ///< Mutes used for double-check pattern + /// Mutes used for double-check pattern tbb::spin_mutex m_framework_mutex; }; diff --git a/xptifw/unit_test/xpti_correctness_tests.cpp b/xptifw/unit_test/xpti_correctness_tests.cpp index d2675f82dce45..cf80a7fee401b 100644 --- a/xptifw/unit_test/xpti_correctness_tests.cpp +++ b/xptifw/unit_test/xpti_correctness_tests.cpp @@ -328,4 +328,4 @@ TEST(xptiCorrectnessTest, xptiUserDefinedEventTypes) { EXPECT_NE(tp_id2, tp_id3); EXPECT_NE(tp_id3, tp_id4); EXPECT_NE(tp_id4, tp_id1); -} \ No newline at end of file +} From f2733918a43e8ba7213d583ef746257296d934b2 Mon Sep 17 00:00:00 2001 From: Vasanth Tovinkere Date: Tue, 17 Mar 2020 11:52:40 -0700 Subject: [PATCH 04/18] [XPTIFW] Changes based on comments from review - I + Minor formatting changes and spelling fixes + Implementation of std container based framework that doesn't depend on TBB Signed-off-by: Vasanth Tovinkere --- xptifw/README.md | 2 +- xptifw/basic_test/README.md | 3 +- xptifw/basic_test/cl_processor.hpp | 47 ++--- xptifw/basic_test/semantic_tests.cpp | 6 +- xptifw/doc/XPTI_Framework.md | 49 +++-- xptifw/include/xpti_int64_hash_table.hpp | 150 ++++++++++++++- xptifw/include/xpti_string_table.hpp | 178 +++++++++++++++++- xptifw/samples/basic_collector/CMakeLists.txt | 10 +- xptifw/src/xpti_trace_framework.cpp | 8 +- 9 files changed, 362 insertions(+), 91 deletions(-) diff --git a/xptifw/README.md b/xptifw/README.md index 43dc3f6848ea3..b32bee81ac169 100644 --- a/xptifw/README.md +++ b/xptifw/README.md @@ -2,7 +2,7 @@ Implementation of the instrumentation framework library to support instrumentation of arbitrary regions of code. This implementation requires the -specification header files used by the proxy library in ```xpti/```. This +specification header files used by the proxy library in `xpti/`. This library is not necessary for building the SYCL runtime library and only required to build tools that extract the traces from instrumented code. diff --git a/xptifw/basic_test/README.md b/xptifw/basic_test/README.md index 880d5237afbc7..4a2260e0bb93c 100644 --- a/xptifw/basic_test/README.md +++ b/xptifw/basic_test/README.md @@ -1,7 +1,8 @@ # Basic tests In order to capture the cost of various API calls in the framework and test the -correctness of the API, a set of basic tests have been created. hey primarily fall under two categories: +correctness of the API, a set of basic tests have been created. They primarily +fall under two categories: 1. Sematic tests: These tests perform correctness checks on the API call to ensure the right data is being retrieved. The sematic tests are categorized into diff --git a/xptifw/basic_test/cl_processor.hpp b/xptifw/basic_test/cl_processor.hpp index 9b859c2590ccb..5b2bc332ceca1 100644 --- a/xptifw/basic_test/cl_processor.hpp +++ b/xptifw/basic_test/cl_processor.hpp @@ -6,6 +6,8 @@ // // #pragma once +#include "xpti_trace_framework.hpp" + #include #include #include @@ -19,8 +21,6 @@ #include #include -#include "xpti_trace_framework.hpp" - namespace test { namespace utils { enum class OptionType { Boolean, Integer, Float, String, Range }; @@ -104,9 +104,7 @@ class cl_parser { void parse(int argc, char **argv) { m_cl_options.resize(argc); - // Go through the command-line options - // list and build an internal - // + // Go through the command-line options list and build an internal m_app_name = argv[0]; for (int i = 1; i < argc; ++i) { m_cl_options[i - 1] = argv[i]; @@ -160,10 +158,8 @@ class cl_parser { std::cout << "Usage:- \n"; std::cout << " " << m_app_name << " "; // Print all required options first - // for (auto &op : m_option_help_lut) { if (op.second.required()) { - // Required! std::cout << op.first << " "; switch (op.second.type()) { case OptionType::Integer: @@ -185,10 +181,8 @@ class cl_parser { } } // Print the optional flags next. - // for (auto &op : m_option_help_lut) { if (!op.second.required()) { - // Not required! std::cout << "[" << op.first << " "; switch (op.second.type()) { case OptionType::Integer: @@ -212,7 +206,6 @@ class cl_parser { } std::cout << "\n Options supported:\n"; // Print help for all of the options - // for (auto &op : m_option_help_lut) { std::stringstream help(op.second.help()); std::string help_line; @@ -238,14 +231,11 @@ class cl_parser { for (auto &op : m_cl_options) { std::size_t pos = op.find_first_of("-"); if (std::string::npos != pos) { - // We have an option provided; let's - // check to see if it is verbose or + // We have an option provided; let's check to see if it is verbose or // abbreviated - // pos = op.find_first_of("-", pos + 1); if (std::string::npos != pos) { // We have a verbose option - // if (op == m_reserved_key) { print_help(); exit(-1); @@ -256,8 +246,7 @@ class cl_parser { m_value_lut[op] = "true"; prev_key = op; } else { - // We have abbreviated option - // + // We have an abbreviated option if (op == m_reserved_key_abbr) { print_help(); exit(-1); @@ -269,14 +258,11 @@ class cl_parser { m_value_lut[prev_key] = "true"; } } else { - // No idea why stringstream will decode the - // last \n as a "" string; this handles that - // case - // + // No idea why stringstream will decode the last \n as a "" string; this + // handles that case if (prev_key.empty() && op.empty()) break; // We have an option value - // if (prev_key.empty()) { std::cout << "Value[" << op << "] provided without specifying an option\n"; @@ -289,12 +275,9 @@ class cl_parser { } for (auto &op : m_option_help_lut) { - // Check to see if an option is required; - // If so, check to see if there's a value - // associated with it. - // + // Check to see if an option is required; If so, check to see if there's a + // value associated with it. if (op.second.required()) { - // Required! if (!m_value_lut.count(op.first)) { std::cout << "Option[" << op.first << "] is required and not provided.\n"; @@ -368,9 +351,7 @@ class table_model { class range_decoder { public: range_decoder(std::string &range_str) : m_range(range_str) { - // Split by commas first - // followed by : for begin,end, step - // + // Split by commas first followed by : for begin,end, step std::stringstream elements(range_str); std::string element; while (std::getline(elements, element, ',')) { @@ -381,12 +362,10 @@ class range_decoder { std::vector range_tokens; std::string e, b; // Now split by : - // while (std::getline(r, e, ':')) { range_tokens.push_back(e); } - // range_tokens should have three entries - // Second entry is the step + // range_tokens should have three entries; Second entry is the step std::cout << range_tokens[0] << ";" << range_tokens[1] << std::endl; long step = std::stol(range_tokens[2]); for (long i = std::stol(range_tokens[0]); @@ -472,8 +451,8 @@ class test_correctness { } // namespace semantic namespace performance { -constexpr long MaxTracepoints = 100000; -constexpr long MinTracepoints = 10; +constexpr int MaxTracepoints = 100000; +constexpr int MinTracepoints = 10; class test_performance { public: struct record { diff --git a/xptifw/basic_test/semantic_tests.cpp b/xptifw/basic_test/semantic_tests.cpp index 267f6dc1c963c..c6e002ae1c79a 100644 --- a/xptifw/basic_test/semantic_tests.cpp +++ b/xptifw/basic_test/semantic_tests.cpp @@ -90,7 +90,7 @@ enum class NColumns { Threads, Notifications, PassRate }; void test_correctness::run_string_table_test_threads( int run_no, int num_threads, test::utils::table_model &t) { xptiReset(); - constexpr long num_strings = 1000; + constexpr int num_strings = 1000; if (!num_threads) { std::vector strings; @@ -200,7 +200,7 @@ void test_correctness::run_string_table_tests() { void test_correctness::run_tracepoint_test_threads( int run_no, int num_threads, test::utils::table_model &t) { xptiReset(); - constexpr long tracepoint_count = 1000; + constexpr int tracepoint_count = 1000; if (!num_threads) { std::vector payloads; @@ -362,7 +362,7 @@ void test_correctness::run_tracepoint_tests() { void test_correctness::run_notification_test_threads( int run_no, int num_threads, test::utils::table_model &t) { xptiReset(); - constexpr long tp_count = 30, callback_count = tp_count * 30; + tp_count = 30, callback_count = tp_count * 30; std::vector payloads; std::vector uids; std::vector events; diff --git a/xptifw/doc/XPTI_Framework.md b/xptifw/doc/XPTI_Framework.md index ad7069ebbce0f..e4e2c31d046a0 100644 --- a/xptifw/doc/XPTI_Framework.md +++ b/xptifw/doc/XPTI_Framework.md @@ -1,4 +1,4 @@ -# **XPTI Tracing Framework** +# XPTI Tracing Framework - [**XPTI Tracing Framework**](#xpti-tracing-framework) - [**Overview**](#overview) @@ -11,7 +11,7 @@ - [**`xptiFinalize`**](#xptifinalize) - [**APIs and Data Structures Exported by the Tracing Framework**](#apis-and-data-structures-exported-by-the-tracing-framework) -## **Overview** +## Overview In order to understand different parts of an application or library, the ability to capture information about the application or library is needed. @@ -38,7 +38,7 @@ framework relies on the concurrent containers in [Threading Building Blocks](git > **NOTE:** This document is best viewed with [Markdown Reader](https://chrome.google.com/webstore/detail/markdown-reader/gpoigdifkoadgajcincpilkjmejcaanc) > plugin for Chrome or the [Markdown Preview Extension]() for Visual Studio Code. -## **Architecture** +## Architecture The framework consists of a proxy library that is a static library for use within your applications. However, the proxy library consists of stubs that @@ -80,7 +80,7 @@ the environment variable `XPTI_SUBSCRIBERS`. The hypothetical trace data captured by the subscriber is shown as well under the `Resulting trace data` part of the diagram. -### **The Dispatcher** +### The Dispatcher The dispatcher is a dynamic library that implements the XPTI API and is dynamically loaded by the static proxy library, if the static library is used to @@ -89,7 +89,7 @@ of the static library is also an option, however the dynamic library will now have to be shipped with the application. Using the static library allows instrumented applications or libraries to get around this problem. -### **The Subscriber** +### The Subscriber A subscriber in XPTI is a shared object that is dynamically loaded by the dispatcher. Events generated by an instrumented application or library are @@ -205,14 +205,15 @@ value of the `unique_id` and `instance_id` should always be unique. > **NOTE:** The specification for a given event stream **must** be consulted > before implementing the callback handlers for various trace types. -## **Tracing Framework and Callback APIs** +## Tracing Framework and Callback APIs + The current version of the instrumentation API adopts a model where traces are generated in pairs for a give trace scope and a scoped class is made available that assists developers instrumenting their code. The APIs are divided into two parts: (1) the public API exported by the tracing framework which are implemented by the static library and the dispatcher and (2) the callback API that tools will implement to create a subscriber. -### **Brief API Concepts** +### Brief API Concepts The XPTI framework exports a small set of API functions that are a part of the static library and dispatcher exports and deemed sufficient for the uses-cases @@ -235,7 +236,7 @@ The API is documented in the file `xpti_trace_framework.h` that can be located under `xpti/doc`. Some of the API functions and concepts that warrant additional insight are discussed further. -### **`xptiInitialize`** +### `xptiInitialize` When instrumenting an application, developers can decide how many streams they want to generate. In some cases, organizing trace data by streams may be @@ -259,14 +260,14 @@ the `xpti_trace_framework.h` header file. The application that is instrumented be prepared to handle the case when multiple initialization callbacks occur for a given stream. -### **`xptiFinalize`** +### `xptiFinalize` The application or library being instrumented to generate a stream of data must attempt to finalize the stream by making this call. This allows the dispatcher to notify all the subscribers that a stream is about to end. -### **APIs and Data Structures Exported by the Tracing Framework** +### APIs and Data Structures Exported by the Tracing Framework We will begin our discussion by detailing the various public APIs that are exported by the framework and when they are meant to be used. The framework API @@ -275,25 +276,24 @@ the API is to support the instrumentation of code that may or may not fall into function boundaries. * First, the places in the code where instrumentation is warranted should be - identified. Each trace point is unique and will be associated with a - ```payload``` data structure that encapsulates: (1) a unique name, such as a + identified. Each trace point is unique and will be associated with a `payload` data structure that encapsulates: (1) a unique name, such as a function or kernel name or something meaningful if the trace point marks a section of code, (2) the source file it is located in, (3) the line number where this interesting event occurs and (4) the column number of the interesting event. Compilers such as `gcc`, `clang` and the Intel compilers can generate all of this information easily through builtin functions. * Secondly, an event must be created for this trace point region and this - process of creating an event will use the ```payload``` information and create + process of creating an event will use the `payload` information and create an event. If the payload has already been registered, then the previously registered and associated event will be returned. This process will also - create a ```unique_id``` for the event. + create a `unique_id` for the event. * Thirdly, the scope of this trace point must be determined and for a given - scope, as in a related pair of events, the ```unique_id``` created at the + scope, as in a related pair of events, the `unique_id` created at the begin trace point **must** be preserved and used for the end trace point as well. * Finally, the callbacks registered for these types of events must be notified. -### **Trace Point Event** +### Trace Point Event The trace point event describes the event used to notify the subscriber and is usually associated with a payload that describes the event. Since application @@ -309,9 +309,9 @@ the node may be emitted as `node_create`, `task_begin` and `task_end` notifications to record the creation, the beginning of the execution of an instance of the node and when the execution of that instance has completed. -#### **Creating the Payload** +#### Creating the Payload -We will first look at the ```xpti::trace_point_payload_t``` data structure that +We will first look at the `xpti::trace_point_payload_t` data structure that is defined in `xpti_data_types.h`. ```cpp @@ -332,12 +332,12 @@ The next section looks at using the payload information to create a trace point event. Each trace point is unique, from a language or code section standpoint. A trace point maybe visited multiple times, but the payload and the event describing the trace point will always be the same. The tracing framework must -guarantee that when a trace point is visited, the same ```unique_id``` is +guarantee that when a trace point is visited, the same `unique_id` is retrieved for it. For frequent visits to the same trace point site, we must be able to look up the `unique_id` of the payload efficiently or we cache the information at the trace point location. -#### **Creating an Event that Represents the Trace Point** +#### Creating an Event that Represents the Trace Point Once a payload structure has been created, it is used to associate the trace point that this payload represents to an event that captures additional @@ -433,7 +433,7 @@ if (event && xptiTraceEnabled()) { ``` -#### **Notifying the registered listeners** +#### Notifying the registered listeners The code example below shows an example 'C' code that is instrumented with the framework API and this will generate traces for the functions in the program. @@ -458,7 +458,7 @@ void function1() { } ``` -## **Performance of the Framework** +## Performance of the Framework In order to estimate the overheads one could experience by using the framework, this section will outline a couple of scenarios. Some of the key operations @@ -552,8 +552,7 @@ that can be serviced for that configuration. Some sample configurations are show run_test --trace-points 5000 --type performance --num-threads 0,1,2,4 --test-id 1,2 --tp-frequency 10 ``` - -## **Modeling and projection** +## Modeling and projection In order to determine the number of events that the framework can service in a second, the performance tests use the following approach. If the total instrumentation cost is 1µs and for this cost to be under 1% total overhead, the amount of work that needs to be accomplished for every trace event would be 1µs x 100 = 100µs. In this case, the maximum number of events that can be notified/serviced would be: @@ -562,7 +561,7 @@ second, the performance tests use the following approach. If the total instrumen The total instrumentation cost would include *some of the time in the infrastructure in the framework* and the *cost of handling each notification through callbacks* in the subscriber. -### **Computing the cost incurred in the framework** +### Computing the cost incurred in the framework On an average, some trace points are visited only once and others 10s-100s of times. We assume that each trace point created will be visited at least 10 diff --git a/xptifw/include/xpti_int64_hash_table.hpp b/xptifw/include/xpti_int64_hash_table.hpp index ec16022369fa2..ea6d30ad5cad1 100644 --- a/xptifw/include/xpti_int64_hash_table.hpp +++ b/xptifw/include/xpti_int64_hash_table.hpp @@ -7,26 +7,28 @@ #include "xpti_data_types.h" #include +#include +#include #ifdef XPTI_STATISTICS #include #endif +#ifdef XPTI_USE_TBB #include #include namespace xpti { -/// @brief A class for mapping one 64-bit value to another 64-bit value -/// @details With each payload, a kernel/function name and the -/// source file name may be passed and we need to ensure that the -/// payload can be cached in a hash map that maps a unique value -/// from the payload to a universal ID. We could use the payload -/// hash for this purpose, but the numbers are non -monotonic and -/// can be harder to debug. -/// +/// \brief A class for mapping one 64-bit value to another 64-bit value +/// \details With each payload, a kernel/function name and the source file name +/// may be passed and we need to ensure that the payload can be cached in a hash +/// map that maps a unique value from the payload to a universal ID. We could +/// use the payload hash for this purpose, but the numbers are non-monotonic and +/// can be harder to debug. This implementation of the hash table uses Threading +/// Building Blocks concurrent containers for multi-threaded efficiency. class Hash64x64Table { public: - typedef tbb::concurrent_hash_map ht_lut_t; + using ht_lut_t = tbb::concurrent_hash_map; Hash64x64Table(int size = 1024) : m_forward(size), m_reverse(size), m_table_size(size) { @@ -145,4 +147,134 @@ class Hash64x64Table { m_lookup; ///< Thread-safe tracking of lookups #endif }; + +#else +namespace xpti { +/// \brief A class for mapping one 64-bit value to another 64-bit value +/// \details With each payload, a kernel/function name and the source file name +/// may be passed and we need to ensure that the payload can be cached in a hash +/// map that maps a unique value from the payload to a universal ID. We could +/// use the payload hash for this purpose, but the numbers are non-monotonic and +/// can be harder to debug. This implementation of the hash table uses std +/// library containers. +class Hash64x64Table { +public: + using ht_lut_t = std::unordered_map; + + Hash64x64Table(int size = 1024) + : m_forward(size), m_reverse(size), m_table_size(size) { +#ifdef XPTI_STATISTICS + m_insert = 0; + m_lookup = 0; +#endif + } + + ~Hash64x64Table() { + m_forward.clear(); + m_reverse.clear(); + } + + // Clear all the contents of this hash table and get it ready for re-use + void clear() { + m_forward.clear(); + m_reverse.clear(); +#ifdef XPTI_STATISTICS + m_insert = 0; + m_lookup = 0; +#endif + } + + // Check to see if a particular key is already present in the table; + // + // On success, the value for the key will be returned. If not, + // xpti::invalid_id will be returned. + int64_t find(int64_t key) { + std::lock_guard lock(m_mutex); + // Try to read it, if already present + auto key_loc = m_forward.find(key); + if (key_loc != m_forward.end()) { +#ifdef XPTI_STATISTICS + m_lookup++; +#endif + return key_loc->second; // We found it, so we return the value + } else + return xpti::invalid_id; + } + + // Add a pair to the hash table. If the key already exists, this + // call returns even if the value happens to be different this time. + // + // If the key does not exists, then the key is inserted into the hash map and + // the reverse lookup populated with the pair. + void add(int64_t key, int64_t value) { + // Try to read it, if already present + auto key_loc = m_forward.find(key); + if (key_loc != m_forward.end()) { +#ifdef XPTI_STATISTICS + m_lookup++; +#endif + } else { // Multiple threads could fall through here + { + // Employ a double-check pattern here + std::lock_guard lock(m_mutex); + auto key_loc = m_forward.find(key); + if (key_loc == m_forward.end()) { + // The key does not exist, so we will add the key-value pair to the + // hash map + m_forward[key] = value; + key_loc = m_forward.find(key); +#ifdef XPTI_STATISTICS + m_insert++; +#endif + // When we insert a new entry into the table, we also need to build + // the reverse lookup; + { + auto val_loc = m_reverse.find(value); + if (val_loc == m_reverse.end()) { + // An entry does not exist, so we will add it to the reverse + // lookup. + m_reverse[value] = key; + } else { + m_forward.erase(key_loc); + } + } + } + // else, we do not add the key-value pair as the key already exists in + // the table! + } + } + } + + // The reverse query allows one to get the value from the key that may have + // been cached somewhere. + int64_t reverseFind(int64_t value) { + std::lock_guard lock(m_mutex); + auto val_loc = m_reverse.find(value); + if (val_loc != m_reverse.end()) { +#ifdef XPTI_STATISTICS + m_lookup++; +#endif + return val_loc->second; + } else + return xpti::invalid_id; + } + + void printStatistics() { +#ifdef XPTI_STATISTICS + printf("Hash table inserts : [%llu]\n", m_insert.load()); + printf("Hash table lookups : [%llu]\n", m_lookup.load()); +#endif + } + +private: + ht_lut_t m_forward; ///< Forward lookup hash map + ht_lut_t m_reverse; ///< Reverse lookup hash map + int32_t m_table_size; ///< Initial size of the hash map + std::mutex m_mutex; ///< Mutex required to implement a double-check pattern +#ifdef XPTI_STATISTICS + safe_uint64_t m_insert, ///< Thread-safe tracking of insertions + m_lookup; ///< Thread-safe tracking of lookups +#endif +}; +#endif } // namespace xpti diff --git a/xptifw/include/xpti_string_table.hpp b/xptifw/include/xpti_string_table.hpp index aa2c02f95b851..a610811a46362 100644 --- a/xptifw/include/xpti_string_table.hpp +++ b/xptifw/include/xpti_string_table.hpp @@ -8,20 +8,25 @@ #include #include +#include +#include #ifdef XPTI_STATISTICS #include #endif +#ifdef XPTI_USE_TBB + #include #include namespace xpti { -/// @brief A string table class to support the payload handling -/// @details With each payload, a kernel/function name and the source file name +/// \brief A string table class to support the payload handling +/// \details With each payload, a kernel/function name and the source file name /// may be passed and we need to ensure that the incoming strings are copied and /// represented in a string table as the incoming strings are guaranteed to be -/// valid only for the duration of the call that handles the payload. +/// valid only for the duration of the call that handles the payload. This +/// implementation used Threading Building Blocks concurrent containers. class StringTable { public: using st_forward_t = tbb::concurrent_hash_map; @@ -69,9 +74,7 @@ class StringTable { if (str.empty()) return xpti::invalid_id; - // Try to see if the string is already - // present in the string table - // + // Try to see if the string is already present in the string table st_forward_t::const_accessor e; if (m_str2id.find(e, str)) { #ifdef XPTI_STATISTICS @@ -83,8 +86,7 @@ class StringTable { // We found it, so we return the string ID return e->second; } else { - // Multiple threads could fall through here - // Release the reader lock held + // Multiple threads could fall through here Release the reader lock held e.release(); string_id_t id; { @@ -178,3 +180,163 @@ class StringTable { #endif }; } // namespace xpti +#else +namespace xpti { +/// \brief A string table class to support the payload handling +/// \details With each payload, a kernel/function name and the source file name +/// may be passed and we need to ensure that the incoming strings are copied and +/// represented in a string table as the incoming strings are guaranteed to be +/// valid only for the duration of the call that handles the payload. This +/// implementation used STL containers protected with std::mutex. +class StringTable { +public: + using st_forward_t = std::unordered_map; + using st_reverse_t = std::unordered_map; + + StringTable(int size = 4096) + : m_str2id(size), m_id2str(size), m_table_size(size) { + m_ids = 1; +#ifdef XPTI_STATISTICS + m_insert = 0; + m_lookup = 0; +#endif + } + + // Clear all the contents of this string table and get it ready for re-use + void clear() { + m_ids = {1}; + m_id2str.clear(); + m_str2id.clear(); + +#ifdef XPTI_STATISTICS + m_insert = 0; + m_lookup = 0; +#endif + } + + // If the string being added to the string table is empty or invalid, then + // the returned string id = invalid_id; + // + // On success, the string will be inserted into two tables - one that maps + // string to string ID and another that maps from string ID to string. If a + // reference string pointer is made available, then the address of the string + // in the string table is returned through the default argument + xpti::string_id_t add(const char *str, const char **ref_str = nullptr) { + if (!str) + return xpti::invalid_id; + + std::string local_str = str; + return add(local_str, ref_str); + } + + xpti::string_id_t add(std::string str, const char **ref_str = nullptr) { + if (str.empty()) + return xpti::invalid_id; + + // Try to see if the string is already present in the string table + auto loc = m_str2id.find(str); + if (loc != m_str2id.end()) { +#ifdef XPTI_STATISTICS + m_lookup++; +#endif + if (ref_str) + *ref_str = loc->first.c_str(); + + // We found it, so we return the string ID + return loc->second; + } else { + // Multiple threads could fall through here + string_id_t id; + { + // Employ a double-check pattern here + std::lock_guard lock(m_mutex); + auto loc = m_str2id.find(str); + // String not present in the table + if (loc == m_str2id.end()) { + // Add it + id = m_ids++; + m_str2id[str] = id; + loc = m_str2id.find(str); + if (ref_str) + *ref_str = loc->first.c_str(); +#ifdef XPTI_STATISTICS + m_insert++; +#endif + // When we insert a new entry into the table, we also need to build + // the reverse lookup; + { + auto id_loc = m_id2str.find(id); + if (id_loc == m_id2str.end()) { + // An entry does not exist, so we will add it to the reverse + // lookup. + m_id2str[id] = loc->first.c_str(); + // Cache the saved string address and send it to the caller + m_strings++; + return id; + } else { + // We cannot have a case where a string is not present in the + // forward lookup and present in the reverse lookup + m_str2id.erase(loc); + if (ref_str) + *ref_str = nullptr; + + return xpti::invalid_id; + } + } + + } else { + // The string has already been added, so we return the stored ID + id = loc->second; +#ifdef XPTI_STATISTICS + m_lookup++; +#endif + if (ref_str) + *ref_str = loc->first.c_str(); + return id; + } + // The m_mutex will be released here! + } + } + return xpti::invalid_id; + } + + // The reverse query allows one to get the string from the string_id_t that + // may have been cached somewhere. + const char *query(xpti::string_id_t id) { + std::lock_guard lock(m_mutex); + auto loc = m_id2str.find(id); + if (loc != m_id2str.end()) { +#ifdef XPTI_STATISTICS + m_lookup++; +#endif + return loc->second; + } else + return nullptr; + } + + int32_t count() { return (int32_t)m_strings; } + + const st_reverse_t &table() { return m_id2str; } + + void printStatistics() { +#ifdef XPTI_STATISTICS + printf("String table inserts: [%llu]\n", m_insert.load()); + printf("String table lookups: [%llu]\n", m_lookup.load()); +#endif + } + +private: + safe_int32_t m_ids; ///< Thread-safe ID generator + st_forward_t m_str2id; ///< Forward lookup hash map + st_reverse_t m_id2str; ///< Reverse lookup hash map + int32_t m_table_size; ///< Initial table size of the hash-map + std::mutex m_mutex; ///< Mutex required for double-check pattern + std::mutex m_accessor; ///< Mutex required for accessing containers + safe_int32_t m_strings; ///< The count of strings in the table +#ifdef XPTI_STATISTICS + safe_uint64_t m_insert, ///< Thread-safe tracking of insertions + m_lookup; ///< Thread-safe tracking of lookups +#endif +}; +} // namespace xpti +#endif diff --git a/xptifw/samples/basic_collector/CMakeLists.txt b/xptifw/samples/basic_collector/CMakeLists.txt index f164a06ad6ae2..637238517faba 100644 --- a/xptifw/samples/basic_collector/CMakeLists.txt +++ b/xptifw/samples/basic_collector/CMakeLists.txt @@ -10,13 +10,11 @@ remove_definitions(-DXPTI_STATIC_LIBRARY) add_definitions(-DXPTI_API_EXPORTS) add_library(basic_collector SHARED ${SOURCES}) add_dependencies(basic_collector xptifw) -if (MSVC) +target_link_libraries(basic_collector + PRIVATE xptifw + ) +if (UNIX) target_link_libraries(basic_collector - PRIVATE xptifw - ) -else() - target_link_libraries(basic_collector - PRIVATE xptifw PRIVATE dl ) endif() diff --git a/xptifw/src/xpti_trace_framework.cpp b/xptifw/src/xpti_trace_framework.cpp index 7cbdbce16f3df..0444497735b11 100644 --- a/xptifw/src/xpti_trace_framework.cpp +++ b/xptifw/src/xpti_trace_framework.cpp @@ -227,8 +227,8 @@ class Subscribers { tbb::spin_mutex m_loader; }; -/// @brief Helper class to create and manage tracepoints -/// @details The class uses the global string table to register the strings it +/// \brief Helper class to create and manage tracepoints +/// \details The class uses the global string table to register the strings it /// encounters in various payloads and builds internal hash maps to manage them. /// This is a single point for managing tracepoints. class Tracepoints { @@ -560,8 +560,8 @@ class Tracepoints { tbb::speculative_spin_mutex m_hash_lock; }; -/// @brief Helper class to manage subscriber callbacks for a given tracepoint -/// @details This class provides a thread-safe way to register and unregister +/// \brief Helper class to manage subscriber callbacks for a given tracepoint +/// \details This class provides a thread-safe way to register and unregister /// callbacks for a given stream. This will be used by tool plugins. /// /// The class also provided a way to notify registered callbacks for a given From 00ac7f36302bac843035ae319d2ffb6196a2be50 Mon Sep 17 00:00:00 2001 From: Vasanth Tovinkere Date: Tue, 17 Mar 2020 12:23:15 -0700 Subject: [PATCH 05/18] [XPTIFW] Updated documentation to reflect optional TBB use Signed-off-by: Vasanth Tovinkere --- xptifw/doc/XPTI_Framework.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xptifw/doc/XPTI_Framework.md b/xptifw/doc/XPTI_Framework.md index e4e2c31d046a0..340d380f13c02 100644 --- a/xptifw/doc/XPTI_Framework.md +++ b/xptifw/doc/XPTI_Framework.md @@ -32,8 +32,8 @@ can use to build performance analytical models. This document describes the different components of this framework and a testing methodology to determine the cost of using this framework in your applications. -In order to provide efficient and performant thread-safe behavior, the -framework relies on the concurrent containers in [Threading Building Blocks](github.com/intel/tbb). +Current implementation uses std containers by default in the framework. There is +also an implementation that relies on the concurrent containers in [Threading Building Blocks](github.com/intel/tbb) and this can be enabled by using the define `-DXPTI_USE_TBB` with `cmake`. > **NOTE:** This document is best viewed with [Markdown Reader](https://chrome.google.com/webstore/detail/markdown-reader/gpoigdifkoadgajcincpilkjmejcaanc) > plugin for Chrome or the [Markdown Preview Extension]() for Visual Studio Code. From 652e4a3d48b3c9c432a95bd8db23387352ec20b7 Mon Sep 17 00:00:00 2001 From: Vasanth Tovinkere Date: Tue, 17 Mar 2020 20:45:19 -0700 Subject: [PATCH 06/18] [XPTIFW] Modified CMake to not use TBB by default + The framework is fully enabled to use TBB or the standard library containers + The default build will use standard library containers in the implementation in order to remove the explicit dependency on TBB + Tests that use TBB for multi-threaded tests are disabled by default + TBB can be enabled with the soft option -DXPTI_ENABLE_TBB=ON Signed-off-by: Vasanth Tovinkere --- xptifw/CMakeLists.txt | 9 +- xptifw/basic_test/CMakeLists.txt | 19 +-- xptifw/basic_test/semantic_tests.cpp | 2 +- xptifw/doc/XPTI_Framework.md | 22 ++- xptifw/include/xpti_string_table.hpp | 3 +- xptifw/samples/basic_collector/CMakeLists.txt | 14 +- xptifw/src/CMakeLists.txt | 19 ++- xptifw/src/xpti_trace_framework.cpp | 148 +++++++++++++++--- 8 files changed, 183 insertions(+), 53 deletions(-) diff --git a/xptifw/CMakeLists.txt b/xptifw/CMakeLists.txt index 1952c34f600e8..ea4376e9e1ace 100644 --- a/xptifw/CMakeLists.txt +++ b/xptifw/CMakeLists.txt @@ -6,6 +6,9 @@ set(XPTIFW_DIR ${CMAKE_CURRENT_LIST_DIR}) # the proxy implementation of XPTI set(XPTI_DIR ${CMAKE_CURRENT_LIST_DIR}/../xpti) +# Create a soft option for enabling the use of TBB +option(XPTI_ENABLE_TBB "Enable TBB in the framework" OFF) + if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) message(STATUS "No build type selected, default to Release") set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Build type (default Release)" FORCE) @@ -21,4 +24,8 @@ include_directories(${CMAKE_SOURCE_DIR}/include) add_subdirectory(src) add_subdirectory(unit_test) add_subdirectory(samples/basic_collector) -add_subdirectory(basic_test) +# The tests in basic_test are written using TBB, so these tests are enabled +# only if TBB has been enabled. +if (XPTI_ENABLE_TBB) + add_subdirectory(basic_test) +endif() diff --git a/xptifw/basic_test/CMakeLists.txt b/xptifw/basic_test/CMakeLists.txt index 50bcc4460c97f..1856bed1c519b 100644 --- a/xptifw/basic_test/CMakeLists.txt +++ b/xptifw/basic_test/CMakeLists.txt @@ -9,17 +9,14 @@ remove_definitions(-DXPTI_STATIC_LIBRARY) add_definitions(-DXPTI_API_EXPORTS -g -O3) add_executable(run_test ${SOURCES}) add_dependencies(run_test xptifw) -if (MSVC) - target_link_libraries(run_test - PRIVATE xptifw - PRIVATE tbb - ) -else() - target_link_libraries(run_test - PRIVATE xptifw - PRIVATE dl - PRIVATE tbb - ) +target_link_libraries(run_test PRIVATE xptifw) +if(UNIX) + target_link_libraries(run_test PRIVATE dl) endif() + +if (XPTI_ENABLE_TBB) + target_link_libraries(run_test PRIVATE tbb) +endif() + # Set the location of the library installation install(TARGETS run_test DESTINATION ${CMAKE_BINARY_DIR}) diff --git a/xptifw/basic_test/semantic_tests.cpp b/xptifw/basic_test/semantic_tests.cpp index c6e002ae1c79a..f873f6c675dc1 100644 --- a/xptifw/basic_test/semantic_tests.cpp +++ b/xptifw/basic_test/semantic_tests.cpp @@ -362,7 +362,7 @@ void test_correctness::run_tracepoint_tests() { void test_correctness::run_notification_test_threads( int run_no, int num_threads, test::utils::table_model &t) { xptiReset(); - tp_count = 30, callback_count = tp_count * 30; + int tp_count = 30, callback_count = tp_count * 30; std::vector payloads; std::vector uids; std::vector events; diff --git a/xptifw/doc/XPTI_Framework.md b/xptifw/doc/XPTI_Framework.md index 340d380f13c02..90ff0485cb229 100644 --- a/xptifw/doc/XPTI_Framework.md +++ b/xptifw/doc/XPTI_Framework.md @@ -10,6 +10,8 @@ - [**`xptiInitialize`**](#xptiinitialize) - [**`xptiFinalize`**](#xptifinalize) - [**APIs and Data Structures Exported by the Tracing Framework**](#apis-and-data-structures-exported-by-the-tracing-framework) + - [**Performance of the Framework**](#performance-of-the-framework) + - [**Modeling and Projection**](#modeling-and-projection) ## Overview @@ -32,8 +34,19 @@ can use to build performance analytical models. This document describes the different components of this framework and a testing methodology to determine the cost of using this framework in your applications. -Current implementation uses std containers by default in the framework. There is -also an implementation that relies on the concurrent containers in [Threading Building Blocks](github.com/intel/tbb) and this can be enabled by using the define `-DXPTI_USE_TBB` with `cmake`. +Current implementation of the framework uses std containers by default. There is +also an implementation that relies on the concurrent containers in [Threading Building Blocks(TBB)](github.com/intel/tbb) and this can be enabled by using the +define `-DXPTI_USE_TBB` with `cmake`. The std container based implementation is a +thread-safe implementation, but has not been optimized for performance. +Increasing the number of threads accessing the framework will increase the +contention costs in the current implementation and may affect the performance of +the framework. + +To enable the build to use TBB for the framework and tests, use the commands as +shown below: + + cd xptifw + cmake -DXPTI_ENABLE_TBB=ON -DXPTI_SOURCE_DIR=$SYCL_HOME/xpti ./ > **NOTE:** This document is best viewed with [Markdown Reader](https://chrome.google.com/webstore/detail/markdown-reader/gpoigdifkoadgajcincpilkjmejcaanc) > plugin for Chrome or the [Markdown Preview Extension]() for Visual Studio Code. @@ -595,12 +608,13 @@ significantly larger than **FW*****cost***. > **NOTE:** All measurements reported in this document were measured on an NUC form-factor machine with Intel® Core™ i7-8559U @ 2.7 GHz processor -running Ubuntu 18.04. +running Ubuntu 18.04. The tests were compiled to use Threading Building Blocks +concurrent containers for these runs. | Operation | Statistic | Scenario |Count| Framework Cost(ns) | |-----------|-----------|----------|-----|------| -| String table insertion| Cost/insertion| Create a large number of strings (>1000) and insert them into the table. Measure the average cost of multi-threaded insertion.|10000|~**250**ns| +| String table insertion| Cost/insertion| Create a large number of strings (>1000) and insert them into the table. Measure the average cost of multi-threaded insertion.|10000|~**150-500**ns| |String table lookup| Cost/lookup| Look up the strings added in the insertion test in random order and measure the average cost of the lookup.|20000| ~**40**ns| |String table insert/lookup| Cost of insert/lookup | Strings that are added to the string table may be looked up multiple times. On an average, we assume that ever string added to the string table is looked up twice. If strings are looked up more often than the twice assumed in this test, then the average cost of insertion/lookup will be lower.|30000|~**130**ns| | Trace point creation | Cost/creation| Create unique trace points and measure the average cost for each creation. |10000|~**1100**ns| diff --git a/xptifw/include/xpti_string_table.hpp b/xptifw/include/xpti_string_table.hpp index a610811a46362..8a4ccb94a1ffd 100644 --- a/xptifw/include/xpti_string_table.hpp +++ b/xptifw/include/xpti_string_table.hpp @@ -20,6 +20,7 @@ #include #include +#pragma message("Using TBB concurrent containers...") namespace xpti { /// \brief A string table class to support the payload handling /// \details With each payload, a kernel/function name and the source file name @@ -331,7 +332,7 @@ class StringTable { st_reverse_t m_id2str; ///< Reverse lookup hash map int32_t m_table_size; ///< Initial table size of the hash-map std::mutex m_mutex; ///< Mutex required for double-check pattern - std::mutex m_accessor; ///< Mutex required for accessing containers + ///< Replace with reader-writer lock in C++14 safe_int32_t m_strings; ///< The count of strings in the table #ifdef XPTI_STATISTICS safe_uint64_t m_insert, ///< Thread-safe tracking of insertions diff --git a/xptifw/samples/basic_collector/CMakeLists.txt b/xptifw/samples/basic_collector/CMakeLists.txt index 637238517faba..735ed7deed27c 100644 --- a/xptifw/samples/basic_collector/CMakeLists.txt +++ b/xptifw/samples/basic_collector/CMakeLists.txt @@ -10,13 +10,13 @@ remove_definitions(-DXPTI_STATIC_LIBRARY) add_definitions(-DXPTI_API_EXPORTS) add_library(basic_collector SHARED ${SOURCES}) add_dependencies(basic_collector xptifw) -target_link_libraries(basic_collector - PRIVATE xptifw - ) -if (UNIX) - target_link_libraries(basic_collector - PRIVATE dl - ) +target_link_libraries(basic_collector PRIVATE xptifw) +if(UNIX) + target_link_libraries(basic_collector PRIVATE dl) +endif() + +if (XPTI_ENABLE_TBB) + target_link_libraries(basic_collector PRIVATE tbb) endif() # Set the location of the library installation install(TARGETS basic_collector DESTINATION ${CMAKE_BINARY_DIR}) diff --git a/xptifw/src/CMakeLists.txt b/xptifw/src/CMakeLists.txt index 85b02eee0a0ea..31a5be3352217 100644 --- a/xptifw/src/CMakeLists.txt +++ b/xptifw/src/CMakeLists.txt @@ -8,16 +8,15 @@ include_directories(${XPTI_DIR}/include) remove_definitions(-DXPTI_STATIC_LIBRARY) add_definitions(-DXPTI_API_EXPORTS) add_library(xptifw SHARED ${SOURCES}) -add_dependencies(xptifw tbb) -if (MSVC) - target_link_libraries(xptifw - PRIVATE tbb - ) -else() - target_link_libraries(xptifw - PRIVATE tbb - PRIVATE dl - ) +if(UNIX) + target_link_libraries(xptifw PRIVATE dl) endif() + +if (XPTI_ENABLE_TBB) + add_dependencies(xptifw tbb) + target_compile_definitions(xptifw PRIVATE XPTI_USE_TBB) + target_link_libraries(xptifw PRIVATE tbb) +endif() + # Set the location of the library installation install(TARGETS xptifw DESTINATION ${CMAKE_BINARY_DIR}) diff --git a/xptifw/src/xpti_trace_framework.cpp b/xptifw/src/xpti_trace_framework.cpp index 0444497735b11..9f1fc31816ad8 100644 --- a/xptifw/src/xpti_trace_framework.cpp +++ b/xptifw/src/xpti_trace_framework.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -18,10 +19,12 @@ #include #endif +#ifdef XPTI_USE_TBB #include #include #include #include +#endif #define XPTI_USER_DEFINED_TRACE_TYPE16(value) \ ((uint16_t)xpti::trace_point_type_t::user_defined | (uint16_t)value) @@ -70,7 +73,11 @@ class Subscribers { // with the valid attribute set to 'false' plugin_data_t queryPlugin(xpti_plugin_handle_t h) { plugin_data_t p; +#ifdef XPTI_USE_TBB tbb::spin_mutex::scoped_lock my_lock(m_mutex); +#else + std::lock_guard lock(m_mutex); +#endif if (m_handle_lut.count(h)) return m_handle_lut[h]; else @@ -90,7 +97,11 @@ class Subscribers { // Check to see if the subscriber has already been loaded; if so, return the // handle from the previously loaded library if (m_name_lut.count(path)) { +#ifdef XPTI_USE_TBB tbb::spin_mutex::scoped_lock my_lock(m_mutex); +#else + std::lock_guard lock(m_mutex); +#endif // This plugin has already been loaded, so let's return previously // recorded handle printf("Plugin (%s) has already been loaded..\n", path); @@ -119,8 +130,11 @@ class Subscribers { d.name = path; d.init = init; d.fini = fini; - +#ifdef XPTI_USE_TBB tbb::spin_mutex::scoped_lock my_lock(m_mutex); +#else + std::lock_guard lock(m_mutex); +#endif m_name_lut[path] = d; m_handle_lut[handle] = d; } else { @@ -197,7 +211,11 @@ class Subscribers { // Let's go through the subscribers and load these plugins; for (auto &path : listeners) { // Load the plugins listed in the environment variable +#ifdef XPTI_USE_TBB tbb::spin_mutex::scoped_lock my_lock(m_loader); +#else + std::lock_guard lock(m_loader); +#endif auto subs_handle = loadPlugin(path.c_str()); if (!subs_handle) { valid_subscribers--; @@ -221,10 +239,17 @@ class Subscribers { plugin_name_lut_t m_name_lut; /// Hash map that maps shared object handle to the plugin data plugin_handle_lut_t m_handle_lut; +#ifdef XPTI_USE_TBB /// Lock to ensure the operation on these maps are safe tbb::spin_mutex m_mutex; /// Lock to ensure that only one load happens at a time tbb::spin_mutex m_loader; +#else + /// Lock to ensure the operation on these maps are safe + std::mutex m_mutex; + /// Lock to ensure that only one load happens at a time + std::mutex m_loader; +#endif }; /// \brief Helper class to create and manage tracepoints @@ -233,10 +258,16 @@ class Subscribers { /// This is a single point for managing tracepoints. class Tracepoints { public: +#ifdef XPTI_USE_TBB using va_uid_t = tbb::concurrent_unordered_map; using uid_payload_t = tbb::concurrent_unordered_map; using uid_event_t = tbb::concurrent_unordered_map; +#else + using va_uid_t = std::unordered_map; + using uid_payload_t = std::unordered_map; + using uid_event_t = std::unordered_map; +#endif Tracepoints(xpti::StringTable &st) : m_uid(1), m_insert(0), m_lookup(0), m_string_table(st) { @@ -291,7 +322,9 @@ class Tracepoints { const xpti::payload_t *payloadData(xpti::trace_event_data_t *e) { if (!e || e->unique_id == xpti::invalid_id) return nullptr; - +#ifndef XPTI_USE_TBB + std::lock_guard lock(m_mutex); +#endif if (e->reserved.payload) return e->reserved.payload; else { @@ -305,6 +338,9 @@ class Tracepoints { if (uid == xpti::invalid_id) return nullptr; +#ifndef XPTI_USE_TBB + std::lock_guard lock(m_mutex); +#endif auto ev = m_events.find(uid); if (ev != m_events.end()) return &(ev->second); @@ -331,7 +367,12 @@ class Tracepoints { return xpti::result_t::XPTI_RESULT_INVALIDARG; } // Protect simultaneous insert operations on the metadata tables +#ifdef XPTI_USE_TBB tbb::spin_mutex::scoped_lock hl(m_metadata_mutex); +#else + std::lock_guard lock(m_metadata_mutex); +#endif + if (e->reserved.metadata.count(key_id)) { return xpti::result_t::XPTI_RESULT_DUPLICATE; } @@ -492,13 +533,20 @@ class Tracepoints { int64_t hash = make_hash(&ptemp); if (hash == xpti::invalid_id) return nullptr; - // If it's valid, we check to see if we can retrieve the previously added - // event structure; we do this as a critical section + // If it's valid, we check to see if we can retrieve the previously added + // event structure; we do this as a critical section +#ifdef XPTI_USE_TBB tbb::speculative_spin_mutex::scoped_lock hl(m_hash_lock); +#else + std::lock_guard lock(m_hash_lock); +#endif uid = m_payload_lut.find(hash); if (uid != xpti::invalid_id) { #ifdef XPTI_STATISTICS m_lookup++; +#endif +#ifndef XPTI_USE_TBB + std::lock_guard lock(m_mutex); #endif auto ev = m_events.find(uid); if (ev != m_events.end()) { @@ -523,11 +571,17 @@ class Tracepoints { // The API allows you to query a Universal ID from the kernel address; so // build the necessary data structures for this. if (ptemp.flags & (uint64_t)payload_flag_t::HashAvailable) { +#ifndef XPTI_USE_TBB + std::lock_guard lock(m_va_mutex); +#endif m_va_lut[(uint64_t)ptemp.code_ptr_va] = uid; } // We also want to query the payload by universal ID that has been // generated - m_payloads[uid] = ptemp; // uses tbb, should be thread-safe +#ifndef XPTI_USE_TBB + std::lock_guard lock(m_mutex); +#endif + m_payloads[uid] = ptemp; // when it uses tbb, should be thread-safe { xpti::trace_event_data_t *ev = &m_events[uid]; // We are seeing this unique ID for the first time, so we will @@ -556,8 +610,15 @@ class Tracepoints { uid_payload_t m_payloads; uid_event_t m_events; va_uid_t m_va_lut; +#ifdef XPTI_USE_TBB tbb::spin_mutex m_metadata_mutex; tbb::speculative_spin_mutex m_hash_lock; +#else + std::mutex m_metadata_mutex; + std::mutex m_hash_lock; + std::mutex m_mutex; + std::mutex m_va_mutex; +#endif }; /// \brief Helper class to manage subscriber callbacks for a given tracepoint @@ -571,11 +632,17 @@ class Tracepoints { class Notifications { public: using cb_entry_t = std::pair; +#ifdef XPTI_USE_TBB using cb_entries_t = tbb::concurrent_vector; using cb_t = tbb::concurrent_hash_map; using stream_cb_t = tbb::concurrent_unordered_map; using statistics_t = tbb::concurrent_unordered_map; - +#else + using cb_entries_t = std::vector; + using cb_t = std::unordered_map; + using stream_cb_t = std::unordered_map; + using statistics_t = std::unordered_map; +#endif Notifications() = default; ~Notifications() = default; @@ -588,19 +655,34 @@ class Notifications { // Initialize first encountered trace // type statistics counters { +#ifdef XPTI_USE_TBB tbb::spin_mutex::scoped_lock sl(m_stats_lock); +#else + std::lock_guard lock(m_stats_lock); +#endif auto instance = m_stats.find(trace_type); if (instance == m_stats.end()) { m_stats[trace_type] = 0; } } +#endif +#ifndef XPTI_USE_TBB + std::lock_guard lock(m_cb_lock); #endif auto &stream_cbs = m_cbs[stream_id]; // thread-safe // What we get is a concurrent_hash_map // of vectors holding the callbacks we // need access to; +#ifdef XPTI_USE_TBB cb_t::accessor a; stream_cbs.insert(a, trace_type); +#else + auto a = stream_cbs.find(trace_type); + if(a == stream_cbs.end()) { + auto b = stream_cbs[trace_type]; + a = stream_cbs.find(trace_type); + } +#endif // If the key does not exist, a new entry is created and an accessor to it // is returned. If it exists, we have access to the previous entry. // @@ -631,13 +713,22 @@ class Notifications { if (!cb) return xpti::result_t::XPTI_RESULT_INVALIDARG; +#ifndef XPTI_USE_TBB + std::lock_guard lock(m_cb_lock); +#endif auto &stream_cbs = m_cbs[stream_id]; // thread-safe // What we get is a concurrent_hash_map of // vectors holding the callbacks we need // access to; +#ifdef XPTI_USE_TBB cb_t::accessor a; - if (stream_cbs.find(a, trace_type)) { + bool success = stream_cbs.find(a, trace_type); +#else + auto a = stream_cbs.find(trace_type); + bool success = (a != stream_cbs.end()); +#endif + if (success) { for (auto &e : a->second) { if (e.second == cb) { if (e.first) { // Already here and active @@ -660,6 +751,9 @@ class Notifications { xpti::result_t unregisterStream(uint8_t stream_id) { // If there are no callbacks registered for the requested stream ID, we // return not found +#ifndef XPTI_USE_TBB + std::lock_guard lock(m_cb_lock); +#endif if (m_cbs.count(stream_id) == 0) return xpti::result_t::XPTI_RESULT_NOTFOUND; @@ -678,19 +772,35 @@ class Notifications { xpti::trace_event_data_t *parent, xpti::trace_event_data_t *object, uint64_t instance, const void *user_data) { - cb_t &stream = m_cbs[stream_id]; // Thread-safe - cb_t::const_accessor a; // read-only accessor - if (stream.find(a, trace_type)) { - // Go through all registered callbacks and invoke them - for (auto &e : a->second) { - if (e.first) - (e.second)(trace_type, parent, object, instance, user_data); + { +#ifndef XPTI_USE_TBB + std::lock_guard lock(m_cb_lock); +#endif + cb_t &stream = m_cbs[stream_id]; // Thread-safe +#ifdef XPTI_USE_TBB + cb_t::const_accessor a; // read-only accessor + bool success = stream.find(a, trace_type); +#else + auto a = stream.find(trace_type); + bool success = (a != stream.end()); +#endif + + if (success) { + // Go through all registered callbacks and invoke them + for (auto &e : a->second) { + if (e.first) + (e.second)(trace_type, parent, object, instance, user_data); + } } } #ifdef XPTI_STATISTICS auto &counter = m_stats[trace_type]; { +#ifdef XPTI_USE_TBB tbb::spin_mutex::scoped_lock sl(m_stats_lock); +#else + std::lock_guard lock(m_stats_lock); +#endif counter++; } #endif @@ -763,10 +873,14 @@ class Notifications { } } } - tbb::spin_mutex m_stats_lock; #endif stream_cb_t m_cbs; - tbb::spin_mutex m_cb_lock; +#ifdef XPTI_USE_TBB + tbb::spin_mutex m_stats_lock; +#else + std::mutex m_cb_lock; + std::mutex m_stats_lock; +#endif statistics_t m_stats; }; @@ -943,8 +1057,6 @@ class Framework { xpti::Tracepoints m_tracepoints; /// Flag indicates whether tracing should be enabled bool m_trace_enabled; - /// Mutes used for double-check pattern - tbb::spin_mutex m_framework_mutex; }; static Framework g_framework; From e46981a1c0e257c49baf058a68f007de5203af72 Mon Sep 17 00:00:00 2001 From: Vasanth Tovinkere Date: Tue, 17 Mar 2020 21:13:44 -0700 Subject: [PATCH 07/18] [XPTIFW] Applied clang-format to missed files Signed-off-by: Vasanth Tovinkere --- xptifw/src/xpti_trace_framework.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xptifw/src/xpti_trace_framework.cpp b/xptifw/src/xpti_trace_framework.cpp index 9f1fc31816ad8..2a921803ca984 100644 --- a/xptifw/src/xpti_trace_framework.cpp +++ b/xptifw/src/xpti_trace_framework.cpp @@ -678,7 +678,7 @@ class Notifications { stream_cbs.insert(a, trace_type); #else auto a = stream_cbs.find(trace_type); - if(a == stream_cbs.end()) { + if (a == stream_cbs.end()) { auto b = stream_cbs[trace_type]; a = stream_cbs.find(trace_type); } From 35b6a2cdf37f5fff3fa8c16ab9c10753124ddcc3 Mon Sep 17 00:00:00 2001 From: Vasanth Tovinkere Date: Wed, 18 Mar 2020 14:52:32 -0700 Subject: [PATCH 08/18] [XPTIFW] Changed coding convention to follow LLVM style Signed-off-by: Vasanth Tovinkere --- xptifw/basic_test/cl_processor.hpp | 448 ++++----- xptifw/basic_test/main.cpp | 94 +- xptifw/basic_test/performance_tests.cpp | 547 ++++++----- xptifw/basic_test/semantic_tests.cpp | 625 ++++++------ xptifw/doc/XPTI_Framework.md | 90 +- xptifw/include/xpti_int64_hash_table.hpp | 188 ++-- xptifw/include/xpti_string_table.hpp | 188 ++-- .../basic_collector/basic_collector.cpp | 160 ++-- xptifw/samples/include/xpti_timers.hpp | 34 +- xptifw/src/xpti_trace_framework.cpp | 903 +++++++++--------- xptifw/unit_test/xpti_api_tests.cpp | 373 ++++---- xptifw/unit_test/xpti_correctness_tests.cpp | 472 +++++---- 12 files changed, 2082 insertions(+), 2040 deletions(-) diff --git a/xptifw/basic_test/cl_processor.hpp b/xptifw/basic_test/cl_processor.hpp index 5b2bc332ceca1..b26f2e6ff7b75 100644 --- a/xptifw/basic_test/cl_processor.hpp +++ b/xptifw/basic_test/cl_processor.hpp @@ -30,16 +30,17 @@ enum class OptionType { Boolean, Integer, Float, String, Range }; typedef std::map table_row_t; typedef std::map table_t; typedef std::vector titles_t; -class scoped_timer { + +class ScopedTimer { public: typedef std::chrono::time_point time_unit_t; - scoped_timer(uint64_t &ns, double &ratio, size_t count = 1) + ScopedTimer(uint64_t &ns, double &ratio, size_t count = 1) : m_duration{ns}, m_average{ratio}, m_instances{count} { m_before = std::chrono::high_resolution_clock::now(); } - ~scoped_timer() { + ~ScopedTimer() { m_after = std::chrono::high_resolution_clock::now(); auto duration = std::chrono::duration_cast( m_after - m_before); @@ -54,111 +55,112 @@ class scoped_timer { time_unit_t m_before, m_after; }; -class cl_option { +class CommandLineOption { public: - cl_option() - : m_required(false), m_type(OptionType::String), - m_help("No help available.") {} - ~cl_option() {} + CommandLineOption() + : MRequired(false), MType(OptionType::String), + MHelp("No help available.") {} + ~CommandLineOption() {} - cl_option &set_required(bool yesOrNo) { - m_required = yesOrNo; + CommandLineOption &setRequired(bool yesOrNo) { + MRequired = yesOrNo; return *this; } - cl_option &set_type(OptionType type) { - m_type = type; + CommandLineOption &setType(OptionType type) { + MType = type; return *this; } - cl_option &set_help(std::string help) { - m_help = help; + CommandLineOption &setHelp(std::string help) { + MHelp = help; return *this; } - cl_option &set_abbreviation(std::string abbr) { - m_abbrev = abbr; + CommandLineOption &setAbbreviation(std::string abbr) { + MAbbrev = abbr; return *this; } - std::string &abbreviation() { return m_abbrev; } - std::string &help() { return m_help; } - OptionType type() { return m_type; } - bool required() { return m_required; } + std::string &abbreviation() { return MAbbrev; } + std::string &help() { return MHelp; } + OptionType type() { return MType; } + bool required() { return MRequired; } private: - bool m_required; - OptionType m_type; - std::string m_help; - std::string m_abbrev; + bool MRequired; + OptionType MType; + std::string MHelp; + std::string MAbbrev; }; -class cl_parser { +class CommandLineParser { public: - typedef std::unordered_map cl_options_t; + typedef std::unordered_map + CommandLineOptions_t; typedef std::unordered_map key_value_t; - cl_parser() { - m_reserved_key = "--help"; - m_reserved_key_abbr = "-h"; + CommandLineParser() { + MReservedKey = "--help"; + MReservedKeyAbbr = "-h"; } - ~cl_parser() {} + ~CommandLineParser() {} void parse(int argc, char **argv) { - m_cl_options.resize(argc); + MCommandLineOptions.resize(argc); // Go through the command-line options list and build an internal - m_app_name = argv[0]; + MAppName = argv[0]; for (int i = 1; i < argc; ++i) { - m_cl_options[i - 1] = argv[i]; + MCommandLineOptions[i - 1] = argv[i]; } - build_abbreviation_table(); + buildAbbreviationTable(); - if (!check_options()) { - print_help(); + if (!checkOptions()) { + printHelp(); exit(-1); } } - cl_option &add_option(std::string key) { - if (key == m_reserved_key) { + CommandLineOption &addOption(std::string key) { + if (key == MReservedKey) { std::cout << "Option[" << key - << "] is a reserved option. Ignoring the add_option() call!\n"; + << "] is a reserved option. Ignoring the addOption() call!\n"; // throw an exception here; } - if (m_option_help_lut.count(key)) { + if (MOptionHelpLUT.count(key)) { std::cout << "Option " << key << " has already been registered!\n"; - return m_option_help_lut[key]; + return MOptionHelpLUT[key]; } - return m_option_help_lut[key]; + return MOptionHelpLUT[key]; } std::string &query(const char *key) { - if (m_option_help_lut.count(key)) { - return m_value_lut[key]; - } else if (m_abbreviated_option_lut.count(key)) { - std::string full_key = m_abbreviated_option_lut[key]; - if (m_value_lut.count(full_key)) { - return m_value_lut[full_key]; + if (MOptionHelpLUT.count(key)) { + return MValueLUT[key]; + } else if (MAbbreviatedOptionLUT.count(key)) { + std::string full_key = MAbbreviatedOptionLUT[key]; + if (MValueLUT.count(full_key)) { + return MValueLUT[full_key]; } - return m_empty_string; + return MEmptyString; } } private: - void build_abbreviation_table() { - for (auto &o : m_option_help_lut) { + void buildAbbreviationTable() { + for (auto &o : MOptionHelpLUT) { std::string &abbr = o.second.abbreviation(); if (!abbr.empty()) { - m_abbreviated_option_lut[abbr] = o.first; + MAbbreviatedOptionLUT[abbr] = o.first; } } } - void print_help() { + void printHelp() { std::cout << "Usage:- \n"; - std::cout << " " << m_app_name << " "; + std::cout << " " << MAppName << " "; // Print all required options first - for (auto &op : m_option_help_lut) { + for (auto &op : MOptionHelpLUT) { if (op.second.required()) { std::cout << op.first << " "; switch (op.second.type()) { @@ -175,13 +177,13 @@ class cl_parser { std::cout << " "; break; case OptionType::Range: - std::cout << " "; + std::cout << " "; break; } } } // Print the optional flags next. - for (auto &op : m_option_help_lut) { + for (auto &op : MOptionHelpLUT) { if (!op.second.required()) { std::cout << "[" << op.first << " "; switch (op.second.type()) { @@ -199,14 +201,14 @@ class cl_parser { std::cout << "] "; break; case OptionType::Range: - std::cout << "] "; + std::cout << "] "; break; } } } std::cout << "\n Options supported:\n"; // Print help for all of the options - for (auto &op : m_option_help_lut) { + for (auto &op : MOptionHelpLUT) { std::stringstream help(op.second.help()); std::string help_line; bool first = true; @@ -225,10 +227,10 @@ class cl_parser { } } - bool check_options() { + bool checkOptions() { bool pass = true; - std::string prev_key; - for (auto &op : m_cl_options) { + std::string PrevKey; + for (auto &op : MCommandLineOptions) { std::size_t pos = op.find_first_of("-"); if (std::string::npos != pos) { // We have an option provided; let's check to see if it is verbose or @@ -236,49 +238,49 @@ class cl_parser { pos = op.find_first_of("-", pos + 1); if (std::string::npos != pos) { // We have a verbose option - if (op == m_reserved_key) { - print_help(); + if (op == MReservedKey) { + printHelp(); exit(-1); - } else if (m_option_help_lut.count(op) == 0) { + } else if (MOptionHelpLUT.count(op) == 0) { std::cout << "Unknown option[" << op << "]!\n"; pass = false; } - m_value_lut[op] = "true"; - prev_key = op; + MValueLUT[op] = "true"; + PrevKey = op; } else { // We have an abbreviated option - if (op == m_reserved_key_abbr) { - print_help(); + if (op == MReservedKeyAbbr) { + printHelp(); exit(-1); - } else if (m_abbreviated_option_lut.count(op) == 0) { + } else if (MAbbreviatedOptionLUT.count(op) == 0) { std::cout << "Unknown option[" << op << "] detected.\n"; pass = false; } - prev_key = m_abbreviated_option_lut[op]; - m_value_lut[prev_key] = "true"; + PrevKey = MAbbreviatedOptionLUT[op]; + MValueLUT[PrevKey] = "true"; } } else { // No idea why stringstream will decode the last \n as a "" string; this // handles that case - if (prev_key.empty() && op.empty()) + if (PrevKey.empty() && op.empty()) break; // We have an option value - if (prev_key.empty()) { + if (PrevKey.empty()) { std::cout << "Value[" << op << "] provided without specifying an option\n"; pass = false; } else { - m_value_lut[prev_key] = op; - prev_key = m_empty_string; + MValueLUT[PrevKey] = op; + PrevKey = MEmptyString; } } } - for (auto &op : m_option_help_lut) { + for (auto &op : MOptionHelpLUT) { // Check to see if an option is required; If so, check to see if there's a // value associated with it. if (op.second.required()) { - if (!m_value_lut.count(op.first)) { + if (!MValueLUT.count(op.first)) { std::cout << "Option[" << op.first << "] is required and not provided.\n"; pass = false; @@ -289,49 +291,49 @@ class cl_parser { return pass; } - std::vector m_cl_options; - cl_options_t m_option_help_lut; - key_value_t m_abbreviated_option_lut; - key_value_t m_value_lut; - std::string m_empty_string; - std::string m_reserved_key; - std::string m_reserved_key_abbr; - std::string m_app_name; + std::vector MCommandLineOptions; + CommandLineOptions_t MOptionHelpLUT; + key_value_t MAbbreviatedOptionLUT; + key_value_t MValueLUT; + std::string MEmptyString; + std::string MReservedKey; + std::string MReservedKeyAbbr; + std::string MAppName; }; -class table_model { +class TableModel { public: typedef std::map row_titles_t; - table_model() {} + TableModel() {} - void set_headers(titles_t &titles) { m_column_titles = titles; } + void setHeaders(titles_t &titles) { MColumnTitles = titles; } - table_row_t &add_row(int row, std::string &row_name) { - if (m_row_titles.count(row)) { + table_row_t &addRow(int row, std::string &row_name) { + if (MRowTitles.count(row)) { std::cout << "Warning: Row title already specified!\n"; } - m_row_titles[row] = row_name; - return m_table[row]; + MRowTitles[row] = row_name; + return MTable[row]; } - table_row_t &add_row(int row, const char *row_name) { - if (m_row_titles.count(row)) { + table_row_t &addRow(int row, const char *row_name) { + if (MRowTitles.count(row)) { std::cout << "Warning: Row title already specified!\n"; } - m_row_titles[row] = row_name; - return m_table[row]; + MRowTitles[row] = row_name; + return MTable[row]; } - table_row_t &operator[](int row) { return m_table[row]; } + table_row_t &operator[](int row) { return MTable[row]; } void print() { std::cout << std::setw(14) << " "; - for (auto &title : m_column_titles) { + for (auto &title : MColumnTitles) { std::cout << std::setw(14) << title; // Column headers } std::cout << "\n"; - for (auto &row : m_table) { - std::cout << std::setw(14) << m_row_titles[row.first]; + for (auto &row : MTable) { + std::cout << std::setw(14) << MRowTitles[row.first]; int prev_col = 0; for (auto &data : row.second) { std::cout << std::fixed << std::setw(14) << std::setprecision(0) @@ -343,49 +345,49 @@ class table_model { } private: - titles_t m_column_titles; - row_titles_t m_row_titles; - table_t m_table; + titles_t MColumnTitles; + row_titles_t MRowTitles; + table_t MTable; }; -class range_decoder { +class RangeDecoder { public: - range_decoder(std::string &range_str) : m_range(range_str) { - // Split by commas first followed by : for begin,end, step + RangeDecoder(std::string &range_str) : MRange(range_str) { + // Split by commas first followed by : for begin,end, Step std::stringstream elements(range_str); std::string element; while (std::getline(elements, element, ',')) { if (element.find_first_of("-:") == std::string::npos) { - m_elements.insert(std::stol(element)); + MElements.insert(std::stol(element)); } else { - std::stringstream r(element); - std::vector range_tokens; - std::string e, b; + std::stringstream R(element); + std::vector RangeTokens; + std::string SubStr; // Now split by : - while (std::getline(r, e, ':')) { - range_tokens.push_back(e); + while (std::getline(R, SubStr, ':')) { + RangeTokens.push_back(SubStr); } - // range_tokens should have three entries; Second entry is the step - std::cout << range_tokens[0] << ";" << range_tokens[1] << std::endl; - long step = std::stol(range_tokens[2]); - for (long i = std::stol(range_tokens[0]); - i <= std::stol(range_tokens[1]); i += step) { - m_elements.insert(i); + // RangeTokens should have three entries; Second entry is the Step + std::cout << RangeTokens[0] << ";" << RangeTokens[1] << std::endl; + long Step = std::stol(RangeTokens[2]); + for (long i = std::stol(RangeTokens[0]); i <= std::stol(RangeTokens[1]); + i += Step) { + MElements.insert(i); } } } } - std::set &decode() { return m_elements; } + std::set &decode() { return MElements; } private: - std::string m_range; - std::set m_elements; + std::string MRange; + std::set MElements; }; } // namespace utils namespace semantic { -class test_correctness { +class TestCorrectness { public: enum class SemanticTests { StringTableTest = 1, @@ -393,67 +395,66 @@ class test_correctness { NotificationTest }; - test_correctness(test::utils::cl_parser &parser) : m_parser(parser) { + TestCorrectness(test::utils::CommandLineParser &parser) : MParser(parser) { xptiInitialize("xpti", 20, 0, "xptiTests"); } void run() { - auto &v = m_parser.query("--type"); + auto &v = MParser.query("--type"); if (v != "semantic") return; - test::utils::range_decoder td(m_parser.query("--num-threads")); - m_threads = td.decode(); - test::utils::range_decoder rd(m_parser.query("--test-id")); - m_tests = rd.decode(); + test::utils::RangeDecoder td(MParser.query("--num-threads")); + MThreads = td.decode(); + test::utils::RangeDecoder rd(MParser.query("--test-id")); + MTests = rd.decode(); - run_tests(); + runTests(); } - void run_tests() { - for (auto test : m_tests) { + void runTests() { + for (auto test : MTests) { switch ((SemanticTests)test) { case SemanticTests::StringTableTest: - run_string_table_tests(); + runStringTableTests(); break; case SemanticTests::TracePointTest: - run_tracepoint_tests(); + runTracepointTests(); break; case SemanticTests::NotificationTest: - run_notification_tests(); + runNotificationTests(); break; default: std::cout << "Unknown test type [" << test << "]: use 1,2,3 or 1:3:1\n"; break; } } - m_table.print(); + MTable.print(); } private: - void run_string_table_tests(); - void run_string_table_test_threads(int run_no, int nt, - test::utils::table_model &t); - void run_tracepoint_tests(); - void run_tracepoint_test_threads(int run_no, int nt, - test::utils::table_model &t); - void run_notification_tests(); - void run_notification_test_threads(int run_no, int nt, - test::utils::table_model &t); - - test::utils::cl_parser &m_parser; - test::utils::table_model m_table; - std::set m_threads, m_tests; - long m_tracepoints; - const char *m_source = "foo.cpp"; - uint64_t m_instance_id = 0; + void runStringTableTests(); + void runStringTableTestThreads(int run_no, int nt, + test::utils::TableModel &t); + void runTracepointTests(); + void runTracepointTestThreads(int run_no, int nt, test::utils::TableModel &t); + void runNotificationTests(); + void runNotificationTestThreads(int run_no, int nt, + test::utils::TableModel &t); + + test::utils::CommandLineParser &MParser; + test::utils::TableModel MTable; + std::set MThreads, MTests; + long MTracepoints; + const char *MSource = "foo.cpp"; + uint64_t MInstanceID = 0; }; } // namespace semantic namespace performance { constexpr int MaxTracepoints = 100000; constexpr int MinTracepoints = 10; -class test_performance { +class TestPerformance { public: struct record { std::string fn; @@ -461,49 +462,49 @@ class test_performance { }; enum class PerformanceTests { DataStructureTest = 1, InstrumentationTest }; - test_performance(test::utils::cl_parser &parser) : m_parser(parser) { + TestPerformance(test::utils::CommandLineParser &parser) : MParser(parser) { xptiInitialize("xpti", 20, 0, "xptiTests"); } - std::string make_random_string(uint8_t length, std::mt19937_64 &gen) { + std::string makeRandomString(uint8_t length, std::mt19937_64 &gen) { if (length > 25) { length = 25; } // A=65, a=97 std::string s(length, '\0'); for (int i = 0; i < length; ++i) { - int ascii = m_case(gen); - int value = m_char(gen); + int ascii = MCaseU(gen); + int value = MCharU(gen); s[i] = (ascii ? value + 97 : value + 65); } return s; } void run() { - auto &v = m_parser.query("--type"); + auto &v = MParser.query("--type"); if (v != "performance") return; - test::utils::range_decoder td(m_parser.query("--num-threads")); - m_threads = td.decode(); - m_tracepoints = std::stol(m_parser.query("--trace-points")); - if (m_tracepoints > MaxTracepoints) { + test::utils::RangeDecoder td(MParser.query("--num-threads")); + MThreads = td.decode(); + MTracepoints = std::stol(MParser.query("--trace-points")); + if (MTracepoints > MaxTracepoints) { std::cout << "Reducing trace points to " << MaxTracepoints << "!\n"; - m_tracepoints = MaxTracepoints; + MTracepoints = MaxTracepoints; } - if (m_tracepoints < 0) { + if (MTracepoints < 0) { std::cout << "Setting trace points to " << MinTracepoints << "!\n"; - m_tracepoints = MinTracepoints; + MTracepoints = MinTracepoints; } - test::utils::range_decoder rd(m_parser.query("--test-id")); - m_tests = rd.decode(); + test::utils::RangeDecoder rd(MParser.query("--test-id")); + MTests = rd.decode(); - std::string dist = m_parser.query("--tp-frequency"); + std::string dist = MParser.query("--tp-frequency"); if (dist.empty()) { // By default, we assume that for every trace point that is created, we // will visit it NINE more times. - m_tp_instances = m_tracepoints * 10; + MTracepointInstances = MTracepoints * 10; } else { float value = std::stof(dist); if (value > 100) { @@ -519,98 +520,101 @@ class test_performance { // trace point create will be creating a new trace point. If it is 2%, // then every 50th trace point will create call will result in a new // trace point. - m_tp_instances = (long)((1.0 / (std::stof(dist) / 100)) * m_tracepoints); + MTracepointInstances = + (long)((1.0 / (std::stof(dist) / 100)) * MTracepoints); } // Check to see if overheads to model are set; if not assume 1.0% - dist = m_parser.query("--overhead"); + dist = MParser.query("--overhead"); if (!dist.empty()) { - m_overhead = std::stof(dist); - if (m_overhead < 0.1) { + MOverhead = std::stof(dist); + if (MOverhead < 0.1) { std::cout << "Overheads to be modeled clamped to range - 0.1%!\n"; - m_overhead = 0.1; - } else if (m_overhead > 15) { + MOverhead = 0.1; + } else if (MOverhead > 15) { std::cout << "Overheads to be modeled clamped to range - 15%!\n"; - m_overhead = 15; + MOverhead = 15; } } // If the number of trace points(TP) required to run tests on is 1000, then // we will run our string table tests on the number of TPs we compute. For a // TP frequency of 10%, we will have TP instances be 1000x10 - m_st_entries = m_tp_instances; + MStringTableEntries = MTracepointInstances; // Mersenne twister RNG engine that is uniform distribution std::random_device q_rd; std::mt19937_64 gen(q_rd()); // Generate the pseudo-random numbers for trace points and string table // random lookup - m_tp = std::uniform_int_distribution(0, m_tracepoints - 1); - m_st = std::uniform_int_distribution(0, m_st_entries - 1); - m_char = std::uniform_int_distribution(0, 25); - m_case = std::uniform_int_distribution(0, 1); - - m_rnd_st.resize(m_st_entries); - m_rnd_tp.resize(m_st_entries); - for (int i = 0; i < m_st_entries; ++i) { - m_rnd_st[i] = m_st(gen); + MTracepointU = std::uniform_int_distribution(0, MTracepoints - 1); + MStringTableU = + std::uniform_int_distribution(0, MStringTableEntries - 1); + MCharU = std::uniform_int_distribution(0, 25); + MCaseU = std::uniform_int_distribution(0, 1); + + MRndmSTIndex.resize(MStringTableEntries); + MRndmTPIndex.resize(MStringTableEntries); + for (int i = 0; i < MStringTableEntries; ++i) { + MRndmSTIndex[i] = MStringTableU(gen); } - for (int i = 0; i < m_st_entries; ++i) { - m_rnd_tp[i] = m_tp(gen); + for (int i = 0; i < MStringTableEntries; ++i) { + MRndmTPIndex[i] = MTracepointU(gen); } // Generate the strings we will be registering with the string table and // also the random lookup table for trace points - for (int i = 0; i < m_tp_instances; ++i) { + for (int i = 0; i < MTracepointInstances; ++i) { record r; - r.lookup = m_rnd_tp[i]; // 0-999999999 - std::string str = make_random_string(5, gen); + r.lookup = MRndmTPIndex[i]; // 0-999999999 + std::string str = makeRandomString(5, gen); r.fn = str + std::to_string(r.lookup); - m_records.push_back(r); - str = make_random_string(8, gen) + std::to_string(i); - m_functions.push_back(str); - str = make_random_string(8, gen) + std::to_string(i); - m_functions2.push_back(str); + MRecords.push_back(r); + str = makeRandomString(8, gen) + std::to_string(i); + MFunctions.push_back(str); + str = makeRandomString(8, gen) + std::to_string(i); + MFunctions2.push_back(str); } // Done with the setup; now run the tests - run_tests(); + runTests(); } - void run_tests() { - for (auto test : m_tests) { + void runTests() { + for (auto test : MTests) { switch ((PerformanceTests)test) { case PerformanceTests::DataStructureTest: - run_data_structure_tests(); + runDataStructureTests(); break; case PerformanceTests::InstrumentationTest: - run_instrumentation_tests(); + runInstrumentationTests(); break; default: std::cout << "Unknown test type [" << test << "]: use 1,2 or 1:2:1\n"; break; } } - m_table.print(); + MTable.print(); } private: - void run_data_structure_tests(); - void run_data_structure_tests_threads(int run_no, int nt, - test::utils::table_model &t); - void run_instrumentation_tests(); - void run_instrumentation_tests_threads(int run_no, int nt, - test::utils::table_model &t); - - test::utils::cl_parser &m_parser; - test::utils::table_model m_table; - std::set m_threads, m_tests; - long m_tracepoints; - long m_tp_instances; - long m_st_entries; - const char *m_source = "foo.cpp"; - uint64_t m_instance_id = 0; - std::uniform_int_distribution m_tp, m_st, m_char, m_case; - std::vector m_rnd_tp, m_rnd_st; - std::vector m_records; - std::vector m_functions, m_functions2; - double m_overhead = 1.0; + void runDataStructureTests(); + void runDataStructureTestsThreads(int run_no, int nt, + test::utils::TableModel &t); + void runInstrumentationTests(); + void runInstrumentationTestsThreads(int run_no, int nt, + test::utils::TableModel &t); + + test::utils::CommandLineParser &MParser; + test::utils::TableModel MTable; + std::set MThreads, MTests; + long MTracepoints; + long MTracepointInstances; + long MStringTableEntries; + const char *MSource = "foo.cpp"; + uint64_t MInstanceID = 0; + std::uniform_int_distribution MTracepointU, MStringTableU, MCharU, + MCaseU; + std::vector MRndmTPIndex, MRndmSTIndex; + std::vector MRecords; + std::vector MFunctions, MFunctions2; + double MOverhead = 1.0; }; } // namespace performance } // namespace test diff --git a/xptifw/basic_test/main.cpp b/xptifw/basic_test/main.cpp index e0215ba1c0e77..741b62ea675a1 100644 --- a/xptifw/basic_test/main.cpp +++ b/xptifw/basic_test/main.cpp @@ -10,67 +10,67 @@ // This test will expose the correctness and performance tests // through the command-line options. int main(int argc, char **argv) { - test::utils::cl_parser options; + test::utils::CommandLineParser options; - options.add_option("--verbose") - .set_abbreviation("-v") - .set_help("Run the tests in verbose mode. Running in this mode " - "may\naffect performance test metrics.\n") - .set_required(false) - .set_type(test::utils::OptionType::String); + options.addOption("--verbose") + .setAbbreviation("-v") + .setHelp("Run the tests in verbose mode. Running in this mode " + "may\naffect performance test metrics.\n") + .setRequired(false) + .setType(test::utils::OptionType::String); - options.add_option("--trace-points") - .set_abbreviation("-t") - .set_help( + options.addOption("--trace-points") + .setAbbreviation("-t") + .setHelp( "Number of trace points to use in the tests - Range [10-100000]\n") - .set_required(true) - .set_type(test::utils::OptionType::Integer); + .setRequired(true) + .setType(test::utils::OptionType::Integer); - options.add_option("--type") - .set_abbreviation("-y") - .set_help("Takes in the type of test to run. The options are:\n\n o " - "semantic\n o performance\n\nSemantic tests will ignore all " - "flags that are meant\nfor performance tests.\n") - .set_required(true) - .set_type(test::utils::OptionType::String); + options.addOption("--type") + .setAbbreviation("-y") + .setHelp("Takes in the type of test to run. The options are:\n\n o " + "semantic\n o performance\n\nSemantic tests will ignore all " + "flags that are meant\nfor performance tests.\n") + .setRequired(true) + .setType(test::utils::OptionType::String); - options.add_option("--test-id") - .set_abbreviation("-i") - .set_help( + options.addOption("--test-id") + .setAbbreviation("-i") + .setHelp( "Takes in the test identifier to run a specific test. These\ntests " "will be identifiers within the semantic or performance tests.\n") - .set_required(true) - .set_type(test::utils::OptionType::Range); + .setRequired(true) + .setType(test::utils::OptionType::Range); - options.add_option("--num-threads") - .set_abbreviation("-n") - .set_help("Number of threads to use to run the tests.\n") - .set_required(true) - .set_type(test::utils::OptionType::Range); + options.addOption("--num-threads") + .setAbbreviation("-n") + .setHelp("Number of threads to use to run the tests.\n") + .setRequired(true) + .setType(test::utils::OptionType::Range); - options.add_option("--overhead") - .set_abbreviation("-o") - .set_help("Overhead limit in percentage - Range[0.1-15]\n") - .set_required(false) - .set_type(test::utils::OptionType::Float); + options.addOption("--overhead") + .setAbbreviation("-o") + .setHelp("Overhead limit in percentage - Range[0.1-15]\n") + .setRequired(false) + .setType(test::utils::OptionType::Float); - options.add_option("--report") - .set_abbreviation("-r") - .set_help("Print the results in tabular form.\n") - .set_required(false) - .set_type(test::utils::OptionType::String); + options.addOption("--report") + .setAbbreviation("-r") + .setHelp("Print the results in tabular form.\n") + .setRequired(false) + .setType(test::utils::OptionType::String); - options.add_option("--tp-frequency") - .set_abbreviation("-f") - .set_help("Trace point creation frequency as a percentage of tracepoint " - "instances-Range [1-100]\n") - .set_required(false) - .set_type(test::utils::OptionType::Float); + options.addOption("--tp-frequency") + .setAbbreviation("-f") + .setHelp("Trace point creation frequency as a percentage of tracepoint " + "instances-Range [1-100]\n") + .setRequired(false) + .setType(test::utils::OptionType::Float); options.parse(argc, argv); - test::semantic::test_correctness ct(options); - test::performance::test_performance pt(options); + test::semantic::TestCorrectness ct(options); + test::performance::TestPerformance pt(options); ct.run(); pt.run(); diff --git a/xptifw/basic_test/performance_tests.cpp b/xptifw/basic_test/performance_tests.cpp index da3630b199db9..5f5b5935d0723 100644 --- a/xptifw/basic_test/performance_tests.cpp +++ b/xptifw/basic_test/performance_tests.cpp @@ -7,7 +7,7 @@ // //----------------------- performance_tests.cpp ----------------------------- // Tests the performance of the API and framework by running real world -// scenarios and computing the average costs and maximum events/sec that can +// scenarios and computing the average costs and maximum Events/sec that can // be serviced by the framework at a given max. overhead constraint. //--------------------------------------------------------------------------- #include @@ -24,7 +24,7 @@ #include "xpti_trace_framework.h" namespace test { -void register_callbacks(uint8_t sid); +void registerCallbacks(uint8_t sid); namespace performance { enum class DSColumns { Threads, ///< Slot used to record the number of threads @@ -52,20 +52,20 @@ enum class FWColumns { EPS2000 ///< Events/sec @ given overhead with CB handler cost of 2000ns }; -void test_performance::run_data_structure_tests_threads( - int run_no, int num_threads, test::utils::table_model &t) { +void TestPerformance::runDataStructureTestsThreads( + int RunNo, int NumThreads, test::utils::TableModel &Model) { xptiReset(); - uint64_t ns; - double ratio; + uint64_t TimeInNS; + double ElapsedTime; // If the num-threads specification includes 0, then a true serial version // outside of TBB is run - if (!num_threads) { - auto &row = t.add_row(run_no, "Serial"); - row[(int)DSColumns::Threads] = num_threads; - // Hold the string ids for measuring lookup later - std::vector ids; - ids.resize(m_tracepoints); + if (!NumThreads) { + auto &ModelRow = Model.addRow(RunNo, "Serial"); + ModelRow[(int)DSColumns::Threads] = NumThreads; + // Hold the string IDs for measuring lookup later + std::vector IDs; + IDs.resize(MTracepoints); // Columns 1, 2: Insert, 2 Lookups // Perform measurement tests to determine the cost of insertions into the // string table, the lookup costs and a composite measurement of insertion @@ -76,360 +76,370 @@ void test_performance::run_data_structure_tests_threads( // will be faster, but we rely on TBB concurrent containers to ensure they // are thread safe { - test::utils::scoped_timer timer(ns, ratio, m_tracepoints); - for (int i = 0; i < m_tracepoints; ++i) { - char *table_str = nullptr; + test::utils::ScopedTimer Timer(TimeInNS, ElapsedTime, MTracepoints); + for (int i = 0; i < MTracepoints; ++i) { + char *TableStrRef = nullptr; // Assume that the string has already been created as it is normally // provided to the Payload constructors - std::string &str = m_functions[i]; - ids[i] = xptiRegisterString(str.c_str(), &table_str); + std::string &FuncName = MFunctions[i]; + IDs[i] = xptiRegisterString(FuncName.c_str(), &TableStrRef); } } - row[(int)DSColumns::STInsert] = ratio; + ModelRow[(int)DSColumns::STInsert] = ElapsedTime; - { // lookup the created strings "m_tracepoints" randomly - test::utils::scoped_timer timer(ns, ratio, m_tracepoints * 2); - for (int i = 0; i < m_tracepoints * 2; ++i) { - int lookup = m_rnd_tp[i % m_st_entries]; - const char *lut_string = xptiLookupString(ids[lookup]); + { // lookup the created strings "MTracepoints" randomly + test::utils::ScopedTimer Timer(TimeInNS, ElapsedTime, MTracepoints * 2); + for (int i = 0; i < MTracepoints * 2; ++i) { + int LookupIndex = MRndmTPIndex[i % MStringTableEntries]; + const char *LUTStrRef = xptiLookupString(IDs[LookupIndex]); } } - row[(int)DSColumns::STLookup] = ratio; + ModelRow[(int)DSColumns::STLookup] = ElapsedTime; } // Column 3: Insert+ 2 Lookups // Perform measurement tests to determine the cost of insertion and 2 // lookups for strings added to the string table { // Create NEW "m_tracepoint" strings - std::vector new_ids; - new_ids.resize(m_tracepoints); - long no_of_operations = m_tracepoints * 3; - test::utils::scoped_timer timer(ns, ratio, no_of_operations); - for (int i = 0; i < m_tracepoints; ++i) { - char *table_str = nullptr; - std::string &str = m_functions2[i]; - new_ids.push_back(xptiRegisterString(str.c_str(), &table_str)); + std::vector NewIDs; + NewIDs.resize(MTracepoints); + long NoOfOperations = MTracepoints * 3; + test::utils::ScopedTimer Timer(TimeInNS, ElapsedTime, NoOfOperations); + for (int i = 0; i < MTracepoints; ++i) { + char *TableStrRef = nullptr; + std::string &FuncName = MFunctions2[i]; + NewIDs.push_back(xptiRegisterString(FuncName.c_str(), &TableStrRef)); } - for (int i = 0; i < m_tracepoints * 2; ++i) { - int lookup = m_rnd_tp[i % m_st_entries]; // Generates a value between - // 0-m_tracepoints-1 - const char *lut_string = xptiLookupString(ids[lookup]); + for (int i = 0; i < MTracepoints * 2; ++i) { + int LookupIndex = + MRndmTPIndex[i % MStringTableEntries]; // Generates a value between + // 0-MTracepoints-1 + const char *LUTStrRef = xptiLookupString(IDs[LookupIndex]); } } - row[(int)DSColumns::STInsertLookup] = ratio; + ModelRow[(int)DSColumns::STInsertLookup] = ElapsedTime; - std::vector uids; - std::vector events; - uids.resize(m_tracepoints); - events.resize(m_tracepoints); + std::vector UIds; + std::vector Events; + UIds.resize(MTracepoints); + Events.resize(MTracepoints); // Column 4: Measure the cost of trace point creation and cache the returned - // event and event ids + // event and event IDs { - // Create "m_tracepoints" number of trace point events - test::utils::scoped_timer timer(ns, ratio, m_tracepoints); - for (int i = 0; i < m_tracepoints; ++i) { - record &r = m_records[i]; - int lookup = r.lookup; + // Create "MTracepoints" number of trace point events + test::utils::ScopedTimer Timer(TimeInNS, ElapsedTime, MTracepoints); + for (int i = 0; i < MTracepoints; ++i) { + record &r = MRecords[i]; + int LookupIndex = r.lookup; std::string &fn = r.fn; - xpti::payload_t p = xpti::payload_t(fn.c_str(), m_source, lookup, - lookup % 80, (void *)r.lookup); - xpti::trace_event_data_t *e = xptiMakeEvent( - fn.c_str(), &p, (uint16_t)xpti::trace_event_type_t::algorithm, - xpti::trace_activity_type_t::active, &m_instance_id); - if (e) { - uids[lookup] = e->unique_id; - events[lookup] = e; + xpti::payload_t P = xpti::payload_t(fn.c_str(), MSource, LookupIndex, + LookupIndex % 80, (void *)r.lookup); + xpti::trace_event_data_t *Ev = xptiMakeEvent( + fn.c_str(), &P, (uint16_t)xpti::trace_event_type_t::algorithm, + xpti::trace_activity_type_t::active, &MInstanceID); + if (Ev) { + UIds[LookupIndex] = Ev->unique_id; + Events[LookupIndex] = Ev; } } } - row[(int)DSColumns::TPCreate] = ratio; + ModelRow[(int)DSColumns::TPCreate] = ElapsedTime; // Column 5: Measure the cost of trace point creation of previously created // trace points in an un-cached manner - { // Lookup "m_tracepoints" instances, uncached where we create the payload + { // Lookup "MTracepoints" instances, uncached where we create the payload // each time - test::utils::scoped_timer timer(ns, ratio, m_tracepoints); - for (int i = 0; i < m_tracepoints; ++i) { - record &r = m_records[i]; - uint64_t lookup = r.lookup; + test::utils::ScopedTimer Timer(TimeInNS, ElapsedTime, MTracepoints); + for (int i = 0; i < MTracepoints; ++i) { + record &r = MRecords[i]; + uint64_t LookupIndex = r.lookup; std::string &fn = r.fn; - xpti::payload_t p = xpti::payload_t(fn.c_str(), m_source, (int)lookup, - (int)lookup % 80, (void *)lookup); - xpti::trace_event_data_t *e = xptiMakeEvent( - fn.c_str(), &p, (uint16_t)xpti::trace_event_type_t::algorithm, - xpti::trace_activity_type_t::active, &m_instance_id); + xpti::payload_t P = + xpti::payload_t(fn.c_str(), MSource, (int)LookupIndex, + (int)LookupIndex % 80, (void *)LookupIndex); + xpti::trace_event_data_t *Ev = xptiMakeEvent( + fn.c_str(), &P, (uint16_t)xpti::trace_event_type_t::algorithm, + xpti::trace_activity_type_t::active, &MInstanceID); } } - row[(int)DSColumns::TPUncachedLookup] = ratio; + ModelRow[(int)DSColumns::TPUncachedLookup] = ElapsedTime; // Column 6: Measure the cost of trace point creation of previously created // trace points in an framework-cached manner - { // Lookup "m_tracepoints" instances, framework-cached - test::utils::scoped_timer timer(ns, ratio, m_tracepoints); - for (int i = 0; i < m_tracepoints; ++i) { - record &r = m_records[i]; - uint64_t lookup = r.lookup; - xpti::trace_event_data_t *e = - const_cast(xptiFindEvent(uids[lookup])); + { // Lookup "MTracepoints" instances, framework-cached + test::utils::ScopedTimer Timer(TimeInNS, ElapsedTime, MTracepoints); + for (int i = 0; i < MTracepoints; ++i) { + record &r = MRecords[i]; + uint64_t LookupIndex = r.lookup; + xpti::trace_event_data_t *Ev = const_cast( + xptiFindEvent(UIds[LookupIndex])); } } - row[(int)DSColumns::TPFWCache] = ratio; + ModelRow[(int)DSColumns::TPFWCache] = ElapsedTime; // Column 7: Measure the cost of trace point creation of previously created // and cached trace points - { // Lookup "m_tracepoints" instances, locally-cached or locally visible - test::utils::scoped_timer timer(ns, ratio, m_tp_instances); - for (int i = 0; i < m_tp_instances; ++i) { - record &r = m_records[i % m_tracepoints]; - uint64_t lookup = r.lookup; // get the random id to lookup - xpti::trace_event_data_t *e = events[lookup]; + { // Lookup "MTracepoints" instances, locally-cached or locally visible + test::utils::ScopedTimer Timer(TimeInNS, ElapsedTime, + MTracepointInstances); + for (int i = 0; i < MTracepointInstances; ++i) { + record &r = MRecords[i % MTracepoints]; + uint64_t LookupIndex = r.lookup; // get the random id to lookup + xpti::trace_event_data_t *Ev = Events[LookupIndex]; } } - row[(int)DSColumns::TPLocalCache] = ratio; - - { // Notify "m_tracepoints" number tps, locally cached - test::utils::scoped_timer timer(ns, ratio, m_tp_instances); - for (int i = 0; i < m_tp_instances; ++i) { - record &r = m_records[i % m_tracepoints]; - uint64_t lookup = r.lookup; - xpti::trace_event_data_t *e = events[lookup]; + ModelRow[(int)DSColumns::TPLocalCache] = ElapsedTime; + + { // Notify "MTracepoints" number tps, locally cached + test::utils::ScopedTimer Timer(TimeInNS, ElapsedTime, + MTracepointInstances); + for (int i = 0; i < MTracepointInstances; ++i) { + record &r = MRecords[i % MTracepoints]; + uint64_t LookupIndex = r.lookup; + xpti::trace_event_data_t *Ev = Events[LookupIndex]; xpti::framework::scoped_notify ev( "xpti", (uint16_t)xpti::trace_point_type_t::region_begin, nullptr, - e, m_instance_id, nullptr); + Ev, MInstanceID, nullptr); } } - row[(int)DSColumns::Notify] = ratio; + ModelRow[(int)DSColumns::Notify] = ElapsedTime; } else { // Now run the same performance tests in multi-threaded mode to accommodate // lock contention costs - std::string row_title = "Threads " + std::to_string(num_threads); - auto &row = t.add_row(run_no, row_title); - row[(int)DSColumns::Threads] = num_threads; + std::string RowTitle = "Threads " + std::to_string(NumThreads); + auto &ModelRow = Model.addRow(RunNo, RowTitle); + ModelRow[(int)DSColumns::Threads] = NumThreads; // Limit TBB to use the number of threads for this run - tbb::task_arena a(num_threads); + tbb::task_arena a(NumThreads); a.execute([&]() { - std::vector ids; - ids.resize(m_tracepoints); + std::vector IDs; + IDs.resize(MTracepoints); // Columns 1, 2: Insert, 2 Lookups // Perform measurement tests to determine the cost of insertions into the // string table, the lookup costs and a composite measurement of insertion // and 2 lookups for strings added to the string table { - { // Create "m_tracepoints" strings - test::utils::scoped_timer timer(ns, ratio, m_tracepoints); - tbb::parallel_for(tbb::blocked_range(0, m_tracepoints), + { // Create "MTracepoints" strings + test::utils::ScopedTimer Timer(TimeInNS, ElapsedTime, MTracepoints); + tbb::parallel_for(tbb::blocked_range(0, MTracepoints), [&](tbb::blocked_range &r) { for (int i = r.begin(); i != r.end(); ++i) { - char *table_str = nullptr; - std::string &str = m_functions[i]; - ids[i] = - xptiRegisterString(str.c_str(), &table_str); + char *TableStrRef = nullptr; + std::string &FuncName = MFunctions[i]; + IDs[i] = xptiRegisterString(FuncName.c_str(), + &TableStrRef); } }); } - row[(int)DSColumns::STInsert] = ratio; + ModelRow[(int)DSColumns::STInsert] = ElapsedTime; - { // lookup the created strings "m_tracepoints*2" linearly - test::utils::scoped_timer timer(ns, ratio, m_tracepoints * 2); - tbb::parallel_for(tbb::blocked_range(0, m_tracepoints * 2), + { // lookup the created strings "MTracepoints*2" linearly + test::utils::ScopedTimer Timer(TimeInNS, ElapsedTime, + MTracepoints * 2); + tbb::parallel_for(tbb::blocked_range(0, MTracepoints * 2), [&](tbb::blocked_range &r) { for (int i = r.begin(); i != r.end(); ++i) { - int lookup = m_rnd_tp[i % m_st_entries]; - const char *lut_string = - xptiLookupString(ids[lookup]); + int LookupIndex = + MRndmTPIndex[i % MStringTableEntries]; + const char *LUTStrRef = + xptiLookupString(IDs[LookupIndex]); } }); } - row[(int)DSColumns::STLookup] = ratio; + ModelRow[(int)DSColumns::STLookup] = ElapsedTime; } // Column 3: Insert+ 2 Lookups // Perform measurement tests to determine the cost of insertion and 2 // lookups for strings added to the string table - { // insert and lookup at the same time "m_st_entries*10" - std::vector new_ids; - new_ids.resize(m_tracepoints); + { // insert and lookup at the same time "MStringTableEntries*10" + std::vector NewIDs; + NewIDs.resize(MTracepoints); tbb::task_group g; - // 2 lookups + 1 insert of m_tracepoints elements that occurs + // 2 lookups + 1 insert of MTracepoints elements that occurs // simultaneously - long no_of_operations = m_tracepoints * 3; - test::utils::scoped_timer timer(ns, ratio, no_of_operations); + long NoOfOperations = MTracepoints * 3; + test::utils::ScopedTimer Timer(TimeInNS, ElapsedTime, NoOfOperations); g.run([&] { // Add new strings - tbb::parallel_for(tbb::blocked_range(0, m_tracepoints), + tbb::parallel_for(tbb::blocked_range(0, MTracepoints), [&](tbb::blocked_range &r) { for (int i = r.begin(); i != r.end(); ++i) { - char *table_str = nullptr; - std::string &str = m_functions2[i]; - new_ids[i] = - xptiRegisterString(str.c_str(), &table_str); + char *TableStrRef = nullptr; + std::string &FuncName = MFunctions2[i]; + NewIDs[i] = xptiRegisterString(FuncName.c_str(), + &TableStrRef); } }); }); g.run([&] { // And read previously added strings tbb::parallel_for( - tbb::blocked_range(0, m_st_entries), + tbb::blocked_range(0, MStringTableEntries), [&](tbb::blocked_range &r) { for (int i = r.begin(); i != r.end(); ++i) { - int lookup = - m_rnd_tp[i % m_st_entries]; // Generates a value between - // 0-m_tracepoints-1 + int LookupIndex = + MRndmTPIndex[i % MStringTableEntries]; // Generates a + // value between + // 0-MTracepoints-1 // Read from previously added strings by looking - // up the old IDs stored in 'ids' - const char *lut_string = xptiLookupString(ids[lookup]); + // up the old IDs stored in 'IDs' + const char *LUTStrRef = xptiLookupString(IDs[LookupIndex]); } }); }); g.wait(); } - row[(int)DSColumns::STInsertLookup] = ratio; + ModelRow[(int)DSColumns::STInsertLookup] = ElapsedTime; - std::vector uids; - std::vector events; - uids.resize(m_tracepoints); - events.resize(m_tracepoints); + std::vector UIds; + std::vector Events; + UIds.resize(MTracepoints); + Events.resize(MTracepoints); // Column 4: Measure the cost of trace point creation and cache the - // returned event and event ids - { // Create "m_tracepoints" number of trace point events - test::utils::scoped_timer timer(ns, ratio, m_tracepoints); + // returned event and event IDs + { // Create "MTracepoints" number of trace point Events + test::utils::ScopedTimer Timer(TimeInNS, ElapsedTime, MTracepoints); tbb::parallel_for( - tbb::blocked_range(0, m_tracepoints), + tbb::blocked_range(0, MTracepoints), [&](tbb::blocked_range &r) { for (int i = r.begin(); i != r.end(); ++i) { - record &r = m_records[i]; - int lookup = r.lookup; + record &r = MRecords[i]; + int LookupIndex = r.lookup; std::string &fn = r.fn; - xpti::payload_t p = - xpti::payload_t(fn.c_str(), m_source, lookup, lookup % 80, - (void *)r.lookup); - xpti::trace_event_data_t *e = xptiMakeEvent( - fn.c_str(), &p, + xpti::payload_t P = + xpti::payload_t(fn.c_str(), MSource, LookupIndex, + LookupIndex % 80, (void *)r.lookup); + xpti::trace_event_data_t *Ev = xptiMakeEvent( + fn.c_str(), &P, (uint16_t)xpti::trace_event_type_t::algorithm, - xpti::trace_activity_type_t::active, &m_instance_id); - if (e) { - uids[lookup] = e->unique_id; - events[lookup] = e; + xpti::trace_activity_type_t::active, &MInstanceID); + if (Ev) { + UIds[LookupIndex] = Ev->unique_id; + Events[LookupIndex] = Ev; } } }); } - row[(int)DSColumns::TPCreate] = ratio; + ModelRow[(int)DSColumns::TPCreate] = ElapsedTime; // Column 5: Measure the cost of trace point creation of previously // created trace points in an un-cached manner - { // Lookup "m_tracepoints" number of trace point events, uncached - test::utils::scoped_timer timer(ns, ratio, m_tracepoints); + { // Lookup "MTracepoints" number of trace point Events, uncached + test::utils::ScopedTimer Timer(TimeInNS, ElapsedTime, MTracepoints); tbb::parallel_for( - tbb::blocked_range(0, m_tracepoints), + tbb::blocked_range(0, MTracepoints), [&](tbb::blocked_range &r) { for (int i = r.begin(); i != r.end(); ++i) { - record &r = m_records[i]; - int lookup = r.lookup; + record &r = MRecords[i]; + int LookupIndex = r.lookup; std::string &fn = r.fn; - xpti::payload_t p = - xpti::payload_t(fn.c_str(), m_source, lookup, lookup % 80, - (void *)r.lookup); - xpti::trace_event_data_t *e = xptiMakeEvent( - fn.c_str(), &p, + xpti::payload_t P = + xpti::payload_t(fn.c_str(), MSource, LookupIndex, + LookupIndex % 80, (void *)r.lookup); + xpti::trace_event_data_t *Ev = xptiMakeEvent( + fn.c_str(), &P, (uint16_t)xpti::trace_event_type_t::algorithm, - xpti::trace_activity_type_t::active, &m_instance_id); + xpti::trace_activity_type_t::active, &MInstanceID); } }); } - row[(int)DSColumns::TPUncachedLookup] = ratio; + ModelRow[(int)DSColumns::TPUncachedLookup] = ElapsedTime; // Column 6: Measure the cost of trace point creation of previously // created trace points in an framework-cached manner - { // Lookup "m_tp_instances" number of trace point events, + { // Lookup "MTracepointInstances" number of trace point Events, // framework-cached - test::utils::scoped_timer timer(ns, ratio, m_tracepoints); - tbb::parallel_for(tbb::blocked_range(0, m_tracepoints), + test::utils::ScopedTimer Timer(TimeInNS, ElapsedTime, MTracepoints); + tbb::parallel_for(tbb::blocked_range(0, MTracepoints), [&](tbb::blocked_range &r) { for (int i = r.begin(); i != r.end(); ++i) { - record &r = m_records[i]; - uint64_t lookup = r.lookup; - xpti::trace_event_data_t *e = + record &r = MRecords[i]; + uint64_t LookupIndex = r.lookup; + xpti::trace_event_data_t *Ev = const_cast( - xptiFindEvent(uids[lookup])); + xptiFindEvent(UIds[LookupIndex])); } }); } - row[(int)DSColumns::TPFWCache] = ratio; + ModelRow[(int)DSColumns::TPFWCache] = ElapsedTime; // Column 7: Measure the cost of trace point creation of previously // created and cached trace points - { // Lookup "m_tracepoints" number of trace point events, locally-cached - test::utils::scoped_timer timer(ns, ratio, m_tp_instances); - tbb::parallel_for(tbb::blocked_range(0, m_tp_instances), + { // Lookup "MTracepoints" number of trace point Events, locally-cached + test::utils::ScopedTimer Timer(TimeInNS, ElapsedTime, + MTracepointInstances); + tbb::parallel_for(tbb::blocked_range(0, MTracepointInstances), [&](tbb::blocked_range &r) { for (int i = r.begin(); i != r.end(); ++i) { - record &r = m_records[i % m_tracepoints]; - uint64_t lookup = + record &r = MRecords[i % MTracepoints]; + uint64_t LookupIndex = r.lookup; // get the random id to lookup - xpti::trace_event_data_t *e = events[lookup]; + xpti::trace_event_data_t *Ev = + Events[LookupIndex]; } }); } - row[(int)DSColumns::TPLocalCache] = ratio; + ModelRow[(int)DSColumns::TPLocalCache] = ElapsedTime; - { // Notify "m_tracepoints" number tps, locally cached - test::utils::scoped_timer timer(ns, ratio, m_tp_instances); + { // Notify "MTracepoints" number tps, locally cached + test::utils::ScopedTimer Timer(TimeInNS, ElapsedTime, + MTracepointInstances); tbb::parallel_for( - tbb::blocked_range(0, m_tp_instances), + tbb::blocked_range(0, MTracepointInstances), [&](tbb::blocked_range &r) { for (int i = r.begin(); i != r.end(); ++i) { - record &r = m_records[i % m_tracepoints]; - uint64_t lookup = r.lookup; - xpti::trace_event_data_t *e = events[lookup]; + record &r = MRecords[i % MTracepoints]; + uint64_t LookupIndex = r.lookup; + xpti::trace_event_data_t *Ev = Events[LookupIndex]; xpti::framework::scoped_notify ev( "xpti", (uint16_t)xpti::trace_point_type_t::region_begin, - nullptr, e, m_instance_id, nullptr); + nullptr, Ev, MInstanceID, nullptr); } }); } - row[(int)DSColumns::Notify] = ratio; + ModelRow[(int)DSColumns::Notify] = ElapsedTime; }); } } -void test_performance::run_data_structure_tests() { - test::utils::table_model table; +void TestPerformance::runDataStructureTests() { + test::utils::TableModel Model; - test::utils::titles_t columns{"Threads", "Str.Insert", "Str.Lookup", + test::utils::titles_t Columns{"Threads", "Str.Insert", "Str.Lookup", "St.Ins/Lu", "TP Create", "TP Un-Cached", "TP FW-Cached", "TP Local", "Notify"}; - std::cout << std::setw(columns.size() * 15 / 2) + std::cout << std::setw(Columns.size() * 15 / 2) << "Data Structure Tests [FW=framework, Lu=lookup, " - "TP=Tracepoint, Time=ns\n"; - table.set_headers(columns); + "TP=Tracepoint, Time=TimeInNS\n"; + Model.setHeaders(Columns); uint8_t sid = xptiRegisterStream("xpti"); - test::register_callbacks(sid); + test::registerCallbacks(sid); - if (m_threads.size()) { - int run_no = 0; - for (auto thread : m_threads) { - run_data_structure_tests_threads(run_no++, thread, table); + if (MThreads.size()) { + int RunNo = 0; + for (auto Thread : MThreads) { + runDataStructureTestsThreads(RunNo++, Thread, Model); } } - table.print(); + Model.print(); } -void test_performance::run_instrumentation_tests_threads( - int run_no, int num_threads, test::utils::table_model &t) { +void TestPerformance::runInstrumentationTestsThreads( + int RunNo, int NumThreads, test::utils::TableModel &Model) { xptiReset(); - uint64_t ns; - double ratio; + uint64_t TimeInNS; + double ElapsedTime; std::vector tp_ids; - tp_ids.resize(m_tracepoints); - std::vector events; - events.resize(m_tracepoints); - // Variables used to compute events/sec + tp_ids.resize(MTracepoints); + std::vector Events; + Events.resize(MTracepoints); + // Variables used to compute Events/sec uint64_t events_per_sec, overhead_based_cost; std::vector> cb_handler_cost = { {FWColumns::EPS10, 10}, @@ -438,114 +448,121 @@ void test_performance::run_instrumentation_tests_threads( {FWColumns::EPS1000, 1000}, {FWColumns::EPS2000, 2000}}; - if (!num_threads) { - auto &row = t.add_row(run_no, "Serial"); - row[(int)FWColumns::Threads] = num_threads; + if (!NumThreads) { + auto &ModelRow = Model.addRow(RunNo, "Serial"); + ModelRow[(int)FWColumns::Threads] = NumThreads; { - test::utils::scoped_timer timer(ns, ratio, m_tp_instances * 2); + test::utils::ScopedTimer Timer(TimeInNS, ElapsedTime, + MTracepointInstances * 2); { - test::utils::scoped_timer timer(ns, ratio, m_tracepoints); - for (int i = 0; i < m_tracepoints; ++i) { - std::string &fn = m_functions[i]; - xpti::payload_t p(fn.c_str(), m_source, i, i % 80, (void *)i); - xpti::trace_event_data_t *e = xptiMakeEvent( - fn.c_str(), &p, (uint16_t)xpti::trace_event_type_t::algorithm, - xpti::trace_activity_type_t::active, &m_instance_id); - if (e) { - tp_ids[i] = e->unique_id; - events[i] = e; + test::utils::ScopedTimer Timer(TimeInNS, ElapsedTime, MTracepoints); + for (int i = 0; i < MTracepoints; ++i) { + std::string &fn = MFunctions[i]; + xpti::payload_t P(fn.c_str(), MSource, i, i % 80, (void *)i); + xpti::trace_event_data_t *Ev = xptiMakeEvent( + fn.c_str(), &P, (uint16_t)xpti::trace_event_type_t::algorithm, + xpti::trace_activity_type_t::active, &MInstanceID); + if (Ev) { + tp_ids[i] = Ev->unique_id; + Events[i] = Ev; } } } - row[(int)FWColumns::TPCreate] = ratio; - for (int i = 0; i < m_tp_instances; ++i) { - int lookup = m_rnd_tp[i % m_st_entries]; - xpti::trace_event_data_t *e = events[lookup]; + ModelRow[(int)FWColumns::TPCreate] = ElapsedTime; + for (int i = 0; i < MTracepointInstances; ++i) { + int LookupIndex = MRndmTPIndex[i % MStringTableEntries]; + xpti::trace_event_data_t *Ev = Events[LookupIndex]; xpti::framework::scoped_notify ev( "xpti", (uint16_t)xpti::trace_point_type_t::region_begin, nullptr, - e, m_instance_id, nullptr); + Ev, MInstanceID, nullptr); } } - row[(int)FWColumns::TPLookupAndNotify] = ratio; + ModelRow[(int)FWColumns::TPLookupAndNotify] = ElapsedTime; for (auto cost : cb_handler_cost) { // Amount of non-instrumentation based work that needs to be present for // it to meet the overhead constraints requested - overhead_based_cost = (ratio + cost.second) * (100.0 / m_overhead); - row[(int)cost.first] = 1000000000 / overhead_based_cost; + overhead_based_cost = (ElapsedTime + cost.second) * (100.0 / MOverhead); + ModelRow[(int)cost.first] = 1000000000 / overhead_based_cost; } } else { - tbb::task_arena a(num_threads); + tbb::task_arena a(NumThreads); - std::string row_title = "Threads " + std::to_string(num_threads); - auto &row = t.add_row(run_no, row_title); - row[(int)FWColumns::Threads] = num_threads; + std::string RowTitle = "Threads " + std::to_string(NumThreads); + auto &ModelRow = Model.addRow(RunNo, RowTitle); + ModelRow[(int)FWColumns::Threads] = NumThreads; { - test::utils::scoped_timer timer(ns, ratio, m_tp_instances * 2); + test::utils::ScopedTimer Timer(TimeInNS, ElapsedTime, + MTracepointInstances * 2); a.execute([&]() { { - test::utils::scoped_timer timer(ns, ratio, m_tracepoints); + test::utils::ScopedTimer Timer(TimeInNS, ElapsedTime, MTracepoints); tbb::parallel_for( - tbb::blocked_range(0, m_tracepoints), + tbb::blocked_range(0, MTracepoints), [&](tbb::blocked_range &r) { for (int i = r.begin(); i != r.end(); ++i) { - std::string &fn = m_functions[i]; - xpti::payload_t p(fn.c_str(), m_source, i, i % 80, (void *)i); - xpti::trace_event_data_t *e = xptiMakeEvent( - fn.c_str(), &p, + std::string &fn = MFunctions[i]; + xpti::payload_t P(fn.c_str(), MSource, i, i % 80, (void *)i); + xpti::trace_event_data_t *Ev = xptiMakeEvent( + fn.c_str(), &P, (uint16_t)xpti::trace_event_type_t::algorithm, - xpti::trace_activity_type_t::active, &m_instance_id); - if (e) { - tp_ids[i] = e->unique_id; - events[i] = e; + xpti::trace_activity_type_t::active, &MInstanceID); + if (Ev) { + tp_ids[i] = Ev->unique_id; + Events[i] = Ev; } } }); } - row[(int)FWColumns::TPCreate] = ratio; + ModelRow[(int)FWColumns::TPCreate] = ElapsedTime; tbb::parallel_for( - tbb::blocked_range(0, m_tp_instances), + tbb::blocked_range(0, MTracepointInstances), [&](tbb::blocked_range &r) { for (int i = r.begin(); i != r.end(); ++i) { - record &r = m_records[i % m_tracepoints]; - uint64_t lookup = r.lookup; - xpti::trace_event_data_t *e = events[lookup]; + record &r = MRecords[i % MTracepoints]; + uint64_t LookupIndex = r.lookup; + xpti::trace_event_data_t *Ev = Events[LookupIndex]; xpti::framework::scoped_notify ev( "xpti", (uint16_t)xpti::trace_point_type_t::region_begin, - nullptr, e, m_instance_id, nullptr); + nullptr, Ev, MInstanceID, nullptr); } }); }); } - row[(int)FWColumns::TPLookupAndNotify] = ratio; + ModelRow[(int)FWColumns::TPLookupAndNotify] = ElapsedTime; for (auto cost : cb_handler_cost) { // Amount of non-instrumentation based work that needs to be present for // it to meet the overhead constraints requested - overhead_based_cost = (ratio + cost.second) * (100.0 / m_overhead); - row[(int)cost.first] = 1000000000 / overhead_based_cost; + overhead_based_cost = (ElapsedTime + cost.second) * (100.0 / MOverhead); + ModelRow[(int)cost.first] = 1000000000 / overhead_based_cost; } } } -void test_performance::run_instrumentation_tests() { - test::utils::table_model table; - - test::utils::titles_t columns{ - "Threads", "TP LU+Notify(ns)", "TP Create(ns)", "Ev/s,cb=10", - "Ev/s,cb=100", "Ev/s,cb=500", "Ev/s,cb=1000", "Ev/s,cb=2000"}; - std::cout << std::setw(columns.size() * 15 / 2) << "Framework Tests\n"; - table.set_headers(columns); +void TestPerformance::runInstrumentationTests() { + test::utils::TableModel Model; + + test::utils::titles_t Columns{"Threads", + "TP LU+Notify(TimeInNS)", + "TP Create(TimeInNS)", + "Ev/s,cb=10", + "Ev/s,cb=100", + "Ev/s,cb=500", + "Ev/s,cb=1000", + "Ev/s,cb=2000"}; + std::cout << std::setw(Columns.size() * 15 / 2) << "Framework Tests\n"; + Model.setHeaders(Columns); uint8_t sid = xptiRegisterStream("xpti"); - test::register_callbacks(sid); + test::registerCallbacks(sid); - if (m_threads.size()) { - int run_no = 0; - for (auto thread : m_threads) { - run_instrumentation_tests_threads(run_no++, thread, table); + if (MThreads.size()) { + int RunNo = 0; + for (auto Thread : MThreads) { + runInstrumentationTestsThreads(RunNo++, Thread, Model); } } - table.print(); + Model.print(); } } // namespace performance diff --git a/xptifw/basic_test/semantic_tests.cpp b/xptifw/basic_test/semantic_tests.cpp index f873f6c675dc1..bf9dea477502f 100644 --- a/xptifw/basic_test/semantic_tests.cpp +++ b/xptifw/basic_test/semantic_tests.cpp @@ -22,47 +22,48 @@ #include "cl_processor.hpp" #include "xpti_trace_framework.h" -static void tp_cb(uint16_t trace_type, xpti::trace_event_data_t *parent, - xpti::trace_event_data_t *event, uint64_t instance, - const void *ud) {} +static void tpCallback(uint16_t trace_type, xpti::trace_event_data_t *parent, + xpti::trace_event_data_t *event, uint64_t instance, + const void *ud) {} namespace test { -void register_callbacks(uint8_t sid) { +void registerCallbacks(uint8_t sid) { xptiRegisterCallback(sid, (uint16_t)xpti::trace_point_type_t::graph_create, - tp_cb); + tpCallback); xptiRegisterCallback(sid, (uint16_t)xpti::trace_point_type_t::node_create, - tp_cb); + tpCallback); xptiRegisterCallback(sid, (uint16_t)xpti::trace_point_type_t::edge_create, - tp_cb); + tpCallback); xptiRegisterCallback(sid, (uint16_t)xpti::trace_point_type_t::region_begin, - tp_cb); + tpCallback); xptiRegisterCallback(sid, (uint16_t)xpti::trace_point_type_t::region_end, - tp_cb); + tpCallback); xptiRegisterCallback(sid, (uint16_t)xpti::trace_point_type_t::task_begin, - tp_cb); + tpCallback); xptiRegisterCallback(sid, (uint16_t)xpti::trace_point_type_t::task_end, - tp_cb); + tpCallback); xptiRegisterCallback(sid, (uint16_t)xpti::trace_point_type_t::barrier_begin, - tp_cb); + tpCallback); xptiRegisterCallback(sid, (uint16_t)xpti::trace_point_type_t::barrier_end, - tp_cb); + tpCallback); xptiRegisterCallback(sid, (uint16_t)xpti::trace_point_type_t::lock_begin, - tp_cb); + tpCallback); xptiRegisterCallback(sid, (uint16_t)xpti::trace_point_type_t::lock_end, - tp_cb); + tpCallback); xptiRegisterCallback(sid, (uint16_t)xpti::trace_point_type_t::transfer_begin, - tp_cb); + tpCallback); xptiRegisterCallback(sid, (uint16_t)xpti::trace_point_type_t::transfer_end, - tp_cb); + tpCallback); xptiRegisterCallback(sid, (uint16_t)xpti::trace_point_type_t::thread_begin, - tp_cb); + tpCallback); xptiRegisterCallback(sid, (uint16_t)xpti::trace_point_type_t::thread_end, - tp_cb); + tpCallback); xptiRegisterCallback(sid, (uint16_t)xpti::trace_point_type_t::wait_begin, - tp_cb); + tpCallback); xptiRegisterCallback(sid, (uint16_t)xpti::trace_point_type_t::wait_end, - tp_cb); - xptiRegisterCallback(sid, (uint16_t)xpti::trace_point_type_t::signal, tp_cb); + tpCallback); + xptiRegisterCallback(sid, (uint16_t)xpti::trace_point_type_t::signal, + tpCallback); } // The semantic namespace contains tests to determine the correctness of the // implementation. The test ensure that the framework is robust under serial and @@ -87,424 +88,424 @@ enum class TPColumns { enum class NColumns { Threads, Notifications, PassRate }; -void test_correctness::run_string_table_test_threads( - int run_no, int num_threads, test::utils::table_model &t) { +void TestCorrectness::runStringTableTestThreads( + int RunNo, int NumThreads, test::utils::TableModel &Model) { xptiReset(); - constexpr int num_strings = 1000; - - if (!num_threads) { - std::vector strings; - std::vector ids; - ids.resize(num_strings); - strings.resize(num_strings); - for (int i = 0; i < num_strings; ++i) { - char *table_str = nullptr; - std::string str = "Function" + std::to_string(i); - ids[i] = xptiRegisterString(str.c_str(), &table_str); - strings[i] = table_str; + constexpr int NumStrings = 1000; + + if (!NumThreads) { + std::vector Strings; + std::vector IDs; + IDs.resize(NumStrings); + Strings.resize(NumStrings); + for (int i = 0; i < NumStrings; ++i) { + char *TableStrRef = nullptr; + std::string StrName = "Function" + std::to_string(i); + IDs[i] = xptiRegisterString(StrName.c_str(), &TableStrRef); + Strings[i] = TableStrRef; } - auto &row = t.add_row(run_no, "Serial"); - row[(int)STColumns::Threads] = num_threads; - row[(int)STColumns::Insertions] = (long double)strings.size(); - int lookup_count = 0; - for (int i = 0; i < strings.size(); ++i) { - const char *table_str = xptiLookupString(ids[i]); - if (table_str == strings[i]) - ++lookup_count; + auto &ModelRow = Model.addRow(RunNo, "Serial"); + ModelRow[(int)STColumns::Threads] = NumThreads; + ModelRow[(int)STColumns::Insertions] = (long double)Strings.size(); + int LookupCount = 0; + for (int i = 0; i < Strings.size(); ++i) { + const char *TableStrRef = xptiLookupString(IDs[i]); + if (TableStrRef == Strings[i]) + ++LookupCount; } - row[(int)STColumns::Lookups] = lookup_count; - int duplicate_count = 0; - for (int i = 0; i < strings.size(); ++i) { - char *table_str = nullptr; - std::string str = "Function" + std::to_string(i); - xpti::string_id_t id = xptiRegisterString(str.c_str(), &table_str); - if (str == table_str && id == ids[i] && table_str == strings[i]) - ++duplicate_count; + ModelRow[(int)STColumns::Lookups] = LookupCount; + int DuplicateCount = 0; + for (int i = 0; i < Strings.size(); ++i) { + char *TableStrRef = nullptr; + std::string StrName = "Function" + std::to_string(i); + xpti::string_id_t id = xptiRegisterString(StrName.c_str(), &TableStrRef); + if (StrName == TableStrRef && id == IDs[i] && TableStrRef == Strings[i]) + ++DuplicateCount; } - row[(int)STColumns::DuplicateInserts] = duplicate_count; - row[(int)STColumns::PassRate] = - (double)(strings.size() + lookup_count + duplicate_count) / - (num_strings * 3) * 100; + ModelRow[(int)STColumns::DuplicateInserts] = DuplicateCount; + ModelRow[(int)STColumns::PassRate] = + (double)(Strings.size() + LookupCount + DuplicateCount) / + (NumStrings * 3) * 100; } else { - tbb::task_arena a(num_threads); + tbb::task_arena a(NumThreads); a.execute([&]() { - std::vector strings; - std::vector ids; - strings.resize(num_strings); - ids.resize(num_strings); - tbb::parallel_for(tbb::blocked_range(0, num_strings), - [&](tbb::blocked_range &r) { - for (int i = r.begin(); i != r.end(); ++i) { - char *table_str = nullptr; - std::string str = "Function" + std::to_string(i); - ids[i] = - xptiRegisterString(str.c_str(), &table_str); - strings[i] = table_str; - } - }); + std::vector Strings; + std::vector IDs; + Strings.resize(NumStrings); + IDs.resize(NumStrings); + tbb::parallel_for( + tbb::blocked_range(0, NumStrings), + [&](tbb::blocked_range &r) { + for (int i = r.begin(); i != r.end(); ++i) { + char *TableStrRef = nullptr; + std::string StrName = "Function" + std::to_string(i); + IDs[i] = xptiRegisterString(StrName.c_str(), &TableStrRef); + Strings[i] = TableStrRef; + } + }); - std::string row_title = "Threads " + std::to_string(num_threads); - auto &row = t.add_row(run_no, row_title); - row[(int)STColumns::Threads] = num_threads; - row[(int)STColumns::Insertions] = (long double)strings.size(); - std::atomic lookup_count = {0}, duplicate_count = {0}; - tbb::parallel_for(tbb::blocked_range(0, num_strings), + std::string RowTitle = "Threads " + std::to_string(NumThreads); + auto &ModelRow = Model.addRow(RunNo, RowTitle); + ModelRow[(int)STColumns::Threads] = NumThreads; + ModelRow[(int)STColumns::Insertions] = (long double)Strings.size(); + std::atomic LookupCount = {0}, DuplicateCount = {0}; + tbb::parallel_for(tbb::blocked_range(0, NumStrings), [&](tbb::blocked_range &r) { for (int i = r.begin(); i != r.end(); ++i) { - const char *table_str = xptiLookupString(ids[i]); - if (table_str == strings[i]) - ++lookup_count; + const char *TableStrRef = xptiLookupString(IDs[i]); + if (TableStrRef == Strings[i]) + ++LookupCount; } }); - row[(int)STColumns::Lookups] = lookup_count; - tbb::parallel_for(tbb::blocked_range(0, num_strings), + ModelRow[(int)STColumns::Lookups] = LookupCount; + tbb::parallel_for(tbb::blocked_range(0, NumStrings), [&](tbb::blocked_range &r) { for (int i = r.begin(); i != r.end(); ++i) { - char *table_str = nullptr; - std::string str = "Function" + std::to_string(i); - xpti::string_id_t id = - xptiRegisterString(str.c_str(), &table_str); - if (str == table_str && id == ids[i] && - table_str == strings[i]) - ++duplicate_count; + char *TableStrRef = nullptr; + std::string StrName = + "Function" + std::to_string(i); + xpti::string_id_t id = xptiRegisterString( + StrName.c_str(), &TableStrRef); + if (StrName == TableStrRef && id == IDs[i] && + TableStrRef == Strings[i]) + ++DuplicateCount; } }); - row[(int)STColumns::DuplicateInserts] = duplicate_count; + ModelRow[(int)STColumns::DuplicateInserts] = DuplicateCount; - row[(int)STColumns::PassRate] = - (double)(strings.size() + lookup_count + duplicate_count) / - (num_strings * 3) * 100; + ModelRow[(int)STColumns::PassRate] = + (double)(Strings.size() + LookupCount + DuplicateCount) / + (NumStrings * 3) * 100; }); } } -void test_correctness::run_string_table_tests() { - test::utils::table_model table; +void TestCorrectness::runStringTableTests() { + test::utils::TableModel Model; - test::utils::titles_t columns{"Threads", "Insert", "Lookup", "Duplicate", + test::utils::titles_t Columns{"Threads", "Insert", "Lookup", "Duplicate", "Pass rate"}; std::cout << std::setw(25) << "String Table Tests\n"; - table.set_headers(columns); + Model.setHeaders(Columns); - if (m_threads.size()) { - int run_no = 0; - for (auto thread : m_threads) { - run_string_table_test_threads(run_no++, thread, table); + if (MThreads.size()) { + int RunNo = 0; + for (auto Thread : MThreads) { + runStringTableTestThreads(RunNo++, Thread, Model); } } - table.print(); + Model.print(); } -void test_correctness::run_tracepoint_test_threads( - int run_no, int num_threads, test::utils::table_model &t) { +void TestCorrectness::runTracepointTestThreads(int RunNo, int NumThreads, + test::utils::TableModel &Model) { xptiReset(); - constexpr int tracepoint_count = 1000; + constexpr int TracepointCount = 1000; - if (!num_threads) { - std::vector payloads; - std::vector uids; - std::vector events; - payloads.resize(tracepoint_count); - uids.resize(tracepoint_count); - events.resize(tracepoint_count); + if (!NumThreads) { + std::vector Payloads; + std::vector UIds; + std::vector Events; + Payloads.resize(TracepointCount); + UIds.resize(TracepointCount); + Events.resize(TracepointCount); - for (uint64_t i = 0; i < tracepoint_count; ++i) { + for (uint64_t i = 0; i < TracepointCount; ++i) { std::string fn = "Function" + std::to_string(i); - xpti::payload_t p = xpti::payload_t(fn.c_str(), m_source, (int)i, + xpti::payload_t P = xpti::payload_t(fn.c_str(), MSource, (int)i, (int)(i % 80), (void *)i); - xpti::trace_event_data_t *e = xptiMakeEvent( - fn.c_str(), &p, (uint16_t)xpti::trace_event_type_t::algorithm, - xpti::trace_activity_type_t::active, &m_instance_id); - if (e) { - uids[i] = e->unique_id; - payloads[i] = e->reserved.payload; - events[i] = e; + xpti::trace_event_data_t *Ev = xptiMakeEvent( + fn.c_str(), &P, (uint16_t)xpti::trace_event_type_t::algorithm, + xpti::trace_activity_type_t::active, &MInstanceID); + if (Ev) { + UIds[i] = Ev->unique_id; + Payloads[i] = Ev->reserved.payload; + Events[i] = Ev; } } - auto &row = t.add_row(run_no, "Serial"); - row[(int)TPColumns::Threads] = num_threads; - row[(int)TPColumns::Insertions] = (long double)events.size(); - - std::atomic lookup_count = {0}; - for (int i = 0; i < events.size(); ++i) { - const xpti::trace_event_data_t *e = xptiFindEvent(uids[i]); - if (e && e->unique_id == uids[i]) - ++lookup_count; + auto &ModelRow = Model.addRow(RunNo, "Serial"); + ModelRow[(int)TPColumns::Threads] = NumThreads; + ModelRow[(int)TPColumns::Insertions] = (long double)Events.size(); + + std::atomic LookupCount = {0}; + for (int i = 0; i < Events.size(); ++i) { + const xpti::trace_event_data_t *Ev = xptiFindEvent(UIds[i]); + if (Ev && Ev->unique_id == UIds[i]) + ++LookupCount; } - row[(int)TPColumns::Lookups] = lookup_count; - std::atomic duplicate_count = {0}; - std::atomic payload_count = {0}; - for (uint64_t i = 0; i < events.size(); ++i) { + ModelRow[(int)TPColumns::Lookups] = LookupCount; + std::atomic DuplicateCount = {0}; + std::atomic PayloadCount = {0}; + for (uint64_t i = 0; i < Events.size(); ++i) { std::string fn = "Function" + std::to_string(i); - xpti::payload_t p = - xpti::payload_t(fn.c_str(), m_source, (int)i, (int)i % 80, (void *)i); - xpti::trace_event_data_t *e = xptiMakeEvent( - fn.c_str(), &p, (uint16_t)xpti::trace_event_type_t::algorithm, - xpti::trace_activity_type_t::active, &m_instance_id); - if (e) { - if (e->unique_id == uids[i]) { - ++duplicate_count; + xpti::payload_t P = + xpti::payload_t(fn.c_str(), MSource, (int)i, (int)i % 80, (void *)i); + xpti::trace_event_data_t *Ev = xptiMakeEvent( + fn.c_str(), &P, (uint16_t)xpti::trace_event_type_t::algorithm, + xpti::trace_activity_type_t::active, &MInstanceID); + if (Ev) { + if (Ev->unique_id == UIds[i]) { + ++DuplicateCount; } - xpti::payload_t *rp = e->reserved.payload; - if (e->unique_id == uids[i] && rp && - std::string(rp->name) == std::string(p.name) && - std::string(rp->source_file) == std::string(p.source_file) && - rp->line_no == p.line_no && rp->column_no == p.column_no) - ++payload_count; + xpti::payload_t *RP = Ev->reserved.payload; + if (Ev->unique_id == UIds[i] && RP && + std::string(RP->name) == std::string(P.name) && + std::string(RP->source_file) == std::string(P.source_file) && + RP->line_no == P.line_no && RP->column_no == P.column_no) + ++PayloadCount; } } - row[(int)TPColumns::DuplicateInserts] = duplicate_count; - row[(int)TPColumns::PayloadLookup] = payload_count; - row[(int)TPColumns::PassRate] = (double)(events.size() + lookup_count + - duplicate_count + payload_count) / - (tracepoint_count * 4) * 100; + ModelRow[(int)TPColumns::DuplicateInserts] = DuplicateCount; + ModelRow[(int)TPColumns::PayloadLookup] = PayloadCount; + ModelRow[(int)TPColumns::PassRate] = + (double)(Events.size() + LookupCount + DuplicateCount + PayloadCount) / + (TracepointCount * 4) * 100; } else { - tbb::task_arena a(num_threads); + tbb::task_arena a(NumThreads); a.execute([&]() { - std::vector payloads; - std::vector uids; - std::vector events; - payloads.resize(tracepoint_count); - uids.resize(tracepoint_count); - events.resize(tracepoint_count); - - tbb::spin_mutex m_lock; + std::vector Payloads; + std::vector UIds; + std::vector Events; + Payloads.resize(TracepointCount); + UIds.resize(TracepointCount); + Events.resize(TracepointCount); + + tbb::spin_mutex MLock; tbb::parallel_for( - tbb::blocked_range(0, tracepoint_count), + tbb::blocked_range(0, TracepointCount), [&](tbb::blocked_range &r) { for (uint64_t i = r.begin(); i != r.end(); ++i) { std::string fn = "Function" + std::to_string(i); - xpti::payload_t p = xpti::payload_t(fn.c_str(), m_source, (int)i, + xpti::payload_t P = xpti::payload_t(fn.c_str(), MSource, (int)i, (int)i % 80, (void *)i); - xpti::trace_event_data_t *e = xptiMakeEvent( - fn.c_str(), &p, (uint16_t)xpti::trace_event_type_t::algorithm, - xpti::trace_activity_type_t::active, &m_instance_id); - if (e) { - uids[i] = e->unique_id; - payloads[i] = e->reserved.payload; - events[i] = e; + xpti::trace_event_data_t *Ev = xptiMakeEvent( + fn.c_str(), &P, (uint16_t)xpti::trace_event_type_t::algorithm, + xpti::trace_activity_type_t::active, &MInstanceID); + if (Ev) { + UIds[i] = Ev->unique_id; + Payloads[i] = Ev->reserved.payload; + Events[i] = Ev; } } }); - std::string row_title = "Threads " + std::to_string(num_threads); - auto &row = t.add_row(run_no, row_title); - row[(int)TPColumns::Threads] = num_threads; - row[(int)TPColumns::Insertions] = (long double)events.size(); - std::atomic lookup_count = {0}, duplicate_count = {0}, - payload_count = {0}; - tbb::parallel_for(tbb::blocked_range(0, tracepoint_count), + std::string RowTitle = "Threads " + std::to_string(NumThreads); + auto &ModelRow = Model.addRow(RunNo, RowTitle); + ModelRow[(int)TPColumns::Threads] = NumThreads; + ModelRow[(int)TPColumns::Insertions] = (long double)Events.size(); + std::atomic LookupCount = {0}, DuplicateCount = {0}, + PayloadCount = {0}; + tbb::parallel_for(tbb::blocked_range(0, TracepointCount), [&](tbb::blocked_range &r) { for (int i = r.begin(); i != r.end(); ++i) { - const xpti::trace_event_data_t *e = - xptiFindEvent(uids[i]); - if (e && e->unique_id == uids[i]) - lookup_count++; + const xpti::trace_event_data_t *Ev = + xptiFindEvent(UIds[i]); + if (Ev && Ev->unique_id == UIds[i]) + LookupCount++; } }); - row[(int)TPColumns::Lookups] = lookup_count; + ModelRow[(int)TPColumns::Lookups] = LookupCount; tbb::parallel_for( - tbb::blocked_range(0, tracepoint_count), + tbb::blocked_range(0, TracepointCount), [&](tbb::blocked_range &r) { for (uint64_t i = r.begin(); i != r.end(); ++i) { std::string fn = "Function" + std::to_string(i); - xpti::payload_t p = xpti::payload_t(fn.c_str(), m_source, (int)i, + xpti::payload_t P = xpti::payload_t(fn.c_str(), MSource, (int)i, (int)i % 80, (void *)i); - xpti::trace_event_data_t *e = xptiMakeEvent( - fn.c_str(), &p, (uint16_t)xpti::trace_event_type_t::algorithm, - xpti::trace_activity_type_t::active, &m_instance_id); - if (e) { - if (e->unique_id == uids[i]) { - ++duplicate_count; + xpti::trace_event_data_t *Ev = xptiMakeEvent( + fn.c_str(), &P, (uint16_t)xpti::trace_event_type_t::algorithm, + xpti::trace_activity_type_t::active, &MInstanceID); + if (Ev) { + if (Ev->unique_id == UIds[i]) { + ++DuplicateCount; } - xpti::payload_t *rp = e->reserved.payload; - if (e->unique_id == uids[i] && rp && - std::string(rp->name) == std::string(p.name) && - std::string(rp->source_file) == - std::string(p.source_file) && - rp->line_no == p.line_no && rp->column_no == p.column_no) - ++payload_count; + xpti::payload_t *RP = Ev->reserved.payload; + if (Ev->unique_id == UIds[i] && RP && + std::string(RP->name) == std::string(P.name) && + std::string(RP->source_file) == + std::string(P.source_file) && + RP->line_no == P.line_no && RP->column_no == P.column_no) + ++PayloadCount; } } }); - row[(int)TPColumns::DuplicateInserts] = duplicate_count; - row[(int)TPColumns::PayloadLookup] = payload_count; - row[(int)TPColumns::PassRate] = - (double)(events.size() + lookup_count + duplicate_count + - payload_count) / - (tracepoint_count * 4) * 100; + ModelRow[(int)TPColumns::DuplicateInserts] = DuplicateCount; + ModelRow[(int)TPColumns::PayloadLookup] = PayloadCount; + ModelRow[(int)TPColumns::PassRate] = + (double)(Events.size() + LookupCount + DuplicateCount + + PayloadCount) / + (TracepointCount * 4) * 100; }); } } -void test_correctness::run_tracepoint_tests() { - test::utils::table_model table; +void TestCorrectness::runTracepointTests() { + test::utils::TableModel Model; - test::utils::titles_t columns{"Threads", "Create", "Lookup", + test::utils::titles_t Columns{"Threads", "Create", "Lookup", "Duplicate", "Payload", "Pass rate"}; std::cout << std::setw(25) << "Tracepoint Tests\n"; - table.set_headers(columns); + Model.setHeaders(Columns); - if (m_threads.size()) { - int run_no = 0; - for (auto thread : m_threads) { - run_tracepoint_test_threads(run_no++, thread, table); + if (MThreads.size()) { + int RunNo = 0; + for (auto Thread : MThreads) { + runTracepointTestThreads(RunNo++, Thread, Model); } } - table.print(); + Model.print(); } -void test_correctness::run_notification_test_threads( - int run_no, int num_threads, test::utils::table_model &t) { +void TestCorrectness::runNotificationTestThreads( + int RunNo, int NumThreads, test::utils::TableModel &Model) { xptiReset(); - int tp_count = 30, callback_count = tp_count * 30; - std::vector payloads; - std::vector uids; - std::vector events; - payloads.resize(tp_count); - uids.resize(tp_count); - events.resize(tp_count); + int TPCount = 30, CallbackCount = TPCount * 30; + std::vector Payloads; + std::vector UIds; + std::vector Events; + Payloads.resize(TPCount); + UIds.resize(TPCount); + Events.resize(TPCount); - if (!num_threads) { + if (!NumThreads) { // assumes tp creation is thread safe - std::atomic notify_count = {0}; - for (uint64_t i = 0; i < tp_count; ++i) { - int index = (int)i; + std::atomic NotifyCount = {0}; + for (uint64_t i = 0; i < TPCount; ++i) { + int Index = (int)i; std::string fn = "Function" + std::to_string(i); - xpti::payload_t p = xpti::payload_t(fn.c_str(), m_source, index, - index % 80, (void *)(i % 10)); - xpti::trace_event_data_t *e = xptiMakeEvent( - fn.c_str(), &p, (uint16_t)xpti::trace_event_type_t::algorithm, - xpti::trace_activity_type_t::active, &m_instance_id); - if (e) { - uids[index] = e->unique_id; - payloads[index] = e->reserved.payload; - events[index] = e; + xpti::payload_t P = xpti::payload_t(fn.c_str(), MSource, Index, + Index % 80, (void *)(i % 10)); + xpti::trace_event_data_t *Ev = xptiMakeEvent( + fn.c_str(), &P, (uint16_t)xpti::trace_event_type_t::algorithm, + xpti::trace_activity_type_t::active, &MInstanceID); + if (Ev) { + UIds[Index] = Ev->unique_id; + Payloads[Index] = Ev->reserved.payload; + Events[Index] = Ev; } - notify_count++; + NotifyCount++; } - auto &row = t.add_row(run_no, "Serial"); - row[(int)NColumns::Threads] = num_threads; - - for (int i = tp_count; i < callback_count; ++i) { - int index = (int)i % tp_count; - void *addr = (void *)(index % 10); - std::string fn = "Function" + std::to_string(index); - xpti::payload_t p = xpti::payload_t(fn.c_str(), m_source, (int)index, - (int)index % 80, addr); - xpti::trace_event_data_t *e = xptiMakeEvent( - fn.c_str(), &p, (uint16_t)xpti::trace_event_type_t::algorithm, - xpti::trace_activity_type_t::active, &m_instance_id); - if (e && e->unique_id == uids[index]) { - uint8_t tp = (index % 10) + 1; - uint16_t tp_type = (uint16_t)(tp << 1); - xpti::framework::scoped_notify ev("xpti", tp_type, nullptr, e, - m_instance_id, nullptr); - notify_count++; + auto &ModelRow = Model.addRow(RunNo, "Serial"); + ModelRow[(int)NColumns::Threads] = NumThreads; + + for (int i = TPCount; i < CallbackCount; ++i) { + int Index = (int)i % TPCount; + void *Address = (void *)(Index % 10); + std::string fn = "Function" + std::to_string(Index); + xpti::payload_t P = xpti::payload_t(fn.c_str(), MSource, (int)Index, + (int)Index % 80, Address); + xpti::trace_event_data_t *Ev = xptiMakeEvent( + fn.c_str(), &P, (uint16_t)xpti::trace_event_type_t::algorithm, + xpti::trace_activity_type_t::active, &MInstanceID); + if (Ev && Ev->unique_id == UIds[Index]) { + uint8_t TP = (Index % 10) + 1; + uint16_t TPType = (uint16_t)(TP << 1); + xpti::framework::scoped_notify ev("xpti", TPType, nullptr, Ev, + MInstanceID, nullptr); + NotifyCount++; } } - uint64_t acc = 0; - for (int i = 0; i < tp_count; ++i) { - acc += events[i]->instance_id; + uint64_t Acc = 0; + for (int i = 0; i < TPCount; ++i) { + Acc += Events[i]->instance_id; } - // Accumulator contains 'callback_count' number of + // Accumulator contains 'CallbackCount' number of // instances that are invoked after creation, so - // each event has 101 instances * tp_count = 1010 + // each event has 101 instances * TPCount = 1010 // - // total instances = callback_count + tp_count; + // total instances = CallbackCount + TPCount; - row[(int)NColumns::Notifications] = (long double)acc; - row[(int)NColumns::PassRate] = (long double)(acc) / (notify_count)*100; + ModelRow[(int)NColumns::Notifications] = (long double)Acc; + ModelRow[(int)NColumns::PassRate] = (long double)(Acc) / (NotifyCount)*100; } else { - tbb::task_arena a(num_threads); + tbb::task_arena a(NumThreads); a.execute([&]() { - std::atomic notify_count = {0}; - tbb::spin_mutex m_lock; + std::atomic NotifyCount = {0}; + tbb::spin_mutex MLock; tbb::parallel_for( - tbb::blocked_range(0, tp_count), - [&](tbb::blocked_range &r) { + tbb::blocked_range(0, TPCount), [&](tbb::blocked_range &r) { for (uint64_t i = r.begin(); i != r.end(); ++i) { - int index = (int)i; + int Index = (int)i; std::string fn = "Function" + std::to_string(i); - xpti::payload_t p = - xpti::payload_t(fn.c_str(), m_source, (int)index, - (int)index % 80, (void *)(i % 10)); - xpti::trace_event_data_t *e = xptiMakeEvent( - fn.c_str(), &p, (uint16_t)xpti::trace_event_type_t::algorithm, - xpti::trace_activity_type_t::active, &m_instance_id); - if (e) { - uids[index] = e->unique_id; - payloads[index] = e->reserved.payload; - events[index] = e; + xpti::payload_t P = + xpti::payload_t(fn.c_str(), MSource, (int)Index, + (int)Index % 80, (void *)(i % 10)); + xpti::trace_event_data_t *Ev = xptiMakeEvent( + fn.c_str(), &P, (uint16_t)xpti::trace_event_type_t::algorithm, + xpti::trace_activity_type_t::active, &MInstanceID); + if (Ev) { + UIds[Index] = Ev->unique_id; + Payloads[Index] = Ev->reserved.payload; + Events[Index] = Ev; } - ++notify_count; + ++NotifyCount; } }); - std::string row_title = "Threads " + std::to_string(num_threads); - auto &row = t.add_row(run_no, row_title); - row[(int)NColumns::Threads] = num_threads; + std::string RowTitle = "Threads " + std::to_string(NumThreads); + auto &ModelRow = Model.addRow(RunNo, RowTitle); + ModelRow[(int)NColumns::Threads] = NumThreads; tbb::parallel_for( - tbb::blocked_range(tp_count, callback_count), + tbb::blocked_range(TPCount, CallbackCount), [&](tbb::blocked_range &r) { for (int i = r.begin(); i != r.end(); ++i) { - int index = (int)i % tp_count; - void *addr = (void *)(index % 10); - std::string fn = "Function" + std::to_string(index); - xpti::payload_t p = xpti::payload_t( - fn.c_str(), m_source, (int)index, (int)index % 80, addr); - xpti::trace_event_data_t *e = xptiMakeEvent( - fn.c_str(), &p, (uint16_t)xpti::trace_event_type_t::algorithm, - xpti::trace_activity_type_t::active, &m_instance_id); - if (e && e->unique_id == uids[index]) { - uint8_t tp = (index % 10) + 1; - uint16_t tp_type = (uint16_t)(tp << 1); - xpti::framework::scoped_notify ev("xpti", tp_type, nullptr, e, - m_instance_id, nullptr); - notify_count++; + int Index = (int)i % TPCount; + void *Address = (void *)(Index % 10); + std::string fn = "Function" + std::to_string(Index); + xpti::payload_t P = xpti::payload_t( + fn.c_str(), MSource, (int)Index, (int)Index % 80, Address); + xpti::trace_event_data_t *Ev = xptiMakeEvent( + fn.c_str(), &P, (uint16_t)xpti::trace_event_type_t::algorithm, + xpti::trace_activity_type_t::active, &MInstanceID); + if (Ev && Ev->unique_id == UIds[Index]) { + uint8_t TP = (Index % 10) + 1; + uint16_t TPType = (uint16_t)(TP << 1); + xpti::framework::scoped_notify ev("xpti", TPType, nullptr, Ev, + MInstanceID, nullptr); + NotifyCount++; } } }); - uint64_t acc = 0; - for (int i = 0; i < tp_count; ++i) { - acc += events[i]->instance_id; + uint64_t Acc = 0; + for (int i = 0; i < TPCount; ++i) { + Acc += Events[i]->instance_id; } - row[(int)NColumns::Notifications] = (long double)acc; - row[(int)NColumns::PassRate] = (double)(acc) / (notify_count)*100; + ModelRow[(int)NColumns::Notifications] = (long double)Acc; + ModelRow[(int)NColumns::PassRate] = (double)(Acc) / (NotifyCount)*100; }); } } -void test_correctness::run_notification_tests() { - test::utils::table_model table; +void TestCorrectness::runNotificationTests() { + test::utils::TableModel Model; - test::utils::titles_t columns{"Threads", "Notify", "Pass rate"}; + test::utils::titles_t Columns{"Threads", "Notify", "Pass rate"}; std::cout << std::setw(25) << "Notification Tests\n"; - table.set_headers(columns); + Model.setHeaders(Columns); - uint8_t sid = xptiRegisterStream("xpti"); + uint8_t SID = xptiRegisterStream("xpti"); // We do not need to register callback for correctness tests - if (m_threads.size()) { - int run_no = 0; - for (auto thread : m_threads) { - run_notification_test_threads(run_no++, thread, table); + if (MThreads.size()) { + int RunNo = 0; + for (auto Thread : MThreads) { + runNotificationTestThreads(RunNo++, Thread, Model); } } - table.print(); + Model.print(); } } // namespace semantic diff --git a/xptifw/doc/XPTI_Framework.md b/xptifw/doc/XPTI_Framework.md index 90ff0485cb229..c8ae89969b2d4 100644 --- a/xptifw/doc/XPTI_Framework.md +++ b/xptifw/doc/XPTI_Framework.md @@ -509,20 +509,34 @@ overhead at 1.5% and for every trace point created, it will be visited twice. A description of the command line arguments is provided in detail below: > **--type, -y [`required`]** -> - This flag takes in the type of tests that need to be run. The allowed options are **[semantic, performance]**. -> - **semantic**: Runs semantic tests that test the correctness of the framework operations and they are split into three separate tests. +> - This flag takes in the type of tests that need to be run. The allowed +options are **[semantic, performance]**. +> - **semantic**: Runs semantic tests that test the correctness of the +framework operations and they are split into three separate tests. > 1. Performs string table tests on a 1000 strings -> 2. Performs tests on trace point events by checking to see if the same event is returned for the same payload and so on. -> 3. Performs notification tests to see if trace events are notified correctly and record the instances of notifications per events. -> - **performance**: Runs performance tests on the provided input configuration and these tests measure the cost of various operations used in the framework. These tests are split into two separate tests. -> 1. Data structure tests that capture the average cost of string table inserts, lookups, trace point event creation and lookup using the same payload or `unique_id` for the event and notification. -> 2. Runs instrumentation tests and projects the number of events that can be serviced per second using the configuration provided on the command line. These tests are where the **--overhead** and **--tp-frequency** arguments are used. +> 2. Performs tests on trace point events by checking to see if the same +event is returned for the same payload and so on. +> 3. Performs notification tests to see if trace events are notified +correctly and record the instances of notifications per events. +> - **performance**: Runs performance tests on the provided input +configuration and these tests measure the cost of various operations used in +the framework. These tests are split into two separate tests. +> 1. Data structure tests that capture the average cost of string table +inserts, lookups, trace point event creation and lookup using the same +payload or `unique_id` for the event and notification. +> 2. Runs instrumentation tests and projects the number of events that can +be serviced per second using the configuration provided on the command line. +These tests are where the **--overhead** and **--tp-frequency** arguments are +used. > **--trace-points, -t [`required`]** -> - Number of trace point events to create and use for the test. The expected range is **[10-100000]**. +> - Number of trace point events to create and use for the test. The expected +range is **[10-100000]**. > **--test-id, -i [`required`]** -> - Takes in a list of tests that are comma separated and runs the requested tests. This command line argument takes in a range as well and the format is described below: +> - Takes in a list of tests that are comma separated and runs the requested +tests. This command line argument takes in a range as well and the format is +described below: > 1. Comma separated sequence: --test-id 1,2,3 > 2. Range description: --test-id 1:3:1 @@ -532,25 +546,40 @@ A description of the command line arguments is provided in detail below: > 2. Range description: --num-threads 0:2:1,4:16:4 > **--tp-frequency, --f** -> - The trace point creation frequency basically allows the test to determine the total number of trace point visits to perform for every trace point event that is created. If the trace point creation frequency is 10%, then every trace point event that is created must be visited 10 times. Since we know how many trace point events were requested from the command line (**--trace-points N**), we multiply this value (N) by 100/f where f = trace point frequency in percent to get the total number of trace point visits. -> - So, if number of trace points is 5000 and trace point frequency is 10%, the total number of trace point visits the test is going to perform is 5000 x 1/0.1 = 50000 +> - The trace point creation frequency basically allows the test to +determine the total number of trace point visits to perform for every trace +point event that is created. If the trace point creation frequency is 10%, +then every trace point event that is created must be visited 10 times. Since +we know how many trace point events were requested from the command line +(**--trace-points N**), we multiply this value (N) by 100/f where f = trace +point frequency in percent to get the total number of trace point visits. +> - So, if number of trace points is 5000 and trace point frequency is 10%, +the total number of trace point visits the test is going to perform is 5000 x +1/0.1 = 50000 >**--overhead** -> - The overhead input allows the test framework to use the measured performance of the trace point creation and notification to come up with an estimate of how many events can be serviced per second with the given configuration. +> - The overhead input allows the test framework to use the measured +performance of the trace point creation and notification to come up with an +estimate of how many events can be serviced per second with the given +configuration. > - The default overheads for which the events/sec are computed is **1%** > - If the overheads desired is 1%, then the following formula is used to compute the events/sec: >

total cost of instrumentation (I) = (cost of trace point creation + cost of notification)

>

So, if --trace-points 5000 --tp-frequency 10, this will be:

>

I = 5000xCost(TP Create) + 50000xCost(Notify)

>

Average cost (A) = I/50000, for 50000 events notified

->

This cost A does not take into account the cost of the callback handler. In our projections, we use a handler cost of 10ns, 100ns and 500ns to get the events/sec that can be serviced. On an average, the handler costs for real-world cases will be somewhere between 80ns-400ns. +>

This cost A does not take into account the cost of the callback +handler. In our projections, we use a handler cost of 10ns, 100ns and 500ns +to get the events/sec that can be serviced. On an average, the handler costs +for real-world cases will be somewhere between 80ns-400ns. >

So, if the average cost is A and this is 1% overhead, the total run time must be 100xA ns

>

Events/second E = 1000,000,000 ns/(100xA)ns

> Using the metrics described above, we run the tests with varying overheads and trace point creation frequencies to determine the maximum number of events -that can be serviced for that configuration. Some sample configurations are shown below: +that can be serviced for that configuration. Some sample configurations are +shown below: - Configuration where each trace point event created is only visited **once** ```bash @@ -568,11 +597,17 @@ that can be serviced for that configuration. Some sample configurations are show ## Modeling and projection In order to determine the number of events that the framework can service in a -second, the performance tests use the following approach. If the total instrumentation cost is 1µs and for this cost to be under 1% total overhead, the amount of work that needs to be accomplished for every trace event would be 1µs x 100 = 100µs. In this case, the maximum number of events that can be notified/serviced would be: +second, the performance tests use the following approach. If the total +instrumentation cost is 1µs and for this cost to be under 1% total +overhead, the amount of work that needs to be accomplished for every trace +event would be 1µs x 100 = 100µs. In this case, the maximum number +of events that can be notified/serviced would be: 1 sec/100µs = 1000000µs/100µs = 10000 events/sec -The total instrumentation cost would include *some of the time in the infrastructure in the framework* and the *cost of handling each notification through callbacks* in the subscriber. +The total instrumentation cost would include *some of the time in the +infrastructure in the framework* and the *cost of handling each notification +through callbacks* in the subscriber. ### Computing the cost incurred in the framework @@ -584,19 +619,30 @@ shown below. ```bash run_test --trace-points 10000 --type performance --num-threads 0,1,2,4 --test-id 1,2 --tp-frequency 10 -We take average cost of a trace point event creation and the cost of 10 notifications for each such event as it is visited 10 times to form the basis of the cost incurred within the framework. This information is reported by the performance test. The total instrumentation cost as discussed in the previous section comprises of a framework cost and a callback handler cost in the subscriber. +We take average cost of a trace point event creation and the cost of 10 +notifications for each such event as it is visited 10 times to form the basis +of the cost incurred within the framework. This information is reported by +the performance test. The total instrumentation cost as discussed in the +previous section comprises of a framework cost and a callback handler cost in +the subscriber. - Framework cost **FW*****cost*** = Avg{TP*create* + 10 x TP*notify*} +Framework cost **FW*****cost*** = Avg{TP*create* + 10 x TP*notify*} - Subscriber cost **Callback*****cost*** = **C*t*** which could be anywhere in the range [10-10000]ns +Subscriber cost **Callback*****cost*** = **C*t*** which could be anywhere in the range [10-10000]ns - Total cost **Cost*****total*** = **FW*****cost*** + **C*t*** +Total cost **Cost*****total*** = **FW*****cost*** + **C*t*** -Using the information from the report or one such instance captured in the table above, we know that: +Using the information from the report or one such instance captured in the +table above, we know that: **FW*****cost*** = ~55ns -Using different values for **C*t*** = [10, 100, 500, 1000]ns, we get the table that shows the events/sec that can be serviced for total instrumentation cost for the configuration. It can be noticed that as the callback handler costs increase, the events/sec is inversely proportional to the callback handler costs. The work unit cost for determining the number of events/sec is given by: +Using different values for **C*t*** = [10, 100, 500, 1000]ns, we +get the table that shows the events/sec that can be serviced for total +instrumentation cost for the configuration. It can be noticed that as the +callback handler costs increase, the events/sec is inversely proportional to +the callback handler costs. The work unit cost for determining the number of +events/sec is given by: **W*****cost*** = **100** x [**FW*****cost*** + **C*t***] for the configuration that limits overheads to 1%. diff --git a/xptifw/include/xpti_int64_hash_table.hpp b/xptifw/include/xpti_int64_hash_table.hpp index ea6d30ad5cad1..9042b08df32d2 100644 --- a/xptifw/include/xpti_int64_hash_table.hpp +++ b/xptifw/include/xpti_int64_hash_table.hpp @@ -31,98 +31,98 @@ class Hash64x64Table { using ht_lut_t = tbb::concurrent_hash_map; Hash64x64Table(int size = 1024) - : m_forward(size), m_reverse(size), m_table_size(size) { + : MForward(size), MReverse(size), MTableSize(size) { #ifdef XPTI_STATISTICS - m_insert = 0; - m_lookup = 0; + MInsertions = 0; + MRetrievals = 0; #endif } ~Hash64x64Table() { - m_forward.clear(); - m_reverse.clear(); + MForward.clear(); + MReverse.clear(); } // Clear all the contents of this hash table and get it ready for re-use void clear() { - m_forward.clear(); - m_reverse.clear(); - m_forward.rehash(m_table_size); - m_reverse.rehash(m_table_size); + MForward.clear(); + MReverse.clear(); + MForward.rehash(MTableSize); + MReverse.rehash(MTableSize); #ifdef XPTI_STATISTICS - m_insert = 0; - m_lookup = 0; + MInsertions = 0; + MRetrievals = 0; #endif } - // Check to see if a particular key is already present in the table; + // Check to see if a particular Key is already present in the table; // - // On success, the value for the key will be returned. If not, + // On success, the value for the Key will be returned. If not, // xpti::invalid_id will be returned. - int64_t find(int64_t key) { + int64_t find(int64_t Key) { // Try to read it, if already present ht_lut_t::const_accessor e; - if (m_forward.find(e, key)) { + if (MForward.find(e, Key)) { #ifdef XPTI_STATISTICS - m_lookup++; + MRetrievals++; #endif return e->second; // We found it, so we return the value } else return xpti::invalid_id; } - // Add a pair to the hash table. If the key already exists, this + // Add a pair to the hash table. If the Key already exists, this // call returns even if the value happens to be different this time. // - // If the key does not exists, then the key is inserted into the hash map and - // the reverse lookup populated with the pair. - void add(int64_t key, int64_t value) { + // If the Key does not exist, then the Key is inserted into the hash map and + // the reverse lookup populated with the pair. + void add(int64_t Key, int64_t Value) { // Try to read it, if already present ht_lut_t::const_accessor e; - if (m_forward.find(e, key)) { + if (MForward.find(e, Key)) { #ifdef XPTI_STATISTICS - m_lookup++; + MRetrievals++; #endif } else { // Multiple threads could fall through here // Release the reader lock held; e.release(); { // Employ a double-check pattern here - tbb::spin_mutex::scoped_lock dc(m_mutex); + tbb::spin_mutex::scoped_lock dc(MMutex); ht_lut_t::accessor f; - if (m_forward.insert(f, key)) { - // The key does not exist, so we will add the key-value pair to the + if (MForward.insert(f, Key)) { + // The Key does not exist, so we will add the Key-Value pair to the // hash map - f->second = value; + f->second = Value; #ifdef XPTI_STATISTICS - m_insert++; + MInsertions++; #endif // When we insert a new entry into the table, we also need to build // the reverse lookup; { ht_lut_t::accessor r; - if (m_reverse.insert(r, value)) { + if (MReverse.insert(r, Value)) { // An entry does not exist, so we will add it to the reverse // lookup. - r->second = key; + r->second = Key; f.release(); r.release(); } } } - // else, we do not add the key-value pair as the key already exists in + // else, we do not add the Key-Value pair as the Key already exists in // the table! } } } - // The reverse query allows one to get the value from the key that may have + // The reverse query allows one to get the Value from the Key that may have // been cached somewhere. - int64_t reverseFind(int64_t value) { + int64_t reverseFind(int64_t Value) { ht_lut_t::const_accessor e; - if (m_reverse.find(e, value)) { + if (MReverse.find(e, Value)) { #ifdef XPTI_STATISTICS - m_lookup++; + MRetrievals++; #endif return e->second; } else @@ -131,20 +131,20 @@ class Hash64x64Table { void printStatistics() { #ifdef XPTI_STATISTICS - printf("Hash table inserts : [%llu]\n", m_insert.load()); - printf("Hash table lookups : [%llu]\n", m_lookup.load()); + printf("Hash table inserts : [%llu]\n", MInsertions.load()); + printf("Hash table lookups : [%llu]\n", MRetrievals.load()); #endif } private: - ht_lut_t m_forward; ///< Forward lookup hash map - ht_lut_t m_reverse; ///< Reverse lookup hash map - int32_t m_table_size; ///< Initial size of the hash map + ht_lut_t MForward; ///< Forward lookup hash map + ht_lut_t MReverse; ///< Reverse lookup hash map + int32_t MTableSize; ///< Initial size of the hash map tbb::spin_mutex - m_mutex; ///< Mutex required to implement a double-check pattern + MMutex; ///< Mutex required to implement a double-check pattern #ifdef XPTI_STATISTICS - safe_uint64_t m_insert, ///< Thread-safe tracking of insertions - m_lookup; ///< Thread-safe tracking of lookups + safe_uint64_t MInsertions, ///< Thread-safe tracking of insertions + MRetrievals; ///< Thread-safe tracking of lookups #endif }; @@ -162,118 +162,118 @@ class Hash64x64Table { using ht_lut_t = std::unordered_map; Hash64x64Table(int size = 1024) - : m_forward(size), m_reverse(size), m_table_size(size) { + : MForward(size), MReverse(size), MTableSize(size) { #ifdef XPTI_STATISTICS - m_insert = 0; - m_lookup = 0; + MInsertions = 0; + MRetrievals = 0; #endif } ~Hash64x64Table() { - m_forward.clear(); - m_reverse.clear(); + MForward.clear(); + MReverse.clear(); } // Clear all the contents of this hash table and get it ready for re-use void clear() { - m_forward.clear(); - m_reverse.clear(); + MForward.clear(); + MReverse.clear(); #ifdef XPTI_STATISTICS - m_insert = 0; - m_lookup = 0; + MInsertions = 0; + MRetrievals = 0; #endif } - // Check to see if a particular key is already present in the table; + // Check to see if a particular Key is already present in the table; // - // On success, the value for the key will be returned. If not, + // On success, the Value for the Key will be returned. If not, // xpti::invalid_id will be returned. - int64_t find(int64_t key) { - std::lock_guard lock(m_mutex); + int64_t find(int64_t Key) { + std::lock_guard Lock(MMutex); // Try to read it, if already present - auto key_loc = m_forward.find(key); - if (key_loc != m_forward.end()) { + auto KeyLoc = MForward.find(Key); + if (KeyLoc != MForward.end()) { #ifdef XPTI_STATISTICS - m_lookup++; + MRetrievals++; #endif - return key_loc->second; // We found it, so we return the value + return KeyLoc->second; // We found it, so we return the Value } else return xpti::invalid_id; } - // Add a pair to the hash table. If the key already exists, this - // call returns even if the value happens to be different this time. + // Add a pair to the hash table. If the Key already exists, this + // call returns even if the Value happens to be different this time. // - // If the key does not exists, then the key is inserted into the hash map and - // the reverse lookup populated with the pair. - void add(int64_t key, int64_t value) { + // If the Key does not exist, then the Key is inserted into the hash map and + // the reverse lookup populated with the pair. + void add(int64_t Key, int64_t Value) { // Try to read it, if already present - auto key_loc = m_forward.find(key); - if (key_loc != m_forward.end()) { + auto KeyLoc = MForward.find(Key); + if (KeyLoc != MForward.end()) { #ifdef XPTI_STATISTICS - m_lookup++; + MRetrievals++; #endif } else { // Multiple threads could fall through here { // Employ a double-check pattern here - std::lock_guard lock(m_mutex); - auto key_loc = m_forward.find(key); - if (key_loc == m_forward.end()) { - // The key does not exist, so we will add the key-value pair to the + std::lock_guard Lock(MMutex); + auto KeyLoc = MForward.find(Key); + if (KeyLoc == MForward.end()) { + // The Key does not exist, so we will add the Key-Value pair to the // hash map - m_forward[key] = value; - key_loc = m_forward.find(key); + MForward[Key] = Value; + KeyLoc = MForward.find(Key); #ifdef XPTI_STATISTICS - m_insert++; + MInsertions++; #endif // When we insert a new entry into the table, we also need to build // the reverse lookup; { - auto val_loc = m_reverse.find(value); - if (val_loc == m_reverse.end()) { + auto ValLoc = MReverse.find(Value); + if (ValLoc == MReverse.end()) { // An entry does not exist, so we will add it to the reverse // lookup. - m_reverse[value] = key; + MReverse[Value] = Key; } else { - m_forward.erase(key_loc); + MForward.erase(KeyLoc); } } } - // else, we do not add the key-value pair as the key already exists in + // else, we do not add the Key-Value pair as the Key already exists in // the table! } } } - // The reverse query allows one to get the value from the key that may have + // The reverse query allows one to get the Value from the Key that may have // been cached somewhere. - int64_t reverseFind(int64_t value) { - std::lock_guard lock(m_mutex); - auto val_loc = m_reverse.find(value); - if (val_loc != m_reverse.end()) { + int64_t reverseFind(int64_t Value) { + std::lock_guard Lock(MMutex); + auto ValLoc = MReverse.find(Value); + if (ValLoc != MReverse.end()) { #ifdef XPTI_STATISTICS - m_lookup++; + MRetrievals++; #endif - return val_loc->second; + return ValLoc->second; } else return xpti::invalid_id; } void printStatistics() { #ifdef XPTI_STATISTICS - printf("Hash table inserts : [%llu]\n", m_insert.load()); - printf("Hash table lookups : [%llu]\n", m_lookup.load()); + printf("Hash table inserts : [%llu]\n", MInsertions.load()); + printf("Hash table lookups : [%llu]\n", MRetrievals.load()); #endif } private: - ht_lut_t m_forward; ///< Forward lookup hash map - ht_lut_t m_reverse; ///< Reverse lookup hash map - int32_t m_table_size; ///< Initial size of the hash map - std::mutex m_mutex; ///< Mutex required to implement a double-check pattern + ht_lut_t MForward; ///< Forward lookup hash map + ht_lut_t MReverse; ///< Reverse lookup hash map + int32_t MTableSize; ///< Initial size of the hash map + std::mutex MMutex; ///< Mutex required to implement a double-check pattern #ifdef XPTI_STATISTICS - safe_uint64_t m_insert, ///< Thread-safe tracking of insertions - m_lookup; ///< Thread-safe tracking of lookups + safe_uint64_t MInsertions, ///< Thread-safe tracking of insertions + MRetrievals; ///< Thread-safe tracking of lookups #endif }; #endif diff --git a/xptifw/include/xpti_string_table.hpp b/xptifw/include/xpti_string_table.hpp index 8a4ccb94a1ffd..a7ffae215fce3 100644 --- a/xptifw/include/xpti_string_table.hpp +++ b/xptifw/include/xpti_string_table.hpp @@ -34,25 +34,25 @@ class StringTable { using st_reverse_t = tbb::concurrent_hash_map; StringTable(int size = 4096) - : m_str2id(size), m_id2str(size), m_table_size(size) { - m_ids = 1; + : MStringToID(size), MIDToString(size), MTableSize(size) { + MIds = 1; #ifdef XPTI_STATISTICS - m_insert = 0; - m_lookup = 0; + MInsertions = 0; + MRetrievals = 0; #endif } // Clear all the contents of this string table and get it ready for re-use void clear() { - m_ids = {1}; - m_id2str.clear(); - m_str2id.clear(); + MIds = {1}; + MIDToString.clear(); + MStringToID.clear(); - m_id2str.rehash(m_table_size); - m_str2id.rehash(m_table_size); + MIDToString.rehash(MTableSize); + MStringToID.rehash(MTableSize); #ifdef XPTI_STATISTICS - m_insert = 0; - m_lookup = 0; + MInsertions = 0; + MRetrievals = 0; #endif } @@ -67,8 +67,8 @@ class StringTable { if (!str) return xpti::invalid_id; - std::string local_str = str; - return add(local_str, ref_str); + std::string LocalStr = str; + return add(LocalStr, ref_str); } xpti::string_id_t add(std::string str, const char **ref_str = nullptr) { @@ -77,9 +77,9 @@ class StringTable { // Try to see if the string is already present in the string table st_forward_t::const_accessor e; - if (m_str2id.find(e, str)) { + if (MStringToID.find(e, str)) { #ifdef XPTI_STATISTICS - m_lookup++; + MRetrievals++; #endif if (ref_str) *ref_str = e->first.c_str(); @@ -92,21 +92,21 @@ class StringTable { string_id_t id; { // Employ a double-check pattern here - tbb::spin_mutex::scoped_lock dc(m_mutex); + tbb::spin_mutex::scoped_lock dc(MMutex); st_forward_t::accessor f; - if (m_str2id.insert(f, str)) { + if (MStringToID.insert(f, str)) { // If the string does not exist, then insert() returns true. Here we // create an ID for it - id = m_ids++; + id = MIds++; f->second = id; #ifdef XPTI_STATISTICS - m_insert++; + MInsertions++; #endif // When we insert a new entry into the table, we also need to build // the reverse lookup; { st_reverse_t::accessor r; - if (m_id2str.insert(r, id)) { + if (MIDToString.insert(r, id)) { // An entry does not exist, so we will add it to the reverse // lookup. r->second = f->first.c_str(); @@ -115,12 +115,12 @@ class StringTable { *ref_str = r->second; f.release(); r.release(); - m_strings++; + MStrings++; return id; } else { // We cannot have a case where a string is not present in the // forward lookup and present in the reverse lookup - m_str2id.erase(f); + MStringToID.erase(f); if (ref_str) *ref_str = nullptr; @@ -132,13 +132,13 @@ class StringTable { // The string has already been added, so we return the stored ID id = f->second; #ifdef XPTI_STATISTICS - m_lookup++; + MRetrievals++; #endif if (ref_str) *ref_str = f->first.c_str(); return id; } - // Both the accessor and m_mutex will be released here! + // Both the accessor and MMutex will be released here! } } return xpti::invalid_id; @@ -148,40 +148,41 @@ class StringTable { // may have been cached somewhere. const char *query(xpti::string_id_t id) { st_reverse_t::const_accessor e; - if (m_id2str.find(e, id)) { + if (MIDToString.find(e, id)) { #ifdef XPTI_STATISTICS - m_lookup++; + MRetrievals++; #endif return e->second; } else return nullptr; } - int32_t count() { return (int32_t)m_strings; } + int32_t count() { return (int32_t)MStrings; } - const st_reverse_t &table() { return m_id2str; } + const st_reverse_t &table() { return MIDToString; } void printStatistics() { #ifdef XPTI_STATISTICS - printf("String table inserts: [%llu]\n", m_insert.load()); - printf("String table lookups: [%llu]\n", m_lookup.load()); + printf("String table inserts: [%llu]\n", MInsertions.load()); + printf("String table lookups: [%llu]\n", MRetrievals.load()); #endif } private: - safe_int32_t m_ids; ///< Thread-safe ID generator - st_forward_t m_str2id; ///< Forward lookup hash map - st_reverse_t m_id2str; ///< Reverse lookup hash map - int32_t m_table_size; ///< Initial table size of the hash-map - tbb::spin_mutex m_mutex; ///< Mutex required for double-check pattern - safe_int32_t m_strings; ///< The count of strings in the table + safe_int32_t MIds; ///< Thread-safe ID generator + st_forward_t MStringToID; ///< Forward lookup hash map + st_reverse_t MIDToString; ///< Reverse lookup hash map + int32_t MTableSize; ///< Initial table size of the hash-map + tbb::spin_mutex MMutex; ///< Mutex required for double-check pattern + safe_int32_t MStrings; ///< The count of strings in the table #ifdef XPTI_STATISTICS - safe_uint64_t m_insert, ///< Thread-safe tracking of insertions - m_lookup; ///< Thread-safe tracking of lookups + safe_uint64_t MInsertions, ///< Thread-safe tracking of insertions + MRetrievals; ///< Thread-safe tracking of lookups #endif }; } // namespace xpti -#else +#else // Non-TBB implementation follows + namespace xpti { /// \brief A string table class to support the payload handling /// \details With each payload, a kernel/function name and the source file name @@ -195,23 +196,23 @@ class StringTable { using st_reverse_t = std::unordered_map; StringTable(int size = 4096) - : m_str2id(size), m_id2str(size), m_table_size(size) { - m_ids = 1; + : MStringToID(size), MIDToString(size), MTableSize(size) { + MIds = 1; #ifdef XPTI_STATISTICS - m_insert = 0; - m_lookup = 0; + MInsertions = 0; + MRetrievals = 0; #endif } // Clear all the contents of this string table and get it ready for re-use void clear() { - m_ids = {1}; - m_id2str.clear(); - m_str2id.clear(); + MIds = {1}; + MIDToString.clear(); + MStringToID.clear(); #ifdef XPTI_STATISTICS - m_insert = 0; - m_lookup = 0; + MInsertions = 0; + MRetrievals = 0; #endif } @@ -226,8 +227,8 @@ class StringTable { if (!str) return xpti::invalid_id; - std::string local_str = str; - return add(local_str, ref_str); + std::string LocalStr = str; + return add(LocalStr, ref_str); } xpti::string_id_t add(std::string str, const char **ref_str = nullptr) { @@ -235,49 +236,50 @@ class StringTable { return xpti::invalid_id; // Try to see if the string is already present in the string table - auto loc = m_str2id.find(str); - if (loc != m_str2id.end()) { + auto Loc = MStringToID.find(str); + if (Loc != MStringToID.end()) { #ifdef XPTI_STATISTICS - m_lookup++; + MRetrievals++; #endif if (ref_str) - *ref_str = loc->first.c_str(); + *ref_str = Loc->first.c_str(); // We found it, so we return the string ID - return loc->second; + return Loc->second; } else { + // String not in the table // Multiple threads could fall through here - string_id_t id; + string_id_t StrID; { // Employ a double-check pattern here - std::lock_guard lock(m_mutex); - auto loc = m_str2id.find(str); + std::lock_guard lock(MMutex); + auto Loc = MStringToID.find(str); // String not present in the table - if (loc == m_str2id.end()) { + if (Loc == MStringToID.end()) { // Add it - id = m_ids++; - m_str2id[str] = id; - loc = m_str2id.find(str); + StrID = MIds++; + MStringToID[str] = StrID; + Loc = MStringToID.find(str); if (ref_str) - *ref_str = loc->first.c_str(); + *ref_str = Loc->first.c_str(); #ifdef XPTI_STATISTICS - m_insert++; + MInsertions++; #endif // When we insert a new entry into the table, we also need to build // the reverse lookup; { - auto id_loc = m_id2str.find(id); - if (id_loc == m_id2str.end()) { + auto IDLoc = MIDToString.find(StrID); + if (IDLoc == MIDToString.end()) { // An entry does not exist, so we will add it to the reverse // lookup. - m_id2str[id] = loc->first.c_str(); + MIDToString[StrID] = Loc->first.c_str(); // Cache the saved string address and send it to the caller - m_strings++; - return id; + MStrings++; + return StrID; } else { // We cannot have a case where a string is not present in the // forward lookup and present in the reverse lookup - m_str2id.erase(loc); + MStringToID.erase(Loc); if (ref_str) *ref_str = nullptr; @@ -287,15 +289,15 @@ class StringTable { } else { // The string has already been added, so we return the stored ID - id = loc->second; + StrID = Loc->second; #ifdef XPTI_STATISTICS - m_lookup++; + MRetrievals++; #endif if (ref_str) - *ref_str = loc->first.c_str(); - return id; + *ref_str = Loc->first.c_str(); + return StrID; } - // The m_mutex will be released here! + // The MMutex will be released here! } } return xpti::invalid_id; @@ -304,39 +306,39 @@ class StringTable { // The reverse query allows one to get the string from the string_id_t that // may have been cached somewhere. const char *query(xpti::string_id_t id) { - std::lock_guard lock(m_mutex); - auto loc = m_id2str.find(id); - if (loc != m_id2str.end()) { + std::lock_guard lock(MMutex); + auto Loc = MIDToString.find(id); + if (Loc != MIDToString.end()) { #ifdef XPTI_STATISTICS - m_lookup++; + MRetrievals++; #endif - return loc->second; + return Loc->second; } else return nullptr; } - int32_t count() { return (int32_t)m_strings; } + int32_t count() { return (int32_t)MStrings; } - const st_reverse_t &table() { return m_id2str; } + const st_reverse_t &table() { return MIDToString; } void printStatistics() { #ifdef XPTI_STATISTICS - printf("String table inserts: [%llu]\n", m_insert.load()); - printf("String table lookups: [%llu]\n", m_lookup.load()); + printf("String table inserts: [%llu]\n", MInsertions.load()); + printf("String table lookups: [%llu]\n", MRetrievals.load()); #endif } private: - safe_int32_t m_ids; ///< Thread-safe ID generator - st_forward_t m_str2id; ///< Forward lookup hash map - st_reverse_t m_id2str; ///< Reverse lookup hash map - int32_t m_table_size; ///< Initial table size of the hash-map - std::mutex m_mutex; ///< Mutex required for double-check pattern - ///< Replace with reader-writer lock in C++14 - safe_int32_t m_strings; ///< The count of strings in the table + safe_int32_t MIds; ///< Thread-safe ID generator + st_forward_t MStringToID; ///< Forward lookup hash map + st_reverse_t MIDToString; ///< Reverse lookup hash map + int32_t MTableSize; ///< Initial table size of the hash-map + std::mutex MMutex; ///< Mutex required for double-check pattern + ///< Replace with reader-writer lock in C++14 + safe_int32_t MStrings; ///< The count of strings in the table #ifdef XPTI_STATISTICS - safe_uint64_t m_insert, ///< Thread-safe tracking of insertions - m_lookup; ///< Thread-safe tracking of lookups + safe_uint64_t MInsertions, ///< Thread-safe tracking of insertions + MRetrievals; ///< Thread-safe tracking of lookups #endif }; } // namespace xpti diff --git a/xptifw/samples/basic_collector/basic_collector.cpp b/xptifw/samples/basic_collector/basic_collector.cpp index 857dce843f67e..7e358146f0727 100644 --- a/xptifw/samples/basic_collector/basic_collector.cpp +++ b/xptifw/samples/basic_collector/basic_collector.cpp @@ -17,11 +17,11 @@ #include "xpti_timers.hpp" #include "xpti_trace_framework.h" -static uint8_t g_stream_id = 0; -std::mutex g_io_mutex; -xpti::thread_id g_tid; +static uint8_t GStreamID = 0; +std::mutex GIOMutex; +xpti::ThreadID GThreadIDEnum; -static const char *tp_types[] = { +static const char *TPTypes[] = { "unknown", "graph_create", "node_create", "edge_create", "region_", "task_", "barrier_", "lock_", "signal ", "transfer_", "thread_", "wait_", @@ -29,10 +29,10 @@ static const char *tp_types[] = { // The lone callback function we are going to use to demonstrate how to attach // the collector to the running executable -XPTI_CALLBACK_API void tp_callback(uint16_t trace_type, - xpti::trace_event_data_t *parent, - xpti::trace_event_data_t *event, - uint64_t instance, const void *user_data); +XPTI_CALLBACK_API void tpCallback(uint16_t trace_type, + xpti::trace_event_data_t *parent, + xpti::trace_event_data_t *event, + uint64_t instance, const void *user_data); // Based on the documentation, every subscriber MUST implement the // xptiTraceInit() and xptiTraceFinish() APIs for their subscriber collector to @@ -48,60 +48,52 @@ XPTI_CALLBACK_API void xptiTraceInit(unsigned int major_version, // Register this stream to get the stream ID; This stream may already have // been registered by the framework and will return the previously // registered stream ID - g_stream_id = xptiRegisterStream(stream_name); + GStreamID = xptiRegisterStream(stream_name); xpti::string_id_t dev_id = xptiRegisterString("sycl_device", &tstr); // Register our lone callback to all pre-defined trace point types - xptiRegisterCallback(g_stream_id, + xptiRegisterCallback(GStreamID, (uint16_t)xpti::trace_point_type_t::graph_create, - tp_callback); - xptiRegisterCallback(g_stream_id, - (uint16_t)xpti::trace_point_type_t::node_create, - tp_callback); - xptiRegisterCallback(g_stream_id, - (uint16_t)xpti::trace_point_type_t::edge_create, - tp_callback); - xptiRegisterCallback(g_stream_id, + tpCallback); + xptiRegisterCallback( + GStreamID, (uint16_t)xpti::trace_point_type_t::node_create, tpCallback); + xptiRegisterCallback( + GStreamID, (uint16_t)xpti::trace_point_type_t::edge_create, tpCallback); + xptiRegisterCallback(GStreamID, (uint16_t)xpti::trace_point_type_t::region_begin, - tp_callback); - xptiRegisterCallback(g_stream_id, - (uint16_t)xpti::trace_point_type_t::region_end, - tp_callback); - xptiRegisterCallback(g_stream_id, - (uint16_t)xpti::trace_point_type_t::task_begin, - tp_callback); + tpCallback); + xptiRegisterCallback( + GStreamID, (uint16_t)xpti::trace_point_type_t::region_end, tpCallback); xptiRegisterCallback( - g_stream_id, (uint16_t)xpti::trace_point_type_t::task_end, tp_callback); - xptiRegisterCallback(g_stream_id, + GStreamID, (uint16_t)xpti::trace_point_type_t::task_begin, tpCallback); + xptiRegisterCallback( + GStreamID, (uint16_t)xpti::trace_point_type_t::task_end, tpCallback); + xptiRegisterCallback(GStreamID, (uint16_t)xpti::trace_point_type_t::barrier_begin, - tp_callback); - xptiRegisterCallback(g_stream_id, - (uint16_t)xpti::trace_point_type_t::barrier_end, - tp_callback); - xptiRegisterCallback(g_stream_id, - (uint16_t)xpti::trace_point_type_t::lock_begin, - tp_callback); + tpCallback); + xptiRegisterCallback( + GStreamID, (uint16_t)xpti::trace_point_type_t::barrier_end, tpCallback); xptiRegisterCallback( - g_stream_id, (uint16_t)xpti::trace_point_type_t::lock_end, tp_callback); - xptiRegisterCallback(g_stream_id, + GStreamID, (uint16_t)xpti::trace_point_type_t::lock_begin, tpCallback); + xptiRegisterCallback( + GStreamID, (uint16_t)xpti::trace_point_type_t::lock_end, tpCallback); + xptiRegisterCallback(GStreamID, (uint16_t)xpti::trace_point_type_t::transfer_begin, - tp_callback); - xptiRegisterCallback(g_stream_id, + tpCallback); + xptiRegisterCallback(GStreamID, (uint16_t)xpti::trace_point_type_t::transfer_end, - tp_callback); - xptiRegisterCallback(g_stream_id, + tpCallback); + xptiRegisterCallback(GStreamID, (uint16_t)xpti::trace_point_type_t::thread_begin, - tp_callback); - xptiRegisterCallback(g_stream_id, - (uint16_t)xpti::trace_point_type_t::thread_end, - tp_callback); - xptiRegisterCallback(g_stream_id, - (uint16_t)xpti::trace_point_type_t::wait_begin, - tp_callback); + tpCallback); + xptiRegisterCallback( + GStreamID, (uint16_t)xpti::trace_point_type_t::thread_end, tpCallback); xptiRegisterCallback( - g_stream_id, (uint16_t)xpti::trace_point_type_t::wait_end, tp_callback); + GStreamID, (uint16_t)xpti::trace_point_type_t::wait_begin, tpCallback); xptiRegisterCallback( - g_stream_id, (uint16_t)xpti::trace_point_type_t::signal, tp_callback); + GStreamID, (uint16_t)xpti::trace_point_type_t::wait_end, tpCallback); + xptiRegisterCallback(GStreamID, (uint16_t)xpti::trace_point_type_t::signal, + tpCallback); printf("Registered all callbacks\n"); } else { // handle the case when a bad stream name has been provided @@ -110,63 +102,51 @@ XPTI_CALLBACK_API void xptiTraceInit(unsigned int major_version, } // -std::string truncate(std::string name) { - size_t pos = name.find_last_of(":"); - if (pos != std::string::npos) { - return name.substr(pos + 1); +std::string truncate(std::string Name) { + size_t Pos = Name.find_last_of(":"); + if (Pos != std::string::npos) { + return Name.substr(Pos + 1); } else { - return name; - } -} - -const char *extract_value(xpti::trace_event_data_t *event) { - auto data = xptiQueryMetadata(event); - char *str_ptr; - xpti::string_id_t kernel_id = xptiRegisterString("kernel_name", &str_ptr); - xpti::string_id_t memory_id = xptiRegisterString("memory_object", &str_ptr); - if (data->count(kernel_id)) { - return xptiLookupString((*data)[kernel_id]); - } else if (data->count(memory_id)) { - return xptiLookupString((*data)[memory_id]); + return Name; } - return event->reserved.payload->name; } XPTI_CALLBACK_API void xptiTraceFinish(const char *stream_name) { // We do nothing here } -XPTI_CALLBACK_API void tp_callback(uint16_t trace_type, - xpti::trace_event_data_t *parent, - xpti::trace_event_data_t *event, - uint64_t instance, const void *user_data) { - auto p = xptiQueryPayload(event); - xpti::timer::tick_t time = xpti::timer::rdtsc(); - auto tid = xpti::timer::get_thread_id(); - uint32_t cpu = g_tid.enum_id(tid); - std::string name; - - if (p->name_sid != xpti::invalid_id) { - name = truncate(p->name); +XPTI_CALLBACK_API void tpCallback(uint16_t TraceType, + xpti::trace_event_data_t *Parent, + xpti::trace_event_data_t *Event, + uint64_t Instance, const void *UserData) { + auto Payload = xptiQueryPayload(Event); + xpti::timer::tick_t Time = xpti::timer::rdtsc(); + auto TID = xpti::timer::getThreadID(); + uint32_t CPU = GThreadIDEnum.enumID(TID); + std::string Name; + + if (Payload->name_sid != xpti::invalid_id) { + Name = truncate(Payload->name); } else { - name = ""; + Name = ""; } - uint64_t id = event ? event->unique_id : 0; + uint64_t ID = Event ? Event->unique_id : 0; // Lock while we print information - std::lock_guard lock(g_io_mutex); + std::lock_guard Lock(GIOMutex); // Print the record information - printf("%-25lu: name=%-35s cpu=%3d event_id=%10lu\n", time, name.c_str(), cpu, - id); + printf("%-25lu: name=%-35s cpu=%3d event_id=%10lu\n", Time, Name.c_str(), CPU, + ID); // Go through all available meta-data for an event and print it out - xpti::metadata_t *metadata = xptiQueryMetadata(event); - for (auto &item : *metadata) { - printf(" %-25s:%s\n", xptiLookupString(item.first), - xptiLookupString(item.second)); + xpti::metadata_t *Metadata = xptiQueryMetadata(Event); + for (auto &Item : *Metadata) { + printf(" %-25s:%s\n", xptiLookupString(Item.first), + xptiLookupString(Item.second)); } - if (p->source_file_sid != xpti::invalid_id && p->line_no > 0) { - printf("---[Source file:line no] %s:%d\n", p->source_file, p->line_no); + if (Payload->source_file_sid != xpti::invalid_id && Payload->line_no > 0) { + printf("---[Source file:line no] %s:%d\n", Payload->source_file, + Payload->line_no); } } diff --git a/xptifw/samples/include/xpti_timers.hpp b/xptifw/samples/include/xpti_timers.hpp index ad80b0962aa3d..1c927760fb900 100644 --- a/xptifw/samples/include/xpti_timers.hpp +++ b/xptifw/samples/include/xpti_timers.hpp @@ -13,14 +13,14 @@ #include namespace xpti { -class thread_id { +class ThreadID { public: typedef std::unordered_map thread_lut_t; - thread_id() : m_tid(0) {} - ~thread_id() {} + ThreadID() : m_tid(0) {} + ~ThreadID() {} - inline uint32_t enum_id(std::thread::id &curr) { + inline uint32_t enumID(std::thread::id &curr) { std::stringstream s; s << curr; std::string str(s.str()); @@ -28,19 +28,19 @@ class thread_id { if (m_thread_lookup.count(str)) { return m_thread_lookup[str]; } else { - uint32_t enum_id = m_tid++; - m_thread_lookup[str] = enum_id; - return enum_id; + uint32_t enumID = m_tid++; + m_thread_lookup[str] = enumID; + return enumID; } } - inline uint32_t enum_id(const std::string &curr) { + inline uint32_t enumID(const std::string &curr) { if (m_thread_lookup.count(curr)) { return m_thread_lookup[curr]; } else { - uint32_t enum_id = m_tid++; - m_thread_lookup[curr] = enum_id; - return enum_id; + uint32_t enumID = m_tid++; + m_thread_lookup[curr] = enumID; + return enumID; } } @@ -59,12 +59,12 @@ inline xpti::timer::tick_t rdtsc() { int rval = QueryPerformanceCounter(&qpcnt); return qpcnt.QuadPart; } -inline uint64_t get_ts_frequency() { +inline uint64_t getTSFrequency() { LARGE_INTEGER freq; QueryPerformanceFrequency(&freq); return freq.QuadPart * 1000; } -inline uint64_t get_cpu() { return GetCurrentProcessorNumber(); } +inline uint64_t getCPU() { return GetCurrentProcessorNumber(); } #else #include #include @@ -76,9 +76,9 @@ inline xpti::timer::tick_t rdtsc() { static_cast(ts.tv_nsec)); } -inline uint64_t get_ts_frequency() { return static_cast(1E9); } +inline uint64_t getTSFrequency() { return static_cast(1E9); } -inline uint64_t get_cpu() { +inline uint64_t getCPU() { #ifdef __linux__ return sched_getcpu(); #else @@ -89,7 +89,7 @@ inline uint64_t get_cpu() { #error Unsupported ISA #endif -inline std::thread::id get_thread_id() { return std::this_thread::get_id(); } +inline std::thread::id getThreadID() { return std::this_thread::get_id(); } #endif } // namespace timer -} // namespace xpti \ No newline at end of file +} // namespace xpti diff --git a/xptifw/src/xpti_trace_framework.cpp b/xptifw/src/xpti_trace_framework.cpp index 2a921803ca984..625058efb0377 100644 --- a/xptifw/src/xpti_trace_framework.cpp +++ b/xptifw/src/xpti_trace_framework.cpp @@ -71,17 +71,17 @@ class Subscribers { // Method to query the plugin data information using the handle. If there's no // information present for the handle provided, the method returns a structure // with the valid attribute set to 'false' - plugin_data_t queryPlugin(xpti_plugin_handle_t h) { - plugin_data_t p; + plugin_data_t queryPlugin(xpti_plugin_handle_t Handle) { + plugin_data_t PData; #ifdef XPTI_USE_TBB - tbb::spin_mutex::scoped_lock my_lock(m_mutex); + tbb::spin_mutex::scoped_lock MyLock(MMutex); #else - std::lock_guard lock(m_mutex); + std::lock_guard Lock(MMutex); #endif - if (m_handle_lut.count(h)) - return m_handle_lut[h]; + if (MHandleLUT.count(Handle)) + return MHandleLUT[Handle]; else - return p; // return invalid plugin data + return PData; // return invalid plugin data } // Load the provided shared object file name using the explicit load API. If @@ -91,101 +91,101 @@ class Subscribers { // handle is returned. // // If not, the shared object is unloaded and a NULL handle is returned. - xpti_plugin_handle_t loadPlugin(const char *path) { - xpti_plugin_handle_t handle = 0; - std::string error; + xpti_plugin_handle_t loadPlugin(const char *Path) { + xpti_plugin_handle_t Handle = 0; + std::string Error; // Check to see if the subscriber has already been loaded; if so, return the // handle from the previously loaded library - if (m_name_lut.count(path)) { + if (MNameLUT.count(Path)) { #ifdef XPTI_USE_TBB - tbb::spin_mutex::scoped_lock my_lock(m_mutex); + tbb::spin_mutex::scoped_lock MyLock(MMutex); #else - std::lock_guard lock(m_mutex); + std::lock_guard Lock(MMutex); #endif // This plugin has already been loaded, so let's return previously // recorded handle - printf("Plugin (%s) has already been loaded..\n", path); - plugin_data_t &d = m_name_lut[path]; - assert(d.valid && "Lookup is invalid!"); - if (d.valid) - return d.handle; + printf("Plugin (%s) has already been loaded..\n", Path); + plugin_data_t &Data = MNameLUT[Path]; + assert(Data.valid && "Lookup is invalid!"); + if (Data.valid) + return Data.handle; } - handle = g_helper.loadLibrary(path, error); - if (handle) { + Handle = g_helper.loadLibrary(Path, Error); + if (Handle) { // The tracing framework requires the tool plugins to implement the // xptiTraceInit() and xptiTraceFinish() functions. If these are not // present, then the plugin will be ruled an invalid plugin and unloaded // from the process. - xpti::plugin_init_t init = - (xpti::plugin_init_t)g_helper.findFunction(handle, "xptiTraceInit"); - xpti::plugin_fini_t fini = - (xpti::plugin_fini_t)g_helper.findFunction(handle, "xptiTraceFinish"); - if (init && fini) { + xpti::plugin_init_t InitFunc = + (xpti::plugin_init_t)g_helper.findFunction(Handle, "xptiTraceInit"); + xpti::plugin_fini_t FiniFunc = + (xpti::plugin_fini_t)g_helper.findFunction(Handle, "xptiTraceFinish"); + if (InitFunc && FiniFunc) { // We appear to have loaded a valid plugin, so we will insert the // plugin information into the two maps guarded by a lock - plugin_data_t d; - d.valid = true; - d.handle = handle; - d.name = path; - d.init = init; - d.fini = fini; + plugin_data_t Data; + Data.valid = true; + Data.handle = Handle; + Data.name = Path; + Data.init = InitFunc; + Data.fini = FiniFunc; #ifdef XPTI_USE_TBB - tbb::spin_mutex::scoped_lock my_lock(m_mutex); + tbb::spin_mutex::scoped_lock MyLock(MMutex); #else - std::lock_guard lock(m_mutex); + std::lock_guard Lock(MMutex); #endif - m_name_lut[path] = d; - m_handle_lut[handle] = d; + MNameLUT[Path] = Data; + MHandleLUT[Handle] = Data; } else { // We may have loaded another shared object that is not a tool plugin // for the tracing framework, so we'll unload it now - unloadPlugin(handle); - handle = nullptr; + unloadPlugin(Handle); + Handle = nullptr; } } else { // Get error from errno - if (!error.empty()) - printf("[%s]: %s\n", path, error.c_str()); + if (!Error.empty()) + printf("[%s]: %s\n", Path, Error.c_str()); } - return handle; + return Handle; } // Unloads the shared object identified by the handle provided. If // successful, returns a success code, else a failure code. - xpti::result_t unloadPlugin(xpti_plugin_handle_t h) { - xpti::result_t res = g_helper.unloadLibrary(h); - if (xpti::result_t::XPTI_RESULT_SUCCESS == res) { - auto it = m_handle_lut.find(h); - if (it != m_handle_lut.end()) { - m_handle_lut.erase(h); + xpti::result_t unloadPlugin(xpti_plugin_handle_t PluginHandle) { + xpti::result_t Res = g_helper.unloadLibrary(PluginHandle); + if (xpti::result_t::XPTI_RESULT_SUCCESS == Res) { + auto Loc = MHandleLUT.find(PluginHandle); + if (Loc != MHandleLUT.end()) { + MHandleLUT.erase(PluginHandle); } } - return res; + return Res; } // Quick test to see if there are registered subscribers - bool hasValidSubscribers() { return (m_handle_lut.size() > 0); } + bool hasValidSubscribers() { return (MHandleLUT.size() > 0); } - void initializeForStream(const char *stream, uint32_t major_revision, + void initializeForStream(const char *Stream, uint32_t major_revision, uint32_t minor_revision, const char *version_string) { // If there are subscribers registered, then initialize the subscribers // with the new stream information. - if (m_handle_lut.size()) { - for (auto &handle : m_handle_lut) { - handle.second.init(major_revision, minor_revision, version_string, - stream); + if (MHandleLUT.size()) { + for (auto &Handle : MHandleLUT) { + Handle.second.init(major_revision, minor_revision, version_string, + Stream); } } } - void finalizeForStream(const char *stream) { + void finalizeForStream(const char *Stream) { // If there are subscribers registered, then finalize the subscribers for // the stream - if (m_handle_lut.size()) { - for (auto &handle : m_handle_lut) { - handle.second.fini(stream); + if (MHandleLUT.size()) { + for (auto &Handle : MHandleLUT) { + Handle.second.fini(Stream); } } } @@ -193,62 +193,61 @@ class Subscribers { void loadFromEnvironmentVariable() { if (!g_helper.checkTraceEnv()) return; - // Load all registered listeners by scanning the environment variable in - // "env"; The environment variable, if set, extract the comma separated + // Load all registered Listeners by scanning the environment variable in + // "Env"; The environment variable, if set, extract the comma separated // tokens into a vector. - std::string token, env = g_helper.getEnvironmentVariable(env_subscribers); - std::vector listeners; - std::stringstream stream(env); + std::string Token, Env = g_helper.getEnvironmentVariable(env_subscribers); + std::vector Listeners; + std::stringstream Stream(Env); // Split the environment variable value by ',' and build a vector of the // tokens (subscribers) - while (std::getline(stream, token, ',')) { - listeners.push_back(token); + while (std::getline(Stream, Token, ',')) { + Listeners.push_back(Token); } - size_t valid_subscribers = listeners.size(); - if (valid_subscribers) { + size_t ValidSubscribers = Listeners.size(); + if (ValidSubscribers) { // Let's go through the subscribers and load these plugins; - for (auto &path : listeners) { + for (auto &Path : Listeners) { // Load the plugins listed in the environment variable #ifdef XPTI_USE_TBB - tbb::spin_mutex::scoped_lock my_lock(m_loader); + tbb::spin_mutex::scoped_lock MyLock(MLoader); #else - std::lock_guard lock(m_loader); + std::lock_guard Lock(MLoader); #endif - auto subs_handle = loadPlugin(path.c_str()); - if (!subs_handle) { - valid_subscribers--; - printf("Failed to load %s successfully...\n", path.c_str()); + auto SubscriberHandle = loadPlugin(Path.c_str()); + if (!SubscriberHandle) { + ValidSubscribers--; + printf("Failed to load %s successfully...\n", Path.c_str()); } } } } void unloadAllPlugins() { - for (auto &item : m_name_lut) { - unloadPlugin(item.second.handle); + for (auto &Item : MNameLUT) { + unloadPlugin(Item.second.handle); } - - m_handle_lut.clear(); - m_name_lut.clear(); + MHandleLUT.clear(); + MNameLUT.clear(); } private: /// Hash map that maps shared object name to the plugin data - plugin_name_lut_t m_name_lut; + plugin_name_lut_t MNameLUT; /// Hash map that maps shared object handle to the plugin data - plugin_handle_lut_t m_handle_lut; + plugin_handle_lut_t MHandleLUT; #ifdef XPTI_USE_TBB /// Lock to ensure the operation on these maps are safe - tbb::spin_mutex m_mutex; + tbb::spin_mutex MMutex; /// Lock to ensure that only one load happens at a time - tbb::spin_mutex m_loader; + tbb::spin_mutex MLoader; #else /// Lock to ensure the operation on these maps are safe - std::mutex m_mutex; + std::mutex MMutex; /// Lock to ensure that only one load happens at a time - std::mutex m_loader; + std::mutex MLoader; #endif }; @@ -270,26 +269,26 @@ class Tracepoints { #endif Tracepoints(xpti::StringTable &st) - : m_uid(1), m_insert(0), m_lookup(0), m_string_table(st) { + : MUId(1), MInsertions(0), MRetrievals(0), MStringTableRef(st) { // Nothing requires to be done at construction time } ~Tracepoints() { clear(); } void clear() { - m_string_table.clear(); + MStringTableRef.clear(); // We will always start our ID // stream from 1. 0 is null_id // and -1 is invalid_id - m_uid = {1}; - m_payload_lut.clear(); - m_insert = m_lookup = {0}; - m_payloads.clear(); - m_events.clear(); - m_va_lut.clear(); + MUId = {1}; + MPayloadLUT.clear(); + MInsertions = MRetrievals = {0}; + MPayloads.clear(); + MEvents.clear(); + MCodePtrLUT.clear(); } - inline uint64_t makeUniqueID() { return m_uid++; } + inline uint64_t makeUniqueID() { return MUId++; } // Create an event with the payload information. If one already exists, the // retrieve the previously added event. If not, we register the provided @@ -310,40 +309,40 @@ class Tracepoints { // 3. Create a mapping from code_ptr <--> Universal ID // 4. Create a mapping from Universal ID <--> Payload // 5. Create a mapping from Universal ID <--> Event - xpti::trace_event_data_t *create(const xpti::payload_t *p, - uint64_t *instance_no) { - return register_event(p, instance_no); + xpti::trace_event_data_t *create(const xpti::payload_t *Payload, + uint64_t *InstanceNo) { + return register_event(Payload, InstanceNo); } // Method to get the payload information from the event structure. This method // uses the Universal ID in the event structure to lookup the payload // information and returns the payload if available. // // This method is thread-safe - const xpti::payload_t *payloadData(xpti::trace_event_data_t *e) { - if (!e || e->unique_id == xpti::invalid_id) + const xpti::payload_t *payloadData(xpti::trace_event_data_t *Event) { + if (!Event || Event->unique_id == xpti::invalid_id) return nullptr; #ifndef XPTI_USE_TBB - std::lock_guard lock(m_mutex); + std::lock_guard Lock(MMutex); #endif - if (e->reserved.payload) - return e->reserved.payload; + if (Event->reserved.payload) + return Event->reserved.payload; else { // Cache it in case it is not already cached - e->reserved.payload = &m_payloads[e->unique_id]; - return e->reserved.payload; + Event->reserved.payload = &MPayloads[Event->unique_id]; + return Event->reserved.payload; } } - const xpti::trace_event_data_t *eventData(int64_t uid) { - if (uid == xpti::invalid_id) + const xpti::trace_event_data_t *eventData(int64_t UId) { + if (UId == xpti::invalid_id) return nullptr; #ifndef XPTI_USE_TBB - std::lock_guard lock(m_mutex); + std::lock_guard Lock(MMutex); #endif - auto ev = m_events.find(uid); - if (ev != m_events.end()) - return &(ev->second); + auto EvLoc = MEvents.find(UId); + if (EvLoc != MEvents.end()) + return &(EvLoc->second); else return nullptr; } @@ -353,30 +352,30 @@ class Tracepoints { // column_no fields that may already be present. Since we are not sure of the // data types, we will allow them to add these pairs as strings. Internally, // we will store key-value pairs as a map of string ids. - xpti::result_t addMetadata(xpti::trace_event_data_t *e, const char *key, - const char *value) { - if (!e || !key || !value) + xpti::result_t addMetadata(xpti::trace_event_data_t *Event, const char *Key, + const char *Value) { + if (!Event || !Key || !Value) return xpti::result_t::XPTI_RESULT_INVALIDARG; - string_id_t key_id = m_string_table.add(key); - if (key_id == xpti::invalid_id) { + string_id_t KeyID = MStringTableRef.add(Key); + if (KeyID == xpti::invalid_id) { return xpti::result_t::XPTI_RESULT_INVALIDARG; } - string_id_t val_id = m_string_table.add(value); - if (val_id == xpti::invalid_id) { + string_id_t ValueID = MStringTableRef.add(Value); + if (ValueID == xpti::invalid_id) { return xpti::result_t::XPTI_RESULT_INVALIDARG; } // Protect simultaneous insert operations on the metadata tables #ifdef XPTI_USE_TBB - tbb::spin_mutex::scoped_lock hl(m_metadata_mutex); + tbb::spin_mutex::scoped_lock HashLock(MMetadataMutex); #else - std::lock_guard lock(m_metadata_mutex); + std::lock_guard HashLock(MMetadataMutex); #endif - if (e->reserved.metadata.count(key_id)) { + if (Event->reserved.metadata.count(KeyID)) { return xpti::result_t::XPTI_RESULT_DUPLICATE; } - e->reserved.metadata[key_id] = val_id; + Event->reserved.metadata[KeyID] = ValueID; return xpti::result_t::XPTI_RESULT_SUCCESS; } @@ -385,10 +384,10 @@ class Tracepoints { // performed. // void printStatistics() { - printf("Tracepoint inserts : [%lu] \n", m_insert.load()); - printf("Tracepoint lookups : [%lu]\n", m_lookup.load()); + printf("Tracepoint inserts : [%lu] \n", MInsertions.load()); + printf("Tracepoint lookups : [%lu]\n", MRetrievals.load()); printf("Tracepoint Hashmap :\n"); - m_payload_lut.printStatistics(); + MPayloadLUT.printStatistics(); } private: @@ -400,29 +399,30 @@ class Tracepoints { /// present or the code pointer information, otherwise /// 3. Add the payload and generate a unique ID /// 4. Cache the computed hash in the payload - int64_t make_hash(xpti::payload_t *p) { + int64_t makeHash(xpti::payload_t *Payload) { // Initialize to invalid hash value - int64_t hash = xpti::invalid_id; + int64_t HashValue = xpti::invalid_id; // If no flags are set, then the payload is not valid - if (p->flags == 0) - return hash; + if (Payload->flags == 0) + return HashValue; // If the hash value has been cached, return and bail early - if (p->flags & (uint64_t)payload_flag_t::HashAvailable) - return p->internal; + if (Payload->flags & (uint64_t)payload_flag_t::HashAvailable) + return Payload->internal; // Add the string information to the string table and use the string IDs // (in addition to any unique addresses) to create a hash value - if ((p->flags & (uint64_t)payload_flag_t::NameAvailable)) { + if ((Payload->flags & (uint64_t)payload_flag_t::NameAvailable)) { // Add the kernel name to the string table; if the add() returns the // address to the string in the string table, we can avoid a query [TBD] - p->name_sid = m_string_table.add(p->name, &p->name); - // p->name = m_string_table.query(p->name_sid); - if (p->flags & (uint64_t)payload_flag_t::SourceFileAvailable) { + Payload->name_sid = MStringTableRef.add(Payload->name, &Payload->name); + // Payload->name = MStringTableRef.query(Payload->name_sid); + if (Payload->flags & (uint64_t)payload_flag_t::SourceFileAvailable) { // Add source file information ot string table - p->source_file_sid = - m_string_table.add(p->source_file, &p->source_file); - // p->source_file = m_string_table.query(p->source_file_sid); - if (p->flags & (uint64_t)payload_flag_t::CodePointerAvailable) { + Payload->source_file_sid = + MStringTableRef.add(Payload->source_file, &Payload->source_file); + // Payload->source_file = + // MStringTableRef.query(Payload->source_file_sid); + if (Payload->flags & (uint64_t)payload_flag_t::CodePointerAvailable) { // We have source file, kernel name info and kernel address; // so we combine all of them to make it unique: // @@ -437,16 +437,17 @@ class Tracepoints { // them. However, if we use the address, which would be the object // address, they both will have different addresses even if they // happen to be on the same line. - uint16_t sname_pack = (uint16_t)(p->name_sid & 0x0000ffff); - uint16_t sfile_pack = (uint16_t)(p->source_file_sid & 0x0000ffff); - uint32_t kernel_sid_pack = XPTI_PACK16_RET32(sfile_pack, sname_pack); - uint32_t addr = - (uint32_t)(((uint64_t)p->code_ptr_va & 0x0000000ffffffff0) >> 4); - hash = XPTI_PACK32_RET64(addr, kernel_sid_pack); + uint16_t NamePack = (uint16_t)(Payload->name_sid & 0x0000ffff); + uint16_t SrcFileNamePack = + (uint16_t)(Payload->source_file_sid & 0x0000ffff); + uint32_t KernelIDPack = XPTI_PACK16_RET32(SrcFileNamePack, NamePack); + uint32_t Address = (uint32_t)( + ((uint64_t)Payload->code_ptr_va & 0x0000000ffffffff0) >> 4); + HashValue = XPTI_PACK32_RET64(Address, KernelIDPack); // Cache the hash once it is computed - p->flags |= (uint64_t)payload_flag_t::HashAvailable; - p->internal = hash; - return hash; + Payload->flags |= (uint64_t)payload_flag_t::HashAvailable; + Payload->internal = HashValue; + return HashValue; } else { // We have both source file and kernel name info // @@ -455,50 +456,53 @@ class Tracepoints { // will use 22 bits of the source file and kernel name ids and form a // 64-bit value with the middle 22-bits being zero representing the // line number. - uint64_t left = 0, middle = 0, right = 0, mask22 = 0x00000000003fffff; + uint64_t LeftPart = 0, MiddlePart = 0, RightPart = 0, + Mask22Bits = 0x00000000003fffff; // If line number info is available, extract 22-bits of it - if (p->flags & (uint64_t)payload_flag_t::LineInfoAvailable) { - middle = p->line_no & mask22; - middle = middle << 22; + if (Payload->flags & (uint64_t)payload_flag_t::LineInfoAvailable) { + MiddlePart = Payload->line_no & Mask22Bits; + MiddlePart = MiddlePart << 22; } // The leftmost 22-bits will represent the file name string id - left = p->source_file_sid & mask22; - left = left << 44; + LeftPart = Payload->source_file_sid & Mask22Bits; + LeftPart = LeftPart << 44; // The rightmost 22-bits will represent the kernel name string id - right = p->name_sid & mask22; - hash = left | middle | right; - p->flags |= (uint64_t)payload_flag_t::HashAvailable; - p->internal = hash; - return hash; + RightPart = Payload->name_sid & Mask22Bits; + HashValue = LeftPart | MiddlePart | RightPart; + Payload->flags |= (uint64_t)payload_flag_t::HashAvailable; + Payload->internal = HashValue; + return HashValue; } - } else if (p->flags & (uint64_t)payload_flag_t::CodePointerAvailable) { + } else if (Payload->flags & + (uint64_t)payload_flag_t::CodePointerAvailable) { // We have both kernel name and kernel address; we use bits 5-36 from // the address and combine it with the kernel name string ID - uint32_t addr = - (uint32_t)(((uint64_t)p->code_ptr_va & 0x0000000ffffffff0) >> 4); - hash = XPTI_PACK32_RET64(addr, p->name_sid); - p->flags |= (uint64_t)payload_flag_t::HashAvailable; - p->internal = hash; - return hash; + uint32_t Address = (uint32_t)( + ((uint64_t)Payload->code_ptr_va & 0x0000000ffffffff0) >> 4); + HashValue = XPTI_PACK32_RET64(Address, Payload->name_sid); + Payload->flags |= (uint64_t)payload_flag_t::HashAvailable; + Payload->internal = HashValue; + return HashValue; } else { // We only have kernel name and this is suspect if the kernel names are // not unique and will replace any previously stored payload information - if (p->name_sid != xpti::invalid_id) { - hash = XPTI_PACK32_RET64(0, p->name_sid); - p->flags |= (uint64_t)payload_flag_t::HashAvailable; - p->internal = hash; - return hash; + if (Payload->name_sid != xpti::invalid_id) { + HashValue = XPTI_PACK32_RET64(0, Payload->name_sid); + Payload->flags |= (uint64_t)payload_flag_t::HashAvailable; + Payload->internal = HashValue; + return HashValue; } } - } else if (p->flags & (uint64_t)payload_flag_t::CodePointerAvailable) { + } else if (Payload->flags & + (uint64_t)payload_flag_t::CodePointerAvailable) { // We are only going to look at Kernel address when kernel name is not // available. - hash = (uint64_t)p->code_ptr_va; - p->flags |= (uint64_t)payload_flag_t::HashAvailable; - p->internal = hash; - return hash; + HashValue = (uint64_t)Payload->code_ptr_va; + Payload->flags |= (uint64_t)payload_flag_t::HashAvailable; + Payload->internal = HashValue; + return HashValue; } - return hash; + return HashValue; } // Register the payload and generate a universal ID for it. @@ -506,118 +510,117 @@ class Tracepoints { // Universal ID that corresponds to the payload. // // This method is thread-safe - // - xpti::trace_event_data_t *register_event(const xpti::payload_t *payload, - uint64_t *instance_no) { - xpti::payload_t ptemp = *payload; + xpti::trace_event_data_t *register_event(const xpti::payload_t *Payload, + uint64_t *InstanceNo) { + xpti::payload_t TempPayload = *Payload; // Initialize to invalid // We need an explicit lock for the rest of the operations as the same // payload could be registered from multiple-threads. // - // 1. make_hash(p) is invariant, although the hash may be created twice and + // 1. makeHash(p) is invariant, although the hash may be created twice and // written to the same field in the structure. If we have a lock guard, we // may be spinning and wasting time instead. We will just compute this in // parallel. - // 2. m_payload_lut is queried by two threads and and both queries return + // 2. MPayloadLUT is queried by two threads and and both queries return // "not found" // 3. This takes both threads to the else clause both threads will create a // unique_id for the payload being registered and add them to the hash table - // [with DIFFERENT IDs] and m_payloads[unique_id] gets updated twice for the + // [with DIFFERENT IDs] and MPayloads[unique_id] gets updated twice for the // same payload with different IDs // 4. ev.unique_id is undefined as it could be one of the two IDs generated // for the payload // - int64_t uid = xpti::invalid_id; + int64_t UId = xpti::invalid_id; // Make a hash value from the payload. If the hash value created is // invalid, return immediately - int64_t hash = make_hash(&ptemp); - if (hash == xpti::invalid_id) + int64_t HashValue = makeHash(&TempPayload); + if (HashValue == xpti::invalid_id) return nullptr; // If it's valid, we check to see if we can retrieve the previously added // event structure; we do this as a critical section #ifdef XPTI_USE_TBB - tbb::speculative_spin_mutex::scoped_lock hl(m_hash_lock); + tbb::speculative_spin_mutex::scoped_lock HashLock(MHashLock); #else - std::lock_guard lock(m_hash_lock); + std::lock_guard HashLock(MHashLock); #endif - uid = m_payload_lut.find(hash); - if (uid != xpti::invalid_id) { + UId = MPayloadLUT.find(HashValue); + if (UId != xpti::invalid_id) { #ifdef XPTI_STATISTICS - m_lookup++; + MRetrievals++; #endif #ifndef XPTI_USE_TBB - std::lock_guard lock(m_mutex); + std::lock_guard Lock(MMutex); #endif - auto ev = m_events.find(uid); - if (ev != m_events.end()) { - ev->second.instance_id++; + auto EvLoc = MEvents.find(UId); + if (EvLoc != MEvents.end()) { + EvLoc->second.instance_id++; // Guarantees that the returned instance ID will be accurate as // it is on the stack - if (instance_no) - *instance_no = ev->second.instance_id; - return &(ev->second); + if (InstanceNo) + *InstanceNo = EvLoc->second.instance_id; + return &(EvLoc->second); } else return nullptr; // we have a problem! } else { #ifdef XPTI_STATISTICS - m_insert++; + MInsertions++; #endif // Create a new unique ID // - uid = m_uid++; + UId = MUId++; // And add it as a pair // - m_payload_lut.add(hash, uid); + MPayloadLUT.add(HashValue, UId); // The API allows you to query a Universal ID from the kernel address; so // build the necessary data structures for this. - if (ptemp.flags & (uint64_t)payload_flag_t::HashAvailable) { + if (TempPayload.flags & (uint64_t)payload_flag_t::HashAvailable) { #ifndef XPTI_USE_TBB - std::lock_guard lock(m_va_mutex); + std::lock_guard Lock(MCodePtrMutex); #endif - m_va_lut[(uint64_t)ptemp.code_ptr_va] = uid; + MCodePtrLUT[(uint64_t)TempPayload.code_ptr_va] = UId; } // We also want to query the payload by universal ID that has been // generated #ifndef XPTI_USE_TBB - std::lock_guard lock(m_mutex); + std::lock_guard Lock(MMutex); #endif - m_payloads[uid] = ptemp; // when it uses tbb, should be thread-safe + MPayloads[UId] = TempPayload; // when it uses tbb, should be thread-safe { - xpti::trace_event_data_t *ev = &m_events[uid]; + xpti::trace_event_data_t *Event = &MEvents[UId]; // We are seeing this unique ID for the first time, so we will // initialize the event structure with defaults and set the unique_id to // the newly generated unique id (uid) - ev->unique_id = uid; - ev->unused = 0; - ev->reserved.payload = &m_payloads[uid]; - ev->data_id = ev->source_id = ev->target_id = 0; - ev->instance_id = 1; - ev->user_data = nullptr; - ev->event_type = (uint16_t)xpti::trace_event_type_t::unknown_event; - ev->activity_type = + Event->unique_id = UId; + Event->unused = 0; + Event->reserved.payload = &MPayloads[UId]; + Event->data_id = Event->source_id = Event->target_id = 0; + Event->instance_id = 1; + Event->user_data = nullptr; + Event->event_type = (uint16_t)xpti::trace_event_type_t::unknown_event; + Event->activity_type = (uint16_t)xpti::trace_activity_type_t::unknown_activity; - *instance_no = ev->instance_id; - return ev; + *InstanceNo = Event->instance_id; + return Event; } } return nullptr; } - xpti::safe_int64_t m_uid; - xpti::Hash64x64Table m_payload_lut; - xpti::StringTable &m_string_table; - xpti::safe_uint64_t m_insert, m_lookup; - uid_payload_t m_payloads; - uid_event_t m_events; - va_uid_t m_va_lut; + xpti::safe_int64_t MUId; + xpti::Hash64x64Table MPayloadLUT; + xpti::StringTable &MStringTableRef; + xpti::safe_uint64_t MInsertions, MRetrievals; + uid_payload_t MPayloads; + uid_event_t MEvents; + va_uid_t MCodePtrLUT; #ifdef XPTI_USE_TBB - tbb::spin_mutex m_metadata_mutex; - tbb::speculative_spin_mutex m_hash_lock; + tbb::spin_mutex MMetadataMutex; + tbb::speculative_spin_mutex MHashLock; #else - std::mutex m_metadata_mutex; - std::mutex m_hash_lock; - std::mutex m_mutex; - std::mutex m_va_mutex; + std::mutex MMetadataMutex; + std::mutex MHashLock; + std::mutex MMutex; + std::mutex MCodePtrMutex; #endif }; @@ -646,9 +649,9 @@ class Notifications { Notifications() = default; ~Notifications() = default; - xpti::result_t registerCallback(uint8_t stream_id, uint16_t trace_type, - xpti::tracepoint_callback_api_t cb) { - if (!cb) + xpti::result_t registerCallback(uint8_t StreamID, uint16_t TraceType, + xpti::tracepoint_callback_api_t cbFunc) { + if (!cbFunc) return xpti::result_t::XPTI_RESULT_INVALIDARG; #ifdef XPTI_STATISTICS @@ -656,31 +659,33 @@ class Notifications { // type statistics counters { #ifdef XPTI_USE_TBB - tbb::spin_mutex::scoped_lock sl(m_stats_lock); + tbb::spin_mutex::scoped_lock Lock(MStatsLock); #else - std::lock_guard lock(m_stats_lock); + std::lock_guard Lock(MStatsLock); #endif - auto instance = m_stats.find(trace_type); - if (instance == m_stats.end()) { - m_stats[trace_type] = 0; + auto InstanceNo = MStats.find(TraceType); + if (InstanceNo == MStats.end()) { + MStats[TraceType] = 0; } } #endif #ifndef XPTI_USE_TBB - std::lock_guard lock(m_cb_lock); + std::lock_guard Lock(MCBsLock); #endif - auto &stream_cbs = m_cbs[stream_id]; // thread-safe - // What we get is a concurrent_hash_map - // of vectors holding the callbacks we - // need access to; + auto &StreamCBs = + MCallbacksByStream[StreamID]; // thread-safe + // What we get is a concurrent_hash_map + // of vectors holding the callbacks we + // need access to; #ifdef XPTI_USE_TBB - cb_t::accessor a; - stream_cbs.insert(a, trace_type); + cb_t::accessor Acc; + StreamCBs.insert(Acc, TraceType); #else - auto a = stream_cbs.find(trace_type); - if (a == stream_cbs.end()) { - auto b = stream_cbs[trace_type]; - a = stream_cbs.find(trace_type); + auto Acc = StreamCBs.find(TraceType); + if (Acc == StreamCBs.end()) { + // Create a new slot and return the accessor for the trace type + auto Tmp = StreamCBs[TraceType]; + Acc = StreamCBs.find(TraceType); } #endif // If the key does not exist, a new entry is created and an accessor to it @@ -692,49 +697,49 @@ class Notifications { // If not, we set the first element of new entry to 'true' indicating that // it is valid. Unregister will just set this flag to false, indicating that // it is no longer valid and is unregistered. - for (auto &e : a->second) { - if (e.second == cb) { - if (e.first) // Already here and active + for (auto &Ele : Acc->second) { + if (Ele.second == cbFunc) { + if (Ele.first) // Already here and active return xpti::result_t::XPTI_RESULT_DUPLICATE; else { // it has been unregistered before, re-enable - e.first = true; + Ele.first = true; return xpti::result_t::XPTI_RESULT_UNDELETE; } } } // If we come here, then we did not find the callback being registered // already in the framework. So, we insert it. - a->second.push_back(std::make_pair(true, cb)); + Acc->second.push_back(std::make_pair(true, cbFunc)); return xpti::result_t::XPTI_RESULT_SUCCESS; } - xpti::result_t unregisterCallback(uint8_t stream_id, uint16_t trace_type, - xpti::tracepoint_callback_api_t cb) { - if (!cb) + xpti::result_t unregisterCallback(uint8_t StreamID, uint16_t TraceType, + xpti::tracepoint_callback_api_t cbFunc) { + if (!cbFunc) return xpti::result_t::XPTI_RESULT_INVALIDARG; #ifndef XPTI_USE_TBB - std::lock_guard lock(m_cb_lock); + std::lock_guard Lock(MCBsLock); #endif - auto &stream_cbs = - m_cbs[stream_id]; // thread-safe - // What we get is a concurrent_hash_map of - // vectors holding the callbacks we need - // access to; + auto &StreamCBs = + MCallbacksByStream[StreamID]; // thread-safe + // What we get is a concurrent_hash_map + // of vectors holding the callbacks we + // need access to; #ifdef XPTI_USE_TBB - cb_t::accessor a; - bool success = stream_cbs.find(a, trace_type); + cb_t::accessor Acc; + bool Success = StreamCBs.find(Acc, TraceType); #else - auto a = stream_cbs.find(trace_type); - bool success = (a != stream_cbs.end()); + auto Acc = StreamCBs.find(TraceType); + bool Success = (Acc != StreamCBs.end()); #endif - if (success) { - for (auto &e : a->second) { - if (e.second == cb) { - if (e.first) { // Already here and active - // unregister, since delete and simultaneous - // iterations by other threads are unsafe - e.first = false; + if (Success) { + for (auto &Ele : Acc->second) { + if (Ele.second == cbFunc) { + if (Ele.first) { // Already here and active + // unregister, since delete and simultaneous + // iterations by other threads are unsafe + Ele.first = false; // releases the accessor return xpti::result_t::XPTI_RESULT_SUCCESS; } else { @@ -748,60 +753,60 @@ class Notifications { return xpti::result_t::XPTI_RESULT_NOTFOUND; } - xpti::result_t unregisterStream(uint8_t stream_id) { + xpti::result_t unregisterStream(uint8_t StreamID) { // If there are no callbacks registered for the requested stream ID, we // return not found #ifndef XPTI_USE_TBB - std::lock_guard lock(m_cb_lock); + std::lock_guard Lock(MCBsLock); #endif - if (m_cbs.count(stream_id) == 0) + if (MCallbacksByStream.count(StreamID) == 0) return xpti::result_t::XPTI_RESULT_NOTFOUND; - auto &stream_cbs = m_cbs[stream_id]; // thread-safe - // Disable all callbacks registered for the stream represented by stream_id - for (auto &it : stream_cbs) { - for (auto &ele : it.second) { - ele.first = false; + auto &StreamCBs = MCallbacksByStream[StreamID]; // thread-safe + // Disable all callbacks registered for the stream represented by StreamID + for (auto &Item : StreamCBs) { + for (auto &Ele : Item.second) { + Ele.first = false; } } // Return success return xpti::result_t::XPTI_RESULT_SUCCESS; } - xpti::result_t notifySubscribers(uint16_t stream_id, uint16_t trace_type, - xpti::trace_event_data_t *parent, - xpti::trace_event_data_t *object, - uint64_t instance, const void *user_data) { + xpti::result_t notifySubscribers(uint16_t StreamID, uint16_t TraceType, + xpti::trace_event_data_t *Parent, + xpti::trace_event_data_t *Object, + uint64_t InstanceNo, const void *UserData) { { #ifndef XPTI_USE_TBB - std::lock_guard lock(m_cb_lock); + std::lock_guard Lock(MCBsLock); #endif - cb_t &stream = m_cbs[stream_id]; // Thread-safe + cb_t &Stream = MCallbacksByStream[StreamID]; // Thread-safe #ifdef XPTI_USE_TBB - cb_t::const_accessor a; // read-only accessor - bool success = stream.find(a, trace_type); + cb_t::const_accessor Acc; // read-only accessor + bool Success = Stream.find(Acc, TraceType); #else - auto a = stream.find(trace_type); - bool success = (a != stream.end()); + auto Acc = Stream.find(TraceType); + bool Success = (Acc != Stream.end()); #endif - if (success) { + if (Success) { // Go through all registered callbacks and invoke them - for (auto &e : a->second) { - if (e.first) - (e.second)(trace_type, parent, object, instance, user_data); + for (auto &Ele : Acc->second) { + if (Ele.first) + (Ele.second)(TraceType, Parent, Object, InstanceNo, UserData); } } } #ifdef XPTI_STATISTICS - auto &counter = m_stats[trace_type]; + auto &Counter = MStats[TraceType]; { #ifdef XPTI_USE_TBB - tbb::spin_mutex::scoped_lock sl(m_stats_lock); + tbb::spin_mutex::scoped_lock Lock(MStatsLock); #else - std::lock_guard lock(m_stats_lock); + std::lock_guard Lock(MStatsLock); #endif - counter++; + Counter++; } #endif return xpti::result_t::XPTI_RESULT_SUCCESS; @@ -810,7 +815,7 @@ class Notifications { void printStatistics() { #ifdef XPTI_STATISTICS printf("Notification statistics:\n"); - for (auto &s : m_stats) { + for (auto &s : MStats) { printf("%19s: [%llu] \n", stringify_trace_type((xpti_trace_point_type_t)s.first).c_str(), s.second); @@ -820,8 +825,8 @@ class Notifications { private: #ifdef XPTI_STATISTICS - std::string stringify_trace_type(xpti_trace_point_type_t trace_type) { - switch (trace_type) { + std::string stringify_trace_type(xpti_trace_point_type_t TraceType) { + switch (TraceType) { case graph_create: return "graph_create"; case node_create: @@ -860,311 +865,307 @@ class Notifications { return "wait_end"; break; default: - if (trace_type & user_defined_trace_point) { + if (TraceType & user_defined_trace_point) { std::string str = "user_defined[" + - std::to_string(XPTI_EXTRACT_USER_DEFINED_ID(trace_type)) + "]"; + std::to_string(XPTI_EXTRACT_USER_DEFINED_ID(TraceType)) + "]"; return str; } else { std::string str = "unknown[" + - std::to_string(XPTI_EXTRACT_USER_DEFINED_ID(trace_type)) + "]"; + std::to_string(XPTI_EXTRACT_USER_DEFINED_ID(TraceType)) + "]"; return str; } } } #endif - stream_cb_t m_cbs; + stream_cb_t MCallbacksByStream; #ifdef XPTI_USE_TBB - tbb::spin_mutex m_stats_lock; + tbb::spin_mutex MStatsLock; #else - std::mutex m_cb_lock; - std::mutex m_stats_lock; + std::mutex MCBsLock; + std::mutex MStatsLock; #endif - statistics_t m_stats; + statistics_t MStats; }; class Framework { public: Framework() - : m_tracepoints(m_string_table), m_universal_ids(0), - m_trace_enabled(false) { + : MTracepoints(MStringTableRef), MUniversalIDs(0), MTraceEnabled(false) { // Load all subscribers on construction - m_subscribers.loadFromEnvironmentVariable(); - m_trace_enabled = - (g_helper.checkTraceEnv() && m_subscribers.hasValidSubscribers()); + MSubscribers.loadFromEnvironmentVariable(); + MTraceEnabled = + (g_helper.checkTraceEnv() && MSubscribers.hasValidSubscribers()); } ~Framework() = default; void clear() { - m_universal_ids = {1}; - m_tracepoints.clear(); - m_string_table.clear(); + MUniversalIDs = {1}; + MTracepoints.clear(); + MStringTableRef.clear(); } - inline void setTraceEnabled(bool yesOrNo = true) { - m_trace_enabled = yesOrNo; - } + inline void setTraceEnabled(bool yesOrNo = true) { MTraceEnabled = yesOrNo; } - inline bool traceEnabled() { return m_trace_enabled; } + inline bool traceEnabled() { return MTraceEnabled; } - inline uint64_t makeUniqueID() { return m_tracepoints.makeUniqueID(); } + inline uint64_t makeUniqueID() { return MTracepoints.makeUniqueID(); } - xpti::result_t addMetadata(xpti::trace_event_data_t *e, const char *key, - const char *value) { - return m_tracepoints.addMetadata(e, key, value); + xpti::result_t addMetadata(xpti::trace_event_data_t *Event, const char *Key, + const char *Value) { + return MTracepoints.addMetadata(Event, Key, Value); } xpti::trace_event_data_t * - createEvent(const xpti::payload_t *payload, uint16_t event_type, - xpti::trace_activity_type_t activity_type, - uint64_t *instance_no) { - if (!payload || !instance_no) + createEvent(const xpti::payload_t *Payload, uint16_t EventType, + xpti::trace_activity_type_t ActivityType, uint64_t *InstanceNo) { + if (!Payload || !InstanceNo) return nullptr; - if (payload->flags == 0) + if (Payload->flags == 0) return nullptr; - xpti::trace_event_data_t *e = m_tracepoints.create(payload, instance_no); + xpti::trace_event_data_t *Event = MTracepoints.create(Payload, InstanceNo); // Event is not managed by anyone. The unique_id that is a part of the event // structure can be used to determine the payload that forms the event. The - // attribute 'ev.user_data' and 'ev.reserved' can be used to store user + // attribute 'ev.UserData' and 'ev.reserved' can be used to store user // defined and system defined data respectively. Currently the 'reserved' // field is not used, but object lifetime management must be employed once // this is active. // - // On the other hand, the 'user_data' field is for user data and should be + // On the other hand, the 'UserData' field is for user data and should be // managed by the user code. The framework will NOT free any memory // allocated to this pointer - e->event_type = event_type; - e->activity_type = (uint16_t)activity_type; - return e; + Event->event_type = EventType; + Event->activity_type = (uint16_t)ActivityType; + return Event; } - inline const xpti::trace_event_data_t *findEvent(int64_t universal_id) { - return m_tracepoints.eventData(universal_id); + inline const xpti::trace_event_data_t *findEvent(int64_t UniversalID) { + return MTracepoints.eventData(UniversalID); } - xpti::result_t initializeStream(const char *stream, uint32_t major_revision, - uint32_t minor_revision, - const char *version_string) { - if (!stream || !version_string) + xpti::result_t initializeStream(const char *Stream, uint32_t MajorRevision, + uint32_t MinorRevision, + const char *VersionString) { + if (!Stream || !VersionString) return xpti::result_t::XPTI_RESULT_INVALIDARG; - m_subscribers.initializeForStream(stream, major_revision, minor_revision, - version_string); + MSubscribers.initializeForStream(Stream, MajorRevision, MinorRevision, + VersionString); return xpti::result_t::XPTI_RESULT_SUCCESS; } - uint8_t registerStream(const char *stream_name) { - return (uint8_t)m_stream_string_table.add(stream_name); + uint8_t registerStream(const char *StreamName) { + return (uint8_t)MStreamStringTable.add(StreamName); } void closeAllStreams() { - auto table = m_stream_string_table.table(); + auto Table = MStreamStringTable.table(); StringTable::st_reverse_t::iterator it; - for (it = table.begin(); it != table.end(); ++it) { + for (it = Table.begin(); it != Table.end(); ++it) { xptiFinalize(it->second); } } - xpti::result_t unregisterStream(const char *stream_name) { - return finalizeStream(stream_name); + xpti::result_t unregisterStream(const char *StreamName) { + return finalizeStream(StreamName); } - uint8_t registerVendor(const char *stream_name) { - return (uint8_t)m_vendor_string_table.add(stream_name); + uint8_t registerVendor(const char *StreamName) { + return (uint8_t)MVendorStringTable.add(StreamName); } - string_id_t registerString(const char *string, char **table_string) { - if (!table_string || !string) + string_id_t registerString(const char *String, char **TableString) { + if (!TableString || !String) return xpti::invalid_id; - *table_string = 0; + *TableString = 0; - const char *ref_str; - auto id = m_string_table.add(string, &ref_str); - *table_string = const_cast(ref_str); + const char *RefStr; + auto ID = MStringTableRef.add(String, &RefStr); + *TableString = const_cast(RefStr); - return id; + return ID; } - const char *lookupString(string_id_t id) { - if (id < 0) + const char *lookupString(string_id_t ID) { + if (ID < 0) return nullptr; - return m_string_table.query(id); + return MStringTableRef.query(ID); } - xpti::result_t registerCallback(uint8_t stream_id, uint16_t trace_type, - xpti::tracepoint_callback_api_t cb) { - return m_notifier.registerCallback(stream_id, trace_type, cb); + xpti::result_t registerCallback(uint8_t StreamID, uint16_t TraceType, + xpti::tracepoint_callback_api_t cbFunc) { + return MNotifier.registerCallback(StreamID, TraceType, cbFunc); } - xpti::result_t unregisterCallback(uint8_t stream_id, uint16_t trace_type, - xpti::tracepoint_callback_api_t cb) { - return m_notifier.unregisterCallback(stream_id, trace_type, cb); + xpti::result_t unregisterCallback(uint8_t StreamID, uint16_t TraceType, + xpti::tracepoint_callback_api_t cbFunc) { + return MNotifier.unregisterCallback(StreamID, TraceType, cbFunc); } - xpti::result_t notifySubscribers(uint8_t stream_id, uint16_t trace_type, - xpti::trace_event_data_t *parent, - xpti::trace_event_data_t *object, - uint64_t instance, const void *user_data) { - if (!m_trace_enabled) + xpti::result_t notifySubscribers(uint8_t StreamID, uint16_t TraceType, + xpti::trace_event_data_t *Parent, + xpti::trace_event_data_t *Object, + uint64_t InstanceNo, const void *UserData) { + if (!MTraceEnabled) return xpti::result_t::XPTI_RESULT_FALSE; - if (!object) + if (!Object) return xpti::result_t::XPTI_RESULT_INVALIDARG; // - // Notify all subscribers for the stream 'stream_id' + // Notify all subscribers for the stream 'StreamID' // - return m_notifier.notifySubscribers(stream_id, trace_type, parent, object, - instance, user_data); + return MNotifier.notifySubscribers(StreamID, TraceType, Parent, Object, + InstanceNo, UserData); } - bool hasSubscribers() { return m_subscribers.hasValidSubscribers(); } + bool hasSubscribers() { return MSubscribers.hasValidSubscribers(); } - xpti::result_t finalizeStream(const char *stream) { - if (!stream) + xpti::result_t finalizeStream(const char *Stream) { + if (!Stream) return xpti::result_t::XPTI_RESULT_INVALIDARG; - m_subscribers.finalizeForStream(stream); - return m_notifier.unregisterStream(m_stream_string_table.add(stream)); + MSubscribers.finalizeForStream(Stream); + return MNotifier.unregisterStream(MStreamStringTable.add(Stream)); } - const xpti::payload_t *queryPayload(xpti::trace_event_data_t *e) { - return m_tracepoints.payloadData(e); + const xpti::payload_t *queryPayload(xpti::trace_event_data_t *Event) { + return MTracepoints.payloadData(Event); } void printStatistics() { - m_notifier.printStatistics(); - m_string_table.printStatistics(); - m_tracepoints.printStatistics(); + MNotifier.printStatistics(); + MStringTableRef.printStatistics(); + MTracepoints.printStatistics(); } private: /// Thread-safe counter used for generating universal IDs - xpti::safe_uint64_t m_universal_ids; + xpti::safe_uint64_t MUniversalIDs; /// Manages loading the subscribers and calling their init() functions - xpti::Subscribers m_subscribers; + xpti::Subscribers MSubscribers; /// Used to send event notification to subscribers - xpti::Notifications m_notifier; + xpti::Notifications MNotifier; /// Thread-safe string table - xpti::StringTable m_string_table; + xpti::StringTable MStringTableRef; /// Thread-safe string table, used for stream IDs - xpti::StringTable m_stream_string_table; + xpti::StringTable MStreamStringTable; /// Thread-safe string table, used for vendor IDs - xpti::StringTable m_vendor_string_table; + xpti::StringTable MVendorStringTable; /// Manages the tracepoints - framework caching - xpti::Tracepoints m_tracepoints; + xpti::Tracepoints MTracepoints; /// Flag indicates whether tracing should be enabled - bool m_trace_enabled; + bool MTraceEnabled; }; -static Framework g_framework; +static Framework GXPTIFramework; } // namespace xpti extern "C" { -XPTI_EXPORT_API uint16_t xptiRegisterUserDefinedTracePoint( - const char *tool_name, uint8_t user_defined_tp) { - uint8_t tool_id = xpti::g_framework.registerVendor(tool_name); - user_defined_tp |= (uint8_t)xpti::trace_point_type_t::user_defined; - uint16_t usr_def_tp = XPTI_PACK08_RET16(tool_id, user_defined_tp); +XPTI_EXPORT_API uint16_t +xptiRegisterUserDefinedTracePoint(const char *ToolName, uint8_t UserDefinedTP) { + uint8_t ToolID = xpti::GXPTIFramework.registerVendor(ToolName); + UserDefinedTP |= (uint8_t)xpti::trace_point_type_t::user_defined; + uint16_t UserDefTracepoint = XPTI_PACK08_RET16(ToolID, UserDefinedTP); - return usr_def_tp; + return UserDefTracepoint; } XPTI_EXPORT_API uint16_t xptiRegisterUserDefinedEventType( - const char *tool_name, uint8_t user_defined_event) { - uint8_t tool_id = xpti::g_framework.registerVendor(tool_name); - user_defined_event |= (uint8_t)xpti::trace_event_type_t::user_defined; - uint16_t usr_def_ev = XPTI_PACK08_RET16(tool_id, user_defined_event); - return usr_def_ev; + const char *ToolName, uint8_t UserDefinedEvent) { + uint8_t ToolID = xpti::GXPTIFramework.registerVendor(ToolName); + UserDefinedEvent |= (uint8_t)xpti::trace_event_type_t::user_defined; + uint16_t UserDefEventType = XPTI_PACK08_RET16(ToolID, UserDefinedEvent); + return UserDefEventType; } -XPTI_EXPORT_API xpti::result_t xptiInitialize(const char *stream, uint32_t maj, +XPTI_EXPORT_API xpti::result_t xptiInitialize(const char *Stream, uint32_t maj, uint32_t min, const char *version) { - return xpti::g_framework.initializeStream(stream, maj, min, version); + return xpti::GXPTIFramework.initializeStream(Stream, maj, min, version); } -XPTI_EXPORT_API void xptiFinalize(const char *stream) { - xpti::g_framework.finalizeStream(stream); +XPTI_EXPORT_API void xptiFinalize(const char *Stream) { + xpti::GXPTIFramework.finalizeStream(Stream); } XPTI_EXPORT_API uint64_t xptiGetUniqueId() { - return xpti::g_framework.makeUniqueID(); + return xpti::GXPTIFramework.makeUniqueID(); } -XPTI_EXPORT_API xpti::string_id_t xptiRegisterString(const char *string, - char **table_string) { - return xpti::g_framework.registerString(string, table_string); +XPTI_EXPORT_API xpti::string_id_t xptiRegisterString(const char *String, + char **RefTableStr) { + return xpti::GXPTIFramework.registerString(String, RefTableStr); } -XPTI_EXPORT_API const char *xptiLookupString(xpti::string_id_t id) { - return xpti::g_framework.lookupString(id); +XPTI_EXPORT_API const char *xptiLookupString(xpti::string_id_t ID) { + return xpti::GXPTIFramework.lookupString(ID); } -XPTI_EXPORT_API uint8_t xptiRegisterStream(const char *stream_name) { - return xpti::g_framework.registerStream(stream_name); +XPTI_EXPORT_API uint8_t xptiRegisterStream(const char *StreamName) { + return xpti::GXPTIFramework.registerStream(StreamName); } -XPTI_EXPORT_API xpti::result_t xptiUnregisterStream(const char *stream_name) { - return xpti::g_framework.unregisterStream(stream_name); +XPTI_EXPORT_API xpti::result_t xptiUnregisterStream(const char *StreamName) { + return xpti::GXPTIFramework.unregisterStream(StreamName); } XPTI_EXPORT_API xpti::trace_event_data_t * -xptiMakeEvent(const char *name, xpti::payload_t *payload, uint16_t event, - xpti::trace_activity_type_t activity, uint64_t *instance_no) { - return xpti::g_framework.createEvent(payload, event, activity, instance_no); +xptiMakeEvent(const char * /*Name*/, xpti::payload_t *Payload, uint16_t Event, + xpti::trace_activity_type_t Activity, uint64_t *InstanceNo) { + return xpti::GXPTIFramework.createEvent(Payload, Event, Activity, InstanceNo); } -XPTI_EXPORT_API void xptiReset() { xpti::g_framework.clear(); } +XPTI_EXPORT_API void xptiReset() { xpti::GXPTIFramework.clear(); } -XPTI_EXPORT_API const xpti::trace_event_data_t *xptiFindEvent(int64_t uid) { - return xpti::g_framework.findEvent(uid); +XPTI_EXPORT_API const xpti::trace_event_data_t *xptiFindEvent(int64_t UId) { + return xpti::GXPTIFramework.findEvent(UId); } XPTI_EXPORT_API const xpti::payload_t * -xptiQueryPayload(xpti::trace_event_data_t *lookup_object) { - return xpti::g_framework.queryPayload(lookup_object); +xptiQueryPayload(xpti::trace_event_data_t *LookupObject) { + return xpti::GXPTIFramework.queryPayload(LookupObject); } XPTI_EXPORT_API xpti::result_t -xptiRegisterCallback(uint8_t stream_id, uint16_t trace_type, - xpti::tracepoint_callback_api_t cb) { - return xpti::g_framework.registerCallback(stream_id, trace_type, cb); +xptiRegisterCallback(uint8_t StreamID, uint16_t TraceType, + xpti::tracepoint_callback_api_t cbFunc) { + return xpti::GXPTIFramework.registerCallback(StreamID, TraceType, cbFunc); } XPTI_EXPORT_API xpti::result_t -xptiUnregisterCallback(uint8_t stream_id, uint16_t trace_type, - xpti::tracepoint_callback_api_t cb) { - return xpti::g_framework.unregisterCallback(stream_id, trace_type, cb); +xptiUnregisterCallback(uint8_t StreamID, uint16_t TraceType, + xpti::tracepoint_callback_api_t cbFunc) { + return xpti::GXPTIFramework.unregisterCallback(StreamID, TraceType, cbFunc); } XPTI_EXPORT_API xpti::result_t -xptiNotifySubscribers(uint8_t stream_id, uint16_t trace_type, - xpti::trace_event_data_t *parent, - xpti::trace_event_data_t *object, uint64_t instance, - const void *temporal_user_data) { - return xpti::g_framework.notifySubscribers( - stream_id, trace_type, parent, object, instance, temporal_user_data); +xptiNotifySubscribers(uint8_t StreamID, uint16_t TraceType, + xpti::trace_event_data_t *Parent, + xpti::trace_event_data_t *Object, uint64_t InstanceNo, + const void *TemporalUserData) { + return xpti::GXPTIFramework.notifySubscribers( + StreamID, TraceType, Parent, Object, InstanceNo, TemporalUserData); } XPTI_EXPORT_API bool xptiTraceEnabled() { - return xpti::g_framework.traceEnabled(); + return xpti::GXPTIFramework.traceEnabled(); } -XPTI_EXPORT_API xpti::result_t xptiAddMetadata(xpti::trace_event_data_t *e, - const char *key, - const char *value) { - return xpti::g_framework.addMetadata(e, key, value); +XPTI_EXPORT_API xpti::result_t xptiAddMetadata(xpti::trace_event_data_t *Event, + const char *Key, + const char *Value) { + return xpti::GXPTIFramework.addMetadata(Event, Key, Value); } XPTI_EXPORT_API xpti::metadata_t * -xptiQueryMetadata(xpti::trace_event_data_t *e) { - return &e->reserved.metadata; +xptiQueryMetadata(xpti::trace_event_data_t *Event) { + return &Event->reserved.metadata; } -XPTI_EXPORT_API void xptiForceSetTraceEnabled(bool yesOrNo) { - xpti::g_framework.setTraceEnabled(yesOrNo); +XPTI_EXPORT_API void xptiForceSetTraceEnabled(bool YesOrNo) { + xpti::GXPTIFramework.setTraceEnabled(YesOrNo); } } // extern "C" diff --git a/xptifw/unit_test/xpti_api_tests.cpp b/xptifw/unit_test/xpti_api_tests.cpp index ae048a7b646a8..8b5b6e6c63bca 100644 --- a/xptifw/unit_test/xpti_api_tests.cpp +++ b/xptifw/unit_test/xpti_api_tests.cpp @@ -11,310 +11,309 @@ #include TEST(xptiApiTest, xptiInitializeBadInput) { - auto result = xptiInitialize(nullptr, 0, 0, nullptr); - EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_INVALIDARG); + auto Result = xptiInitialize(nullptr, 0, 0, nullptr); + EXPECT_EQ(Result, xpti::result_t::XPTI_RESULT_INVALIDARG); } TEST(xptiApiTest, xptiRegisterStringBadInput) { - char *tstr; - - auto id = xptiRegisterString(nullptr, nullptr); - EXPECT_EQ(id, xpti::invalid_id); - id = xptiRegisterString(nullptr, &tstr); - EXPECT_EQ(id, xpti::invalid_id); - id = xptiRegisterString("foo", nullptr); - EXPECT_EQ(id, xpti::invalid_id); + char *TStr; + + auto ID = xptiRegisterString(nullptr, nullptr); + EXPECT_EQ(ID, xpti::invalid_id); + ID = xptiRegisterString(nullptr, &TStr); + EXPECT_EQ(ID, xpti::invalid_id); + ID = xptiRegisterString("foo", nullptr); + EXPECT_EQ(ID, xpti::invalid_id); } TEST(xptiApiTest, xptiRegisterStringGoodInput) { - char *tstr = nullptr; + char *TStr = nullptr; - auto id = xptiRegisterString("foo", &tstr); - EXPECT_NE(id, xpti::invalid_id); - EXPECT_NE(tstr, nullptr); - EXPECT_STREQ("foo", tstr); + auto ID = xptiRegisterString("foo", &TStr); + EXPECT_NE(ID, xpti::invalid_id); + EXPECT_NE(TStr, nullptr); + EXPECT_STREQ("foo", TStr); } TEST(xptiApiTest, xptiLookupStringBadInput) { - const char *tstr; + const char *TStr; xptiReset(); - tstr = xptiLookupString(-1); - EXPECT_EQ(tstr, nullptr); + TStr = xptiLookupString(-1); + EXPECT_EQ(TStr, nullptr); } TEST(xptiApiTest, xptiLookupStringGoodInput) { - char *tstr = nullptr; + char *TStr = nullptr; - auto id = xptiRegisterString("foo", &tstr); - EXPECT_NE(id, xpti::invalid_id); - EXPECT_NE(tstr, nullptr); - EXPECT_STREQ("foo", tstr); + auto ID = xptiRegisterString("foo", &TStr); + EXPECT_NE(ID, xpti::invalid_id); + EXPECT_NE(TStr, nullptr); + EXPECT_STREQ("foo", TStr); - const char *lstr = xptiLookupString(id); - EXPECT_EQ(lstr, tstr); - EXPECT_STREQ(lstr, tstr); - EXPECT_STREQ("foo", lstr); + const char *LookUpString = xptiLookupString(ID); + EXPECT_EQ(LookUpString, TStr); + EXPECT_STREQ(LookUpString, TStr); + EXPECT_STREQ("foo", LookUpString); } TEST(xptiApiTest, xptiGetUniqueId) { - std::set ids; + std::set IDs; for (int i = 0; i < 10; ++i) { - auto id = xptiGetUniqueId(); - auto loc = ids.find(id); - EXPECT_EQ(loc, ids.end()); - ids.insert(id); + auto ID = xptiGetUniqueId(); + auto Loc = IDs.find(ID); + EXPECT_EQ(Loc, IDs.end()); + IDs.insert(ID); } } TEST(xptiApiTest, xptiRegisterStreamBadInput) { - auto id = xptiRegisterStream(nullptr); - EXPECT_EQ(id, (uint8_t)xpti::invalid_id); + auto ID = xptiRegisterStream(nullptr); + EXPECT_EQ(ID, (uint8_t)xpti::invalid_id); } TEST(xptiApiTest, xptiRegisterStreamGoodInput) { - auto id = xptiRegisterStream("foo"); - EXPECT_NE(id, xpti::invalid_id); - auto new_id = xptiRegisterStream("foo"); - EXPECT_EQ(id, new_id); + auto ID = xptiRegisterStream("foo"); + EXPECT_NE(ID, xpti::invalid_id); + auto NewID = xptiRegisterStream("foo"); + EXPECT_EQ(ID, NewID); } TEST(xptiApiTest, xptiUnregisterStreamBadInput) { - auto result = xptiUnregisterStream(nullptr); - EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_INVALIDARG); + auto Result = xptiUnregisterStream(nullptr); + EXPECT_EQ(Result, xpti::result_t::XPTI_RESULT_INVALIDARG); } TEST(xptiApiTest, xptiUnregisterStreamGoodInput) { - auto id = xptiRegisterStream("foo"); - EXPECT_NE(id, xpti::invalid_id); - auto res = xptiUnregisterStream("NoSuchStream"); - EXPECT_EQ(res, xpti::result_t::XPTI_RESULT_NOTFOUND); + auto ID = xptiRegisterStream("foo"); + EXPECT_NE(ID, xpti::invalid_id); + auto Result = xptiUnregisterStream("NoSuchStream"); + EXPECT_EQ(Result, xpti::result_t::XPTI_RESULT_NOTFOUND); // Event though stream exists, no callbacks registered - auto new_res = xptiUnregisterStream("foo"); - EXPECT_EQ(new_res, xpti::result_t::XPTI_RESULT_NOTFOUND); + auto NewResult = xptiUnregisterStream("foo"); + EXPECT_EQ(NewResult, xpti::result_t::XPTI_RESULT_NOTFOUND); } TEST(xptiApiTest, xptiMakeEventBadInput) { - xpti::payload_t p; - auto result = - xptiMakeEvent(nullptr, &p, 0, (xpti::trace_activity_type_t)1, nullptr); - EXPECT_EQ(result, nullptr); - p = xpti::payload_t("foo", "foo.cpp", 1, 0, (void *)13); - EXPECT_NE(p.flags, 0); - result = - xptiMakeEvent(nullptr, &p, 0, (xpti::trace_activity_type_t)1, nullptr); - EXPECT_EQ(result, nullptr); - result = xptiMakeEvent("foo", &p, 0, (xpti::trace_activity_type_t)1, nullptr); - EXPECT_EQ(result, nullptr); + xpti::payload_t P; + auto Result = + xptiMakeEvent(nullptr, &P, 0, (xpti::trace_activity_type_t)1, nullptr); + EXPECT_EQ(Result, nullptr); + P = xpti::payload_t("foo", "foo.cpp", 1, 0, (void *)13); + EXPECT_NE(P.flags, 0); + Result = + xptiMakeEvent(nullptr, &P, 0, (xpti::trace_activity_type_t)1, nullptr); + EXPECT_EQ(Result, nullptr); + Result = xptiMakeEvent("foo", &P, 0, (xpti::trace_activity_type_t)1, nullptr); + EXPECT_EQ(Result, nullptr); } TEST(xptiApiTest, xptiMakeEventGoodInput) { uint64_t instance; - xpti::payload_t p("foo", "foo.cpp", 1, 0, (void *)13); - auto result = - xptiMakeEvent("foo", &p, 0, (xpti::trace_activity_type_t)1, &instance); - EXPECT_NE(result, nullptr); + xpti::payload_t Payload("foo", "foo.cpp", 1, 0, (void *)13); + auto Result = xptiMakeEvent("foo", &Payload, 0, + (xpti::trace_activity_type_t)1, &instance); + EXPECT_NE(Result, nullptr); EXPECT_EQ(instance, 1); - p = xpti::payload_t("foo", "foo.cpp", 1, 0, (void *)13); - auto new_result = - xptiMakeEvent("foo", &p, 0, (xpti::trace_activity_type_t)1, &instance); - EXPECT_EQ(result, new_result); + Payload = xpti::payload_t("foo", "foo.cpp", 1, 0, (void *)13); + auto NewResult = xptiMakeEvent("foo", &Payload, 0, + (xpti::trace_activity_type_t)1, &instance); + EXPECT_EQ(Result, NewResult); EXPECT_EQ(instance, 2); } TEST(xptiApiTest, xptiFindEventBadInput) { - auto result = xptiFindEvent(0); - EXPECT_EQ(result, nullptr); - result = xptiFindEvent(1000000); - EXPECT_EQ(result, nullptr); + auto Result = xptiFindEvent(0); + EXPECT_EQ(Result, nullptr); + Result = xptiFindEvent(1000000); + EXPECT_EQ(Result, nullptr); } TEST(xptiApiTest, xptiFindEventGoodInput) { - uint64_t instance; - xpti::payload_t p("foo", "foo.cpp", 1, 0, (void *)13); - - auto result = - xptiMakeEvent("foo", &p, 0, (xpti::trace_activity_type_t)1, &instance); - EXPECT_NE(result, nullptr); - EXPECT_GT(instance, 1); - auto new_result = xptiFindEvent(result->unique_id); - EXPECT_EQ(result, new_result); + uint64_t Instance; + xpti::payload_t Payload("foo", "foo.cpp", 1, 0, (void *)13); + + auto Result = xptiMakeEvent("foo", &Payload, 0, + (xpti::trace_activity_type_t)1, &Instance); + EXPECT_NE(Result, nullptr); + EXPECT_GT(Instance, 1); + auto NewResult = xptiFindEvent(Result->unique_id); + EXPECT_EQ(Result, NewResult); } TEST(xptiApiTest, xptiQueryPayloadBadInput) { - auto result = xptiQueryPayload(nullptr); - EXPECT_EQ(result, nullptr); + auto Result = xptiQueryPayload(nullptr); + EXPECT_EQ(Result, nullptr); } TEST(xptiApiTest, xptiQueryPayloadGoodInput) { uint64_t instance; - xpti::payload_t p("foo", "foo.cpp", 1, 0, (void *)13); - ; - auto result = - xptiMakeEvent("foo", &p, 0, (xpti::trace_activity_type_t)1, &instance); - EXPECT_NE(result, nullptr); + xpti::payload_t Payload("foo", "foo.cpp", 1, 0, (void *)13); + auto Result = xptiMakeEvent("foo", &Payload, 0, + (xpti::trace_activity_type_t)1, &instance); + EXPECT_NE(Result, nullptr); EXPECT_GT(instance, 1); - auto new_result = xptiQueryPayload(result); - EXPECT_STREQ(p.name, new_result->name); - EXPECT_STREQ(p.source_file, new_result->source_file); - // new_result->name_sid will have a string id whereas 'p' will not - EXPECT_NE(p.name_sid, new_result->name_sid); - EXPECT_NE(p.source_file_sid, new_result->source_file_sid); - EXPECT_EQ(p.line_no, new_result->line_no); + auto NewResult = xptiQueryPayload(Result); + EXPECT_STREQ(Payload.name, NewResult->name); + EXPECT_STREQ(Payload.source_file, NewResult->source_file); + // NewResult->name_sid will have a string ID whereas 'Payload' will not + EXPECT_NE(Payload.name_sid, NewResult->name_sid); + EXPECT_NE(Payload.source_file_sid, NewResult->source_file_sid); + EXPECT_EQ(Payload.line_no, NewResult->line_no); } TEST(xptiApiTest, xptiTraceEnabled) { // If no env is set, this should be false // The state is determined at app startup // XPTI_TRACE_ENABLE=1 or 0 and XPTI_FRAMEWORK_DISPATCHER= - // result false - auto result = xptiTraceEnabled(); - EXPECT_EQ(result, false); + // Result false + auto Result = xptiTraceEnabled(); + EXPECT_EQ(Result, false); } -XPTI_CALLBACK_API void trace_point_callback( - uint16_t trace_type, - xpti::trace_event_data_t *parent, - xpti::trace_event_data_t *event, - uint64_t instance, - const void *user_data) { +XPTI_CALLBACK_API void trace_point_callback(uint16_t trace_type, + xpti::trace_event_data_t *parent, + xpti::trace_event_data_t *event, + uint64_t instance, + const void *user_data) { - if(user_data) - (*(int *)user_data) = 1; + if (user_data) + (*(int *)user_data) = 1; } -XPTI_CALLBACK_API void trace_point_callback2( - uint16_t trace_type, - xpti::trace_event_data_t *parent, - xpti::trace_event_data_t *event, - uint64_t instance, - const void *user_data) { - if(user_data) - (*(int *)user_data) = 1; +XPTI_CALLBACK_API void trace_point_callback2(uint16_t trace_type, + xpti::trace_event_data_t *parent, + xpti::trace_event_data_t *event, + uint64_t instance, + const void *user_data) { + if (user_data) + (*(int *)user_data) = 1; } TEST(xptiApiTest, xptiRegisterCallbackBadInput) { - uint8_t stream_id = xptiRegisterStream("foo"); - auto result = xptiRegisterCallback(stream_id, 1, nullptr); - EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_INVALIDARG); + uint8_t StreamID = xptiRegisterStream("foo"); + auto Result = xptiRegisterCallback(StreamID, 1, nullptr); + EXPECT_EQ(Result, xpti::result_t::XPTI_RESULT_INVALIDARG); } TEST(xptiApiTest, xptiRegisterCallbackGoodInput) { uint64_t instance; - xpti::payload_t p("foo", "foo.cpp", 1, 0, (void *)13); + xpti::payload_t Payload("foo", "foo.cpp", 1, 0, (void *)13); - auto event = - xptiMakeEvent("foo", &p, 0, (xpti::trace_activity_type_t)1, &instance); - EXPECT_NE(event, nullptr); + auto Event = xptiMakeEvent("foo", &Payload, 0, (xpti::trace_activity_type_t)1, + &instance); + EXPECT_NE(Event, nullptr); - uint8_t stream_id = xptiRegisterStream("foo"); - auto result = xptiRegisterCallback(stream_id, 1, trace_point_callback); - EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_SUCCESS); - result = xptiRegisterCallback(stream_id, 1, trace_point_callback); - EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_DUPLICATE); + uint8_t StreamID = xptiRegisterStream("foo"); + auto Result = xptiRegisterCallback(StreamID, 1, trace_point_callback); + EXPECT_EQ(Result, xpti::result_t::XPTI_RESULT_SUCCESS); + Result = xptiRegisterCallback(StreamID, 1, trace_point_callback); + EXPECT_EQ(Result, xpti::result_t::XPTI_RESULT_DUPLICATE); } TEST(xptiApiTest, xptiUnregisterCallbackBadInput) { - uint8_t stream_id = xptiRegisterStream("foo"); - auto result = xptiUnregisterCallback(stream_id, 1, nullptr); - EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_INVALIDARG); + uint8_t StreamID = xptiRegisterStream("foo"); + auto Result = xptiUnregisterCallback(StreamID, 1, nullptr); + EXPECT_EQ(Result, xpti::result_t::XPTI_RESULT_INVALIDARG); } TEST(xptiApiTest, xptiUnregisterCallbackGoodInput) { uint64_t instance; - xpti::payload_t p("foo", "foo.cpp", 1, 0, (void *)13); - - auto event = - xptiMakeEvent("foo", &p, 0, (xpti::trace_activity_type_t)1, &instance); - EXPECT_NE(event, nullptr); - - uint8_t stream_id = xptiRegisterStream("foo"); - auto result = xptiUnregisterCallback(stream_id, 1, trace_point_callback2); - EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_NOTFOUND); - result = xptiRegisterCallback(stream_id, 1, trace_point_callback2); - EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_SUCCESS); - result = xptiUnregisterCallback(stream_id, 1, trace_point_callback2); - EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_SUCCESS); - result = xptiUnregisterCallback(stream_id, 1, trace_point_callback2); - EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_DUPLICATE); - result = xptiRegisterCallback(stream_id, 1, trace_point_callback2); - EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_UNDELETE); + xpti::payload_t Payload("foo", "foo.cpp", 1, 0, (void *)13); + + auto Event = xptiMakeEvent("foo", &Payload, 0, (xpti::trace_activity_type_t)1, + &instance); + EXPECT_NE(Event, nullptr); + + uint8_t StreamID = xptiRegisterStream("foo"); + auto Result = xptiUnregisterCallback(StreamID, 1, trace_point_callback2); + EXPECT_EQ(Result, xpti::result_t::XPTI_RESULT_NOTFOUND); + Result = xptiRegisterCallback(StreamID, 1, trace_point_callback2); + EXPECT_EQ(Result, xpti::result_t::XPTI_RESULT_SUCCESS); + Result = xptiUnregisterCallback(StreamID, 1, trace_point_callback2); + EXPECT_EQ(Result, xpti::result_t::XPTI_RESULT_SUCCESS); + Result = xptiUnregisterCallback(StreamID, 1, trace_point_callback2); + EXPECT_EQ(Result, xpti::result_t::XPTI_RESULT_DUPLICATE); + Result = xptiRegisterCallback(StreamID, 1, trace_point_callback2); + EXPECT_EQ(Result, xpti::result_t::XPTI_RESULT_UNDELETE); } TEST(xptiApiTest, xptiNotifySubscribersBadInput) { - uint8_t stream_id = xptiRegisterStream("foo"); - auto result = xptiNotifySubscribers(stream_id, 1, nullptr, nullptr, 0, nullptr); - EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_FALSE); + uint8_t StreamID = xptiRegisterStream("foo"); + auto Result = + xptiNotifySubscribers(StreamID, 1, nullptr, nullptr, 0, nullptr); + EXPECT_EQ(Result, xpti::result_t::XPTI_RESULT_FALSE); xptiForceSetTraceEnabled(true); - result = xptiNotifySubscribers(stream_id, 1, nullptr, nullptr, 0, nullptr); - EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_INVALIDARG); + Result = xptiNotifySubscribers(StreamID, 1, nullptr, nullptr, 0, nullptr); + EXPECT_EQ(Result, xpti::result_t::XPTI_RESULT_INVALIDARG); } TEST(xptiApiTest, xptiNotifySubscribersGoodInput) { uint64_t instance; - xpti::payload_t p("foo", "foo.cpp", 1, 0, (void *)13); + xpti::payload_t Payload("foo", "foo.cpp", 1, 0, (void *)13); - auto event = - xptiMakeEvent("foo", &p, 0, (xpti::trace_activity_type_t)1, &instance); - EXPECT_NE(event, nullptr); + auto Event = xptiMakeEvent("foo", &Payload, 0, (xpti::trace_activity_type_t)1, + &instance); + EXPECT_NE(Event, nullptr); - uint8_t stream_id = xptiRegisterStream("foo"); + uint8_t StreamID = xptiRegisterStream("foo"); xptiForceSetTraceEnabled(true); int foo_return = 0; - auto result = xptiRegisterCallback(stream_id, 1, trace_point_callback2); - result = xptiNotifySubscribers(stream_id, 1, nullptr, event, 0, (void *)(&foo_return)); - EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_SUCCESS); + auto Result = xptiRegisterCallback(StreamID, 1, trace_point_callback2); + Result = xptiNotifySubscribers(StreamID, 1, nullptr, Event, 0, + (void *)(&foo_return)); + EXPECT_EQ(Result, xpti::result_t::XPTI_RESULT_SUCCESS); EXPECT_EQ(foo_return, 1); } TEST(xptiApiTest, xptiAddMetadataBadInput) { uint64_t instance; - xpti::payload_t p("foo", "foo.cpp", 1, 0, (void *)13); - - auto event = - xptiMakeEvent("foo", &p, 0, (xpti::trace_activity_type_t)1, &instance); - EXPECT_NE(event, nullptr); - - auto result = xptiAddMetadata(nullptr, nullptr, nullptr); - EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_INVALIDARG); - result = xptiAddMetadata(event, nullptr, nullptr); - EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_INVALIDARG); - result = xptiAddMetadata(event, "foo", nullptr); - EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_INVALIDARG); - result = xptiAddMetadata(event, nullptr, "bar"); - EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_INVALIDARG); + xpti::payload_t Payload("foo", "foo.cpp", 1, 0, (void *)13); + + auto Event = xptiMakeEvent("foo", &Payload, 0, (xpti::trace_activity_type_t)1, + &instance); + EXPECT_NE(Event, nullptr); + + auto Result = xptiAddMetadata(nullptr, nullptr, nullptr); + EXPECT_EQ(Result, xpti::result_t::XPTI_RESULT_INVALIDARG); + Result = xptiAddMetadata(Event, nullptr, nullptr); + EXPECT_EQ(Result, xpti::result_t::XPTI_RESULT_INVALIDARG); + Result = xptiAddMetadata(Event, "foo", nullptr); + EXPECT_EQ(Result, xpti::result_t::XPTI_RESULT_INVALIDARG); + Result = xptiAddMetadata(Event, nullptr, "bar"); + EXPECT_EQ(Result, xpti::result_t::XPTI_RESULT_INVALIDARG); } TEST(xptiApiTest, xptiAddMetadataGoodInput) { uint64_t instance; - xpti::payload_t p("foo", "foo.cpp", 1, 0, (void *)13); + xpti::payload_t Payload("foo", "foo.cpp", 1, 0, (void *)13); - auto event = - xptiMakeEvent("foo", &p, 0, (xpti::trace_activity_type_t)1, &instance); - EXPECT_NE(event, nullptr); + auto Event = xptiMakeEvent("foo", &Payload, 0, (xpti::trace_activity_type_t)1, + &instance); + EXPECT_NE(Event, nullptr); - auto result = xptiAddMetadata(event, "foo", "bar"); - EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_SUCCESS); - result = xptiAddMetadata(event, "foo", "bar"); - EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_DUPLICATE); + auto Result = xptiAddMetadata(Event, "foo", "bar"); + EXPECT_EQ(Result, xpti::result_t::XPTI_RESULT_SUCCESS); + Result = xptiAddMetadata(Event, "foo", "bar"); + EXPECT_EQ(Result, xpti::result_t::XPTI_RESULT_DUPLICATE); } TEST(xptiApiTest, xptiQueryMetadata) { uint64_t instance; - xpti::payload_t p("foo", "foo.cpp", 1, 0, (void *)13); + xpti::payload_t Payload("foo", "foo.cpp", 1, 0, (void *)13); - auto event = - xptiMakeEvent("foo", &p, 0, (xpti::trace_activity_type_t)1, &instance); - EXPECT_NE(event, nullptr); + auto Event = xptiMakeEvent("foo", &Payload, 0, (xpti::trace_activity_type_t)1, + &instance); + EXPECT_NE(Event, nullptr); - auto md = xptiQueryMetadata(event); + auto md = xptiQueryMetadata(Event); EXPECT_NE(md, nullptr); - auto result = xptiAddMetadata(event, "foo1", "bar1"); - EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_SUCCESS); + auto Result = xptiAddMetadata(Event, "foo1", "bar1"); + EXPECT_EQ(Result, xpti::result_t::XPTI_RESULT_SUCCESS); char *ts; EXPECT_TRUE(md->size() > 1); - auto id = (*md)[xptiRegisterString("foo1", &ts)]; - auto str = xptiLookupString(id); + auto ID = (*md)[xptiRegisterString("foo1", &ts)]; + auto str = xptiLookupString(ID); EXPECT_STREQ(str, "bar1"); } diff --git a/xptifw/unit_test/xpti_correctness_tests.cpp b/xptifw/unit_test/xpti_correctness_tests.cpp index cf80a7fee401b..5d11032898899 100644 --- a/xptifw/unit_test/xpti_correctness_tests.cpp +++ b/xptifw/unit_test/xpti_correctness_tests.cpp @@ -11,10 +11,10 @@ #include -XPTI_CALLBACK_API void tp_callback(uint16_t trace_type, - xpti::trace_event_data_t *parent, - xpti::trace_event_data_t *event, - uint64_t instance, const void *user_data) { +XPTI_CALLBACK_API void tpCallback(uint16_t trace_type, + xpti::trace_event_data_t *parent, + xpti::trace_event_data_t *event, + uint64_t instance, const void *user_data) { if (user_data) (*(int *)user_data) = trace_type; @@ -22,194 +22,187 @@ XPTI_CALLBACK_API void tp_callback(uint16_t trace_type, #define NOTIFY(stream, tt, event, retval) \ { \ - xpti::result_t result = xptiNotifySubscribers(stream, tt, nullptr, event, \ + xpti::result_t Result = xptiNotifySubscribers(stream, tt, nullptr, event, \ 0, (void *)(&retval)); \ - EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_SUCCESS); \ + EXPECT_EQ(Result, xpti::result_t::XPTI_RESULT_SUCCESS); \ EXPECT_EQ(retval, tt); \ } TEST(xptiCorrectnessTest, xptiMakeEvent) { - uint64_t instance = 0; + uint64_t Instance = 0; xpti::payload_t p("foo", "foo.cpp", 1, 0, (void *)13); - auto result = - xptiMakeEvent("foo", &p, 0, (xpti::trace_activity_type_t)1, &instance); - EXPECT_NE(result, nullptr); + auto Result = + xptiMakeEvent("foo", &p, 0, (xpti::trace_activity_type_t)1, &Instance); + EXPECT_NE(Result, nullptr); p = xpti::payload_t("foo", "foo.cpp", 1, 0, (void *)13); - auto new_result = - xptiMakeEvent("foo", &p, 0, (xpti::trace_activity_type_t)1, &instance); - EXPECT_EQ(result, new_result); - EXPECT_EQ(result->unique_id, new_result->unique_id); - EXPECT_EQ(result->reserved.payload, new_result->reserved.payload); - EXPECT_STREQ(result->reserved.payload->name, "foo"); - EXPECT_STREQ(result->reserved.payload->source_file, "foo.cpp"); - EXPECT_EQ(result->reserved.payload->line_no, 1); + auto NewResult = + xptiMakeEvent("foo", &p, 0, (xpti::trace_activity_type_t)1, &Instance); + EXPECT_EQ(Result, NewResult); + EXPECT_EQ(Result->unique_id, NewResult->unique_id); + EXPECT_EQ(Result->reserved.payload, NewResult->reserved.payload); + EXPECT_STREQ(Result->reserved.payload->name, "foo"); + EXPECT_STREQ(Result->reserved.payload->source_file, "foo.cpp"); + EXPECT_EQ(Result->reserved.payload->line_no, 1); } TEST(xptiCorrectnessTest, xptiRegisterString) { - char *tstr = nullptr; - auto id = xptiRegisterString("foo", &tstr); - EXPECT_NE(id, xpti::invalid_id); - EXPECT_NE(tstr, nullptr); - EXPECT_STREQ("foo", tstr); + char *TStr = nullptr; + auto ID = xptiRegisterString("foo", &TStr); + EXPECT_NE(ID, xpti::invalid_id); + EXPECT_NE(TStr, nullptr); + EXPECT_STREQ("foo", TStr); - const char *lutstr = xptiLookupString(id); - EXPECT_EQ(tstr, lutstr); - EXPECT_STREQ(lutstr, tstr); + const char *LUTStr = xptiLookupString(ID); + EXPECT_EQ(TStr, LUTStr); + EXPECT_STREQ(LUTStr, TStr); } TEST(xptiCorrectnessTest, xptiInitializeForDefaultTracePointTypes) { // We will test functionality of a subscriber // without actually creating a plugin - uint8_t stream_id = xptiRegisterStream("test_foo"); - auto result = xptiRegisterCallback( - stream_id, (uint16_t)xpti::trace_point_type_t::graph_create, tp_callback); - EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_SUCCESS); - result = xptiRegisterCallback( - stream_id, (uint16_t)xpti::trace_point_type_t::node_create, tp_callback); - EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_SUCCESS); - result = xptiRegisterCallback( - stream_id, (uint16_t)xpti::trace_point_type_t::edge_create, tp_callback); - EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_SUCCESS); - result = xptiRegisterCallback( - stream_id, (uint16_t)xpti::trace_point_type_t::region_begin, tp_callback); - EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_SUCCESS); - result = xptiRegisterCallback( - stream_id, (uint16_t)xpti::trace_point_type_t::region_end, tp_callback); - EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_SUCCESS); - result = xptiRegisterCallback( - stream_id, (uint16_t)xpti::trace_point_type_t::task_begin, tp_callback); - EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_SUCCESS); - result = xptiRegisterCallback( - stream_id, (uint16_t)xpti::trace_point_type_t::task_end, tp_callback); - EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_SUCCESS); - result = xptiRegisterCallback( - stream_id, (uint16_t)xpti::trace_point_type_t::barrier_begin, - tp_callback); - EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_SUCCESS); - result = xptiRegisterCallback( - stream_id, (uint16_t)xpti::trace_point_type_t::barrier_end, tp_callback); - EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_SUCCESS); - result = xptiRegisterCallback( - stream_id, (uint16_t)xpti::trace_point_type_t::lock_begin, tp_callback); - EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_SUCCESS); - result = xptiRegisterCallback( - stream_id, (uint16_t)xpti::trace_point_type_t::lock_end, tp_callback); - EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_SUCCESS); - result = xptiRegisterCallback( - stream_id, (uint16_t)xpti::trace_point_type_t::transfer_begin, - tp_callback); - EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_SUCCESS); - result = xptiRegisterCallback( - stream_id, (uint16_t)xpti::trace_point_type_t::transfer_end, tp_callback); - EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_SUCCESS); - result = xptiRegisterCallback( - stream_id, (uint16_t)xpti::trace_point_type_t::thread_begin, tp_callback); - EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_SUCCESS); - result = xptiRegisterCallback( - stream_id, (uint16_t)xpti::trace_point_type_t::thread_end, tp_callback); - EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_SUCCESS); - result = xptiRegisterCallback( - stream_id, (uint16_t)xpti::trace_point_type_t::wait_begin, tp_callback); - EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_SUCCESS); - result = xptiRegisterCallback( - stream_id, (uint16_t)xpti::trace_point_type_t::wait_end, tp_callback); - EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_SUCCESS); - result = xptiRegisterCallback( - stream_id, (uint16_t)xpti::trace_point_type_t::signal, tp_callback); - EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_SUCCESS); + uint8_t StreamID = xptiRegisterStream("test_foo"); + auto Result = xptiRegisterCallback( + StreamID, (uint16_t)xpti::trace_point_type_t::graph_create, tpCallback); + EXPECT_EQ(Result, xpti::result_t::XPTI_RESULT_SUCCESS); + Result = xptiRegisterCallback( + StreamID, (uint16_t)xpti::trace_point_type_t::node_create, tpCallback); + EXPECT_EQ(Result, xpti::result_t::XPTI_RESULT_SUCCESS); + Result = xptiRegisterCallback( + StreamID, (uint16_t)xpti::trace_point_type_t::edge_create, tpCallback); + EXPECT_EQ(Result, xpti::result_t::XPTI_RESULT_SUCCESS); + Result = xptiRegisterCallback( + StreamID, (uint16_t)xpti::trace_point_type_t::region_begin, tpCallback); + EXPECT_EQ(Result, xpti::result_t::XPTI_RESULT_SUCCESS); + Result = xptiRegisterCallback( + StreamID, (uint16_t)xpti::trace_point_type_t::region_end, tpCallback); + EXPECT_EQ(Result, xpti::result_t::XPTI_RESULT_SUCCESS); + Result = xptiRegisterCallback( + StreamID, (uint16_t)xpti::trace_point_type_t::task_begin, tpCallback); + EXPECT_EQ(Result, xpti::result_t::XPTI_RESULT_SUCCESS); + Result = xptiRegisterCallback( + StreamID, (uint16_t)xpti::trace_point_type_t::task_end, tpCallback); + EXPECT_EQ(Result, xpti::result_t::XPTI_RESULT_SUCCESS); + Result = xptiRegisterCallback( + StreamID, (uint16_t)xpti::trace_point_type_t::barrier_begin, tpCallback); + EXPECT_EQ(Result, xpti::result_t::XPTI_RESULT_SUCCESS); + Result = xptiRegisterCallback( + StreamID, (uint16_t)xpti::trace_point_type_t::barrier_end, tpCallback); + EXPECT_EQ(Result, xpti::result_t::XPTI_RESULT_SUCCESS); + Result = xptiRegisterCallback( + StreamID, (uint16_t)xpti::trace_point_type_t::lock_begin, tpCallback); + EXPECT_EQ(Result, xpti::result_t::XPTI_RESULT_SUCCESS); + Result = xptiRegisterCallback( + StreamID, (uint16_t)xpti::trace_point_type_t::lock_end, tpCallback); + EXPECT_EQ(Result, xpti::result_t::XPTI_RESULT_SUCCESS); + Result = xptiRegisterCallback( + StreamID, (uint16_t)xpti::trace_point_type_t::transfer_begin, tpCallback); + EXPECT_EQ(Result, xpti::result_t::XPTI_RESULT_SUCCESS); + Result = xptiRegisterCallback( + StreamID, (uint16_t)xpti::trace_point_type_t::transfer_end, tpCallback); + EXPECT_EQ(Result, xpti::result_t::XPTI_RESULT_SUCCESS); + Result = xptiRegisterCallback( + StreamID, (uint16_t)xpti::trace_point_type_t::thread_begin, tpCallback); + EXPECT_EQ(Result, xpti::result_t::XPTI_RESULT_SUCCESS); + Result = xptiRegisterCallback( + StreamID, (uint16_t)xpti::trace_point_type_t::thread_end, tpCallback); + EXPECT_EQ(Result, xpti::result_t::XPTI_RESULT_SUCCESS); + Result = xptiRegisterCallback( + StreamID, (uint16_t)xpti::trace_point_type_t::wait_begin, tpCallback); + EXPECT_EQ(Result, xpti::result_t::XPTI_RESULT_SUCCESS); + Result = xptiRegisterCallback( + StreamID, (uint16_t)xpti::trace_point_type_t::wait_end, tpCallback); + EXPECT_EQ(Result, xpti::result_t::XPTI_RESULT_SUCCESS); + Result = xptiRegisterCallback( + StreamID, (uint16_t)xpti::trace_point_type_t::signal, tpCallback); + EXPECT_EQ(Result, xpti::result_t::XPTI_RESULT_SUCCESS); } TEST(xptiCorrectnessTest, xptiNotifySubscribersForDefaultTracePointTypes) { - uint64_t instance; + uint64_t Instance; xpti::payload_t p("foo", "foo.cpp", 1, 0, (void *)13); xptiForceSetTraceEnabled(true); - uint8_t stream_id = xptiRegisterStream("test_foo"); - auto result = xptiRegisterCallback( - stream_id, (uint16_t)xpti::trace_point_type_t::graph_create, tp_callback); - result = xptiRegisterCallback( - stream_id, (uint16_t)xpti::trace_point_type_t::node_create, tp_callback); - result = xptiRegisterCallback( - stream_id, (uint16_t)xpti::trace_point_type_t::edge_create, tp_callback); - result = xptiRegisterCallback( - stream_id, (uint16_t)xpti::trace_point_type_t::region_begin, tp_callback); - result = xptiRegisterCallback( - stream_id, (uint16_t)xpti::trace_point_type_t::region_end, tp_callback); - result = xptiRegisterCallback( - stream_id, (uint16_t)xpti::trace_point_type_t::task_begin, tp_callback); - result = xptiRegisterCallback( - stream_id, (uint16_t)xpti::trace_point_type_t::task_end, tp_callback); - result = xptiRegisterCallback( - stream_id, (uint16_t)xpti::trace_point_type_t::barrier_begin, - tp_callback); - result = xptiRegisterCallback( - stream_id, (uint16_t)xpti::trace_point_type_t::barrier_end, tp_callback); - result = xptiRegisterCallback( - stream_id, (uint16_t)xpti::trace_point_type_t::lock_begin, tp_callback); - result = xptiRegisterCallback( - stream_id, (uint16_t)xpti::trace_point_type_t::lock_end, tp_callback); - result = xptiRegisterCallback( - stream_id, (uint16_t)xpti::trace_point_type_t::transfer_begin, - tp_callback); - result = xptiRegisterCallback( - stream_id, (uint16_t)xpti::trace_point_type_t::transfer_end, tp_callback); - result = xptiRegisterCallback( - stream_id, (uint16_t)xpti::trace_point_type_t::thread_begin, tp_callback); - result = xptiRegisterCallback( - stream_id, (uint16_t)xpti::trace_point_type_t::thread_end, tp_callback); - result = xptiRegisterCallback( - stream_id, (uint16_t)xpti::trace_point_type_t::wait_begin, tp_callback); - result = xptiRegisterCallback( - stream_id, (uint16_t)xpti::trace_point_type_t::wait_end, tp_callback); - result = xptiRegisterCallback( - stream_id, (uint16_t)xpti::trace_point_type_t::signal, tp_callback); + uint8_t StreamID = xptiRegisterStream("test_foo"); + auto Result = xptiRegisterCallback( + StreamID, (uint16_t)xpti::trace_point_type_t::graph_create, tpCallback); + Result = xptiRegisterCallback( + StreamID, (uint16_t)xpti::trace_point_type_t::node_create, tpCallback); + Result = xptiRegisterCallback( + StreamID, (uint16_t)xpti::trace_point_type_t::edge_create, tpCallback); + Result = xptiRegisterCallback( + StreamID, (uint16_t)xpti::trace_point_type_t::region_begin, tpCallback); + Result = xptiRegisterCallback( + StreamID, (uint16_t)xpti::trace_point_type_t::region_end, tpCallback); + Result = xptiRegisterCallback( + StreamID, (uint16_t)xpti::trace_point_type_t::task_begin, tpCallback); + Result = xptiRegisterCallback( + StreamID, (uint16_t)xpti::trace_point_type_t::task_end, tpCallback); + Result = xptiRegisterCallback( + StreamID, (uint16_t)xpti::trace_point_type_t::barrier_begin, tpCallback); + Result = xptiRegisterCallback( + StreamID, (uint16_t)xpti::trace_point_type_t::barrier_end, tpCallback); + Result = xptiRegisterCallback( + StreamID, (uint16_t)xpti::trace_point_type_t::lock_begin, tpCallback); + Result = xptiRegisterCallback( + StreamID, (uint16_t)xpti::trace_point_type_t::lock_end, tpCallback); + Result = xptiRegisterCallback( + StreamID, (uint16_t)xpti::trace_point_type_t::transfer_begin, tpCallback); + Result = xptiRegisterCallback( + StreamID, (uint16_t)xpti::trace_point_type_t::transfer_end, tpCallback); + Result = xptiRegisterCallback( + StreamID, (uint16_t)xpti::trace_point_type_t::thread_begin, tpCallback); + Result = xptiRegisterCallback( + StreamID, (uint16_t)xpti::trace_point_type_t::thread_end, tpCallback); + Result = xptiRegisterCallback( + StreamID, (uint16_t)xpti::trace_point_type_t::wait_begin, tpCallback); + Result = xptiRegisterCallback( + StreamID, (uint16_t)xpti::trace_point_type_t::wait_end, tpCallback); + Result = xptiRegisterCallback( + StreamID, (uint16_t)xpti::trace_point_type_t::signal, tpCallback); - auto ge = - xptiMakeEvent("foo", &p, 0, (xpti::trace_activity_type_t)1, &instance); - EXPECT_NE(ge, nullptr); + auto GE = + xptiMakeEvent("foo", &p, 0, (xpti::trace_activity_type_t)1, &Instance); + EXPECT_NE(GE, nullptr); - int foo_return = 0; - NOTIFY(stream_id, (uint16_t)xpti::trace_point_type_t::graph_create, ge, - foo_return); - NOTIFY(stream_id, (uint16_t)xpti::trace_point_type_t::node_create, ge, - foo_return); - NOTIFY(stream_id, (uint16_t)xpti::trace_point_type_t::edge_create, ge, - foo_return); - NOTIFY(stream_id, (uint16_t)xpti::trace_point_type_t::region_begin, ge, - foo_return); - NOTIFY(stream_id, (uint16_t)xpti::trace_point_type_t::region_end, ge, - foo_return); - NOTIFY(stream_id, (uint16_t)xpti::trace_point_type_t::task_begin, ge, - foo_return); - NOTIFY(stream_id, (uint16_t)xpti::trace_point_type_t::task_end, ge, - foo_return); - NOTIFY(stream_id, (uint16_t)xpti::trace_point_type_t::barrier_begin, ge, - foo_return); - NOTIFY(stream_id, (uint16_t)xpti::trace_point_type_t::barrier_end, ge, - foo_return); - NOTIFY(stream_id, (uint16_t)xpti::trace_point_type_t::lock_begin, ge, - foo_return); - NOTIFY(stream_id, (uint16_t)xpti::trace_point_type_t::lock_end, ge, - foo_return); - NOTIFY(stream_id, (uint16_t)xpti::trace_point_type_t::transfer_begin, ge, - foo_return); - NOTIFY(stream_id, (uint16_t)xpti::trace_point_type_t::transfer_end, ge, - foo_return); - NOTIFY(stream_id, (uint16_t)xpti::trace_point_type_t::thread_begin, ge, - foo_return); - NOTIFY(stream_id, (uint16_t)xpti::trace_point_type_t::thread_end, ge, - foo_return); - NOTIFY(stream_id, (uint16_t)xpti::trace_point_type_t::wait_begin, ge, - foo_return); - NOTIFY(stream_id, (uint16_t)xpti::trace_point_type_t::wait_end, ge, - foo_return); - NOTIFY(stream_id, (uint16_t)xpti::trace_point_type_t::signal, ge, foo_return); + int FooReturn = 0; + NOTIFY(StreamID, (uint16_t)xpti::trace_point_type_t::graph_create, GE, + FooReturn); + NOTIFY(StreamID, (uint16_t)xpti::trace_point_type_t::node_create, GE, + FooReturn); + NOTIFY(StreamID, (uint16_t)xpti::trace_point_type_t::edge_create, GE, + FooReturn); + NOTIFY(StreamID, (uint16_t)xpti::trace_point_type_t::region_begin, GE, + FooReturn); + NOTIFY(StreamID, (uint16_t)xpti::trace_point_type_t::region_end, GE, + FooReturn); + NOTIFY(StreamID, (uint16_t)xpti::trace_point_type_t::task_begin, GE, + FooReturn); + NOTIFY(StreamID, (uint16_t)xpti::trace_point_type_t::task_end, GE, FooReturn); + NOTIFY(StreamID, (uint16_t)xpti::trace_point_type_t::barrier_begin, GE, + FooReturn); + NOTIFY(StreamID, (uint16_t)xpti::trace_point_type_t::barrier_end, GE, + FooReturn); + NOTIFY(StreamID, (uint16_t)xpti::trace_point_type_t::lock_begin, GE, + FooReturn); + NOTIFY(StreamID, (uint16_t)xpti::trace_point_type_t::lock_end, GE, FooReturn); + NOTIFY(StreamID, (uint16_t)xpti::trace_point_type_t::transfer_begin, GE, + FooReturn); + NOTIFY(StreamID, (uint16_t)xpti::trace_point_type_t::transfer_end, GE, + FooReturn); + NOTIFY(StreamID, (uint16_t)xpti::trace_point_type_t::thread_begin, GE, + FooReturn); + NOTIFY(StreamID, (uint16_t)xpti::trace_point_type_t::thread_end, GE, + FooReturn); + NOTIFY(StreamID, (uint16_t)xpti::trace_point_type_t::wait_begin, GE, + FooReturn); + NOTIFY(StreamID, (uint16_t)xpti::trace_point_type_t::wait_end, GE, FooReturn); + NOTIFY(StreamID, (uint16_t)xpti::trace_point_type_t::signal, GE, FooReturn); } TEST(xptiCorrectnessTest, xptiInitializeForUserDefinedTracePointTypes) { // We will test functionality of a subscriber // without actually creating a plugin - uint8_t stream_id = xptiRegisterStream("test_foo"); + uint8_t StreamID = xptiRegisterStream("test_foo"); typedef enum { extn1_begin = XPTI_TRACE_POINT_BEGIN(0), extn1_end = XPTI_TRACE_POINT_END(0), @@ -217,27 +210,26 @@ TEST(xptiCorrectnessTest, xptiInitializeForUserDefinedTracePointTypes) { extn2_end = XPTI_TRACE_POINT_END(1) } tp_extension_t; - auto tt_type = - xptiRegisterUserDefinedTracePoint("test_foo_tool", extn1_begin); - auto result = xptiRegisterCallback(stream_id, tt_type, tp_callback); - EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_SUCCESS); - tt_type = xptiRegisterUserDefinedTracePoint("test_foo_tool", extn1_end); - result = xptiRegisterCallback(stream_id, tt_type, tp_callback); - EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_SUCCESS); - tt_type = xptiRegisterUserDefinedTracePoint("test_foo_tool", extn2_begin); - result = xptiRegisterCallback(stream_id, tt_type, tp_callback); - EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_SUCCESS); - tt_type = xptiRegisterUserDefinedTracePoint("test_foo_tool", extn2_end); - result = xptiRegisterCallback(stream_id, tt_type, tp_callback); - EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_SUCCESS); + auto TTType = xptiRegisterUserDefinedTracePoint("test_foo_tool", extn1_begin); + auto Result = xptiRegisterCallback(StreamID, TTType, tpCallback); + EXPECT_EQ(Result, xpti::result_t::XPTI_RESULT_SUCCESS); + TTType = xptiRegisterUserDefinedTracePoint("test_foo_tool", extn1_end); + Result = xptiRegisterCallback(StreamID, TTType, tpCallback); + EXPECT_EQ(Result, xpti::result_t::XPTI_RESULT_SUCCESS); + TTType = xptiRegisterUserDefinedTracePoint("test_foo_tool", extn2_begin); + Result = xptiRegisterCallback(StreamID, TTType, tpCallback); + EXPECT_EQ(Result, xpti::result_t::XPTI_RESULT_SUCCESS); + TTType = xptiRegisterUserDefinedTracePoint("test_foo_tool", extn2_end); + Result = xptiRegisterCallback(StreamID, TTType, tpCallback); + EXPECT_EQ(Result, xpti::result_t::XPTI_RESULT_SUCCESS); } TEST(xptiCorrectnessTest, xptiNotifySubscribersForUserDefinedTracePointTypes) { - uint64_t instance; + uint64_t Instance; xpti::payload_t p("foo", "foo.cpp", 1, 0, (void *)13); xptiForceSetTraceEnabled(true); - uint8_t stream_id = xptiRegisterStream("test_foo"); + uint8_t StreamID = xptiRegisterStream("test_foo"); typedef enum { extn1_begin = XPTI_TRACE_POINT_BEGIN(0), extn1_end = XPTI_TRACE_POINT_END(0), @@ -245,56 +237,56 @@ TEST(xptiCorrectnessTest, xptiNotifySubscribersForUserDefinedTracePointTypes) { extn2_end = XPTI_TRACE_POINT_END(1) } tp_extension_t; - auto tt_type1 = + auto TTType1 = xptiRegisterUserDefinedTracePoint("test_foo_tool", extn1_begin); - auto result = xptiRegisterCallback(stream_id, tt_type1, tp_callback); - EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_DUPLICATE); - auto tt_type2 = xptiRegisterUserDefinedTracePoint("test_foo_tool", extn1_end); - result = xptiRegisterCallback(stream_id, tt_type2, tp_callback); - EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_DUPLICATE); - auto tt_type3 = + auto Result = xptiRegisterCallback(StreamID, TTType1, tpCallback); + EXPECT_EQ(Result, xpti::result_t::XPTI_RESULT_DUPLICATE); + auto TTType2 = xptiRegisterUserDefinedTracePoint("test_foo_tool", extn1_end); + Result = xptiRegisterCallback(StreamID, TTType2, tpCallback); + EXPECT_EQ(Result, xpti::result_t::XPTI_RESULT_DUPLICATE); + auto TTType3 = xptiRegisterUserDefinedTracePoint("test_foo_tool", extn2_begin); - result = xptiRegisterCallback(stream_id, tt_type3, tp_callback); - EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_DUPLICATE); - auto tt_type4 = xptiRegisterUserDefinedTracePoint("test_foo_tool", extn2_end); - result = xptiRegisterCallback(stream_id, tt_type4, tp_callback); - EXPECT_EQ(result, xpti::result_t::XPTI_RESULT_DUPLICATE); + Result = xptiRegisterCallback(StreamID, TTType3, tpCallback); + EXPECT_EQ(Result, xpti::result_t::XPTI_RESULT_DUPLICATE); + auto TTType4 = xptiRegisterUserDefinedTracePoint("test_foo_tool", extn2_end); + Result = xptiRegisterCallback(StreamID, TTType4, tpCallback); + EXPECT_EQ(Result, xpti::result_t::XPTI_RESULT_DUPLICATE); - auto ge = - xptiMakeEvent("foo", &p, 0, (xpti::trace_activity_type_t)1, &instance); - EXPECT_NE(ge, nullptr); + auto GE = + xptiMakeEvent("foo", &p, 0, (xpti::trace_activity_type_t)1, &Instance); + EXPECT_NE(GE, nullptr); - int foo_return = 0; - NOTIFY(stream_id, tt_type1, ge, foo_return); - NOTIFY(stream_id, tt_type2, ge, foo_return); - NOTIFY(stream_id, tt_type3, ge, foo_return); - NOTIFY(stream_id, tt_type4, ge, foo_return); + int FooReturn = 0; + NOTIFY(StreamID, TTType1, GE, FooReturn); + NOTIFY(StreamID, TTType2, GE, FooReturn); + NOTIFY(StreamID, TTType3, GE, FooReturn); + NOTIFY(StreamID, TTType4, GE, FooReturn); - auto tool_id1 = XPTI_TOOL_ID(tt_type1); - auto tool_id2 = XPTI_TOOL_ID(tt_type2); - auto tool_id3 = XPTI_TOOL_ID(tt_type3); - auto tool_id4 = XPTI_TOOL_ID(tt_type4); - EXPECT_EQ(tool_id1, tool_id2); - EXPECT_EQ(tool_id2, tool_id3); - EXPECT_EQ(tool_id3, tool_id4); - EXPECT_EQ(tool_id4, tool_id1); + auto ToolID1 = XPTI_TOOL_ID(TTType1); + auto ToolID2 = XPTI_TOOL_ID(TTType2); + auto ToolID3 = XPTI_TOOL_ID(TTType3); + auto ToolID4 = XPTI_TOOL_ID(TTType4); + EXPECT_EQ(ToolID1, ToolID2); + EXPECT_EQ(ToolID2, ToolID3); + EXPECT_EQ(ToolID3, ToolID4); + EXPECT_EQ(ToolID4, ToolID1); - auto tp_id1 = XPTI_EXTRACT_USER_DEFINED_ID(tt_type1); - auto tp_id2 = XPTI_EXTRACT_USER_DEFINED_ID(tt_type2); - auto tp_id3 = XPTI_EXTRACT_USER_DEFINED_ID(tt_type3); - auto tp_id4 = XPTI_EXTRACT_USER_DEFINED_ID(tt_type4); - EXPECT_NE(tp_id1, tp_id2); - EXPECT_NE(tp_id2, tp_id3); - EXPECT_NE(tp_id3, tp_id4); - EXPECT_NE(tp_id4, tp_id1); + auto TpID1 = XPTI_EXTRACT_USER_DEFINED_ID(TTType1); + auto TpID2 = XPTI_EXTRACT_USER_DEFINED_ID(TTType2); + auto TpID3 = XPTI_EXTRACT_USER_DEFINED_ID(TTType3); + auto TpID4 = XPTI_EXTRACT_USER_DEFINED_ID(TTType4); + EXPECT_NE(TpID1, TpID2); + EXPECT_NE(TpID2, TpID3); + EXPECT_NE(TpID3, TpID4); + EXPECT_NE(TpID4, TpID1); } TEST(xptiCorrectnessTest, xptiUserDefinedEventTypes) { - uint64_t instance; + uint64_t Instance; xpti::payload_t p("foo", "foo.cpp", 1, 0, (void *)13); xptiForceSetTraceEnabled(true); - uint8_t stream_id = xptiRegisterStream("test_foo"); + uint8_t StreamID = xptiRegisterStream("test_foo"); typedef enum { extn_ev1 = XPTI_EVENT(0), extn_ev2 = XPTI_EVENT(1), @@ -302,30 +294,30 @@ TEST(xptiCorrectnessTest, xptiUserDefinedEventTypes) { extn_ev4 = XPTI_EVENT(3) } event_extension_t; - auto ev_type1 = xptiRegisterUserDefinedEventType("test_foo_tool", extn_ev1); - auto ev_type2 = xptiRegisterUserDefinedEventType("test_foo_tool", extn_ev2); - auto ev_type3 = xptiRegisterUserDefinedEventType("test_foo_tool", extn_ev3); - auto ev_type4 = xptiRegisterUserDefinedEventType("test_foo_tool", extn_ev4); - EXPECT_NE(ev_type1, ev_type2); - EXPECT_NE(ev_type2, ev_type3); - EXPECT_NE(ev_type3, ev_type4); - EXPECT_NE(ev_type4, ev_type1); + auto EventType1 = xptiRegisterUserDefinedEventType("test_foo_tool", extn_ev1); + auto EventType2 = xptiRegisterUserDefinedEventType("test_foo_tool", extn_ev2); + auto EventType3 = xptiRegisterUserDefinedEventType("test_foo_tool", extn_ev3); + auto EventType4 = xptiRegisterUserDefinedEventType("test_foo_tool", extn_ev4); + EXPECT_NE(EventType1, EventType2); + EXPECT_NE(EventType2, EventType3); + EXPECT_NE(EventType3, EventType4); + EXPECT_NE(EventType4, EventType1); - auto tool_id1 = XPTI_TOOL_ID(ev_type1); - auto tool_id2 = XPTI_TOOL_ID(ev_type2); - auto tool_id3 = XPTI_TOOL_ID(ev_type3); - auto tool_id4 = XPTI_TOOL_ID(ev_type4); - EXPECT_EQ(tool_id1, tool_id2); - EXPECT_EQ(tool_id2, tool_id3); - EXPECT_EQ(tool_id3, tool_id4); - EXPECT_EQ(tool_id4, tool_id1); + auto ToolID1 = XPTI_TOOL_ID(EventType1); + auto ToolID2 = XPTI_TOOL_ID(EventType2); + auto ToolID3 = XPTI_TOOL_ID(EventType3); + auto ToolID4 = XPTI_TOOL_ID(EventType4); + EXPECT_EQ(ToolID1, ToolID2); + EXPECT_EQ(ToolID2, ToolID3); + EXPECT_EQ(ToolID3, ToolID4); + EXPECT_EQ(ToolID4, ToolID1); - auto tp_id1 = XPTI_EXTRACT_USER_DEFINED_ID(ev_type1); - auto tp_id2 = XPTI_EXTRACT_USER_DEFINED_ID(ev_type2); - auto tp_id3 = XPTI_EXTRACT_USER_DEFINED_ID(ev_type3); - auto tp_id4 = XPTI_EXTRACT_USER_DEFINED_ID(ev_type4); - EXPECT_NE(tp_id1, tp_id2); - EXPECT_NE(tp_id2, tp_id3); - EXPECT_NE(tp_id3, tp_id4); - EXPECT_NE(tp_id4, tp_id1); + auto TpID1 = XPTI_EXTRACT_USER_DEFINED_ID(EventType1); + auto TpID2 = XPTI_EXTRACT_USER_DEFINED_ID(EventType2); + auto TpID3 = XPTI_EXTRACT_USER_DEFINED_ID(EventType3); + auto TpID4 = XPTI_EXTRACT_USER_DEFINED_ID(EventType4); + EXPECT_NE(TpID1, TpID2); + EXPECT_NE(TpID2, TpID3); + EXPECT_NE(TpID3, TpID4); + EXPECT_NE(TpID4, TpID1); } From a11c304a96081583adc8190c6c7d6d6a0ff1e35d Mon Sep 17 00:00:00 2001 From: Vasanth Tovinkere Date: Thu, 19 Mar 2020 09:31:57 -0700 Subject: [PATCH 09/18] [XPTIFW] Addressed comments from first review of the documentation Signed-off-by: Vasanth Tovinkere --- xptifw/doc/XPTI_Framework.md | 188 ++++++++++++++++++++--------------- 1 file changed, 109 insertions(+), 79 deletions(-) diff --git a/xptifw/doc/XPTI_Framework.md b/xptifw/doc/XPTI_Framework.md index c8ae89969b2d4..22faa318d19a7 100644 --- a/xptifw/doc/XPTI_Framework.md +++ b/xptifw/doc/XPTI_Framework.md @@ -9,6 +9,7 @@ - [**Brief API Concepts**](#brief-api-concepts) - [**`xptiInitialize`**](#xptiinitialize) - [**`xptiFinalize`**](#xptifinalize) + - [**`xptiTraceEnabled`**](#xptitraceenabled) - [**APIs and Data Structures Exported by the Tracing Framework**](#apis-and-data-structures-exported-by-the-tracing-framework) - [**Performance of the Framework**](#performance-of-the-framework) - [**Modeling and Projection**](#modeling-and-projection) @@ -18,7 +19,7 @@ In order to understand different parts of an application or library, the ability to capture information about the application or library is needed. Using such information, one can create meaningful representations such as -call-graphs, execution trace views etc. XPTI tracing framework is once such +call-graphs, execution trace views etc. XPTI tracing framework is one such framework that allows developers to instrument their code with XPTI API and forward interesting or useful events during the application's execution, as determined by the developer. @@ -34,13 +35,14 @@ can use to build performance analytical models. This document describes the different components of this framework and a testing methodology to determine the cost of using this framework in your applications. -Current implementation of the framework uses std containers by default. There is -also an implementation that relies on the concurrent containers in [Threading Building Blocks(TBB)](github.com/intel/tbb) and this can be enabled by using the -define `-DXPTI_USE_TBB` with `cmake`. The std container based implementation is a -thread-safe implementation, but has not been optimized for performance. -Increasing the number of threads accessing the framework will increase the -contention costs in the current implementation and may affect the performance of -the framework. +Current implementation of the framework uses std containers by default. There +is also an implementation that relies on the concurrent containers in +[Threading Building Blocks(TBB)](github.com/intel/tbb) and this can be enabled +by using the define `-DXPTI_USE_TBB` with `cmake`. The std container based +implementation is a thread-safe implementation, but has not been optimized for +performance. Increasing the number of threads accessing the framework will +increase the contention costs in the current implementation and may affect the +performance of the framework. To enable the build to use TBB for the framework and tests, use the commands as shown below: @@ -69,6 +71,8 @@ has been enabled and where to find the dynamic component. Both of these must be `XPTI_TRACE_ENABLE=true` and to disable, the possible values are `XPTI_TRACE_ENABLE=0` or `XPTI_TRACE_ENABLE=false`. + Currently, if the variable is not defined, it is assumed to be `true`. + 2. The environment variable `XPTI_FRAMEWORK_DISPATCHER` points to the XPTI dispatcher or the dynamic component that allows the static library to load the shared object into memory and dispatch the event streams to subscribers @@ -79,19 +83,20 @@ has been enabled and where to find the dynamic component. Both of these must be ![XPTI Architecture](xpti_arch.png) -In the above diagram, the interactions between a sample application and library -that have been instrumented with XPTI API, the XPTI dispatcher and the -subscriber loaded by the dispatcher. All API calls made by the application and -the library go to the static library in this diagram and if `XPTI_TRACE_ENABLE` -is not enabled or if the path to the dispatcher is not provided in the -environment variable `XPTI_FRAMEWORK_DISPATCHER`, then the calls to the static -library return immediately. +The above diagram describes the dependencies and/or the interactions between a +sample application or library that has been instrumented with XPTI API, the +XPTI dispatcher and the subscriber loaded by the dispatcher. All API calls +made by the application and the library go to the static library in this +diagram and if `XPTI_TRACE_ENABLE` is not enabled or if the path to the +dispatcher is not provided in the environment variable +`XPTI_FRAMEWORK_DISPATCHER`, then the calls to the static library +`xptiTraceEnabled()` return immediately. In the case both these variables are enabled, then the calls will be forwarded to the dynamic library which will attempt to load the subscribers pointed to by the environment variable `XPTI_SUBSCRIBERS`. The hypothetical trace data captured by the subscriber is shown as well under the `Resulting trace data` -part of the diagram. +part of the above diagram. ### The Dispatcher @@ -112,14 +117,14 @@ the generated event streams and **must** follow the protocol or handshake defined for an event stream. There are three important things that a subscriber must implement to be -functional: (1) `xptiTraceInit`, (2) `xptiTraceFinish` and (3) callback handlers. -The `xptiTraceInit` and `xptiTraceFinish` API calls are used by the dispatcher -loading the subscriber dynamically to determine if the subscriber is a valid -subscriber. If these entry points are not present, then the subscriber is not -loaded. - -The `xptiTraceInit` callback is called by the dispatcher when the generator of a -new stream of data makes a call to `xptiInitialize` for the new stream. The +functional: (1) `xptiTraceInit`, (2) `xptiTraceFinish` and (3) callback +handlers. The `xptiTraceInit` and `xptiTraceFinish` API calls are used by the +dispatcher loading the subscriber dynamically to determine if the subscriber +is a valid subscriber. If these entry points are not present, then the +subscriber is not loaded. + +The `xptiTraceInit` callback is called by the dispatcher when the generator of +a new stream of data makes a call to `xptiInitialize` for the new stream. The implementation of the `xptiTraceInit` function is where the subscriber would follow the specification or protocol defined for the stream to subscribe to events from various trace point types. The code snippet below shows an example @@ -204,13 +209,13 @@ For example, the specification for a given event stream, the trace point type requires strict conformance to the specification for the stream. In addition to the `user_data`, the unique id that describes the event is -available under `event->unique_id`. For most cases, this should be sufficient to -resolve a given event. However, in many cases, a particular event may be +available under `event->unique_id`. For most cases, this should be sufficient +to resolve a given event. However, in many cases, a particular event may be exercised within a loop. Since a trace point event is based on the -instrumentation at a specific location in the code, the `unique_id` of this will -always remain the same. However, with each instance of this event, and instance -ID may be emitted that keeps track of the instance of this event. The combined -value of the `unique_id` and `instance_id` should always be unique. +instrumentation at a specific location in the code, the `unique_id` of this +will always remain the same. However, with each instance of this event, and +instance ID may be emitted that keeps track of the instance of this event. The +combined value of the `unique_id` and `instance_id` should always be unique. > **NOTE:** A subscriber **must** implement the `xptiTraceInit` and > `xptiTraceFinish` APIs for the dispatcher to successfully load the subscriber. @@ -234,10 +239,10 @@ that have been considered so far. Since the primary goal is to gather execution traces of compute elements in an application, the APIs address this scope for now. Currently, they allow developers to capture relationship information as nodes and edges in a graph, where the nodes represent a compute element or an -action with a latency associated with it. The edges represent the dependencies -between the compute elements which be events or memory objects. In addition to -such relationship events, the API allows to you trace arbitrary regions of code -similar to conventional tracing. +action with a latency associated with it and the edges represent the +dependencies between the compute elements which may be events or memory +objects. In addition to such relationship events, the API allows to you trace +arbitrary regions of code similar to conventional tracing methods. For each interesting trace point in an application, a notification can be sent out by the framework. However, if there are no subscribers to consume this @@ -245,9 +250,9 @@ notification event, the framework returns immediately. This allows developers that want to instrument applications or run-times to limit the overheads considerably. -The API is documented in the file `xpti_trace_framework.h` that can be located -under `xpti/doc`. Some of the API functions and concepts that warrant additional -insight are discussed further. +The API is documented in the file `xpti_trace_framework.h` that can be found +under `xpti/doc`. Some of the API functions and concepts that warrant +additional insight are discussed further. ### `xptiInitialize` @@ -261,9 +266,9 @@ stream must be defined for a given stream as a contract or specification. This allows subscribers to rely on this specification to implement a tool that can consume this data and do something useful with it. -The `xptiIntialize` function reports to all the subscribers that a new stream of -data is about to be generated and the name of the stream along with some version -information of the stream is sent to the subscriber. +The `xptiIntialize` function reports to all the subscribers that a new stream +of data is about to be generated and the name of the stream along with some +version information of the stream is sent to the subscriber. The version information is primarily provided to ensure that subscribers to the event stream can choose not to handle an event stream if it is an unsupported @@ -279,17 +284,33 @@ The application or library being instrumented to generate a stream of data must attempt to finalize the stream by making this call. This allows the dispatcher to notify all the subscribers that a stream is about to end. +### `xptiTraceEnabled` + +To recap some of the discussion in the [Architecture](#architecture) section, +this API call returns `true` in the following situations: + +1. When `XPTI_TRACE_ENABLE` is not set, but the `XPTI_FRAMEWORK_DISPATCHER` +and `XPTI_SUBSCRIBERS` variables are set to valid libraries. This assumes that +a tool has been created and pointed to by `XPTI_SUBSCRIBERS` and the tool has +been linked against the dynamic component or dispatcher. In general, the +dynamic component or the dispatcher and the tool component or the subscriber +are owned by the tool attempting to listen to the instrumented stream of data. + +2. When using the static library for linking in the instrumented application +or library, this call returns `true` only if `XPTI_FRAMEWORK_DISPATCHER` is +set to a valid library and `XPTI_TRACE_ENABLE` is not set to `false`. ### APIs and Data Structures Exported by the Tracing Framework We will begin our discussion by detailing the various public APIs that are exported by the framework and when they are meant to be used. The framework API -is what will be used by developers instrumenting their code. The primary goal of -the API is to support the instrumentation of code that may or may not fall into -function boundaries. +is what will be used by developers instrumenting their code. The primary goal +of the API is to support the instrumentation of code that may or may not fall +into function boundaries. * First, the places in the code where instrumentation is warranted should be - identified. Each trace point is unique and will be associated with a `payload` data structure that encapsulates: (1) a unique name, such as a + identified. Each trace point is unique and will be associated with a + `payload` data structure that encapsulates: (1) a unique name, such as a function or kernel name or something meaningful if the trace point marks a section of code, (2) the source file it is located in, (3) the line number where this interesting event occurs and (4) the column number of the @@ -312,8 +333,8 @@ The trace point event describes the event used to notify the subscriber and is usually associated with a payload that describes the event. Since application code is being instrumented with XPTI, the payload may consist of a `function` `name`, `source file name` and `line number`, which forms a unique combination -of strings and numbers that is used to create the `unique_id` associated with an -event. Using the `event` or the `unique_id`, one should be able to query the +of strings and numbers that is used to create the `unique_id` associated with +an event. Using the `event` or the `unique_id`, one should be able to query the `payload` information. When a notification occurs for a trace point, the trace point event and trace point type information is sent to the subscriber. A given event may be used to notify subscribers as multiple trace point types. For @@ -334,16 +355,16 @@ xpti::payload_t p("function1", "main.cpp", 104, 5, function1); ``` The payload data structure can be created with a set of unique descriptors for -the region of code being instrumented, such as a function name, source file name -and line number, for example. However, it can also take in a function name and -pointer to the function or just a pointer to the function that uniquely -describes the payload that will be used to create a trace point event. This -information is used by the `xptiMakeEvent` function to create a `unique_id` for -the trace point event. +the region of code being instrumented, such as a function name, source file +name and line number, for example. However, it can also take in a function +name and pointer to the function or just a pointer to the function that +uniquely describes the payload that will be used to create a trace point +event. This information is used by the `xptiMakeEvent` function to create a +`unique_id` for the trace point event. The next section looks at using the payload information to create a trace point -event. Each trace point is unique, from a language or code section standpoint. A -trace point maybe visited multiple times, but the payload and the event +event. Each trace point is unique, from a language or code section standpoint. +A trace point maybe visited multiple times, but the payload and the event describing the trace point will always be the same. The tracing framework must guarantee that when a trace point is visited, the same `unique_id` is retrieved for it. For frequent visits to the same trace point site, we must be @@ -390,9 +411,9 @@ xptiNotifySubscribers(stream_id, tp1_start, parent, event, instance, nullptr); ``` -If the callback handler for this stream needs to know if this is an extension or -a predefined type, they can use the following macros to decipher the trace point -type. +If the callback handler for this stream needs to know if this is an extension +or a predefined type, they can use the following macros to decipher the trace +point type. ```cpp uint8_t tool_vendor_id = XPTI_TOOL_ID(tp1_start); @@ -408,9 +429,9 @@ else { ``` This mechanism allows different kinds of information to be captured and the -trace point type describes the type of information expected by the notification. -The trace point type is only used when notifying the subscribers of an event -with the trace point type acting as a qualifier for the event. +trace point type describes the type of information expected by the +notification. The trace point type is only used when notifying the subscribers +of an event with the trace point type acting as a qualifier for the event. In a similar manner, the `xpti::trace_event_type_t` can also be extended. The events that are predefined by the framework fall under `{graph, algorithm,` @@ -420,8 +441,8 @@ framework provides APIs to extend this set to meet the need of the specific tracing activity using the `xptiRegisterUserDefinedEventType` API function. The code sample below shows a sample code snippet that creates such an trace -point event using a payload and uses the created event to notify all subscribers -of the event qualified by a trace point type. +point event using a payload and uses the created event to notify all +subscribers of the event qualified by a trace point type. ```cpp if ( xptiTraceEnabled() ) { @@ -460,11 +481,13 @@ void function1() { xpt::trace_event_data_t event; if (xptiTraceEnabled()) { xpti::payload_t p("function1","main.cpp",104, 2,function1); - event = xptiMakeEvent("function1",&p, xpti::trace_event_type_t::algorithm, + event = xptiMakeEvent("function1",&p, + xpti::trace_event_type_t::algorithm, xpti::trace_activity_type_t::active, &instance_id); } xpti::framework::scoped_notify ev("myStream", - xpti::trace_point_type_t::region_begin, nullptr, &event,instance_id); + xpti::trace_point_type_t::region_begin, nullptr, &event, + instance_id); for(int i = 0; i < 5; ++i ) { function2(); } @@ -478,7 +501,8 @@ this section will outline a couple of scenarios. Some of the key operations that would result in overheads are listed below. For each of these operations, we will construct scenarios that will provide us with measurements to determine how many events/sec we can process before the overheads start to become a -problem. We will use an overhead limit of 1% as this data will be used to build an analytical model in the future. +problem. We will use an overhead limit of 1% as this data will be used to +build an analytical model in the future. | Data structure | Operation | Description | | -------------- | ------------- |------------ | @@ -563,16 +587,19 @@ performance of the trace point creation and notification to come up with an estimate of how many events can be serviced per second with the given configuration. > - The default overheads for which the events/sec are computed is **1%** -> - If the overheads desired is 1%, then the following formula is used to compute the events/sec: ->

total cost of instrumentation (I) = (cost of trace point creation + cost of notification)

+> - If the overheads desired is 1%, then the following formula is used to +> compute the events/sec: +>

total cost of instrumentation (I) = (cost of trace point +> creation + cost of notification)

>

So, if --trace-points 5000 --tp-frequency 10, this will be:

>

I = 5000xCost(TP Create) + 50000xCost(Notify)

>

Average cost (A) = I/50000, for 50000 events notified

>

This cost A does not take into account the cost of the callback -handler. In our projections, we use a handler cost of 10ns, 100ns and 500ns -to get the events/sec that can be serviced. On an average, the handler costs -for real-world cases will be somewhere between 80ns-400ns. ->

So, if the average cost is A and this is 1% overhead, the total run time must be 100xA ns

+> handler. In our projections, we use a handler cost of 10ns, 100ns and +> 500ns to get the events/sec that can be serviced. On an average, the +> handler costs for real-world cases will be somewhere between 80ns-400ns. +>

So, if the average cost is A and this is 1% overhead, the total run +> time must be 100xA ns

>

Events/second E = 1000,000,000 ns/(100xA)ns

> @@ -626,11 +653,14 @@ the performance test. The total instrumentation cost as discussed in the previous section comprises of a framework cost and a callback handler cost in the subscriber. -Framework cost **FW*****cost*** = Avg{TP*create* + 10 x TP*notify*} +Framework cost **FW*****cost*** = Avg{TP*create* + 10 x +TP*notify*} -Subscriber cost **Callback*****cost*** = **C*t*** which could be anywhere in the range [10-10000]ns +Subscriber cost **Callback*****cost*** = **C*t*** which +could be anywhere in the range [10-10000]ns -Total cost **Cost*****total*** = **FW*****cost*** + **C*t*** +Total cost **Cost*****total*** = **FW*****cost*** + +**C*t*** Using the information from the report or one such instance captured in the table above, we know that: @@ -644,7 +674,8 @@ callback handler costs increase, the events/sec is inversely proportional to the callback handler costs. The work unit cost for determining the number of events/sec is given by: -**W*****cost*** = **100** x [**FW*****cost*** + **C*t***] for the configuration that limits overheads to 1%. +**W*****cost*** = **100** x [**FW*****cost*** + +**C*t***] for the configuration that limits overheads to 1%. The more times a trace point event is visited, the more events per second can be serviced by the framework as the cost of a trace point event creation can @@ -653,10 +684,9 @@ be amortized over all the visits to the same trace point. However, significantly larger than **FW*****cost***. > **NOTE:** All measurements reported in this document were measured on an NUC -form-factor machine with Intel® Core™ i7-8559U @ 2.7 GHz processor -running Ubuntu 18.04. The tests were compiled to use Threading Building Blocks -concurrent containers for these runs. - +> form-factor machine with Intel® Core™ i7-8559U @ 2.7 GHz processor +> running Ubuntu 18.04. The tests were compiled to use Threading Building +> Blocks concurrent containers for these runs. | Operation | Statistic | Scenario |Count| Framework Cost(ns) | |-----------|-----------|----------|-----|------| From e351451073f6562921f00401764c9ec8eade7ca4 Mon Sep 17 00:00:00 2001 From: Vasanth Tovinkere Date: Fri, 20 Mar 2020 16:53:55 -0700 Subject: [PATCH 10/18] [XPTIFW] Updated documentation plus minor changes Signed-off-by: Vasanth Tovinkere --- xptifw/CMakeLists.txt | 2 +- xptifw/doc/XPTI_Framework.md | 4 ++-- xptifw/include/xpti_string_table.hpp | 7 +++---- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/xptifw/CMakeLists.txt b/xptifw/CMakeLists.txt index ea4376e9e1ace..38647cfb2b6da 100644 --- a/xptifw/CMakeLists.txt +++ b/xptifw/CMakeLists.txt @@ -20,7 +20,7 @@ set(CMAKE_BINARY_DIR ${CMAKE_SOURCE_DIR}/lib/${CMAKE_BUILD_TYPE}) set(EXECUTABLE_OUTPUT_PATH ${CMAKE_BINARY_DIR}) set(LIBRARY_OUTPUT_PATH ${CMAKE_BINARY_DIR}) -include_directories(${CMAKE_SOURCE_DIR}/include) +include_directories(${CMAKE_SOURCE_DIR}/include ${XPTI_DIR}/include) add_subdirectory(src) add_subdirectory(unit_test) add_subdirectory(samples/basic_collector) diff --git a/xptifw/doc/XPTI_Framework.md b/xptifw/doc/XPTI_Framework.md index 22faa318d19a7..8cb7f49f2c163 100644 --- a/xptifw/doc/XPTI_Framework.md +++ b/xptifw/doc/XPTI_Framework.md @@ -747,8 +747,8 @@ void function1() { | | Serial | 2% | ~360K | ~137K | ~37K|~19K| | | 4 | 2% | ~345K | ~135K | ~37K|~19K| -The above data from the table is only provided to provide a guideline on what -to expect with the instrumentation. The events/second can be quite high if the +The above data from the table is only provided as a guideline on what to +expect with the instrumentation. The events/second can be quite high if the callback handlers are written efficiently. So the range of events that can be serviced can be ~300,000 to 10000 per second depending on the cost of handling the callbacks. diff --git a/xptifw/include/xpti_string_table.hpp b/xptifw/include/xpti_string_table.hpp index a7ffae215fce3..5fa276253bd50 100644 --- a/xptifw/include/xpti_string_table.hpp +++ b/xptifw/include/xpti_string_table.hpp @@ -258,10 +258,9 @@ class StringTable { if (Loc == MStringToID.end()) { // Add it StrID = MIds++; - MStringToID[str] = StrID; - Loc = MStringToID.find(str); + auto Entry = MStringToID.insert(st_forward_t::value_type(str, StrID)); if (ref_str) - *ref_str = Loc->first.c_str(); + *ref_str = Entry.first->first.c_str(); #ifdef XPTI_STATISTICS MInsertions++; #endif @@ -272,7 +271,7 @@ class StringTable { if (IDLoc == MIDToString.end()) { // An entry does not exist, so we will add it to the reverse // lookup. - MIDToString[StrID] = Loc->first.c_str(); + MIDToString[StrID] = Entry.first->first.c_str(); // Cache the saved string address and send it to the caller MStrings++; return StrID; From 05dbefdee16a8b92e54e88c3b8d6812a4f5098a3 Mon Sep 17 00:00:00 2001 From: Vasanth Tovinkere Date: Tue, 7 Apr 2020 06:34:55 -0700 Subject: [PATCH 11/18] [XPTI] Additional test added to the unit tests Signed-off-by: Vasanth Tovinkere --- xptifw/unit_test/xpti_correctness_tests.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/xptifw/unit_test/xpti_correctness_tests.cpp b/xptifw/unit_test/xpti_correctness_tests.cpp index 5d11032898899..a92cd0e5bfaac 100644 --- a/xptifw/unit_test/xpti_correctness_tests.cpp +++ b/xptifw/unit_test/xpti_correctness_tests.cpp @@ -281,6 +281,13 @@ TEST(xptiCorrectnessTest, xptiNotifySubscribersForUserDefinedTracePointTypes) { EXPECT_NE(TpID4, TpID1); } +TEST(xptiCorrectnessTest, xptiGetUniqueId) { + auto Result = xptiGetUniqueId(); + EXPECT_NE(Result, 0); + auto Result1 = xptiGetUniqueId(); + EXPECT_NE(Result, Result1); +} + TEST(xptiCorrectnessTest, xptiUserDefinedEventTypes) { uint64_t Instance; xpti::payload_t p("foo", "foo.cpp", 1, 0, (void *)13); From 3d5c23f583ef407537fd7959f27f955a0919279a Mon Sep 17 00:00:00 2001 From: Vasanth Tovinkere Date: Tue, 7 Apr 2020 06:36:10 -0700 Subject: [PATCH 12/18] [XTPIFW] Documention update to include API details Signed-off-by: Vasanth Tovinkere --- xptifw/doc/XPTI_Framework.md | 183 ++++++++++++++++++++++++++++++----- 1 file changed, 158 insertions(+), 25 deletions(-) diff --git a/xptifw/doc/XPTI_Framework.md b/xptifw/doc/XPTI_Framework.md index 8cb7f49f2c163..383faf76f8887 100644 --- a/xptifw/doc/XPTI_Framework.md +++ b/xptifw/doc/XPTI_Framework.md @@ -1,18 +1,27 @@ # XPTI Tracing Framework -- [**XPTI Tracing Framework**](#xpti-tracing-framework) - - [**Overview**](#overview) - - [**Architecture**](#architecture) - - [**The Dispatcher**](#the-dispatcher) - - [**The Subscriber**](#the-subscriber) - - [**Tracing Framework and Callback APIs**](#tracing-framework-and-callback-apis) - - [**Brief API Concepts**](#brief-api-concepts) - - [**`xptiInitialize`**](#xptiinitialize) - - [**`xptiFinalize`**](#xptifinalize) - - [**`xptiTraceEnabled`**](#xptitraceenabled) - - [**APIs and Data Structures Exported by the Tracing Framework**](#apis-and-data-structures-exported-by-the-tracing-framework) - - [**Performance of the Framework**](#performance-of-the-framework) - - [**Modeling and Projection**](#modeling-and-projection) +- [XPTI Tracing Framework](#xpti-tracing-framework) + - [Overview](#overview) + - [Architecture](#architecture) + - [The Dispatcher](#the-dispatcher) + - [The Subscriber](#the-subscriber) + - [Tracing Framework and Callback APIs](#tracing-framework-and-callback-apis) + - [Brief API Concepts](#brief-api-concepts) + - [`xptiInitialize`](#xptiinitialize) + - [`xptiFinalize`](#xptifinalize) + - [`xptiTraceEnabled`](#xptitraceenabled) + - [APIs and Data Structures Exported by the Tracing Framework](#apis-and-data-structures-exported-by-the-tracing-framework) + - [Trace Point Event](#trace-point-event) + - [Creating the Payload](#creating-the-payload) + - [Creating an Event that Represents the Trace Point](#creating-an-event-that-represents-the-trace-point) + - [`xptiRegisterUserDefinedTracePoint`](#xptiregisteruserdefinedtracepoint) + - [`xptiRegisterUserDefinedEventType`](#xptiregisteruserdefinedeventtype) + - [`xptiMakeEvent`](#xptimakeevent) + - [Notifying the registered listeners](#notifying-the-registered-listeners) + - [`xptiNotifySubscribers`](#xptinotifysubscribers) + - [Performance of the Framework](#performance-of-the-framework) + - [Modeling and projection](#modeling-and-projection) + - [Computing the cost incurred in the framework](#computing-the-cost-incurred-in-the-framework) ## Overview @@ -379,16 +388,40 @@ information about the trace point. The framework has a list of predefined trace point types that may be used to mark various trace points with an appropriate type. They are declared in the header file `xpti_data_types.h` and are used to describe the creation of a graph, node, edges or the instantiation of a node as -`task_begin` and `task_end` pair of trace point notifications. +`task_begin` and `task_end` pair of trace point notifications. These trace points represent the types of actions commonly associated with instrumenting a library or application. However, in cases where there is no direct mapping from the predefined trace point types to a language structure or -if they need to describe an action that is orthogonal to code structure such as +if one needs to describe an action that is orthogonal to code structure such as diagnostic information for executing code, the framework allows each instrumentation stream to extend the available trace point types with new trace point types that map to these unsupported constructs. -The basic pre-defined types may be extended as follows: +#### `xptiRegisterUserDefinedTracePoint` + +This API allows streams to extend the existing trace point types and generate +new types that map to the action about to be described by this API. The +description of the API is followed by a code example that shows the use of +this API. + +```cpp + uint16_t xptiRegisterUserDefinedTracePoint( + const char *vendor_name, + uint8_t user_defined_tp); +``` + +| Argument | Description | +| -------- | ----------- | +|`vendor_name` | The name of the tool or vendor implementing the tool that is describing this extension to the trace points. | +| `user_defined_tp`| The user defined trace point which is of type `uint16_t`. The 8 most significant bits of the 16-bit value encodes the `vendor_name` and 8 least significant bits are used to encode the extensions for this tool. A maximum of **128** new user defined trace points can be created per `vendor_name`. | + +The code example of extending the default or pre-defined trace point types is +shown below. As you can see in the example, the user defined trace point types +are initialized in the `enum` using the macros `XPTI_TRACE_POINT_BEGIN` and +`XPTI_TRACE_POINT_END` for the same trace point type `0`. By default, the +trace point types are designed to define the scope of the action be described. +This requires 1-bit to represent begin or end, which leaves the remaining +7-bits to describe 128 unique trace point extensions for a given `tool_name`. ```cpp typedef enum { @@ -433,12 +466,86 @@ trace point type describes the type of information expected by the notification. The trace point type is only used when notifying the subscribers of an event with the trace point type acting as a qualifier for the event. -In a similar manner, the `xpti::trace_event_type_t` can also be extended. -The events that are predefined by the framework fall under `{graph, algorithm,` -`barrier, scheduler, async, lock, offload_read, offload_write, user_defined}`. -If a particular library or application needs to extend these event types, the -framework provides APIs to extend this set to meet the need of the specific -tracing activity using the `xptiRegisterUserDefinedEventType` API function. +#### `xptiRegisterUserDefinedEventType` + +This API allows streams to extend the existing trace point event types and +generate new types that map to the semantic description of the trace event +being created. The description of the API is followed by a code example that +shows the use of this API. + +```cpp + uint16_t xptiRegisterUserDefinedTracePoint( + const char *vendor_name, + uint8_t user_defined_event); +``` +| Argument | Description | +| -------- | ----------- | +|`vendor_name` | The name of the tool or vendor implementing the tool that is describing this extension to the event types. | +| `user_defined_event`| The user defined event which is of type `uint16_t`. The 8 most significant bits of the 16-bit value encodes the `vendor_name` and 8 least significant bits are used to encode the extensions for this tool. A maximum of **128** new user defined event types can be created per `vendor_name`. | + +Similar to trace point types, the `xpti::trace_event_type_t` can also be +extended. The events that are predefined by the framework fall under `{graph,` +`algorithm, barrier, scheduler, async, lock, offload_read, offload_write,` +`user_defined}`. Let's take the example of having to extend the event types to include a diagnostic category. + +```cpp +typedef enum { + my_diagnostic_A = XPTI_EVENT(0), + my_diagnostic_B = XPTI_EVENT(1) +} event_extension_t; +... +uint16_t my_ev1 = xptiRegisterUserDefinedEventType("myTest", my_diagnostic_A) +... +uint64_t InstanceNo; +MyEvent = xptiMakeEvent("application_foo", &Payload, + my_ev1, xpti::trace_activity_type_t::active, + &InstanceNo); +``` + +When this information is provided to the callback handlers in subscribers +through notifications, the handler can decide what it wants to do with the +extended types. If it is not designed to handle it, it can choose to ignore +the event. On the other hand, a subscriber that is designed to handle it must +conform to the specifications defined by the stream that is generating the +extended type events. + +```cpp +uint8_t tool_vendor_id = XPTI_TOOL_ID(tp1_start); +uint8_t ev_type = XPTI_EXTRACT_USER_DEFINED_ID(tp1_start); + +if(tool_vendor_id == 0) { + // Default pre-defined type +} +else { + // User-defined event type + // Here: tp_type will be event_extension_t::my_diagnostic_A +} +``` + +#### `xptiMakeEvent` + +The `xptiMakeEvent` combines the payload information with information about the trace point being defined to create an `xpti::trace_event_data_t`. + +```cpp + xpti::trace_event_data_t *xptiMakeEvent(const char *name, + xpti::payload_t *payload, uint16_t event, + xpti::trace_activity_type_t activity, + uint64_t *instance_no); +``` + +| Argument | Description | +| -------- | ----------- | +|`name` | Name of the event, which is typically a function or kernel name. | +| `payload`| The payload that this trace event represents. The payload in `XPTI` represents the source file, function name and line number, if available. If the source information is not available, it may contain a function name and code pointer virtual address or just the virtual address. This allows one to get the payload as meta-data for a given trace point. | +| `event` | The event type this trace point represents. It could be one of the predefined types or an extended type. | +| `activity` | Describes the activity type, as in active time or overhead time etc. | +| `instance_no` | If `xptiMakeEvent` is used each time this code location is visited to create or look up a previously created event, the `instance_no` parameter is incremented to indicate the instance ID of the current visit. | + +The created trace event data type is returned. In case the payload information +is the same, a previously created event is returned. If global `user_data` +needs to be specified for this trace event that may be used by tools, it can +be allocated and stored in `xpti::trace_event_data_t` structure under +`user_data`. The code sample below shows a sample code snippet that creates such an trace point event using a payload and uses the created event to notify all @@ -447,12 +554,14 @@ subscribers of the event qualified by a trace point type. ```cpp if ( xptiTraceEnabled() ) { // example + uint64_t instance_no; auto stream_id = xptiRegisterStream("myStream"); xptiInitialize("myStream", 1, 0, "myStream 1.0"); xpti::payload_t p("application_graph"); auto event = xptiMakeEvent( "app", &p, xpti::trace_event_type_t::graph, - xpti::trace_activity_type_t::active); + xpti::trace_activity_type_t::active, + &instance_no); } ... if (event && xptiTraceEnabled()) { @@ -462,6 +571,7 @@ if (event && xptiTraceEnabled()) { xpti::trace_point_type_t::graph_create, nullptr, // no parent event, + instance_no, nullptr // no user data); } @@ -469,11 +579,34 @@ if (event && xptiTraceEnabled()) { #### Notifying the registered listeners +As discussed in previous sections, creating a trace point is only one part of a trace point definition. The part that actually lets a tool know that such a trace event occurred is through a notification of the aforementioned event. In this section, we will describe the API and its use. + +#### `xptiNotifySubscribers` + +```cpp + xpti::result_t xptiNotifySubscribers(uint8_t stream_id, + uint16_t trace_type, + xpti::trace_event_data_t *parent, + xpti::trace_event_data_t *event, + uint64_t instance, + const void *temporal_user_data); +``` + +| Argument | Description | +| -------- | ----------- | +|`stream_id` | The stream that this notification belongs to. The stream ID is obtained from `xptiRegisterStream`. | +| `trace_type`| The trace point type that describes the current notification. It could be one of the pre-defined types or a user-extension. | +|`parent`| A parent trace event, if present. | +|`event` | The current trace event for which the notification is being sent out. | +|`instance` | This value indicates the instance of the current trace event. If this is being used to monitor functions, this value should indicate the call count at that time. | +| `temporal_user_data` | This is a field that holds per instance user data and is valid for just that value of `instance`| + The code example below shows an example 'C' code that is instrumented with the framework API and this will generate traces for the functions in the program. However, in this example, we use the helper scoped class provided by the -framework to emit notifications for `xpti::trace_point_type_t::task_begin` and -`xpti::trace_point_type_t::task_begin` automatically. +framework to emit notifications for the begin and end of the scope through +the `xpti::trace_point_type_t::task_begin` and +`xpti::trace_point_type_t::task_end` automatically. In this example, the per instance user data is not sent and the `scoped_notify` defaults that to `nullptr`. ```cpp void function1() { From dbc83ebc926be1914eecd5738b19f445d04061bf Mon Sep 17 00:00:00 2001 From: Vasanth Tovinkere Date: Wed, 8 Apr 2020 09:00:54 -0700 Subject: [PATCH 13/18] [XPTIFW][DOC] Added a section on using the sample subscriber Signed-off-by: Vasanth Tovinkere --- xptifw/doc/XPTI_Framework.md | 63 ++++++++++++++++++++++++++++++++++-- 1 file changed, 61 insertions(+), 2 deletions(-) diff --git a/xptifw/doc/XPTI_Framework.md b/xptifw/doc/XPTI_Framework.md index 383faf76f8887..9f78cee64bcd0 100644 --- a/xptifw/doc/XPTI_Framework.md +++ b/xptifw/doc/XPTI_Framework.md @@ -5,6 +5,7 @@ - [Architecture](#architecture) - [The Dispatcher](#the-dispatcher) - [The Subscriber](#the-subscriber) + - [Using the Reference Dispatcher and Subscriber](#using-the-reference-dispatcher-and-subscriber) - [Tracing Framework and Callback APIs](#tracing-framework-and-callback-apis) - [Brief API Concepts](#brief-api-concepts) - [`xptiInitialize`](#xptiinitialize) @@ -56,8 +57,10 @@ performance of the framework. To enable the build to use TBB for the framework and tests, use the commands as shown below: - cd xptifw - cmake -DXPTI_ENABLE_TBB=ON -DXPTI_SOURCE_DIR=$SYCL_HOME/xpti ./ + ```bash + % cd xptifw + % cmake -DXPTI_ENABLE_TBB=ON -DXPTI_SOURCE_DIR=$SYCL_HOME/xpti ./ + ``` > **NOTE:** This document is best viewed with [Markdown Reader](https://chrome.google.com/webstore/detail/markdown-reader/gpoigdifkoadgajcincpilkjmejcaanc) > plugin for Chrome or the [Markdown Preview Extension]() for Visual Studio Code. @@ -232,6 +235,62 @@ combined value of the `unique_id` and `instance_id` should always be unique. > **NOTE:** The specification for a given event stream **must** be consulted > before implementing the callback handlers for various trace types. +### Using the Reference Dispatcher and Subscriber + +The XPTI framework package provides a reference implementation of the XPTI +dispatcher and a sample subscriber that can be used to see what is being emitted +by any stream generated using XPTI. If you wish to skip the rest of the +document and inspect the generated stream, you can follow the steps outlined +below. + +1. **Build the XPTI framework dispatcher:** The instructions below show how to + build the library with standard containers. If you have access to TBB, you + can enable the macro `-DXPTI_USE_TBB` in the cmake command. + + ```bash + % cd xptifw + % cmake -DXPTI_SOURCE_DIR=$SYCL_HOME/xpti ./ + % make + ``` + + The binaries will be built and installed in `lib/Release`. These include the + dispatcher, a sample subscriber that prints the contents of the stream, the + unit test and a performance characterization application for the framework. + +2. **Run an instrumented SYCL application:** + To enable the dispatcher and subscriber, set the following environment + variables. The commands for enabling the environment variables are provided + for Linux environments in the example below: + + ```bash + % export XPTI_TRACE_ENABLE=1 + % export XPTI_FRAMEWORK_DISPATCHER=/path/to/libxptifw.so + % export XPTI_SUBSCRIBERS=/path/to/libbasic_collector.so + ``` + + You can now run a SYCL application that has been linked with a runtime that + supports the XPTI instrumentation and inspect the resulting stream. + +3. **Running the unit tests:** The unit tests included cover the exported API + and incorporate some correctness tests. + + ```bash + % /lib/Release/xpti_tests + ``` +4. **Understanding the throughput of the framework:** This document discusses + the performance of the framework in detail in the sections [Performance of the Framework](#performance-of-the-framework) and [Modeling and projection](#modeling-and-projection). For details on the command line arguments, + please refer to these sections. + + ```bash + % /lib/Release/run_test --trace-points 1000 --type performance --overhead 1.5 --num-threads 0,1,2,3 --test-id 1,2 --tp-frequency 50 + ``` + + The above command will run the performance tests in which 1000 trace points + are created and each trace point visited twice. The trace point creation and + notification costs are measured in single thread and multi-threaded + scenarios and the output shows the throughput projection of the framework + using the events/sec metric at 1.5% overheads to the application runtime. + ## Tracing Framework and Callback APIs The current version of the instrumentation API adopts a model where traces are From 937808189ceb266f4433f5ead1cfcf07e84439d9 Mon Sep 17 00:00:00 2001 From: Vasanth Tovinkere Date: Wed, 8 Apr 2020 10:42:20 -0700 Subject: [PATCH 14/18] [XPTIFW] Minor doc changes + include reordering Signed-off-by: Vasanth Tovinkere --- xptifw/basic_test/performance_tests.cpp | 8 ++++---- xptifw/basic_test/semantic_tests.cpp | 8 ++++---- xptifw/doc/XPTI_Framework.md | 4 ++++ xptifw/samples/basic_collector/basic_collector.cpp | 6 +++--- xptifw/unit_test/xpti_api_tests.cpp | 3 +-- xptifw/unit_test/xpti_correctness_tests.cpp | 3 +-- 6 files changed, 17 insertions(+), 15 deletions(-) diff --git a/xptifw/basic_test/performance_tests.cpp b/xptifw/basic_test/performance_tests.cpp index 5f5b5935d0723..2ae1b521c878b 100644 --- a/xptifw/basic_test/performance_tests.cpp +++ b/xptifw/basic_test/performance_tests.cpp @@ -10,10 +10,6 @@ // scenarios and computing the average costs and maximum Events/sec that can // be serviced by the framework at a given max. overhead constraint. //--------------------------------------------------------------------------- -#include -#include -#include - #include "tbb/concurrent_vector.h" #include "tbb/parallel_for.h" #include "tbb/spin_mutex.h" @@ -23,6 +19,10 @@ #include "cl_processor.hpp" #include "xpti_trace_framework.h" +#include +#include +#include + namespace test { void registerCallbacks(uint8_t sid); namespace performance { diff --git a/xptifw/basic_test/semantic_tests.cpp b/xptifw/basic_test/semantic_tests.cpp index bf9dea477502f..9dbff4130692d 100644 --- a/xptifw/basic_test/semantic_tests.cpp +++ b/xptifw/basic_test/semantic_tests.cpp @@ -9,10 +9,6 @@ // Tests the correctness of the API by comparing it agains the spec and // expected results. //-------------------------------------------------------------------------- -#include -#include -#include - #include "tbb/concurrent_vector.h" #include "tbb/parallel_for.h" #include "tbb/spin_mutex.h" @@ -22,6 +18,10 @@ #include "cl_processor.hpp" #include "xpti_trace_framework.h" +#include +#include +#include + static void tpCallback(uint16_t trace_type, xpti::trace_event_data_t *parent, xpti::trace_event_data_t *event, uint64_t instance, const void *ud) {} diff --git a/xptifw/doc/XPTI_Framework.md b/xptifw/doc/XPTI_Framework.md index 9f78cee64bcd0..8964b5cf88d1c 100644 --- a/xptifw/doc/XPTI_Framework.md +++ b/xptifw/doc/XPTI_Framework.md @@ -281,6 +281,10 @@ below. the performance of the framework in detail in the sections [Performance of the Framework](#performance-of-the-framework) and [Modeling and projection](#modeling-and-projection). For details on the command line arguments, please refer to these sections. + > **NOTE:** These tests rely on the availability of TBB for creating the + multi-threaded tests and will not be created if TBB has not been enabled + during the build process. + ```bash % /lib/Release/run_test --trace-points 1000 --type performance --overhead 1.5 --num-threads 0,1,2,3 --test-id 1,2 --tp-frequency 50 ``` diff --git a/xptifw/samples/basic_collector/basic_collector.cpp b/xptifw/samples/basic_collector/basic_collector.cpp index 7e358146f0727..c09ba143cb8ec 100644 --- a/xptifw/samples/basic_collector/basic_collector.cpp +++ b/xptifw/samples/basic_collector/basic_collector.cpp @@ -5,6 +5,9 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // // +#include "xpti_timers.hpp" +#include "xpti_trace_framework.h" + #include #include #include @@ -14,9 +17,6 @@ #include #include -#include "xpti_timers.hpp" -#include "xpti_trace_framework.h" - static uint8_t GStreamID = 0; std::mutex GIOMutex; xpti::ThreadID GThreadIDEnum; diff --git a/xptifw/unit_test/xpti_api_tests.cpp b/xptifw/unit_test/xpti_api_tests.cpp index 8b5b6e6c63bca..5420ab40bf6e1 100644 --- a/xptifw/unit_test/xpti_api_tests.cpp +++ b/xptifw/unit_test/xpti_api_tests.cpp @@ -5,11 +5,10 @@ // #include "xpti_trace_framework.hpp" +#include #include #include -#include - TEST(xptiApiTest, xptiInitializeBadInput) { auto Result = xptiInitialize(nullptr, 0, 0, nullptr); EXPECT_EQ(Result, xpti::result_t::XPTI_RESULT_INVALIDARG); diff --git a/xptifw/unit_test/xpti_correctness_tests.cpp b/xptifw/unit_test/xpti_correctness_tests.cpp index a92cd0e5bfaac..9f8b7ece34bd7 100644 --- a/xptifw/unit_test/xpti_correctness_tests.cpp +++ b/xptifw/unit_test/xpti_correctness_tests.cpp @@ -6,11 +6,10 @@ #include "xpti_trace_framework.h" #include "xpti_trace_framework.hpp" +#include #include #include -#include - XPTI_CALLBACK_API void tpCallback(uint16_t trace_type, xpti::trace_event_data_t *parent, xpti::trace_event_data_t *event, From 7944ef6b33c00b8986c35b207a8ea05d593c2bd6 Mon Sep 17 00:00:00 2001 From: Vasanth Tovinkere Date: Tue, 14 Apr 2020 12:53:39 -0700 Subject: [PATCH 15/18] [XPTIFW][DOC] Fixed typos Signed-off-by: Vasanth Tovinkere --- xptifw/basic_test/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/xptifw/basic_test/README.md b/xptifw/basic_test/README.md index 4a2260e0bb93c..2736a792ea74a 100644 --- a/xptifw/basic_test/README.md +++ b/xptifw/basic_test/README.md @@ -4,9 +4,9 @@ In order to capture the cost of various API calls in the framework and test the correctness of the API, a set of basic tests have been created. They primarily fall under two categories: -1. Sematic tests: These tests perform correctness checks on the API call to -ensure the right data is being retrieved. The sematic tests are categorized into -string table tests, trace point tests and notification tests. +1. Semantic tests: These tests perform correctness checks on the API call to +ensure the right data is being retrieved. The semantic tests are categorized +into string table tests, trace point tests and notification tests. 2. Performance tests: These test attempt to capture the average cost of various operations that are a part of creating trace points in applications. The tests From 826503d34c9a8fb43f901908280732ca95bbd3ea Mon Sep 17 00:00:00 2001 From: Vasanth Tovinkere Date: Tue, 21 Apr 2020 08:02:11 -0700 Subject: [PATCH 16/18] [XPTIFW] Adressing review feedback + Coding conventions mismatch Signed-off-by: Vasanth Tovinkere --- xptifw/basic_test/cl_processor.hpp | 319 ++++++++++++------------ xptifw/basic_test/performance_tests.cpp | 6 +- 2 files changed, 164 insertions(+), 161 deletions(-) diff --git a/xptifw/basic_test/cl_processor.hpp b/xptifw/basic_test/cl_processor.hpp index b26f2e6ff7b75..e312abcadab7b 100644 --- a/xptifw/basic_test/cl_processor.hpp +++ b/xptifw/basic_test/cl_processor.hpp @@ -27,32 +27,32 @@ enum class OptionType { Boolean, Integer, Float, String, Range }; // We are using C++ 11, hence we cannot use // std::variant or std::any -typedef std::map table_row_t; -typedef std::map table_t; -typedef std::vector titles_t; +using table_row_t = std::map; +using table_t = std::map; +using titles_t = std::vector; class ScopedTimer { public: typedef std::chrono::time_point time_unit_t; ScopedTimer(uint64_t &ns, double &ratio, size_t count = 1) - : m_duration{ns}, m_average{ratio}, m_instances{count} { - m_before = std::chrono::high_resolution_clock::now(); + : MDuration{ns}, MAverage{ratio}, MInstances{count} { + MBefore = std::chrono::high_resolution_clock::now(); } ~ScopedTimer() { - m_after = std::chrono::high_resolution_clock::now(); - auto duration = std::chrono::duration_cast( - m_after - m_before); - m_duration = duration.count(); - m_average = (double)m_duration / m_instances; + MAfter = std::chrono::high_resolution_clock::now(); + auto duration = + std::chrono::duration_cast(MAfter - MBefore); + MDuration = duration.count(); + MAverage = (double)MDuration / MInstances; } private: - uint64_t &m_duration; - double &m_average; - size_t m_instances; - time_unit_t m_before, m_after; + uint64_t &MDuration; + double &MAverage; + size_t MInstances; + time_unit_t MBefore, MAfter; }; class CommandLineOption { @@ -120,27 +120,27 @@ class CommandLineParser { } } - CommandLineOption &addOption(std::string key) { - if (key == MReservedKey) { - std::cout << "Option[" << key + CommandLineOption &addOption(std::string Key) { + if (Key == MReservedKey) { + std::cout << "Option[" << Key << "] is a reserved option. Ignoring the addOption() call!\n"; // throw an exception here; } - if (MOptionHelpLUT.count(key)) { - std::cout << "Option " << key << " has already been registered!\n"; - return MOptionHelpLUT[key]; + if (MOptionHelpLUT.count(Key)) { + std::cout << "Option " << Key << " has already been registered!\n"; + return MOptionHelpLUT[Key]; } - return MOptionHelpLUT[key]; + return MOptionHelpLUT[Key]; } - std::string &query(const char *key) { - if (MOptionHelpLUT.count(key)) { - return MValueLUT[key]; - } else if (MAbbreviatedOptionLUT.count(key)) { - std::string full_key = MAbbreviatedOptionLUT[key]; - if (MValueLUT.count(full_key)) { - return MValueLUT[full_key]; + std::string &query(const char *Key) { + if (MOptionHelpLUT.count(Key)) { + return MValueLUT[Key]; + } else if (MAbbreviatedOptionLUT.count(Key)) { + std::string FullKey = MAbbreviatedOptionLUT[Key]; + if (MValueLUT.count(FullKey)) { + return MValueLUT[FullKey]; } return MEmptyString; } @@ -148,10 +148,10 @@ class CommandLineParser { private: void buildAbbreviationTable() { - for (auto &o : MOptionHelpLUT) { - std::string &abbr = o.second.abbreviation(); + for (auto &Option : MOptionHelpLUT) { + std::string &abbr = Option.second.abbreviation(); if (!abbr.empty()) { - MAbbreviatedOptionLUT[abbr] = o.first; + MAbbreviatedOptionLUT[abbr] = Option.first; } } } @@ -160,10 +160,10 @@ class CommandLineParser { std::cout << "Usage:- \n"; std::cout << " " << MAppName << " "; // Print all required options first - for (auto &op : MOptionHelpLUT) { - if (op.second.required()) { - std::cout << op.first << " "; - switch (op.second.type()) { + for (auto &Option : MOptionHelpLUT) { + if (Option.second.required()) { + std::cout << Option.first << " "; + switch (Option.second.type()) { case OptionType::Integer: std::cout << " "; break; @@ -183,10 +183,10 @@ class CommandLineParser { } } // Print the optional flags next. - for (auto &op : MOptionHelpLUT) { - if (!op.second.required()) { - std::cout << "[" << op.first << " "; - switch (op.second.type()) { + for (auto &Option : MOptionHelpLUT) { + if (!Option.second.required()) { + std::cout << "[" << Option.first << " "; + switch (Option.second.type()) { case OptionType::Integer: std::cout << "] "; break; @@ -208,87 +208,88 @@ class CommandLineParser { } std::cout << "\n Options supported:\n"; // Print help for all of the options - for (auto &op : MOptionHelpLUT) { - std::stringstream help(op.second.help()); - std::string help_line; - bool first = true; - - while (std::getline(help, help_line, '\n')) { - if (first) { - std::string options = op.first + ", " + op.second.abbreviation(); - first = false; + for (auto &Option : MOptionHelpLUT) { + std::stringstream Help(Option.second.help()); + std::string HelpLine; + bool FirstTime = true; + + while (std::getline(Help, HelpLine, '\n')) { + if (FirstTime) { + std::string options = + Option.first + ", " + Option.second.abbreviation(); + FirstTime = false; std::cout << " " << std::left << std::setw(20) << options << " " - << help_line << "\n"; + << HelpLine << "\n"; } else { std::cout << " " << std::left << std::setw(20) << " " - << " " << help_line << "\n"; + << " " << HelpLine << "\n"; } } } } bool checkOptions() { - bool pass = true; + bool Pass = true; std::string PrevKey; - for (auto &op : MCommandLineOptions) { - std::size_t pos = op.find_first_of("-"); - if (std::string::npos != pos) { + for (auto &Option : MCommandLineOptions) { + std::size_t Pos = Option.find_first_of("-"); + if (std::string::npos != Pos) { // We have an option provided; let's check to see if it is verbose or // abbreviated - pos = op.find_first_of("-", pos + 1); - if (std::string::npos != pos) { + Pos = Option.find_first_of("-", Pos + 1); + if (std::string::npos != Pos) { // We have a verbose option - if (op == MReservedKey) { + if (Option == MReservedKey) { printHelp(); exit(-1); - } else if (MOptionHelpLUT.count(op) == 0) { - std::cout << "Unknown option[" << op << "]!\n"; - pass = false; + } else if (MOptionHelpLUT.count(Option) == 0) { + std::cout << "Unknown option[" << Option << "]!\n"; + Pass = false; } - MValueLUT[op] = "true"; - PrevKey = op; + MValueLUT[Option] = "true"; + PrevKey = Option; } else { // We have an abbreviated option - if (op == MReservedKeyAbbr) { + if (Option == MReservedKeyAbbr) { printHelp(); exit(-1); - } else if (MAbbreviatedOptionLUT.count(op) == 0) { - std::cout << "Unknown option[" << op << "] detected.\n"; - pass = false; + } else if (MAbbreviatedOptionLUT.count(Option) == 0) { + std::cout << "Unknown option[" << Option << "] detected.\n"; + Pass = false; } - PrevKey = MAbbreviatedOptionLUT[op]; + PrevKey = MAbbreviatedOptionLUT[Option]; MValueLUT[PrevKey] = "true"; } } else { // No idea why stringstream will decode the last \n as a "" string; this // handles that case - if (PrevKey.empty() && op.empty()) + if (PrevKey.empty() && Option.empty()) break; // We have an option value if (PrevKey.empty()) { - std::cout << "Value[" << op + std::cout << "Value[" << Option << "] provided without specifying an option\n"; - pass = false; + Pass = false; } else { - MValueLUT[PrevKey] = op; + MValueLUT[PrevKey] = Option; PrevKey = MEmptyString; } } } - for (auto &op : MOptionHelpLUT) { + for (auto &Option : MOptionHelpLUT) { // Check to see if an option is required; If so, check to see if there's a // value associated with it. - if (op.second.required()) { - if (!MValueLUT.count(op.first)) { - std::cout << "Option[" << op.first + if (Option.second.required()) { + if (!MValueLUT.count(Option.first)) { + std::cout << "Option[" << Option.first << "] is required and not provided.\n"; - pass = false; + Pass = false; } } } - return pass; + return Pass; } std::vector MCommandLineOptions; @@ -303,41 +304,42 @@ class CommandLineParser { class TableModel { public: - typedef std::map row_titles_t; + using row_titles_t = std::map; + TableModel() {} - void setHeaders(titles_t &titles) { MColumnTitles = titles; } + void setHeaders(titles_t &Titles) { MColumnTitles = Titles; } - table_row_t &addRow(int row, std::string &row_name) { - if (MRowTitles.count(row)) { + table_row_t &addRow(int Row, std::string &RowName) { + if (MRowTitles.count(Row)) { std::cout << "Warning: Row title already specified!\n"; } - MRowTitles[row] = row_name; - return MTable[row]; + MRowTitles[Row] = RowName; + return MTable[Row]; } - table_row_t &addRow(int row, const char *row_name) { - if (MRowTitles.count(row)) { + + table_row_t &addRow(int Row, const char *RowName) { + if (MRowTitles.count(Row)) { std::cout << "Warning: Row title already specified!\n"; } - MRowTitles[row] = row_name; - return MTable[row]; + MRowTitles[Row] = RowName; + return MTable[Row]; } - table_row_t &operator[](int row) { return MTable[row]; } + table_row_t &operator[](int Row) { return MTable[Row]; } void print() { std::cout << std::setw(14) << " "; - for (auto &title : MColumnTitles) { - std::cout << std::setw(14) << title; // Column headers + for (auto &Title : MColumnTitles) { + std::cout << std::setw(14) << Title; // Column headers } std::cout << "\n"; - for (auto &row : MTable) { - std::cout << std::setw(14) << MRowTitles[row.first]; - int prev_col = 0; - for (auto &data : row.second) { + for (auto &Row : MTable) { + std::cout << std::setw(14) << MRowTitles[Row.first]; + for (auto &Data : Row.second) { std::cout << std::fixed << std::setw(14) << std::setprecision(0) - << data.second; + << Data.second; } std::cout << "\n"; } @@ -352,15 +354,15 @@ class TableModel { class RangeDecoder { public: - RangeDecoder(std::string &range_str) : MRange(range_str) { + RangeDecoder(std::string &RangeStr) : MRange(RangeStr) { // Split by commas first followed by : for begin,end, Step - std::stringstream elements(range_str); - std::string element; - while (std::getline(elements, element, ',')) { - if (element.find_first_of("-:") == std::string::npos) { - MElements.insert(std::stol(element)); + std::stringstream Elements(RangeStr); + std::string Element; + while (std::getline(Elements, Element, ',')) { + if (Element.find_first_of("-:") == std::string::npos) { + MElements.insert(std::stol(Element)); } else { - std::stringstream R(element); + std::stringstream R(Element); std::vector RangeTokens; std::string SubStr; // Now split by : @@ -395,13 +397,13 @@ class TestCorrectness { NotificationTest }; - TestCorrectness(test::utils::CommandLineParser &parser) : MParser(parser) { + TestCorrectness(test::utils::CommandLineParser &Parser) : MParser(Parser) { xptiInitialize("xpti", 20, 0, "xptiTests"); } void run() { - auto &v = MParser.query("--type"); - if (v != "semantic") + auto &V = MParser.query("--type"); + if (V != "semantic") return; test::utils::RangeDecoder td(MParser.query("--num-threads")); @@ -413,8 +415,8 @@ class TestCorrectness { } void runTests() { - for (auto test : MTests) { - switch ((SemanticTests)test) { + for (auto Test : MTests) { + switch ((SemanticTests)Test) { case SemanticTests::StringTableTest: runStringTableTests(); break; @@ -425,7 +427,7 @@ class TestCorrectness { runNotificationTests(); break; default: - std::cout << "Unknown test type [" << test << "]: use 1,2,3 or 1:3:1\n"; + std::cout << "Unknown test type [" << Test << "]: use 1,2,3 or 1:3:1\n"; break; } } @@ -434,13 +436,14 @@ class TestCorrectness { private: void runStringTableTests(); - void runStringTableTestThreads(int run_no, int nt, - test::utils::TableModel &t); + void runStringTableTestThreads(int RunNo, int NThreads, + test::utils::TableModel &Table); void runTracepointTests(); - void runTracepointTestThreads(int run_no, int nt, test::utils::TableModel &t); + void runTracepointTestThreads(int RunNo, int nt, + test::utils::TableModel &Table); void runNotificationTests(); - void runNotificationTestThreads(int run_no, int nt, - test::utils::TableModel &t); + void runNotificationTestThreads(int RunNo, int NThreads, + test::utils::TableModel &Table); test::utils::CommandLineParser &MParser; test::utils::TableModel MTable; @@ -462,31 +465,31 @@ class TestPerformance { }; enum class PerformanceTests { DataStructureTest = 1, InstrumentationTest }; - TestPerformance(test::utils::CommandLineParser &parser) : MParser(parser) { + TestPerformance(test::utils::CommandLineParser &Parser) : MParser(Parser) { xptiInitialize("xpti", 20, 0, "xptiTests"); } - std::string makeRandomString(uint8_t length, std::mt19937_64 &gen) { - if (length > 25) { - length = 25; + std::string makeRandomString(uint8_t Length, std::mt19937_64 &Gen) { + if (Length > 25) { + Length = 25; } // A=65, a=97 - std::string s(length, '\0'); - for (int i = 0; i < length; ++i) { - int ascii = MCaseU(gen); - int value = MCharU(gen); + std::string s(Length, '\0'); + for (int i = 0; i < Length; ++i) { + int ascii = MCaseU(Gen); + int value = MCharU(Gen); s[i] = (ascii ? value + 97 : value + 65); } return s; } void run() { - auto &v = MParser.query("--type"); - if (v != "performance") + auto &V = MParser.query("--type"); + if (V != "performance") return; - test::utils::RangeDecoder td(MParser.query("--num-threads")); - MThreads = td.decode(); + test::utils::RangeDecoder Td(MParser.query("--num-threads")); + MThreads = Td.decode(); MTracepoints = std::stol(MParser.query("--trace-points")); if (MTracepoints > MaxTracepoints) { std::cout << "Reducing trace points to " << MaxTracepoints << "!\n"; @@ -497,23 +500,23 @@ class TestPerformance { MTracepoints = MinTracepoints; } - test::utils::RangeDecoder rd(MParser.query("--test-id")); - MTests = rd.decode(); + test::utils::RangeDecoder Rd(MParser.query("--test-id")); + MTests = Rd.decode(); - std::string dist = MParser.query("--tp-frequency"); - if (dist.empty()) { + std::string Dist = MParser.query("--tp-frequency"); + if (Dist.empty()) { // By default, we assume that for every trace point that is created, we // will visit it NINE more times. MTracepointInstances = MTracepoints * 10; } else { - float value = std::stof(dist); - if (value > 100) { + float Value = std::stof(Dist); + if (Value > 100) { std::cout << "Trace point creation frequency limited to 100%!\n"; - value = 100; + Value = 100; } - if (value < 0) { + if (Value < 0) { std::cout << "Trace point creation frequency set to 1%!\n"; - value = 1; + Value = 1; } // If not, we compute the number of trace point instances based on the // trace point frequency value; If the frequency is 10%, then every 10th @@ -521,12 +524,12 @@ class TestPerformance { // then every 50th trace point will create call will result in a new // trace point. MTracepointInstances = - (long)((1.0 / (std::stof(dist) / 100)) * MTracepoints); + (long)((1.0 / (std::stof(Dist) / 100)) * MTracepoints); } // Check to see if overheads to model are set; if not assume 1.0% - dist = MParser.query("--overhead"); - if (!dist.empty()) { - MOverhead = std::stof(dist); + Dist = MParser.query("--overhead"); + if (!Dist.empty()) { + MOverhead = std::stof(Dist); if (MOverhead < 0.1) { std::cout << "Overheads to be modeled clamped to range - 0.1%!\n"; MOverhead = 0.1; @@ -541,8 +544,8 @@ class TestPerformance { // TP frequency of 10%, we will have TP instances be 1000x10 MStringTableEntries = MTracepointInstances; // Mersenne twister RNG engine that is uniform distribution - std::random_device q_rd; - std::mt19937_64 gen(q_rd()); + std::random_device QRd; + std::mt19937_64 Gen(QRd()); // Generate the pseudo-random numbers for trace points and string table // random lookup MTracepointU = std::uniform_int_distribution(0, MTracepoints - 1); @@ -554,31 +557,31 @@ class TestPerformance { MRndmSTIndex.resize(MStringTableEntries); MRndmTPIndex.resize(MStringTableEntries); for (int i = 0; i < MStringTableEntries; ++i) { - MRndmSTIndex[i] = MStringTableU(gen); + MRndmSTIndex[i] = MStringTableU(Gen); } for (int i = 0; i < MStringTableEntries; ++i) { - MRndmTPIndex[i] = MTracepointU(gen); + MRndmTPIndex[i] = MTracepointU(Gen); } // Generate the strings we will be registering with the string table and // also the random lookup table for trace points for (int i = 0; i < MTracepointInstances; ++i) { - record r; - r.lookup = MRndmTPIndex[i]; // 0-999999999 - std::string str = makeRandomString(5, gen); - r.fn = str + std::to_string(r.lookup); - MRecords.push_back(r); - str = makeRandomString(8, gen) + std::to_string(i); - MFunctions.push_back(str); - str = makeRandomString(8, gen) + std::to_string(i); - MFunctions2.push_back(str); + record Rec; + Rec.lookup = MRndmTPIndex[i]; // 0-999999999 + std::string Str = makeRandomString(5, Gen); + Rec.fn = Str + std::to_string(Rec.lookup); + MRecords.push_back(Rec); + Str = makeRandomString(8, Gen) + std::to_string(i); + MFunctions.push_back(Str); + Str = makeRandomString(8, Gen) + std::to_string(i); + MFunctions2.push_back(Str); } // Done with the setup; now run the tests runTests(); } void runTests() { - for (auto test : MTests) { - switch ((PerformanceTests)test) { + for (auto Test : MTests) { + switch ((PerformanceTests)Test) { case PerformanceTests::DataStructureTest: runDataStructureTests(); break; @@ -586,7 +589,7 @@ class TestPerformance { runInstrumentationTests(); break; default: - std::cout << "Unknown test type [" << test << "]: use 1,2 or 1:2:1\n"; + std::cout << "Unknown test type [" << Test << "]: use 1,2 or 1:2:1\n"; break; } } @@ -595,11 +598,11 @@ class TestPerformance { private: void runDataStructureTests(); - void runDataStructureTestsThreads(int run_no, int nt, - test::utils::TableModel &t); + void runDataStructureTestsThreads(int RunNo, int NThreads, + test::utils::TableModel &Table); void runInstrumentationTests(); - void runInstrumentationTestsThreads(int run_no, int nt, - test::utils::TableModel &t); + void runInstrumentationTestsThreads(int RunNo, int NThreads, + test::utils::TableModel &Table); test::utils::CommandLineParser &MParser; test::utils::TableModel MTable; diff --git a/xptifw/basic_test/performance_tests.cpp b/xptifw/basic_test/performance_tests.cpp index 2ae1b521c878b..215988f0b0733 100644 --- a/xptifw/basic_test/performance_tests.cpp +++ b/xptifw/basic_test/performance_tests.cpp @@ -413,7 +413,7 @@ void TestPerformance::runDataStructureTests() { "TP FW-Cached", "TP Local", "Notify"}; std::cout << std::setw(Columns.size() * 15 / 2) << "Data Structure Tests [FW=framework, Lu=lookup, " - "TP=Tracepoint, Time=TimeInNS\n"; + "TP=Tracepoint, Time=ns\n"; Model.setHeaders(Columns); uint8_t sid = xptiRegisterStream("xpti"); @@ -543,8 +543,8 @@ void TestPerformance::runInstrumentationTests() { test::utils::TableModel Model; test::utils::titles_t Columns{"Threads", - "TP LU+Notify(TimeInNS)", - "TP Create(TimeInNS)", + "TP LU+Notify(ns)", + "TP Create(ns)", "Ev/s,cb=10", "Ev/s,cb=100", "Ev/s,cb=500", From 9f2bc8639691b1673b2b493a07662f7044e9aef9 Mon Sep 17 00:00:00 2001 From: Vasanth Tovinkere Date: Tue, 21 Apr 2020 08:15:47 -0700 Subject: [PATCH 17/18] [XPTIFW] Addressing clang-format check fail Signed-off-by: Vasanth Tovinkere --- xptifw/basic_test/performance_tests.cpp | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/xptifw/basic_test/performance_tests.cpp b/xptifw/basic_test/performance_tests.cpp index 215988f0b0733..1b850f92b110c 100644 --- a/xptifw/basic_test/performance_tests.cpp +++ b/xptifw/basic_test/performance_tests.cpp @@ -542,14 +542,9 @@ void TestPerformance::runInstrumentationTestsThreads( void TestPerformance::runInstrumentationTests() { test::utils::TableModel Model; - test::utils::titles_t Columns{"Threads", - "TP LU+Notify(ns)", - "TP Create(ns)", - "Ev/s,cb=10", - "Ev/s,cb=100", - "Ev/s,cb=500", - "Ev/s,cb=1000", - "Ev/s,cb=2000"}; + test::utils::titles_t Columns{ + "Threads", "TP LU+Notify(ns)", "TP Create(ns)", "Ev/s,cb=10", + "Ev/s,cb=100", "Ev/s,cb=500", "Ev/s,cb=1000", "Ev/s,cb=2000"}; std::cout << std::setw(Columns.size() * 15 / 2) << "Framework Tests\n"; Model.setHeaders(Columns); uint8_t sid = xptiRegisterStream("xpti"); From 60af01fba8e4cf2492a0c7c87e432aee697babd7 Mon Sep 17 00:00:00 2001 From: Vasanth Tovinkere Date: Wed, 22 Apr 2020 09:19:51 -0700 Subject: [PATCH 18/18] [XPTIFW] Addressing review feedback II Signed-off-by: Vasanth Tovinkere --- xptifw/basic_test/cl_processor.hpp | 13 +++++-------- xptifw/include/xpti_int64_hash_table.hpp | 2 +- xptifw/include/xpti_string_table.hpp | 3 +-- xptifw/samples/basic_collector/basic_collector.cpp | 2 +- xptifw/samples/include/xpti_timers.hpp | 9 +++------ xptifw/src/xpti_trace_framework.cpp | 11 +++-------- 6 files changed, 14 insertions(+), 26 deletions(-) diff --git a/xptifw/basic_test/cl_processor.hpp b/xptifw/basic_test/cl_processor.hpp index e312abcadab7b..b769ac134e354 100644 --- a/xptifw/basic_test/cl_processor.hpp +++ b/xptifw/basic_test/cl_processor.hpp @@ -33,8 +33,8 @@ using titles_t = std::vector; class ScopedTimer { public: - typedef std::chrono::time_point - time_unit_t; + using time_unit_t = + std::chrono::time_point; ScopedTimer(uint64_t &ns, double &ratio, size_t count = 1) : MDuration{ns}, MAverage{ratio}, MInstances{count} { MBefore = std::chrono::high_resolution_clock::now(); @@ -60,7 +60,6 @@ class CommandLineOption { CommandLineOption() : MRequired(false), MType(OptionType::String), MHelp("No help available.") {} - ~CommandLineOption() {} CommandLineOption &setRequired(bool yesOrNo) { MRequired = yesOrNo; @@ -93,17 +92,15 @@ class CommandLineOption { class CommandLineParser { public: - typedef std::unordered_map - CommandLineOptions_t; - typedef std::unordered_map key_value_t; + using CommandLineOptions_t = + std::unordered_map; + using key_value_t = std::unordered_map; CommandLineParser() { MReservedKey = "--help"; MReservedKeyAbbr = "-h"; } - ~CommandLineParser() {} - void parse(int argc, char **argv) { MCommandLineOptions.resize(argc); // Go through the command-line options list and build an internal diff --git a/xptifw/include/xpti_int64_hash_table.hpp b/xptifw/include/xpti_int64_hash_table.hpp index 9042b08df32d2..909348753cac6 100644 --- a/xptifw/include/xpti_int64_hash_table.hpp +++ b/xptifw/include/xpti_int64_hash_table.hpp @@ -11,7 +11,7 @@ #include #ifdef XPTI_STATISTICS -#include +#include #endif #ifdef XPTI_USE_TBB diff --git a/xptifw/include/xpti_string_table.hpp b/xptifw/include/xpti_string_table.hpp index 5fa276253bd50..03bdb08722822 100644 --- a/xptifw/include/xpti_string_table.hpp +++ b/xptifw/include/xpti_string_table.hpp @@ -7,12 +7,11 @@ #include "xpti_data_types.h" #include -#include #include #include #ifdef XPTI_STATISTICS -#include +#include #endif #ifdef XPTI_USE_TBB diff --git a/xptifw/samples/basic_collector/basic_collector.cpp b/xptifw/samples/basic_collector/basic_collector.cpp index c09ba143cb8ec..f8d455d26cb73 100644 --- a/xptifw/samples/basic_collector/basic_collector.cpp +++ b/xptifw/samples/basic_collector/basic_collector.cpp @@ -9,10 +9,10 @@ #include "xpti_trace_framework.h" #include +#include #include #include #include -#include #include #include #include diff --git a/xptifw/samples/include/xpti_timers.hpp b/xptifw/samples/include/xpti_timers.hpp index 1c927760fb900..cf0f1593c5510 100644 --- a/xptifw/samples/include/xpti_timers.hpp +++ b/xptifw/samples/include/xpti_timers.hpp @@ -15,10 +15,7 @@ namespace xpti { class ThreadID { public: - typedef std::unordered_map thread_lut_t; - - ThreadID() : m_tid(0) {} - ~ThreadID() {} + using thread_lut_t = std::unordered_map; inline uint32_t enumID(std::thread::id &curr) { std::stringstream s; @@ -45,13 +42,13 @@ class ThreadID { } private: - std::atomic m_tid; + std::atomic m_tid = {0}; thread_lut_t m_thread_lookup; }; namespace timer { #include -typedef uint64_t tick_t; +using tick_t = uint64_t; #if defined(_WIN32) || defined(_WIN64) #include "windows.h" inline xpti::timer::tick_t rdtsc() { diff --git a/xptifw/src/xpti_trace_framework.cpp b/xptifw/src/xpti_trace_framework.cpp index 625058efb0377..1d24cd1d255e0 100644 --- a/xptifw/src/xpti_trace_framework.cpp +++ b/xptifw/src/xpti_trace_framework.cpp @@ -8,6 +8,7 @@ #include "xpti_string_table.hpp" #include +#include #include #include #include @@ -15,10 +16,6 @@ #include #include -#ifdef XPTI_STATISTICS -#include -#endif - #ifdef XPTI_USE_TBB #include #include @@ -104,7 +101,6 @@ class Subscribers { #endif // This plugin has already been loaded, so let's return previously // recorded handle - printf("Plugin (%s) has already been loaded..\n", Path); plugin_data_t &Data = MNameLUT[Path]; assert(Data.valid && "Lookup is invalid!"); if (Data.valid) @@ -384,10 +380,12 @@ class Tracepoints { // performed. // void printStatistics() { +#ifdef XPTI_STATISTICS printf("Tracepoint inserts : [%lu] \n", MInsertions.load()); printf("Tracepoint lookups : [%lu]\n", MRetrievals.load()); printf("Tracepoint Hashmap :\n"); MPayloadLUT.printStatistics(); +#endif } private: @@ -646,8 +644,6 @@ class Notifications { using stream_cb_t = std::unordered_map; using statistics_t = std::unordered_map; #endif - Notifications() = default; - ~Notifications() = default; xpti::result_t registerCallback(uint8_t StreamID, uint16_t TraceType, xpti::tracepoint_callback_api_t cbFunc) { @@ -898,7 +894,6 @@ class Framework { MTraceEnabled = (g_helper.checkTraceEnv() && MSubscribers.hasValidSubscribers()); } - ~Framework() = default; void clear() { MUniversalIDs = {1};

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