URJA is a lightweight dynamic monitoring library designed to log per-thread hardware performance counters (via PAPI) and energy consumption (via RAPL) for multithreaded applications with no code changes to the target program.
It also provides simple, built-in dynamic frequency scaling policies based on runtime metrics (e.g., L3 cache miss ratio).
- Linux
- papi (6.0+)
- C++17
- You have installed PAPI and exported
PAPI_DIRpointing to its installation location, - The target program uses
pthread_createfor thread spawning, - For shell energy backend - file
/path/to/rapl_read.shis available and accessible (see below).
Install PAPI and point to it
export PAPI_DIR=/path/to/papi/Build the shared library
makeThere are two primary ways to run your application with URJA.
scripts/urja sets LD_PRELOAD and the mode-specific environment variables for you, then launches your application directly. It locates liburja.so relative to its own path, so it works regardless of where the repo is cloned.
scripts/urja [mode] [--] /path/to/your_app [app args...]mode is one of stdio, file, csv (default for this wrapper), naive, trident, fsllcm — see Configuration below for what each mode does and its settings. For example:
scripts/urja trident /path/to/your_appIn csv mode, URJA_CSV_PAPI_FILE/URJA_CSV_ENERGY_FILE default to auto-generated names in the current directory (tagged with the command name, a timestamp, and the PID) rather than a fixed path — since this wrapper is meant to be callable from anywhere via $PATH, there's no single sane fixed default, and repeated or concurrent runs must not overwrite each other.
Each mode's settings (thresholds, frequencies, file paths, ...) fall back to sane defaults but can be overridden by exporting the corresponding environment variable before calling urja — this also lets you set a mode once and reuse it across multiple runs without repeating it as an argument:
export URJA_LOGGER=naive
export URJA_NAIVE_MAX_FREQ=3200000
scripts/urja /path/to/your_appAdd
scripts/to yourPATH(e.g.export PATH="$HOME/urja/scripts:$PATH") to invoke it as plainurjafrom anywhere.
You can also manually preload the library.
Set your desired configuration options (see below).
Run your application, specifying LD_PRELOAD directly:
LD_PRELOAD=build/bin/liburja.so /path/to/your_appURJA is configured entirely through environment variables.
- Description: Comma-separated list of PAPI events to monitor.
- Example:
export URJA_PAPI_EVENTS=PAPI_TOT_CYC,PAPI_TOT_INS,PAPI_L3_TCM
- Description: The monitoring and logging interval in milliseconds.
- Example:
export URJA_INTERVAL_MS=200
- Description: Selects the backend for reading energy consumption.
- Options:
sysfs: (Recommended) Reads from/sys/class/powercap/intel-rapl.shell: Uses the deprecatedrapl_read.shscript (requires root).
- Example:
export URJA_ENERGY_BACKEND=sysfs
- Description: Selects the output logger or dynamic scaling policy. If unset, the library itself defaults to
stdio(relevant for Method 2 below); thescripts/urjawrapper defaults tocsvinstead. - Options:
stdio: Prints monitoring data to stdout.file: Writes monitoring data to a specified log file.csv: Writes PAPI counters and energy readings to two separate CSV files.naive: Enables a simple dynamic frequency scaling policy.trident: Enables a 3-level dynamic frequency scaling policy.fsllcm: Enables the FS-LLCM N-level dynamic frequency scaling policy, linearly mapped from LLC misses per kilo-instruction.
- Example:
export URJA_LOGGER=naive
- Description: The full path for the output log file when using
URJA_LOGGER=file. - Example:
export URJA_LOG_FILE=/tmp/urja.log
- Description: Full path for the CSV file holding per-thread PAPI counter deltas (columns:
timestamp,tag,tid,pthread_id,counter,value). - Example:
export URJA_CSV_PAPI_FILE=/tmp/urja_papi.csv
- Description: Full path for the CSV file holding energy readings (columns:
timestamp,tag,domain,value). - Example:
export URJA_CSV_ENERGY_FILE=/tmp/urja_energy.csv
Both files are truncated (not appended) at startup, and are flushed once per monitoring interval so a killed job still leaves usable data. Free-text INIT/ERROR/DEBUG messages are printed to stderr rather than written into either CSV.
This policy adjusts CPU frequency between two levels (min/max) based on a single threshold.
- Description: The performance counter ratio (e.g., L3 miss ratio) threshold to trigger frequency changes.
- Example:
export URJA_NAIVE_THRESHOLD=0.005
- Description: The minimum frequency (in kHz) for the policy.
- Example:
export URJA_NAIVE_MIN_FREQ=800000
- Description: The maximum frequency (in kHz) for the policy.
- Example:
export URJA_NAIVE_MAX_FREQ=2800000
This policy adjusts CPU frequency between three levels (min/mid/max) based on two
thresholds on ratio = L3_TCM / TOT_INS (LLC misses per instruction, i.e.
MPKI / 1000), aggregated over all threads each interval:
| Condition | Interpretation | Frequency |
|---|---|---|
ratio < LOWER_THRESHOLD - HYSTERESIS |
compute-bound | MAX_FREQ |
LOWER_THRESHOLD + HYSTERESIS <= ratio <= UPPER_THRESHOLD - HYSTERESIS |
balanced | MID_FREQ |
ratio > UPPER_THRESHOLD + HYSTERESIS |
memory-bound | MIN_FREQ |
| otherwise (inside a hysteresis band) | ambiguous | unchanged |
The controller keeps a sliding window of the last WINDOW_SIZE ratios. When it
sees more than TRANSITION_LIMIT interval-to-interval jumps larger than
NOISE_FLOOR inside that window (a phase-changing / noisy workload) it decides on
the windowed mean instead of the instantaneous ratio, to suppress frequency
oscillation; otherwise it uses the instantaneous ratio for responsiveness.
The defaults below were calibrated from an NPB3.4.3 D-class sweep. Note
HYSTERESIS must be smaller than LOWER_THRESHOLD, otherwise
LOWER_THRESHOLD - HYSTERESIS is negative and the MAX_FREQ branch can never be
taken.
- Description: Below this ratio (minus
HYSTERESIS) the workload is treated as compute-bound and frequency is set toMAX_FREQ. - Default:
0.003(3 MPKI) - Example:
export URJA_TRIDENT_LOWER_THRESHOLD=0.003
- Description: Above this ratio (plus
HYSTERESIS) the workload is treated as memory-bound and frequency is set toMIN_FREQ. Between the two thresholds,MID_FREQis used. - Default:
0.045(45 MPKI) - Example:
export URJA_TRIDENT_UPPER_THRESHOLD=0.045
- Description: Half-width of the dead band around each threshold; a decision only changes once the ratio moves this far past a threshold. Must be
< URJA_TRIDENT_LOWER_THRESHOLD. - Default:
0.001(1 MPKI) - Example:
export URJA_TRIDENT_HYSTERESIS=0.001
- Description: Number of past intervals kept for the moving average / transition count. Its time span is
WINDOW_SIZE * URJA_INTERVAL_MS. Smaller values track phase changes faster; larger values smooth more. - Default:
6 - Example:
export URJA_TRIDENT_WINDOW_SIZE=6
- Description: Minimum interval-to-interval change in
ratiothat counts as a "transition" for the window-average collapse. - Default:
0.01(10 MPKI) - Example:
export URJA_TRIDENT_NOISE_FLOOR=0.01
- Description: If the number of transitions in the window exceeds this, the controller switches from the instantaneous ratio to the windowed mean.
- Default:
2 - Example:
export URJA_TRIDENT_TRANSITION_LIMIT=2
- Description: The minimum frequency (in kHz).
- Example:
export URJA_TRIDENT_MIN_FREQ=800000
- Description: The middle frequency (in kHz).
- Example:
export URJA_TRIDENT_MID_FREQ=1400000
- Description: The maximum frequency (in kHz).
- Example:
export URJA_TRIDENT_MAX_FREQ=2000000
Frequency Scaling via LLC Misses, from Hebbar, R. and Milenkovic, A., 2022. PMU-events-driven DVFS techniques for improving energy efficiency of modern processors. ACM TOMPECS, 7(1), pp.1-31.
This policy computes LLC misses per kilo-instruction (MPKI) each interval and linearly maps it onto a caller-supplied list of P-states: P_0 (index 0, highest frequency) through P_n (last index, lowest frequency). A low MPKI (compute-bound) selects a state near P_0; a high MPKI (memory-bound) selects a state near P_n. MPKI is bounded to URJA_FSLLCM_MAX before mapping, so anything at or above that value pins the lowest state.
- Description: Comma-separated list of frequencies (in kHz), ordered from
P_0(highest) toP_n(lowest). At least two states are required. - Example:
export URJA_FSLLCM_PSTATES=3900000,3400000,2900000,2400000,1900000,1400000,900000
- Description: The MPKI value that maps to the lowest P-state (
P_n); anything higher is clamped to it. - Default:
100 - Example:
export URJA_FSLLCM_MAX=100
The observation interval is
URJA_INTERVAL_MS, shared with the other loggers — there is no separate interval setting for this policy.
To allow URJA (via PAPI) to access hardware counters, make sure:
cat /proc/sys/kernel/perf_event_paranoidReturns 2 or less (e.g., 2, 1, 0, or -1).
Use the following command to lower it temporarily:
sudo sh -c 'echo 2 > /proc/sys/kernel/perf_event_paranoid'
⚠️ This is not recommended and will be depricated in the future.
You only need to run URJA with root access if you choose the shell energy backend (e.g., /path/to/rapl_read.sh).
In that case, the script must be executable and present in the sudoers list without password prompt to allow non-interactive execution.
For example, add this line to your /etc/sudoers file using visudo:
your_username ALL=(ALL) NOPASSWD: /path/to/rapl_read.shMake sure the script is readable and executable:
sudo chmod +x /path/to/rapl_read.shThe source code for /path/to/rapl_read.sh is:
#!/bin/sh
MODE="$1"
if [ -z "$MODE" ]; then
printf '%-20s; %-20s; %-15s; %20s; %20s\n' "name" "socket:domain_id" "domain" "energy_uj" "max_energy_uj"
for f in `find /sys/class/powercap/intel-rapl\:* | grep -P "\d+"`; do
name=`echo $f | rev | cut -d/ -f1 | rev`
id=`echo $name | cut -d: -f2,3`;
domain=`cat $f/name`
energy=`cat $f/energy_uj`
max_energy=`cat $f/max_energy_range_uj`
printf '%-20s; %-20s; %-15s; %20s; %20s\n' ${name} ${id} ${domain} ${energy} ${max_energy}
done
exit 0
fi
for f in $(find /sys/class/powercap/intel-rapl\:* | grep -P "\d+"); do
domain_name=$(basename "$f")
case "$MODE" in
-h|--help)
echo "Usage: $0 [-n|--name | -m|--max | -i|--instant]"
echo " -n, --name Print <name>-<domain>"
echo " -m, --max Print max_energy_uj values"
echo " -i, --instant Print current energy_uj values"
exit 0
;;
-n|--name)
domain=$(cat "$f/name" | tr ' ' '-')
echo "${domain_name}-${domain}"
;;
-m|--max)
cat "$f/max_energy_range_uj"
;;
-i|--instant)
cat "$f/energy_uj"
;;
*)
echo "Usage: $0 [-n|--name | -m|--max | -i|--instant]"
exit 1
;;
esac
done