From 8a254ad6e584ae47af7fbedce4b402b6c0c12786 Mon Sep 17 00:00:00 2001 From: Mikhail Malyshev Date: Wed, 1 Jul 2026 11:24:06 +0000 Subject: [PATCH 1/3] lib: expose physical core_id in pqos_coreinfo Topology consumers need to identify SMT siblings as logical CPUs that share the same physical core. L2 cache id is not sufficient for this: on Intel E-core modules a single L2 is shared by 4 distinct physical cores. Add an unsigned core_id field to struct pqos_coreinfo and populate it on Linux from /sys/devices/system/cpu/cpu%u/topology/core_id. The OS interface reads it in os_cpuinfo_topology() via a new os_cpuinfo_cpu_core() (mirroring os_cpuinfo_cpu_socket()); the MSR/MMIO CPUID topology builder (cpuinfo_build_topo()) reads the same sysfs value. SMT siblings are then the logical CPUs sharing (socket, core_id). Signed-off-by: Mikhail Malyshev --- lib/cpuinfo.c | 19 ++++++++++++++++++- lib/os_cpuinfo.c | 35 ++++++++++++++++++++++++++++++++--- lib/pqos.h | 1 + 3 files changed, 51 insertions(+), 4 deletions(-) diff --git a/lib/cpuinfo.c b/lib/cpuinfo.c index 3173305c..5f522520 100644 --- a/lib/cpuinfo.c +++ b/lib/cpuinfo.c @@ -489,8 +489,25 @@ cpuinfo_build_topo(struct apic_info *apic) for (i = 0; i < max_core_count; ++i) if (detect_cpu(i, apic, &l_cpu->cores[core_count], - max_core_count) == 0) + max_core_count) == 0) { +#ifdef __linux__ + /* + * The APIC-derived topology does not give the + * physical core id, so read it from sysfs (same + * source as the OS interface). SMT siblings share + * (socket, core_id). Left at 0 if unavailable. + */ + char core_path[256]; + + snprintf(core_path, sizeof(core_path) - 1, + "/sys/devices/system/cpu/cpu%u/topology/" + "core_id", + l_cpu->cores[core_count].lcore); + pqos_fread_uint(core_path, + &l_cpu->cores[core_count].core_id); +#endif core_count++; + } if (set_affinity_mask(current_mask, max_core_count) != 0) { LOG_ERROR("Couldn't restore original CPU affinity mask!"); diff --git a/lib/os_cpuinfo.c b/lib/os_cpuinfo.c index 69000d78..9fa23e73 100644 --- a/lib/os_cpuinfo.c +++ b/lib/os_cpuinfo.c @@ -191,6 +191,28 @@ os_cpuinfo_cpu_socket(unsigned lcore, unsigned *socket) return pqos_fread_uint(buf, socket); } +/** + * @brief Detects physical core id of \a lcore + * + * Reads topology/core_id which identifies the physical core within the + * package. SMT siblings share the same (socket, core_id) pair. + * + * @param [in] lcore Logical core id + * @param [out] core_id physical core id within the socket + * + * @return Operations status + * @retval PQOS_RETVAL_OK on success + */ +PQOS_STATIC int +os_cpuinfo_cpu_core(unsigned lcore, unsigned *core_id) +{ + char buf[256]; + + snprintf(buf, sizeof(buf) - 1, SYSTEM_CPU "/cpu%u/topology/core_id", + lcore); + return pqos_fread_uint(buf, core_id); +} + /** * @brief Detects cache topology of \a lcore * @@ -309,6 +331,13 @@ os_cpuinfo_topology(void) break; } + retval = os_cpuinfo_cpu_core(lcore, &info->core_id); + if (retval != PQOS_RETVAL_OK) { + LOG_ERROR("Failed to get core id for lcore %u\n", + lcore); + break; + } + retval = os_cpuinfo_cpu_node(lcore, &info->numa); if (retval != PQOS_RETVAL_OK) { LOG_ERROR("Failed to get NUMA node for lcore %u\n", @@ -326,11 +355,11 @@ os_cpuinfo_topology(void) info->lcore = lcore; - LOG_DEBUG("Detected core %u, socket %u, " + LOG_DEBUG("Detected core %u, socket %u, core id %u, " "NUMAnode %u, " "L2 ID %u, L3 ID %u\n", - info->lcore, info->socket, info->numa, info->l2_id, - info->l3_id); + info->lcore, info->socket, info->core_id, info->numa, + info->l2_id, info->l3_id); cpu->num_cores++; } diff --git a/lib/pqos.h b/lib/pqos.h index 7d3d4978..8f22bc53 100644 --- a/lib/pqos.h +++ b/lib/pqos.h @@ -424,6 +424,7 @@ struct pqos_cap { struct pqos_coreinfo { unsigned lcore; /**< logical core id */ unsigned socket; /**< socket id in the system */ + unsigned core_id; /**< physical core id within socket */ unsigned l3_id; /**< L3/LLC cluster id */ unsigned l2_id; /**< L2 cluster id */ unsigned l3cat_id; /**< L3 CAT classes id */ From 9f8e4c7b6c813993afbddf37139fcdaf0a5b6d33 Mon Sep 17 00:00:00 2001 From: Mikhail Malyshev Date: Wed, 1 Jul 2026 11:24:07 +0000 Subject: [PATCH 2/3] lib/go: add Go bindings for libpqos with physical core_id CGO bindings for libpqos (init/fini, capability detection, cpuinfo, allocation and monitoring). CoreInfo exposes the physical core id as CoreID so callers can group logical CPUs into SMT siblings by (Socket, CoreID). Signed-off-by: Mikhail Malyshev --- lib/go/.gitignore | 43 ++ lib/go/API.md | 987 +++++++++++++++++++++++++++++ lib/go/Makefile | 133 ++++ lib/go/examples/capability/main.go | 264 ++++++++ lib/go/examples/simple/main.go | 115 ++++ lib/go/go.mod | 7 + lib/go/pqos/allocation.go | 649 +++++++++++++++++++ lib/go/pqos/capability.go | 409 ++++++++++++ lib/go/pqos/cpuinfo.go | 309 +++++++++ lib/go/pqos/monitoring.go | 500 +++++++++++++++ lib/go/pqos/pqos.go | 295 +++++++++ lib/go/pqos/pqos_test.go | 375 +++++++++++ 12 files changed, 4086 insertions(+) create mode 100644 lib/go/.gitignore create mode 100644 lib/go/API.md create mode 100644 lib/go/Makefile create mode 100644 lib/go/examples/capability/main.go create mode 100644 lib/go/examples/simple/main.go create mode 100644 lib/go/go.mod create mode 100644 lib/go/pqos/allocation.go create mode 100644 lib/go/pqos/capability.go create mode 100644 lib/go/pqos/cpuinfo.go create mode 100644 lib/go/pqos/monitoring.go create mode 100644 lib/go/pqos/pqos.go create mode 100644 lib/go/pqos/pqos_test.go diff --git a/lib/go/.gitignore b/lib/go/.gitignore new file mode 100644 index 00000000..4025f084 --- /dev/null +++ b/lib/go/.gitignore @@ -0,0 +1,43 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool +*.out + +# Dependency directories +vendor/ + +# Go workspace file +go.work + +# Build artifacts +examples/*/capability +examples/*/simple +examples/capability/capability +examples/simple/simple + +# IDE and editor files +.vscode/ +.idea/ +*.swp +*.swo +*~ +.DS_Store + +# Temporary files +*.tmp +*.bak + +# Go build cache +.cache/ + +# Coverage reports +coverage.txt +coverage.html diff --git a/lib/go/API.md b/lib/go/API.md new file mode 100644 index 00000000..1f0a59e1 --- /dev/null +++ b/lib/go/API.md @@ -0,0 +1,987 @@ +# Intel(R) RDT PQoS Go Bindings - Complete API Reference + +**Version:** 1.0.0 +**Package:** `github.com/intel/intel-cmt-cat/lib/go/pqos` + +## Table of Contents + +- [Overview](#overview) +- [Core Library](#core-library) +- [Capability Detection](#capability-detection) +- [CPU Information](#cpu-information) +- [Cache Allocation](#cache-allocation) +- [Memory Bandwidth Allocation](#memory-bandwidth-allocation) +- [Resource Monitoring](#resource-monitoring) +- [Constants](#constants) +- [Examples](#examples) + +--- + +## Overview + +This document provides a complete reference for all public APIs in the PQoS Go bindings. + +### Import + +```go +import "github.com/intel/intel-cmt-cat/lib/go/pqos" +``` + +--- + +## Core Library + +### PQoS Type + +Main library handle implementing singleton pattern. + +```go +type PQoS struct { + // Private fields +} +``` + +### GetInstance + +Returns the singleton PQoS instance. + +```go +func GetInstance() *PQoS +``` + +**Example:** +```go +p := pqos.GetInstance() +``` + +### Init + +Initializes the PQoS library. + +```go +func (p *PQoS) Init(cfg *Config) error +``` + +**Parameters:** +- `cfg` - Configuration (can be nil for defaults) + +**Returns:** Error if initialization fails + +**Example:** +```go +cfg := &pqos.Config{ + Interface: pqos.InterOS, + Verbose: pqos.LogVerbosityDefault, +} +err := p.Init(cfg) +``` + +### Fini + +Shuts down the PQoS library. + +```go +func (p *PQoS) Fini() error +``` + +**Returns:** Error if finalization fails + +**Example:** +```go +defer p.Fini() +``` + +### IsInitialized + +Checks if library is initialized. + +```go +func (p *PQoS) IsInitialized() bool +``` + +**Returns:** True if initialized + +### GetInterface + +Gets the currently active interface. + +```go +func (p *PQoS) GetInterface() (int, error) +``` + +**Returns:** Interface type constant and error + +**Example:** +```go +iface, _ := p.GetInterface() +fmt.Println(pqos.InterfaceToString(iface)) +``` + +### Config Type + +```go +type Config struct { + Interface int // InterMSR, InterOS, InterAuto + Verbose int // Log verbosity level + LogFile *os.File // Optional log file +} +``` + +--- + +## Capability Detection + +### GetCapability + +Retrieves system capabilities. + +```go +func (p *PQoS) GetCapability() (*Capability, error) +``` + +**Returns:** Capability structure and error + +**Example:** +```go +cap, err := p.GetCapability() +if err != nil { + log.Fatal(err) +} +``` + +### Capability Type + +```go +type Capability struct { + // Private fields +} +``` + +**Methods:** + +#### HasL3CA + +```go +func (c *Capability) HasL3CA() bool +``` + +Checks if L3 Cache Allocation is supported. + +#### HasL2CA + +```go +func (c *Capability) HasL2CA() bool +``` + +Checks if L2 Cache Allocation is supported. + +#### HasMBA + +```go +func (c *Capability) HasMBA() bool +``` + +Checks if Memory Bandwidth Allocation is supported. + +#### HasMon + +```go +func (c *Capability) HasMon() bool +``` + +Checks if resource monitoring is supported. + +#### GetL3CA + +```go +func (c *Capability) GetL3CA() (*L3CACapability, error) +``` + +Gets L3 CAT capability details. + +**Returns:** L3CACapability structure + +```go +type L3CACapability struct { + MemSize uint // Structure size + NumClasses uint // Number of COS + NumWays uint // Number of cache ways + WaySize uint // Way size in bytes + WayContention uint64 // Contention bitmask + CDP bool // CDP supported + CDPOn bool // CDP enabled + NonContiguousCBM bool // Non-contiguous CBM support + IORDT bool // I/O RDT supported + IORDTOn bool // I/O RDT enabled +} +``` + +#### GetL2CA + +```go +func (c *Capability) GetL2CA() (*L2CACapability, error) +``` + +Gets L2 CAT capability details. + +**Returns:** L2CACapability structure + +```go +type L2CACapability struct { + MemSize uint + NumClasses uint + NumWays uint + WaySize uint + WayContention uint64 + CDP bool + CDPOn bool + NonContiguousCBM bool +} +``` + +#### GetMBA + +```go +func (c *Capability) GetMBA() (*MBACapability, error) +``` + +Gets MBA capability details. + +**Returns:** MBACapability structure + +```go +type MBACapability struct { + MemSize uint // Structure size + NumClasses uint // Number of COS + ThrottleMax uint // Max throttle value + ThrottleStep uint // Throttle granularity + IsLinear bool // Linear throttling + Ctrl bool // Controller supported + CtrlOn bool // Controller enabled + MBA40 bool // MBA 4.0 supported + MBA40On bool // MBA 4.0 enabled +} +``` + +#### GetMon + +```go +func (c *Capability) GetMon() (*MonCapability, error) +``` + +Gets monitoring capability details. + +**Returns:** MonCapability structure + +```go +type MonCapability struct { + MemSize uint // Structure size + MaxRMID uint // Max RMID + L3Size uint // L3 cache size + NumEvents uint // Number of events + IORDT bool // I/O RDT monitoring + IORDTOn bool // I/O RDT enabled + SNCNum uint // SNC clusters + SNCMode int // SNC mode + Events []Monitor // Supported events +} + +type Monitor struct { + Type uint // Event type + MaxRMID uint // Max RMID for event + ScaleFactor uint32 // Scale factor + CounterLength uint // Counter bits + IORDT bool // I/O RDT support +} +``` + +--- + +## CPU Information + +### GetCPUInfo + +Gets CPU topology information. + +```go +func (c *Capability) GetCPUInfo() (*CPUInfo, error) +``` + +**Returns:** CPUInfo structure + +```go +type CPUInfo struct { + MemSize uint // Structure size + L2 CacheInfo // L2 cache info + L3 CacheInfo // L3 cache info + Vendor int // CPU vendor + NumCores uint // Number of cores + Cores []CoreInfo // Core information +} + +type CoreInfo struct { + LCore uint // Logical core ID + Socket uint // Socket ID + CoreID uint // Physical core ID within socket (SMT siblings share Socket+CoreID) + L3ID uint // L3 cluster ID + L2ID uint // L2 cluster ID + L3CATID uint // L3 CAT ID + MBAID uint // MBA ID + NUMA uint // NUMA node + SMBAID uint // SMBA ID +} + +type CacheInfo struct { + Detected bool // Cache detected + NumWays uint // Number of ways + NumSets uint // Number of sets + NumPartitions uint // Number of partitions + LineSize uint // Line size (bytes) + TotalSize uint // Total size (bytes) + WaySize uint // Way size (bytes) +} +``` + +**Methods:** + +```go +func (c *CPUInfo) GetNumCores() uint +func (c *CPUInfo) GetNumSockets() uint +func (c *CPUInfo) GetSocketIDs() []uint +func (c *CPUInfo) GetL3IDs() []uint +func (c *CPUInfo) GetL3CATIDs() []uint +func (c *CPUInfo) GetMBAIDs() []uint +func (c *CPUInfo) GetCoresBySocket(socket uint) []CoreInfo +func (c *CPUInfo) GetCoresByL3ID(l3ID uint) []CoreInfo +func (c *CPUInfo) GetCoresByNUMA(numa uint) []CoreInfo +func (c *CPUInfo) FindCore(lcore uint) *CoreInfo +func (c *CPUInfo) GetL3CacheSize() uint +func (c *CPUInfo) GetL2CacheSize() uint +``` + +--- + +## Cache Allocation + +### L3CA Type + +L3 Cache Allocation configuration. + +```go +type L3CA struct { + ClassID uint // Class of Service + CDP bool // Code/Data Prioritization + WaysMask uint64 // Cache ways mask (non-CDP) + CodeMask uint64 // Code mask (CDP) + DataMask uint64 // Data mask (CDP) +} +``` + +### L3CASet + +Configures L3 cache allocation. + +```go +func (p *PQoS) L3CASet(l3CatID uint, cos []L3CA) error +``` + +**Parameters:** +- `l3CatID` - L3 cache cluster ID +- `cos` - Array of L3CA configurations + +**Example:** +```go +cos := []pqos.L3CA{ + { + ClassID: 1, + CDP: false, + WaysMask: 0x0F, // Use first 4 ways + }, +} +err := p.L3CASet(0, cos) +``` + +### L3CAGet + +Retrieves L3 cache allocation configuration. + +```go +func (p *PQoS) L3CAGet(l3CatID uint, maxNumCOS uint) ([]L3CA, error) +``` + +**Parameters:** +- `l3CatID` - L3 cache cluster ID +- `maxNumCOS` - Maximum number of classes to retrieve + +**Returns:** Array of L3CA configurations and error + +**Example:** +```go +cos, err := p.L3CAGet(0, 16) +``` + +### L3CAGetMinCBMBits + +Gets minimum CBM bits required. + +```go +func (p *PQoS) L3CAGetMinCBMBits() (uint, error) +``` + +**Returns:** Minimum bits and error + +### L2CA Type + +L2 Cache Allocation configuration. + +```go +type L2CA struct { + ClassID uint // Class of Service + CDP bool // Code/Data Prioritization + WaysMask uint64 // Cache ways mask (non-CDP) + CodeMask uint64 // Code mask (CDP) + DataMask uint64 // Data mask (CDP) +} +``` + +### L2CASet + +Configures L2 cache allocation. + +```go +func (p *PQoS) L2CASet(l2ID uint, cos []L2CA) error +``` + +**Parameters:** +- `l2ID` - L2 cache cluster ID +- `cos` - Array of L2CA configurations + +### L2CAGet + +Retrieves L2 cache allocation configuration. + +```go +func (p *PQoS) L2CAGet(l2ID uint, maxNumCOS uint) ([]L2CA, error) +``` + +### L2CAGetMinCBMBits + +Gets minimum CBM bits required. + +```go +func (p *PQoS) L2CAGetMinCBMBits() (uint, error) +``` + +--- + +## Memory Bandwidth Allocation + +### MBA Type + +Memory Bandwidth Allocation configuration. + +```go +type MBA struct { + ClassID uint // Class of Service + MBMax uint // Max bandwidth (% or MBps) + Ctrl bool // Controller mode + SMBA bool // Slow memory bandwidth +} +``` + +### MBASet + +Configures memory bandwidth allocation. + +```go +func (p *PQoS) MBASet(mbaID uint, requested []MBA) ([]MBA, error) +``` + +**Parameters:** +- `mbaID` - MBA cluster ID +- `requested` - Requested MBA configurations + +**Returns:** Actual MBA configurations applied and error + +**Example:** +```go +requested := []pqos.MBA{ + { + ClassID: 1, + MBMax: 50, // 50% bandwidth + Ctrl: false, + }, +} +actual, err := p.MBASet(0, requested) +``` + +### MBAGet + +Retrieves memory bandwidth allocation configuration. + +```go +func (p *PQoS) MBAGet(mbaID uint, maxNumCOS uint) ([]MBA, error) +``` + +**Parameters:** +- `mbaID` - MBA cluster ID +- `maxNumCOS` - Maximum number of classes + +**Returns:** Array of MBA configurations and error + +--- + +## Core and Process Association + +### AllocAssocSet + +Associates a logical core with a Class of Service. + +```go +func (p *PQoS) AllocAssocSet(lcore uint, classID uint) error +``` + +**Parameters:** +- `lcore` - Logical core ID +- `classID` - Class of Service ID + +**Example:** +```go +err := p.AllocAssocSet(0, 1) // Associate core 0 with COS 1 +``` + +### AllocAssocGet + +Gets the COS associated with a core. + +```go +func (p *PQoS) AllocAssocGet(lcore uint) (uint, error) +``` + +**Returns:** Class ID and error + +### AllocAssocSetPID + +Associates a process with a COS (OS interface). + +```go +func (p *PQoS) AllocAssocSetPID(pid int, classID uint) error +``` + +**Parameters:** +- `pid` - Process ID +- `classID` - Class of Service ID + +### AllocAssocGetPID + +Gets the COS associated with a process. + +```go +func (p *PQoS) AllocAssocGetPID(pid int) (uint, error) +``` + +**Returns:** Class ID and error + +### AllocAssign + +Automatically assigns an available COS to cores. + +```go +func (p *PQoS) AllocAssign(technology uint, cores []uint) (uint, error) +``` + +**Parameters:** +- `technology` - Technology bitmask (1 << CapType) +- `cores` - Array of core IDs + +**Returns:** Assigned class ID and error + +**Example:** +```go +tech := (1 << pqos.CapTypeL3CA) | (1 << pqos.CapTypeMBA) +classID, err := p.AllocAssign(tech, []uint{0, 1, 2, 3}) +``` + +### AllocRelease + +Releases cores back to default COS#0. + +```go +func (p *PQoS) AllocRelease(cores []uint) error +``` + +### AllocAssignPID + +Automatically assigns an available COS to processes. + +```go +func (p *PQoS) AllocAssignPID(technology uint, pids []int) (uint, error) +``` + +### AllocReleasePID + +Releases processes back to default COS#0. + +```go +func (p *PQoS) AllocReleasePID(pids []int) error +``` + +### AllocReset + +Resets all allocation configuration to defaults. + +```go +func (p *PQoS) AllocReset() error +``` + +--- + +## Resource Monitoring + +### MonData Type + +Monitoring group data. + +```go +type MonData struct { + Valid bool // Structure validity + Event uint // Monitored events + Values EventValues // Event metrics + Cores []uint // Monitored cores + PIDs []int // Monitored processes +} + +type EventValues struct { + LLC uint64 // LLC occupancy (bytes) + MBMLocal uint64 // Local mem BW reading + MBMTotal uint64 // Total mem BW reading + MBMRemote uint64 // Remote mem BW reading + MBMLocalDelta uint64 // Local mem BW delta + MBMTotalDelta uint64 // Total mem BW delta + MBMRemoteDelta uint64 // Remote mem BW delta + IPCRetired uint64 // Instructions retired + IPCRetiredDelta uint64 // Instructions delta + IPCUnhalted uint64 // Unhalted cycles + IPCUnhaltedDelta uint64 // Unhalted cycles delta + LLCMissTotal uint64 // LLC misses + LLCMissDelta uint64 // LLC misses delta + LLCRefTotal uint64 // LLC references + LLCRefDelta uint64 // LLC references delta +} +``` + +**Methods:** + +```go +func (m *MonData) GetLLCOccupancy() uint64 +func (m *MonData) GetMBMLocalBandwidth() uint64 +func (m *MonData) GetMBMTotalBandwidth() uint64 +func (m *MonData) GetMBMRemoteBandwidth() uint64 +func (m *MonData) GetIPC() float64 +func (m *MonData) GetLLCMissRate() float64 +``` + +### MonStartCores + +Starts resource monitoring on cores. + +```go +func (p *PQoS) MonStartCores(cores []uint, event uint) (*MonData, error) +``` + +**Parameters:** +- `cores` - Array of logical core IDs +- `event` - Combination of monitoring events + +**Returns:** MonData structure and error + +**Example:** +```go +event := pqos.MonEventL3Occup | pqos.MonEventTMemBW +group, err := p.MonStartCores([]uint{0, 1}, event) +``` + +### MonStartPIDs + +Starts resource monitoring on processes. + +```go +func (p *PQoS) MonStartPIDs(pids []int, event uint) (*MonData, error) +``` + +**Parameters:** +- `pids` - Array of process IDs +- `event` - Combination of monitoring events + +**Returns:** MonData structure and error + +**Example:** +```go +event := pqos.MonEventL3Occup | pqos.MonEventTMemBW +group, err := p.MonStartPIDs([]int{1234, 5678}, event) +``` + +### MonAddPIDs + +Adds PIDs to an existing monitoring group. + +```go +func (p *PQoS) MonAddPIDs(group *MonData, pids []int) error +``` + +### MonRemovePIDs + +Removes PIDs from a monitoring group. + +```go +func (p *PQoS) MonRemovePIDs(group *MonData, pids []int) error +``` + +### MonPoll + +Polls monitoring data for groups. + +```go +func (p *PQoS) MonPoll(groups []*MonData) error +``` + +**Parameters:** +- `groups` - Array of monitoring groups to poll + +**Example:** +```go +err := p.MonPoll([]*pqos.MonData{group1, group2}) +if err != nil { + log.Printf("Poll error: %v", err) +} +fmt.Printf("LLC: %d bytes\n", group1.GetLLCOccupancy()) +``` + +### MonStop + +Stops resource monitoring for a group. + +```go +func (p *PQoS) MonStop(group *MonData) error +``` + +### MonReset + +Resets all monitoring by binding cores to RMID0. + +```go +func (p *PQoS) MonReset() error +``` + +### MonAssocGet + +Gets RMID association of a core. + +```go +func (p *PQoS) MonAssocGet(lcore uint) (uint32, error) +``` + +**Returns:** RMID and error + +--- + +## Constants + +### Interface Types + +```go +const ( + InterMSR // MSR interface + InterOS // OS interface (resctrl) + InterOSResctrlMon // OS with resctrl monitoring + InterAuto // Auto-detect +) +``` + +### Log Verbosity + +```go +const ( + LogVerbositySilent = -1 + LogVerbosityDefault = 0 + LogVerbosityVerbose = 1 + LogVerbositySuperVerbose = 2 +) +``` + +### Capability Types + +```go +const ( + CapTypeMon // Monitoring + CapTypeL3CA // L3 Cache Allocation + CapTypeL2CA // L2 Cache Allocation + CapTypeMBA // Memory Bandwidth Allocation + CapTypeSMBA // Slow Memory Bandwidth Allocation +) +``` + +### Monitoring Events + +```go +const ( + MonEventL3Occup // LLC occupancy + MonEventLMemBW // Local memory bandwidth + MonEventTMemBW // Total memory bandwidth + MonEventRMemBW // Remote memory bandwidth + PerfEventLLCMiss // LLC misses + PerfEventIPC // Instructions per clock + PerfEventLLCRef // LLC references +) +``` + +### CPU Vendors + +```go +const ( + VendorUnknown + VendorIntel + VendorAMD +) +``` + +### Return Values + +```go +const ( + RetvalOK + RetvalError + RetvalParam + RetvalResource + RetvalInit + RetvalTransport + RetvalPerfCtr + RetvalBusy + RetvalInter + RetvalOverflow +) +``` + +--- + +## Helper Functions + +### String Conversion Functions + +```go +func InterfaceToString(iface int) string +func VerbosityToString(verbose int) string +func VendorToString(vendor int) string +func CapTypeToString(capType int) string +func MonEventToString(event uint) string +``` + +--- + +## Examples + +### Complete Workflow Example + +```go +package main + +import ( + "fmt" + "log" + "time" + + "github.com/intel/intel-cmt-cat/lib/go/pqos" +) + +func main() { + // Initialize + p := pqos.GetInstance() + cfg := &pqos.Config{ + Interface: pqos.InterAuto, + Verbose: pqos.LogVerbosityDefault, + } + + if err := p.Init(cfg); err != nil { + log.Fatal(err) + } + defer p.Fini() + + // Get capabilities + cap, _ := p.GetCapability() + + // Configure L3 CAT + if cap.HasL3CA() { + cos := []pqos.L3CA{ + {ClassID: 1, WaysMask: 0x0F}, // 4 ways + {ClassID: 2, WaysMask: 0xF0}, // 4 ways + } + p.L3CASet(0, cos) + + // Associate cores with COS + p.AllocAssocSet(0, 1) // Core 0 -> COS 1 + p.AllocAssocSet(1, 2) // Core 1 -> COS 2 + } + + // Start monitoring + if cap.HasMon() { + event := pqos.MonEventL3Occup | pqos.MonEventTMemBW + group, _ := p.MonStartCores([]uint{0, 1}, event) + defer p.MonStop(group) + + // Poll for 10 seconds + for i := 0; i < 10; i++ { + time.Sleep(time.Second) + p.MonPoll([]*pqos.MonData{group}) + + fmt.Printf("LLC: %d KB, BW: %d MB/s\n", + group.GetLLCOccupancy()/1024, + group.GetMBMTotalBandwidth()/(1024*1024)) + } + } + + // Reset to defaults + p.AllocReset() +} +``` + +--- + +## Error Handling + +All functions that can fail return an error. Always check errors: + +```go +if err := p.Init(cfg); err != nil { + log.Fatalf("Initialization failed: %v", err) +} +``` + +Common errors: +- `"PQoS not initialized"` - Call Init() first +- `"initialization error"` - Check permissions, hardware support +- `"parameter error"` - Invalid parameters +- `"interface not supported"` - Interface not available + +--- + +## Thread Safety + +The PQoS type is thread-safe. Multiple goroutines can safely call methods on the same instance. + +```go +go func() { + cos, _ := p.L3CAGet(0, 16) + // Process... +}() + +go func() { + group, _ := p.MonStartCores([]uint{0}, pqos.MonEventL3Occup) + // Monitor... +}() +``` + +--- + +## License + +BSD 3-Clause License - Copyright(c) 2025 Zededa, Inc. diff --git a/lib/go/Makefile b/lib/go/Makefile new file mode 100644 index 00000000..0fe80578 --- /dev/null +++ b/lib/go/Makefile @@ -0,0 +1,133 @@ +# Makefile for Intel(R) RDT PQoS Go bindings +# +# Copyright(c) 2025 Zededa, Inc. +# SPDX-License-Identifier: BSD-3-Clause + +.PHONY: all build test clean examples help install-lib check-deps + +# Go parameters +GOCMD=go +GOBUILD=$(GOCMD) build +GOCLEAN=$(GOCMD) clean +GOTEST=$(GOCMD) test +GOGET=$(GOCMD) get +GOMOD=$(GOCMD) mod +GOFMT=$(GOCMD) fmt + +# Directories +PKG_DIR=./pqos +EXAMPLES_DIR=./examples +LIB_DIR=../ + +# Library paths +LIBPQOS_PATH=/usr/local/lib +LIBPQOS_INCLUDE=/usr/local/include + +# CGO flags +export CGO_LDFLAGS=-L$(LIBPQOS_PATH) -lpqos +export CGO_CFLAGS=-I$(LIBPQOS_INCLUDE) +export LD_LIBRARY_PATH=$(LIBPQOS_PATH):$$LD_LIBRARY_PATH + +all: check-deps build examples + +help: + @echo "Intel(R) RDT PQoS Go Bindings Makefile" + @echo "" + @echo "Targets:" + @echo " all - Check dependencies, build package and examples" + @echo " build - Build the Go package" + @echo " examples - Build all examples" + @echo " test - Run tests" + @echo " clean - Remove built binaries" + @echo " install-lib - Build and install libpqos C library" + @echo " check-deps - Check if libpqos is installed" + @echo " fmt - Format Go code" + @echo " tidy - Tidy go.mod" + @echo " help - Show this help message" + @echo "" + @echo "Examples:" + @echo " make build - Build the package" + @echo " make examples - Build all examples" + @echo " make test - Run tests" + @echo " sudo make install-lib - Install libpqos (requires root)" + +check-deps: + @echo "Checking dependencies..." + @if [ ! -f "$(LIBPQOS_PATH)/libpqos.so.6" ]; then \ + echo "ERROR: libpqos not found at $(LIBPQOS_PATH)"; \ + echo "Please run 'sudo make install-lib' to install it"; \ + exit 1; \ + fi + @if [ ! -f "$(LIBPQOS_INCLUDE)/pqos.h" ]; then \ + echo "ERROR: pqos.h not found at $(LIBPQOS_INCLUDE)"; \ + echo "Please run 'sudo make install-lib' to install it"; \ + exit 1; \ + fi + @echo "Dependencies OK" + +build: check-deps + @echo "Building pqos package..." + cd $(PKG_DIR) && $(GOBUILD) -v + +examples: check-deps + @echo "Building examples..." + @for dir in $(EXAMPLES_DIR)/*/ ; do \ + if [ -f "$$dir/main.go" ]; then \ + echo "Building $$dir..."; \ + cd "$$dir" && $(GOBUILD) -o $$(basename $$dir) || exit 1; \ + cd - > /dev/null; \ + fi \ + done + @echo "Examples built successfully" + +test: check-deps + @echo "Running tests..." + cd $(PKG_DIR) && $(GOTEST) -v + +fmt: + @echo "Formatting Go code..." + $(GOFMT) ./... + +tidy: + @echo "Tidying go.mod..." + $(GOMOD) tidy + +clean: + @echo "Cleaning..." + $(GOCLEAN) + @for dir in $(EXAMPLES_DIR)/*/ ; do \ + if [ -f "$$dir/main.go" ]; then \ + rm -f "$$dir/$$(basename $$dir)"; \ + fi \ + done + @echo "Clean complete" + +install-lib: + @echo "Building and installing libpqos..." + @if [ "$$(id -u)" != "0" ]; then \ + echo "ERROR: This target must be run as root (use sudo)"; \ + exit 1; \ + fi + cd $(LIB_DIR) && $(MAKE) clean && $(MAKE) && $(MAKE) install + @echo "libpqos installed successfully" + @echo "You may need to run: sudo ldconfig" + +# Development targets +dev-setup: install-lib tidy + @echo "Development environment ready" + +run-capability: examples + @echo "Running capability example..." + @if [ "$$(id -u)" != "0" ]; then \ + echo "Note: This may require root privileges"; \ + echo "Try: sudo make run-capability"; \ + echo ""; \ + fi + $(EXAMPLES_DIR)/capability/capability + +# Check if running with sufficient privileges +check-priv: + @if [ "$$(id -u)" != "0" ]; then \ + echo "WARNING: Not running as root. Some operations may fail."; \ + echo "Consider running with: sudo make "; \ + fi diff --git a/lib/go/examples/capability/main.go b/lib/go/examples/capability/main.go new file mode 100644 index 00000000..2e8ded5c --- /dev/null +++ b/lib/go/examples/capability/main.go @@ -0,0 +1,264 @@ +// Copyright(c) 2025 Zededa, Inc. +// SPDX-License-Identifier: BSD-3-Clause + +package main + +import ( + "fmt" + "log" + "os" + + "github.com/intel/intel-cmt-cat/lib/go/pqos" +) + +func main() { + // Initialize PQoS library + p := pqos.GetInstance() + cfg := &pqos.Config{ + Interface: pqos.InterAuto, + Verbose: pqos.LogVerbosityDefault, + } + + fmt.Println("Initializing Intel(R) RDT PQoS library...") + if err := p.Init(cfg); err != nil { + log.Fatalf("Failed to initialize PQoS: %v", err) + } + defer func() { + if err := p.Fini(); err != nil { + log.Printf("Failed to finalize PQoS: %v", err) + } + }() + + // Get active interface + iface, err := p.GetInterface() + if err != nil { + log.Fatalf("Failed to get interface: %v", err) + } + fmt.Printf("Active interface: %s\n\n", pqos.InterfaceToString(iface)) + + // Get capabilities + cap, err := p.GetCapability() + if err != nil { + log.Fatalf("Failed to get capabilities: %v", err) + } + + // Display CPU information + fmt.Println("=== CPU Information ===") + cpuInfo, err := cap.GetCPUInfo() + if err != nil { + log.Printf("Warning: Failed to get CPU info: %v", err) + } else { + displayCPUInfo(cpuInfo) + } + + fmt.Println("\n=== Intel(R) RDT Capabilities ===") + + // Check L3 Cache Allocation + if cap.HasL3CA() { + fmt.Println("\n--- L3 Cache Allocation Technology (CAT) ---") + l3ca, err := cap.GetL3CA() + if err != nil { + log.Printf("Error getting L3CA details: %v", err) + } else { + displayL3CA(l3ca) + } + } else { + fmt.Println("\nL3 Cache Allocation: Not supported") + } + + // Check L2 Cache Allocation + if cap.HasL2CA() { + fmt.Println("\n--- L2 Cache Allocation Technology (CAT) ---") + l2ca, err := cap.GetL2CA() + if err != nil { + log.Printf("Error getting L2CA details: %v", err) + } else { + displayL2CA(l2ca, cpuInfo) + } + } else { + fmt.Println("\nL2 Cache Allocation: Not supported") + } + + // Check Memory Bandwidth Allocation + if cap.HasMBA() { + fmt.Println("\n--- Memory Bandwidth Allocation (MBA) ---") + mba, err := cap.GetMBA() + if err != nil { + log.Printf("Error getting MBA details: %v", err) + } else { + displayMBA(mba) + } + } else { + fmt.Println("\nMemory Bandwidth Allocation: Not supported") + } + + // Check Monitoring + if cap.HasMon() { + fmt.Println("\n--- Resource Monitoring ---") + mon, err := cap.GetMon() + if err != nil { + log.Printf("Error getting MON details: %v", err) + } else { + displayMon(mon) + } + } else { + fmt.Println("\nResource Monitoring: Not supported") + } + + fmt.Println("\n=== Summary ===") + fmt.Printf("L3 CAT: %s\n", supportStatus(cap.HasL3CA())) + fmt.Printf("L2 CAT: %s\n", supportStatus(cap.HasL2CA())) + fmt.Printf("MBA: %s\n", supportStatus(cap.HasMBA())) + fmt.Printf("MON: %s\n", supportStatus(cap.HasMon())) +} + +func displayCPUInfo(cpu *pqos.CPUInfo) { + fmt.Printf("Vendor: %s\n", pqos.VendorToString(cpu.Vendor)) + fmt.Printf("Total Cores: %d\n", cpu.NumCores) + fmt.Printf("Sockets: %d\n", cpu.GetNumSockets()) + + if cpu.L3.Detected { + fmt.Printf("L3 Cache: %d KB (%d ways, %d sets)\n", + cpu.L3.TotalSize/1024, cpu.L3.NumWays, cpu.L3.NumSets) + } + if cpu.L2.Detected { + fmt.Printf("L2 Cache: %d KB (%d ways, %d sets)\n", + cpu.L2.TotalSize/1024, cpu.L2.NumWays, cpu.L2.NumSets) + } + + // Display socket topology + fmt.Printf("\nSocket Topology:\n") + for _, socketID := range cpu.GetSocketIDs() { + cores := cpu.GetCoresBySocket(socketID) + fmt.Printf(" Socket %d: %d cores ", socketID, len(cores)) + if len(cores) <= 16 { + fmt.Printf("(") + for i, core := range cores { + if i > 0 { + fmt.Printf(", ") + } + fmt.Printf("%d", core.LCore) + } + fmt.Printf(")") + } + fmt.Println() + } +} + +func displayL3CA(l3ca *pqos.L3CACapability) { + fmt.Printf("Number of Classes: %d\n", l3ca.NumClasses) + fmt.Printf("Number of Ways: %d\n", l3ca.NumWays) + fmt.Printf("Way Size: %d KB\n", l3ca.WaySize/1024) + fmt.Printf("Total L3 Cache: %d MB\n", (l3ca.NumWays*l3ca.WaySize)/(1024*1024)) + fmt.Printf("CDP Support: %s\n", yesNo(l3ca.CDP)) + fmt.Printf("CDP Enabled: %s\n", yesNo(l3ca.CDPOn)) + fmt.Printf("Non-Contiguous CBM: %s\n", yesNo(l3ca.NonContiguousCBM)) + fmt.Printf("I/O RDT Support: %s\n", yesNo(l3ca.IORDT)) + if l3ca.IORDT { + fmt.Printf("I/O RDT Enabled: %s\n", yesNo(l3ca.IORDTOn)) + } + if l3ca.WayContention != 0 { + fmt.Printf("Way Contention Mask: 0x%x\n", l3ca.WayContention) + } +} + +func displayL2CA(l2ca *pqos.L2CACapability, cpuInfo *pqos.CPUInfo) { + fmt.Printf("Number of Classes: %d\n", l2ca.NumClasses) + fmt.Printf("Number of Ways: %d\n", l2ca.NumWays) + fmt.Printf("Way Size: %d KB\n", l2ca.WaySize/1024) + fmt.Printf("Total L2 Cache: %d KB\n", (l2ca.NumWays*l2ca.WaySize)/1024) + fmt.Printf("CDP Support: %s\n", yesNo(l2ca.CDP)) + fmt.Printf("CDP Enabled: %s\n", yesNo(l2ca.CDPOn)) + fmt.Printf("Non-Contiguous CBM: %s\n", yesNo(l2ca.NonContiguousCBM)) + if l2ca.WayContention != 0 { + fmt.Printf("Way Contention Mask: 0x%x\n", l2ca.WayContention) + } + + // Display L2 topology + if cpuInfo != nil { + l2IDs := cpuInfo.GetL2IDs() + if len(l2IDs) > 0 { + fmt.Printf("\nL2 Cache Domains: %d\n", cpuInfo.GetNumL2Domains()) + // Show first few domains as examples + maxDisplay := 8 + if len(l2IDs) > maxDisplay { + fmt.Printf("Showing first %d of %d domains:\n", maxDisplay, len(l2IDs)) + l2IDs = l2IDs[:maxDisplay] + } + for _, l2ID := range l2IDs { + cores := cpuInfo.GetCoresByL2ID(l2ID) + fmt.Printf(" L2 Domain %d: %d cores ", l2ID, len(cores)) + if len(cores) <= 8 { + fmt.Printf("(") + for i, core := range cores { + if i > 0 { + fmt.Printf(", ") + } + fmt.Printf("%d", core.LCore) + } + fmt.Printf(")") + } + fmt.Println() + } + } + } +} + +func displayMBA(mba *pqos.MBACapability) { + fmt.Printf("Number of Classes: %d\n", mba.NumClasses) + fmt.Printf("Throttle Maximum: %d%%\n", mba.ThrottleMax) + fmt.Printf("Throttle Step: %d%%\n", mba.ThrottleStep) + fmt.Printf("Linear Mode: %s\n", yesNo(mba.IsLinear)) + fmt.Printf("Controller Support: %s\n", yesNo(mba.Ctrl)) + if mba.Ctrl { + fmt.Printf("Controller Enabled: %s\n", yesNo(mba.CtrlOn)) + } + fmt.Printf("MBA 4.0 Support: %s\n", yesNo(mba.MBA40)) + if mba.MBA40 { + fmt.Printf("MBA 4.0 Enabled: %s\n", yesNo(mba.MBA40On)) + } +} + +func displayMon(mon *pqos.MonCapability) { + fmt.Printf("Maximum RMID: %d\n", mon.MaxRMID) + fmt.Printf("L3 Cache Size: %d MB\n", mon.L3Size/(1024*1024)) + fmt.Printf("Number of Events: %d\n", mon.NumEvents) + fmt.Printf("I/O RDT Support: %s\n", yesNo(mon.IORDT)) + if mon.IORDT { + fmt.Printf("I/O RDT Enabled: %s\n", yesNo(mon.IORDTOn)) + } + if mon.SNCNum > 0 { + fmt.Printf("SNC Clusters: %d\n", mon.SNCNum) + fmt.Printf("SNC Mode: %d\n", mon.SNCMode) + } + + if len(mon.Events) > 0 { + fmt.Println("\nSupported Events:") + for _, event := range mon.Events { + fmt.Printf(" - %-12s (Max RMID: %d, Scale Factor: %d, Counter Length: %d bits)\n", + pqos.MonEventToString(event.Type), event.MaxRMID, event.ScaleFactor, event.CounterLength) + } + } +} + +func yesNo(b bool) string { + if b { + return "Yes" + } + return "No" +} + +func supportStatus(supported bool) string { + if supported { + return "Supported" + } + return "✗ Not supported" +} + +func init() { + // Check if running with appropriate privileges + if os.Geteuid() != 0 { + fmt.Fprintf(os.Stderr, "Warning: This program may require root privileges to access MSR interface.\n") + fmt.Fprintf(os.Stderr, " Consider running with sudo or using OS interface.\n\n") + } +} diff --git a/lib/go/examples/simple/main.go b/lib/go/examples/simple/main.go new file mode 100644 index 00000000..3b082c2c --- /dev/null +++ b/lib/go/examples/simple/main.go @@ -0,0 +1,115 @@ +// Copyright(c) 2025 Zededa, Inc. +// SPDX-License-Identifier: BSD-3-Clause + +// Simple example demonstrating basic usage of PQoS Go bindings +package main + +import ( + "fmt" + "log" + + "github.com/intel/intel-cmt-cat/lib/go/pqos" +) + +func main() { + fmt.Println("Intel(R) RDT PQoS - Simple Example") + fmt.Println("===================================\n") + + // Step 1: Get the PQoS singleton instance + p := pqos.GetInstance() + + // Step 2: Configure initialization + // Use InterAuto to automatically select the best interface + cfg := &pqos.Config{ + Interface: pqos.InterAuto, + Verbose: pqos.LogVerbosityDefault, + } + + // Step 3: Initialize the library + fmt.Println("Initializing PQoS library...") + if err := p.Init(cfg); err != nil { + log.Fatalf("Failed to initialize PQoS: %v", err) + } + // Always clean up when done + defer func() { + fmt.Println("\nShutting down PQoS library...") + if err := p.Fini(); err != nil { + log.Printf("Warning: Failed to finalize PQoS: %v", err) + } + }() + + // Step 4: Get the active interface + iface, err := p.GetInterface() + if err != nil { + log.Printf("Warning: Could not get interface: %v", err) + } else { + fmt.Printf("Active interface: %s\n\n", pqos.InterfaceToString(iface)) + } + + // Step 5: Get system capabilities + fmt.Println("Detecting capabilities...") + cap, err := p.GetCapability() + if err != nil { + log.Fatalf("Failed to get capabilities: %v", err) + } + + // Step 6: Check what features are supported + fmt.Println("\nSupported Features:") + fmt.Printf(" L3 Cache Allocation (CAT): %v\n", cap.HasL3CA()) + fmt.Printf(" L2 Cache Allocation (CAT): %v\n", cap.HasL2CA()) + fmt.Printf(" Memory Bandwidth Alloc: %v\n", cap.HasMBA()) + fmt.Printf(" Resource Monitoring: %v\n", cap.HasMon()) + + // Step 7: Get CPU information + cpuInfo, err := cap.GetCPUInfo() + if err != nil { + log.Printf("Warning: Could not get CPU info: %v", err) + return + } + + fmt.Println("\nSystem Information:") + fmt.Printf(" CPU Vendor: %s\n", pqos.VendorToString(cpuInfo.Vendor)) + fmt.Printf(" Total Cores: %d\n", cpuInfo.NumCores) + fmt.Printf(" Sockets: %d\n", cpuInfo.GetNumSockets()) + + if cpuInfo.L3.Detected { + fmt.Printf(" L3 Cache: %d MB total\n", cpuInfo.GetL3CacheSize()/(1024*1024)) + } + + // Step 8: If L3 CAT is supported, show details + if cap.HasL3CA() { + fmt.Println("\nL3 Cache Allocation Details:") + l3ca, err := cap.GetL3CA() + if err != nil { + log.Printf(" Error: %v", err) + } else { + fmt.Printf(" Classes of Service: %d\n", l3ca.NumClasses) + fmt.Printf(" Cache Ways: %d\n", l3ca.NumWays) + fmt.Printf(" Way Size: %d KB\n", l3ca.WaySize/1024) + fmt.Printf(" CDP Support: %v\n", l3ca.CDP) + if l3ca.CDP { + fmt.Printf(" CDP Enabled: %v\n", l3ca.CDPOn) + } + } + } + + // Step 9: If monitoring is supported, show event details + if cap.HasMon() { + fmt.Println("\nMonitoring Capabilities:") + mon, err := cap.GetMon() + if err != nil { + log.Printf(" Error: %v", err) + } else { + fmt.Printf(" Max RMID: %d\n", mon.MaxRMID) + fmt.Printf(" Supported Events: %d\n", mon.NumEvents) + if len(mon.Events) > 0 { + fmt.Println(" Events:") + for _, event := range mon.Events { + fmt.Printf(" - %s\n", pqos.MonEventToString(event.Type)) + } + } + } + } + + fmt.Println("\nSimple example completed successfully!") +} diff --git a/lib/go/go.mod b/lib/go/go.mod new file mode 100644 index 00000000..b5430cdc --- /dev/null +++ b/lib/go/go.mod @@ -0,0 +1,7 @@ +module github.com/intel/intel-cmt-cat/lib/go + +go 1.21 + +require ( +// No external dependencies - uses CGO to interface with libpqos +) diff --git a/lib/go/pqos/allocation.go b/lib/go/pqos/allocation.go new file mode 100644 index 00000000..fb82e827 --- /dev/null +++ b/lib/go/pqos/allocation.go @@ -0,0 +1,649 @@ +// Copyright(c) 2025 Zededa, Inc. +// SPDX-License-Identifier: BSD-3-Clause + +package pqos + +/* +#cgo LDFLAGS: -L${SRCDIR}/../../../lib -lpqos +#cgo CFLAGS: -I${SRCDIR}/../../../lib +#include +#include + +// Helper to set ways_mask in L3CA union +static inline void set_l3ca_ways_mask(struct pqos_l3ca *ca, uint64_t mask) { + ca->u.ways_mask = mask; +} + +// Helper to get ways_mask from L3CA union +static inline uint64_t get_l3ca_ways_mask(struct pqos_l3ca *ca) { + return ca->u.ways_mask; +} + +// Helper to set CDP masks in L3CA union +static inline void set_l3ca_cdp_masks(struct pqos_l3ca *ca, uint64_t code, uint64_t data) { + ca->u.s.code_mask = code; + ca->u.s.data_mask = data; +} + +// Helper to get CDP masks from L3CA union +static inline void get_l3ca_cdp_masks(struct pqos_l3ca *ca, uint64_t *code, uint64_t *data) { + *code = ca->u.s.code_mask; + *data = ca->u.s.data_mask; +} + +// Helper to set ways_mask in L2CA union +static inline void set_l2ca_ways_mask(struct pqos_l2ca *ca, uint64_t mask) { + ca->u.ways_mask = mask; +} + +// Helper to get ways_mask from L2CA union +static inline uint64_t get_l2ca_ways_mask(struct pqos_l2ca *ca) { + return ca->u.ways_mask; +} + +// Helper to set CDP masks in L2CA union +static inline void set_l2ca_cdp_masks(struct pqos_l2ca *ca, uint64_t code, uint64_t data) { + ca->u.s.code_mask = code; + ca->u.s.data_mask = data; +} + +// Helper to get CDP masks from L2CA union +static inline void get_l2ca_cdp_masks(struct pqos_l2ca *ca, uint64_t *code, uint64_t *data) { + *code = ca->u.s.code_mask; + *data = ca->u.s.data_mask; +} +*/ +import "C" +import ( + "fmt" +) + +// L3CA represents L3 Cache Allocation configuration +type L3CA struct { + // ClassID is the class of service + ClassID uint + // CDP indicates if code and data masks are used separately + CDP bool + // WaysMask is the bit mask for L3 cache ways (when CDP is false) + WaysMask uint64 + // CodeMask is the bit mask for code (when CDP is true) + CodeMask uint64 + // DataMask is the bit mask for data (when CDP is true) + DataMask uint64 +} + +// L2CA represents L2 Cache Allocation configuration +type L2CA struct { + // ClassID is the class of service + ClassID uint + // CDP indicates if code and data masks are used separately + CDP bool + // WaysMask is the bit mask for L2 cache ways (when CDP is false) + WaysMask uint64 + // CodeMask is the bit mask for code (when CDP is true) + CodeMask uint64 + // DataMask is the bit mask for data (when CDP is true) + DataMask uint64 +} + +// MBA represents Memory Bandwidth Allocation configuration +type MBA struct { + // ClassID is the class of service + ClassID uint + // MBMax is the maximum available bandwidth + // - Without MBA controller: percentage (0-100) + // - With MBA controller: MBps + MBMax uint + // Ctrl indicates if MBA controller is being used + Ctrl bool + // SMBA indicates if this is SMBA (Slow Memory Bandwidth Allocation) + SMBA bool +} + +// L3CASet configures L3 cache allocation for a given L3 cluster +// +// Parameters: +// - l3CatID: L3 cache allocation ID (cluster) +// - cos: slice of L3CA configurations to set +// +// Returns error if operation fails +func (p *PQoS) L3CASet(l3CatID uint, cos []L3CA) error { + p.mu.Lock() + defer p.mu.Unlock() + + if !p.initialized { + return ErrNotInitialized + } + + if len(cos) == 0 { + return fmt.Errorf("empty COS array") + } + + // Convert Go slice to C array + cCos := make([]C.struct_pqos_l3ca, len(cos)) + for i, ca := range cos { + cCos[i].class_id = C.uint(ca.ClassID) + cCos[i].cdp = C.int(0) + if ca.CDP { + cCos[i].cdp = C.int(1) + C.set_l3ca_cdp_masks(&cCos[i], C.uint64_t(ca.CodeMask), C.uint64_t(ca.DataMask)) + } else { + C.set_l3ca_ways_mask(&cCos[i], C.uint64_t(ca.WaysMask)) + } + } + + ret := C.pqos_l3ca_set(C.uint(l3CatID), C.uint(len(cos)), &cCos[0]) + if ret != C.PQOS_RETVAL_OK { + return retvalToError(ret, "pqos_l3ca_set") + } + + return nil +} + +// L3CAGet retrieves L3 cache allocation configuration for a given L3 cluster +// +// Parameters: +// - l3CatID: L3 cache allocation ID (cluster) +// - maxNumCOS: maximum number of classes to retrieve +// +// Returns slice of L3CA configurations and error if operation fails +func (p *PQoS) L3CAGet(l3CatID uint, maxNumCOS uint) ([]L3CA, error) { + p.mu.Lock() + defer p.mu.Unlock() + + if !p.initialized { + return nil, ErrNotInitialized + } + + if maxNumCOS == 0 { + return nil, fmt.Errorf("maxNumCOS must be greater than 0") + } + + // Allocate C array + cCos := make([]C.struct_pqos_l3ca, maxNumCOS) + var numCos C.uint + + ret := C.pqos_l3ca_get(C.uint(l3CatID), C.uint(maxNumCOS), &numCos, &cCos[0]) + if ret != C.PQOS_RETVAL_OK { + return nil, retvalToError(ret, "pqos_l3ca_get") + } + + // Convert C array to Go slice + result := make([]L3CA, numCos) + for i := C.uint(0); i < numCos; i++ { + ca := &cCos[i] + result[i].ClassID = uint(ca.class_id) + result[i].CDP = int(ca.cdp) != 0 + + if result[i].CDP { + var code, data C.uint64_t + C.get_l3ca_cdp_masks(ca, &code, &data) + result[i].CodeMask = uint64(code) + result[i].DataMask = uint64(data) + } else { + result[i].WaysMask = uint64(C.get_l3ca_ways_mask(ca)) + } + } + + return result, nil +} + +// L3CAGetMinCBMBits retrieves the minimum number of bits that must be set in cache bitmask +func (p *PQoS) L3CAGetMinCBMBits() (uint, error) { + p.mu.Lock() + defer p.mu.Unlock() + + if !p.initialized { + return 0, ErrNotInitialized + } + + var minBits C.uint + ret := C.pqos_l3ca_get_min_cbm_bits(&minBits) + if ret != C.PQOS_RETVAL_OK { + return 0, retvalToError(ret, "pqos_l3ca_get_min_cbm_bits") + } + + return uint(minBits), nil +} + +// L2CASet configures L2 cache allocation for a given L2 cluster +// +// Parameters: +// - l2ID: L2 cache ID (cluster) +// - cos: slice of L2CA configurations to set +// +// Returns error if operation fails +func (p *PQoS) L2CASet(l2ID uint, cos []L2CA) error { + p.mu.Lock() + defer p.mu.Unlock() + + if !p.initialized { + return ErrNotInitialized + } + + if len(cos) == 0 { + return fmt.Errorf("empty COS array") + } + + // Convert Go slice to C array + cCos := make([]C.struct_pqos_l2ca, len(cos)) + for i, ca := range cos { + cCos[i].class_id = C.uint(ca.ClassID) + cCos[i].cdp = C.int(0) + if ca.CDP { + cCos[i].cdp = C.int(1) + C.set_l2ca_cdp_masks(&cCos[i], C.uint64_t(ca.CodeMask), C.uint64_t(ca.DataMask)) + } else { + C.set_l2ca_ways_mask(&cCos[i], C.uint64_t(ca.WaysMask)) + } + } + + ret := C.pqos_l2ca_set(C.uint(l2ID), C.uint(len(cos)), &cCos[0]) + if ret != C.PQOS_RETVAL_OK { + return retvalToError(ret, "pqos_l2ca_set") + } + + return nil +} + +// L2CAGet retrieves L2 cache allocation configuration for a given L2 cluster +// +// Parameters: +// - l2ID: L2 cache ID (cluster) +// - maxNumCOS: maximum number of classes to retrieve +// +// Returns slice of L2CA configurations and error if operation fails +func (p *PQoS) L2CAGet(l2ID uint, maxNumCOS uint) ([]L2CA, error) { + p.mu.Lock() + defer p.mu.Unlock() + + if !p.initialized { + return nil, ErrNotInitialized + } + + if maxNumCOS == 0 { + return nil, fmt.Errorf("maxNumCOS must be greater than 0") + } + + // Allocate C array + cCos := make([]C.struct_pqos_l2ca, maxNumCOS) + var numCos C.uint + + ret := C.pqos_l2ca_get(C.uint(l2ID), C.uint(maxNumCOS), &numCos, &cCos[0]) + if ret != C.PQOS_RETVAL_OK { + return nil, retvalToError(ret, "pqos_l2ca_get") + } + + // Convert C array to Go slice + result := make([]L2CA, numCos) + for i := C.uint(0); i < numCos; i++ { + ca := &cCos[i] + result[i].ClassID = uint(ca.class_id) + result[i].CDP = int(ca.cdp) != 0 + + if result[i].CDP { + var code, data C.uint64_t + C.get_l2ca_cdp_masks(ca, &code, &data) + result[i].CodeMask = uint64(code) + result[i].DataMask = uint64(data) + } else { + result[i].WaysMask = uint64(C.get_l2ca_ways_mask(ca)) + } + } + + return result, nil +} + +// L2CAGetMinCBMBits retrieves the minimum number of bits that must be set in cache bitmask +func (p *PQoS) L2CAGetMinCBMBits() (uint, error) { + p.mu.Lock() + defer p.mu.Unlock() + + if !p.initialized { + return 0, ErrNotInitialized + } + + var minBits C.uint + ret := C.pqos_l2ca_get_min_cbm_bits(&minBits) + if ret != C.PQOS_RETVAL_OK { + return 0, retvalToError(ret, "pqos_l2ca_get_min_cbm_bits") + } + + return uint(minBits), nil +} + +// MBASet configures memory bandwidth allocation for a given MBA ID +// +// Parameters: +// - mbaID: MBA ID +// - requested: slice of requested MBA configurations +// +// Returns actual MBA configurations applied and error if operation fails +func (p *PQoS) MBASet(mbaID uint, requested []MBA) ([]MBA, error) { + p.mu.Lock() + defer p.mu.Unlock() + + if !p.initialized { + return nil, ErrNotInitialized + } + + if len(requested) == 0 { + return nil, fmt.Errorf("empty MBA array") + } + + // Convert Go slice to C arrays + cRequested := make([]C.struct_pqos_mba, len(requested)) + cActual := make([]C.struct_pqos_mba, len(requested)) + + for i, mba := range requested { + cRequested[i].class_id = C.uint(mba.ClassID) + cRequested[i].mb_max = C.uint(mba.MBMax) + cRequested[i].ctrl = C.int(0) + if mba.Ctrl { + cRequested[i].ctrl = C.int(1) + } + cRequested[i].smba = C.int(0) + if mba.SMBA { + cRequested[i].smba = C.int(1) + } + } + + ret := C.pqos_mba_set(C.uint(mbaID), C.uint(len(requested)), &cRequested[0], &cActual[0]) + if ret != C.PQOS_RETVAL_OK { + return nil, retvalToError(ret, "pqos_mba_set") + } + + // Convert actual values to Go slice + result := make([]MBA, len(requested)) + for i := 0; i < len(requested); i++ { + result[i].ClassID = uint(cActual[i].class_id) + result[i].MBMax = uint(cActual[i].mb_max) + result[i].Ctrl = int(cActual[i].ctrl) != 0 + result[i].SMBA = int(cActual[i].smba) != 0 + } + + return result, nil +} + +// MBAGet retrieves memory bandwidth allocation configuration for a given MBA ID +// +// Parameters: +// - mbaID: MBA ID +// - maxNumCOS: maximum number of classes to retrieve +// +// Returns slice of MBA configurations and error if operation fails +func (p *PQoS) MBAGet(mbaID uint, maxNumCOS uint) ([]MBA, error) { + p.mu.Lock() + defer p.mu.Unlock() + + if !p.initialized { + return nil, ErrNotInitialized + } + + if maxNumCOS == 0 { + return nil, fmt.Errorf("maxNumCOS must be greater than 0") + } + + // Allocate C array + cMBA := make([]C.struct_pqos_mba, maxNumCOS) + var numCos C.uint + + ret := C.pqos_mba_get(C.uint(mbaID), C.uint(maxNumCOS), &numCos, &cMBA[0]) + if ret != C.PQOS_RETVAL_OK { + return nil, retvalToError(ret, "pqos_mba_get") + } + + // Convert C array to Go slice + result := make([]MBA, numCos) + for i := C.uint(0); i < numCos; i++ { + result[i].ClassID = uint(cMBA[i].class_id) + result[i].MBMax = uint(cMBA[i].mb_max) + result[i].Ctrl = int(cMBA[i].ctrl) != 0 + result[i].SMBA = int(cMBA[i].smba) != 0 + } + + return result, nil +} + +// AllocAssocSet associates a logical core with a class of service +// +// Parameters: +// - lcore: logical core ID +// - classID: class of service +// +// Returns error if operation fails +func (p *PQoS) AllocAssocSet(lcore uint, classID uint) error { + p.mu.Lock() + defer p.mu.Unlock() + + if !p.initialized { + return ErrNotInitialized + } + + ret := C.pqos_alloc_assoc_set(C.uint(lcore), C.uint(classID)) + if ret != C.PQOS_RETVAL_OK { + return retvalToError(ret, "pqos_alloc_assoc_set") + } + + return nil +} + +// AllocAssocGet retrieves the class of service associated with a logical core +// +// Parameters: +// - lcore: logical core ID +// +// Returns class ID and error if operation fails +func (p *PQoS) AllocAssocGet(lcore uint) (uint, error) { + p.mu.Lock() + defer p.mu.Unlock() + + if !p.initialized { + return 0, ErrNotInitialized + } + + var classID C.uint + ret := C.pqos_alloc_assoc_get(C.uint(lcore), &classID) + if ret != C.PQOS_RETVAL_OK { + return 0, retvalToError(ret, "pqos_alloc_assoc_get") + } + + return uint(classID), nil +} + +// AllocAssocSetPID associates a task (process) with a class of service (OS interface) +// +// Parameters: +// - pid: process ID +// - classID: class of service +// +// Returns error if operation fails +func (p *PQoS) AllocAssocSetPID(pid int, classID uint) error { + p.mu.Lock() + defer p.mu.Unlock() + + if !p.initialized { + return ErrNotInitialized + } + + ret := C.pqos_alloc_assoc_set_pid(C.pid_t(pid), C.uint(classID)) + if ret != C.PQOS_RETVAL_OK { + return retvalToError(ret, "pqos_alloc_assoc_set_pid") + } + + return nil +} + +// AllocAssocGetPID retrieves the class of service associated with a task (OS interface) +// +// Parameters: +// - pid: process ID +// +// Returns class ID and error if operation fails +func (p *PQoS) AllocAssocGetPID(pid int) (uint, error) { + p.mu.Lock() + defer p.mu.Unlock() + + if !p.initialized { + return 0, ErrNotInitialized + } + + var classID C.uint + ret := C.pqos_alloc_assoc_get_pid(C.pid_t(pid), &classID) + if ret != C.PQOS_RETVAL_OK { + return 0, retvalToError(ret, "pqos_alloc_assoc_get_pid") + } + + return uint(classID), nil +} + +// AllocAssign assigns first available COS to cores +// +// Parameters: +// - technology: bit mask selecting technologies (1 << CapType) +// - cores: list of core IDs +// +// Returns assigned class ID and error if operation fails +func (p *PQoS) AllocAssign(technology uint, cores []uint) (uint, error) { + p.mu.Lock() + defer p.mu.Unlock() + + if !p.initialized { + return 0, ErrNotInitialized + } + + if len(cores) == 0 { + return 0, fmt.Errorf("empty cores array") + } + + // Convert Go slice to C array + cCores := make([]C.uint, len(cores)) + for i, core := range cores { + cCores[i] = C.uint(core) + } + + var classID C.uint + ret := C.pqos_alloc_assign(C.uint(technology), &cCores[0], C.uint(len(cores)), &classID) + if ret != C.PQOS_RETVAL_OK { + return 0, retvalToError(ret, "pqos_alloc_assign") + } + + return uint(classID), nil +} + +// AllocRelease reassigns cores to default COS#0 +// +// Parameters: +// - cores: list of core IDs +// +// Returns error if operation fails +func (p *PQoS) AllocRelease(cores []uint) error { + p.mu.Lock() + defer p.mu.Unlock() + + if !p.initialized { + return ErrNotInitialized + } + + if len(cores) == 0 { + return fmt.Errorf("empty cores array") + } + + // Convert Go slice to C array + cCores := make([]C.uint, len(cores)) + for i, core := range cores { + cCores[i] = C.uint(core) + } + + ret := C.pqos_alloc_release(&cCores[0], C.uint(len(cores))) + if ret != C.PQOS_RETVAL_OK { + return retvalToError(ret, "pqos_alloc_release") + } + + return nil +} + +// AllocAssignPID assigns first available COS to tasks (OS interface) +// +// Parameters: +// - technology: bit mask selecting technologies (1 << CapType) +// - pids: list of process IDs +// +// Returns assigned class ID and error if operation fails +func (p *PQoS) AllocAssignPID(technology uint, pids []int) (uint, error) { + p.mu.Lock() + defer p.mu.Unlock() + + if !p.initialized { + return 0, ErrNotInitialized + } + + if len(pids) == 0 { + return 0, fmt.Errorf("empty pids array") + } + + // Convert Go slice to C array + cPIDs := make([]C.pid_t, len(pids)) + for i, pid := range pids { + cPIDs[i] = C.pid_t(pid) + } + + var classID C.uint + ret := C.pqos_alloc_assign_pid(C.uint(technology), &cPIDs[0], C.uint(len(pids)), &classID) + if ret != C.PQOS_RETVAL_OK { + return 0, retvalToError(ret, "pqos_alloc_assign_pid") + } + + return uint(classID), nil +} + +// AllocReleasePID reassigns tasks to default COS#0 (OS interface) +// +// Parameters: +// - pids: list of process IDs +// +// Returns error if operation fails +func (p *PQoS) AllocReleasePID(pids []int) error { + p.mu.Lock() + defer p.mu.Unlock() + + if !p.initialized { + return ErrNotInitialized + } + + if len(pids) == 0 { + return fmt.Errorf("empty pids array") + } + + // Convert Go slice to C array + cPIDs := make([]C.pid_t, len(pids)) + for i, pid := range pids { + cPIDs[i] = C.pid_t(pid) + } + + ret := C.pqos_alloc_release_pid(&cPIDs[0], C.uint(len(pids))) + if ret != C.PQOS_RETVAL_OK { + return retvalToError(ret, "pqos_alloc_release_pid") + } + + return nil +} + +// AllocReset resets allocation configuration to default +func (p *PQoS) AllocReset() error { + p.mu.Lock() + defer p.mu.Unlock() + + if !p.initialized { + return ErrNotInitialized + } + + // Use pqos_alloc_reset_config with NULL to reset to defaults + // pqos_alloc_reset is deprecated, use pqos_alloc_reset_config instead + ret := C.pqos_alloc_reset_config(nil) + if ret != C.PQOS_RETVAL_OK { + return retvalToError(ret, "pqos_alloc_reset_config") + } + + return nil +} diff --git a/lib/go/pqos/capability.go b/lib/go/pqos/capability.go new file mode 100644 index 00000000..ffa9de79 --- /dev/null +++ b/lib/go/pqos/capability.go @@ -0,0 +1,409 @@ +// Copyright(c) 2025 Zededa, Inc. +// SPDX-License-Identifier: BSD-3-Clause + +package pqos + +/* +#cgo LDFLAGS: -L${SRCDIR}/../../../lib -lpqos +#cgo CFLAGS: -I${SRCDIR}/../../../lib +#include +#include + +// Helper functions to access union members (CGO doesn't handle unions well) +static inline struct pqos_cap_l3ca* get_l3ca_cap(struct pqos_capability *cap) { + return cap->u.l3ca; +} + +static inline struct pqos_cap_l2ca* get_l2ca_cap(struct pqos_capability *cap) { + return cap->u.l2ca; +} + +static inline struct pqos_cap_mba* get_mba_cap(struct pqos_capability *cap) { + return cap->u.mba; +} + +static inline struct pqos_cap_mon* get_mon_cap(struct pqos_capability *cap) { + return cap->u.mon; +} + +// Helper to access flexible array member in pqos_cap_mon +static inline struct pqos_monitor* get_mon_event(struct pqos_cap_mon *mon, unsigned int idx) { + return &mon->events[idx]; +} +*/ +import "C" +import ( + "fmt" +) + +// Capability types +const ( + // CapTypeMon represents monitoring capability + CapTypeMon = C.PQOS_CAP_TYPE_MON + // CapTypeL3CA represents L3 cache allocation capability + CapTypeL3CA = C.PQOS_CAP_TYPE_L3CA + // CapTypeL2CA represents L2 cache allocation capability + CapTypeL2CA = C.PQOS_CAP_TYPE_L2CA + // CapTypeMBA represents memory bandwidth allocation capability + CapTypeMBA = C.PQOS_CAP_TYPE_MBA + // CapTypeSMBA represents slow memory bandwidth allocation capability + CapTypeSMBA = C.PQOS_CAP_TYPE_SMBA +) + +// MonEvent represents monitoring event types +const ( + // MonEventL3Occup monitors LLC occupancy + MonEventL3Occup = C.PQOS_MON_EVENT_L3_OCCUP + // MonEventLMemBW monitors local memory bandwidth + MonEventLMemBW = C.PQOS_MON_EVENT_LMEM_BW + // MonEventTMemBW monitors total memory bandwidth + MonEventTMemBW = C.PQOS_MON_EVENT_TMEM_BW + // MonEventRMemBW monitors remote memory bandwidth (virtual event) + MonEventRMemBW = C.PQOS_MON_EVENT_RMEM_BW + // PerfEventLLCMiss monitors LLC misses + PerfEventLLCMiss = C.PQOS_PERF_EVENT_LLC_MISS + // PerfEventIPC monitors instructions per clock + PerfEventIPC = C.PQOS_PERF_EVENT_IPC + // PerfEventLLCRef monitors LLC references + PerfEventLLCRef = C.PQOS_PERF_EVENT_LLC_REF +) + +// Capability represents PQoS capabilities +type Capability struct { + cap *C.struct_pqos_cap + cpu *C.struct_pqos_cpuinfo +} + +// L3CACapability represents L3 Cache Allocation capability +type L3CACapability struct { + // MemSize is the byte size of the structure + MemSize uint + // NumClasses is the number of classes of service + NumClasses uint + // NumWays is the number of cache ways + NumWays uint + // WaySize is the way size in bytes + WaySize uint + // WayContention is the ways contention bit mask + WayContention uint64 + // CDP indicates if Code Data Prioritization is supported + CDP bool + // CDPOn indicates if CDP is currently enabled + CDPOn bool + // NonContiguousCBM indicates if non-contiguous CBM is supported + NonContiguousCBM bool + // IORDT indicates if I/O RDT is supported + IORDT bool + // IORDTOn indicates if I/O RDT is currently enabled + IORDTOn bool +} + +// L2CACapability represents L2 Cache Allocation capability +type L2CACapability struct { + // MemSize is the byte size of the structure + MemSize uint + // NumClasses is the number of classes of service + NumClasses uint + // NumWays is the number of cache ways + NumWays uint + // WaySize is the way size in bytes + WaySize uint + // WayContention is the ways contention bit mask + WayContention uint64 + // CDP indicates if Code Data Prioritization is supported + CDP bool + // CDPOn indicates if CDP is currently enabled + CDPOn bool + // NonContiguousCBM indicates if non-contiguous CBM is supported + NonContiguousCBM bool +} + +// MBACapability represents Memory Bandwidth Allocation capability +type MBACapability struct { + // MemSize is the byte size of the structure + MemSize uint + // NumClasses is the number of classes of service + NumClasses uint + // ThrottleMax is the maximum MBA can be throttled + ThrottleMax uint + // ThrottleStep is the MBA granularity + ThrottleStep uint + // IsLinear indicates if MBA is linear (true) or nonlinear (false) + IsLinear bool + // Ctrl indicates if MBA controller is supported + Ctrl bool + // CtrlOn indicates if MBA controller is enabled + CtrlOn bool + // MBA40 indicates if MBA 4.0 extensions are supported + MBA40 bool + // MBA40On indicates if MBA 4.0 extensions are enabled + MBA40On bool +} + +// Monitor represents a single monitoring capability +type Monitor struct { + // Type is the monitoring event type + Type uint + // MaxRMID is the maximum RMID supported for this event + MaxRMID uint + // ScaleFactor is the factor to scale RMID value to bytes + ScaleFactor uint32 + // CounterLength is the counter bit length + CounterLength uint + // IORDT indicates if I/O RDT monitoring is supported + IORDT bool +} + +// MonCapability represents monitoring capability +type MonCapability struct { + // MemSize is the byte size of the structure + MemSize uint + // MaxRMID is the maximum RMID supported by socket + MaxRMID uint + // L3Size is the L3 cache size in bytes + L3Size uint + // NumEvents is the number of supported events + NumEvents uint + // IORDT indicates if I/O RDT monitoring is supported + IORDT bool + // IORDTOn indicates if I/O RDT monitoring is enabled + IORDTOn bool + // SNCNum is the number of monitoring clusters + SNCNum uint + // SNCMode is the SNC mode + SNCMode int + // Events is the list of supported monitoring events + Events []Monitor +} + +// GetCapability retrieves PQoS capabilities and CPU information +func (p *PQoS) GetCapability() (*Capability, error) { + p.mu.Lock() + defer p.mu.Unlock() + + if !p.initialized { + return nil, fmt.Errorf("PQoS not initialized") + } + + var cap *C.struct_pqos_cap + var cpu *C.struct_pqos_cpuinfo + + ret := C.pqos_cap_get(&cap, &cpu) + if ret != C.PQOS_RETVAL_OK { + return nil, retvalToError(ret, "pqos_cap_get") + } + + return &Capability{cap: cap, cpu: cpu}, nil +} + +// GetL3CA retrieves L3 Cache Allocation capability +func (c *Capability) GetL3CA() (*L3CACapability, error) { + if c.cap == nil { + return nil, fmt.Errorf("capability not initialized") + } + + var l3cap *C.struct_pqos_capability + ret := C.pqos_cap_get_type(c.cap, C.PQOS_CAP_TYPE_L3CA, &l3cap) + if ret != C.PQOS_RETVAL_OK { + return nil, retvalToError(ret, "pqos_cap_get_type(L3CA)") + } + + l3 := C.get_l3ca_cap(l3cap) + if l3 == nil { + return nil, fmt.Errorf("L3CA capability not available") + } + return &L3CACapability{ + MemSize: uint(l3.mem_size), + NumClasses: uint(l3.num_classes), + NumWays: uint(l3.num_ways), + WaySize: uint(l3.way_size), + WayContention: uint64(l3.way_contention), + CDP: int(l3.cdp) != 0, + CDPOn: int(l3.cdp_on) != 0, + NonContiguousCBM: uint(l3.non_contiguous_cbm) != 0, + IORDT: int(l3.iordt) != 0, + IORDTOn: int(l3.iordt_on) != 0, + }, nil +} + +// GetL2CA retrieves L2 Cache Allocation capability +func (c *Capability) GetL2CA() (*L2CACapability, error) { + if c.cap == nil { + return nil, fmt.Errorf("capability not initialized") + } + + var l2cap *C.struct_pqos_capability + ret := C.pqos_cap_get_type(c.cap, C.PQOS_CAP_TYPE_L2CA, &l2cap) + if ret != C.PQOS_RETVAL_OK { + return nil, retvalToError(ret, "pqos_cap_get_type(L2CA)") + } + + l2 := C.get_l2ca_cap(l2cap) + if l2 == nil { + return nil, fmt.Errorf("L2CA capability not available") + } + return &L2CACapability{ + MemSize: uint(l2.mem_size), + NumClasses: uint(l2.num_classes), + NumWays: uint(l2.num_ways), + WaySize: uint(l2.way_size), + WayContention: uint64(l2.way_contention), + CDP: int(l2.cdp) != 0, + CDPOn: int(l2.cdp_on) != 0, + NonContiguousCBM: uint(l2.non_contiguous_cbm) != 0, + }, nil +} + +// GetMBA retrieves Memory Bandwidth Allocation capability +func (c *Capability) GetMBA() (*MBACapability, error) { + if c.cap == nil { + return nil, fmt.Errorf("capability not initialized") + } + + var mbacap *C.struct_pqos_capability + ret := C.pqos_cap_get_type(c.cap, C.PQOS_CAP_TYPE_MBA, &mbacap) + if ret != C.PQOS_RETVAL_OK { + return nil, retvalToError(ret, "pqos_cap_get_type(MBA)") + } + + mba := C.get_mba_cap(mbacap) + if mba == nil { + return nil, fmt.Errorf("MBA capability not available") + } + return &MBACapability{ + MemSize: uint(mba.mem_size), + NumClasses: uint(mba.num_classes), + ThrottleMax: uint(mba.throttle_max), + ThrottleStep: uint(mba.throttle_step), + IsLinear: int(mba.is_linear) != 0, + Ctrl: int(mba.ctrl) != 0, + CtrlOn: int(mba.ctrl_on) != 0, + MBA40: int(mba.mba40) != 0, + MBA40On: int(mba.mba40_on) != 0, + }, nil +} + +// GetMon retrieves monitoring capability +func (c *Capability) GetMon() (*MonCapability, error) { + if c.cap == nil { + return nil, fmt.Errorf("capability not initialized") + } + + var moncap *C.struct_pqos_capability + ret := C.pqos_cap_get_type(c.cap, C.PQOS_CAP_TYPE_MON, &moncap) + if ret != C.PQOS_RETVAL_OK { + return nil, retvalToError(ret, "pqos_cap_get_type(MON)") + } + + mon := C.get_mon_cap(moncap) + if mon == nil { + return nil, fmt.Errorf("MON capability not available") + } + capability := &MonCapability{ + MemSize: uint(mon.mem_size), + MaxRMID: uint(mon.max_rmid), + L3Size: uint(mon.l3_size), + NumEvents: uint(mon.num_events), + IORDT: int(mon.iordt) != 0, + IORDTOn: int(mon.iordt_on) != 0, + SNCNum: uint(mon.snc_num), + SNCMode: int(mon.snc_mode), + Events: make([]Monitor, 0, mon.num_events), + } + + // Convert event array to Go slice + if mon.num_events > 0 { + for i := C.uint(0); i < mon.num_events; i++ { + event := C.get_mon_event(mon, i) + capability.Events = append(capability.Events, Monitor{ + Type: uint(event._type), + MaxRMID: uint(event.max_rmid), + ScaleFactor: uint32(event.scale_factor), + CounterLength: uint(event.counter_length), + IORDT: int(event.iordt) != 0, + }) + } + } + + return capability, nil +} + +// HasL3CA checks if L3 Cache Allocation is supported +func (c *Capability) HasL3CA() bool { + if c.cap == nil { + return false + } + var l3cap *C.struct_pqos_capability + ret := C.pqos_cap_get_type(c.cap, C.PQOS_CAP_TYPE_L3CA, &l3cap) + return ret == C.PQOS_RETVAL_OK && l3cap != nil +} + +// HasL2CA checks if L2 Cache Allocation is supported +func (c *Capability) HasL2CA() bool { + if c.cap == nil { + return false + } + var l2cap *C.struct_pqos_capability + ret := C.pqos_cap_get_type(c.cap, C.PQOS_CAP_TYPE_L2CA, &l2cap) + return ret == C.PQOS_RETVAL_OK && l2cap != nil +} + +// HasMBA checks if Memory Bandwidth Allocation is supported +func (c *Capability) HasMBA() bool { + if c.cap == nil { + return false + } + var mbacap *C.struct_pqos_capability + ret := C.pqos_cap_get_type(c.cap, C.PQOS_CAP_TYPE_MBA, &mbacap) + return ret == C.PQOS_RETVAL_OK && mbacap != nil +} + +// HasMon checks if monitoring is supported +func (c *Capability) HasMon() bool { + if c.cap == nil { + return false + } + var moncap *C.struct_pqos_capability + ret := C.pqos_cap_get_type(c.cap, C.PQOS_CAP_TYPE_MON, &moncap) + return ret == C.PQOS_RETVAL_OK && moncap != nil +} + +// CapTypeToString converts a capability type to a string +func CapTypeToString(capType int) string { + switch capType { + case CapTypeMon: + return "MON" + case CapTypeL3CA: + return "L3CA" + case CapTypeL2CA: + return "L2CA" + case CapTypeMBA: + return "MBA" + case CapTypeSMBA: + return "SMBA" + default: + return fmt.Sprintf("UNKNOWN(%d)", capType) + } +} + +// MonEventToString converts a monitoring event to a string +func MonEventToString(event uint) string { + switch event { + case MonEventL3Occup: + return "L3_OCCUP" + case MonEventLMemBW: + return "LMEM_BW" + case MonEventTMemBW: + return "TMEM_BW" + case MonEventRMemBW: + return "RMEM_BW" + case PerfEventLLCMiss: + return "LLC_MISS" + case PerfEventIPC: + return "IPC" + case PerfEventLLCRef: + return "LLC_REF" + default: + return fmt.Sprintf("UNKNOWN(0x%x)", event) + } +} diff --git a/lib/go/pqos/cpuinfo.go b/lib/go/pqos/cpuinfo.go new file mode 100644 index 00000000..b8877208 --- /dev/null +++ b/lib/go/pqos/cpuinfo.go @@ -0,0 +1,309 @@ +// Copyright(c) 2025 Zededa, Inc. +// SPDX-License-Identifier: BSD-3-Clause + +package pqos + +/* +#cgo LDFLAGS: -L${SRCDIR}/../../../lib -lpqos +#cgo CFLAGS: -I${SRCDIR}/../../../lib +#include +#include + +// Helper to access flexible array member in pqos_cpuinfo +static inline struct pqos_coreinfo* get_core_info(struct pqos_cpuinfo *cpu, unsigned int idx) { + return &cpu->cores[idx]; +} +*/ +import "C" +import ( + "fmt" +) + +// Vendor represents CPU vendor +const ( + VendorUnknown = C.PQOS_VENDOR_UNKNOWN + VendorIntel = C.PQOS_VENDOR_INTEL + VendorAMD = C.PQOS_VENDOR_AMD +) + +// CoreInfo represents information about a single CPU core +type CoreInfo struct { + // LCore is the logical core ID + LCore uint + // Socket is the socket ID in the system + Socket uint + // CoreID is the physical core ID within the socket. SMT siblings + // share the same (Socket, CoreID) pair. + CoreID uint + // L3ID is the L3/LLC cluster ID + L3ID uint + // L2ID is the L2 cluster ID + L2ID uint + // L3CATID is the L3 CAT classes ID + L3CATID uint + // MBAID is the MBA ID + MBAID uint + // NUMA is the NUMA node in the system + NUMA uint + // SMBAID is the SMBA ID + SMBAID uint +} + +// CacheInfo represents cache information +type CacheInfo struct { + // Detected indicates if cache was detected and is valid + Detected bool + // NumWays is the number of cache ways + NumWays uint + // NumSets is the number of sets + NumSets uint + // NumPartitions is the number of partitions + NumPartitions uint + // LineSize is the cache line size in bytes + LineSize uint + // TotalSize is the total cache size in bytes + TotalSize uint + // WaySize is the cache way size in bytes + WaySize uint +} + +// CPUInfo represents CPU topology information +type CPUInfo struct { + // MemSize is the byte size of the structure + MemSize uint + // L2 contains L2 cache information + L2 CacheInfo + // L3 contains L3 cache information + L3 CacheInfo + // Vendor is the CPU vendor (Intel/AMD) + Vendor int + // NumCores is the number of cores in the system + NumCores uint + // Cores is the list of core information + Cores []CoreInfo +} + +// GetCPUInfo retrieves CPU topology information from the capability +func (c *Capability) GetCPUInfo() (*CPUInfo, error) { + if c.cpu == nil { + return nil, fmt.Errorf("CPU info not available") + } + + cpuInfo := &CPUInfo{ + MemSize: uint(c.cpu.mem_size), + Vendor: int(c.cpu.vendor), + NumCores: uint(c.cpu.num_cores), + L2: CacheInfo{ + Detected: int(c.cpu.l2.detected) != 0, + NumWays: uint(c.cpu.l2.num_ways), + NumSets: uint(c.cpu.l2.num_sets), + NumPartitions: uint(c.cpu.l2.num_partitions), + LineSize: uint(c.cpu.l2.line_size), + TotalSize: uint(c.cpu.l2.total_size), + WaySize: uint(c.cpu.l2.way_size), + }, + L3: CacheInfo{ + Detected: int(c.cpu.l3.detected) != 0, + NumWays: uint(c.cpu.l3.num_ways), + NumSets: uint(c.cpu.l3.num_sets), + NumPartitions: uint(c.cpu.l3.num_partitions), + LineSize: uint(c.cpu.l3.line_size), + TotalSize: uint(c.cpu.l3.total_size), + WaySize: uint(c.cpu.l3.way_size), + }, + Cores: make([]CoreInfo, 0, c.cpu.num_cores), + } + + // Convert cores array to Go slice + if c.cpu.num_cores > 0 { + for i := C.uint(0); i < c.cpu.num_cores; i++ { + core := C.get_core_info(c.cpu, i) + cpuInfo.Cores = append(cpuInfo.Cores, CoreInfo{ + LCore: uint(core.lcore), + Socket: uint(core.socket), + CoreID: uint(core.core_id), + L3ID: uint(core.l3_id), + L2ID: uint(core.l2_id), + L3CATID: uint(core.l3cat_id), + MBAID: uint(core.mba_id), + NUMA: uint(core.numa), + SMBAID: uint(core.smba_id), + }) + } + } + + return cpuInfo, nil +} + +// GetNumCores returns the number of CPU cores +func (c *CPUInfo) GetNumCores() uint { + return c.NumCores +} + +// GetNumSockets returns the number of CPU sockets +func (c *CPUInfo) GetNumSockets() uint { + sockets := make(map[uint]bool) + for _, core := range c.Cores { + sockets[core.Socket] = true + } + return uint(len(sockets)) +} + +// GetCoresBySocket returns all cores for a given socket +func (c *CPUInfo) GetCoresBySocket(socket uint) []CoreInfo { + var cores []CoreInfo + for _, core := range c.Cores { + if core.Socket == socket { + cores = append(cores, core) + } + } + return cores +} + +// GetCoresByL3ID returns all cores for a given L3 cluster +func (c *CPUInfo) GetCoresByL3ID(l3ID uint) []CoreInfo { + var cores []CoreInfo + for _, core := range c.Cores { + if core.L3ID == l3ID { + cores = append(cores, core) + } + } + return cores +} + +// GetCoresByL2ID returns all cores for a given L2 cluster +func (c *CPUInfo) GetCoresByL2ID(l2ID uint) []CoreInfo { + var cores []CoreInfo + for _, core := range c.Cores { + if core.L2ID == l2ID { + cores = append(cores, core) + } + } + return cores +} + +// GetCoresByNUMA returns all cores for a given NUMA node +func (c *CPUInfo) GetCoresByNUMA(numa uint) []CoreInfo { + var cores []CoreInfo + for _, core := range c.Cores { + if core.NUMA == numa { + cores = append(cores, core) + } + } + return cores +} + +// GetSocketIDs returns a list of all socket IDs +func (c *CPUInfo) GetSocketIDs() []uint { + sockets := make(map[uint]bool) + for _, core := range c.Cores { + sockets[core.Socket] = true + } + + ids := make([]uint, 0, len(sockets)) + for socket := range sockets { + ids = append(ids, socket) + } + return ids +} + +// GetL3IDs returns a list of all L3 cluster IDs +func (c *CPUInfo) GetL3IDs() []uint { + l3IDs := make(map[uint]bool) + for _, core := range c.Cores { + l3IDs[core.L3ID] = true + } + + ids := make([]uint, 0, len(l3IDs)) + for l3ID := range l3IDs { + ids = append(ids, l3ID) + } + return ids +} + +// GetL2IDs returns a list of all L2 cluster IDs +func (c *CPUInfo) GetL2IDs() []uint { + l2IDs := make(map[uint]bool) + for _, core := range c.Cores { + l2IDs[core.L2ID] = true + } + + ids := make([]uint, 0, len(l2IDs)) + for l2ID := range l2IDs { + ids = append(ids, l2ID) + } + return ids +} + +// GetL3CATIDs returns a list of all L3 CAT cluster IDs +func (c *CPUInfo) GetL3CATIDs() []uint { + l3CATIDs := make(map[uint]bool) + for _, core := range c.Cores { + l3CATIDs[core.L3CATID] = true + } + + ids := make([]uint, 0, len(l3CATIDs)) + for l3CATID := range l3CATIDs { + ids = append(ids, l3CATID) + } + return ids +} + +// GetMBAIDs returns a list of all MBA IDs +func (c *CPUInfo) GetMBAIDs() []uint { + mbaIDs := make(map[uint]bool) + for _, core := range c.Cores { + mbaIDs[core.MBAID] = true + } + + ids := make([]uint, 0, len(mbaIDs)) + for mbaID := range mbaIDs { + ids = append(ids, mbaID) + } + return ids +} + +// GetNumL2Domains returns the number of L2 cache domains +func (c *CPUInfo) GetNumL2Domains() uint { + return uint(len(c.GetL2IDs())) +} + +// VendorToString converts a vendor constant to a string +func VendorToString(vendor int) string { + switch vendor { + case VendorIntel: + return "Intel" + case VendorAMD: + return "AMD" + case VendorUnknown: + return "Unknown" + default: + return fmt.Sprintf("Unknown(%d)", vendor) + } +} + +// FindCore finds a core by its logical core ID +func (c *CPUInfo) FindCore(lcore uint) *CoreInfo { + for i := range c.Cores { + if c.Cores[i].LCore == lcore { + return &c.Cores[i] + } + } + return nil +} + +// GetL3CacheSize returns the total L3 cache size in bytes +func (c *CPUInfo) GetL3CacheSize() uint { + if c.L3.Detected { + return c.L3.TotalSize + } + return 0 +} + +// GetL2CacheSize returns the total L2 cache size in bytes +func (c *CPUInfo) GetL2CacheSize() uint { + if c.L2.Detected { + return c.L2.TotalSize + } + return 0 +} diff --git a/lib/go/pqos/monitoring.go b/lib/go/pqos/monitoring.go new file mode 100644 index 00000000..cb0d72e9 --- /dev/null +++ b/lib/go/pqos/monitoring.go @@ -0,0 +1,500 @@ +// Copyright(c) 2025 Zededa, Inc. +// SPDX-License-Identifier: BSD-3-Clause + +package pqos + +/* +#cgo LDFLAGS: -L${SRCDIR}/../../../lib -lpqos +#cgo CFLAGS: -I${SRCDIR}/../../../lib +#include +#include +#include + +// Helper to get number of cores from mon_data +static inline unsigned get_mon_num_cores(struct pqos_mon_data *group) { + return group->num_cores; +} + +// Helper to get core from mon_data cores array +static inline unsigned get_mon_core(struct pqos_mon_data *group, unsigned idx) { + return group->cores[idx]; +} + +// Helper to get number of PIDs from mon_data +static inline unsigned get_mon_num_pids(struct pqos_mon_data *group) { + return group->num_pids; +} + +// Helper to get PID from mon_data pids array +static inline pid_t get_mon_pid(struct pqos_mon_data *group, unsigned idx) { + return group->pids[idx]; +} +*/ +import "C" +import ( + "fmt" +) + +// EventValues represents monitoring event values +type EventValues struct { + // LLC is the L3 cache occupancy in bytes + LLC uint64 + // MBMLocal is the local memory bandwidth reading in bytes + MBMLocal uint64 + // MBMTotal is the total memory bandwidth reading in bytes + MBMTotal uint64 + // MBMRemote is the remote memory bandwidth reading in bytes + MBMRemote uint64 + // MBMLocalDelta is the local memory bandwidth delta in bytes + MBMLocalDelta uint64 + // MBMTotalDelta is the total memory bandwidth delta in bytes + MBMTotalDelta uint64 + // MBMRemoteDelta is the remote memory bandwidth delta in bytes + MBMRemoteDelta uint64 + // IPCRetired is the instructions retired reading + IPCRetired uint64 + // IPCRetiredDelta is the instructions retired delta + IPCRetiredDelta uint64 + // IPCUnhalted is the unhalted cycles reading + IPCUnhalted uint64 + // IPCUnhaltedDelta is the unhalted cycles delta + IPCUnhaltedDelta uint64 + // LLCMissTotal is the LLC miss counter + LLCMissTotal uint64 + // LLCMissDelta is the LLC miss delta + LLCMissDelta uint64 + // LLCRefTotal is the LLC reference counter + LLCRefTotal uint64 + // LLCRefDelta is the LLC reference delta + LLCRefDelta uint64 +} + +// MonData represents a monitoring group +type MonData struct { + // Valid indicates if the structure is valid + Valid bool + // Event is the combination of monitored events + Event uint + // Values contains the monitoring event values + Values EventValues + // Cores is the list of monitored core IDs (core monitoring) + Cores []uint + // PIDs is the list of monitored process IDs (process monitoring) + PIDs []int + + // Internal C pointer (not exposed to users) + group *C.struct_pqos_mon_data +} + +// MonStartCores starts resource monitoring on selected cores +// +// Parameters: +// - cores: array of logical core IDs to monitor +// - event: combination of monitoring events (MonEventL3Occup, MonEventTMemBW, etc.) +// +// Returns MonData structure and error if operation fails +func (p *PQoS) MonStartCores(cores []uint, event uint) (*MonData, error) { + p.mu.Lock() + defer p.mu.Unlock() + + if !p.initialized { + return nil, ErrNotInitialized + } + + if len(cores) == 0 { + return nil, fmt.Errorf("empty cores array") + } + + // Convert Go slice to C array + cCores := make([]C.uint, len(cores)) + for i, core := range cores { + cCores[i] = C.uint(core) + } + + var group *C.struct_pqos_mon_data + ret := C.pqos_mon_start_cores( + C.uint(len(cores)), + &cCores[0], + C.enum_pqos_mon_event(event), + nil, // context + nil, // mem_region + &group, + ) + + if ret != C.PQOS_RETVAL_OK { + return nil, retvalToError(ret, "pqos_mon_start_cores") + } + + return cMonDataToGo(group), nil +} + +// MonStartPIDs starts resource monitoring on selected processes +// +// Parameters: +// - pids: array of process IDs to monitor +// - event: combination of monitoring events +// +// Returns MonData structure and error if operation fails +func (p *PQoS) MonStartPIDs(pids []int, event uint) (*MonData, error) { + p.mu.Lock() + defer p.mu.Unlock() + + if !p.initialized { + return nil, ErrNotInitialized + } + + if len(pids) == 0 { + return nil, fmt.Errorf("empty pids array") + } + + // Convert Go slice to C array + cPIDs := make([]C.pid_t, len(pids)) + for i, pid := range pids { + cPIDs[i] = C.pid_t(pid) + } + + var group *C.struct_pqos_mon_data + ret := C.pqos_mon_start_pids2( + C.uint(len(pids)), + &cPIDs[0], + C.enum_pqos_mon_event(event), + nil, // context + &group, + ) + + if ret != C.PQOS_RETVAL_OK { + return nil, retvalToError(ret, "pqos_mon_start_pids2") + } + + return cMonDataToGo(group), nil +} + +// MonAddPIDs adds PIDs to an existing monitoring group +// +// Parameters: +// - group: monitoring group to add PIDs to +// - pids: array of process IDs to add +// +// Returns error if operation fails +func (p *PQoS) MonAddPIDs(group *MonData, pids []int) error { + p.mu.Lock() + defer p.mu.Unlock() + + if !p.initialized { + return ErrNotInitialized + } + + if group == nil || group.group == nil { + return fmt.Errorf("invalid monitoring group") + } + + if len(pids) == 0 { + return fmt.Errorf("empty pids array") + } + + // Convert Go slice to C array + cPIDs := make([]C.pid_t, len(pids)) + for i, pid := range pids { + cPIDs[i] = C.pid_t(pid) + } + + ret := C.pqos_mon_add_pids(C.uint(len(pids)), &cPIDs[0], group.group) + if ret != C.PQOS_RETVAL_OK { + return retvalToError(ret, "pqos_mon_add_pids") + } + + // Update Go structure + updateMonDataFromC(group) + + return nil +} + +// MonRemovePIDs removes PIDs from an existing monitoring group +// +// Parameters: +// - group: monitoring group to remove PIDs from +// - pids: array of process IDs to remove +// +// Returns error if operation fails +func (p *PQoS) MonRemovePIDs(group *MonData, pids []int) error { + p.mu.Lock() + defer p.mu.Unlock() + + if !p.initialized { + return ErrNotInitialized + } + + if group == nil || group.group == nil { + return fmt.Errorf("invalid monitoring group") + } + + if len(pids) == 0 { + return fmt.Errorf("empty pids array") + } + + // Convert Go slice to C array + cPIDs := make([]C.pid_t, len(pids)) + for i, pid := range pids { + cPIDs[i] = C.pid_t(pid) + } + + ret := C.pqos_mon_remove_pids(C.uint(len(pids)), &cPIDs[0], group.group) + if ret != C.PQOS_RETVAL_OK { + return retvalToError(ret, "pqos_mon_remove_pids") + } + + // Update Go structure + updateMonDataFromC(group) + + return nil +} + +// MonStop stops resource monitoring for a group +// +// Parameters: +// - group: monitoring group to stop +// +// Returns error if operation fails +func (p *PQoS) MonStop(group *MonData) error { + p.mu.Lock() + defer p.mu.Unlock() + + if !p.initialized { + return ErrNotInitialized + } + + if group == nil || group.group == nil { + return fmt.Errorf("invalid monitoring group") + } + + ret := C.pqos_mon_stop(group.group) + if ret != C.PQOS_RETVAL_OK { + return retvalToError(ret, "pqos_mon_stop") + } + + // Invalidate the group + group.Valid = false + group.group = nil + + return nil +} + +// MonPoll polls monitoring data for groups +// +// Parameters: +// - groups: array of monitoring groups to poll +// +// Returns error if operation fails +func (p *PQoS) MonPoll(groups []*MonData) error { + p.mu.Lock() + defer p.mu.Unlock() + + if !p.initialized { + return ErrNotInitialized + } + + if len(groups) == 0 { + return fmt.Errorf("empty groups array") + } + + // Convert Go slice to C array of pointers + cGroups := make([]*C.struct_pqos_mon_data, len(groups)) + for i, group := range groups { + if group == nil || group.group == nil { + return fmt.Errorf("invalid monitoring group at index %d", i) + } + cGroups[i] = group.group + } + + ret := C.pqos_mon_poll(&cGroups[0], C.uint(len(groups))) + if ret != C.PQOS_RETVAL_OK && ret != C.PQOS_RETVAL_OVERFLOW { + return retvalToError(ret, "pqos_mon_poll") + } + + // Update all groups from C data + for _, group := range groups { + updateMonDataFromC(group) + } + + if ret == C.PQOS_RETVAL_OVERFLOW { + return fmt.Errorf("MBM counter overflow detected") + } + + return nil +} + +// MonReset resets monitoring by binding all cores with RMID0 +func (p *PQoS) MonReset() error { + p.mu.Lock() + defer p.mu.Unlock() + + if !p.initialized { + return ErrNotInitialized + } + + ret := C.pqos_mon_reset() + if ret != C.PQOS_RETVAL_OK { + return retvalToError(ret, "pqos_mon_reset") + } + + return nil +} + +// MonAssocGet reads RMID association of a logical core +// +// Parameters: +// - lcore: logical core ID +// +// Returns RMID and error if operation fails +func (p *PQoS) MonAssocGet(lcore uint) (uint32, error) { + p.mu.Lock() + defer p.mu.Unlock() + + if !p.initialized { + return 0, ErrNotInitialized + } + + var rmid C.pqos_rmid_t + ret := C.pqos_mon_assoc_get(C.uint(lcore), &rmid) + if ret != C.PQOS_RETVAL_OK { + return 0, retvalToError(ret, "pqos_mon_assoc_get") + } + + return uint32(rmid), nil +} + +// Helper function to convert C mon_data to Go MonData +func cMonDataToGo(cGroup *C.struct_pqos_mon_data) *MonData { + if cGroup == nil { + return nil + } + + group := &MonData{ + Valid: int(cGroup.valid) != 0, + Event: uint(cGroup.event), + group: cGroup, + } + + // Copy event values + group.Values = EventValues{ + LLC: uint64(cGroup.values.llc), + MBMLocal: uint64(cGroup.values.mbm_local), + MBMTotal: uint64(cGroup.values.mbm_total), + MBMRemote: uint64(cGroup.values.mbm_remote), + MBMLocalDelta: uint64(cGroup.values.mbm_local_delta), + MBMTotalDelta: uint64(cGroup.values.mbm_total_delta), + MBMRemoteDelta: uint64(cGroup.values.mbm_remote_delta), + IPCRetired: uint64(cGroup.values.ipc_retired), + IPCRetiredDelta: uint64(cGroup.values.ipc_retired_delta), + IPCUnhalted: uint64(cGroup.values.ipc_unhalted), + IPCUnhaltedDelta: uint64(cGroup.values.ipc_unhalted_delta), + LLCMissTotal: uint64(cGroup.values.llc_misses), + LLCMissDelta: uint64(cGroup.values.llc_misses_delta), + LLCRefTotal: uint64(cGroup.values.llc_references), + LLCRefDelta: uint64(cGroup.values.llc_references_delta), + } + + // Copy cores if present + numCores := C.get_mon_num_cores(cGroup) + if numCores > 0 { + group.Cores = make([]uint, numCores) + for i := C.uint(0); i < numCores; i++ { + group.Cores[i] = uint(C.get_mon_core(cGroup, i)) + } + } + + // Copy PIDs if present + numPIDs := C.get_mon_num_pids(cGroup) + if numPIDs > 0 { + group.PIDs = make([]int, numPIDs) + for i := C.uint(0); i < numPIDs; i++ { + group.PIDs[i] = int(C.get_mon_pid(cGroup, i)) + } + } + + return group +} + +// Helper function to update MonData from C structure +func updateMonDataFromC(group *MonData) { + if group == nil || group.group == nil { + return + } + + cGroup := group.group + group.Valid = int(cGroup.valid) != 0 + group.Event = uint(cGroup.event) + + // Update event values + group.Values = EventValues{ + LLC: uint64(cGroup.values.llc), + MBMLocal: uint64(cGroup.values.mbm_local), + MBMTotal: uint64(cGroup.values.mbm_total), + MBMRemote: uint64(cGroup.values.mbm_remote), + MBMLocalDelta: uint64(cGroup.values.mbm_local_delta), + MBMTotalDelta: uint64(cGroup.values.mbm_total_delta), + MBMRemoteDelta: uint64(cGroup.values.mbm_remote_delta), + IPCRetired: uint64(cGroup.values.ipc_retired), + IPCRetiredDelta: uint64(cGroup.values.ipc_retired_delta), + IPCUnhalted: uint64(cGroup.values.ipc_unhalted), + IPCUnhaltedDelta: uint64(cGroup.values.ipc_unhalted_delta), + LLCMissTotal: uint64(cGroup.values.llc_misses), + LLCMissDelta: uint64(cGroup.values.llc_misses_delta), + LLCRefTotal: uint64(cGroup.values.llc_references), + LLCRefDelta: uint64(cGroup.values.llc_references_delta), + } + + // Update cores + numCores := C.get_mon_num_cores(cGroup) + if numCores > 0 { + group.Cores = make([]uint, numCores) + for i := C.uint(0); i < numCores; i++ { + group.Cores[i] = uint(C.get_mon_core(cGroup, i)) + } + } + + // Update PIDs + numPIDs := C.get_mon_num_pids(cGroup) + if numPIDs > 0 { + group.PIDs = make([]int, numPIDs) + for i := C.uint(0); i < numPIDs; i++ { + group.PIDs[i] = int(C.get_mon_pid(cGroup, i)) + } + } +} + +// GetLLCOccupancy returns the L3 cache occupancy in bytes +func (m *MonData) GetLLCOccupancy() uint64 { + return m.Values.LLC +} + +// GetMBMLocalBandwidth returns the local memory bandwidth in bytes +func (m *MonData) GetMBMLocalBandwidth() uint64 { + return m.Values.MBMLocalDelta +} + +// GetMBMTotalBandwidth returns the total memory bandwidth in bytes +func (m *MonData) GetMBMTotalBandwidth() uint64 { + return m.Values.MBMTotalDelta +} + +// GetMBMRemoteBandwidth returns the remote memory bandwidth in bytes +func (m *MonData) GetMBMRemoteBandwidth() uint64 { + return m.Values.MBMRemoteDelta +} + +// GetIPC returns the instructions per cycle +func (m *MonData) GetIPC() float64 { + if m.Values.IPCUnhaltedDelta == 0 { + return 0.0 + } + return float64(m.Values.IPCRetiredDelta) / float64(m.Values.IPCUnhaltedDelta) +} + +// GetLLCMissRate returns the LLC miss rate +func (m *MonData) GetLLCMissRate() float64 { + if m.Values.LLCRefDelta == 0 { + return 0.0 + } + return float64(m.Values.LLCMissDelta) / float64(m.Values.LLCRefDelta) +} diff --git a/lib/go/pqos/pqos.go b/lib/go/pqos/pqos.go new file mode 100644 index 00000000..22d09c63 --- /dev/null +++ b/lib/go/pqos/pqos.go @@ -0,0 +1,295 @@ +// Copyright(c) 2025 Zededa, Inc. +// SPDX-License-Identifier: BSD-3-Clause + +// Package pqos provides Go bindings for Intel(R) Resource Director Technology (RDT) +// Platform QoS (PQoS) library. +// +// This package wraps the libpqos C library and provides idiomatic Go interfaces +// for monitoring and allocation of platform resources including: +// - Cache Monitoring Technology (CMT) +// - Memory Bandwidth Monitoring (MBM) +// - Cache Allocation Technology (CAT) +// - Code and Data Prioritization (CDP) +// - Memory Bandwidth Allocation (MBA) +package pqos + +/* +#cgo LDFLAGS: -L${SRCDIR}/../../../lib -lpqos +#cgo CFLAGS: -I${SRCDIR}/../../../lib +#include +#include + +// Helper function to set the interface field (since 'interface' is a Go keyword) +static inline void set_pqos_config_interface(struct pqos_config *cfg, enum pqos_interface iface) { + cfg->interface = iface; +} +*/ +import "C" +import ( + "fmt" + "os" + "sync" + "unsafe" +) + +// Interface types define how the library interacts with the hardware +const ( + // InterMSR uses Model Specific Registers interface + InterMSR = C.PQOS_INTER_MSR + // InterOS uses Operating System interface (resctrl on Linux) + InterOS = C.PQOS_INTER_OS + // InterOSResctrlMon uses OS interface with resctrl monitoring + InterOSResctrlMon = C.PQOS_INTER_OS_RESCTRL_MON + // InterAuto automatically selects the best available interface + InterAuto = C.PQOS_INTER_AUTO +) + +// Log verbosity levels +const ( + // LogVerbositySilent suppresses all log messages + LogVerbositySilent = -1 + // LogVerbosityDefault shows warning and error messages + LogVerbosityDefault = 0 + // LogVerbosityVerbose shows warning, error and info messages + LogVerbosityVerbose = 1 + // LogVerbositySuperVerbose shows warning, error, info and debug messages + LogVerbositySuperVerbose = 2 +) + +// Return values +const ( + RetvalOK = C.PQOS_RETVAL_OK // Success + RetvalError = C.PQOS_RETVAL_ERROR // Generic error + RetvalParam = C.PQOS_RETVAL_PARAM // Parameter error + RetvalResource = C.PQOS_RETVAL_RESOURCE // Resource error + RetvalInit = C.PQOS_RETVAL_INIT // Initialization error + RetvalTransport = C.PQOS_RETVAL_TRANSPORT // Transport error + RetvalPerfCtr = C.PQOS_RETVAL_PERF_CTR // Performance counter error + RetvalBusy = C.PQOS_RETVAL_BUSY // Resource busy error + RetvalInter = C.PQOS_RETVAL_INTER // Interface not supported + RetvalOverflow = C.PQOS_RETVAL_OVERFLOW // Data overflow +) + +// Config represents PQoS library configuration +type Config struct { + // Interface specifies which interface to use (MSR, OS, etc.) + Interface int + // Verbose sets the logging verbosity level + Verbose int + // LogFile is an optional file for logging (defaults to stderr) + LogFile *os.File +} + +// PQoS represents the main library handle +type PQoS struct { + mu sync.Mutex + initialized bool +} + +var ( + instanceOnce sync.Once + instance *PQoS + + initOnce sync.Once + initErr error +) + +var ( + // ErrNotInitialized is returned when an operation is attempted before Init() + ErrNotInitialized = fmt.Errorf("pqos: not initialized") +) + +// GetInstance returns the singleton instance of PQoS. +// The instance is created lazily on first call. +func GetInstance() *PQoS { + instanceOnce.Do(func() { + instance = &PQoS{} + }) + return instance +} + +// Init initializes the PQoS library with the given configuration. +// This must be called before any other PQoS operations. +// +// The first call to Init() performs the actual initialization. +// Subsequent calls are no-ops and return nil (or the cached error). +// This is safe to call from multiple goroutines. +// +// Note: Only the first Init() call's config is used. Later calls +// ignore their config parameter and just mark the instance as initialized. +// +// Example: +// +// cfg := &pqos.Config{ +// Interface: pqos.InterOS, +// Verbose: pqos.LogVerbosityDefault, +// } +// pqos := pqos.GetInstance() +// if err := pqos.Init(cfg); err != nil { +// log.Fatal(err) +// } +// defer pqos.Fini() +func (p *PQoS) Init(cfg *Config) error { + p.mu.Lock() + defer p.mu.Unlock() + + // Fast path: this instance already initialized + if p.initialized { + return nil + } + + // Do the process-wide pqos_init() only once + initOnce.Do(func() { + if cfg == nil { + cfg = &Config{ + Interface: InterAuto, + Verbose: LogVerbosityDefault, + } + } + + var cConfig C.struct_pqos_config + // Note: 'interface' is a Go keyword, so we use a C helper function to set it + C.set_pqos_config_interface(&cConfig, C.enum_pqos_interface(cfg.Interface)) + cConfig.verbose = C.int(cfg.Verbose) + + // Set log file descriptor + if cfg.LogFile != nil { + cConfig.fd_log = C.int(cfg.LogFile.Fd()) + } else { + cConfig.fd_log = C.int(os.Stderr.Fd()) + } + + ret := C.pqos_init(&cConfig) + if ret != C.PQOS_RETVAL_OK { + initErr = retvalToError(ret, "pqos_init") + return + } + }) + + // Everyone sees the same init result + if initErr != nil { + return initErr + } + + p.initialized = true + return nil +} + +// Fini shuts down the PQoS library and releases resources. +// This should be called when the library is no longer needed. +// +// Note: This affects the entire process. After Fini() is called, +// Init() cannot be called again in the same process (sync.Once limitation). +func (p *PQoS) Fini() error { + p.mu.Lock() + defer p.mu.Unlock() + + if !p.initialized { + return ErrNotInitialized + } + + ret := C.pqos_fini() + if ret != C.PQOS_RETVAL_OK { + return retvalToError(ret, "pqos_fini") + } + + p.initialized = false + return nil +} + +// IsInitialized returns whether the library has been initialized +func (p *PQoS) IsInitialized() bool { + p.mu.Lock() + defer p.mu.Unlock() + return p.initialized +} + +// GetInterface returns the currently active PQoS interface +func (p *PQoS) GetInterface() (int, error) { + p.mu.Lock() + defer p.mu.Unlock() + + if !p.initialized { + return 0, ErrNotInitialized + } + + var iface C.enum_pqos_interface + ret := C.pqos_inter_get(&iface) + if ret != C.PQOS_RETVAL_OK { + return 0, retvalToError(ret, "pqos_inter_get") + } + + return int(iface), nil +} + +// retvalToError converts a C return value to a Go error with context +func retvalToError(retval C.int, function string) error { + var msg string + switch retval { + case C.PQOS_RETVAL_OK: + return nil + case C.PQOS_RETVAL_ERROR: + msg = "generic error" + case C.PQOS_RETVAL_PARAM: + msg = "parameter error" + case C.PQOS_RETVAL_RESOURCE: + msg = "resource error" + case C.PQOS_RETVAL_INIT: + msg = "initialization error" + case C.PQOS_RETVAL_TRANSPORT: + msg = "transport error" + case C.PQOS_RETVAL_PERF_CTR: + msg = "performance counter error" + case C.PQOS_RETVAL_BUSY: + msg = "resource busy" + case C.PQOS_RETVAL_INTER: + msg = "interface not supported" + case C.PQOS_RETVAL_OVERFLOW: + msg = "data overflow" + default: + msg = fmt.Sprintf("unknown error code %d", retval) + } + return fmt.Errorf("%s failed: %s", function, msg) +} + +// InterfaceToString converts an interface constant to a string name +func InterfaceToString(iface int) string { + switch iface { + case InterMSR: + return "MSR" + case InterOS: + return "OS" + case InterOSResctrlMon: + return "OS_RESCTRL_MON" + case InterAuto: + return "AUTO" + default: + return fmt.Sprintf("UNKNOWN(%d)", iface) + } +} + +// VerbosityToString converts a verbosity level to a string name +func VerbosityToString(verbose int) string { + switch verbose { + case LogVerbositySilent: + return "SILENT" + case LogVerbosityDefault: + return "DEFAULT" + case LogVerbosityVerbose: + return "VERBOSE" + case LogVerbositySuperVerbose: + return "SUPER_VERBOSE" + default: + return fmt.Sprintf("UNKNOWN(%d)", verbose) + } +} + +// safeCString converts a Go string to a C string, ensuring proper cleanup +func safeCString(s string) *C.char { + return C.CString(s) +} + +// freeCString frees a C string +func freeCString(s *C.char) { + C.free(unsafe.Pointer(s)) +} diff --git a/lib/go/pqos/pqos_test.go b/lib/go/pqos/pqos_test.go new file mode 100644 index 00000000..791e07ba --- /dev/null +++ b/lib/go/pqos/pqos_test.go @@ -0,0 +1,375 @@ +// Copyright(c) 2025 Zededa, Inc. +// SPDX-License-Identifier: BSD-3-Clause + +package pqos + +import ( + "testing" +) + +// TestGetInstance tests the singleton pattern +func TestGetInstance(t *testing.T) { + p1 := GetInstance() + p2 := GetInstance() + + if p1 != p2 { + t.Error("GetInstance() should return the same instance") + } + + if p1 == nil { + t.Error("GetInstance() returned nil") + } +} + +// TestIsInitializedBeforeInit tests that IsInitialized returns false before Init +func TestIsInitializedBeforeInit(t *testing.T) { + p := GetInstance() + if p.IsInitialized() { + t.Error("IsInitialized() should return false before Init()") + } +} + +// TestInitWithNilConfig tests initialization with nil config +func TestInitWithNilConfig(t *testing.T) { + p := GetInstance() + + // Clean up any previous initialization + if p.IsInitialized() { + p.Fini() + } + + err := p.Init(nil) + if err != nil { + t.Skipf("Init with nil config failed (may require hardware/privileges): %v", err) + } + defer p.Fini() + + if !p.IsInitialized() { + t.Error("IsInitialized() should return true after successful Init()") + } +} + +// TestInitWithConfig tests initialization with explicit config +func TestInitWithConfig(t *testing.T) { + p := GetInstance() + + // Clean up any previous initialization + if p.IsInitialized() { + p.Fini() + } + + cfg := &Config{ + Interface: InterAuto, + Verbose: LogVerbositySilent, + } + + err := p.Init(cfg) + if err != nil { + t.Skipf("Init failed (may require hardware/privileges): %v", err) + } + defer p.Fini() + + if !p.IsInitialized() { + t.Error("IsInitialized() should return true after successful Init()") + } +} + +// TestDoubleInit tests that calling Init twice is idempotent (no error) +func TestDoubleInit(t *testing.T) { + p := GetInstance() + + // Clean up any previous initialization + if p.IsInitialized() { + p.Fini() + } + + cfg := &Config{ + Interface: InterAuto, + Verbose: LogVerbositySilent, + } + + err := p.Init(cfg) + if err != nil { + t.Skipf("First Init failed (may require hardware/privileges): %v", err) + } + defer p.Fini() + + // Try to init again - should be no-op, no error + err = p.Init(cfg) + if err != nil { + t.Errorf("Second Init() should be idempotent (no error), got: %v", err) + } +} + +// TestFiniWithoutInit tests that Fini without Init returns an error +func TestFiniWithoutInit(t *testing.T) { + p := GetInstance() + + // Ensure not initialized + if p.IsInitialized() { + p.Fini() + } + + err := p.Fini() + if err == nil { + t.Error("Fini() without Init() should return an error") + } +} + +// TestInterfaceToString tests interface name conversion +func TestInterfaceToString(t *testing.T) { + tests := []struct { + iface int + expected string + }{ + {InterMSR, "MSR"}, + {InterOS, "OS"}, + {InterOSResctrlMon, "OS_RESCTRL_MON"}, + {InterAuto, "AUTO"}, + {999, "UNKNOWN(999)"}, + } + + for _, tt := range tests { + result := InterfaceToString(tt.iface) + if result != tt.expected { + t.Errorf("InterfaceToString(%d) = %s, expected %s", tt.iface, result, tt.expected) + } + } +} + +// TestVerbosityToString tests verbosity level name conversion +func TestVerbosityToString(t *testing.T) { + tests := []struct { + verbose int + expected string + }{ + {LogVerbositySilent, "SILENT"}, + {LogVerbosityDefault, "DEFAULT"}, + {LogVerbosityVerbose, "VERBOSE"}, + {LogVerbositySuperVerbose, "SUPER_VERBOSE"}, + {999, "UNKNOWN(999)"}, + } + + for _, tt := range tests { + result := VerbosityToString(tt.verbose) + if result != tt.expected { + t.Errorf("VerbosityToString(%d) = %s, expected %s", tt.verbose, result, tt.expected) + } + } +} + +// TestVendorToString tests vendor name conversion +func TestVendorToString(t *testing.T) { + tests := []struct { + vendor int + expected string + }{ + {VendorIntel, "Intel"}, + {VendorAMD, "AMD"}, + {VendorUnknown, "Unknown"}, + {999, "Unknown(999)"}, + } + + for _, tt := range tests { + result := VendorToString(tt.vendor) + if result != tt.expected { + t.Errorf("VendorToString(%d) = %s, expected %s", tt.vendor, result, tt.expected) + } + } +} + +// TestCapTypeToString tests capability type name conversion +func TestCapTypeToString(t *testing.T) { + tests := []struct { + capType int + expected string + }{ + {CapTypeMon, "MON"}, + {CapTypeL3CA, "L3CA"}, + {CapTypeL2CA, "L2CA"}, + {CapTypeMBA, "MBA"}, + {CapTypeSMBA, "SMBA"}, + {999, "UNKNOWN(999)"}, + } + + for _, tt := range tests { + result := CapTypeToString(tt.capType) + if result != tt.expected { + t.Errorf("CapTypeToString(%d) = %s, expected %s", tt.capType, result, tt.expected) + } + } +} + +// TestMonEventToString tests monitoring event name conversion +func TestMonEventToString(t *testing.T) { + tests := []struct { + event uint + expected string + }{ + {MonEventL3Occup, "L3_OCCUP"}, + {MonEventLMemBW, "LMEM_BW"}, + {MonEventTMemBW, "TMEM_BW"}, + {MonEventRMemBW, "RMEM_BW"}, + {PerfEventLLCMiss, "LLC_MISS"}, + {PerfEventIPC, "IPC"}, + {PerfEventLLCRef, "LLC_REF"}, + } + + for _, tt := range tests { + result := MonEventToString(tt.event) + if result != tt.expected { + t.Errorf("MonEventToString(0x%x) = %s, expected %s", tt.event, result, tt.expected) + } + } +} + +// TestGetCapabilityWithoutInit tests that GetCapability fails without Init +func TestGetCapabilityWithoutInit(t *testing.T) { + p := GetInstance() + + // Ensure not initialized + if p.IsInitialized() { + p.Fini() + } + + _, err := p.GetCapability() + if err == nil { + t.Error("GetCapability() without Init() should return an error") + } +} + +// TestGetInterfaceWithoutInit tests that GetInterface fails without Init +func TestGetInterfaceWithoutInit(t *testing.T) { + p := GetInstance() + + // Ensure not initialized + if p.IsInitialized() { + p.Fini() + } + + _, err := p.GetInterface() + if err == nil { + t.Error("GetInterface() without Init() should return an error") + } +} + +// Benchmark tests +func BenchmarkGetInstance(b *testing.B) { + for i := 0; i < b.N; i++ { + _ = GetInstance() + } +} + +func BenchmarkInterfaceToString(b *testing.B) { + for i := 0; i < b.N; i++ { + _ = InterfaceToString(InterOS) + } +} + +// TestCPUInfoL2Methods tests L2 topology discovery methods +func TestCPUInfoL2Methods(t *testing.T) { + // Create a mock CPUInfo with some test data + cpuInfo := &CPUInfo{ + NumCores: 8, + Cores: []CoreInfo{ + {LCore: 0, Socket: 0, L2ID: 0, L3ID: 0}, + {LCore: 1, Socket: 0, L2ID: 0, L3ID: 0}, // Shares L2 with core 0 + {LCore: 2, Socket: 0, L2ID: 1, L3ID: 0}, + {LCore: 3, Socket: 0, L2ID: 1, L3ID: 0}, // Shares L2 with core 2 + {LCore: 4, Socket: 0, L2ID: 2, L3ID: 0}, + {LCore: 5, Socket: 0, L2ID: 2, L3ID: 0}, // Shares L2 with core 4 + {LCore: 6, Socket: 0, L2ID: 3, L3ID: 0}, + {LCore: 7, Socket: 0, L2ID: 3, L3ID: 0}, // Shares L2 with core 6 + }, + } + + // Test GetL2IDs + l2IDs := cpuInfo.GetL2IDs() + if len(l2IDs) != 4 { + t.Errorf("GetL2IDs() returned %d domains, expected 4", len(l2IDs)) + } + + // Test GetNumL2Domains + numDomains := cpuInfo.GetNumL2Domains() + if numDomains != 4 { + t.Errorf("GetNumL2Domains() = %d, expected 4", numDomains) + } + + // Test GetCoresByL2ID + cores := cpuInfo.GetCoresByL2ID(0) + if len(cores) != 2 { + t.Errorf("GetCoresByL2ID(0) returned %d cores, expected 2", len(cores)) + } + if cores[0].LCore != 0 || cores[1].LCore != 1 { + t.Errorf("GetCoresByL2ID(0) returned wrong cores") + } + + cores = cpuInfo.GetCoresByL2ID(1) + if len(cores) != 2 { + t.Errorf("GetCoresByL2ID(1) returned %d cores, expected 2", len(cores)) + } + if cores[0].LCore != 2 || cores[1].LCore != 3 { + t.Errorf("GetCoresByL2ID(1) returned wrong cores") + } + + // Test with non-existent L2ID + cores = cpuInfo.GetCoresByL2ID(999) + if len(cores) != 0 { + t.Errorf("GetCoresByL2ID(999) should return empty slice") + } +} + +// TestCPUInfoL2MethodsComplexTopology tests L2 methods with complex topology +func TestCPUInfoL2MethodsComplexTopology(t *testing.T) { + // Simulate complex topology where non-adjacent cores share L2 + cpuInfo := &CPUInfo{ + NumCores: 8, + Cores: []CoreInfo{ + {LCore: 0, Socket: 0, L2ID: 0, L3ID: 0}, + {LCore: 1, Socket: 0, L2ID: 1, L3ID: 0}, + {LCore: 2, Socket: 0, L2ID: 2, L3ID: 0}, + {LCore: 3, Socket: 0, L2ID: 3, L3ID: 0}, + {LCore: 4, Socket: 0, L2ID: 0, L3ID: 0}, // Shares L2 with core 0 (non-adjacent) + {LCore: 5, Socket: 0, L2ID: 1, L3ID: 0}, // Shares L2 with core 1 + {LCore: 6, Socket: 0, L2ID: 2, L3ID: 0}, // Shares L2 with core 2 + {LCore: 7, Socket: 0, L2ID: 3, L3ID: 0}, // Shares L2 with core 3 + }, + } + + l2IDs := cpuInfo.GetL2IDs() + if len(l2IDs) != 4 { + t.Errorf("GetL2IDs() returned %d domains, expected 4", len(l2IDs)) + } + + // Verify non-adjacent cores sharing L2 + cores := cpuInfo.GetCoresByL2ID(0) + if len(cores) != 2 { + t.Errorf("GetCoresByL2ID(0) returned %d cores, expected 2", len(cores)) + } + if cores[0].LCore != 0 || cores[1].LCore != 4 { + t.Errorf("GetCoresByL2ID(0) should return cores 0 and 4, got %d and %d", cores[0].LCore, cores[1].LCore) + } +} + +// TestCPUInfoL2MethodsEmptyCores tests L2 methods with empty core list +func TestCPUInfoL2MethodsEmptyCores(t *testing.T) { + cpuInfo := &CPUInfo{ + NumCores: 0, + Cores: []CoreInfo{}, + } + + l2IDs := cpuInfo.GetL2IDs() + if len(l2IDs) != 0 { + t.Errorf("GetL2IDs() should return empty slice for no cores") + } + + numDomains := cpuInfo.GetNumL2Domains() + if numDomains != 0 { + t.Errorf("GetNumL2Domains() should return 0 for no cores") + } + + cores := cpuInfo.GetCoresByL2ID(0) + if len(cores) != 0 { + t.Errorf("GetCoresByL2ID(0) should return empty slice for no cores") + } +} From 45c0fb4c4bf9e35ac92259453bacb6ca95539bac Mon Sep 17 00:00:00 2001 From: Mikhail Malyshev Date: Wed, 1 Jul 2026 11:24:07 +0000 Subject: [PATCH 3/3] lib: don't fall back to /dev/mem for ACPI tables exposed via sysfs On CONFIG_STRICT_DEVMEM=y kernels, acpi_get_sig() falling back to acpi_get_mmap() maps and reads physical memory via /dev/mem, which the kernel restricts. The mmap of the RSDP/BIOS region either fails or, worse, succeeds and then faults on access, taking down a CGO host process such as the Go bindings. When the kernel exposes ACPI tables under /sys/firmware/acpi/tables but the requested table is not present there, return NULL instead of scanning /dev/mem. The memory fallback is still used on legacy kernels that do not expose the sysfs ACPI tables directory at all. Fixes: intel/intel-cmt-cat#307 Signed-off-by: Mikhail Malyshev --- lib/acpi.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/lib/acpi.c b/lib/acpi.c index fbcea4b7..73068027 100644 --- a/lib/acpi.c +++ b/lib/acpi.c @@ -473,6 +473,20 @@ acpi_get_sig(const char *sig) return tbl; } + /* + * When the kernel exposes ACPI tables under /sys/firmware/acpi/tables + * it exposes all of them there (every table listed in the RSDT/XSDT, + * whether or not the kernel supports it). So if that directory exists + * but this table is not in it, the table is not present on the + * platform and scanning memory for it would be pointless. + */ + if (pqos_dir_exists(ACPI_TABLE_FS_PATH)) { + LOG_DEBUG("ACPI table %s not present in sysfs; " + "skipping memory scan\n", + sig); + return NULL; + } + LOG_DEBUG("Trying to obtain %s acpi table from ACPI memory\n", sig); return acpi_get_mmap(sig); }