probe β Non-Intrusive Instrumentation
An observer-pattern framework for attaching monitoring, profiling, and analysis tools to simulation components without modifying their source code.
📚 What is this & why does it exist?
π‘ Think of it like medical monitoringβ¦Imagine a patient (System & Standard Library Β· gem5 Wiki" class="xref">SimObject) in a hospital. ProbePoint is a sensor port on the patient's body β a pulse oximeter clip, blood pressure cuff, or EKG lead. ProbeListener is the monitoring device plugged into that sensor. The patient doesn't need surgery to add monitoring β you just clip on a new sensor. Similarly, gem5 components expose probe points that external listeners can attach to without changing the component's core logic.
Architecture Overview
The probe framework implements the Observer design pattern. SimObject subclasses register named ProbePoint objects during initialization. Separate ProbeListener objects (often from different SimObjects) attach to these points to receive notifications when interesting events occur β without the producer needing to know about the consumer.
graph TB
subgraph "Producer (e.g., CPU)"
CPU["BaseCPU"]
PP1["ProbePoint<InstPtr>\n'Commit'"]
PP2["ProbePoint<Addr>\n'DataAccess'"]
CPU --> PP1
CPU --> PP2
end
subgraph "Framework"
PM["ProbeManager\n(registry)"]
end
subgraph "Consumers"
PL1["ProbeListener\n(SimpleCache stats)"]
PL2["ProbeListener\n(PMU counter)"]
PL3["ProbeListener\n(Trace logger)"]
end
PP1 -->|"notify"| PL1
PP1 -->|"notify"| PL2
PP2 -->|"notify"| PL3
PM --> PP1
PM --> PP2
ProbePoint
A ProbePoint is a named notification source templated on the type of data it emits. When the producing component wants to signal an event, it calls notify() on the probe point, which fans out to all registered listeners.
ProbePointArg Class
// Type-erased base for probe point registration
class ProbePoint
{
protected:
std::string name;
ProbeManager *manager;
public:
ProbePoint(ProbeManager *manager, const std::string &name);
virtual ~ProbePoint();
const std::string &getName() const { return name; }
};
// Typed probe point with argument
template<typename Arg>
class ProbePointArg : public ProbePoint
{
std::vector<ProbeListener *> listeners;
public:
ProbePointArg(ProbeManager *manager, const std::string &name);
// Fire notification to all listeners
void notify(const Arg &val) {
for (auto *l : listeners)
static_cast<ProbeListenerArg<Arg> *>(l)->notify(val);
}
void addListener(ProbeListener *l);
void removeListener(ProbeListener *l);
bool hasListeners() const { return !listeners.empty(); }
};
ProbeListener
A ProbeListener attaches to a specific probe point and receives callbacks when events occur. The listener specifies which SimObject and probe point name to listen to.
ProbeListenerArg Class
class ProbeListener
{
protected:
ProbeManager *manager;
std::string pointName;
public:
ProbeListener(ProbeManager *pm, const std::string &name);
virtual ~ProbeListener();
};
template<typename Arg>
class ProbeListenerArg : public ProbeListener
{
public:
ProbeListenerArg(ProbeManager *pm, const std::string &name,
std::function<void(const Arg &)> callback);
virtual void notify(const Arg &val) {
callback(val);
}
private:
std::function<void(const Arg &)> callback;
};
ProbeManager
Each SimObject has a ProbeManager that serves as a registry for its probe points. Listeners use the manager to find and connect to named probe points. The manager handles the lifecycle of point-listener connections.
class ProbeManager
{
SimObject *object;
std::vector<ProbePoint *> points;
public:
ProbeManager(SimObject *obj);
// Register a new probe point
void addPoint(ProbePoint *point);
// Find probe point by name
ProbePoint *getPoint(const std::string &name) const;
// Connect listener to named point
bool addListener(const std::string &pointName,
ProbeListener *listener);
bool removeListener(const std::string &pointName,
ProbeListener *listener);
};
Standard Probe Points
gem5 defines several standard probe points across its components. These provide a consistent instrumentation interface for common events.
| Probe Point | Source Component | Argument Type | Description |
|---|---|---|---|
Commit | CPU | StaticInstPtr | Fired when an instruction commits |
Fetch | CPU | StaticInstPtr | Fired when an instruction is fetched |
DataAccess | CPU | PacketPtr | Fired on data memory access |
Miss | Cache Hierarchy | gem5 Wiki" href="../../mem_doc/cache/index.html">Cache | PacketPtr | Fired on cache miss |
Hit | Cache | PacketPtr | Fired on cache hit |
Fill | Cache | PacketPtr | Fired when a cache line is filled |
Eviction | Cache | Addr | Fired when a cache line is evicted |
TLBMiss | TLB | Addr | Fired on TLB miss |
PwrStateChange | ClockedObject | PowerState | Fired on power state transition |
Usage Patterns
Registering Probe Points (Producer)
// In a CPU's header file
class MyCPU : public BaseCPU
{
ProbePointArg<StaticInstPtr> *ppCommit;
ProbePointArg<PacketPtr> *ppDataAccess;
public:
void regProbePoints() override {
ppCommit = new ProbePointArg<StaticInstPtr>(
getProbeManager(), "Commit");
ppDataAccess = new ProbePointArg<PacketPtr>(
getProbeManager(), "DataAccess");
}
// In execute logic:
void commitInst(StaticInstPtr inst) {
// ... normal commit logic ...
ppCommit->notify(inst); // Fire probe
}
};
Attaching Listeners (Consumer)
// A PMU-like listener object
class MyProfiler : public SimObject
{
uint64_t commitCount = 0;
ProbeListenerArg<StaticInstPtr> *listener;
public:
void regProbeListeners() override {
// Attach to CPU's "Commit" probe point
listener = new ProbeListenerArg<StaticInstPtr>(
cpu->getProbeManager(), "Commit",
[this](const StaticInstPtr &inst) {
commitCount++;
if (inst->isMemRef())
memAccessCount++;
});
}
};
Conditional Probing
// Check if anyone is listening before doing expensive work
void MyCPU::commitInst(StaticInstPtr inst) {
// ... normal commit logic ...
if (ppCommit->hasListeners()) {
// Only construct the notification data if someone cares
ppCommit->notify(inst);
}
}
Built-in Probe Listeners
gem5 ships with several pre-built probe listener implementations for common analysis tasks:
PMU (Performance Monitoring Unit)
The AArch32, AArch64, TrustZone security extensions, SVE/SVE2 vector processing, PMU, and hardware transactional memory." href="../../arch_doc/arm/index.html">ARM PMU implementation uses probe points to count architectural events (instructions retired, cache misses, branch mispredictions) without modifying the CPU pipeline code.
Stack Distance Calculator
Attaches to cache access probe points to compute reuse distance distributions β essential for cache sizing analysis.
Memory Trace Recorder
Records all memory accesses to a trace file by listening to DataAccess probe points, enabling offline analysis.
π Narrated Walkthrough: Adding a Custom Probe Listener
- Identify the Probe β Determine which SimObject exposes the event you want to monitor. Check the component's
regProbePoints()method for available probe point names. - Create a Listener Class β Define a new SimObject subclass (or use an existing one) that will receive probe notifications. Override
regProbeListeners(). - Instantiate ProbeListenerArg β In
regProbeListeners(), create aProbeListenerArgwith the target'sProbeManager, the point name, and your callback function. - Handle Notifications β Your callback receives the typed argument whenever the probe fires. Update counters, log data, or trigger other actions.
- Python Configuration β Instantiate your listener in the Python config and pass it a reference to the SimObject you want to monitor.
- Zero Overhead β If no listener is attached, the
notify()call is essentially a no-op (empty vector iteration), so unmonitored components pay minimal cost.
sequenceDiagram
participant Init as Initialization
participant CPU as CPU Component
participant PM as ProbeManager
participant PP as ProbePoint
participant PL as ProbeListener
participant User as User Callback
Init->>CPU: regProbePoints()
CPU->>PP: new ProbePointArg("Commit")
CPU->>PM: addPoint(ppCommit)
Init->>PL: regProbeListeners()
PL->>PM: addListener("Commit", listener)
PM->>PP: addListener(listener)
Note over CPU,User: Simulation Running
CPU->>PP: notify(inst)
PP->>PL: notify(inst)
PL->>User: callback(inst)
User->>User: Update statistics
Design Principles
- Non-intrusive β Producers don't need to know about consumers; adding monitoring doesn't require changing component code
- Type-safe β Template parameterization ensures listeners receive correctly-typed data
- Low overhead β No listeners attached = minimal cost (empty vector check)
- Decoupled β Listeners and producers are independent SimObjects configured in Python
- Composable β Multiple listeners can attach to the same probe point simultaneously
Key Components Reference
| Component | Header | Purpose |
|---|---|---|
ProbePoint | probe/probe.hh | Type-erased base for named notification sources |
ProbePointArg<T> | probe/probe.hh | Typed probe point that fires notifications with argument |
ProbeListener | probe/probe.hh | Base class for probe event receivers |
ProbeListenerArg<T> | probe/probe.hh | Typed listener with callback function |
ProbeManager | probe/probe.hh | Per-SimObject registry of probe points |
Glossary
| Term | Definition |
|---|---|
| Observer Pattern | Design pattern where subjects notify observers of state changes without tight coupling |
| Probe Point | A named notification source on a SimObject that fires when specific events occur |
| Probe Listener | An observer that attaches to a probe point and receives event callbacks |
| Non-Intrusive | Monitoring that can be added/removed without modifying the monitored component's source code |
| PMU | Performance Monitoring Unit β hardware counter system modeled using probe listeners |
| Reuse Distance | Number of unique addresses accessed between two accesses to the same address β key cache metric |