diff --git a/.github/workflows/ci-cas-security-dockerfile.yml b/.github/workflows/ci-cas-security-dockerfile.yml index 5c1fef80..e6d66c20 100644 --- a/.github/workflows/ci-cas-security-dockerfile.yml +++ b/.github/workflows/ci-cas-security-dockerfile.yml @@ -26,8 +26,10 @@ jobs: python-version: '3.11' - name: CAS Security Orchestrator (Source Only) - uses: intel-innersource/applications.security.monitoring.cas@v2 + uses: intel-innersource/applications.security.monitoring.cas@v3.0.2 with: + itsf-username: ${{ secrets.ITSF_USERNAME }} + itsf-password: ${{ secrets.ITSF_PASSWORD }} sdl-api-key: ${{ secrets.SDL_API_KEY }} sdl-project-id: ${{ secrets.SDL_PROJECT_ID }} sdl-idsid-value: ${{ secrets.SDL_IDSID_VALUE }} diff --git a/.github/workflows/ci-cas-security.yml b/.github/workflows/ci-cas-security.yml index 71d6312f..5d543097 100644 --- a/.github/workflows/ci-cas-security.yml +++ b/.github/workflows/ci-cas-security.yml @@ -32,7 +32,7 @@ jobs: pip install -r Intel-PMT/tools/docker/requirements.txt || true - name: CAS Security Orchestrator (Source Only) - uses: intel-innersource/applications.security.monitoring.cas@v2 + uses: intel-innersource/applications.security.monitoring.cas@v3.0.2 with: sdl-api-key: ${{ secrets.SDL_API_KEY }} sdl-project-id: ${{ secrets.SDL_PROJECT_ID }} diff --git a/.github/workflows/macos-scan-build.yml b/.github/workflows/macos-scan-build.yml deleted file mode 100644 index 565a7d86..00000000 --- a/.github/workflows/macos-scan-build.yml +++ /dev/null @@ -1,47 +0,0 @@ -name: Mac OS X scan-build - -on: - push: - branches: [ master ] - pull_request: - branches: [ master ] - -permissions: - contents: read - -jobs: - build: - - runs-on: macos-15-intel - - steps: - - name: Harden Runner - uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2 - with: - egress-policy: audit - - - name: Set SDKROOT and verify kernel headers - shell: bash - run: | - SDKROOT="$(xcrun --sdk macosx --show-sdk-path)" - echo "SDKROOT=$SDKROOT" >> "$GITHUB_ENV" - test -f "$SDKROOT/System/Library/Frameworks/Kernel.framework/Headers/IOKit/IOLib.h" || { - echo "Kernel IOLib.h not found under SDK: $SDKROOT" >&2 - exit 1 - } - - - name: install llvm 15 - run: | - brew install llvm@15 - - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - with: - submodules: recursive - - name: cmake - run: | - rm -rf ${{ github.workspace }}/build - $(brew --prefix llvm@15)/bin/scan-build cmake -B ${{ github.workspace }}/build -DCMAKE_INSTALL_PREFIX=${{ github.workspace }} - - name: make - run: | - cd ${{ github.workspace }}/build - $(brew --prefix llvm@15)/bin/scan-build --exclude src/simdjson --status-bugs make -j diff --git a/.github/workflows/macosx_build.yml b/.github/workflows/macosx_build.yml deleted file mode 100644 index fe290ef6..00000000 --- a/.github/workflows/macosx_build.yml +++ /dev/null @@ -1,43 +0,0 @@ -name: Mac OS X build - -on: - push: - branches: [ master ] - pull_request: - branches: [ master ] - -permissions: - contents: read - -jobs: - build: - - runs-on: macos-15-intel - - steps: - - name: Harden Runner - uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2 - with: - egress-policy: audit - - - name: Set SDKROOT and verify kernel headers - shell: bash - run: | - SDKROOT="$(xcrun --sdk macosx --show-sdk-path)" - echo "SDKROOT=$SDKROOT" >> "$GITHUB_ENV" - test -f "$SDKROOT/System/Library/Frameworks/Kernel.framework/Headers/IOKit/IOLib.h" || { - echo "Kernel IOLib.h not found under SDK: $SDKROOT" >&2 - exit 1 - } - - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - with: - submodules: recursive - - name: cmake - run: | - rm -rf ${{ github.workspace }}/build - cmake -B ${{ github.workspace }}/build -DCMAKE_INSTALL_PREFIX=${{ github.workspace }} - - name: make - run: | - cd ${{ github.workspace }}/build - sudo make install diff --git a/CMakeLists.txt b/CMakeLists.txt index a353345d..739ec050 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -46,6 +46,9 @@ if(PCM_X_ARTIFACTS) endif() message(STATUS "System: ${CMAKE_SYSTEM}") +if(APPLE) + message(FATAL_ERROR "macOS/OSX is not supported.") +endif() if(UNIX AND NOT APPLE) if(${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD") set(FREE_BSD TRUE) @@ -54,7 +57,7 @@ if(UNIX AND NOT APPLE) endif() endif() -if(UNIX) # APPLE, LINUX, FREE_BSD +if(UNIX) # LINUX, FREE_BSD if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Build type (default Release)" FORCE) endif() @@ -101,10 +104,6 @@ if(UNIX) # APPLE, LINUX, FREE_BSD elseif() set (PCM_DYNAMIC "") endif() - if(APPLE) - set(PCM_NO_ASAN ON) - message(STATUS "AddressSanitizer is currently disabled on MacOS") - endif() if(PCM_NO_ASAN) message(STATUS "AddressSanitizer is disabled") set(PCM_ASAN "") @@ -226,7 +225,7 @@ if(UNIX) Intel(r) Performance Counter Monitor (Intel(r) PCM) is an application programming\n\ interface (API) and a set of tools based on the API to monitor\n\ performance and energy metrics of Intel(r) Core(tm), Xeon(r), Atom(tm)\n\ - and Xeon Phi(tm) processors. PCM works on Linux, Windows, Mac OS X,\n\ + and Xeon Phi(tm) processors. PCM works on Linux, Windows,\n\ FreeBSD and DragonFlyBSD operating systems.") set(CPACK_RPM_PACKAGE_DESCRIPTION ${CPACK_PACKAGE_DESCRIPTION}) diff --git a/Intel-PMT b/Intel-PMT index 8e57e182..d98b63ec 160000 --- a/Intel-PMT +++ b/Intel-PMT @@ -1 +1 @@ -Subproject commit 8e57e182feeaa55427199356c0b4c77bf92db89f +Subproject commit d98b63ec38a1e0b7617e5656c7ce9388a27b3ed7 diff --git a/README.md b/README.md index 0a1b8237..fa7f37cc 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Intel® Performance Counter Monitor (Intel® PCM) [PCM Tools](#pcm-tools) | [Building PCM](#building-pcm-tools) | [Downloading Pre-Compiled PCM](#downloading-pre-compiled-pcm-tools) | [FAQ](#frequently-asked-questions-faq) | [API Documentation](#pcm-api-documentation) | [Environment Variables](#pcm-environment-variables) | [Compilation Options](#custom-compilation-options) -Intel® Performance Counter Monitor (Intel® PCM) is an application programming interface (API) and a set of tools based on the API to monitor performance and energy metrics of Intel® Core™, Xeon®, Atom™ and Xeon Phi™ processors. PCM works on Linux, Windows, Mac OS X, FreeBSD, DragonFlyBSD and ChromeOS operating systems. +Intel® Performance Counter Monitor (Intel® PCM) is an application programming interface (API) and a set of tools based on the API to monitor performance and energy metrics of Intel® Core™, Xeon®, Atom™ and Xeon Phi™ processors. PCM works on Linux, Windows, FreeBSD, DragonFlyBSD and ChromeOS operating systems. *Github repository statistics:* ![Custom badge](https://img.shields.io/endpoint?url=https%3A%2F%2Fhetthbszh0.execute-api.us-east-2.amazonaws.com%2Fdefault%2Fpcm-clones) ![Custom badge](https://img.shields.io/endpoint?url=https%3A%2F%2F5urjfrshcd.execute-api.us-east-2.amazonaws.com%2Fdefault%2Fpcm-yesterday-clones) ![Custom badge](https://img.shields.io/endpoint?url=https%3A%2F%2Fcsqqh18g3l.execute-api.us-east-2.amazonaws.com%2Fdefault%2Fpcm-today-clones) @@ -21,7 +21,6 @@ Current Build Status - Linux: [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/intel/pcm/linux_make.yml?branch=master)](https://github.com/intel/pcm/actions/workflows/linux_make.yml?query=branch%3Amaster) - Windows: [![Build status](https://ci.appveyor.com/api/projects/status/github/intel/pcm?branch=master&svg=true)](https://ci.appveyor.com/project/opcm/pcm) - FreeBSD: [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/intel/pcm/freebsd_build.yml?branch=master)](https://github.com/intel/pcm/actions/workflows/freebsd_build.yml?query=branch%3Amaster) -- OS X: [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/intel/pcm/macosx_build.yml?branch=master)](https://github.com/intel/pcm/actions/workflows/macosx_build.yml?query=branch%3Amaster) - Docker container: [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/intel/pcm/docker.yml?branch=master)](doc/DOCKER_README.md) -------------------------------------------------------------------------------- @@ -55,10 +54,9 @@ PCM provides a number of command-line utilities for real-time monitoring: Graphical front ends: - **pcm Grafana dashboard** : front-end for Grafana (in [scripts/grafana](scripts/grafana) directory). Full Grafana Readme is [here](scripts/grafana/README.md) ![pcm grafana output](https://raw.githubusercontent.com/wiki/intel/pcm/pcm-dashboard.png) -- **pcm-sensor** : front-end for KDE KSysGuard - **pcm-service** : front-end for Windows perfmon -There are also utilities for reading/writing model specific registers (**pcm-msr**), PCI configuration registers (**pcm-pcicfg**), memory mapped registers (**pcm-mmio**) and TPMI registers (**pcm-tpmi**) supported on Linux, Windows, Mac OS X and FreeBSD. +There are also utilities for reading/writing model specific registers (**pcm-msr**), PCI configuration registers (**pcm-pcicfg**), memory mapped registers (**pcm-mmio**) and TPMI registers (**pcm-tpmi**) supported on Linux, Windows, FreeBSD and DragonFlyBSD. And finally a daemon that stores core, memory and QPI counters in shared memory that can be be accessed by non-root users. @@ -98,7 +96,7 @@ Debug is default on Windows. Specify config to build Release: ``` cmake --build . --config Release ``` -On Windows and MacOs additional drivers and steps are required. Please find instructions here: [WINDOWS_HOWTO.md](doc/WINDOWS_HOWTO.md) and [MAC_HOWTO.txt](doc/MAC_HOWTO.txt). +On Windows additional drivers and steps are required. Please find instructions here: [WINDOWS_HOWTO.md](doc/WINDOWS_HOWTO.md). FreeBSD/DragonFlyBSD-specific details can be found in [FREEBSD_HOWTO.txt](doc/FREEBSD_HOWTO.txt) diff --git a/doc/MAC_HOWTO.txt b/doc/MAC_HOWTO.txt deleted file mode 100644 index 4cdd2dfb..00000000 --- a/doc/MAC_HOWTO.txt +++ /dev/null @@ -1,64 +0,0 @@ -Building and Installing - -Note: xcode is required to build the driver and dynamic library. - -Requirements -____________ -Building and installing requires make, cmake, gcc, and xcode. -It has been tested on the following system configurations: - OS X 12.0.1, Xcode 13.1, Apple LLVM compiler 13.0.0 - -Build PCM and MacMSRDriver ------------------ - mkdir build && cd build - cmake .. && cmake --build . - -PCM utilities will be located in build/bin folder, libraries libpcm.dylib and libPcmMsr.dylib - in build/lib. - -Automatic Install ------------------ - cd build - sudo make install - -Install command loads the driver, installs the library into /usr/lib and installs the library headers into /usr/include. -Also PCM utilities are installing to /usr/local/sbin. - -Manual Install --------------- -Build steps are the same. -To install do the following: - 1) load the driver by running src/MacMSRDriver/kextload.sh - 2) copy build/lib/libPcmMsr.dylib to a location on your path (auto-install uses /usr/lib) - 3) copy src/MacMSRDriver/MSRKernel.h to a location on your path (auto-install uses /usr/include) - 4) copy src/MacMSRDriver/MSRAccessorPublic.h as MSRAccessor.h to a location on your path (auto-install uses /usr/include) - -kext Signatures ---------------- -As of OS X El Capitan, kexts must be signed. So after building the kext, kextload.sh may fail with: - - /System/Library/Extensions/PcmMsrDriver.kext failed to load - (libkern/kext) not loadable (reason unspecified); check the system/kernel logs for errors or try kextutil(8). - -In this event, you will need to either disable System Integrity Protection or sign the kext. -You can disable SIP by rebooting into Recovery (reboot, command-option-R), opening a shell, csrutil disable and reboot again. -Signing a kext is more involved. You can't self-sign and will first need to obtain a Developer ID from Apple: - - https://developer.apple.com/contact/kext/ - -With this ID, you can then sign your kext with codesign. - -PCM Execution ----------------------- -Now you can run ./pcm utility. -See description of other built utilities in LINUX_HOWTO.txt - -Logging/Debugging ----------------------- - -Sometimes you will get errors while running utilities that may come from the kernel, and you can use something like this DTrace script to correlate it with user-land behavior: - -$ sudo dtrace -n 'fbt:mach_kernel:_ZN*IOUser*:return /execname == "pcm"/ { @hgram[probefunc, arg1, ustack(20)] = count(); }' -c ./pcm - -Various commands that can help diagnose errors: - -$ kmutil log stream -$ kmutil inspect -b com.intel.driver.PcmMsr diff --git a/doc/NUMA_NODE_API.md b/doc/NUMA_NODE_API.md index 211ac96d..439df560 100644 --- a/doc/NUMA_NODE_API.md +++ b/doc/NUMA_NODE_API.md @@ -78,11 +78,6 @@ if (numa_node >= 0) { - -1 if NUMA is disabled, not supported, or device affinity information unavailable - **Note**: FreeBSD doesn't have a standardized sysctl path for PCI device NUMA affinity across all versions -### macOS - -- **Method**: Returns -1 (macOS typically doesn't expose NUMA for PCI devices) -- **Return**: -1 (not applicable) - ## Use Cases 1. **Performance Optimization**: Place processing threads on the same NUMA node as the device diff --git a/doc/PCM-EXPORTER.md b/doc/PCM-EXPORTER.md index f815f713..1e9f1ba3 100644 --- a/doc/PCM-EXPORTER.md +++ b/doc/PCM-EXPORTER.md @@ -18,7 +18,7 @@ $ ./pcm-sensor-server --help Usage: ./pcm-sensor-server [OPTION] Valid Options: - -d : Run in the background (Linux/macOS only) + -d : Run in the background (non-Windows) -p portnumber : Run on port (default port is 9738) -l|--listen address : Listen on IP address
(default: all interfaces) -r|--reset : Reset programming of the performance counters. diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index e9726a0c..ce61b16e 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -23,8 +23,6 @@ if(UNIX) target_link_libraries(c_example_shlib PUBLIC PCM_SHARED PRIVATE Threads::Threads) # numa_to_socket_example - if(NOT APPLE) - add_executable(numa_to_socket_example numa_to_socket_example.cpp) - target_link_libraries(numa_to_socket_example PUBLIC PCM_SHARED PRIVATE Threads::Threads) - endif() + add_executable(numa_to_socket_example numa_to_socket_example.cpp) + target_link_libraries(numa_to_socket_example PUBLIC PCM_SHARED PRIVATE Threads::Threads) endif(UNIX) diff --git a/examples/numa_node_example.cpp b/examples/numa_node_example.cpp index 2d4f07e3..8af4fef1 100644 --- a/examples/numa_node_example.cpp +++ b/examples/numa_node_example.cpp @@ -26,7 +26,6 @@ int main() // On Linux: uses /proc/bus/pci/ or PciHandleMM for memory-mapped access // On Windows: uses Windows driver // On FreeBSD: uses /dev/pci - // On macOS: uses PCIDriver PciHandleType handle(segment, bus, device, function); std::cout << "Successfully opened PCI device " diff --git a/examples/numa_to_socket_example.cpp b/examples/numa_to_socket_example.cpp index 6f207015..e985b64a 100644 --- a/examples/numa_to_socket_example.cpp +++ b/examples/numa_to_socket_example.cpp @@ -58,7 +58,6 @@ int main() std::cout << "\nNote: This is normal on:\n"; std::cout << " - Single-socket systems\n"; std::cout << " - Systems without NUMA support\n"; - std::cout << " - macOS (not implemented)\n"; std::cout << " - FreeBSD without NUMA enabled (vm.ndomains <= 1)\n"; } diff --git a/pcm.spec b/pcm.spec index b234980d..edb5c0ee 100644 --- a/pcm.spec +++ b/pcm.spec @@ -25,7 +25,7 @@ BuildRequires: libasan %description -Intel(r) Performance Counter Monitor (Intel(r) PCM) is an application programming interface (API) and a set of tools based on the API to monitor performance and energy metrics of Intel(r) Core(tm), Xeon(r), Atom(tm) and Xeon Phi(tm) processors. PCM works on Linux, Windows, Mac OS X, FreeBSD and DragonFlyBSD operating systems. +Intel(r) Performance Counter Monitor (Intel(r) PCM) is an application programming interface (API) and a set of tools based on the API to monitor performance and energy metrics of Intel(r) Core(tm), Xeon(r), Atom(tm) and Xeon Phi(tm) processors. PCM works on Linux, Windows, FreeBSD and DragonFlyBSD operating systems. %prep %setup -n pcm-master @@ -81,7 +81,6 @@ rm -rf $RPM_BUILD_ROOT %{_sbindir}/pcm-accel %{_sbindir}/pcm-pcie %{_sbindir}/pcm-power -%{_sbindir}/pcm-sensor %{_sbindir}/pcm-sensor-server %{_sbindir}/pcm-tsx %{_sbindir}/pcm-raw diff --git a/scripts/cppcheck.sh b/scripts/cppcheck.sh index 1db0e23a..1ea1f213 100755 --- a/scripts/cppcheck.sh +++ b/scripts/cppcheck.sh @@ -1,5 +1,5 @@ -cppcheck $1 --force --enable=warning --inline-suppr -iPCMService.cpp -isimdjson -ipugixml -iPcmMsrDriver_info.c -igoogletest -DTEXT -j $2 2> cppcheck.out +cppcheck $1 --force --enable=warning --inline-suppr -iPCMService.cpp -isimdjson -ipugixml -igoogletest -DTEXT -j $2 2> cppcheck.out if [ -s cppcheck.out ] then diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 95447f1f..946cf40d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -2,15 +2,13 @@ # Copyright (c) 2022-2025, Intel Corporation # All pcm-* executables -set(PROJECT_NAMES pcm pcm-numa pcm-latency pcm-power pcm-msr pcm-memory pcm-tsx pcm-pcie pcm-core pcm-iio pcm-pcicfg pcm-mmio pcm-tpmi pcm-raw pcm-accel pcm-sensor-server) +set(PROJECT_NAMES pcm pcm-numa pcm-latency pcm-power pcm-msr pcm-memory pcm-tsx pcm-pcie pcm-core pcm-iio pcm-pcicfg pcm-mmio pcm-tpmi pcm-raw pcm-io pcm-accel pcm-sensor-server) set(MINIMUM_OPENSSL_VERSION 1.1.1) file(GLOB COMMON_SOURCES pcm-accel-common.cpp msr.cpp cpucounters.cpp pci.cpp mmio.cpp tpmi.cpp pmt.cpp bw.cpp utils.cpp topology.cpp debug.cpp threadpool.cpp uncore_pmu_discovery.cpp pcm-iio-pmu.cpp pcm-iio-topology.cpp lspci.cpp dashboard.cpp ${PCM_PUGIXML_CPP}) -if (NOT APPLE) - file(GLOB UNIX_SOURCES resctrl.cpp) -endif() +file(GLOB UNIX_SOURCES resctrl.cpp) if (LINUX) if(EXISTS "/etc/os-release") # AND IS_READABLE "/etc/os-release" (3.29 cmake required :-( ) @@ -37,11 +35,8 @@ if(NOT PCM_NO_ASAN) endif() endif() -if(UNIX) # LINUX, FREE_BSD, APPLE - if (NOT APPLE) - set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS} -s") # --strip-unneeded for packaging - endif() - list(APPEND PROJECT_NAMES pcm-sensor) +if(UNIX) # LINUX, FREE_BSD + set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS} -s") # --strip-unneeded for packaging # libpcm.a add_library(PCM_STATIC STATIC ${COMMON_SOURCES} ${UNIX_SOURCES}) @@ -84,11 +79,7 @@ if(UNIX) # LINUX, FREE_BSD, APPLE endif() endif() - if(APPLE) - add_subdirectory(MacMSRDriver) - include_directories("${CMAKE_SOURCE_DIR}/src/MacMSRDriver") # target_include_directories doesn't work - target_link_libraries(PCM_SHARED PRIVATE PCM_STATIC_SILENT PcmMsr Threads::Threads) - elseif(LINUX) + if(LINUX) target_link_libraries(PCM_SHARED PRIVATE PCM_STATIC_SILENT Threads::Threads "${PCM_DYNAMIC_ASAN}") else() target_link_libraries(PCM_SHARED PRIVATE PCM_STATIC_SILENT Threads::Threads) @@ -205,8 +196,21 @@ if(PCM_BUILD_EXECUTABLES) # specific file for pcm-raw project if(${PROJECT_NAME} STREQUAL pcm-raw) set(LIBS ${LIBS} PCM_SIMDJSON) + target_sources(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/event-resolver.cpp) endif(${PROJECT_NAME} STREQUAL pcm-raw) + # specific files for pcm-io project + if(${PROJECT_NAME} STREQUAL pcm-io) + set(LIBS ${LIBS} PCM_SIMDJSON) + target_sources(${PROJECT_NAME} PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/event-resolver.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/pcm-io-metrics.cpp) + add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + ${CMAKE_CURRENT_SOURCE_DIR}/metrics + $/metrics) + endif(${PROJECT_NAME} STREQUAL pcm-io) + if(${PROJECT_NAME} STREQUAL pcm-sensor-server) if(NO_SSL) message(STATUS "SSL is disabled") @@ -241,11 +245,6 @@ if(PCM_BUILD_EXECUTABLES) install(TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_SBINDIR}) endif(LINUX OR FREE_BSD) - if(APPLE) - set(LIBS ${LIBS} Threads::Threads PcmMsr) - install(TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_SBINDIR}) - endif(APPLE) - if(MSVC) target_compile_definitions(${PROJECT_NAME} PRIVATE _UNICODE UNICODE _CONSOLE) # for all, except pcm-lib and pcm-service endif(MSVC) @@ -258,7 +257,7 @@ endif(PCM_BUILD_EXECUTABLES) # Install ####################### -if(UNIX) # APPLE, LINUX, FREE_BSD +if(UNIX) # LINUX, FREE_BSD if(LINUX) # Daemon & client file(GLOB DAEMON_SOURCES "daemon/*.cpp") diff --git a/src/MacMSRDriver/CMakeLists.txt b/src/MacMSRDriver/CMakeLists.txt deleted file mode 100644 index 977d438c..00000000 --- a/src/MacMSRDriver/CMakeLists.txt +++ /dev/null @@ -1,19 +0,0 @@ -# SPDX-License-Identifier: BSD-3-Clause -# Copyright (c) 2022, Intel Corporation - -set(CMAKE_MACOSX_RPATH 1) - -set(CMAKE_CXX_FLAGS "-Wall") -set(CMAKE_CXX_FLAGS_RELEASE "-O3") -set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g") - -file(GLOB LIB_FILES PCIDriverInterface.cpp MSRAccessor.cpp) - -find_library(IOKIT_LIBRARY IOKit) -add_library(PcmMsr SHARED ${LIB_FILES}) -target_link_libraries(PcmMsr PRIVATE ${IOKIT_LIBRARY}) - -add_subdirectory(PcmMsr) - -# Installation -install(TARGETS PcmMsr DESTINATION "lib") diff --git a/src/MacMSRDriver/MSRAccessor.cpp b/src/MacMSRDriver/MSRAccessor.cpp deleted file mode 100644 index a44c7d6b..00000000 --- a/src/MacMSRDriver/MSRAccessor.cpp +++ /dev/null @@ -1,166 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -// Copyright (c) 2012, Intel Corporation -// written by Austen Ott -// -#include "MSRAccessor.h" -#include -#include -#include - -using namespace std; - -MSRAccessor::MSRAccessor() -{ - service = IOServiceGetMatchingService(kIOMainPortDefault, - IOServiceMatching(kPcmMsrDriverClassName)); - openConnection(); -} - -int32_t MSRAccessor::buildTopology(uint32_t num_cores, void* pTopos) -{ - size_t topology_struct_size = sizeof(TopologyEntry)*num_cores; - - kern_return_t ret = IOConnectCallStructMethod(connect, kBuildTopology, - NULL, 0, - pTopos, &topology_struct_size); - return (ret == KERN_SUCCESS) ? 0 : -1; -} - -int32_t MSRAccessor::read(uint32_t core_num, uint64_t msr_num, uint64_t * value) -{ - pcm_msr_data_t idatas, odatas; - - size_t struct_size = sizeof(pcm_msr_data_t); - idatas.msr_num = (uint32_t)msr_num; - idatas.cpu_num = core_num; - - kern_return_t ret = IOConnectCallStructMethod(connect, kReadMSR, - &idatas, struct_size, - &odatas, &struct_size); - - if(ret == KERN_SUCCESS) - { - *value = odatas.value; - return sizeof(uint64_t); - } else { - return -1; - } -} - -int32_t MSRAccessor::write(uint32_t core_num, uint64_t msr_num, uint64_t value){ - pcm_msr_data_t idatas; - - idatas.value = value; - idatas.msr_num = (uint32_t)msr_num; - idatas.cpu_num = core_num; - - kern_return_t ret = IOConnectCallStructMethod(connect, kWriteMSR, - &idatas, sizeof(pcm_msr_data_t), - NULL, NULL); - - if(ret == KERN_SUCCESS) - { - return sizeof(uint64_t); - } else { - return -1; - } -} - -uint32_t MSRAccessor::getNumInstances() -{ - kern_return_t kernResult; - uint32_t output_count = 1; - uint64_t knum_insts = 0; - - kernResult = IOConnectCallScalarMethod(connect, - kGetNumInstances, - NULL, 0, - &knum_insts, &output_count); - - if (kernResult != KERN_SUCCESS) - { - cerr << "IOConnectCallScalarMethod returned 0x" << hex << setw(8) << kernResult << dec << endl; - } - // TODO add error handling; also, number-of-instance related - // functions may go away as they do not appear to be used. - return knum_insts; -} - -uint32_t MSRAccessor::incrementNumInstances() -{ - kern_return_t kernResult; - uint32_t output_count = 1; - uint64_t knum_insts = 0; - - kernResult = IOConnectCallScalarMethod(connect, - kIncrementNumInstances, - NULL, 0, - &knum_insts, &output_count); - - if (kernResult != KERN_SUCCESS) - { - cerr << "IOConnectCallScalarMethod returned 0x" << hex << setw(8) << kernResult << dec << endl; - } - // TODO add error handling; also, these functions may go away as - // they do not appear to be used. - return knum_insts; -} - -uint32_t MSRAccessor::decrementNumInstances() -{ - kern_return_t kernResult; - uint32_t output_count = 1; - uint64_t knum_insts = 0; - - kernResult = IOConnectCallScalarMethod(connect, kDecrementNumInstances, - NULL, 0, - &knum_insts, &output_count); - - if (kernResult != KERN_SUCCESS) - { - cerr << "IOConnectCallScalarMethod returned 0x" << hex << setw(8) << kernResult << dec << endl; - } - // TODO add error handling; also, these functions may go away as - // they do not appear to be used. - return knum_insts; -} - -MSRAccessor::~MSRAccessor() -{ - closeConnection(); -} - -kern_return_t MSRAccessor::openConnection() -{ - kern_return_t kernResult = IOServiceOpen(service, mach_task_self(), 0, &connect); - - if (kernResult != KERN_SUCCESS) - { - cerr << "IOServiceOpen returned 0x" << hex << setw(8) << kernResult << dec << endl; - } else { - kernResult = IOConnectCallScalarMethod(connect, kOpenDriver, NULL, 0, NULL, NULL); - - if (kernResult != KERN_SUCCESS) - { - cerr << "kOpenDriver returned 0x" << hex << setw(8) << kernResult << dec << endl; - } - } - - return kernResult; -} - -void MSRAccessor::closeConnection() -{ - kern_return_t kernResult = IOConnectCallScalarMethod(connect, kCloseDriver, - NULL, 0, NULL, NULL); - if (kernResult != KERN_SUCCESS) - { - cerr << "kCloseDriver returned 0x" << hex << setw(8) << kernResult << dec << endl; - } - - kernResult = IOServiceClose(connect); - if (kernResult != KERN_SUCCESS) - { - cerr << "IOServiceClose returned 0x" << hex << setw(8) << kernResult << dec << endl; - } -} diff --git a/src/MacMSRDriver/MSRAccessor.h b/src/MacMSRDriver/MSRAccessor.h deleted file mode 100644 index 469420c6..00000000 --- a/src/MacMSRDriver/MSRAccessor.h +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -// Copyright (c) 2012, Intel Corporation -// written by Austen Ott -// - -#include -#include "PcmMsr/UserKernelShared.h" - -class MSRAccessor -{ -private: - io_service_t service; - io_connect_t connect; - kern_return_t openConnection(); - void closeConnection(); -public: - MSRAccessor(); - int32_t read(uint32_t cpu_num,uint64_t msr_num, uint64_t * value); - int32_t write(uint32_t cpu_num, uint64_t msr_num, uint64_t value); - int32_t buildTopology(uint32_t num_cores, void*); - - uint32_t getNumInstances(); - uint32_t incrementNumInstances(); - uint32_t decrementNumInstances(); - ~MSRAccessor(); -}; diff --git a/src/MacMSRDriver/MSRKernel.h b/src/MacMSRDriver/MSRKernel.h deleted file mode 100644 index 808d4a0b..00000000 --- a/src/MacMSRDriver/MSRKernel.h +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -// Copyright (c) 2012, Intel Corporation -// written by Austen Ott -// -#define PcmMsrDriverClassName com_intel_driver_PcmMsr -#define kPcmMsrDriverClassName "com_intel_driver_PcmMsr" -#ifndef MSR_KERNEL_SHARED -#define MSR_KERNEL_SHARED -#include -typedef struct { - uint64_t value; - uint32_t cpu_num; - uint32_t msr_num; -} pcm_msr_data_t; - -#endif diff --git a/src/MacMSRDriver/PCIDriverInterface.cpp b/src/MacMSRDriver/PCIDriverInterface.cpp deleted file mode 100644 index 3575c39c..00000000 --- a/src/MacMSRDriver/PCIDriverInterface.cpp +++ /dev/null @@ -1,228 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -// Copyright (c) 2013, Intel Corporation -// written by Patrick Konsor -// - -#include -#include -#include "PCIDriverInterface.h" -#include -#include "PcmMsr/UserKernelShared.h" - -io_connect_t PCIDriver_connect = 0; -std::map PCIDriver_mmap; - -// setupDriver -#ifdef __cplusplus -extern "C" -#endif -int PCIDriver_setupDriver() -{ - kern_return_t kern_result; - io_iterator_t iterator; - bool driverFound = false; - io_service_t local_driver_service; - - // get services - kern_result = IOServiceGetMatchingServices(kIOMainPortDefault, - IOServiceMatching(kPcmMsrDriverClassName), - &iterator); - if (kern_result != KERN_SUCCESS) { - fprintf(stderr, "[error] IOServiceGetMatchingServices returned 0x%08x\n", kern_result); - return kern_result; - } - - // find service - while ((local_driver_service = IOIteratorNext(iterator)) != IO_OBJECT_NULL) { - driverFound = true; - break; - } - - if (driverFound == false) { - fprintf(stderr, "[error] No matching drivers found \"%s\".\n", kPcmMsrDriverClassName); - return KERN_FAILURE; - } - IOObjectRelease(iterator); - - // connect to service - kern_result = IOServiceOpen(local_driver_service, mach_task_self(), 0, &PCIDriver_connect); - if (kern_result != KERN_SUCCESS) { - fprintf(stderr, "[error] IOServiceOpen returned 0x%08x\n", kern_result); - return kern_result; - } - - return KERN_SUCCESS; -} - - -// read32 -#ifdef __cplusplus -extern "C" -#endif -uint32_t PCIDriver_read32(uint32_t addr, uint32_t* val) -{ - if (!PCIDriver_connect) { - if (PCIDriver_setupDriver() != KERN_SUCCESS) { - return KERN_FAILURE; - } - } - - uint64_t input[] = { (uint64_t)addr }; - uint64_t val_ = 0; - uint32_t outputCnt = 1; - kern_return_t result = IOConnectCallScalarMethod(PCIDriver_connect, kRead, input, 1, &val_, &outputCnt); - *val = (uint32_t)val_; - return result; -} - - -// read64 -#ifdef __cplusplus -extern "C" -#endif -uint32_t PCIDriver_read64(uint32_t addr, uint64_t* val) -{ - if (!PCIDriver_connect) { - if (PCIDriver_setupDriver() != KERN_SUCCESS) { - return KERN_FAILURE; - } - } - - kern_return_t result; - uint64_t input[] = { (uint64_t)addr }; - uint64_t lo = 0; - uint64_t hi = 0; - uint32_t outputCnt = 1; - result = IOConnectCallScalarMethod(PCIDriver_connect, kRead, input, 1, &lo, &outputCnt); - input[0] = (uint64_t)addr + 4; - result |= IOConnectCallScalarMethod(PCIDriver_connect, kRead, input, 1, &hi, &outputCnt); - *val = (hi << 32) | lo; - return result; -} - - -// write32 -#ifdef __cplusplus -extern "C" -#endif -uint32_t PCIDriver_write32(uint32_t addr, uint32_t val) -{ - if (!PCIDriver_connect) { - if (PCIDriver_setupDriver() != KERN_SUCCESS) { - return KERN_FAILURE; - } - } - - uint64_t input[] = { (uint64_t)addr, (uint64_t)val }; - return IOConnectCallScalarMethod(PCIDriver_connect, kWrite, input, 2, NULL, 0); -} - - -// write64 -#ifdef __cplusplus -extern "C" -#endif -uint32_t PCIDriver_write64(uint32_t addr, uint64_t val) -{ - if (!PCIDriver_connect) { - if (PCIDriver_setupDriver() != KERN_SUCCESS) { - return KERN_FAILURE; - } - } - - kern_return_t result; - uint64_t input[] = { (uint64_t)addr, val & 0xffffffff }; - result = IOConnectCallScalarMethod(PCIDriver_connect, kWrite, input, 2, NULL, 0); - input[0] = (uint64_t)addr + 4; - input[1] = val >> 32; - result |= IOConnectCallScalarMethod(PCIDriver_connect, kWrite, input, 2, NULL, 0); - return result; -} - -// mapMemory -#ifdef __cplusplus -extern "C" -#endif -uint32_t PCIDriver_mapMemory(uint32_t address, uint8_t** virtual_address) -{ - if (!PCIDriver_connect) { - if (PCIDriver_setupDriver() != KERN_SUCCESS) { - return KERN_FAILURE; - } - } - - uint64_t input[] = { (uint64_t)address }; - uint64_t output[2]; - uint32_t outputCnt = 2; - kern_return_t result = IOConnectCallScalarMethod(PCIDriver_connect, kMapMemory, input, 1, output, &outputCnt); - PCIDriver_mmap[(uint8_t*)output[1]] = (void*)output[0]; - *virtual_address = (uint8_t*)output[1]; - return result; -} - - -// unmapMemory -#ifdef __cplusplus -extern "C" -#endif -uint32_t PCIDriver_unmapMemory(uint8_t* virtual_address) -{ - if (!PCIDriver_connect) { - if (PCIDriver_setupDriver() != KERN_SUCCESS) { - return KERN_FAILURE; - } - } - - void* memory_map = PCIDriver_mmap[virtual_address]; - if (memory_map != NULL) { - uint64_t input[] = { (uint64_t)memory_map }; - kern_return_t result = IOConnectCallScalarMethod(PCIDriver_connect, kUnmapMemory, input, 1, NULL, 0); - PCIDriver_mmap.erase(virtual_address); // remove from map - return result; - } else { - return KERN_INVALID_ADDRESS; - } -} - -// readMemory32 -#ifdef __cplusplus -extern "C" -#endif -uint32_t PCIDriver_readMemory32(uint8_t* address, uint32_t* val) -{ - if (!PCIDriver_connect) { - if (PCIDriver_setupDriver() != KERN_SUCCESS) { - return KERN_FAILURE; - } - } - uint64_t input[] = { (uint64_t)address }; - uint64_t val_ = 0; - uint32_t outputCnt = 1; - kern_return_t result = IOConnectCallScalarMethod(PCIDriver_connect, kReadMemory, input, 1, &val_, &outputCnt); - *val = (uint32_t)val_; - return result; -} - - -// readMemory64 -#ifdef __cplusplus -extern "C" -#endif -uint32_t PCIDriver_readMemory64(uint8_t* address, uint64_t* val) -{ - if (!PCIDriver_connect) { - if (PCIDriver_setupDriver() != KERN_SUCCESS) { - return KERN_FAILURE; - } - } - kern_return_t result; - uint64_t input[] = { (uint64_t)address }; - uint64_t lo = 0; - uint64_t hi = 0; - uint32_t outputCnt = 1; - result = IOConnectCallScalarMethod(PCIDriver_connect, kReadMemory, input, 1, &lo, &outputCnt); - input[0] = (uint64_t)address + 4; - result |= IOConnectCallScalarMethod(PCIDriver_connect, kReadMemory, input, 1, &hi, &outputCnt); - *val = (hi << 32) | lo; - return result; -} diff --git a/src/MacMSRDriver/PCIDriverInterface.h b/src/MacMSRDriver/PCIDriverInterface.h deleted file mode 100644 index 593fcc37..00000000 --- a/src/MacMSRDriver/PCIDriverInterface.h +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -// Copyright (c) 2013, Intel Corporation -// written by Patrick Konsor -// - -#ifndef pci_driver_driverinterface_h -#define pci_driver_driverinterface_h - -#ifdef __cplusplus -extern "C" { -#endif - -#define PCI_ENABLE 0x80000000 -#define FORM_PCI_ADDR(bus,dev,fun,off) (((PCI_ENABLE)) | \ - ((bus & 0xFF) << 16) | \ - ((dev & 0x1F) << 11) | \ - ((fun & 0x07) << 8) | \ - ((off & 0xFF) << 0)) - -uint32_t PCIDriver_read32(uint32_t addr, uint32_t* val); -uint32_t PCIDriver_read64(uint32_t addr, uint64_t* val); -uint32_t PCIDriver_write32(uint32_t addr, uint32_t val); -uint32_t PCIDriver_write64(uint32_t addr, uint64_t val); -uint32_t PCIDriver_mapMemory(uint32_t address, uint8_t** virtual_address); -uint32_t PCIDriver_unmapMemory(uint8_t* virtual_address); -uint32_t PCIDriver_readMemory32(uint8_t* address, uint32_t* val); -uint32_t PCIDriver_readMemory64(uint8_t* address, uint64_t* val); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/src/MacMSRDriver/PcmMsr.xcodeproj/project.pbxproj b/src/MacMSRDriver/PcmMsr.xcodeproj/project.pbxproj deleted file mode 100644 index 91b08058..00000000 --- a/src/MacMSRDriver/PcmMsr.xcodeproj/project.pbxproj +++ /dev/null @@ -1,458 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 46; - objects = { - -/* Begin PBXBuildFile section */ - 81ADBF0A156EBD73006D9B47 /* PcmMsrClient.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81ADBF09156EBD73006D9B47 /* PcmMsrClient.cpp */; }; - 81ADBF1A156EEDB9006D9B47 /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 81ADBF0B156EDBA1006D9B47 /* IOKit.framework */; }; - 81DEAF6315703531005E8EC6 /* MSRAccessor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81ADBF1C156EFF69006D9B47 /* MSRAccessor.cpp */; }; - 81DEAF6615703946005E8EC6 /* DriverInterface.c in Sources */ = {isa = PBXBuildFile; fileRef = 81DEAF6515703946005E8EC6 /* DriverInterface.c */; }; - 81DEAF67157039F6005E8EC6 /* DriverInterface.h in Headers */ = {isa = PBXBuildFile; fileRef = 81ADBF17156EECDA006D9B47 /* DriverInterface.h */; }; - 81DEAF68157039FB005E8EC6 /* MSRAccessor.h in Headers */ = {isa = PBXBuildFile; fileRef = 81ADBF1B156EFF56006D9B47 /* MSRAccessor.h */; }; - 81F91BC6156D9BF8007DD788 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 81F91BC4156D9BF8007DD788 /* InfoPlist.strings */; }; - 81F91BC9156D9BF8007DD788 /* PcmMsr.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 81F91BC8156D9BF8007DD788 /* PcmMsr.cpp */; }; - 895805FC1760E6E5006ED117 /* PCIDriverInterface.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 895805FA1760E6E5006ED117 /* PCIDriverInterface.cpp */; }; - 895805FD1760E6E5006ED117 /* PCIDriverInterface.h in Headers */ = {isa = PBXBuildFile; fileRef = 895805FB1760E6E5006ED117 /* PCIDriverInterface.h */; }; -/* End PBXBuildFile section */ - -/* Begin PBXBuildRule section */ - 816FC6A5158296D200D9DEB4 /* PBXBuildRule */ = { - isa = PBXBuildRule; - compilerSpec = com.apple.compilers.proxy.script; - fileType = pattern.proxy; - isEditable = 1; - outputFiles = ( - ); - }; -/* End PBXBuildRule section */ - -/* Begin PBXCopyFilesBuildPhase section */ - 816FC6A31582965F00D9DEB4 /* CopyFiles */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = /usr/local/lib; - dstSubfolderSpec = 0; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - -/* Begin PBXFileReference section */ - 81ADBF08156EBD65006D9B47 /* PcmMsrClient.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PcmMsrClient.h; sourceTree = ""; }; - 81ADBF09156EBD73006D9B47 /* PcmMsrClient.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PcmMsrClient.cpp; sourceTree = ""; }; - 81ADBF0B156EDBA1006D9B47 /* IOKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOKit.framework; path = System/Library/Frameworks/IOKit.framework; sourceTree = SDKROOT; }; - 81ADBF0D156EDD11006D9B47 /* UserKernelShared.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = UserKernelShared.h; path = PcmMsr/UserKernelShared.h; sourceTree = ""; }; - 81ADBF12156EEB93006D9B47 /* libPcmMsr.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = libPcmMsr.dylib; sourceTree = BUILT_PRODUCTS_DIR; }; - 81ADBF17156EECDA006D9B47 /* DriverInterface.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DriverInterface.h; sourceTree = ""; }; - 81ADBF1B156EFF56006D9B47 /* MSRAccessor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSRAccessor.h; sourceTree = ""; }; - 81ADBF1C156EFF69006D9B47 /* MSRAccessor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MSRAccessor.cpp; sourceTree = ""; }; - 81DEAF6515703946005E8EC6 /* DriverInterface.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = DriverInterface.c; sourceTree = ""; }; - 81F91BBC156D9BF8007DD788 /* PcmMsrDriver.kext */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PcmMsrDriver.kext; sourceTree = BUILT_PRODUCTS_DIR; }; - 81F91BC3156D9BF8007DD788 /* PcmMsr-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "PcmMsr-Info.plist"; sourceTree = ""; }; - 81F91BC5156D9BF8007DD788 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; - 81F91BC7156D9BF8007DD788 /* PcmMsr.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PcmMsr.h; sourceTree = ""; }; - 81F91BC8156D9BF8007DD788 /* PcmMsr.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = PcmMsr.cpp; sourceTree = ""; }; - 81F91BCA156D9BF8007DD788 /* PcmMsr-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "PcmMsr-Prefix.pch"; sourceTree = ""; }; - 895805FA1760E6E5006ED117 /* PCIDriverInterface.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PCIDriverInterface.cpp; sourceTree = ""; }; - 895805FB1760E6E5006ED117 /* PCIDriverInterface.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PCIDriverInterface.h; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 81ADBF0F156EEB93006D9B47 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 81ADBF1A156EEDB9006D9B47 /* IOKit.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 81F91BB7156D9BF8007DD788 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 81ADBF16156EECC6006D9B47 /* PcmMsrLibrary */ = { - isa = PBXGroup; - children = ( - 81ADBF17156EECDA006D9B47 /* DriverInterface.h */, - 81DEAF6515703946005E8EC6 /* DriverInterface.c */, - 81ADBF1B156EFF56006D9B47 /* MSRAccessor.h */, - 81ADBF1C156EFF69006D9B47 /* MSRAccessor.cpp */, - 895805FA1760E6E5006ED117 /* PCIDriverInterface.cpp */, - 895805FB1760E6E5006ED117 /* PCIDriverInterface.h */, - 81F91BC1156D9BF8007DD788 /* PcmMsr */, - ); - name = PcmMsrLibrary; - sourceTree = ""; - }; - 81F91BAF156D9BF8007DD788 = { - isa = PBXGroup; - children = ( - 81ADBF0D156EDD11006D9B47 /* UserKernelShared.h */, - 81ADBF16156EECC6006D9B47 /* PcmMsrLibrary */, - 81F91BBE156D9BF8007DD788 /* Frameworks */, - 81F91BBD156D9BF8007DD788 /* Products */, - ); - sourceTree = ""; - }; - 81F91BBD156D9BF8007DD788 /* Products */ = { - isa = PBXGroup; - children = ( - 81F91BBC156D9BF8007DD788 /* PcmMsrDriver.kext */, - 81ADBF12156EEB93006D9B47 /* libPcmMsr.dylib */, - ); - name = Products; - sourceTree = ""; - }; - 81F91BBE156D9BF8007DD788 /* Frameworks */ = { - isa = PBXGroup; - children = ( - 81ADBF0B156EDBA1006D9B47 /* IOKit.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; - 81F91BC1156D9BF8007DD788 /* PcmMsr */ = { - isa = PBXGroup; - children = ( - 81F91BC7156D9BF8007DD788 /* PcmMsr.h */, - 81F91BC8156D9BF8007DD788 /* PcmMsr.cpp */, - 81F91BC2156D9BF8007DD788 /* Supporting Files */, - 81ADBF08156EBD65006D9B47 /* PcmMsrClient.h */, - 81ADBF09156EBD73006D9B47 /* PcmMsrClient.cpp */, - ); - path = PcmMsr; - sourceTree = ""; - }; - 81F91BC2156D9BF8007DD788 /* Supporting Files */ = { - isa = PBXGroup; - children = ( - 81F91BC3156D9BF8007DD788 /* PcmMsr-Info.plist */, - 81F91BC4156D9BF8007DD788 /* InfoPlist.strings */, - 81F91BCA156D9BF8007DD788 /* PcmMsr-Prefix.pch */, - ); - name = "Supporting Files"; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXHeadersBuildPhase section */ - 81ADBF10156EEB93006D9B47 /* Headers */ = { - isa = PBXHeadersBuildPhase; - buildActionMask = 2147483647; - files = ( - 81DEAF67157039F6005E8EC6 /* DriverInterface.h in Headers */, - 81DEAF68157039FB005E8EC6 /* MSRAccessor.h in Headers */, - 895805FD1760E6E5006ED117 /* PCIDriverInterface.h in Headers */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 81F91BB8156D9BF8007DD788 /* Headers */ = { - isa = PBXHeadersBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXHeadersBuildPhase section */ - -/* Begin PBXNativeTarget section */ - 81ADBF11156EEB93006D9B47 /* PcmMsrLibrary */ = { - isa = PBXNativeTarget; - buildConfigurationList = 81ADBF13156EEB93006D9B47 /* Build configuration list for PBXNativeTarget "PcmMsrLibrary" */; - buildPhases = ( - 81ADBF0E156EEB93006D9B47 /* Sources */, - 81ADBF0F156EEB93006D9B47 /* Frameworks */, - 81ADBF10156EEB93006D9B47 /* Headers */, - 816FC6A31582965F00D9DEB4 /* CopyFiles */, - ); - buildRules = ( - 816FC6A5158296D200D9DEB4 /* PBXBuildRule */, - ); - dependencies = ( - ); - name = PcmMsrLibrary; - productName = PcmMsrLibrary; - productReference = 81ADBF12156EEB93006D9B47 /* libPcmMsr.dylib */; - productType = "com.apple.product-type.library.dynamic"; - }; - 81F91BBB156D9BF8007DD788 /* PcmMsrDriver */ = { - isa = PBXNativeTarget; - buildConfigurationList = 81F91BCD156D9BF8007DD788 /* Build configuration list for PBXNativeTarget "PcmMsrDriver" */; - buildPhases = ( - 81F91BB6156D9BF8007DD788 /* Sources */, - 81F91BB7156D9BF8007DD788 /* Frameworks */, - 81F91BB8156D9BF8007DD788 /* Headers */, - 81F91BB9156D9BF8007DD788 /* Resources */, - 81F91BBA156D9BF8007DD788 /* Rez */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = PcmMsrDriver; - productName = PcmMsr; - productReference = 81F91BBC156D9BF8007DD788 /* PcmMsrDriver.kext */; - productType = "com.apple.product-type.kernel-extension"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 81F91BB1156D9BF8007DD788 /* Project object */ = { - isa = PBXProject; - attributes = { - LastUpgradeCheck = 0710; - }; - buildConfigurationList = 81F91BB4156D9BF8007DD788 /* Build configuration list for PBXProject "PcmMsr" */; - compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; - hasScannedForEncodings = 0; - knownRegions = ( - English, - en, - ); - mainGroup = 81F91BAF156D9BF8007DD788; - productRefGroup = 81F91BBD156D9BF8007DD788 /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 81F91BBB156D9BF8007DD788 /* PcmMsrDriver */, - 81ADBF11156EEB93006D9B47 /* PcmMsrLibrary */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 81F91BB9156D9BF8007DD788 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 81F91BC6156D9BF8007DD788 /* InfoPlist.strings in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXRezBuildPhase section */ - 81F91BBA156D9BF8007DD788 /* Rez */ = { - isa = PBXRezBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXRezBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 81ADBF0E156EEB93006D9B47 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 81DEAF6315703531005E8EC6 /* MSRAccessor.cpp in Sources */, - 81DEAF6615703946005E8EC6 /* DriverInterface.c in Sources */, - 895805FC1760E6E5006ED117 /* PCIDriverInterface.cpp in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 81F91BB6156D9BF8007DD788 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 81F91BC9156D9BF8007DD788 /* PcmMsr.cpp in Sources */, - 81ADBF0A156EBD73006D9B47 /* PcmMsrClient.cpp in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXVariantGroup section */ - 81F91BC4156D9BF8007DD788 /* InfoPlist.strings */ = { - isa = PBXVariantGroup; - children = ( - 81F91BC5156D9BF8007DD788 /* en */, - ); - name = InfoPlist.strings; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - -/* Begin XCBuildConfiguration section */ - 81ADBF14156EEB93006D9B47 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_ENABLE_OBJC_ARC = YES; - COMBINE_HIDPI_IMAGES = YES; - DEBUG_INFORMATION_FORMAT = dwarf; - DEPLOYMENT_LOCATION = NO; - EXECUTABLE_PREFIX = lib; - GCC_VERSION = com.apple.compilers.llvm.clang.1_0; - INSTALL_PATH = /usr/lib; - PRODUCT_NAME = PcmMsr; - }; - name = Debug; - }; - 81ADBF15156EEB93006D9B47 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_ENABLE_OBJC_ARC = YES; - COMBINE_HIDPI_IMAGES = YES; - DEBUG_INFORMATION_FORMAT = dwarf; - DEPLOYMENT_LOCATION = NO; - EXECUTABLE_PREFIX = lib; - GCC_VERSION = com.apple.compilers.llvm.clang.1_0; - INSTALL_PATH = /usr/lib; - PRODUCT_NAME = PcmMsr; - }; - name = Release; - }; - 81F91BCB156D9BF8007DD788 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_ENABLE_OBJC_EXCEPTIONS = YES; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_SYMBOLS_PRIVATE_EXTERN = NO; - GCC_VERSION = com.apple.compilers.llvm.clang.1_0; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 13.0; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = macosx; - }; - name = Debug; - }; - 81F91BCC156D9BF8007DD788 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = YES; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_ENABLE_OBJC_EXCEPTIONS = YES; - GCC_NO_COMMON_BLOCKS = YES; - GCC_VERSION = com.apple.compilers.llvm.clang.1_0; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 13.0; - SDKROOT = macosx; - }; - name = Release; - }; - 81F91BCE156D9BF8007DD788 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1.0.0d1; - GCC_PRECOMPILE_PREFIX_HEADER = YES; - GCC_PREFIX_HEADER = "PcmMsr/PcmMsr-Prefix.pch"; - GCC_VERSION = com.apple.compilers.llvm.clang.1_0; - INFOPLIST_FILE = "PcmMsr/PcmMsr-Info.plist"; - MODULE_NAME = com.intel.driver.PcmMsrDriver; - MODULE_VERSION = 1.0.0d1; - ONLY_ACTIVE_ARCH = YES; - PRODUCT_BUNDLE_IDENTIFIER = com.intel.driver.PcmMsr; - PRODUCT_NAME = PcmMsrDriver; - SDKROOT = macosx; - VALID_ARCHS = x86_64; - WRAPPER_EXTENSION = kext; - }; - name = Debug; - }; - 81F91BCF156D9BF8007DD788 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1.0.0d1; - GCC_PRECOMPILE_PREFIX_HEADER = YES; - GCC_PREFIX_HEADER = "PcmMsr/PcmMsr-Prefix.pch"; - GCC_VERSION = com.apple.compilers.llvm.clang.1_0; - INFOPLIST_FILE = "PcmMsr/PcmMsr-Info.plist"; - MODULE_NAME = com.intel.driver.PcmMsrDriver; - MODULE_VERSION = 1.0.0d1; - ONLY_ACTIVE_ARCH = YES; - PRODUCT_BUNDLE_IDENTIFIER = com.intel.driver.PcmMsr; - PRODUCT_NAME = PcmMsrDriver; - SDKROOT = macosx; - VALID_ARCHS = x86_64; - WRAPPER_EXTENSION = kext; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 81ADBF13156EEB93006D9B47 /* Build configuration list for PBXNativeTarget "PcmMsrLibrary" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 81ADBF14156EEB93006D9B47 /* Debug */, - 81ADBF15156EEB93006D9B47 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 81F91BB4156D9BF8007DD788 /* Build configuration list for PBXProject "PcmMsr" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 81F91BCB156D9BF8007DD788 /* Debug */, - 81F91BCC156D9BF8007DD788 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 81F91BCD156D9BF8007DD788 /* Build configuration list for PBXNativeTarget "PcmMsrDriver" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 81F91BCE156D9BF8007DD788 /* Debug */, - 81F91BCF156D9BF8007DD788 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 81F91BB1156D9BF8007DD788 /* Project object */; -} diff --git a/src/MacMSRDriver/PcmMsr.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/src/MacMSRDriver/PcmMsr.xcodeproj/project.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index c39911a7..00000000 --- a/src/MacMSRDriver/PcmMsr.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/src/MacMSRDriver/PcmMsr.xcodeproj/project.xcworkspace/xcuserdata/aiott.xcuserdatad/UserInterfaceState.xcuserstate b/src/MacMSRDriver/PcmMsr.xcodeproj/project.xcworkspace/xcuserdata/aiott.xcuserdatad/UserInterfaceState.xcuserstate deleted file mode 100644 index 28016b08..00000000 Binary files a/src/MacMSRDriver/PcmMsr.xcodeproj/project.xcworkspace/xcuserdata/aiott.xcuserdatad/UserInterfaceState.xcuserstate and /dev/null differ diff --git a/src/MacMSRDriver/PcmMsr.xcodeproj/project.xcworkspace/xcuserdata/pjkerly.xcuserdatad/UserInterfaceState.xcuserstate b/src/MacMSRDriver/PcmMsr.xcodeproj/project.xcworkspace/xcuserdata/pjkerly.xcuserdatad/UserInterfaceState.xcuserstate deleted file mode 100644 index 0d032e62..00000000 Binary files a/src/MacMSRDriver/PcmMsr.xcodeproj/project.xcworkspace/xcuserdata/pjkerly.xcuserdatad/UserInterfaceState.xcuserstate and /dev/null differ diff --git a/src/MacMSRDriver/PcmMsr.xcodeproj/xcuserdata/aiott.xcuserdatad/xcdebugger/Breakpoints.xcbkptlist b/src/MacMSRDriver/PcmMsr.xcodeproj/xcuserdata/aiott.xcuserdatad/xcdebugger/Breakpoints.xcbkptlist deleted file mode 100644 index d6f81fad..00000000 --- a/src/MacMSRDriver/PcmMsr.xcodeproj/xcuserdata/aiott.xcuserdatad/xcdebugger/Breakpoints.xcbkptlist +++ /dev/null @@ -1,105 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/src/MacMSRDriver/PcmMsr.xcodeproj/xcuserdata/aiott.xcuserdatad/xcschemes/PcmMsr.xcscheme b/src/MacMSRDriver/PcmMsr.xcodeproj/xcuserdata/aiott.xcuserdatad/xcschemes/PcmMsr.xcscheme deleted file mode 100644 index a36963e0..00000000 --- a/src/MacMSRDriver/PcmMsr.xcodeproj/xcuserdata/aiott.xcuserdatad/xcschemes/PcmMsr.xcscheme +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/MacMSRDriver/PcmMsr.xcodeproj/xcuserdata/aiott.xcuserdatad/xcschemes/PcmMsrLibrary.xcscheme b/src/MacMSRDriver/PcmMsr.xcodeproj/xcuserdata/aiott.xcuserdatad/xcschemes/PcmMsrLibrary.xcscheme deleted file mode 100644 index 2a910c47..00000000 --- a/src/MacMSRDriver/PcmMsr.xcodeproj/xcuserdata/aiott.xcuserdatad/xcschemes/PcmMsrLibrary.xcscheme +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/MacMSRDriver/PcmMsr.xcodeproj/xcuserdata/aiott.xcuserdatad/xcschemes/xcschememanagement.plist b/src/MacMSRDriver/PcmMsr.xcodeproj/xcuserdata/aiott.xcuserdatad/xcschemes/xcschememanagement.plist deleted file mode 100644 index be6a1cf0..00000000 --- a/src/MacMSRDriver/PcmMsr.xcodeproj/xcuserdata/aiott.xcuserdatad/xcschemes/xcschememanagement.plist +++ /dev/null @@ -1,37 +0,0 @@ - - - - - SchemeUserState - - PcmMsr.xcscheme - - orderHint - 0 - - PcmMsrLibrary.xcscheme - - orderHint - 1 - - - SuppressBuildableAutocreation - - 81ADBF11156EEB93006D9B47 - - primary - - - 81DEAF55157008B7005E8EC6 - - primary - - - 81F91BBB156D9BF8007DD788 - - primary - - - - - diff --git a/src/MacMSRDriver/PcmMsr.xcodeproj/xcuserdata/pjkerly.xcuserdatad/xcschemes/PcmMsrDriver.xcscheme b/src/MacMSRDriver/PcmMsr.xcodeproj/xcuserdata/pjkerly.xcuserdatad/xcschemes/PcmMsrDriver.xcscheme deleted file mode 100644 index aad0e724..00000000 --- a/src/MacMSRDriver/PcmMsr.xcodeproj/xcuserdata/pjkerly.xcuserdatad/xcschemes/PcmMsrDriver.xcscheme +++ /dev/null @@ -1,80 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/MacMSRDriver/PcmMsr.xcodeproj/xcuserdata/pjkerly.xcuserdatad/xcschemes/PcmMsrLibrary.xcscheme b/src/MacMSRDriver/PcmMsr.xcodeproj/xcuserdata/pjkerly.xcuserdatad/xcschemes/PcmMsrLibrary.xcscheme deleted file mode 100644 index 200c8af4..00000000 --- a/src/MacMSRDriver/PcmMsr.xcodeproj/xcuserdata/pjkerly.xcuserdatad/xcschemes/PcmMsrLibrary.xcscheme +++ /dev/null @@ -1,80 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/MacMSRDriver/PcmMsr.xcodeproj/xcuserdata/pjkerly.xcuserdatad/xcschemes/xcschememanagement.plist b/src/MacMSRDriver/PcmMsr.xcodeproj/xcuserdata/pjkerly.xcuserdatad/xcschemes/xcschememanagement.plist deleted file mode 100644 index 78ca9edf..00000000 --- a/src/MacMSRDriver/PcmMsr.xcodeproj/xcuserdata/pjkerly.xcuserdatad/xcschemes/xcschememanagement.plist +++ /dev/null @@ -1,32 +0,0 @@ - - - - - SchemeUserState - - PcmMsrDriver.xcscheme - - orderHint - 0 - - PcmMsrLibrary.xcscheme - - orderHint - 1 - - - SuppressBuildableAutocreation - - 81ADBF11156EEB93006D9B47 - - primary - - - 81F91BBB156D9BF8007DD788 - - primary - - - - - diff --git a/src/MacMSRDriver/PcmMsr/CMakeLists.txt b/src/MacMSRDriver/PcmMsr/CMakeLists.txt deleted file mode 100644 index 1666429f..00000000 --- a/src/MacMSRDriver/PcmMsr/CMakeLists.txt +++ /dev/null @@ -1,58 +0,0 @@ -message(STATUS ${IOKIT_LIBRARY}) - -add_executable( - PcmMsrDriver - MACOSX_BUNDLE - PcmMsr.cpp - PcmMsrClient.cpp - PcmMsrDriver_info.c - PcmMsr-Info.plist -) - -set_target_properties(PcmMsrDriver PROPERTIES BUNDLE_EXTENSION kext MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/PcmMsr-Info.plist) - -# For KEXT compilation on macOS, we must explicitly point to the Kernel frameworks within the SDK. -if(APPLE) - # Find the active macOS SDK path - execute_process( - COMMAND xcrun --sdk macosx --show-sdk-path - OUTPUT_VARIABLE MACOSX_SDK_PATH - OUTPUT_STRIP_TRAILING_WHITESPACE - RESULT_VARIABLE XCRUN_RESULT - ) - if(NOT XCRUN_RESULT EQUAL 0 OR NOT MACOSX_SDK_PATH) - message(FATAL_ERROR "Failed to find macOS SDK path using xcrun") - endif() - message(STATUS "Using SDK for KEXT: ${MACOSX_SDK_PATH}") -endif() - -target_include_directories(PcmMsrDriver PRIVATE - "${MACOSX_SDK_PATH}/System/Library/Frameworks/Kernel.framework/PrivateHeaders" - "${MACOSX_SDK_PATH}/System/Library/Frameworks/Kernel.framework/Headers" -) -target_compile_definitions(PcmMsrDriver PRIVATE - -DKERNEL - -DKERNEL_PRIVATE - -DDRIVER_PRIVATE - -DAPPLE - -DNeXT -) - -target_compile_options(PcmMsrDriver PRIVATE - "-ffreestanding" - "$<$:-fapple-kext>" -) - -target_link_libraries(PcmMsrDriver PRIVATE - "-lkmodc++" - "-lkmod" - "-lcc_kext" - "-nostdlib" - "-Xlinker -export_dynamic" - "-Xlinker -kext" -) - -# PcmMsrDriver.kext is built here and located in 'build/bin' -set(LIB_EXT_PATH "/Library/Extensions") -install(TARGETS PcmMsrDriver DESTINATION "${LIB_EXT_PATH}/") -install(CODE "execute_process(COMMAND kmutil load -b com.intel.driver.PcmMsr)") diff --git a/src/MacMSRDriver/PcmMsr/PcmMsr-Info.plist b/src/MacMSRDriver/PcmMsr/PcmMsr-Info.plist deleted file mode 100644 index 007fba9d..00000000 --- a/src/MacMSRDriver/PcmMsr/PcmMsr-Info.plist +++ /dev/null @@ -1,57 +0,0 @@ - - - - - CFBundleDevelopmentRegion - English - CFBundleExecutable - PcmMsrDriver - CFBundleIdentifier - com.intel.driver.PcmMsr - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - PcmMsrDriver - CFBundlePackageType - KEXT - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleVersion - 1 - IOKitPersonalities - - PcmMsrClient - - CFBundleIdentifier - com.intel.driver.PcmMsr - IOClass - com_intel_driver_PcmMsr - IOMatchCategory - com_intel_driver_PcmMsr - IOProbeScore - 1000 - IOProviderClass - IOResources - IOResourceMatch - IOKit - IOUserClientClass - com_intel_driver_PcmMsrClient - - - OSBundleLibraries - - com.apple.kpi.bsd - 10.9 - com.apple.kpi.mach - 10.9 - com.apple.kpi.unsupported - 10.9 - com.apple.kpi.iokit - 10.9 - com.apple.kpi.libkern - 10.9 - - - diff --git a/src/MacMSRDriver/PcmMsr/PcmMsr-Prefix.pch b/src/MacMSRDriver/PcmMsr/PcmMsr-Prefix.pch deleted file mode 100644 index 9e1e7e47..00000000 --- a/src/MacMSRDriver/PcmMsr/PcmMsr-Prefix.pch +++ /dev/null @@ -1,4 +0,0 @@ -// -// Prefix header for all source files of the 'PcmMsr' target in the 'PcmMsr' project -// - diff --git a/src/MacMSRDriver/PcmMsr/PcmMsr.cpp b/src/MacMSRDriver/PcmMsr/PcmMsr.cpp deleted file mode 100644 index a603a93c..00000000 --- a/src/MacMSRDriver/PcmMsr/PcmMsr.cpp +++ /dev/null @@ -1,316 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -// Copyright (c) 2012, Intel Corporation -// written by Austen Ott -// -#include -#include -#include "PcmMsr.h" - -PcmMsrDriverClassName *g_pci_driver = NULL; - -#define wrmsr(msr,lo,hi) \ -asm volatile ("wrmsr" : : "c" (msr), "a" (lo), "d" (hi)) -#define rdmsr(msr,lo,hi) \ -asm volatile ("\trdmsr\n" : "=a" (lo), "=d" (hi) : "c" (msr)) - -extern "C" { - extern void mp_rendezvous_no_intrs(void (*func)(void *), - void *arg); - extern int cpu_number(void); -} - -inline uint64_t RDMSR(uint32_t msr) -{ - uint64_t value; - uint32_t low, hi; - rdmsr(msr, low, hi); - value = ((uint64_t) hi << 32) | low; - return value; -} - -inline void WRMSR(uint32_t msr, uint64_t value) -{ - uint32_t low, hi; - low = (uint32_t)value; - hi = (uint32_t) (value >> 32); - wrmsr(msr, low, hi); -} - -void cpuReadMSR(void* pIData){ - pcm_msr_data_t* data = (pcm_msr_data_t*)pIData; - int cpu = cpu_number(); - if(data->cpu_num == cpu) - { - data->value = RDMSR(data->msr_num); - } -} - -void cpuWriteMSR(void* pIDatas){ - pcm_msr_data_t* idatas = (pcm_msr_data_t*)pIDatas; - int cpu = cpu_number(); - if(idatas->cpu_num == cpu) - { - WRMSR(idatas->msr_num, idatas->value); - } -} - -void cpuGetTopoData(void* pTopos){ - TopologyEntry* entries = (TopologyEntry*)pTopos; - const int cpu = cpu_number(); - - TopologyEntry & entry = entries[cpu]; - entry.os_id = cpu; - - uint32 smtMaskWidth = 0; - uint32 coreMaskWidth = 0; - uint32 l2CacheMaskShift = 0; - uint32 l3CacheMaskShift = 0; - initCoreMasks(smtMaskWidth, coreMaskWidth, l2CacheMaskShift, l3CacheMaskShift); - PCM_CPUID_INFO cpuid_args; - pcm_cpuid(0xb, 0x0, cpuid_args); - const auto apic_id = cpuid_args.array[3]; - fillEntry(entry, smtMaskWidth, coreMaskWidth, l2CacheMaskShift, apic_id); - entry.l3_cache_id = extract_bits_32(apic_id, l3CacheMaskShift, 31); -} - -OSDefineMetaClassAndStructors(com_intel_driver_PcmMsr, IOService) - -#define super IOService - -bool PcmMsrDriverClassName::start(IOService* provider){ - bool success; - success = super::start(provider); - - if (!g_pci_driver) { - g_pci_driver = this; - } - - if (success) { - registerService(); - } - - return success; -} - -int32_t PcmMsrDriverClassName::getNumCores() -{ - int32_t ncpus = 0; - size_t ncpus_size = sizeof(ncpus); - if(sysctlbyname("hw.logicalcpu", &ncpus, &ncpus_size, NULL, 0)) - { - IOLog("%s[%p]::%s() -- sysctl failure retrieving hw.logicalcpu", - getName(), this, __FUNCTION__); - ncpus = 0; - } - - return ncpus; -} - -bool PcmMsrDriverClassName::init(OSDictionary *dict) -{ - bool result = super::init(dict); - - if (result) { - num_cores = getNumCores(); - } - - return result && num_cores; -} - -void PcmMsrDriverClassName::free() -{ - super::free(); -} - -// We override handleOpen, handleIsOpen, and handleClose to allow multiple clients to access the driver -// simultaneously. We always return true for these because we don't care who is accessing and we -// don't know how many people will be accessing it. -bool PcmMsrDriverClassName::handleOpen(IOService * forClient, IOOptionBits opts, void* args){ - return true; -} - -bool PcmMsrDriverClassName::handleIsOpen(const IOService* forClient) const{ - return true; -} - -void PcmMsrDriverClassName::handleClose(IOService* forClient, IOOptionBits opts){ -} - -IOReturn PcmMsrDriverClassName::readMSR(pcm_msr_data_t* idatas,pcm_msr_data_t* odatas){ - // All the msr_nums should be the same, so we just use the first one to pass to all cores - IOReturn ret = kIOReturnBadArgument; - if(idatas->cpu_num < num_cores) - { - mp_rendezvous_no_intrs(cpuReadMSR, (void*)idatas); - - odatas->cpu_num = idatas->cpu_num; - odatas->msr_num = idatas->msr_num; - odatas->value = idatas->value; - ret = kIOReturnSuccess; - } - else - { - IOLog("Tried to read from a core with id higher than max core id.\n"); - } - return ret; -} - -IOReturn PcmMsrDriverClassName::writeMSR(pcm_msr_data_t* idata){ - IOReturn ret = kIOReturnBadArgument; - if(idata->cpu_num < num_cores) - { - mp_rendezvous_no_intrs(cpuWriteMSR, (void*)idata); - - ret = kIOReturnSuccess; - } - else - { - IOLog("Tried to write to a core with id higher than max core id.\n"); - } - - return ret; -} - -IOReturn PcmMsrDriverClassName::buildTopology(TopologyEntry* odata, uint32_t input_num_cores) -{ - size_t topologyBufferSize; - - // TODO figure out when input_num_cores is used rather than num_cores - if (os_mul_overflow(sizeof(TopologyEntry), (size_t) num_cores, &topologyBufferSize)) - { - return kIOReturnBadArgument; - } - - TopologyEntry *topologies = - (TopologyEntry *)IOMallocAligned(topologyBufferSize, 32); - - if (topologies == nullptr) - { - return kIOReturnNoMemory; - } - - mp_rendezvous_no_intrs(cpuGetTopoData, (void*)topologies); - - for(uint32_t i = 0; i < num_cores && i < input_num_cores; i++) - { - odata[i].os_id = topologies[i].os_id; - odata[i].thread_id = topologies[i].thread_id; - odata[i].core_id = topologies[i].core_id; - odata[i].tile_id = topologies[i].tile_id; - odata[i].socket_id = topologies[i].socket_id; - } - - IOFreeAligned(topologies, topologyBufferSize); - return kIOReturnSuccess; -} - -IOReturn PcmMsrDriverClassName::getNumInstances(uint32_t* num_insts){ - *num_insts = num_clients; - return kIOReturnSuccess; -} - -IOReturn PcmMsrDriverClassName::incrementNumInstances(uint32_t* num_insts){ - *num_insts = ++num_clients; - return kIOReturnSuccess; -} - -IOReturn PcmMsrDriverClassName::decrementNumInstances(uint32_t* num_insts){ - *num_insts = --num_clients; - return kIOReturnSuccess; -} - -// read -uint32_t PcmMsrDriverClassName::read(uint32_t pci_address) -{ - uint32_t value = 0; - - __asm__("\t" - "movw $0xCF8,%%dx\n\t" - "andb $0xFC,%%al\n\t" - "outl %%eax,%%dx\n\t" - "movl $0xCFC,%%edx\n\t" - "in %%dx,%%eax\n" - : "=a"(value) - : "a"(pci_address) - : "%edx"); - - return value; -} - - -// write -void PcmMsrDriverClassName::write(uint32_t pci_address, uint32_t value) -{ - - __asm__("\t" - "movw $0xCF8,%%dx\n\t" - "andb $0xFC,%%al\n\t" - "outl %%eax,%%dx\n\t" - "movl $0xCFC,%%edx\n\t" - "movl %%ebx,%%eax\n\t" - "outl %%eax,%%dx\n" - : - : "a"(pci_address), "b"(value) - : "%edx"); -} - - -// mapMemory -void* PcmMsrDriverClassName::mapMemory (uint32_t address, UInt8 **virtual_address) -{ - PRINT_DEBUG("%s[%p]::%s()\n", getName(), this, __FUNCTION__); - - IOMemoryMap *memory_map = NULL; - IOMemoryDescriptor *memory_descriptor = NULL; - #ifndef __clang_analyzer__ // address a false-positive - memory_descriptor = IOMemoryDescriptor::withPhysicalAddress(address, - 4096, - kIODirectionInOut); - #endif - if (memory_descriptor) { - IOReturn ioErr = memory_descriptor->prepare(kIODirectionInOut); - if (ioErr == kIOReturnSuccess) { - memory_map = memory_descriptor->map(); - if (memory_map) { - if (virtual_address) { - *virtual_address = (UInt8*)memory_map->getVirtualAddress(); - } else { - IOLog("%s[%p]::%s() -- virtual_address is null\n", getName(), this, __FUNCTION__); - } - } else { - IOLog("%s[%p]::%s() -- IOMemoryDescriptor::map() failure\n", getName(), this, __FUNCTION__); - } - } - else { - IOLog("%s[%p]::%s() -- IOMemoryDescriptor::prepare() failure\n", getName(), this, __FUNCTION__); - } - if (!memory_map) - { - memory_descriptor->release(); - } - } else { - IOLog("%s[%p]::%s() -- IOMemoryDescriptor::withPhysicalAddress() failure\n", getName(), this, __FUNCTION__); - } - - return (void*)memory_map; -} - - -// unmapMemory -void PcmMsrDriverClassName::unmapMemory (void *memory_map) -{ - PRINT_DEBUG("%s[%p]::%s()\n", getName(), this, __FUNCTION__); - - IOMemoryMap *m_map = (IOMemoryMap*)memory_map; - - if (m_map) { - m_map->getMemoryDescriptor()->complete(); - #ifndef __clang_analyzer__ // address a false-positive - m_map->getMemoryDescriptor()->release(); - #endif - m_map->unmap(); - m_map->release(); - } - - return; -} diff --git a/src/MacMSRDriver/PcmMsr/PcmMsr.h b/src/MacMSRDriver/PcmMsr/PcmMsr.h deleted file mode 100644 index ed19c470..00000000 --- a/src/MacMSRDriver/PcmMsr/PcmMsr.h +++ /dev/null @@ -1,47 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -// Copyright (c) 2012, Intel Corporation -// written by Austen Ott -// -#include -#include "UserKernelShared.h" - -class PcmMsrDriverClassName : public IOService -{ - OSDeclareDefaultStructors(com_intel_driver_PcmMsr) -public: - // IOService methods - virtual bool start(IOService* provider) override; - - virtual IOReturn writeMSR(pcm_msr_data_t* data); - virtual IOReturn readMSR(pcm_msr_data_t* idata,pcm_msr_data_t* odata); - virtual IOReturn buildTopology(TopologyEntry* odata, uint32_t input_num_cores); - virtual bool init(OSDictionary *dict) override; - virtual void free(void) override; - virtual bool handleOpen(IOService* forClient, IOOptionBits opts, void* args) override; - virtual bool handleIsOpen(const IOService* forClient) const override; - virtual void handleClose(IOService* forClient, IOOptionBits opts) override; - - virtual int32_t getNumCores(); - - virtual IOReturn incrementNumInstances(uint32_t* num_instances); - virtual IOReturn decrementNumInstances(uint32_t* num_instances); - virtual IOReturn getNumInstances(uint32_t* num_instances); - - // PCI classes - static uint32_t read(uint32_t pci_address); - static void write(uint32_t pci_address, uint32_t value); - void* mapMemory(uint32_t address, UInt8 **virtual_address); - void unmapMemory(void* memory_map); - -private: - // number of providers currently using the driver - uint32_t num_clients = 0; - int32_t num_cores; -}; - -#ifdef DEBUG -#define _DEBUG 1 -#else -#define _DEBUG 0 -#endif -#define PRINT_DEBUG if (_DEBUG) IOLog diff --git a/src/MacMSRDriver/PcmMsr/PcmMsrClient.cpp b/src/MacMSRDriver/PcmMsr/PcmMsrClient.cpp deleted file mode 100644 index 92f0bbd3..00000000 --- a/src/MacMSRDriver/PcmMsr/PcmMsrClient.cpp +++ /dev/null @@ -1,336 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -// Copyright (c) 2012, Intel Corporation -// written by Austen Ott -// -#include -#include -#include -#include "PcmMsrClient.h" - -#define super IOUserClient - -OSDefineMetaClassAndStructors(com_intel_driver_PcmMsrClient, IOUserClient) - -const IOExternalMethodDispatch PcmMsrClientClassName::sMethods[kNumberOfMethods] = { - { (IOExternalMethodAction) &PcmMsrClientClassName::sOpenDriver, 0, 0, 0, 0}, - { (IOExternalMethodAction) &PcmMsrClientClassName::sCloseDriver, 0, 0, 0, 0}, - { (IOExternalMethodAction) &PcmMsrClientClassName::sReadMSR, 0, kIOUCVariableStructureSize, 0, kIOUCVariableStructureSize}, - { (IOExternalMethodAction) &PcmMsrClientClassName::sWriteMSR, 0, kIOUCVariableStructureSize, 0, 0}, - { (IOExternalMethodAction) &PcmMsrClientClassName::sBuildTopology, 0, 0, 0, kIOUCVariableStructureSize}, - { (IOExternalMethodAction) &PcmMsrClientClassName::sGetNumInstances, 0, 0, 1, 0}, - { (IOExternalMethodAction) &PcmMsrClientClassName::sIncrementNumInstances, 0, 0, 1, 0}, - { (IOExternalMethodAction) &PcmMsrClientClassName::sDecrementNumInstances, 0, 0, 1, 0}, - { (IOExternalMethodAction) &PcmMsrClientClassName::sRead, 1, 0, 1, 0 }, - { (IOExternalMethodAction) &PcmMsrClientClassName::sWrite, 2, 0, 0, 0 }, - { (IOExternalMethodAction) &PcmMsrClientClassName::sMapMemory, 1, 0, 2, 0 }, - { (IOExternalMethodAction) &PcmMsrClientClassName::sUnmapMemory, 1, 0, 0, 0 }, - { (IOExternalMethodAction) &PcmMsrClientClassName::sReadMemory, 1, 0, 1, 0 } - -}; - -IOReturn PcmMsrClientClassName::externalMethod(uint32_t selector, IOExternalMethodArguments* args, IOExternalMethodDispatch* dispatch, OSObject* target, void* reference) -{ - if (selector < (uint32_t) kNumberOfMethods) { - dispatch = (IOExternalMethodDispatch *) &sMethods[selector]; - - if (!target) { - target = this; - } - } - - return super::externalMethod(selector, args, dispatch, target, reference); -} - -bool PcmMsrClientClassName::initWithTask(task_t owningTask, void *securityToken, UInt32 type, OSDictionary *properties) -{ - if(!IOUserClient::initWithTask(owningTask, securityToken, type, properties)) { - return false; - } - - sSecurityToken = securityToken; - return true; -} - -bool PcmMsrClientClassName::start(IOService* provider) -{ - bool result = false; - - if(clientHasPrivilege(sSecurityToken, kIOClientPrivilegeAdministrator) != kIOReturnSuccess) - return false; - - fProvider = OSDynamicCast(PcmMsrDriverClassName, provider); - - if (fProvider != NULL) { - result = super::start(provider); - } - else - IOLog("PcmMsrClientClassName::start failed.\n"); - - return result; -} - -IOReturn PcmMsrClientClassName::clientClose(void) -{ - closeUserClient(); - - if (!terminate()) { - IOLog("PcmMsrClientClassName::clientClose failed.\n"); - } - - return kIOReturnSuccess; -} - -bool PcmMsrClientClassName::didTerminate(IOService* provider, IOOptionBits options, bool* defer) -{ - closeUserClient(); - *defer = false; - - return super::didTerminate(provider, options, defer); -} - - -IOReturn PcmMsrClientClassName::sOpenDriver(PcmMsrClientClassName* target, void* reference, IOExternalMethodArguments* arguments) -{ - return target->openUserClient(); -} - -IOReturn PcmMsrClientClassName::openUserClient(void) -{ - IOReturn result = kIOReturnSuccess; - - if (fProvider == NULL || isInactive()) { - result = kIOReturnNotAttached; - IOLog("%s::%s returned kIOReturnNotAttached.\n", getName(), __FUNCTION__); - } else if (!fProvider->open(this)) { - result = kIOReturnExclusiveAccess; - IOLog("%s::%s returned kIOReturnExclusiveAccess.\n", getName(), __FUNCTION__); - } - - return result; -} - -IOReturn PcmMsrClientClassName::checkActiveAndOpened (const char* memberFunction) -{ - if (fProvider == NULL || isInactive()) { - IOLog("%s::%s returned kIOReturnNotAttached.\n", getName(), memberFunction); - return (IOReturn)kIOReturnNotAttached; - - } else if (!fProvider->isOpen(this)) { - IOLog("%s::%s returned kIOReturnNotOpen.\n", getName(), memberFunction); - return (IOReturn)kIOReturnNotOpen; - } - return kIOReturnSuccess; -} - - -IOReturn PcmMsrClientClassName::sCloseDriver(PcmMsrClientClassName* target, void* reference, IOExternalMethodArguments* arguments) -{ - return target->closeUserClient(); -} - -IOReturn PcmMsrClientClassName::closeUserClient(void) -{ - IOReturn result = checkActiveAndOpened (__FUNCTION__); - - if (result == kIOReturnSuccess) - fProvider->close(this); - - return result; -} - -IOReturn PcmMsrClientClassName::sReadMSR(PcmMsrClientClassName* target, void* reference, IOExternalMethodArguments* arguments){ - return target->readMSR((pcm_msr_data_t*) arguments->structureInput, (pcm_msr_data_t*) arguments->structureOutput); -} - -IOReturn PcmMsrClientClassName::readMSR(pcm_msr_data_t* idata, pcm_msr_data_t* odata) -{ - IOReturn result = checkActiveAndOpened (__FUNCTION__); - - if (result == kIOReturnSuccess) - result = fProvider->readMSR(idata, odata); - - return result; -} - -IOReturn PcmMsrClientClassName::sWriteMSR(PcmMsrClientClassName* target, void* reference, IOExternalMethodArguments* arguments){ - return target -> writeMSR((pcm_msr_data_t*)arguments->structureInput); -} - -IOReturn PcmMsrClientClassName::writeMSR(pcm_msr_data_t* data) -{ - IOReturn result = checkActiveAndOpened (__FUNCTION__); - - if (result == kIOReturnSuccess) - result = fProvider->writeMSR(data); - - return result; -} - -IOReturn PcmMsrClientClassName::sBuildTopology(PcmMsrClientClassName* target, void* reference, IOExternalMethodArguments* args){ - return target -> buildTopology((TopologyEntry*)args->structureOutput, args->structureOutputSize); -} - -IOReturn PcmMsrClientClassName::buildTopology(TopologyEntry* data, size_t output_size) -{ - uint32_t num_cores = (uint32_t) (output_size / sizeof(TopologyEntry) ); - IOReturn result = checkActiveAndOpened (__FUNCTION__); - - if (result == kIOReturnSuccess) - result = fProvider->buildTopology(data, num_cores); - - return result; -} - -IOReturn PcmMsrClientClassName::sGetNumInstances(PcmMsrClientClassName* target, void* reference, IOExternalMethodArguments* args){ - return target->getNumInstances((uint32_t*)&args->scalarOutput[0]); -} -IOReturn PcmMsrClientClassName::getNumInstances(uint32_t* num_insts){ - return fProvider->getNumInstances(num_insts); -} - -IOReturn PcmMsrClientClassName::sIncrementNumInstances(PcmMsrClientClassName* target, void* reference, IOExternalMethodArguments* args){ - return target->incrementNumInstances((uint32_t*)&args->scalarOutput[0]); -} -IOReturn PcmMsrClientClassName::incrementNumInstances(uint32_t* num_insts){ - return fProvider->incrementNumInstances(num_insts); -} - -IOReturn PcmMsrClientClassName::sDecrementNumInstances(PcmMsrClientClassName* target, void* reference, IOExternalMethodArguments* args){ - return target->decrementNumInstances((uint32_t*)&args->scalarOutput[0]); -} -IOReturn PcmMsrClientClassName::decrementNumInstances(uint32_t* num_insts){ - return fProvider->decrementNumInstances(num_insts); -} - - - -extern PcmMsrDriverClassName* g_pci_driver; - -// read32 -IOReturn PcmMsrClientClassName::sRead(PcmMsrClientClassName* target, void* reference, IOExternalMethodArguments* arguments) { - return target->read(arguments->scalarInput, arguments->scalarInputCount, arguments->scalarOutput, arguments->scalarOutputCount); -} -IOReturn PcmMsrClientClassName::read(const uint64_t* input, uint32_t inputSize, uint64_t* output, uint32_t outputSize) -{ - PRINT_DEBUG("%s[%p]::%s()\n", getName(), this, __FUNCTION__); - - if (inputSize != 1) { - IOLog("%s[%p]::%s(): returning kIOReturnBadArgument.\n", getName(), this, __FUNCTION__); - return kIOReturnBadArgument; - } - - uint32_t addr = (uint32_t)input[0]; - PRINT_DEBUG("addr: %x\n", addr); - - if (g_pci_driver) { - output[0] = g_pci_driver->read(addr); - } - IOLog("val: %llx\n", output[0]); - - return kIOReturnSuccess; -} - - -// write32 -IOReturn PcmMsrClientClassName::sWrite(PcmMsrClientClassName* target, void* reference, IOExternalMethodArguments* arguments) { - return target->write(arguments->scalarInput, arguments->scalarInputCount); -} -IOReturn PcmMsrClientClassName::write(const uint64_t* input, uint32_t inputSize) -{ - PRINT_DEBUG("%s[%p]::%s()\n", getName(), this, __FUNCTION__); - - if (inputSize != 2) { - IOLog("%s[%p]::%s(): returning kIOReturnBadArgument.\n", getName(), this, __FUNCTION__); - return kIOReturnBadArgument; - } - - uint32_t addr = (uint32_t)input[0]; - uint32_t val = (uint32_t)input[1]; - PRINT_DEBUG("addr: %x, val: %x\n", addr, val); - - if (g_pci_driver) { - g_pci_driver->write(addr, val); - } - - return kIOReturnSuccess; -} - - -// mapMemory -IOReturn PcmMsrClientClassName::sMapMemory(PcmMsrClientClassName* target, void* reference, IOExternalMethodArguments* arguments) { - return target->mapMemory(arguments->scalarInput, arguments->scalarInputCount, arguments->scalarOutput, arguments->scalarOutputCount); -} -IOReturn PcmMsrClientClassName::mapMemory(const uint64_t* input, uint32_t inputSize, uint64_t* output, uint32_t outputSize) -{ - PRINT_DEBUG("%s[%p]::%s()\n", getName(), this, __FUNCTION__); - - if (inputSize != 1) { - IOLog("%s[%p]::%s(): returning kIOReturnBadArgument.\n", getName(), this, __FUNCTION__); - return kIOReturnBadArgument; - } - - uint32_t address = (uint32_t)input[0]; - PRINT_DEBUG("address: %x\n", address); - - if (g_pci_driver) { - uint8_t* virtual_address = NULL; - void* memory_map = g_pci_driver->mapMemory(address, (uint8_t**)&virtual_address); - output[0] = (uint64_t)memory_map; - output[1] = (uint64_t)virtual_address; - PRINT_DEBUG("memory_map: %p\n", memory_map); - PRINT_DEBUG("virtual_address: %p\n", virtual_address); - } - - return kIOReturnSuccess; -} - - -// unmapMemory -IOReturn PcmMsrClientClassName::sUnmapMemory(PcmMsrClientClassName* target, void* reference, IOExternalMethodArguments* arguments) { - return target->unmapMemory(arguments->scalarInput, arguments->scalarInputCount); -} -IOReturn PcmMsrClientClassName::unmapMemory(const uint64_t* input, uint32_t inputSize) -{ - PRINT_DEBUG("%s[%p]::%s()\n", getName(), this, __FUNCTION__); - - if (inputSize != 1) { - IOLog("%s[%p]::%s(): returning kIOReturnBadArgument.\n", getName(), this, __FUNCTION__); - return kIOReturnBadArgument; - } - - void* memory_map = (void*)input[0]; - PRINT_DEBUG("memory_map: %p\n", memory_map); - - if (g_pci_driver) { - g_pci_driver->unmapMemory(memory_map); - } - - return kIOReturnSuccess; -} - - -// readMemory -IOReturn PcmMsrClientClassName::sReadMemory(PcmMsrClientClassName* target, void* reference, IOExternalMethodArguments* arguments) { - return target->readMemory(arguments->scalarInput, arguments->scalarInputCount, arguments->scalarOutput, arguments->scalarOutputCount); -} -IOReturn PcmMsrClientClassName::readMemory(const uint64_t* input, uint32_t inputSize, uint64_t* output, uint32_t outputSize) -{ - PRINT_DEBUG("%s[%p]::%s()\n", getName(), this, __FUNCTION__); - - if (inputSize != 1) { - IOLog("%s[%p]::%s(): returning kIOReturnBadArgument.\n", getName(), this, __FUNCTION__); - return kIOReturnBadArgument; - } - - uint8_t* address = (uint8_t*)input[0]; - PRINT_DEBUG("address: %p\n", address); - - uint32_t val = 0; - if (g_pci_driver) { - val = *(uint32_t*)address; - } - output[0] = (uint64_t)val; - PRINT_DEBUG("val: %x\n", val); - - return kIOReturnSuccess; -} diff --git a/src/MacMSRDriver/PcmMsr/PcmMsrClient.h b/src/MacMSRDriver/PcmMsr/PcmMsrClient.h deleted file mode 100644 index 77da0b93..00000000 --- a/src/MacMSRDriver/PcmMsr/PcmMsrClient.h +++ /dev/null @@ -1,75 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -// Copyright (c) 2012, Intel Corporation -// written by Austen Ott -// -#include -#include -#include "PcmMsr.h" - -#define PcmMsrClientClassName com_intel_driver_PcmMsrClient - -class PcmMsrClientClassName : public IOUserClient -{ - OSDeclareDefaultStructors(com_intel_driver_PcmMsrClient) - -protected: - PcmMsrDriverClassName* fProvider; - void* sSecurityToken; - static const IOExternalMethodDispatch sMethods[kNumberOfMethods]; - -public: - virtual bool initWithTask(task_t owningTask, void *securityToken, UInt32 type, OSDictionary *properties) override; - virtual bool start(IOService *provider) override; - - virtual IOReturn clientClose(void) override; - - virtual bool didTerminate(IOService* provider, IOOptionBits opts, bool* defer) override; - -protected: - IOReturn checkActiveAndOpened (const char* memberFunction); - - virtual IOReturn externalMethod(uint32_t selector, - IOExternalMethodArguments* arguments, - IOExternalMethodDispatch* dispatch, - OSObject* target, void* reference) override; - - static IOReturn sOpenDriver(PcmMsrClientClassName* target, void* reference, IOExternalMethodArguments* args); - virtual IOReturn openUserClient(void); - - static IOReturn sCloseDriver(PcmMsrClientClassName* target, void* reference, IOExternalMethodArguments* args); - virtual IOReturn closeUserClient(void); - - static IOReturn sReadMSR(PcmMsrClientClassName* target, void* reference, IOExternalMethodArguments* args); - virtual IOReturn readMSR(pcm_msr_data_t* idata, pcm_msr_data_t* odata); - - static IOReturn sWriteMSR(PcmMsrClientClassName* target, void* reference, IOExternalMethodArguments* args); - virtual IOReturn writeMSR(pcm_msr_data_t* data); - - static IOReturn sBuildTopology(PcmMsrClientClassName* target, void* reference, IOExternalMethodArguments* args); - virtual IOReturn buildTopology(TopologyEntry* data, size_t output_size); - - static IOReturn sGetNumInstances(PcmMsrClientClassName* target, void* reference, IOExternalMethodArguments* args); - virtual IOReturn getNumInstances(uint32_t* num_insts); - - static IOReturn sIncrementNumInstances(PcmMsrClientClassName* target, void* reference, IOExternalMethodArguments* args); - virtual IOReturn incrementNumInstances(uint32_t* num_insts); - - static IOReturn sDecrementNumInstances(PcmMsrClientClassName* target, void* reference, IOExternalMethodArguments* args); - virtual IOReturn decrementNumInstances(uint32_t* num_insts); - - // PCI functions - static IOReturn sRead(PcmMsrClientClassName* target, void* reference, IOExternalMethodArguments* arguments); - virtual IOReturn read(const uint64_t* input, uint32_t inputSize, uint64_t* output, uint32_t outputSize); - - static IOReturn sWrite(PcmMsrClientClassName* target, void* reference, IOExternalMethodArguments* arguments); - virtual IOReturn write(const uint64_t* input, uint32_t inputSize); - - static IOReturn sMapMemory(PcmMsrClientClassName* target, void* reference, IOExternalMethodArguments* arguments); - virtual IOReturn mapMemory(const uint64_t* input, uint32_t inputSize, uint64_t* output, uint32_t outputSize); - - static IOReturn sUnmapMemory(PcmMsrClientClassName* target, void* reference, IOExternalMethodArguments* arguments); - virtual IOReturn unmapMemory(const uint64_t* input, uint32_t inputSize); - - static IOReturn sReadMemory(PcmMsrClientClassName* target, void* reference, IOExternalMethodArguments* arguments); - virtual IOReturn readMemory(const uint64_t* input, uint32_t inputSize, uint64_t* output, uint32_t outputSize); -}; diff --git a/src/MacMSRDriver/PcmMsr/PcmMsrDriver_info.c b/src/MacMSRDriver/PcmMsr/PcmMsrDriver_info.c deleted file mode 100644 index 9cac48b6..00000000 --- a/src/MacMSRDriver/PcmMsr/PcmMsrDriver_info.c +++ /dev/null @@ -1,9 +0,0 @@ -#include - -extern kern_return_t _start(kmod_info_t *ki, void *data); -extern kern_return_t _stop(kmod_info_t *ki, void *data); - -__attribute__((visibility("default"))) KMOD_EXPLICIT_DECL(com.intel.driver.PcmMsrDriver, "1.0.0d1", _start, _stop) -__private_extern__ kmod_start_func_t *_realmain = 0; -__private_extern__ kmod_stop_func_t *_antimain = 0; -__private_extern__ int _kext_apple_cc = __APPLE_CC__ ; diff --git a/src/MacMSRDriver/PcmMsr/UserKernelShared.h b/src/MacMSRDriver/PcmMsr/UserKernelShared.h deleted file mode 100644 index f9dccac5..00000000 --- a/src/MacMSRDriver/PcmMsr/UserKernelShared.h +++ /dev/null @@ -1,53 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -// Copyright (c) 2012, Intel Corporation -// written by Austen Ott -// -#define PcmMsrDriverClassName com_intel_driver_PcmMsr -#define kPcmMsrDriverClassName "com_intel_driver_PcmMsr" - -#ifndef USER_KERNEL_SHARED -#define USER_KERNEL_SHARED - -#define PCM_API - -// kIOMainPortDefault is not supported before macOS Monterey -#if (MAC_OS_X_VERSION_MAX_ALLOWED < 120000) - #define kIOMainPortDefault kIOMasterPortDefault -#endif - -#include -#include "../../topologyentry.h" - -using namespace pcm; - -typedef struct { - uint64_t value; - uint32_t cpu_num; - uint32_t msr_num; -} pcm_msr_data_t; - -typedef struct { - uint64_t value; - uint32_t msr_num; - bool mask; - char padding[115]; -} k_pcm_msr_data_t; - -enum { - kOpenDriver, - kCloseDriver, - kReadMSR, - kWriteMSR, - kBuildTopology, - kGetNumInstances, - kIncrementNumInstances, - kDecrementNumInstances, - // PCI functions - kRead, - kWrite, - kMapMemory, - kUnmapMemory, - kReadMemory, - kNumberOfMethods -}; -#endif diff --git a/src/MacMSRDriver/PcmMsr/en.lproj/InfoPlist.strings b/src/MacMSRDriver/PcmMsr/en.lproj/InfoPlist.strings deleted file mode 100644 index 477b28ff..00000000 --- a/src/MacMSRDriver/PcmMsr/en.lproj/InfoPlist.strings +++ /dev/null @@ -1,2 +0,0 @@ -/* Localized versions of Info.plist keys */ - diff --git a/src/MacMSRDriver/kextload.sh b/src/MacMSRDriver/kextload.sh deleted file mode 100644 index c5ad090d..00000000 --- a/src/MacMSRDriver/kextload.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash - -cp -R ../../build/bin/PcmMsrDriver.kext /Library/Extensions/. -chown -R root:wheel /Library/Extensions/PcmMsrDriver.kext -kextload /Library/Extensions/PcmMsrDriver.kext diff --git a/src/MacMSRDriver/kextunload.sh b/src/MacMSRDriver/kextunload.sh deleted file mode 100644 index 2a145828..00000000 --- a/src/MacMSRDriver/kextunload.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env bash - -kextunload /Library/Extensions/PcmMsrDriver.kext -rm -rf /Library/Extensions/PcmMsrDriver.kext diff --git a/src/PMURegisterDeclarations/GenuineIntel-6-DD-0.json b/src/PMURegisterDeclarations/GenuineIntel-6-DD-0.json new file mode 100644 index 00000000..e3b4a8ed --- /dev/null +++ b/src/PMURegisterDeclarations/GenuineIntel-6-DD-0.json @@ -0,0 +1,145 @@ +{ + "core" : { + "programmable" : { + "EventCode": {"Config": 0, "Position": 0, "Width": 8}, + "UMask": {"Config": 0, "Position": 8, "Width": 8}, + "User": {"Config": 0, "Position": 16, "Width": 1, "DefaultValue": 1}, + "OS": {"Config": 0, "Position": 17, "Width": 1, "DefaultValue": 1}, + "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1}, + "PinControl": {"Config": 0, "Position": 19, "Width": 1, "DefaultValue": 0}, + "APICInt": {"Config": 0, "Position": 20, "Width": 1, "DefaultValue": 0}, + "Enable": {"Config": 0, "Position": 22, "Width": 1, "DefaultValue": 1}, + "Invert": {"Config": 0, "Position": 23, "Width": 1}, + "CounterMask": {"Config": 0, "Position": 24, "Width": 8}, + "MSRIndex": { + "0x1a6" : {"Config": 1, "Position": 0, "Width": 64}, + "0x1a7" : {"Config": 2, "Position": 0, "Width": 64}, + "0x3f6" : {"Config": 3, "Position": 0, "Width": 64}, + "0x3f7" : {"Config": 4, "Position": 0, "Width": 64} + } + }, + "fixed0" : { + "OS": {"Config": 0, "Position": 0, "Width": 1, "DefaultValue": 1}, + "User": {"Config": 0, "Position": 1, "Width": 1, "DefaultValue": 1}, + "EnablePMI": {"Config": 0, "Position": 3, "Width": 1, "DefaultValue": 0}, + "EventCode": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, + "UMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, + "EdgeDetect": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, + "Invert": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, + "CounterMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"} + }, + "fixed1" : { + "OS": {"Config": 0, "Position": 4, "Width": 1, "DefaultValue": 1}, + "User": {"Config": 0, "Position": 5, "Width": 1, "DefaultValue": 1}, + "EnablePMI": {"Config": 0, "Position": 7, "Width": 1, "DefaultValue": 0}, + "EventCode": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, + "UMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, + "EdgeDetect": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, + "Invert": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, + "CounterMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"} + }, + "fixed2" : { + "OS": {"Config": 0, "Position": 8, "Width": 1, "DefaultValue": 1}, + "User": {"Config": 0, "Position": 9, "Width": 1, "DefaultValue": 1}, + "EnablePMI": {"Config": 0, "Position": 11, "Width": 1, "DefaultValue": 0}, + "EventCode": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, + "UMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, + "EdgeDetect": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, + "Invert": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"}, + "CounterMask": {"Config": 0, "Position": -1, "__comment": "position=-1 means field ignored"} + } + }, + "cha" : { + "programmable" : { + "EventCode": {"Config": 0, "Position": 0, "Width": 8}, + "UMask": {"Config": 0, "Position": 8, "Width": 8}, + "TIDEnable": {"Config": 0, "Position": 16, "Width": 1, "DefaultValue": 0}, + "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, + "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0}, + "UMaskExt": {"Config": 0, "Position": 32, "Width": 26}, + "TID": {"Config": 1, "Position": 0, "Width": 10, "DefaultValue": 0} + } + }, + "imc" : { + "programmable" : { + "EventCode": {"Config": 0, "Position": 0, "Width": 8}, + "UMask": {"Config": 0, "Position": 8, "Width": 8}, + "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, + "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0} + } + }, + "xpi" : { + "__comment" : "this is for UPI LL and QPI LL uncore PMUs", + "programmable" : { + "EventCode": {"Config": 0, "Position": 0, "Width": 8}, + "UMask": {"Config": 0, "Position": 8, "Width": 8}, + "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, + "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0}, + "UMaskExt": {"Config": 0, "Position": 32, "Width": 24} + } + }, + "m2m" : { + "programmable" : { + "EventCode": {"Config": 0, "Position": 0, "Width": 8}, + "UMask": {"Config": 0, "Position": 8, "Width": 8}, + "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, + "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0}, + "UMaskExt": {"Config": 0, "Position": 32, "Width": 8} + } + }, + "m3upi" : { + "programmable" : { + "EventCode": {"Config": 0, "Position": 0, "Width": 8}, + "UMask": {"Config": 0, "Position": 8, "Width": 8}, + "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, + "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0} + } + }, + "mdf" : { + "programmable" : { + "EventCode": {"Config": 0, "Position": 0, "Width": 8}, + "UMask": {"Config": 0, "Position": 8, "Width": 8}, + "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, + "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0} + } + }, + "irp" : { + "programmable" : { + "EventCode": {"Config": 0, "Position": 0, "Width": 8}, + "UMask": {"Config": 0, "Position": 8, "Width": 8}, + "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, + "Threshold": {"Config": 0, "Position": 24, "Width": 8, "DefaultValue": 0} + } + }, + "pcu" : { + "programmable" : { + "EventCode": {"Config": 0, "Position": 0, "Width": 8}, + "UMask": {"Config": 0, "Position": 8, "Width": 8}, + "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0} + } + }, + "pciex8" : { + "programmable" : { + "EventCode": {"Config": 0, "Position": 0, "Width": 8}, + "UMask": {"Config": 0, "Position": 8, "Width": 8}, + "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0} + } + }, + "pciex16" : { + "programmable" : { + "EventCode": {"Config": 0, "Position": 0, "Width": 8}, + "UMask": {"Config": 0, "Position": 8, "Width": 8}, + "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0} + } + }, + "iio" : { + "programmable" : { + "EventCode": {"Config": 0, "Position": 0, "Width": 8}, + "UMask": {"Config": 0, "Position": 8, "Width": 8}, + "EdgeDetect": {"Config": 0, "Position": 18, "Width": 1, "DefaultValue": 0}, + "Threshold": {"Config": 0, "Position": 24, "Width": 12, "DefaultValue": 0}, + "PortMask": {"Config": 0, "Position": 36, "Width": 12}, + "FCMask": {"Config": 0, "Position": 48, "Width": 3} + } + } +} diff --git a/src/cpuasynchcounter.h b/src/cpuasynchcounter.h deleted file mode 100644 index 7b33fe68..00000000 --- a/src/cpuasynchcounter.h +++ /dev/null @@ -1,212 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -// Copyright (c) 2009-2022, Intel Corporation -// -// asynchronous CPU conters -// -// contact: Thomas Willhalm - -#ifndef CPUASYNCHCOUNTER_HEADER -#define CPUASYNCHCOUNTER_HEADER - - -/*! \file cpuasynchcounter.h - \brief Implementation of a POSIX thread that periodically saves the current state of counters and exposes them to other threads -*/ - -#include -#include -#include "cpucounters.h" - -#define DELAY 1 // in seconds - -using namespace pcm; - -void * UpdateCounters(void *); - -class AsynchronCounterState { - PCM * m; - - CoreCounterState * cstates1, * cstates2; - SocketCounterState * skstates1, * skstates2; - SystemCounterState sstate1, sstate2; - - pthread_t UpdateThread; - pthread_mutex_t CounterMutex; - - friend void * UpdateCounters(void *); - - AsynchronCounterState(const AsynchronCounterState &) = delete; - const AsynchronCounterState & operator = (const AsynchronCounterState &) = delete; - -public: - AsynchronCounterState() - { - m = PCM::getInstance(); - PCM::ErrorCode status = m->program(); - if (status != PCM::Success) - { - std::cerr << "\nCannot access CPU counters. Try to run 'pcm 1' to check the PMU access status.\n\n"; - exit(-1); - } - - cstates1 = new CoreCounterState[m->getNumCores()]; - cstates2 = new CoreCounterState[m->getNumCores()]; - skstates1 = new SocketCounterState[m->getNumSockets()]; - skstates2 = new SocketCounterState[m->getNumSockets()]; - - for (uint32 i = 0; i < m->getNumCores(); ++i) { - cstates1[i] = getCoreCounterState(i); - cstates2[i] = getCoreCounterState(i); - } - - for (uint32 i = 0; i < m->getNumSockets(); ++i) { - skstates1[i] = getSocketCounterState(i); - skstates2[i] = getSocketCounterState(i); - } - - pthread_mutex_init(&CounterMutex, NULL); - pthread_create(&UpdateThread, NULL, UpdateCounters, this); - } - ~AsynchronCounterState() - { - pthread_cancel(UpdateThread); - if (pthread_mutex_destroy(&CounterMutex) != 0) std::cerr << "pthread_mutex_destroy failed\n"; - try { - m->cleanup(); - } catch (const std::runtime_error & e) - { - std::cerr << "PCM Error in ~AsynchronCounterState(). Exception " << e.what() << "\n"; - } - deleteAndNullifyArray(cstates1); - deleteAndNullifyArray(cstates2); - deleteAndNullifyArray(skstates1); - deleteAndNullifyArray(skstates2); - } - - uint32 getNumCores() - { return m->getNumCores(); } - - uint32 getNumSockets() - { return m->getNumSockets(); } - - uint32 getQPILinksPerSocket() - { - return m->getQPILinksPerSocket(); - } - - uint32 getSocketId(uint32 c) - { - return m->getSocketId(c); - } - - const char * getXpi() { - return m->xPI(); - } - - template - T get(uint32 core) - { - pthread_mutex_lock(&CounterMutex); - T value = func(cstates2[core]); - pthread_mutex_unlock(&CounterMutex); - return value; - } - template - T get(uint32 core) - { - pthread_mutex_lock(&CounterMutex); - T value = func(cstates1[core], cstates2[core]); - pthread_mutex_unlock(&CounterMutex); - return value; - } - - template - T get(int param, uint32 core) - { - pthread_mutex_lock(&CounterMutex); - T value = func(param, cstates1[core], cstates2[core]); - pthread_mutex_unlock(&CounterMutex); - return value; - } - - template - T getSocket(uint32 socket) - { - pthread_mutex_lock(&CounterMutex); - T value = func(skstates2[socket]); - pthread_mutex_unlock(&CounterMutex); - return value; - } - - template - T getSocket(uint32 socket) - { - pthread_mutex_lock(&CounterMutex); - T value = func(skstates1[socket], skstates2[socket]); - pthread_mutex_unlock(&CounterMutex); - return value; - } - - template - T getSocket(int param, uint32 socket) - { - pthread_mutex_lock(&CounterMutex); - T value = func(param, skstates1[socket], skstates2[socket]); - pthread_mutex_unlock(&CounterMutex); - return value; - } - - template - T getSocket(uint32 socket, uint32 param) - { - pthread_mutex_lock(&CounterMutex); - T value = func(socket, param, sstate1, sstate2); - pthread_mutex_unlock(&CounterMutex); - return value; - } - - template - T getSystem() - { - pthread_mutex_lock(&CounterMutex); - T value = func(sstate1, sstate2); - pthread_mutex_unlock(&CounterMutex); - return value; - } - - template - T getSystem(int param) - { - pthread_mutex_lock(&CounterMutex); - T value = func(param, sstate1, sstate2); - pthread_mutex_unlock(&CounterMutex); - return value; - } -}; - -void * UpdateCounters(void * state) -{ - AsynchronCounterState * s = (AsynchronCounterState *)state; - - while (true) { - if (pthread_mutex_lock(&(s->CounterMutex)) != 0) std::cerr << "pthread_mutex_lock failed\n"; - for (uint32 core = 0; core < s->m->getNumCores(); ++core) { - s->cstates1[core] = std::move(s->cstates2[core]); - s->cstates2[core] = s->m->getCoreCounterState(core); - } - - for (uint32 socket = 0; socket < s->m->getNumSockets(); ++socket) { - s->skstates1[socket] = std::move(s->skstates2[socket]); - s->skstates2[socket] = s->m->getSocketCounterState(socket); - } - - s->sstate1 = std::move(s->sstate2); - s->sstate2 = s->m->getSystemCounterState(); - - if (pthread_mutex_unlock(&(s->CounterMutex)) != 0) std::cerr << "pthread_mutex_unlock failed\n"; - sleep(1); - } - return NULL; -} - -#endif diff --git a/src/cpucounters.cpp b/src/cpucounters.cpp index 5decbaaf..5fb5bbd3 100644 --- a/src/cpucounters.cpp +++ b/src/cpucounters.cpp @@ -76,19 +76,8 @@ #include #include -#ifdef __APPLE__ -#include -#include -#include -#endif - namespace pcm { -#ifdef __APPLE__ -// convertUnknownToInt is used in the safe sysctl call to convert an unknown size to an int -int convertUnknownToInt(size_t size, char* value); -#endif - #ifdef _MSC_VER void PCM_API restrictDriverAccess(LPCTSTR path) @@ -775,6 +764,7 @@ void PCM::initCStateSupportTables() case BDX: case KNL: PCM_CSTATE_ARRAY(pkgCStateMsr, PCM_PARAM_PROTECT({0, 0, 0x60D, 0x3F8, 0, 0, 0x3F9, 0x3FA, 0, 0, 0}) ); + case CWF: case SKX: case ICX: case SPR: @@ -834,6 +824,7 @@ void PCM::initCStateSupportTables() case LNL: case ARL: case PTL: + case CWF: case SNOWRIDGE: case ELKHART_LAKE: case JASPER_LAKE: @@ -1208,7 +1199,6 @@ bool PCM::discoverSystemTopology() } } -#ifndef __APPLE__ auto populateEntry = [&topologyDomainMap,&smtMaskWidth, &coreMaskWidth, &l2CacheMaskShift, &l3CacheMaskShift](TopologyEntry& entry) { auto getAPICID = [&](const uint32 leaf) @@ -1265,7 +1255,6 @@ bool PCM::discoverSystemTopology() } entry.l3_cache_id = extract_bits_32(getAPICID(0xb), l3CacheMaskShift, 31); }; -#endif auto populateHybridEntry = [this](TopologyEntry& entry, int core) -> bool { @@ -1356,7 +1345,7 @@ bool PCM::discoverSystemTopology() deleteAndNullifyArray(base_slpi); #else - // for Linux, Mac OS, FreeBSD and DragonFlyBSD + // for Linux, FreeBSD and DragonFlyBSD TopologyEntry entry; @@ -1440,62 +1429,6 @@ bool PCM::discoverSystemTopology() socketIdMap[entry.socket_id] = 0; } -#else // Getting processor info for Mac OS -#define SAFE_SYSCTLBYNAME(message, ret_value) \ - { \ - size_t size; \ - char *pParam; \ - if(0 != sysctlbyname(message, NULL, &size, NULL, 0)) \ - { \ - std::cerr << "Unable to determine size of " << message << " sysctl return type.\n"; \ - return false; \ - } \ - if(NULL == (pParam = (char *)malloc(size))) \ - { \ - std::cerr << "Unable to allocate memory for " << message << "\n"; \ - return false; \ - } \ - if(0 != sysctlbyname(message, (void*)pParam, &size, NULL, 0)) \ - { \ - std::cerr << "Unable to get " << message << " from sysctl.\n"; \ - return false; \ - } \ - ret_value = convertUnknownToInt(size, pParam); \ - freeAndNullify(pParam); \ - } -// End SAFE_SYSCTLBYNAME - - // Using OSXs sysctl to get the number of CPUs right away - SAFE_SYSCTLBYNAME("hw.logicalcpu", num_cores) - num_online_cores = num_cores; - -#undef SAFE_SYSCTLBYNAME - - // The OSX version needs the MSR handle earlier so that it can build the CPU topology. - // This topology functionality should potentially go into a different KEXT - for(int i = 0; i < num_cores; i++) - { - MSR.push_back(std::make_shared(i)); - } - - assert(num_cores > 0); - TopologyEntry entries[num_cores]; - if (MSR[0]->buildTopology(num_cores, entries) != 0) { - std::cerr << "Unable to build CPU topology" << std::endl; - return false; - } - for(int i = 0; i < num_cores; i++){ - socketIdMap[entries[i].socket_id] = 0; - if(entries[i].os_id >= 0) - { - if (populateHybridEntry(entries[i], i) == false) - { - return false; - } - topology.push_back(entries[i]); - } - } -// End of OSX specific code #endif #endif //end of ifdef _MSC_VER @@ -1681,12 +1614,6 @@ void PCM::printSystemTopology() const bool PCM::initMSR() { -#ifdef __APPLE__ - for (size_t i=0; i < MSR.size(); ++i) - { - systemTopology->addMSRHandleToOSThread(MSR[i], (uint32)i); - } -#else try { for (int i = 0; i < (int)num_cores; ++i) @@ -1716,7 +1643,6 @@ bool PCM::initMSR() #endif return false; } -#endif return true; } @@ -1736,6 +1662,7 @@ bool PCM::detectNominalFrequency() MSR[socketRefCore[0]]->read(PLATFORM_INFO_ADDR, &freq); const uint64 bus_freq = ( cpu_family_model == SANDY_BRIDGE + || cpu_family_model == CWF || cpu_family_model == JAKETOWN || cpu_family_model == IVYTOWN || cpu_family_model == HASWELLX @@ -2080,6 +2007,7 @@ void PCM::initUncoreObjects() } switch (cpu_family_model) { + case CWF: case ICX: case SNOWRIDGE: case SPR: @@ -2301,6 +2229,7 @@ void PCM::initUncorePMUsDirect() case SRF: case GNR: case GNR_D: + case CWF: uncorePMUs[s].resize(1); { std::vector > CounterControlRegs{ @@ -2454,6 +2383,7 @@ void PCM::initUncorePMUsDirect() case GNR: case GNR_D: case SRF: + case CWF: uncorePMUs[s].resize(1); addPMUsFromDiscoveryRef(uncorePMUs[s][0][PCU_PMU_ID], SPR_PCU_BOX_TYPE, 0xE); if (uncorePMUs[s][0][PCU_PMU_ID].empty()) @@ -2482,6 +2412,7 @@ void PCM::initUncorePMUsDirect() case GNR: case GNR_D: case SRF: + case CWF: addMDFPMUs(BHS_MDF_BOX_TYPE); break; } @@ -2531,6 +2462,7 @@ void PCM::initUncorePMUsDirect() case GNR_D: case GRR: case SRF: + case CWF: uncorePMUs[s].resize(1); if (safe_getenv("PCM_NO_PCIE_GEN5_DISCOVERY") == std::string("1")) { @@ -2637,6 +2569,7 @@ void PCM::initUncorePMUsDirect() case PCM::GNR: case PCM::GNR_D: case PCM::SRF: + case PCM::CWF: for (uint32 s = 0; s < (uint32)num_sockets; ++s) { auto & handle = MSR[socketRefCore[s]]; @@ -2868,6 +2801,7 @@ void PCM::initUncorePMUsDirect() case GNR: case GNR_D: case SRF: + case CWF: irpStacks = BHS_M2IOSF_NUM; IRP_CTL_REG_OFFSET = BHS_IRP_CTL_REG_OFFSET; IRP_CTR_REG_OFFSET = BHS_IRP_CTR_REG_OFFSET; @@ -3010,6 +2944,7 @@ void PCM::initUncorePMUsDirect() case PCM::GNR: case PCM::GNR_D: case PCM::SRF: + case PCM::CWF: for (size_t die = 0; die < uncorePMUDiscovery->getNumDies(s); ++die) { const auto n_units = (std::min)(uncorePMUDiscovery->getNumBoxes(SPR_CXLCM_BOX_TYPE, s, die), @@ -3507,6 +3442,7 @@ bool PCM::isSocketOnline(int32 socket_id) const bool PCM::isCPUModelSupported(const int model_) { return ( model_ == NEHALEM_EP + || model_ == CWF || model_ == NEHALEM_EX || model_ == WESTMERE_EP || model_ == WESTMERE_EX @@ -3850,6 +3786,7 @@ PCM::ErrorCode PCM::program(const PCM::ProgramMode mode_, const void * parameter break; case GRR: case SRF: + case CWF: LLCArchEventInit(coreEventDesc); coreEventDesc[2].event_number = CMT_MEM_LOAD_RETIRED_L2_MISS_EVTNR; coreEventDesc[2].umask_value = CMT_MEM_LOAD_RETIRED_L2_MISS_UMASK; @@ -5109,6 +5046,8 @@ const char * PCM::cpuFamilyModelToUArchCodename(const int32 cpu_family_model_, c { switch(cpu_family_model_) { + case CWF: + return "Clearwater Forest"; case CENTERTON: return "Centerton"; case BAYTRAIL: @@ -5531,31 +5470,6 @@ bool PCM::supportsRDTSCP() const return 1 == supports; } -#ifdef __APPLE__ - -int convertUnknownToInt(size_t size, char* value) -{ - if(sizeof(int) == size) - { - return *(int*)value; - } - else if(sizeof(long) == size) - { - return *(long *)value; - } - else if(sizeof(long long) == size) - { - return *(long long *)value; - } - else - { - // In this case, we don't know what it is so we guess int - return *(int *)value; - } -} - -#endif - uint64 PCM::getTickCount(uint64 multiplier, int32 core) { @@ -5909,29 +5823,51 @@ PCM::ErrorCode PCM::programServerUncoreLatencyMetrics(bool enable_pmm) if (enable_pmm == false) { //DDR is false - if (ICX == cpu_family_model || SPR == cpu_family_model || EMR == cpu_family_model) - { - DDRConfig[0] = MC_CH_PCI_PMON_CTL_EVENT(0x80) + MC_CH_PCI_PMON_CTL_UMASK(1); // DRAM RPQ occupancy - DDRConfig[1] = MC_CH_PCI_PMON_CTL_EVENT(0x10) + MC_CH_PCI_PMON_CTL_UMASK(1); // DRAM RPQ Insert - DDRConfig[2] = MC_CH_PCI_PMON_CTL_EVENT(0x81) + MC_CH_PCI_PMON_CTL_UMASK(0); // DRAM WPQ Occupancy - DDRConfig[3] = MC_CH_PCI_PMON_CTL_EVENT(0x20) + MC_CH_PCI_PMON_CTL_UMASK(0); // DRAM WPQ Insert + switch (cpu_family_model) + { + case GNR: + case GNR_D: + case SRF: + case CWF: + // Official perfmon event names (GNR/GNR-D/SRF/CWF iMC uncore; see Intel perfmon JSONs for GNR/SRF/CWF iMC uncore): + // On these CPUs each iMC channel has two sub-channels (SCH0/SCH1) and two pseudo-channels (PCH0/PCH1); + // we use the SCH0_PCH0 variant for all four counters (analogous to the PCH0 pick on ICX). + DDRConfig[0] = MC_CH_PCI_PMON_CTL_EVENT(0x80) + MC_CH_PCI_PMON_CTL_UMASK(0x00); // DRAM RPQ occupancy -> UNC_M_RPQ_OCCUPANCY_SCH0_PCH0 + DDRConfig[1] = MC_CH_PCI_PMON_CTL_EVENT(0x10) + MC_CH_PCI_PMON_CTL_UMASK(0x10); // DRAM RPQ Insert -> UNC_M_RPQ_INSERTS.SCH0_PCH0 + DDRConfig[2] = MC_CH_PCI_PMON_CTL_EVENT(0x84) + MC_CH_PCI_PMON_CTL_UMASK(0x00); // DRAM WPQ Occupancy -> UNC_M_WPQ_OCCUPANCY_SCH0_PCH0 + DDRConfig[3] = MC_CH_PCI_PMON_CTL_EVENT(0x22) + MC_CH_PCI_PMON_CTL_UMASK(0x10); // DRAM WPQ Insert -> UNC_M_WPQ_INSERTS.SCH0_PCH0 + break; - } else { + case ICX: + case SPR: + case EMR: + // Official perfmon event names (ICX/SPR/EMR iMC uncore; see Intel perfmon JSONs for ICX/SPR/EMR iMC uncore): + DDRConfig[0] = MC_CH_PCI_PMON_CTL_EVENT(0x80) + MC_CH_PCI_PMON_CTL_UMASK(1); // DRAM RPQ occupancy -> UNC_M_RPQ_OCCUPANCY_PCH0 + DDRConfig[1] = MC_CH_PCI_PMON_CTL_EVENT(0x10) + MC_CH_PCI_PMON_CTL_UMASK(1); // DRAM RPQ Insert -> UNC_M_RPQ_INSERTS.PCH0 + DDRConfig[2] = MC_CH_PCI_PMON_CTL_EVENT(0x82) + MC_CH_PCI_PMON_CTL_UMASK(0); // DRAM WPQ Occupancy -> UNC_M_WPQ_OCCUPANCY_PCH0 (event 0x81 was RPQ occupancy PCH1, not WPQ; WPQ occupancy is 0x82/0x83 on ICX/SPR/EMR) + DDRConfig[3] = MC_CH_PCI_PMON_CTL_EVENT(0x20) + MC_CH_PCI_PMON_CTL_UMASK(1); // DRAM WPQ Insert -> UNC_M_WPQ_INSERTS.PCH0 + break; - DDRConfig[0] = MC_CH_PCI_PMON_CTL_EVENT(0x80) + MC_CH_PCI_PMON_CTL_UMASK(0); // DRAM RPQ occupancy - DDRConfig[1] = MC_CH_PCI_PMON_CTL_EVENT(0x10) + MC_CH_PCI_PMON_CTL_UMASK(0); // DRAM RPQ Insert - DDRConfig[2] = MC_CH_PCI_PMON_CTL_EVENT(0x81) + MC_CH_PCI_PMON_CTL_UMASK(0); // DRAM WPQ Occupancy - DDRConfig[3] = MC_CH_PCI_PMON_CTL_EVENT(0x20) + MC_CH_PCI_PMON_CTL_UMASK(0); // DRAM WPQ Insert - } + default: + // Official perfmon event names (SKX/CLX iMC uncore; see Intel perfmon JSONs for SKX/CLX iMC uncore): + DDRConfig[0] = MC_CH_PCI_PMON_CTL_EVENT(0x80) + MC_CH_PCI_PMON_CTL_UMASK(0); // DRAM RPQ occupancy -> UNC_M_RPQ_OCCUPANCY + DDRConfig[1] = MC_CH_PCI_PMON_CTL_EVENT(0x10) + MC_CH_PCI_PMON_CTL_UMASK(0); // DRAM RPQ Insert -> UNC_M_RPQ_INSERTS + DDRConfig[2] = MC_CH_PCI_PMON_CTL_EVENT(0x81) + MC_CH_PCI_PMON_CTL_UMASK(0); // DRAM WPQ Occupancy -> UNC_M_WPQ_OCCUPANCY + DDRConfig[3] = MC_CH_PCI_PMON_CTL_EVENT(0x20) + MC_CH_PCI_PMON_CTL_UMASK(0); // DRAM WPQ Insert -> UNC_M_WPQ_INSERTS + break; + } } else { - DDRConfig[0] = MC_CH_PCI_PMON_CTL_EVENT(0xe0) + MC_CH_PCI_PMON_CTL_UMASK(1); // PMM RDQ occupancy - DDRConfig[1] = MC_CH_PCI_PMON_CTL_EVENT(0xe3) + MC_CH_PCI_PMON_CTL_UMASK(0); // PMM RDQ Insert - DDRConfig[2] = MC_CH_PCI_PMON_CTL_EVENT(0xe4) + MC_CH_PCI_PMON_CTL_UMASK(1); // PMM WPQ Occupancy - DDRConfig[3] = MC_CH_PCI_PMON_CTL_EVENT(0xe7) + MC_CH_PCI_PMON_CTL_UMASK(0); // PMM WPQ Insert + // Official perfmon event names (PMM/DCPMM iMC uncore; ICX names, SPR/EMR use the *_SCH0 suffixed variants; see Intel perfmon JSONs for ICX/SPR/EMR iMC uncore): + DDRConfig[0] = MC_CH_PCI_PMON_CTL_EVENT(0xe0) + MC_CH_PCI_PMON_CTL_UMASK(1); // PMM RDQ occupancy -> UNC_M_PMM_RPQ_OCCUPANCY.ALL (SPR/EMR: UNC_M_PMM_RPQ_OCCUPANCY.ALL_SCH0) + DDRConfig[1] = MC_CH_PCI_PMON_CTL_EVENT(0xe3) + MC_CH_PCI_PMON_CTL_UMASK(0); // PMM RDQ Insert -> UNC_M_PMM_RPQ_INSERTS + DDRConfig[2] = MC_CH_PCI_PMON_CTL_EVENT(0xe4) + MC_CH_PCI_PMON_CTL_UMASK(1); // PMM WPQ Occupancy -> UNC_M_PMM_WPQ_OCCUPANCY.ALL (SPR/EMR: UNC_M_PMM_WPQ_OCCUPANCY.ALL_SCH0) + DDRConfig[3] = MC_CH_PCI_PMON_CTL_EVENT(0xe7) + MC_CH_PCI_PMON_CTL_UMASK(0); // PMM WPQ Insert -> UNC_M_PMM_WPQ_INSERTS } if (DDRLatencyMetricsAvailable()) { + if (MSR.empty() || serverUncorePMUs.empty()) return PCM::MSRAccessDenied; + for (size_t i = 0; i < (size_t)serverUncorePMUs.size(); ++i) { serverUncorePMUs[i]->programIMC(DDRConfig); @@ -5968,6 +5904,7 @@ PCM::ErrorCode PCM::programServerUncorePowerMetrics(int mc_profile, int pcu_prof switch (cpu_family_model) { + case CWF: case SPR: case EMR: case SRF: @@ -5989,6 +5926,7 @@ PCM::ErrorCode PCM::programServerUncorePowerMetrics(int mc_profile, int pcu_prof case 1: switch (cpu_family_model) { + case CWF: case SPR: case EMR: case SRF: @@ -6012,12 +5950,12 @@ PCM::ErrorCode PCM::programServerUncorePowerMetrics(int mc_profile, int pcu_prof case 3: PCUCntConf[1] = PCU_MSR_PMON_CTL_EVENT(0x04); // Thermal frequency limit cycles: FREQ_MAX_LIMIT_THERMAL_CYCLES PCUCntConf[2] = PCU_MSR_PMON_CTL_EVENT(0x05); // Power frequency limit cycles: FREQ_MAX_POWER_CYCLES - PCUCntConf[3] = PCU_MSR_PMON_CTL_EVENT(0x07); // Clipped frequency limit cycles: FREQ_MAX_CURRENT_CYCLES (not supported on SKX,ICX,SNOWRIDGE,SPR,EMR,SRF,GNR) + PCUCntConf[3] = PCU_MSR_PMON_CTL_EVENT(0x07); // Clipped frequency limit cycles: FREQ_MAX_CURRENT_CYCLES (not supported on SKX,ICX,SNOWRIDGE,SPR,EMR,SRF,GNR,CWF) break; case 4: // not supported on SKX, ICX, SNOWRIDGE, SPR, EMR PCUCntConf[1] = PCU_MSR_PMON_CTL_EVENT(0x06); // OS frequency limit cycles: FREQ_MAX_OS_CYCLES PCUCntConf[2] = PCU_MSR_PMON_CTL_EVENT(0x05); // Power frequency limit cycles: FREQ_MAX_POWER_CYCLES - PCUCntConf[3] = PCU_MSR_PMON_CTL_EVENT(0x07); // Clipped frequency limit cycles: FREQ_MAX_CURRENT_CYCLES (not supported on SKX,ICX,SNOWRIDGE,SPR,EMR,SRF,GNR) + PCUCntConf[3] = PCU_MSR_PMON_CTL_EVENT(0x07); // Clipped frequency limit cycles: FREQ_MAX_CURRENT_CYCLES (not supported on SKX,ICX,SNOWRIDGE,SPR,EMR,SRF,GNR,CWF) break; case 5: if (JAKETOWN == cpu_family_model) @@ -6030,6 +5968,7 @@ PCM::ErrorCode PCM::programServerUncorePowerMetrics(int mc_profile, int pcu_prof PCUCntConf[2] = PCU_MSR_PMON_CTL_EVENT(0x60) ; // cycles spent changing frequency: FREQ_TRANS_CYCLES } else if ( HASWELLX == cpu_family_model + || CWF == cpu_family_model || BDX_DE == cpu_family_model || BDX == cpu_family_model || SKX == cpu_family_model @@ -6061,6 +6000,7 @@ PCM::ErrorCode PCM::programServerUncorePowerMetrics(int mc_profile, int pcu_prof PCUCntConf[3] = PCU_MSR_PMON_CTL_EVENT(0x2D) + PCU_MSR_PMON_CTL_EDGE_DET ; // PC6 transitions } else if ( HASWELLX == cpu_family_model + || CWF == cpu_family_model || BDX_DE == cpu_family_model || BDX == cpu_family_model || SKX == cpu_family_model @@ -6073,8 +6013,8 @@ PCM::ErrorCode PCM::programServerUncorePowerMetrics(int mc_profile, int pcu_prof || GNR_D == cpu_family_model ) { - PCUCntConf[0] = PCU_MSR_PMON_CTL_EVENT(0x4E) ; // PC1e residenicies (not supported on SKX,ICX,SNOWRIDGE,SPR,EMR,SRF,GNR) - PCUCntConf[1] = PCU_MSR_PMON_CTL_EVENT(0x4E) + PCU_MSR_PMON_CTL_EDGE_DET ; // PC1 transitions (not supported on SKX,ICX,SNOWRIDGE,SPR,EMR,SRF,GNR) + PCUCntConf[0] = PCU_MSR_PMON_CTL_EVENT(0x4E) ; // PC1e residenicies (not supported on SKX,ICX,SNOWRIDGE,SPR,EMR,SRF,GNR,CWF) + PCUCntConf[1] = PCU_MSR_PMON_CTL_EVENT(0x4E) + PCU_MSR_PMON_CTL_EDGE_DET ; // PC1 transitions (not supported on SKX,ICX,SNOWRIDGE,SPR,EMR,SRF,GNR,CWF) PCUCntConf[2] = PCU_MSR_PMON_CTL_EVENT(0x2B) + PCU_MSR_PMON_CTL_EDGE_DET ; // PC2e transitions PCUCntConf[3] = PCU_MSR_PMON_CTL_EVENT(0x2D) + PCU_MSR_PMON_CTL_EDGE_DET ; // PC6 transitions } else @@ -7513,11 +7453,6 @@ int32 PCM::mapNUMANodeToSocket(uint32 numa_node_id) const } #endif - return cacheAndReturn(-1); -#elif defined(__APPLE__) - // On macOS, NUMA information is not readily available - // For now, return -1 to indicate the mapping is not available - (void)numa_node_id; // Suppress unused parameter warning return cacheAndReturn(-1); #else // Unsupported platform @@ -8218,6 +8153,7 @@ void ServerUncorePMUs::initRegisterLocations(const PCM * pcm) break; case PCM::SRF: case PCM::GNR: + case PCM::CWF: { PCM_PCICFG_QPI_INIT(0, BHS); PCM_PCICFG_QPI_INIT(1, BHS); @@ -8459,6 +8395,7 @@ void ServerUncorePMUs::initDirect(uint32 socket_, const PCM * pcm) { switch (cpu_family_model) { + case PCM::CWF: case PCM::ICX: case PCM::SNOWRIDGE: case PCM::SPR: @@ -8662,6 +8599,7 @@ void ServerUncorePMUs::initDirect(uint32 socket_, const PCM * pcm) break; case PCM::GNR: case PCM::SRF: + case PCM::CWF: initBHSiMCPMUs(12); break; case PCM::GNR_D: @@ -8752,6 +8690,7 @@ void ServerUncorePMUs::initDirect(uint32 socket_, const PCM * pcm) break; case PCM::GNR: case PCM::SRF: + case PCM::CWF: m3upiPMUs.push_back( UncorePMU( std::make_shared(handle, BHS_M3UPI_PCI_PMON_BOX_CTL_ADDR), @@ -8918,6 +8857,7 @@ void ServerUncorePMUs::initDirect(uint32 socket_, const PCM * pcm) case PCM::EMR: case PCM::GNR: case PCM::SRF: + case PCM::CWF: xpiPMUs.push_back( UncorePMU( std::make_shared(handle, SPR_UPI_PCI_PMON_BOX_CTL_ADDR), @@ -9459,6 +9399,7 @@ void ServerUncorePMUs::programServerUncoreMemoryMetrics(const ServerUncoreMemory case PCM::GNR_D: case PCM::GRR: case PCM::SRF: + case PCM::CWF: if (metrics == PmemMemoryMode) { std::cerr << "PCM Error: PMM/Pmem metrics are not available on your platform\n"; @@ -9556,6 +9497,7 @@ void ServerUncorePMUs::program() case PCM::GNR_D: case PCM::GRR: case PCM::SRF: + case PCM::CWF: MCCntConfig[EventPosition::READ] = MC_CH_PCI_PMON_CTL_EVENT(0x05) + MC_CH_PCI_PMON_CTL_UMASK(0xcf); // monitor reads on counter 0: CAS_COUNT_SCH0.RD MCCntConfig[EventPosition::WRITE] = MC_CH_PCI_PMON_CTL_EVENT(0x05) + MC_CH_PCI_PMON_CTL_UMASK(0xf0); // monitor writes on counter 1: CAS_COUNT_SCH0.WR MCCntConfig[EventPosition::READ2] = MC_CH_PCI_PMON_CTL_EVENT(0x06) + MC_CH_PCI_PMON_CTL_UMASK(0xcf); // monitor reads on counter 2: CAS_COUNT_SCH1.RD @@ -9690,6 +9632,7 @@ uint64 ServerUncorePMUs::getImcReadsForChannels(uint32 beginChannel, uint32 endC case PCM::GNR_D: case PCM::GRR: case PCM::SRF: + case PCM::CWF: result += getMCCounter(i, EventPosition::READ2); break; } @@ -9709,6 +9652,7 @@ uint64 ServerUncorePMUs::getImcWrites() case PCM::GNR_D: case PCM::GRR: case PCM::SRF: + case PCM::CWF: result += getMCCounter(i, EventPosition::WRITE2); break; } @@ -9826,6 +9770,7 @@ void ServerUncorePMUs::program_power_metrics(int mc_profile) unsigned int UNC_M_POWER_CKE_CYCLES = 0x83; switch (cpu_family_model) { + case PCM::CWF: case PCM::ICX: case PCM::SNOWRIDGE: case PCM::SPR: @@ -9839,6 +9784,7 @@ void ServerUncorePMUs::program_power_metrics(int mc_profile) unsigned int UNC_M_POWER_CHANNEL_PPD_CYCLES = 0x85; switch (cpu_family_model) { + case PCM::CWF: case PCM::SRF: case PCM::GNR: case PCM::GNR_D: @@ -9848,6 +9794,7 @@ void ServerUncorePMUs::program_power_metrics(int mc_profile) unsigned int UNC_M_SELF_REFRESH_ENTER_SUCCESS_CYCLES_UMASK = 0; switch (cpu_family_model) { + case PCM::CWF: case PCM::SRF: case PCM::GNR: case PCM::GNR_D: @@ -9955,6 +9902,7 @@ void ServerUncorePMUs::programM2M() case PCM::GNR: case PCM::GNR_D: case PCM::SRF: + case PCM::CWF: cfg[EventPosition::NM_HIT] = M2M_PCI_PMON_CTL_EVENT(0x1F) + M2M_PCI_PMON_CTL_UMASK(0x0F); // UNC_B2CMI_TAG_HIT.ALL cfg[EventPosition::M2M_CLOCKTICKS] = 0; // CLOCKTICKS cfg[EventPosition::MM_MISS_CLEAN] = M2M_PCI_PMON_CTL_EVENT(0x4B) + M2M_PCI_PMON_CTL_UMASK(0x05); // UNC_B2CMI_TAG_MISS.CLEAN @@ -10275,7 +10223,7 @@ void ServerUncorePMUs::cleanupMemTest(const ServerUncorePMUs::MemTestParam & par munmap(b, memBufferBlockSize); #elif defined(_MSC_VER) VirtualFree(b, memBufferBlockSize, MEM_RELEASE); -#elif defined(__FreeBSD__) || defined(__APPLE__) +#elif defined(__FreeBSD__) (void) b; // avoid the unused variable warning (void) memBufferBlockSize; // avoid the unused variable warning #else @@ -10308,6 +10256,7 @@ uint64 ServerUncorePMUs::computeQPISpeed(const uint32 core_nr, const int cpufami { case PCM::GNR: case PCM::SRF: + case PCM::CWF: UPISpeedMap = { { 0, 2500}, { 1, 12800}, @@ -10429,6 +10378,7 @@ uint64 PCM::CX_MSR_PMON_CTRY(uint32 Cbo, uint32 Ctr) const case SNOWRIDGE: return CX_MSR_PMON_BOX_CTL(Cbo) + SERVER_CHA_MSR_PMON_CTR0_OFFSET + Ctr; + case CWF: case SPR: case EMR: case GNR: @@ -10462,6 +10412,7 @@ uint64 PCM::CX_MSR_PMON_BOX_FILTER(uint32 Cbo) const case ICX: return CX_MSR_PMON_BOX_CTL(Cbo) + SERVER_CHA_MSR_PMON_BOX_FILTER_OFFSET; + case CWF: case SPR: case EMR: case GNR: @@ -10508,6 +10459,7 @@ uint64 PCM::CX_MSR_PMON_CTLY(uint32 Cbo, uint32 Ctl) const case SNOWRIDGE: return CX_MSR_PMON_BOX_CTL(Cbo) + SERVER_CHA_MSR_PMON_CTL0_OFFSET + Ctl; + case CWF: case SPR: case EMR: case GNR: @@ -10540,6 +10492,7 @@ uint64 PCM::CX_MSR_PMON_BOX_CTL(uint32 Cbo) const case ICX: return ICX_CHA_MSR_PMON_BOX_CTL[Cbo]; + case CWF: case SPR: case EMR: case GNR: @@ -10616,6 +10569,7 @@ uint32 PCM::getMaxNumOfCBoxesInternal() const uint64 val = 0; switch (cpu_family_model) { + case CWF: case GRR: case GNR: case GNR_D: @@ -10743,6 +10697,7 @@ void PCM::programIIOCounters(uint64 rawEvents[4], int IIOStack) case PCM::GRR: stacks_count = GRR_M2IOSF_NUM; break; + case PCM::CWF: case PCM::GNR: case PCM::GNR_D: case PCM::SRF: @@ -10839,6 +10794,7 @@ void PCM::programPCIeEventGroup(eventGroup_t &eventGroup) switch (cpu_family_model) { + case PCM::CWF: case PCM::GNR: case PCM::GNR_D: case PCM::GRR: @@ -10890,6 +10846,7 @@ void PCM::programCbo(const uint64 * events, const uint32 opCode, const uint32 nc pmu.initFreeze(UNC_PMON_UNIT_CTL_FRZ_EN); if ( ICX != cpu_family_model + && CWF != cpu_family_model && SNOWRIDGE != cpu_family_model && SPR != cpu_family_model && EMR != cpu_family_model @@ -11157,6 +11114,7 @@ bool PCM::supportIDXAccelDev() const switch (this->getCPUFamilyModel()) { + case PCM::CWF: case PCM::SPR: case PCM::EMR: case PCM::GNR: @@ -11402,6 +11360,7 @@ void UncorePMU::freeze(const uint32 extra) { switch (getCPUFamilyModel()) { + case PCM::CWF: case PCM::SPR: case PCM::EMR: case PCM::GNR: @@ -11419,6 +11378,7 @@ void UncorePMU::unfreeze(const uint32 extra) { switch (getCPUFamilyModel()) { + case PCM::CWF: case PCM::SPR: case PCM::EMR: case PCM::GNR: @@ -11441,6 +11401,7 @@ bool UncorePMU::initFreeze(const uint32 extra, const char* xPICheckMsg) switch (getCPUFamilyModel()) { + case PCM::CWF: case PCM::SPR: case PCM::EMR: case PCM::GNR: @@ -11482,6 +11443,7 @@ void UncorePMU::resetUnfreeze(const uint32 extra) { switch (getCPUFamilyModel()) { + case PCM::CWF: case PCM::SPR: case PCM::EMR: case PCM::GNR: diff --git a/src/cpucounters.h b/src/cpucounters.h index fc6c9a33..c1498132 100644 --- a/src/cpucounters.h +++ b/src/cpucounters.h @@ -688,6 +688,11 @@ class PCM_API PCM }; private: std::unordered_map strToUncorePMUID_ { + {"cbo", CBO_PMU_ID}, + {"cha", CBO_PMU_ID}, + {"mdf", MDF_PMU_ID}, + {"pcu", PCU_PMU_ID}, + {"ubox", UBOX_PMU_ID}, {"pciex8", PCIE_GEN5x8_PMU_ID}, {"pciex16", PCIE_GEN5x16_PMU_ID} }; @@ -1268,6 +1273,7 @@ class PCM_API PCM { switch (cpu_family_model) { + case CWF: case SPR: case EMR: case GNR: @@ -1756,6 +1762,7 @@ class PCM_API PCM case ELKHART_LAKE: case JASPER_LAKE: case SRF: + case CWF: case GRR: return eCoreOCREvent; } @@ -1950,6 +1957,7 @@ class PCM_API PCM //! \brief Identifiers of supported CPU models enum SupportedCPUModels { + CWF = PCM_CPU_FAMILY_MODEL(6, 221), NEHALEM_EP = PCM_CPU_FAMILY_MODEL(6, 26), NEHALEM = PCM_CPU_FAMILY_MODEL(6, 30), ATOM = PCM_CPU_FAMILY_MODEL(6, 28), @@ -2085,7 +2093,6 @@ class PCM_API PCM //! \note On Linux: Uses /sys/devices/system/node/nodeX/cpulist //! \note On Windows: Uses GetLogicalProcessorInformationEx (may have limitations with multi-group processors) //! \note On FreeBSD: Uses vm.ndomains and cpuset_getdomain (FreeBSD 12.0+) - //! \note On macOS: Not implemented, returns -1 int32 mapNUMANodeToSocket(uint32 numa_node_id) const; size_t getNumCXLPorts(uint32 socket) const @@ -2113,6 +2120,7 @@ class PCM_API PCM case NEHALEM_EX: case WESTMERE_EX: return 4; + case CWF: case JAKETOWN: case IVYTOWN: case HASWELLX: @@ -2143,6 +2151,7 @@ class PCM_API PCM case NEHALEM_EX: case WESTMERE_EX: return 2; + case CWF: case JAKETOWN: case IVYTOWN: case HASWELLX: @@ -2174,6 +2183,7 @@ class PCM_API PCM case NEHALEM_EX: case WESTMERE_EX: return 4; + case CWF: case JAKETOWN: case IVYTOWN: case HASWELLX: @@ -2208,6 +2218,7 @@ class PCM_API PCM case NEHALEM_EX: case WESTMERE_EX: return 4; + case CWF: case JAKETOWN: case IVYTOWN: case HASWELLX: @@ -2256,6 +2267,8 @@ class PCM_API PCM case ARL: case PTL: return 12; + case CWF: + return 8; case SNOWRIDGE: case ELKHART_LAKE: case JASPER_LAKE: @@ -2326,6 +2339,7 @@ class PCM_API PCM { switch (cpu_family_model) { + case CWF: case NEHALEM_EP: case NEHALEM_EX: case WESTMERE_EP: @@ -2585,6 +2599,7 @@ class PCM_API PCM { return ( cpu_family_model == PCM::JAKETOWN + || cpu_family_model == PCM::CWF || cpu_family_model == PCM::IVYTOWN || cpu_family_model == PCM::SANDY_BRIDGE || cpu_family_model == PCM::IVY_BRIDGE @@ -2625,6 +2640,7 @@ class PCM_API PCM { return ( cpu_family_model == PCM::JAKETOWN + || cpu_family_model == PCM::CWF || cpu_family_model == PCM::IVYTOWN || cpu_family_model == PCM::HASWELLX || cpu_family_model == PCM::BDX_DE @@ -2671,6 +2687,7 @@ class PCM_API PCM return getQPILinksPerSocket() > 0 && ( cpu_family_model == PCM::NEHALEM_EX + || cpu_family_model == PCM::CWF || cpu_family_model == PCM::WESTMERE_EX || cpu_family_model == PCM::JAKETOWN || cpu_family_model == PCM::IVYTOWN @@ -2690,6 +2707,7 @@ class PCM_API PCM return getQPILinksPerSocket() > 0 && ( cpu_family_model == PCM::NEHALEM_EX + || cpu_family_model == PCM::CWF || cpu_family_model == PCM::WESTMERE_EX || cpu_family_model == PCM::JAKETOWN || cpu_family_model == PCM::IVYTOWN @@ -2705,6 +2723,7 @@ class PCM_API PCM bool localMemoryRequestRatioMetricAvailable() const { return cpu_family_model == PCM::HASWELLX + || cpu_family_model == PCM::CWF || cpu_family_model == PCM::BDX || cpu_family_model == PCM::SKX || cpu_family_model == PCM::ICX @@ -2726,6 +2745,7 @@ class PCM_API PCM cpu_family_model == PCM::SRF || cpu_family_model == PCM::GNR || cpu_family_model == PCM::GNR_D + || cpu_family_model == PCM::CWF ); } @@ -2769,12 +2789,14 @@ class PCM_API PCM || cpu_family_model == PCM::SRF || cpu_family_model == PCM::GNR || cpu_family_model == PCM::GNR_D + || cpu_family_model == PCM::CWF ); } bool uncoreFrequencyMetricAvailable() const { return MSR.empty() == false + && PCM::CWF != cpu_family_model && getMaxNumOfUncorePMUs(UBOX_PMU_ID) > 0ULL && getNumCores() == getNumOnlineCores() && PCM::GNR != cpu_family_model @@ -2785,6 +2807,10 @@ class PCM_API PCM bool LatencyMetricsAvailable() const { + // Note: GNR/GNR-D are included (P-core: L1D_PEND_MISS.PENDING / MEM_LOAD_RETIRED + // encodings match), but the E-core parts SRF/CWF are intentionally excluded since + // they lack the L1D_PEND_MISS fill-buffer-occupancy event and use different + // MEM_LOAD_UOPS_RETIRED umasks. return ( cpu_family_model == PCM::HASWELLX || cpu_family_model == PCM::BDX @@ -2792,6 +2818,8 @@ class PCM_API PCM || cpu_family_model == PCM::ICX || cpu_family_model == PCM::SPR || cpu_family_model == PCM::EMR + || cpu_family_model == PCM::GNR + || cpu_family_model == PCM::GNR_D || useSKLPath() ); } @@ -2803,6 +2831,10 @@ class PCM_API PCM || cpu_family_model == PCM::ICX || cpu_family_model == PCM::SPR || cpu_family_model == PCM::EMR + || cpu_family_model == PCM::GNR + || cpu_family_model == PCM::GNR_D + || cpu_family_model == PCM::SRF + || cpu_family_model == PCM::CWF ); } @@ -2863,6 +2895,7 @@ class PCM_API PCM { return ( cpu_family_model == PCM::JAKETOWN + || cpu_family_model == PCM::CWF || cpu_family_model == PCM::SNOWRIDGE || cpu_family_model == PCM::IVYTOWN || cpu_family_model == PCM::HASWELLX @@ -2891,6 +2924,7 @@ class PCM_API PCM { return ( cpu_family_model_ == PCM::SKX + || cpu_family_model_ == PCM::CWF || cpu_family_model_ == PCM::ICX || cpu_family_model_ == PCM::SPR || cpu_family_model_ == PCM::EMR @@ -2916,6 +2950,7 @@ class PCM_API PCM { return ( cpu_family_model == PCM::SKX + || cpu_family_model == PCM::CWF || cpu_family_model == PCM::ICX || cpu_family_model == PCM::SPR || cpu_family_model == PCM::EMR @@ -4520,6 +4555,7 @@ uint64 getL2CacheMisses(const CounterStateType & before, const CounterStateType || cpu_family_model == PCM::LNL || cpu_family_model == PCM::ARL || cpu_family_model == PCM::PTL + || cpu_family_model == PCM::CWF ) { return after.Event[BasicCounterState::SKLL2MissPos] - before.Event[BasicCounterState::SKLL2MissPos]; } @@ -4637,6 +4673,7 @@ uint64 getL3CacheHitsSnoop(const CounterStateType & before, const CounterStateTy || cpu_family_model == PCM::LNL || cpu_family_model == PCM::ARL || cpu_family_model == PCM::PTL + || cpu_family_model == PCM::CWF ) { const int64 misses = getL3CacheMisses(before, after); diff --git a/src/event-resolver.cpp b/src/event-resolver.cpp new file mode 100644 index 00000000..36094123 --- /dev/null +++ b/src/event-resolver.cpp @@ -0,0 +1,606 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2026, Intel Corporation + +#include "event-resolver.h" +#include "utils.h" +#include "debug.h" + +#include +#include +#include +#include +#include + +namespace pcm { + +std::string PerfmonEventResolver::findPerfmonPath(const std::string& programPath) +{ + const std::string marker = "mapfile.csv"; + + // 1. Next to the binary (build output: bin/perfmon/) + size_t lastSlash = programPath.find_last_of("/\\"); + std::string binDir = (lastSlash != std::string::npos) ? programPath.substr(0, lastSlash) : "."; + std::string candidate = binDir + "/perfmon"; + if (std::ifstream(candidate + "/" + marker).good()) return candidate; + + // 2. Install path + candidate = getInstallPathPrefix() + "perfmon"; + if (std::ifstream(candidate + "/" + marker).good()) return candidate; + + return "."; +} + +const std::map PerfmonEventResolver::s_pmuNameMap = { + {"cbo", "cha"}, + {"b2cmi", "m2m"}, + {"upi", "xpi"}, + {"upi ll", "xpi"}, + {"b2upi", "m3upi"}, + {"qpi", "xpi"}, + {"qpi ll", "xpi"} +}; + +void PerfmonEventResolver::addLocalEvents(const std::vector>& events) +{ + for (const auto& [name, fields] : events) + m_localEvents[name] = fields; +} + +#ifdef PCM_SIMDJSON_AVAILABLE + +static void lowerCase(std::string& str) +{ + std::transform(str.begin(), str.end(), str.begin(), [](unsigned char c) + { +#ifdef _MSC_VER + return std::tolower(c, std::locale()); +#else + return std::tolower(c); +#endif + }); +} + +bool PerfmonEventResolver::init(const std::string& cpuFamilyModel, const std::string& eventFilePrefix) +{ + // getCPUFamilyModelString() includes stepping (e.g. "GenuineIntel-6-6A-0"). + auto lastDash = cpuFamilyModel.rfind('-'); + const std::string cpuBase = (lastDash != std::string::npos) ? cpuFamilyModel.substr(0, lastDash) : cpuFamilyModel; + const int stepping = (lastDash != std::string::npos) ? std::stoi(cpuFamilyModel.substr(lastDash + 1)) : 0; + + if (!loadPerfmonEvents(cpuFamilyModel, eventFilePrefix)) return false; + + if (!loadPMUDeclarations(cpuBase, stepping, eventFilePrefix)) return false; + + m_initialized = true; + return true; +} + +bool PerfmonEventResolver::parseTSV(const std::string& path) +{ + std::ifstream inFile(path); + if (!inFile.is_open()) return false; + + std::string line; + bool colNamesParsed = false; + int eventNamePos = -1; + std::unordered_map> eventMap; + + while (std::getline(inFile, line)) + { + if (line.size() == 1 && line[0] == '\n') continue; + + // Trim whitespace + auto wsLeft = line.find_first_not_of(' '); + auto wsRight = line.find_last_not_of(' '); + if (wsLeft == std::string::npos) continue; + line = line.substr(wsLeft, wsRight - wsLeft + 1); + + if (line[0] == '#') continue; + + if (!colNamesParsed) + { + std::vector colNames = split(line, '\t'); + eventMap["COL_NAMES"] = colNames; + auto it = std::find(colNames.begin(), colNames.end(), "EventName"); + if (it == colNames.end()) + { + std::cerr << "ERROR: First row does not contain EventName\n"; + return false; + } + eventNamePos = static_cast(it - colNames.begin()); + colNamesParsed = true; + continue; + } + std::vector entry = split(line, '\t'); + if (eventNamePos < static_cast(entry.size())) + eventMap[entry[eventNamePos]] = entry; + } + m_eventMapsTSV.push_back(std::move(eventMap)); + return true; +} + +bool PerfmonEventResolver::parseMapfile(const std::string& cpuFamilyModel, const std::string& prefix, + std::multimap& eventFiles) +{ + const std::string mapfilePath = prefix + "/mapfile.csv"; + + std::ifstream in(mapfilePath); + if (!in.is_open()) + { + std::cerr << "ERROR: File " << mapfilePath << " can't be opened.\n"; + std::cerr << " use -ep option to specify the perfmon directory,\n"; + std::cerr << " or run 'git clone https://github.com/intel/perfmon' to download the perfmon event repository\n"; + return false; + } + + std::string line; + int32 fmsPos = -1, filenamePos = -1, eventTypePos = -1; + + if (std::getline(in, line)) + { + auto header = split(line, ','); + for (int32 i = 0; i < static_cast(header.size()); ++i) + { + if (header[i] == "Family-model") fmsPos = i; + else if (header[i] == "Filename") filenamePos = i; + else if (header[i] == "EventType") eventTypePos = i; + } + } + else + { + std::cerr << "ERROR: Can't read first line from mapfile.csv\n"; + return false; + } + + if (fmsPos < 0 || filenamePos < 0 || eventTypePos < 0) + { + std::cerr << "ERROR: mapfile.csv header missing required columns\n"; + return false; + } + + bool headerPrinted = false; + while (std::getline(in, line)) + { + auto tokens = split(line, ','); + const int32 tokenCount = static_cast(tokens.size()); + if (fmsPos >= tokenCount || filenamePos >= tokenCount || eventTypePos >= tokenCount) + { + std::cerr << "WARNING: skipping malformed mapfile.csv line: " << line << "\n"; + continue; + } + + std::regex fmsRegex(tokens[fmsPos]); + std::cmatch fmsMatch; + if (std::regex_search(cpuFamilyModel.c_str(), fmsMatch, fmsRegex)) + { + if (!headerPrinted) + { + std::cerr << "Matched event files:\n"; + headerPrinted = true; + } + std::cerr << tokens[fmsPos] << " " << tokens[eventTypePos] << " " << tokens[filenamePos] << "\n"; + eventFiles.insert(std::make_pair(tokens[eventTypePos], tokens[filenamePos])); + } + } + + if (eventFiles.empty()) + { + std::cerr << "ERROR: CPU " << cpuFamilyModel << " not found in mapfile.csv\n"; + return false; + } + return true; +} + +bool PerfmonEventResolver::loadEventFile(const std::string& eventType, const std::string& filename, const std::string& prefix) +{ + if (eventType != "core" && eventType != "uncore" && eventType != "uncore experimental") + return true; + + const std::string path1 = prefix + filename; + const std::string path2 = prefix + filename.substr(filename.rfind('/')); + const std::string path3 = getInstallPathPrefix() + "perfmon" + filename; + + std::string path; + if (std::ifstream(path1).good()) + path = path1; + else if (std::ifstream(path2).good()) + path = path2; + else if (std::ifstream(path3).good()) + path = path3; + else + { + std::cerr << "ERROR: Can't open event file at " << path1 << " or " << path2 << " or " << path3 << "\n"; + std::cerr << "Make sure you have downloaded " << filename + << " from https://raw.githubusercontent.com/intel/perfmon/main" + << filename << "\n"; + return false; + } + + try + { + if (path.find(".json") != std::string::npos) + { + m_jsonParsers.push_back(std::make_shared()); + auto jsonObjects = m_jsonParsers.back()->load(path); + if (jsonObjects["Header"].error() != simdjson::NO_SUCH_FIELD) jsonObjects = jsonObjects["Events"]; + + for (simdjson::dom::object eventObj : jsonObjects) + { + const std::string eventName{eventObj["EventName"].get_c_str()}; + if (!eventName.empty()) m_eventMapJSON[eventName] = eventObj; + } + } + else if (path.find(".tsv") != std::string::npos) + { + if (!parseTSV(path)) return false; + } + } + catch (std::exception& e) + { + std::cerr << "Error while parsing " << path << ": " << e.what() << "\n"; + return false; + } + return true; +} + +bool PerfmonEventResolver::loadPerfmonEvents(const std::string& cpuFamilyModel, const std::string& prefix) +{ + std::multimap eventFiles; + if (!parseMapfile(cpuFamilyModel, prefix, eventFiles)) return false; + + for (const auto& [eventType, filename] : eventFiles) + { + if (!loadEventFile(eventType, filename, prefix)) return false; + } + + return !m_eventMapJSON.empty() || !m_eventMapsTSV.empty(); +} + +bool PerfmonEventResolver::loadPMUDeclarations(const std::string& cpuFamilyModel, int stepping, const std::string& prefix) +{ + // Extract family and model from the family-model string for stepping iteration + // Format: "GenuineIntel-6-6A" -> we append "-" + std::string path; + std::string errMsg; + + // Normalize: strip trailing slashes so find_last_of correctly finds the parent separator + std::string normalizedPrefix = prefix; + while (!normalizedPrefix.empty() && normalizedPrefix.back() == '/') + normalizedPrefix.pop_back(); + + size_t lastSlash = normalizedPrefix.find_last_of('/'); + std::string baseDir = (lastSlash != std::string::npos) ? normalizedPrefix.substr(0, lastSlash) : "."; + + for (int s = stepping; s >= 0; --s) + { + const std::string relPath = "PMURegisterDeclarations/" + cpuFamilyModel + "-" + std::to_string(s) + ".json"; + + const std::string candidates[] = { + baseDir + "/" + relPath, // sibling of perfmon dir (build output / install) + normalizedPrefix + "/" + relPath, // inside the provided prefix + relPath, // relative to CWD + getInstallPathPrefix() + relPath, // system install path + }; + + for (const auto& declPath : candidates) + { + std::ifstream in(declPath); + if (in.is_open()) + { + path = declPath; + in.close(); + break; + } + } + if (!path.empty()) break; + + errMsg = "PMURegisterDeclarations file not found for " + cpuFamilyModel + " stepping " + std::to_string(s); + } + + if (path.empty()) + { + std::cerr << "ERROR: " << errMsg << "\n"; + return false; + } + + try + { + m_jsonParsers.push_back(std::make_shared()); + m_pmuDeclarations = std::make_shared(); + *m_pmuDeclarations = m_jsonParsers.back()->load(path); + m_pmuDeclPath = path; + } + catch (std::exception& e) + { + std::cerr << "Error while parsing " << path << ": " << e.what() << "\n"; + return false; + } + return true; +} + +bool PerfmonEventResolver::isEvent(const std::string& eventName) const +{ + if (m_localEvents.find(eventName) != m_localEvents.end()) return true; + + if (m_eventMapJSON.find(eventName) != m_eventMapJSON.end()) return true; + + for (const auto& tsvMap : m_eventMapsTSV) + { + if (tsvMap.find(eventName) != tsvMap.end()) return true; + } + return false; +} + +bool PerfmonEventResolver::isField(const std::string& eventName, + const std::string& fieldName) const +{ + auto localIt = m_localEvents.find(eventName); + if (localIt != m_localEvents.end()) + return localIt->second.find(fieldName) != localIt->second.end(); + + auto jsonIt = m_eventMapJSON.find(eventName); + if (jsonIt != m_eventMapJSON.end()) + { + auto fieldResult = jsonIt->second[fieldName]; + return fieldResult.error() != simdjson::NO_SUCH_FIELD; + } + + for (const auto& tsvMap : m_eventMapsTSV) + { + auto eventIt = tsvMap.find(eventName); + if (eventIt != tsvMap.end()) + { + auto colIt = tsvMap.find("COL_NAMES"); + if (colIt == tsvMap.end()) continue; + const auto& colNames = colIt->second; + auto nameIt = std::find(colNames.begin(), colNames.end(), fieldName); + if (nameIt != colNames.end()) + { + size_t pos = nameIt - colNames.begin(); + return pos < eventIt->second.size(); + } + } + } + return false; +} + +std::string PerfmonEventResolver::getField(const std::string& eventName, const std::string& fieldName) const +{ + auto localIt = m_localEvents.find(eventName); + if (localIt != m_localEvents.end()) + { + auto fieldIt = localIt->second.find(fieldName); + return (fieldIt != localIt->second.end()) ? fieldIt->second : ""; + } + + auto jsonIt = m_eventMapJSON.find(eventName); + if (jsonIt != m_eventMapJSON.end()) + { + auto fieldResult = jsonIt->second[fieldName]; + if (fieldResult.error() == simdjson::NO_SUCH_FIELD) return ""; + return std::string(fieldResult.get_c_str()); + } + + for (const auto& tsvMap : m_eventMapsTSV) + { + auto eventIt = tsvMap.find(eventName); + if (eventIt != tsvMap.end()) + { + auto colIt = tsvMap.find("COL_NAMES"); + if (colIt == tsvMap.end()) continue; + const auto& colNames = colIt->second; + auto nameIt = std::find(colNames.begin(), colNames.end(), fieldName); + if (nameIt != colNames.end()) + { + size_t pos = nameIt - colNames.begin(); + if (pos < eventIt->second.size()) return eventIt->second[pos]; + } + } + } + return ""; +} + +std::vector PerfmonEventResolver::getEventNames() const +{ + std::vector names; + names.reserve(m_eventMapJSON.size()); + for (const auto& [event, _] : m_eventMapJSON) names.push_back(event); + + for (const auto& tsvMap : m_eventMapsTSV) + { + for (const auto& [event, _] : tsvMap) + { + if (event != "COL_NAMES") names.push_back(event); + } + } + return names; +} + +std::vector> PerfmonEventResolver::getEventFields(const std::string& eventName) const +{ + std::vector> fields; + auto jsonIt = m_eventMapJSON.find(eventName); + if (jsonIt != m_eventMapJSON.end()) + { + for (const auto& kv : jsonIt->second) + { + std::string key{kv.key.begin(), kv.key.end()}; + std::string_view val; + if (!kv.value.get(val)) + fields.push_back({key, std::string(val)}); + else + fields.push_back({key, ""}); + } + return fields; + } + + for (const auto& tsvMap : m_eventMapsTSV) + { + auto eventIt = tsvMap.find(eventName); + if (eventIt != tsvMap.end()) + { + auto colIt = tsvMap.find("COL_NAMES"); + if (colIt == tsvMap.end()) continue; + const auto& colNames = colIt->second; + for (size_t i = 0; i < colNames.size() && i < eventIt->second.size(); ++i) + fields.push_back({colNames[i], eventIt->second[i]}); + return fields; + } + } + return fields; +} + +std::string PerfmonEventResolver::mapPMUName(const std::string& unit) const +{ + std::string lower = unit; + lowerCase(lower); + auto it = s_pmuNameMap.find(lower); + return (it != s_pmuNameMap.end()) ? it->second : lower; +} + +bool PerfmonEventResolver::resolveEvent(const std::string& eventName, std::string& pmuName, PCM::RawEventConfig& config) const +{ + if (!m_initialized || !isEvent(eventName)) return false; + + config = PCM::RawEventConfig{{0, 0, 0, 0, 0}, eventName}; + + // Determine PMU name from Unit field + pmuName = !isField(eventName, "Unit") ? "core" : mapPMUName(getField(eventName, "Unit")); + + // Look up PMU register declarations + auto pmuObj = (*m_pmuDeclarations)[pmuName]; + if (pmuObj.error() == simdjson::NO_SUCH_FIELD) + { + std::cerr << "ERROR: PMU \"" << pmuName << "\" not found in PMURegisterDeclarations for event " << eventName << "\n"; + return false; + } + + simdjson::dom::object pmuDeclObj; + try + { + pmuDeclObj = (*m_pmuDeclarations)[pmuName]["programmable"].get_object(); + } + catch (const std::exception& e) + { + std::cerr << "ERROR: No programmable section for PMU \"" << pmuName << "\": " << e.what() << "\n"; + return false; + } + + auto setConfig = [](PCM::RawEventConfig& cfg, const simdjson::dom::object& fieldDesc, uint64 value, int64_t position) + { + const auto cfgIdx = uint64_t(fieldDesc["Config"]); + if (cfgIdx >= cfg.first.size()) + throw std::runtime_error("Config field value is out of bounds"); + const auto width = uint64_t(fieldDesc["Width"]); + cfg.first[cfgIdx] = insertBits(cfg.first[cfgIdx], value, position, width); + }; + + for (const auto& registerKeyValue : pmuDeclObj) + { + simdjson::dom::object fieldDesc = registerKeyValue.value; + const std::string fieldName{registerKeyValue.key.begin(), registerKeyValue.key.end()}; + + if (fieldName == "MSRIndex") + { + std::string msrIndexStr = getField(eventName, fieldName); + if (msrIndexStr.empty()) continue; + lowerCase(msrIndexStr); + if (msrIndexStr == "0" || msrIndexStr == "0x00") continue; + + // Use first MSR index if comma-separated + auto msrIndexes = split(msrIndexStr, ','); + if (msrIndexes.empty()) continue; + std::string selectedMsr = msrIndexes[0]; + + try + { + simdjson::dom::object msrObject = registerKeyValue.value[selectedMsr]; + std::string msrValueStr = getField(eventName, "MSRValue"); + if (!msrValueStr.empty()) + { + const auto value = read_number(msrValueStr.c_str()); + const auto position = int64_t(msrObject["Position"]); + setConfig(config, msrObject, value, position); + } + } + catch (std::exception&) + { + // MSR sub-key not found in declarations, skip + } + continue; + } + + const int64_t position = int64_t(fieldDesc["Position"]); + if (position == -1) continue; // field ignored per declarations + + if (!isField(eventName, fieldName)) + { + // Use DefaultValue if available + if (fieldDesc["DefaultValue"].error() == simdjson::NO_SUCH_FIELD) + { + std::cerr << "ERROR: DefaultValue not provided for field \"" << fieldName << "\" in PMURegisterDeclarations\n"; + return false; + } + const auto cfgIdx = uint64_t(fieldDesc["Config"]); + if (cfgIdx >= config.first.size()) + throw std::runtime_error("Config field value is out of bounds"); + config.first[cfgIdx] |= uint64_t(fieldDesc["DefaultValue"]) << position; + } + else + { + std::string fieldValueStr = getField(eventName, fieldName); + // Remove double quotes and use first value if comma-separated + fieldValueStr.erase( + std::remove(fieldValueStr.begin(), fieldValueStr.end(), '\"'), + fieldValueStr.end()); + auto fieldValues = split(fieldValueStr, ','); + if (fieldValues.empty()) continue; + setConfig(config, fieldDesc, read_number(fieldValues[0].c_str()), position); + } + } + return true; +} + +#else // !PCM_SIMDJSON_AVAILABLE + +bool PerfmonEventResolver::init(const std::string&, const std::string&) +{ + return false; +} + +bool PerfmonEventResolver::isEvent(const std::string& eventName) const +{ + return m_localEvents.find(eventName) != m_localEvents.end(); +} + +bool PerfmonEventResolver::isField(const std::string& eventName, const std::string& fieldName) const +{ + auto localIt = m_localEvents.find(eventName); + if (localIt != m_localEvents.end()) + return localIt->second.find(fieldName) != localIt->second.end(); + return false; +} + +std::string PerfmonEventResolver::getField(const std::string& eventName, const std::string& fieldName) const +{ + auto localIt = m_localEvents.find(eventName); + if (localIt != m_localEvents.end()) + { + auto fieldIt = localIt->second.find(fieldName); + return (fieldIt != localIt->second.end()) ? fieldIt->second : ""; + } + return ""; +} + +std::string PerfmonEventResolver::mapPMUName(const std::string& unit) const { return unit; } +std::vector PerfmonEventResolver::getEventNames() const { return {}; } +std::vector> PerfmonEventResolver::getEventFields(const std::string&) const { return {}; } + +bool PerfmonEventResolver::resolveEvent(const std::string&, std::string&, PCM::RawEventConfig&) const +{ + return false; +} + +#endif // PCM_SIMDJSON_AVAILABLE + +} // namespace pcm diff --git a/src/event-resolver.h b/src/event-resolver.h new file mode 100644 index 00000000..7b838c95 --- /dev/null +++ b/src/event-resolver.h @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2026, Intel Corporation +#pragma once + +#include +#include +#include +#include +#include +#include "cpucounters.h" + +#ifdef PCM_SIMDJSON_AVAILABLE +#include "simdjson.h" +#endif + +namespace pcm { + +// Field name → value map for a locally defined event +using LocalEvent = std::unordered_map; + +class PerfmonEventResolver { +public: + // Search for the perfmon directory containing mapfile.csv. + // Checks: next to programPath binary, then install prefix. + // Falls back to "." (the current working directory) if no earlier location matches. + static std::string findPerfmonPath(const std::string& programPath); + + // Initialize from explicit CPU identification. + // eventFilePrefix must point to a directory containing mapfile.csv and PMURegisterDeclarations/. + bool init(const std::string& cpuFamilyModel, const std::string& eventFilePrefix); + + // Register local events (from metrics.json "events" array). + // Local events take priority over perfmon database events. + void addLocalEvents(const std::vector>& events); + + // Query interface (for event validation) + bool isEvent(const std::string& eventName) const; + bool isField(const std::string& eventName, const std::string& fieldName) const; + std::string getField(const std::string& eventName, const std::string& fieldName) const; + + // Map PMU unit name to PMURegisterDeclarations key (e.g. "cbo" -> "cha") + std::string mapPMUName(const std::string& unit) const; + + // Resolve event name to PMU name + raw config (for PMU programming) + bool resolveEvent(const std::string& eventName, std::string& pmuName, PCM::RawEventConfig& config) const; + + // Enumeration interface (for listing events) + std::vector getEventNames() const; + std::vector> getEventFields(const std::string& eventName) const; + + // Access PMU register declarations (for advanced event programming in pcm-raw) +#ifdef PCM_SIMDJSON_AVAILABLE + const simdjson::dom::element* getPMUDeclarations() const { return m_pmuDeclarations.get(); } +#endif + const std::string& getPMUDeclarationsPath() const { return m_pmuDeclPath; } + + bool isInitialized() const { return m_initialized; } +private: + +#ifdef PCM_SIMDJSON_AVAILABLE + bool loadPerfmonEvents(const std::string& cpuFamilyModel, const std::string& prefix); + bool parseMapfile(const std::string& cpuFamilyModel, const std::string& prefix, + std::multimap& eventFiles); + bool loadEventFile(const std::string& eventType, const std::string& filename, const std::string& prefix); + bool loadPMUDeclarations(const std::string& cpuFamilyModel, int stepping, const std::string& prefix); + bool parseTSV(const std::string& path); + + std::unordered_map m_eventMapJSON; + std::vector>> m_eventMapsTSV; + std::shared_ptr m_pmuDeclarations; + std::vector> m_jsonParsers; +#endif + + bool m_initialized = false; + std::string m_pmuDeclPath; + static const std::map s_pmuNameMap; + std::unordered_map m_localEvents; +}; + +} // namespace pcm diff --git a/src/metrics/icelake-sp/io.json b/src/metrics/icelake-sp/io.json new file mode 100644 index 00000000..72c395b7 --- /dev/null +++ b/src/metrics/icelake-sp/io.json @@ -0,0 +1,127 @@ +{ + "metrics": [ + { + "name": "PCIRdCur (B)", + "formula": "(UNC_CHA_TOR_INSERTS.IO_MISS_PCIRDCUR + UNC_CHA_TOR_INSERTS.IO_HIT_PCIRDCUR) * 64", + "short_name": "PCIRdCur", + "aggregation": "socket", + "description": "Total PCIe read bytes, calculated as the sum of PCIe full cache line read misses and hits multiplied by 64 bytes per cache line." + }, + { + "name": "PCIRdCur Miss (B)", + "formula": "UNC_CHA_TOR_INSERTS.IO_MISS_PCIRDCUR * 64", + "short_name": "PCIRdCur Miss", + "aggregation": "socket", + "description": "Total PCIe read bytes due to full cache line read misses multiplied by 64 bytes per cache line." + }, + { + "name": "PCIRdCur Hit (B)", + "formula": "UNC_CHA_TOR_INSERTS.IO_HIT_PCIRDCUR * 64", + "short_name": "PCIRdCur Hit", + "aggregation": "socket", + "description": "Total PCIe read bytes due to full cache line read hits multiplied by 64 bytes per cache line." + }, + { + "name": "ItoM (B)", + "formula": "(UNC_CHA_TOR_INSERTS.IO_MISS_ITOM + UNC_CHA_TOR_INSERTS.IO_HIT_ITOM) * 64", + "short_name": "ItoM", + "aggregation": "socket", + "description": "Total PCIe write bytes, calculated as the sum of PCIe full cache line write misses and hits multiplied by 64 bytes per cache line." + }, + { + "name": "ItoM Miss (B)", + "formula": "UNC_CHA_TOR_INSERTS.IO_MISS_ITOM * 64", + "short_name": "ItoM Miss", + "aggregation": "socket", + "description": "Total PCIe write bytes due to full cache line write misses multiplied by 64 bytes per cache line." + }, + { + "name": "ItoM Hit (B)", + "formula": "UNC_CHA_TOR_INSERTS.IO_HIT_ITOM * 64", + "short_name": "ItoM Hit", + "aggregation": "socket", + "description": "Total PCIe write bytes due to full cache line write hits multiplied by 64 bytes per cache line." + }, + { + "name": "ItoMCacheNear (B)", + "formula": "(UNC_CHA_TOR_INSERTS.IO_MISS_ITOMCACHENEAR + UNC_CHA_TOR_INSERTS.IO_HIT_ITOMCACHENEAR) * 64", + "short_name": "ItoMCacheNear", + "aggregation": "socket", + "description": "Total PCIe write bytes for partial cache line writes, calculated as the sum of partial write misses and hits multiplied by 64 bytes per cache line." + }, + { + "name": "ItoMCacheNear Miss (B)", + "formula": "UNC_CHA_TOR_INSERTS.IO_MISS_ITOMCACHENEAR * 64", + "short_name": "ItoMCacheNear Miss", + "aggregation": "socket", + "description": "Total PCIe write bytes for partial cache line write misses multiplied by 64 bytes per cache line." + }, + { + "name": "ItoMCacheNear Hit (B)", + "formula": "UNC_CHA_TOR_INSERTS.IO_HIT_ITOMCACHENEAR * 64", + "short_name": "ItoMCacheNear Hit", + "aggregation": "socket", + "description": "Total PCIe write bytes for partial cache line write hits multiplied by 64 bytes per cache line." + }, + { + "name": "Total Read (B)", + "formula": "(UNC_CHA_TOR_INSERTS.IO_MISS_PCIRDCUR + UNC_CHA_TOR_INSERTS.IO_HIT_PCIRDCUR) * 64", + "short_name": "Total Read (B)", + "aggregation": "system", + "description": "Total PCIe read bytes system-wide, calculated as the sum of PCIe full cache line read misses and hits multiplied by 64 bytes per cache line." + }, + { + "name": "Total Write (B)", + "formula": "(UNC_CHA_TOR_INSERTS.IO_MISS_ITOM + UNC_CHA_TOR_INSERTS.IO_HIT_ITOM + UNC_CHA_TOR_INSERTS.IO_MISS_ITOMCACHENEAR + UNC_CHA_TOR_INSERTS.IO_HIT_ITOMCACHENEAR) * 64", + "short_name": "Total Write (B)", + "aggregation": "system", + "description": "Total PCIe write bytes system-wide, calculated as the sum of all PCIe write misses and hits (both full and partial cache line) multiplied by 64 bytes per cache line." + }, + { + "name": "WCILF", + "formula": "UNC_CHA_TOR_INSERTS.IA_WCILF", + "short_name": "WCILF", + "aggregation": "socket", + "description": "Number of full cache line write requests issued by CPU to IO device. Includes MOVDIR64." + }, + { + "name": "WCIL", + "formula": "UNC_CHA_TOR_INSERTS.IA_WCIL", + "short_name": "WCIL", + "aggregation": "socket", + "description": "Number of partial cache line write requests issued by CPU to IO device. Includes MOVDIRI." + }, + { + "name": "WiL Miss", + "formula": "UNC_CHA_TOR_INSERTS.IA_MISS_WIL", + "short_name": "WiL Miss", + "aggregation": "socket", + "description": "Number of WiL (Write Invalidate Line) requests issued by CPU that missed cache." + }, + { + "name": "UCRdF Miss", + "formula": "UNC_CHA_TOR_INSERTS.IA_MISS_UCRDF", + "short_name": "UCRdF Miss", + "aggregation": "socket", + "description": "Number of UCRdF (Uncached Read Full) requests issued by CPU that missed cache." + } + ], + "layout": { + "sections": [ + { + "title": "PCIe Data", + "rows": ["Total", "Miss", "Hit"], + "columns": { + "PCIRdCur (B)": ["PCIRdCur (B)", "PCIRdCur Miss (B)", "PCIRdCur Hit (B)"], + "ItoM (B)": ["ItoM (B)", "ItoM Miss (B)", "ItoM Hit (B)"], + "ItoMCacheNear (B)": ["ItoMCacheNear (B)", "ItoMCacheNear Miss (B)", "ItoMCacheNear Hit (B)"] + }, + "system-wide-metrics": ["Total Read (B)", "Total Write (B)"] + }, + { + "title": "MMIO Access", + "metrics": ["WCILF", "WCIL", "WiL Miss", "UCRdF Miss"] + } + ] + } +} diff --git a/src/mmio.cpp b/src/mmio.cpp index 46a5a2a6..db11c27d 100644 --- a/src/mmio.cpp +++ b/src/mmio.cpp @@ -177,63 +177,6 @@ OwnMMIORange::~OwnMMIORange() CloseHandle(hDriver); } -#elif __APPLE__ - -#include "PCIDriverInterface.h" - -MMIORange::MMIORange(const uint64 physical_address, const uint64 size_, const bool, const bool silent_, const int core_) : - mmapAddr(NULL), - size(size_), - silent(silent_), - core(core_) -{ - if (core_ >= 0) - { - throw std::runtime_error("MMIORange on MacOSX does not support core affinity"); - } - if (size > 4096) - { - if (!silent) - { - std::cerr << "PCM Error: the driver does not support mapping of regions > 4KB\n"; - } - return; - } - if (physical_address) { - PCIDriver_mapMemory((uint32_t)physical_address, (uint8_t **)&mmapAddr); - } -} - -uint32 MMIORange::read32(uint64 offset) -{ - warnAlignment<4>("MMIORange::read32", silent, offset); - uint32 val = 0; - PCIDriver_readMemory32((uint8_t *)mmapAddr + offset, &val); - return val; -} - -uint64 MMIORange::read64(uint64 offset) -{ - warnAlignment<8>("MMIORange::read64", silent, offset); - uint64 val = 0; - PCIDriver_readMemory64((uint8_t *)mmapAddr + offset, &val); - return val; -} - -void MMIORange::write32(uint64 offset, uint32 val) -{ - std::cerr << "PCM Error: the driver does not support writing to MMIORange\n"; -} -void MMIORange::write64(uint64 offset, uint64 val) -{ - std::cerr << "PCM Error: the driver does not support writing to MMIORange\n"; -} - -MMIORange::~MMIORange() -{ - if(mmapAddr) PCIDriver_unmapMemory((uint8_t *)mmapAddr); -} - #elif defined(__linux__) || defined(__FreeBSD__) || defined(__DragonFly__) MMIORange::MMIORange(const uint64 baseAddr_, const uint64 size_, const bool readonly_, const bool silent_, const int core_) : diff --git a/src/mmio.h b/src/mmio.h index d2c10281..7853f929 100644 --- a/src/mmio.h +++ b/src/mmio.h @@ -160,18 +160,14 @@ class MMIORange } }; -#elif defined(__APPLE__) || defined(__linux__) || defined(__FreeBSD__) || defined(__DragonFly__) +#elif defined(__linux__) || defined(__FreeBSD__) || defined(__DragonFly__) class MMIORange { -#ifndef __APPLE__ int32 fd; -#endif char * mmapAddr; const uint64 size; -#ifndef __APPLE__ const bool readonly; -#endif const bool silent; const int core; MMIORange(const MMIORange &) = delete; diff --git a/src/msr.cpp b/src/msr.cpp index 2709c5b0..9f33711f 100644 --- a/src/msr.cpp +++ b/src/msr.cpp @@ -101,65 +101,6 @@ int32 MsrHandle::read(uint64 msr_number, uint64 * value) return status ? sizeof(uint64) : 0; } -#elif __APPLE__ -// OSX Version - -MSRAccessor * MsrHandle::driver = NULL; -int MsrHandle::num_handles = 0; - -MsrHandle::MsrHandle(uint32 cpu) -{ - cpu_id = cpu; - if (!driver) - { - driver = new MSRAccessor(); - MsrHandle::num_handles = 1; - } - else - { - MsrHandle::num_handles++; - } -} - -MsrHandle::~MsrHandle() -{ - MsrHandle::num_handles--; - if (MsrHandle::num_handles == 0) - { - deleteAndNullify(driver); - } -} - -int32 MsrHandle::write(uint64 msr_number, uint64 value) -{ - return driver->write(cpu_id, msr_number, value); -} - -int32 MsrHandle::read(uint64 msr_number, uint64 * value) -{ - return driver->read(cpu_id, msr_number, value); -} - -int32 MsrHandle::buildTopology(uint32 num_cores, void * ptr) -{ - return driver->buildTopology(num_cores, ptr); -} - -uint32 MsrHandle::getNumInstances() -{ - return driver->getNumInstances(); -} - -uint32 MsrHandle::incrementNumInstances() -{ - return driver->incrementNumInstances(); -} - -uint32 MsrHandle::decrementNumInstances() -{ - return driver->decrementNumInstances(); -} - #elif defined(__FreeBSD__) || defined(__DragonFly__) MsrHandle::MsrHandle(uint32 cpu) : fd(-1), cpu_id(cpu) diff --git a/src/msr.h b/src/msr.h index 879a4515..7cfa1157 100644 --- a/src/msr.h +++ b/src/msr.h @@ -16,8 +16,6 @@ #ifdef _MSC_VER #include "windows.h" -#elif __APPLE__ -#include #endif #include "mutex.h" @@ -31,9 +29,6 @@ class MsrHandle { #ifdef _MSC_VER HANDLE hDriver; -#elif __APPLE__ - static MSRAccessor * driver; - static int num_handles; #else int32 fd; #endif @@ -47,12 +42,6 @@ class MsrHandle int32 read(uint64 msr_number, uint64 * value); int32 write(uint64 msr_number, uint64 value); int32 getCoreId() { return (int32)cpu_id; } -#ifdef __APPLE__ - int32 buildTopology(uint32 num_cores, void *); - uint32 getNumInstances(); - uint32 incrementNumInstances(); - uint32 decrementNumInstances(); -#endif virtual ~MsrHandle(); }; @@ -106,36 +95,6 @@ class SafeMsrHandle mutex.unlock(); } -#ifdef __APPLE__ - int32 buildTopology(uint32 num_cores, void * p) - { - if (pHandle) - return pHandle->buildTopology(num_cores, p); - - throw std::exception(); - } - uint32 getNumInstances() - { - if (pHandle) - return pHandle->getNumInstances(); - - throw std::exception(); - } - uint32 incrementNumInstances() - { - if (pHandle) - return pHandle->incrementNumInstances(); - - throw std::exception(); - } - uint32 decrementNumInstances() - { - if (pHandle) - return pHandle->decrementNumInstances(); - - throw std::exception(); - } -#endif virtual ~SafeMsrHandle() { } }; diff --git a/src/opCode-6-221.txt b/src/opCode-6-221.txt new file mode 100644 index 00000000..f43e1df1 --- /dev/null +++ b/src/opCode-6-221.txt @@ -0,0 +1,46 @@ +# Inbound (PCIe device DMA into system) payload events: +# for writes - UNC_IIO_DATA_REQ_OF_CPU.MEM_WRITE.PART[X]; for reads - UNC_IIO_DATA_REQ_OF_CPU.MEM_READ.PART[X] +ctr=0,unit=iio,ev_sel=0x83,umask=0x1,ch_mask=1,fc_mask=0x7,multiplier=4,hname=IB write,vname=Part0 +ctr=1,unit=iio,ev_sel=0x83,umask=0x1,ch_mask=2,fc_mask=0x7,multiplier=4,hname=IB write,vname=Part1 +ctr=0,unit=iio,ev_sel=0x83,umask=0x1,ch_mask=4,fc_mask=0x7,multiplier=4,hname=IB write,vname=Part2 +ctr=1,unit=iio,ev_sel=0x83,umask=0x1,ch_mask=8,fc_mask=0x7,multiplier=4,hname=IB write,vname=Part3 +ctr=0,unit=iio,ev_sel=0x83,umask=0x1,ch_mask=16,fc_mask=0x7,multiplier=4,hname=IB write,vname=Part4 +ctr=1,unit=iio,ev_sel=0x83,umask=0x1,ch_mask=32,fc_mask=0x7,multiplier=4,hname=IB write,vname=Part5 +ctr=0,unit=iio,ev_sel=0x83,umask=0x1,ch_mask=64,fc_mask=0x7,multiplier=4,hname=IB write,vname=Part6 +ctr=1,unit=iio,ev_sel=0x83,umask=0x1,ch_mask=128,fc_mask=0x7,multiplier=4,hname=IB write,vname=Part7 +ctr=0,unit=iio,ev_sel=0x83,umask=0x4,ch_mask=1,fc_mask=0x7,multiplier=4,hname=IB read,vname=Part0 +ctr=1,unit=iio,ev_sel=0x83,umask=0x4,ch_mask=2,fc_mask=0x7,multiplier=4,hname=IB read,vname=Part1 +ctr=0,unit=iio,ev_sel=0x83,umask=0x4,ch_mask=4,fc_mask=0x7,multiplier=4,hname=IB read,vname=Part2 +ctr=1,unit=iio,ev_sel=0x83,umask=0x4,ch_mask=8,fc_mask=0x7,multiplier=4,hname=IB read,vname=Part3 +ctr=0,unit=iio,ev_sel=0x83,umask=0x4,ch_mask=16,fc_mask=0x7,multiplier=4,hname=IB read,vname=Part4 +ctr=1,unit=iio,ev_sel=0x83,umask=0x4,ch_mask=32,fc_mask=0x7,multiplier=4,hname=IB read,vname=Part5 +ctr=0,unit=iio,ev_sel=0x83,umask=0x4,ch_mask=64,fc_mask=0x7,multiplier=4,hname=IB read,vname=Part6 +ctr=1,unit=iio,ev_sel=0x83,umask=0x4,ch_mask=128,fc_mask=0x7,multiplier=4,hname=IB read,vname=Part7 +# Outbound (CPU MMIO to the PCIe device) payload events: +# for writes - UNC_IIO_DATA_REQ_BY_CPU.MEM_WRITE.PART[X]; for reads - UNC_IIO_DATA_REQ_BY_CPU.MEM_READ.PART[X] +ctr=2,unit=iio,ev_sel=0xc0,umask=0x1,ch_mask=1,fc_mask=0x7,multiplier=4,hname=OB write,vname=Part0 +ctr=3,unit=iio,ev_sel=0xc0,umask=0x1,ch_mask=2,fc_mask=0x7,multiplier=4,hname=OB write,vname=Part1 +ctr=2,unit=iio,ev_sel=0xc0,umask=0x1,ch_mask=4,fc_mask=0x7,multiplier=4,hname=OB write,vname=Part2 +ctr=3,unit=iio,ev_sel=0xc0,umask=0x1,ch_mask=8,fc_mask=0x7,multiplier=4,hname=OB write,vname=Part3 +ctr=2,unit=iio,ev_sel=0xc0,umask=0x1,ch_mask=16,fc_mask=0x7,multiplier=4,hname=OB write,vname=Part4 +ctr=3,unit=iio,ev_sel=0xc0,umask=0x1,ch_mask=32,fc_mask=0x7,multiplier=4,hname=OB write,vname=Part5 +ctr=2,unit=iio,ev_sel=0xc0,umask=0x1,ch_mask=64,fc_mask=0x7,multiplier=4,hname=OB write,vname=Part6 +ctr=3,unit=iio,ev_sel=0xc0,umask=0x1,ch_mask=128,fc_mask=0x7,multiplier=4,hname=OB write,vname=Part7 +ctr=2,unit=iio,ev_sel=0xc0,umask=0x4,ch_mask=1,fc_mask=0x7,multiplier=4,hname=OB read,vname=Part0 +ctr=3,unit=iio,ev_sel=0xc0,umask=0x4,ch_mask=2,fc_mask=0x7,multiplier=4,hname=OB read,vname=Part1 +ctr=2,unit=iio,ev_sel=0xc0,umask=0x4,ch_mask=4,fc_mask=0x7,multiplier=4,hname=OB read,vname=Part2 +ctr=3,unit=iio,ev_sel=0xc0,umask=0x4,ch_mask=8,fc_mask=0x7,multiplier=4,hname=OB read,vname=Part3 +ctr=2,unit=iio,ev_sel=0xc0,umask=0x4,ch_mask=16,fc_mask=0x7,multiplier=4,hname=OB read,vname=Part4 +ctr=3,unit=iio,ev_sel=0xc0,umask=0x4,ch_mask=32,fc_mask=0x7,multiplier=4,hname=OB read,vname=Part5 +ctr=2,unit=iio,ev_sel=0xc0,umask=0x4,ch_mask=64,fc_mask=0x7,multiplier=4,hname=OB read,vname=Part6 +ctr=3,unit=iio,ev_sel=0xc0,umask=0x4,ch_mask=128,fc_mask=0x7,multiplier=4,hname=OB read,vname=Part7 +# IOMMU events: +# UNC_IIO_IOMMU0.CTXT_CACHE_LOOKUPS; UNC_IIO_IOMMU0.MISSES; UNC_IIO_IOMMU0.CTXT_CACHE_HITS; UNC_IIO_IOMMU1.SLPWC_256T_HITS; UNC_IIO_IOMMU1.SLPWC_512G_HITS; UNC_IIO_IOMMU1.SLPWC_1G_HITS; UNC_IIO_IOMMU1.SLPWC_2M_HITS; UNC_IIO_IOMMU1.NUM_MEM_ACCESSES +ctr=0,unit=iio,ev_sel=0x40,umask=0x01,ch_mask=0x0,fc_mask=0x0,multiplier=1,hname=IOTLB Lookup,vname=Total +ctr=1,unit=iio,ev_sel=0x40,umask=0x20,ch_mask=0x0,fc_mask=0x0,multiplier=1,hname=IOTLB Miss,vname=Total +ctr=2,unit=iio,ev_sel=0x40,umask=0x80,ch_mask=0x0,fc_mask=0x0,multiplier=1,hname=Ctxt Cache Hit,vname=Total +ctr=3,unit=iio,ev_sel=0x41,umask=0x10,ch_mask=0x0,fc_mask=0x0,multiplier=1,hname=256T Cache Hit,vname=Total +ctr=0,unit=iio,ev_sel=0x41,umask=0x08,ch_mask=0x0,fc_mask=0x0,multiplier=1,hname=512G Cache Hit,vname=Total +ctr=1,unit=iio,ev_sel=0x41,umask=0x04,ch_mask=0x0,fc_mask=0x0,multiplier=1,hname=1G Cache Hit,vname=Total +ctr=2,unit=iio,ev_sel=0x41,umask=0x02,ch_mask=0x0,fc_mask=0x0,multiplier=1,hname=2M Cache Hit,vname=Total +ctr=3,unit=iio,ev_sel=0x41,umask=0xc0,ch_mask=0x0,fc_mask=0x0,multiplier=1,hname=IOMMU Mem Access,vname=Total diff --git a/src/pci.cpp b/src/pci.cpp index 6a008640..0085ebd8 100644 --- a/src/pci.cpp +++ b/src/pci.cpp @@ -495,66 +495,6 @@ static void readSRATTable(std::unordered_map& pciToNuma) DBG(2, "SRAT parsing complete, found ", pciToNuma.size(), " PCI device entries"); } -#elif __APPLE__ - -PciHandle::PciHandle(uint32, uint32 bus_, uint32 device_, uint32 function_) : - fd(-1), - bus(bus_), - device(device_), - function(function_), - numaNode(-1) -{ } - -int32 PciHandle::getNUMANode() const -{ - return numaNode; -} - -bool PciHandle::exists(uint32 groupnr_, uint32 bus_, uint32 device_, uint32 function_) -{ - if (groupnr_ != 0) - { - std::cerr << "Non-zero PCI group segments are not supported in PCM/APPLE OSX\n"; - return false; - } - uint32_t pci_address = FORM_PCI_ADDR(bus_, device_, function_, 0); - uint32_t value = 0; - PCIDriver_read32(pci_address, &value); - uint32_t vendor_id = value & 0xffff; - uint32_t device_id = (value >> 16) & 0xffff; - - //if (vendor_id == PCM_INTEL_PCI_VENDOR_ID) { - if (vendor_id != 0xffff && device_id != 0xffff) { - return true; - } else { - return false; - } -} - -int32 PciHandle::read32(uint64 offset, uint32 * value) -{ - warnAlignment<4>("PciHandle::read32", false, offset); - uint32_t pci_address = FORM_PCI_ADDR(bus, device, function, (uint32_t)offset); - return PCIDriver_read32(pci_address, value); -} - -int32 PciHandle::write32(uint64 offset, uint32 value) -{ - warnAlignment<4>("PciHandle::write32", false, offset); - uint32_t pci_address = FORM_PCI_ADDR(bus, device, function, (uint32_t)offset); - return PCIDriver_write32(pci_address, value); -} - -int32 PciHandle::read64(uint64 offset, uint64 * value) -{ - warnAlignment<4>("PciHandle::read64", false, offset); - uint32_t pci_address = FORM_PCI_ADDR(bus, device, function, (uint32_t)offset); - return PCIDriver_read64(pci_address, value); -} - -PciHandle::~PciHandle() -{ } - #elif defined (__FreeBSD__) || defined(__DragonFly__) // Helper function to compute NUMA node for FreeBSD diff --git a/src/pci.h b/src/pci.h index 3b267d41..39a001be 100644 --- a/src/pci.h +++ b/src/pci.h @@ -23,10 +23,6 @@ #include #endif -#ifdef __APPLE__ -#include "PCIDriverInterface.h" -#endif - #include namespace pcm { @@ -80,9 +76,6 @@ class PciHandle #ifdef _MSC_VER typedef PciHandle PciHandleType; -#elif __APPLE__ -// This may need to change if it can be implemented for OSX -typedef PciHandle PciHandleType; #elif defined(__FreeBSD__) || defined(__DragonFly__) typedef PciHandle PciHandleType; #elif defined(__linux__) diff --git a/src/pcm-iio-pmu.cpp b/src/pcm-iio-pmu.cpp index 9b0dd49d..a238ba95 100644 --- a/src/pcm-iio-pmu.cpp +++ b/src/pcm-iio-pmu.cpp @@ -289,6 +289,7 @@ ccr* get_ccr(uint32 cpu_family_model, uint64_t& ccr) case PCM::SRF: case PCM::GNR: case PCM::GNR_D: + case PCM::CWF: return new pcm::ccr(ccr, ccr::ccr_type::icx); default: std::cerr << PCM::cpuFamilyModelToUArchCodename(cpu_family_model) << " is not supported! Program aborted" << std::endl; diff --git a/src/pcm-io-metrics.cpp b/src/pcm-io-metrics.cpp new file mode 100644 index 00000000..1ba3c588 --- /dev/null +++ b/src/pcm-io-metrics.cpp @@ -0,0 +1,782 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2026, Intel Corporation + +#include "pcm-io-metrics.h" + +#include +#include + +namespace pcm { + +namespace { + +struct FormulaParser { + const std::string& input; + const std::unordered_map& vars; + size_t pos = 0; + + void skipWS() + { + while (pos < input.size() && std::isspace(static_cast(input[pos]))) + ++pos; + } + + double expression() + { + double left = term(); + skipWS(); + while (pos < input.size() && (input[pos] == '+' || input[pos] == '-')) + { + char op = input[pos++]; + double right = term(); + left = (op == '+') ? left + right : left - right; + skipWS(); + } + return left; + } + + double term() + { + double left = factor(); + skipWS(); + while (pos < input.size() && (input[pos] == '*' || input[pos] == '/')) + { + char op = input[pos++]; + double right = factor(); + if (op == '*') + left *= right; + else + left = (right != 0.0) ? left / right : 0.0; + skipWS(); + } + return left; + } + + double factor() + { + skipWS(); + if (pos < input.size() && input[pos] == '(') + { + ++pos; + double val = expression(); + skipWS(); + if (pos < input.size() && input[pos] == ')') + ++pos; + return val; + } + if (pos < input.size() && (std::isdigit(static_cast(input[pos])) || input[pos] == '.')) + { + size_t start = pos; + while (pos < input.size() && (std::isdigit(static_cast(input[pos])) || input[pos] == '.')) + ++pos; + return std::stod(input.substr(start, pos - start)); + } + if (pos < input.size() && (std::isalpha(static_cast(input[pos])) || input[pos] == '_')) + { + size_t start = pos; + while (pos < input.size() && + (std::isalnum(static_cast(input[pos])) || input[pos] == '_' || input[pos] == '.')) + ++pos; + std::string name = input.substr(start, pos - start); + auto it = vars.find(name); + return (it != vars.end()) ? it->second : 0.0; + } + return 0.0; + } +}; + +struct BoxChars { + const char* horizontal; + const char* vertical; + const char* top_left; + const char* top_right; + const char* bottom_left; + const char* bottom_right; + const char* tee_down; + const char* tee_up; + const char* tee_right; + const char* tee_left; + const char* cross; +}; + +#ifdef _MSC_VER +static const BoxChars BOX { + "\xC4", "\xB3", "\xDA", "\xBF", "\xC0", "\xD9", + "\xC2", "\xC1", "\xC3", "\xB4", "\xC5" +}; +#else +static const BoxChars BOX { + u8"\u2500", u8"\u2502", u8"\u250C", u8"\u2510", + u8"\u2514", u8"\u2518", u8"\u252C", u8"\u2534", + u8"\u251C", u8"\u2524", u8"\u253C" +}; +#endif + +void renderCenteredText(std::ostream& os, const std::string& text, size_t width) +{ + size_t pad = (width > text.size()) ? width - text.size() : 0; + size_t left = pad / 2; + for (size_t j = 0; j < left; ++j) os << " "; + os << text; + for (size_t j = left; j < pad; ++j) os << " "; +} + +void renderLine(std::ostream& os, const char* left, const char* mid, + const char* right, const std::vector& colWidths) +{ + os << left; + for (size_t i = 0; i < colWidths.size(); ++i) + { + for (size_t j = 0; j < colWidths[i]; ++j) + os << BOX.horizontal; + if (i + 1 < colWidths.size()) + os << mid; + } + os << right << "\n"; +} + +} // anonymous namespace + +double FormulaEvaluator::evaluate(const std::string& formula, + const std::unordered_map& variables) const +{ + FormulaParser parser{formula, variables}; + return parser.expression(); +} + +std::set FormulaEvaluator::extractVariables(const std::string& formula) const +{ + std::set vars; + size_t i = 0; + while (i < formula.size()) + { + if (std::isalpha(static_cast(formula[i])) || formula[i] == '_') + { + size_t start = i; + while (i < formula.size() && + (std::isalnum(static_cast(formula[i])) || formula[i] == '_' || formula[i] == '.')) + ++i; + vars.insert(formula.substr(start, i - start)); + } + else + { + ++i; + } + } + return vars; +} + +// --- TableRenderer --- + +void TableRenderer::setHeaders(const std::vector& headers) +{ + m_headers = headers; +} + +void TableRenderer::addRow(const std::vector& values) +{ + m_rows.push_back({false, false, "", values, {}}); +} + +void TableRenderer::addSectionHeader(const std::string& title) +{ + m_rows.push_back({true, false, title, {}, {}}); +} + +void TableRenderer::addSystemSection(const std::string& title, + const std::vector>& pairs) +{ + m_rows.push_back({false, true, title, {}, pairs}); +} + +std::vector TableRenderer::calculateColumnWidths() const +{ + const size_t padding = 2; + std::vector widths(m_headers.size(), 0); + for (size_t i = 0; i < m_headers.size(); ++i) + widths[i] = m_headers[i].size(); + for (const auto& row : m_rows) + { + if (row.isSectionHeader) continue; + for (size_t i = 0; i < row.values.size() && i < widths.size(); ++i) + widths[i] = (std::max)(widths[i], row.values[i].size()); + } + for (auto& w : widths) + w += padding; + return widths; +} + +size_t TableRenderer::calculateTableWidth(const std::vector& colWidths) const +{ + // total = sum of column widths + (numCols + 1) border chars + // but border chars are multi-byte on Linux; we track display columns here + size_t width = colWidths.size() + 1; // border characters + for (auto w : colWidths) + width += w; + return width; +} + +void TableRenderer::render(std::ostream& os) const +{ + if (m_headers.empty()) return; + + auto colWidths = calculateColumnWidths(); + size_t tableWidth = calculateTableWidth(colWidths); + // innerWidth = tableWidth minus the 2 outer border chars (in display columns) + size_t innerWidth = tableWidth - 2; + + // If first row is a section header, render it above the column headers + bool titleAtTop = !m_rows.empty() && m_rows[0].isSectionHeader; + size_t firstDataRow = titleAtTop ? 1 : 0; + + if (titleAtTop) + { + // Full-width top border (no column dividers) + os << BOX.top_left; + for (size_t j = 0; j < innerWidth; ++j) + os << BOX.horizontal; + os << BOX.top_right << "\n"; + + // Section title row (pad clamped to 0 if title exceeds inner width, + // so the closing border still renders instead of underflowing). + os << BOX.vertical << " " << m_rows[0].sectionTitle; + const size_t titleSize = m_rows[0].sectionTitle.size(); + size_t pad = (innerWidth > titleSize + 1) ? innerWidth - 1 - titleSize : 0; + for (size_t j = 0; j < pad; ++j) + os << " "; + os << BOX.vertical << "\n"; + + // Columned separator before column headers + renderLine(os, BOX.tee_right, BOX.tee_down, BOX.tee_left, colWidths); + } + else + { + // Top border with column dividers + renderLine(os, BOX.top_left, BOX.tee_down, BOX.top_right, colWidths); + } + + // Header row (left-aligned) + os << BOX.vertical; + for (size_t i = 0; i < m_headers.size(); ++i) + { + os << " " << m_headers[i]; + size_t pad = colWidths[i] - 1 - m_headers[i].size(); + for (size_t j = 0; j < pad; ++j) + os << " "; + os << BOX.vertical; + } + os << "\n"; + + if (m_rows.size() <= firstDataRow) + { + // No data rows: close immediately + renderLine(os, BOX.bottom_left, BOX.tee_up, BOX.bottom_right, colWidths); + return; + } + + bool needDataSeparator = true; + bool lastRowWasSystem = false; + std::vector lastWideColWidths; + for (size_t ri = firstDataRow; ri < m_rows.size(); ++ri) + { + const auto& row = m_rows[ri]; + if (row.isSectionHeader) + { + // Full-width separator before section title + os << BOX.tee_right; + for (size_t j = 0; j < innerWidth; ++j) + os << BOX.horizontal; + os << BOX.tee_left << "\n"; + + // Section title row (left-aligned, spans full width) + os << BOX.vertical << " " << row.sectionTitle; + const size_t titleSize = row.sectionTitle.size(); + size_t pad = (innerWidth > titleSize + 1) ? innerWidth - 1 - titleSize : 0; + for (size_t j = 0; j < pad; ++j) + os << " "; + os << BOX.vertical << "\n"; + + // Columned separator after section title + renderLine(os, BOX.tee_right, BOX.tee_down, BOX.tee_left, colWidths); + needDataSeparator = false; + lastRowWasSystem = false; + } + else if (row.isSystemSection) + { + needDataSeparator = false; + size_t numColumns = row.systemPairs.size(); + if (numColumns == 0) continue; + + // Transition separator: closes socket columns with tee_up (┴) + renderLine(os, BOX.tee_right, BOX.tee_up, BOX.tee_left, colWidths); + + // Title row (left-aligned) + os << BOX.vertical << " " << row.sectionTitle; + const size_t titleSize = row.sectionTitle.size(); + size_t titlePad = (innerWidth > titleSize + 1) ? innerWidth - 1 - titleSize : 0; + for (size_t j = 0; j < titlePad; ++j) os << " "; + os << BOX.vertical << "\n"; + + // Compute equal column widths across all system metrics. + // Guard against underflow when the parent table is too narrow to fit + // all system columns: fall back to 1-char-per-column so the table stays + // visible (borders may misalign with the socket section, but no loop). + const size_t separators = numColumns - 1; + size_t innerSpace = (innerWidth > separators) ? innerWidth - separators : numColumns; + std::vector wideColWidths(numColumns, innerSpace / numColumns); + for (size_t r = 0; r < innerSpace % numColumns; ++r) wideColWidths[r]++; + + // Opening multi-column separator (┬) + renderLine(os, BOX.tee_right, BOX.tee_down, BOX.tee_left, wideColWidths); + + // Name row (centered) + os << BOX.vertical; + for (size_t i = 0; i < numColumns; ++i) + { + renderCenteredText(os, row.systemPairs[i].first, wideColWidths[i]); + os << BOX.vertical; + } + os << "\n"; + + // Inner separator (┼) + renderLine(os, BOX.tee_right, BOX.cross, BOX.tee_left, wideColWidths); + + // Value row (centered) + os << BOX.vertical; + for (size_t i = 0; i < numColumns; ++i) + { + renderCenteredText(os, row.systemPairs[i].second, wideColWidths[i]); + os << BOX.vertical; + } + os << "\n"; + + lastWideColWidths = wideColWidths; + lastRowWasSystem = true; + } + else + { + if (needDataSeparator) + { + renderLine(os, BOX.tee_right, BOX.cross, BOX.tee_left, colWidths); + needDataSeparator = false; + } + // Data row (right-aligned) + os << BOX.vertical; + for (size_t i = 0; i < m_headers.size(); ++i) + { + const std::string& val = (i < row.values.size()) ? row.values[i] : ""; + size_t pad = colWidths[i] - 1 - val.size(); + for (size_t j = 0; j < pad; ++j) + os << " "; + os << val << " " << BOX.vertical; + } + os << "\n"; + lastRowWasSystem = false; + } + } + + // Bottom border + if (lastRowWasSystem) + renderLine(os, BOX.bottom_left, BOX.tee_up, BOX.bottom_right, lastWideColWidths); + else + renderLine(os, BOX.bottom_left, BOX.tee_up, BOX.bottom_right, colWidths); +} + +std::string TableRenderer::renderToString() const +{ + std::ostringstream oss; + render(oss); + return oss.str(); +} + +void TableRenderer::renderStandaloneSystemSection( + std::ostream& os, + const std::string& title, + const std::vector>& pairs) +{ + const size_t numCols = pairs.size(); + if (numCols == 0) return; + + const size_t padding = 2; + std::vector colWidths(numCols, 0); + for (size_t i = 0; i < numCols; ++i) + colWidths[i] = (std::max)(pairs[i].first.size(), pairs[i].second.size()) + padding; + + size_t innerWidth = numCols - 1; // column separators + for (auto w : colWidths) innerWidth += w; + + if (!title.empty()) + { + // Full-width top border + os << BOX.top_left; + for (size_t j = 0; j < innerWidth; ++j) os << BOX.horizontal; + os << BOX.top_right << "\n"; + + // Title row (left-aligned, clamp pad to 0 on overflow) + os << BOX.vertical << " " << title; + size_t pad = (innerWidth > title.size() + 1) ? innerWidth - 1 - title.size() : 0; + for (size_t j = 0; j < pad; ++j) os << " "; + os << BOX.vertical << "\n"; + + // Separator: ├───┬───┤ + renderLine(os, BOX.tee_right, BOX.tee_down, BOX.tee_left, colWidths); + } + else + { + // Top border with column dividers: ┌───┬───┐ + renderLine(os, BOX.top_left, BOX.tee_down, BOX.top_right, colWidths); + } + + // Name row (centered) + os << BOX.vertical; + for (size_t i = 0; i < numCols; ++i) + { + renderCenteredText(os, pairs[i].first, colWidths[i]); + os << BOX.vertical; + } + os << "\n"; + + // Inner separator: ├───┼───┤ + renderLine(os, BOX.tee_right, BOX.cross, BOX.tee_left, colWidths); + + // Value row (centered) + os << BOX.vertical; + for (size_t i = 0; i < numCols; ++i) + { + renderCenteredText(os, pairs[i].second, colWidths[i]); + os << BOX.vertical; + } + os << "\n"; + + // Bottom border: └───┴───┘ + renderLine(os, BOX.bottom_left, BOX.tee_up, BOX.bottom_right, colWidths); +} + +// --- MetricsConfig --- + +#ifdef PCM_SIMDJSON_AVAILABLE + +bool MetricsConfig::load(const std::string& path) +{ + try + { + m_jsonParser = std::make_shared(); + simdjson::dom::element doc = m_jsonParser->load(path); + return parseMetrics(doc); + } + catch (std::exception& e) + { + std::cerr << "Error loading metrics from " << path << ": " << e.what() << "\n"; + return false; + } +} + +bool MetricsConfig::loadFromString(const std::string& jsonStr) +{ + try + { + m_jsonParser = std::make_shared(); + simdjson::dom::element doc = m_jsonParser->parse(jsonStr); + return parseMetrics(doc); + } + catch (std::exception& e) + { + std::cerr << "Error parsing metrics JSON: " << e.what() << "\n"; + return false; + } +} + +bool MetricsConfig::parseMetrics(simdjson::dom::element doc) +{ + // Parse optional "events" array (local event definitions) + m_localEvents.clear(); + auto eventsArr = doc["events"]; + if (!eventsArr.error()) + { + for (simdjson::dom::object eventObj : eventsArr) + { + std::string eventName; + LocalEvent fields; + for (const auto& kv : eventObj) + { + std::string key{kv.key.begin(), kv.key.end()}; + std::string_view val; + std::string valStr; + if (!kv.value.get(val)) valStr = std::string(val); + + if (key == "EventName") + eventName = valStr; + else + fields[key] = valStr; + } + if (!eventName.empty()) + { + m_localEvents.emplace_back(eventName, std::move(fields)); + } + } + } + + auto metricsArr = doc["metrics"]; + if (metricsArr.error()) + { + std::cerr << "ERROR: No \"metrics\" array in metrics JSON\n"; + return false; + } + + m_metrics.clear(); + for (simdjson::dom::object metricObj : metricsArr) + { + IOMetric m; + m.name = std::string{metricObj["name"].get_c_str()}; + m.formula = std::string{metricObj["formula"].get_c_str()}; + + auto shortName = metricObj["short_name"]; + if (!shortName.error()) + m.short_name = std::string{shortName.get_c_str()}; + + auto agg = metricObj["aggregation"]; + if (!agg.error()) + m.aggregation = std::string{agg.get_c_str()}; + else + m.aggregation = "socket"; + + auto desc = metricObj["description"]; + if (!desc.error()) + m.description = std::string{desc.get_c_str()}; + + m_metrics.push_back(std::move(m)); + } + parseLayout(doc); + return !m_metrics.empty(); +} + +void MetricsConfig::parseLayout(simdjson::dom::element doc) +{ + m_layout.clear(); + + auto layoutObj = doc["layout"]; + if (layoutObj.error()) + { + generateFlatLayout(); + return; + } + + auto sectionsArr = layoutObj["sections"]; + if (sectionsArr.error()) + { + generateFlatLayout(); + return; + } + + try + { + for (simdjson::dom::object sectionObj : sectionsArr) + { + LayoutSection section; + auto title = sectionObj["title"]; + if (!title.error()) + { + section.title = std::string{title.get_c_str()}; + } + + auto rowsEl = sectionObj["rows"]; + if (!rowsEl.error()) + { + // Multi-row section: parse rowLabels, columns, system-wide-metrics + for (auto rowLabel : rowsEl.get_array()) + section.rowLabels.emplace_back(rowLabel.get_c_str()); + + auto columnsEl = sectionObj["columns"]; + if (!columnsEl.error()) + { + simdjson::dom::object colsObj; + if (!columnsEl.get(colsObj)) + { + for (const auto& kv : colsObj) + { + std::string colHeader{kv.key.begin(), kv.key.end()}; + std::vector colMetrics; + for (auto metricName : kv.value.get_array()) + colMetrics.emplace_back(metricName.get_c_str()); + section.columns.emplace_back(std::move(colHeader), std::move(colMetrics)); + } + } + } + + auto sysEl = sectionObj["system-wide-metrics"]; + if (!sysEl.error()) + { + for (auto m : sysEl.get_array()) + section.systemWideMetrics.emplace_back(m.get_c_str()); + } + } + else + { + // Flat section: parse metrics list + auto metricsArr = sectionObj["metrics"]; + if (!metricsArr.error()) + { + for (auto metricName : metricsArr.get_array()) + section.metrics.emplace_back(metricName.get_c_str()); + } + } + m_layout.emplace_back(std::move(section)); + } + } + catch (const std::exception& e) + { + std::cerr << "WARNING: Malformed layout section in metrics JSON: " << e.what() << "\n"; + m_layout.clear(); + } + + if (m_layout.empty()) generateFlatLayout(); +} + +#else // !PCM_SIMDJSON_AVAILABLE + +bool MetricsConfig::load(const std::string&) { return false; } +bool MetricsConfig::loadFromString(const std::string&) { return false; } + +#endif // PCM_SIMDJSON_AVAILABLE + +void MetricsConfig::generateFlatLayout() +{ + m_layout.clear(); + LayoutSection section; + for (const auto& metric : m_metrics) + { + section.metrics.emplace_back(metric.name); + } + m_layout.emplace_back(std::move(section)); +} + +std::set MetricsConfig::extractEventNames() const +{ + std::set allEvents; + for (const auto& metric : m_metrics) + { + auto vars = m_evaluator.extractVariables(metric.formula); + allEvents.insert(vars.begin(), vars.end()); + } + return allEvents; +} + +std::set MetricsConfig::getLayoutMetricNames() const +{ + std::set names; + for (const auto& section : m_layout) + { + for (const auto& n : section.metrics) names.insert(n); + for (const auto& col : section.columns) + for (const auto& n : col.second) names.insert(n); + for (const auto& n : section.systemWideMetrics) names.insert(n); + } + return names; +} + +bool ValidationResult::allValid() const +{ + for (const auto& m : metrics) + { + if (!m.valid) return false; + } + return true; +} + +ValidationResult MetricsConfig::validateEvents(const EventValidator& validator) const +{ + ValidationResult result; + for (const auto& metric : m_metrics) + { + MetricValidation mv; + mv.metricName = metric.name; + auto vars = m_evaluator.extractVariables(metric.formula); + for (const auto& var : vars) + { + if (!validator(var)) + { + mv.missingEvents.insert(var); + } + } + mv.valid = mv.missingEvents.empty(); + result.metrics.emplace_back(std::move(mv)); + } + return result; +} + +bool MetricsConfig::printValidatedMetrics(std::ostream& os, const EventValidator& validator) const +{ + auto result = validateEvents(validator); + size_t validCount = 0; + for (const auto& mv : result.metrics) + { + if (mv.valid) + { + os << " [OK] " << mv.metricName << "\n"; + ++validCount; + } + else + { + os << " [INVALID] " << mv.metricName << " (missing:"; + for (const auto& ev : mv.missingEvents) + { + os << " " << ev; + } + os << ")\n"; + } + } + os << "\n" << validCount << " of " << result.metrics.size() << " metrics valid\n"; + return result.allValid(); +} + +// --- CounterConstraintGrouper --- + +bool CounterConstraintGrouper::parseCounterField(const std::string& counterStr, std::set& allowed) +{ + allowed.clear(); + if (counterStr.empty()) + return false; + + if (isFixedCounter(counterStr)) + return true; + + std::stringstream ss(counterStr); + for (int i = 0; ss >> i;) + { + allowed.insert(i); + if (ss.peek() == ',') + ss.ignore(); + } + return !allowed.empty(); +} + +bool CounterConstraintGrouper::isFixedCounter(const std::string& counterStr) +{ + return counterStr.find("Fixed") != std::string::npos + || counterStr.find("FIXED") != std::string::npos; +} + +CounterConstraintGrouper::EventPlacement CounterConstraintGrouper::placeEvent( + const std::string& pmuName, const std::set& allowedCounters) +{ + for (size_t g = 0; g < m_slotMap.size(); ++g) + { + auto& occupied = m_slotMap[g][pmuName]; + for (int c : allowedCounters) + { + if (occupied.find(c) == occupied.end()) + { + occupied.insert(c); + return {g, static_cast(c)}; + } + } + } + + size_t newG = m_slotMap.size(); + m_slotMap.emplace_back(); + int c = *allowedCounters.begin(); + m_slotMap[newG][pmuName].insert(c); + return {newG, static_cast(c)}; +} + +} // namespace pcm diff --git a/src/pcm-io-metrics.h b/src/pcm-io-metrics.h new file mode 100644 index 00000000..34f5969a --- /dev/null +++ b/src/pcm-io-metrics.h @@ -0,0 +1,146 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2026, Intel Corporation +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "event-resolver.h" + +#ifdef PCM_SIMDJSON_AVAILABLE +#include +#include "simdjson.h" +#endif + +namespace pcm { + +struct IOMetric { + std::string name; + std::string formula; + std::string short_name; + std::string aggregation; // "socket", "system", "stack" + std::string description; +}; + +struct LayoutSection { + std::string title; + std::vector metrics; // references IOMetric::name (flat scheme) + + // Multi-row scheme — all empty => flat section + std::vector rowLabels; // e.g. {"Total", "Miss", "Hit"} + // Ordered pairs: (column-group-header, [metric-name-per-row]). + // std::vector preserves JSON insertion order (simdjson dom::object is ordered). + std::vector>> columns; + std::vector systemWideMetrics; // metric names for the system section + + bool isMultiRow() const { return !rowLabels.empty(); } +}; + +using EventValidator = std::function; + +struct MetricValidation { + std::string metricName; + bool valid; + std::set missingEvents; +}; + +struct ValidationResult { + std::vector metrics; + bool allValid() const; +}; + +class FormulaEvaluator { +public: + double evaluate(const std::string& formula, const std::unordered_map& variables) const; + std::set extractVariables(const std::string& formula) const; +}; + +class TableRenderer { +public: + void setHeaders(const std::vector& headers); + void addRow(const std::vector& values); + void addSectionHeader(const std::string& title); + void addSystemSection(const std::string& title, + const std::vector>& pairs); + void render(std::ostream& os) const; + std::string renderToString() const; + + // Renders a standalone titled box with a two-row centered table + // (metric names row + values row). Used for sections that contain + // only system-aggregated metrics, which have no per-socket rows. + static void renderStandaloneSystemSection( + std::ostream& os, + const std::string& title, + const std::vector>& pairs); + +private: + struct Row { + bool isSectionHeader = false; + bool isSystemSection = false; + std::string sectionTitle; + std::vector values; + std::vector> systemPairs; + }; + std::vector m_headers; + std::vector m_rows; + + std::vector calculateColumnWidths() const; + size_t calculateTableWidth(const std::vector& colWidths) const; +}; + +class CounterConstraintGrouper { +public: + struct EventPlacement { + size_t groupIndex; + size_t counterIndex; + }; + + static bool parseCounterField(const std::string& counterStr, std::set& allowed); + static bool isFixedCounter(const std::string& counterStr); + + EventPlacement placeEvent(const std::string& pmuName, + const std::set& allowedCounters); + +private: + std::vector>> m_slotMap; +}; + +class MetricsConfig { +public: + bool load(const std::string& path); + bool loadFromString(const std::string& jsonStr); + + const std::vector& getMetrics() const { return m_metrics; } + const std::vector& getLayout() const { return m_layout; } + const std::vector>& getLocalEvents() const { return m_localEvents; } + std::set extractEventNames() const; + // Returns the set of metric names referenced by any layout section. + // Walks flat `metrics`, multi-row `columns[*].second`, and `systemWideMetrics`. + // When `layout` is absent in JSON, generateFlatLayout() populates m_layout + // with every metric name, so this returns all metrics in that case. + std::set getLayoutMetricNames() const; + ValidationResult validateEvents(const EventValidator& validator) const; + bool printValidatedMetrics(std::ostream& os, const EventValidator& validator) const; + +private: + std::vector m_metrics; + std::vector m_layout; + std::vector> m_localEvents; + FormulaEvaluator m_evaluator; + + void generateFlatLayout(); + +#ifdef PCM_SIMDJSON_AVAILABLE + bool parseMetrics(simdjson::dom::element doc); + void parseLayout(simdjson::dom::element doc); + std::shared_ptr m_jsonParser; +#endif +}; + +} // namespace pcm diff --git a/src/pcm-io.cpp b/src/pcm-io.cpp new file mode 100644 index 00000000..80ba46ce --- /dev/null +++ b/src/pcm-io.cpp @@ -0,0 +1,1168 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2026, Intel Corporation + +// written by Alexander Antonov + +#include "cpucounters.h" +#include "pcm-io-metrics.h" +#include "event-resolver.h" +#include "utils.h" + +#include +#include +#include +#include +#include +#include + +#define PCM_DELAY_DEFAULT 1.0 + +namespace pcm { + +class MetricsDisplay { +public: + void init(const MetricsConfig* config, uint32 numSockets); + void display(std::ostream& os, bool csv, bool useLayout) const; + void printHeader(std::ostream& os, bool csv) const; + + // Point to the platform's counter values (no copy, valid for lifetime of platform) + void setCounterValues(const std::vector>* values); + +private: + const MetricsConfig* m_config = nullptr; + uint32 m_numSockets = 0; + const std::vector>* m_counterValues = nullptr; + + void displayLayoutMode(std::ostream& os) const; + void displayMultiRowSection(std::ostream& os, const LayoutSection& section) const; + void displayFlatMode(std::ostream& os) const; + void displayCsv(std::ostream& os) const; + bool hasLayoutSections() const; + std::unordered_map getSystemCounterValues() const; + static std::string formatValue(double value); + static std::string formatCsvValue(double value); + static std::string metricDisplayName(const IOMetric& metric); +}; + +class MetricsDrivenPlatform { +public: + bool init(PCM* pcm, const std::string& metricsPath, const std::string& eventPrefix); + void collect(int delayMs); + const MetricsConfig& getConfig() const { return m_config; } + MetricsDisplay& getDisplay() { return m_display; } + int getNumGroups() const { return m_numGroups; } + static std::string cpuModelToDir(int cpuModel); + +private: + PCM* m_pcm = nullptr; + uint32 m_numSockets = 0; + int m_numGroups = 1; + MetricsConfig m_config; + MetricsDisplay m_display; + PerfmonEventResolver m_resolver; + std::vector m_pmuConfigGroups; + + struct EventLocation { + size_t groupIndex; + std::string pmuName; + size_t counterIndex; + }; + std::unordered_map m_eventLocations; + + // PMU counter reading dispatch + using CounterGetter = std::function; + using UnitCountGetter = std::function; + struct PMUCounterDesc { + CounterGetter getter; + UnitCountGetter getNumUnits; + }; + std::unordered_map m_pmuCounterDescs; + void initPMUCounterDescs(); + + std::vector> m_groupBeforeStates; // [group][socket] + std::vector> m_groupAfterStates; // [group][socket] + std::vector> m_counterValues; + + void readCounterValues(); +}; + +} // namespace pcm + +using namespace std; +using namespace pcm; + +// --- MetricsDrivenPlatform implementation --- + +std::string MetricsDrivenPlatform::cpuModelToDir(int cpuModel) +{ + switch (cpuModel) + { + case (int)PCM::ICX: + return "icelake-sp"; + default: + return ""; + } +} + +// Counter-constraint-aware event grouping +// +// Each hardware PMU has N physical counters (e.g., CHA has 4: slots 0-3). +// Each perfmon event has a "Counter" field listing which slots it can use +// (e.g., "0,1" means only slots 0 or 1). The position in the programmable +// vector maps 1:1 to the hardware counter slot: +// +// programmable[0] -> HW counter 0 +// programmable[1] -> HW counter 1 +// ... +// +// When more events need the same slot than one group can hold, we create +// multiple measurement groups that are time-multiplexed during collect(). +// +// Example: 5 CHA events with these constraints: +// +// Event A: Counter "0,1" Event D: Counter "0,1,2,3" +// Event B: Counter "0,1" Event E: Counter "2,3" +// Event C: Counter "0" +// +// Group 0 Group 1 +// slot 0: C (only fits 0) slot 0: A (spillover) +// slot 1: B (fits 0,1) +// slot 2: D (fits 0-3) +// slot 3: E (fits 2,3) +// +// collect() programs Group 0, sleeps, reads counters, +// then programs Group 1, sleeps, reads counters. +// +// Events without a Counter field are rejected (local events in metrics.json +// must explicitly declare it). Fixed-counter events go to the fixed vector. +// Register events (mmio, pcicfg, pmt, tpmi) are rejected — not yet supported. +// +bool MetricsDrivenPlatform::init(PCM* pcm, const std::string& metricsPath, const std::string& eventPrefix) +{ + m_pcm = pcm; + + if (!m_config.load(metricsPath)) + { + cerr << "ERROR: Failed to load metrics from " << metricsPath << "\n"; + return false; + } + + if (!m_resolver.init(pcm->getCPUFamilyModelString(), eventPrefix)) + { + cerr << "ERROR: Failed to initialize perfmon event resolver\n"; + cerr << " CPU: " << pcm->getCPUFamilyModelString() << "\n"; + cerr << " Event prefix: " << eventPrefix << "\n"; + return false; + } + + // Register local events from metrics.json (takes priority over perfmon) + m_resolver.addLocalEvents(m_config.getLocalEvents()); + + // Resolve all events referenced in metric formulas, batching into groups + // respecting per-event hardware counter constraints from the Counter field. + CounterConstraintGrouper grouper; + PCM::RawEventConfig placeholder{{0, 0, 0, 0, 0, 0}, ""}; + auto eventNames = m_config.extractEventNames(); + for (const auto& eventName : eventNames) + { + if (m_eventLocations.count(eventName)) + continue; + + std::string pmuName; + PCM::RawEventConfig config; + if (!m_resolver.resolveEvent(eventName, pmuName, config)) + { + cerr << "WARNING: Could not resolve event: " << eventName << "\n"; + continue; + } + + if (isRegisterEvent(pmuName)) + { + cerr << "ERROR: Register-based events (mmio/pcicfg/pmt/tpmi) are not yet supported in pcm-io. " + << "Event: " << eventName << " (PMU: " << pmuName << ")\n"; + return false; + } + + std::string counterStr = m_resolver.getField(eventName, "Counter"); + if (counterStr.empty()) + { + cerr << "ERROR: Event \"" << eventName << "\" has no Counter field. " + << "Add \"Counter\": \"0,1,2,3\" (or appropriate value) to the event definition in metrics.json\n"; + return false; + } + + if (CounterConstraintGrouper::isFixedCounter(counterStr)) + { + if (m_pmuConfigGroups.empty()) + m_pmuConfigGroups.emplace_back(); + m_pmuConfigGroups[0][pmuName].fixed.push_back(config); + m_eventLocations[eventName] = {0, pmuName, 0}; + continue; + } + + std::set allowedCounters; + if (!CounterConstraintGrouper::parseCounterField(counterStr, allowedCounters)) + { + cerr << "ERROR: Could not parse Counter field \"" << counterStr + << "\" for event " << eventName << "\n"; + return false; + } + + auto placement = grouper.placeEvent(pmuName, allowedCounters); + + while (m_pmuConfigGroups.size() <= placement.groupIndex) + m_pmuConfigGroups.emplace_back(); + + auto& prog = m_pmuConfigGroups[placement.groupIndex][pmuName].programmable; + if (prog.size() <= placement.counterIndex) + prog.resize(placement.counterIndex + 1, placeholder); + prog[placement.counterIndex] = config; + m_eventLocations[eventName] = {placement.groupIndex, pmuName, placement.counterIndex}; + } + + if (m_eventLocations.empty()) + { + cerr << "ERROR: No events could be resolved\n"; + return false; + } + + // Allocate counter state vectors (programming happens in collect()) + m_numSockets = pcm->getNumSockets(); + m_counterValues.resize(m_numSockets); + + m_numGroups = static_cast(m_pmuConfigGroups.size()); + m_groupBeforeStates.resize(m_numGroups); + m_groupAfterStates.resize(m_numGroups); + for (int g = 0; g < m_numGroups; ++g) + { + m_groupBeforeStates[g].resize(m_numSockets); + m_groupAfterStates[g].resize(m_numSockets); + } + + if (m_numGroups > 1) + cerr << "INFO: Events split into " << m_numGroups << " measurement groups\n"; + + m_display.init(&m_config, m_numSockets); + initPMUCounterDescs(); + + return true; +} + +void MetricsDrivenPlatform::collect(int delayMs) +{ + for (size_t g = 0; g < m_pmuConfigGroups.size(); ++g) + { + PCM::ErrorCode status = m_pcm->program(m_pmuConfigGroups[g], true); + if (status != PCM::Success) + { + m_pcm->checkError(status); + return; + } + + m_pcm->globalFreezeUncoreCounters(); + for (uint32 s = 0; s < m_numSockets; ++s) + m_groupBeforeStates[g][s] = m_pcm->getServerUncoreCounterState(s); + m_pcm->globalUnfreezeUncoreCounters(); + + MySleepMs(delayMs); + + m_pcm->globalFreezeUncoreCounters(); + for (uint32 s = 0; s < m_numSockets; ++s) + m_groupAfterStates[g][s] = m_pcm->getServerUncoreCounterState(s); + m_pcm->globalUnfreezeUncoreCounters(); + } + + readCounterValues(); + m_display.setCounterValues(&m_counterValues); +} + +void MetricsDrivenPlatform::initPMUCounterDescs() +{ + // Helper for discovery-based PMUs (CBO, MDF, PCU, UBOX, and future DMR types) + auto discoveryDesc = [this](int pmuId) -> PMUCounterDesc { + return { + [pmuId](uint32 u, uint32 c, const ServerUncoreCounterState& b, const ServerUncoreCounterState& a) { + return getUncoreCounter(pmuId, u, c, b, a); + }, + [this, pmuId](uint32 s) { return m_pcm->getMaxNumOfUncorePMUs(pmuId, s); } + }; + }; + + m_pmuCounterDescs["cbo"] = discoveryDesc(PCM::CBO_PMU_ID); + m_pmuCounterDescs["cha"] = discoveryDesc(PCM::CBO_PMU_ID); + m_pmuCounterDescs["pcu"] = discoveryDesc(PCM::PCU_PMU_ID); + m_pmuCounterDescs["ubox"] = discoveryDesc(PCM::UBOX_PMU_ID); + m_pmuCounterDescs["mdf"] = discoveryDesc(PCM::MDF_PMU_ID); + + // IIO / IRP — indexed by stack + m_pmuCounterDescs["iio"] = { + [](uint32 u, uint32 c, const ServerUncoreCounterState& b, const ServerUncoreCounterState& a) { return getIIOCounter(u, c, b, a); }, + [this](uint32) { return static_cast(m_pcm->getMaxNumOfIIOStacks()); } + }; + m_pmuCounterDescs["irp"] = { + [](uint32 u, uint32 c, const ServerUncoreCounterState& b, const ServerUncoreCounterState& a) { return getIRPCounter(u, c, b, a); }, + [this](uint32) { return static_cast(m_pcm->getMaxNumOfIIOStacks()); } + }; + + // Memory controller + m_pmuCounterDescs["imc"] = { + [](uint32 u, uint32 c, const ServerUncoreCounterState& b, const ServerUncoreCounterState& a) { return getMCCounter(u, c, b, a); }, + [this](uint32) { return static_cast(m_pcm->getMCChannelsPerSocket()); } + }; + m_pmuCounterDescs["m2m"] = { + [](uint32 u, uint32 c, const ServerUncoreCounterState& b, const ServerUncoreCounterState& a) { return getM2MCounter(u, c, b, a); }, + [this](uint32) { return static_cast(m_pcm->getMCPerSocket()); } + }; + + // UPI / M3UPI interconnect + PMUCounterDesc upiDesc = { + [](uint32 u, uint32 c, const ServerUncoreCounterState& b, const ServerUncoreCounterState& a) { return getXPICounter(u, c, b, a); }, + [this](uint32) { return static_cast(m_pcm->getQPILinksPerSocket()); } + }; + m_pmuCounterDescs["xpi"] = upiDesc; + m_pmuCounterDescs["upi"] = upiDesc; + m_pmuCounterDescs["qpi"] = upiDesc; + + m_pmuCounterDescs["m3upi"] = { + [](uint32 u, uint32 c, const ServerUncoreCounterState& b, const ServerUncoreCounterState& a) { return getM3UPICounter(u, c, b, a); }, + [this](uint32) { return static_cast(m_pcm->getQPILinksPerSocket()); } + }; +} + +void MetricsDrivenPlatform::readCounterValues() +{ + for (uint32 s = 0; s < m_numSockets; ++s) + { + m_counterValues[s].clear(); + for (const auto& [eventName, loc] : m_eventLocations) + { + auto it = m_pmuCounterDescs.find(loc.pmuName); + if (it == m_pmuCounterDescs.end()) continue; + + const auto& desc = it->second; + size_t numUnits = desc.getNumUnits(s); + double sum = 0.0; + for (size_t u = 0; u < numUnits; ++u) + { + sum += static_cast(desc.getter((uint32)u, (uint32)loc.counterIndex, + m_groupBeforeStates[loc.groupIndex][s], + m_groupAfterStates[loc.groupIndex][s])); + } + // Rescale by group count to approximate a full-interval count: each group + // only observed delay/numGroups of wall time. Assumes the event's rate is + // steady across the interval. + m_counterValues[s][eventName] = sum * m_numGroups; + } + } +} + +// --- MetricsDisplay implementation --- + +void MetricsDisplay::init(const MetricsConfig* config, uint32 numSockets) +{ + m_config = config; + m_numSockets = numSockets; +} + +void MetricsDisplay::setCounterValues(const std::vector>* values) +{ + m_counterValues = values; +} + +std::unordered_map MetricsDisplay::getSystemCounterValues() const +{ + std::unordered_map systemValues; + for (const auto& socketValues : *m_counterValues) + { + for (const auto& [name, val] : socketValues) + systemValues[name] += val; + } + return systemValues; +} + +std::string MetricsDisplay::formatValue(double value) +{ + if (value >= 0.0 && value <= static_cast(UINT64_MAX)) + return unit_format(static_cast(value)); + + std::ostringstream oss; + oss << std::fixed << std::setprecision(2) << value; + return oss.str(); +} + +// Raw CSV value: integer when representable as uint64, otherwise fixed-point. +// Guards against UB from casting negative or out-of-range doubles to uint64. +std::string MetricsDisplay::formatCsvValue(double value) +{ + if (value >= 0.0 && value <= static_cast(UINT64_MAX)) + return std::to_string(static_cast(value)); + + std::ostringstream oss; + oss << std::fixed << std::setprecision(2) << value; + return oss.str(); +} + +std::string MetricsDisplay::metricDisplayName(const IOMetric& metric) +{ + return metric.short_name.empty() ? metric.name : metric.short_name; +} + +void MetricsDisplay::printHeader(std::ostream& os, bool csv) const +{ + if (!csv) return; + + const auto& metrics = m_config->getMetrics(); + const auto allowed = m_config->getLayoutMetricNames(); + os << "Skt"; + for (const auto& metric : metrics) + { + if (metric.aggregation == "system") continue; + if (!allowed.count(metric.name)) continue; + os << "," << metricDisplayName(metric); + } + for (const auto& metric : metrics) + { + if (metric.aggregation != "system") continue; + if (!allowed.count(metric.name)) continue; + os << "," << metricDisplayName(metric); + } + os << "\n"; +} + +bool MetricsDisplay::hasLayoutSections() const +{ + const auto& layout = m_config->getLayout(); + if (layout.size() > 1) return true; + for (const auto& s : layout) + if (s.isMultiRow() || !s.title.empty()) return true; + return false; +} + +void MetricsDisplay::display(std::ostream& os, bool csv, bool useLayout) const +{ + if (csv) + displayCsv(os); + else if (useLayout && hasLayoutSections()) + displayLayoutMode(os); + else + displayFlatMode(os); +} + +void MetricsDisplay::displayCsv(std::ostream& os) const +{ + FormulaEvaluator evaluator; + const auto& metrics = m_config->getMetrics(); + const auto allowed = m_config->getLayoutMetricNames(); + + size_t numSocketMetrics = 0; + size_t numSystemMetrics = 0; + for (const auto& m : metrics) + { + if (!allowed.count(m.name)) continue; + if (m.aggregation == "system") ++numSystemMetrics; + else ++numSocketMetrics; + } + + if (numSocketMetrics > 0) + { + for (uint32 s = 0; s < m_numSockets; ++s) + { + os << s; + for (const auto& m : metrics) + { + if (m.aggregation == "system") continue; + if (!allowed.count(m.name)) continue; + double val = evaluator.evaluate(m.formula, (*m_counterValues)[s]); + os << "," << formatCsvValue(val); + } + for (size_t i = 0; i < numSystemMetrics; ++i) + os << ","; + os << "\n"; + } + } + + if (numSystemMetrics == 0) return; + + auto systemValues = getSystemCounterValues(); + os << "*"; + for (size_t i = 0; i < numSocketMetrics; ++i) + os << ","; + for (const auto& m : metrics) + { + if (m.aggregation != "system") continue; + if (!allowed.count(m.name)) continue; + double val = evaluator.evaluate(m.formula, systemValues); + os << "," << formatCsvValue(val); + } + os << "\n"; +} + +void MetricsDisplay::displayLayoutMode(std::ostream& os) const +{ + for (const auto& section : m_config->getLayout()) + { + if (section.isMultiRow()) + { + displayMultiRowSection(os, section); + continue; + } + + // Flat section rendering + FormulaEvaluator evaluator; + const auto& metrics = m_config->getMetrics(); + std::vector headers; + std::vector metricIdxs; + std::vector sysMetricIdxs; + bool hasSocketMetrics = false; + bool hasSystemMetrics = false; + + for (const auto& metricName : section.metrics) + { + for (size_t i = 0; i < metrics.size(); ++i) + { + if (metrics[i].name == metricName) + { + if (metrics[i].aggregation == "system") + { + sysMetricIdxs.push_back(i); + hasSystemMetrics = true; + } + else + { + headers.push_back(metricDisplayName(metrics[i])); + metricIdxs.push_back(i); + hasSocketMetrics = true; + } + break; + } + } + } + + if (headers.empty() && sysMetricIdxs.empty()) continue; + + if (!hasSocketMetrics && hasSystemMetrics) + { + auto systemValues = getSystemCounterValues(); + std::vector> sysSection; + for (size_t idx : sysMetricIdxs) + { + std::string name = metricDisplayName(metrics[idx]); + double val = evaluator.evaluate(metrics[idx].formula, systemValues); + sysSection.emplace_back(name, formatValue(val)); + } + TableRenderer::renderStandaloneSystemSection(os, section.title, sysSection); + os << "\n"; + continue; + } + + std::vector fullHeaders; + if (hasSocketMetrics) + fullHeaders.push_back("Skt"); + fullHeaders.insert(fullHeaders.end(), headers.begin(), headers.end()); + + TableRenderer table; + table.setHeaders(fullHeaders); + + if (!section.title.empty()) + table.addSectionHeader(section.title); + + if (hasSocketMetrics) + { + for (uint32 s = 0; s < m_numSockets; ++s) + { + std::vector row; + row.push_back(std::to_string(s)); + for (size_t idx : metricIdxs) + { + if (metrics[idx].aggregation == "system") continue; + double val = evaluator.evaluate(metrics[idx].formula, (*m_counterValues)[s]); + row.push_back(formatValue(val)); + } + table.addRow(row); + } + } + + if (hasSystemMetrics) + { + auto systemValues = getSystemCounterValues(); + std::vector> sysSection; + for (size_t idx : sysMetricIdxs) + { + std::string name = metricDisplayName(metrics[idx]); + double val = evaluator.evaluate(metrics[idx].formula, systemValues); + sysSection.emplace_back(name, formatValue(val)); + } + table.addSystemSection("System Wide", sysSection); + } + + table.render(os); + os << "\n"; + } +} + +void MetricsDisplay::displayMultiRowSection(std::ostream& os, const LayoutSection& section) const +{ + FormulaEvaluator evaluator; + const auto& metrics = m_config->getMetrics(); + const size_t numRows = section.rowLabels.size(); + + // Headers: "Skt" | "" (row-label col) | col-group-1 | col-group-2 | ... + std::vector headers = {"Skt", ""}; + // metricMatrix[colIdx][rowIdx] = index into metrics[], or -1 if absent + std::vector> metricMatrix; + for (const auto& [colHeader, colMetricNames] : section.columns) + { + headers.push_back(colHeader); + std::vector col(numRows, -1); + for (size_t r = 0; r < colMetricNames.size() && r < numRows; ++r) + { + for (size_t mi = 0; mi < metrics.size(); ++mi) + { + if (metrics[mi].name == colMetricNames[r]) + { + col[r] = static_cast(mi); + break; + } + } + } + metricMatrix.push_back(std::move(col)); + } + + TableRenderer table; + table.setHeaders(headers); + if (!section.title.empty()) + table.addSectionHeader(section.title); + + for (uint32 s = 0; s < m_numSockets; ++s) + { + for (size_t r = 0; r < numRows; ++r) + { + std::vector row; + row.push_back(r == 0 ? std::to_string(s) : ""); // socket number only in first sub-row + row.push_back(section.rowLabels[r]); + for (const auto& col : metricMatrix) + { + int mi = col[r]; + if (mi < 0) + row.push_back(""); + else + { + double val = evaluator.evaluate(metrics[mi].formula, (*m_counterValues)[s]); + row.push_back(formatValue(val)); + } + } + table.addRow(row); + } + } + + if (!section.systemWideMetrics.empty()) + { + auto systemValues = getSystemCounterValues(); + std::vector> sysSection; + for (const auto& sysName : section.systemWideMetrics) + { + for (const auto& m : metrics) + { + if (m.name == sysName) + { + double val = evaluator.evaluate(m.formula, systemValues); + sysSection.emplace_back(metricDisplayName(m), formatValue(val)); + break; + } + } + } + if (!sysSection.empty()) + table.addSystemSection("System Wide", sysSection); + } + + table.render(os); + os << "\n"; +} + +void MetricsDisplay::displayFlatMode(std::ostream& os) const +{ + FormulaEvaluator evaluator; + const auto& metrics = m_config->getMetrics(); + + std::vector socketMetricIndices; + std::vector systemMetricIndices; + for (size_t i = 0; i < metrics.size(); ++i) + { + if (metrics[i].aggregation == "system") + systemMetricIndices.push_back(i); + else + socketMetricIndices.push_back(i); + } + + const bool hasSocketMetrics = !socketMetricIndices.empty(); + const bool hasSystemMetrics = !systemMetricIndices.empty(); + + if (!hasSocketMetrics && hasSystemMetrics) + { + auto systemValues = getSystemCounterValues(); + std::vector> sysSection; + for (size_t idx : systemMetricIndices) + { + std::string name = metricDisplayName(metrics[idx]); + double val = evaluator.evaluate(metrics[idx].formula, systemValues); + sysSection.emplace_back(name, formatValue(val)); + } + TableRenderer::renderStandaloneSystemSection(os, "", sysSection); + return; + } + + std::vector fullHeaders; + if (hasSocketMetrics) + { + fullHeaders.push_back("Skt"); + for (size_t idx : socketMetricIndices) + fullHeaders.push_back(metricDisplayName(metrics[idx])); + } + + TableRenderer table; + table.setHeaders(fullHeaders); + + if (hasSocketMetrics) + { + for (uint32 s = 0; s < m_numSockets; ++s) + { + std::vector row; + row.push_back(std::to_string(s)); + for (size_t idx : socketMetricIndices) + { + double val = evaluator.evaluate(metrics[idx].formula, (*m_counterValues)[s]); + row.push_back(formatValue(val)); + } + table.addRow(row); + } + } + + if (hasSystemMetrics) + { + auto systemValues = getSystemCounterValues(); + std::vector> sysSection; + for (size_t idx : systemMetricIndices) + { + std::string name = metricDisplayName(metrics[idx]); + double val = evaluator.evaluate(metrics[idx].formula, systemValues); + sysSection.emplace_back(name, formatValue(val)); + } + table.addSystemSection("System Wide", sysSection); + } + + table.render(os); +} + +// --- CLI --- + +static void print_usage(const string& progname) +{ + cout << "\n Usage: \n " << progname + << " --help | [delay] [options] [-- external_program [external_program_options]]\n"; + cout << " => time interval to sample performance counters (seconds).\n"; + cout << " If not specified, or 0, with external program given\n"; + cout << " will read counters only after external program finishes\n"; + cout << " Supported are: \n"; + cout << " -h | --help | /h => print this help and exit\n"; + cout << " -silent => silence information output and print only measurements\n"; + cout << " --version => print application version\n"; + cout << " -csv[=file.csv] | /csv[=file.csv] => output compact CSV format to screen or\n" + << " to a file, in case filename is provided\n"; + cout << " -i[=number] | /i[=number] => allow to determine number of iterations\n"; + cout << " -ep | /ep => event file prefix (perfmon directory path)\n"; + cout << " --metrics => custom metrics.json file path\n"; + cout << " --no-layout => flat output without section grouping\n"; + cout << " --validate => validate events against perfmon and exit\n"; + cout << " --show-format => print metrics.json authoring guide and exit\n"; + cout << "\n"; + cout << " Examples:\n"; + cout << " " << progname << " 1 => print counters every second\n"; + cout << " " << progname << " 0.5 -csv=test.log => save counter values to test.log in CSV format\n"; + cout << " " << progname << " --validate => check which metrics are available on this CPU\n"; + cout << "\n"; +} + +static void print_metrics_format() +{ + cout << "\n metrics.json authoring guide\n"; + cout << " ============================\n\n"; + cout << " A metrics.json file has three top-level sections:\n"; + cout << " events (optional) - local event definitions\n"; + cout << " metrics (required) - metric names and formulas\n"; + cout << " layout (optional) - how to group metrics for display\n\n"; + + cout << " [events] - optional array of local event definitions\n"; + cout << " Local events override same-name perfmon events. Fields:\n"; + cout << " EventName (required) => lookup key referenced from metric formulas\n"; + cout << " Unit (uncore) => CHA, iMC, M2M, UPI LL, IIO, IRP, PCU, UBOX, M3UPI etc\n"; + cout << " (omitted => event is treated as core)\n"; + cout << " EventCode (required) => hex, e.g. \"0x35\"\n"; + cout << " UMask (required) => hex, e.g. \"0x01\"\n"; + cout << " UMaskExt (optional) => Extension UMask\n"; + cout << " Counter (required) => counter slot(s), e.g. \"0,1,2,3\" or\n"; + cout << " \"Fixed counter 0\"\n"; + cout << " MSRIndex (optional) => offcore events only\n"; + cout << " MSRValue (optional) => offcore events only\n\n"; + + cout << " [metrics] - required array of metric definitions\n"; + cout << " name (required) => unique metric identifier\n"; + cout << " formula (required) => arithmetic over event names\n"; + cout << " operators: + - * / and parentheses;\n"; + cout << " operands: event names and numeric literals\n"; + cout << " short_name (optional) => compact column header\n"; + cout << " aggregation (optional) => \"socket\" (default) | \"system\" | \"stack\"\n"; + cout << " description (optional) => shown in available-metrics listing\n"; + cout << " Event names shared across metrics are deduplicated - two metrics\n"; + cout << " referencing the same events cost only the unique event count.\n\n"; + + cout << " [layout] - optional; if omitted, all metrics render in a single flat table\n"; + cout << " sections (required when layout present) - array of section objects:\n"; + cout << " title (optional) => section heading\n"; + cout << " Flat section:\n"; + cout << " metrics (array) => metric names in display order\n"; + cout << " Multi-row section:\n"; + cout << " rows (array) => row labels (e.g. [\"Total\",\"Miss\",\"Hit\"])\n"; + cout << " columns (object) => column header => [metricName per row]\n"; + cout << " system-wide-metrics (optional) => metrics rendered as system rows\n\n"; + + cout << " See src/metrics/icelake-sp/io.json for a complete example.\n\n"; +} + +static std::string findMetricsPath(const std::string& programPath, const std::string& platformDir) +{ + if (platformDir.empty()) return ""; + + const std::string relPath = "metrics/" + platformDir + "/io.json"; + + // 1. Next to the binary (post-build copy) + size_t lastSlash = programPath.find_last_of("/\\"); + std::string binDir = (lastSlash != std::string::npos) ? programPath.substr(0, lastSlash) : "."; + std::string candidate = binDir + "/" + relPath; + if (std::ifstream(candidate).good()) return candidate; + + // 2. Install path + candidate = getInstallPathPrefix() + relPath; + if (std::ifstream(candidate).good()) return candidate; + + return ""; +} + +static void print_available_metrics(const string& metricsPath, const string& platformDir) +{ + if (metricsPath.empty()) return; + + MetricsConfig config; + if (!config.load(metricsPath)) return; + + cout << " Available metrics for " << platformDir << ":\n\n"; + + const auto& layout = config.getLayout(); + const auto& metrics = config.getMetrics(); + + auto printMetricByName = [&](const std::string& name, const std::string& indent) { + for (const auto& metric : metrics) + if (metric.name == name) + { + cout << indent << metric.name << " = " << metric.formula << "\n"; + if (!metric.description.empty()) + cout << std::string(indent.size(), ' ') << " " << metric.description << "\n"; + break; + } + }; + + bool hasLayout = layout.size() > 1 || (!layout.empty() && layout[0].isMultiRow()); + if (hasLayout) + { + for (const auto& section : layout) + { + if (!section.title.empty()) + cout << " [" << section.title << "]\n"; + + if (section.isMultiRow()) + { + for (const auto& [colHeader, colMetricNames] : section.columns) + { + cout << " " << colHeader << ":\n"; + for (const auto& mName : colMetricNames) + printMetricByName(mName, " "); + } + for (const auto& mName : section.systemWideMetrics) + printMetricByName(mName, " [sys] "); + } + else + { + for (const auto& metricName : section.metrics) + printMetricByName(metricName, " "); + } + cout << "\n"; + } + } + else + { + for (const auto& metric : metrics) + { + cout << " " << metric.name << " = " << metric.formula << "\n"; + if (!metric.description.empty()) + cout << " " << metric.description << "\n"; + } + cout << "\n"; + } +} + +static double resolveDelay(double delay, bool csv, bool hasExternalCmd, PCM* m) +{ + m->setBlocked(hasExternalCmd && delay <= 0.0); + + if (csv) + { + if (delay <= 0.0) delay = PCM_DELAY_DEFAULT; + } + else + { + if (((delay < 1.0) && (delay > 0.0)) || (delay <= 0.0)) + { + cerr << "For non-CSV mode delay < 1.0s does not make a lot of practical sense. " + "Default delay 1s is used. Consider CSV mode for lower delay values\n"; + delay = PCM_DELAY_DEFAULT; + } + } + + cerr << "Update every " << delay << " seconds\n"; + + return delay; +} + +static bool printValidation(std::ostream& os, const std::string& metricsPath, + const std::string& cpuFamilyModel, const std::string& perfmonPath) +{ + MetricsConfig config; + if (!config.load(metricsPath)) + { + cerr << "ERROR: Failed to load metrics from " << metricsPath << "\n"; + return false; + } + PerfmonEventResolver resolver; + if (!resolver.init(cpuFamilyModel, perfmonPath)) + { + cerr << "ERROR: Failed to initialize event resolver\n"; + return false; + } + resolver.addLocalEvents(config.getLocalEvents()); + return config.printValidatedMetrics(os, [&resolver](const std::string& event) { return resolver.isEvent(event); }); +} + +PCM_MAIN_NOTHROW; + +int mainThrows(int argc, char* argv[]) +{ + if (print_version(argc, argv)) + exit(EXIT_SUCCESS); + + null_stream nullStream2; +#ifdef PCM_FORCE_SILENT + null_stream nullStream1; + cout.rdbuf(&nullStream1); + cerr.rdbuf(&nullStream2); +#else + check_and_set_silent(argc, argv, nullStream2); +#endif + + set_signal_handlers(); + + cerr << "\n"; + cerr << " Intel(r) Performance Counter Monitor: Metrics-Driven I/O Bandwidth Monitoring Utility\n"; + cerr << " This utility measures I/O bandwidth using JSON-defined metrics and perfmon events\n"; + cerr << "\n"; + + double delay = -1.0; + bool csv = false; + bool useLayout = true; + bool validateOnly = false; + bool showHelp = false; + std::string perfmonPath; + std::string metricsPath; + char* sysCmd = nullptr; + char** sysArgv = nullptr; + MainLoop mainLoop; + + string program = string(argv[0]); + + PCM* m = PCM::getInstance(); + + if (argc > 1) do + { + argv++; + argc--; + string arg_value; + + if (check_argument_equals(*argv, {"--help", "-h", "/h"})) + { + showHelp = true; + continue; + } + else if (check_argument_equals(*argv, {"-silent", "/silent"})) + { + continue; + } + else if (check_argument_equals(*argv, {"-csv", "/csv"})) + { + csv = true; + } + else if (extract_argument_value(*argv, {"-csv", "/csv"}, arg_value)) + { + csv = true; + if (!arg_value.empty()) + m->setOutput(arg_value); + continue; + } + else if (mainLoop.parseArg(*argv)) + { + continue; + } + else if (check_argument_equals(*argv, {"--no-layout"})) + { + useLayout = false; + continue; + } + else if (check_argument_equals(*argv, {"--validate"})) + { + validateOnly = true; + continue; + } + else if (check_argument_equals(*argv, {"--show-format"})) + { + print_metrics_format(); + exit(EXIT_SUCCESS); + } + else if (check_argument_equals(*argv, {"-ep", "/ep"})) + { + argv++; + argc--; + if (argc <= 0) + { + cerr << "ERROR: no parameter provided for option -ep\n"; + exit(EXIT_FAILURE); + } + perfmonPath = *argv; + continue; + } + else if (check_argument_equals(*argv, {"--metrics"})) + { + argv++; + argc--; + if (argc <= 0) + { + cerr << "ERROR: no parameter provided for option --metrics\n"; + exit(EXIT_FAILURE); + } + metricsPath = *argv; + continue; + } + else if (check_argument_equals(*argv, {"--"})) + { + argv++; + sysCmd = *argv; + sysArgv = argv; + break; + } + else + { + delay = parse_delay(*argv, program, (print_usage_func)print_usage); + continue; + } + } while (argc > 1); + + // Auto-detect platform + std::string platformDir = MetricsDrivenPlatform::cpuModelToDir(m->getCPUFamilyModel()); + if (platformDir.empty() && !showHelp) + { + print_cpu_details(); + cerr << "ERROR: No metrics definition available for this CPU model.\n"; + cerr << "Use --metrics to specify a custom metrics.json file.\n"; + exit(EXIT_FAILURE); + } + + if (perfmonPath.empty()) perfmonPath = PerfmonEventResolver::findPerfmonPath(program); + + if (perfmonPath.empty() && !showHelp) + { + cerr << "ERROR: Could not find perfmon directory (mapfile.csv not found).\n"; + cerr << "Use -ep to specify the perfmon directory location.\n"; + exit(EXIT_FAILURE); + } + + if (!perfmonPath.empty() && !std::ifstream(perfmonPath + "/mapfile.csv").good()) + { + cerr << "WARNING: mapfile.csv not found in " << perfmonPath << "\n"; + } + + if (metricsPath.empty()) metricsPath = findMetricsPath(program, platformDir); + + if (metricsPath.empty() && !showHelp) + { + cerr << "ERROR: Could not find metrics.json for platform " << platformDir << "\n"; + cerr << "Use --metrics to specify the file location.\n"; + exit(EXIT_FAILURE); + } + + cout << "Metrics file: " << metricsPath << "\n"; + cout << "Perfmon event path: " << perfmonPath << "\n"; + + if (showHelp) + { + print_usage(program); + print_available_metrics(metricsPath, platformDir); + exit(EXIT_SUCCESS); + } + + std::string cpuFamilyModel = m->getCPUFamilyModelString(); + + if (validateOnly) + { + cerr << "\nMetrics validation for " << platformDir << ":\n\n"; + exit(printValidation(cout, metricsPath, cpuFamilyModel, perfmonPath) ? EXIT_SUCCESS : EXIT_FAILURE); + } + + // Initialize platform + MetricsDrivenPlatform platform; + if (!platform.init(m, metricsPath, perfmonPath)) + { + cerr << "ERROR: Platform initialization failed\n\nMetrics validation:\n\n"; + printValidation(cerr, metricsPath, cpuFamilyModel, perfmonPath); + exit(EXIT_FAILURE); + } + + // Delay handling + delay = resolveDelay(delay, csv, sysCmd != nullptr, m); + + const auto& config = platform.getConfig(); + cerr << "Monitoring " << config.getMetrics().size() << " metrics, " << config.extractEventNames().size() << " events\n\n"; + + int delayMs = static_cast(delay * 1000) / platform.getNumGroups(); + + if (sysCmd) MySystem(sysCmd, sysArgv); + + auto& display = platform.getDisplay(); + bool firstIteration = true; + mainLoop([&]() + { + if (!csv) cout << flush; + + platform.collect(delayMs); + + if (firstIteration || csv) + { + display.printHeader(cout, csv); + firstIteration = false; + } + + display.display(cout, csv, useLayout); + + if (m->isBlocked()) return false; + + return true; + }); + + exit(EXIT_SUCCESS); +} diff --git a/src/pcm-latency.cpp b/src/pcm-latency.cpp index 144d6ed8..27a7bec0 100644 --- a/src/pcm-latency.cpp +++ b/src/pcm-latency.cpp @@ -42,6 +42,16 @@ using namespace pcm; #define PCM_DELAY_DEFAULT 3.0 // in seconds #define MAX_CORES 4096 +// Core PMU event encodings used for the L1 fill-buffer latency metric +// (verified against the perfmon core event JSONs for HSX/BDX/SKX/ICX/SPR/EMR/SKL). +constexpr uint64 EVENT_L1D_PEND_MISS = 0x48; // L1D_PEND_MISS +constexpr uint64 UMASK_L1D_PEND_MISS_PENDING = 0x01; // .PENDING -> L1d fill-buffer occupancy +constexpr uint64 EVENT_MEM_LOAD_RETIRED = 0xd1; // MEM_LOAD_RETIRED / MEM_LOAD_UOPS_RETIRED +constexpr uint64 UMASK_MEM_LOAD_RETIRED_FB_HIT = 0x40; // .FB_HIT (HIT_LFB on HSX/BDX) +constexpr uint64 UMASK_MEM_LOAD_RETIRED_L1_MISS = 0x08; // .L1_MISS +constexpr uint64 UMASK_MEM_LOAD_RETIRED_FB_HIT_OR_L1_MISS = + UMASK_MEM_LOAD_RETIRED_FB_HIT | UMASK_MEM_LOAD_RETIRED_L1_MISS; // fill-buffer inserts + EventSelectRegister regs[2]; const uint8_t max_sockets = 64; @@ -200,40 +210,47 @@ void store_latency_core(PCM *m) void print_verbose(PCM *m, int ddr_ip) { - cout << "L1 Cache Latency ============================= \n"; - for (unsigned int i=0; igetNumCores(); i++) + if (m->LatencyMetricsAvailable()) { - cout << "Core: " << i << "\n"; - cout << "L1 Occupancy read: " << core_latency[0].core[i].occ_rd << "\n"; - cout << "L1 Inserts read: " << core_latency[0].core[i].insert_rd << "\n"; - cout << "\n"; + cout << "L1 Cache Latency ============================= \n"; + for (unsigned int i=0; igetNumCores(); i++) + { + cout << "Core: " << i << "\n"; + cout << "L1 Occupancy read: " << core_latency[0].core[i].occ_rd << "\n"; + cout << "L1 Inserts read: " << core_latency[0].core[i].insert_rd << "\n"; + cout << "\n"; + } } if (ddr_ip == DDR) { cout << "DDR Latency =================================\n"; - cout << "Read Inserts Socket0: " << uncore_event[DDR].skt[0].rinsert << "\n"; - cout << "Read Occupancy Socket0: " << uncore_event[DDR].skt[0].roccupancy << "\n"; - cout << "Read Inserts Socket1: " << uncore_event[DDR].skt[1].rinsert << "\n"; - cout << "Read Occupancy Socket1: " << uncore_event[DDR].skt[1].roccupancy << "\n"; + for (unsigned int n=0; ngetNumSockets(); n++) + { + cout << "Read Inserts Socket" << n << ": " << uncore_event[DDR].skt[n].rinsert << "\n"; + cout << "Read Occupancy Socket" << n << ": " << uncore_event[DDR].skt[n].roccupancy << "\n"; + } cout << "\n"; - cout << "Write Inserts Socket0: " << uncore_event[DDR].skt[0].winsert << "\n"; - cout << "Write Occupancy Socket0: " << uncore_event[DDR].skt[0].woccupancy << "\n"; - cout << "Write Inserts Socket1: " << uncore_event[DDR].skt[1].winsert << "\n"; - cout << "Write Occupancy Socket1: " << uncore_event[DDR].skt[1].woccupancy << "\n"; + for (unsigned int n=0; ngetNumSockets(); n++) + { + cout << "Write Inserts Socket" << n << ": " << uncore_event[DDR].skt[n].winsert << "\n"; + cout << "Write Occupancy Socket" << n << ": " << uncore_event[DDR].skt[n].woccupancy << "\n"; + } } if (ddr_ip == PMM) { cout << "PMM Latency =================================\n"; - cout << "Read Inserts Socket0: " << uncore_event[PMM].skt[0].rinsert << "\n"; - cout << "Read Occupancy Socket0: " << uncore_event[PMM].skt[0].roccupancy << "\n"; - cout << "Read Inserts Socket1: " << uncore_event[PMM].skt[1].rinsert << "\n"; - cout << "Read Occupancy Socket1: " << uncore_event[PMM].skt[1].roccupancy << "\n"; + for (unsigned int n=0; ngetNumSockets(); n++) + { + cout << "Read Inserts Socket" << n << ": " << uncore_event[PMM].skt[n].rinsert << "\n"; + cout << "Read Occupancy Socket" << n << ": " << uncore_event[PMM].skt[n].roccupancy << "\n"; + } cout << "\n"; - cout << "Write Inserts Socket0: " << uncore_event[PMM].skt[0].winsert << "\n"; - cout << "Write Occupancy Socket0: " << uncore_event[PMM].skt[0].woccupancy << "\n"; - cout << "Write Inserts Socket1: " << uncore_event[PMM].skt[1].winsert << "\n"; - cout << "Write Occupancy Socket1: " << uncore_event[PMM].skt[1].woccupancy << "\n"; + for (unsigned int n=0; ngetNumSockets(); n++) + { + cout << "Write Inserts Socket" << n << ": " << uncore_event[PMM].skt[n].winsert << "\n"; + cout << "Write Occupancy Socket" << n << ": " << uncore_event[PMM].skt[n].woccupancy << "\n"; + } } } @@ -310,34 +327,37 @@ void print_core_stats(PCM *m, unsigned int core_size_per_socket, vector>> sk_th; - unsigned int sid, cid, tid; - unsigned int core_size_per_socket=0; - //Populate Core info per Socket and thread_id - //Create 3D vector with Socket as 1D, Thread as 2D and Core info for the 3D - for (sid = 0; sid < m->getNumSockets(); sid++) + if (m->LatencyMetricsAvailable()) { - vector < vector > tmp_thread; - for (tid = 0; tid < m->getThreadsPerCore(); tid++) + vector < vector < vector < struct core_info >>> sk_th; + unsigned int sid, cid, tid; + unsigned int core_size_per_socket=0; + //Populate Core info per Socket and thread_id + //Create 3D vector with Socket as 1D, Thread as 2D and Core info for the 3D + for (sid = 0; sid < m->getNumSockets(); sid++) { - vector tmp_core; - for (cid = 0; cid < m->getNumCores(); cid++) + vector < vector > tmp_thread; + for (tid = 0; tid < m->getThreadsPerCore(); tid++) { - if ((sid == (unsigned int)(m->getSocketId(cid))) && (tid == (unsigned int)(m->getThreadId(cid)))) + vector tmp_core; + for (cid = 0; cid < m->getNumCores(); cid++) { - core_info tmp; - tmp.core_id = cid; - tmp.latency = core_latency[L1].core[cid].latency; - tmp_core.push_back(tmp); + if ((sid == (unsigned int)(m->getSocketId(cid))) && (tid == (unsigned int)(m->getThreadId(cid)))) + { + core_info tmp; + tmp.core_id = cid; + tmp.latency = core_latency[L1].core[cid].latency; + tmp_core.push_back(tmp); + } } + core_size_per_socket = (unsigned int)tmp_core.size(); + tmp_thread.push_back(tmp_core); } - core_size_per_socket = (unsigned int)tmp_core.size(); - tmp_thread.push_back(tmp_core); + sk_th.push_back(tmp_thread); } - sk_th.push_back(tmp_thread); - } - print_core_stats(m, core_size_per_socket, sk_th); + print_core_stats(m, core_size_per_socket, sk_th); + } if (m->DDRLatencyMetricsAvailable()) { @@ -364,7 +384,7 @@ void check_status(PCM *m, PCM::ErrorCode status) m->checkError(status); print_cpu_details(); - if(!(m->LatencyMetricsAvailable())) + if(!(m->LatencyMetricsAvailable()) && !(m->DDRLatencyMetricsAvailable())) { cerr << "Platform not Supported! Program aborted\n"; exit(EXIT_FAILURE); @@ -387,22 +407,11 @@ void build_registers(PCM *m, PCM::ExtendedCustomCoreEventDescription conf, bool exit(EXIT_FAILURE); } - //Check for Maximum Custom Core Events - if (m->getMaxCustomCoreEvents() < 2) - { - cout << "System should support a minimum of 2 Custom Core Events to run pcm-latency\n"; - exit(EXIT_FAILURE); - } -//Creating conf - conf.fixedCfg = NULL; // default - conf.nGPCounters = 2; - conf.gpCounterCfg = regs; - conf.OffcoreResponseMsrValue[0] = 0; - conf.OffcoreResponseMsrValue[1] = 0; - -// Registers for L1 cache - regs[FB_OCC_RD] = build_core_register(FB_OCC_RD, 0, 1, 1, 1, 0x01, 0x48, 0); //L1d Fill Buffer Occupancy (Read Only) - regs[FB_INS_RD] = build_core_register(FB_INS_RD, 0, 1, 1, 1, 0x48, 0xd1, 0); //MEM_LOAD_RETIRED(FB_HIT + L1_MISS) + // Core L1 fill-buffer latency requires the L1D_PEND_MISS / MEM_LOAD_RETIRED core + // events, which only exist on the P-core parts covered by LatencyMetricsAvailable(). + // On parts that only support DDR uncore latency (e.g. the E-core SRF/CWF) we skip the + // core path and report DDR latency only. + const bool core_latency_available = m->LatencyMetricsAvailable(); //Restructuring Counters for (int i=0; i <5; i++) @@ -414,9 +423,35 @@ void build_registers(PCM *m, PCM::ExtendedCustomCoreEventDescription conf, bool //Program Core and Uncore m->resetPMU(); - PCM::ErrorCode status = m->program(PCM::EXT_CUSTOM_CORE_EVENTS, &conf); - check_status(m, status); - m->programServerUncoreLatencyMetrics(enable_pmm); + if (core_latency_available) + { + //Check for Maximum Custom Core Events + if (m->getMaxCustomCoreEvents() < 2) + { + cout << "System should support a minimum of 2 Custom Core Events to run pcm-latency\n"; + exit(EXIT_FAILURE); + } + //Creating conf + conf.fixedCfg = NULL; // default + conf.nGPCounters = 2; + conf.gpCounterCfg = regs; + conf.OffcoreResponseMsrValue[0] = 0; + conf.OffcoreResponseMsrValue[1] = 0; + + // Registers for L1 cache + regs[FB_OCC_RD] = build_core_register(FB_OCC_RD, 0, 1, 1, 1, UMASK_L1D_PEND_MISS_PENDING, EVENT_L1D_PEND_MISS, 0); //L1d Fill Buffer Occupancy (Read Only) + regs[FB_INS_RD] = build_core_register(FB_INS_RD, 0, 1, 1, 1, UMASK_MEM_LOAD_RETIRED_FB_HIT_OR_L1_MISS, EVENT_MEM_LOAD_RETIRED, 0); //MEM_LOAD_RETIRED(FB_HIT + L1_MISS) + + PCM::ErrorCode status = m->program(PCM::EXT_CUSTOM_CORE_EVENTS, &conf); + check_status(m, status); + m->checkError(m->programServerUncoreLatencyMetrics(enable_pmm)); + } + else + { + // DDR-only path (no core L1 latency events, e.g. E-core SRF/CWF): just program the + // server uncore latency metrics and validate that, like pcm-memory does. + check_status(m, m->programServerUncoreLatencyMetrics(enable_pmm)); + } } void collect_data(PCM *m, bool enable_pmm, bool enable_verbose, int delay_ms, MainLoop & mainLoop) @@ -425,18 +460,23 @@ void collect_data(PCM *m, bool enable_pmm, bool enable_verbose, int delay_ms, Ma BeforeState = new ServerUncoreCounterState[m->getNumSockets()]; AfterState = new ServerUncoreCounterState[m->getNumSockets()]; + const bool core_latency_available = m->LatencyMetricsAvailable(); + mainLoop([&]() { collect_beforestate_uncore(m); - collect_beforestate_core(m); + if (core_latency_available) + collect_beforestate_core(m); MySleepMs(delay_ms); collect_afterstate_uncore(m); - collect_afterstate_core(m); + if (core_latency_available) + collect_afterstate_core(m); store_latency_uncore(m, enable_pmm, delay_ms);// 0 for DDR - store_latency_core(m); + if (core_latency_available) + store_latency_core(m); print_all_stats(m, enable_pmm, enable_verbose); std::cout << std::flush; diff --git a/src/pcm-memory.cpp b/src/pcm-memory.cpp index bddb0d8f..ab6a63fc 100644 --- a/src/pcm-memory.cpp +++ b/src/pcm-memory.cpp @@ -425,6 +425,7 @@ void printSocketBWFooter(PCM *m, uint32 no_columns, uint32 skt, const memdata_t cout << "\n"; } if ( md->metrics == PartialWrites + && m->getCPUFamilyModel() != PCM::CWF && m->getCPUFamilyModel() != PCM::SRF && m->getCPUFamilyModel() != PCM::GNR && m->getCPUFamilyModel() != PCM::GNR_D @@ -739,6 +740,7 @@ void display_bandwidth_csv(PCM *m, memdata_t *md, uint64 /*elapsedTime*/, const if (m->HBMmemoryTrafficMetricsAvailable() == false) { if ( md->metrics == PartialWrites + && m->getCPUFamilyModel() != PCM::CWF && m->getCPUFamilyModel() != PCM::GNR && m->getCPUFamilyModel() != PCM::GNR_D && m->getCPUFamilyModel() != PCM::SRF @@ -1005,6 +1007,7 @@ void calculate_bandwidth(PCM *m, writes = getMCCounter(channel, ServerUncorePMUs::EventPosition::WRITE, uncState1[skt], uncState2[skt]); switch (cpu_family_model) { + case PCM::CWF: case PCM::GNR: case PCM::GNR_D: case PCM::GRR: @@ -1073,6 +1076,7 @@ void calculate_bandwidth(PCM *m, else if ( cpu_family_model != PCM::GNR && cpu_family_model != PCM::GNR_D + && cpu_family_model != PCM::CWF && cpu_family_model != PCM::SRF && cpu_family_model != PCM::GRR ) diff --git a/src/pcm-pcie.cpp b/src/pcm-pcie.cpp index 97726488..ea7dae65 100644 --- a/src/pcm-pcie.cpp +++ b/src/pcm-pcie.cpp @@ -93,8 +93,6 @@ void print_usage(const string & progname) cout << "\n"; } -// getPlatform() is defined inline in pcm-pcie.h. - PCM_MAIN_NOTHROW; int mainThrows(int argc, char * argv[]) @@ -216,7 +214,7 @@ int mainThrows(int argc, char * argv[]) if (!platform) { print_cpu_details(); - cerr << "Jaketown, Ivytown, Haswell, Broadwell-DE, Skylake, Icelake, Snowridge and Sapphirerapids Server CPU is required for this tool! Program aborted\n"; + cerr << "Jaketown, Ivytown, Haswell, Broadwell-DE, Skylake, Icelake, Snowridge, Sapphirerapids, Sierra Forest and Graniterapids Server CPU is required for this tool! Program aborted. Please use pcm-io instead.\n"; exit(EXIT_FAILURE); } diff --git a/src/pcm-pcie.h b/src/pcm-pcie.h index 7f3f09e1..bd1844fa 100644 --- a/src/pcm-pcie.h +++ b/src/pcm-pcie.h @@ -1215,6 +1215,7 @@ inline IPlatform *IPlatform::getPlatform(PCM *m, bool csv, bool bandwidth, case PCM::GNR: case PCM::GNR_D: case PCM::SRF: + case PCM::CWF: return new BirchStreamPlatform(m, csv, bandwidth, verbose, delay); case PCM::GRR: return new LoganvillePlatform(m, csv, bandwidth, verbose, delay); diff --git a/src/pcm-power.cpp b/src/pcm-power.cpp index 2af6b956..77104d56 100644 --- a/src/pcm-power.cpp +++ b/src/pcm-power.cpp @@ -870,6 +870,7 @@ int mainThrows(int argc, char * argv[]) metrics.push_back(Metric("Thermal freq limit cycles", 100. * getNormalizedPCUCounter(u, 1, BeforeState[socket], AfterState[socket]), "%")); metrics.push_back(Metric("Power freq limit cycles", 100. * getNormalizedPCUCounter(u, 2, BeforeState[socket], AfterState[socket]), "%")); if (cpu_family_model != PCM::SKX + && cpu_family_model != PCM::CWF && cpu_family_model != PCM::ICX && cpu_family_model != PCM::SNOWRIDGE && cpu_family_model != PCM::SPR @@ -885,6 +886,7 @@ int mainThrows(int argc, char * argv[]) case 4: if ( cpu_family_model == PCM::SKX + || cpu_family_model == PCM::CWF || cpu_family_model == PCM::ICX || cpu_family_model == PCM::SNOWRIDGE || cpu_family_model == PCM::SPR @@ -924,6 +926,7 @@ int mainThrows(int argc, char * argv[]) } switch (cpu_family_model) { + case PCM::CWF: case PCM::IVYTOWN: case PCM::HASWELLX: case PCM::BDX_DE: diff --git a/src/pcm-raw.cpp b/src/pcm-raw.cpp index 8bfdd02b..b679f083 100644 --- a/src/pcm-raw.cpp +++ b/src/pcm-raw.cpp @@ -29,6 +29,7 @@ #if PCM_SIMDJSON_AVAILABLE #include "simdjson.h" +#include "event-resolver.h" #endif #ifdef _MSC_VER @@ -152,335 +153,36 @@ bool tooManyEvents(const std::string & pmuName, const int event_pos, const std:: #ifdef PCM_SIMDJSON_AVAILABLE using namespace simdjson; -std::vector > JSONparsers; -std::unordered_map PMUEventMapJSON; -std::vector>> PMUEventMapsTSV; -std::shared_ptr PMURegisterDeclarations; -std::string eventFileLocationPrefix = "."; - -bool parse_tsv(const string &path) { - bool col_names_parsed = false; - int event_name_pos = -1; - ifstream inFile; - string line; - inFile.open(path); - std::unordered_map> PMUEventMap; - - while (getline(inFile, line)) { - if (line.size() == 1 && line[0] == '\n') - continue; - // Trim whitespaces left/right // MOVE to utils - auto ws_left_count = 0; - for (size_t i = 0 ; i < line.size() ; i++) { - if (line[i] == ' ') ws_left_count++; - else break; - } - auto ws_right_count = 0; - for (size_t i = line.size() - 1 ; i > 0 ; i--) { - if (line[i] == ' ') ws_right_count++; - else break; - } - line.erase(0, ws_left_count); - line.erase(line.size() - ws_right_count, ws_right_count); - if (line[0] == '#') - continue; - if (!col_names_parsed) { - // Consider first row as Column name row - std::vector col_names = split(line, '\t'); - PMUEventMap["COL_NAMES"] = col_names; - const auto event_name_it = std::find(col_names.begin(), col_names.end(), "EventName"); - if (event_name_it == col_names.end()) { - cerr << "ERROR: First row does not contain EventName\n"; - inFile.close(); - return false; - } - event_name_pos = (int)(event_name_it - col_names.begin()); - col_names_parsed = true; - continue; - } - std::vector entry = split(line, '\t'); - std::string event_name = entry[event_name_pos]; - PMUEventMap[event_name] = entry; - } - inFile.close(); - PMUEventMapsTSV.push_back(PMUEventMap); - return true; -} +static pcm::PerfmonEventResolver s_resolver; +std::string eventFileLocationPrefix; bool initPMUEventMap() { - static bool inited = false; + if (s_resolver.isInitialized()) return true; - if (inited == true) - { - return true; - } - inited = true; - const auto mapfile = "mapfile.csv"; - const auto mapfilePath = eventFileLocationPrefix + "/" + mapfile; - const auto mapfilePathAlt = getInstallPathPrefix() + "perfmon/" + mapfile; - std::ifstream in(mapfilePath); - std::string line, item; + return s_resolver.init(PCM::getInstance()->getCPUFamilyModelString(), eventFileLocationPrefix); +} - if (!in.is_open()) - { - in.open(mapfilePathAlt); - if (!in.is_open()) - { - cerr << "ERROR: File " << mapfilePath << " or " << mapfilePathAlt << " can't be open. \n"; - #ifndef _MSC_VER - cerr << " run 'make install' in the pcm build directory if you cloned PCM source repository recursively with submodules, or\n"; - #endif - cerr << " use -ep /perfmon option if you cloned PCM source repository recursively with submodules,\n"; - cerr << " or run 'git clone https://github.com/intel/perfmon' to download the perfmon event repository and use -ep option\n"; - cerr << " or download the file from https://raw.githubusercontent.com/intel/perfmon/main/" << mapfile << " \n"; - return false; - } - } - int32 FMSPos = -1; - int32 FilenamePos = -1; - int32 EventTypetPos = -1; - if (std::getline(in, line)) - { - auto header = split(line, ','); - for (int32 i = 0; i < (int32)header.size(); ++i) - { - if (header[i] == "Family-model") - { - FMSPos = i; - } - else if (header[i] == "Filename") - { - FilenamePos = i; - } - else if (header[i] == "EventType") - { - EventTypetPos = i; - } - } - } - else - { - cerr << "Can't read first line from " << mapfile << " \n"; - return false; - } - DBG(1, FMSPos , " " , FilenamePos , " " , EventTypetPos); - assert(FMSPos >= 0); - assert(FilenamePos >= 0); - assert(EventTypetPos >= 0); - const std::string ourFMS = PCM::getInstance()->getCPUFamilyModelString(); - DBG(1, "Our FMS: " , ourFMS); - std::multimap eventFiles; - cerr << "Matched event files:\n"; - while (std::getline(in, line)) +void print_event_description(const std::string& eventStr) +{ + for (const auto& key : {"BriefDescription", "PublicDescription"}) { - auto tokens = split(line, ','); - assert(FMSPos < (int32)tokens.size()); - assert(FilenamePos < (int32)tokens.size()); - assert(EventTypetPos < (int32)tokens.size()); - std::regex FMSRegex(tokens[FMSPos]); - std::cmatch FMSMatch; - if (std::regex_search(ourFMS.c_str(), FMSMatch, FMSRegex)) - { - cerr << tokens[FMSPos] << " " << tokens[EventTypetPos] << " " << tokens[FilenamePos] << "\n"; - eventFiles.insert(std::make_pair(tokens[EventTypetPos], tokens[FilenamePos])); - } - } - in.close(); + std::string val = s_resolver.getField(eventStr, key); - if (eventFiles.empty()) - { - cerr << "ERROR: CPU " << ourFMS << " not found in " << mapfile << "\n"; - return false; + if (!val.empty()) std::cout << key << " : " << std::quoted(val) << "\n"; } - - for (const auto& evfile : eventFiles) - { - std::string path; - auto printError = [&evfile]() - { - cerr << "Make sure you have downloaded " << evfile.second << " from https://raw.githubusercontent.com/intel/perfmon/main/" + evfile.second + " \n"; - }; - try { - - cerr << evfile.first << " " << evfile.second << "\n"; - - if (evfile.first == "core" || evfile.first == "uncore" || evfile.first == "uncore experimental") - { - const std::string path1 = eventFileLocationPrefix + evfile.second; - const std::string path2 = eventFileLocationPrefix + evfile.second.substr(evfile.second.rfind('/')); - const std::string path3 = getInstallPathPrefix() + "perfmon" + evfile.second; - - if (std::ifstream(path1).good()) - { - path = path1; - } - else if (std::ifstream(path2).good()) - { - path = path2; - } - else if (std::ifstream(path3).good()) - { - path = path3; - } - else - { - std::cerr << "ERROR: Can't open event file at location " << path1 << " or " << path2 << " or " << path3 << "\n"; - printError(); - return false; - } - - if (path.find(".json") != std::string::npos) { - JSONparsers.push_back(std::make_shared()); - auto JSONObjects = JSONparsers.back()->load(path); - if (JSONObjects["Header"].error() != NO_SUCH_FIELD) - { - JSONObjects = JSONObjects["Events"]; - } - for (simdjson::dom::object eventObj : JSONObjects) { - // cout << "Event ----------------\n"; - const std::string EventName{eventObj["EventName"].get_c_str()}; - if (EventName.empty()) - { - cerr << "Did not find EventName in JSON object:\n"; - for (const auto& keyValue : eventObj) - { - cout << "key: " << keyValue.key << " value: " << keyValue.value << "\n"; - } - } - else - { - PMUEventMapJSON[EventName] = eventObj; - } - } - } else if (path.find(".tsv") != std::string::npos) { - if (!parse_tsv(path)) - return false; - } else { - cerr << "ERROR: Could not determine Event file type (JSON/TSV)\n"; - return false; - } - } - } - catch (std::exception& e) - { - cerr << "Error while opening and/or parsing " << path << " : " << e.what() << "\n"; - printError(); - return false; - } - } - if (PMUEventMapJSON.empty() && PMUEventMapsTSV.empty()) - { - return false; - } - - return true; } -class EventMap { -public: - static bool isEvent(const std::string &eventStr) { - if (PMUEventMapJSON.find(eventStr) != PMUEventMapJSON.end()) - return true; - for (const auto &EventMapTSV : PMUEventMapsTSV) { - if (EventMapTSV.find(eventStr) != EventMapTSV.end()) - return true; - } - return false; - } - - static bool isField(const std::string &eventStr, const std::string event) { - if (PMUEventMapJSON.find(eventStr) != PMUEventMapJSON.end()) { - const auto eventObj = PMUEventMapJSON[eventStr]; - const auto unitObj = eventObj[event]; - return unitObj.error() != NO_SUCH_FIELD; - } - - for (auto &EventMapTSV : PMUEventMapsTSV) { - if (EventMapTSV.find(eventStr) != EventMapTSV.end()) { - const auto &col_names = EventMapTSV["COL_NAMES"]; - const auto event_name_it = std::find(col_names.begin(), col_names.end(), event); - if (event_name_it != col_names.end()) { - const size_t event_name_pos = event_name_it - col_names.begin(); - return event_name_pos < EventMapTSV[eventStr].size(); - } - } - } - - return false; - } - - static std::string getField(const std::string &eventStr, const std::string &event) { - std::string res; - - if (PMUEventMapJSON.find(eventStr) != PMUEventMapJSON.end()) { - const auto eventObj = PMUEventMapJSON[eventStr]; - const auto unitObj = eventObj[event]; - return std::string(unitObj.get_c_str()); - } - - for (auto &EventMapTSV : PMUEventMapsTSV) { - if (EventMapTSV.find(eventStr) != EventMapTSV.end()) { - const auto col_names = EventMapTSV["COL_NAMES"]; - const auto event_name_it = std::find(col_names.begin(), col_names.end(), event); - if (event_name_it != col_names.end()) { - const auto event_name_pos = event_name_it - col_names.begin(); - res = EventMapTSV[eventStr][event_name_pos]; - } - } - } - return res; - } - - static void print_event_description(const std::string &eventStr) { - if (PMUEventMapJSON.find(eventStr) != PMUEventMapJSON.end()) { - const auto eventObj = PMUEventMapJSON[eventStr]; - for (const auto & key : {"BriefDescription", "PublicDescription"}) - std::cout << key << " : " << eventObj[key] << "\n"; - return; - } - } - - static void print_event(const std::string &eventStr) { - if (PMUEventMapJSON.find(eventStr) != PMUEventMapJSON.end()) { - const auto eventObj = PMUEventMapJSON[eventStr]; - for (const auto & keyValue : eventObj) - std::cout << keyValue.key << " : " << keyValue.value << "\n"; - return; - } - - for (auto &EventMapTSV : PMUEventMapsTSV) { - if (EventMapTSV.find(eventStr) != EventMapTSV.end()) { - const auto &col_names = EventMapTSV["COL_NAMES"]; - const auto event = EventMapTSV[eventStr]; - if (EventMapTSV.find(eventStr) != EventMapTSV.end()) { - for (size_t i = 0 ; i < col_names.size() ; i++) - std::cout << col_names[i] << " : " << event[i] << "\n"; - return; - } - } - } - } - - static void print_event_debug(const std::string &eventStr, const int debugLevel = 1) { - if (PMUEventMapJSON.find(eventStr) != PMUEventMapJSON.end()) { - const auto eventObj = PMUEventMapJSON[eventStr]; - for (const auto & keyValue : eventObj) - DBG(debugLevel, "JSON " , keyValue.key , " : " , keyValue.value); - } +void print_event(const std::string& eventStr) +{ + for (const auto& kv : s_resolver.getEventFields(eventStr)) + std::cout << kv.first << " : " << kv.second << "\n"; +} - for (auto &EventMapTSV : PMUEventMapsTSV) { - if (EventMapTSV.find(eventStr) != EventMapTSV.end()) { - const auto &col_names = EventMapTSV["COL_NAMES"]; - const auto event = EventMapTSV[eventStr]; - if (EventMapTSV.find(eventStr) != EventMapTSV.end()) { - for (size_t i = 0 ; i < col_names.size() ; i++) - DBG(debugLevel, "TSV " , col_names[i] , " : " , event[i]); - } - } - } - } -}; +void print_event_debug(const std::string& eventStr, const int debugLevel = 1) { + for (const auto& kv : s_resolver.getEventFields(eventStr)) + DBG(debugLevel, kv.first, " : ", kv.second); +} void printAllEventDescriptions() { @@ -489,10 +191,10 @@ void printAllEventDescriptions() cerr << "ERROR: PMU Event map can not be initialized\n"; return; } - for (const auto& event : PMUEventMapJSON) + for (const auto& eventName : s_resolver.getEventNames()) { - std::cout << event.first << "\n"; - EventMap::print_event_description(event.first); + std::cout << eventName << "\n"; + print_event_description(eventName); std::cout << "\n"; } } @@ -525,7 +227,7 @@ AddEventStatus addEventFromDB(PCM::RawPMUConfigs& curPMUConfigs, string fullEven const auto eventStr = EventTokens[0]; - EventMap::print_event_debug(eventStr); + print_event_debug(eventStr); DBG(2, "size: " , eventStr.size()); PCM::RawEventConfig config = { {0,0,0,0,0}, "" }; @@ -598,7 +300,7 @@ AddEventStatus addEventFromDB(PCM::RawPMUConfigs& curPMUConfigs, string fullEven return AddEventStatus::OK; } - if (!EventMap::isEvent(eventStr)) + if (!s_resolver.isEvent(eventStr)) { cerr << "ERROR: event " << eventStr << " could not be found in event database. Ignoring the event.\n"; return AddEventStatus::OK; @@ -609,80 +311,23 @@ AddEventStatus addEventFromDB(PCM::RawPMUConfigs& curPMUConfigs, string fullEven auto * pcm = PCM::getInstance(); assert(pcm); - int stepping = pcm->getCPUStepping(); - assert(stepping >= 0); - std::string path, err_msg; - - for (; stepping >= 0; --stepping) - { - try - { - path = std::string("PMURegisterDeclarations/") + pcm->getCPUFamilyModelString(pcm->getCPUFamily(), pcm->getInternalCPUModel(), (uint32)stepping) + ".json"; - - std::ifstream in(path); - if (!in.is_open()) - { - const auto alt_path = getInstallPathPrefix() + path; - in.open(alt_path); - if (!in.is_open()) - { - err_msg = std::string("event file ") + path + " or " + alt_path + " is not available."; - throw std::invalid_argument(err_msg); - } - path = alt_path; - } - in.close(); - break; - } - catch (std::invalid_argument & e) - { - std::cerr << "INFO: " << e.what() << "\n"; - path.clear(); - } - } - - if (path.empty()) - { - throw std::invalid_argument(err_msg); - } - - if (PMURegisterDeclarations.get() == nullptr) + const auto* PMURegisterDeclarations = s_resolver.getPMUDeclarations(); + if (!PMURegisterDeclarations) { - // declaration not loaded yet - try { - - JSONparsers.push_back(std::make_shared()); - PMURegisterDeclarations = std::make_shared(); - *PMURegisterDeclarations = JSONparsers.back()->load(path); - } - catch (std::exception& e) - { - cerr << "Error while opening and/or parsing " << path << " : " << e.what() << "\n"; - return AddEventStatus::Failed; - } + cerr << "ERROR: PMU Register Declarations not loaded\n"; + return AddEventStatus::Failed; } - static std::map pmuNameMap = { - {std::string("cbo"), std::string("cha")}, - {std::string("b2cmi"), std::string("m2m")}, - {std::string("upi"), std::string("xpi")}, - {std::string("upi ll"), std::string("xpi")}, - {std::string("b2upi"), std::string("m3upi")}, - {std::string("qpi"), std::string("xpi")}, - {std::string("qpi ll"), std::string("xpi")} - }; - - if (!EventMap::isField(eventStr, "Unit")) + if (!s_resolver.isField(eventStr, "Unit")) { pmuName = "core"; config = initCoreConfig(); } else { - std::string unit = EventMap::getField(eventStr, "Unit"); - lowerCase(unit); + std::string unit = s_resolver.getField(eventStr, "Unit"); DBG(2, eventStr , " is uncore event for unit " , unit); - pmuName = (pmuNameMap.find(unit) == pmuNameMap.end()) ? unit : pmuNameMap[unit]; + pmuName = s_resolver.mapPMUName(unit); } config.second = fullEventStr; @@ -690,7 +335,7 @@ AddEventStatus addEventFromDB(PCM::RawPMUConfigs& curPMUConfigs, string fullEven if (1) { DBG(2, "pmuName: " , pmuName , " full event ", fullEventStr); - std::string CounterStr = EventMap::getField(eventStr, "Counter"); + std::string CounterStr = s_resolver.getField(eventStr, "Counter"); DBG(2, "Counter: " , CounterStr); int fixedCounter = -1; fixed = (pcm_sscanf(CounterStr) >> s_expect("Fixed counter ") >> fixedCounter) ? true : false; @@ -702,7 +347,7 @@ AddEventStatus addEventFromDB(PCM::RawPMUConfigs& curPMUConfigs, string fullEven // loop through counter string and check if event pos matches any counter values for (int i = 0; ss >> i;) { if(event_pos == i) - counter_match = true; + counter_match = true; if (ss.peek() == ',') ss.ignore(); } @@ -717,9 +362,9 @@ AddEventStatus addEventFromDB(PCM::RawPMUConfigs& curPMUConfigs, string fullEven } } bool offcore = false; - if (EventMap::isField(eventStr, "Offcore")) + if (s_resolver.isField(eventStr, "Offcore")) { - const std::string offcoreStr = EventMap::getField(eventStr, "Offcore"); + const std::string offcoreStr = s_resolver.getField(eventStr, "Offcore"); offcore = (offcoreStr == "1"); } if (pmuName == "core" && curPMUConfigs[pmuName].programmable.empty() && fixed == false) @@ -740,7 +385,7 @@ AddEventStatus addEventFromDB(PCM::RawPMUConfigs& curPMUConfigs, string fullEven auto PMUObj = (*PMURegisterDeclarations)[pmuName]; if (PMUObj.error() == NO_SUCH_FIELD) { - cerr << "ERROR: PMU \"" << pmuName << "\" not found for event " << fullEventStr << " in " << path << ", ignoring the event.\n"; + cerr << "ERROR: PMU \"" << pmuName << "\" not found for event " << fullEventStr << " in " << s_resolver.getPMUDeclarationsPath() << ", ignoring the event.\n"; return AddEventStatus::OK; } simdjson::dom::object PMUDeclObj; @@ -770,7 +415,7 @@ AddEventStatus addEventFromDB(PCM::RawPMUConfigs& curPMUConfigs, string fullEven const std::string fieldNameStr{ registerKeyValue.key.begin(), registerKeyValue.key.end() }; if (fieldNameStr == "MSRIndex") { - string fieldValueStr = EventMap::getField(eventStr, fieldNameStr); + string fieldValueStr = s_resolver.getField(eventStr, fieldNameStr); DBG(2, "MSR field " , fieldNameStr , " value is " , fieldValueStr , " (" , read_number(fieldValueStr.c_str()) , ") offcore=" , offcore);; lowerCase(fieldValueStr); if (fieldValueStr == "0" || fieldValueStr == "0x00") @@ -790,7 +435,7 @@ AddEventStatus addEventFromDB(PCM::RawPMUConfigs& curPMUConfigs, string fullEven } DBG(2, " MSR field " , fieldNameStr , " value is " , MSRIndexStr , " (" , read_number(MSRIndexStr.c_str()) , ") offcore=" , offcore); MSRObject = registerKeyValue.value[MSRIndexStr]; - const string msrValueStr = EventMap::getField(eventStr, "MSRValue"); + const string msrValueStr = s_resolver.getField(eventStr, "MSRValue"); setMSRValue(msrValueStr); continue; } @@ -799,12 +444,12 @@ AddEventStatus addEventFromDB(PCM::RawPMUConfigs& curPMUConfigs, string fullEven { continue; // field ignored } - if (!EventMap::isField(eventStr, fieldNameStr)) + if (!s_resolver.isField(eventStr, fieldNameStr)) { DBG(2, fieldNameStr , " not found"); if (fieldDescriptionObj["DefaultValue"].error() == NO_SUCH_FIELD) { - cerr << "ERROR: DefaultValue not provided for field \"" << fieldNameStr << "\" in " << path << "\n"; + cerr << "ERROR: DefaultValue not provided for field \"" << fieldNameStr << "\" in " << s_resolver.getPMUDeclarationsPath() << "\n"; return AddEventStatus::Failed; } else @@ -818,7 +463,7 @@ AddEventStatus addEventFromDB(PCM::RawPMUConfigs& curPMUConfigs, string fullEven { auto getFieldValueArray = [&eventStr](const std::string & fieldNameStr) { - std::string fieldValueStr = EventMap::getField(eventStr, fieldNameStr); + std::string fieldValueStr = s_resolver.getField(eventStr, fieldNameStr); // remove all double quote characters from the fieldValueStr string fieldValueStr.erase(std::remove(fieldValueStr.begin(), fieldValueStr.end(), '\"'), fieldValueStr.end()); return split(fieldValueStr, ','); @@ -841,7 +486,7 @@ AddEventStatus addEventFromDB(PCM::RawPMUConfigs& curPMUConfigs, string fullEven const auto adjustedMaxSize = getFieldValueArray(secondField).size(); if (offcoreEventIndex >= adjustedMaxSize) { - std::cerr << "ERROR: too many offcore events specified (max is " << adjustedMaxSize << "). " << fieldNameStr << " string: " << EventMap::getField(eventStr, fieldNameStr) + std::cerr << "ERROR: too many offcore events specified (max is " << adjustedMaxSize << "). " << fieldNameStr << " string: " << s_resolver.getField(eventStr, fieldNameStr) << " for " << fullEventStr << " event\n"; return AddEventStatus::FailedTooManyEvents; } @@ -875,7 +520,7 @@ AddEventStatus addEventFromDB(PCM::RawPMUConfigs& curPMUConfigs, string fullEven { if (fieldValueArray.size() > 1) { - std::cout << "WARNING: multiple field values specified for field " << fieldNameStr << " for event " << fullEventStr << ": " << EventMap::getField(eventStr, fieldNameStr) + std::cout << "WARNING: multiple field values specified for field " << fieldNameStr << " for event " << fullEventStr << ": " << s_resolver.getField(eventStr, fieldNameStr) << ", choosing the first one...\n"; } DBG(2, "Setting field " , fieldNameStr , " value is " , fieldValueArray[0] , " (" , read_number(fieldValueArray[0].c_str()) , ")"); @@ -1022,7 +667,7 @@ AddEventStatus addEventFromDB(PCM::RawPMUConfigs& curPMUConfigs, string fullEven catch (std::exception& e) { cerr << "Error while setting a register field for event " << fullEventStr << " : " << e.what() << "\n"; - EventMap::print_event(eventStr); + print_event(eventStr); return AddEventStatus::Failed; } } @@ -2522,6 +2167,14 @@ int mainThrows(int argc, char * argv[]) #ifdef PCM_SIMDJSON_AVAILABLE parseParam(argc, argv, "ep", [](const char* p) { eventFileLocationPrefix = p;}); + + if (eventFileLocationPrefix.empty()) + eventFileLocationPrefix = PerfmonEventResolver::findPerfmonPath(program); + + if (!eventFileLocationPrefix.empty() && !std::ifstream(eventFileLocationPrefix + "/mapfile.csv").good()) + { + cerr << "WARNING: mapfile.csv not found in " << eventFileLocationPrefix << "\n"; + } #endif if (argc > 1) do diff --git a/src/pcm-sensor-server.cpp b/src/pcm-sensor-server.cpp index 08691f1d..9d33ac21 100644 --- a/src/pcm-sensor-server.cpp +++ b/src/pcm-sensor-server.cpp @@ -73,6 +73,7 @@ typedef int socket_t; #include #include #include +#include #include #include @@ -2831,8 +2832,13 @@ std::string& compressLWSAndRemoveCR( std::string& line ) { for ( pos = 0; pos < end; ++pos ) { start = pos; - if ( ::isspace( line[pos] ) ) { - while ( (pos+1) < line.size() && ::isspace( line[++pos] ) ) { + // Cast to unsigned char before passing to ::isspace: on platforms + // where char is signed, attacker-controlled bytes with the high bit + // set would otherwise be passed as negative values, which is + // undefined behavior for the functions. + if ( ::isspace( static_cast( line[pos] ) ) ) { + while ( (pos+1) < line.size() && + ::isspace( static_cast( line[++pos] ) ) ) { } if ( (pos - start) > 1 ) { line.erase( start+1, pos-start-1 ); @@ -2850,21 +2856,229 @@ std::string& compressLWSAndRemoveCR( std::string& line ) { return line; } +// Bounds applied to the server-side request reader to mitigate +// Slowloris-style thread-pool exhaustion (CWE-400/CWE-770). The per-read +// socket timeout (SO_RCVTIMEO) only fires when no data arrives, so an +// attacker that dribbles a single byte before each timeout can keep a +// worker thread blocked on std::getline() indefinitely. The bounds below +// cap the total wall-clock time spent parsing the request line and +// headers, plus the size of each line and the cumulative header bytes, +// so a worker cannot be tied up by partial-but-progressing input. +static constexpr std::chrono::seconds kRequestHeaderDeadline{ 30 }; +static constexpr size_t kMaxRequestLineBytes = 8192; +static constexpr size_t kMaxHeaderLineBytes = 8192; +static constexpr size_t kMaxTotalHeaderBytes = 64 * 1024; +// Upper bound on the number of distinct headers (request headers plus any +// chunked trailer headers) accepted for a single request. The cumulative +// byte cap above already bounds total memory, but an explicit count ceiling +// keeps the headers_ container small and rejects header-flood requests early. +static constexpr size_t kMaxHeaderCount = 100; + +// Scoped guard that temporarily tightens the underlying socket's SO_RCVTIMEO +// so a single blocking read cannot exceed the remaining wall-clock budget, +// then restores the previous SO_RCVTIMEO on destruction. Without this, a +// single rs.get() can block for up to the configured socket recv timeout +// (default 10s) even after the request-header deadline has effectively +// expired, letting a slow client hold a worker past the intended cutoff. +// +// Important guarantees: +// * The previous SO_RCVTIMEO is read via getsockopt at construction and +// restored verbatim at destruction, so the socket's idle / keep-alive +// behavior for the NEXT request on the same connection is unchanged. +// * setsockopt is called directly on the FD; the socketbuf's stored +// timeout_ member is NOT modified, so setTimeout()'s "remembered" value +// remains the deployment-configured value. +// * The tightened value is only applied when it is strictly smaller than +// the currently configured timeout, so we never EXTEND a deployment's +// existing (shorter) recv timeout. +// * If rs is not backed by a basic_socketbuf (e.g. unit tests using a +// stringstream) the guard is a no-op. +template +class ScopedRecvTimeout { +public: + ScopedRecvTimeout( std::basic_istream& rs, + std::chrono::steady_clock::time_point deadline ) + : fd_( INVALID_SOCKET ), active_( false ) { + auto* sb = dynamic_cast*>( rs.rdbuf() ); + if ( sb == nullptr ) + return; + socket_t fd = sb->socket(); + if ( fd == INVALID_SOCKET ) + return; + auto now = std::chrono::steady_clock::now(); + long long remaining_ms = + std::chrono::duration_cast( deadline - now ).count(); + if ( remaining_ms <= 0 ) + remaining_ms = 1; // already past; let the next deadline check throw + +#ifdef _WIN32 + DWORD prev = 0; + int prev_len = sizeof( prev ); + if ( getsockopt( fd, SOL_SOCKET, SO_RCVTIMEO, (char*)&prev, &prev_len ) != 0 ) + return; + DWORD desired = static_cast( remaining_ms ); + // Only tighten: never extend an existing shorter SO_RCVTIMEO. A prev + // value of 0 means "no timeout (block forever)", which is also + // weaker than any finite deadline, so override it. + if ( prev != 0 && desired >= prev ) + return; + if ( setsockopt( fd, SOL_SOCKET, SO_RCVTIMEO, (char*)&desired, sizeof( desired ) ) != 0 ) + return; + prev_ = prev; +#else + struct timeval prev; + socklen_t prev_len = sizeof( prev ); + if ( getsockopt( fd, SOL_SOCKET, SO_RCVTIMEO, (char*)&prev, &prev_len ) != 0 ) + return; + struct timeval desired; + desired.tv_sec = static_cast( remaining_ms / 1000 ); + desired.tv_usec = static_cast( ( remaining_ms % 1000 ) * 1000 ); + long long prev_ms = static_cast( prev.tv_sec ) * 1000LL + + static_cast( prev.tv_usec ) / 1000LL; + // Only tighten: never extend an existing shorter SO_RCVTIMEO. A prev + // value of 0 means "no timeout (block forever)", weaker than any + // finite deadline, so override it. + if ( prev_ms != 0 && remaining_ms >= prev_ms ) + return; + if ( setsockopt( fd, SOL_SOCKET, SO_RCVTIMEO, (char*)&desired, sizeof( desired ) ) != 0 ) + return; + prev_ = prev; +#endif + fd_ = fd; + active_ = true; + } + + ~ScopedRecvTimeout() { + if ( !active_ || fd_ == INVALID_SOCKET ) + return; + try { + // Restore previous SO_RCVTIMEO directly via setsockopt so the + // socketbuf's stored timeout_ member is left untouched and the + // deployment-configured idle/keep-alive timeout is preserved for + // subsequent reads on this connection. A destructor must not throw, + // so check the return value but only log on failure; the worst case + // is that the socket retains the tightened timeout for the rest of + // this connection. + if (setsockopt(fd_, SOL_SOCKET, SO_RCVTIMEO, (char*)&prev_, sizeof(prev_)) != 0) { + DBG(1, "ScopedRecvTimeout: setsockopt(SO_RCVTIMEO) restore failed, errno=", errno); + } + } + catch (...) + { + // no exceptions are allowed in destructors, so catch all and log; + // the worst case is that the socket retains the tightened timeout + // for the rest of this connection. + } + } + + ScopedRecvTimeout( const ScopedRecvTimeout& ) = delete; + ScopedRecvTimeout& operator=( const ScopedRecvTimeout& ) = delete; + +private: + socket_t fd_; + bool active_; +#ifdef _WIN32 + DWORD prev_{ 0 }; +#else + struct timeval prev_{ 0, 0 }; +#endif +}; + +// Bounded line reader. Behaves like std::getline(rs, out) but enforces both a +// wall-clock deadline and a maximum line length *while* the line is being read, +// not only after a terminating newline arrives. This closes a Slowloris-style +// gap where an attacker can drip bytes slowly enough that SO_RCVTIMEO never +// fires (so std::getline blocks indefinitely) yet frequently enough that no +// single per-recv timeout triggers either. Reads one character at a time so +// the deadline / size checks are evaluated for every byte; in addition the +// underlying socket's SO_RCVTIMEO is shrunk to the remaining deadline at the +// start of the read so a single blocking rs.get() also cannot exceed the +// wall-clock budget. +// +// Stops at '\n' (consumed, not appended) or at end-of-file. Sets the stream's +// failbit on read failure / EOF without any data, mirroring std::getline so +// callers' existing rs.fail() handling continues to work. Throws +// std::runtime_error once the deadline elapses or the per-line byte cap is +// exceeded; the caller's existing catch turns this into 400 Bad Request. +// Constrained to char streams: the rest of the request parser +// (HTTPHeader::parse, compressLWSAndRemoveCR, std::string requestLine, etc.) +// is only meaningful for byte-oriented HTTP input, and the output buffer is a +// std::string. The function signature itself enforces this constraint, so any +// future instantiation of operator>> on a wsocketstream fails to compile here +// rather than producing a silent narrowing or push_back type mismatch. +template +static void readLineBounded( std::basic_istream& rs, + std::string& out, + size_t maxBytes, + std::chrono::steady_clock::time_point deadline ) { + out.clear(); + // Bound a single blocking recv to the remaining deadline so the per-byte + // deadline check below cannot be delayed past the cutoff by the socket's + // configured SO_RCVTIMEO. Only tightens (never extends) the existing + // timeout, and restores it on scope exit so the deployment-configured + // idle timeout for subsequent reads on this connection is preserved. + ScopedRecvTimeout recvGuard( rs, deadline ); + typename Traits::int_type ch; + bool readAny = false; + while ( true ) { + // Re-check the deadline before every byte so a slow drip cannot keep + // a worker thread parked here past the cutoff. + if ( std::chrono::steady_clock::now() > deadline ) + throw std::runtime_error( "Request header read timeout exceeded" ); + ch = rs.get(); + if ( Traits::eq_int_type( ch, Traits::eof() ) ) { + // Match std::getline semantics: failbit is set only when no + // characters were extracted. rs.get() sets failbit|eofbit on EOF + // regardless, so when we did read bytes before EOF we must + // explicitly clear failbit (preserving badbit) so existing + // rs.fail() handling does not take the failure path on an + // otherwise successful line read terminated by EOF. + if ( !readAny ) { + rs.setstate( std::ios_base::failbit | std::ios_base::eofbit ); + } else { + std::ios_base::iostate s = rs.rdstate(); + s &= ~std::ios_base::failbit; + s |= std::ios_base::eofbit; + rs.clear( s ); + } + return; + } + readAny = true; + char c = Traits::to_char_type( ch ); + if ( c == '\n' ) { + return; + } + if ( out.size() >= maxBytes ) { + throw std::runtime_error( "HTTP line exceeds maximum allowed length" ); + } + out.push_back( c ); + } +} + // This method is for a server reading a request from the client template basic_socketstream& operator>>( basic_socketstream& rs, HTTPRequest& m ) { DBG( 3, "Reading from the socket" ); + // Bound the total time spent reading the request line and headers + // (see kRequestHeaderDeadline comment above). + auto const requestDeadline = std::chrono::steady_clock::now() + kRequestHeaderDeadline; + auto checkRequestDeadline = [requestDeadline]() { + if ( std::chrono::steady_clock::now() > requestDeadline ) + throw std::runtime_error( "Request header read timeout exceeded" ); + }; + // Read something like: GET /persecond/10 HTTP/1.1\r\n std::string requestLine, method, url, protocol; // We need to read a line and check if the request is valid // Fuzzers like to remove spaces so there are not enough elements // on the line and then we're in trouble with the old method - std::getline( rs, requestLine ); + readLineBounded( rs, requestLine, kMaxRequestLineBytes, requestDeadline ); if ( rs.fail() ) { DBG( 3, "Could not read from socket, might have been closed due to e.g. timeout" ); throw std::runtime_error( "Could not read from socket, might have been closed due to e.g. timeout" ); } + checkRequestDeadline(); size_t nlPos = requestLine.find( '\n', 0 ); if ( nlPos != std::string::npos ) requestLine.erase( nlPos, 1 ); @@ -2911,32 +3125,88 @@ basic_socketstream& operator>>( basic_socketstream // m.debugPrint(); std::string line; std::string concatLine; + size_t totalHeaderBytes = 0; + size_t headerCount = 0; + bool haveCurrentHeader = false; while ( true ) { - std::getline( rs, line ); + readLineBounded( rs, line, kMaxHeaderLineBytes, requestDeadline ); + if ( rs.fail() ) { + // Mirror the request-line and trailer reads: an unexpected + // disconnect or stream error mid-headers must not be treated as + // a valid empty terminator line, which would otherwise cause the + // server to proceed with a truncated request. + throw std::runtime_error( "Could not read header from socket, connection closed or stream error" ); + } + checkRequestDeadline(); + totalHeaderBytes += line.size(); + if ( totalHeaderBytes > kMaxTotalHeaderBytes ) { + throw std::runtime_error( "HTTP headers exceed maximum allowed total length" ); + } DBG( 3, "Line with whitespace: '", line, "'" ); - concatLine += compressLWSAndRemoveCR( line ); - DBG( 3, "Line without whitespace: '", line, "'" ); - DBG( 3, "ConcatLine: '", concatLine, "'" ); - // empty line is separator between headers and body - if ( concatLine.empty() ) { + // Detect a folded-header continuation line *before* compressing + // whitespace, since compressLWSAndRemoveCR collapses runs of + // whitespace and would obscure the leading SP/HTAB. Continuation + // detection used to rely on rs.peek() to look at the next byte, + // but rs.peek() can block in basic_socketbuf::underflow() outside + // the per-byte deadline checks performed by readLineBounded, which + // would let a Slowloris client hold a worker past + // kRequestHeaderDeadline. By inspecting the just-read line instead, + // every blocking read is performed inside readLineBounded where + // the deadline is enforced. + const bool isContinuation = !line.empty() + && ( line.front() == ' ' || line.front() == '\t' ); + + // Compress LWS / strip trailing CR in-place. This mutates `line`, + // which is safe because the line.front() check above is already done. + std::string compressed = compressLWSAndRemoveCR( line ); + DBG( 3, "Line without whitespace: '", compressed, "'" ); + + // An empty line (after CR stripping) is the separator between + // headers and body. Finalize any pending folded header first. + if ( compressed.empty() ) { + if ( haveCurrentHeader && !concatLine.empty() ) { + HTTPHeader hh = HTTPHeader::parse( concatLine ); + hh.debugPrint(); + if ( hh.type() == HeaderType::Invalid ) { + throw std::runtime_error( std::string("Bad Request received: ") + hh.invalidReason() ); + } + if ( ++headerCount > kMaxHeaderCount ) { + throw std::runtime_error( "HTTP request exceeds maximum allowed header count" ); + } + m.addHeader( hh ); + concatLine.clear(); + haveCurrentHeader = false; + } break; } - // Header spans multiple lines if a line starts with SP or HTAB, fetch another line and append to concatLine - if ( rs.peek() == ' ' || rs.peek() == '\t' ) + if ( isContinuation ) { + // A continuation line without a preceding header is malformed. + if ( !haveCurrentHeader ) { + throw std::runtime_error( "Bad Request received: header continuation without preceding header" ); + } + concatLine += compressed; continue; + } - HTTPHeader hh; - hh = HTTPHeader::parse( concatLine ); - hh.debugPrint(); - if ( hh.type() == HeaderType::Invalid ) { - // Bad request, throw exception, catch in httpconnection, create response there - throw std::runtime_error( std::string("Bad Request received: ") + hh.invalidReason() ); + // Non-continuation: parse any pending header that has now been + // fully accumulated, then start a fresh header with this line. + if ( haveCurrentHeader && !concatLine.empty() ) { + HTTPHeader hh = HTTPHeader::parse( concatLine ); + hh.debugPrint(); + if ( hh.type() == HeaderType::Invalid ) { + throw std::runtime_error( std::string("Bad Request received: ") + hh.invalidReason() ); + } + if ( ++headerCount > kMaxHeaderCount ) { + throw std::runtime_error( "HTTP request exceeds maximum allowed header count" ); + } + m.addHeader( hh ); + concatLine.clear(); } - m.addHeader( hh ); - // Parsing of header done, clear concatLine to start fresh - concatLine.clear(); + concatLine = compressed; + haveCurrentHeader = true; + DBG( 3, "ConcatLine: '", concatLine, "'" ); } DBG( 3, "Done parsing headers" ); @@ -3012,16 +3282,52 @@ basic_socketstream& operator>>( basic_socketstream // There is now either a \r\n pair in the stream, or footers/trailers, lets see: std::string remainder; size_t numHeadersAdded = 0; - std::getline( rs, remainder, '\n' ); + readLineBounded( rs, remainder, kMaxHeaderLineBytes, requestDeadline ); + if ( rs.fail() ) { + throw std::runtime_error( "Could not read trailer from socket, connection closed or stream error" ); + } + checkRequestDeadline(); DBG( 3, "Parsing remainder '", remainder, "'" ); - while ( remainder[0] != '\r' ) { + // Share the cumulative header byte counter with the main + // header loop above so the kMaxTotalHeaderBytes cap applies + // to headers + trailers together, not separately. + totalHeaderBytes += remainder.size(); + if ( totalHeaderBytes > kMaxTotalHeaderBytes ) { + throw std::runtime_error( "HTTP headers exceed maximum allowed total length" ); + } + // Only a bare CR line (after readLineBounded strips the + // terminating LF) marks the end of the trailer section. + // Any other non-empty line, including malformed content that + // starts with '\r', must still be parsed/validated rather + // than being silently treated as the trailer terminator. + while ( !remainder.empty() ) { + if ( remainder == "\r" ) { + break; + } + // Strip trailing '\r' (and compress folding LWS) before + // parsing so HTTPHeader::parse does not see a stray CR + // in the header value, matching the main header loop. + compressLWSAndRemoveCR( remainder ); HTTPHeader hh = HTTPHeader::parse( remainder ); if ( hh.type() == HeaderType::Invalid ) { // Bad request, throw exception, catch in httpconnection, create response there throw std::runtime_error( std::string("Bad Request received: ") + hh.invalidReason() ); } + if ( ++headerCount > kMaxHeaderCount ) { + throw std::runtime_error( "HTTP request exceeds maximum allowed header count" ); + } m.addHeader( hh ); ++numHeadersAdded; + readLineBounded( rs, remainder, kMaxHeaderLineBytes, requestDeadline ); + if ( rs.fail() ) { + throw std::runtime_error( "Could not read trailer from socket, connection closed or stream error" ); + } + checkRequestDeadline(); + totalHeaderBytes += remainder.size(); + if ( totalHeaderBytes > kMaxTotalHeaderBytes ) { + throw std::runtime_error( "HTTP headers exceed maximum allowed total length" ); + } + DBG( 3, "Parsing remainder '", remainder, "'" ); } // If trailer contains 3 headers then 3 headers should be added if ( numHeadersAdded != trailerLength ) @@ -3209,7 +3515,25 @@ class HTTPConnection : public Work { // Do processing of the request here auto callback = callbackList_[request.method()]; if ( callback ) { - (*callback)( hs_, request, response ); + try { + (*callback)( hs_, request, response ); + } catch ( const std::exception& e ) { + // A request handler must never take down the worker thread + // (and with it the whole process). Turn any unexpected + // exception into a 500 response so the connection fails + // gracefully instead of calling std::terminate. + DBG( 3, "Exception while handling request: ", e.what() ); + response = HTTPResponse(); + response.setProtocol( request.protocol() ); + std::string body( "500 Internal Server Error." ); + response.createResponse( TextPlain, body, RC_500_InternalServerError ); + } catch ( ... ) { + DBG( 3, "Unknown exception while handling request" ); + response = HTTPResponse(); + response.setProtocol( request.protocol() ); + std::string body( "500 Internal Server Error." ); + response.createResponse( TextPlain, body, RC_500_InternalServerError ); + } } else { std::string body( "501 Not Implemented." ); body += " Method \"" + HTTPMethodProperties::getMethodAsString(request.method()) + "\" is not implemented (yet)."; @@ -3304,6 +3628,17 @@ class PeriodicCounterFetcher : public Work class HTTPServer : public Server { public: + // The internal history of aggregators is permanently capped at + // maxAggregators_ entries (see addAggregator()), so the only valid indices + // are 0 .. maxAggregators_ - 1. Answering /persecond/X compares the newest + // sample (index 0) with the sample X seconds earlier (index X), which needs + // X + 1 retained entries. The largest X that can ever be satisfied is + // therefore maxAggregators_ - 1. Deriving the accepted bound from the cap + // keeps the route validation and the retention policy in sync and prevents + // the off-by-one that caused /persecond/30 to block a worker forever. + static constexpr size_t maxAggregators_ = 30; + static constexpr size_t maxPerSecondSeconds_ = maxAggregators_ - 1; + HTTPServer() : Server( "", 80 ), stopped_( false ){ DBG( 3, "HTTPServer::HTTPServer()" ); callbackList_.resize( 256 ); @@ -3360,26 +3695,33 @@ class HTTPServer : public Server { void addAggregator( std::shared_ptr agp ) { DBG( 4, "HTTPServer::addAggregator( agp=", std::hex, agp.get(), " ) called" ); - agVectorMutex_.lock(); - agVector_.insert( agVector_.begin(), agp ); - if ( agVector_.size() > 30 ) { - DBG( 4, "HTTPServer::addAggregator(): Removing last Aggegator" ); - agVector_.pop_back(); + { + std::lock_guard lock( agVectorMutex_ ); + agVector_.insert( agVector_.begin(), agp ); + if ( agVector_.size() > maxAggregators_ ) { + DBG( 4, "HTTPServer::addAggregator(): Removing last Aggegator" ); + agVector_.pop_back(); + } } - agVectorMutex_.unlock(); + agVectorCV_.notify_all(); } std::pair,std::shared_ptr> getAggregators( size_t index, size_t index2 ) { if ( index == index2 ) throw std::runtime_error("BUG: getAggregator: both indices are equal. Fix the code!" ); - // simply wait until we have enough samples to return - while( agVector_.size() < ( (std::max)( index, index2 ) + 1 ) ) - std::this_thread::sleep_for(std::chrono::seconds(1)); - - agVectorMutex_.lock(); + // The history is permanently capped at maxAggregators_ entries, so any + // request for an index that can never be retained would otherwise wait + // forever. Fail fast instead of blocking a worker thread indefinitely. + if ( (std::max)( index, index2 ) >= maxAggregators_ ) + throw std::runtime_error("BUG: getAggregator: requested index can never be satisfied. Fix the code!" ); + + // Wait under the mutex until we have enough samples to return, using the + // condition variable so we don't race against addAggregator(). + auto needSize = (std::max)( index, index2 ) + 1; + std::unique_lock lock( agVectorMutex_ ); + agVectorCV_.wait( lock, [&]{ return agVector_.size() >= needSize; } ); auto ret = std::make_pair( agVector_[ index ], agVector_[ index2 ] ); - agVectorMutex_.unlock(); return ret; } @@ -3425,6 +3767,7 @@ class HTTPServer : public Server { std::vector callbackList_; std::vector> agVector_; std::mutex agVectorMutex_; + std::condition_variable agVectorCV_; PeriodicCounterFetcher* pcf_; bool stopped_; }; @@ -3888,7 +4231,7 @@ void my_get_callback( HTTPServer* hs, HTTPRequest const & req, HTTPResponse & re
    \n\
  • / : This will fetch the counter values since start of the daemon, minus overflow so should be considered absolute numbers and should be used for further processing by yourself.
  • \n\
  • /persecond : This will fetch data from the internal sample thread which samples every second and returns the difference between the last 2 samples.
  • \n\ -
  • /persecond/X : This will fetch data from the internal sample thread which samples every second and returns the difference between the last 2 samples which are X seconds apart. X can be at most 30 seconds without changing the source code.
  • \n\ +
  • /persecond/X : This will fetch data from the internal sample thread which samples every second and returns the difference between the last 2 samples which are X seconds apart. X can be at most 29 seconds without changing the source code.
  • \n\
  • /metrics : The Prometheus server does not send an Accept header to decide what format to return so it got its own endpoint that will always return data in the Prometheus format. pcm-sensor-server is sending the header \"Content-Type: text/plain; version=0.0.4\" as required. This /metrics endpoints mimics the same behavior as / and data is thus absolute, not relative.
  • \n\
  • /dashboard/influxdb : This will return JSON for a Grafana dashboard with InfluxDB backend that holds all counters. Please see the documentation for more information.
  • \n\
  • /dashboard/prometheus : This will return JSON for a Grafana dashboard with Prometheus backend that holds all counters. Please see the documentation for more information.
  • \n\ @@ -3942,16 +4285,16 @@ void my_get_callback( HTTPServer* hs, HTTPRequest const & req, HTTPResponse & re if ( std::all_of( url.path_.begin(), url.path_.end(), ::isdigit ) ) { size_t seconds; try { - seconds = std::stoll( url.path_ ); - } catch ( std::exception& e ) { + seconds = std::stoull( url.path_ ); + } catch ( const std::exception& e ) { DBG( 3, "Error during conversion of /persecond/ seconds: ", e.what() ); seconds = 0; } - if ( 1 <= seconds && 30 >= seconds ) { + if ( 1 <= seconds && HTTPServer::maxPerSecondSeconds_ >= seconds ) { aggregatorPair = hs->getAggregators( seconds, 0 ); } else { - DBG( 3, "seconds equals 0 or seconds larger than 30 is not allowed" ); - std::string body( "400 Bad Request. seconds equals 0 or seconds larger than 30 is not allowed" ); + DBG( 3, "seconds equals 0 or seconds larger than ", HTTPServer::maxPerSecondSeconds_, " is not allowed" ); + std::string body( "400 Bad Request. seconds equals 0 or seconds larger than " + std::to_string( HTTPServer::maxPerSecondSeconds_ ) + " is not allowed" ); resp.createResponse( TextPlain, body, RC_400_BadRequest ); return; } @@ -4071,7 +4414,7 @@ void printHelpText( std::string const & programName ) { #endif std::cout << " -r|--reset : Reset programming of the performance counters.\n"; std::cout << " -D|--debug level : level = 0: no debug info, > 0 increase verbosity.\n"; -#if !defined(__APPLE__) && !defined(_WIN32) +#if !defined(_WIN32) std::cout << " -R|--real-time : If possible the daemon will run with real time\n"; std::cout << " priority, could be useful under heavy load to \n"; std::cout << " stabilize the async counter fetching.\n"; @@ -4102,9 +4445,7 @@ int mainThrows(int argc, char * argv[]) { bool useSSL = false; #endif bool forcedProgramming = false; -#ifndef __APPLE__ bool useRealtimePriority = false; -#endif bool forceRTMAbortMode = false; bool printTopology = false; bool useIPv4 = false; @@ -4209,12 +4550,10 @@ int mainThrows(int argc, char * argv[]) { throw std::runtime_error( "main: Error no debug level argument given" ); } } -#ifndef __APPLE__ else if ( check_argument_equals( argv[i], {"-R", "--real-time"} ) ) { useRealtimePriority = true; } -#endif else if ( check_argument_equals( argv[i], {"--help", "-h", "/h"} ) ) { printHelpText( argv[0] ); @@ -4338,7 +4677,7 @@ int mainThrows(int argc, char * argv[]) { } #endif -#if !defined(__APPLE__) && !defined(_WIN32) +#if !defined(_WIN32) if ( useRealtimePriority ) { int priority = sched_get_priority_min( SCHED_RR ); if ( priority == -1 ) { diff --git a/src/pcm-sensor.cpp b/src/pcm-sensor.cpp deleted file mode 100644 index f2202c69..00000000 --- a/src/pcm-sensor.cpp +++ /dev/null @@ -1,679 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -// Copyright (c) 2009-2022, Intel Corporation -// -// monitor CPU conters for ksysguard -// -// contact: Thomas Willhalm, Patrick Ungerer, Roman Dementiev -// -// This program is not a tutorial on how to write nice interpreters -// but a proof of concept on using ksysguard with performance counters -// - -/*! \file pcm-sensor.cpp - \brief Example of using CPU counters: implements a graphical plugin for KDE ksysguard -*/ -#include -#include -#include -#include "cpuasynchcounter.h" -#include "utils.h" - -using namespace std; -using namespace pcm; - -PCM_MAIN_NOTHROW; - -int mainThrows(int /* argc */, char * /*argv*/ []) -{ - set_signal_handlers(); - - AsynchronCounterState counters; - - cout << "CPU counter sensor " << PCM_VERSION << "\n"; - cout << "ksysguardd 1.2.0\n"; - cout << "ksysguardd> "; - - while (1) - { - string s; - cin >> s; - - const auto xpi = counters.getXpi(); - - // list counters - if (s == "monitors") { - for (uint32 i = 0; i < counters.getNumCores(); ++i) { - for (uint32 a = 0; a < counters.getNumSockets(); ++a) - if (a == counters.getSocketId(i)) { - cout << "Socket" << a << "/CPU" << i << "/Frequency\tfloat\n"; - cout << "Socket" << a << "/CPU" << i << "/IPC\tfloat\n"; - cout << "Socket" << a << "/CPU" << i << "/L2CacheHitRatio\tfloat\n"; - cout << "Socket" << a << "/CPU" << i << "/L3CacheHitRatio\tfloat\n"; - cout << "Socket" << a << "/CPU" << i << "/L2CacheMisses\tinteger\n"; - cout << "Socket" << a << "/CPU" << i << "/L3CacheMisses\tinteger\n"; - cout << "Socket" << a << "/CPU" << i << "/L3Occupancy\tfloat\n"; - cout << "Socket" << a << "/CPU" << i << "/LocalMemoryBandwidth\tfloat\n"; - cout << "Socket" << a << "/CPU" << i << "/RemoteMemoryBandwidth\tfloat\n"; - cout << "Socket" << a << "/CPU" << i << "/CoreC0StateResidency\tfloat\n"; - cout << "Socket" << a << "/CPU" << i << "/CoreC3StateResidency\tfloat\n"; - cout << "Socket" << a << "/CPU" << i << "/CoreC6StateResidency\tfloat\n"; - cout << "Socket" << a << "/CPU" << i << "/CoreC7StateResidency\tfloat\n"; - cout << "Socket" << a << "/CPU" << i << "/ThermalHeadroom\tinteger\n"; - } - } - for (uint32 a = 0; a < counters.getNumSockets(); ++a) { - cout << "Socket" << a << "/BytesReadFromMC\tfloat\n"; - cout << "Socket" << a << "/BytesWrittenToMC\tfloat\n"; - cout << "Socket" << a << "/BytesReadFromPMM\tfloat\n"; - cout << "Socket" << a << "/BytesWrittenToPMM\tfloat\n"; - cout << "Socket" << a << "/Frequency\tfloat\n"; - cout << "Socket" << a << "/IPC\tfloat\n"; - cout << "Socket" << a << "/L2CacheHitRatio\tfloat\n"; - cout << "Socket" << a << "/L3CacheHitRatio\tfloat\n"; - cout << "Socket" << a << "/L2CacheMisses\tinteger\n"; - cout << "Socket" << a << "/L3CacheMisses\tinteger\n"; - cout << "Socket" << a << "/L3Occupancy\tfloat\n"; - cout << "Socket" << a << "/LocalMemoryBandwidth\tfloat\n"; - cout << "Socket" << a << "/RemoteMemoryBandwidth\tfloat\n"; - cout << "Socket" << a << "/CoreC0StateResidency\tfloat\n"; - cout << "Socket" << a << "/CoreC3StateResidency\tfloat\n"; - cout << "Socket" << a << "/CoreC6StateResidency\tfloat\n"; - cout << "Socket" << a << "/CoreC7StateResidency\tfloat\n"; - cout << "Socket" << a << "/PackageC2StateResidency\tfloat\n"; - cout << "Socket" << a << "/PackageC3StateResidency\tfloat\n"; - cout << "Socket" << a << "/PackageC6StateResidency\tfloat\n"; - cout << "Socket" << a << "/PackageC7StateResidency\tfloat\n"; - cout << "Socket" << a << "/ThermalHeadroom\tinteger\n"; - cout << "Socket" << a << "/CPUEnergy\tfloat\n"; - cout << "Socket" << a << "/DRAMEnergy\tfloat\n"; - } - for (uint32 a = 0; a < counters.getNumSockets(); ++a) { - for (uint32 l = 0; l < counters.getQPILinksPerSocket(); ++l) - cout << "Socket" << a << "/BytesIncomingTo" << xpi << l << "\tfloat\n"; - } - - cout << xpi << "_Traffic\tfloat\n"; - cout << "Frequency\tfloat\n"; - cout << "IPC\tfloat\n"; //double check output - cout << "L2CacheHitRatio\tfloat\n"; - cout << "L3CacheHitRatio\tfloat\n"; - cout << "L2CacheMisses\tinteger\n"; - cout << "L3CacheMisses\tinteger\n"; - cout << "CoreC0StateResidency\tfloat\n"; - cout << "CoreC3StateResidency\tfloat\n"; - cout << "CoreC6StateResidency\tfloat\n"; - cout << "CoreC7StateResidency\tfloat\n"; - cout << "PackageC2StateResidency\tfloat\n"; - cout << "PackageC3StateResidency\tfloat\n"; - cout << "PackageC6StateResidency\tfloat\n"; - cout << "PackageC7StateResidency\tfloat\n"; - cout << "CPUEnergy\tfloat\n"; - cout << "DRAMEnergy\tfloat\n"; - } - - // provide metadata - - for (uint32 i = 0; i < counters.getNumCores(); ++i) { - for (uint32 a = 0; a < counters.getNumSockets(); ++a) - if (a == counters.getSocketId(i)) { - { - stringstream c; - c << "Socket" << a << "/CPU" << i << "/Frequency?"; - if (s == c.str()) { - cout << "FREQ. CPU" << i << "\t\t\tMHz\n"; - } - } - { - stringstream c; - c << "Socket" << a << "/CPU" << i << "/ThermalHeadroom?"; - if (s == c.str()) { - cout << "Temperature reading in 1 degree Celsius relative to the TjMax temperature (thermal headroom) for CPU" << i << "\t\t\t°C\n"; - } - } - { - stringstream c; - c << "Socket" << a << "/CPU" << i << "/CoreC0StateResidency?"; - if (s == c.str()) { - cout << "core C0-state residency for CPU" << i << "\t\t\t%\n"; - } - } - { - stringstream c; - c << "Socket" << a << "/CPU" << i << "/CoreC3StateResidency?"; - if (s == c.str()) { - cout << "core C3-state residency for CPU" << i << "\t\t\t%\n"; - } - } - { - stringstream c; - c << "Socket" << a << "/CPU" << i << "/CoreC6StateResidency?"; - if (s == c.str()) { - cout << "core C6-state residency for CPU" << i << "\t\t\t%\n"; - } - } - { - stringstream c; - c << "Socket" << a << "/CPU" << i << "/CoreC7StateResidency?"; - if (s == c.str()) { - cout << "core C7-state residency for CPU" << i << "\t\t\t%\n"; - } - } - } - } - for (uint32 i = 0; i < counters.getNumCores(); ++i) { - for (uint32 a = 0; a < counters.getNumSockets(); ++a) - if (a == counters.getSocketId(i)) { - stringstream c; - c << "Socket" << a << "/CPU" << i << "/IPC?"; - if (s == c.str()) { - cout << "IPC CPU" << i << "\t0\t\t\n"; - //cout << "CPU" << i << "\tInstructions per Cycle\t0\t1\t \n"; - } - } - } - for (uint32 i = 0; i < counters.getNumCores(); ++i) { - for (uint32 a = 0; a < counters.getNumSockets(); ++a) - if (a == counters.getSocketId(i)) { - stringstream c; - c << "Socket" << a << "/CPU" << i << "/L2CacheHitRatio?"; - if (s == c.str()) { - cout << "L2 Cache Hit Ratio CPU" << i << "\t0\t\t\n"; - // cout << "CPU" << i << "\tL2 Cache Hit Ratio\t0\t1\t \n"; - } - } - } - for (uint32 i = 0; i < counters.getNumCores(); ++i) { - for (uint32 a = 0; a < counters.getNumSockets(); ++a) - if (a == counters.getSocketId(i)) { - stringstream c; - c << "Socket" << a << "/CPU" << i << "/L3CacheHitRatio?"; - if (s == c.str()) { - cout << "L3 Cache Hit Ratio CPU" << i << "\t0\t\t \n"; - } - } - } - for (uint32 i = 0; i < counters.getNumCores(); ++i) { - for (uint32 a = 0; a < counters.getNumSockets(); ++a) - if (a == counters.getSocketId(i)) { - stringstream c; - c << "Socket" << a << "/CPU" << i << "/L2CacheMisses?"; - if (s == c.str()) { - cout << "L2 Cache Misses CPU" << i << "\t0\t\t \n"; - //cout << "CPU" << i << "\tL2 Cache Misses\t0\t1\t \n"; - } - } - } - for (uint32 i = 0; i < counters.getNumCores(); ++i) { - for (uint32 a = 0; a < counters.getNumSockets(); ++a) - if (a == counters.getSocketId(i)) { - stringstream c; - c << "Socket" << a << "/CPU" << i << "/L3CacheMisses?"; - if (s == c.str()) { - cout << "L3 Cache Misses CPU" << i << "\t0\t\t \n"; - //cout << "CPU" << i << "\tL3 Cache Misses\t0\t1\t \n"; - } - } - } - for (uint32 i = 0; i < counters.getNumCores(); ++i) { - for (uint32 a = 0; a < counters.getNumSockets(); ++a) - if (a == counters.getSocketId(i)) { - stringstream c; - c << "Socket" << a << "/CPU" << i << "/L3Occupancy?"; - if (s == c.str()) { - cout << "L3 Cache Occupancy CPU " << i << "\t0\t\t \n"; - //cout << "CPU" << i << "\tL3 Cache Occupancy\t0\t1\t \n"; - } - } - } - for (uint32 i = 0; i < counters.getNumCores(); ++i) { - for (uint32 a = 0; a < counters.getNumSockets(); ++a) - if (a == counters.getSocketId(i)) { - stringstream c; - c << "Socket" << a << "/CPU" << i << "/LocalMemoryBandwidth?"; - if (s == c.str()) { - cout << "Local Memory Bandwidth CPU " << i << "\t0\t\t \n"; - //cout << "CPU" << i << "\tLocal Memory Bandwidth\t0\t1\t \n"; - } - } - } - for (uint32 i = 0; i < counters.getNumCores(); ++i) { - for (uint32 a = 0; a < counters.getNumSockets(); ++a) - if (a == counters.getSocketId(i)) { - stringstream c; - c << "Socket" << a << "/CPU" << i << "/RemoteMemoryBandwidth?"; - if (s == c.str()) { - cout << "Remote Memory Bandwidth CPU " << i << "\t0\t\t \n"; - //cout << "CPU" << i << "\tRemote Memory Bandwidth\t0\t1\t \n"; - } - } - } - for (uint32 i = 0; i < counters.getNumSockets(); ++i) { - stringstream c; - c << "Socket" << i << "/BytesReadFromMC?"; - if (s == c.str()) { - cout << "read from MC Socket" << i << "\t0\t\tGB\n"; - } - } - for (uint32 i = 0; i < counters.getNumSockets(); ++i) { - stringstream c; - c << "Socket" << i << "/BytesReadFromPMM?"; - if (s == c.str()) { - cout << "read from PMM memory on Socket" << i << "\t0\t\tGB\n"; - } - } - for (uint32 i = 0; i < counters.getNumSockets(); ++i) { - stringstream c; - c << "Socket" << i << "/DRAMEnergy?"; - if (s == c.str()) { - cout << "Energy consumed by DRAM on socket " << i << "\t0\t\tJoule\n"; - } - } - for (uint32 i = 0; i < counters.getNumSockets(); ++i) { - stringstream c; - c << "Socket" << i << "/CPUEnergy?"; - if (s == c.str()) { - cout << "Energy consumed by CPU package " << i << "\t0\t\tJoule\n"; - } - } - for (uint32 i = 0; i < counters.getNumSockets(); ++i) { - stringstream c; - c << "Socket" << i << "/ThermalHeadroom?"; - if (s == c.str()) { - cout << "Temperature reading in 1 degree Celsius relative to the TjMax temperature (thermal headroom) for CPU package " << i << "\t0\t\t°C\n"; - } - } - for (uint32 i = 0; i < counters.getNumSockets(); ++i) { - stringstream c; - c << "Socket" << i << "/CoreC0StateResidency?"; - if (s == c.str()) { - cout << "core C0-state residency for CPU package " << i << "\t0\t\t%\n"; - } - } - for (uint32 i = 0; i < counters.getNumSockets(); ++i) { - stringstream c; - c << "Socket" << i << "/CoreC3StateResidency?"; - if (s == c.str()) { - cout << "core C3-state residency for CPU package " << i << "\t0\t\t%\n"; - } - } - for (uint32 i = 0; i < counters.getNumSockets(); ++i) { - stringstream c; - c << "Socket" << i << "/CoreC6StateResidency?"; - if (s == c.str()) { - cout << "core C6-state residency for CPU package " << i << "\t0\t\t%\n"; - } - } - for (uint32 i = 0; i < counters.getNumSockets(); ++i) { - stringstream c; - c << "Socket" << i << "/CoreC7StateResidency?"; - if (s == c.str()) { - cout << "core C7-state residency for CPU package " << i << "\t0\t\t%\n"; - } - } - for (uint32 i = 0; i < counters.getNumSockets(); ++i) { - stringstream c; - c << "Socket" << i << "/PackageC2StateResidency?"; - if (s == c.str()) { - cout << "package C2-state residency for CPU package " << i << "\t0\t\t%\n"; - } - } - for (uint32 i = 0; i < counters.getNumSockets(); ++i) { - stringstream c; - c << "Socket" << i << "/PackageC3StateResidency?"; - if (s == c.str()) { - cout << "package C3-state residency for CPU package " << i << "\t0\t\t%\n"; - } - } - for (uint32 i = 0; i < counters.getNumSockets(); ++i) { - stringstream c; - c << "Socket" << i << "/PackageC6StateResidency?"; - if (s == c.str()) { - cout << "package C6-state residency for CPU package " << i << "\t0\t\t%\n"; - } - } - for (uint32 i = 0; i < counters.getNumSockets(); ++i) { - stringstream c; - c << "Socket" << i << "/PackageC7StateResidency?"; - if (s == c.str()) { - cout << "package C7-state residency for CPU package " << i << "\t0\t\t%\n"; - } - } - for (uint32 i = 0; i < counters.getNumSockets(); ++i) { - stringstream c; - c << "Socket" << i << "/BytesWrittenToPMM?"; - if (s == c.str()) { - cout << "written to PMM memory on Socket" << i << "\t0\t\tGB\n"; - //cout << "CPU" << i << "\tBytes written to memory channel\t0\t1\t GB\n"; - } - } - - for (uint32 l = 0; l < counters.getQPILinksPerSocket(); ++l) { - for (uint32 i = 0; i < counters.getNumSockets(); ++i) { - stringstream c; - c << "Socket" << i << "/BytesIncomingTo" << xpi << l << "?"; - if (s == c.str()) { - //cout << "Socket" << i << "\tBytes incoming to QPI link\t" << l<< "\t\t GB\n"; - cout << "incoming to Socket" << i << " " << xpi << " Link" << l << "\t0\t\tGB\n"; - } - } - } - - { - stringstream c; - c << xpi << "_Traffic?"; - if (s == c.str()) { - cout << "Traffic on all " << xpi << " links\t0\t\tGB\n"; - } - } - - for (uint32 i = 0; i < counters.getNumSockets(); ++i) { - stringstream c; - c << "Socket" << i << "/Frequency?"; - if (s == c.str()) { - cout << "Socket" << i << " Frequency\t0\t\tMHz\n"; - } - } - - for (uint32 i = 0; i < counters.getNumSockets(); ++i) { - stringstream c; - c << "Socket" << i << "/IPC?"; - if (s == c.str()) { - cout << "Socket" << i << " IPC\t0\t\t\n"; - } - } - - for (uint32 i = 0; i < counters.getNumSockets(); ++i) { - stringstream c; - c << "Socket" << i << "/L2CacheHitRatio?"; - if (s == c.str()) { - cout << "Socket" << i << " L2 Cache Hit Ratio\t0\t\t\n"; - } - } - - for (uint32 i = 0; i < counters.getNumSockets(); ++i) { - stringstream c; - c << "Socket" << i << "/L3CacheHitRatio?"; - if (s == c.str()) { - cout << "Socket" << i << " L3 Cache Hit Ratio\t0\t\t\n"; - } - } - - for (uint32 i = 0; i < counters.getNumSockets(); ++i) { - stringstream c; - c << "Socket" << i << "/L2CacheMisses?"; - if (s == c.str()) { - cout << "Socket" << i << " L2 Cache Misses\t0\t\t\n"; - } - } - - for (uint32 i = 0; i < counters.getNumSockets(); ++i) { - stringstream c; - c << "Socket" << i << "/L3CacheMisses?"; - if (s == c.str()) { - cout << "Socket" << i << " L3 Cache Misses\t0\t\t\n"; - } - } - - for (uint32 i = 0; i < counters.getNumSockets(); ++i) { - stringstream c; - c << "Socket" << i << "/L3Occupancy"; - if (s == c.str()) { - cout << "Socket" << i << " L3 Cache Occupancy\t0\t\t\n"; - } - } - - for (uint32 i = 0; i < counters.getNumSockets(); ++i) { - stringstream c; - c << "Socket" << i << "/LocalMemoryBandwidth"; - if (s == c.str()) { - cout << "Socket" << i << " Local Memory Bandwidth\t0\t\t\n"; - } - } - - for (uint32 i = 0; i < counters.getNumSockets(); ++i) { - stringstream c; - c << "Socket" << i << "/RemoteMemoryBandwidth"; - if (s == c.str()) { - cout << "Socket" << i << " Remote Memory Bandwidth\t0\t\t\n"; - } - } - - { - stringstream c; - c << "Frequency?"; - if (s == c.str()) { - cout << "Frequency system wide\t0\t\tMhz\n"; - } - } - - { - stringstream c; - c << "IPC?"; - if (s == c.str()) { - cout << "IPC system wide\t0\t\t\n"; - } - } - - { - stringstream c; - c << "L2CacheHitRatio?"; - if (s == c.str()) { - cout << "System wide L2 Cache Hit Ratio\t0\t\t\n"; - } - } - - { - stringstream c; - c << "L3CacheHitRatio?"; - if (s == c.str()) { - cout << "System wide L3 Cache Hit Ratio\t0\t\t\n"; - } - } - - { - stringstream c; - c << "L2CacheMisses?"; - if (s == c.str()) { - cout << "System wide L2 Cache Misses\t0\t\t\n"; - } - } - - { - stringstream c; - c << "L3CacheMisses?"; - if (s == c.str()) { - cout << "System wide L3 Cache Misses\t0\t\t\n"; - } - } - - { - stringstream c; - c << "L3CacheMisses?"; - if (s == c.str()) { - cout << "System wide L3 Cache Misses\t0\t\t\n"; - } - } - - { - stringstream c; - c << "DRAMEnergy?"; - if (s == c.str()) { - cout << "System wide energy consumed by DRAM \t0\t\tJoule\n"; - } - } - { - stringstream c; - c << "CPUEnergy?"; - if (s == c.str()) { - cout << "System wide energy consumed by CPU packages \t0\t\tJoule\n"; - } - } - { - stringstream c; - c << "CoreC0StateResidency?"; - if (s == c.str()) { - cout << "System wide core C0-state residency \t0\t\t%\n"; - } - } - { - stringstream c; - c << "CoreC3StateResidency?"; - if (s == c.str()) { - cout << "System wide core C3-state residency \t0\t\t%\n"; - } - } - { - stringstream c; - c << "CoreC6StateResidency?"; - if (s == c.str()) { - cout << "System wide core C6-state residency \t0\t\t%\n"; - } - } - { - stringstream c; - c << "CoreC7StateResidency?"; - if (s == c.str()) { - cout << "System wide core C7-state residency \t0\t\t%\n"; - } - } - { - stringstream c; - c << "PackageC2StateResidency?"; - if (s == c.str()) { - cout << "System wide package C2-state residency \t0\t\t%\n"; - } - } - { - stringstream c; - c << "PackageC3StateResidency?"; - if (s == c.str()) { - cout << "System wide package C3-state residency \t0\t\t%\n"; - } - } - { - stringstream c; - c << "PackageC6StateResidency?"; - if (s == c.str()) { - cout << "System wide package C6-state residency \t0\t\t%\n"; - } - } - { - stringstream c; - c << "PackageC7StateResidency?"; - if (s == c.str()) { - cout << "System wide package C7-state residency \t0\t\t%\n"; - } - } - - // sensors - -#define OUTPUT_CORE_METRIC(name, function) \ - for (uint32 i = 0; i(i) / 1000000)) - OUTPUT_CORE_METRIC("/IPC", (counters.get(i))) - OUTPUT_CORE_METRIC("/L2CacheHitRatio", (counters.get(i))) - OUTPUT_CORE_METRIC("/L3CacheHitRatio", (counters.get(i))) - OUTPUT_CORE_METRIC("/L2CacheMisses", (counters.get(i))) - OUTPUT_CORE_METRIC("/L3CacheMisses", (counters.get(i))) - OUTPUT_CORE_METRIC("/L3Occupancy", (counters.get(i))) - OUTPUT_CORE_METRIC("/LocalMemoryBandwidth", (counters.get(i))) - OUTPUT_CORE_METRIC("/RemoteMemoryBandwidth", (counters.get(i))) - OUTPUT_CORE_METRIC("/CoreC0StateResidency", (counters.get(0, i) * 100.)) - OUTPUT_CORE_METRIC("/CoreC3StateResidency", (counters.get(3, i) * 100.)) - OUTPUT_CORE_METRIC("/CoreC6StateResidency", (counters.get(6, i) * 100.)) - OUTPUT_CORE_METRIC("/CoreC7StateResidency", (counters.get(7, i) * 100.)) - OUTPUT_CORE_METRIC("/ThermalHeadroom", (counters.get(i))) - - #define OUTPUT_SOCKET_METRIC(name, function) \ - for (uint32 i = 0; i(i))) - OUTPUT_SOCKET_METRIC("/CPUEnergy", (counters.getSocket(i))) - OUTPUT_SOCKET_METRIC("/CoreC0StateResidency", (counters.getSocket(0, i) * 100.)) - OUTPUT_SOCKET_METRIC("/CoreC3StateResidency", (counters.getSocket(3, i) * 100.)) - OUTPUT_SOCKET_METRIC("/CoreC6StateResidency", (counters.getSocket(6, i) * 100.)) - OUTPUT_SOCKET_METRIC("/CoreC7StateResidency", (counters.getSocket(7, i) * 100.)) - OUTPUT_SOCKET_METRIC("/PackageC2StateResidency", (counters.getSocket(2, i) * 100.)) - OUTPUT_SOCKET_METRIC("/PackageC3StateResidency", (counters.getSocket(3, i) * 100.)) - OUTPUT_SOCKET_METRIC("/PackageC6StateResidency", (counters.getSocket(6, i) * 100.)) - OUTPUT_SOCKET_METRIC("/PackageC7StateResidency", (counters.getSocket(7, i) * 100.)) - OUTPUT_SOCKET_METRIC("/ThermalHeadroom", (counters.getSocket(i))) - OUTPUT_SOCKET_METRIC("/BytesReadFromMC", (double(counters.getSocket(i)) / 1024 / 1024 / 1024)) - OUTPUT_SOCKET_METRIC("/BytesWrittenToMC", (double(counters.getSocket(i)) / 1024 / 1024 / 1024)) - OUTPUT_SOCKET_METRIC("/BytesReadFromPMM", (double(counters.getSocket(i)) / 1024 / 1024 / 1024)) - OUTPUT_SOCKET_METRIC("/BytesWrittenToPMM", (double(counters.getSocket(i)) / 1024 / 1024 / 1024)) - OUTPUT_SOCKET_METRIC("/Frequency", (counters.getSocket(i) / 1000000)) - OUTPUT_SOCKET_METRIC("/IPC", (counters.getSocket(i))) - OUTPUT_SOCKET_METRIC("/L2CacheHitRatio", (counters.getSocket(i))) - OUTPUT_SOCKET_METRIC("/L3CacheHitRatio", (counters.getSocket(i))) - OUTPUT_SOCKET_METRIC("/L2CacheMisses", (counters.getSocket(i))) - OUTPUT_SOCKET_METRIC("/L3CacheMisses", (counters.getSocket(i))) - OUTPUT_SOCKET_METRIC("/L3Occupancy", (counters.getSocket(i))) - OUTPUT_SOCKET_METRIC("/LocalMemoryBandwidth", (counters.getSocket(i))) - OUTPUT_SOCKET_METRIC("/RemoteMemoryBandwidth", (counters.getSocket(i))) - - for (uint32 l = 0; l < counters.getQPILinksPerSocket(); ++l) { - for (uint32 i = 0; i < counters.getNumSockets(); ++i) { - stringstream c; - c << "Socket" << i << "/BytesIncomingTo" << xpi << l; - if (s == c.str()) { - cout << double(counters.getSocket(i, l)) / 1024 / 1024 / 1024 << "\n"; - } - } - } - - #define OUTPUT_SYSTEM_METRIC(name, function) \ - { \ - stringstream c; \ - c << name; \ - if (s == c.str()) { \ - cout << function << "\n"; \ - } \ - } - - OUTPUT_SYSTEM_METRIC("DRAMEnergy", (counters.getSystem())) - OUTPUT_SYSTEM_METRIC("CPUEnergy", (counters.getSystem())) - OUTPUT_SYSTEM_METRIC("CoreC0StateResidency", (counters.getSystem(0) * 100.)) - OUTPUT_SYSTEM_METRIC("CoreC3StateResidency", (counters.getSystem(3) * 100.)) - OUTPUT_SYSTEM_METRIC("CoreC6StateResidency", (counters.getSystem(6) * 100.)) - OUTPUT_SYSTEM_METRIC("CoreC7StateResidency", (counters.getSystem(7) * 100.)) - OUTPUT_SYSTEM_METRIC("PackageC2StateResidency", (counters.getSystem(2) * 100.)) - OUTPUT_SYSTEM_METRIC("PackageC3StateResidency", (counters.getSystem(3) * 100.)) - OUTPUT_SYSTEM_METRIC("PackageC6StateResidency", (counters.getSystem(6) * 100.)) - OUTPUT_SYSTEM_METRIC("PackageC7StateResidency", (counters.getSystem(7) * 100.)) - OUTPUT_SYSTEM_METRIC("Frequency", (double(counters.getSystem()) / 1000000)) - OUTPUT_SYSTEM_METRIC("IPC", (double(counters.getSystem()))) - OUTPUT_SYSTEM_METRIC("L2CacheHitRatio", (double(counters.getSystem()))) - OUTPUT_SYSTEM_METRIC("L3CacheHitRatio", (double(counters.getSystem()))) - OUTPUT_SYSTEM_METRIC("L2CacheMisses", (double(counters.getSystem()))) - OUTPUT_SYSTEM_METRIC("L3CacheMisses", (double(counters.getSystem()))) - OUTPUT_SYSTEM_METRIC(std::string(xpi + std::string("_Traffic")), - (double(counters.getSystem()) / 1024 / 1024 / 1024)) - - // exit - if (s == "quit" || s == "exit" || s == "") { - break; - } - - - cout << "ksysguardd> "; - } - - return 0; -} diff --git a/src/realtime.cpp b/src/realtime.cpp deleted file mode 100644 index e679253a..00000000 --- a/src/realtime.cpp +++ /dev/null @@ -1,275 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -// Copyright (c) 2009-2022, Intel Corporation -// written by Roman Dementiev -// - -#include "cpucounters.h" -#include "cpuasynchcounter.h" -#include -#include -#include -#include -#include - -/*! \file realtime.cpp - \brief Two use-cases: realtime data structure performance analysis and memory-bandwidth aware scheduling -*/ - -using std::cout; - -inline double my_timestamp() -{ - struct timeval tp; - gettimeofday(&tp, NULL); - return double(tp.tv_sec) + tp.tv_usec / 1000000.; -} - -long long int fib(long long int num) -{ - long long int result = 1, a = 1, b = 1; - - for (long long int i = 3; i <= num; ++i) - { - result = a + b; - a = b; - b = result; - } - - return result; -} - -SystemCounterState before_sstate, after_sstate; -double before_time, after_time; - -AsynchronCounterState counters; - -long long int all_fib = 0; - - -void CPU_intensive_task() -{ - cout << "CPU task\n"; - all_fib += fib(80000000ULL + (rand() % 2)); -} - - -template -void Memory_intensive_task(DS & ds) -{ - cout << "Mem task\n"; - // cppcheck-suppress ignoredReturnValue - std::find(ds.begin(), ds.end(), ds.size()); -} - -double currentMemoryBandwidth() -{ - return (counters.getSystem() + counters.getSystem()) / (1024 * 1024); -} - -template -void measure(DS & ds, size_t repeat, size_t nelements) -{ - SystemCounterState before_sstate, after_sstate; - double before_ts = 0.0, after_ts; - - // warm up - // cppcheck-suppress ignoredReturnValue - std::find(ds.begin(), ds.end(), nelements); - - double before1_ts; -#if 0 - for (int kkk = 1000; kkk > 0; --kkk) - { - ::sleep(1); - before1_ts = my_timestamp(); - - // start measuring - before_sstate = getSystemCounterState(); - before_ts = my_timestamp(); - - cout << "Response time of getSystemCounterState(): " << 1000. * (before_ts - before1_ts) << " ms\n"; - } -#endif - - // cppcheck-suppress ignoredReturnValue - for (int j = 0; j < repeat; ++j) std::find(ds.begin(), ds.end(), nelements); - - // stop measuring - after_sstate = getSystemCounterState(); - after_ts = my_timestamp(); - - - cout << "\nSearch runtime: " << ((after_ts - before_ts) * 1000. / repeat) << " ms \n"; - cout << "Search runtime per element: " << ((after_ts - before_ts) * 1000000000. / repeat) / nelements << " ns \n"; - - cout << "Number of L2 cache misses per 1000 elements: " - << (1000. * getL2CacheMisses(before_sstate, after_sstate) / repeat) / nelements << - " \nL2 Cache hit ratio : " << getL2CacheHitRatio(before_sstate, after_sstate) * 100. << " %\n"; - - - cout << "Number of L3 cache misses per 1000 elements: " - << (1000. * getL3CacheMisses(before_sstate, after_sstate) / repeat) / nelements << - " \nL3 Cache hit ratio : " << getL3CacheHitRatio(before_sstate, after_sstate) * 100. << " %\n"; - - cout << "Bytes written to memory controller per element: " << - (double(getBytesWrittenToMC(before_sstate, after_sstate)) / repeat) / nelements << "\n"; - - cout << "Bytes read from memory controller per element : " << - (double(getBytesReadFromMC(before_sstate, after_sstate)) / repeat) / nelements << "\n"; - - - cout << "Used memory bandwidth: " << - ((getBytesReadFromMC(before_sstate, after_sstate) + getBytesWrittenToMC(before_sstate, after_sstate)) / (after_ts - before_ts)) / (1024 * 1024) << " MByte/sec\n"; - - cout << "Instructions retired: " << getInstructionsRetired(before_sstate, after_sstate) / 1000000 << "mln\n"; - - cout << "CPU cycles: " << getCycles(before_sstate, after_sstate) / 1000000 << "mln\n"; - - cout << "Instructions per cycle: " << getCoreIPC(before_sstate, after_sstate) << "\n"; - cout << flush; -} - -#if 0 -typedef int T; - -#else - -struct T -{ - int key[1] = { 0 }; - int data[15] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };; - - T() { } - T(int a) { key[0] = a; } - - bool operator == (const T & k) const - { - return k.key[0] == key[0]; - } -}; - -#endif - -int main(int argc, char * argv[]) -{ - PCM * m = PCM::getInstance(); - - if (!m->good()) - { - cout << "Can not access CPU counters\n"; - cout << "Try to execute 'modprobe msr' as root user and then\n"; - cout << "you also must have read and write permissions for /dev/cpu/?/msr devices (the 'chown' command can help)."; - return -1; - } - - if (m->program() != PCM::Success) { - cout << "Program was not successful...\n"; - deleteAndNullify(m); - return -1; - } - - int nelements = atoi(argv[1]); - - -#if 1 /* use-case: compare data structures in real-time */ - std::list list; - std::vector vector; - int i = 0; - - for ( ; i < nelements; ++i) - { - list.push_back(i); - vector.push_back(i); - } - - - unsigned long long int totalops = 200000ULL * 1000ULL * 64ULL / sizeof(T); - int repeat = totalops / nelements, j; - - cout << "\n\nElements to traverse: " << totalops << "\n"; - cout << "Items in data structure: " << nelements << "\n"; - cout << "Elements data size: " << sizeof(T) * nelements / 1024 << " KB\n"; - cout << "Test repetitions: " << repeat << "\n"; - - cout << "\n*List data structure*\n"; - measure(list, repeat, nelements); - - cout << "\n\n*Vector/array data structure*\n"; - measure(vector, repeat, nelements); - -#else - /* use-case: memory bandwidth-aware scheduling */ - - std::vector vector; - nelements = 13000000; - - int i = 0; - - cout << "Elements data size: " << sizeof(T) * nelements / 1024 << " KB\n"; - - for ( ; i < nelements; ++i) - { - vector.push_back(i); - } - - double before_ts, after_ts; - - before_ts = my_timestamp(); - { - int m_tasks = 1000; - int c_tasks = 1000; - while (m_tasks + c_tasks != 0) - { - if (m_tasks > 0) - { - Memory_intensive_task(vector); - --m_tasks; - continue; - } - - if (c_tasks > 0) - { - CPU_intensive_task(); - --c_tasks; - } - } - } - after_ts = my_timestamp(); - - cout << "In order scheduling, Running time: " << (after_ts - before_ts) << " seconds\n"; - - - before_ts = my_timestamp(); - { - int m_tasks = 1000; - int c_tasks = 1000; - while (m_tasks + c_tasks != 0) - { - double band = currentMemoryBandwidth(); - //cout << "Mem band: " << band << " MB/sec\n"; - if (m_tasks > 0 && (band < (25 * 1024 /* MB/sec*/) - || c_tasks == 0)) - { - Memory_intensive_task(vector); - --m_tasks; - continue; - } - - if (c_tasks > 0) - { - CPU_intensive_task(); - --c_tasks; - continue; - } - } - } - - after_ts = my_timestamp(); - - cout << "CPU monitoring conscoius scheduling, Running time: " << (after_ts - before_ts) << " seconds\n"; - -#endif - m->cleanup(); - - return 0; -} diff --git a/src/threadpool.h b/src/threadpool.h index ccf2cee0..58615c4a 100644 --- a/src/threadpool.h +++ b/src/threadpool.h @@ -5,12 +5,18 @@ #include "debug.h" +#include +#include +#include +#include +#include #include #include #include #include #include #include +#include namespace pcm { @@ -76,7 +82,52 @@ class ThreadPool { public: static ThreadPool& getInstance() { - static ThreadPool tp_(64); + // Scale the worker pool with available hardware concurrency rather than + // hard-coding a small fixed size. The fixed 64-thread pool combined with + // line-oriented blocking parsing made it cheap for a remote attacker to + // saturate all workers (CWE-400/CWE-770). The per-request wall-clock + // deadline added in the request reader is the primary defense, but + // sizing the pool generously (and at minimum 64) raises the bar for + // any future similar resource-exhaustion attempts and lets larger + // hosts actually use their cores. + // + // An upper bound (kMaxThreads) prevents the pool from growing without + // limit on hosts (or container runtimes) that report very large + // hardware_concurrency() values, which could itself exhaust memory or + // scheduler resources. The pool size is also overridable at startup + // via the PCM_SENSOR_SERVER_POOL_SIZE environment variable so + // deployments can tune it without rebuilding. + static const unsigned int kMinThreads = 64; + static const unsigned int kMaxThreads = 256; + static const unsigned int n = []() { + if ( const char* env = std::getenv( "PCM_SENSOR_SERVER_POOL_SIZE" ) ) { + try { + const std::string envStr( env ); + std::size_t pos = 0; + unsigned long v = std::stoul( envStr, &pos ); + if ( envStr.find_first_not_of( " \t\n\r\f\v", pos ) == std::string::npos && + v >= kMinThreads && v <= kMaxThreads ) + return static_cast( v ); + } catch ( const std::invalid_argument& ) { + // fall through to default sizing on unparseable value + } catch ( const std::out_of_range& ) { + // fall through to default sizing on out-of-range value + } + } + const unsigned int hw = std::thread::hardware_concurrency(); + // Compute hw*2 in a wider type to avoid overflow before clamping + // on platforms / container runtimes that report a very large + // hardware_concurrency() value. + // min/max are wrapped in extra parentheses to defeat the macro + // definitions of min/max that introduces on MSVC + // (see AGENTS.md for the project-wide convention). + const std::uint64_t scaled = static_cast( hw ) * 2u; + const std::uint64_t clamped = (std::min)( + kMaxThreads, + (std::max)( kMinThreads, scaled ) ); + return static_cast( clamped ); + }(); + static ThreadPool tp_( static_cast( n ) ); return tp_; } diff --git a/src/topologyentry.h b/src/topologyentry.h index a0c0c661..e3943ba2 100644 --- a/src/topologyentry.h +++ b/src/topologyentry.h @@ -4,9 +4,7 @@ #pragma once #include "types.h" -#ifndef USER_KERNEL_SHARED #include "debug.h" -#endif namespace pcm { @@ -116,17 +114,13 @@ struct PCM_API TopologyEntry // describes a core inline void fillEntry(TopologyEntry & entry, const uint32 & smtMaskWidth, const uint32 & coreMaskWidth, const uint32 & l2CacheMaskShift, const int apic_id) { - #ifndef USER_KERNEL_SHARED DBG(1, "entry.os_id = ", entry.os_id, " apic_id = ", apic_id); - #endif entry.thread_id = smtMaskWidth ? extract_bits_32(apic_id, 0, smtMaskWidth - 1) : 0; entry.core_id = coreMaskWidth ? extract_bits_32(apic_id, smtMaskWidth, smtMaskWidth + coreMaskWidth - 1) : 0; entry.socket_id = extract_bits_32(apic_id, smtMaskWidth + coreMaskWidth, 31); entry.tile_id = extract_bits_32(apic_id, l2CacheMaskShift, 31); entry.socket_unique_core_id = entry.core_id; - #ifndef USER_KERNEL_SHARED DBG(1, "entry.os_id = ", entry.os_id, " apic_id = ", apic_id, " entry.thread_id = ", entry.thread_id, " entry.core_id = ", entry.core_id, " entry.socket_id = ", entry.socket_id , " entry.tile_id = ", entry.tile_id, " entry.socket_unique_core_id = ", entry.socket_unique_core_id); - #endif } inline bool initCoreMasks(uint32 & smtMaskWidth, uint32 & coreMaskWidth, uint32 & l2CacheMaskShift, uint32 & l3CacheMaskShift) @@ -149,9 +143,7 @@ inline bool initCoreMasks(uint32 & smtMaskWidth, uint32 & coreMaskWidth, uint32 } levelType = extract_bits_32(cpuid_args.array[2], 8, 15); levelShift = extract_bits_32(cpuid_args.array[0], 0, 4); - #ifndef USER_KERNEL_SHARED DBG(1, "levelType = ", levelType, " levelShift = ", levelShift); - #endif switch (levelType) { case 1: //level type is SMT, so levelShift is the SMT_Mask_Width @@ -181,7 +173,6 @@ inline bool initCoreMasks(uint32 & smtMaskWidth, uint32 & coreMaskWidth, uint32 return false; } - (void) coreMaskWidth; // to suppress warnings on MacOS (unused vars) uint32 threadsSharingL2 = 0; uint32 l2CacheMaskWidth = 0; @@ -194,9 +185,7 @@ inline bool initCoreMasks(uint32 & smtMaskWidth, uint32 & coreMaskWidth, uint32 l2CacheMaskShift++; } -#ifndef USER_KERNEL_SHARED DBG(1, "Number of threads sharing L2 cache = " , threadsSharingL2, " [the most significant bit = " , l2CacheMaskShift , "]"); -#endif uint32 threadsSharingL3 = 0; uint32 l3CacheMaskWidth = 0; @@ -209,23 +198,16 @@ inline bool initCoreMasks(uint32 & smtMaskWidth, uint32 & coreMaskWidth, uint32 l3CacheMaskShift++; } -#ifndef USER_KERNEL_SHARED DBG(1, "Number of threads sharing L3 cache = " , threadsSharingL3, " [the most significant bit = " , l3CacheMaskShift , "]"); -#endif - (void) threadsSharingL2; // to suppress warnings on MacOS (unused vars) - (void) threadsSharingL3; // to suppress warnings on MacOS (unused vars) // Validate l3CacheMaskShift and ensure the bit range is correct if (l3CacheMaskShift > 31) { -#ifndef USER_KERNEL_SHARED DBG(0, "Invalid bit range for L3 cache ID extraction = ", l3CacheMaskShift); -#endif return false; } -#ifndef USER_KERNEL_SHARED uint32 it = 0; for (int i = 0; i < 100; ++i) @@ -258,11 +240,8 @@ inline bool initCoreMasks(uint32 & smtMaskWidth, uint32 & coreMaskWidth, uint32 " shift = " , CacheMaskShift); ++it; } -#endif } - #ifndef USER_KERNEL_SHARED DBG(1, "smtMaskWidth = ", smtMaskWidth, " coreMaskWidth = ", coreMaskWidth, " l2CacheMaskShift = ", l2CacheMaskShift, " l3CacheMaskShift = ", l3CacheMaskShift); - #endif return true; } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 528f0d16..951bc217 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -11,15 +11,13 @@ if(UNIX) add_executable(daemon_alignment_test ${TEST_FILE}) target_link_libraries(daemon_alignment_test) - if(NOT APPLE) - # numa_to_socket_test - add_executable(numa_to_socket_test numa_to_socket_test.cpp) - target_link_libraries(numa_to_socket_test Threads::Threads PCM_STATIC) - - # cache_verification_test - add_executable(cache_verification_test cache_verification_test.cpp) - target_link_libraries(cache_verification_test Threads::Threads PCM_STATIC) - endif() + # numa_to_socket_test + add_executable(numa_to_socket_test numa_to_socket_test.cpp) + target_link_libraries(numa_to_socket_test Threads::Threads PCM_STATIC) + + # cache_verification_test + add_executable(cache_verification_test cache_verification_test.cpp) + target_link_libraries(cache_verification_test Threads::Threads PCM_STATIC) # PCM_STATIC + pcm_sensor = urltest if(LINUX) diff --git a/tests/numa_to_socket_test.cpp b/tests/numa_to_socket_test.cpp index b2395904..1da97c9e 100644 --- a/tests/numa_to_socket_test.cpp +++ b/tests/numa_to_socket_test.cpp @@ -56,7 +56,6 @@ int main() std::cout << "This may be expected on:\n"; std::cout << " - Single-socket systems\n"; std::cout << " - Systems without NUMA support\n"; - std::cout << " - macOS (not implemented)\n"; std::cout << " - Systems where NUMA information is not available\n"; } diff --git a/tests/test.sh b/tests/test.sh index d6f0b86d..14e0173e 100755 --- a/tests/test.sh +++ b/tests/test.sh @@ -492,7 +492,7 @@ online_offline_cores 1 echo "Running Unit Tests" failed=() for test_binary in ./tests/utests/*; do - if [ -x "$test_binary" ]; then + if [ -x "$test_binary" ] && [ -f "$test_binary" ]; then echo "Running $test_binary" "$test_binary" if [ "$?" -ne "0" ]; then diff --git a/tests/utests/CMakeLists.txt b/tests/utests/CMakeLists.txt index a079ae39..aa394920 100644 --- a/tests/utests/CMakeLists.txt +++ b/tests/utests/CMakeLists.txt @@ -10,25 +10,23 @@ enable_testing() include_directories(${CMAKE_SOURCE_DIR}/src/) include_directories("${GMOCK_DIR}/include") -if(APPLE) - include_directories("${CMAKE_SOURCE_DIR}/src/MacMSRDriver") # target_include_directories doesn't work -endif() - file(GLOB LSPCI_TEST_FILES lspci-utest.cpp ${CMAKE_SOURCE_DIR}/src/lspci.cpp) file(GLOB PCM_IIO_TEST_FILES pcm-iio-utest.cpp ${CMAKE_SOURCE_DIR}/src/pcm-iio-pmu.cpp ${CMAKE_SOURCE_DIR}/src/pcm-iio-topology.cpp) file(GLOB READ_NUMBER_TEST_FILES read-number-utest.cpp) +file(GLOB EVENT_RESOLVER_TEST_FILES event-resolver-utest.cpp ${CMAKE_SOURCE_DIR}/src/event-resolver.cpp) +file(GLOB PCM_IO_METRICS_TEST_FILES pcm-io-metrics-utest.cpp ${CMAKE_SOURCE_DIR}/src/pcm-io-metrics.cpp) file(GLOB PCM_SENSOR_SERVER_OVERFLOW_TEST_FILES pcm-sensor-server-overflow-utest.cpp) +file(GLOB PCM_SENSOR_SERVER_HEADER_LIMITS_TEST_FILES pcm-sensor-server-header-limits-utest.cpp) -if(APPLE) - set(LIBS PcmMsr Threads::Threads PCM_STATIC) -else() - set(LIBS Threads::Threads PCM_STATIC) -endif() +set(LIBS Threads::Threads PCM_STATIC) add_executable(lspci-utest ${LSPCI_TEST_FILES}) add_executable(pcm-iio-utest ${PCM_IIO_TEST_FILES}) add_executable(read-number-utest ${READ_NUMBER_TEST_FILES}) +add_executable(event-resolver-utest ${EVENT_RESOLVER_TEST_FILES}) +add_executable(pcm-io-metrics-utest ${PCM_IO_METRICS_TEST_FILES}) add_executable(pcm-sensor-server-overflow-utest ${PCM_SENSOR_SERVER_OVERFLOW_TEST_FILES}) +add_executable(pcm-sensor-server-header-limits-utest ${PCM_SENSOR_SERVER_HEADER_LIMITS_TEST_FILES}) configure_file( ${CMAKE_SOURCE_DIR}/src/opCode-6-174.txt @@ -36,6 +34,16 @@ configure_file( COPYONLY ) +# Copy perfmon data for event-resolver tests. +# build/bin/ copy is for CI (test.sh CWD), utests copy is for local dev. +# PMURegisterDeclarations is copied to build/bin/ by src/CMakeLists.txt. +file(COPY ${CMAKE_SOURCE_DIR}/perfmon + DESTINATION ${CMAKE_BINARY_DIR}/bin) +file(COPY ${CMAKE_SOURCE_DIR}/perfmon + DESTINATION ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) +file(COPY ${CMAKE_SOURCE_DIR}/src/PMURegisterDeclarations + DESTINATION ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) + target_link_libraries( lspci-utest GTest::gtest_main @@ -57,6 +65,22 @@ target_link_libraries( ${LIBS} ) +target_link_libraries( + event-resolver-utest + GTest::gtest_main + GTest::gmock_main + ${LIBS} + PCM_SIMDJSON +) + +target_link_libraries( + pcm-io-metrics-utest + GTest::gtest_main + GTest::gmock_main + ${LIBS} + PCM_SIMDJSON +) + target_link_libraries( pcm-sensor-server-overflow-utest GTest::gtest_main @@ -64,8 +88,18 @@ target_link_libraries( ${LIBS} ) +target_link_libraries( + pcm-sensor-server-header-limits-utest + GTest::gtest_main + GTest::gmock_main + ${LIBS} +) + include(GoogleTest) gtest_discover_tests(lspci-utest) gtest_discover_tests(pcm-iio-utest) gtest_discover_tests(read-number-utest) +gtest_discover_tests(event-resolver-utest) +gtest_discover_tests(pcm-io-metrics-utest) gtest_discover_tests(pcm-sensor-server-overflow-utest) +gtest_discover_tests(pcm-sensor-server-header-limits-utest) diff --git a/tests/utests/event-resolver-utest.cpp b/tests/utests/event-resolver-utest.cpp new file mode 100644 index 00000000..ececb3a6 --- /dev/null +++ b/tests/utests/event-resolver-utest.cpp @@ -0,0 +1,442 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2026, Intel Corporation + +#include "event-resolver.h" +#include "utils.h" +#include + +using namespace pcm; + +// Test fixture: initializes resolver with real ICX perfmon data +class EventResolverTest : public ::testing::Test { +protected: + void SetUp() override + { + ASSERT_TRUE(resolver.init("GenuineIntel-6-6A-0", "perfmon")); + } + PerfmonEventResolver resolver; +}; + +TEST_F(EventResolverTest, InitWithRealPerfmon) +{ + EXPECT_TRUE(resolver.isInitialized()); +} + +TEST(EventResolverInitTest, InitFailsWithBadPath) +{ + PerfmonEventResolver resolver; + EXPECT_FALSE(resolver.init("GenuineIntel-6-6A-0", "nonexistent/path")); + EXPECT_FALSE(resolver.isInitialized()); +} + +TEST(EventResolverInitTest, InitFailsWithBadCPU) +{ + PerfmonEventResolver resolver; + EXPECT_FALSE(resolver.init("GenuineIntel-99-FF-0", "perfmon")); +} + +TEST_F(EventResolverTest, IsEventFindsUncoreEvent) +{ + // UNC_CHA_DIR_UPDATE.HA is the first event in icelakex_uncore.json + EXPECT_TRUE(resolver.isEvent("UNC_CHA_DIR_UPDATE.HA")); +} + +TEST_F(EventResolverTest, IsEventFindsCoreEvent) +{ + // INST_RETIRED.ANY should be in icelakex_core.json + EXPECT_TRUE(resolver.isEvent("INST_RETIRED.ANY")); +} + +TEST_F(EventResolverTest, UnknownEventReturnsFalse) +{ + EXPECT_FALSE(resolver.isEvent("NONEXISTENT_EVENT.FOO")); +} + +TEST_F(EventResolverTest, IsFieldAndGetField) +{ + const std::string event = "UNC_CHA_DIR_UPDATE.HA"; + + EXPECT_TRUE(resolver.isField(event, "Unit")); + EXPECT_TRUE(resolver.isField(event, "EventCode")); + EXPECT_TRUE(resolver.isField(event, "UMask")); + EXPECT_TRUE(resolver.isField(event, "EventName")); + + EXPECT_EQ(resolver.getField(event, "Unit"), "CHA"); + EXPECT_EQ(resolver.getField(event, "EventCode"), "0x54"); + EXPECT_EQ(resolver.getField(event, "UMask"), "0x01"); +} + +TEST_F(EventResolverTest, GetFieldMissing) +{ + const std::string event = "UNC_CHA_DIR_UPDATE.HA"; + + EXPECT_FALSE(resolver.isField(event, "NonExistentField")); + EXPECT_EQ(resolver.getField(event, "NonExistentField"), ""); +} + +TEST_F(EventResolverTest, GetFieldForUnknownEvent) +{ + EXPECT_FALSE(resolver.isField("NONEXISTENT.EVENT", "Unit")); + EXPECT_EQ(resolver.getField("NONEXISTENT.EVENT", "Unit"), ""); +} + +TEST_F(EventResolverTest, ResolveEventBitPacking) +{ + // UNC_CHA_DIR_UPDATE.HA: Unit=CHA, EventCode=0x54, UMask=0x01 + // PMURegisterDeclarations for CHA programmable: + // EventCode: Config[0], Position 0, Width 8 + // UMask: Config[0], Position 8, Width 8 + std::string pmuName; + PCM::RawEventConfig config; + + ASSERT_TRUE(resolver.resolveEvent("UNC_CHA_DIR_UPDATE.HA", pmuName, config)); + EXPECT_EQ(pmuName, "cha"); + + // Check EventCode in bits 0-7 of config[0] + uint64 eventCode = config.first[0] & 0xFF; + EXPECT_EQ(eventCode, 0x54u); + + // Check UMask in bits 8-15 of config[0] + uint64 umask = (config.first[0] >> 8) & 0xFF; + EXPECT_EQ(umask, 0x01u); + + // Event name stored in second element + EXPECT_EQ(config.second, "UNC_CHA_DIR_UPDATE.HA"); +} + +TEST_F(EventResolverTest, ResolveSecondEvent) +{ + // UNC_CHA_DIR_UPDATE.TOR: Unit=CHA, EventCode=0x54, UMask=0x02 + std::string pmuName; + PCM::RawEventConfig config; + + ASSERT_TRUE(resolver.resolveEvent("UNC_CHA_DIR_UPDATE.TOR", pmuName, config)); + EXPECT_EQ(pmuName, "cha"); + + uint64 eventCode = config.first[0] & 0xFF; + EXPECT_EQ(eventCode, 0x54u); + + uint64 umask = (config.first[0] >> 8) & 0xFF; + EXPECT_EQ(umask, 0x02u); +} + +TEST_F(EventResolverTest, ResolveEventUnknownReturnsFalse) +{ + std::string pmuName; + PCM::RawEventConfig config; + EXPECT_FALSE(resolver.resolveEvent("NONEXISTENT.EVENT", pmuName, config)); +} + +TEST_F(EventResolverTest, ResolveUninitializedReturnsFalse) +{ + PerfmonEventResolver uninitResolver; + std::string pmuName; + PCM::RawEventConfig config; + EXPECT_FALSE(uninitResolver.resolveEvent("UNC_CHA_DIR_UPDATE.HA", pmuName, config)); +} + +// Verify all ICX TMA events exist with correct EventCode, UMask, and Unit fields +TEST_F(EventResolverTest, AllICXTmaEventsFieldValues) +{ + struct TmaEvent { std::string name, eventCode, umask, unit; }; + // 216 events from perfmon/ICX/metrics/icelakex_metrics.json (base names, modifiers stripped) + // 4 PERF_METRICS.* events excluded — they are fixed-counter metrics, not in perfmon DB + const std::vector tmaEvents = { + {"ARITH.DIVIDER_ACTIVE", "0x14", "0x09", ""}, + {"ARITH.FP_DIVIDER_ACTIVE", "0x14", "0x01", ""}, + {"ASSISTS.ANY", "0xc1", "0x07", ""}, + {"ASSISTS.FP", "0xc1", "0x02", ""}, + {"BACLEARS.ANY", "0xe6", "0x01", ""}, + {"BR_INST_RETIRED.ALL_BRANCHES", "0xc4", "0x00", ""}, + {"BR_INST_RETIRED.COND_NTAKEN", "0xc4", "0x10", ""}, + {"BR_INST_RETIRED.COND_TAKEN", "0xc4", "0x01", ""}, + {"BR_INST_RETIRED.FAR_BRANCH", "0xc4", "0x40", ""}, + {"BR_INST_RETIRED.NEAR_CALL", "0xc4", "0x02", ""}, + {"BR_INST_RETIRED.NEAR_RETURN", "0xc4", "0x08", ""}, + {"BR_INST_RETIRED.NEAR_TAKEN", "0xc4", "0x20", ""}, + {"BR_MISP_RETIRED.ALL_BRANCHES", "0xc5", "0x00", ""}, + {"BR_MISP_RETIRED.COND_NTAKEN", "0xc5", "0x10", ""}, + {"BR_MISP_RETIRED.COND_TAKEN", "0xc5", "0x01", ""}, + {"BR_MISP_RETIRED.INDIRECT", "0xc5", "0x80", ""}, + {"BR_MISP_RETIRED.RET", "0xc5", "0x08", ""}, + {"CORE_POWER.LVL0_TURBO_LICENSE", "0x28", "0x07", ""}, + {"CORE_POWER.LVL1_TURBO_LICENSE", "0x28", "0x18", ""}, + {"CORE_POWER.LVL2_TURBO_LICENSE", "0x28", "0x20", ""}, + {"CPU_CLK_UNHALTED.DISTRIBUTED", "0xec", "0x02", ""}, + {"CPU_CLK_UNHALTED.ONE_THREAD_ACTIVE", "0x3C", "0x02", ""}, + {"CPU_CLK_UNHALTED.REF_DISTRIBUTED", "0x3c", "0x08", ""}, + {"CPU_CLK_UNHALTED.REF_TSC", "0x00", "0x03", ""}, + {"CPU_CLK_UNHALTED.THREAD", "0x00", "0x02", ""}, + {"CPU_CLK_UNHALTED.THREAD_P", "0x3C", "0x00", ""}, + {"CYCLE_ACTIVITY.CYCLES_L1D_MISS", "0xA3", "0x08", ""}, + {"CYCLE_ACTIVITY.CYCLES_MEM_ANY", "0xA3", "0x10", ""}, + {"CYCLE_ACTIVITY.STALLS_L1D_MISS", "0xA3", "0x0C", ""}, + {"CYCLE_ACTIVITY.STALLS_L2_MISS", "0xa3", "0x05", ""}, + {"CYCLE_ACTIVITY.STALLS_L3_MISS", "0xa3", "0x06", ""}, + {"CYCLE_ACTIVITY.STALLS_MEM_ANY", "0xa3", "0x14", ""}, + {"CYCLE_ACTIVITY.STALLS_TOTAL", "0xa3", "0x04", ""}, + {"DECODE.LCP", "0x87", "0x01", ""}, + {"DSB2MITE_SWITCHES.PENALTY_CYCLES", "0xab", "0x02", ""}, + {"DTLB_LOAD_MISSES.STLB_HIT", "0x08", "0x20", ""}, + {"DTLB_LOAD_MISSES.WALK_ACTIVE", "0x08", "0x10", ""}, + {"DTLB_LOAD_MISSES.WALK_COMPLETED", "0x08", "0x0e", ""}, + {"DTLB_LOAD_MISSES.WALK_COMPLETED_1G", "0x08", "0x08", ""}, + {"DTLB_LOAD_MISSES.WALK_COMPLETED_2M_4M", "0x08", "0x04", ""}, + {"DTLB_LOAD_MISSES.WALK_COMPLETED_4K", "0x08", "0x02", ""}, + {"DTLB_LOAD_MISSES.WALK_PENDING", "0x08", "0x10", ""}, + {"DTLB_STORE_MISSES.STLB_HIT", "0x49", "0x20", ""}, + {"DTLB_STORE_MISSES.WALK_ACTIVE", "0x49", "0x10", ""}, + {"DTLB_STORE_MISSES.WALK_COMPLETED", "0x49", "0x0e", ""}, + {"DTLB_STORE_MISSES.WALK_COMPLETED_1G", "0x49", "0x08", ""}, + {"DTLB_STORE_MISSES.WALK_COMPLETED_2M_4M", "0x49", "0x04", ""}, + {"DTLB_STORE_MISSES.WALK_COMPLETED_4K", "0x49", "0x02", ""}, + {"DTLB_STORE_MISSES.WALK_PENDING", "0x49", "0x10", ""}, + {"EXE_ACTIVITY.1_PORTS_UTIL", "0xa6", "0x02", ""}, + {"EXE_ACTIVITY.2_PORTS_UTIL", "0xa6", "0x04", ""}, + {"EXE_ACTIVITY.3_PORTS_UTIL", "0xa6", "0x08", ""}, + {"EXE_ACTIVITY.BOUND_ON_STORES", "0xA6", "0x40", ""}, + {"FP_ARITH_INST_RETIRED.128B_PACKED_DOUBLE", "0xc7", "0x04", ""}, + {"FP_ARITH_INST_RETIRED.128B_PACKED_SINGLE", "0xc7", "0x08", ""}, + {"FP_ARITH_INST_RETIRED.256B_PACKED_DOUBLE", "0xc7", "0x10", ""}, + {"FP_ARITH_INST_RETIRED.256B_PACKED_SINGLE", "0xc7", "0x20", ""}, + {"FP_ARITH_INST_RETIRED.4_FLOPS", "0xc7", "0x18", ""}, + {"FP_ARITH_INST_RETIRED.512B_PACKED_DOUBLE", "0xc7", "0x40", ""}, + {"FP_ARITH_INST_RETIRED.512B_PACKED_SINGLE", "0xc7", "0x80", ""}, + {"FP_ARITH_INST_RETIRED.8_FLOPS", "0xc7", "0x60", ""}, + {"FP_ARITH_INST_RETIRED.SCALAR", "0xc7", "0x03", ""}, + {"FP_ARITH_INST_RETIRED.SCALAR_DOUBLE", "0xc7", "0x01", ""}, + {"FP_ARITH_INST_RETIRED.SCALAR_SINGLE", "0xc7", "0x02", ""}, + {"FP_ARITH_INST_RETIRED.VECTOR", "0xc7", "0xfc", ""}, + {"FRONTEND_RETIRED.ANY_DSB_MISS", "0xc6", "0x01", ""}, + {"FRONTEND_RETIRED.L2_MISS", "0xc6", "0x01", ""}, + {"ICACHE_16B.IFDATA_STALL", "0x80", "0x04", ""}, + {"ICACHE_DATA.STALLS", "0x80", "0x04", ""}, + {"ICACHE_TAG.STALLS", "0x83", "0x04", ""}, + {"IDQ.DSB_CYCLES_ANY", "0x79", "0x08", ""}, + {"IDQ.DSB_CYCLES_OK", "0x79", "0x08", ""}, + {"IDQ.DSB_UOPS", "0x79", "0x08", ""}, + {"IDQ.MITE_CYCLES_ANY", "0x79", "0x04", ""}, + {"IDQ.MITE_CYCLES_OK", "0x79", "0x04", ""}, + {"IDQ.MITE_UOPS", "0x79", "0x04", ""}, + {"IDQ.MS_SWITCHES", "0x79", "0x30", ""}, + {"IDQ.MS_UOPS", "0x79", "0x30", ""}, + {"IDQ_UOPS_NOT_DELIVERED.CYCLES_0_UOPS_DELIV.CORE", "0x9c", "0x01", ""}, + {"INST_DECODED.DECODERS", "0x55", "0x01", ""}, + {"INST_RETIRED.ANY", "0x00", "0x01", ""}, + {"INST_RETIRED.ANY_P", "0xc0", "0x00", ""}, + {"INST_RETIRED.NOP", "0xc0", "0x02", ""}, + {"INT_MISC.CLEARS_COUNT", "0x0D", "0x01", ""}, + {"INT_MISC.CLEAR_RESTEER_CYCLES", "0x0d", "0x80", ""}, + {"INT_MISC.UOP_DROPPING", "0x0d", "0x10", ""}, + {"ITLB_MISSES.WALK_ACTIVE", "0x85", "0x10", ""}, + {"ITLB_MISSES.WALK_COMPLETED", "0x85", "0x0e", ""}, + {"ITLB_MISSES.WALK_COMPLETED_2M_4M", "0x85", "0x04", ""}, + {"ITLB_MISSES.WALK_COMPLETED_4K", "0x85", "0x02", ""}, + {"ITLB_MISSES.WALK_PENDING", "0x85", "0x10", ""}, + {"L1D.REPLACEMENT", "0x51", "0x01", ""}, + {"L1D_PEND_MISS.FB_FULL", "0x48", "0x02", ""}, + {"L1D_PEND_MISS.FB_FULL_PERIODS", "0x48", "0x02", ""}, + {"L1D_PEND_MISS.L2_STALL", "0x48", "0x04", ""}, + {"L1D_PEND_MISS.PENDING", "0x48", "0x01", ""}, + {"L1D_PEND_MISS.PENDING_CYCLES", "0x48", "0x01", ""}, + {"L2_LINES_IN.ALL", "0xF1", "0x1F", ""}, + {"L2_LINES_OUT.NON_SILENT", "0xF2", "0x02", ""}, + {"L2_LINES_OUT.SILENT", "0xF2", "0x01", ""}, + {"L2_LINES_OUT.USELESS_HWPF", "0xf2", "0x04", ""}, + {"L2_RQSTS.ALL_CODE_RD", "0x24", "0xE4", ""}, + {"L2_RQSTS.ALL_DEMAND_DATA_RD", "0x24", "0xE1", ""}, + {"L2_RQSTS.ALL_DEMAND_MISS", "0x24", "0x27", ""}, + {"L2_RQSTS.ALL_RFO", "0x24", "0xE2", ""}, + {"L2_RQSTS.CODE_RD_MISS", "0x24", "0x24", ""}, + {"L2_RQSTS.DEMAND_DATA_RD_HIT", "0x24", "0xc1", ""}, + {"L2_RQSTS.DEMAND_DATA_RD_MISS", "0x24", "0x21", ""}, + {"L2_RQSTS.RFO_HIT", "0x24", "0xc2", ""}, + {"L2_RQSTS.RFO_MISS", "0x24", "0x22", ""}, + {"L2_RQSTS.SWPF_MISS", "0x24", "0x28", ""}, + {"LD_BLOCKS.NO_SR", "0x03", "0x08", ""}, + {"LD_BLOCKS.STORE_FORWARD", "0x03", "0x02", ""}, + {"LD_BLOCKS_PARTIAL.ADDRESS_ALIAS", "0x07", "0x01", ""}, + {"LONGEST_LAT_CACHE.MISS", "0x2e", "0x41", ""}, + {"LSD.UOPS", "0xa8", "0x01", ""}, + {"MACHINE_CLEARS.COUNT", "0xc3", "0x01", ""}, + {"MACHINE_CLEARS.MEMORY_ORDERING", "0xc3", "0x02", ""}, + {"MEM_INST_RETIRED.ALL_LOADS", "0xd0", "0x81", ""}, + {"MEM_INST_RETIRED.ALL_STORES", "0xd0", "0x82", ""}, + {"MEM_INST_RETIRED.ANY", "0xd0", "0x83", ""}, + {"MEM_INST_RETIRED.LOCK_LOADS", "0xd0", "0x21", ""}, + {"MEM_INST_RETIRED.SPLIT_STORES", "0xd0", "0x42", ""}, + {"MEM_LOAD_L3_HIT_RETIRED.XSNP_HIT", "0xd2", "0x02", ""}, + {"MEM_LOAD_L3_HIT_RETIRED.XSNP_HITM", "0xd2", "0x04", ""}, + {"MEM_LOAD_L3_HIT_RETIRED.XSNP_MISS", "0xd2", "0x01", ""}, + {"MEM_LOAD_L3_MISS_RETIRED.LOCAL_DRAM", "0xd3", "0x01", ""}, + {"MEM_LOAD_L3_MISS_RETIRED.REMOTE_DRAM", "0xd3", "0x02", ""}, + {"MEM_LOAD_L3_MISS_RETIRED.REMOTE_FWD", "0xd3", "0x08", ""}, + {"MEM_LOAD_L3_MISS_RETIRED.REMOTE_HITM", "0xd3", "0x04", ""}, + {"MEM_LOAD_MISC_RETIRED.UC", "0xd4", "0x04", ""}, + {"MEM_LOAD_RETIRED.FB_HIT", "0xd1", "0x40", ""}, + {"MEM_LOAD_RETIRED.L1_HIT", "0xd1", "0x01", ""}, + {"MEM_LOAD_RETIRED.L1_MISS", "0xd1", "0x08", ""}, + {"MEM_LOAD_RETIRED.L2_HIT", "0xd1", "0x02", ""}, + {"MEM_LOAD_RETIRED.L2_MISS", "0xd1", "0x10", ""}, + {"MEM_LOAD_RETIRED.L3_HIT", "0xd1", "0x04", ""}, + {"MEM_LOAD_RETIRED.L3_MISS", "0xd1", "0x20", ""}, + {"MISC_RETIRED.PAUSE_INST", "0xcc", "0x40", ""}, + {"OCR.DEMAND_DATA_RD.L3_HIT.SNOOP_HITM", "0xB7, 0xBB", "0x01", ""}, + {"OCR.DEMAND_DATA_RD.L3_HIT.SNOOP_HIT_WITH_FWD", "0xB7, 0xBB", "0x01", ""}, + {"OCR.DEMAND_RFO.L3_HIT.SNOOP_HITM", "0xB7, 0xBB", "0x01", ""}, + {"OCR.DEMAND_RFO.L3_MISS", "0xB7, 0xBB", "0x01", ""}, + {"OCR.STREAMING_WR.ANY_RESPONSE", "0xB7, 0xBB", "0x01", ""}, + {"OFFCORE_REQUESTS.ALL_DATA_RD", "0xB0", "0x08", ""}, + {"OFFCORE_REQUESTS.ALL_REQUESTS", "0xB0", "0x80", ""}, + {"OFFCORE_REQUESTS.DEMAND_DATA_RD", "0xb0", "0x01", ""}, + {"OFFCORE_REQUESTS_OUTSTANDING.ALL_DATA_RD", "0x60", "0x08", ""}, + {"OFFCORE_REQUESTS_OUTSTANDING.CYCLES_WITH_DATA_RD", "0x60", "0x08", ""}, + {"OFFCORE_REQUESTS_OUTSTANDING.CYCLES_WITH_DEMAND_CODE_RD", "0x60", "0x02", ""}, + {"OFFCORE_REQUESTS_OUTSTANDING.CYCLES_WITH_DEMAND_RFO", "0x60", "0x04", ""}, + {"OFFCORE_REQUESTS_OUTSTANDING.DEMAND_DATA_RD", "0x60", "0x01", ""}, + {"RESOURCE_STALLS.SCOREBOARD", "0xa2", "0x02", ""}, + {"RS_EVENTS.EMPTY_CYCLES", "0x5e", "0x01", ""}, + {"SQ_MISC.BUS_LOCK", "0xF4", "0x10", ""}, + {"SW_PREFETCH_ACCESS.ANY", "0x32", "0x0F", ""}, + {"TOPDOWN.SLOTS", "0x00", "0x04", ""}, + {"UNC_CHA_DIR_UPDATE.HA", "0x54", "0x01", "CHA"}, + {"UNC_CHA_DIR_UPDATE.TOR", "0x54", "0x02", "CHA"}, + {"UNC_CHA_REQUESTS.READS_LOCAL", "0x50", "0x01", "CHA"}, + {"UNC_CHA_REQUESTS.READS_REMOTE", "0x50", "0x02", "CHA"}, + {"UNC_CHA_REQUESTS.WRITES_LOCAL", "0x50", "0x04", "CHA"}, + {"UNC_CHA_REQUESTS.WRITES_REMOTE", "0x50", "0x08", "CHA"}, + {"UNC_CHA_TOR_INSERTS.IA_MISS_CRD", "0x35", "0x01", "CHA"}, + {"UNC_CHA_TOR_INSERTS.IA_MISS_CRD_PREF", "0x35", "0x01", "CHA"}, + {"UNC_CHA_TOR_INSERTS.IA_MISS_DRD", "0x35", "0x01", "CHA"}, + {"UNC_CHA_TOR_INSERTS.IA_MISS_DRD_DDR", "0x35", "0x01", "CHA"}, + {"UNC_CHA_TOR_INSERTS.IA_MISS_DRD_LOCAL", "0x35", "0x01", "CHA"}, + {"UNC_CHA_TOR_INSERTS.IA_MISS_DRD_PMM", "0x35", "0x01", "CHA"}, + {"UNC_CHA_TOR_INSERTS.IA_MISS_DRD_PREF", "0x35", "0x01", "CHA"}, + {"UNC_CHA_TOR_INSERTS.IA_MISS_DRD_PREF_LOCAL", "0x35", "0x01", "CHA"}, + {"UNC_CHA_TOR_INSERTS.IA_MISS_DRD_PREF_REMOTE", "0x35", "0x01", "CHA"}, + {"UNC_CHA_TOR_INSERTS.IA_MISS_DRD_REMOTE", "0x35", "0x01", "CHA"}, + {"UNC_CHA_TOR_INSERTS.IA_MISS_LLCPREFDATA", "0x35", "0x01", "CHA"}, + {"UNC_CHA_TOR_INSERTS.IO_HIT_ITOM", "0x35", "0x04", "CHA"}, + {"UNC_CHA_TOR_INSERTS.IO_HIT_ITOMCACHENEAR", "0x35", "0x04", "CHA"}, + {"UNC_CHA_TOR_INSERTS.IO_HIT_PCIRDCUR", "0x35", "0x04", "CHA"}, + {"UNC_CHA_TOR_INSERTS.IO_ITOM", "0x35", "0x04", "CHA"}, + {"UNC_CHA_TOR_INSERTS.IO_ITOMCACHENEAR", "0x35", "0x04", "CHA"}, + {"UNC_CHA_TOR_INSERTS.IO_ITOMCACHENEAR_LOCAL", "0x35", "0x04", "CHA"}, + {"UNC_CHA_TOR_INSERTS.IO_ITOMCACHENEAR_REMOTE", "0x35", "0x04", "CHA"}, + {"UNC_CHA_TOR_INSERTS.IO_ITOM_LOCAL", "0x35", "0x04", "CHA"}, + {"UNC_CHA_TOR_INSERTS.IO_ITOM_REMOTE", "0x35", "0x04", "CHA"}, + {"UNC_CHA_TOR_INSERTS.IO_MISS_ITOM", "0x35", "0x04", "CHA"}, + {"UNC_CHA_TOR_INSERTS.IO_MISS_ITOMCACHENEAR", "0x35", "0x04", "CHA"}, + {"UNC_CHA_TOR_INSERTS.IO_MISS_PCIRDCUR", "0x35", "0x04", "CHA"}, + {"UNC_CHA_TOR_INSERTS.IO_MISS_RFO", "0x35", "0x04", "CHA"}, + {"UNC_CHA_TOR_INSERTS.IO_PCIRDCUR", "0x35", "0x04", "CHA"}, + {"UNC_CHA_TOR_INSERTS.IO_PCIRDCUR_LOCAL", "0x35", "0x04", "CHA"}, + {"UNC_CHA_TOR_INSERTS.IO_PCIRDCUR_REMOTE", "0x35", "0x04", "CHA"}, + {"UNC_CHA_TOR_INSERTS.IO_RFO", "0x35", "0x04", "CHA"}, + {"UNC_CHA_TOR_OCCUPANCY.IA_MISS_DRD", "0x36", "0x01", "CHA"}, + {"UNC_CHA_TOR_OCCUPANCY.IA_MISS_DRD_DDR", "0x36", "0x01", "CHA"}, + {"UNC_CHA_TOR_OCCUPANCY.IA_MISS_DRD_LOCAL", "0x36", "0x01", "CHA"}, + {"UNC_CHA_TOR_OCCUPANCY.IA_MISS_DRD_PMM", "0x36", "0x01", "CHA"}, + {"UNC_CHA_TOR_OCCUPANCY.IA_MISS_DRD_REMOTE", "0x36", "0x01", "CHA"}, + {"UNC_M2M_DIRECTORY_UPDATE.ANY", "0x2e", "0x01", "M2M"}, + {"UNC_M_CAS_COUNT.RD", "0x04", "0x0f", "iMC"}, + {"UNC_M_CAS_COUNT.WR", "0x04", "0x30", "iMC"}, + {"UNC_UPI_RxL_FLITS.ALL_DATA", "0x03", "0x0F", "UPI LL"}, + {"UNC_UPI_TxL_FLITS.ALL_DATA", "0x02", "0x0F", "UPI LL"}, + {"UOPS_DECODED.DEC0", "0x56", "0x01", ""}, + {"UOPS_DISPATCHED.PORT_0", "0xa1", "0x01", ""}, + {"UOPS_DISPATCHED.PORT_1", "0xa1", "0x02", ""}, + {"UOPS_DISPATCHED.PORT_2_3", "0xa1", "0x04", ""}, + {"UOPS_DISPATCHED.PORT_4_9", "0xa1", "0x10", ""}, + {"UOPS_DISPATCHED.PORT_5", "0xa1", "0x20", ""}, + {"UOPS_DISPATCHED.PORT_6", "0xa1", "0x40", ""}, + {"UOPS_DISPATCHED.PORT_7_8", "0xa1", "0x80", ""}, + {"UOPS_EXECUTED.CORE_CYCLES_GE_1", "0xB1", "0x02", ""}, + {"UOPS_EXECUTED.CYCLES_GE_3", "0xb1", "0x01", ""}, + {"UOPS_EXECUTED.THREAD", "0xb1", "0x01", ""}, + {"UOPS_EXECUTED.X87", "0xB1", "0x10", ""}, + {"UOPS_ISSUED.ANY", "0x0e", "0x01", ""}, + {"UOPS_ISSUED.VECTOR_WIDTH_MISMATCH", "0x0e", "0x02", ""}, + {"UOPS_RETIRED.SLOTS", "0xc2", "0x02", ""}, + }; + + ASSERT_EQ(tmaEvents.size(), 216u); + + for (const auto& evt : tmaEvents) + { + ASSERT_TRUE(resolver.isEvent(evt.name)) << "Event not found: " << evt.name; + EXPECT_EQ(resolver.getField(evt.name, "EventCode"), evt.eventCode) + << "EventCode mismatch for " << evt.name; + EXPECT_EQ(resolver.getField(evt.name, "UMask"), evt.umask) + << "UMask mismatch for " << evt.name; + if (!evt.unit.empty()) + { + EXPECT_EQ(resolver.getField(evt.name, "Unit"), evt.unit) + << "Unit mismatch for " << evt.name; + } + } +} + +// --- Local Events Tests --- + +TEST_F(EventResolverTest, AddLocalEventsNewEvent) +{ + // Register a custom event not in perfmon + std::vector> localEvents = { + {"MY_CUSTOM_EVENT.SUB", {{"Unit", "CHA"}, {"EventCode", "0x99"}, {"UMask", "0x42"}}} + }; + resolver.addLocalEvents(localEvents); + + EXPECT_TRUE(resolver.isEvent("MY_CUSTOM_EVENT.SUB")); + EXPECT_TRUE(resolver.isField("MY_CUSTOM_EVENT.SUB", "Unit")); + EXPECT_TRUE(resolver.isField("MY_CUSTOM_EVENT.SUB", "EventCode")); + EXPECT_TRUE(resolver.isField("MY_CUSTOM_EVENT.SUB", "UMask")); + EXPECT_FALSE(resolver.isField("MY_CUSTOM_EVENT.SUB", "NonExistent")); + + EXPECT_EQ(resolver.getField("MY_CUSTOM_EVENT.SUB", "Unit"), "CHA"); + EXPECT_EQ(resolver.getField("MY_CUSTOM_EVENT.SUB", "EventCode"), "0x99"); + EXPECT_EQ(resolver.getField("MY_CUSTOM_EVENT.SUB", "UMask"), "0x42"); + EXPECT_EQ(resolver.getField("MY_CUSTOM_EVENT.SUB", "NonExistent"), ""); +} + +TEST_F(EventResolverTest, AddLocalEventsOverridesPerfmon) +{ + // UNC_CHA_DIR_UPDATE.HA exists in perfmon with Unit=CHA, EventCode=0x54, UMask=0x01 + ASSERT_TRUE(resolver.isEvent("UNC_CHA_DIR_UPDATE.HA")); + EXPECT_EQ(resolver.getField("UNC_CHA_DIR_UPDATE.HA", "Unit"), "CHA"); + + // Override with local definition + std::vector> localEvents = { + {"UNC_CHA_DIR_UPDATE.HA", {{"Unit", "CUSTOM_UNIT"}, {"EventCode", "0xFF"}, {"UMask", "0xAA"}}} + }; + resolver.addLocalEvents(localEvents); + + // Local fields should win + EXPECT_TRUE(resolver.isEvent("UNC_CHA_DIR_UPDATE.HA")); + EXPECT_EQ(resolver.getField("UNC_CHA_DIR_UPDATE.HA", "Unit"), "CUSTOM_UNIT"); + EXPECT_EQ(resolver.getField("UNC_CHA_DIR_UPDATE.HA", "EventCode"), "0xFF"); + EXPECT_EQ(resolver.getField("UNC_CHA_DIR_UPDATE.HA", "UMask"), "0xAA"); +} + +TEST_F(EventResolverTest, AddLocalEventResolves) +{ + // Register a CHA event with known EventCode, UMask, and UMaskExt + // (UMaskExt is required by ICX CHA PMURegisterDeclarations with no DefaultValue) + std::vector> localEvents = { + {"MY_LOCAL_CHA_EVENT.TEST", {{"Unit", "CHA"}, {"EventCode", "0x35"}, {"UMask", "0x04"}, {"UMaskExt", "0x00"}}} + }; + resolver.addLocalEvents(localEvents); + + std::string pmuName; + PCM::RawEventConfig config; + ASSERT_TRUE(resolver.resolveEvent("MY_LOCAL_CHA_EVENT.TEST", pmuName, config)); + EXPECT_EQ(pmuName, "cha"); + + // EventCode in bits 0-7 + uint64 eventCode = config.first[0] & 0xFF; + EXPECT_EQ(eventCode, 0x35u); + + // UMask in bits 8-15 + uint64 umask = (config.first[0] >> 8) & 0xFF; + EXPECT_EQ(umask, 0x04u); +} diff --git a/tests/utests/pcm-io-metrics-utest.cpp b/tests/utests/pcm-io-metrics-utest.cpp new file mode 100644 index 00000000..d2e0d5be --- /dev/null +++ b/tests/utests/pcm-io-metrics-utest.cpp @@ -0,0 +1,1142 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2026, Intel Corporation + +#include "pcm-io-metrics.h" +#include +#include +#include + +using namespace pcm; + +// --- CounterConstraintGrouper tests --- + +TEST(ParseCounterFieldTest, AllFourCounters) +{ + std::set allowed; + EXPECT_TRUE(CounterConstraintGrouper::parseCounterField("0,1,2,3", allowed)); + EXPECT_EQ(allowed, (std::set{0, 1, 2, 3})); +} + +TEST(ParseCounterFieldTest, RestrictedCounters01) +{ + std::set allowed; + EXPECT_TRUE(CounterConstraintGrouper::parseCounterField("0,1", allowed)); + EXPECT_EQ(allowed, (std::set{0, 1})); +} + +TEST(ParseCounterFieldTest, RestrictedCounters23) +{ + std::set allowed; + EXPECT_TRUE(CounterConstraintGrouper::parseCounterField("2,3", allowed)); + EXPECT_EQ(allowed, (std::set{2, 3})); +} + +TEST(ParseCounterFieldTest, SingleCounter) +{ + std::set allowed; + EXPECT_TRUE(CounterConstraintGrouper::parseCounterField("0", allowed)); + EXPECT_EQ(allowed, (std::set{0})); +} + +TEST(ParseCounterFieldTest, FixedUpperCase) +{ + std::set allowed; + EXPECT_TRUE(CounterConstraintGrouper::parseCounterField("FIXED", allowed)); + EXPECT_TRUE(allowed.empty()); +} + +TEST(ParseCounterFieldTest, FixedCounterN) +{ + std::set allowed; + EXPECT_TRUE(CounterConstraintGrouper::parseCounterField("Fixed counter 0", allowed)); + EXPECT_TRUE(allowed.empty()); +} + +TEST(ParseCounterFieldTest, EmptyStringReturnsFalse) +{ + std::set allowed; + EXPECT_FALSE(CounterConstraintGrouper::parseCounterField("", allowed)); +} + +TEST(ParseCounterFieldTest, EightCounters) +{ + std::set allowed; + EXPECT_TRUE(CounterConstraintGrouper::parseCounterField("0,1,2,3,4,5,6,7", allowed)); + EXPECT_EQ(allowed, (std::set{0, 1, 2, 3, 4, 5, 6, 7})); +} + +TEST(IsFixedCounterTest, FixedUpperCase) +{ + EXPECT_TRUE(CounterConstraintGrouper::isFixedCounter("FIXED")); +} + +TEST(IsFixedCounterTest, FixedCounterN) +{ + EXPECT_TRUE(CounterConstraintGrouper::isFixedCounter("Fixed counter 0")); +} + +TEST(IsFixedCounterTest, ProgrammableCounters) +{ + EXPECT_FALSE(CounterConstraintGrouper::isFixedCounter("0,1,2,3")); +} + +TEST(IsFixedCounterTest, EmptyString) +{ + EXPECT_FALSE(CounterConstraintGrouper::isFixedCounter("")); +} + +TEST(EventGroupingTest, BasicSequentialPlacement) +{ + CounterConstraintGrouper grouper; + std::set all4{0, 1, 2, 3}; + auto p0 = grouper.placeEvent("cha", all4); + auto p1 = grouper.placeEvent("cha", all4); + auto p2 = grouper.placeEvent("cha", all4); + auto p3 = grouper.placeEvent("cha", all4); + + EXPECT_EQ(p0.groupIndex, 0u); + EXPECT_EQ(p1.groupIndex, 0u); + EXPECT_EQ(p2.groupIndex, 0u); + EXPECT_EQ(p3.groupIndex, 0u); + + std::set counters{p0.counterIndex, p1.counterIndex, p2.counterIndex, p3.counterIndex}; + EXPECT_EQ(counters, (std::set{0, 1, 2, 3})); +} + +TEST(EventGroupingTest, OverflowToSecondGroup) +{ + CounterConstraintGrouper grouper; + std::set all4{0, 1, 2, 3}; + for (int i = 0; i < 4; ++i) + grouper.placeEvent("cha", all4); + + auto p4 = grouper.placeEvent("cha", all4); + EXPECT_EQ(p4.groupIndex, 1u); +} + +TEST(EventGroupingTest, CounterZeroOnlyConflict) +{ + CounterConstraintGrouper grouper; + std::set zero{0}; + auto p0 = grouper.placeEvent("cha", zero); + auto p1 = grouper.placeEvent("cha", zero); + + EXPECT_EQ(p0.groupIndex, 0u); + EXPECT_EQ(p0.counterIndex, 0u); + EXPECT_EQ(p1.groupIndex, 1u); + EXPECT_EQ(p1.counterIndex, 0u); +} + +TEST(EventGroupingTest, MixedConstraintsFitOneGroup) +{ + CounterConstraintGrouper grouper; + std::set c01{0, 1}; + std::set c23{2, 3}; + std::set all4{0, 1, 2, 3}; + + auto pA = grouper.placeEvent("cha", c01); + auto pB = grouper.placeEvent("cha", c23); + auto pC = grouper.placeEvent("cha", all4); + + EXPECT_EQ(pA.groupIndex, 0u); + EXPECT_EQ(pB.groupIndex, 0u); + EXPECT_EQ(pC.groupIndex, 0u); + + EXPECT_TRUE(c01.count(pA.counterIndex)); + EXPECT_TRUE(c23.count(pB.counterIndex)); + EXPECT_NE(pA.counterIndex, pC.counterIndex); + EXPECT_NE(pB.counterIndex, pC.counterIndex); +} + +TEST(EventGroupingTest, IIOPatternFitsOneGroup) +{ + CounterConstraintGrouper grouper; + std::set c01{0, 1}; + std::set c23{2, 3}; + + auto p0 = grouper.placeEvent("iio", c01); + auto p1 = grouper.placeEvent("iio", c01); + auto p2 = grouper.placeEvent("iio", c23); + auto p3 = grouper.placeEvent("iio", c23); + + EXPECT_EQ(p0.groupIndex, 0u); + EXPECT_EQ(p1.groupIndex, 0u); + EXPECT_EQ(p2.groupIndex, 0u); + EXPECT_EQ(p3.groupIndex, 0u); +} + +TEST(EventGroupingTest, IIOPatternOverflow) +{ + CounterConstraintGrouper grouper; + std::set c01{0, 1}; + std::set c23{2, 3}; + + grouper.placeEvent("iio", c01); + grouper.placeEvent("iio", c01); + auto overflow = grouper.placeEvent("iio", c01); + grouper.placeEvent("iio", c23); + grouper.placeEvent("iio", c23); + + EXPECT_EQ(overflow.groupIndex, 1u); + EXPECT_TRUE(c01.count(overflow.counterIndex)); +} + +TEST(EventGroupingTest, MultiplePMUsIndependent) +{ + CounterConstraintGrouper grouper; + std::set all4{0, 1, 2, 3}; + + auto pCha = grouper.placeEvent("cha", all4); + auto pIio = grouper.placeEvent("iio", all4); + + EXPECT_EQ(pCha.groupIndex, 0u); + EXPECT_EQ(pIio.groupIndex, 0u); + EXPECT_EQ(pCha.counterIndex, 0u); + EXPECT_EQ(pIio.counterIndex, 0u); +} + +class FormulaEvaluatorTest : public ::testing::Test { +protected: + FormulaEvaluator eval; + std::unordered_map noVars; +}; + +TEST_F(FormulaEvaluatorTest, Constants) +{ + EXPECT_DOUBLE_EQ(eval.evaluate("42", noVars), 42.0); + EXPECT_DOUBLE_EQ(eval.evaluate("3.14", noVars), 3.14); + EXPECT_DOUBLE_EQ(eval.evaluate("0", noVars), 0.0); +} + +TEST_F(FormulaEvaluatorTest, Arithmetic) +{ + EXPECT_DOUBLE_EQ(eval.evaluate("2 + 3 * 4", noVars), 14.0); + EXPECT_DOUBLE_EQ(eval.evaluate("(2 + 3) * 4", noVars), 20.0); + EXPECT_DOUBLE_EQ(eval.evaluate("10 - 3 - 2", noVars), 5.0); + EXPECT_DOUBLE_EQ(eval.evaluate("20 / 4", noVars), 5.0); + EXPECT_DOUBLE_EQ(eval.evaluate("2 + 3", noVars), 5.0); + EXPECT_DOUBLE_EQ(eval.evaluate("6 * 7", noVars), 42.0); +} + +TEST_F(FormulaEvaluatorTest, WithVariables) +{ + std::unordered_map vars = { + {"UNC_CHA_TOR_INSERTS.IO_PCIRDCUR", 100.0}, + {"UNC_CHA_TOR_INSERTS.IO_ITOM", 50.0} + }; + EXPECT_DOUBLE_EQ(eval.evaluate("UNC_CHA_TOR_INSERTS.IO_PCIRDCUR * 64", vars), 6400.0); + EXPECT_DOUBLE_EQ( + eval.evaluate("(UNC_CHA_TOR_INSERTS.IO_PCIRDCUR + UNC_CHA_TOR_INSERTS.IO_ITOM) * 64", vars), + 9600.0); +} + +TEST_F(FormulaEvaluatorTest, NestedParens) +{ + std::unordered_map vars = { + {"a", 2.0}, {"b", 3.0}, {"c", 7.0}, {"d", 1.0} + }; + // ((2+3) * (7-1)) / 2 = (5*6)/2 = 15 + EXPECT_DOUBLE_EQ(eval.evaluate("((a + b) * (c - d)) / 2", vars), 15.0); +} + +TEST_F(FormulaEvaluatorTest, DivisionByZero) +{ + EXPECT_DOUBLE_EQ(eval.evaluate("10 / 0", noVars), 0.0); + std::unordered_map vars = {{"x", 0.0}}; + EXPECT_DOUBLE_EQ(eval.evaluate("42 / x", vars), 0.0); +} + +TEST_F(FormulaEvaluatorTest, UnknownVariableIsZero) +{ + EXPECT_DOUBLE_EQ(eval.evaluate("UNKNOWN_EVENT * 64", noVars), 0.0); +} + +TEST_F(FormulaEvaluatorTest, ExtractVariables) +{ + auto vars = eval.extractVariables("UNC_CHA_TOR_INSERTS.IO_PCIRDCUR * 64"); + EXPECT_EQ(vars.size(), 1u); + EXPECT_EQ(vars.count("UNC_CHA_TOR_INSERTS.IO_PCIRDCUR"), 1u); + + auto vars2 = eval.extractVariables("(UNC_CHA_TOR_INSERTS.IO_ITOM + UNC_CHA_TOR_INSERTS.IO_ITOMCACHENEAR) * 64"); + EXPECT_EQ(vars2.size(), 2u); + EXPECT_EQ(vars2.count("UNC_CHA_TOR_INSERTS.IO_ITOM"), 1u); + EXPECT_EQ(vars2.count("UNC_CHA_TOR_INSERTS.IO_ITOMCACHENEAR"), 1u); +} + +TEST_F(FormulaEvaluatorTest, ExtractVariablesNoDuplicates) +{ + auto vars = eval.extractVariables("x + x * x"); + EXPECT_EQ(vars.size(), 1u); + EXPECT_EQ(vars.count("x"), 1u); +} + +TEST_F(FormulaEvaluatorTest, ExtractVariablesNoConstants) +{ + auto vars = eval.extractVariables("42 + 3.14"); + EXPECT_TRUE(vars.empty()); +} + +static const char* kTestMetricsJSON = R"json({ + "metrics": [ + { + "name": "PCIe Rd (B)", + "formula": "UNC_CHA_TOR_INSERTS.IO_PCIRDCUR * 64", + "short_name": "PCIeRd", + "aggregation": "socket" + }, + { + "name": "PCIe Wr (B)", + "formula": "(UNC_CHA_TOR_INSERTS.IO_ITOM + UNC_CHA_TOR_INSERTS.IO_ITOMCACHENEAR) * 64", + "short_name": "PCIeWr", + "aggregation": "socket" + }, + { + "name": "Total BW (B)", + "formula": "(UNC_CHA_TOR_INSERTS.IO_PCIRDCUR + UNC_CHA_TOR_INSERTS.IO_ITOM + UNC_CHA_TOR_INSERTS.IO_ITOMCACHENEAR) * 64", + "aggregation": "system" + } + ] +})json"; + +TEST(MetricsConfigTest, LoadFromString) +{ + MetricsConfig config; + ASSERT_TRUE(config.loadFromString(kTestMetricsJSON)); + EXPECT_EQ(config.getMetrics().size(), 3u); +} + +TEST(MetricsConfigTest, MetricFields) +{ + MetricsConfig config; + ASSERT_TRUE(config.loadFromString(kTestMetricsJSON)); + + const auto& metrics = config.getMetrics(); + EXPECT_EQ(metrics[0].name, "PCIe Rd (B)"); + EXPECT_EQ(metrics[0].formula, "UNC_CHA_TOR_INSERTS.IO_PCIRDCUR * 64"); + EXPECT_EQ(metrics[0].short_name, "PCIeRd"); + EXPECT_EQ(metrics[0].aggregation, "socket"); + + EXPECT_EQ(metrics[1].name, "PCIe Wr (B)"); + EXPECT_EQ(metrics[1].short_name, "PCIeWr"); + + EXPECT_EQ(metrics[2].name, "Total BW (B)"); + EXPECT_EQ(metrics[2].short_name, ""); + EXPECT_EQ(metrics[2].aggregation, "system"); +} + +TEST(MetricsConfigTest, DefaultAggregation) +{ + MetricsConfig config; + ASSERT_TRUE(config.loadFromString(R"({ + "metrics": [ + { "name": "Foo", "formula": "x * 2" } + ] + })")); + EXPECT_EQ(config.getMetrics()[0].aggregation, "socket"); +} + +TEST(MetricsConfigTest, MetricDescriptionWhenPresent) +{ + MetricsConfig config; + ASSERT_TRUE(config.loadFromString(R"json({ + "metrics": [ + { + "name": "Foo", + "formula": "x * 2", + "description": "Total foo bytes, computed as x times two." + } + ] + })json")); + EXPECT_EQ(config.getMetrics()[0].description, + "Total foo bytes, computed as x times two."); +} + +TEST(MetricsConfigTest, MetricDescriptionDefaultsEmpty) +{ + MetricsConfig config; + ASSERT_TRUE(config.loadFromString(kTestMetricsJSON)); + for (const auto& metric : config.getMetrics()) + EXPECT_TRUE(metric.description.empty()); +} + +TEST(MetricsConfigTest, ExtractEventNames) +{ + MetricsConfig config; + ASSERT_TRUE(config.loadFromString(kTestMetricsJSON)); + + auto events = config.extractEventNames(); + EXPECT_EQ(events.size(), 3u); + EXPECT_EQ(events.count("UNC_CHA_TOR_INSERTS.IO_PCIRDCUR"), 1u); + EXPECT_EQ(events.count("UNC_CHA_TOR_INSERTS.IO_ITOM"), 1u); + EXPECT_EQ(events.count("UNC_CHA_TOR_INSERTS.IO_ITOMCACHENEAR"), 1u); +} + +TEST(MetricsConfigTest, LoadFromStringInvalidJSON) +{ + MetricsConfig config; + EXPECT_FALSE(config.loadFromString("not valid json")); +} + +TEST(MetricsConfigTest, LoadFromStringMissingMetrics) +{ + MetricsConfig config; + EXPECT_FALSE(config.loadFromString(R"({"other": "data"})")); +} + +TEST(MetricsConfigTest, LoadFromStringEmptyMetrics) +{ + MetricsConfig config; + EXPECT_FALSE(config.loadFromString(R"({"metrics": []})")); +} + +TEST(MetricsConfigTest, LoadFromFile) +{ + const std::string path = "test_metrics_tmp.json"; + { + std::ofstream out(path); + out << kTestMetricsJSON; + } + MetricsConfig config; + ASSERT_TRUE(config.load(path)); + EXPECT_EQ(config.getMetrics().size(), 3u); + (void)std::remove(path.c_str()); +} + +TEST(MetricsConfigTest, LoadFromFileBadPath) +{ + MetricsConfig config; + EXPECT_FALSE(config.load("nonexistent/metrics.json")); +} + +// --- TableRenderer Tests --- + +// Box-drawing character helpers for building expected strings +#ifdef _MSC_VER +#define B_H "\xC4" +#define B_V "\xB3" +#define B_TL "\xDA" +#define B_TR "\xBF" +#define B_BL "\xC0" +#define B_BR "\xD9" +#define B_TD "\xC2" +#define B_TU "\xC1" +#define B_ML "\xC3" +#define B_MR "\xB4" +#define B_X "\xC5" +#else +#define B_H u8"\u2500" +#define B_V u8"\u2502" +#define B_TL u8"\u250C" +#define B_TR u8"\u2510" +#define B_BL u8"\u2514" +#define B_BR u8"\u2518" +#define B_TD u8"\u252C" +#define B_TU u8"\u2534" +#define B_ML u8"\u251C" +#define B_MR u8"\u2524" +#define B_X u8"\u253C" +#endif + +static std::string hline(int n) +{ + std::string s; + for (int i = 0; i < n; ++i) s += B_H; + return s; +} + +class TableRendererTest : public ::testing::Test { +protected: + TableRenderer renderer; +}; + +TEST_F(TableRendererTest, RenderSingleColumn) +{ + renderer.setHeaders({"Value"}); + renderer.addRow({"42"}); + std::string result = renderer.renderToString(); + + // Column width = max(5, 2) + 2 = 7 + std::string expected = + std::string(B_TL) + hline(7) + B_TR + "\n" + + B_V + " Value " + B_V + "\n" + + B_ML + hline(7) + B_MR + "\n" + + B_V + " 42 " + B_V + "\n" + + B_BL + hline(7) + B_BR + "\n"; + + EXPECT_EQ(result, expected); +} + +TEST_F(TableRendererTest, RenderMultipleColumns) +{ + renderer.setHeaders({"PCIeRd", "PCIeWr", "Total"}); + renderer.addRow({"1234", "5678", "6912"}); + renderer.addRow({"100", "200", "300"}); + std::string result = renderer.renderToString(); + + // Col widths: max(6,4)+2=8, max(6,4)+2=8, max(5,4)+2=7 + std::string expected = + std::string(B_TL) + hline(8) + B_TD + hline(8) + B_TD + hline(7) + B_TR + "\n" + + B_V + " PCIeRd " + B_V + " PCIeWr " + B_V + " Total " + B_V + "\n" + + B_ML + hline(8) + B_X + hline(8) + B_X + hline(7) + B_MR + "\n" + + B_V + " 1234 " + B_V + " 5678 " + B_V + " 6912 " + B_V + "\n" + + B_V + " 100 " + B_V + " 200 " + B_V + " 300 " + B_V + "\n" + + B_BL + hline(8) + B_TU + hline(8) + B_TU + hline(7) + B_BR + "\n"; + + EXPECT_EQ(result, expected); +} + +TEST_F(TableRendererTest, RenderWithSectionHeader) +{ + renderer.setHeaders({"Read", "Write"}); + renderer.addSectionHeader("PCIe BW"); + renderer.addRow({"100", "200"}); + std::string result = renderer.renderToString(); + + // Col widths: max(4,3)+2=6, max(5,3)+2=7 + // Table width = 6+7+3 borders = 16 display cols, inner = 14 + // Section header is first row → title-at-top: full-width top border, then title, then columned separator + std::string expected = + std::string(B_TL) + hline(14) + B_TR + "\n" + + B_V + " PCIe BW " + B_V + "\n" + + B_ML + hline(6) + B_TD + hline(7) + B_MR + "\n" + + B_V + " Read " + B_V + " Write " + B_V + "\n" + + B_ML + hline(6) + B_X + hline(7) + B_MR + "\n" + + B_V + " 100 " + B_V + " 200 " + B_V + "\n" + + B_BL + hline(6) + B_TU + hline(7) + B_BR + "\n"; + + EXPECT_EQ(result, expected); +} + +TEST_F(TableRendererTest, RenderEmptyTable) +{ + renderer.setHeaders({"A", "B"}); + std::string result = renderer.renderToString(); + + // Col widths: 1+2=3, 1+2=3 + std::string expected = + std::string(B_TL) + hline(3) + B_TD + hline(3) + B_TR + "\n" + + B_V + " A " + B_V + " B " + B_V + "\n" + + B_BL + hline(3) + B_TU + hline(3) + B_BR + "\n"; + + EXPECT_EQ(result, expected); +} + +TEST_F(TableRendererTest, RenderWithSystemSection) +{ + renderer.setHeaders({"Skt", "RdCur"}); + renderer.addSectionHeader("PCIe Data"); + renderer.addRow({"0", "100"}); + renderer.addSystemSection("System Wide", + {{"TotRd", "6400"}, {"TotWr", "3200"}}); + std::string result = renderer.renderToString(); + + EXPECT_NE(result.find("System Wide"), std::string::npos); + EXPECT_NE(result.find("TotRd"), std::string::npos); + EXPECT_NE(result.find("TotWr"), std::string::npos); + EXPECT_NE(result.find("6400"), std::string::npos); + EXPECT_NE(result.find("3200"), std::string::npos); + EXPECT_GT(result.size(), 0u); +} + +TEST(TableRendererStandaloneTest, RenderStandaloneSystemSectionWithTitle) +{ + std::ostringstream oss; + TableRenderer::renderStandaloneSystemSection(oss, "System Only", + {{"Total Read (B)", "0"}, {"Total Write (B)", "51"}}); + + // colW: max(14,1)+2=16, max(15,2)+2=17; innerWidth = 16+17+1 = 34 + // Title " System Only" -> pad = 34 - 1 - 11 = 22 + // Name centering: "Total Read (B)" in 16 => 1 left, 1 right + // "Total Write (B)" in 17 => 1 left, 1 right + // Value centering: "0" in 16 => 7 left, 8 right + // "51" in 17 => 7 left, 8 right + std::string expected = + std::string(B_TL) + hline(34) + B_TR + "\n" + + B_V + " System Only" + std::string(22, ' ') + B_V + "\n" + + B_ML + hline(16) + B_TD + hline(17) + B_MR + "\n" + + B_V + " Total Read (B) " + B_V + " Total Write (B) " + B_V + "\n" + + B_ML + hline(16) + B_X + hline(17) + B_MR + "\n" + + B_V + " 0 " + B_V + " 51 " + B_V + "\n" + + B_BL + hline(16) + B_TU + hline(17) + B_BR + "\n"; + + EXPECT_EQ(oss.str(), expected); +} + +TEST(TableRendererStandaloneTest, RenderStandaloneSystemSectionNoTitle) +{ + std::ostringstream oss; + TableRenderer::renderStandaloneSystemSection(oss, "", + {{"Total Read (B)", "0"}, {"Total Write (B)", "51"}}); + + // Same column widths as with-title case; top border has column divider. + std::string expected = + std::string(B_TL) + hline(16) + B_TD + hline(17) + B_TR + "\n" + + B_V + " Total Read (B) " + B_V + " Total Write (B) " + B_V + "\n" + + B_ML + hline(16) + B_X + hline(17) + B_MR + "\n" + + B_V + " 0 " + B_V + " 51 " + B_V + "\n" + + B_BL + hline(16) + B_TU + hline(17) + B_BR + "\n"; + + EXPECT_EQ(oss.str(), expected); +} + +TEST(TableRendererStandaloneTest, RenderStandaloneSystemSectionEmpty) +{ + std::ostringstream oss; + TableRenderer::renderStandaloneSystemSection(oss, "Empty", {}); + EXPECT_EQ(oss.str(), ""); +} + +// --- Layout Tests --- + +static const char* kLayoutMetricsJSON = R"json({ + "metrics": [ + { "name": "PCIe Rd (B)", "formula": "A * 64", "short_name": "PCIeRd", "aggregation": "socket" }, + { "name": "PCIe Wr (B)", "formula": "B * 64", "short_name": "PCIeWr", "aggregation": "socket" }, + { "name": "Total BW (B)", "formula": "(A + B) * 64", "aggregation": "system" } + ], + "layout": { + "sections": [ + { "title": "PCIe Bandwidth", "metrics": ["PCIe Rd (B)", "PCIe Wr (B)"] }, + { "title": "Total", "metrics": ["Total BW (B)"] } + ] + } +})json"; + +TEST(LayoutTest, LayoutParsing) +{ + MetricsConfig config; + ASSERT_TRUE(config.loadFromString(kLayoutMetricsJSON)); + + const auto& layout = config.getLayout(); + ASSERT_EQ(layout.size(), 2u); + + EXPECT_EQ(layout[0].title, "PCIe Bandwidth"); + ASSERT_EQ(layout[0].metrics.size(), 2u); + EXPECT_EQ(layout[0].metrics[0], "PCIe Rd (B)"); + EXPECT_EQ(layout[0].metrics[1], "PCIe Wr (B)"); + + EXPECT_EQ(layout[1].title, "Total"); + ASSERT_EQ(layout[1].metrics.size(), 1u); + EXPECT_EQ(layout[1].metrics[0], "Total BW (B)"); +} + +TEST(LayoutTest, LayoutMissingUsesFlat) +{ + MetricsConfig config; + ASSERT_TRUE(config.loadFromString(kTestMetricsJSON)); + + const auto& layout = config.getLayout(); + ASSERT_EQ(layout.size(), 1u); + EXPECT_EQ(layout[0].title, ""); + ASSERT_EQ(layout[0].metrics.size(), 3u); + EXPECT_EQ(layout[0].metrics[0], "PCIe Rd (B)"); + EXPECT_EQ(layout[0].metrics[1], "PCIe Wr (B)"); + EXPECT_EQ(layout[0].metrics[2], "Total BW (B)"); +} + +TEST(LayoutTest, LayoutEmptySectionsUsesFlat) +{ + MetricsConfig config; + ASSERT_TRUE(config.loadFromString(R"json({ + "metrics": [ + { "name": "Foo", "formula": "x * 2" } + ], + "layout": { + "sections": [] + } + })json")); + + const auto& layout = config.getLayout(); + ASSERT_EQ(layout.size(), 1u); + EXPECT_EQ(layout[0].title, ""); + ASSERT_EQ(layout[0].metrics.size(), 1u); + EXPECT_EQ(layout[0].metrics[0], "Foo"); +} + +TEST(LayoutTest, LayoutSingleSection) +{ + MetricsConfig config; + ASSERT_TRUE(config.loadFromString(R"json({ + "metrics": [ + { "name": "Read BW", "formula": "R * 64" }, + { "name": "Write BW", "formula": "W * 64" } + ], + "layout": { + "sections": [ + { "title": "Bandwidth", "metrics": ["Read BW", "Write BW"] } + ] + } + })json")); + + const auto& layout = config.getLayout(); + ASSERT_EQ(layout.size(), 1u); + EXPECT_EQ(layout[0].title, "Bandwidth"); + ASSERT_EQ(layout[0].metrics.size(), 2u); + EXPECT_EQ(layout[0].metrics[0], "Read BW"); + EXPECT_EQ(layout[0].metrics[1], "Write BW"); +} + +TEST(LayoutTest, LayoutMalformedSection) +{ + MetricsConfig config; + // Section missing "title" and "metrics" keys — should not crash, load still succeeds + ASSERT_TRUE(config.loadFromString(R"json({ + "metrics": [ + { "name": "A", "formula": "x * 1" }, + { "name": "B", "formula": "y * 2" } + ], + "layout": { + "sections": [ + { "other_key": "irrelevant" }, + { "title": "Valid", "metrics": ["A"] } + ] + } + })json")); + + const auto& layout = config.getLayout(); + ASSERT_EQ(layout.size(), 2u); + // Malformed section: empty title, no metrics + EXPECT_EQ(layout[0].title, ""); + EXPECT_TRUE(layout[0].metrics.empty()); + // Valid section still parsed + EXPECT_EQ(layout[1].title, "Valid"); + ASSERT_EQ(layout[1].metrics.size(), 1u); + EXPECT_EQ(layout[1].metrics[0], "A"); +} + +TEST(LayoutTest, GetLayoutMetricNamesFlatDedup) +{ + // Two flat sections share "PCIe Rd (B)" — must dedupe. + MetricsConfig config; + ASSERT_TRUE(config.loadFromString(R"json({ + "metrics": [ + { "name": "PCIe Rd (B)", "formula": "A", "aggregation": "socket" }, + { "name": "PCIe Wr (B)", "formula": "B", "aggregation": "socket" }, + { "name": "Total BW (B)", "formula": "A+B", "aggregation": "system" } + ], + "layout": { + "sections": [ + { "title": "Focus", "metrics": ["PCIe Rd (B)", "Total BW (B)"] }, + { "metrics": ["PCIe Rd (B)", "PCIe Wr (B)"] } + ] + } + })json")); + + auto names = config.getLayoutMetricNames(); + EXPECT_EQ(names.size(), 3u); + EXPECT_TRUE(names.count("PCIe Rd (B)")); + EXPECT_TRUE(names.count("PCIe Wr (B)")); + EXPECT_TRUE(names.count("Total BW (B)")); +} + +TEST(LayoutTest, GetLayoutMetricNamesMultiRow) +{ + // Must collect names from columns AND system-wide-metrics. + MetricsConfig config; + ASSERT_TRUE(config.loadFromString(R"json({ + "metrics": [ + {"name":"PCIRdCur", "formula":"A", "aggregation":"socket"}, + {"name":"PCIRdCur Miss", "formula":"B", "aggregation":"socket"}, + {"name":"ItoM", "formula":"D", "aggregation":"socket"}, + {"name":"Total Read (B)", "formula":"A+B","aggregation":"system"} + ], + "layout": { + "sections": [ + { + "rows": ["Total", "Miss"], + "columns": { + "PCIRdCur Events": ["PCIRdCur", "PCIRdCur Miss"], + "ItoM Events": ["ItoM"] + }, + "system-wide-metrics": ["Total Read (B)"] + } + ] + } + })json")); + + auto names = config.getLayoutMetricNames(); + EXPECT_EQ(names.size(), 4u); + EXPECT_TRUE(names.count("PCIRdCur")); + EXPECT_TRUE(names.count("PCIRdCur Miss")); + EXPECT_TRUE(names.count("ItoM")); + EXPECT_TRUE(names.count("Total Read (B)")); +} + +TEST(LayoutTest, GetLayoutMetricNamesNoLayoutReturnsAll) +{ + // When layout is omitted, generateFlatLayout() populates every metric + // so the helper returns all of them. + MetricsConfig config; + ASSERT_TRUE(config.loadFromString(kTestMetricsJSON)); + + auto names = config.getLayoutMetricNames(); + EXPECT_EQ(names.size(), 3u); + EXPECT_TRUE(names.count("PCIe Rd (B)")); + EXPECT_TRUE(names.count("PCIe Wr (B)")); + EXPECT_TRUE(names.count("Total BW (B)")); +} + +// --- Validation Tests --- + +TEST(ValidationTest, ValidateAllEventsPresent) +{ + MetricsConfig config; + ASSERT_TRUE(config.loadFromString(kTestMetricsJSON)); + + auto result = config.validateEvents([](const std::string&) { return true; }); + + EXPECT_TRUE(result.allValid()); + ASSERT_EQ(result.metrics.size(), 3u); + for (const auto& m : result.metrics) + { + EXPECT_TRUE(m.valid); + EXPECT_TRUE(m.missingEvents.empty()); + } +} + +TEST(ValidationTest, ValidateMissingEvent) +{ + MetricsConfig config; + ASSERT_TRUE(config.loadFromString(kTestMetricsJSON)); + + auto result = config.validateEvents([](const std::string& event) { + return event != "UNC_CHA_TOR_INSERTS.IO_ITOMCACHENEAR"; + }); + + EXPECT_FALSE(result.allValid()); + // Metric 0 "PCIe Rd (B)": formula uses only IO_PCIRDCUR -> valid + EXPECT_TRUE(result.metrics[0].valid); + // Metric 1 "PCIe Wr (B)": formula uses IO_ITOM + IO_ITOMCACHENEAR -> invalid + EXPECT_FALSE(result.metrics[1].valid); + ASSERT_EQ(result.metrics[1].missingEvents.size(), 1u); + EXPECT_EQ(result.metrics[1].missingEvents.count("UNC_CHA_TOR_INSERTS.IO_ITOMCACHENEAR"), 1u); + // Metric 2 "Total BW (B)": formula uses all three -> invalid + EXPECT_FALSE(result.metrics[2].valid); + ASSERT_EQ(result.metrics[2].missingEvents.size(), 1u); + EXPECT_EQ(result.metrics[2].missingEvents.count("UNC_CHA_TOR_INSERTS.IO_ITOMCACHENEAR"), 1u); +} + +TEST(ValidationTest, ValidateMultipleMissing) +{ + MetricsConfig config; + ASSERT_TRUE(config.loadFromString(kTestMetricsJSON)); + + auto result = config.validateEvents([](const std::string&) { return false; }); + + EXPECT_FALSE(result.allValid()); + ASSERT_EQ(result.metrics.size(), 3u); + for (const auto& m : result.metrics) + { + EXPECT_FALSE(m.valid); + EXPECT_FALSE(m.missingEvents.empty()); + } + // Metric 0 has 1 event, metric 1 has 2, metric 2 has 3 + EXPECT_EQ(result.metrics[0].missingEvents.size(), 1u); + EXPECT_EQ(result.metrics[1].missingEvents.size(), 2u); + EXPECT_EQ(result.metrics[2].missingEvents.size(), 3u); +} + +TEST(ValidationTest, PrintValidatedMetrics) +{ + MetricsConfig config; + ASSERT_TRUE(config.loadFromString(kTestMetricsJSON)); + + std::ostringstream os; + bool allValid = config.printValidatedMetrics(os, [](const std::string& event) { + return event != "UNC_CHA_TOR_INSERTS.IO_ITOMCACHENEAR"; + }); + + EXPECT_FALSE(allValid); + std::string output = os.str(); + EXPECT_NE(output.find("[OK]"), std::string::npos); + EXPECT_NE(output.find("PCIe Rd (B)"), std::string::npos); + EXPECT_NE(output.find("[INVALID]"), std::string::npos); + EXPECT_NE(output.find("PCIe Wr (B)"), std::string::npos); + EXPECT_NE(output.find("UNC_CHA_TOR_INSERTS.IO_ITOMCACHENEAR"), std::string::npos); + EXPECT_NE(output.find("1 of 3 metrics valid"), std::string::npos); + + std::ostringstream os2; + bool allValidTrue = config.printValidatedMetrics(os2, [](const std::string&) { return true; }); + EXPECT_TRUE(allValidTrue); +} + +// --- Local Events Tests --- + +static const char* kLocalEventsJSON = R"json({ + "events": [ + { + "EventName": "MY_CUSTOM_EVENT.SUB", + "Unit": "CHA", + "EventCode": "0x99", + "UMask": "0x42", + "BriefDescription": "A custom event" + }, + { + "EventName": "ANOTHER_EVENT.FOO", + "Unit": "IIO", + "EventCode": "0x10", + "UMask": "0x01" + } + ], + "metrics": [ + { + "name": "Custom Metric", + "formula": "MY_CUSTOM_EVENT.SUB * 64", + "aggregation": "socket" + } + ] +})json"; + +TEST(LocalEventsTest, LocalEventsParsing) +{ + MetricsConfig config; + ASSERT_TRUE(config.loadFromString(kLocalEventsJSON)); + + const auto& localEvents = config.getLocalEvents(); + ASSERT_EQ(localEvents.size(), 2u); + + EXPECT_EQ(localEvents[0].first, "MY_CUSTOM_EVENT.SUB"); + EXPECT_EQ(localEvents[0].second.at("Unit"), "CHA"); + EXPECT_EQ(localEvents[0].second.at("EventCode"), "0x99"); + EXPECT_EQ(localEvents[0].second.at("UMask"), "0x42"); + EXPECT_EQ(localEvents[0].second.at("BriefDescription"), "A custom event"); + // EventName should NOT be in the fields map + EXPECT_EQ(localEvents[0].second.count("EventName"), 0u); + + EXPECT_EQ(localEvents[1].first, "ANOTHER_EVENT.FOO"); + EXPECT_EQ(localEvents[1].second.at("Unit"), "IIO"); +} + +TEST(LocalEventsTest, LocalEventsEmpty) +{ + MetricsConfig config; + ASSERT_TRUE(config.loadFromString(kTestMetricsJSON)); + + const auto& localEvents = config.getLocalEvents(); + EXPECT_TRUE(localEvents.empty()); +} + +TEST(LocalEventsTest, LocalEventsValidation) +{ + MetricsConfig config; + ASSERT_TRUE(config.loadFromString(kLocalEventsJSON)); + + // Build a set of known events from local definitions + std::set knownEvents; + for (const auto& [name, fields] : config.getLocalEvents()) + knownEvents.insert(name); + + auto result = config.validateEvents([&knownEvents](const std::string& event) { + return knownEvents.count(event) > 0; + }); + + // "Custom Metric" uses MY_CUSTOM_EVENT.SUB which is locally defined + EXPECT_TRUE(result.allValid()); + ASSERT_EQ(result.metrics.size(), 1u); + EXPECT_TRUE(result.metrics[0].valid); +} + +// --- Multi-Row Layout Tests --- + +static const char* kMultiRowJSON = R"json({ + "metrics": [ + {"name":"PCIRdCur", "formula":"A", "aggregation":"socket"}, + {"name":"PCIRdCur Miss", "formula":"B", "aggregation":"socket"}, + {"name":"PCIRdCur Hit", "formula":"C", "aggregation":"socket"}, + {"name":"ItoM", "formula":"D", "aggregation":"socket"}, + {"name":"Total Read (B)", "formula":"A+B","aggregation":"system"} + ], + "layout": { + "sections": [ + { + "title": "PCIe Data", + "rows": ["Total", "Miss", "Hit"], + "columns": { + "PCIRdCur Events": ["PCIRdCur", "PCIRdCur Miss", "PCIRdCur Hit"], + "ItoM Events": ["ItoM"] + }, + "system-wide-metrics": ["Total Read (B)"] + } + ] + } +})json"; + +TEST(MultiRowLayoutTest, ParsesRowLabels) +{ + MetricsConfig config; + ASSERT_TRUE(config.loadFromString(kMultiRowJSON)); + const auto& layout = config.getLayout(); + ASSERT_EQ(layout.size(), 1u); + EXPECT_TRUE(layout[0].isMultiRow()); + ASSERT_EQ(layout[0].rowLabels.size(), 3u); + EXPECT_EQ(layout[0].rowLabels[0], "Total"); + EXPECT_EQ(layout[0].rowLabels[1], "Miss"); + EXPECT_EQ(layout[0].rowLabels[2], "Hit"); +} + +TEST(MultiRowLayoutTest, ParsesColumns) +{ + MetricsConfig config; + ASSERT_TRUE(config.loadFromString(kMultiRowJSON)); + const auto& layout = config.getLayout(); + ASSERT_EQ(layout[0].columns.size(), 2u); + // Column order preserved + EXPECT_EQ(layout[0].columns[0].first, "PCIRdCur Events"); + ASSERT_EQ(layout[0].columns[0].second.size(), 3u); + EXPECT_EQ(layout[0].columns[0].second[0], "PCIRdCur"); + EXPECT_EQ(layout[0].columns[0].second[1], "PCIRdCur Miss"); + EXPECT_EQ(layout[0].columns[0].second[2], "PCIRdCur Hit"); + EXPECT_EQ(layout[0].columns[1].first, "ItoM Events"); + ASSERT_EQ(layout[0].columns[1].second.size(), 1u); + EXPECT_EQ(layout[0].columns[1].second[0], "ItoM"); +} + +TEST(MultiRowLayoutTest, ParsesSystemWideMetrics) +{ + MetricsConfig config; + ASSERT_TRUE(config.loadFromString(kMultiRowJSON)); + const auto& layout = config.getLayout(); + ASSERT_EQ(layout[0].systemWideMetrics.size(), 1u); + EXPECT_EQ(layout[0].systemWideMetrics[0], "Total Read (B)"); +} + +TEST(MultiRowLayoutTest, ParsesTitle) +{ + MetricsConfig config; + ASSERT_TRUE(config.loadFromString(kMultiRowJSON)); + EXPECT_EQ(config.getLayout()[0].title, "PCIe Data"); + // Flat-metrics list should be empty for a multi-row section + EXPECT_TRUE(config.getLayout()[0].metrics.empty()); +} + +TEST(MultiRowLayoutTest, FlatSectionIsNotMultiRow) +{ + MetricsConfig config; + ASSERT_TRUE(config.loadFromString(kLayoutMetricsJSON)); + const auto& layout = config.getLayout(); + ASSERT_GE(layout.size(), 1u); + EXPECT_FALSE(layout[0].isMultiRow()); + EXPECT_FALSE(layout[0].metrics.empty()); +} + +TEST(MultiRowLayoutTest, MixedLayout) +{ + MetricsConfig config; + ASSERT_TRUE(config.loadFromString(R"json({ + "metrics": [ + {"name":"A","formula":"x","aggregation":"socket"}, + {"name":"B","formula":"y","aggregation":"socket"}, + {"name":"C","formula":"z","aggregation":"socket"} + ], + "layout": { + "sections": [ + { + "title": "Multi", + "rows": ["Row1", "Row2"], + "columns": { + "Col Group": ["A", "B"] + } + }, + { + "title": "Flat", + "metrics": ["C"] + } + ] + } + })json")); + const auto& layout = config.getLayout(); + ASSERT_EQ(layout.size(), 2u); + EXPECT_TRUE(layout[0].isMultiRow()); + EXPECT_EQ(layout[0].title, "Multi"); + EXPECT_FALSE(layout[1].isMultiRow()); + EXPECT_EQ(layout[1].title, "Flat"); + ASSERT_EQ(layout[1].metrics.size(), 1u); + EXPECT_EQ(layout[1].metrics[0], "C"); +} + +// --- Multi-Row Rendering Tests (via TableRenderer directly) --- + +class MultiRowRenderTest : public ::testing::Test { +protected: + TableRenderer renderer; +}; + +TEST_F(MultiRowRenderTest, ColumnCountMatchesHeaders) +{ + // Simulate 2 sockets x 3 sub-rows with 2 column groups + renderer.setHeaders({"Skt", "", "ColA", "ColB"}); + renderer.addSectionHeader("Section Title"); + // Socket 0 + renderer.addRow({"0", "Total", "100", "200"}); + renderer.addRow({"", "Miss", "30", "50"}); + renderer.addRow({"", "Hit", "70", "150"}); + // Socket 1 + renderer.addRow({"1", "Total", "110", "210"}); + renderer.addRow({"", "Miss", "35", "55"}); + renderer.addRow({"", "Hit", "75", "155"}); + + std::string result = renderer.renderToString(); + EXPECT_NE(result.find("Section Title"), std::string::npos); + EXPECT_NE(result.find("ColA"), std::string::npos); + EXPECT_NE(result.find("ColB"), std::string::npos); + EXPECT_NE(result.find("Total"), std::string::npos); + EXPECT_NE(result.find("Miss"), std::string::npos); + EXPECT_NE(result.find("Hit"), std::string::npos); + EXPECT_NE(result.find("100"), std::string::npos); + EXPECT_NE(result.find("200"), std::string::npos); +} + +TEST_F(MultiRowRenderTest, EmptyCellForShortColumn) +{ + // "Short Col" has only 1 metric mapped to row 0; rows 1 and 2 get empty cells + renderer.setHeaders({"Skt", "", "Full Col", "Short Col"}); + renderer.addRow({"0", "Total", "1000", "500"}); + renderer.addRow({"", "Miss", "200", ""}); // empty for Short Col + renderer.addRow({"", "Hit", "800", ""}); // empty for Short Col + + std::string result = renderer.renderToString(); + EXPECT_NE(result.find("1000"), std::string::npos); + EXPECT_NE(result.find("500"), std::string::npos); + EXPECT_NE(result.find("200"), std::string::npos); + EXPECT_NE(result.find("800"), std::string::npos); + EXPECT_GT(result.size(), 0u); +} + +TEST_F(MultiRowRenderTest, SocketNumberOnlyInFirstSubRow) +{ + renderer.setHeaders({"Skt", "", "Val"}); + renderer.addRow({"0", "Total", "100"}); + renderer.addRow({"", "Miss", "30"}); + renderer.addRow({"", "Hit", "70"}); + + std::string result = renderer.renderToString(); + // The socket number "0" should appear right-aligned in the Skt cell exactly once. + // Skt column width = max(len("Skt")=3, len("0")=1, len("")=0) + 2 = 5. + // Right-aligned "0" in a 5-wide cell: " 0 " bordered by the vertical box char. + std::string pattern = std::string(B_V) + " 0 " + B_V; + size_t count = 0; + size_t pos = 0; + while ((pos = result.find(pattern, pos)) != std::string::npos) { + ++count; + ++pos; + } + EXPECT_EQ(count, 1u) << "Socket number '0' should appear in exactly one row\n" << result; +} + +TEST_F(MultiRowRenderTest, SystemSectionAfterMultiRows) +{ + renderer.setHeaders({"Skt", "", "Events"}); + renderer.addSectionHeader("PCIe Data"); + renderer.addRow({"0", "Total", "1000"}); + renderer.addRow({"", "Miss", "200"}); + renderer.addRow({"", "Hit", "800"}); + renderer.addSystemSection("System Wide", {{"TotRd", "1000"}, {"TotWr", "500"}}); + + std::string result = renderer.renderToString(); + EXPECT_NE(result.find("PCIe Data"), std::string::npos); + EXPECT_NE(result.find("System Wide"), std::string::npos); + EXPECT_NE(result.find("TotRd"), std::string::npos); + EXPECT_NE(result.find("TotWr"), std::string::npos); + EXPECT_NE(result.find("1000"), std::string::npos); + EXPECT_NE(result.find("500"), std::string::npos); +} diff --git a/tests/utests/pcm-sensor-server-header-limits-utest.cpp b/tests/utests/pcm-sensor-server-header-limits-utest.cpp new file mode 100644 index 00000000..8068b6fa --- /dev/null +++ b/tests/utests/pcm-sensor-server-header-limits-utest.cpp @@ -0,0 +1,161 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2009-2025, Intel Corporation + +// Regression tests for the unbounded HTTP header consumption issue in the +// server-side request parser (src/pcm-sensor-server.cpp, the +// operator>>( basic_socketstream&, HTTPRequest& ) header loop, CWE-770). +// +// An unauthenticated remote client could previously drive unbounded memory +// growth by sending either a very large number of distinct headers or one +// folded header whose value was extended with whitespace-continuation lines +// without limit. The parser now enforces three ceilings before storing or +// extending header data: +// * kMaxHeaderCount - maximum number of distinct headers +// * kMaxHeaderLineBytes - maximum size of a single CRLF-terminated header line +// * kMaxTotalHeaderBytes - maximum cumulative header bytes per request +// +// Each test below drives the real request parser through a socketpair and +// verifies that an over-limit request is rejected with a std::runtime_error, +// while a well-formed request within the limits is accepted. + +#include +#include +#include +#include +#include +#include +#include + +// Pull the real request parser out of pcm-sensor-server.cpp without bringing +// in its main(). The same mechanism is used by the other sensor-server tests. +#define UNIT_TEST 1 +#include "../../src/pcm-sensor-server.cpp" +#undef UNIT_TEST + +#include + +namespace { + +// Write the full request payload to the peer end of the socketpair from a +// background thread, then shut the write direction down so the parser sees a +// clean end-of-stream if it ever reaches it. Writing from a separate thread +// avoids deadlocking on payloads larger than the socket's send buffer. +class RequestWriter { + RequestWriter( const RequestWriter& ) = delete; + RequestWriter& operator=( const RequestWriter& ) = delete; +public: + RequestWriter( int fd, std::string payload ) + : fd_( fd ), payload_( std::move( payload ) ), + thread_( [this]() { run(); } ) {} + + ~RequestWriter() { + if ( thread_.joinable() ) + thread_.join(); + ::close( fd_ ); + } + +private: + void run() { + size_t off = 0; + while ( off < payload_.size() ) { + ssize_t n = ::write( fd_, payload_.data() + off, payload_.size() - off ); + if ( n <= 0 ) { + if ( n < 0 && ( errno == EINTR ) ) + continue; + break; // peer closed or error; nothing more we can do + } + off += static_cast( n ); + } + ::shutdown( fd_, SHUT_WR ); + } + + int fd_; + std::string payload_; + std::thread thread_; +}; + +// Drive the parser over a socketpair with the given raw request bytes and +// return whether parsing threw (the rejection path). server_fd is closed by +// the socketstream destructor; the writer thread owns client_fd's write side. +void parseThrows( const std::string& request, bool& threw ) { + int sv[2]; + ASSERT_EQ( 0, ::socketpair( AF_UNIX, SOCK_STREAM, 0, sv ) ) + << "socketpair failed: " << std::strerror( errno ); + + RequestWriter writer( sv[1], request ); + + socketstream rs( sv[0] ); + HTTPRequest req; + threw = false; + try { + rs >> req; + } catch ( std::exception const& ) { + threw = true; + } + // sv[0] is closed by rs' destructor; sv[1] is closed by RequestWriter + // after its thread finishes and joins. +} + +} // namespace + +// A well-formed request that stays within every limit must be accepted. +TEST( PcmSensorServerHeaderLimitsTest, AcceptsRequestWithinLimits ) { + std::string req = "GET / HTTP/1.1\r\nHost: 127.0.0.1\r\n"; + for ( size_t i = 0; i < kMaxHeaderCount - 1; ++i ) { + req += "X-Pad-" + std::to_string( i ) + ": a\r\n"; + } + req += "\r\n"; + bool threw = false; + parseThrows( req, threw ); + EXPECT_FALSE( threw ) + << "Request with " << kMaxHeaderCount + << " headers (at the limit) should be accepted."; +} + +// More than kMaxHeaderCount distinct headers must be rejected (header flood). +TEST( PcmSensorServerHeaderLimitsTest, RejectsTooManyDistinctHeaders ) { + std::string req = "GET / HTTP/1.1\r\n"; + for ( size_t i = 0; i < kMaxHeaderCount + 50; ++i ) { + req += "X-Pad-" + std::to_string( i ) + ": a\r\n"; + } + req += "\r\n"; + bool threw = false; + parseThrows( req, threw ); + EXPECT_TRUE( threw ) + << "A request exceeding kMaxHeaderCount distinct headers must be rejected."; +} + +// A single folded header extended past kMaxHeaderLineBytes / the cumulative +// kMaxTotalHeaderBytes cap via whitespace-continuation lines must be rejected. +TEST( PcmSensorServerHeaderLimitsTest, RejectsOversizedFoldedHeader ) { + std::string req = "GET / HTTP/1.1\r\nX-Fold: a\r\n"; + // Each continuation line begins with a space (folding) and adds bytes to + // the same logical header value. Enough lines to blow past the total cap. + const std::string cont = " " + std::string( 4096, 'a' ) + "\r\n"; + const size_t lines = ( kMaxTotalHeaderBytes / cont.size() ) + 8; + for ( size_t i = 0; i < lines; ++i ) { + req += cont; + } + req += "\r\n"; + bool threw = false; + parseThrows( req, threw ); + EXPECT_TRUE( threw ) + << "An unbounded folded header must be rejected once it exceeds the byte caps."; +} + +// Many headers whose cumulative size exceeds kMaxTotalHeaderBytes but whose +// count stays under kMaxHeaderCount must still be rejected by the byte cap. +TEST( PcmSensorServerHeaderLimitsTest, RejectsOversizedTotalHeaderBytes ) { + std::string req = "GET / HTTP/1.1\r\n"; + // ~2 KB per header * 60 headers ~= 120 KB > kMaxTotalHeaderBytes (64 KB), + // while staying under kMaxHeaderCount. + const std::string value( 2048, 'a' ); + for ( size_t i = 0; i < 60; ++i ) { + req += "X-Big-" + std::to_string( i ) + ": " + value + "\r\n"; + } + req += "\r\n"; + bool threw = false; + parseThrows( req, threw ); + EXPECT_TRUE( threw ) + << "Cumulative header bytes exceeding kMaxTotalHeaderBytes must be rejected."; +}