In today’s world, the conversation around sustainability is no longer limited to industries like transportation or manufacturing — software, too, leaves a carbon footprint. Every line of code we write consumes energy and memory when executed. While the energy usage of a single program may seem negligible, when multiplied across millions of devices and data centers, the impact becomes significant.

“The greatest threat to our planet is the belief that someone else will save it.” – Robert Swan

Most developers use profilers to track execution time, memory usage, and performance bottlenecks. But what if we could also measure energy consumption alongside efficiency sort at the method level?

This is where Spectra comes in.

What is Spectra?

Spectra (Software Performance Energy, Consumption, Time, Resources & Analytics) is a research-driven platform that helps developers measure, analyze, and optimize the energy consumption of their software, which is available at https://github.com/ptidejteam/ptidej-SPECTRA

Unlike traditional performance profilers that focus solely on metrics like execution time, memory usage, or invocation count, Spectra adds a critical new dimension: energy usage.

By correlating energy consumption with execution time, memory, and method invocations, Spectra provides developers with actionable insights to:

  • Identify "energy hotspots" in code.
  • Compare overloaded methods to see which variant is more energy-efficient.
  • Optimize code to reduce carbon footprint without sacrificing performance.
  • Visualize analytics through an interactive dashboard.

In short, Spectra allows developers to write efficiently greener software.

How Spectra Work?

Spectra integrates two powerful tools:

  • JProfiler → For profiling method-level execution time, invocation counts, and hotspots.
  • JoularJX → For capturing method-level energy consumption directly from the JVM.

The outputs of these tools are then merged into a unified Excel-based report showing:

  • Method names (with signatures to handle overloads).
  • Execution time.
  • Invocation counts.
  • Energy consumed.


This helps developers compare overloaded methods, identify energy hotspots, and make data-driven optimizations.

Challenges We Faced (and Solved) 🕵🏼‍♂️

Developing Spectra was nothing sort of roller coaster ride, solving complex challenges, mitigating buggy code and finding workarounds. At a core level, these are the primary hurdles we experienced ( and deciphered eventually)  

1. Distinguishing Overloaded Methods 📜

By default, JoularJX output often merge overloaded methods under the same name. For example:

public void foo(String s) {
    System.out.println("foo(String): " + s);
}

public void foo(String s, Double d) {
    System.out.println("foo(String, Double): " + s + ", " + d);
}
public void foo(Float f, Double d, String s) {
    System.out.println("foo( Float, Double, String): " + f + ", " + s + ", " + d);
}


All these might appear as just foo in reports.

Method Signature

Invocations


Execution Time (µs)



Energy (mJ)



foo

102

4977749

68.4275

Our solution 🏋🏻‍♂️: a custom resolve() function that uses the class name, method name, and line number from the stack trace and find the exact version of method at the byte-code level to build a unique identifier:

With this, foo(String) , foo(String, Double)  and foo(Float, Double, String s) now appear as distinct entries in the merged dataset.

2. Orchestrating JProfiler Tools

JProfiler’s command-line tools are powerful but fragmented:

jps -l → Find running Java processes.
jpcontroller → Control profiling sessions.
jpexport → Export profiling data in CSV/XML.

At first, running these in a single JVM led to process conflicts, sync issues and even permission errors.

Our fix 🛠️: launch each tool in a separate JVM:

# Launch JProfiler session and attach jpcontroller using pid

jprofiler -agentpath:/path/to/jprofiler/bin/linux-x64/libjprofilerti.so=port=8849

jpcontroller -n -f output/jprofiler/command.txt [pid]


# Export call tree & hotspots

jpexport output/jprofiler/spectra.jps CallTree -format=xml -aggregation=method output/jprofiler/calltree.csv.xml
jpexport output/jprofiler/spectra.jps Hotspots -format=csv output/jprofiler/hotspots.csv


# Launch JoularJX session 

joularjx -javaagent:target/Spectra-with-dependencies.jar -cp target/classes, ca.concordia.ptidej.spectra.example.OverloadTest

This clean separation avoids synchronization issues and ensures stable profiling.

3. Merging JoularJX and JProfiler Outputs ⚙️🔧

JProfiler outputs performance data (XML/CSV), while JoularJX outputs energy traces (CSV) 📊

To unify them, we built CSVMerger, a small Java utility: 🏗️

        String xmlFilePath = "Output/JProfiler/calltree.csv.xml";
        String allObjectsCsvPath = "Output/Jprofiler/invocationData.csv";
        String hotspotsCsvPath = "Output/Jprofiler/hotspots.csv";


        String energyCsvPath = "Output/energy.csv";


        String xmlEnergyOutputPath = "Results/callTree_energy_data.xlsx";
        String allObjectsEnergyOutputPath = "Results/invocation_energy_data.xlsx";
        String hotSpotEnergyOutputPath = "Results/hotspots_energy_data.xlsx";

        try {
  
            // Merge Energy data and call-tree detail (and output to excel)
             Map<String, MethodData> methodDataMap = parseXMLData(xmlFilePath);
            Map<String, MethodData> xmlEnergyData = mergeAndFilterEnergyData(methodDataMap, energyCsvPath, true);
            writeToExcel(xmlEnergyData, xmlEnergyOutputPath);

            // Merge with Method-Invocation data and Energy usage (and output to excel)
            Map<String, MethodData> allObjectsEnergyData = mergeAndFilterAllObjectsEnergyData(allObjectsCsvPath, energyCsvPath);
            writeToExcel(allObjectsEnergyData, allObjectsEnergyOutputPath);


            // Merge hotspots with energy usage (and output excel)
            Map<String, MethodData> hotspotsData = parseHotspotsCsv(hotspotsCsvPath);
            Map<String, MethodData> mergedHotspotsEnergy = mergeHotspotsWithEnergy(hotspotsData, energyCsvPath);
       writeToExcel(mergedHotspotsEnergy, hotSpotEnergyOutputPath);
        } catch (Exception e) {
            System.err.println("Error during CSVMerger execution: " + e.getMessage());
            e.printStackTrace();
        }

        System.out.println("Data successfully merged and written to Excel files.");
    }```

This generates a detailed report like this:

Method Signature

Invocations


Execution Time (µs)



Energy (mJ)



foo(String)

102

4977749

68.4275

foo(String, Double)



22

4939974

50.9087

foo( Float, Double, String)


33

9948385

121.9001

bar(int)

66

5967378

67.4392

bar(Double)

23

5685865

36.7979

Developers can now see both performance and energy data in one place.

What’s Next ? ‼️

Spectra is evolving, and our roadmap includes:

Intuitive dashboards (UI) → that shows these critical performance and energy data in an interactive and elucidate format, allowing developers to make informed-decisions.

Standalone Application → which can be applied to any program / application to measure their trade-offs between effience and energy usage.

Takeaway 📁

With Spectra, developers can move beyond “fast code” to also write energy-efficient code. ♻️🌍

We tackled three major challenges:

  • Differentiating overloaded methods with stack-trace resolution.
  • Running JProfiler tools in isolated JVMs to avoid conflicts.
  • Merging energy + performance into a unified dataset.

The result? Developers now have a practical way to measure and reduce the carbon footprint of their code.

Because in the end, greener code is not just better for performance — it’s better for the planet 🌱