diff --git a/src/cpu/kvm/BaseKvmCPU.py b/src/cpu/kvm/BaseKvmCPU.py index 2c90acf3e8..a6590ac4f4 100644 --- a/src/cpu/kvm/BaseKvmCPU.py +++ b/src/cpu/kvm/BaseKvmCPU.py @@ -72,6 +72,12 @@ class BaseKvmCPU(BaseCPU): usePerfOverflow = Param.Bool( False, "Use perf event overflow counters (EXPERIMENTAL)" ) + allowHybridPerf = Param.Bool( + True, + "Enable hybrid performance counters if hybrid host architecture " + "detected. Required for accurate stats if gem5 may run on E-core on a " + "hybrid host architecture (uncommon).", + ) alwaysSyncTC = Param.Bool( False, "Always sync thread contexts on entry/exit" ) diff --git a/src/cpu/kvm/base.cc b/src/cpu/kvm/base.cc index eaa771d8cf..1c9764465d 100644 --- a/src/cpu/kvm/base.cc +++ b/src/cpu/kvm/base.cc @@ -72,6 +72,7 @@ BaseKvmCPU::BaseKvmCPU(const BaseKvmCPUParams ¶ms) threadContextDirty(true), kvmStateDirty(false), usePerf(params.usePerf), + allowHybridPerf(params.allowHybridPerf), vcpuID(-1), vcpuFD(-1), vcpuMMapSize(0), _kvmRun(NULL), mmioRing(NULL), pageSize(sysconf(_SC_PAGE_SIZE)), @@ -107,8 +108,8 @@ BaseKvmCPU::BaseKvmCPU(const BaseKvmCPUParams ¶ms) // If we use perf, we create new PerfKVMCounters if (usePerf) { - hwCycles = std::unique_ptr(new PerfKvmCounter()); - hwInstructions = std::unique_ptr(new PerfKvmCounter()); + hwCycles.reset(PerfKvmCounter::create(allowHybridPerf)); + hwInstructions.reset(PerfKvmCounter::create(allowHybridPerf)); } else { inform("Using KVM CPU without perf. The stats related to the number " "of cycles and instructions executed by the KVM CPU will not " @@ -1409,7 +1410,7 @@ BaseKvmCPU::setupInstCounter(uint64_t period) assert(hwCycles->attached()); hwInstructions->attach(cfgInstructions, 0, // TID (0 => currentThread) - *hwCycles); + hwCycles.get()); if (period) hwInstructions->enableSignals(KVM_KICK_SIGNAL); diff --git a/src/cpu/kvm/base.hh b/src/cpu/kvm/base.hh index 3cf70a0bef..e592c27688 100644 --- a/src/cpu/kvm/base.hh +++ b/src/cpu/kvm/base.hh @@ -656,6 +656,12 @@ class BaseKvmCPU : public BaseCPU /** True if using perf; False otherwise*/ bool usePerf; + /** + * Whether to permit using hybrid performance counters if hybrid host + * architecture is auto-detected. + */ + bool allowHybridPerf; + /** KVM internal ID of the vCPU */ long vcpuID; diff --git a/src/cpu/kvm/perfevent.cc b/src/cpu/kvm/perfevent.cc index c5e33abf82..187a0cdd76 100644 --- a/src/cpu/kvm/perfevent.cc +++ b/src/cpu/kvm/perfevent.cc @@ -35,6 +35,7 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#include #include #include #include @@ -67,33 +68,54 @@ PerfKvmCounterConfig::~PerfKvmCounterConfig() { } - -PerfKvmCounter::PerfKvmCounter(PerfKvmCounterConfig &config, pid_t tid) - : fd(-1), ringBuffer(NULL), pageSize(-1) -{ - attach(config, tid, -1); -} - -PerfKvmCounter::PerfKvmCounter(PerfKvmCounterConfig &config, - pid_t tid, const PerfKvmCounter &parent) - : fd(-1), ringBuffer(NULL), pageSize(-1) -{ - attach(config, tid, parent); -} - PerfKvmCounter::PerfKvmCounter() +{ +} + +PerfKvmCounter * +PerfKvmCounter::create(bool allow_hybrid) +{ + // Check if we're running on a hybrid host architecture. Linux exposes + // this via sysfs. If the directory /sys/devices/cpu exists, then we are + // running on a regular architecture. Otherwise, /sys/devices/cpu_core and + // /sys/devices/cpu_atom should exist. For simplicity, we use the + // existence of /sys/devices/cpu_atom to indicate a hybrid host + // architecture. + const char *atom_path = "/sys/devices/cpu_atom"; + if (allow_hybrid) { + if (DIR *atom_dir = opendir(atom_path)) { + closedir(atom_dir); + + // Since we're running on a hybrid architecture, use a hybrid + // performance counter. This uses two 'physical' performance + // counters to implement a 'logical' one which is the sum of the + // two. + return new HybridPerfKvmCounter(); + } + + if (errno != ENOENT) + warn("Unexpected error code from opendir(%s): %s\n", + atom_path, std::strerror(errno)); + } + + // We're running on a regular architecture, so use a regular + // performance counter. + return new SimplePerfKvmCounter(); +} + +SimplePerfKvmCounter::SimplePerfKvmCounter() : fd(-1), ringBuffer(NULL), pageSize(-1) { } -PerfKvmCounter::~PerfKvmCounter() +SimplePerfKvmCounter::~SimplePerfKvmCounter() { if (attached()) detach(); } void -PerfKvmCounter::detach() +SimplePerfKvmCounter::detach() { assert(attached()); @@ -107,35 +129,35 @@ PerfKvmCounter::detach() } void -PerfKvmCounter::start() +SimplePerfKvmCounter::start() { if (ioctl(PERF_EVENT_IOC_ENABLE, PERF_IOC_FLAG_GROUP) == -1) panic("KVM: Failed to enable performance counters (%i)\n", errno); } void -PerfKvmCounter::stop() +SimplePerfKvmCounter::stop() { if (ioctl(PERF_EVENT_IOC_DISABLE, PERF_IOC_FLAG_GROUP) == -1) panic("KVM: Failed to disable performance counters (%i)\n", errno); } void -PerfKvmCounter::period(uint64_t period) +SimplePerfKvmCounter::period(uint64_t period) { if (ioctl(PERF_EVENT_IOC_PERIOD, &period) == -1) panic("KVM: Failed to set period of performance counter (%i)\n", errno); } void -PerfKvmCounter::refresh(int refresh) +SimplePerfKvmCounter::refresh(int refresh) { if (ioctl(PERF_EVENT_IOC_REFRESH, refresh) == -1) panic("KVM: Failed to refresh performance counter (%i)\n", errno); } uint64_t -PerfKvmCounter::read() const +SimplePerfKvmCounter::read() const { uint64_t value; @@ -144,7 +166,7 @@ PerfKvmCounter::read() const } void -PerfKvmCounter::enableSignals(pid_t tid, int signal) +SimplePerfKvmCounter::enableSignals(pid_t tid, int signal) { struct f_owner_ex sigowner; @@ -159,11 +181,15 @@ PerfKvmCounter::enableSignals(pid_t tid, int signal) } void -PerfKvmCounter::attach(PerfKvmCounterConfig &config, - pid_t tid, int group_fd) +SimplePerfKvmCounter::attach(const PerfKvmCounterConfig &config, + pid_t tid, const PerfKvmCounter *parent) { assert(!attached()); + int group_fd = -1; + if (parent) + group_fd = dynamic_cast(*parent).fd; + fd = syscall(__NR_perf_event_open, &config.attr, tid, -1, // CPU (-1 => Any CPU that the task happens to run on) @@ -201,7 +227,7 @@ PerfKvmCounter::sysGettid() } void -PerfKvmCounter::mmapPerf(int pages) +SimplePerfKvmCounter::mmapPerf(int pages) { assert(attached()); assert(ringBuffer == NULL); @@ -224,21 +250,21 @@ PerfKvmCounter::mmapPerf(int pages) } int -PerfKvmCounter::fcntl(int cmd, long p1) +SimplePerfKvmCounter::fcntl(int cmd, long p1) { assert(attached()); return ::fcntl(fd, cmd, p1); } int -PerfKvmCounter::ioctl(int request, long p1) +SimplePerfKvmCounter::ioctl(int request, long p1) { assert(attached()); return ::ioctl(fd, request, p1); } void -PerfKvmCounter::read(void *buf, size_t size) const +SimplePerfKvmCounter::read(void *buf, size_t size) const { char *_buf = (char *)buf; size_t _size = size; @@ -265,4 +291,97 @@ PerfKvmCounter::read(void *buf, size_t size) const } while (_size); } +PerfKvmCounterConfig +HybridPerfKvmCounter::fixupConfig(const PerfKvmCounterConfig &in, + ConfigSubtype config_subtype) +{ + PerfKvmCounterConfig out = in; + out.attr.config |= config_subtype; + if (out.attr.sample_period > 1) + out.attr.sample_period /= 2; + return out; +} + +void +HybridPerfKvmCounter::attach(const PerfKvmCounterConfig &config, pid_t tid, + const PerfKvmCounter *parent) +{ + // We should only be using hybrid performance counters for hardware + // events. + assert(config.attr.type == PERF_TYPE_HARDWARE); + + const SimplePerfKvmCounter *parent_core_counter = nullptr; + const SimplePerfKvmCounter *parent_atom_counter = nullptr; + if (parent) { + const HybridPerfKvmCounter &hybrid_parent = + dynamic_cast(*parent); + parent_core_counter = &hybrid_parent.coreCounter; + parent_atom_counter = &hybrid_parent.atomCounter; + } + + coreCounter.attach(fixupConfig(config, ConfigCore), tid, + parent_core_counter); + atomCounter.attach(fixupConfig(config, ConfigAtom), tid, + parent_atom_counter); +} + +void +HybridPerfKvmCounter::detach() +{ + coreCounter.detach(); + atomCounter.detach(); +} + +bool +HybridPerfKvmCounter::attached() const +{ + assert(coreCounter.attached() == atomCounter.attached()); + return coreCounter.attached(); +} + +void +HybridPerfKvmCounter::start() +{ + coreCounter.start(); + atomCounter.start(); +} + +void +HybridPerfKvmCounter::stop() +{ + coreCounter.stop(); + atomCounter.stop(); +} + +void +HybridPerfKvmCounter::period(uint64_t period) +{ + if (period > 1) + period /= 2; + coreCounter.period(period); + atomCounter.period(period); +} + +void +HybridPerfKvmCounter::refresh(int refresh) +{ + coreCounter.refresh(refresh); + atomCounter.refresh(refresh); +} + +uint64_t +HybridPerfKvmCounter::read() const +{ + // To get the logical counter value, we simply sum the individual physical + // counter values. + return coreCounter.read() + atomCounter.read(); +} + +void +HybridPerfKvmCounter::enableSignals(pid_t tid, int signal) +{ + coreCounter.enableSignals(tid, signal); + atomCounter.enableSignals(tid, signal); +} + } // namespace gem5 diff --git a/src/cpu/kvm/perfevent.hh b/src/cpu/kvm/perfevent.hh index 70a246f7e9..ab75bf6874 100644 --- a/src/cpu/kvm/perfevent.hh +++ b/src/cpu/kvm/perfevent.hh @@ -170,46 +170,23 @@ class PerfKvmCounterConfig */ class PerfKvmCounter { -public: - /** - * Create and attach a new counter group. - * - * @param config Counter configuration - * @param tid Thread to sample (0 indicates current thread) - */ - PerfKvmCounter(PerfKvmCounterConfig &config, pid_t tid); - /** - * Create and attach a new counter and make it a member of an - * exist counter group. - * - * @param config Counter configuration - * @param tid Thread to sample (0 indicates current thread) - * @param parent Group leader - */ - PerfKvmCounter(PerfKvmCounterConfig &config, - pid_t tid, const PerfKvmCounter &parent); - /** - * Create a new counter, but don't attach it. - */ + protected: + /** Don't create directly; use PerfKvmCounter::create(). */ PerfKvmCounter(); - ~PerfKvmCounter(); + public: + virtual ~PerfKvmCounter() = default; /** - * Attach a counter. - * - * @note This operation is only supported if the counter isn't - * already attached. - * - * @param config Counter configuration - * @param tid Thread to sample (0 indicates current thread) + * Create a new performance counter. + * This automatically selects the appropriate implementation of + * PerfKvmCounter, depending on whether the host has a hybrid architecture + * (rare case) or not (common case). */ - void attach(PerfKvmCounterConfig &config, pid_t tid) { - attach(config, tid, -1); - } + static PerfKvmCounter *create(bool allow_hybrid); /** - * Attach a counter and make it a member of an existing counter + * Attach a counter and optionally make it a member of an existing counter * group. * * @note This operation is only supported if the counter isn't @@ -217,18 +194,16 @@ public: * * @param config Counter configuration * @param tid Thread to sample (0 indicates current thread) - * @param parent Group leader + * @param parent Group leader (nullptr indicates no group leader) */ - void attach(PerfKvmCounterConfig &config, - pid_t tid, const PerfKvmCounter &parent) { - attach(config, tid, parent.fd); - } + virtual void attach(const PerfKvmCounterConfig &config, + pid_t tid, const PerfKvmCounter *parent = nullptr) = 0; /** Detach a counter from PerfEvent. */ - void detach(); + virtual void detach() = 0; /** Check if a counter is attached. */ - bool attached() const { return fd != -1; } + virtual bool attached() const = 0; /** * Start counting. @@ -236,7 +211,7 @@ public: * @note If this counter is a group leader, it will start the * entire group. */ - void start(); + virtual void start() = 0; /** * Stop counting. @@ -244,7 +219,7 @@ public: * @note If this counter is a group leader, it will stop the * entire group. */ - void stop(); + virtual void stop() = 0; /** * Update the period of an overflow counter. @@ -260,9 +235,15 @@ public: * since it has inverted check for the return value when copying * parameters from userspace. * + * @note When using a hybrid perf counter, this actually sets + * the period to 1/2 of the value provided. This ensures that an + * overflow will always trigger before more than \p period events + * occur, even in the pathological case when the host execution is + * evenly split between a P-core and E-core. + * * @param period Overflow period in events */ - void period(uint64_t period); + virtual void period(uint64_t period) = 0; /** * Enable a counter for a fixed number of events. @@ -276,12 +257,12 @@ public: * @param refresh Number of overflows before disabling the * counter. */ - void refresh(int refresh); + virtual void refresh(int refresh) = 0; /** * Read the current value of a counter. */ - uint64_t read() const; + virtual uint64_t read() const = 0; /** * Enable signal delivery to a thread on counter overflow. @@ -289,7 +270,7 @@ public: * @param tid Thread to deliver signal to * @param signal Signal to send upon overflow */ - void enableSignals(pid_t tid, int signal); + virtual void enableSignals(pid_t tid, int signal) = 0; /** * Enable signal delivery on counter overflow. Identical to @@ -306,15 +287,43 @@ private: // Disallow assignment PerfKvmCounter &operator=(const PerfKvmCounter &that); - void attach(PerfKvmCounterConfig &config, pid_t tid, int group_fd); - /** * Get the TID of the current thread. * * @return Current thread's TID */ pid_t sysGettid(); +}; +/** + * An instance of a single physical performance counter. + * In almost all cases, this is the PerfKvmCounter implementation to use. The + * only situation in which you should _not_ use this counter is for creating + a hardware event on a hybrid host architecture. In that case, use + * a HybridPerfKvmCounter. + */ +class SimplePerfKvmCounter final : public PerfKvmCounter +{ + private: + /** Don't create directly; use PerfKvmCounter::create(). */ + SimplePerfKvmCounter(); + + public: + ~SimplePerfKvmCounter(); + + void attach(const PerfKvmCounterConfig &config, + pid_t tid, const PerfKvmCounter *parent) override; + + void detach() override; + bool attached() const override { return fd != -1; } + void start() override; + void stop() override; + void period(uint64_t period) override; + void refresh(int refresh) override; + uint64_t read() const override; + void enableSignals(pid_t tid, int signal) override; + + private: /** * MMAP the PerfEvent file descriptor. * @@ -379,6 +388,58 @@ private: /** Cached host page size */ long pageSize; + + friend class PerfKvmCounter; + friend class HybridPerfKvmCounter; +}; + +/** + * Implements a single logical performance counter using two + * physical performance counters (i.e., SimplePerfKvmCounter). + * Use this for hardware counters (i.e., of type PERF_TYPE_HARDWARE) + * when running on a hybrid host architecture. Such architectures have + * multiple types of cores, each of which require their own individual + * performance counter. + */ +class HybridPerfKvmCounter : public PerfKvmCounter +{ + private: + /** Don't create directly; use PerfKvmCounter::create(). */ + HybridPerfKvmCounter() = default; + + public: + void attach(const PerfKvmCounterConfig &config, pid_t tid, + const PerfKvmCounter *parent) override; + void detach() override; + bool attached() const override; + void start() override; + void stop() override; + void period(uint64_t period) override; + void refresh(int refresh) override; + uint64_t read() const override; + void enableSignals(pid_t pid, int signal) override; + + private: + SimplePerfKvmCounter coreCounter; + SimplePerfKvmCounter atomCounter; + + using ConfigSubtype = decltype(perf_event_attr::config); + using SamplePeriod = decltype(perf_event_attr::sample_type); + + /** @{ */ + /** + * These constants for specifying core vs. atom events are taken from + * Linux perf's documentation (tools/perf/Documentation/intel-hybrid.txt + * in the linux source tree). + */ + static inline constexpr ConfigSubtype ConfigCore = 0x4UL << 32; + static inline constexpr ConfigSubtype ConfigAtom = 0x8UL << 32; + /** @} */ + + static PerfKvmCounterConfig fixupConfig(const PerfKvmCounterConfig &in, + ConfigSubtype config_subtype); + + friend class PerfKvmCounter; }; } // namespace gem5