Overview
Joular Code for Java is part of the
project.
Joular Code for Java is a lightweight and efficient Java agent for monitoring the energy consumption of methods and execution branches at the source code level.
This project is part of Joular Code, and is the successor of JoularJX.
It is a Java agent where you can simply hook it to the Java Virtual Machine when starting your Java program, or attach it to a program already running. To get power readings, it reads Intel and AMD RAPL directly (through powercap) on Linux, and uses the shared memory ring buffer of PowerJoular on Windows, macOS and Raspberry Pi devices (and on Linux too, if you prefer). Inside a virtual machine, it reads the power the host writes to a file shared with the guest.
Features
- Monitor power consumption and energy of each method and execution branch at runtime
- Uses a Java agent, no source code instrumentation or modification needed
- Samples the JVM stack at high frequency (by default, every 10 milliseconds) and attributes energy every second
- No runtime dependencies: the agent runs on the JDK alone
- Gets the CPU power from Linux RAPL directly, from PowerJoular’s shared memory ring buffer, or from the host of a virtual machine
- Generates CSV files with the power (watts) and the energy (joules) of each execution branch
- Provides two sets of results: one for all methods (including the JDK ones), and one filtered and calculated for your application’s methods
- Works on Windows, macOS, Linux and Raspberry Pi
License
Joular Code for Java is licensed under the GNU LGPL 3 license only (LGPL-3.0-only).
Copyright © 2026, Adel Noureddine. All rights reserved. This program and the accompanying materials are made available under the terms of the GNU Lesser General Public License v3.0 only (LGPL-3.0-only) which accompanies this distribution.
Author : Adel Noureddine
Supported Platforms
Joular Code for Java supports the following platforms and operating systems:
- PC/Servers using a RAPL supported Intel processor (since Sandy Bridge) or a RAPL supported AMD processor (since Ryzen), on Linux and on Windows.
- Macs, with Apple Silicon or Intel processors.
- Raspberry Pi devices and Asus Tinker Board, on Linux.
- Virtual machines (any supported guest on any host).
On Linux PC/Servers, Joular Code for Java can read RAPL directly, with nothing else to install. On Windows, macOS and Raspberry Pi, it gets the CPU power from PowerJoular (version 2.0.0 or later), which runs alongside the monitored application and writes its power data to a shared memory ring buffer. In a virtual machine, it reads the power that the host writes to a file shared with the guest. See Power Sources for the details.
Only the power of the CPU is attributed to methods. The power of the GPU is not taken into account.
On Raspberry Pi and Asus Tinker Board, the power comes from the research-based regression models of PowerJoular. The supported Raspberry Pi and Asus Tinker Board models are listed below. We support all revisions of each model lineup. However, the model is generated and trained on a specific revision (listed between brackets), and the accuracy is best on this particular revision.
- Raspberry Pi devices (multiple models) on Linux:
- Model Zero W (rev 1.1), for 32 bits OS
- Model 1 B (rev 2), for 32 bits OS
- Model 1 B+ (rev 1.2), for 32 bits OS
- Model 2 B (rev 1.1), for 32 bits OS
- Model 3 B (rev 1.2), for 32 bits OS
- Model 3 B+ (rev 1.3), for 32 bits OS
- Model 4 B (rev 1.1, and rev 1.2), for both 32 bits and 64 bits OS
- Model 400 (rev 1.0), for 64 bits OS
- Model 5 B (rev 1.0), for 64 bits OS
- Asus Tinker Board (S)
The models listed for 32 bits OS are also used on a 64 bits OS.
| Platform | Supported OS | Based on | Supported Architecture |
|---|---|---|---|
| Linux PC/Server | Linux | RAPL (using powercap), or PowerJoular | x86, x86_64 |
| Windows PC/Server | Windows | PowerJoular (RAPL through EMI, PawnIO or Hubblo’s driver) | x86_64 |
| Mac | macOS | PowerJoular (powermetrics) | Apple Silicon (ARM), Intel (x86_64) |
| Raspberry Pi | Linux | PowerJoular (our regression models) | ARM |
| Asus Tinker Board | Linux | PowerJoular (our regression models) | ARM |
| Virtual Machine | Supported guests (Windows, Linux, macOS), any host | Host’s architecture (RAPL, regression models, others) | x86, x86_64, ARM |
Java Virtual Machine
Joular Code for Java requires Java 21 or later.
It also requires com.sun.management.OperatingSystemMXBean, to measure the CPU time of the process and the CPU load of the system.
This is available in all standard HotSpot JVMs (OpenJDK, Oracle JDK). Minimal or embedded JVMs that do not provide this class are not supported.
The JVM must also report the CPU time of its threads (ThreadMXBean.isThreadCpuTimeSupported()).
If any of these is missing, Joular Code for Java does not start, and the application runs unmonitored.
Installation
Joular Code for Java is a Java agent, and therefore provided as a .jar file.
Just use the compiled JAR package of Joular Code for Java, from the releases of our GitHub repository, or compile it yourself (see Compilation).
The JAR has no runtime dependencies: it holds nothing but its own classes, and needs no additional classpath setup.
You can install it wherever you want, and give its full path to -javaagent.
Joular Code for Java requires, at minimum, Java 21, to run the application being monitored.
Joular Code for Java gets the CPU power from one of three sources, depending on the platform or operating system:
- On Linux PC/Servers, it reads RAPL directly through powercap, on Intel or AMD CPUs (since Ryzen). The RAPL files are only readable by root on most distributions (Linux kernel 5.10 and newer): run the application as root, give its user read access to these files (for example with a udev rule), or run PowerJoular with
-ras root and setpower-source-type=ringbuffer. - On Windows, macOS and Raspberry Pi devices (and on Linux, with
power-source-type=ringbuffer), it reads the shared memory ring buffer of PowerJoular. Install PowerJoular (version 2.0.0 or later), and run it with the-roption alongside your application. On Windows, start PowerJoular before the application, and do not restart it while the application runs. - In virtual machines, set
power-source-type=vmandvm-power-file: it then reads the power consumption of the virtual machine (measured in the host) from a file shared between the host and the guest (see Virtual Machines).
PowerJoular needs sudo on macOS, and on Linux PC/Servers unless its user can read the RAPL files, and a terminal with administrative rights on Windows with the PawnIO driver.
The Java application needs none of these.
A commented example of the configuration file, joularcodejava.properties.example, is available in the repository and with the releases.
Copy it as joularcodejava.properties to the folder where you run the Java command, or give its path with -Djoularcodejava.properties (see Configuration Properties).
Without a configuration file, the default values are used.
Quick Usage
Joular Code for Java is a Java agent where you can simply hook it to the Java Virtual Machine when starting your Java program’s main class:
java -javaagent:joularcodejava-<version>.jar YourProgramMainClass
If your program is a JAR file, then just run it as usual while adding Joular Code for Java:
java -javaagent:joularcodejava-<version>.jar -jar yourProgram.jar
On Linux PC/Servers, Joular Code for Java reads the CPU power from RAPL directly, which needs root, or read access to the RAPL files (see Installation).
Everywhere else (Windows, macOS, Raspberry Pi), start PowerJoular with the -r option, so it writes its power data to the shared memory ring buffer Joular Code for Java reads (with sudo on macOS, and from a terminal with administrative rights on Windows with the PawnIO driver):
powerjoular -r
On Windows, start PowerJoular before the application, and do not restart it while the application runs.
On Linux, you can also run sudo powerjoular -r and set power-source-type=ringbuffer, so the Java application does not need root.
Joular Code for Java will generate two CSV files, and will create these files in a joular-code-java-results folder:
methods-power-all.csv: the power and energy of every execution branch, including the JDK ones.methods-power-app.csv: the power and energy of the execution branches of your application, according to themethods-filtering-prefixsetting.
To focus the second file on your own code, set the packages of your application in joularcodejava.properties, in the folder where you run the Java command:
methods-filtering-prefix=com.example
To use a configuration file from another path:
java -Djoularcodejava.properties=/path/to/joularcodejava.properties -javaagent:joularcodejava-<version>.jar -jar yourProgram.jar
Joular Code for Java writes its messages on the standard error, and leaves the standard output to the application. At startup, it says which power source it reads and where the results are written.
Compilation
To build Joular Code for Java, you need Java 21 or later and Maven (3.6.3 or later), then just clone the repository and build:
git clone https://github.com/joular/joularcode-java.git
cd joularcode-java
mvn clean install
This produces the JAR in the target folder:
target/joularcodejava-<version>.jar
The build also runs the unit tests. JUnit is the only dependency, and is used for the tests only. The agent itself has no runtime dependencies, so the JAR holds nothing but its own classes.
Configuration Properties
Joular Code for Java can be configured by modifying the joularcodejava.properties file, read from:
- The path given with the
-Djoularcodejava.properties=<path>JVM property - Otherwise,
joularcodejava.propertiesin the folder where you run the Java command
A missing file, or a property left empty, means the default value is used.
A path given with -Djoularcodejava.properties where there is no file also means the defaults are used: the working folder is not searched then.
The configuration is read once, when the agent starts.
When the agent is attached to a running JVM, both are those of that JVM: its own -Djoularcodejava.properties, and its working folder.
joularcodejava.properties.example is a commented example you can start from.
The following properties are available:
power-source-type: where the CPU power comes from:auto,rapl,ringbufferorvm(default:auto). See Power Sources.powerjoular-ringbuffer-path: the path to the PowerJoular ring buffer (default:/dev/shm/powerjoularon Linux,/tmp/powerjoularon macOS,%PROGRAMDATA%\powerjoularon Windows).vm-power-file: the path for the power consumption of the virtual machine. Inside a virtual machine, indicate the file containing the power consumption of the VM (which is usually a file in the host that is shared with the guest). It must be set whenpower-source-typeisvm(default: empty). See Virtual Machines.vm-power-format: power format of the shared VM power file:watts(a file containing one value, the power consumption of the VM), orpowerjoular(the row PowerJoular writes with-oin the host, containing 3 columns: timestamp, CPU utilization of the VM and CPU power of the VM) (default:powerjoular).stack-monitoring-sample-rate: the sample rate (in milliseconds) for the agent to monitor methods usage by calling the JVM stack, from 1 to 1000 (default:10). A higher value means less accurate energy data, and a lower value means a higher overhead of the agent. Below 5 milliseconds, a warning is shown, as sampling can noticeably slow the application down. A value that is not a number from 1 to 1000 is replaced by the default, with a warning.results-path: the folder where the CSV files are written (default:joular-code-java-results). A relative path starts from the folder where the Java command runs.methods-filtering-prefix: list of package or class name prefixes, separated by commas, which will be used to filter the methods of your application, for examplecom.example,org.myapp(default: empty, no filter). See Generated Files for explanations.
The values of power-source-type and vm-power-format can be written in upper or lower case.
An unknown value for either of them is not replaced by the default: Joular Code for Java says why on the standard error, and the monitoring is off (the application still runs as usual).
The same happens with vm without vm-power-file, or rapl on another OS than Linux.
Method filtering
The methods-filtering-prefix property controls which methods appear in the methods-power-app.csv file:
- If left empty, all methods (except those of the agent’s own thread) appear in both result files, which then hold the same data.
- When set, each branch is written to
methods-power-app.csvwith only its methods that match a prefix, and the branches that end up the same are added up. So the energy of the methods that do not match (e.g., the JDK ones) and that are called by a matched method goes to the matched method. A branch with no matching method is left out of this file. methods-power-all.csvalways contains every observed branch, regardless of this setting.
A method matches when its full name (package.Class.method) starts with one of the prefixes.
The prefix is compared as plain text, not as a package: com.example also matches com.examples. Use com.example. to match the com.example package and its sub-packages, but not com.examples.
When the application runs as root
Give the configuration file with -Djoularcodejava.properties, rather than leaving it in a folder others can write to, since the file sets where the results are written.
Power Sources
Joular Code for Java gets the CPU power of the machine from one of three sources (RAPL, the PowerJoular ring buffer, or the host of a virtual machine), chosen with the power-source-type property:
auto(the default): RAPL on Linux, and the PowerJoular ring buffer everywhere else, including Linux machines without RAPL such as the Raspberry Pi.rapl: Linux RAPL, read directly.ringbuffer: the shared memory ring buffer PowerJoular writes with-r.vm: inside a virtual machine, the file the host writes the power of the virtual machine to.
With auto, RAPL is picked on a Linux machine that has it, even when the application is not allowed to read it.
In that case, the monitoring is off (the application still runs as usual), so either give the application read access to RAPL, or run PowerJoular with -r and set power-source-type=ringbuffer.
When the source cannot be opened, Joular Code for Java says why on the standard error, and the application carries on without monitoring. When the power cannot be read, the cycle gets no energy, and the monitoring carries on once it can be read again.
Linux RAPL (rapl)
Joular Code for Java reads the energy counters of the CPU packages directly from /sys/class/powercap/intel-rapl:N, on Intel and AMD processors alike.
On a server with several sockets, the packages of every socket are added up.
The sub-domains (cores, uncore, DRAM) and the psys domain, which covers more than the CPU, are left out.
Nothing else is needed, but the energy_uj files are only readable by root on most distributions. You can:
- Run the application as root
- Give its user read access to those files (for example with a udev rule)
- Run PowerJoular with
-ras root, and setpower-source-type=ringbuffer, so the Java application does not need root
PowerJoular reads the first package only, so on a machine with several sockets, rapl and ringbuffer do not give the same values.
power-source-type=rapl
PowerJoular ring buffer (ringbuffer)
Joular Code for Java reads the shared memory area that PowerJoular (version 2.0.0 or later) writes with -r.
PowerJoular measures the hardware through Joular Core: RAPL on Linux and Windows, powermetrics on Macs, and our regression power models on Raspberry Pi.
It runs as a process of its own, so the privileges needed to measure the hardware stay out of the Java application:
- On Linux PC/Servers and on macOS, run PowerJoular with
sudo. - On Windows, PowerJoular needs no special rights with the Energy Meter Interface or Hubblo’s RAPL driver, but needs a terminal with administrative rights with the PawnIO driver.
- On Raspberry Pi, no special rights are needed.
Each monitoring cycle ends when PowerJoular publishes a new measurement, so the stack samples and the power describe the same second.
PowerJoular may be started before or after the application, and restarted while it runs, except on Windows: a mapped file cannot be deleted there, so PowerJoular has to be started before the application, and not restarted while it runs.
The default paths of the ring buffer are:
- Linux:
/dev/shm/powerjoular - macOS:
/tmp/powerjoular - Windows:
%PROGRAMDATA%\powerjoular
power-source-type=ringbuffer
powerjoular-ringbuffer-path=/dev/shm/powerjoular
When PowerJoular publishes no measurement for two seconds, or when its latest measurement is more than five seconds old, Joular Code for Java takes PowerJoular as stopped, and no energy is attributed until it publishes again.
When PowerJoular cannot measure the CPU (for example, run without sudo on a machine with a graphic card), it writes a CPU power of 0: Joular Code for Java then attributes nothing and writes no rows, without a warning. Run powerjoular -d -r to check that the CPU can be measured.
Joular Code for Java never follows a symbolic link when it opens the ring buffer, and only opens a regular file there, since it may run as root and the default path is in a folder every user can write to.
Virtual machine (vm)
The CPU of a virtual machine cannot be measured from inside it, but its host can measure the process the virtual machine runs as. Run PowerJoular on the host for that process, share the file it writes with the virtual machine, and point Joular Code for Java at it. Nothing else has to run in the virtual machine.
See Virtual Machines for the details.
power-source-type=vm
vm-power-file=/mnt/shared/vm-power.csv-<pid>.csv
vm-power-format=powerjoular
Generated Files
Joular Code for Java will generate two CSV files, and will create these files in the joular-code-java-results folder (which can be changed with the results-path property):
| File | Contents |
|---|---|
methods-power-all.csv | Power and energy of all observed execution branches, including the JDK ones |
methods-power-app.csv | Power and energy of the execution branches of your application, according to the methods-filtering-prefix setting |
At the end of every monitoring cycle (about 1 second), one row is added to each file for every execution branch that consumed power during the cycle. Branches with a power of zero are not written.
The files are added to, and not overwritten: running the application again with the same results folder adds its rows after the previous ones.
Use another results-path, or move the files away, to keep each run on its own.
Joular Code for Java never follows a symbolic link when it opens its result files, since it may run as root, and the results folder may be one every user can write to.
The filtered file
The second file is not just a subset of the first one, but rather a recalculation done by Joular Code for Java to provide accurate data: the methods that start with the filtered prefixes are allocated the power or energy of the methods they call that do not match a prefix (the JDK’s, but also those of libraries and frameworks).
For example, if Package1.MethodA calls java.io.PrintStream.println to print some text to a terminal, then we calculate:
- In the first file, the power or energy of the branch ending in
println(throughMethodA), separately from the branch ending inMethodA. The latter won’t include the power consumed byprintln. - In the second file, if we filter methods from
Package1, then the power consumption ofprintlnwill be added toMethodApower consumption, and the file will only provide power or energy ofPackage1methods.
We manage to do this by analyzing the stack trace of all running threads at runtime.
In the filtered file, a branch only has the methods that match a prefix, and the branches that end up the same are added up into one row.
The samples with no matching method at all are left out of this file, so its totals are lower than those of the first file.
When methods-filtering-prefix is empty, both files hold the same data.
CSV format
Both files share the same format:
timestamp,branch,power_watts,energy_joules,interval_seconds,coverage
| Column | Type | Description |
|---|---|---|
timestamp | long (ms) | Unix timestamp in milliseconds at the end of the monitoring cycle |
branch | string | The call chain, from the oldest to the newest method, separated by semicolons (e.g., com.example.Main.run;com.example.Service.process) |
power_watts | double | Estimated power consumed by this branch during the cycle (W) |
energy_joules | double | Energy = power_watts × interval_seconds (J) |
interval_seconds | double | Duration of the monitoring cycle (s), usually about 1.0 |
coverage | double | The share of the JVM’s CPU time during the cycle that belonged to threads Joular Code for Java actually sampled, from 0.0 to 1.0 |
A branch with a comma, a double quote or a line break in it is written between double quotes (standard CSV), so read the files with a CSV parser.
To get the total energy of a branch over the whole execution, add up its energy_joules values.
Example output
timestamp,branch,power_watts,energy_joules,interval_seconds,coverage
1746000000000,com.example.Main.main;com.example.Worker.compute,2.341500000,2.341500000,1.000000000,1.0000
1746000001000,com.example.Main.main;com.example.Worker.compute,2.158300000,2.158300000,1.000000000,0.9974
What coverage means
Power is split between threads by the CPU time each one used, and the denominator is every thread that used CPU, not only the ones that were caught in a sample. Power drawn by a thread that was never sampled is therefore left unattributed, rather than shared out over the threads it did see.
coverage says how much of the JVM’s CPU time is represented: at 1.0 everything was accounted for, and at 0.6 only 60% of what the JVM consumed is attributed to the observed threads at that timestamp.
Below 1.0, the power reported per branch is a lower bound. When coverage drops below 0.5, Joular Code for Java shows a warning (once, until it rises again).
Threads that end during a cycle, even ones started before it, are not counted here, because the JVM stops reporting a thread’s CPU time once it has ended.
Their CPU time is still part of the JVM’s, so their power is spread over the threads still alive at the end of the cycle (like the power of the garbage collector), and coverage does not drop because of them.
Integration with Systems and Tools
Joular Code for Java is a Java agent, and the compiled JAR file has no external dependencies. Therefore, it can be integrated and used in any setup running a standard JVM (OpenJDK, Oracle JDK), Java 21 or later (see Supported Platforms).
For instance, you can add Joular Code for Java to the run/execute parameters of your favorite IDE (Eclipse, IntelliJ IDEA, NetBeans, etc.), or to your development workflow or continuous integration and delivery processes (CI/CD).
Joular Code for Java runs with the same configuration options and generated files on all supported platforms, from Windows to Linux, from x86_64 servers and PC to ARM Raspberry Pi devices.
The generated CSV files are added to at the end of every monitoring cycle (about every second), so other tools can read them while the application runs, or after it ends.
Attaching to a running JVM
The agent can also be loaded into a JVM that is already running, without restarting it, from another Java program using the Attach API (com.sun.tools.attach.VirtualMachine, in the jdk.attach module):
VirtualMachine vm = VirtualMachine.attach(pid);
vm.loadAgent("/path/to/joularcodejava-<version>.jar");
vm.detach();
Monitoring starts when the agent is loaded, so whatever the application did before that is not in the results.
JDK 21 and later allow the attach by default but print a warning: start the target JVM with -XX:+EnableDynamicAgentLoading to silence it, or with -XX:-EnableDynamicAgentLoading to forbid it.
The configuration is read when the agent attaches, in the target JVM: -Djoularcodejava.properties=... belongs on its own command line, and the default joularcodejava.properties and a relative results-path are taken from its working folder.
Attaching again while Joular Code for Java is monitoring is ignored, with a warning, rather than starting a second monitor. If the first one has stopped (for instance, because its power source could not be opened), attaching again reads the configuration again and starts monitoring.
Application servers and long-running applications
Joular Code for Java monitors the JVM until it stops, and closes its files when the JVM shuts down.
It works the same for a program that ends on its own and for an application server or a service that keeps running (Spring Boot, Tomcat, etc.).
Unlike JoularJX, there is no application-server property to set.
The cycle in progress when the JVM shuts down is not written, so the last second or so of a run is not in the results, and a program that ends within its first cycle leaves files with only their header.
Messages
Joular Code for Java writes its messages on the standard error, and leaves the standard output to the application.
The first line gives its version (Joular Code for Java: version <version>), and the next ones read as dd-MM-yyyy HH:mm:ss LEVEL: message.
If anything goes wrong in Joular Code for Java, it says so there. When the power cannot be read, or a cycle fails, that cycle gets no rows and the monitoring resumes by itself. When Joular Code for Java cannot start, or cannot open its power source, the application carries on unmonitored.
Virtual Machines
Joular Code for Java also works inside virtual machines. All its functionalities work the same inside a virtual machine as with bare metal installation.
In virtual machines, Joular Code for Java in the guest OS needs to get the power consumption of the virtual machine instance itself. This can only be done by installing on the host OS, a power monitoring tool (such as PowerJoular or other ones), and monitoring the power consumption of the specific guest virtual machine process.
The power data of the VM process need to be written to a shared file between the host and the guest (virtiofs, 9p, a shared folder). Inside the guest, Joular Code for Java will read this file every monitoring cycle (every second) and use the reported power value as the CPU power of the entire virtual machine. Nothing else has to run in the virtual machine, and the host may start writing the file before or after the Java application.
Joular Code for Java is agnostic to what power tools are installed in the host and can work with any available tool that is capable of monitoring the VM process, as long as it writes the power alone, in watts (vm-power-format=watts), or the row PowerJoular writes.
Only the first line of the file is read, in one of the two formats given with vm-power-format:
powerjoular(the default): the row PowerJoular rewrites every second with-ofor a monitored process,timestamp,cpu_usage,cpu_power. Use-oon the host, not-f:-fstarts the file with a header, which is refused. The file of the whole host, with five columns, is refused too, since it would charge all of the host’s power to one virtual machine.watts: the power alone, in watts, written by any tool. The value is used as it is, however long ago it was written.
With the powerjoular format, the host writes the file every second, on its own clock.
When a cycle finds no new row, or catches the file while it is rewritten, the last row is used again, for up to two cycles.
After that, the host is taken as stopped, and no energy is attributed until it writes again.
A negative value, such as the -1.0000 PowerJoular writes for a process it cannot read, counts as no reading.
Some shared folders cache files in the guest (9p with cache=loose, virtiofs with cache=always), which can hide the host’s updates: mount the share without caching.
Use case example with Joular Code for Java on guest and PowerJoular on host
A use case example is using Joular Code for Java on the guest OS and PowerJoular on the host OS.
In the host OS
- Install PowerJoular
- Run PowerJoular while specifying the PID of the virtual machine of the guest OS, and writing the power data in a CSV file in overwrite mode.
- For instance, you can run PowerJoular with the following command:
powerjoular -p $VM_PID -o /home/vm/vm.csv - This writes two files:
/home/vm/vm.csvwith the power of the whole host, and/home/vm/vm.csv-$VM_PID.csvwith the power of the virtual machine process. - Share the
/home/vm/vm.csv-$VM_PID.csvbetween the host OS and the guest OS (read-only for the guest, as PowerJoular on the host usually runs as root)
In the guest OS
- Get Joular Code for Java (download or compile it)
- Share the
/home/vm/vm.csv-$VM_PID.csvbetween the host OS and the guest OS, potentially having a different path of the file inside the guest. For instance,/opt/vm/vm.csv - Modify
joularcodejava.propertiesand setpower-source-typetovm,vm-power-fileto the shared file/opt/vm/vm.csv, andvm-power-formatto the proper format (in this case topowerjoular). - Start your Java application with the Joular Code for Java agent as usual.
power-source-type=vm
vm-power-file=/opt/vm/vm.csv
vm-power-format=powerjoular
Use case example with another tool on the host
Have the tool on the host write the power of the virtual machine in watts, and nothing else, to the shared file.
Then read that file in the guest with the watts format:
power-source-type=vm
vm-power-file=/opt/vm/vm-power.txt
vm-power-format=watts
How Joular Code for Java Works
Joular Code for Java is a Java agent that hooks to the Java Virtual Machine (JVM) on startup along with the monitored application (or attaches to it later). It runs in a separate thread and collects information about CPU usage of the JVM process, each thread running in the JVM, and then for each method and execution branch of the application.
Joular Code for Java is the successor of JoularJX, itself the successor of Jalen, and the core approach of statistical sampling is based on and inspired by the work we did in monitoring energy hotspots in software (ASE 2012 conference paper, and ASE Journal paper in 2015).
The monitoring process
The monitoring process is as follows, every monitoring cycle (about 1 second):
- Every
stack-monitoring-sample-ratemilliseconds (by default, 10 milliseconds), Joular Code for Java captures the stack trace of everyRUNNABLEthread, counting how often each execution branch is seen. - At the start and the end of each cycle, it reads the CPU time of each thread and of the whole JVM, using the JDK’s
ThreadMXBeanandOperatingSystemMXBean. - It reads the CPU power of the machine over the cycle, from RAPL, PowerJoular, or the host of a virtual machine (see Power Sources).
- It calculates the power of the JVM: the JVM’s share of the CPU power is its own CPU load over the machine’s.
- It attributes the power to methods and execution branches: each thread receives a part of the JVM’s power proportional to its CPU time. Within each thread, the power is then distributed to its execution branches, proportionally to how often each was seen in the stack samples.
- It writes the results to the CSV files: both the power (W) and the energy (J = W × cycle duration) of each branch for that cycle.
In short:
JVM power = CPU power × JVM CPU load / machine CPU load
thread power = JVM power × thread CPU time / CPU time of all the JVM's threads
branch power = thread power × samples of the branch in the thread / samples of the thread
A branch seen on several threads (for example, the workers of a thread pool running the same code) gets the sum of its power on each of them. The JVM’s share is never more than the whole CPU power, and when the machine’s CPU load is unknown, the JVM’s own load is used instead.
Threads are weighted by their CPU time rather than by their number of samples, so a thread blocked in native I/O (which Java still reports as RUNNABLE) is not charged for waiting.
The JVM’s own native threads (garbage collector, JIT compiler) are part of the JVM’s power, but not of any Java thread’s CPU time, so their share is spread over the Java threads in proportion to their own CPU time.
The agent’s own monitoring thread is left out: it is not sampled, and its CPU time is taken out of the JVM’s.
Execution branches
During the monitoring cycles, Joular Code for Java does not only identify the method being executed (the method on top of the stack trace), but also its execution branch (all the methods calling it), and provides the power and energy of each execution branch, as seen in the following figure:
Usually the method on the top of the stack trace is a method from the JDK.
For example, calling System.out.println() from the application’s method Main will call other methods from the JDK (such as buffers, writeln, etc.).
Joular Code for Java checks, in the stack trace, which methods of the branch belong to the application we wish to monitor (with the methods-filtering-prefix setting), and thus allocates to them the power of the methods they call that are not part of the application (the JDK’s, libraries, frameworks), in the filtered file (see Generated Files).
Two stack traces are the same branch when they go through the same methods, whatever the line numbers.
Timing of the cycles
The length of a cycle depends on the power source:
- With RAPL, a cycle lasts one second (until the first stack sample after one second, so a little more with a large
stack-monitoring-sample-rate), and the energy is read at its very end, so the power and the stack samples cover the same time. - With the PowerJoular ring buffer, a cycle ends when PowerJoular publishes a measurement, or after two seconds without one, in which case that cycle gets no rows. While there is no ring buffer to read yet, cycles last one second and get no rows.
- In a virtual machine, a cycle lasts one second too, like with RAPL, and gets the latest power the host wrote.
Each cycle starts where the previous one ended, so no time is lost while the results are written.
Limits
Sampling the stacks of the JVM’s threads cannot see:
- Threads that end during a cycle, even ones started before it, as the JVM no longer reports their CPU time once they have ended. Their power is spread over the threads still alive, like the garbage collector’s, and
coveragedoes not show it. - Virtual threads, which are not in the thread dumps Joular Code for Java takes.
- Whether a sampled thread was actually running on a CPU at that instant.
The coverage column of the generated files says how much of the CPU time of the JVM’s threads was accounted for in each cycle.
When the stacks cannot be sampled as often as asked (with many threads, and a very low stack-monitoring-sample-rate), Joular Code for Java shows a warning: set a higher stack-monitoring-sample-rate, or accept coarser data.