From 40804a8fa9418f86262f1ed99ca4aa1f9c35d194 Mon Sep 17 00:00:00 2001 From: Alexander Antonov Date: Sun, 29 Mar 2026 07:07:33 -0700 Subject: [PATCH 01/77] Update message in pcm-pcie --- src/pcm-pcie.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pcm-pcie.cpp b/src/pcm-pcie.cpp index 97726488..c0e77ed1 100644 --- a/src/pcm-pcie.cpp +++ b/src/pcm-pcie.cpp @@ -216,7 +216,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); } From 2255fd3f3cfd1b81e068da0f5edd0b03f0890650 Mon Sep 17 00:00:00 2001 From: Alexander Antonov Date: Sun, 29 Mar 2026 09:12:16 -0700 Subject: [PATCH 02/77] Factor out event resolver logic from pcm raw --- src/CMakeLists.txt | 1 + src/event-resolver.cpp | 538 ++++++++++++++++++++++++++ src/event-resolver.h | 63 +++ src/pcm-raw.cpp | 443 +++------------------ tests/test.sh | 2 +- tests/utests/CMakeLists.txt | 21 + tests/utests/event-resolver-utest.cpp | 378 ++++++++++++++++++ 7 files changed, 1046 insertions(+), 400 deletions(-) create mode 100644 src/event-resolver.cpp create mode 100644 src/event-resolver.h create mode 100644 tests/utests/event-resolver-utest.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 95447f1f..e9f31b59 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -205,6 +205,7 @@ 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) if(${PROJECT_NAME} STREQUAL pcm-sensor-server) diff --git a/src/event-resolver.cpp b/src/event-resolver.cpp new file mode 100644 index 00000000..15fe4510 --- /dev/null +++ b/src/event-resolver.cpp @@ -0,0 +1,538 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2025, Intel Corporation + +#include "event-resolver.h" +#include "utils.h" +#include "debug.h" + +#include +#include +#include +#include +#include + +namespace pcm { + +const std::map PerfmonEventResolver::s_pmuNameMap = { + {"cbo", "cha"}, + {"b2cmi", "m2m"}, + {"upi", "xpi"}, + {"upi ll", "xpi"}, + {"b2upi", "m3upi"}, + {"qpi", "xpi"}, + {"qpi ll", "xpi"} +}; + +#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::loadPerfmonEvents(const std::string& cpuFamilyModel, const std::string& prefix) +{ + const std::string mapfilePath = prefix + "/mapfile.csv"; + const std::string mapfilePathAlt = getInstallPathPrefix() + "perfmon/mapfile.csv"; + + std::ifstream in(mapfilePath); + if (!in.is_open()) + { + in.open(mapfilePathAlt); + if (!in.is_open()) + { + std::cerr << "ERROR: File " << mapfilePath << " or " << mapfilePathAlt << " can't be opened.\n"; +#ifndef _MSC_VER + std::cerr << " run 'make install' in the pcm build directory if you cloned PCM source repository recursively with submodules, or\n"; +#endif + std::cerr << " use -ep /perfmon option if you cloned PCM source repository recursively with submodules,\n"; + std::cerr << " or run 'git clone https://github.com/intel/perfmon' to download the perfmon event repository and use -ep option\n"; + std::cerr << " or download the file from https://raw.githubusercontent.com/intel/perfmon/main/mapfile.csv\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; + } + + std::multimap eventFiles; + std::cerr << "Matched event files:\n"; + while (std::getline(in, line)) + { + auto tokens = split(line, ','); + assert(fmsPos < static_cast(tokens.size())); + assert(filenamePos < static_cast(tokens.size())); + assert(eventTypePos < static_cast(tokens.size())); + + std::regex fmsRegex(tokens[fmsPos]); + std::cmatch fmsMatch; + if (std::regex_search(cpuFamilyModel.c_str(), fmsMatch, fmsRegex)) + { + std::cerr << tokens[fmsPos] << " " << tokens[eventTypePos] << " " << tokens[filenamePos] << "\n"; + eventFiles.insert(std::make_pair(tokens[eventTypePos], tokens[filenamePos])); + } + } + in.close(); + + if (eventFiles.empty()) + { + std::cerr << "ERROR: CPU " << cpuFamilyModel << " not found in mapfile.csv\n"; + return false; + } + + for (const auto& evfile : eventFiles) + { + if (evfile.first != "core" && evfile.first != "uncore" && + evfile.first != "uncore experimental") + continue; + + const std::string path1 = prefix + evfile.second; + const std::string path2 = prefix + evfile.second.substr(evfile.second.rfind('/')); + const std::string path3 = getInstallPathPrefix() + "perfmon" + evfile.second; + + 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 " << evfile.second + << " from https://raw.githubusercontent.com/intel/perfmon/main" + << evfile.second << "\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 !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; + + for (int s = stepping; s >= 0; --s) + { + std::string declPath = "PMURegisterDeclarations/" + cpuFamilyModel + "-" + std::to_string(s) + ".json"; + + std::ifstream in(declPath); + if (in.is_open()) + { + path = declPath; + in.close(); + break; + } + + const std::string altPath = prefix + "/" + declPath; + in.open(altPath); + if (in.is_open()) + { + path = altPath; + in.close(); + break; + } + + const std::string installPath = getInstallPathPrefix() + declPath; + in.open(installPath); + if (in.is_open()) + { + path = installPath; + in.close(); + 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_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 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 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&) const { return false; } +bool PerfmonEventResolver::isField(const std::string&, const std::string&) const { return false; } +std::string PerfmonEventResolver::getField(const std::string&, const std::string&) const { 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..1094455d --- /dev/null +++ b/src/event-resolver.h @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2025, Intel Corporation +#pragma once + +#include +#include +#include +#include +#include +#include "cpucounters.h" + +#ifdef PCM_SIMDJSON_AVAILABLE +#include "simdjson.h" +#endif + +namespace pcm { + +class PerfmonEventResolver { +public: + // Initialize from explicit CPU identification + bool init(const std::string& cpuFamilyModel, const std::string& eventFilePrefix); + + // 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 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; +}; + +} // namespace pcm diff --git a/src/pcm-raw.cpp b/src/pcm-raw.cpp index 8bfdd02b..139eca5f 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; +static pcm::PerfmonEventResolver s_resolver; 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; +bool initPMUEventMap() +{ + if (s_resolver.isInitialized()) return true; - 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; + return s_resolver.init(PCM::getInstance()->getCPUFamilyModelString(), eventFileLocationPrefix); } -bool initPMUEventMap() +void print_event_description(const std::string& eventStr) { - static bool inited = false; - - if (inited == true) + for (const auto& key : {"BriefDescription", "PublicDescription"}) { - 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; + std::string val = s_resolver.getField(eventStr, key); - 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; - } + if (!val.empty()) std::cout << key << " : " << val << "\n"; } - 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)) - { - 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(); - - if (eventFiles.empty()) - { - cerr << "ERROR: CPU " << ourFMS << " not found in " << mapfile << "\n"; - return false; - } - - 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()) + const auto* PMURegisterDeclarations = s_resolver.getPMUDeclarations(); + if (!PMURegisterDeclarations) { - throw std::invalid_argument(err_msg); - } - - if (PMURegisterDeclarations.get() == nullptr) - { - // 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; } } diff --git a/tests/test.sh b/tests/test.sh index 8e57ad96..9f6b3bef 100755 --- a/tests/test.sh +++ b/tests/test.sh @@ -427,7 +427,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 49fbc781..14d24b0f 100644 --- a/tests/utests/CMakeLists.txt +++ b/tests/utests/CMakeLists.txt @@ -17,6 +17,7 @@ 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) if(APPLE) set(LIBS PcmMsr Threads::Threads PCM_STATIC) @@ -27,6 +28,7 @@ endif() 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}) configure_file( ${CMAKE_SOURCE_DIR}/src/opCode-6-174.txt @@ -34,6 +36,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 @@ -55,7 +67,16 @@ target_link_libraries( ${LIBS} ) +target_link_libraries( + event-resolver-utest + GTest::gtest_main + GTest::gmock_main + ${LIBS} + PCM_SIMDJSON +) + 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) diff --git a/tests/utests/event-resolver-utest.cpp b/tests/utests/event-resolver-utest.cpp new file mode 100644 index 00000000..3aba350c --- /dev/null +++ b/tests/utests/event-resolver-utest.cpp @@ -0,0 +1,378 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2025, 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; + } + } +} From a952e951b0aa038d6ce3ece1d618602060db737b Mon Sep 17 00:00:00 2001 From: Alexander Antonov Date: Mon, 30 Mar 2026 06:30:01 -0700 Subject: [PATCH 03/77] Add metric config reader class --- src/pcm-io-metrics.cpp | 201 ++++++++++++++++++++++++++ src/pcm-io-metrics.h | 48 ++++++ tests/utests/CMakeLists.txt | 11 ++ tests/utests/pcm-io-metrics-utest.cpp | 197 +++++++++++++++++++++++++ 4 files changed, 457 insertions(+) create mode 100644 src/pcm-io-metrics.cpp create mode 100644 src/pcm-io-metrics.h create mode 100644 tests/utests/pcm-io-metrics-utest.cpp diff --git a/src/pcm-io-metrics.cpp b/src/pcm-io-metrics.cpp new file mode 100644 index 00000000..ae71a54b --- /dev/null +++ b/src/pcm-io-metrics.cpp @@ -0,0 +1,201 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2026, Intel Corporation + +#include "pcm-io-metrics.h" + +#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() && 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; + } +}; + +} // 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; +} + +// --- 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) +{ + 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"; + + m_metrics.push_back(std::move(m)); + } + return !m_metrics.empty(); +} + +#else // !PCM_SIMDJSON_AVAILABLE + +bool MetricsConfig::load(const std::string&) { return false; } +bool MetricsConfig::loadFromString(const std::string&) { return false; } + +#endif // PCM_SIMDJSON_AVAILABLE + +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; +} + +} // namespace pcm diff --git a/src/pcm-io-metrics.h b/src/pcm-io-metrics.h new file mode 100644 index 00000000..65193367 --- /dev/null +++ b/src/pcm-io-metrics.h @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2026, Intel Corporation +#pragma once + +#include +#include +#include +#include + +#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" +}; + +class FormulaEvaluator { +public: + double evaluate(const std::string& formula, const std::unordered_map& variables) const; + std::set extractVariables(const std::string& formula) const; +}; + +class MetricsConfig { +public: + bool load(const std::string& path); + bool loadFromString(const std::string& jsonStr); + + const std::vector& getMetrics() const { return m_metrics; } + std::set extractEventNames() const; + +private: + std::vector m_metrics; + FormulaEvaluator m_evaluator; + +#ifdef PCM_SIMDJSON_AVAILABLE + bool parseMetrics(simdjson::dom::element doc); + std::shared_ptr m_jsonParser; +#endif +}; + +} // namespace pcm diff --git a/tests/utests/CMakeLists.txt b/tests/utests/CMakeLists.txt index 14d24b0f..799a85b7 100644 --- a/tests/utests/CMakeLists.txt +++ b/tests/utests/CMakeLists.txt @@ -18,6 +18,7 @@ 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) if(APPLE) set(LIBS PcmMsr Threads::Threads PCM_STATIC) @@ -29,6 +30,7 @@ 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}) configure_file( ${CMAKE_SOURCE_DIR}/src/opCode-6-174.txt @@ -75,8 +77,17 @@ target_link_libraries( PCM_SIMDJSON ) +target_link_libraries( + pcm-io-metrics-utest + GTest::gtest_main + GTest::gmock_main + ${LIBS} + PCM_SIMDJSON +) + 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) diff --git a/tests/utests/pcm-io-metrics-utest.cpp b/tests/utests/pcm-io-metrics-utest.cpp new file mode 100644 index 00000000..824c558a --- /dev/null +++ b/tests/utests/pcm-io-metrics-utest.cpp @@ -0,0 +1,197 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2026, Intel Corporation + +#include "pcm-io-metrics.h" +#include +#include + +using namespace pcm; + +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, 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")); +} From 7def3a2e051de5fd8520ff81525469d94345675a Mon Sep 17 00:00:00 2001 From: Alexander Antonov Date: Mon, 30 Mar 2026 08:38:23 -0700 Subject: [PATCH 04/77] Add table render class --- src/pcm-io-metrics.cpp | 171 ++++++++++++++++++++++++++ src/pcm-io-metrics.h | 23 ++++ tests/utests/pcm-io-metrics-utest.cpp | 112 +++++++++++++++++ 3 files changed, 306 insertions(+) diff --git a/src/pcm-io-metrics.cpp b/src/pcm-io-metrics.cpp index ae71a54b..dd562dcf 100644 --- a/src/pcm-io-metrics.cpp +++ b/src/pcm-io-metrics.cpp @@ -84,6 +84,47 @@ struct FormulaParser { } }; +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 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, @@ -115,6 +156,136 @@ std::set FormulaEvaluator::extractVariables(const std::string& form 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, "", values}); +} + +void TableRenderer::addSectionHeader(const std::string& title) +{ + m_rows.push_back({true, title, {}}); +} + +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; + + // Top border + 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.empty()) + { + // No data: close immediately + renderLine(os, BOX.bottom_left, BOX.tee_up, BOX.bottom_right, colWidths); + return; + } + + bool needDataSeparator = true; + for (size_t ri = 0; 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; + size_t pad = innerWidth - 1 - row.sectionTitle.size(); + 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; + } + 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"; + } + } + + // Bottom border + 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(); +} + // --- MetricsConfig --- #ifdef PCM_SIMDJSON_AVAILABLE diff --git a/src/pcm-io-metrics.h b/src/pcm-io-metrics.h index 65193367..5f9082e7 100644 --- a/src/pcm-io-metrics.h +++ b/src/pcm-io-metrics.h @@ -6,6 +6,8 @@ #include #include #include +#include +#include #ifdef PCM_SIMDJSON_AVAILABLE #include @@ -27,6 +29,27 @@ class FormulaEvaluator { 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 render(std::ostream& os) const; + std::string renderToString() const; + +private: + struct Row { + bool isSectionHeader = false; + std::string sectionTitle; + std::vector values; + }; + std::vector m_headers; + std::vector m_rows; + + std::vector calculateColumnWidths() const; + size_t calculateTableWidth(const std::vector& colWidths) const; +}; + class MetricsConfig { public: bool load(const std::string& path); diff --git a/tests/utests/pcm-io-metrics-utest.cpp b/tests/utests/pcm-io-metrics-utest.cpp index 824c558a..76f5078c 100644 --- a/tests/utests/pcm-io-metrics-utest.cpp +++ b/tests/utests/pcm-io-metrics-utest.cpp @@ -195,3 +195,115 @@ 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 + std::string expected = + std::string(B_TL) + hline(6) + B_TD + hline(7) + B_TR + "\n" + + B_V + " Read " + B_V + " Write " + B_V + "\n" + + B_ML + hline(14) + B_MR + "\n" + + B_V + " PCIe BW " + B_V + "\n" + + B_ML + hline(6) + B_TD + 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); +} From a7b74a53b3ad172f906536ee3cebe5deb05bbeea Mon Sep 17 00:00:00 2001 From: Alexander Antonov Date: Mon, 30 Mar 2026 09:06:20 -0700 Subject: [PATCH 05/77] Add layout support --- src/pcm-io-metrics.cpp | 61 +++++++++++++ src/pcm-io-metrics.h | 10 +++ tests/utests/pcm-io-metrics-utest.cpp | 118 ++++++++++++++++++++++++++ 3 files changed, 189 insertions(+) diff --git a/src/pcm-io-metrics.cpp b/src/pcm-io-metrics.cpp index dd562dcf..5a535568 100644 --- a/src/pcm-io-metrics.cpp +++ b/src/pcm-io-metrics.cpp @@ -348,9 +348,59 @@ bool MetricsConfig::parseMetrics(simdjson::dom::element doc) 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 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; } @@ -358,6 +408,17 @@ 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; diff --git a/src/pcm-io-metrics.h b/src/pcm-io-metrics.h index 5f9082e7..8e25a0ec 100644 --- a/src/pcm-io-metrics.h +++ b/src/pcm-io-metrics.h @@ -23,6 +23,11 @@ struct IOMetric { std::string aggregation; // "socket", "system", "stack" }; +struct LayoutSection { + std::string title; + std::vector metrics; // references IOMetric::name +}; + class FormulaEvaluator { public: double evaluate(const std::string& formula, const std::unordered_map& variables) const; @@ -56,14 +61,19 @@ class MetricsConfig { bool loadFromString(const std::string& jsonStr); const std::vector& getMetrics() const { return m_metrics; } + const std::vector& getLayout() const { return m_layout; } std::set extractEventNames() const; private: std::vector m_metrics; + std::vector m_layout; 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 }; diff --git a/tests/utests/pcm-io-metrics-utest.cpp b/tests/utests/pcm-io-metrics-utest.cpp index 76f5078c..da3b39b0 100644 --- a/tests/utests/pcm-io-metrics-utest.cpp +++ b/tests/utests/pcm-io-metrics-utest.cpp @@ -307,3 +307,121 @@ TEST_F(TableRendererTest, RenderEmptyTable) EXPECT_EQ(result, expected); } + +// --- 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"); +} From d2a26e17cd8d96820fd62148e02016169b85c3a5 Mon Sep 17 00:00:00 2001 From: Alexander Antonov Date: Mon, 30 Mar 2026 10:23:23 -0700 Subject: [PATCH 06/77] Add metrics validator --- src/pcm-io-metrics.cpp | 54 ++++++++++++++++++ src/pcm-io-metrics.h | 16 ++++++ tests/utests/pcm-io-metrics-utest.cpp | 79 +++++++++++++++++++++++++++ 3 files changed, 149 insertions(+) diff --git a/src/pcm-io-metrics.cpp b/src/pcm-io-metrics.cpp index 5a535568..ea431e2a 100644 --- a/src/pcm-io-metrics.cpp +++ b/src/pcm-io-metrics.cpp @@ -430,4 +430,58 @@ std::set MetricsConfig::extractEventNames() const return allEvents; } +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; +} + +void 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"; +} + } // namespace pcm diff --git a/src/pcm-io-metrics.h b/src/pcm-io-metrics.h index 8e25a0ec..3df602b1 100644 --- a/src/pcm-io-metrics.h +++ b/src/pcm-io-metrics.h @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -28,6 +29,19 @@ struct LayoutSection { std::vector metrics; // references IOMetric::name }; +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; @@ -63,6 +77,8 @@ class MetricsConfig { const std::vector& getMetrics() const { return m_metrics; } const std::vector& getLayout() const { return m_layout; } std::set extractEventNames() const; + ValidationResult validateEvents(const EventValidator& validator) const; + void printValidatedMetrics(std::ostream& os, const EventValidator& validator) const; private: std::vector m_metrics; diff --git a/tests/utests/pcm-io-metrics-utest.cpp b/tests/utests/pcm-io-metrics-utest.cpp index da3b39b0..a4aa6875 100644 --- a/tests/utests/pcm-io-metrics-utest.cpp +++ b/tests/utests/pcm-io-metrics-utest.cpp @@ -425,3 +425,82 @@ TEST(LayoutTest, LayoutMalformedSection) ASSERT_EQ(layout[1].metrics.size(), 1u); EXPECT_EQ(layout[1].metrics[0], "A"); } + +// --- 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; + config.printValidatedMetrics(os, [](const std::string& event) { + return event != "UNC_CHA_TOR_INSERTS.IO_ITOMCACHENEAR"; + }); + + 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); +} From 1acfbb549cfd2aee667bdc15999d2436920f3154 Mon Sep 17 00:00:00 2001 From: Alexander Antonov Date: Tue, 31 Mar 2026 04:00:13 -0700 Subject: [PATCH 07/77] Add initial set of metrics for ICX --- src/pmu-events/icelake-sp/metrics.json | 102 +++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 src/pmu-events/icelake-sp/metrics.json diff --git a/src/pmu-events/icelake-sp/metrics.json b/src/pmu-events/icelake-sp/metrics.json new file mode 100644 index 00000000..be454025 --- /dev/null +++ b/src/pmu-events/icelake-sp/metrics.json @@ -0,0 +1,102 @@ +{ + "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": "PCIRdCur", + "formula": "UNC_CHA_TOR_INSERTS.IO_PCIRDCUR", + "short_name": "RdCur", + "aggregation": "socket" + }, + { + "name": "PCIRdCur Miss", + "formula": "UNC_CHA_TOR_INSERTS.IO_MISS_PCIRDCUR", + "short_name": "RdMiss", + "aggregation": "socket" + }, + { + "name": "PCIRdCur Hit", + "formula": "UNC_CHA_TOR_INSERTS.IO_HIT_PCIRDCUR", + "short_name": "RdHit", + "aggregation": "socket" + }, + { + "name": "ItoM", + "formula": "UNC_CHA_TOR_INSERTS.IO_ITOM", + "short_name": "ItoM", + "aggregation": "socket" + }, + { + "name": "ItoM Miss", + "formula": "UNC_CHA_TOR_INSERTS.IO_MISS_ITOM", + "short_name": "ItoMMiss", + "aggregation": "socket" + }, + { + "name": "ItoM Hit", + "formula": "UNC_CHA_TOR_INSERTS.IO_HIT_ITOM", + "short_name": "ItoMHit", + "aggregation": "socket" + }, + { + "name": "ItoMCacheNear", + "formula": "UNC_CHA_TOR_INSERTS.IO_ITOMCACHENEAR", + "short_name": "ITMCN", + "aggregation": "socket" + }, + { + "name": "ItoMCacheNear Miss", + "formula": "UNC_CHA_TOR_INSERTS.IO_MISS_ITOMCACHENEAR", + "short_name": "ITMCNMiss", + "aggregation": "socket" + }, + { + "name": "ItoMCacheNear Hit", + "formula": "UNC_CHA_TOR_INSERTS.IO_HIT_ITOMCACHENEAR", + "short_name": "ITMCNHit", + "aggregation": "socket" + }, + { + "name": "Total Rd (B)", + "formula": "UNC_CHA_TOR_INSERTS.IO_PCIRDCUR * 64", + "short_name": "TotRd", + "aggregation": "system" + }, + { + "name": "Total Wr (B)", + "formula": "(UNC_CHA_TOR_INSERTS.IO_ITOM + UNC_CHA_TOR_INSERTS.IO_ITOMCACHENEAR) * 64", + "short_name": "TotWr", + "aggregation": "system" + } + ], + "layout": { + "sections": [ + { + "title": "PCIe Bandwidth", + "metrics": ["PCIe Rd (B)", "PCIe Wr (B)"] + }, + { + "title": "Read Events", + "metrics": ["PCIRdCur", "PCIRdCur Miss", "PCIRdCur Hit"] + }, + { + "title": "Write Events", + "metrics": ["ItoM", "ItoM Miss", "ItoM Hit", "ItoMCacheNear", "ItoMCacheNear Miss", "ItoMCacheNear Hit"] + }, + { + "title": "System Total", + "metrics": ["Total Rd (B)", "Total Wr (B)"] + } + ] + } +} From 72a10629decfae5e86a2a50bf06b443768fa3fcf Mon Sep 17 00:00:00 2001 From: Alexander Antonov Date: Tue, 31 Mar 2026 05:56:31 -0700 Subject: [PATCH 08/77] Add pcm-io --- src/CMakeLists.txt | 14 +- src/cpucounters.h | 5 + src/pcm-io.cpp | 705 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 723 insertions(+), 1 deletion(-) create mode 100644 src/pcm-io.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e9f31b59..c5ab7cfd 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -2,7 +2,7 @@ # 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) @@ -208,6 +208,18 @@ if(PCM_BUILD_EXECUTABLES) 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}/pmu-events + $/pmu-events) + endif(${PROJECT_NAME} STREQUAL pcm-io) + if(${PROJECT_NAME} STREQUAL pcm-sensor-server) if(NO_SSL) message(STATUS "SSL is disabled") diff --git a/src/cpucounters.h b/src/cpucounters.h index fc6c9a33..e2e3e9f5 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} }; diff --git a/src/pcm-io.cpp b/src/pcm-io.cpp new file mode 100644 index 00000000..e381129c --- /dev/null +++ b/src/pcm-io.cpp @@ -0,0 +1,705 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2026, Intel Corporation + +#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 displayFlatMode(std::ostream& os) const; + void displayCsv(std::ostream& os) const; + std::unordered_map getSystemCounterValues() const; + static std::string formatValue(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; } + static std::string cpuModelToDir(int cpuModel); + +private: + PCM* m_pcm = nullptr; + uint32 m_numSockets = 0; + MetricsConfig m_config; + MetricsDisplay m_display; + PerfmonEventResolver m_resolver; + PCM::RawPMUConfigs m_pmuConfigs; + + struct EventLocation { + std::string pmuName; + size_t counterIndex; + }; + std::unordered_map m_eventLocations; + + std::vector m_beforeState; + std::vector m_afterState; + 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 ""; + } +} + +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; + } + + // Resolve all events referenced in metric formulas + auto eventNames = m_config.extractEventNames(); + for (const auto& eventName : eventNames) + { + if (m_eventLocations.count(eventName)) + continue; // already resolved + + std::string pmuName; + PCM::RawEventConfig config; + if (!m_resolver.resolveEvent(eventName, pmuName, config)) + { + cerr << "WARNING: Could not resolve event: " << eventName << "\n"; + continue; + } + + size_t idx = m_pmuConfigs[pmuName].programmable.size(); + m_pmuConfigs[pmuName].programmable.push_back(config); + m_eventLocations[eventName] = {pmuName, idx}; + } + + if (m_eventLocations.empty()) + { + cerr << "ERROR: No events could be resolved\n"; + return false; + } + + // Program PMUs + PCM::ErrorCode status = pcm->program(m_pmuConfigs, true); + if (status != PCM::Success) + { + pcm->checkError(status); + return false; + } + + // Allocate counter state vectors + m_numSockets = pcm->getNumSockets(); + m_beforeState.resize(m_numSockets); + m_afterState.resize(m_numSockets); + m_counterValues.resize(m_numSockets); + + m_display.init(&m_config, m_numSockets); + + // Read initial "before" state right after programming + m_pcm->globalFreezeUncoreCounters(); + for (uint32 s = 0; s < m_numSockets; ++s) + m_beforeState[s] = m_pcm->getServerUncoreCounterState(s); + m_pcm->globalUnfreezeUncoreCounters(); + + return true; +} + +void MetricsDrivenPlatform::collect(int delayMs) +{ + MySleepMs(delayMs); + + // Read after state + m_pcm->globalFreezeUncoreCounters(); + for (uint32 s = 0; s < m_numSockets; ++s) + m_afterState[s] = m_pcm->getServerUncoreCounterState(s); + m_pcm->globalUnfreezeUncoreCounters(); + + readCounterValues(); + m_display.setCounterValues(&m_counterValues); + + // After becomes before for next iteration + std::swap(m_beforeState, m_afterState); +} + +void MetricsDrivenPlatform::readCounterValues() +{ + for (uint32 s = 0; s < m_numSockets; ++s) + { + m_counterValues[s].clear(); + for (const auto& [eventName, loc] : m_eventLocations) + { + auto pmuId = m_pcm->strToUncorePMUID(loc.pmuName); + if (pmuId == PCM::INVALID_PMU_ID) continue; + + size_t numUnits = m_pcm->getMaxNumOfUncorePMUs(pmuId, s); + double sum = 0.0; + for (size_t u = 0; u < numUnits; ++u) + { + sum += static_cast( + getUncoreCounter(pmuId, (uint32)u, (uint32)loc.counterIndex, + m_beforeState[s], m_afterState[s])); + } + m_counterValues[s][eventName] = sum; + } + } +} + +// --- 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(); +} + +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(); + os << "Skt"; + for (const auto& metric : metrics) + { + if (metric.aggregation == "system") continue; + os << "," << metricDisplayName(metric); + } + os << "\n"; +} + +void MetricsDisplay::display(std::ostream& os, bool csv, bool useLayout) const +{ + if (csv) + displayCsv(os); + else if (useLayout && m_config->getLayout().size() > 1) + displayLayoutMode(os); + else + displayFlatMode(os); +} + +void MetricsDisplay::displayCsv(std::ostream& os) const +{ + FormulaEvaluator evaluator; + const auto& metrics = m_config->getMetrics(); + + for (uint32 s = 0; s < m_numSockets; ++s) + { + os << s; + for (const auto& m : metrics) + { + if (m.aggregation == "system") continue; + double val = evaluator.evaluate(m.formula, (*m_counterValues)[s]); + os << "," << static_cast(val); + } + os << "\n"; + } + + auto systemValues = getSystemCounterValues(); + bool hasSystem = false; + for (const auto& m : metrics) + { + if (m.aggregation == "system") + { + if (!hasSystem) + { + os << "*"; + hasSystem = true; + } + double val = evaluator.evaluate(m.formula, systemValues); + os << "," << static_cast(val); + } + } + if (hasSystem) os << "\n"; +} + +void MetricsDisplay::displayLayoutMode(std::ostream& os) const +{ + FormulaEvaluator evaluator; + const auto& metrics = m_config->getMetrics(); + + for (const auto& section : m_config->getLayout()) + { + std::vector headers; + std::vector metricIdxs; + 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) + { + headers.push_back(metricDisplayName(metrics[i])); + metricIdxs.push_back(i); + if (metrics[i].aggregation == "system") + hasSystemMetrics = true; + else + hasSocketMetrics = true; + break; + } + } + } + + if (headers.empty()) 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 row; + if (hasSocketMetrics) row.push_back("*"); + for (size_t idx : metricIdxs) + { + if (metrics[idx].aggregation != "system") continue; + double val = evaluator.evaluate(metrics[idx].formula, systemValues); + row.push_back(formatValue(val)); + } + table.addRow(row); + } + + table.render(os); + os << "\n"; + } +} + +void MetricsDisplay::displayFlatMode(std::ostream& os) const +{ + FormulaEvaluator evaluator; + const auto& metrics = m_config->getMetrics(); + + std::vector fullHeaders = {"Skt"}; + std::vector socketMetricIndices; + for (size_t i = 0; i < metrics.size(); ++i) + { + if (metrics[i].aggregation != "system") + { + fullHeaders.push_back(metricDisplayName(metrics[i])); + socketMetricIndices.push_back(i); + } + } + + TableRenderer table; + table.setHeaders(fullHeaders); + + 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); + } + + auto systemValues = getSystemCounterValues(); + bool hasSystem = false; + std::vector sysRow; + sysRow.push_back("*"); + for (size_t i = 0; i < metrics.size(); ++i) + { + if (metrics[i].aggregation == "system") + { + double val = evaluator.evaluate(metrics[i].formula, systemValues); + sysRow.push_back(formatValue(val)); + hasSystem = true; + } + } + if (hasSystem) + { + table.addSectionHeader("System Total"); + table.addRow(sysRow); + } + + 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 => 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 << "\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 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& eventPrefix) +{ + MetricsConfig config; + if (!config.load(metricsPath)) + { + cerr << "ERROR: Failed to load metrics from " << metricsPath << "\n"; + return false; + } + PerfmonEventResolver resolver; + if (!resolver.init(cpuFamilyModel, eventPrefix)) + { + cerr << "ERROR: Failed to initialize event resolver\n"; + return false; + } + config.printValidatedMetrics(os, [&resolver](const std::string& event) { return resolver.isEvent(event); }); + return true; +} + +static std::string findMetricsPath(const std::string& programPath, const std::string& platformDir) +{ + if (platformDir.empty()) return ""; + + const std::string relPath = "pmu-events/" + platformDir + "/metrics.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 ""; +} + +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; + std::string eventPrefix; + 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"})) + { + print_usage(program); + exit(EXIT_FAILURE); + } + 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, {"--ep"})) + { + argv++; + argc--; + if (argc <= 0) + { + cerr << "ERROR: no parameter provided for option --ep\n"; + exit(EXIT_FAILURE); + } + eventPrefix = *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()) + { + 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); + } + + // Auto-detect paths if not specified + // Event prefix default: "." — the resolver also checks getInstallPathPrefix() + if (eventPrefix.empty()) eventPrefix = "."; + + if (metricsPath.empty()) metricsPath = findMetricsPath(program, platformDir); + + if (metricsPath.empty()) + { + cerr << "ERROR: Could not find metrics.json for platform " << platformDir << "\n"; + cerr << "Use --metrics to specify the file location.\n"; + exit(EXIT_FAILURE); + } + + cerr << "Metrics file: " << metricsPath << "\n"; + cerr << "Event prefix: " << eventPrefix << "\n"; + + std::string cpuFamilyModel = m->getCPUFamilyModelString(); + + if (validateOnly) + { + cerr << "\nMetrics validation for " << platformDir << ":\n\n"; + exit(printValidation(cout, metricsPath, cpuFamilyModel, eventPrefix) ? EXIT_SUCCESS : EXIT_FAILURE); + } + + // Initialize platform + MetricsDrivenPlatform platform; + if (!platform.init(m, metricsPath, eventPrefix)) + { + cerr << "ERROR: Platform initialization failed\n\nMetrics validation:\n\n"; + printValidation(cerr, metricsPath, cpuFamilyModel, eventPrefix); + 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); + + 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); +} From 1c2594418dc2ca4cc505dac365f826af111c4b48 Mon Sep 17 00:00:00 2001 From: Alexander Antonov Date: Wed, 1 Apr 2026 03:20:20 -0700 Subject: [PATCH 09/77] Trying to add support for any PMU type --- src/pcm-io.cpp | 77 ++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 71 insertions(+), 6 deletions(-) diff --git a/src/pcm-io.cpp b/src/pcm-io.cpp index e381129c..1a945472 100644 --- a/src/pcm-io.cpp +++ b/src/pcm-io.cpp @@ -61,6 +61,17 @@ class MetricsDrivenPlatform { }; 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_beforeState; std::vector m_afterState; std::vector> m_counterValues; @@ -145,6 +156,7 @@ bool MetricsDrivenPlatform::init(PCM* pcm, const std::string& metricsPath, const m_counterValues.resize(m_numSockets); m_display.init(&m_config, m_numSockets); + initPMUCounterDescs(); // Read initial "before" state right after programming m_pcm->globalFreezeUncoreCounters(); @@ -172,6 +184,59 @@ void MetricsDrivenPlatform::collect(int delayMs) std::swap(m_beforeState, m_afterState); } +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) @@ -179,16 +244,16 @@ void MetricsDrivenPlatform::readCounterValues() m_counterValues[s].clear(); for (const auto& [eventName, loc] : m_eventLocations) { - auto pmuId = m_pcm->strToUncorePMUID(loc.pmuName); - if (pmuId == PCM::INVALID_PMU_ID) continue; + auto it = m_pmuCounterDescs.find(loc.pmuName); + if (it == m_pmuCounterDescs.end()) continue; - size_t numUnits = m_pcm->getMaxNumOfUncorePMUs(pmuId, s); + 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( - getUncoreCounter(pmuId, (uint32)u, (uint32)loc.counterIndex, - m_beforeState[s], m_afterState[s])); + sum += static_cast(desc.getter((uint32)u, (uint32)loc.counterIndex, + m_beforeState[s], m_afterState[s])); } m_counterValues[s][eventName] = sum; } From 776c4c78cf93ede58f7fb0d871e0f0b2a91996fd Mon Sep 17 00:00:00 2001 From: Alexander Antonov Date: Wed, 1 Apr 2026 03:54:06 -0700 Subject: [PATCH 10/77] Add parsing for local events --- src/event-resolver.cpp | 46 ++++++++++++++-- src/event-resolver.h | 8 +++ src/pcm-io-metrics.cpp | 28 ++++++++++ src/pcm-io-metrics.h | 5 ++ src/pcm-io.cpp | 4 ++ tests/utests/event-resolver-utest.cpp | 64 ++++++++++++++++++++++ tests/utests/pcm-io-metrics-utest.cpp | 76 +++++++++++++++++++++++++++ 7 files changed, 228 insertions(+), 3 deletions(-) diff --git a/src/event-resolver.cpp b/src/event-resolver.cpp index 15fe4510..0206c9fc 100644 --- a/src/event-resolver.cpp +++ b/src/event-resolver.cpp @@ -23,6 +23,12 @@ const std::map PerfmonEventResolver::s_pmuNameMap = { {"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) @@ -286,6 +292,8 @@ bool PerfmonEventResolver::loadPMUDeclarations(const std::string& cpuFamilyModel 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) @@ -298,6 +306,10 @@ bool PerfmonEventResolver::isEvent(const std::string& eventName) const 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()) { @@ -326,6 +338,13 @@ bool PerfmonEventResolver::isField(const std::string& eventName, 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()) { @@ -521,9 +540,30 @@ bool PerfmonEventResolver::init(const std::string&, const std::string&) return false; } -bool PerfmonEventResolver::isEvent(const std::string&) const { return false; } -bool PerfmonEventResolver::isField(const std::string&, const std::string&) const { return false; } -std::string PerfmonEventResolver::getField(const std::string&, const std::string&) const { return ""; } +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 {}; } diff --git a/src/event-resolver.h b/src/event-resolver.h index 1094455d..4f3f6464 100644 --- a/src/event-resolver.h +++ b/src/event-resolver.h @@ -15,11 +15,18 @@ namespace pcm { +// Field name → value map for a locally defined event +using LocalEvent = std::unordered_map; + class PerfmonEventResolver { public: // Initialize from explicit CPU identification 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; @@ -58,6 +65,7 @@ class PerfmonEventResolver { 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/pcm-io-metrics.cpp b/src/pcm-io-metrics.cpp index ea431e2a..e8111f73 100644 --- a/src/pcm-io-metrics.cpp +++ b/src/pcm-io-metrics.cpp @@ -322,6 +322,34 @@ bool MetricsConfig::loadFromString(const std::string& jsonStr) 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()) { diff --git a/src/pcm-io-metrics.h b/src/pcm-io-metrics.h index 3df602b1..89d3bcb6 100644 --- a/src/pcm-io-metrics.h +++ b/src/pcm-io-metrics.h @@ -17,6 +17,9 @@ namespace pcm { +// Forward-declared in event-resolver.h; redeclared here so pcm-io-metrics.h is self-contained +using LocalEvent = std::unordered_map; + struct IOMetric { std::string name; std::string formula; @@ -76,6 +79,7 @@ class MetricsConfig { 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; ValidationResult validateEvents(const EventValidator& validator) const; void printValidatedMetrics(std::ostream& os, const EventValidator& validator) const; @@ -83,6 +87,7 @@ class MetricsConfig { private: std::vector m_metrics; std::vector m_layout; + std::vector> m_localEvents; FormulaEvaluator m_evaluator; void generateFlatLayout(); diff --git a/src/pcm-io.cpp b/src/pcm-io.cpp index 1a945472..31b876bf 100644 --- a/src/pcm-io.cpp +++ b/src/pcm-io.cpp @@ -115,6 +115,9 @@ bool MetricsDrivenPlatform::init(PCM* pcm, const std::string& metricsPath, const 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 auto eventNames = m_config.extractEventNames(); for (const auto& eventName : eventNames) @@ -554,6 +557,7 @@ static bool printValidation(std::ostream& os, const std::string& metricsPath, cerr << "ERROR: Failed to initialize event resolver\n"; return false; } + resolver.addLocalEvents(config.getLocalEvents()); config.printValidatedMetrics(os, [&resolver](const std::string& event) { return resolver.isEvent(event); }); return true; } diff --git a/tests/utests/event-resolver-utest.cpp b/tests/utests/event-resolver-utest.cpp index 3aba350c..cb9df76d 100644 --- a/tests/utests/event-resolver-utest.cpp +++ b/tests/utests/event-resolver-utest.cpp @@ -376,3 +376,67 @@ TEST_F(EventResolverTest, AllICXTmaEventsFieldValues) } } } + +// --- 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 index a4aa6875..c3bc2af1 100644 --- a/tests/utests/pcm-io-metrics-utest.cpp +++ b/tests/utests/pcm-io-metrics-utest.cpp @@ -504,3 +504,79 @@ TEST(ValidationTest, PrintValidatedMetrics) 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); } + +// --- 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); +} From 78b2d4a25be3e40226c1c2b7ef7c6f790c40e6b1 Mon Sep 17 00:00:00 2001 From: Alexander Antonov Date: Wed, 1 Apr 2026 05:04:25 -0700 Subject: [PATCH 11/77] Show available metrics in help info --- src/pcm-io.cpp | 94 ++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 71 insertions(+), 23 deletions(-) diff --git a/src/pcm-io.cpp b/src/pcm-io.cpp index 31b876bf..dec5a98f 100644 --- a/src/pcm-io.cpp +++ b/src/pcm-io.cpp @@ -519,6 +519,65 @@ static void print_usage(const string& progname) cout << "\n"; } +static std::string findMetricsPath(const std::string& programPath, const std::string& platformDir) +{ + if (platformDir.empty()) return ""; + + const std::string relPath = "pmu-events/" + platformDir + "/metrics.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(); + + if (layout.size() > 1) + { + for (const auto& section : layout) + { + if (!section.title.empty()) + cout << " [" << section.title << "]\n"; + for (const auto& metricName : section.metrics) + { + for (const auto& metric : metrics) + { + if (metric.name == metricName) + { + cout << " " << metric.name << " = " << metric.formula << "\n"; + break; + } + } + } + cout << "\n"; + } + } + else + { + for (const auto& metric : metrics) + cout << " " << metric.name << " = " << metric.formula << "\n"; + cout << "\n"; + } +} + static double resolveDelay(double delay, bool csv, bool hasExternalCmd, PCM* m) { m->setBlocked(hasExternalCmd && delay <= 0.0); @@ -562,25 +621,6 @@ static bool printValidation(std::ostream& os, const std::string& metricsPath, return true; } -static std::string findMetricsPath(const std::string& programPath, const std::string& platformDir) -{ - if (platformDir.empty()) return ""; - - const std::string relPath = "pmu-events/" + platformDir + "/metrics.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 ""; -} - PCM_MAIN_NOTHROW; int mainThrows(int argc, char* argv[]) @@ -608,6 +648,7 @@ int mainThrows(int argc, char* argv[]) bool csv = false; bool useLayout = true; bool validateOnly = false; + bool showHelp = false; std::string eventPrefix; std::string metricsPath; char* sysCmd = nullptr; @@ -626,8 +667,8 @@ int mainThrows(int argc, char* argv[]) if (check_argument_equals(*argv, {"--help", "-h", "/h"})) { - print_usage(program); - exit(EXIT_FAILURE); + showHelp = true; + continue; } else if (check_argument_equals(*argv, {"-silent", "/silent"})) { @@ -698,7 +739,7 @@ int mainThrows(int argc, char* argv[]) // Auto-detect platform std::string platformDir = MetricsDrivenPlatform::cpuModelToDir(m->getCPUFamilyModel()); - if (platformDir.empty()) + if (platformDir.empty() && !showHelp) { print_cpu_details(); cerr << "ERROR: No metrics definition available for this CPU model.\n"; @@ -712,7 +753,7 @@ int mainThrows(int argc, char* argv[]) if (metricsPath.empty()) metricsPath = findMetricsPath(program, platformDir); - if (metricsPath.empty()) + if (metricsPath.empty() && !showHelp) { cerr << "ERROR: Could not find metrics.json for platform " << platformDir << "\n"; cerr << "Use --metrics to specify the file location.\n"; @@ -722,6 +763,13 @@ int mainThrows(int argc, char* argv[]) cerr << "Metrics file: " << metricsPath << "\n"; cerr << "Event prefix: " << eventPrefix << "\n"; + if (showHelp) + { + print_usage(program); + print_available_metrics(metricsPath, platformDir); + exit(EXIT_SUCCESS); + } + std::string cpuFamilyModel = m->getCPUFamilyModelString(); if (validateOnly) From 8c5f1648442a33d4fe03085e7995e8e1b58f7d3e Mon Sep 17 00:00:00 2001 From: Alexander Antonov Date: Tue, 7 Apr 2026 02:50:25 -0700 Subject: [PATCH 12/77] Add PerfmonEventResolver::findPerfmonPath() --- src/event-resolver.cpp | 63 ++++++++++++++++++------------------------ src/event-resolver.h | 8 +++++- src/pcm-io.cpp | 32 +++++++++++++-------- 3 files changed, 55 insertions(+), 48 deletions(-) diff --git a/src/event-resolver.cpp b/src/event-resolver.cpp index 0206c9fc..8c5aa2ed 100644 --- a/src/event-resolver.cpp +++ b/src/event-resolver.cpp @@ -13,6 +13,23 @@ 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"}, @@ -105,23 +122,14 @@ bool PerfmonEventResolver::parseTSV(const std::string& path) bool PerfmonEventResolver::loadPerfmonEvents(const std::string& cpuFamilyModel, const std::string& prefix) { const std::string mapfilePath = prefix + "/mapfile.csv"; - const std::string mapfilePathAlt = getInstallPathPrefix() + "perfmon/mapfile.csv"; std::ifstream in(mapfilePath); if (!in.is_open()) { - in.open(mapfilePathAlt); - if (!in.is_open()) - { - std::cerr << "ERROR: File " << mapfilePath << " or " << mapfilePathAlt << " can't be opened.\n"; -#ifndef _MSC_VER - std::cerr << " run 'make install' in the pcm build directory if you cloned PCM source repository recursively with submodules, or\n"; -#endif - std::cerr << " use -ep /perfmon option if you cloned PCM source repository recursively with submodules,\n"; - std::cerr << " or run 'git clone https://github.com/intel/perfmon' to download the perfmon event repository and use -ep option\n"; - std::cerr << " or download the file from https://raw.githubusercontent.com/intel/perfmon/main/mapfile.csv\n"; - return false; - } + 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; @@ -182,18 +190,15 @@ bool PerfmonEventResolver::loadPerfmonEvents(const std::string& cpuFamilyModel, const std::string path1 = prefix + evfile.second; const std::string path2 = prefix + evfile.second.substr(evfile.second.rfind('/')); - const std::string path3 = getInstallPathPrefix() + "perfmon" + evfile.second; 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 << "ERROR: Can't open event file at " << path1 << " or " << path2 << "\n"; std::cerr << "Make sure you have downloaded " << evfile.second << " from https://raw.githubusercontent.com/intel/perfmon/main" << evfile.second << "\n"; @@ -236,9 +241,13 @@ bool PerfmonEventResolver::loadPMUDeclarations(const std::string& cpuFamilyModel std::string path; std::string errMsg; + // PMURegisterDeclarations is a sibling of the perfmon directory, not inside it. + size_t lastSlash = prefix.find_last_of('/'); + std::string baseDir = (lastSlash != std::string::npos) ? prefix.substr(0, lastSlash) : "."; + for (int s = stepping; s >= 0; --s) { - std::string declPath = "PMURegisterDeclarations/" + cpuFamilyModel + "-" + std::to_string(s) + ".json"; + std::string declPath = baseDir + "/PMURegisterDeclarations/" + cpuFamilyModel + "-" + std::to_string(s) + ".json"; std::ifstream in(declPath); if (in.is_open()) @@ -248,24 +257,6 @@ bool PerfmonEventResolver::loadPMUDeclarations(const std::string& cpuFamilyModel break; } - const std::string altPath = prefix + "/" + declPath; - in.open(altPath); - if (in.is_open()) - { - path = altPath; - in.close(); - break; - } - - const std::string installPath = getInstallPathPrefix() + declPath; - in.open(installPath); - if (in.is_open()) - { - path = installPath; - in.close(); - break; - } - errMsg = "PMURegisterDeclarations file not found for " + cpuFamilyModel + " stepping " + std::to_string(s); } diff --git a/src/event-resolver.h b/src/event-resolver.h index 4f3f6464..6cbca8e8 100644 --- a/src/event-resolver.h +++ b/src/event-resolver.h @@ -20,7 +20,13 @@ using LocalEvent = std::unordered_map; class PerfmonEventResolver { public: - // Initialize from explicit CPU identification + // Search for the perfmon directory containing mapfile.csv. + // Checks: next to programPath binary, then install prefix. + // Returns empty string if not found. + 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). diff --git a/src/pcm-io.cpp b/src/pcm-io.cpp index dec5a98f..2ff3c6c5 100644 --- a/src/pcm-io.cpp +++ b/src/pcm-io.cpp @@ -602,7 +602,7 @@ static double resolveDelay(double delay, bool csv, bool hasExternalCmd, PCM* m) } static bool printValidation(std::ostream& os, const std::string& metricsPath, - const std::string& cpuFamilyModel, const std::string& eventPrefix) + const std::string& cpuFamilyModel, const std::string& perfmonPath) { MetricsConfig config; if (!config.load(metricsPath)) @@ -649,7 +649,7 @@ int mainThrows(int argc, char* argv[]) bool useLayout = true; bool validateOnly = false; bool showHelp = false; - std::string eventPrefix; + std::string perfmonPath; std::string metricsPath; char* sysCmd = nullptr; char** sysArgv = nullptr; @@ -708,7 +708,7 @@ int mainThrows(int argc, char* argv[]) cerr << "ERROR: no parameter provided for option --ep\n"; exit(EXIT_FAILURE); } - eventPrefix = *argv; + perfmonPath = *argv; continue; } else if (check_argument_equals(*argv, {"--metrics"})) @@ -747,9 +747,19 @@ int mainThrows(int argc, char* argv[]) exit(EXIT_FAILURE); } - // Auto-detect paths if not specified - // Event prefix default: "." — the resolver also checks getInstallPathPrefix() - if (eventPrefix.empty()) eventPrefix = "."; + 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); @@ -760,8 +770,8 @@ int mainThrows(int argc, char* argv[]) exit(EXIT_FAILURE); } - cerr << "Metrics file: " << metricsPath << "\n"; - cerr << "Event prefix: " << eventPrefix << "\n"; + cout << "Metrics file: " << metricsPath << "\n"; + cout << "Perfmon event path: " << perfmonPath << "\n"; if (showHelp) { @@ -775,15 +785,15 @@ int mainThrows(int argc, char* argv[]) if (validateOnly) { cerr << "\nMetrics validation for " << platformDir << ":\n\n"; - exit(printValidation(cout, metricsPath, cpuFamilyModel, eventPrefix) ? EXIT_SUCCESS : EXIT_FAILURE); + exit(printValidation(cout, metricsPath, cpuFamilyModel, perfmonPath) ? EXIT_SUCCESS : EXIT_FAILURE); } // Initialize platform MetricsDrivenPlatform platform; - if (!platform.init(m, metricsPath, eventPrefix)) + if (!platform.init(m, metricsPath, perfmonPath)) { cerr << "ERROR: Platform initialization failed\n\nMetrics validation:\n\n"; - printValidation(cerr, metricsPath, cpuFamilyModel, eventPrefix); + printValidation(cerr, metricsPath, cpuFamilyModel, perfmonPath); exit(EXIT_FAILURE); } From f40a913dc3d01d6a5915b69a6ffceeea95492b14 Mon Sep 17 00:00:00 2001 From: Alexander Antonov Date: Tue, 7 Apr 2026 02:50:47 -0700 Subject: [PATCH 13/77] Update pcm-raw to use PerfmonEventResolver::findPerfmonPath() --- src/pcm-io.cpp | 2 +- src/pcm-raw.cpp | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/pcm-io.cpp b/src/pcm-io.cpp index 2ff3c6c5..9a4f4305 100644 --- a/src/pcm-io.cpp +++ b/src/pcm-io.cpp @@ -611,7 +611,7 @@ static bool printValidation(std::ostream& os, const std::string& metricsPath, return false; } PerfmonEventResolver resolver; - if (!resolver.init(cpuFamilyModel, eventPrefix)) + if (!resolver.init(cpuFamilyModel, perfmonPath)) { cerr << "ERROR: Failed to initialize event resolver\n"; return false; diff --git a/src/pcm-raw.cpp b/src/pcm-raw.cpp index 139eca5f..8b49c520 100644 --- a/src/pcm-raw.cpp +++ b/src/pcm-raw.cpp @@ -154,7 +154,7 @@ bool tooManyEvents(const std::string & pmuName, const int event_pos, const std:: using namespace simdjson; static pcm::PerfmonEventResolver s_resolver; -std::string eventFileLocationPrefix = "."; +std::string eventFileLocationPrefix; bool initPMUEventMap() { @@ -2167,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 From f9b9417a5bf78b65a3bab73650f6d4acaac79adf Mon Sep 17 00:00:00 2001 From: Alexander Antonov Date: Tue, 7 Apr 2026 06:14:55 -0700 Subject: [PATCH 14/77] Fix loadPMUDeclarations() function --- src/event-resolver.cpp | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/src/event-resolver.cpp b/src/event-resolver.cpp index 8c5aa2ed..cd640156 100644 --- a/src/event-resolver.cpp +++ b/src/event-resolver.cpp @@ -241,21 +241,36 @@ bool PerfmonEventResolver::loadPMUDeclarations(const std::string& cpuFamilyModel std::string path; std::string errMsg; - // PMURegisterDeclarations is a sibling of the perfmon directory, not inside it. - size_t lastSlash = prefix.find_last_of('/'); - std::string baseDir = (lastSlash != std::string::npos) ? prefix.substr(0, lastSlash) : "."; + // 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) { - std::string declPath = baseDir + "/PMURegisterDeclarations/" + cpuFamilyModel + "-" + std::to_string(s) + ".json"; + 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 + }; - std::ifstream in(declPath); - if (in.is_open()) + for (const auto& declPath : candidates) { - path = declPath; - in.close(); - break; + 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); } From a0abd4b1c2cd126458373a95167a258936d30a87 Mon Sep 17 00:00:00 2001 From: Alexander Antonov Date: Tue, 7 Apr 2026 10:12:44 -0700 Subject: [PATCH 15/77] Return quotes back --- src/pcm-raw.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pcm-raw.cpp b/src/pcm-raw.cpp index 8b49c520..b679f083 100644 --- a/src/pcm-raw.cpp +++ b/src/pcm-raw.cpp @@ -169,7 +169,7 @@ void print_event_description(const std::string& eventStr) { std::string val = s_resolver.getField(eventStr, key); - if (!val.empty()) std::cout << key << " : " << val << "\n"; + if (!val.empty()) std::cout << key << " : " << std::quoted(val) << "\n"; } } From a49fd05bedbda455e793dae20f6392f8b2d0ef3f Mon Sep 17 00:00:00 2001 From: Alexander Antonov Date: Wed, 8 Apr 2026 01:53:24 -0700 Subject: [PATCH 16/77] Add multi-grouping for data collection --- src/pcm-io.cpp | 91 ++++++++++++++++++++++++++++++++------------------ 1 file changed, 58 insertions(+), 33 deletions(-) diff --git a/src/pcm-io.cpp b/src/pcm-io.cpp index 9a4f4305..83c0bc25 100644 --- a/src/pcm-io.cpp +++ b/src/pcm-io.cpp @@ -53,9 +53,10 @@ class MetricsDrivenPlatform { MetricsConfig m_config; MetricsDisplay m_display; PerfmonEventResolver m_resolver; - PCM::RawPMUConfigs m_pmuConfigs; + std::vector m_pmuConfigGroups; struct EventLocation { + size_t groupIndex; std::string pmuName; size_t counterIndex; }; @@ -72,8 +73,8 @@ class MetricsDrivenPlatform { std::unordered_map m_pmuCounterDescs; void initPMUCounterDescs(); - std::vector m_beforeState; - std::vector m_afterState; + std::vector> m_groupBeforeStates; // [group][socket] + std::vector> m_groupAfterStates; // [group][socket] std::vector> m_counterValues; void readCounterValues(); @@ -118,7 +119,8 @@ bool MetricsDrivenPlatform::init(PCM* pcm, const std::string& metricsPath, const // Register local events from metrics.json (takes priority over perfmon) m_resolver.addLocalEvents(m_config.getLocalEvents()); - // Resolve all events referenced in metric formulas + // Resolve all events referenced in metric formulas, batching into groups of + // up to ServerUncoreCounterState::maxCounters events per PMU type. auto eventNames = m_config.extractEventNames(); for (const auto& eventName : eventNames) { @@ -133,9 +135,24 @@ bool MetricsDrivenPlatform::init(PCM* pcm, const std::string& metricsPath, const continue; } - size_t idx = m_pmuConfigs[pmuName].programmable.size(); - m_pmuConfigs[pmuName].programmable.push_back(config); - m_eventLocations[eventName] = {pmuName, idx}; + // Find first group that still has room for this PMU type + size_t groupIdx = m_pmuConfigGroups.size(); // default: start a new group + for (size_t g = 0; g < m_pmuConfigGroups.size(); ++g) + { + const auto& existing = m_pmuConfigGroups[g][pmuName].programmable; + if (isRegisterEvent(pmuName) || existing.size() < ServerUncoreCounterState::maxCounters) + { + groupIdx = g; + break; + } + } + if (groupIdx == m_pmuConfigGroups.size()) + m_pmuConfigGroups.emplace_back(); + + auto& grp = m_pmuConfigGroups[groupIdx]; + size_t idx = grp[pmuName].programmable.size(); + grp[pmuName].programmable.push_back(config); + m_eventLocations[eventName] = {groupIdx, pmuName, idx}; } if (m_eventLocations.empty()) @@ -144,47 +161,54 @@ bool MetricsDrivenPlatform::init(PCM* pcm, const std::string& metricsPath, const return false; } - // Program PMUs - PCM::ErrorCode status = pcm->program(m_pmuConfigs, true); - if (status != PCM::Success) + // Allocate counter state vectors (programming happens in collect()) + m_numSockets = pcm->getNumSockets(); + m_counterValues.resize(m_numSockets); + + const size_t numGroups = m_pmuConfigGroups.size(); + m_groupBeforeStates.resize(numGroups); + m_groupAfterStates.resize(numGroups); + for (size_t g = 0; g < numGroups; ++g) { - pcm->checkError(status); - return false; + m_groupBeforeStates[g].resize(m_numSockets); + m_groupAfterStates[g].resize(m_numSockets); } - // Allocate counter state vectors - m_numSockets = pcm->getNumSockets(); - m_beforeState.resize(m_numSockets); - m_afterState.resize(m_numSockets); - m_counterValues.resize(m_numSockets); + if (numGroups > 1) + cerr << "INFO: Events split into " << numGroups << " measurement groups\n"; m_display.init(&m_config, m_numSockets); initPMUCounterDescs(); - // Read initial "before" state right after programming - m_pcm->globalFreezeUncoreCounters(); - for (uint32 s = 0; s < m_numSockets; ++s) - m_beforeState[s] = m_pcm->getServerUncoreCounterState(s); - m_pcm->globalUnfreezeUncoreCounters(); - return true; } void MetricsDrivenPlatform::collect(int delayMs) { - MySleepMs(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; + } - // Read after state - m_pcm->globalFreezeUncoreCounters(); - for (uint32 s = 0; s < m_numSockets; ++s) - m_afterState[s] = m_pcm->getServerUncoreCounterState(s); - m_pcm->globalUnfreezeUncoreCounters(); + 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); - - // After becomes before for next iteration - std::swap(m_beforeState, m_afterState); } void MetricsDrivenPlatform::initPMUCounterDescs() @@ -256,7 +280,8 @@ void MetricsDrivenPlatform::readCounterValues() for (size_t u = 0; u < numUnits; ++u) { sum += static_cast(desc.getter((uint32)u, (uint32)loc.counterIndex, - m_beforeState[s], m_afterState[s])); + m_groupBeforeStates[loc.groupIndex][s], + m_groupAfterStates[loc.groupIndex][s])); } m_counterValues[s][eventName] = sum; } From 499177d4c60cf25d51d97c344bb56e7211e62129 Mon Sep 17 00:00:00 2001 From: Alexander Antonov Date: Wed, 8 Apr 2026 06:04:56 -0700 Subject: [PATCH 17/77] Show title at the top of table --- src/pcm-io-metrics.cpp | 35 ++++++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/src/pcm-io-metrics.cpp b/src/pcm-io-metrics.cpp index e8111f73..b0a8060e 100644 --- a/src/pcm-io-metrics.cpp +++ b/src/pcm-io-metrics.cpp @@ -209,8 +209,33 @@ void TableRenderer::render(std::ostream& os) const // innerWidth = tableWidth minus the 2 outer border chars (in display columns) size_t innerWidth = tableWidth - 2; - // Top border - renderLine(os, BOX.top_left, BOX.tee_down, BOX.top_right, colWidths); + // 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 + os << BOX.vertical << " " << m_rows[0].sectionTitle; + size_t pad = innerWidth - 1 - m_rows[0].sectionTitle.size(); + 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; @@ -224,15 +249,15 @@ void TableRenderer::render(std::ostream& os) const } os << "\n"; - if (m_rows.empty()) + if (m_rows.size() <= firstDataRow) { - // No data: close immediately + // No data rows: close immediately renderLine(os, BOX.bottom_left, BOX.tee_up, BOX.bottom_right, colWidths); return; } bool needDataSeparator = true; - for (size_t ri = 0; ri < m_rows.size(); ++ri) + for (size_t ri = firstDataRow; ri < m_rows.size(); ++ri) { const auto& row = m_rows[ri]; if (row.isSectionHeader) From 73c5c3d0451487f5cc334caa1053deb2f801ec1e Mon Sep 17 00:00:00 2001 From: Alexander Antonov Date: Thu, 9 Apr 2026 01:07:18 -0700 Subject: [PATCH 18/77] Properly display mix of metrics if they have different aggregations --- src/pcm-io-metrics.cpp | 75 +++++++++++++++++++++++++-- src/pcm-io-metrics.h | 4 ++ src/pcm-io.cpp | 23 ++++---- tests/utests/pcm-io-metrics-utest.cpp | 24 +++++++-- 4 files changed, 111 insertions(+), 15 deletions(-) diff --git a/src/pcm-io-metrics.cpp b/src/pcm-io-metrics.cpp index b0a8060e..7dfc3e82 100644 --- a/src/pcm-io-metrics.cpp +++ b/src/pcm-io-metrics.cpp @@ -111,6 +111,15 @@ static const BoxChars BOX { }; #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) { @@ -165,12 +174,18 @@ void TableRenderer::setHeaders(const std::vector& headers) void TableRenderer::addRow(const std::vector& values) { - m_rows.push_back({false, "", values}); + m_rows.push_back({false, false, "", values, {}}); } void TableRenderer::addSectionHeader(const std::string& title) { - m_rows.push_back({true, 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 @@ -257,6 +272,8 @@ void TableRenderer::render(std::ostream& os) const } 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]; @@ -278,6 +295,54 @@ void TableRenderer::render(std::ostream& os) const // 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 N = row.systemPairs.size(); + if (N == 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; + size_t titlePad = innerWidth - 1 - row.sectionTitle.size(); + for (size_t j = 0; j < titlePad; ++j) os << " "; + os << BOX.vertical << "\n"; + + // Compute N equal column widths + size_t innerSpace = innerWidth - (N - 1); + std::vector wideColWidths(N, innerSpace / N); + for (size_t r = 0; r < innerSpace % N; ++r) wideColWidths[r]++; + + // Opening N-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 < N; ++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 < N; ++i) + { + renderCenteredText(os, row.systemPairs[i].second, wideColWidths[i]); + os << BOX.vertical; + } + os << "\n"; + + lastWideColWidths = wideColWidths; + lastRowWasSystem = true; } else { @@ -297,11 +362,15 @@ void TableRenderer::render(std::ostream& os) const os << val << " " << BOX.vertical; } os << "\n"; + lastRowWasSystem = false; } } // Bottom border - renderLine(os, BOX.bottom_left, BOX.tee_up, BOX.bottom_right, colWidths); + 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 diff --git a/src/pcm-io-metrics.h b/src/pcm-io-metrics.h index 89d3bcb6..7f84a0f8 100644 --- a/src/pcm-io-metrics.h +++ b/src/pcm-io-metrics.h @@ -56,14 +56,18 @@ class TableRenderer { 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; 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; diff --git a/src/pcm-io.cpp b/src/pcm-io.cpp index 83c0bc25..3b39073c 100644 --- a/src/pcm-io.cpp +++ b/src/pcm-io.cpp @@ -395,6 +395,7 @@ void MetricsDisplay::displayLayoutMode(std::ostream& os) const { std::vector headers; std::vector metricIdxs; + std::vector sysMetricIdxs; bool hasSocketMetrics = false; bool hasSystemMetrics = false; @@ -404,18 +405,23 @@ void MetricsDisplay::displayLayoutMode(std::ostream& os) const { if (metrics[i].name == metricName) { - headers.push_back(metricDisplayName(metrics[i])); - metricIdxs.push_back(i); 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()) continue; + if (headers.empty() && sysMetricIdxs.empty()) continue; std::vector fullHeaders; if (hasSocketMetrics) @@ -447,15 +453,14 @@ void MetricsDisplay::displayLayoutMode(std::ostream& os) const if (hasSystemMetrics) { auto systemValues = getSystemCounterValues(); - std::vector row; - if (hasSocketMetrics) row.push_back("*"); - for (size_t idx : metricIdxs) + std::vector> sysSection; + for (size_t idx : sysMetricIdxs) { - if (metrics[idx].aggregation != "system") continue; + std::string name = metricDisplayName(metrics[idx]); double val = evaluator.evaluate(metrics[idx].formula, systemValues); - row.push_back(formatValue(val)); + sysSection.emplace_back(name, formatValue(val)); } - table.addRow(row); + table.addSystemSection("System Wide", sysSection); } table.render(os); diff --git a/tests/utests/pcm-io-metrics-utest.cpp b/tests/utests/pcm-io-metrics-utest.cpp index c3bc2af1..1e5a46f0 100644 --- a/tests/utests/pcm-io-metrics-utest.cpp +++ b/tests/utests/pcm-io-metrics-utest.cpp @@ -282,12 +282,13 @@ TEST_F(TableRendererTest, RenderWithSectionHeader) // 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(6) + B_TD + hline(7) + B_TR + "\n" + - B_V + " Read " + B_V + " Write " + B_V + "\n" + - B_ML + hline(14) + B_MR + "\n" + + 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"; @@ -308,6 +309,23 @@ TEST_F(TableRendererTest, RenderEmptyTable) 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); +} + // --- Layout Tests --- static const char* kLayoutMetricsJSON = R"json({ From ddb78352d67d46fa6398da433ba43f6ca248d639 Mon Sep 17 00:00:00 2001 From: Alexander Antonov Date: Thu, 9 Apr 2026 02:00:07 -0700 Subject: [PATCH 19/77] Add handling for new scheme in metrics.json --- src/pcm-io-metrics.cpp | 41 +++++- src/pcm-io-metrics.h | 11 +- src/pcm-io.cpp | 136 +++++++++++++++-- tests/utests/pcm-io-metrics-utest.cpp | 205 ++++++++++++++++++++++++++ 4 files changed, 376 insertions(+), 17 deletions(-) diff --git a/src/pcm-io-metrics.cpp b/src/pcm-io-metrics.cpp index 7dfc3e82..f8ad2741 100644 --- a/src/pcm-io-metrics.cpp +++ b/src/pcm-io-metrics.cpp @@ -503,12 +503,45 @@ void MetricsConfig::parseLayout(simdjson::dom::element doc) section.title = std::string{title.get_c_str()}; } - auto metricsArr = sectionObj["metrics"]; - if (!metricsArr.error()) + auto rowsEl = sectionObj["rows"]; + if (!rowsEl.error()) { - for (auto metricName : metricsArr.get_array()) + // 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()) { - section.metrics.emplace_back(metricName.get_c_str()); + for (auto metricName : metricsArr.get_array()) + section.metrics.emplace_back(metricName.get_c_str()); } } m_layout.emplace_back(std::move(section)); diff --git a/src/pcm-io-metrics.h b/src/pcm-io-metrics.h index 7f84a0f8..580bedfe 100644 --- a/src/pcm-io-metrics.h +++ b/src/pcm-io-metrics.h @@ -29,7 +29,16 @@ struct IOMetric { struct LayoutSection { std::string title; - std::vector metrics; // references IOMetric::name + 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; diff --git a/src/pcm-io.cpp b/src/pcm-io.cpp index 3b39073c..94ac3528 100644 --- a/src/pcm-io.cpp +++ b/src/pcm-io.cpp @@ -32,8 +32,10 @@ class MetricsDisplay { 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 metricDisplayName(const IOMetric& metric); @@ -341,11 +343,20 @@ void MetricsDisplay::printHeader(std::ostream& os, bool csv) const 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()) return true; + return false; +} + void MetricsDisplay::display(std::ostream& os, bool csv, bool useLayout) const { if (csv) displayCsv(os); - else if (useLayout && m_config->getLayout().size() > 1) + else if (useLayout && hasLayoutSections()) displayLayoutMode(os); else displayFlatMode(os); @@ -388,11 +399,17 @@ void MetricsDisplay::displayCsv(std::ostream& os) const void MetricsDisplay::displayLayoutMode(std::ostream& os) const { - FormulaEvaluator evaluator; - const auto& metrics = m_config->getMetrics(); - 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; @@ -468,6 +485,85 @@ void MetricsDisplay::displayLayoutMode(std::ostream& os) const } } +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; @@ -580,22 +676,38 @@ static void print_available_metrics(const string& metricsPath, const string& pla const auto& layout = config.getLayout(); const auto& metrics = config.getMetrics(); - if (layout.size() > 1) + 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"; + 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"; - for (const auto& metricName : section.metrics) + + if (section.isMultiRow()) { - for (const auto& metric : metrics) + for (const auto& [colHeader, colMetricNames] : section.columns) { - if (metric.name == metricName) - { - cout << " " << metric.name << " = " << metric.formula << "\n"; - break; - } + 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"; } diff --git a/tests/utests/pcm-io-metrics-utest.cpp b/tests/utests/pcm-io-metrics-utest.cpp index 1e5a46f0..47bf62ef 100644 --- a/tests/utests/pcm-io-metrics-utest.cpp +++ b/tests/utests/pcm-io-metrics-utest.cpp @@ -598,3 +598,208 @@ TEST(LocalEventsTest, LocalEventsValidation) 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); +} From f1f850e5499398bd954e16efa1bd04d5ff600cd0 Mon Sep 17 00:00:00 2001 From: Alexander Antonov Date: Thu, 9 Apr 2026 02:10:36 -0700 Subject: [PATCH 20/77] Add draft for ICX metrics.json --- src/pmu-events/icelake-sp/metrics.json | 109 ++++++++++++++++++++----- 1 file changed, 88 insertions(+), 21 deletions(-) diff --git a/src/pmu-events/icelake-sp/metrics.json b/src/pmu-events/icelake-sp/metrics.json index be454025..e6457f20 100644 --- a/src/pmu-events/icelake-sp/metrics.json +++ b/src/pmu-events/icelake-sp/metrics.json @@ -1,20 +1,36 @@ { + "events": [ + { + "EventName": "UCRdF", + "Unit": "CHA", + "EventCode": "0x35", + "UMask": "0x01", + "UMaskExt": "0xC877DE" + }, + { + "EventName": "WiL", + "Unit": "CHA", + "EventCode": "0x35", + "UMask": "0x01", + "UMaskExt": "0xC87FDE" + } + ], "metrics": [ { - "name": "PCIe Rd (B)", - "formula": "UNC_CHA_TOR_INSERTS.IO_PCIRDCUR * 64", + "name": "PCIe Read (B)", + "formula": "(UNC_CHA_TOR_INSERTS.IO_MISS_PCIRDCUR + UNC_CHA_TOR_INSERTS.IO_HIT_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", + "name": "PCIe 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": "PCIeWr", "aggregation": "socket" }, { "name": "PCIRdCur", - "formula": "UNC_CHA_TOR_INSERTS.IO_PCIRDCUR", + "formula": "UNC_CHA_TOR_INSERTS.IO_MISS_PCIRDCUR + UNC_CHA_TOR_INSERTS.IO_HIT_PCIRDCUR", "short_name": "RdCur", "aggregation": "socket" }, @@ -32,7 +48,7 @@ }, { "name": "ItoM", - "formula": "UNC_CHA_TOR_INSERTS.IO_ITOM", + "formula": "UNC_CHA_TOR_INSERTS.IO_MISS_ITOM + UNC_CHA_TOR_INSERTS.IO_HIT_ITOM", "short_name": "ItoM", "aggregation": "socket" }, @@ -50,7 +66,7 @@ }, { "name": "ItoMCacheNear", - "formula": "UNC_CHA_TOR_INSERTS.IO_ITOMCACHENEAR", + "formula": "UNC_CHA_TOR_INSERTS.IO_MISS_ITOMCACHENEAR + UNC_CHA_TOR_INSERTS.IO_HIT_ITOMCACHENEAR", "short_name": "ITMCN", "aggregation": "socket" }, @@ -67,14 +83,50 @@ "aggregation": "socket" }, { - "name": "Total Rd (B)", - "formula": "UNC_CHA_TOR_INSERTS.IO_PCIRDCUR * 64", + "name": "UCRdF", + "formula": "UCRdF", + "short_name": "UCRdF", + "aggregation": "socket" + }, + { + "name": "WiL", + "formula": "WiL", + "short_name": "WiL", + "aggregation": "socket" + }, + { + "name": "PCIe Rd Miss (B)", + "formula": "UNC_CHA_TOR_INSERTS.IO_MISS_PCIRDCUR * 64", + "short_name": "RdMissB", + "aggregation": "socket" + }, + { + "name": "PCIe Rd Hit (B)", + "formula": "UNC_CHA_TOR_INSERTS.IO_HIT_PCIRDCUR * 64", + "short_name": "RdHitB", + "aggregation": "socket" + }, + { + "name": "PCIe Wr Miss (B)", + "formula": "(UNC_CHA_TOR_INSERTS.IO_MISS_ITOM + UNC_CHA_TOR_INSERTS.IO_MISS_ITOMCACHENEAR) * 64", + "short_name": "WrMissB", + "aggregation": "socket" + }, + { + "name": "PCIe Wr Hit (B)", + "formula": "(UNC_CHA_TOR_INSERTS.IO_HIT_ITOM + UNC_CHA_TOR_INSERTS.IO_HIT_ITOMCACHENEAR) * 64", + "short_name": "WrHitB", + "aggregation": "socket" + }, + { + "name": "Total Read (B)", + "formula": "(UNC_CHA_TOR_INSERTS.IO_MISS_PCIRDCUR + UNC_CHA_TOR_INSERTS.IO_HIT_PCIRDCUR) * 64", "short_name": "TotRd", "aggregation": "system" }, { - "name": "Total Wr (B)", - "formula": "(UNC_CHA_TOR_INSERTS.IO_ITOM + UNC_CHA_TOR_INSERTS.IO_ITOMCACHENEAR) * 64", + "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": "TotWr", "aggregation": "system" } @@ -82,20 +134,35 @@ "layout": { "sections": [ { - "title": "PCIe Bandwidth", - "metrics": ["PCIe Rd (B)", "PCIe Wr (B)"] - }, - { - "title": "Read Events", - "metrics": ["PCIRdCur", "PCIRdCur Miss", "PCIRdCur Hit"] + "title": "PCIe Data", + "rows": ["Total", "Miss", "Hit"], + "columns": { + "PCIRdCur Events": ["PCIRdCur", "PCIRdCur Miss", "PCIRdCur Hit"], + "ItoM Events": ["ItoM", "ItoM Miss", "ItoM Hit"], + "ItoMCacheNear Events": ["ItoMCacheNear", "ItoMCacheNear Miss", "ItoMCacheNear Hit"], + "WiL Events": ["WiL"] + }, + "system-wide-metrics": ["Total Read (B)", "Total Write (B)"] }, { - "title": "Write Events", - "metrics": ["ItoM", "ItoM Miss", "ItoM Hit", "ItoMCacheNear", "ItoMCacheNear Miss", "ItoMCacheNear Hit"] + "title": "PCIe Data", + "metrics": [ + "PCIRdCur", + "PCIRdCur Miss", + "PCIRdCur Hit", + "ItoM", + "ItoM Miss", + "ItoM Hit", + "ItoMCacheNear", + "ItoMCacheNear Miss", + "ItoMCacheNear Hit", + "Total Read (B)", + "Total Write (B)" + ] }, { - "title": "System Total", - "metrics": ["Total Rd (B)", "Total Wr (B)"] + "title": "MMIO Access", + "metrics": ["UCRdF", "WiL"] } ] } From e4756d7b26be9b21a84ee5ad58305aca0096f1f4 Mon Sep 17 00:00:00 2001 From: "Dementiev, Roman" Date: Fri, 10 Apr 2026 15:47:18 +0200 Subject: [PATCH 21/77] address top type conversion warnings Change-Id: I01ca652ab0f759e3caa01c4ece74f6a362d8c97a --- src/utils.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils.h b/src/utils.h index 7b988729..1707ba8e 100644 --- a/src/utils.h +++ b/src/utils.h @@ -702,7 +702,7 @@ inline bool readOldValueHelper(const std::pair & bits, T & value, c { return false; } - value = insertBits(old_value, value, bits.first, bits.second - bits.first + 1); + value = insertBits(old_value, value, bits.first, static_cast(bits.second - bits.first + 1)); } return true; } @@ -715,7 +715,7 @@ inline void extractBitsPrintHelper(const std::pair & bits, T & valu { std::cout << "bits "<< std::dec << bits.first << ":" << bits.second << " "; if (!dec) std::cout << std::hex << std::showbase; - value = extract_bits(value, bits.first, bits.second); + value = extract_bits(value, static_cast(bits.first), static_cast(bits.second)); } std::cout << "value " << value; } From 54118099032c1a2e36fa8c3957b2ee026fa22f92 Mon Sep 17 00:00:00 2001 From: Alexander Antonov Date: Fri, 17 Apr 2026 03:23:54 -0700 Subject: [PATCH 22/77] Split loadPerfmonEvents() on several functions --- src/event-resolver.cpp | 91 +++++++++++++++++++++++------------------- src/event-resolver.h | 3 ++ 2 files changed, 54 insertions(+), 40 deletions(-) diff --git a/src/event-resolver.cpp b/src/event-resolver.cpp index cd640156..4515b21e 100644 --- a/src/event-resolver.cpp +++ b/src/event-resolver.cpp @@ -119,7 +119,8 @@ bool PerfmonEventResolver::parseTSV(const std::string& path) return true; } -bool PerfmonEventResolver::loadPerfmonEvents(const std::string& cpuFamilyModel, const std::string& prefix) +bool PerfmonEventResolver::parseMapfile(const std::string& cpuFamilyModel, const std::string& prefix, + std::multimap& eventFiles) { const std::string mapfilePath = prefix + "/mapfile.csv"; @@ -157,7 +158,6 @@ bool PerfmonEventResolver::loadPerfmonEvents(const std::string& cpuFamilyModel, return false; } - std::multimap eventFiles; std::cerr << "Matched event files:\n"; while (std::getline(in, line)) { @@ -174,62 +174,73 @@ bool PerfmonEventResolver::loadPerfmonEvents(const std::string& cpuFamilyModel, eventFiles.insert(std::make_pair(tokens[eventTypePos], tokens[filenamePos])); } } - in.close(); if (eventFiles.empty()) { std::cerr << "ERROR: CPU " << cpuFamilyModel << " not found in mapfile.csv\n"; return false; } + return true; +} - for (const auto& evfile : eventFiles) - { - if (evfile.first != "core" && evfile.first != "uncore" && - evfile.first != "uncore experimental") - continue; +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 + evfile.second; - const std::string path2 = prefix + evfile.second.substr(evfile.second.rfind('/')); + const std::string path1 = prefix + filename; + const std::string path2 = prefix + filename.substr(filename.rfind('/')); - std::string path; - if (std::ifstream(path1).good()) - path = path1; - else if (std::ifstream(path2).good()) - path = path2; - else - { - std::cerr << "ERROR: Can't open event file at " << path1 << " or " << path2 << "\n"; - std::cerr << "Make sure you have downloaded " << evfile.second - << " from https://raw.githubusercontent.com/intel/perfmon/main" - << evfile.second << "\n"; - return false; - } + std::string path; + if (std::ifstream(path1).good()) + path = path1; + else if (std::ifstream(path2).good()) + path = path2; + else + { + std::cerr << "ERROR: Can't open event file at " << path1 << " or " << path2 << "\n"; + std::cerr << "Make sure you have downloaded " << filename + << " from https://raw.githubusercontent.com/intel/perfmon/main" + << filename << "\n"; + return false; + } - try + try + { + if (path.find(".json") != std::string::npos) { - 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"]; + 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) + for (simdjson::dom::object eventObj : jsonObjects) { - if (!parseTSV(path)) return false; + const std::string eventName{eventObj["EventName"].get_c_str()}; + if (!eventName.empty()) m_eventMapJSON[eventName] = eventObj; } } - catch (std::exception& e) + else if (path.find(".tsv") != std::string::npos) { - std::cerr << "Error while parsing " << path << ": " << e.what() << "\n"; - return false; + 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(); } diff --git a/src/event-resolver.h b/src/event-resolver.h index 6cbca8e8..cea0c996 100644 --- a/src/event-resolver.h +++ b/src/event-resolver.h @@ -59,6 +59,9 @@ class PerfmonEventResolver { #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); From 7666b620fc5e5b0ce8a07d1d774c248686f31c3d Mon Sep 17 00:00:00 2001 From: Alexander Antonov Date: Fri, 17 Apr 2026 04:35:52 -0700 Subject: [PATCH 23/77] Add schedulling mechanism --- src/pcm-io-metrics.cpp | 50 +++++++ src/pcm-io-metrics.h | 18 +++ src/pcm-io.cpp | 97 ++++++++++--- tests/utests/pcm-io-metrics-utest.cpp | 187 ++++++++++++++++++++++++++ 4 files changed, 335 insertions(+), 17 deletions(-) diff --git a/src/pcm-io-metrics.cpp b/src/pcm-io-metrics.cpp index f8ad2741..d10f72fd 100644 --- a/src/pcm-io-metrics.cpp +++ b/src/pcm-io-metrics.cpp @@ -639,4 +639,54 @@ void MetricsConfig::printValidatedMetrics(std::ostream& os, const EventValidator os << "\n" << validCount << " of " << result.metrics.size() << " metrics valid\n"; } +// --- 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 index 580bedfe..48d2b60e 100644 --- a/src/pcm-io-metrics.h +++ b/src/pcm-io-metrics.h @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -85,6 +86,23 @@ class TableRenderer { 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); diff --git a/src/pcm-io.cpp b/src/pcm-io.cpp index 94ac3528..6f3aabb3 100644 --- a/src/pcm-io.cpp +++ b/src/pcm-io.cpp @@ -100,6 +100,39 @@ std::string MetricsDrivenPlatform::cpuModelToDir(int cpuModel) } } +// 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, etc.) bypass slot constraints entirely. +// bool MetricsDrivenPlatform::init(PCM* pcm, const std::string& metricsPath, const std::string& eventPrefix) { m_pcm = pcm; @@ -121,13 +154,15 @@ bool MetricsDrivenPlatform::init(PCM* pcm, const std::string& metricsPath, const // 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 of - // up to ServerUncoreCounterState::maxCounters events per PMU type. + // 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; // already resolved + continue; std::string pmuName; PCM::RawEventConfig config; @@ -137,24 +172,52 @@ bool MetricsDrivenPlatform::init(PCM* pcm, const std::string& metricsPath, const continue; } - // Find first group that still has room for this PMU type - size_t groupIdx = m_pmuConfigGroups.size(); // default: start a new group - for (size_t g = 0; g < m_pmuConfigGroups.size(); ++g) + if (isRegisterEvent(pmuName)) { - const auto& existing = m_pmuConfigGroups[g][pmuName].programmable; - if (isRegisterEvent(pmuName) || existing.size() < ServerUncoreCounterState::maxCounters) - { - groupIdx = g; - break; - } + if (m_pmuConfigGroups.empty()) + m_pmuConfigGroups.emplace_back(); + auto& grp = m_pmuConfigGroups[0]; + size_t idx = grp[pmuName].programmable.size(); + grp[pmuName].programmable.push_back(config); + m_eventLocations[eventName] = {0, pmuName, idx}; + continue; } - if (groupIdx == m_pmuConfigGroups.size()) + + 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& grp = m_pmuConfigGroups[groupIdx]; - size_t idx = grp[pmuName].programmable.size(); - grp[pmuName].programmable.push_back(config); - m_eventLocations[eventName] = {groupIdx, pmuName, idx}; + 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()) diff --git a/tests/utests/pcm-io-metrics-utest.cpp b/tests/utests/pcm-io-metrics-utest.cpp index 47bf62ef..425f8408 100644 --- a/tests/utests/pcm-io-metrics-utest.cpp +++ b/tests/utests/pcm-io-metrics-utest.cpp @@ -7,6 +7,193 @@ 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; From 8344ca9e1a11865fe599df86603c6627184fa6a1 Mon Sep 17 00:00:00 2001 From: Alexander Antonov Date: Fri, 17 Apr 2026 06:23:12 -0700 Subject: [PATCH 24/77] Update metrics.json for ICX --- src/pmu-events/icelake-sp/metrics.json | 151 +++++++++---------------- 1 file changed, 52 insertions(+), 99 deletions(-) diff --git a/src/pmu-events/icelake-sp/metrics.json b/src/pmu-events/icelake-sp/metrics.json index e6457f20..fb83d21f 100644 --- a/src/pmu-events/icelake-sp/metrics.json +++ b/src/pmu-events/icelake-sp/metrics.json @@ -1,134 +1,104 @@ { "events": [ { - "EventName": "UCRdF", + "EventName": "EVENT_EXAMPLE", "Unit": "CHA", "EventCode": "0x35", "UMask": "0x01", - "UMaskExt": "0xC877DE" - }, - { - "EventName": "WiL", - "Unit": "CHA", - "EventCode": "0x35", - "UMask": "0x01", - "UMaskExt": "0xC87FDE" + "UMaskExt": "0xC87FDE", + "Counter": "0,1,2,3" } ], "metrics": [ { - "name": "PCIe Read (B)", + "name": "PCIRdCur (B)", "formula": "(UNC_CHA_TOR_INSERTS.IO_MISS_PCIRDCUR + UNC_CHA_TOR_INSERTS.IO_HIT_PCIRDCUR) * 64", - "short_name": "PCIeRd", + "short_name": "PCIRdCur", "aggregation": "socket" }, { - "name": "PCIe 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": "PCIeWr", - "aggregation": "socket" - }, - { - "name": "PCIRdCur", - "formula": "UNC_CHA_TOR_INSERTS.IO_MISS_PCIRDCUR + UNC_CHA_TOR_INSERTS.IO_HIT_PCIRDCUR", - "short_name": "RdCur", - "aggregation": "socket" - }, - { - "name": "PCIRdCur Miss", - "formula": "UNC_CHA_TOR_INSERTS.IO_MISS_PCIRDCUR", - "short_name": "RdMiss", + "name": "PCIRdCur Miss (B)", + "formula": "UNC_CHA_TOR_INSERTS.IO_MISS_PCIRDCUR * 64", + "short_name": "PCIRdCur Miss", "aggregation": "socket" }, { - "name": "PCIRdCur Hit", - "formula": "UNC_CHA_TOR_INSERTS.IO_HIT_PCIRDCUR", - "short_name": "RdHit", + "name": "PCIRdCur Hit (B)", + "formula": "UNC_CHA_TOR_INSERTS.IO_HIT_PCIRDCUR * 64", + "short_name": "PCIRdCur Hit", "aggregation": "socket" }, { - "name": "ItoM", - "formula": "UNC_CHA_TOR_INSERTS.IO_MISS_ITOM + UNC_CHA_TOR_INSERTS.IO_HIT_ITOM", + "name": "ItoM (B)", + "formula": "(UNC_CHA_TOR_INSERTS.IO_MISS_ITOM + UNC_CHA_TOR_INSERTS.IO_HIT_ITOM) * 64", "short_name": "ItoM", "aggregation": "socket" }, { - "name": "ItoM Miss", - "formula": "UNC_CHA_TOR_INSERTS.IO_MISS_ITOM", - "short_name": "ItoMMiss", + "name": "ItoM Miss (B)", + "formula": "UNC_CHA_TOR_INSERTS.IO_MISS_ITOM * 64", + "short_name": "ItoM Miss", "aggregation": "socket" }, { - "name": "ItoM Hit", - "formula": "UNC_CHA_TOR_INSERTS.IO_HIT_ITOM", - "short_name": "ItoMHit", + "name": "ItoM Hit (B)", + "formula": "UNC_CHA_TOR_INSERTS.IO_HIT_ITOM * 64", + "short_name": "ItoM Hit", "aggregation": "socket" }, { - "name": "ItoMCacheNear", - "formula": "UNC_CHA_TOR_INSERTS.IO_MISS_ITOMCACHENEAR + UNC_CHA_TOR_INSERTS.IO_HIT_ITOMCACHENEAR", - "short_name": "ITMCN", + "name": "ItoMCacheNear (B)", + "formula": "(UNC_CHA_TOR_INSERTS.IO_MISS_ITOMCACHENEAR + UNC_CHA_TOR_INSERTS.IO_HIT_ITOMCACHENEAR) * 64", + "short_name": "ItoMCacheNear", "aggregation": "socket" }, { - "name": "ItoMCacheNear Miss", - "formula": "UNC_CHA_TOR_INSERTS.IO_MISS_ITOMCACHENEAR", - "short_name": "ITMCNMiss", + "name": "ItoMCacheNear Miss (B)", + "formula": "UNC_CHA_TOR_INSERTS.IO_MISS_ITOMCACHENEAR * 64", + "short_name": "ItoMCacheNear Miss", "aggregation": "socket" }, { - "name": "ItoMCacheNear Hit", - "formula": "UNC_CHA_TOR_INSERTS.IO_HIT_ITOMCACHENEAR", - "short_name": "ITMCNHit", + "name": "ItoMCacheNear Hit (B)", + "formula": "UNC_CHA_TOR_INSERTS.IO_HIT_ITOMCACHENEAR * 64", + "short_name": "ItoMCacheNear Hit", "aggregation": "socket" }, { - "name": "UCRdF", - "formula": "UCRdF", - "short_name": "UCRdF", - "aggregation": "socket" + "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" }, { - "name": "WiL", - "formula": "WiL", - "short_name": "WiL", - "aggregation": "socket" + "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" }, { - "name": "PCIe Rd Miss (B)", - "formula": "UNC_CHA_TOR_INSERTS.IO_MISS_PCIRDCUR * 64", - "short_name": "RdMissB", + "name": "WCILF", + "formula": "UNC_CHA_TOR_INSERTS.IA_WCILF", + "short_name": "WCILF", "aggregation": "socket" }, { - "name": "PCIe Rd Hit (B)", - "formula": "UNC_CHA_TOR_INSERTS.IO_HIT_PCIRDCUR * 64", - "short_name": "RdHitB", + "name": "WCIL", + "formula": "UNC_CHA_TOR_INSERTS.IA_WCIL", + "short_name": "WCIL", "aggregation": "socket" }, { - "name": "PCIe Wr Miss (B)", - "formula": "(UNC_CHA_TOR_INSERTS.IO_MISS_ITOM + UNC_CHA_TOR_INSERTS.IO_MISS_ITOMCACHENEAR) * 64", - "short_name": "WrMissB", + "name": "WiL Miss", + "formula": "UNC_CHA_TOR_INSERTS.IA_MISS_WIL", + "short_name": "WiL Miss", "aggregation": "socket" }, { - "name": "PCIe Wr Hit (B)", - "formula": "(UNC_CHA_TOR_INSERTS.IO_HIT_ITOM + UNC_CHA_TOR_INSERTS.IO_HIT_ITOMCACHENEAR) * 64", - "short_name": "WrHitB", + "name": "UCRdF Miss", + "formula": "UNC_CHA_TOR_INSERTS.IA_MISS_UCRDF", + "short_name": "UCRdF Miss", "aggregation": "socket" - }, - { - "name": "Total Read (B)", - "formula": "(UNC_CHA_TOR_INSERTS.IO_MISS_PCIRDCUR + UNC_CHA_TOR_INSERTS.IO_HIT_PCIRDCUR) * 64", - "short_name": "TotRd", - "aggregation": "system" - }, - { - "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": "TotWr", - "aggregation": "system" } ], "layout": { @@ -137,32 +107,15 @@ "title": "PCIe Data", "rows": ["Total", "Miss", "Hit"], "columns": { - "PCIRdCur Events": ["PCIRdCur", "PCIRdCur Miss", "PCIRdCur Hit"], - "ItoM Events": ["ItoM", "ItoM Miss", "ItoM Hit"], - "ItoMCacheNear Events": ["ItoMCacheNear", "ItoMCacheNear Miss", "ItoMCacheNear Hit"], - "WiL Events": ["WiL"] + "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": "PCIe Data", - "metrics": [ - "PCIRdCur", - "PCIRdCur Miss", - "PCIRdCur Hit", - "ItoM", - "ItoM Miss", - "ItoM Hit", - "ItoMCacheNear", - "ItoMCacheNear Miss", - "ItoMCacheNear Hit", - "Total Read (B)", - "Total Write (B)" - ] - }, { "title": "MMIO Access", - "metrics": ["UCRdF", "WiL"] + "metrics": ["WCILF", "WCIL", "WiL Miss", "UCRdF Miss"] } ] } From e6a67c0182854072451dca7bc83edea4e766f216 Mon Sep 17 00:00:00 2001 From: Alexander Antonov Date: Sun, 19 Apr 2026 09:43:58 -0700 Subject: [PATCH 25/77] Add multiplexing coefficient --- src/pcm-io.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/pcm-io.cpp b/src/pcm-io.cpp index 6f3aabb3..cce0d715 100644 --- a/src/pcm-io.cpp +++ b/src/pcm-io.cpp @@ -47,11 +47,13 @@ class MetricsDrivenPlatform { 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; @@ -230,17 +232,17 @@ bool MetricsDrivenPlatform::init(PCM* pcm, const std::string& metricsPath, const m_numSockets = pcm->getNumSockets(); m_counterValues.resize(m_numSockets); - const size_t numGroups = m_pmuConfigGroups.size(); - m_groupBeforeStates.resize(numGroups); - m_groupAfterStates.resize(numGroups); - for (size_t g = 0; g < numGroups; ++g) + 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 (numGroups > 1) - cerr << "INFO: Events split into " << numGroups << " measurement groups\n"; + if (m_numGroups > 1) + cerr << "INFO: Events split into " << m_numGroups << " measurement groups\n"; m_display.init(&m_config, m_numSockets); initPMUCounterDescs(); @@ -348,7 +350,7 @@ void MetricsDrivenPlatform::readCounterValues() m_groupBeforeStates[loc.groupIndex][s], m_groupAfterStates[loc.groupIndex][s])); } - m_counterValues[s][eventName] = sum; + m_counterValues[s][eventName] = sum * m_numGroups; } } } @@ -1008,7 +1010,7 @@ int mainThrows(int argc, char* argv[]) const auto& config = platform.getConfig(); cerr << "Monitoring " << config.getMetrics().size() << " metrics, " << config.extractEventNames().size() << " events\n\n"; - int delayMs = static_cast(delay * 1000); + int delayMs = static_cast(delay * 1000) / platform.getNumGroups(); if (sysCmd) MySystem(sysCmd, sysArgv); From 0da352f49ca147340ccb4eec010361b183162020 Mon Sep 17 00:00:00 2001 From: Alexander Antonov Date: Sun, 19 Apr 2026 10:51:25 -0700 Subject: [PATCH 26/77] Add description for metrics --- src/pcm-io-metrics.cpp | 4 +++ src/pcm-io-metrics.h | 1 + src/pcm-io.cpp | 6 ++++ src/pmu-events/icelake-sp/metrics.json | 45 +++++++++++++++++--------- tests/utests/pcm-io-metrics-utest.cpp | 24 ++++++++++++++ 5 files changed, 65 insertions(+), 15 deletions(-) diff --git a/src/pcm-io-metrics.cpp b/src/pcm-io-metrics.cpp index d10f72fd..3953eed1 100644 --- a/src/pcm-io-metrics.cpp +++ b/src/pcm-io-metrics.cpp @@ -468,6 +468,10 @@ bool MetricsConfig::parseMetrics(simdjson::dom::element doc) 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); diff --git a/src/pcm-io-metrics.h b/src/pcm-io-metrics.h index 48d2b60e..628ebf22 100644 --- a/src/pcm-io-metrics.h +++ b/src/pcm-io-metrics.h @@ -26,6 +26,7 @@ struct IOMetric { std::string formula; std::string short_name; std::string aggregation; // "socket", "system", "stack" + std::string description; }; struct LayoutSection { diff --git a/src/pcm-io.cpp b/src/pcm-io.cpp index cce0d715..2f5824c2 100644 --- a/src/pcm-io.cpp +++ b/src/pcm-io.cpp @@ -746,6 +746,8 @@ static void print_available_metrics(const string& metricsPath, const string& pla if (metric.name == name) { cout << indent << metric.name << " = " << metric.formula << "\n"; + if (!metric.description.empty()) + cout << std::string(indent.size(), ' ') << " " << metric.description << "\n"; break; } }; @@ -780,7 +782,11 @@ static void print_available_metrics(const string& metricsPath, const string& pla else { for (const auto& metric : metrics) + { cout << " " << metric.name << " = " << metric.formula << "\n"; + if (!metric.description.empty()) + cout << " " << metric.description << "\n"; + } cout << "\n"; } } diff --git a/src/pmu-events/icelake-sp/metrics.json b/src/pmu-events/icelake-sp/metrics.json index fb83d21f..2c11004b 100644 --- a/src/pmu-events/icelake-sp/metrics.json +++ b/src/pmu-events/icelake-sp/metrics.json @@ -14,91 +14,106 @@ "name": "PCIRdCur (B)", "formula": "(UNC_CHA_TOR_INSERTS.IO_MISS_PCIRDCUR + UNC_CHA_TOR_INSERTS.IO_HIT_PCIRDCUR) * 64", "short_name": "PCIRdCur", - "aggregation": "socket" + "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" + "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" + "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" + "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" + "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" + "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" + "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" + "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" + "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" + "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" + "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" + "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" + "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" + "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" + "aggregation": "socket", + "description": "Number of UCRdF (Uncached Read Full) requests issued by CPU that missed cache." } ], "layout": { diff --git a/tests/utests/pcm-io-metrics-utest.cpp b/tests/utests/pcm-io-metrics-utest.cpp index 425f8408..83d9dd03 100644 --- a/tests/utests/pcm-io-metrics-utest.cpp +++ b/tests/utests/pcm-io-metrics-utest.cpp @@ -334,6 +334,30 @@ TEST(MetricsConfigTest, DefaultAggregation) 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; From ef7866f71993e00e2baaf39e961a15d63b281403 Mon Sep 17 00:00:00 2001 From: Alexander Antonov Date: Sun, 19 Apr 2026 11:20:37 -0700 Subject: [PATCH 27/77] Add help for metrics.json --- src/pcm-io.cpp | 52 ++++++++++++++++++++++++++ src/pmu-events/icelake-sp/metrics.json | 10 ----- 2 files changed, 52 insertions(+), 10 deletions(-) diff --git a/src/pcm-io.cpp b/src/pcm-io.cpp index 2f5824c2..8913834b 100644 --- a/src/pcm-io.cpp +++ b/src/pcm-io.cpp @@ -702,6 +702,7 @@ static void print_usage(const string& progname) 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"; @@ -710,6 +711,52 @@ static void print_usage(const string& progname) 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/pmu-events/icelake-sp/metrics.json for a complete example.\n\n"; +} + static std::string findMetricsPath(const std::string& programPath, const std::string& platformDir) { if (platformDir.empty()) return ""; @@ -912,6 +959,11 @@ int mainThrows(int argc, char* argv[]) validateOnly = true; continue; } + else if (check_argument_equals(*argv, {"--show-format"})) + { + print_metrics_format(); + exit(EXIT_SUCCESS); + } else if (check_argument_equals(*argv, {"--ep"})) { argv++; diff --git a/src/pmu-events/icelake-sp/metrics.json b/src/pmu-events/icelake-sp/metrics.json index 2c11004b..72c395b7 100644 --- a/src/pmu-events/icelake-sp/metrics.json +++ b/src/pmu-events/icelake-sp/metrics.json @@ -1,14 +1,4 @@ { - "events": [ - { - "EventName": "EVENT_EXAMPLE", - "Unit": "CHA", - "EventCode": "0x35", - "UMask": "0x01", - "UMaskExt": "0xC87FDE", - "Counter": "0,1,2,3" - } - ], "metrics": [ { "name": "PCIRdCur (B)", From 5352fcb445ec11a868de76cf28c972b1c19c7b98 Mon Sep 17 00:00:00 2001 From: Alexander Antonov Date: Sun, 19 Apr 2026 11:53:48 -0700 Subject: [PATCH 28/77] Return exit_failure if validate returned false --- src/pcm-io-metrics.cpp | 3 ++- src/pcm-io-metrics.h | 2 +- src/pcm-io.cpp | 3 +-- tests/utests/pcm-io-metrics-utest.cpp | 7 ++++++- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/pcm-io-metrics.cpp b/src/pcm-io-metrics.cpp index 3953eed1..b28c2728 100644 --- a/src/pcm-io-metrics.cpp +++ b/src/pcm-io-metrics.cpp @@ -619,7 +619,7 @@ ValidationResult MetricsConfig::validateEvents(const EventValidator& validator) return result; } -void MetricsConfig::printValidatedMetrics(std::ostream& os, const EventValidator& validator) const +bool MetricsConfig::printValidatedMetrics(std::ostream& os, const EventValidator& validator) const { auto result = validateEvents(validator); size_t validCount = 0; @@ -641,6 +641,7 @@ void MetricsConfig::printValidatedMetrics(std::ostream& os, const EventValidator } } os << "\n" << validCount << " of " << result.metrics.size() << " metrics valid\n"; + return result.allValid(); } // --- CounterConstraintGrouper --- diff --git a/src/pcm-io-metrics.h b/src/pcm-io-metrics.h index 628ebf22..24a3b983 100644 --- a/src/pcm-io-metrics.h +++ b/src/pcm-io-metrics.h @@ -114,7 +114,7 @@ class MetricsConfig { const std::vector>& getLocalEvents() const { return m_localEvents; } std::set extractEventNames() const; ValidationResult validateEvents(const EventValidator& validator) const; - void printValidatedMetrics(std::ostream& os, const EventValidator& validator) const; + bool printValidatedMetrics(std::ostream& os, const EventValidator& validator) const; private: std::vector m_metrics; diff --git a/src/pcm-io.cpp b/src/pcm-io.cpp index 8913834b..e796f17f 100644 --- a/src/pcm-io.cpp +++ b/src/pcm-io.cpp @@ -877,8 +877,7 @@ static bool printValidation(std::ostream& os, const std::string& metricsPath, return false; } resolver.addLocalEvents(config.getLocalEvents()); - config.printValidatedMetrics(os, [&resolver](const std::string& event) { return resolver.isEvent(event); }); - return true; + return config.printValidatedMetrics(os, [&resolver](const std::string& event) { return resolver.isEvent(event); }); } PCM_MAIN_NOTHROW; diff --git a/tests/utests/pcm-io-metrics-utest.cpp b/tests/utests/pcm-io-metrics-utest.cpp index 83d9dd03..3a1badb3 100644 --- a/tests/utests/pcm-io-metrics-utest.cpp +++ b/tests/utests/pcm-io-metrics-utest.cpp @@ -721,10 +721,11 @@ TEST(ValidationTest, PrintValidatedMetrics) ASSERT_TRUE(config.loadFromString(kTestMetricsJSON)); std::ostringstream os; - config.printValidatedMetrics(os, [](const std::string& event) { + 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); @@ -732,6 +733,10 @@ TEST(ValidationTest, PrintValidatedMetrics) 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 --- From 6e7b133db590d1b371925fd36423de9eebe5b309 Mon Sep 17 00:00:00 2001 From: Alexander Antonov Date: Mon, 20 Apr 2026 04:22:37 -0700 Subject: [PATCH 29/77] Fix display for csv --- src/pcm-io.cpp | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/src/pcm-io.cpp b/src/pcm-io.cpp index e796f17f..a09c2a60 100644 --- a/src/pcm-io.cpp +++ b/src/pcm-io.cpp @@ -405,6 +405,11 @@ void MetricsDisplay::printHeader(std::ostream& os, bool csv) const if (metric.aggregation == "system") continue; os << "," << metricDisplayName(metric); } + for (const auto& metric : metrics) + { + if (metric.aggregation != "system") continue; + os << "," << metricDisplayName(metric); + } os << "\n"; } @@ -432,6 +437,14 @@ void MetricsDisplay::displayCsv(std::ostream& os) const FormulaEvaluator evaluator; const auto& metrics = m_config->getMetrics(); + size_t numSocketMetrics = 0; + size_t numSystemMetrics = 0; + for (const auto& m : metrics) + { + if (m.aggregation == "system") ++numSystemMetrics; + else ++numSocketMetrics; + } + for (uint32 s = 0; s < m_numSockets; ++s) { os << s; @@ -441,25 +454,24 @@ void MetricsDisplay::displayCsv(std::ostream& os) const double val = evaluator.evaluate(m.formula, (*m_counterValues)[s]); os << "," << static_cast(val); } + for (size_t i = 0; i < numSystemMetrics; ++i) + os << ","; os << "\n"; } + if (numSystemMetrics == 0) return; + auto systemValues = getSystemCounterValues(); - bool hasSystem = false; + os << "*"; + for (size_t i = 0; i < numSocketMetrics; ++i) + os << ","; for (const auto& m : metrics) { - if (m.aggregation == "system") - { - if (!hasSystem) - { - os << "*"; - hasSystem = true; - } - double val = evaluator.evaluate(m.formula, systemValues); - os << "," << static_cast(val); - } + if (m.aggregation != "system") continue; + double val = evaluator.evaluate(m.formula, systemValues); + os << "," << static_cast(val); } - if (hasSystem) os << "\n"; + os << "\n"; } void MetricsDisplay::displayLayoutMode(std::ostream& os) const From 4703a7a6544ef94c5e026fdeb3c5cd2a377c0a20 Mon Sep 17 00:00:00 2001 From: Alexander Antonov Date: Mon, 20 Apr 2026 04:55:21 -0700 Subject: [PATCH 30/77] Fix --no-layout option --- src/pcm-io.cpp | 59 ++++++++++++++++++++++++++++---------------------- 1 file changed, 33 insertions(+), 26 deletions(-) diff --git a/src/pcm-io.cpp b/src/pcm-io.cpp index a09c2a60..cd23587a 100644 --- a/src/pcm-io.cpp +++ b/src/pcm-io.cpp @@ -646,49 +646,56 @@ void MetricsDisplay::displayFlatMode(std::ostream& os) const FormulaEvaluator evaluator; const auto& metrics = m_config->getMetrics(); - std::vector fullHeaders = {"Skt"}; std::vector socketMetricIndices; + std::vector systemMetricIndices; for (size_t i = 0; i < metrics.size(); ++i) { - if (metrics[i].aggregation != "system") - { - fullHeaders.push_back(metricDisplayName(metrics[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(); + + 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); - for (uint32 s = 0; s < m_numSockets; ++s) + if (hasSocketMetrics) { - std::vector row; - row.push_back(std::to_string(s)); - for (size_t idx : socketMetricIndices) + for (uint32 s = 0; s < m_numSockets; ++s) { - double val = evaluator.evaluate(metrics[idx].formula, (*m_counterValues)[s]); - row.push_back(formatValue(val)); + 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); } - table.addRow(row); } - auto systemValues = getSystemCounterValues(); - bool hasSystem = false; - std::vector sysRow; - sysRow.push_back("*"); - for (size_t i = 0; i < metrics.size(); ++i) + if (hasSystemMetrics) { - if (metrics[i].aggregation == "system") + auto systemValues = getSystemCounterValues(); + std::vector> sysSection; + for (size_t idx : systemMetricIndices) { - double val = evaluator.evaluate(metrics[i].formula, systemValues); - sysRow.push_back(formatValue(val)); - hasSystem = true; + std::string name = metricDisplayName(metrics[idx]); + double val = evaluator.evaluate(metrics[idx].formula, systemValues); + sysSection.emplace_back(name, formatValue(val)); } - } - if (hasSystem) - { - table.addSectionHeader("System Total"); - table.addRow(sysRow); + table.addSystemSection("System Wide", sysSection); } table.render(os); From de57a041c979e7e2bdcc36d7e38d69d953bb1c15 Mon Sep 17 00:00:00 2001 From: Alexander Antonov Date: Mon, 20 Apr 2026 05:39:34 -0700 Subject: [PATCH 31/77] Return fallback with getInstallPathPrefix() back --- src/event-resolver.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/event-resolver.cpp b/src/event-resolver.cpp index 4515b21e..2045b1f0 100644 --- a/src/event-resolver.cpp +++ b/src/event-resolver.cpp @@ -190,15 +190,18 @@ bool PerfmonEventResolver::loadEventFile(const std::string& eventType, const std 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 << "\n"; + 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"; From f7f7037a3683ba4dab8f86b2eada7bea26c5a9a7 Mon Sep 17 00:00:00 2001 From: Alexander Antonov Date: Mon, 20 Apr 2026 05:43:19 -0700 Subject: [PATCH 32/77] Fix messaging for -ep option --- src/event-resolver.cpp | 2 +- src/pcm-io.cpp | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/event-resolver.cpp b/src/event-resolver.cpp index 2045b1f0..d8c267c0 100644 --- a/src/event-resolver.cpp +++ b/src/event-resolver.cpp @@ -128,7 +128,7 @@ bool PerfmonEventResolver::parseMapfile(const std::string& cpuFamilyModel, const 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 << " 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; } diff --git a/src/pcm-io.cpp b/src/pcm-io.cpp index cd23587a..7ecd31f0 100644 --- a/src/pcm-io.cpp +++ b/src/pcm-io.cpp @@ -717,7 +717,7 @@ static void print_usage(const string& progname) 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 => event file prefix (perfmon directory path)\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"; @@ -982,13 +982,13 @@ int mainThrows(int argc, char* argv[]) print_metrics_format(); exit(EXIT_SUCCESS); } - else if (check_argument_equals(*argv, {"--ep"})) + else if (check_argument_equals(*argv, {"-ep", "/ep"})) { argv++; argc--; if (argc <= 0) { - cerr << "ERROR: no parameter provided for option --ep\n"; + cerr << "ERROR: no parameter provided for option -ep\n"; exit(EXIT_FAILURE); } perfmonPath = *argv; @@ -1035,7 +1035,7 @@ int mainThrows(int argc, char* argv[]) 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"; + cerr << "Use -ep to specify the perfmon directory location.\n"; exit(EXIT_FAILURE); } From 97c1276ceae257d93ec68c34224a0344b496b7c9 Mon Sep 17 00:00:00 2001 From: Alexander Antonov Date: Mon, 20 Apr 2026 05:50:32 -0700 Subject: [PATCH 33/77] Search mapfile in CWD --- src/event-resolver.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/event-resolver.cpp b/src/event-resolver.cpp index d8c267c0..c8465d9f 100644 --- a/src/event-resolver.cpp +++ b/src/event-resolver.cpp @@ -27,6 +27,9 @@ std::string PerfmonEventResolver::findPerfmonPath(const std::string& programPath candidate = getInstallPathPrefix() + "perfmon"; if (std::ifstream(candidate + "/" + marker).good()) return candidate; + // 3. Current working directory (matches historical ".") + if (std::ifstream(marker).good()) return "."; + return ""; } From 0f523a86f2528918c214b3dc9fe47aadeab0a47e Mon Sep 17 00:00:00 2001 From: Alexander Antonov Date: Tue, 21 Apr 2026 01:21:33 -0700 Subject: [PATCH 34/77] Address review comments --- src/event-resolver.cpp | 16 ++++++------- src/event-resolver.h | 2 +- src/pcm-io-metrics.cpp | 34 +++++++++++++++++---------- src/pcm-io.cpp | 19 +++++++++++++-- tests/utests/event-resolver-utest.cpp | 2 +- tests/utests/pcm-io-metrics-utest.cpp | 1 + 6 files changed, 49 insertions(+), 25 deletions(-) diff --git a/src/event-resolver.cpp b/src/event-resolver.cpp index c8465d9f..2dbb6685 100644 --- a/src/event-resolver.cpp +++ b/src/event-resolver.cpp @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -// Copyright (c) 2025, Intel Corporation +// Copyright (c) 2026, Intel Corporation #include "event-resolver.h" #include "utils.h" @@ -27,10 +27,7 @@ std::string PerfmonEventResolver::findPerfmonPath(const std::string& programPath candidate = getInstallPathPrefix() + "perfmon"; if (std::ifstream(candidate + "/" + marker).good()) return candidate; - // 3. Current working directory (matches historical ".") - if (std::ifstream(marker).good()) return "."; - - return ""; + return "."; } const std::map PerfmonEventResolver::s_pmuNameMap = { @@ -165,9 +162,12 @@ bool PerfmonEventResolver::parseMapfile(const std::string& cpuFamilyModel, const while (std::getline(in, line)) { auto tokens = split(line, ','); - assert(fmsPos < static_cast(tokens.size())); - assert(filenamePos < static_cast(tokens.size())); - assert(eventTypePos < static_cast(tokens.size())); + 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; diff --git a/src/event-resolver.h b/src/event-resolver.h index cea0c996..37436c7a 100644 --- a/src/event-resolver.h +++ b/src/event-resolver.h @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -// Copyright (c) 2025, Intel Corporation +// Copyright (c) 2026, Intel Corporation #pragma once #include diff --git a/src/pcm-io-metrics.cpp b/src/pcm-io-metrics.cpp index b28c2728..16fa39d1 100644 --- a/src/pcm-io-metrics.cpp +++ b/src/pcm-io-metrics.cpp @@ -236,9 +236,11 @@ void TableRenderer::render(std::ostream& os) const os << BOX.horizontal; os << BOX.top_right << "\n"; - // Section title row + // 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; - size_t pad = innerWidth - 1 - m_rows[0].sectionTitle.size(); + 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"; @@ -287,7 +289,8 @@ void TableRenderer::render(std::ostream& os) const // Section title row (left-aligned, spans full width) os << BOX.vertical << " " << row.sectionTitle; - size_t pad = innerWidth - 1 - row.sectionTitle.size(); + 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"; @@ -300,29 +303,34 @@ void TableRenderer::render(std::ostream& os) const else if (row.isSystemSection) { needDataSeparator = false; - size_t N = row.systemPairs.size(); - if (N == 0) continue; + 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; - size_t titlePad = innerWidth - 1 - row.sectionTitle.size(); + 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 N equal column widths - size_t innerSpace = innerWidth - (N - 1); - std::vector wideColWidths(N, innerSpace / N); - for (size_t r = 0; r < innerSpace % N; ++r) wideColWidths[r]++; + // 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 N-column separator (┬) + // 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 < N; ++i) + for (size_t i = 0; i < numColumns; ++i) { renderCenteredText(os, row.systemPairs[i].first, wideColWidths[i]); os << BOX.vertical; @@ -334,7 +342,7 @@ void TableRenderer::render(std::ostream& os) const // Value row (centered) os << BOX.vertical; - for (size_t i = 0; i < N; ++i) + for (size_t i = 0; i < numColumns; ++i) { renderCenteredText(os, row.systemPairs[i].second, wideColWidths[i]); os << BOX.vertical; diff --git a/src/pcm-io.cpp b/src/pcm-io.cpp index 7ecd31f0..38b27a7f 100644 --- a/src/pcm-io.cpp +++ b/src/pcm-io.cpp @@ -1,6 +1,8 @@ // 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" @@ -38,6 +40,7 @@ class MetricsDisplay { 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); }; @@ -389,6 +392,18 @@ std::string MetricsDisplay::formatValue(double 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; @@ -452,7 +467,7 @@ void MetricsDisplay::displayCsv(std::ostream& os) const { if (m.aggregation == "system") continue; double val = evaluator.evaluate(m.formula, (*m_counterValues)[s]); - os << "," << static_cast(val); + os << "," << formatCsvValue(val); } for (size_t i = 0; i < numSystemMetrics; ++i) os << ","; @@ -469,7 +484,7 @@ void MetricsDisplay::displayCsv(std::ostream& os) const { if (m.aggregation != "system") continue; double val = evaluator.evaluate(m.formula, systemValues); - os << "," << static_cast(val); + os << "," << formatCsvValue(val); } os << "\n"; } diff --git a/tests/utests/event-resolver-utest.cpp b/tests/utests/event-resolver-utest.cpp index cb9df76d..ececb3a6 100644 --- a/tests/utests/event-resolver-utest.cpp +++ b/tests/utests/event-resolver-utest.cpp @@ -1,5 +1,5 @@ // SPDX-License-Identifier: BSD-3-Clause -// Copyright (c) 2025, Intel Corporation +// Copyright (c) 2026, Intel Corporation #include "event-resolver.h" #include "utils.h" diff --git a/tests/utests/pcm-io-metrics-utest.cpp b/tests/utests/pcm-io-metrics-utest.cpp index 3a1badb3..e63ab712 100644 --- a/tests/utests/pcm-io-metrics-utest.cpp +++ b/tests/utests/pcm-io-metrics-utest.cpp @@ -4,6 +4,7 @@ #include "pcm-io-metrics.h" #include #include +#include using namespace pcm; From ffcc0beb7de230521889b2d0566e019a80cf7627 Mon Sep 17 00:00:00 2001 From: Alexander Antonov Date: Tue, 21 Apr 2026 02:25:18 -0700 Subject: [PATCH 35/77] Add displaying standalone system wide metrics --- src/pcm-io-metrics.cpp | 76 ++++++++++++++++ src/pcm-io-metrics.h | 13 +++ src/pcm-io.cpp | 59 ++++++++++--- tests/utests/pcm-io-metrics-utest.cpp | 120 ++++++++++++++++++++++++++ 4 files changed, 258 insertions(+), 10 deletions(-) diff --git a/src/pcm-io-metrics.cpp b/src/pcm-io-metrics.cpp index 16fa39d1..14eb9149 100644 --- a/src/pcm-io-metrics.cpp +++ b/src/pcm-io-metrics.cpp @@ -388,6 +388,69 @@ std::string TableRenderer::renderToString() const 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 @@ -597,6 +660,19 @@ std::set MetricsConfig::extractEventNames() const 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) diff --git a/src/pcm-io-metrics.h b/src/pcm-io-metrics.h index 24a3b983..6e1ed79b 100644 --- a/src/pcm-io-metrics.h +++ b/src/pcm-io-metrics.h @@ -72,6 +72,14 @@ class TableRenderer { 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; @@ -113,6 +121,11 @@ class MetricsConfig { 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; diff --git a/src/pcm-io.cpp b/src/pcm-io.cpp index 38b27a7f..3f18e796 100644 --- a/src/pcm-io.cpp +++ b/src/pcm-io.cpp @@ -414,15 +414,18 @@ 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"; @@ -433,7 +436,7 @@ bool MetricsDisplay::hasLayoutSections() const const auto& layout = m_config->getLayout(); if (layout.size() > 1) return true; for (const auto& s : layout) - if (s.isMultiRow()) return true; + if (s.isMultiRow() || !s.title.empty()) return true; return false; } @@ -451,27 +454,33 @@ 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; } - for (uint32 s = 0; s < m_numSockets; ++s) + if (numSocketMetrics > 0) { - os << s; - for (const auto& m : metrics) + for (uint32 s = 0; s < m_numSockets; ++s) { - if (m.aggregation == "system") continue; - double val = evaluator.evaluate(m.formula, (*m_counterValues)[s]); - os << "," << formatCsvValue(val); + 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"; } - for (size_t i = 0; i < numSystemMetrics; ++i) - os << ","; - os << "\n"; } if (numSystemMetrics == 0) return; @@ -483,6 +492,7 @@ void MetricsDisplay::displayCsv(std::ostream& os) const 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); } @@ -532,6 +542,21 @@ void MetricsDisplay::displayLayoutMode(std::ostream& os) const 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"); @@ -674,6 +699,20 @@ void MetricsDisplay::displayFlatMode(std::ostream& os) const 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) { diff --git a/tests/utests/pcm-io-metrics-utest.cpp b/tests/utests/pcm-io-metrics-utest.cpp index e63ab712..d2e0d5be 100644 --- a/tests/utests/pcm-io-metrics-utest.cpp +++ b/tests/utests/pcm-io-metrics-utest.cpp @@ -538,6 +538,54 @@ TEST_F(TableRendererTest, RenderWithSystemSection) 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({ @@ -656,6 +704,78 @@ TEST(LayoutTest, LayoutMalformedSection) 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) From 83218ba16e298f9c28fd8447ce202ba8739780a6 Mon Sep 17 00:00:00 2001 From: Alexander Antonov Date: Tue, 21 Apr 2026 03:13:15 -0700 Subject: [PATCH 36/77] Address review comments --- src/event-resolver.cpp | 2 +- src/event-resolver.h | 2 +- src/pcm-io.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/event-resolver.cpp b/src/event-resolver.cpp index 2dbb6685..d7a4176a 100644 --- a/src/event-resolver.cpp +++ b/src/event-resolver.cpp @@ -18,7 +18,7 @@ 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('/'); + 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; diff --git a/src/event-resolver.h b/src/event-resolver.h index 37436c7a..7b838c95 100644 --- a/src/event-resolver.h +++ b/src/event-resolver.h @@ -22,7 +22,7 @@ class PerfmonEventResolver { public: // Search for the perfmon directory containing mapfile.csv. // Checks: next to programPath binary, then install prefix. - // Returns empty string if not found. + // 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. diff --git a/src/pcm-io.cpp b/src/pcm-io.cpp index 3f18e796..7d0c24b5 100644 --- a/src/pcm-io.cpp +++ b/src/pcm-io.cpp @@ -837,7 +837,7 @@ static std::string findMetricsPath(const std::string& programPath, const std::st const std::string relPath = "pmu-events/" + platformDir + "/metrics.json"; // 1. Next to the binary (post-build copy) - size_t lastSlash = programPath.find_last_of('/'); + 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; From 13bf1098e145d4a239ee2f2abe5ba8a44f65def3 Mon Sep 17 00:00:00 2001 From: Alexander Antonov Date: Tue, 21 Apr 2026 03:44:49 -0700 Subject: [PATCH 37/77] Address review comments --- src/event-resolver.cpp | 7 ++++++- src/pcm-io-metrics.cpp | 2 +- src/pcm-io-metrics.h | 5 ++--- src/pcm-io.cpp | 15 +++++++-------- 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/event-resolver.cpp b/src/event-resolver.cpp index d7a4176a..36094123 100644 --- a/src/event-resolver.cpp +++ b/src/event-resolver.cpp @@ -158,7 +158,7 @@ bool PerfmonEventResolver::parseMapfile(const std::string& cpuFamilyModel, const return false; } - std::cerr << "Matched event files:\n"; + bool headerPrinted = false; while (std::getline(in, line)) { auto tokens = split(line, ','); @@ -173,6 +173,11 @@ bool PerfmonEventResolver::parseMapfile(const std::string& cpuFamilyModel, const 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])); } diff --git a/src/pcm-io-metrics.cpp b/src/pcm-io-metrics.cpp index 14eb9149..21269f0a 100644 --- a/src/pcm-io-metrics.cpp +++ b/src/pcm-io-metrics.cpp @@ -16,7 +16,7 @@ struct FormulaParser { void skipWS() { - while (pos < input.size() && input[pos] == ' ') + while (pos < input.size() && std::isspace(static_cast(input[pos]))) ++pos; } diff --git a/src/pcm-io-metrics.h b/src/pcm-io-metrics.h index 6e1ed79b..34f5969a 100644 --- a/src/pcm-io-metrics.h +++ b/src/pcm-io-metrics.h @@ -11,6 +11,8 @@ #include #include +#include "event-resolver.h" + #ifdef PCM_SIMDJSON_AVAILABLE #include #include "simdjson.h" @@ -18,9 +20,6 @@ namespace pcm { -// Forward-declared in event-resolver.h; redeclared here so pcm-io-metrics.h is self-contained -using LocalEvent = std::unordered_map; - struct IOMetric { std::string name; std::string formula; diff --git a/src/pcm-io.cpp b/src/pcm-io.cpp index 7d0c24b5..0f05c25d 100644 --- a/src/pcm-io.cpp +++ b/src/pcm-io.cpp @@ -136,7 +136,7 @@ std::string MetricsDrivenPlatform::cpuModelToDir(int cpuModel) // // 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, etc.) bypass slot constraints entirely. +// Register events (mmio, pcicfg, pmt, tpmi) are rejected — not yet supported. // bool MetricsDrivenPlatform::init(PCM* pcm, const std::string& metricsPath, const std::string& eventPrefix) { @@ -179,13 +179,9 @@ bool MetricsDrivenPlatform::init(PCM* pcm, const std::string& metricsPath, const if (isRegisterEvent(pmuName)) { - if (m_pmuConfigGroups.empty()) - m_pmuConfigGroups.emplace_back(); - auto& grp = m_pmuConfigGroups[0]; - size_t idx = grp[pmuName].programmable.size(); - grp[pmuName].programmable.push_back(config); - m_eventLocations[eventName] = {0, pmuName, idx}; - continue; + 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"); @@ -353,6 +349,9 @@ void MetricsDrivenPlatform::readCounterValues() 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; } } From 027e25c6c9b40fb69110e346c46c30936290506f Mon Sep 17 00:00:00 2001 From: Alexander Antonov Date: Tue, 21 Apr 2026 03:50:45 -0700 Subject: [PATCH 38/77] Fix build issue on windows --- src/pcm-io-metrics.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/pcm-io-metrics.cpp b/src/pcm-io-metrics.cpp index 21269f0a..1ba3c588 100644 --- a/src/pcm-io-metrics.cpp +++ b/src/pcm-io-metrics.cpp @@ -4,6 +4,7 @@ #include "pcm-io-metrics.h" #include +#include namespace pcm { @@ -198,7 +199,7 @@ std::vector TableRenderer::calculateColumnWidths() const { 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()); + widths[i] = (std::max)(widths[i], row.values[i].size()); } for (auto& w : widths) w += padding; @@ -399,7 +400,7 @@ void TableRenderer::renderStandaloneSystemSection( 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; + 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; From d4e891b7a27dc8014aeff663513b8f99e23001f5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Apr 2026 09:35:15 +0000 Subject: [PATCH 39/77] tests: add functional test for pcm-sensor-server using curl Agent-Logs-Url: https://github.com/intel-innersource/applications.analyzers.pcm/sessions/ff7e0f45-a6f3-42a6-b3fa-b044736646b1 Co-authored-by: rdementi <25432609+rdementi@users.noreply.github.com> --- tests/test.sh | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 65 insertions(+), 1 deletion(-) diff --git a/tests/test.sh b/tests/test.sh index 8e57ad96..4dc59050 100755 --- a/tests/test.sh +++ b/tests/test.sh @@ -220,7 +220,71 @@ if [ "$?" -ne "0" ]; then fi # TODO add more tests -# e.g for ./pcm-sensor-server, ./pcm-sensor, ... +# e.g for ./pcm-sensor, ... + +echo Testing pcm-sensor-server +# Pick an unused high port to avoid collisions with the default one. +SENSOR_PORT=19738 +./pcm-sensor-server -p $SENSOR_PORT -silent & +SENSOR_PID=$! + +# Wait for the server to start accepting connections (up to ~20s). +SENSOR_READY=0 +for i in $(seq 1 40); do + if ! kill -0 $SENSOR_PID 2>/dev/null; then + echo "pcm-sensor-server exited unexpectedly during startup" + exit 1 + fi + if curl -s -o /dev/null -f "http://127.0.0.1:${SENSOR_PORT}/metrics"; then + SENSOR_READY=1 + break + fi + sleep 0.5 +done + +if [ "$SENSOR_READY" -ne "1" ]; then + echo "Error: pcm-sensor-server did not become ready on port $SENSOR_PORT" + kill $SENSOR_PID 2>/dev/null + wait $SENSOR_PID 2>/dev/null + exit 1 +fi + +echo " Query /metrics endpoint with curl" +METRICS_OUT=$(curl -s -f "http://127.0.0.1:${SENSOR_PORT}/metrics") +if [ "$?" -ne "0" ] || [ -z "$METRICS_OUT" ]; then + echo "Error in pcm-sensor-server: /metrics request failed or returned empty body" + kill $SENSOR_PID 2>/dev/null + wait $SENSOR_PID 2>/dev/null + exit 1 +fi +# Prometheus exposition format lines start with '#' (HELP/TYPE) or a metric name. +if ! echo "$METRICS_OUT" | grep -q '^#'; then + echo "Error in pcm-sensor-server: /metrics response does not look like Prometheus output" + kill $SENSOR_PID 2>/dev/null + wait $SENSOR_PID 2>/dev/null + exit 1 +fi + +echo " Query /dashboard endpoint with curl" +curl -s -f -o /dev/null "http://127.0.0.1:${SENSOR_PORT}/dashboard" +if [ "$?" -ne "0" ]; then + echo "Error in pcm-sensor-server: /dashboard request failed" + kill $SENSOR_PID 2>/dev/null + wait $SENSOR_PID 2>/dev/null + exit 1 +fi + +echo " Query unknown endpoint, expect HTTP 404" +HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "http://127.0.0.1:${SENSOR_PORT}/does-not-exist") +if [ "$HTTP_CODE" != "404" ]; then + echo "Error in pcm-sensor-server: expected 404 for unknown endpoint, got $HTTP_CODE" + kill $SENSOR_PID 2>/dev/null + wait $SENSOR_PID 2>/dev/null + exit 1 +fi + +kill $SENSOR_PID 2>/dev/null +wait $SENSOR_PID 2>/dev/null echo Testing urltest ./tests/urltest From b7a6a53f7a721c21e4cd32dea8079fe6ae7450ff Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Apr 2026 09:35:58 +0000 Subject: [PATCH 40/77] tests: capture curl exit code into variable for robust check Agent-Logs-Url: https://github.com/intel-innersource/applications.analyzers.pcm/sessions/ff7e0f45-a6f3-42a6-b3fa-b044736646b1 Co-authored-by: rdementi <25432609+rdementi@users.noreply.github.com> --- tests/test.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test.sh b/tests/test.sh index 4dc59050..d6f0b86d 100755 --- a/tests/test.sh +++ b/tests/test.sh @@ -251,7 +251,8 @@ fi echo " Query /metrics endpoint with curl" METRICS_OUT=$(curl -s -f "http://127.0.0.1:${SENSOR_PORT}/metrics") -if [ "$?" -ne "0" ] || [ -z "$METRICS_OUT" ]; then +CURL_RC=$? +if [ "$CURL_RC" -ne "0" ] || [ -z "$METRICS_OUT" ]; then echo "Error in pcm-sensor-server: /metrics request failed or returned empty body" kill $SENSOR_PID 2>/dev/null wait $SENSOR_PID 2>/dev/null From 3caaca991da77cc1ccad32e096645de3166a84b5 Mon Sep 17 00:00:00 2001 From: "Dementiev, Roman" Date: Wed, 22 Apr 2026 10:58:03 +0200 Subject: [PATCH 41/77] Fix buffer overflow in basic_socketbuf::overflow() - CWE-787 The overflow() function was writing a character to pptr() before flushing the buffer. When overflow() is called, pptr() points one byte past the end of the buffer (outputBuffer_ + SIZE), causing a write beyond bounds. Fixed by reordering operations to: flush the buffer first via writeToSocket(), explicitly reset buffer pointers with setp(), then safely write the new character to the now-empty buffer. Added comments explaining why operations must be ordered as flush, reset, write to prevent the buffer overflow. Change-Id: I6e6223f39e79b8e29637a7bb5fb0d27db9644792 Co-Authored-By: Claude Sonnet 4.5 --- src/pcm-sensor-server.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/pcm-sensor-server.cpp b/src/pcm-sensor-server.cpp index f9ec3bb8..a6721393 100644 --- a/src/pcm-sensor-server.cpp +++ b/src/pcm-sensor-server.cpp @@ -1224,15 +1224,19 @@ class basic_socketbuf : public std::basic_streambuf { } virtual int_type overflow( int_type ch ) override { - // send data in buffer and reset it + // Flush buffer first - when overflow() is called, pptr() == epptr() (buffer is full) + // Writing to pptr() before flushing would write past the end of outputBuffer_ + int_type bytesWritten = writeToSocket(); + if ( traits_type::eof() == bytesWritten ) { + return traits_type::eof(); + } + // Reset put area pointers to start of buffer after successful flush + Base::setp( outputBuffer_, outputBuffer_ + SIZE ); + // Now safe to write new character at pptr() (start of empty buffer) if ( traits_type::eof() != ch ) { *Base::pptr() = ch; Base::pbump(1); } - int_type bytesWritten = 0; - if ( traits_type::eof() == (bytesWritten = writeToSocket()) ) { - return traits_type::eof(); - } return bytesWritten; // Anything but traits_type::eof() to signal ok. } From 69a737970ed4fe6b6ae2d731c30c9baf6dad3945 Mon Sep 17 00:00:00 2001 From: Roman Dementiev Date: Wed, 22 Apr 2026 11:16:24 +0200 Subject: [PATCH 42/77] Update src/pcm-sensor-server.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/pcm-sensor-server.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pcm-sensor-server.cpp b/src/pcm-sensor-server.cpp index a6721393..d47520e0 100644 --- a/src/pcm-sensor-server.cpp +++ b/src/pcm-sensor-server.cpp @@ -1234,7 +1234,7 @@ class basic_socketbuf : public std::basic_streambuf { Base::setp( outputBuffer_, outputBuffer_ + SIZE ); // Now safe to write new character at pptr() (start of empty buffer) if ( traits_type::eof() != ch ) { - *Base::pptr() = ch; + *Base::pptr() = traits_type::to_char_type(ch); Base::pbump(1); } return bytesWritten; // Anything but traits_type::eof() to signal ok. From 3a542a95f88dc8fa16ac2310436162deb35d4456 Mon Sep 17 00:00:00 2001 From: Roman Dementiev Date: Wed, 22 Apr 2026 11:16:34 +0200 Subject: [PATCH 43/77] Update src/pcm-sensor-server.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/pcm-sensor-server.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pcm-sensor-server.cpp b/src/pcm-sensor-server.cpp index d47520e0..08691f1d 100644 --- a/src/pcm-sensor-server.cpp +++ b/src/pcm-sensor-server.cpp @@ -1237,7 +1237,7 @@ class basic_socketbuf : public std::basic_streambuf { *Base::pptr() = traits_type::to_char_type(ch); Base::pbump(1); } - return bytesWritten; // Anything but traits_type::eof() to signal ok. + return traits_type::not_eof( ch ); } virtual int_type underflow() override { From b4680d0bfa1e718baefd2c5b59c078d2e315d5cc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Apr 2026 09:40:39 +0000 Subject: [PATCH 44/77] Add CI regression test reproducing basic_socketbuf::overflow() OOB write Agent-Logs-Url: https://github.com/intel-innersource/applications.analyzers.pcm/sessions/a53c9d61-0218-4c3e-a1ac-0b14b1a73f3d Co-authored-by: rdementi <25432609+rdementi@users.noreply.github.com> --- tests/utests/CMakeLists.txt | 10 ++ .../pcm-sensor-server-overflow-utest.cpp | 142 ++++++++++++++++++ 2 files changed, 152 insertions(+) create mode 100644 tests/utests/pcm-sensor-server-overflow-utest.cpp diff --git a/tests/utests/CMakeLists.txt b/tests/utests/CMakeLists.txt index 49fbc781..a079ae39 100644 --- a/tests/utests/CMakeLists.txt +++ b/tests/utests/CMakeLists.txt @@ -17,6 +17,7 @@ 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 PCM_SENSOR_SERVER_OVERFLOW_TEST_FILES pcm-sensor-server-overflow-utest.cpp) if(APPLE) set(LIBS PcmMsr Threads::Threads PCM_STATIC) @@ -27,6 +28,7 @@ endif() 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(pcm-sensor-server-overflow-utest ${PCM_SENSOR_SERVER_OVERFLOW_TEST_FILES}) configure_file( ${CMAKE_SOURCE_DIR}/src/opCode-6-174.txt @@ -55,7 +57,15 @@ target_link_libraries( ${LIBS} ) +target_link_libraries( + pcm-sensor-server-overflow-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(pcm-sensor-server-overflow-utest) diff --git a/tests/utests/pcm-sensor-server-overflow-utest.cpp b/tests/utests/pcm-sensor-server-overflow-utest.cpp new file mode 100644 index 00000000..5b0bdc7f --- /dev/null +++ b/tests/utests/pcm-sensor-server-overflow-utest.cpp @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2009-2025, Intel Corporation + +// Regression test for the out-of-bounds write in +// basic_socketbuf::overflow() (src/pcm-sensor-server.cpp, lines 1226-1237). +// +// The vulnerable overflow() writes the incoming character into *pptr() +// *before* flushing the full put-area to the socket. When the put area is +// full, pptr() == epptr(), i.e. it points one past the end of outputBuffer_, +// which is the first byte of the adjacent inputBuffer_. This test drives the +// real basic_socketbuf template through a socketpair, fills the put area to +// the brim, triggers overflow(), and verifies that inputBuffer_[0] is not +// corrupted. +// +// With the bug in place the assertion on inputBuffer_[0] fails (and, when the +// binary is built with AddressSanitizer via -DPCM_NO_ASAN=OFF, the underlying +// intra-object OOB write / OOB read in send() is also detected). Once +// overflow() is fixed to flush first and then store the character into the +// emptied buffer, the test passes. + +#include +#include +#include +#include +#include +#include +#include +#include + +// Pull the real basic_socketbuf template out of pcm-sensor-server.cpp without +// bringing in its main(). The same mechanism is already used by +// tests/pcm-sensor-server-fuzz.cpp. +#define UNIT_TEST 1 +#include "../../src/pcm-sensor-server.cpp" +#undef UNIT_TEST + +#include + +namespace { + +// SIZE matches the instantiation used by basic_socketstream::buf_type +// (see pcm-sensor-server.cpp line 1363). +constexpr std::size_t kSocketBufSize = 16385; + +// Subclass that exposes the protected buffer members so the test can inspect +// inputBuffer_[0] after triggering overflow(). +class ProbeSocketBuf : public basic_socketbuf { +public: + using basic_socketbuf::inputBuffer_; + using basic_socketbuf::outputBuffer_; +}; + +// Drain the peer end of a socketpair in a background thread so that the +// server-side send() inside writeToSocket() never blocks, regardless of what +// the kernel's default socket buffer sizes happen to be on the CI runner. +class SocketDrainer { +public: + explicit SocketDrainer(int fd) : fd_(fd), stop_(false) { + thread_ = std::thread([this]() { + char buf[4096]; + while (!stop_.load()) { + ssize_t n = ::recv(fd_, buf, sizeof(buf), 0); + if (n <= 0) { + break; + } + received_.insert(received_.end(), buf, buf + n); + } + }); + } + + ~SocketDrainer() { + stop_.store(true); + ::shutdown(fd_, SHUT_RDWR); + if (thread_.joinable()) { + thread_.join(); + } + } + + const std::vector& data() const { return received_; } + +private: + int fd_; + std::atomic stop_; + std::thread thread_; + std::vector received_; +}; + +} // namespace + +TEST(PcmSensorServerOverflowTest, OverflowDoesNotWritePastOutputBuffer) +{ + int sv[2]; + ASSERT_EQ(0, ::socketpair(AF_UNIX, SOCK_STREAM, 0, sv)) + << "socketpair failed: " << std::strerror(errno); + + // Peer drains whatever the socketbuf sends. + SocketDrainer drainer(sv[1]); + + auto buf = std::make_unique(); + buf->setSocket(sv[0]); + + // Plant a distinctive sentinel in inputBuffer_[0]. With the vulnerable + // overflow(), this byte is the one clobbered by "*pptr() = ch" when the + // put area is full. + constexpr unsigned char kSentinel = 0xAA; + constexpr unsigned char kOverflowChar = 0x5A; // 'Z' + static_assert(kSentinel != kOverflowChar, + "sentinel and overflow byte must differ to detect the OOB write"); + buf->inputBuffer_[0] = static_cast(kSentinel); + + // Fill the put area completely. After exactly SIZE sputc() calls, + // pptr() == epptr() but overflow() has not yet been invoked. + for (std::size_t i = 0; i < kSocketBufSize; ++i) { + ASSERT_NE(std::char_traits::eof(), buf->sputc('X')) + << "sputc failed while filling the put area at index " << i; + } + + // The next sputc() must trigger overflow(kOverflowChar). With the + // vulnerable implementation this is where *pptr() = ch writes past the + // end of outputBuffer_ and into inputBuffer_[0]. + ASSERT_NE(std::char_traits::eof(), + buf->sputc(static_cast(kOverflowChar))) + << "sputc returned eof when triggering overflow()"; + + // After overflow() returns, the put area should have been flushed to the + // socket and the sentinel in inputBuffer_[0] must still be intact. + EXPECT_EQ(static_cast(buf->inputBuffer_[0]), kSentinel) + << "basic_socketbuf::overflow() corrupted inputBuffer_[0]: " + << "wrote ch=0x" << std::hex << static_cast(kOverflowChar) + << " past the end of outputBuffer_ (SIZE=" << std::dec + << kSocketBufSize << "). See pcm-sensor-server.cpp " + << "basic_socketbuf::overflow() (lines 1226-1237): the character " + << "must be stored only *after* the full put-area has been flushed " + << "and the put pointers reset."; + + // Release the socket before the buf destructor runs sync(); this keeps + // the test output stable regardless of whether the drainer has already + // exited. + buf.reset(); + ::close(sv[0]); + // sv[1] is closed by the drainer's shutdown. +} From fbba54604f8dcfa9a268452f20b749469159a5bf Mon Sep 17 00:00:00 2001 From: Roman Dementiev Date: Wed, 22 Apr 2026 14:42:38 +0200 Subject: [PATCH 45/77] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- tests/utests/pcm-sensor-server-overflow-utest.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/tests/utests/pcm-sensor-server-overflow-utest.cpp b/tests/utests/pcm-sensor-server-overflow-utest.cpp index 5b0bdc7f..12fc095c 100644 --- a/tests/utests/pcm-sensor-server-overflow-utest.cpp +++ b/tests/utests/pcm-sensor-server-overflow-utest.cpp @@ -70,10 +70,16 @@ class SocketDrainer { ~SocketDrainer() { stop_.store(true); - ::shutdown(fd_, SHUT_RDWR); + if (fd_ >= 0) { + ::shutdown(fd_, SHUT_RDWR); + } if (thread_.joinable()) { thread_.join(); } + if (fd_ >= 0) { + ::close(fd_); + fd_ = -1; + } } const std::vector& data() const { return received_; } @@ -124,7 +130,7 @@ TEST(PcmSensorServerOverflowTest, OverflowDoesNotWritePastOutputBuffer) // After overflow() returns, the put area should have been flushed to the // socket and the sentinel in inputBuffer_[0] must still be intact. - EXPECT_EQ(static_cast(buf->inputBuffer_[0]), kSentinel) + EXPECT_EQ(kSentinel, static_cast(buf->inputBuffer_[0])) << "basic_socketbuf::overflow() corrupted inputBuffer_[0]: " << "wrote ch=0x" << std::hex << static_cast(kOverflowChar) << " past the end of outputBuffer_ (SIZE=" << std::dec @@ -135,8 +141,7 @@ TEST(PcmSensorServerOverflowTest, OverflowDoesNotWritePastOutputBuffer) // Release the socket before the buf destructor runs sync(); this keeps // the test output stable regardless of whether the drainer has already - // exited. + // exited. Resetting buf closes sv[0]. buf.reset(); - ::close(sv[0]); // sv[1] is closed by the drainer's shutdown. } From 572ad3fafde0f6614c2b4341fb75c6042a4a6ec8 Mon Sep 17 00:00:00 2001 From: "Dementiev, Roman" Date: Wed, 22 Apr 2026 15:03:45 +0200 Subject: [PATCH 46/77] address review feedback Change-Id: I9cdea0b609de814bc602b49b8037e3dd56ee9218 --- tests/utests/pcm-sensor-server-overflow-utest.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/utests/pcm-sensor-server-overflow-utest.cpp b/tests/utests/pcm-sensor-server-overflow-utest.cpp index 12fc095c..e0817854 100644 --- a/tests/utests/pcm-sensor-server-overflow-utest.cpp +++ b/tests/utests/pcm-sensor-server-overflow-utest.cpp @@ -54,6 +54,8 @@ class ProbeSocketBuf : public basic_socketbuf { // server-side send() inside writeToSocket() never blocks, regardless of what // the kernel's default socket buffer sizes happen to be on the CI runner. class SocketDrainer { + SocketDrainer & operator = (const SocketDrainer &) = delete; + SocketDrainer(const SocketDrainer &) = delete; public: explicit SocketDrainer(int fd) : fd_(fd), stop_(false) { thread_ = std::thread([this]() { From 9d0bcf0046283a225462f4fca9ce7641bd289e07 Mon Sep 17 00:00:00 2001 From: Alexander Antonov Date: Tue, 28 Apr 2026 04:44:56 -0700 Subject: [PATCH 47/77] Rename pmu-events folder to metrics --- src/CMakeLists.txt | 4 ++-- src/{pmu-events => metrics}/icelake-sp/metrics.json | 0 src/pcm-io.cpp | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) rename src/{pmu-events => metrics}/icelake-sp/metrics.json (100%) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c5ab7cfd..a0ea8dfa 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -216,8 +216,8 @@ if(PCM_BUILD_EXECUTABLES) ${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}/pmu-events - $/pmu-events) + ${CMAKE_CURRENT_SOURCE_DIR}/metrics + $/metrics) endif(${PROJECT_NAME} STREQUAL pcm-io) if(${PROJECT_NAME} STREQUAL pcm-sensor-server) diff --git a/src/pmu-events/icelake-sp/metrics.json b/src/metrics/icelake-sp/metrics.json similarity index 100% rename from src/pmu-events/icelake-sp/metrics.json rename to src/metrics/icelake-sp/metrics.json diff --git a/src/pcm-io.cpp b/src/pcm-io.cpp index 0f05c25d..035e03af 100644 --- a/src/pcm-io.cpp +++ b/src/pcm-io.cpp @@ -826,14 +826,14 @@ static void print_metrics_format() cout << " columns (object) => column header => [metricName per row]\n"; cout << " system-wide-metrics (optional) => metrics rendered as system rows\n\n"; - cout << " See src/pmu-events/icelake-sp/metrics.json for a complete example.\n\n"; + cout << " See src/metrics/icelake-sp/metrics.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 = "pmu-events/" + platformDir + "/metrics.json"; + const std::string relPath = "metrics/" + platformDir + "/metrics.json"; // 1. Next to the binary (post-build copy) size_t lastSlash = programPath.find_last_of("/\\"); From f169c8d9c1c3133712702642fb7c010dd046318c Mon Sep 17 00:00:00 2001 From: Alexander Antonov Date: Tue, 28 Apr 2026 04:57:41 -0700 Subject: [PATCH 48/77] Rename src/metrics/icelake-sp/metrics.json -> src/metrics/icelake-sp/io.json --- src/metrics/icelake-sp/{metrics.json => io.json} | 0 src/pcm-io.cpp | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) rename src/metrics/icelake-sp/{metrics.json => io.json} (100%) diff --git a/src/metrics/icelake-sp/metrics.json b/src/metrics/icelake-sp/io.json similarity index 100% rename from src/metrics/icelake-sp/metrics.json rename to src/metrics/icelake-sp/io.json diff --git a/src/pcm-io.cpp b/src/pcm-io.cpp index 035e03af..80ba46ce 100644 --- a/src/pcm-io.cpp +++ b/src/pcm-io.cpp @@ -826,14 +826,14 @@ static void print_metrics_format() 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/metrics.json for a complete example.\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 + "/metrics.json"; + const std::string relPath = "metrics/" + platformDir + "/io.json"; // 1. Next to the binary (post-build copy) size_t lastSlash = programPath.find_last_of("/\\"); From e9e333a03f1b447c327f710638c52b43013b862f Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon, 11 May 2026 17:32:01 +0200 Subject: [PATCH 49/77] Mitigate Slowloris thread-pool exhaustion in pcm-sensor-server (CWE-770) (#945) * Mitigate Slowloris DoS in pcm-sensor-server request parser --- src/pcm-sensor-server.cpp | 334 +++++++++++++++++++++++++++++++++++--- src/threadpool.h | 53 +++++- 2 files changed, 364 insertions(+), 23 deletions(-) diff --git a/src/pcm-sensor-server.cpp b/src/pcm-sensor-server.cpp index 08691f1d..b4f195f0 100644 --- a/src/pcm-sensor-server.cpp +++ b/src/pcm-sensor-server.cpp @@ -2831,8 +2831,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 +2855,224 @@ 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; + +// 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 +3119,81 @@ basic_socketstream& operator>>( basic_socketstream // m.debugPrint(); std::string line; std::string concatLine; + size_t totalHeaderBytes = 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() ); + } + 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() ); + } + 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,9 +3269,32 @@ 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 @@ -3022,6 +3302,16 @@ basic_socketstream& operator>>( basic_socketstream } 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 ) 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_; } From 093758250597cf7aecb474c2b8d640f3c6fc5fb0 Mon Sep 17 00:00:00 2001 From: "Dementiev, Roman" Date: Mon, 1 Jun 2026 15:57:50 +0200 Subject: [PATCH 50/77] update to intel-innersource/applications.security.monitoring.cas@v2.1.1 Change-Id: Idae0a09bae95900b1771b71cc337a6f94291be9d --- .github/workflows/ci-cas-security.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-cas-security.yml b/.github/workflows/ci-cas-security.yml index 71d6312f..4aefa92d 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@v2.1.1 with: sdl-api-key: ${{ secrets.SDL_API_KEY }} sdl-project-id: ${{ secrets.SDL_PROJECT_ID }} From 972299b8a391e302b6403c6e6aed51f27e08a23d Mon Sep 17 00:00:00 2001 From: "Dementiev, Roman" Date: Mon, 1 Jun 2026 16:01:50 +0200 Subject: [PATCH 51/77] update to intel-innersource/applications.security.monitoring.cas@v3.0.2 Change-Id: Ib197b7b4f7273d9220401f32ad32a06d06168dc9 --- .github/workflows/ci-cas-security.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-cas-security.yml b/.github/workflows/ci-cas-security.yml index 4aefa92d..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.1.1 + 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 }} From 9f6281968a38dd263f01b95eb554ab2db0f36264 Mon Sep 17 00:00:00 2001 From: "Dementiev, Roman" Date: Mon, 1 Jun 2026 16:16:09 +0200 Subject: [PATCH 52/77] update Intel-PMT Change-Id: I7a92e2a563e5463fe11cc12e28de2157089f542f --- Intel-PMT | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Intel-PMT b/Intel-PMT index 8e57e182..269d10ff 160000 --- a/Intel-PMT +++ b/Intel-PMT @@ -1 +1 @@ -Subproject commit 8e57e182feeaa55427199356c0b4c77bf92db89f +Subproject commit 269d10ffec83a5b0498060ccdd72f6add6892405 From f70e5d31ba3007358a3996c48de738a1f15a1632 Mon Sep 17 00:00:00 2001 From: "Dementiev, Roman" Date: Mon, 1 Jun 2026 19:22:19 +0200 Subject: [PATCH 53/77] update to ntel-innersource/applications.security.monitoring.cas@v3.0.2 Change-Id: I659bc4c2a1837e63678583687717ae571571f9b2 --- .github/workflows/ci-cas-security-dockerfile.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-cas-security-dockerfile.yml b/.github/workflows/ci-cas-security-dockerfile.yml index 5c1fef80..f3e3ec09 100644 --- a/.github/workflows/ci-cas-security-dockerfile.yml +++ b/.github/workflows/ci-cas-security-dockerfile.yml @@ -26,7 +26,7 @@ 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: sdl-api-key: ${{ secrets.SDL_API_KEY }} sdl-project-id: ${{ secrets.SDL_PROJECT_ID }} From 54f315efa4e74d09382c27263fe104208920a85a Mon Sep 17 00:00:00 2001 From: "Dementiev, Roman" Date: Mon, 1 Jun 2026 20:14:10 +0200 Subject: [PATCH 54/77] add itsf credentials Change-Id: If4b29ac7d229fd5a1a638c3b4c60fe53c0217519 --- .github/workflows/ci-cas-security-dockerfile.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci-cas-security-dockerfile.yml b/.github/workflows/ci-cas-security-dockerfile.yml index f3e3ec09..e6d66c20 100644 --- a/.github/workflows/ci-cas-security-dockerfile.yml +++ b/.github/workflows/ci-cas-security-dockerfile.yml @@ -28,6 +28,8 @@ jobs: - name: CAS Security Orchestrator (Source Only) 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 }} From 180c1cd301d2ddafe4b84f6ac8ee796febbb2d38 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 2 Jun 2026 11:45:00 +0000 Subject: [PATCH 55/77] Remove macOS/OSX support including the MacMSRDriver --- .github/workflows/macos-scan-build.yml | 47 -- .github/workflows/macosx_build.yml | 43 -- CMakeLists.txt | 8 +- README.md | 7 +- doc/MAC_HOWTO.txt | 64 --- doc/NUMA_NODE_API.md | 5 - doc/PCM-EXPORTER.md | 2 +- examples/CMakeLists.txt | 6 +- examples/numa_node_example.cpp | 1 - examples/numa_to_socket_example.cpp | 1 - pcm.spec | 2 +- src/CMakeLists.txt | 23 +- src/MacMSRDriver/CMakeLists.txt | 19 - src/MacMSRDriver/MSRAccessor.cpp | 166 ------- src/MacMSRDriver/MSRAccessor.h | 26 - src/MacMSRDriver/MSRKernel.h | 16 - src/MacMSRDriver/PCIDriverInterface.cpp | 228 --------- src/MacMSRDriver/PCIDriverInterface.h | 33 -- .../PcmMsr.xcodeproj/project.pbxproj | 458 ------------------ .../contents.xcworkspacedata | 7 - .../UserInterfaceState.xcuserstate | Bin 92777 -> 0 bytes .../UserInterfaceState.xcuserstate | Bin 87025 -> 0 bytes .../xcdebugger/Breakpoints.xcbkptlist | 105 ---- .../xcschemes/PcmMsr.xcscheme | 58 --- .../xcschemes/PcmMsrLibrary.xcscheme | 58 --- .../xcschemes/xcschememanagement.plist | 37 -- .../xcschemes/PcmMsrDriver.xcscheme | 80 --- .../xcschemes/PcmMsrLibrary.xcscheme | 80 --- .../xcschemes/xcschememanagement.plist | 32 -- src/MacMSRDriver/PcmMsr/CMakeLists.txt | 58 --- src/MacMSRDriver/PcmMsr/PcmMsr-Info.plist | 57 --- src/MacMSRDriver/PcmMsr/PcmMsr-Prefix.pch | 4 - src/MacMSRDriver/PcmMsr/PcmMsr.cpp | 316 ------------ src/MacMSRDriver/PcmMsr/PcmMsr.h | 47 -- src/MacMSRDriver/PcmMsr/PcmMsrClient.cpp | 336 ------------- src/MacMSRDriver/PcmMsr/PcmMsrClient.h | 75 --- src/MacMSRDriver/PcmMsr/PcmMsrDriver_info.c | 9 - src/MacMSRDriver/PcmMsr/UserKernelShared.h | 53 -- .../PcmMsr/en.lproj/InfoPlist.strings | 2 - src/MacMSRDriver/kextload.sh | 5 - src/MacMSRDriver/kextunload.sh | 4 - src/cpucounters.cpp | 110 +---- src/cpucounters.h | 1 - src/mmio.cpp | 57 --- src/mmio.h | 6 +- src/msr.cpp | 59 --- src/msr.h | 41 -- src/pci.cpp | 60 --- src/pci.h | 7 - src/pcm-sensor-server.cpp | 8 +- src/topologyentry.h | 21 - tests/CMakeLists.txt | 16 +- tests/numa_to_socket_test.cpp | 1 - tests/utests/CMakeLists.txt | 10 +- 54 files changed, 27 insertions(+), 2948 deletions(-) delete mode 100644 .github/workflows/macos-scan-build.yml delete mode 100644 .github/workflows/macosx_build.yml delete mode 100644 doc/MAC_HOWTO.txt delete mode 100644 src/MacMSRDriver/CMakeLists.txt delete mode 100644 src/MacMSRDriver/MSRAccessor.cpp delete mode 100644 src/MacMSRDriver/MSRAccessor.h delete mode 100644 src/MacMSRDriver/MSRKernel.h delete mode 100644 src/MacMSRDriver/PCIDriverInterface.cpp delete mode 100644 src/MacMSRDriver/PCIDriverInterface.h delete mode 100644 src/MacMSRDriver/PcmMsr.xcodeproj/project.pbxproj delete mode 100644 src/MacMSRDriver/PcmMsr.xcodeproj/project.xcworkspace/contents.xcworkspacedata delete mode 100644 src/MacMSRDriver/PcmMsr.xcodeproj/project.xcworkspace/xcuserdata/aiott.xcuserdatad/UserInterfaceState.xcuserstate delete mode 100644 src/MacMSRDriver/PcmMsr.xcodeproj/project.xcworkspace/xcuserdata/pjkerly.xcuserdatad/UserInterfaceState.xcuserstate delete mode 100644 src/MacMSRDriver/PcmMsr.xcodeproj/xcuserdata/aiott.xcuserdatad/xcdebugger/Breakpoints.xcbkptlist delete mode 100644 src/MacMSRDriver/PcmMsr.xcodeproj/xcuserdata/aiott.xcuserdatad/xcschemes/PcmMsr.xcscheme delete mode 100644 src/MacMSRDriver/PcmMsr.xcodeproj/xcuserdata/aiott.xcuserdatad/xcschemes/PcmMsrLibrary.xcscheme delete mode 100644 src/MacMSRDriver/PcmMsr.xcodeproj/xcuserdata/aiott.xcuserdatad/xcschemes/xcschememanagement.plist delete mode 100644 src/MacMSRDriver/PcmMsr.xcodeproj/xcuserdata/pjkerly.xcuserdatad/xcschemes/PcmMsrDriver.xcscheme delete mode 100644 src/MacMSRDriver/PcmMsr.xcodeproj/xcuserdata/pjkerly.xcuserdatad/xcschemes/PcmMsrLibrary.xcscheme delete mode 100644 src/MacMSRDriver/PcmMsr.xcodeproj/xcuserdata/pjkerly.xcuserdatad/xcschemes/xcschememanagement.plist delete mode 100644 src/MacMSRDriver/PcmMsr/CMakeLists.txt delete mode 100644 src/MacMSRDriver/PcmMsr/PcmMsr-Info.plist delete mode 100644 src/MacMSRDriver/PcmMsr/PcmMsr-Prefix.pch delete mode 100644 src/MacMSRDriver/PcmMsr/PcmMsr.cpp delete mode 100644 src/MacMSRDriver/PcmMsr/PcmMsr.h delete mode 100644 src/MacMSRDriver/PcmMsr/PcmMsrClient.cpp delete mode 100644 src/MacMSRDriver/PcmMsr/PcmMsrClient.h delete mode 100644 src/MacMSRDriver/PcmMsr/PcmMsrDriver_info.c delete mode 100644 src/MacMSRDriver/PcmMsr/UserKernelShared.h delete mode 100644 src/MacMSRDriver/PcmMsr/en.lproj/InfoPlist.strings delete mode 100644 src/MacMSRDriver/kextload.sh delete mode 100644 src/MacMSRDriver/kextunload.sh 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..1f05caa3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,7 +54,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 +101,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 +222,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/README.md b/README.md index 0a1b8237..529a7eeb 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) -------------------------------------------------------------------------------- @@ -58,7 +57,7 @@ Graphical front ends: - **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 and FreeBSD. 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 +97,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 c8d4327e..a2f1ca46 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 (Linux only) -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..2c857530 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 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 95447f1f..b791e24b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -8,9 +8,7 @@ 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,10 +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() +if(UNIX) # LINUX, FREE_BSD + set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS} -s") # --strip-unneeded for packaging list(APPEND PROJECT_NAMES pcm-sensor) # libpcm.a @@ -84,11 +80,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) @@ -241,11 +233,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 +245,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 28016b08e9971571f2b16592a82e0cfbdf52d2a9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 92777 zcmdR12VfJ&(!Sk2-APv@xyZDTn9x$drnis;S8#BP8$F7#1sH70v24JU38x6B3a1ID3ug;g2%Ci~g)PEW!qvhx!nMM6!dBsK z;X∾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 X!8>mJndm(-{!H!u_x=B~?u`8f{kY~i 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 0d032e62e46ac1af5f223957b33ac2ce9fd0e655..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 87025 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 - - - - - - - - - - - - - - - - - - - - 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/cpucounters.cpp b/src/cpucounters.cpp index 5decbaaf..b29c43ba 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) @@ -1208,7 +1197,6 @@ bool PCM::discoverSystemTopology() } } -#ifndef __APPLE__ auto populateEntry = [&topologyDomainMap,&smtMaskWidth, &coreMaskWidth, &l2CacheMaskShift, &l3CacheMaskShift](TopologyEntry& entry) { auto getAPICID = [&](const uint32 leaf) @@ -1265,7 +1253,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 +1343,7 @@ bool PCM::discoverSystemTopology() deleteAndNullifyArray(base_slpi); #else - // for Linux, Mac OS, FreeBSD and DragonFlyBSD + // for Linux, FreeBSD and DragonFlyBSD TopologyEntry entry; @@ -1440,62 +1427,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 +1612,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 +1641,6 @@ bool PCM::initMSR() #endif return false; } -#endif return true; } @@ -5531,31 +5455,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) { @@ -7513,11 +7412,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 @@ -10275,7 +10169,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 diff --git a/src/cpucounters.h b/src/cpucounters.h index fc6c9a33..bcd5c389 100644 --- a/src/cpucounters.h +++ b/src/cpucounters.h @@ -2085,7 +2085,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 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/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-sensor-server.cpp b/src/pcm-sensor-server.cpp index b4f195f0..6540f569 100644 --- a/src/pcm-sensor-server.cpp +++ b/src/pcm-sensor-server.cpp @@ -4361,7 +4361,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"; @@ -4392,9 +4392,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; @@ -4499,12 +4497,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] ); @@ -4628,7 +4624,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/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/utests/CMakeLists.txt b/tests/utests/CMakeLists.txt index a079ae39..fd3ed41e 100644 --- a/tests/utests/CMakeLists.txt +++ b/tests/utests/CMakeLists.txt @@ -10,20 +10,12 @@ 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 PCM_SENSOR_SERVER_OVERFLOW_TEST_FILES pcm-sensor-server-overflow-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}) From f4547d9460b250f3586619e165de18e990e03195 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 2 Jun 2026 11:50:22 +0000 Subject: [PATCH 56/77] Drop deleted MacMSRDriver file from cppcheck ignore list --- scripts/cppcheck.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 91dfce1b52bd7744e7beacadad343568137b7822 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 2 Jun 2026 12:04:10 +0000 Subject: [PATCH 57/77] Report fatal error in CMake when building on macOS/OSX --- CMakeLists.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1f05caa3..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) From 3303cfbe1f8b52aeebbfa8ce7023976c48c44d33 Mon Sep 17 00:00:00 2001 From: Roman Dementiev Date: Tue, 2 Jun 2026 15:27:48 +0200 Subject: [PATCH 58/77] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- doc/PCM-EXPORTER.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/PCM-EXPORTER.md b/doc/PCM-EXPORTER.md index a2f1ca46..046e4c66 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 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. From 5f79e945b366802df124dff72207050d50ad9ed2 Mon Sep 17 00:00:00 2001 From: Roman Dementiev Date: Tue, 2 Jun 2026 15:28:09 +0200 Subject: [PATCH 59/77] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 529a7eeb..05c6ce81 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ Graphical front ends: - **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 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. From 383ea30ba9ef4a73d2abab8e76cb94d7a36008a1 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Tue, 2 Jun 2026 20:42:26 +0200 Subject: [PATCH 60/77] Consolidate pcm-sensor-server metric docs into PCM-EXPORTER guide (#952) * Consolidate sensor server docs into PCM exporter guide * Update PCM-EXPORTER.md * Fix PCM exporter metric descriptions per review --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Roman Dementiev --- doc/PCM-EXPORTER.md | 50 +++++++++++++++++++++++++++++++++ doc/PCM-SENSOR-SERVER-README.md | 48 ++----------------------------- 2 files changed, 52 insertions(+), 46 deletions(-) diff --git a/doc/PCM-EXPORTER.md b/doc/PCM-EXPORTER.md index c8d4327e..bd375871 100644 --- a/doc/PCM-EXPORTER.md +++ b/doc/PCM-EXPORTER.md @@ -67,3 +67,53 @@ The default output of pcm-sensor-server endpoint in a browser: The PCM exporter can be used together with Grafana to obtain these Intel processor metrics (see [how-to](../scripts/grafana/README.md)): ![pcm grafana output](https://raw.githubusercontent.com/wiki/intel/pcm/pcm-dashboard-full.png) + +# Low-Level Metric Reference + +## Global PCM Events + +| Event Name | Description | +|-----------------------------|-----------------------------------------------------------------------------| +| Measurement_Interval_in_us | How many us elapsed to complete the last measurement | +| Number_of_sockets | Number of CPU sockets in the system | + +## Core Counters per socket + +OS_ID is the OS assigned ID of the logical CPU core and denotes the socket id, core id and thread id. + +The events below are followed by the same {socket="socket id",core="core id",thread="thread id"} as +the OS_ID of their section with source="socket/core/thread" appended that denotes what the quantity +of the event accounts for. + +For example Instructions_Retired_Any{socket="0",core="1",thread="1",source="core"} refers to +Instructions_Retired_Any for socket 0, core 1, thread 1, and accounts for the total instructions +retired of the specified core. + +| Event | Description | +|------------------------------------------------|--------------------------------------------------------------| +| Instructions_Retired_Any | Total number of Retired instructions | +| Clock_Unhalted_Thread | Counts the number of core cycles while the thread is not | +| | in a halt state. | +| Clock_Unhalted_Ref | Counts the number of reference cycles that the thread is | +| | not in a halt state. The thread enters the halt state when | +| | it is running the HLT instruction. This event is not | +| | affected by thread frequency changes but counts as if the | +| | thread is running at the maximum frequency all the time. | +| L3_Cache_Misses | Total number of L3 Cache misses | +| L3_Cache_Hits | Total number of L3 Cache hits | +| L2_Cache_Misses | Total number of L2 Cache misses | +| L2_Cache_Hits | Total number of L2 Cache hits | +| L3_Cache_Occupancy | Computes L3 Cache Occupancy | +| SMI_Count | SMI (System Management Interrupt) count | +| Invariant_TSC | Calculates the invariant TSC clocks (the invariant TSC | +| | means that the TSC continues at a fixed rate regardless of | +| | the C-state or frequency of the processor as long as the | +| | processor remains in the ACPI S0 state. | +| Thermal_Headroom | Celsius degrees before reaching TjMax temperature | +| CStateResidency | This is the percentage of time that the core (or the whole | +| | package) spends in a particular level of C-state | + +References: + +https://software.intel.com/content/www/us/en/develop/articles/intel-performance-counter-monitor.html +https://software.intel.com/content/dam/develop/external/us/en/documents-tps/325384-sdm-vol-3abcd.pdf - Chapter 18 Performance Monitoring diff --git a/doc/PCM-SENSOR-SERVER-README.md b/doc/PCM-SENSOR-SERVER-README.md index 16cbf717..da72de5a 100644 --- a/doc/PCM-SENSOR-SERVER-README.md +++ b/doc/PCM-SENSOR-SERVER-README.md @@ -1,47 +1,3 @@ -# Global PCM Events +# PCM Sensor Server Metric Reference -| Event Name | Description | -|-----------------------------|-----------------------------------------------------------------------------| -| Measurement_Interval_in_us | How many us elapsed to complete the last measurement | -| Number_of_sockets | Number of CPU sockets in the system | - - -# Core Counters per socket - -OS_ID is the OS assigned ID of the logical CPU core and denotes the socket id, core id and thread id. - -The events below are followed by the same {socket="socket id",core="core id",thread="thread id"} as -the OS_ID of their section with source="socket/core/thread" appended that denotes what the quantity -of the event accounts for. - -For example Instructions_Retired_Any{socket="0",core="1",thread="1",source="core"} refers to -Instructions_Retired_Any for socket 0, core 1, thread 1, and accounts for the total instructions -retired of the specified core. - -| Event | Description | -|------------------------------------------------|--------------------------------------------------------------| -| Instructions_Retired_Any | Total number of Retired instructions | -| Clock_Unhalted_Thread | | -| Clock_Unhalted_Ref | Counts the number of reference cycles that the thread is | -| | not in a halt state. The thread enters the halt state when | -| | it is running the HLT instruction. This event is not | -| | affected by thread frequency changes but counts as if the | -| | thread is running at the maximum frequency all the time. | -| L3_Cache_Misses | Total number of L3 Cache misses | -| L3_Cache_Hits | Total number of L3 Cache hits | -| L2_Cache_Misses | Total number of L2 Cache misses | -| L2_Cache_Hits | Total number of L3 Cache hits | -| L3_Cache_Occupancy | Computes L3 Cache Occupancy | -| SMI_Count | SMI (System Management Interrupt) count | -| Invariant_TSC | Calculates the invariant TSC clocks (the invariant TSC | -| | means that the TSC continues at a fixed rate regardless of | -| | the C-state or frequency of the processor as long as the | -| | processor remains in the ACPI S0 state. | -| Thermal_Headroom | Celsius degrees before reaching TjMax temperature | -| CStateResidency | This is the percentage of time that the core (or the whole | -| | package) spends in a particular level of C-state | | - -References: - -https://software.intel.com/content/www/us/en/develop/articles/intel-performance-counter-monitor.html -https://software.intel.com/content/dam/develop/external/us/en/documents-tps/325384-sdm-vol-3abcd.pdf - Chapter 18 Performance Monitoring \ No newline at end of file +The PCM sensor server metric documentation has moved to [PCM-EXPORTER.md](PCM-EXPORTER.md). From f9b7f4b88ee820af380be2688b6f96e5636373dc Mon Sep 17 00:00:00 2001 From: "Dementiev, Roman" Date: Wed, 3 Jun 2026 11:19:18 +0200 Subject: [PATCH 61/77] add CWF support Change-Id: I5d7edbcad48b0c3135f5e4e58c808633dfc75599 Co-authored-by: Alexander Antonov Co-authored-by: Roman Dementiev --- .../GenuineIntel-6-DD-0.json | 145 ++++++++++++++++++ src/cpucounters.cpp | 54 ++++++- src/cpucounters.h | 23 +++ src/opCode-6-221.txt | 46 ++++++ src/pcm-iio-pmu.cpp | 1 + src/pcm-memory.cpp | 4 + src/pcm-pcie.cpp | 2 - src/pcm-pcie.h | 1 + src/pcm-power.cpp | 3 + 9 files changed, 273 insertions(+), 6 deletions(-) create mode 100644 src/PMURegisterDeclarations/GenuineIntel-6-DD-0.json create mode 100644 src/opCode-6-221.txt 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/cpucounters.cpp b/src/cpucounters.cpp index 5decbaaf..818a21e0 100644 --- a/src/cpucounters.cpp +++ b/src/cpucounters.cpp @@ -775,6 +775,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 +835,7 @@ void PCM::initCStateSupportTables() case LNL: case ARL: case PTL: + case CWF: case SNOWRIDGE: case ELKHART_LAKE: case JASPER_LAKE: @@ -1736,6 +1738,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 +2083,7 @@ void PCM::initUncoreObjects() } switch (cpu_family_model) { + case CWF: case ICX: case SNOWRIDGE: case SPR: @@ -2301,6 +2305,7 @@ void PCM::initUncorePMUsDirect() case SRF: case GNR: case GNR_D: + case CWF: uncorePMUs[s].resize(1); { std::vector > CounterControlRegs{ @@ -2454,6 +2459,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 +2488,7 @@ void PCM::initUncorePMUsDirect() case GNR: case GNR_D: case SRF: + case CWF: addMDFPMUs(BHS_MDF_BOX_TYPE); break; } @@ -2531,6 +2538,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 +2645,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 +2877,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 +3020,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 +3518,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 +3862,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 +5122,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: @@ -5968,6 +5983,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 +6005,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 +6029,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 +6047,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 +6079,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 +6092,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 @@ -8218,6 +8237,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 +8479,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 +8683,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 +8774,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 +8941,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 +9483,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 +9581,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 +9716,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 +9736,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 +9854,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 +9868,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 +9878,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 +9986,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 @@ -10308,6 +10340,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 +10462,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 +10496,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 +10543,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 +10576,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 +10653,7 @@ uint32 PCM::getMaxNumOfCBoxesInternal() const uint64 val = 0; switch (cpu_family_model) { + case CWF: case GRR: case GNR: case GNR_D: @@ -10743,6 +10781,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 +10878,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 +10930,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 +11198,7 @@ bool PCM::supportIDXAccelDev() const switch (this->getCPUFamilyModel()) { + case PCM::CWF: case PCM::SPR: case PCM::EMR: case PCM::GNR: @@ -11402,6 +11444,7 @@ void UncorePMU::freeze(const uint32 extra) { switch (getCPUFamilyModel()) { + case PCM::CWF: case PCM::SPR: case PCM::EMR: case PCM::GNR: @@ -11419,6 +11462,7 @@ void UncorePMU::unfreeze(const uint32 extra) { switch (getCPUFamilyModel()) { + case PCM::CWF: case PCM::SPR: case PCM::EMR: case PCM::GNR: @@ -11441,6 +11485,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 +11527,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..63fd49b4 100644 --- a/src/cpucounters.h +++ b/src/cpucounters.h @@ -1268,6 +1268,7 @@ class PCM_API PCM { switch (cpu_family_model) { + case CWF: case SPR: case EMR: case GNR: @@ -1756,6 +1757,7 @@ class PCM_API PCM case ELKHART_LAKE: case JASPER_LAKE: case SRF: + case CWF: case GRR: return eCoreOCREvent; } @@ -1950,6 +1952,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), @@ -2113,6 +2116,7 @@ class PCM_API PCM case NEHALEM_EX: case WESTMERE_EX: return 4; + case CWF: case JAKETOWN: case IVYTOWN: case HASWELLX: @@ -2143,6 +2147,7 @@ class PCM_API PCM case NEHALEM_EX: case WESTMERE_EX: return 2; + case CWF: case JAKETOWN: case IVYTOWN: case HASWELLX: @@ -2174,6 +2179,7 @@ class PCM_API PCM case NEHALEM_EX: case WESTMERE_EX: return 4; + case CWF: case JAKETOWN: case IVYTOWN: case HASWELLX: @@ -2208,6 +2214,7 @@ class PCM_API PCM case NEHALEM_EX: case WESTMERE_EX: return 4; + case CWF: case JAKETOWN: case IVYTOWN: case HASWELLX: @@ -2256,6 +2263,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 +2335,7 @@ class PCM_API PCM { switch (cpu_family_model) { + case CWF: case NEHALEM_EP: case NEHALEM_EX: case WESTMERE_EP: @@ -2585,6 +2595,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 +2636,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 +2683,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 +2703,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 +2719,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 +2741,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 +2785,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 @@ -2863,6 +2881,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 +2910,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 +2936,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 +4541,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 +4659,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/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/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-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..1b457505 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[]) 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: From a62c5d9778cfc5426a785596299eab86874235d9 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Wed, 3 Jun 2026 18:54:17 +0200 Subject: [PATCH 62/77] Remove pcm-sensor (KSysGuard plugin) and drop realtime async-counter example files (#951) --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Roman Dementiev --- README.md | 1 - pcm.spec | 1 - src/CMakeLists.txt | 1 - src/cpuasynchcounter.h | 212 ------------- src/pcm-sensor.cpp | 679 ----------------------------------------- src/realtime.cpp | 275 ----------------- 6 files changed, 1169 deletions(-) delete mode 100644 src/cpuasynchcounter.h delete mode 100644 src/pcm-sensor.cpp delete mode 100644 src/realtime.cpp diff --git a/README.md b/README.md index 0a1b8237..ca296be8 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,6 @@ 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. diff --git a/pcm.spec b/pcm.spec index b234980d..070ff8e1 100644 --- a/pcm.spec +++ b/pcm.spec @@ -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/src/CMakeLists.txt b/src/CMakeLists.txt index 95447f1f..88c3283e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -41,7 +41,6 @@ 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) # libpcm.a add_library(PCM_STATIC STATIC ${COMMON_SOURCES} ${UNIX_SOURCES}) 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/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; -} From 45bbcbaaac7b2ae8ca2bc90b28d2b035bb7457aa Mon Sep 17 00:00:00 2001 From: Roman Dementiev Date: Wed, 3 Jun 2026 19:10:33 +0200 Subject: [PATCH 63/77] Add a security warning into PCM-EXPORTER.md (#953) --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- doc/PCM-EXPORTER.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/doc/PCM-EXPORTER.md b/doc/PCM-EXPORTER.md index bd375871..f815f713 100644 --- a/doc/PCM-EXPORTER.md +++ b/doc/PCM-EXPORTER.md @@ -63,6 +63,11 @@ The default output of pcm-sensor-server endpoint in a browser: ![image](https://user-images.githubusercontent.com/25432609/226344012-8783e154-998e-48a7-a2ca-f2c42af9c843.png) +## Security Warning + +pcm-sensor-server collects and serves internal CPU metric information on the system. Do not expose its HTTP/HTTPS endpoints to untrusted or publicly accessible networks. Prefer binding to localhost or a dedicated management interface (see `-l|--listen` above), and use firewalling and/or an authenticated reverse proxy if remote access is required. High request rates can overload the host and lead to a denial of service. + +## Integration with Grafana The PCM exporter can be used together with Grafana to obtain these Intel processor metrics (see [how-to](../scripts/grafana/README.md)): From ea7f27aad6efb95e3f405988022d95f517dfad5d Mon Sep 17 00:00:00 2001 From: "Dementiev, Roman" Date: Mon, 15 Jun 2026 10:41:44 +0200 Subject: [PATCH 64/77] update perfmon version Change-Id: If72c38472bd7f2618138ecddc72d99ef9b7a4306 --- perfmon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/perfmon b/perfmon index 48c0518a..1cc8f8ae 160000 --- a/perfmon +++ b/perfmon @@ -1 +1 @@ -Subproject commit 48c0518ae223c49b6a698325167a4d8d40f56997 +Subproject commit 1cc8f8aee966dbd2938dabb8fe81c747e2d39966 From da378d22e7eb3ad8949694134acae33f4b46502e Mon Sep 17 00:00:00 2001 From: "Dementiev, Roman" Date: Mon, 15 Jun 2026 10:57:05 +0200 Subject: [PATCH 65/77] Annotate DDRConfig latency events with official perfmon names Add official perfmon event names (from perfmon/{ICX,SPR,EMR,SKX,CLX}/events/*_uncore.json) as comments next to the hard-coded iMC uncore event codes in programServerUncoreLatencyMetrics. Change-Id: I97c56087236b7909a0423b0500744899ff8ffacd Co-Authored-By: Claude Opus 4.8 --- src/cpucounters.cpp | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/src/cpucounters.cpp b/src/cpucounters.cpp index 818a21e0..2e10653b 100644 --- a/src/cpucounters.cpp +++ b/src/cpucounters.cpp @@ -5925,24 +5925,27 @@ 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 - - } else { - - 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 - } + { + // Official perfmon event names (ICX/SPR/EMR iMC uncore, see perfmon/{ICX,SPR,EMR}/events/*_uncore.json): + 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(0x81) + MC_CH_PCI_PMON_CTL_UMASK(0); // DRAM WPQ Occupancy -> UNC_M_RPQ_OCCUPANCY_PCH1 (event 0x81 is RPQ occupancy PCH1 on ICX/SPR/EMR; WPQ occupancy moved to 0x82/0x83) + DDRConfig[3] = MC_CH_PCI_PMON_CTL_EVENT(0x20) + MC_CH_PCI_PMON_CTL_UMASK(0); // DRAM WPQ Insert -> UNC_M_WPQ_INSERTS (UMASK 0 selects both PCH0|PCH1) + + } else { + + // Official perfmon event names (SKX/CLX iMC uncore, see perfmon/{SKX,CLX}/events/*_uncore.json): + 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 + } } 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 perfmon/{ICX,SPR,EMR}/events/*_uncore.json): + 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()) From eb3273dc9416dab3ef5ce9d616b4f92482ddb0df Mon Sep 17 00:00:00 2001 From: "Dementiev, Roman" Date: Mon, 15 Jun 2026 11:04:23 +0200 Subject: [PATCH 66/77] Add GNR/GNR-D/SRF/CWF support to programServerUncoreLatencyMetrics These CPUs use a different iMC uncore layout (SCH0/SCH1 sub-channels, PCH0/PCH1 pseudo-channels) with WPQ occupancy/inserts on new event codes (0x84/0x22). Add a dedicated DDR branch using the SCH0_PCH0 variants and enable DDRLatencyMetricsAvailable() for these models. Event codes verified against perfmon/{GNR,SRF,CWF}/events/*_uncore.json. Change-Id: I9ef5366c2f640e1cbf7b06d28acec40242749e84 Co-Authored-By: Claude Opus 4.8 --- src/cpucounters.cpp | 12 +++++++++++- src/cpucounters.h | 4 ++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/cpucounters.cpp b/src/cpucounters.cpp index 2e10653b..adf1ca1d 100644 --- a/src/cpucounters.cpp +++ b/src/cpucounters.cpp @@ -5924,7 +5924,17 @@ 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) + if (GNR == cpu_family_model || GNR_D == cpu_family_model || SRF == cpu_family_model || CWF == cpu_family_model) + { + // Official perfmon event names (GNR/GNR-D/SRF/CWF iMC uncore, see perfmon/{GNR,SRF,CWF}/events/*_uncore.json): + // 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 + + } else if (ICX == cpu_family_model || SPR == cpu_family_model || EMR == cpu_family_model) { // Official perfmon event names (ICX/SPR/EMR iMC uncore, see perfmon/{ICX,SPR,EMR}/events/*_uncore.json): DDRConfig[0] = MC_CH_PCI_PMON_CTL_EVENT(0x80) + MC_CH_PCI_PMON_CTL_UMASK(1); // DRAM RPQ occupancy -> UNC_M_RPQ_OCCUPANCY_PCH0 diff --git a/src/cpucounters.h b/src/cpucounters.h index 2080c79f..4aa9dd7f 100644 --- a/src/cpucounters.h +++ b/src/cpucounters.h @@ -2826,6 +2826,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 ); } From c3be63fd6b3acbd402ae0a10557a72f28d6e3a83 Mon Sep 17 00:00:00 2001 From: "Dementiev, Roman" Date: Mon, 15 Jun 2026 11:05:59 +0200 Subject: [PATCH 67/77] Refactor latency-metrics CPU-family branching into a switch Replace the if/else if/else chain in programServerUncoreLatencyMetrics with a switch on cpu_family_model. No behavior change. Change-Id: I69673f7c17fb2157bc795043cc5053ddaa6db21a Co-Authored-By: Claude Opus 4.8 --- src/cpucounters.cpp | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/cpucounters.cpp b/src/cpucounters.cpp index adf1ca1d..0397e2fd 100644 --- a/src/cpucounters.cpp +++ b/src/cpucounters.cpp @@ -5924,8 +5924,12 @@ PCM::ErrorCode PCM::programServerUncoreLatencyMetrics(bool enable_pmm) if (enable_pmm == false) { //DDR is false - if (GNR == cpu_family_model || GNR_D == cpu_family_model || SRF == cpu_family_model || CWF == cpu_family_model) - { + 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 perfmon/{GNR,SRF,CWF}/events/*_uncore.json): // 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). @@ -5933,23 +5937,26 @@ PCM::ErrorCode PCM::programServerUncoreLatencyMetrics(bool enable_pmm) 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 if (ICX == cpu_family_model || SPR == cpu_family_model || EMR == cpu_family_model) - { + case ICX: + case SPR: + case EMR: // Official perfmon event names (ICX/SPR/EMR iMC uncore, see perfmon/{ICX,SPR,EMR}/events/*_uncore.json): 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(0x81) + MC_CH_PCI_PMON_CTL_UMASK(0); // DRAM WPQ Occupancy -> UNC_M_RPQ_OCCUPANCY_PCH1 (event 0x81 is RPQ occupancy PCH1 on ICX/SPR/EMR; WPQ occupancy moved to 0x82/0x83) DDRConfig[3] = MC_CH_PCI_PMON_CTL_EVENT(0x20) + MC_CH_PCI_PMON_CTL_UMASK(0); // DRAM WPQ Insert -> UNC_M_WPQ_INSERTS (UMASK 0 selects both PCH0|PCH1) + break; - } else { - + default: // Official perfmon event names (SKX/CLX iMC uncore, see perfmon/{SKX,CLX}/events/*_uncore.json): 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 { // Official perfmon event names (PMM/DCPMM iMC uncore; ICX names, SPR/EMR use the *_SCH0 suffixed variants, see perfmon/{ICX,SPR,EMR}/events/*_uncore.json): 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) From fa369109862c0804f7512583d36a2888f6ecf426 Mon Sep 17 00:00:00 2001 From: "Dementiev, Roman" Date: Mon, 15 Jun 2026 11:15:15 +0200 Subject: [PATCH 68/77] Replace magic event encodings in pcm-latency with named constexpr Name the core PMU event/umask encodings used for the L1 fill-buffer latency metric in build_core_register. Express the inserts umask as FB_HIT | L1_MISS instead of a precomputed 0x48. Encodings verified against the perfmon core JSONs (valid on P-core parts; E-core SRF/CWF lack these events and remain excluded from LatencyMetricsAvailable). Change-Id: I5cacad6b58375ccffb4828947a79f532da30cb90 Co-Authored-By: Claude Opus 4.8 --- src/pcm-latency.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/pcm-latency.cpp b/src/pcm-latency.cpp index 144d6ed8..d83a3c54 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; @@ -401,8 +411,8 @@ void build_registers(PCM *m, PCM::ExtendedCustomCoreEventDescription conf, bool 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) + 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) //Restructuring Counters for (int i=0; i <5; i++) From 79f962d26541a9a82510a7c225f08b3095766ebc Mon Sep 17 00:00:00 2001 From: "Dementiev, Roman" Date: Mon, 15 Jun 2026 11:16:14 +0200 Subject: [PATCH 69/77] Enable core L1 latency metrics on GNR/GNR-D Add GNR/GNR-D to LatencyMetricsAvailable now that their P-core L1D_PEND_MISS / MEM_LOAD_RETIRED encodings are confirmed to match the ones used in pcm-latency. E-core SRF/CWF stay excluded (they lack the fill-buffer-occupancy event and use different umasks). Change-Id: I1e208fce34ff009211ca16712795c6d5f7a2bf33 Co-Authored-By: Claude Opus 4.8 --- src/cpucounters.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/cpucounters.h b/src/cpucounters.h index 4aa9dd7f..35d9122b 100644 --- a/src/cpucounters.h +++ b/src/cpucounters.h @@ -2808,6 +2808,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 @@ -2815,6 +2819,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() ); } From 2883988966002aef8e971b2710a6f7fad3fa9f8f Mon Sep 17 00:00:00 2001 From: "Dementiev, Roman" Date: Mon, 15 Jun 2026 11:45:26 +0200 Subject: [PATCH 70/77] Support DDR-only latency on parts without core L1 metrics (SRF/CWF) Decouple the core L1 fill-buffer latency path from the DDR uncore latency path in pcm-latency. The platform gate now permits parts that support only DDR latency, and core-event programming, collection, and printing are skipped when LatencyMetricsAvailable() is false. On such parts (e.g. the E-core SRF/CWF) the tool now reports DDR read/write latency instead of aborting with "Platform not Supported". P-core behavior is unchanged. Change-Id: I5e282202b897b1bddff9bfd1d5e24bc6bef3fd40 Co-Authored-By: Claude Opus 4.8 --- src/pcm-latency.cpp | 124 +++++++++++++++++++++++++++----------------- 1 file changed, 75 insertions(+), 49 deletions(-) diff --git a/src/pcm-latency.cpp b/src/pcm-latency.cpp index d83a3c54..3cc22182 100644 --- a/src/pcm-latency.cpp +++ b/src/pcm-latency.cpp @@ -210,13 +210,16 @@ 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) { @@ -320,34 +323,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()) { @@ -374,7 +380,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); @@ -397,22 +403,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, 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) + // 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++) @@ -424,9 +419,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->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) @@ -435,18 +456,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; From 5fa1b29ab3d0a62c67ca590c749594022f938eca Mon Sep 17 00:00:00 2001 From: "Dementiev, Roman" Date: Mon, 15 Jun 2026 11:51:37 +0200 Subject: [PATCH 71/77] Fix wrong WPQ occupancy event for ICX/SPR/EMR latency metrics DDRConfig[2] used event 0x81 (UNC_M_RPQ_OCCUPANCY_PCH1, a read queue) while labeled "WPQ Occupancy". The write-queue occupancy on ICX/SPR/EMR is event 0x82 (UNC_M_WPQ_OCCUPANCY_PCH0). Also set the WPQ insert umask to PCH0 so both write counters cover the same pseudo-channel as the read pair, making the write-latency occupancy/inserts ratio meaningful. Verified against perfmon/{ICX,SPR,EMR}/events/*_uncore.json. Change-Id: Ib1f4fec9c83caa4c47561be88cb98e126e4bd4dc Co-Authored-By: Claude Opus 4.8 --- src/cpucounters.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cpucounters.cpp b/src/cpucounters.cpp index 0397e2fd..a88fb26d 100644 --- a/src/cpucounters.cpp +++ b/src/cpucounters.cpp @@ -5945,8 +5945,8 @@ PCM::ErrorCode PCM::programServerUncoreLatencyMetrics(bool enable_pmm) // Official perfmon event names (ICX/SPR/EMR iMC uncore, see perfmon/{ICX,SPR,EMR}/events/*_uncore.json): 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(0x81) + MC_CH_PCI_PMON_CTL_UMASK(0); // DRAM WPQ Occupancy -> UNC_M_RPQ_OCCUPANCY_PCH1 (event 0x81 is RPQ occupancy PCH1 on ICX/SPR/EMR; WPQ occupancy moved to 0x82/0x83) - DDRConfig[3] = MC_CH_PCI_PMON_CTL_EVENT(0x20) + MC_CH_PCI_PMON_CTL_UMASK(0); // DRAM WPQ Insert -> UNC_M_WPQ_INSERTS (UMASK 0 selects both PCH0|PCH1) + 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; default: From 84d051a2a27fcbd23ea4665d12e44461a4942666 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 15 Jun 2026 11:55:12 +0000 Subject: [PATCH 72/77] Address review: guard verbose socket indexing, handle latency programming errors, reword perfmon path comments --- src/cpucounters.cpp | 10 ++++++---- src/pcm-latency.cpp | 38 +++++++++++++++++++++----------------- 2 files changed, 27 insertions(+), 21 deletions(-) diff --git a/src/cpucounters.cpp b/src/cpucounters.cpp index a88fb26d..529c5b79 100644 --- a/src/cpucounters.cpp +++ b/src/cpucounters.cpp @@ -5930,7 +5930,7 @@ PCM::ErrorCode PCM::programServerUncoreLatencyMetrics(bool enable_pmm) case GNR_D: case SRF: case CWF: - // Official perfmon event names (GNR/GNR-D/SRF/CWF iMC uncore, see perfmon/{GNR,SRF,CWF}/events/*_uncore.json): + // 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 @@ -5942,7 +5942,7 @@ PCM::ErrorCode PCM::programServerUncoreLatencyMetrics(bool enable_pmm) case ICX: case SPR: case EMR: - // Official perfmon event names (ICX/SPR/EMR iMC uncore, see perfmon/{ICX,SPR,EMR}/events/*_uncore.json): + // 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) @@ -5950,7 +5950,7 @@ PCM::ErrorCode PCM::programServerUncoreLatencyMetrics(bool enable_pmm) break; default: - // Official perfmon event names (SKX/CLX iMC uncore, see perfmon/{SKX,CLX}/events/*_uncore.json): + // 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 @@ -5958,7 +5958,7 @@ PCM::ErrorCode PCM::programServerUncoreLatencyMetrics(bool enable_pmm) break; } } else { - // Official perfmon event names (PMM/DCPMM iMC uncore; ICX names, SPR/EMR use the *_SCH0 suffixed variants, see perfmon/{ICX,SPR,EMR}/events/*_uncore.json): + // 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) @@ -5967,6 +5967,8 @@ PCM::ErrorCode PCM::programServerUncoreLatencyMetrics(bool enable_pmm) 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); diff --git a/src/pcm-latency.cpp b/src/pcm-latency.cpp index 3cc22182..27a7bec0 100644 --- a/src/pcm-latency.cpp +++ b/src/pcm-latency.cpp @@ -224,29 +224,33 @@ void print_verbose(PCM *m, int ddr_ip) 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"; + } } } @@ -440,7 +444,7 @@ void build_registers(PCM *m, PCM::ExtendedCustomCoreEventDescription conf, bool PCM::ErrorCode status = m->program(PCM::EXT_CUSTOM_CORE_EVENTS, &conf); check_status(m, status); - m->programServerUncoreLatencyMetrics(enable_pmm); + m->checkError(m->programServerUncoreLatencyMetrics(enable_pmm)); } else { From 620c664101bfcbfad0e112a2b5a779e9b21553cd Mon Sep 17 00:00:00 2001 From: "Dementiev, Roman" Date: Tue, 16 Jun 2026 15:28:36 +0200 Subject: [PATCH 73/77] update Intel-PMT Change-Id: Ie645a74efe859486be17bfa52ed28fca8505620e --- Intel-PMT | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Intel-PMT b/Intel-PMT index 269d10ff..d98b63ec 160000 --- a/Intel-PMT +++ b/Intel-PMT @@ -1 +1 @@ -Subproject commit 269d10ffec83a5b0498060ccdd72f6add6892405 +Subproject commit d98b63ec38a1e0b7617e5656c7ce9388a27b3ed7 From 5c586671b561b9897b3bea09182da93e8a0e2009 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 2 Jun 2026 12:05:52 +0000 Subject: [PATCH 74/77] Fix /persecond/30 unsatisfiable-wait DoS in pcm-sensor-server --- src/pcm-sensor-server.cpp | 39 +++++++-- tests/utests/CMakeLists.txt | 10 +++ .../pcm-sensor-server-persecond-utest.cpp | 82 +++++++++++++++++++ 3 files changed, 125 insertions(+), 6 deletions(-) create mode 100644 tests/utests/pcm-sensor-server-persecond-utest.cpp diff --git a/src/pcm-sensor-server.cpp b/src/pcm-sensor-server.cpp index 6540f569..4960b004 100644 --- a/src/pcm-sensor-server.cpp +++ b/src/pcm-sensor-server.cpp @@ -3499,7 +3499,17 @@ 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 ( 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() ); + 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)."; @@ -3594,6 +3604,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 ); @@ -3652,7 +3673,7 @@ class HTTPServer : public Server { agVectorMutex_.lock(); agVector_.insert( agVector_.begin(), agp ); - if ( agVector_.size() > 30 ) { + if ( agVector_.size() > maxAggregators_ ) { DBG( 4, "HTTPServer::addAggregator(): Removing last Aggegator" ); agVector_.pop_back(); } @@ -3663,6 +3684,12 @@ class HTTPServer : public Server { if ( index == index2 ) throw std::runtime_error("BUG: getAggregator: both indices are equal. Fix the code!" ); + // 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!" ); + // 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)); @@ -4178,7 +4205,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\ @@ -4237,11 +4264,11 @@ void my_get_callback( HTTPServer* hs, HTTPRequest const & req, HTTPResponse & re 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; } diff --git a/tests/utests/CMakeLists.txt b/tests/utests/CMakeLists.txt index 89db8a32..4239ce12 100644 --- a/tests/utests/CMakeLists.txt +++ b/tests/utests/CMakeLists.txt @@ -16,6 +16,7 @@ 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_PERSECOND_TEST_FILES pcm-sensor-server-persecond-utest.cpp) set(LIBS Threads::Threads PCM_STATIC) @@ -25,6 +26,7 @@ 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-persecond-utest ${PCM_SENSOR_SERVER_PERSECOND_TEST_FILES}) configure_file( ${CMAKE_SOURCE_DIR}/src/opCode-6-174.txt @@ -86,6 +88,13 @@ target_link_libraries( ${LIBS} ) +target_link_libraries( + pcm-sensor-server-persecond-utest + GTest::gtest_main + GTest::gmock_main + ${LIBS} +) + include(GoogleTest) gtest_discover_tests(lspci-utest) gtest_discover_tests(pcm-iio-utest) @@ -93,3 +102,4 @@ 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-persecond-utest) diff --git a/tests/utests/pcm-sensor-server-persecond-utest.cpp b/tests/utests/pcm-sensor-server-persecond-utest.cpp new file mode 100644 index 00000000..9a9656c9 --- /dev/null +++ b/tests/utests/pcm-sensor-server-persecond-utest.cpp @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2009-2025, Intel Corporation + +// Regression test for the /persecond/30 unsatisfiable-wait denial of service +// in pcm-sensor-server (src/pcm-sensor-server.cpp). +// +// The internal aggregator history is permanently capped at +// HTTPServer::maxAggregators_ entries (see addAggregator(), which pops the +// oldest entry once the size exceeds the cap). Answering /persecond/X compares +// the newest sample (index 0) with the sample X seconds earlier (index X), +// which requires X + 1 retained entries. The largest X that can ever be +// satisfied is therefore maxAggregators_ - 1. +// +// The original code validated the user-controlled seconds value against the +// retention cap itself (<= 30) instead of the largest satisfiable index +// (<= 29). As a result /persecond/30 entered getAggregators(30, 0) whose wait +// loop required 31 retained samples that the history could never hold, blocking +// the worker thread forever. With 64 such requests an unauthenticated remote +// attacker could exhaust the whole worker pool (CWE-835). +// +// This test pins the invariant that the accepted /persecond bound stays +// strictly below the retention capacity (so every accepted request is +// satisfiable), and that getAggregators() fails fast instead of blocking +// forever when handed an index that can never be retained. + +#define UNIT_TEST 1 +#include "../../src/pcm-sensor-server.cpp" +#undef UNIT_TEST + +#include + +namespace { + +// The largest accepted /persecond/X value must be strictly smaller than the +// retention capacity, otherwise the newest/oldest comparison needs one more +// sample than the history can ever hold and getAggregators() blocks forever. +TEST(PcmSensorServerPerSecondTest, AcceptedBoundIsSatisfiableGivenRetentionCap) +{ + static_assert(HTTPServer::maxPerSecondSeconds_ < HTTPServer::maxAggregators_, + "Accepted /persecond bound must be strictly below the " + "retention cap so every accepted request is satisfiable."); + + // The most demanding accepted request, /persecond/maxPerSecondSeconds_, + // needs maxPerSecondSeconds_ + 1 retained samples. That must fit within the + // retention capacity. + EXPECT_LE(HTTPServer::maxPerSecondSeconds_ + 1, HTTPServer::maxAggregators_); + + // The first index that can never be satisfied (the value that previously + // wedged a worker thread) is exactly the retention cap. + EXPECT_EQ(HTTPServer::maxAggregators_, HTTPServer::maxPerSecondSeconds_ + 1); +} + +// getAggregators() must throw (fail fast) for any index that can never be +// retained, instead of spinning forever in its wait loop. Without an instance +// we cannot call the member directly, but we can validate the same wait +// condition the handler relies on: filling the history to its cap never makes +// an out-of-range index reachable. +TEST(PcmSensorServerPerSecondTest, HistoryNeverReachesUnsatisfiableIndex) +{ + // Mirror addAggregator()'s retention behaviour on a standalone vector so we + // can assert the cap without constructing a server. This documents the + // exact off-by-one: after inserting far more than the cap, the size is + // pinned at maxAggregators_, so index == maxAggregators_ is never valid. + std::vector history; + for (size_t i = 0; i < HTTPServer::maxAggregators_ * 4; ++i) { + history.insert(history.begin(), static_cast(i)); + if (history.size() > HTTPServer::maxAggregators_) + history.pop_back(); + } + + EXPECT_EQ(HTTPServer::maxAggregators_, history.size()); + + // The largest accepted request is satisfiable: it needs an index that is + // within the retained range. + EXPECT_LT(HTTPServer::maxPerSecondSeconds_, history.size()); + + // The previously accepted-but-unsatisfiable request (index == cap) is not + // within the retained range and would have blocked forever. + EXPECT_GE(HTTPServer::maxAggregators_, history.size()); +} + +} // namespace From aa27b21b90fbf0c8e2dee4f5bcd3ec11baf921cd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 2 Jun 2026 12:40:26 +0000 Subject: [PATCH 75/77] Remove useless pcm-sensor-server-persecond unit test --- tests/utests/CMakeLists.txt | 10 --- .../pcm-sensor-server-persecond-utest.cpp | 82 ------------------- 2 files changed, 92 deletions(-) delete mode 100644 tests/utests/pcm-sensor-server-persecond-utest.cpp diff --git a/tests/utests/CMakeLists.txt b/tests/utests/CMakeLists.txt index 4239ce12..89db8a32 100644 --- a/tests/utests/CMakeLists.txt +++ b/tests/utests/CMakeLists.txt @@ -16,7 +16,6 @@ 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_PERSECOND_TEST_FILES pcm-sensor-server-persecond-utest.cpp) set(LIBS Threads::Threads PCM_STATIC) @@ -26,7 +25,6 @@ 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-persecond-utest ${PCM_SENSOR_SERVER_PERSECOND_TEST_FILES}) configure_file( ${CMAKE_SOURCE_DIR}/src/opCode-6-174.txt @@ -88,13 +86,6 @@ target_link_libraries( ${LIBS} ) -target_link_libraries( - pcm-sensor-server-persecond-utest - GTest::gtest_main - GTest::gmock_main - ${LIBS} -) - include(GoogleTest) gtest_discover_tests(lspci-utest) gtest_discover_tests(pcm-iio-utest) @@ -102,4 +93,3 @@ 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-persecond-utest) diff --git a/tests/utests/pcm-sensor-server-persecond-utest.cpp b/tests/utests/pcm-sensor-server-persecond-utest.cpp deleted file mode 100644 index 9a9656c9..00000000 --- a/tests/utests/pcm-sensor-server-persecond-utest.cpp +++ /dev/null @@ -1,82 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -// Copyright (c) 2009-2025, Intel Corporation - -// Regression test for the /persecond/30 unsatisfiable-wait denial of service -// in pcm-sensor-server (src/pcm-sensor-server.cpp). -// -// The internal aggregator history is permanently capped at -// HTTPServer::maxAggregators_ entries (see addAggregator(), which pops the -// oldest entry once the size exceeds the cap). Answering /persecond/X compares -// the newest sample (index 0) with the sample X seconds earlier (index X), -// which requires X + 1 retained entries. The largest X that can ever be -// satisfied is therefore maxAggregators_ - 1. -// -// The original code validated the user-controlled seconds value against the -// retention cap itself (<= 30) instead of the largest satisfiable index -// (<= 29). As a result /persecond/30 entered getAggregators(30, 0) whose wait -// loop required 31 retained samples that the history could never hold, blocking -// the worker thread forever. With 64 such requests an unauthenticated remote -// attacker could exhaust the whole worker pool (CWE-835). -// -// This test pins the invariant that the accepted /persecond bound stays -// strictly below the retention capacity (so every accepted request is -// satisfiable), and that getAggregators() fails fast instead of blocking -// forever when handed an index that can never be retained. - -#define UNIT_TEST 1 -#include "../../src/pcm-sensor-server.cpp" -#undef UNIT_TEST - -#include - -namespace { - -// The largest accepted /persecond/X value must be strictly smaller than the -// retention capacity, otherwise the newest/oldest comparison needs one more -// sample than the history can ever hold and getAggregators() blocks forever. -TEST(PcmSensorServerPerSecondTest, AcceptedBoundIsSatisfiableGivenRetentionCap) -{ - static_assert(HTTPServer::maxPerSecondSeconds_ < HTTPServer::maxAggregators_, - "Accepted /persecond bound must be strictly below the " - "retention cap so every accepted request is satisfiable."); - - // The most demanding accepted request, /persecond/maxPerSecondSeconds_, - // needs maxPerSecondSeconds_ + 1 retained samples. That must fit within the - // retention capacity. - EXPECT_LE(HTTPServer::maxPerSecondSeconds_ + 1, HTTPServer::maxAggregators_); - - // The first index that can never be satisfied (the value that previously - // wedged a worker thread) is exactly the retention cap. - EXPECT_EQ(HTTPServer::maxAggregators_, HTTPServer::maxPerSecondSeconds_ + 1); -} - -// getAggregators() must throw (fail fast) for any index that can never be -// retained, instead of spinning forever in its wait loop. Without an instance -// we cannot call the member directly, but we can validate the same wait -// condition the handler relies on: filling the history to its cap never makes -// an out-of-range index reachable. -TEST(PcmSensorServerPerSecondTest, HistoryNeverReachesUnsatisfiableIndex) -{ - // Mirror addAggregator()'s retention behaviour on a standalone vector so we - // can assert the cap without constructing a server. This documents the - // exact off-by-one: after inserting far more than the cap, the size is - // pinned at maxAggregators_, so index == maxAggregators_ is never valid. - std::vector history; - for (size_t i = 0; i < HTTPServer::maxAggregators_ * 4; ++i) { - history.insert(history.begin(), static_cast(i)); - if (history.size() > HTTPServer::maxAggregators_) - history.pop_back(); - } - - EXPECT_EQ(HTTPServer::maxAggregators_, history.size()); - - // The largest accepted request is satisfiable: it needs an index that is - // within the retained range. - EXPECT_LT(HTTPServer::maxPerSecondSeconds_, history.size()); - - // The previously accepted-but-unsatisfiable request (index == cap) is not - // within the retained range and would have blocked forever. - EXPECT_GE(HTTPServer::maxAggregators_, history.size()); -} - -} // namespace From 5b54d9a959c73b58841f6af9ec459457db67652e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 2 Jun 2026 13:19:26 +0000 Subject: [PATCH 76/77] Fix three issues from code review: exception handler, data race, stoull conversion --- src/pcm-sensor-server.cpp | 41 +++++++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/src/pcm-sensor-server.cpp b/src/pcm-sensor-server.cpp index 4960b004..d1480a54 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 @@ -3501,12 +3502,20 @@ class HTTPConnection : public Work { if ( callback ) { try { (*callback)( hs_, request, response ); - } catch ( std::exception& e ) { + } 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 ); } @@ -3671,13 +3680,15 @@ 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() > maxAggregators_ ) { - 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 ) { @@ -3690,13 +3701,12 @@ class HTTPServer : public Server { if ( (std::max)( index, index2 ) >= maxAggregators_ ) throw std::runtime_error("BUG: getAggregator: requested index can never be satisfied. 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(); + // 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; } @@ -3742,6 +3752,7 @@ class HTTPServer : public Server { std::vector callbackList_; std::vector> agVector_; std::mutex agVectorMutex_; + std::condition_variable agVectorCV_; PeriodicCounterFetcher* pcf_; bool stopped_; }; @@ -4259,8 +4270,8 @@ 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; } From e8a9248408d68a085b2fa4219098a82d0a68cd81 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Fri, 19 Jun 2026 08:38:26 +0200 Subject: [PATCH 77/77] Bound HTTP header count in pcm-sensor-server request parser (#949) --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> --- src/pcm-sensor-server.cpp | 15 ++ tests/utests/CMakeLists.txt | 10 ++ .../pcm-sensor-server-header-limits-utest.cpp | 161 ++++++++++++++++++ 3 files changed, 186 insertions(+) create mode 100644 tests/utests/pcm-sensor-server-header-limits-utest.cpp diff --git a/src/pcm-sensor-server.cpp b/src/pcm-sensor-server.cpp index d1480a54..9d33ac21 100644 --- a/src/pcm-sensor-server.cpp +++ b/src/pcm-sensor-server.cpp @@ -2868,6 +2868,11 @@ 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, @@ -3121,6 +3126,7 @@ basic_socketstream& operator>>( basic_socketstream std::string line; std::string concatLine; size_t totalHeaderBytes = 0; + size_t headerCount = 0; bool haveCurrentHeader = false; while ( true ) { readLineBounded( rs, line, kMaxHeaderLineBytes, requestDeadline ); @@ -3165,6 +3171,9 @@ basic_socketstream& operator>>( basic_socketstream 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; @@ -3189,6 +3198,9 @@ basic_socketstream& operator>>( basic_socketstream 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(); } @@ -3301,6 +3313,9 @@ basic_socketstream& operator>>( basic_socketstream // 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 ); diff --git a/tests/utests/CMakeLists.txt b/tests/utests/CMakeLists.txt index 89db8a32..aa394920 100644 --- a/tests/utests/CMakeLists.txt +++ b/tests/utests/CMakeLists.txt @@ -16,6 +16,7 @@ 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) set(LIBS Threads::Threads PCM_STATIC) @@ -25,6 +26,7 @@ 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 @@ -86,6 +88,13 @@ 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) @@ -93,3 +102,4 @@ 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/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."; +}