Skip to content

Commit

Permalink
several cleanup measures. (#91)
Browse files Browse the repository at this point in the history
* several cleanup measures.

* upgrade dependencies

* use avg for power measures in prometheus example
  • Loading branch information
keilhofh authored Sep 26, 2024
1 parent a939f98 commit 0288d76
Show file tree
Hide file tree
Showing 21 changed files with 62 additions and 75 deletions.
22 changes: 13 additions & 9 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [not yet released]
- add prometheus interface and configuration
- fix calculation of energy for intervals different to 1sec (1 Ws = 1 J)
- add cloud toolkit estimation method
- use double/Double instead of BigDecimal: refactor all BigDecimals to double/Double values (for slightly better performance and slightly less precise results)
- fix calculation of energy for intervals different to 1sec (1 Ws = 1 J)
- use double/Double instead of BigDecimal:
- refactor all BigDecimals to double/Double values (for slightly better performance and slightly less precise results)
- refactor MeasureMethod hierarchy
- upgrade com.fasterxml.jackson.datatype:jackson-datatype-jsr310 to 2.17.2
- org.junit.jupiter:junit-jupiter to 5.11.0
- upgrade org.assertj:assertj-core to 3.26.3
- upgrade snakeyaml to 2.3
- upgrade junit-jupiter to 5.11.0
- upgrade org.slf4j:slf4j-api to 2.0.16
- upgrade gradle to 8.10.1
- separate jPowerMonitor jar from demo application jpowermonitor-demo.jar. See Readme for more information.
- dependency updates:
- upgrade com.fasterxml.jackson.datatype:jackson-datatype-jsr310 to 2.17.2
- org.apache.httpcomponents.client5:httpclient5 to 5.4
- org.junit.jupiter:junit-jupiter to 5.11.1
- upgrade org.assertj:assertj-core to 3.26.3
- upgrade snakeyaml to 2.3
- upgrade junit-jupiter to 5.11.0
- upgrade org.slf4j:slf4j-api to 2.0.16
- upgrade gradle to 8.10.1

## 2024-01-17 - release 1.1.2
- upgrade httpclient to 5.3
Expand Down
18 changes: 10 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ The power consumption of Java applications should become measurable, and thus vi
This library includes an extension for measuring unit tests, as well as a Java agent for measuring any Java application.
The Java agent collects the activity of the application to be measured at regular, configurable intervals. The agent takes into account the power consumption provided by the configured measurement tool.
The CPU usage of the program and the current power consumption are aggregated to energy consumption per method over runtime and written into a CSV file.
The result of the measurement is the energy consumption in watt hours or joule.
The result of the measurement is the energy consumption in watt-hours or joule.

### Quick Start
- Install and configure Tool __Libre Hardware Monitor__: https://github.com/LibreHardwareMonitor/LibreHardwareMonitor
Expand All @@ -19,7 +19,7 @@ The result of the measurement is the energy consumption in watt hours or joule.
- The tool __HWiNFO__ or __another tool that writes sensor values to a CSV file__ could be used alternatively, __measurement -> method__ must be set to 'csv' and Logging to CSV in HWiNFO must be active: https://www.hwinfo.com/
- Configure HWiNFO to log the values of the power sensors to the CSV file.
- Start the logging in HWiNFO to a file e.g. in your project directory.
- If no measurement tool available or no measurement interfaces accessible (e. g. in virtualized cloud scenarios) configure
- If no measurement tool available or no measurement interfaces accessible (e.g. in virtualized cloud scenarios) configure
__measurement -> method__ to 'est' (@since jpowermonitor-1.2.0) and configure appropriate 'cpuMinWatts' / 'cpuMaxWatts' according to your platform (@see https://github.com/cloud-carbon-footprint/cloud-carbon-coefficients/tree/main/data).
- To start the __Java agent__, the "fat jar" (incl. dependencies, with name `jpowermonitor-<version>-all.jar`) must be __downloaded from mvn central [here](https://repo.maven.apache.org/maven2/io/github/msg-systems/jpowermonitor/)__ or __must first be built__ with the Gradle task `shadowJar`.
- __Copy `src/main/resources/jpowermonitor-template.yaml` to the execution directory and rename it to `./jpowermonitor.yaml`__.
Expand Down Expand Up @@ -70,15 +70,15 @@ For the configuration of the JUnit extension a yaml file with the name of the ex
| javaAgentCfg -> measurementIntervalInMs | Energy measurement interval in milliseconds for the Java Agent. This is the interval the data source for the sensor values is questioned for new values. | X | 1000 |
| javaAgentCfg -> gatherStatisticsIntervalInMs | Gather statistics interval in milliseconds. This is the interval the stacktrace of each active thread is questioned for active methods. Should be smaller than `measurementIntervalInMs`. | X | 10 |
| javaAgentCfg -> writeEnergyMeasurementsToCsvIntervalInS | Write energy measurement results to CSV files interval in seconds. Leave empty to write energy measurement results only at program exit (be sure your application to measure exits "gracefully", thus by calling System.exit(..), else results might be lost!). | X | 30 |
| javaAgentCfg -> monitoring | Section for configuration of monitoring interfaces. @since jpowermonitor:1.3.0 | | |
| javaAgentCfg -> monitoring -> prometheus | Section for configuration of Prometheus monitoring interface. @since jpowermonitor:1.3.0 | | |
| javaAgentCfg -> monitoring | Section for configuration of monitoring interfaces. @since jpowermonitor:1.2.0 | | |
| javaAgentCfg -> monitoring -> prometheus | Section for configuration of Prometheus monitoring interface. @since jpowermonitor:1.2.0 | | |
| javaAgentCfg -> monitoring -> prometheus -> enabled | Set to true, if prometheus monitoring should be enabled. This will start a HttpServer on the configured port. | X | false |
| javaAgentCfg -> monitoring -> prometheus -> httpPort | Prometheus Http Server Port. Prometheus will ask the jPowerMonitored application for metrics on this port. | X | 1234 |
| javaAgentCfg -> monitoring -> prometheus -> writeEnergyIntervalInS | Write energy measurement results to Prometheus interval in seconds. | X | 30 |
| javaAgentCfg -> monitoring -> prometheus -> publishJvmMetrics | Should the default JVM Metric be published to Prometheus? This includes information about GC, memory etc. | X | false |

If no base load (`energyInIdleMode`) is specified for a path, this is measured before each test. So a mixed operation between configuration of the base load and measurement is also possible and the results can be compared (some sensors provide very similar values).
For non current measuring sensors (e.g. temperature) the base load is not calculated extra and also not subtracted from the measured value! It is only output if a base load must also be calculated for a current-measuring sensor because this is not specified in the configuration.
For non-current measuring sensors (e.g. temperature) the base load is not calculated extra and also not subtracted from the measured value! It is only output if a base load must also be calculated for a current-measuring sensor because this is not specified in the configuration.

### Integration into your own project
#### Java Agent
Expand All @@ -91,7 +91,7 @@ For non current measuring sensors (e.g. temperature) the base load is not calcul
- e.g. for use with gradle and Spring Boot you may use
```
bootRun {
jvmArgs += ["-javaagent:./lib/jpowermonitor-1.3.0-all.jar=./lib/jpowermonitor.yaml"]
jvmArgs += ["-javaagent:./lib/jpowermonitor-1.2.0-all.jar=./lib/jpowermonitor.yaml"]
}
```
in your gradle script.
Expand Down Expand Up @@ -150,7 +150,9 @@ https://prometheus.io/docs/visualization/grafana
`{__name__=~"jPowerMonitor_.*"}`
##### Top 5 power per method filtered metrics
`topk(5, sort_desc(sum by(method) (jPowerMonitor_power_per_method_filtered{job=~"jPowerMonitor"})))`
Show average power per method as multiple threads may execute same method and usually one wants to see the average:
`topk(5, sort_desc(avg by(method) (jPowerMonitor_power_per_method_filtered{job=~"jPowerMonitor"})))`
##### Top 5 energy per method filtered metrics
`topk(5, sort_desc(sum by(method) (jPowerMonitor_energy_per_method_filtered{job=~"jPowerMonitor"})))`
Expand All @@ -175,5 +177,5 @@ This markdown can be converted to html with
`pandoc --self-contained -t slidy -c docs/slidy.css -o Readme.html README.md` to html and from there to pdf via print function of the browser.
# Copyright & License
Copyright &copy; 2022-2023 msg for banking ag <br/>
Copyright &copy; 2022-2024 msg for banking ag <br/>
Licensed under [Apache License 2.0](./LICENSE.txt)
2 changes: 1 addition & 1 deletion algorithm.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,5 @@ MEASUREMENT_INTERVAL
- POWER_THREAD (W) = POWER_TOTAL * (THREAD_TIME / APPLICATION_TIME)
- determine power usage of each method per MEASUREMENT_INTERVAL
- POWER_METHOD (W) = POWER_THREAD * (METHOD_ACTIVITY * STATISTICS_INTERVAL / MEASUREMENT_INTERVAL) -> currently fix ration of 1 / 100
- determine energy usage of each method in MEASUREMENT_INTERVAL (currenty assumption: MEASUREMENT_INTERVAL always 1000ms, so W = J)
- determine energy usage of each method in MEASUREMENT_INTERVAL (current assumption: MEASUREMENT_INTERVAL always 1000ms, so W = J)
- ENERGY_METHOD (J) = POWER_METHOD * MEASUREMENT_INTERVAL / 1000
8 changes: 4 additions & 4 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ configurations {

dependencies {
implementation(
[group: 'org.apache.httpcomponents.client5', name: 'httpclient5', version: '5.3.1'],
[group: 'org.apache.httpcomponents.client5', name: 'httpclient5', version: '5.4'],
[group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version: '2.17.2'],
[group: 'org.yaml', name: 'snakeyaml', version: '2.3'],
[group: 'io.prometheus', name: 'simpleclient', version: '0.16.0'],
Expand All @@ -53,13 +53,13 @@ dependencies {
[group: 'org.slf4j', name: 'slf4j-simple', version: "2.0.16"],
)
compileOnly(
[group: 'org.jetbrains', name: 'annotations', version: "24.1.0"],
[group: 'org.junit.jupiter', name: 'junit-jupiter', version: "5.11.0"],
[group: 'org.jetbrains', name: 'annotations', version: "25.0.0"],
[group: 'org.junit.jupiter', name: 'junit-jupiter', version: "5.11.1"],
)
testImplementation(
// use logger only in test implementation in order to have a minimal set of dependencies in main source
[group: 'org.slf4j', name: 'slf4j-simple', version: "2.0.16"],
[group: 'org.junit.jupiter', name: 'junit-jupiter', version: "5.11.0"],
[group: 'org.junit.jupiter', name: 'junit-jupiter', version: "5.11.1"],
[group: 'org.assertj', name: 'assertj-core', version: "3.26.3"]
)
}
Expand Down
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ public static void premain(String args, Instrumentation inst) {
ThreadMXBean threadMXBean = CpuAndThreadUtils.initializeAndGetThreadMxBeanOrFailAndQuitApplication();

JavaAgentCfg javaAgentCfg = cfg.getJavaAgent();
log.debug("Start monitoring application with PID " + pid + ", javaAgentCfg.getMeasurementIntervalInMs():" + javaAgentCfg.getMeasurementIntervalInMs());
log.debug("Start monitoring application with PID {}, javaAgentCfg.getMeasurementIntervalInMs(): {}", pid, javaAgentCfg.getMeasurementIntervalInMs());
// TimerTask to calculate power consumption per thread at runtime using a configurable measurement interval
// start Timer as daemon thread, so that it does not prevent applications from stopping
Timer powerMeasurementTimer = new Timer("PowerMeasurementCollector", true);
Expand All @@ -82,7 +82,7 @@ public static void premain(String args, Instrumentation inst) {
}
long delayAndPeriodPmc = javaAgentCfg.getMeasurementIntervalInMs();
powerMeasurementTimer.schedule(powerMeasurementCollector, delayAndPeriodPmc, delayAndPeriodPmc);
log.debug("Scheduled PowerMeasurementCollector with delay " + delayAndPeriodPmc + "ms and period " + delayAndPeriodPmc + "ms");
log.debug("Scheduled PowerMeasurementCollector with delay {} ms and period {} ms", delayAndPeriodPmc, delayAndPeriodPmc);
// TimerTask to write energy measurement statistics to CSV files while application still running
if (javaAgentCfg.getWriteEnergyMeasurementsToCsvIntervalInS() > 0) {
CsvResultsWriter cw = new CsvResultsWriter();
Expand All @@ -96,7 +96,7 @@ public void run() {
cw.writeEnergyConsumptionPerMethodFiltered(powerMeasurementCollector.getEnergyConsumptionPerMethod(true));
}
}, delayAndPeriodCw, delayAndPeriodCw);
log.debug("Scheduled CsvResultsWriter with delay " + delayAndPeriodCw + "ms and period " + delayAndPeriodCw + "ms");
log.debug("Scheduled CsvResultsWriter with delay {} ms and period {} ms", delayAndPeriodCw, delayAndPeriodCw);
}
if (javaAgentCfg.getMonitoring().getPrometheus().isEnabled()) {
PrometheusWriter pw = new PrometheusWriter(javaAgentCfg.getMonitoring().getPrometheus());
Expand All @@ -108,7 +108,7 @@ public void run() {
pw.writeEnergyConsumptionPerMethodFiltered(powerMeasurementCollector.getEnergyConsumptionPerMethod(true));
}
}, delayAndPeriodPw, delayAndPeriodPw);
log.debug("Scheduled PrometheusWriter with delay " + delayAndPeriodPw + "ms and period " + delayAndPeriodPw + "ms");
log.debug("Scheduled PrometheusWriter with delay {} ms and period {} ms", delayAndPeriodPw, delayAndPeriodPw);
}

// Gracefully stop measurement at application shutdown
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ public boolean areDataPointsAddable(@NotNull DataPoint dp1, @NotNull DataPoint d
if (dp1.getUnit() == null || dp2.getUnit() == null
|| dp1.getValue() == null || dp2.getValue() == null
|| !dp1.getUnit().equals(dp2.getUnit())) {
log.warn("not addable: dp1 = " + dp1 + ", dp2 = " + dp2);
log.warn("not addable: dp1 = {}, dp2 = {}", dp1, dp2);
return false;
}
return true;
Expand Down
Loading

0 comments on commit 0288d76

Please sign in to comment.