From 0e0b84864b326adc501ca1b4e9c29b22f3e2be71 Mon Sep 17 00:00:00 2001 From: Rushin Makwana <67875241+Rushin-Makwana@users.noreply.github.com> Date: Fri, 14 Nov 2025 18:48:11 -0500 Subject: [PATCH] Revert "Renamed some variables and arranged the files cleanly" --- META-INF/MANIFEST.MF | 2 +- dependency-reduced-pom.xml | 2 +- pom.xml | 2 +- .../ptidej/spectra/Profile/Constants.java | 18 - .../ptidej/spectra/analysis/CSVMerger.java | 3 +- .../spectra/{Profile => joularjx}/Agent.java | 293 +++--- .../{Profile => joularjx}/Launcher.java | 29 +- .../MonitoringHandler.java | 922 +++++++++--------- .../ResultTreeManager.java | 7 +- .../ShutdownHandler.java | 5 +- src/main/resources/META-INF/MANIFEST.MF | 2 +- .../spectra/example/TestPtidejPOMLoadJDK.java | 6 +- .../spectra/example/TestPtidejPomCore.java | 2 +- 13 files changed, 647 insertions(+), 646 deletions(-) delete mode 100644 src/main/java/ca/concordia/ptidej/spectra/Profile/Constants.java rename src/main/java/ca/concordia/ptidej/spectra/{Profile => joularjx}/Agent.java (95%) rename src/main/java/ca/concordia/ptidej/spectra/{Profile => joularjx}/Launcher.java (86%) rename src/main/java/ca/concordia/ptidej/spectra/{Profile => joularjx}/MonitoringHandler.java (97%) rename src/main/java/ca/concordia/ptidej/spectra/{Profile => joularjx}/ResultTreeManager.java (96%) rename src/main/java/ca/concordia/ptidej/spectra/{Profile => joularjx}/ShutdownHandler.java (97%) diff --git a/META-INF/MANIFEST.MF b/META-INF/MANIFEST.MF index d0b531b..9fef02e 100644 --- a/META-INF/MANIFEST.MF +++ b/META-INF/MANIFEST.MF @@ -3,5 +3,5 @@ Created-By: Maven JAR Plugin 3.4.2 Build-Jdk-Spec: 21 Implementation-Title: Spectra Implementation-Version: 0.0.1-SNAPSHOT -Premain-Class: ca.concordia.ptidej.spectra.Profile.Agent +Premain-Class: ca.concordia.ptidej.spectra.joularjx.Agent diff --git a/dependency-reduced-pom.xml b/dependency-reduced-pom.xml index aae9c12..e70facb 100644 --- a/dependency-reduced-pom.xml +++ b/dependency-reduced-pom.xml @@ -45,7 +45,7 @@ - ca.concordia.ptidej.spectra.Profile.Agent + ca.concordia.ptidej.spectra.joularjx.Agent diff --git a/pom.xml b/pom.xml index 3808910..3d7cd9e 100644 --- a/pom.xml +++ b/pom.xml @@ -128,7 +128,7 @@ - ca.concordia.ptidej.spectra.Profile.Agent + ca.concordia.ptidej.spectra.joularjx.Agent diff --git a/src/main/java/ca/concordia/ptidej/spectra/Profile/Constants.java b/src/main/java/ca/concordia/ptidej/spectra/Profile/Constants.java deleted file mode 100644 index 2199e96..0000000 --- a/src/main/java/ca/concordia/ptidej/spectra/Profile/Constants.java +++ /dev/null @@ -1,18 +0,0 @@ -package ca.concordia.ptidej.spectra.Profile; - -import java.util.List; - -public final class Constants { - public static final String PROJECT_ROOT = "/Users/mac/Documents/RA/SPECTRA"; - public static final String JOULARJX_PATH = PROJECT_ROOT + "/src/main/resources/joularjx-3.0.1.jar"; - public static final String JPROFILER_AGENT = "/Applications/JProfiler.app/Contents/Resources/app/bin/macos/libjprofilerti.jnilib"; - public static final String MY_AGENT_PATH = PROJECT_ROOT + "/target/Spectra-with-dependencies.jar"; - public static final String JPCONTROLLER_PATH = "/Applications/JProfiler.app/Contents/Resources/app/bin/jpcontroller"; - public static final List JPEXPORT_COMMAND = List.of( - "/Applications/JProfiler.app/Contents/Resources/app/bin/jpexport", - PROJECT_ROOT + "/output/jprofiler/snapshot.jps", "AllObjects", "-format=csv", - PROJECT_ROOT + "/output/Jprofiler/allobjects.csv", "CallTree", "-format=xml", - "-aggregation=method", PROJECT_ROOT + "/output/Jprofiler/calltree.csv.xml", - "Hotspots", "-format=csv", PROJECT_ROOT + "/output/Jprofiler/hotspots.csv"); - - } \ No newline at end of file diff --git a/src/main/java/ca/concordia/ptidej/spectra/analysis/CSVMerger.java b/src/main/java/ca/concordia/ptidej/spectra/analysis/CSVMerger.java index 48bebd3..0ef9dcf 100644 --- a/src/main/java/ca/concordia/ptidej/spectra/analysis/CSVMerger.java +++ b/src/main/java/ca/concordia/ptidej/spectra/analysis/CSVMerger.java @@ -29,8 +29,6 @@ import org.w3c.dom.Node; import org.w3c.dom.NodeList; -import static ca.concordia.ptidej.spectra.Profile.Constants.PROJECT_ROOT; - public class CSVMerger { public static boolean runCSVMerger(String fileName) { final String xmlFilePath = Constants.XML_FILE_PATH; @@ -573,6 +571,7 @@ private static void createHeaderRow(Row headerRow, String[] headers, } public final class Constants { + public static final String PROJECT_ROOT = "/Users/mac/Documents/RA/SPECTRA"; public static final String XML_FILE_PATH = PROJECT_ROOT + "/Output/JProfiler/calltree.csv.xml"; public static final String ALL_OBJECTS_CSV_PATH = PROJECT_ROOT + "/Output/Jprofiler/allobjects.csv"; public static final String HOTSPOTS_CSV_PATH = PROJECT_ROOT + "/Output/Jprofiler/hotspots.csv"; diff --git a/src/main/java/ca/concordia/ptidej/spectra/Profile/Agent.java b/src/main/java/ca/concordia/ptidej/spectra/joularjx/Agent.java similarity index 95% rename from src/main/java/ca/concordia/ptidej/spectra/Profile/Agent.java rename to src/main/java/ca/concordia/ptidej/spectra/joularjx/Agent.java index 4828707..9812a5b 100644 --- a/src/main/java/ca/concordia/ptidej/spectra/Profile/Agent.java +++ b/src/main/java/ca/concordia/ptidej/spectra/joularjx/Agent.java @@ -1,146 +1,147 @@ - -package ca.concordia.ptidej.spectra.Profile; - -import java.lang.instrument.Instrumentation; -import java.lang.management.ManagementFactory; -import java.lang.management.ThreadMXBean; -import java.util.logging.Level; -import java.util.logging.Logger; - -import org.noureddine.joularjx.result.CsvResultWriter; -import org.noureddine.joularjx.cpu.Cpu; -import org.noureddine.joularjx.cpu.CpuFactory; -import org.noureddine.joularjx.monitor.MonitoringStatus; -import org.noureddine.joularjx.result.ResultWriter; -import org.noureddine.joularjx.utils.AgentProperties; -import org.noureddine.joularjx.utils.JoularJXLogging; - - -import com.sun.management.OperatingSystemMXBean; - -public class Agent { - - public static final String NAME_THREAD_NAME = "JoularJX Agent Thread"; - public static final String COMPUTATION_THREAD_NAME = "JoularJX Agent Computation"; - private static final Logger logger = JoularJXLogging.getLogger(); - - /** - * JVM hook to statically load the java agent at startup. After the Java Virtual - * Machine (JVM) has initialized, the premain method will be called. Then the - * real application main method will be called. - */ - public static void premain(String args, Instrumentation inst) { - Thread.currentThread().setName(NAME_THREAD_NAME); - AgentProperties properties = new AgentProperties(); - JoularJXLogging.updateLevel(properties.getLoggerLevel()); - - logger.info("+---------------------------------+"); - logger.info("| Spectra-JoularJX Agent |"); - logger.info("+---------------------------------+"); - - ThreadMXBean threadBean = createThreadBean(); - - // Get Process ID of current application - long appPid = ProcessHandle.current().pid(); - - // Creating the required folders to store the result files generated later on - ResultTreeManager resultTreeManager = new ResultTreeManager(properties, 123, - 123456789); - if (!resultTreeManager.create()) { - logger.log(Level.WARNING, - "Error(s) occurred while creating the result folder hierarchy. Some results may not be reported."); - } - - Cpu cpu = CpuFactory.getCpu(properties); - - OperatingSystemMXBean osBean = createOperatingSystemBean(cpu); - MonitoringStatus status = new MonitoringStatus(); - -// final ResultWriter writer = new ResultWriter() { -// @Override -// public void write(final String methodName, final double methodPower) { -// System.out.println(methodName + " : " + methodPower); -// } -// -// @Override -// public void setTarget(String name, boolean overwrite) throws IOException { -// // throw new RuntimeException("Boom!"); -// //Thread.dumpStack(); -// -// } -// -// @Override -// public void closeTarget() throws IOException { -// //Thread.dumpStack(); -// } -// }; - - ResultWriter writer = new CsvResultWriter(); - MonitoringHandler monitoringHandler = new MonitoringHandler(appPid, properties, writer, cpu, status, osBean, - threadBean, resultTreeManager); - ShutdownHandler shutdownHandler = new ShutdownHandler(appPid, writer, cpu, status, properties, - resultTreeManager); - - logger.log(Level.INFO, "Initialization finished"); - - new Thread(monitoringHandler, COMPUTATION_THREAD_NAME).start(); - Runtime.getRuntime().addShutdownHook(new Thread(shutdownHandler)); - - } - - /** - * Creates and returns a ThreadMXBean. Checks if the Thread CPU Time is - * supported by the JVM and enables it if it is disabled. - */ - private static ThreadMXBean createThreadBean() { - ThreadMXBean threadBean = ManagementFactory.getThreadMXBean(); - // Check if CPU Time measurement is supported by the JVM. Quit otherwise - if (!threadBean.isThreadCpuTimeSupported()) { - logger.log(Level.SEVERE, "Thread CPU Time is not supported on this Java Virtual Machine. Existing..."); - System.exit(1); - } - - // Enable CPU Time measurement if it is disabled - if (!threadBean.isThreadCpuTimeEnabled()) { - threadBean.setThreadCpuTimeEnabled(true); - } - - return threadBean; - } - - /** - * Creates and returns an OperatingSystemMXBean, used to collect CPU and process - * loads. - * - * @param cpu a {@link Cpu} implementation - * @return an OperatingSystemMXBean - */ - private static OperatingSystemMXBean createOperatingSystemBean(Cpu cpu) { - // Get OS MxBean to collect CPU and Process loads - OperatingSystemMXBean osBean = (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean(); - - // Loop for a couple of seconds to initialize OSMXBean to get accurate details - // (first call will return -1) - logger.log(Level.INFO, "Please wait while initializing JoularJX..."); - for (int i = 0; i < 2; i++) { - osBean.getCpuLoad(); // In future when Java 17 becomes widely deployed, use getCpuLoad() instead - osBean.getProcessCpuLoad(); - - cpu.initialize(); - - try { - Thread.sleep(500); - } catch (InterruptedException exception) { - Thread.currentThread().interrupt(); - } - } - return osBean; - } - - /** - * Private constructor - */ - private Agent() { - } - -} + +package ca.concordia.ptidej.spectra.joularjx; + +import java.io.IOException; +import java.lang.instrument.Instrumentation; +import java.lang.management.ManagementFactory; +import java.lang.management.ThreadMXBean; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.noureddine.joularjx.result.CsvResultWriter; +import org.noureddine.joularjx.cpu.Cpu; +import org.noureddine.joularjx.cpu.CpuFactory; +import org.noureddine.joularjx.monitor.MonitoringStatus; +import org.noureddine.joularjx.result.ResultWriter; +import org.noureddine.joularjx.utils.AgentProperties; +import org.noureddine.joularjx.utils.JoularJXLogging; + + +import com.sun.management.OperatingSystemMXBean; + +public class Agent { + + public static final String NAME_THREAD_NAME = "JoularJX Agent Thread"; + public static final String COMPUTATION_THREAD_NAME = "JoularJX Agent Computation"; + private static final Logger logger = JoularJXLogging.getLogger(); + + /** + * JVM hook to statically load the java agent at startup. After the Java Virtual + * Machine (JVM) has initialized, the premain method will be called. Then the + * real application main method will be called. + */ + public static void premain(String args, Instrumentation inst) { + Thread.currentThread().setName(NAME_THREAD_NAME); + AgentProperties properties = new AgentProperties(); + JoularJXLogging.updateLevel(properties.getLoggerLevel()); + + logger.info("+---------------------------------+"); + logger.info("| Spectra-JoularJX Agent |"); + logger.info("+---------------------------------+"); + + ThreadMXBean threadBean = createThreadBean(); + + // Get Process ID of current application + long appPid = ProcessHandle.current().pid(); + + // Creating the required folders to store the result files generated later on + ResultTreeManager resultTreeManager = new ResultTreeManager(properties, 123, + 123456789); + if (!resultTreeManager.create()) { + logger.log(Level.WARNING, + "Error(s) occurred while creating the result folder hierarchy. Some results may not be reported."); + } + + Cpu cpu = CpuFactory.getCpu(properties); + + OperatingSystemMXBean osBean = createOperatingSystemBean(cpu); + MonitoringStatus status = new MonitoringStatus(); + +// final ResultWriter writer = new ResultWriter() { +// @Override +// public void write(final String methodName, final double methodPower) { +// System.out.println(methodName + " : " + methodPower); +// } +// +// @Override +// public void setTarget(String name, boolean overwrite) throws IOException { +// // throw new RuntimeException("Boom!"); +// //Thread.dumpStack(); +// +// } +// +// @Override +// public void closeTarget() throws IOException { +// //Thread.dumpStack(); +// } +// }; + + ResultWriter writer = new CsvResultWriter(); + MonitoringHandler monitoringHandler = new MonitoringHandler(appPid, properties, writer, cpu, status, osBean, + threadBean, resultTreeManager); + ShutdownHandler shutdownHandler = new ShutdownHandler(appPid, writer, cpu, status, properties, + resultTreeManager); + + logger.log(Level.INFO, "Initialization finished"); + + new Thread(monitoringHandler, COMPUTATION_THREAD_NAME).start(); + Runtime.getRuntime().addShutdownHook(new Thread(shutdownHandler)); + + } + + /** + * Creates and returns a ThreadMXBean. Checks if the Thread CPU Time is + * supported by the JVM and enables it if it is disabled. + */ + private static ThreadMXBean createThreadBean() { + ThreadMXBean threadBean = ManagementFactory.getThreadMXBean(); + // Check if CPU Time measurement is supported by the JVM. Quit otherwise + if (!threadBean.isThreadCpuTimeSupported()) { + logger.log(Level.SEVERE, "Thread CPU Time is not supported on this Java Virtual Machine. Existing..."); + System.exit(1); + } + + // Enable CPU Time measurement if it is disabled + if (!threadBean.isThreadCpuTimeEnabled()) { + threadBean.setThreadCpuTimeEnabled(true); + } + + return threadBean; + } + + /** + * Creates and returns an OperatingSystemMXBean, used to collect CPU and process + * loads. + * + * @param cpu a {@link Cpu} implementation + * @return an OperatingSystemMXBean + */ + private static OperatingSystemMXBean createOperatingSystemBean(Cpu cpu) { + // Get OS MxBean to collect CPU and Process loads + OperatingSystemMXBean osBean = (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean(); + + // Loop for a couple of seconds to initialize OSMXBean to get accurate details + // (first call will return -1) + logger.log(Level.INFO, "Please wait while initializing JoularJX..."); + for (int i = 0; i < 2; i++) { + osBean.getCpuLoad(); // In future when Java 17 becomes widely deployed, use getCpuLoad() instead + osBean.getProcessCpuLoad(); + + cpu.initialize(); + + try { + Thread.sleep(500); + } catch (InterruptedException exception) { + Thread.currentThread().interrupt(); + } + } + return osBean; + } + + /** + * Private constructor + */ + private Agent() { + } + +} diff --git a/src/main/java/ca/concordia/ptidej/spectra/Profile/Launcher.java b/src/main/java/ca/concordia/ptidej/spectra/joularjx/Launcher.java similarity index 86% rename from src/main/java/ca/concordia/ptidej/spectra/Profile/Launcher.java rename to src/main/java/ca/concordia/ptidej/spectra/joularjx/Launcher.java index c797efe..c6d958f 100644 --- a/src/main/java/ca/concordia/ptidej/spectra/Profile/Launcher.java +++ b/src/main/java/ca/concordia/ptidej/spectra/joularjx/Launcher.java @@ -1,4 +1,4 @@ -package ca.concordia.ptidej.spectra.Profile; +package ca.concordia.ptidej.spectra.joularjx; import java.io.BufferedReader; import java.io.File; @@ -9,10 +9,16 @@ import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import com.sun.jdi.connect.IllegalConnectorArgumentsException; +import com.sun.jdi.connect.VMStartException; import org.noureddine.joularjx.result.ResultWriter; import com.sun.jdi.Bootstrap; +import com.sun.jdi.VirtualMachine; import com.sun.jdi.connect.Connector; import com.sun.jdi.connect.LaunchingConnector; @@ -98,7 +104,7 @@ private long launchJProfiler(final String javaPath, final String jprofilerAgent, final List command = new ArrayList<>(); command.add(javaPath + "/bin" + "/java"); - command.add("-agentpath:" + jprofilerAgent + "=port=8849,nowait,config=" + Constants.PROJECT_ROOT + "/src/main/resources/jprofiler_config.xml"); + command.add("-agentpath:" + jprofilerAgent + "=port=8849,nowait,config=" + Constants.SPECTRA_PATH + "/src/main/resources/jprofiler_config.xml"); command.add("-cp"); command.add(classpath); command.add("-Djdk.attach.allowAttachSelf=true"); @@ -143,7 +149,7 @@ private long launchJProfiler(final String javaPath, final String jprofilerAgent, Process jpcontroller = null; try { jpcontroller = new ProcessBuilder(Constants.JPCONTROLLER_PATH, - "-n", "-f", Constants.PROJECT_ROOT + "/Output/JProfiler/command.txt").inheritIO().start(); + "-n", "-f", Constants.SPECTRA_PATH + "/Output/JProfiler/command.txt").inheritIO().start(); } catch (IOException e) { throw new RuntimeException(e); } @@ -203,7 +209,7 @@ private long launchJoularjx(final String javaPath, final String joularjxPath, command.add("sudo"); command.add("-S"); command.add(javaPath + "/bin" + "/java"); - command.add("-Djoularjx.config=" + Constants.PROJECT_ROOT + "/src/test/resources/config.properties"); + command.add("-Djoularjx.config=" + Constants.SPECTRA_PATH + "/src/test/resources/config.properties"); command.add("-javaagent:" + spectraAgentPath); command.add("-cp"); command.add(/**joularjxPath + "=include=*,exclude=-XX:-Inline" + File.pathSeparator + */classpath); @@ -283,4 +289,19 @@ private void enterPassword(Process process) throws IOException { os.flush(); } } + + public final class Constants { + public static final String SPECTRA_PATH = "/Users/mac/Documents/RA/SPECTRA"; + public static final String JOULARJX_PATH = SPECTRA_PATH + "/src/main/resources/joularjx-3.0.1.jar"; + public static final String JPROFILER_AGENT = "/Applications/JProfiler.app/Contents/Resources/app/bin/macos/libjprofilerti.jnilib"; + public static final String MY_AGENT_PATH = SPECTRA_PATH + "/target/Spectra-with-dependencies.jar"; + public static final String JPCONTROLLER_PATH = "/Applications/JProfiler.app/Contents/Resources/app/bin/jpcontroller"; + public static final List JPEXPORT_COMMAND = List.of( + "/Applications/JProfiler.app/Contents/Resources/app/bin/jpexport", + SPECTRA_PATH + "/output/jprofiler/snapshot.jps", "AllObjects", "-format=csv", + SPECTRA_PATH + "/output/Jprofiler/allobjects.csv", "CallTree", "-format=xml", + "-aggregation=method", SPECTRA_PATH + "/output/Jprofiler/calltree.csv.xml", + "Hotspots", "-format=csv", SPECTRA_PATH + "/output/Jprofiler/hotspots.csv"); + + } } diff --git a/src/main/java/ca/concordia/ptidej/spectra/Profile/MonitoringHandler.java b/src/main/java/ca/concordia/ptidej/spectra/joularjx/MonitoringHandler.java similarity index 97% rename from src/main/java/ca/concordia/ptidej/spectra/Profile/MonitoringHandler.java rename to src/main/java/ca/concordia/ptidej/spectra/joularjx/MonitoringHandler.java index 44692be..1b7ab47 100644 --- a/src/main/java/ca/concordia/ptidej/spectra/Profile/MonitoringHandler.java +++ b/src/main/java/ca/concordia/ptidej/spectra/joularjx/MonitoringHandler.java @@ -1,462 +1,462 @@ - -package ca.concordia.ptidej.spectra.Profile; - -import java.io.IOException; -import java.lang.management.ThreadMXBean; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.function.ObjDoubleConsumer; -import java.util.function.Predicate; -import java.util.logging.Level; -import java.util.logging.Logger; - -import org.apache.bcel.classfile.*; -import org.apache.bcel.generic.Type; -import org.apache.bcel.util.SyntheticRepository; -import org.noureddine.joularjx.Agent; -import org.noureddine.joularjx.cpu.Cpu; -import org.noureddine.joularjx.monitor.MonitoringStatus; -import org.noureddine.joularjx.result.ResultWriter; -import org.noureddine.joularjx.utils.AgentProperties; -import org.noureddine.joularjx.utils.CallTree; -import org.noureddine.joularjx.utils.JoularJXLogging; -import org.noureddine.joularjx.utils.Scope; -import org.noureddine.joularjx.utils.StackTraceFilter; - - -import com.sun.management.OperatingSystemMXBean; - -/** - * The MonitoringHandler performs all the sampling and energy computation step, and stores the data in dedicated MonitoringStatus structures or in files. - */ -public class MonitoringHandler extends org.noureddine.joularjx.monitor.MonitoringHandler implements Runnable { - - private static final String DESTROY_THREAD_NAME = "DestroyJavaVM"; - private static final Logger logger = JoularJXLogging.getLogger(); - - private final long appPid; - private final AgentProperties properties; - private final ResultWriter resultWriter; - private final Cpu cpu; - private final MonitoringStatus status; - private final OperatingSystemMXBean osBean; - private final ThreadMXBean threadBean; - private final ResultTreeManager resultTreeManager; - private final long sampleTimeMilliseconds = 1000; - private final long sampleRateMilliseconds; - private final int sampleIterations; - - /** - * Creates a new MonitoringHandler. - * @param appPid the PID of the monitored application - * @param properties the agent's configuration properties - * @param resultWriter the writer that will be used to save data in files - * @param cpu an implementation of the CPU interface, depending on the OS and hardware - * @param status where all the runtime data will be saved - * @param osBean the OperatingSystemMXBean, used to collect CPU and process loads - * @param threadBean the ThreadMXBean, used to collect thread CPU time - * @param resultTreeManager the ResultTreeManager, used to provide filepaths for runtime generated files - */ - public MonitoringHandler(long appPid, AgentProperties properties, ResultWriter resultWriter, Cpu cpu, - MonitoringStatus status, OperatingSystemMXBean osBean, ThreadMXBean threadBean, ResultTreeManager resultTreeManager) { - super(appPid, properties, resultWriter, cpu, status, osBean, - threadBean, resultTreeManager); - this.appPid = 123; - this.properties = properties; - this.resultWriter = resultWriter; - this.cpu = cpu; - this.status = status; - this.osBean = osBean; - this.threadBean = threadBean; - this.resultTreeManager = resultTreeManager; - this.sampleRateMilliseconds = properties.stackMonitoringSampleRate(); - this.sampleIterations = (int) (sampleTimeMilliseconds / sampleRateMilliseconds); - } - - @Override - public void run() { - logger.log(Level.INFO, String.format("Started monitoring application with ID %d", appPid)); - - // CPU time for each thread - Map threadsCpuTime = new HashMap<>(); - - while (!destroyingVM()) { - try { - double energyBefore = cpu.getInitialPower(); - - final Map> samples = sample(); - var methodsStats = extractStats(samples, methodName -> true); - var methodsStatsFiltered = extractStats(samples, properties::filtersMethod); - - //Collecting call trees stats only if the option is enabled - Map> callTreesStats = null; - Map> filteredCallTreeStats = null; - if (this.properties.callTreesConsumption()) { - callTreesStats = extractCallTreesStats(samples, methodName -> true); - filteredCallTreeStats = extractCallTreesStats(samples, properties::filtersMethod); - } - - double cpuLoad = osBean.getSystemCpuLoad(); // In future when Java 17 becomes widely deployed, use getCpuLoad() instead - double processCpuLoad = osBean.getProcessCpuLoad(); - - double energyAfter = cpu.getCurrentPower(cpuLoad); - double cpuEnergy = energyAfter - energyBefore; - - // Check if energy after is smaller than before - // Meaning: RAPL energy has wrapped - if (energyBefore > energyAfter) { - cpuEnergy += cpu.getMaxPower(cpuLoad); - } - - // if cpuEnergy is negative, skip this cycle. - // this happens when energy counter is reset during program execution - if (cpuEnergy < 0) { - logger.info("Negative energy delta detected, skipping this cycle: " + cpuEnergy); - continue; - } - - // Calculate CPU energy consumption of the process of the JVM all its apps - double processEnergy = calculateProcessCpuEnergy(cpuLoad, processCpuLoad, cpuEnergy); - - // Adds current power to total energy - status.addConsumedEnergy(processEnergy); - - // Now we have: - // CPU energy for JVM process - // CPU energy for all processes - // We need to calculate energy for each thread -// long totalThreadsCpuTime = updateThreadsCpuTime(methodsStats, threadsCpuTime); -// var threadCpuTimePercentages = getThreadsCpuTimePercentage(threadsCpuTime, totalThreadsCpuTime, processEnergy); - - var threadCpuTimePercentages = getThreadsCpuTimePercentage(methodsStats, threadsCpuTime, processEnergy); - - updateMethodsConsumedEnergy(methodsStats, threadCpuTimePercentages, status::addMethodConsumedEnergy, Scope.ALL); - updateMethodsConsumedEnergy(methodsStatsFiltered, threadCpuTimePercentages, status::addFilteredMethodConsumedEnergy, Scope.FILTERED); - - //Updating call trees consumption if option is enabled - if (this.properties.callTreesConsumption()) { - updateCallTreesConsumedEnergy(callTreesStats, threadCpuTimePercentages, status::addCallTreeConsumedEnergy); - updateCallTreesConsumedEnergy(filteredCallTreeStats, threadCpuTimePercentages, status::addFilteredCallTreeConsumedEnergy); - - //Writing runtime call trees power only if option is enabled - if (this.properties.saveCallTreesRuntimeData()) { - if (this.properties.overwriteCallTreesRuntimeData()) { - this.saveResults(callTreesStats, threadCpuTimePercentages, this.resultTreeManager.getAllRuntimeCallTreePath() + String.format("/joularJX-%d-all-call-trees-power", appPid)); - this.saveResults(filteredCallTreeStats, threadCpuTimePercentages, this.resultTreeManager.getFilteredRuntimeCallTreePath() + String.format("/joularJX-%d-filtered-call-trees-power", appPid)); - } else { - this.saveResults(callTreesStats, threadCpuTimePercentages, this.resultTreeManager.getAllRuntimeCallTreePath() + String.format("/joularJX-%d-%d-all-call-trees-power", appPid )); - this.saveResults(filteredCallTreeStats, threadCpuTimePercentages, this.resultTreeManager.getFilteredRuntimeCallTreePath() + String.format("/joularJX-%d-%d-filtered-call-trees-power", appPid )); - } - } - } - - //Writing runtime method's power only if option is enabled - if (this.properties.savesRuntimeData()) { - if (this.properties.overwritesRuntimeData()) { - this.saveResults(methodsStats, threadCpuTimePercentages, this.resultTreeManager.getAllRuntimeMethodsPath() + String.format("/joularJX--all-methods-power", appPid)); - this.saveResults(methodsStatsFiltered, threadCpuTimePercentages, this.resultTreeManager.getFilteredRuntimeMethodsPath() + String.format("/joularJX--filtered-methods-power", appPid)); - } else { - this.saveResults(methodsStats, threadCpuTimePercentages, - this.resultTreeManager.getAllRuntimeMethodsPath() + String.format("/joularJX-%d-all-methods-power", appPid)); - this.saveResults(methodsStatsFiltered, threadCpuTimePercentages, this.resultTreeManager.getFilteredRuntimeMethodsPath() + String.format("/joularJX-%d-filtered-methods-power", appPid )); - } - } - - Thread.sleep(sampleRateMilliseconds); - } catch (InterruptedException exception) { - Thread.currentThread().interrupt(); - } catch (IOException exception) { - logger.log(Level.SEVERE, "Cannot perform IO \"{0}\"", exception.getMessage()); - logger.throwing(getClass().getName(), "run", exception); - System.exit(1); - } - } - } - - /** - * Performs the sampling step. Collects a set of stack traces for each thread. - * The sampling step is performed multiple time at the frequecy of SAMPLE_RATE_MILLSECONDS, for the duration of SAMPLE_TIME_MILLISECONDS - * @return for each Thread, a List of it's the stack traces - */ - private Map> sample() { - Map> result = new HashMap<>(); - try { - for (int duration = 0; duration < sampleTimeMilliseconds; duration += sampleRateMilliseconds) { - for (var entry : Thread.getAllStackTraces().entrySet()) { - String threadName = entry.getKey().getName(); - //Ignoring agent related threads, if option is enabled - if (this.properties.hideAgentConsumption() && (threadName.equals(Agent.COMPUTATION_THREAD_NAME))) { - continue; //Ignoring the thread - } - - // Only check runnable threads (not waiting or blocked) - if (entry.getKey().getState() == Thread.State.RUNNABLE) { - var target = result.computeIfAbsent(entry.getKey(), - t -> new ArrayList<>(sampleIterations)); - target.add(entry.getValue()); - } - } - - Thread.sleep(sampleRateMilliseconds); - } - } catch (InterruptedException exception) { - Thread.currentThread().interrupt(); - } - - return result; - } - - /** - * Return the occurences of each method call during monitoring loop, per thread. - * @param samples the result of the sampking step. A List of StackTraces of each Thread - * @param covers a Predicate, used to filter method names - * @return for each Thread, a Map of each method and its occurences during the last monitoring loop - */ - private Map> extractStats(Map> samples, - Predicate covers) { - Map> stats = new HashMap<>(); - - for (var entry : samples.entrySet()) { - Map target = new HashMap<>(); - stats.put(entry.getKey(), target); - - for (StackTraceElement[] stackTrace : entry.getValue()) { - for (StackTraceElement stackTraceElement : stackTrace) { - try { - // Get class and method details - String className = stackTraceElement.getClassName(); - String methodName = stackTraceElement.getMethodName(); - int lineNumber = stackTraceElement.getLineNumber(); - - // Use the resolve method to get the full method name - String fullMethodName = resolve(Class.forName(className), methodName, - lineNumber); - if (fullMethodName != null && covers.test(fullMethodName)) { - target.merge(fullMethodName, 1, Integer::sum); - break; - } - } catch (Exception e) { - logger.log(Level.WARNING, "Error resolving method: " + stackTraceElement, e); - } - } - } - } - - return stats; - } - - /** - * Returns the occurences of each call tree during monitoring loop, per thread. - * @param samples the result of the sampling step. A List of StackTraces of each Thread. - * @param filter a Predicate, used to filter method names within the call tree. - * @return for each Thread, a Map of each CallTree and its occurences during the last monitoring loop. - */ - private Map> extractCallTreesStats(Map> samples, Predicate filter) { - Map> stats = new HashMap<>(); - - for (var entry : samples.entrySet()) { - Map target = new HashMap<>(); - stats.put(entry.getKey(), target); - - for (var stackTraceEntry : entry.getValue()) { - List stackTrace = StackTraceFilter.filter(stackTraceEntry, filter); - if (!stackTrace.isEmpty()) { - target.merge(new CallTree(stackTrace), 1, Integer::sum); - } - } - } - - return stats; - } - - - /** - * Updates the CPU times for each Thread. - * Returns for each thread (PID) it's percentage of CPU time used - * @param methodsStats a map of method occurrences for each thread - * @param threadsCpuTime a map of CPU time per PID, contains the cpu time for each tread, resulting from the last call to getThreadCpuTime(threadId) - * @param processEnergy the energy consumed by the process in the last monitoring period - * @return for each PID, the percentage of energy used by the associated thread - */ - private Map getThreadsCpuTimePercentage(Map> methodsStats, - Map threadsCpuTime, double processEnergy) { - Map threadsCpuTimePercentage = new HashMap(); - - Map actualThreadsCpuTime = new HashMap(); - double totalThreadsCpuTime = 0; - // first compute the proportion of cpu time for each thread in the last sampling period - for (Entry> threadEntry : methodsStats.entrySet()) { - long threadId = threadEntry.getKey().getId(); - long currentThreadCpuTime = threadBean.getThreadCpuTime(threadId); - long previousThreadCpuTime = threadsCpuTime.getOrDefault(threadId, 0l); - if (currentThreadCpuTime < 0) { // thread has quit - // TODO ignore last sampling period?? - long jump = this.sampleRateMilliseconds / 10; - currentThreadCpuTime = previousThreadCpuTime + jump; // assume interval of 1 millisecond - logger.info("Thread CPU time negative, taking previous time + " + jump + " : " + currentThreadCpuTime + " for thread: " + threadId); - } - - threadsCpuTime.put(threadId, currentThreadCpuTime); - long delta = currentThreadCpuTime - previousThreadCpuTime; - double adjustedThreadCpuTime = delta * threadEntry.getValue().values().stream().mapToDouble(i -> i).sum() / sampleIterations; - totalThreadsCpuTime += adjustedThreadCpuTime; - actualThreadsCpuTime.put(threadId, adjustedThreadCpuTime); - } - - // compute the proportion of total energy consumed by the thread using its proportion of cpu time in the last sampling period - for (Entry threadEntry : actualThreadsCpuTime.entrySet()) { - double threadEnergy = totalThreadsCpuTime > 0d ? threadEntry.getValue() * processEnergy / totalThreadsCpuTime : 0d; - threadsCpuTimePercentage.put(threadEntry.getKey(), threadEnergy); - } - - return threadsCpuTimePercentage; - } - - /** - * Update method's consumed energy. - * @param methodsStats method's encounters statistics per Thread - * @param threadCpuTimePercentages a map of CPU time usage per PID - * @param updateMethodConsumedEnergy an object consumer, used to update all or only filtered methods - * @param scope the scope (all methods or only filterd methods). Used for energy consumption tracking - */ - private void updateMethodsConsumedEnergy(Map> methodsStats, - Map threadCpuTimePercentages, - ObjDoubleConsumer updateMethodConsumedEnergy, - Scope scope) { - for (var threadEntry : methodsStats.entrySet()) { - double totalEncounters = threadEntry.getValue().values().stream().mapToDouble(i -> i).sum(); - for (var methodEntry : threadEntry.getValue().entrySet()) { - double methodPower = 0.0; - if (totalEncounters >= Double.MIN_VALUE) { - methodPower = threadCpuTimePercentages.get(threadEntry.getKey().getId()) * (methodEntry.getValue() / totalEncounters); - } - - //Only of consumption evolution tracking is enabled - if (this.properties.trackConsumptionEvolution()) { - //computing the UNIX EPOCH timestamp - long unixTimestamp = System.currentTimeMillis() / 1000L; - - if (scope == Scope.ALL) { - this.status.trackMethodConsumption(methodEntry.getKey(), unixTimestamp, methodPower); - } else { - this.status.trackFilteredMethodConsumption(methodEntry.getKey(), unixTimestamp, methodPower); - } - } - - updateMethodConsumedEnergy.accept(methodEntry.getKey(), methodPower); - } - } - } - - /** - * Update call trees consumed energy. - * @param stats call trees encounters statistics per Thread - * @param threadCpuTimePercentages map of CPU time usage per PID - * @param callTreeConsumer the method used to update the energy consumption - */ - private void updateCallTreesConsumedEnergy(Map> stats, Map threadCpuTimePercentages, ObjDoubleConsumer callTreeConsumer) { - for (var entry : stats.entrySet()) { - double totalEncounters = entry.getValue().values().stream().mapToDouble(i -> i).sum(); - - for (var callTreeEntry : entry.getValue().entrySet()) { - double stackTracePower = 0.0; - if (totalEncounters >= Double.MIN_VALUE) { - stackTracePower = threadCpuTimePercentages.get(entry.getKey().getId()) * (callTreeEntry.getValue() / totalEncounters); - } - - callTreeConsumer.accept(callTreeEntry.getKey(), stackTracePower); - } - } - } - - /** - * Calculate process energy consumption - * @param totalCpuUsage Total CPU usage - * @param processCpuUsage Process CPU usage - * @param cpuEnergy CPU energy - * @return Process energy consumption - */ - private double calculateProcessCpuEnergy(double totalCpuUsage, double processCpuUsage, double cpuEnergy) { - return (processCpuUsage * cpuEnergy) / totalCpuUsage; - } - - /** - * Writes the results in a file. The filename is partially defined by the given parameters. - * @param The type of key that will be written in the file. Must implement the toString() method. - * @param stats the data to be written, given under the form of a Map, Double>> where the Double is the enrgy consumption. - * @param threadCpuTimePercentages a map of CPU time usage per Thread (PID) - * @param filePath the path of the file where the data will be written - * @throws IOException if an I/O error occurs while writing the file - */ - public void saveResults(Map> stats, Map threadCpuTimePercentages, String filePath) throws IOException { - resultWriter.setTarget(filePath, true); - - for (var statEntry : stats.entrySet()) { - for (var entry : statEntry.getValue().entrySet()) { - double power = threadCpuTimePercentages.get(statEntry.getKey().getId()) * (entry.getValue() / 100.0); - resultWriter.write(entry.getKey().toString(), power); - } - } - resultWriter.closeTarget(); - } - - /** - * Indicate if the JVM is destroying - * @return true if the JVM destroying thread is present, false otherwise - */ - private boolean destroyingVM() { - if (!this.properties.isApplicationServer()) { - return Thread.getAllStackTraces().keySet().stream() - .anyMatch(thread -> thread.getName().equals(DESTROY_THREAD_NAME)); - } else { - return false; - } - } - - - public static String resolve(Class clazz, String methodName, int lineNumber) { - JavaClass javaClass; - try { - // Load class using BCEL repository - SyntheticRepository repo = SyntheticRepository.getInstance(); - javaClass = repo.loadClass(clazz.getName()); - } catch (ClassNotFoundException e) { - e.printStackTrace(); - return null; - } - - String fullSignature = null; - String className = javaClass.getClassName(); - - for (Method method : javaClass.getMethods()) { - LineNumberTable lineNumberTable = method.getLineNumberTable(); - if (lineNumberTable != null) { - LineNumber[] lineNumbers = lineNumberTable.getLineNumberTable(); - if (lineNumbers.length > 0) { - int startLine = lineNumbers[0].getLineNumber(); - int endLine = lineNumbers[lineNumbers.length - 1].getLineNumber(); - - if (method.getName().equals(methodName) && - lineNumber >= startLine && lineNumber <= endLine) { - - StringBuilder params = new StringBuilder(); - for (Type paramType : method.getArgumentTypes()) { - params.append(paramType.toString()).append(", "); - } - if (params.length() > 0) { - params.setLength(params.length() - 2); // Remove trailing comma - } - - fullSignature = className + "." + methodName + "(" + params + ")"; - break; // Found the method; exit the loop - } - } - } - } - - return fullSignature; - } + +package ca.concordia.ptidej.spectra.joularjx; + +import java.io.IOException; +import java.lang.management.ThreadMXBean; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.function.ObjDoubleConsumer; +import java.util.function.Predicate; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.apache.bcel.classfile.*; +import org.apache.bcel.generic.Type; +import org.apache.bcel.util.SyntheticRepository; +import org.noureddine.joularjx.Agent; +import org.noureddine.joularjx.cpu.Cpu; +import org.noureddine.joularjx.monitor.MonitoringStatus; +import org.noureddine.joularjx.result.ResultWriter; +import org.noureddine.joularjx.utils.AgentProperties; +import org.noureddine.joularjx.utils.CallTree; +import org.noureddine.joularjx.utils.JoularJXLogging; +import org.noureddine.joularjx.utils.Scope; +import org.noureddine.joularjx.utils.StackTraceFilter; + + +import com.sun.management.OperatingSystemMXBean; + +/** + * The MonitoringHandler performs all the sampling and energy computation step, and stores the data in dedicated MonitoringStatus structures or in files. + */ +public class MonitoringHandler extends org.noureddine.joularjx.monitor.MonitoringHandler implements Runnable { + + private static final String DESTROY_THREAD_NAME = "DestroyJavaVM"; + private static final Logger logger = JoularJXLogging.getLogger(); + + private final long appPid; + private final AgentProperties properties; + private final ResultWriter resultWriter; + private final Cpu cpu; + private final MonitoringStatus status; + private final OperatingSystemMXBean osBean; + private final ThreadMXBean threadBean; + private final ResultTreeManager resultTreeManager; + private final long sampleTimeMilliseconds = 1000; + private final long sampleRateMilliseconds; + private final int sampleIterations; + + /** + * Creates a new MonitoringHandler. + * @param appPid the PID of the monitored application + * @param properties the agent's configuration properties + * @param resultWriter the writer that will be used to save data in files + * @param cpu an implementation of the CPU interface, depending on the OS and hardware + * @param status where all the runtime data will be saved + * @param osBean the OperatingSystemMXBean, used to collect CPU and process loads + * @param threadBean the ThreadMXBean, used to collect thread CPU time + * @param resultTreeManager the ResultTreeManager, used to provide filepaths for runtime generated files + */ + public MonitoringHandler(long appPid, AgentProperties properties, ResultWriter resultWriter, Cpu cpu, + MonitoringStatus status, OperatingSystemMXBean osBean, ThreadMXBean threadBean, ResultTreeManager resultTreeManager) { + super(appPid, properties, resultWriter, cpu, status, osBean, + threadBean, resultTreeManager); + this.appPid = 123; + this.properties = properties; + this.resultWriter = resultWriter; + this.cpu = cpu; + this.status = status; + this.osBean = osBean; + this.threadBean = threadBean; + this.resultTreeManager = resultTreeManager; + this.sampleRateMilliseconds = properties.stackMonitoringSampleRate(); + this.sampleIterations = (int) (sampleTimeMilliseconds / sampleRateMilliseconds); + } + + @Override + public void run() { + logger.log(Level.INFO, String.format("Started monitoring application with ID %d", appPid)); + + // CPU time for each thread + Map threadsCpuTime = new HashMap<>(); + + while (!destroyingVM()) { + try { + double energyBefore = cpu.getInitialPower(); + + final Map> samples = sample(); + var methodsStats = extractStats(samples, methodName -> true); + var methodsStatsFiltered = extractStats(samples, properties::filtersMethod); + + //Collecting call trees stats only if the option is enabled + Map> callTreesStats = null; + Map> filteredCallTreeStats = null; + if (this.properties.callTreesConsumption()) { + callTreesStats = extractCallTreesStats(samples, methodName -> true); + filteredCallTreeStats = extractCallTreesStats(samples, properties::filtersMethod); + } + + double cpuLoad = osBean.getSystemCpuLoad(); // In future when Java 17 becomes widely deployed, use getCpuLoad() instead + double processCpuLoad = osBean.getProcessCpuLoad(); + + double energyAfter = cpu.getCurrentPower(cpuLoad); + double cpuEnergy = energyAfter - energyBefore; + + // Check if energy after is smaller than before + // Meaning: RAPL energy has wrapped + if (energyBefore > energyAfter) { + cpuEnergy += cpu.getMaxPower(cpuLoad); + } + + // if cpuEnergy is negative, skip this cycle. + // this happens when energy counter is reset during program execution + if (cpuEnergy < 0) { + logger.info("Negative energy delta detected, skipping this cycle: " + cpuEnergy); + continue; + } + + // Calculate CPU energy consumption of the process of the JVM all its apps + double processEnergy = calculateProcessCpuEnergy(cpuLoad, processCpuLoad, cpuEnergy); + + // Adds current power to total energy + status.addConsumedEnergy(processEnergy); + + // Now we have: + // CPU energy for JVM process + // CPU energy for all processes + // We need to calculate energy for each thread +// long totalThreadsCpuTime = updateThreadsCpuTime(methodsStats, threadsCpuTime); +// var threadCpuTimePercentages = getThreadsCpuTimePercentage(threadsCpuTime, totalThreadsCpuTime, processEnergy); + + var threadCpuTimePercentages = getThreadsCpuTimePercentage(methodsStats, threadsCpuTime, processEnergy); + + updateMethodsConsumedEnergy(methodsStats, threadCpuTimePercentages, status::addMethodConsumedEnergy, Scope.ALL); + updateMethodsConsumedEnergy(methodsStatsFiltered, threadCpuTimePercentages, status::addFilteredMethodConsumedEnergy, Scope.FILTERED); + + //Updating call trees consumption if option is enabled + if (this.properties.callTreesConsumption()) { + updateCallTreesConsumedEnergy(callTreesStats, threadCpuTimePercentages, status::addCallTreeConsumedEnergy); + updateCallTreesConsumedEnergy(filteredCallTreeStats, threadCpuTimePercentages, status::addFilteredCallTreeConsumedEnergy); + + //Writing runtime call trees power only if option is enabled + if (this.properties.saveCallTreesRuntimeData()) { + if (this.properties.overwriteCallTreesRuntimeData()) { + this.saveResults(callTreesStats, threadCpuTimePercentages, this.resultTreeManager.getAllRuntimeCallTreePath() + String.format("/joularJX-%d-all-call-trees-power", appPid)); + this.saveResults(filteredCallTreeStats, threadCpuTimePercentages, this.resultTreeManager.getFilteredRuntimeCallTreePath() + String.format("/joularJX-%d-filtered-call-trees-power", appPid)); + } else { + this.saveResults(callTreesStats, threadCpuTimePercentages, this.resultTreeManager.getAllRuntimeCallTreePath() + String.format("/joularJX-%d-%d-all-call-trees-power", appPid )); + this.saveResults(filteredCallTreeStats, threadCpuTimePercentages, this.resultTreeManager.getFilteredRuntimeCallTreePath() + String.format("/joularJX-%d-%d-filtered-call-trees-power", appPid )); + } + } + } + + //Writing runtime method's power only if option is enabled + if (this.properties.savesRuntimeData()) { + if (this.properties.overwritesRuntimeData()) { + this.saveResults(methodsStats, threadCpuTimePercentages, this.resultTreeManager.getAllRuntimeMethodsPath() + String.format("/joularJX--all-methods-power", appPid)); + this.saveResults(methodsStatsFiltered, threadCpuTimePercentages, this.resultTreeManager.getFilteredRuntimeMethodsPath() + String.format("/joularJX--filtered-methods-power", appPid)); + } else { + this.saveResults(methodsStats, threadCpuTimePercentages, + this.resultTreeManager.getAllRuntimeMethodsPath() + String.format("/joularJX-%d-all-methods-power", appPid)); + this.saveResults(methodsStatsFiltered, threadCpuTimePercentages, this.resultTreeManager.getFilteredRuntimeMethodsPath() + String.format("/joularJX-%d-filtered-methods-power", appPid )); + } + } + + Thread.sleep(sampleRateMilliseconds); + } catch (InterruptedException exception) { + Thread.currentThread().interrupt(); + } catch (IOException exception) { + logger.log(Level.SEVERE, "Cannot perform IO \"{0}\"", exception.getMessage()); + logger.throwing(getClass().getName(), "run", exception); + System.exit(1); + } + } + } + + /** + * Performs the sampling step. Collects a set of stack traces for each thread. + * The sampling step is performed multiple time at the frequecy of SAMPLE_RATE_MILLSECONDS, for the duration of SAMPLE_TIME_MILLISECONDS + * @return for each Thread, a List of it's the stack traces + */ + private Map> sample() { + Map> result = new HashMap<>(); + try { + for (int duration = 0; duration < sampleTimeMilliseconds; duration += sampleRateMilliseconds) { + for (var entry : Thread.getAllStackTraces().entrySet()) { + String threadName = entry.getKey().getName(); + //Ignoring agent related threads, if option is enabled + if (this.properties.hideAgentConsumption() && (threadName.equals(Agent.COMPUTATION_THREAD_NAME))) { + continue; //Ignoring the thread + } + + // Only check runnable threads (not waiting or blocked) + if (entry.getKey().getState() == Thread.State.RUNNABLE) { + var target = result.computeIfAbsent(entry.getKey(), + t -> new ArrayList<>(sampleIterations)); + target.add(entry.getValue()); + } + } + + Thread.sleep(sampleRateMilliseconds); + } + } catch (InterruptedException exception) { + Thread.currentThread().interrupt(); + } + + return result; + } + + /** + * Return the occurences of each method call during monitoring loop, per thread. + * @param samples the result of the sampking step. A List of StackTraces of each Thread + * @param covers a Predicate, used to filter method names + * @return for each Thread, a Map of each method and its occurences during the last monitoring loop + */ + private Map> extractStats(Map> samples, + Predicate covers) { + Map> stats = new HashMap<>(); + + for (var entry : samples.entrySet()) { + Map target = new HashMap<>(); + stats.put(entry.getKey(), target); + + for (StackTraceElement[] stackTrace : entry.getValue()) { + for (StackTraceElement stackTraceElement : stackTrace) { + try { + // Get class and method details + String className = stackTraceElement.getClassName(); + String methodName = stackTraceElement.getMethodName(); + int lineNumber = stackTraceElement.getLineNumber(); + + // Use the resolve method to get the full method name + String fullMethodName = resolve(Class.forName(className), methodName, + lineNumber); + if (fullMethodName != null && covers.test(fullMethodName)) { + target.merge(fullMethodName, 1, Integer::sum); + break; + } + } catch (Exception e) { + logger.log(Level.WARNING, "Error resolving method: " + stackTraceElement, e); + } + } + } + } + + return stats; + } + + /** + * Returns the occurences of each call tree during monitoring loop, per thread. + * @param samples the result of the sampling step. A List of StackTraces of each Thread. + * @param filter a Predicate, used to filter method names within the call tree. + * @return for each Thread, a Map of each CallTree and its occurences during the last monitoring loop. + */ + private Map> extractCallTreesStats(Map> samples, Predicate filter) { + Map> stats = new HashMap<>(); + + for (var entry : samples.entrySet()) { + Map target = new HashMap<>(); + stats.put(entry.getKey(), target); + + for (var stackTraceEntry : entry.getValue()) { + List stackTrace = StackTraceFilter.filter(stackTraceEntry, filter); + if (!stackTrace.isEmpty()) { + target.merge(new CallTree(stackTrace), 1, Integer::sum); + } + } + } + + return stats; + } + + + /** + * Updates the CPU times for each Thread. + * Returns for each thread (PID) it's percentage of CPU time used + * @param methodsStats a map of method occurrences for each thread + * @param threadsCpuTime a map of CPU time per PID, contains the cpu time for each tread, resulting from the last call to getThreadCpuTime(threadId) + * @param processEnergy the energy consumed by the process in the last monitoring period + * @return for each PID, the percentage of energy used by the associated thread + */ + private Map getThreadsCpuTimePercentage(Map> methodsStats, + Map threadsCpuTime, double processEnergy) { + Map threadsCpuTimePercentage = new HashMap(); + + Map actualThreadsCpuTime = new HashMap(); + double totalThreadsCpuTime = 0; + // first compute the proportion of cpu time for each thread in the last sampling period + for (Entry> threadEntry : methodsStats.entrySet()) { + long threadId = threadEntry.getKey().getId(); + long currentThreadCpuTime = threadBean.getThreadCpuTime(threadId); + long previousThreadCpuTime = threadsCpuTime.getOrDefault(threadId, 0l); + if (currentThreadCpuTime < 0) { // thread has quit + // TODO ignore last sampling period?? + long jump = this.sampleRateMilliseconds / 10; + currentThreadCpuTime = previousThreadCpuTime + jump; // assume interval of 1 millisecond + logger.info("Thread CPU time negative, taking previous time + " + jump + " : " + currentThreadCpuTime + " for thread: " + threadId); + } + + threadsCpuTime.put(threadId, currentThreadCpuTime); + long delta = currentThreadCpuTime - previousThreadCpuTime; + double adjustedThreadCpuTime = delta * threadEntry.getValue().values().stream().mapToDouble(i -> i).sum() / sampleIterations; + totalThreadsCpuTime += adjustedThreadCpuTime; + actualThreadsCpuTime.put(threadId, adjustedThreadCpuTime); + } + + // compute the proportion of total energy consumed by the thread using its proportion of cpu time in the last sampling period + for (Entry threadEntry : actualThreadsCpuTime.entrySet()) { + double threadEnergy = totalThreadsCpuTime > 0d ? threadEntry.getValue() * processEnergy / totalThreadsCpuTime : 0d; + threadsCpuTimePercentage.put(threadEntry.getKey(), threadEnergy); + } + + return threadsCpuTimePercentage; + } + + /** + * Update method's consumed energy. + * @param methodsStats method's encounters statistics per Thread + * @param threadCpuTimePercentages a map of CPU time usage per PID + * @param updateMethodConsumedEnergy an object consumer, used to update all or only filtered methods + * @param scope the scope (all methods or only filterd methods). Used for energy consumption tracking + */ + private void updateMethodsConsumedEnergy(Map> methodsStats, + Map threadCpuTimePercentages, + ObjDoubleConsumer updateMethodConsumedEnergy, + Scope scope) { + for (var threadEntry : methodsStats.entrySet()) { + double totalEncounters = threadEntry.getValue().values().stream().mapToDouble(i -> i).sum(); + for (var methodEntry : threadEntry.getValue().entrySet()) { + double methodPower = 0.0; + if (totalEncounters >= Double.MIN_VALUE) { + methodPower = threadCpuTimePercentages.get(threadEntry.getKey().getId()) * (methodEntry.getValue() / totalEncounters); + } + + //Only of consumption evolution tracking is enabled + if (this.properties.trackConsumptionEvolution()) { + //computing the UNIX EPOCH timestamp + long unixTimestamp = System.currentTimeMillis() / 1000L; + + if (scope == Scope.ALL) { + this.status.trackMethodConsumption(methodEntry.getKey(), unixTimestamp, methodPower); + } else { + this.status.trackFilteredMethodConsumption(methodEntry.getKey(), unixTimestamp, methodPower); + } + } + + updateMethodConsumedEnergy.accept(methodEntry.getKey(), methodPower); + } + } + } + + /** + * Update call trees consumed energy. + * @param stats call trees encounters statistics per Thread + * @param threadCpuTimePercentages map of CPU time usage per PID + * @param callTreeConsumer the method used to update the energy consumption + */ + private void updateCallTreesConsumedEnergy(Map> stats, Map threadCpuTimePercentages, ObjDoubleConsumer callTreeConsumer) { + for (var entry : stats.entrySet()) { + double totalEncounters = entry.getValue().values().stream().mapToDouble(i -> i).sum(); + + for (var callTreeEntry : entry.getValue().entrySet()) { + double stackTracePower = 0.0; + if (totalEncounters >= Double.MIN_VALUE) { + stackTracePower = threadCpuTimePercentages.get(entry.getKey().getId()) * (callTreeEntry.getValue() / totalEncounters); + } + + callTreeConsumer.accept(callTreeEntry.getKey(), stackTracePower); + } + } + } + + /** + * Calculate process energy consumption + * @param totalCpuUsage Total CPU usage + * @param processCpuUsage Process CPU usage + * @param cpuEnergy CPU energy + * @return Process energy consumption + */ + private double calculateProcessCpuEnergy(double totalCpuUsage, double processCpuUsage, double cpuEnergy) { + return (processCpuUsage * cpuEnergy) / totalCpuUsage; + } + + /** + * Writes the results in a file. The filename is partially defined by the given parameters. + * @param The type of key that will be written in the file. Must implement the toString() method. + * @param stats the data to be written, given under the form of a Map, Double>> where the Double is the enrgy consumption. + * @param threadCpuTimePercentages a map of CPU time usage per Thread (PID) + * @param filePath the path of the file where the data will be written + * @throws IOException if an I/O error occurs while writing the file + */ + public void saveResults(Map> stats, Map threadCpuTimePercentages, String filePath) throws IOException { + resultWriter.setTarget(filePath, true); + + for (var statEntry : stats.entrySet()) { + for (var entry : statEntry.getValue().entrySet()) { + double power = threadCpuTimePercentages.get(statEntry.getKey().getId()) * (entry.getValue() / 100.0); + resultWriter.write(entry.getKey().toString(), power); + } + } + resultWriter.closeTarget(); + } + + /** + * Indicate if the JVM is destroying + * @return true if the JVM destroying thread is present, false otherwise + */ + private boolean destroyingVM() { + if (!this.properties.isApplicationServer()) { + return Thread.getAllStackTraces().keySet().stream() + .anyMatch(thread -> thread.getName().equals(DESTROY_THREAD_NAME)); + } else { + return false; + } + } + + + public static String resolve(Class clazz, String methodName, int lineNumber) { + JavaClass javaClass; + try { + // Load class using BCEL repository + SyntheticRepository repo = SyntheticRepository.getInstance(); + javaClass = repo.loadClass(clazz.getName()); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + return null; + } + + String fullSignature = null; + String className = javaClass.getClassName(); + + for (Method method : javaClass.getMethods()) { + LineNumberTable lineNumberTable = method.getLineNumberTable(); + if (lineNumberTable != null) { + LineNumber[] lineNumbers = lineNumberTable.getLineNumberTable(); + if (lineNumbers.length > 0) { + int startLine = lineNumbers[0].getLineNumber(); + int endLine = lineNumbers[lineNumbers.length - 1].getLineNumber(); + + if (method.getName().equals(methodName) && + lineNumber >= startLine && lineNumber <= endLine) { + + StringBuilder params = new StringBuilder(); + for (Type paramType : method.getArgumentTypes()) { + params.append(paramType.toString()).append(", "); + } + if (params.length() > 0) { + params.setLength(params.length() - 2); // Remove trailing comma + } + + fullSignature = className + "." + methodName + "(" + params + ")"; + break; // Found the method; exit the loop + } + } + } + } + + return fullSignature; + } } \ No newline at end of file diff --git a/src/main/java/ca/concordia/ptidej/spectra/Profile/ResultTreeManager.java b/src/main/java/ca/concordia/ptidej/spectra/joularjx/ResultTreeManager.java similarity index 96% rename from src/main/java/ca/concordia/ptidej/spectra/Profile/ResultTreeManager.java rename to src/main/java/ca/concordia/ptidej/spectra/joularjx/ResultTreeManager.java index 1e563b1..3a51bc6 100644 --- a/src/main/java/ca/concordia/ptidej/spectra/Profile/ResultTreeManager.java +++ b/src/main/java/ca/concordia/ptidej/spectra/joularjx/ResultTreeManager.java @@ -1,4 +1,4 @@ -package ca.concordia.ptidej.spectra.Profile; +package ca.concordia.ptidej.spectra.joularjx; import org.noureddine.joularjx.utils.AgentProperties; @@ -9,8 +9,7 @@ import java.util.logging.Logger; import org.noureddine.joularjx.utils.JoularJXLogging; -import static ca.concordia.ptidej.spectra.Profile.Constants.PROJECT_ROOT; - +import static ca.concordia.ptidej.spectra.joularjx.Launcher.Constants.SPECTRA_PATH; public class ResultTreeManager extends org.noureddine.joularjx.result.ResultTreeManager { private static final Logger logger = JoularJXLogging.getLogger(); @@ -39,7 +38,7 @@ public ResultTreeManager(AgentProperties properties, long pid, long startTimesta super(properties, pid, startTimestamp); this.properties = properties; Object[] var10002 = new Object[]{pid, startTimestamp}; - this.runDirectoryPath = PROJECT_ROOT + "/Output/Joularjx/data/"; + this.runDirectoryPath = SPECTRA_PATH + "/Output/Joularjx/data/"; String allDirectoryPath = this.runDirectoryPath + "/all"; String filteredDirectoryPath = this.runDirectoryPath + "/app"; this.allTotalMethodsPath = allDirectoryPath + "/total/methods"; diff --git a/src/main/java/ca/concordia/ptidej/spectra/Profile/ShutdownHandler.java b/src/main/java/ca/concordia/ptidej/spectra/joularjx/ShutdownHandler.java similarity index 97% rename from src/main/java/ca/concordia/ptidej/spectra/Profile/ShutdownHandler.java rename to src/main/java/ca/concordia/ptidej/spectra/joularjx/ShutdownHandler.java index 884746d..ca074cc 100644 --- a/src/main/java/ca/concordia/ptidej/spectra/Profile/ShutdownHandler.java +++ b/src/main/java/ca/concordia/ptidej/spectra/joularjx/ShutdownHandler.java @@ -1,4 +1,4 @@ -package ca.concordia.ptidej.spectra.Profile; +package ca.concordia.ptidej.spectra.joularjx; import java.io.IOException; import java.util.Map; @@ -11,8 +11,7 @@ import org.noureddine.joularjx.utils.AgentProperties; import org.noureddine.joularjx.utils.JoularJXLogging; -import static ca.concordia.ptidej.spectra.Profile.Constants.PROJECT_ROOT; - +import static ca.concordia.ptidej.spectra.analysis.CSVMerger.Constants.PROJECT_ROOT; /** * The ShutdownHandler is meant to be called at the end of the agent and is responsible for displaying and writing all the consumption data in dedicated files. diff --git a/src/main/resources/META-INF/MANIFEST.MF b/src/main/resources/META-INF/MANIFEST.MF index 3ec5d1a..afdc91f 100644 --- a/src/main/resources/META-INF/MANIFEST.MF +++ b/src/main/resources/META-INF/MANIFEST.MF @@ -1 +1 @@ -Premain-Class: ca.concordia.ptidej.spectra.Profile.Agent +Premain-Class: ca.concordia.ptidej.spectra.joularjx.Agent diff --git a/src/test/java/ca/concordia/ptidej/spectra/example/TestPtidejPOMLoadJDK.java b/src/test/java/ca/concordia/ptidej/spectra/example/TestPtidejPOMLoadJDK.java index c9ded7a..e4928ab 100644 --- a/src/test/java/ca/concordia/ptidej/spectra/example/TestPtidejPOMLoadJDK.java +++ b/src/test/java/ca/concordia/ptidej/spectra/example/TestPtidejPOMLoadJDK.java @@ -1,6 +1,6 @@ package ca.concordia.ptidej.spectra.example; -import ca.concordia.ptidej.spectra.Profile.Launcher; +import ca.concordia.ptidej.spectra.joularjx.Launcher; import org.junit.Test; import org.noureddine.joularjx.result.CsvResultWriter; import org.noureddine.joularjx.result.ResultWriter; @@ -14,12 +14,12 @@ public void testOverload() throws IOException { ResultWriter writer = new CsvResultWriter(); final Launcher launcher = new Launcher(); - String classpath = "../POM/target/test-classes:../POM/target/pom-core-1.0.0-tests.jar" + String ptidejClasspath = "../POM/target/test-classes:../POM/target/pom-core-1.0.0-tests.jar" + File.pathSeparator + "../POM/target/pom-core-1.0.0-jar-with-dependencies.jar"; launcher.launch( writer, - classpath, "org.junit.runner.JUnitCore", + ptidejClasspath, "org.junit.runner.JUnitCore", "pom.test.classfile.general.TestLoadJDK10" ); diff --git a/src/test/java/ca/concordia/ptidej/spectra/example/TestPtidejPomCore.java b/src/test/java/ca/concordia/ptidej/spectra/example/TestPtidejPomCore.java index 44e8fb4..d6e43cc 100644 --- a/src/test/java/ca/concordia/ptidej/spectra/example/TestPtidejPomCore.java +++ b/src/test/java/ca/concordia/ptidej/spectra/example/TestPtidejPomCore.java @@ -1,6 +1,6 @@ package ca.concordia.ptidej.spectra.example; -import ca.concordia.ptidej.spectra.Profile.Launcher; +import ca.concordia.ptidej.spectra.joularjx.Launcher; import org.junit.Test; import org.noureddine.joularjx.result.CsvResultWriter; import org.noureddine.joularjx.result.ResultWriter;