├── extension ├── src │ ├── test │ │ ├── resources │ │ │ └── mockito-extensions │ │ │ │ └── org.mockito.plugins.MockMaker │ │ └── java │ │ │ └── io │ │ │ └── retit │ │ │ └── opentelemetry │ │ │ └── javaagent │ │ │ └── extension │ │ │ ├── frameworks │ │ │ ├── quarkus │ │ │ │ ├── QuarkusWithInternalOtelSupportAndExtensionIT.java │ │ │ │ ├── QuarkusRESTApplicationIT.java │ │ │ │ └── QuarkusWithExternalOtelAgentAndExtensionIT.java │ │ │ ├── spring │ │ │ │ ├── SpringWithExternalOtelAgentAndExtensionIT.java │ │ │ │ └── SpringRESTApplicationIT.java │ │ │ └── AbstractFrameworkIT.java │ │ │ ├── jdk │ │ │ ├── JDK8JavaAgentExtensionIT.java │ │ │ └── JDK21JavaAgentExtensionIT.java │ │ │ ├── resources │ │ │ ├── windows │ │ │ │ └── WindowsDataCollectorTest.java │ │ │ ├── linux │ │ │ │ └── LinuxDataCollectorTest.java │ │ │ └── macos │ │ │ │ └── MacOSDataCollectorTest.java │ │ │ ├── emissions │ │ │ └── EmissionDataLoaderTest.java │ │ │ ├── common │ │ │ ├── MetricDemand.java │ │ │ ├── SpanDemand.java │ │ │ └── ContainerLogMetricAndSpanExtractingTest.java │ │ │ └── processor │ │ │ └── RETITSpanProcessorTest.java │ └── main │ │ ├── resources │ │ └── ccf-coefficients │ │ │ ├── ccf-coefficients.md │ │ │ ├── instances │ │ │ ├── coefficients-azure-use.csv │ │ │ └── coefficients-gcp-use.csv │ │ │ ├── grid-emissions │ │ │ ├── grid-emissions-factors-gcp.csv │ │ │ ├── grid-emissions-factors-azure.csv │ │ │ └── grid-emissions-factors-aws.csv │ │ │ └── embodied-emissions │ │ │ └── coefficients-gcp-embodied-mean.csv │ │ └── java │ │ └── io │ │ └── retit │ │ └── opentelemetry │ │ └── javaagent │ │ └── extension │ │ ├── resources │ │ ├── common │ │ │ ├── ThreadHandle.java │ │ │ ├── CLibrary.java │ │ │ ├── IResourceDemandDataCollector.java │ │ │ └── NativeFacade.java │ │ ├── macos │ │ │ ├── MacOSDataCollector.java │ │ │ └── MacOSSystemLibrary.java │ │ ├── linux │ │ │ ├── LinuxCLibrary.java │ │ │ └── LinuxDataCollector.java │ │ ├── windows │ │ │ ├── WindowsDataCollector.java │ │ │ └── WindowsKernel32Library.java │ │ └── jvm │ │ │ ├── IBMDataCollector.java │ │ │ ├── HotSpotDataCollector.java │ │ │ ├── JavaAgentGCHandler.java │ │ │ └── JavaAgentGCNotificationListener.java │ │ ├── emissions │ │ ├── CloudProvider.java │ │ ├── embodied │ │ │ └── EmbodiedEmissions.java │ │ ├── CloudCarbonFootprintInstanceData.java │ │ └── CloudCarbonFootprintCoefficients.java │ │ ├── energy │ │ ├── MemoryEnergyData.java │ │ ├── NetworkEnergyData.java │ │ └── StorageEnergyData.java │ │ ├── OpenTelemetryCustomizer.java │ │ ├── commons │ │ ├── CSVParser.java │ │ └── InstanceConfiguration.java │ │ └── processor │ │ └── RETITSpanProcessor.java └── pom.xml ├── img ├── jdk21_dashboard.png ├── jdk8_dashboard.png ├── spring_dashboard.png ├── demo_architecture.png ├── quarkus_dashboard.png ├── extension_data_capture.png └── energy_emission_calculations.png ├── .mvn └── wrapper │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── examples ├── spring-rest-service │ ├── src │ │ └── main │ │ │ ├── resources │ │ │ └── application.properties │ │ │ └── java │ │ │ └── io │ │ │ └── retit │ │ │ └── spring │ │ │ └── carbon │ │ │ ├── SpringCarbonEmissionsApplication.java │ │ │ ├── TestRESTEndpoint.java │ │ │ └── TestService.java │ ├── README.md │ └── pom.xml ├── docker │ ├── grafana │ │ └── provisioning │ │ │ ├── datasources │ │ │ └── default.yaml │ │ │ └── dashboards │ │ │ └── default.yml │ ├── prometheus │ │ └── prometheus-config.yaml │ ├── otelcollector │ │ └── otelcol-config.yml │ └── docker-compose.yml ├── pom.xml ├── quarkus-rest-service │ ├── src │ │ └── main │ │ │ ├── resources │ │ │ └── application.properties │ │ │ └── java │ │ │ └── io │ │ │ └── retit │ │ │ └── opentelemetry │ │ │ └── quarkus │ │ │ ├── TestRESTEndpoint.java │ │ │ ├── TestService.java │ │ │ ├── OpenTelemetryService.java │ │ │ └── ResourceDemandMeasurementService.java │ └── README.md ├── README.md ├── simple-jdk8-application │ ├── README.md │ ├── src │ │ └── main │ │ │ └── java │ │ │ └── io │ │ │ └── retit │ │ │ └── opentelemetry │ │ │ └── SampleApplication.java │ └── pom.xml └── simple-jdk21-application │ ├── README.md │ ├── src │ └── main │ │ └── java │ │ └── io │ │ └── retit │ │ └── opentelemetry │ │ └── SampleApplication.java │ └── pom.xml ├── .github ├── dependabot.yml └── workflows │ ├── release.yml │ └── maven.yml ├── etc ├── spotbugs-exclusion-filter.xml └── pmd-configuration.xml ├── .gitignore └── pom.xml /extension/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker: -------------------------------------------------------------------------------- 1 | mock-maker-inline -------------------------------------------------------------------------------- /img/jdk21_dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RETIT/opentelemetry-javaagent-extension/HEAD/img/jdk21_dashboard.png -------------------------------------------------------------------------------- /img/jdk8_dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RETIT/opentelemetry-javaagent-extension/HEAD/img/jdk8_dashboard.png -------------------------------------------------------------------------------- /img/spring_dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RETIT/opentelemetry-javaagent-extension/HEAD/img/spring_dashboard.png -------------------------------------------------------------------------------- /img/demo_architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RETIT/opentelemetry-javaagent-extension/HEAD/img/demo_architecture.png -------------------------------------------------------------------------------- /img/quarkus_dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RETIT/opentelemetry-javaagent-extension/HEAD/img/quarkus_dashboard.png -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RETIT/opentelemetry-javaagent-extension/HEAD/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /img/extension_data_capture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RETIT/opentelemetry-javaagent-extension/HEAD/img/extension_data_capture.png -------------------------------------------------------------------------------- /img/energy_emission_calculations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RETIT/opentelemetry-javaagent-extension/HEAD/img/energy_emission_calculations.png -------------------------------------------------------------------------------- /examples/spring-rest-service/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=spring-carbon-emissions 2 | server.port=8081 3 | server.tomcat.accept-count=1000 4 | server.tomcat.max-connections=10000 5 | server.tomcat.threads.max=1000 -------------------------------------------------------------------------------- /examples/docker/grafana/provisioning/datasources/default.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: 1 2 | 3 | datasources: 4 | - name: Prometheus 5 | uid: api-metrics 6 | type: prometheus 7 | url: http://prometheus:9090 8 | editable: true 9 | isDefault: true 10 | -------------------------------------------------------------------------------- /examples/docker/grafana/provisioning/dashboards/default.yml: -------------------------------------------------------------------------------- 1 | apiVersion: 1 2 | 3 | providers: 4 | - name: Default # A uniquely identifiable name for the provider 5 | folder: Carbon Emissions # The folder where to place the dashboards 6 | type: file 7 | options: 8 | path: 9 | /etc/grafana/provisioning/dashboards -------------------------------------------------------------------------------- /examples/docker/prometheus/prometheus-config.yaml: -------------------------------------------------------------------------------- 1 | global: 2 | evaluation_interval: 30s 3 | scrape_interval: 5s 4 | scrape_configs: 5 | - job_name: otel 6 | honor_labels: true 7 | static_configs: 8 | - targets: 9 | - 'otelcol:9464' 10 | - job_name: otel-collector 11 | static_configs: 12 | - targets: 13 | - 'otelcol:8888' -------------------------------------------------------------------------------- /extension/src/main/resources/ccf-coefficients/ccf-coefficients.md: -------------------------------------------------------------------------------- 1 | The csv files in this repository are copied from the [CCF coefficients project](https://github.com/cloud-carbon-footprint/ccf-coefficients) and are licensed under the [Apache 2.0 license](https://github.com/cloud-carbon-footprint/ccf-coefficients/blob/main/LICENSE). This LICENSE is also included in this directory. -------------------------------------------------------------------------------- /extension/src/test/java/io/retit/opentelemetry/javaagent/extension/frameworks/quarkus/QuarkusWithInternalOtelSupportAndExtensionIT.java: -------------------------------------------------------------------------------- 1 | package io.retit.opentelemetry.javaagent.extension.frameworks.quarkus; 2 | 3 | import io.retit.opentelemetry.javaagent.extension.frameworks.AbstractFrameworkIT; 4 | 5 | public class QuarkusWithInternalOtelSupportAndExtensionIT extends AbstractFrameworkIT { 6 | } 7 | -------------------------------------------------------------------------------- /extension/src/test/java/io/retit/opentelemetry/javaagent/extension/jdk/JDK8JavaAgentExtensionIT.java: -------------------------------------------------------------------------------- 1 | package io.retit.opentelemetry.javaagent.extension.jdk; 2 | 3 | import org.junit.jupiter.api.BeforeEach; 4 | 5 | public class JDK8JavaAgentExtensionIT extends JavaAgentExtensionIT { 6 | 7 | @BeforeEach 8 | public void setupApplication() { 9 | String image = "simple-jdk8-application:feature"; 10 | commonSetup(image); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "maven" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /examples/spring-rest-service/src/main/java/io/retit/spring/carbon/SpringCarbonEmissionsApplication.java: -------------------------------------------------------------------------------- 1 | package io.retit.spring.carbon; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.scheduling.annotation.EnableScheduling; 6 | 7 | @EnableScheduling 8 | @SpringBootApplication 9 | public class SpringCarbonEmissionsApplication { 10 | 11 | public static void main(final String[] args) { 12 | SpringApplication.run(SpringCarbonEmissionsApplication.class, args); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /examples/docker/otelcollector/otelcol-config.yml: -------------------------------------------------------------------------------- 1 | receivers: 2 | otlp: 3 | protocols: 4 | grpc: 5 | endpoint: 0.0.0.0:4317 6 | http: 7 | endpoint: 0.0.0.0:4318 8 | 9 | exporters: 10 | otlp: 11 | endpoint: "jaeger:4317" 12 | tls: 13 | insecure: true 14 | 15 | prometheus: 16 | endpoint: "otelcol:9464" 17 | 18 | processors: 19 | batch: 20 | 21 | service: 22 | pipelines: 23 | 24 | metrics: 25 | receivers: [otlp] 26 | processors: [batch] 27 | exporters: [prometheus] 28 | 29 | traces: 30 | receivers: [otlp] 31 | processors: [batch] 32 | exporters: [otlp] 33 | -------------------------------------------------------------------------------- /extension/src/main/resources/ccf-coefficients/instances/coefficients-azure-use.csv: -------------------------------------------------------------------------------- 1 | ,Architecture,Min Watts,Max Watts,GB/Chip 2 | 0,EPYC 3rd Gen,0.44538981119791665,2.0193277994791665,128.0 3 | 1,Haswell,1.9005681818181814,6.012910353535353,27.310344827586206 4 | 2,Cascade Lake,0.6389493581523519,3.9673047343937564,98.11764705882354 5 | 3,Skylake,0.6446044454253452,4.193436438541878,80.43037974683544 6 | 4,Broadwell,0.7128342245989304,3.6853275401069516,69.6470588235294 7 | 5,EPYC 2nd Gen,0.4742621527777778,1.6929615162037037,129.77777777777777 8 | 6,Coffee Lake,1.138425925925926,5.421759259259258,19.555555555555557 9 | 7,EPYC 1st Gen,0.82265625,2.553125,89.6 10 | -------------------------------------------------------------------------------- /extension/src/main/resources/ccf-coefficients/instances/coefficients-gcp-use.csv: -------------------------------------------------------------------------------- 1 | ,Architecture,Min Watts,Max Watts,GB/Chip 2 | 0,Skylake,0.6446044454253452,3.8984738056304855,80.43037974683544 3 | 1,Broadwell,0.7128342245989304,3.3857473048128344,69.6470588235294 4 | 2,Haswell,1.9005681818181814,5.9688982156043195,27.310344827586206 5 | 3,EPYC 2nd Gen,0.4742621527777778,1.5751872939814815,129.77777777777777 6 | 4,Cascade Lake,0.6389493581523519,3.6424520285114035,98.11764705882354 7 | 5,EPYC 3rd Gen,0.44538981119791665,1.8719357994791666,128.0 8 | 6,Ivy Bridge,3.0369270833333335,8.199689511111112,14.933333333333334 9 | 7,Sandy Bridge,2.1694411458333334,8.550185877430936,16.480916030534353 10 | -------------------------------------------------------------------------------- /extension/src/test/java/io/retit/opentelemetry/javaagent/extension/jdk/JDK21JavaAgentExtensionIT.java: -------------------------------------------------------------------------------- 1 | package io.retit.opentelemetry.javaagent.extension.jdk; 2 | 3 | import org.junit.jupiter.api.BeforeEach; 4 | import org.junit.jupiter.api.Test; 5 | 6 | public class JDK21JavaAgentExtensionIT extends JavaAgentExtensionIT { 7 | 8 | @BeforeEach 9 | public void setupApplication() { 10 | String image = "simple-jdk21-application:feature"; 11 | commonSetup(image); 12 | } 13 | 14 | @Test 15 | public void testCPUDemandWithVirtualThreads() { 16 | applicationContainer.withEnv("RUN_MODE", "VIRTUAL_THREAD"); 17 | testOnlyCPUDemand(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /extension/src/test/java/io/retit/opentelemetry/javaagent/extension/frameworks/spring/SpringWithExternalOtelAgentAndExtensionIT.java: -------------------------------------------------------------------------------- 1 | package io.retit.opentelemetry.javaagent.extension.frameworks.spring; 2 | 3 | import io.retit.opentelemetry.javaagent.extension.frameworks.AbstractFrameworkIT; 4 | import org.junit.jupiter.api.BeforeEach; 5 | import org.junit.jupiter.api.Test; 6 | 7 | public class SpringWithExternalOtelAgentAndExtensionIT extends AbstractFrameworkIT { 8 | @BeforeEach 9 | public void beforeEach() { 10 | commonSetup("spring-rest-service:feature", "spring-app", 8081, true, false); 11 | } 12 | 13 | @Test 14 | public void testCallEachEndpointAndAssertSpansAndMetrics() { 15 | super.testCallEachEndpointAndAsserSpansAndMetrics(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /extension/src/main/java/io/retit/opentelemetry/javaagent/extension/resources/common/ThreadHandle.java: -------------------------------------------------------------------------------- 1 | package io.retit.opentelemetry.javaagent.extension.resources.common; 2 | 3 | import com.sun.jna.Pointer; 4 | import com.sun.jna.PointerType; 5 | 6 | /** 7 | * The Handle class is used as data structure to store pointers to the Thread handle. 8 | */ 9 | public class ThreadHandle extends PointerType { 10 | /** 11 | * Constructor for the Handle class with the Pointer address. 12 | * @param address - the Pointer address. 13 | */ 14 | public ThreadHandle(final Pointer address) { 15 | super(address); 16 | } 17 | 18 | /** 19 | * Default constructor of the Handle class. 20 | */ 21 | public ThreadHandle() { 22 | super(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /extension/src/test/java/io/retit/opentelemetry/javaagent/extension/frameworks/spring/SpringRESTApplicationIT.java: -------------------------------------------------------------------------------- 1 | package io.retit.opentelemetry.javaagent.extension.frameworks.spring; 2 | 3 | import io.retit.opentelemetry.javaagent.extension.frameworks.AbstractFrameworkIT; 4 | import org.junit.jupiter.api.Disabled; 5 | import org.junit.jupiter.api.Test; 6 | 7 | public class SpringRESTApplicationIT extends AbstractFrameworkIT { 8 | /** 9 | * This test will run continuously until it is manually stopped, so it is disabled by default. 10 | */ 11 | @Disabled 12 | @Test 13 | public void runTestContinuously() { 14 | // this test is meant to be executed for a Spring app that has been started by the user. 15 | CONTAINER_URL = "http://localhost:8081"; 16 | super.runTestContinuously(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /extension/src/main/java/io/retit/opentelemetry/javaagent/extension/resources/macos/MacOSDataCollector.java: -------------------------------------------------------------------------------- 1 | package io.retit.opentelemetry.javaagent.extension.resources.macos; 2 | 3 | import io.retit.opentelemetry.javaagent.extension.resources.common.IResourceDemandDataCollector; 4 | import io.retit.opentelemetry.javaagent.extension.resources.common.NativeFacade; 5 | import io.retit.opentelemetry.javaagent.extension.resources.common.CommonResourceDemandDataCollector; 6 | 7 | /** 8 | * An {@link IResourceDemandDataCollector 9 | * IResourceDemandDataCollector} which retrieves resource demands on Mac 10 | * systems. 11 | */ 12 | public class MacOSDataCollector extends CommonResourceDemandDataCollector { 13 | @Override 14 | protected long getPlatformSpecificThreadCpuTime() { 15 | return NativeFacade.getCurrentThreadCpuTime(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /extension/src/test/java/io/retit/opentelemetry/javaagent/extension/frameworks/quarkus/QuarkusRESTApplicationIT.java: -------------------------------------------------------------------------------- 1 | package io.retit.opentelemetry.javaagent.extension.frameworks.quarkus; 2 | 3 | import io.retit.opentelemetry.javaagent.extension.frameworks.AbstractFrameworkIT; 4 | import org.junit.jupiter.api.Disabled; 5 | import org.junit.jupiter.api.Test; 6 | 7 | public class QuarkusRESTApplicationIT extends AbstractFrameworkIT { 8 | 9 | /** 10 | * This test will run continuously until it is manually stopped, so it is disabled by default. 11 | */ 12 | @Disabled 13 | @Test 14 | public void runTestContinuously() { 15 | // this test is meant to be executed for a Quarkus app that has been started by the user. 16 | CONTAINER_URL = "http://localhost:8080"; 17 | super.runTestContinuously(); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /extension/src/main/java/io/retit/opentelemetry/javaagent/extension/emissions/CloudProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RETIT GmbH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.retit.opentelemetry.javaagent.extension.emissions; 18 | 19 | /** 20 | * Enumeration of Supported Cloud providers. 21 | */ 22 | public enum CloudProvider { 23 | AWS, GCP, AZURE 24 | } 25 | -------------------------------------------------------------------------------- /extension/src/test/java/io/retit/opentelemetry/javaagent/extension/frameworks/quarkus/QuarkusWithExternalOtelAgentAndExtensionIT.java: -------------------------------------------------------------------------------- 1 | package io.retit.opentelemetry.javaagent.extension.frameworks.quarkus; 2 | 3 | import io.retit.opentelemetry.javaagent.extension.frameworks.AbstractFrameworkIT; 4 | import org.junit.jupiter.api.BeforeEach; 5 | import org.junit.jupiter.api.Disabled; 6 | import org.junit.jupiter.api.Test; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | public class QuarkusWithExternalOtelAgentAndExtensionIT extends AbstractFrameworkIT { 11 | private static final Logger LOGGER = LoggerFactory.getLogger(QuarkusWithExternalOtelAgentAndExtensionIT.class); 12 | 13 | @BeforeEach 14 | public void beforeEach() { 15 | commonSetup("quarkus-rest-service:feature", "quarkus-app", 8080, true, false); 16 | } 17 | 18 | @Test 19 | public void testCallEachEndpointAndAssertSpansAndMetrics() { 20 | super.testCallEachEndpointAndAsserSpansAndMetrics(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.8/apache-maven-3.9.8-bin.zip 18 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar 19 | -------------------------------------------------------------------------------- /examples/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | io.retit 6 | examples 7 | 0.0.1-SNAPSHOT 8 | pom 9 | 10 | 11 | io.retit 12 | opentelemetry-javaagent-extension 13 | 0.0.1-SNAPSHOT 14 | 15 | 16 | 17 | mirror.gcr.io/library/eclipse-temurin:21-jre 18 | 3.5.1 19 | 20 | 21 | 22 | simple-jdk8-application 23 | simple-jdk21-application 24 | quarkus-rest-service 25 | spring-rest-service 26 | 27 | -------------------------------------------------------------------------------- /extension/src/main/resources/ccf-coefficients/grid-emissions/grid-emissions-factors-gcp.csv: -------------------------------------------------------------------------------- 1 | Region,Location,CO2e (metric ton/kWh),Source 2 | us-central1,Iowa,0.000479,Google 3 | us-east1,South Carolina,0.0005,Google 4 | us-east4,Northern Virginia,0.000383,Google 5 | us-west1,Oregon,0.000117,Google 6 | us-west2,Los Angeles,0.000248,Google 7 | us-west3,Salt Lake City,0.000561,Google 8 | us-west4,Las Vegas,0.000491,Google 9 | asia-east1,Taiwan,0.000541,Google 10 | asia-east2,Hong Kong,0.000626,Google 11 | asia-northeast1,Japan,0.000524,Google 12 | asia-northeast2,Japan,0.000524,Google 13 | asia-northeast3,South Korea,0.00054,Google 14 | asia-south1,India,0.000723,Google 15 | asia-southeast1,Singapore,0.000493,Google 16 | asia-southeast2,Indonesia,0.000772,Google 17 | australia-southeast1,Australia,0.000725,Google 18 | europe-north1,Finland,0.000181,Google 19 | europe-west1,Belgium,0.000196,Google 20 | europe-west2,England,0.000257,Google 21 | europe-west3,Germany,0.000319,Google 22 | europe-west4,Netherlands,0.000474,Google 23 | europe-west6,Switzerland,0.000029,Google 24 | northamerica-northeast1,Canada,0.000143,Google 25 | southamerica-east1,Brazil,0.000109,Google -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Releases 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | 8 | jobs: 9 | 10 | build: 11 | runs-on: ubuntu-latest 12 | permissions: 13 | contents: write 14 | steps: 15 | - uses: actions/checkout@v4 16 | - name: Set up JDK 21 17 | uses: actions/setup-java@v3 18 | with: 19 | java-version: '21' 20 | distribution: 'temurin' 21 | cache: maven 22 | - name: Compile and run Checkstyle, PMD and Spotbugs checks 23 | run: mvn -B checkstyle:check pmd:check compile spotbugs:check --file pom.xml 24 | - name: Package Extension and create Docker Container for Sample Application 25 | run: mvn -B package --file pom.xml 26 | - name: Run Integration Test 27 | run: mvn -B test -Dtest=JDK8JavaAgentExtensionIT,JDK21JavaAgentExtensionIT,SpringWithExternalOtelAgentAndExtensionIT,QuarkusWithExternalOtelAgentAndExtensionIT --file ./extension/pom.xml 28 | - name: Create Release and Upload Artifact 29 | uses: ncipollo/release-action@v1 30 | with: 31 | artifacts: "extension/target/io.retit.opentelemetry.javaagent.extension.jar" -------------------------------------------------------------------------------- /extension/src/main/java/io/retit/opentelemetry/javaagent/extension/resources/common/CLibrary.java: -------------------------------------------------------------------------------- 1 | package io.retit.opentelemetry.javaagent.extension.resources.common; 2 | 3 | import com.sun.jna.Library; 4 | import com.sun.jna.NativeLong; 5 | import com.sun.jna.Structure; 6 | 7 | /** 8 | * This interface provides access to the clock_gettime method of the time.h library. 9 | * See https://man7.org/linux/man-pages/man2/clock_gettime.2.html 10 | * This call can be used in Linux since 2.6.12 and for MacOS versions higher than 10. 11 | */ 12 | @SuppressWarnings("PMD") 13 | public interface CLibrary extends Library { 14 | 15 | /** 16 | * Retrievs the time specified by the clock id. 17 | * 18 | * @param clockid -the clock for which the time needs to be fetched 19 | * @param tp - pointer to the TimeSpec containing the measurements. 20 | * @return - return 0 for success, -1 on error 21 | */ 22 | int clock_gettime(int clockid, TimeSpec tp); 23 | 24 | @Structure.FieldOrder({"tv_sec", "tv_nsec"}) 25 | class TimeSpec extends Structure { 26 | public NativeLong tv_sec; // seconds 27 | public int tv_nsec; // nanoseconds 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /examples/quarkus-rest-service/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | quarkus.http.port=8080 2 | quarkus.package.jar.type=uber-jar 3 | 4 | quarkus.package.jar.add-runner-suffix=false 5 | 6 | quarkus.container-image.build=true 7 | quarkus.jib.base-jvm-image=mirror.gcr.io/library/eclipse-temurin:21-jre 8 | quarkus.container-image.image=quarkus-rest-service:feature 9 | #quarkus.jib.jvm-entrypoint=java,-javaagent:./otel/opentelemetry-javaagent-all.jar,-Dotel.javaagent.extensions=./otel/io.retit.opentelemetry.javaagent.extension.jar,-Dio.retit.emissions.cloud.provider=aws,-Dio.retit.emissions.cloud.provider.region=af-south-1,-Dio.retit.emissions.cloud.provider.instance.type=a1.medium,-Dotel.logs.exporter=none,-Dotel.traces.exporter=none,-Dresource.attributes=quarkus-app,-D-jar quarkus-run.jar 10 | #quarkus.jib.jvm-entrypoint=java,-Dio.retit.emissions.cloud.provider=aws,-Dio.retit.emissions.cloud.provider.region=af-south-1,-Dio.retit.emissions.cloud.provider.instance.type=a1.medium,-Dotel.logs.exporter=none,-jar quarkus-run.jar 11 | 12 | quarkus.otel.exporter.otlp.endpoint=http://otelcol:4317 13 | quarkus.application.name=quarkus-app 14 | quarkus.otel.metrics.enabled=true 15 | quarkus.otel.logs.enabled=false -------------------------------------------------------------------------------- /extension/src/main/resources/ccf-coefficients/grid-emissions/grid-emissions-factors-azure.csv: -------------------------------------------------------------------------------- 1 | Region,Location,NERC Region,CO2e (metric ton/kWh),Source 2 | Central US,Iowa,MRO,0.00047223,EPA 3 | East US,Virginia,SERC,0.000415755,EPA 4 | East US 2,Virginia,SERC,0.000415755,EPA 5 | East US 3,Georgia,SERC,0.000415755,EPA 6 | North Central US,Illinois,RFC,0.000440187,EPA 7 | South Central US,Texas,TRE,0.000396293,EPA 8 | West Central US,Wyoming,WECC,0.000350861,EPA 9 | West US,California,WECC,0.000350861,EPA 10 | West US 2,Washington,WECC,0.000350861,EPA 11 | West US 3,Arizona,WECC,0.000350861,EPA 12 | East Asia,Hong Kong,,0.00081,carbonfootprint.com 13 | Southeast Asia,Singapore,,0.0004085,EMA Singapore 14 | North Europe,Ireland,,0.000316,EEA 15 | West Europe,Netherlands,,0.00039,EEA 16 | Central India,Pune,,0.000708,carbonfootprint.com 17 | South India,Chennai,,0.000708,carbonfootprint.com 18 | West India,Mumbai,,0.000708,carbonfootprint.com 19 | UK South,London,,0.000228,EEA 20 | UK West,Cardiff,,0.000228,EEA 21 | France Central,Paris,,0.000067,EEA 22 | Finland Central,Helsinki,,0.00077,EEA, 23 | Germany West Central,Frankfurt,,0.00402,EEA 24 | Belgium Central,Brussels,,0.000154,EEA 25 | Sweden Central,Gavle,,0.000009,EEA -------------------------------------------------------------------------------- /examples/quarkus-rest-service/src/main/java/io/retit/opentelemetry/quarkus/TestRESTEndpoint.java: -------------------------------------------------------------------------------- 1 | package io.retit.opentelemetry.quarkus; 2 | 3 | import jakarta.inject.Inject; 4 | import jakarta.ws.rs.DELETE; 5 | import jakarta.ws.rs.GET; 6 | import jakarta.ws.rs.POST; 7 | import jakarta.ws.rs.Path; 8 | 9 | import java.io.IOException; 10 | 11 | /** 12 | * This is an example REST service that provides three endpoints for HTTP GET / POST and DELETE. 13 | */ 14 | @Path("/test-rest-endpoint") 15 | public class TestRESTEndpoint { 16 | 17 | @Inject 18 | private TestService testService; 19 | 20 | @GET 21 | @Path("getData") 22 | public String getData() throws InterruptedException, IOException { 23 | return "GET " + testService.veryComplexBusinessFunction(3000, "GET"); 24 | } 25 | 26 | @POST 27 | @Path("postData") 28 | public String postData() throws InterruptedException, IOException { 29 | return "POST" + testService.veryComplexBusinessFunction(4000, "POST"); 30 | } 31 | 32 | @DELETE 33 | @Path("deleteData") 34 | public String deleteData() throws InterruptedException, IOException { 35 | return "DELETE" + testService.veryComplexBusinessFunction(6000, "DELETE"); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /extension/src/main/resources/ccf-coefficients/grid-emissions/grid-emissions-factors-aws.csv: -------------------------------------------------------------------------------- 1 | Region,Country,NERC Region,CO2e (metric ton/kWh),Source 2 | us-east-1,United States,SERC,0.000415755,EPA 3 | us-east-2,United States,RFC,0.000440187,EPA 4 | us-west-1,United States,WECC,0.000350861,EPA 5 | us-west-2,United States,WECC,0.000350861,EPA 6 | us-gov-east-1,United States,SERC,0.000415755,EPA 7 | us-gov-west-1,United States,WECC,0.000350861,EPA 8 | af-south-1,South Africa,,0.000928,carbonfootprint.com 9 | ap-east-1,Hong Kong,,0.00081,carbonfootprint.com 10 | ap-south-1,India,,0.000708,carbonfootprint.com 11 | ap-northeast-3,Japan,,0.000506,carbonfootprint.com 12 | ap-northeast-2,South Korea,,0.0005,carbonfootprint.com 13 | ap-southeast-1,Singapore,,0.0004085,EMA Singapore 14 | ap-southeast-2,Australia,,0.00079,carbonfootprint.com 15 | ap-northeast-1,Japan,,0.000506,carbonfootprint.com 16 | ca-central-1,Canada,,0.00013,carbonfootprint.com 17 | cn-north-1,China,,0.000555,carbonfootprint.com 18 | cn-northwest-1,China,,0.000555,carbonfootprint.com 19 | eu-central-1,Germany,,0.000338,EEA 20 | eu-west-1,Ireland,,0.000316,EEA 21 | eu-west-2,England,,0.000228,EEA 22 | eu-south-1,Italy,,0.000233,EEA 23 | eu-west-3,France,,0.000052,EEA 24 | eu-north-1,Sweden,,0.000008,EEA 25 | me-south-1,Bahrain,,0.000732,carbonfootprint.com 26 | sa-east-1,Brazil,,0.000074,carbonfootprint.com -------------------------------------------------------------------------------- /etc/spotbugs-exclusion-filter.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /extension/src/main/java/io/retit/opentelemetry/javaagent/extension/resources/macos/MacOSSystemLibrary.java: -------------------------------------------------------------------------------- 1 | package io.retit.opentelemetry.javaagent.extension.resources.macos; 2 | 3 | import com.sun.jna.Native; 4 | import io.retit.opentelemetry.javaagent.extension.resources.common.CLibrary; 5 | import io.retit.opentelemetry.javaagent.extension.resources.common.NativeFacade; 6 | import io.retit.opentelemetry.javaagent.extension.resources.common.ThreadHandle; 7 | 8 | /** 9 | * JNA Library which allows access to the native System API of MacOS. 10 | * Generally, this class should not be accessed directly - the wrapper 11 | * functions defined in {@link NativeFacade} should be used instead. 12 | * Currently, we only define those methods which we need for our purposes. 13 | * If you need another method from the glibc API, just add the method 14 | * to the interface. However, please also add a generalized method 15 | * to {@link NativeFacade} if possible. 16 | */ 17 | @SuppressWarnings("PMD") 18 | public interface MacOSSystemLibrary extends CLibrary { 19 | /** 20 | * This is a clock that measures CPU time consumed by this 21 | * thread. 22 | */ 23 | int CLOCK_THREAD_CPUTIME_ID = 16; 24 | 25 | MacOSSystemLibrary INSTANCE = Native.load("System", MacOSSystemLibrary.class); 26 | 27 | ThreadHandle pthread_self(); 28 | 29 | int pthread_mach_thread_np(ThreadHandle threadHandle); 30 | } 31 | -------------------------------------------------------------------------------- /extension/src/main/java/io/retit/opentelemetry/javaagent/extension/resources/linux/LinuxCLibrary.java: -------------------------------------------------------------------------------- 1 | package io.retit.opentelemetry.javaagent.extension.resources.linux; 2 | 3 | import com.sun.jna.Native; 4 | import io.retit.opentelemetry.javaagent.extension.resources.common.CLibrary; 5 | import io.retit.opentelemetry.javaagent.extension.resources.common.NativeFacade; 6 | 7 | /** 8 | * JNA Library which allows access to the native C API of Linux. 9 | * Generally, this class should not be accessed directly - the wrapper 10 | * functions defined in {@link NativeFacade} should be used instead. 11 | * Currently, we only define those methods which we need for our purposes. 12 | * If you need another method from the glibc API, just add the method 13 | * to the interface. However, please also add a generalized method 14 | * to {@link NativeFacade} if possible. 15 | */ 16 | public interface LinuxCLibrary extends CLibrary { 17 | /** 18 | * This is a clock that measures CPU time consumed by this 19 | * thread. 20 | */ 21 | int CLOCK_THREAD_CPUTIME_ID = 3; 22 | 23 | /** 24 | * The instance provided by the JNA library to interact with the native c library. 25 | */ 26 | LinuxCLibrary INSTANCE = Native.loadLibrary("c", LinuxCLibrary.class); 27 | 28 | /** 29 | * Fetches the thread id of the calling thread. 30 | * 31 | * @return - the thread Id. 32 | */ 33 | int gettid(); 34 | } -------------------------------------------------------------------------------- /examples/spring-rest-service/src/main/java/io/retit/spring/carbon/TestRESTEndpoint.java: -------------------------------------------------------------------------------- 1 | package io.retit.spring.carbon; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.web.bind.annotation.DeleteMapping; 5 | import org.springframework.web.bind.annotation.GetMapping; 6 | import org.springframework.web.bind.annotation.PostMapping; 7 | import org.springframework.web.bind.annotation.RequestMapping; 8 | import org.springframework.web.bind.annotation.RestController; 9 | 10 | import java.io.IOException; 11 | 12 | /** 13 | * This is an example REST service that provides three endpoints for HTTP GET / POST and DELETE. 14 | */ 15 | @RestController 16 | @RequestMapping("/test-rest-endpoint") 17 | public class TestRESTEndpoint { 18 | 19 | @Autowired 20 | private TestService testService; 21 | 22 | @GetMapping 23 | @RequestMapping("getData") 24 | public String getData() throws IOException { 25 | return "GET " + testService.veryComplexBusinessFunction(3000); 26 | } 27 | 28 | @PostMapping 29 | @RequestMapping("postData") 30 | public String postData() throws IOException { 31 | return "POST" + testService.veryComplexBusinessFunction(4000); 32 | } 33 | 34 | @DeleteMapping 35 | @RequestMapping("deleteData") 36 | public String deleteData() throws IOException { 37 | return "DELETE" + testService.veryComplexBusinessFunction(6000); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /extension/src/main/java/io/retit/opentelemetry/javaagent/extension/resources/windows/WindowsDataCollector.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RETIT GmbH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.retit.opentelemetry.javaagent.extension.resources.windows; 18 | 19 | import io.retit.opentelemetry.javaagent.extension.resources.common.IResourceDemandDataCollector; 20 | import io.retit.opentelemetry.javaagent.extension.resources.common.NativeFacade; 21 | import io.retit.opentelemetry.javaagent.extension.resources.common.CommonResourceDemandDataCollector; 22 | 23 | /** 24 | * An {@link IResourceDemandDataCollector 25 | * IResourceDemandDataCollector} which retrieves resource demands on Windows 26 | * systems. 27 | */ 28 | public class WindowsDataCollector extends CommonResourceDemandDataCollector { 29 | 30 | @Override 31 | protected long getPlatformSpecificThreadCpuTime() { 32 | return NativeFacade.getCurrentThreadCpuTime(); 33 | } 34 | } 35 | 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .metadata 3 | **/target/ 4 | bin/ 5 | tmp/ 6 | *.tmp 7 | *.bak 8 | *.swp 9 | *~.nib 10 | local.properties 11 | .settings/ 12 | .loadpath 13 | .recommenders 14 | 15 | # Eclipse Core 16 | .project 17 | 18 | # External tool builders 19 | .externalToolBuilders/ 20 | 21 | # Locally stored "Eclipse launch configurations" 22 | *.launch 23 | 24 | # PyDev specific (Python IDE for Eclipse) 25 | *.pydevproject 26 | 27 | # CDT-specific (C/C++ Development Tooling) 28 | .cproject 29 | 30 | # JDT-specific (Eclipse Java Development Tools) 31 | .classpath 32 | 33 | # Java annotation processor (APT) 34 | .factorypath 35 | 36 | # PDT-specific (PHP Development Tools) 37 | .buildpath 38 | 39 | # sbteclipse plugin 40 | .target 41 | 42 | # Tern plugin 43 | .tern-project 44 | 45 | # TeXlipse plugin 46 | .texlipse 47 | 48 | # STS (Spring Tool Suite) 49 | .springBeans 50 | 51 | # Code Recommenders 52 | .recommenders/ 53 | 54 | # Old SVN files 55 | .svn/ 56 | 57 | # IntelliJ 58 | *.iml 59 | **/.idea 60 | **/.DS_Store 61 | 62 | # Visual Studio Code 63 | .vscode 64 | 65 | # Maven 66 | de.retit.apm/de.retit.apm.javaagent.sampleapplication/dependency-reduced-pom.xml 67 | /sampleapplication/dependency-reduced-pom.xml 68 | /examples/simple-jdk8-application/dependency-reduced-pom.xml 69 | /examples/simple-jdk21-application/dependency-reduced-pom.xml 70 | /extension/dependency-reduced-pom.xml 71 | /examples/quarkus-rest-service/src/main/jib/otel/opentelemetry-javaagent.jar 72 | /examples/quarkus-rest-service/src/main/jib/otel/io.retit.opentelemetry.javaagent.extension.jar 73 | -------------------------------------------------------------------------------- /extension/src/test/java/io/retit/opentelemetry/javaagent/extension/resources/windows/WindowsDataCollectorTest.java: -------------------------------------------------------------------------------- 1 | package io.retit.opentelemetry.javaagent.extension.resources.windows; 2 | 3 | import com.sun.jna.Platform; 4 | import io.retit.opentelemetry.javaagent.extension.resources.common.NativeFacade; 5 | import org.junit.jupiter.api.Assertions; 6 | import org.junit.jupiter.api.Test; 7 | 8 | public class WindowsDataCollectorTest { 9 | 10 | @Test 11 | public void testNativeThreadIdWindows() { 12 | if (Platform.isWindows()) { 13 | long javaThreadId = Thread.currentThread().getId(); 14 | long nativeThreadId = NativeFacade.getThreadId(); 15 | Assertions.assertNotEquals(0, nativeThreadId); 16 | Assertions.assertNotEquals(javaThreadId, nativeThreadId); 17 | } 18 | } 19 | 20 | @Test 21 | public void testPlatformSpecificThreadCpuTimeWindows() { 22 | if (Platform.isWindows()) { 23 | WindowsDataCollector windowsDataCollector = new WindowsDataCollector(); 24 | long currentThreadCpuTimeThreadQueryThreadTime = windowsDataCollector.getPlatformSpecificThreadCpuTime(); 25 | long currentThreadCpuTimeThreadMXBean = windowsDataCollector.getCurrentThreadCpuTime(); 26 | 27 | long difference = currentThreadCpuTimeThreadMXBean - currentThreadCpuTimeThreadQueryThreadTime; 28 | // assert that the measurements are less than 20ms different (as they consume cpu time themselves) 29 | Assertions.assertTrue(difference < 20_000_000); 30 | } 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /extension/src/test/java/io/retit/opentelemetry/javaagent/extension/resources/linux/LinuxDataCollectorTest.java: -------------------------------------------------------------------------------- 1 | package io.retit.opentelemetry.javaagent.extension.resources.linux; 2 | 3 | import com.sun.jna.Platform; 4 | import io.retit.opentelemetry.javaagent.extension.resources.common.NativeFacade; 5 | import org.junit.jupiter.api.Assertions; 6 | import org.junit.jupiter.api.Test; 7 | 8 | public class LinuxDataCollectorTest { 9 | 10 | @Test 11 | public void testNativeThreadIdLinux() { 12 | if(Platform.isLinux()) { 13 | long javaThreadId = Thread.currentThread().getId(); 14 | long nativeThreadId = NativeFacade.getThreadId(); 15 | Assertions.assertNotEquals(0, nativeThreadId); 16 | Assertions.assertNotEquals(javaThreadId, nativeThreadId); 17 | } 18 | } 19 | 20 | @Test 21 | public void testPlatformSpecificThreadCpuTimeLinux() { 22 | if (Platform.isLinux()) { 23 | LinuxDataCollector linuxDataCollector = new LinuxDataCollector(); 24 | // call it once as after the first invocation the clock tick configuration is cached 25 | linuxDataCollector.getPlatformSpecificThreadCpuTime(); 26 | long currentThreadCpuTimeThreadProcFS = linuxDataCollector.getPlatformSpecificThreadCpuTime(); 27 | long currentThreadCpuTimeThreadMXBean = linuxDataCollector.getCurrentThreadCpuTime(); 28 | long difference = currentThreadCpuTimeThreadMXBean - currentThreadCpuTimeThreadProcFS; 29 | // assert that the measurements are less than 2ms different (as they consume cpu time themselves) 30 | Assertions.assertTrue(difference < 2_000_000); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /extension/src/test/java/io/retit/opentelemetry/javaagent/extension/resources/macos/MacOSDataCollectorTest.java: -------------------------------------------------------------------------------- 1 | package io.retit.opentelemetry.javaagent.extension.resources.macos; 2 | 3 | import com.sun.jna.Platform; 4 | import io.retit.opentelemetry.javaagent.extension.resources.common.NativeFacade; 5 | import org.junit.jupiter.api.Assertions; 6 | import org.junit.jupiter.api.Test; 7 | 8 | public class MacOSDataCollectorTest { 9 | 10 | @Test 11 | public void testNativeThreadIdMacOS() { 12 | if(Platform.isMac()) { 13 | long javaThreadId = Thread.currentThread().getId(); 14 | long nativeThreadId = NativeFacade.getThreadId(); 15 | Assertions.assertNotEquals(0, nativeThreadId); 16 | Assertions.assertNotEquals(javaThreadId, nativeThreadId); 17 | } 18 | } 19 | 20 | @Test 21 | public void testPlatformSpecificThreadCpuTimeMacOS() { 22 | if (Platform.isMac()) { 23 | MacOSDataCollector macosDataCollector = new MacOSDataCollector(); 24 | // call it once as after the first invocation the clock tick configuration is cached 25 | macosDataCollector.getPlatformSpecificThreadCpuTime(); 26 | long currentThreadCpuTimeThreadProcFS = macosDataCollector.getPlatformSpecificThreadCpuTime(); 27 | long currentThreadCpuTimeThreadMXBean = macosDataCollector.getCurrentThreadCpuTime(); 28 | 29 | long difference = currentThreadCpuTimeThreadMXBean - currentThreadCpuTimeThreadProcFS; 30 | // assert that the measurements are less than 2ms different (as they consume cpu time themselves) 31 | Assertions.assertTrue(difference < 2_000_000); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /extension/src/main/java/io/retit/opentelemetry/javaagent/extension/resources/jvm/IBMDataCollector.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RETIT GmbH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.retit.opentelemetry.javaagent.extension.resources.jvm; 18 | 19 | import io.retit.opentelemetry.javaagent.extension.resources.common.CommonResourceDemandDataCollector; 20 | 21 | /** 22 | * Resource demand collector for IBM JVMs. 23 | */ 24 | public class IBMDataCollector extends CommonResourceDemandDataCollector { 25 | @Override 26 | protected long getPlatformSpecificThreadCpuTime() { 27 | return 0; 28 | } 29 | 30 | @Override 31 | public long getCurrentThreadAllocatedBytes() { 32 | throw new UnsupportedOperationException("Disk IO demand cannot read for IBM JVMs"); 33 | } 34 | 35 | @Override 36 | public long[] getDiskBytesReadAndWritten() { 37 | throw new UnsupportedOperationException("Disk IO demand cannot read for IBM JVMs"); 38 | } 39 | 40 | @Override 41 | public long[] getNetworkBytesReadAndWritten() { 42 | throw new UnsupportedOperationException("Network IO demand cannot read for IBM JVMs"); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /extension/src/test/java/io/retit/opentelemetry/javaagent/extension/emissions/EmissionDataLoaderTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RETIT GmbH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.retit.opentelemetry.javaagent.extension.emissions; 18 | 19 | import io.retit.opentelemetry.javaagent.extension.commons.Constants; 20 | import io.retit.opentelemetry.javaagent.extension.emissions.embodied.EmbodiedEmissions; 21 | import org.junit.jupiter.api.Assertions; 22 | import org.junit.jupiter.api.Test; 23 | 24 | public class EmissionDataLoaderTest { 25 | 26 | @Test 27 | public void testEmissionDataLoaderForOneCloudProvider() { 28 | System.setProperty(Constants.RETIT_EMISSIONS_CLOUD_PROVIDER_CONFIGURATION_PROPERTY, "AWS"); 29 | System.setProperty(Constants.RETIT_EMISSIONS_CLOUD_PROVIDER_REGION_CONFIGURATION_PROPERTY, "af-south-1"); 30 | System.setProperty(Constants.RETIT_EMISSIONS_CLOUD_PROVIDER_INSTANCE_TYPE_CONFIGURATION_PROPERTY, "a1.medium"); 31 | Assertions.assertNotNull(CloudCarbonFootprintData.getConfigInstance()); 32 | double embodiedEmissions = EmbodiedEmissions.getInstance().calculateEmbodiedEmissionsInMilliGramPerMinute(); 33 | Assertions.assertTrue(embodiedEmissions > 0.0); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /examples/spring-rest-service/src/main/java/io/retit/spring/carbon/TestService.java: -------------------------------------------------------------------------------- 1 | package io.retit.spring.carbon; 2 | 3 | import org.springframework.stereotype.Service; 4 | 5 | import java.io.IOException; 6 | import java.nio.charset.StandardCharsets; 7 | import java.nio.file.Files; 8 | import java.nio.file.Path; 9 | import java.util.Arrays; 10 | import java.util.concurrent.ThreadLocalRandom; 11 | 12 | /** 13 | * Example service that does some very complex processing. 14 | */ 15 | @Service 16 | public class TestService { 17 | 18 | public String veryComplexBusinessFunction(final int size) throws IOException { 19 | 20 | Path tempFile = Files.createTempFile("sampleapplication", "veryComplexBusinessFunction"); 21 | 22 | int[] data = naiveSortingWithONSquareComplexity(generateRandomInputArray(size)); 23 | 24 | Files.write(tempFile, String.valueOf(data).getBytes(StandardCharsets.UTF_8)); 25 | 26 | Files.delete(tempFile); 27 | return String.valueOf(Arrays.stream(data).sum()); 28 | } 29 | 30 | private static int[] generateRandomInputArray(final int size) { 31 | int array[] = new int[size]; 32 | 33 | for (int i = 0; i < size; i++) { 34 | array[i] = ThreadLocalRandom.current().nextInt(0, size * 10); 35 | } 36 | 37 | return array; 38 | } 39 | 40 | // O(n²) 41 | private static int[] naiveSortingWithONSquareComplexity(final int... inputArray) { 42 | // Outer loop 43 | for (int i = 0; i < inputArray.length; i++) { 44 | 45 | // Inner nested loop pointing 1 index ahead 46 | for (int j = i + 1; j < inputArray.length; j++) { 47 | 48 | // Checking elements 49 | int temp; 50 | if (inputArray[j] < inputArray[i]) { 51 | 52 | // Swapping 53 | temp = inputArray[i]; 54 | inputArray[i] = inputArray[j]; 55 | inputArray[j] = temp; 56 | } 57 | } 58 | 59 | } 60 | return inputArray; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /extension/src/test/java/io/retit/opentelemetry/javaagent/extension/common/MetricDemand.java: -------------------------------------------------------------------------------- 1 | package io.retit.opentelemetry.javaagent.extension.common; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import static io.retit.opentelemetry.javaagent.extension.common.ContainerLogMetricAndSpanExtractingTest.METRIC_NAMES; 7 | 8 | public class MetricDemand { 9 | public String metricName; 10 | public Double metricValue; 11 | 12 | public static List extractMetricValuesFromLog(String logMessage) { 13 | 14 | List demands = new ArrayList<>(); 15 | String valueAttributeInLog = "value="; 16 | String sampleApplicationName = "io.retit.opentelemetry.SampleApplication"; 17 | 18 | String emissionMetricNotTransactionRelated = "io.retit.emissions"; 19 | 20 | for (String key : METRIC_NAMES) { 21 | if (logMessage.contains(key)) { 22 | String dataForCurrentMetric = logMessage.substring(logMessage.indexOf(key) + 1); 23 | 24 | if (!key.startsWith(emissionMetricNotTransactionRelated) && dataForCurrentMetric.contains(sampleApplicationName)) { 25 | dataForCurrentMetric = dataForCurrentMetric.substring(dataForCurrentMetric.indexOf(sampleApplicationName)); 26 | } 27 | 28 | if (dataForCurrentMetric.indexOf(valueAttributeInLog) != -1) { 29 | 30 | MetricDemand metricDemand = new MetricDemand(); 31 | int valueIndex = dataForCurrentMetric.indexOf(valueAttributeInLog); 32 | 33 | String valueString = dataForCurrentMetric.substring(valueIndex + valueAttributeInLog.length(), dataForCurrentMetric.indexOf(",", valueIndex)); 34 | double value = Double.parseDouble(valueString); 35 | 36 | metricDemand.metricName = key; 37 | metricDemand.metricValue = value; 38 | demands.add(metricDemand); 39 | } 40 | 41 | } 42 | 43 | } 44 | return demands; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | This directory contains several examples on how to use the OpenTelemetry extension for different application types. You will find details on how to use the examples in the corresponding readme files: 2 | 3 | - A simple [JDK8-based application](simple-jdk8-application/README.md) 4 | - A simple [JDK21-based application](simple-jdk21-application/README.md) 5 | - A [Quarkus-based REST service](quarkus-rest-service/README.md) 6 | - A [Spring-based REST service](spring-rest-service/README.md) 7 | 8 | # Starting OpenTelemetry Backends for the Example Applications 9 | 10 | For all application types, we are publishing the metric and span data to OpenTelemetry compatible backends ([OpenTelemetry Collector](https://github.com/open-telemetry/opentelemetry-collector/tree/main), [Prometheus](https://prometheus.io/), [Grafana](https://grafana.com/) and optionally [Jaeger](https://www.jaegertracing.io/)) as shown in the following architectural overview: 11 | 12 | ![demo_architecture.png](../img/demo_architecture.png) 13 | 14 | These backends can be started using the following command: 15 | 16 | ```bash 17 | docker compose -f ./docker/docker-compose.yml up -d 18 | ``` 19 | 20 | This will start an [OpenTelemetry Collector](https://github.com/open-telemetry/opentelemetry-collector/tree/main) to which the metric and trace data is being sent. Furthermore, it starts [Prometheus](https://prometheus.io/) instance to store the metric data and a [Grafana](https://grafana.com/) instance to visualize the metrics stored in Prometheus. Within the Grafana instance, you will find different [pre-configured dashboards](https://github.com/RETIT/opentelemetry-javaagent-extension/tree/main/examples/docker/grafana/provisioning/dashboards) that you use to display the data of the different application types. You can open the Grafana dashboads using the following URL: 21 | 22 | http://localhost:3000/grafana/dashboards 23 | 24 | You can optionally also start a [Jaeger](https://www.jaegertracing.io/) instance by commenting out the corresponding section in the docker compose file to visualize the span attributes. 25 | 26 | -------------------------------------------------------------------------------- /extension/src/main/java/io/retit/opentelemetry/javaagent/extension/resources/common/IResourceDemandDataCollector.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RETIT GmbH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.retit.opentelemetry.javaagent.extension.resources.common; 18 | 19 | /** 20 | * Interface which defines methods for retrieving resource demands. 21 | */ 22 | public interface IResourceDemandDataCollector { 23 | 24 | /** 25 | * get the number of currently allocated bytes for this thread. 26 | * 27 | * @return the number of bytes allocated 28 | */ 29 | long getCurrentThreadAllocatedBytes(); 30 | 31 | /** 32 | * get the current thread's CPU time. 33 | * 34 | * @return the current CPU time 35 | */ 36 | long getCurrentThreadCpuTime(); 37 | 38 | /** 39 | * get the amount of bytes read and written to the storage by current thread. 40 | * 41 | * @return an array were the first element depicts the amount of bytes read from the storage and 42 | * the second element depicts the amount of bytes written to the storage. 43 | */ 44 | long[] getDiskBytesReadAndWritten(); 45 | 46 | /** 47 | * get the amount of bytes read and written to the network by current thread. 48 | * 49 | * @return an array were the first element depicts the amount of bytes read from the network and 50 | * the second element depicts the amount of bytes written to the network. 51 | */ 52 | long[] getNetworkBytesReadAndWritten(); 53 | } 54 | -------------------------------------------------------------------------------- /extension/src/main/java/io/retit/opentelemetry/javaagent/extension/resources/jvm/HotSpotDataCollector.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RETIT GmbH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.retit.opentelemetry.javaagent.extension.resources.jvm; 18 | 19 | import io.retit.opentelemetry.javaagent.extension.resources.common.CommonResourceDemandDataCollector; 20 | 21 | /** 22 | * A JVM data collector used for both Oracle's HotSpot as well as OpenJDK JVMs. 23 | */ 24 | public class HotSpotDataCollector extends CommonResourceDemandDataCollector { 25 | 26 | @Override 27 | protected long getPlatformSpecificThreadCpuTime() { 28 | return 0; 29 | } 30 | 31 | @Override 32 | public long getCurrentThreadAllocatedBytes() { 33 | long bytes = -1; 34 | if (getThreadMXBean() instanceof com.sun.management.ThreadMXBean) { 35 | com.sun.management.ThreadMXBean sunThreadMXBean = (com.sun.management.ThreadMXBean) getThreadMXBean(); 36 | // Returns an approximation of the total amount of memory, in bytes, allocated in heap memory for the thread of the specified ID. 37 | bytes = sunThreadMXBean.getThreadAllocatedBytes(Thread.currentThread().getId()); 38 | } 39 | return bytes; 40 | } 41 | 42 | @Override 43 | public long[] getDiskBytesReadAndWritten() { 44 | throw new UnsupportedOperationException("Disk IO demand cannot read for HotSpot JVMs"); 45 | } 46 | 47 | @Override 48 | public long[] getNetworkBytesReadAndWritten() { 49 | throw new UnsupportedOperationException("Network IO demand cannot read for HotSpot JVMs"); 50 | } 51 | } -------------------------------------------------------------------------------- /extension/src/main/java/io/retit/opentelemetry/javaagent/extension/energy/MemoryEnergyData.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RETIT GmbH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.retit.opentelemetry.javaagent.extension.energy; 18 | 19 | import io.retit.opentelemetry.javaagent.extension.emissions.CloudCarbonFootprintCoefficients; 20 | import io.retit.opentelemetry.javaagent.extension.emissions.CloudCarbonFootprintData; 21 | 22 | import java.util.logging.Logger; 23 | 24 | /** 25 | * Wrapper class to calculate the energy in kWh required for one GB of main memory. 26 | */ 27 | public class MemoryEnergyData { 28 | 29 | private static final Logger LOGGER = Logger.getLogger(MemoryEnergyData.class.getName()); 30 | 31 | private static final MemoryEnergyData INSTANCE = new MemoryEnergyData(); 32 | 33 | private final double kwhPerGBMinute; 34 | 35 | /** 36 | * Private constructor to prevent instantiation. 37 | * Initializes the {@link CloudCarbonFootprintData} to load necessary configuration. 38 | */ 39 | private MemoryEnergyData() { 40 | // convert to one minute 41 | this.kwhPerGBMinute = CloudCarbonFootprintCoefficients.MEMORY_KWH_PER_GB_HOUR / 60; 42 | 43 | LOGGER.info("Initialized MemoryEnergyData using following data: " + this); 44 | } 45 | 46 | /** 47 | * Provides a global access point to the {@code CpuEmissions} instance, implementing a singleton pattern. 48 | * 49 | * @return The single instance of {@code CpuEmissions}. 50 | */ 51 | public static MemoryEnergyData getInstance() { 52 | return INSTANCE; 53 | } 54 | 55 | public double getKwhPerGBMinute() { 56 | return kwhPerGBMinute; 57 | } 58 | 59 | @Override 60 | public String toString() { 61 | return "MemoryEnergyData{" 62 | + "kwhPerGBMinute=" + kwhPerGBMinute 63 | + '}'; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /extension/src/main/java/io/retit/opentelemetry/javaagent/extension/energy/NetworkEnergyData.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RETIT GmbH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.retit.opentelemetry.javaagent.extension.energy; 18 | 19 | import io.retit.opentelemetry.javaagent.extension.emissions.CloudCarbonFootprintCoefficients; 20 | import io.retit.opentelemetry.javaagent.extension.emissions.CloudCarbonFootprintData; 21 | 22 | import java.util.logging.Logger; 23 | 24 | /** 25 | * Wrapper class to calculate the energy in kWh required to process one GB over the network. 26 | */ 27 | public class NetworkEnergyData { 28 | 29 | private static final Logger LOGGER = Logger.getLogger(NetworkEnergyData.class.getName()); 30 | 31 | private static final NetworkEnergyData INSTANCE = new NetworkEnergyData(); 32 | 33 | private final double kwhPerGBMinute; 34 | 35 | /** 36 | * Private constructor to prevent instantiation. 37 | * Initializes the {@link CloudCarbonFootprintData} to load necessary configuration. 38 | */ 39 | private NetworkEnergyData() { 40 | // convert to kWh and then to one minute 41 | 42 | this.kwhPerGBMinute = CloudCarbonFootprintCoefficients.NETWORK_KWH_PER_GB_HOUR / 60; 43 | 44 | LOGGER.info("Initialized NetworkEnergyData using following data: " + this); 45 | } 46 | 47 | /** 48 | * Provides a global access point to the {@code CpuEmissions} instance, implementing a singleton pattern. 49 | * 50 | * @return The single instance of {@code CpuEmissions}. 51 | */ 52 | public static NetworkEnergyData getInstance() { 53 | return INSTANCE; 54 | } 55 | 56 | public double getKwhPerGBMinute() { 57 | return kwhPerGBMinute; 58 | } 59 | 60 | @Override 61 | public String toString() { 62 | return "NetworkEnergyData{" 63 | + "kwhPerGBMinute=" + kwhPerGBMinute 64 | + '}'; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /examples/quarkus-rest-service/src/main/java/io/retit/opentelemetry/quarkus/TestService.java: -------------------------------------------------------------------------------- 1 | package io.retit.opentelemetry.quarkus; 2 | 3 | import jakarta.enterprise.context.ApplicationScoped; 4 | import jakarta.inject.Inject; 5 | 6 | import java.io.IOException; 7 | import java.nio.charset.StandardCharsets; 8 | import java.nio.file.Files; 9 | import java.nio.file.Path; 10 | import java.util.Arrays; 11 | import java.util.concurrent.ThreadLocalRandom; 12 | 13 | /** 14 | * Example service that does some very complex processing. 15 | */ 16 | @ApplicationScoped 17 | public class TestService { 18 | 19 | @Inject 20 | private ResourceDemandMeasurementService resourceDemandMeasurementService; 21 | 22 | public String veryComplexBusinessFunction(final int size, final String httpMethod) throws InterruptedException, IOException { 23 | ResourceDemandMeasurementService.Measurement measurement = resourceDemandMeasurementService.measure(); 24 | Path tempFile = Files.createTempFile("sampleapplication", "veryComplexBusinessFunction"); 25 | 26 | int[] data = naiveSortingWithONSquareComplexity(generateRandomInputArray(size)); 27 | 28 | Files.write(tempFile, String.valueOf(data).getBytes(StandardCharsets.UTF_8)); 29 | 30 | Files.delete(tempFile); 31 | resourceDemandMeasurementService.measureAndPublishMetrics(measurement, httpMethod); 32 | return String.valueOf(Arrays.stream(data).sum()); 33 | } 34 | 35 | private static int[] generateRandomInputArray(final int size) throws InterruptedException { 36 | int array[] = new int[size]; 37 | 38 | for (int i = 0; i < size; i++) { 39 | array[i] = ThreadLocalRandom.current().nextInt(0, size * 10); 40 | } 41 | 42 | Thread.sleep(16); 43 | return array; 44 | } 45 | 46 | // O(n²) 47 | private static int[] naiveSortingWithONSquareComplexity(final int... inputArray) { 48 | // Outer loop 49 | for (int i = 0; i < inputArray.length; i++) { 50 | 51 | // Inner nested loop pointing 1 index ahead 52 | for (int j = i + 1; j < inputArray.length; j++) { 53 | 54 | // Checking elements 55 | int temp; 56 | if (inputArray[j] < inputArray[i]) { 57 | 58 | // Swapping 59 | temp = inputArray[i]; 60 | inputArray[i] = inputArray[j]; 61 | inputArray[j] = temp; 62 | } 63 | } 64 | 65 | } 66 | return inputArray; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /extension/src/main/java/io/retit/opentelemetry/javaagent/extension/resources/jvm/JavaAgentGCHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RETIT GmbH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.retit.opentelemetry.javaagent.extension.resources.jvm; 18 | 19 | import javax.management.ListenerNotFoundException; 20 | import javax.management.NotificationEmitter; 21 | import java.lang.management.GarbageCollectorMXBean; 22 | import java.lang.management.ManagementFactory; 23 | import java.util.logging.Level; 24 | import java.util.logging.Logger; 25 | 26 | /** 27 | * Utility class to register the {@link JavaAgentGCNotificationListener}. 28 | */ 29 | public class JavaAgentGCHandler { 30 | 31 | private static final Logger LOGGER = Logger.getLogger(JavaAgentGCHandler.class.getName()); 32 | 33 | private static final JavaAgentGCNotificationListener JAVA_AGENT_GC_NOTIFICATION_LISTENER = new JavaAgentGCNotificationListener(); 34 | 35 | /** 36 | * Registers the {@link JavaAgentGCNotificationListener} as lister to the {@link GarbageCollectorMXBean}. 37 | */ 38 | public static void addJavaAgentGCListener() { 39 | for (GarbageCollectorMXBean garbageCollectorMXBean : ManagementFactory.getGarbageCollectorMXBeans()) { 40 | if (garbageCollectorMXBean instanceof NotificationEmitter) { 41 | NotificationEmitter notificationEmitter = (NotificationEmitter) garbageCollectorMXBean; 42 | try { 43 | notificationEmitter.removeNotificationListener(JAVA_AGENT_GC_NOTIFICATION_LISTENER); 44 | } catch (ListenerNotFoundException ex) { 45 | LOGGER.log(Level.FINE, "Error removing listener", ex); 46 | } 47 | notificationEmitter.addNotificationListener(JAVA_AGENT_GC_NOTIFICATION_LISTENER, null, null); 48 | LOGGER.log(Level.INFO, "Added JavaAgent GC Listener to {0}", garbageCollectorMXBean.getObjectName()); 49 | } else { 50 | LOGGER.log(Level.WARNING, 51 | "Could not register RETIT APM GC Lister as GC MXBean does not implement NotificationEmitter: {0}", 52 | garbageCollectorMXBean.getObjectName()); 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /examples/docker/docker-compose.yml: -------------------------------------------------------------------------------- 1 | x-default-logging: &logging 2 | driver: "json-file" 3 | options: 4 | max-size: "5m" 5 | max-file: "2" 6 | 7 | networks: 8 | default: 9 | name: apm-metrics 10 | driver: bridge 11 | 12 | services: 13 | # Grafana 14 | grafana: 15 | image: grafana/grafana:11.2.0 16 | container_name: grafana 17 | deploy: 18 | resources: 19 | limits: 20 | memory: 100M 21 | volumes: 22 | - ./grafana/grafana.ini:/etc/grafana/grafana.ini 23 | - ./grafana/provisioning/datasources/:/etc/grafana/provisioning/datasources/ 24 | - ./grafana/provisioning/dashboards/:/etc/grafana/provisioning/dashboards/ 25 | ports: 26 | - "3000:3000" 27 | logging: *logging 28 | 29 | # OpenTelemetry Collector 30 | otelcol: 31 | image: otel/opentelemetry-collector-contrib:0.107.0 32 | container_name: otel-col 33 | deploy: 34 | resources: 35 | limits: 36 | memory: 125M 37 | restart: unless-stopped 38 | command: [ "--config=/etc/otelcol-config.yml" ] 39 | volumes: 40 | - ./otelcollector/otelcol-config.yml:/etc/otelcol-config.yml 41 | ports: 42 | - "4317:4317" # OTLP over gRPC receiver 43 | - "4318:4318" # OTLP over HTTP receiver 44 | - "9464:9464" # Prometheus exporter 45 | - "8888:8888" # metrics endpoint 46 | logging: *logging 47 | 48 | # Prometheus 49 | prometheus: 50 | image: quay.io/prometheus/prometheus:v2.53.2 51 | container_name: prometheus 52 | command: 53 | - --web.console.templates=/etc/prometheus/consoles 54 | - --web.console.libraries=/etc/prometheus/console_libraries 55 | - --storage.tsdb.retention.time=1h 56 | - --config.file=/etc/prometheus/prometheus-config.yaml 57 | - --storage.tsdb.path=/prometheus 58 | - --web.enable-lifecycle 59 | - --web.route-prefix=/ 60 | - --enable-feature=exemplar-storage 61 | volumes: 62 | - ./prometheus/prometheus-config.yaml:/etc/prometheus/prometheus-config.yaml 63 | deploy: 64 | resources: 65 | limits: 66 | memory: 300M 67 | ports: 68 | - "9090:9090" 69 | logging: *logging 70 | 71 | #jaeger: 72 | # image: jaegertracing/all-in-one:1.60.0 73 | # container_name: jaeger 74 | # command: 75 | # - "--memory.max-traces" 76 | # - "10000" 77 | # - "--query.base-path" 78 | # - "/jaeger/ui" 79 | # deploy: 80 | # resources: 81 | # limits: 82 | # memory: 300M 83 | # restart: unless-stopped 84 | # ports: 85 | # - "16686:16686" # Jaeger UI 86 | # - "4317" # OTLP gRPC default port 87 | # environment: 88 | # - COLLECTOR_OTLP_ENABLED=true 89 | # - METRICS_STORAGE_TYPE=prometheus 90 | # logging: *logging 91 | -------------------------------------------------------------------------------- /examples/simple-jdk8-application/README.md: -------------------------------------------------------------------------------- 1 | To see the extension in action for a very simple java application we will create the setup depicted in the following image. We will setup an Application that will be instrumented using the OpenTelemetry Java-Agent including our extension. This application will publish resource demand and carbon emission metrics. These metrics will be received by an OpenTelemetry collector which transforms the metrics into a format compatible to Prometheus. Prometheus will fetch the metrics from the collector and store them. Using Grafana, the metrics will be visualized in a preconfigured dashboard. 2 | 3 | ## Please note before you start with the JDK8 sample application: 4 | 5 | 1. You need to build the whole project before you continue, for details on how to build the project, take a look at the top level [README.md](../../README.md#building-the-project). 6 | 2. Afterwards, you need to start OpenTelemetry compatible tracing and metrics backends as explained in the [examples README.md](../README.md#starting-opentelemetry-backends-for-the-example-applications) 7 | 8 | 9 | ![demo_architecture.png](../../img/demo_architecture.png) 10 | 11 | Once the preconditions ([building the project and starting the OpenTelemetry backends](#please-note-before-you-start-with-the-jdk8-sample-application)) are completed, the sample Application can be started with the OpenTelemetry Java agent attached from the current directory as follows. 12 | ```bash 13 | java -javaagent:./target/jib/opentelemetry-javaagent-all.jar \ 14 | -Dotel.service.name=simple-jdk8-application \ 15 | -Dotel.logs.exporter=logging \ 16 | -Dotel.javaagent.extensions=./target/jib/io.retit.opentelemetry.javaagent.extension.jar \ 17 | -Dio.retit.emissions.cloud.provider=aws \ 18 | -Dio.retit.emissions.cloud.provider.region=af-south-1 \ 19 | -Dio.retit.emissions.cloud.provider.instance.type=a1.medium \ 20 | -DRUN_MODE=continuously \ 21 | -jar ./target/simple-jdk8-application.jar 22 | ``` 23 | 24 | This application will run until you stop it and generate data. While it is generating data, you can look at the data in the backends. The easiest way is to check out the [Grafana dashboard](http://localhost:3000/grafana/dashboards) here: 25 | 26 | http://localhost:3000/grafana/dashboards 27 | 28 | After some time you can see the data produced by this application in the following dashboard. As an example the CPU and memory demands are shown as they are supported on most plattforms as well as the Emission Calculation Factors. Furthermore, we have integrated a [Software Carbon Intensity](https://sci.greensoftware.foundation/) calculation for each transaction based on this data. This calculation is based on our work presented at the [Symposium on Software Performance 2024](https://fb-swt.gi.de/fileadmin/FB/SWT/Softwaretechnik-Trends/Verzeichnis/Band_44_Heft_4/SSP24_16_camera-ready_5255.pdf). 29 | 30 | ![dashboard.png](../../img/jdk8_dashboard.png) -------------------------------------------------------------------------------- /examples/simple-jdk21-application/README.md: -------------------------------------------------------------------------------- 1 | To see the extension in action for a very simple java application we will create the setup depicted in the following image. We will setup an Application that will be instrumented using the OpenTelemetry Java-Agent including our extension. This application will publish resource demand and carbon emission metrics. These metrics will be received by an OpenTelemetry collector which transforms the metrics into a format compatible to Prometheus. Prometheus will fetch the metrics from the collector and store them. Using Grafana, the metrics will be visualized in a preconfigured dashboard. 2 | 3 | ## Please note before you start with the JDK21 sample application: 4 | 5 | 1. You need to build the whole project before you continue, for details on how to build the project, take a look at the top level [README.md](../../README.md#building-the-project). 6 | 2. Afterwards, you need to start OpenTelemetry compatible tracing and metrics backends as explained in the [examples README.md](../README.md#starting-opentelemetry-backends-for-the-example-applications) 7 | 8 | 9 | ![demo_architecture.png](../../img/demo_architecture.png) 10 | 11 | Once the preconditions ([building the project and starting the OpenTelemetry backends](#please-note-before-you-start-with-the-jdk8-sample-application)) are completed, the sample Application can be started with the OpenTelemetry Java agent attached from the current directory as follows. 12 | ```bash 13 | java -javaagent:./target/jib/opentelemetry-javaagent-all.jar \ 14 | -Dotel.service.name=simple-jdk21-application \ 15 | -Dotel.logs.exporter=logging \ 16 | -Dotel.javaagent.extensions=./target/jib/io.retit.opentelemetry.javaagent.extension.jar \ 17 | -Dio.retit.emissions.cloud.provider=aws \ 18 | -Dio.retit.emissions.cloud.provider.region=af-south-1 \ 19 | -Dio.retit.emissions.cloud.provider.instance.type=a1.medium \ 20 | -DRUN_MODE=continuously \ 21 | -jar ./target/simple-jdk21-application.jar 22 | ``` 23 | 24 | This application will run until you stop it and generate data. While it is generating data, you can look at the data in the backends. The easiest way is to check out the [Grafana dashboard](http://localhost:3000/grafana/dashboards) here: 25 | 26 | http://localhost:3000/grafana/dashboards 27 | 28 | After some time you can see the data produced by this application in the following dashboard. As an example the CPU and memory demands are shown as they are supported on most plattforms as well as the Emission Calculation Factors. Furthermore, we have integrated a [Software Carbon Intensity](https://sci.greensoftware.foundation/) calculation for each transaction based on this data. This calculation is based on our work presented at the [Symposium on Software Performance 2024](https://fb-swt.gi.de/fileadmin/FB/SWT/Softwaretechnik-Trends/Verzeichnis/Band_44_Heft_4/SSP24_16_camera-ready_5255.pdf). 29 | 30 | ![dashboard.png](../../img/jdk21_dashboard.png) -------------------------------------------------------------------------------- /.github/workflows/maven.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-maven 3 | 4 | # This workflow uses actions that are not certified by GitHub. 5 | # They are provided by a third-party and are governed by 6 | # separate terms of service, privacy policy, and support 7 | # documentation. 8 | 9 | name: Java CI with Maven 10 | 11 | on: 12 | push: 13 | branches: [ "main" ] 14 | pull_request: 15 | branches: [ "main" ] 16 | 17 | permissions: 18 | read-all 19 | 20 | jobs: 21 | build: 22 | runs-on: ubuntu-latest 23 | 24 | permissions: 25 | pull-requests: write 26 | 27 | steps: 28 | - uses: actions/checkout@v4 29 | - name: Start Measurement 30 | uses: green-coding-solutions/eco-ci-energy-estimation@v5 31 | with: 32 | task: start-measurement 33 | co2-calculation-method: constant 34 | co2-grid-intensity-constant: 334 # German average in 2024 35 | send-data: false 36 | continue-on-error: true 37 | - name: Set up JDK 21 38 | uses: actions/setup-java@v3 39 | with: 40 | java-version: '21' 41 | distribution: 'temurin' 42 | cache: maven 43 | - name: Compile and run Checkstyle, PMD and Spotbugs checks 44 | run: mvn -B checkstyle:check pmd:check compile spotbugs:check --file pom.xml 45 | - name: Package Extension and create Docker Container for Sample Application 46 | run: mvn -B package --file pom.xml 47 | - name: Run Integration Test 48 | run: mvn -B test -Dtest=JDK8JavaAgentExtensionIT,JDK21JavaAgentExtensionIT,SpringWithExternalOtelAgentAndExtensionIT,QuarkusWithExternalOtelAgentAndExtensionIT --file ./extension/pom.xml 49 | - name: Get Energy Results 50 | uses: green-coding-solutions/eco-ci-energy-estimation@v5 51 | with: 52 | task: get-measurement 53 | pr-comment: true 54 | co2-calculation-method: constant 55 | co2-grid-intensity-constant: 334 # German average in 2024 56 | send-data: false 57 | continue-on-error: true 58 | - name: Show Energy Results 59 | uses: green-coding-solutions/eco-ci-energy-estimation@v5 60 | with: 61 | task: display-results 62 | pr-comment: true 63 | co2-calculation-method: constant 64 | co2-grid-intensity-constant: 334 # German average in 2024 65 | send-data: false 66 | continue-on-error: true 67 | # Optional: Uploads the full dependency graph to GitHub to improve the quality of Dependabot alerts this repository can receive 68 | #- name: Update dependency graph 69 | # uses: advanced-security/maven-dependency-submission-action@571e99aab1055c2e71a1e2309b9691de18d6b7d6 70 | 71 | -------------------------------------------------------------------------------- /extension/src/main/java/io/retit/opentelemetry/javaagent/extension/energy/StorageEnergyData.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RETIT GmbH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.retit.opentelemetry.javaagent.extension.energy; 18 | 19 | import io.retit.opentelemetry.javaagent.extension.commons.Constants; 20 | import io.retit.opentelemetry.javaagent.extension.commons.InstanceConfiguration; 21 | import io.retit.opentelemetry.javaagent.extension.emissions.CloudCarbonFootprintCoefficients; 22 | import io.retit.opentelemetry.javaagent.extension.emissions.CloudCarbonFootprintData; 23 | 24 | import java.util.logging.Logger; 25 | 26 | /** 27 | * Wrapper class to calculate the energy in kWh required to store one GB on storage (e.g., HDD/SSD). 28 | */ 29 | public class StorageEnergyData { 30 | 31 | private static final Logger LOGGER = Logger.getLogger(StorageEnergyData.class.getName()); 32 | 33 | private static final StorageEnergyData INSTANCE = new StorageEnergyData(); 34 | 35 | private final double kwhPerGBMinute; 36 | 37 | /** 38 | * Private constructor to prevent instantiation. 39 | * Initializes the {@link CloudCarbonFootprintData} to load necessary configuration. 40 | */ 41 | private StorageEnergyData() { 42 | // convert to kWh and then to one minute 43 | this.kwhPerGBMinute = (Constants.RETIT_EMISSIONS_STORAGE_TYPE_CONFIGURATION_PROPERTY_VALUE_SSD 44 | .equalsIgnoreCase(InstanceConfiguration.getStorageType()) 45 | ? CloudCarbonFootprintCoefficients.STORAGE_ENERGY_CONSUMPTION_WH_SSD_PER_TB_HOUR / 1_000 46 | : CloudCarbonFootprintCoefficients.STORAGE_ENERGY_CONSUMPTION_WH_HDD_PER_TB_HOUR / 1_000) / 60.0; 47 | 48 | LOGGER.info("Initialized StorageEnergyData using following data: " + this); 49 | } 50 | 51 | /** 52 | * Provides a global access point to the {@code CpuEmissions} instance, implementing a singleton pattern. 53 | * 54 | * @return The single instance of {@code CpuEmissions}. 55 | */ 56 | public static StorageEnergyData getInstance() { 57 | return INSTANCE; 58 | } 59 | 60 | public double getKwhPerGBMinute() { 61 | return kwhPerGBMinute; 62 | } 63 | 64 | @Override 65 | public String toString() { 66 | return "StorageEnergyData{" 67 | + "kwhPerGBMinute=" + kwhPerGBMinute 68 | + '}'; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /extension/src/main/java/io/retit/opentelemetry/javaagent/extension/emissions/embodied/EmbodiedEmissions.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RETIT GmbH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.retit.opentelemetry.javaagent.extension.emissions.embodied; 18 | 19 | import io.retit.opentelemetry.javaagent.extension.commons.Constants; 20 | import io.retit.opentelemetry.javaagent.extension.commons.InstanceConfiguration; 21 | import io.retit.opentelemetry.javaagent.extension.emissions.CloudCarbonFootprintCoefficients; 22 | import io.retit.opentelemetry.javaagent.extension.emissions.CloudCarbonFootprintData; 23 | 24 | /** 25 | * The {@code EmbodiedEmissions} class calculates the embodied carbon emissions associated with the usage of CPU resources. 26 | * Embodied emissions refer to the carbon footprint incurred during the manufacturing and provisioning of computing resources. 27 | */ 28 | public class EmbodiedEmissions { 29 | 30 | private static final EmbodiedEmissions INSTANCE = new EmbodiedEmissions(); 31 | private final CloudCarbonFootprintData configLoader; 32 | 33 | /** 34 | * Private constructor to prevent direct instantiation. 35 | * Initializes the {@link CloudCarbonFootprintData} to load necessary configuration for emissions calculations. 36 | */ 37 | private EmbodiedEmissions() { 38 | configLoader = CloudCarbonFootprintData.getConfigInstance(); 39 | } 40 | 41 | /** 42 | * Provides a global access point to the {@code EmbodiedEmissions} instance, implementing a singleton pattern. 43 | * This ensures that only one instance of the class is created and used throughout the application. 44 | * 45 | * @return The single instance of {@code EmbodiedEmissions}. 46 | */ 47 | public static EmbodiedEmissions getInstance() { 48 | return INSTANCE; 49 | } 50 | 51 | /** 52 | * Calculates the embodied carbon emissions in grams in minutes. 53 | * This method estimates the emissions based on the cloud instance's specifications and the total embodied emissions data. 54 | * 55 | * @return The calculated embodied carbon emissions in milligrams. 56 | */ 57 | public double calculateEmbodiedEmissionsInMilliGramPerMinute() { 58 | if (InstanceConfiguration.getCloudProviderInstanceType() == null || Constants.RETIT_VALUE_NOT_SET.equals(InstanceConfiguration.getCloudProviderInstanceType())) { 59 | return 0; 60 | } else { 61 | return configLoader.getCloudInstanceDetails().getTotalEmbodiedEmissions() * (CloudCarbonFootprintCoefficients.TOTAL_EMBODIED_EMISSIONS_TO_GRAMS_PER_HOUR / 60) 62 | * (configLoader.getCloudInstanceDetails().getInstanceVCpuCount() / configLoader.getCloudInstanceDetails().getPlatformTotalVCpuCount()) * 1_000; 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /extension/src/main/java/io/retit/opentelemetry/javaagent/extension/OpenTelemetryCustomizer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RETIT GmbH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.retit.opentelemetry.javaagent.extension; 18 | 19 | import com.google.auto.service.AutoService; 20 | import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizer; 21 | import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider; 22 | import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; 23 | import io.opentelemetry.sdk.trace.SdkTracerProviderBuilder; 24 | import io.opentelemetry.sdk.trace.SpanProcessor; 25 | import io.retit.opentelemetry.javaagent.extension.commons.InstanceConfiguration; 26 | import io.retit.opentelemetry.javaagent.extension.processor.RETITSpanProcessor; 27 | import io.retit.opentelemetry.javaagent.extension.resources.jvm.JavaAgentGCHandler; 28 | 29 | import java.util.logging.Logger; 30 | 31 | /** 32 | * The OpenTelemetryCustomizer is responsible for adding the resource demand carbon emission extensions to the Otel agent. 33 | */ 34 | @AutoService(AutoConfigurationCustomizerProvider.class) 35 | public class OpenTelemetryCustomizer implements AutoConfigurationCustomizerProvider { 36 | 37 | private static final Logger LOGGER = Logger.getLogger(OpenTelemetryCustomizer.class.getName()); 38 | 39 | @Override 40 | public void customize(final AutoConfigurationCustomizer autoConfiguration) { 41 | if (InstanceConfiguration.isLogGCEventDefaultTrue()) { 42 | JavaAgentGCHandler.addJavaAgentGCListener(); 43 | } 44 | 45 | // TracerProviderCustomizer call must be wrapped inside a customizer call which happens after it. 46 | // Otherwise we would not get the autoconfigured SpanExporter 47 | autoConfiguration.addSpanExporterCustomizer((delegate, unused) -> { 48 | autoConfiguration.addTracerProviderCustomizer(this::configureSdkTracerProvider); 49 | return delegate; 50 | }); 51 | } 52 | 53 | /** 54 | * Prepares the list of {@link SpanProcessor} in the {@link SdkTracerProviderBuilder} to include a {@link RETITSpanProcessor}. 55 | * 56 | * @param tracerProvider - preconfigured {@link SdkTracerProviderBuilder} from auto-configuration 57 | * @param config - preconfigured {@link ConfigProperties} from auto-configuration 58 | * @return {@link SdkTracerProviderBuilder} with adjusted SdkTracerProviderBuilder 59 | */ 60 | private SdkTracerProviderBuilder configureSdkTracerProvider( 61 | final SdkTracerProviderBuilder tracerProvider, final ConfigProperties config) { 62 | 63 | LOGGER.info("Adding RETITSpanProcessor to tracerProvider " + tracerProvider + "with config " + config); 64 | return tracerProvider 65 | .addSpanProcessor(new RETITSpanProcessor()); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /extension/src/main/java/io/retit/opentelemetry/javaagent/extension/commons/CSVParser.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RETIT GmbH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.retit.opentelemetry.javaagent.extension.commons; 18 | 19 | import java.io.BufferedReader; 20 | import java.io.IOException; 21 | import java.io.InputStreamReader; 22 | import java.nio.charset.StandardCharsets; 23 | import java.util.ArrayList; 24 | import java.util.List; 25 | import java.util.logging.Logger; 26 | 27 | /** 28 | * Helper class to parse values from CSV files. 29 | */ 30 | public class CSVParser { 31 | 32 | private static final Logger LOGGER = Logger.getLogger(CSVParser.class.getName()); 33 | private static final char QUOTE_CHAR = '\"'; 34 | 35 | /** 36 | * Utility method for reading a CSV file. 37 | * 38 | * @param fileName - the CSV file to read 39 | * @return a list of String[] representing the lines of the CSV file. 40 | */ 41 | public static List readAllCSVLinesExceptHeader(final String fileName) { 42 | List csvLinesWithoutHeader = new ArrayList<>(); 43 | 44 | try (BufferedReader reader = new BufferedReader(new 45 | InputStreamReader(CSVParser.class.getResourceAsStream(fileName), StandardCharsets.UTF_8))) { 46 | 47 | String line = reader.readLine(); 48 | boolean firstLine = true; 49 | while (line != null) { 50 | if (!firstLine) { 51 | String[] fields = parseCSVLine(line); 52 | csvLinesWithoutHeader.add(fields); 53 | } 54 | line = reader.readLine(); 55 | if (firstLine) { 56 | firstLine = false; 57 | } 58 | } 59 | } catch (IOException e) { 60 | LOGGER.warning("Failed to load instance details from CSV file"); 61 | } 62 | 63 | return csvLinesWithoutHeader; 64 | } 65 | 66 | /** 67 | * Parses a single line of a CSV file and ignores fields in quotes. 68 | * 69 | * @param line - the line to parse 70 | * @return - the CSV attributes of the line. 71 | */ 72 | private static String[] parseCSVLine(final String line) { 73 | boolean inQuotes = false; 74 | StringBuilder field = new StringBuilder(); 75 | List fields = new ArrayList<>(); 76 | for (char c : line.toCharArray()) { 77 | if (c == QUOTE_CHAR) { 78 | inQuotes = !inQuotes; 79 | } else if (c == ',' && !inQuotes) { 80 | fields.add(field.toString().trim()); 81 | field = new StringBuilder(); 82 | } else { 83 | field.append(c); 84 | } 85 | } 86 | fields.add(field.toString().trim()); 87 | return fields.toArray(new String[0]); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /examples/quarkus-rest-service/src/main/java/io/retit/opentelemetry/quarkus/OpenTelemetryService.java: -------------------------------------------------------------------------------- 1 | package io.retit.opentelemetry.quarkus; 2 | 3 | import io.opentelemetry.api.GlobalOpenTelemetry; 4 | import io.opentelemetry.api.common.Attributes; 5 | import io.opentelemetry.api.metrics.LongCounter; 6 | import io.opentelemetry.api.metrics.Meter; 7 | import jakarta.enterprise.context.ApplicationScoped; 8 | 9 | /** 10 | * This service is used to publish resource demand and CO2 emission data as OpenTelemetry metrics. 11 | */ 12 | @ApplicationScoped 13 | public class OpenTelemetryService { 14 | 15 | /** 16 | * This method publishes the CPU demand as OpenTelemetry metric. 17 | * 18 | * @param cpuTimeInMS - the CPU time in milliseconds. 19 | * @param attributes - the attributes to be published along with the CPU time metric. 20 | */ 21 | public void publishCpuTimeMetric(final long cpuTimeInMS, final Attributes attributes) { 22 | // Record data 23 | 24 | getLongCounter("cpu_demand", 25 | "CPU Demand Metric", "ms") 26 | .add(cpuTimeInMS, attributes); 27 | } 28 | 29 | /** 30 | * This method publishes the memory demand as OpenTelemetry metric. 31 | * 32 | * @param memoryDemandInKByte - the memory demand in kilobytes. 33 | * @param attributes - the attributes to be published along with the memory demand metric. 34 | */ 35 | public void publishMemoryDemandMetric(final long memoryDemandInKByte, final Attributes attributes) { 36 | // Record data 37 | getLongCounter("memory_demand", 38 | "Memory Demand Metric", "kByte") 39 | .add(memoryDemandInKByte * 1024, attributes); 40 | } 41 | 42 | /** 43 | * This method publishes the call count of specific service as OpenTelemetry metric. Each 44 | * individual call is tracked as a single call, therefore there is no call count parameter. 45 | * 46 | * @param attributes - the attributes to be published along with the memory demand metric. 47 | */ 48 | public void publishCallCountMetric(final Attributes attributes) { 49 | // Record data 50 | getLongCounter("call_count", 51 | "Tracks the number of calls to a service", "1") 52 | .add(1, attributes); 53 | } 54 | 55 | /** 56 | * Returns the OpenTelemetry Meter that is required to publish OpenTelemetry metrics. 57 | * 58 | * @return - an OpenTelemetry Meter instance. 59 | */ 60 | private Meter getOpenTelemetryMeter() { 61 | // Gets or creates a named meter instance 62 | return GlobalOpenTelemetry.get().meterBuilder("instrumentation-library-name") 63 | .setInstrumentationVersion("1.0.0") 64 | .build(); 65 | } 66 | 67 | /** 68 | * Creates an OpenTelemetry LongCounter with the provided name, description and unit. 69 | * 70 | * @param counterName - the name of the LongCounter. 71 | * @param description - the description of the LongCounter. 72 | * @param unit - the unit of the LongCounter. 73 | * @return a LongCounter instance. 74 | */ 75 | private LongCounter getLongCounter(final String counterName, final String description, final String unit) { 76 | return getOpenTelemetryMeter() 77 | .counterBuilder(counterName) 78 | .setDescription(description) 79 | .setUnit(unit) 80 | .build(); 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /examples/quarkus-rest-service/src/main/java/io/retit/opentelemetry/quarkus/ResourceDemandMeasurementService.java: -------------------------------------------------------------------------------- 1 | package io.retit.opentelemetry.quarkus; 2 | 3 | import io.opentelemetry.api.common.AttributeKey; 4 | import io.opentelemetry.api.common.Attributes; 5 | import jakarta.enterprise.context.ApplicationScoped; 6 | import jakarta.inject.Inject; 7 | 8 | import java.lang.management.ManagementFactory; 9 | import java.lang.management.ThreadMXBean; 10 | 11 | /** 12 | * This service can be used to measure the CPU and memory resource demands of a service. 13 | */ 14 | @ApplicationScoped 15 | public class ResourceDemandMeasurementService { 16 | 17 | @Inject 18 | private OpenTelemetryService otelService; 19 | 20 | /** 21 | * Data structure to capture the resource measurements. 22 | */ 23 | public static final class Measurement { 24 | long cpuTime; 25 | long bytes; 26 | 27 | public Measurement(final long cpuTime, final long bytes) { 28 | this.cpuTime = cpuTime; 29 | this.bytes = bytes; 30 | } 31 | 32 | public long getCpuTime() { 33 | return cpuTime; 34 | } 35 | 36 | public long getBytes() { 37 | return bytes; 38 | } 39 | } 40 | 41 | /** 42 | * Returns the total CPU time used by the current thread since its creation. 43 | * 44 | * @return - the total CPU time of the current thread in nanoseconds. 45 | */ 46 | protected long getCurrentThreadCpuTime() { 47 | ThreadMXBean mxBean = ManagementFactory.getThreadMXBean(); 48 | return mxBean.getCurrentThreadCpuTime(); 49 | } 50 | 51 | /** 52 | * Returns the total heap allocation by the current thread since its creation. 53 | * 54 | * @return - the total heap allocation of the current thread in bytes. 55 | */ 56 | protected long getCurrentThreadMemoryConsumption() { 57 | ThreadMXBean mxBean = ManagementFactory.getThreadMXBean(); 58 | com.sun.management.ThreadMXBean sunThreadMXBean = (com.sun.management.ThreadMXBean) mxBean; 59 | return sunThreadMXBean.getThreadAllocatedBytes(Thread.currentThread().getId()); 60 | } 61 | 62 | /** 63 | * Measures the CPU time and memory consumption of the current thread. 64 | * 65 | * @return a Measurement object containing the CPU time and memory consumption of the current thread. 66 | */ 67 | public Measurement measure() { 68 | return new Measurement(getCurrentThreadCpuTime(), getCurrentThreadMemoryConsumption()); 69 | } 70 | 71 | /** 72 | * Calculates the CPU and memory consumption of the current thread based on the startMeasurement 73 | * and publishes the results as OpenTelemetry metrics using the attributes HTTP method and apiCall name 74 | * provided as method parameters. 75 | * 76 | * @param startMeasurement - the measurement object collected at the beginning of a service operation. 77 | * @param httpMethod - the HTTP method of the service operation that will be added as metric attribute. 78 | * @return - the attributes published along with the resource demand metrics. 79 | */ 80 | public Attributes measureAndPublishMetrics(final Measurement startMeasurement, final String httpMethod) { 81 | Measurement endMeasurement = measure(); 82 | 83 | Attributes attributes = Attributes.of(AttributeKey.stringKey("httpmethod"), httpMethod); 84 | 85 | otelService.publishCpuTimeMetric((endMeasurement.getCpuTime() - startMeasurement.getCpuTime()) / 1_000_000, attributes); 86 | otelService.publishMemoryDemandMetric((endMeasurement.getBytes() - startMeasurement.getBytes()) / 1_000, attributes); 87 | otelService.publishCallCountMetric(attributes); 88 | 89 | return attributes; 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /extension/src/main/resources/ccf-coefficients/embodied-emissions/coefficients-gcp-embodied-mean.csv: -------------------------------------------------------------------------------- 1 | ,type,total_mean 2 | 0,e2-standard-2,1230.46 3 | 1,e2-standard-4,1230.46 4 | 2,e2-standard-8,1230.46 5 | 3,e2-standard-16,1230.46 6 | 4,e2-standard-32,1230.46 7 | 5,e2-highmem-2,1230.46 8 | 6,e2-highmem-4,1230.46 9 | 7,e2-highmem-8,1230.46 10 | 8,e2-highmem-16,1230.46 11 | 9,e2-highcpu-2,1097.21 12 | 10,e2-highcpu-4,1097.21 13 | 11,e2-highcpu-8,1097.21 14 | 12,e2-highcpu-16,1097.21 15 | 13,e2-highcpu-32,1097.21 16 | 14,e2-micro,1230.46 17 | 15,e2-small,1230.46 18 | 16,e2-medium,1230.46 19 | 17,n2-standard-2,1888.46 20 | 18,n2-standard-4,1888.46 21 | 19,n2-standard-8,1888.46 22 | 20,n2-standard-16,1888.46 23 | 21,n2-standard-32,1888.46 24 | 22,n2-standard-48,1888.46 25 | 23,n2-standard-64,1888.46 26 | 24,n2-standard-80,1888.46 27 | 25,n2-standard-96,1888.46 28 | 26,n2-standard-128,1888.46 29 | 27,n2-highmem-2,2377.04 30 | 28,n2-highmem-4,2377.04 31 | 29,n2-highmem-8,2377.04 32 | 30,n2-highmem-16,2377.04 33 | 31,n2-highmem-32,2377.04 34 | 32,n2-highmem-48,2377.04 35 | 33,n2-highmem-64,2377.04 36 | 34,n2-highmem-80,2377.04 37 | 35,n2-highmem-96,2377.04 38 | 36,n2-highmem-128,2377.04 39 | 37,n2-highcpu-2,1311.04 40 | 38,n2-highcpu-4,1311.04 41 | 39,n2-highcpu-8,1311.04 42 | 40,n2-highcpu-16,1311.04 43 | 41,n2-highcpu-32,1311.04 44 | 42,n2-highcpu-48,1311.04 45 | 43,n2-highcpu-64,1311.04 46 | 44,n2-highcpu-80,1311.04 47 | 45,n2-highcpu-96,1311.04 48 | 46,n2d-standard-2,2321.46 49 | 47,n2d-standard-4,2321.46 50 | 48,n2d-standard-8,2321.46 51 | 49,n2d-standard-16,2321.46 52 | 50,n2d-standard-32,2321.46 53 | 51,n2d-standard-48,2321.46 54 | 52,n2d-standard-64,2321.46 55 | 53,n2d-standard-80,2321.46 56 | 54,n2d-standard-96,2321.46 57 | 55,n2d-standard-128,2321.46 58 | 56,n2d-standard-224,2321.46 59 | 57,n2d-highmem-2,2143.79 60 | 58,n2d-highmem-4,2143.79 61 | 59,n2d-highmem-8,2143.79 62 | 60,n2d-highmem-16,2143.79 63 | 61,n2d-highmem-32,2143.79 64 | 62,n2d-highmem-48,2143.79 65 | 63,n2d-highmem-64,2143.79 66 | 64,n2d-highmem-80,2143.79 67 | 65,n2d-highmem-96,2143.79 68 | 66,n2d-highcpu-2,1388.71 69 | 67,n2d-highcpu-4,1388.71 70 | 68,n2d-highcpu-8,1388.71 71 | 69,n2d-highcpu-16,1388.71 72 | 70,n2d-highcpu-32,1388.71 73 | 71,n2d-highcpu-48,1388.71 74 | 72,n2d-highcpu-64,1388.71 75 | 73,n2d-highcpu-80,1388.71 76 | 74,n2d-highcpu-96,1388.71 77 | 75,n2d-highcpu-128,1388.71 78 | 76,n2d-highcpu-224,1388.71 79 | 77,t2d-standard-1,1310.92 80 | 78,t2d-standard-2,1310.92 81 | 79,t2d-standard-4,1310.92 82 | 80,t2d-standard-8,1310.92 83 | 81,t2d-standard-16,1310.92 84 | 82,t2d-standard-32,1310.92 85 | 83,t2d-standard-48,1310.92 86 | 84,t2d-standard-60,1310.92 87 | 85,n1-standard-1,1677.48 88 | 86,n1-standard-2,1677.48 89 | 87,n1-standard-4,1677.48 90 | 88,n1-standard-8,1677.48 91 | 89,n1-standard-16,1677.48 92 | 90,n1-standard-32,1677.48 93 | 91,n1-standard-64,1677.48 94 | 92,n1-standard-96,1677.48 95 | 93,n1-highmem-2,2043.92 96 | 94,n1-highmem-4,2043.92 97 | 95,n1-highmem-8,2043.92 98 | 96,n1-highmem-16,2043.92 99 | 97,n1-highmem-32,2043.92 100 | 98,n1-highmem-64,2043.92 101 | 99,n1-highmem-96,2043.92 102 | 100,n1-highcpu-2,1297.72 103 | 101,n1-highcpu-4,1297.72 104 | 102,n1-highcpu-8,1297.72 105 | 103,n1-highcpu-16,1297.72 106 | 104,n1-highcpu-32,1297.72 107 | 105,n1-highcpu-64,1297.72 108 | 106,n1-highcpu-96,1297.72 109 | 107,f1-micro,1577.48 110 | 108,g1-small,1577.48 111 | 109,c2-standard-4,1510.92 112 | 110,c2-standard-8,1510.92 113 | 111,c2-standard-16,1510.92 114 | 112,c2-standard-30,1510.92 115 | 113,c2-standard-60,1510.92 116 | 114,m1-ultramem-40,3067.66 117 | 115,m1-ultramem-80,3067.66 118 | 116,m1-ultramem-160,3067.66 119 | 117,m1-megamem-96,3167.66 120 | 118,m2-ultramem-208,1100.0 121 | 119,m2-ultramem-416,1100.0 122 | 120,m2-megamem-416,1100.0 123 | 121,a2-highgpu-1g,5465.5 124 | 122,a2-highgpu-2g,5465.5 125 | 123,a2-highgpu-4g,5465.5 126 | 124,a2-highgpu-8g,5465.5 127 | 125,a2-megagpu-16g,5465.5 128 | -------------------------------------------------------------------------------- /examples/spring-rest-service/README.md: -------------------------------------------------------------------------------- 1 | To see the extension in action for a Spring-based REST service we will create the setup depicted in the following image. We will setup a Spring-Application that will be instrumented using the OpenTelemetry Java-Agent including our extension. This application will publish resource demand and carbon emission metrics. These metrics will be received by an OpenTelemetry collector which transforms the metrics into a format compatible to Prometheus. Prometheus will fetch the metrics from the collector and store them. Using Grafana, the metrics will be visualized in a preconfigured dashboard. 2 | 3 | ## Please note before you start with the Spring-REST sample application: 4 | 5 | 1. You need to build the whole project before you continue, for details on how to build the project, take a look at the top level [README.md](../../README.md#building-the-project). 6 | 2. Afterwards, you need to start OpenTelemetry compatible tracing and metrics backends as explained in the [examples README.md](../README.md#starting-opentelemetry-backends-for-the-example-applications) 7 | 8 | 9 | ![demo_architecture.png](../../img/demo_architecture.png) 10 | 11 | Once the preconditions ([building the project and starting the OpenTelemetry backends](#please-note-before-you-start-with-the-jdk8-sample-application) are completed, the Spring-based REST-Service application can be started with the OpenTelemetry Java agent attached from the current directory as follows. 12 | ```bash 13 | java -javaagent:./target/jib/otel/opentelemetry-javaagent.jar \ 14 | -Dotel.service.name=spring-app \ 15 | -Dotel.logs.exporter=logging \ 16 | -Dotel.javaagent.extensions=./target/jib/otel/io.retit.opentelemetry.javaagent.extension.jar \ 17 | -Dio.retit.emissions.cloud.provider=aws \ 18 | -Dio.retit.emissions.cloud.provider.region=af-south-1 \ 19 | -Dio.retit.emissions.cloud.provider.instance.type=a1.medium \ 20 | -jar ./target/spring-rest-service.jar 21 | ``` 22 | 23 | This application will run until you stop it. Once the service is started, you can issue requests to the following endpoints: 24 | 25 | curl --request GET --url http://localhost:8081/test-rest-endpoint/getData 26 | curl --request POST --url http://localhost:8081/test-rest-endpoint/postData 27 | curl --request DELETE --url http://localhost:8081/test-rest-endpoint/deleteData 28 | 29 | If you want to automate the execution of these requests, take a look at the following test method in the extension module of the project: 30 | 31 | [io.retit.opentelemetry.javaagent.extension.frameworks.spring.SpringRESTApplicationIT.runTestContinuously](../../extension/src/test/java/io/retit/opentelemetry/javaagent/extension/frameworks/spring/SpringRESTApplicationIT.java) 32 | 33 | When executing this test method, the test will send requests to the Spring REST application until you manually stop the tests. 34 | 35 | As an alternative to generate load on the application, you can also find an [Apache JMeter](https://jmeter.apache.org/) load test script in the following location: 36 | 37 | [src/test/resource/jmeter_testplan.jmx](src/test/resources/jmeter_testplan.jmx) 38 | 39 | Once you have issued a few requests to one or more of these endpoints you will see the CO2 emissions for each endpoint in Grafana. 40 | 41 | 42 | The easiest way is to check out the [Grafana dashboard](http://localhost:3000/grafana/dashboards) here: 43 | 44 | http://localhost:3000/grafana/dashboards 45 | 46 | After some time you can see the data produced by this application in the following dashboard. As an example the CPU and memory demands are shown as they are supported on most plattforms as well as the Emission Calculation Factors. Furthermore, we have integrated a [Software Carbon Intensity](https://sci.greensoftware.foundation/) calculation for each transaction based on this data. This calculation is based on our work presented at the [Symposium on Software Performance 2024](https://fb-swt.gi.de/fileadmin/FB/SWT/Softwaretechnik-Trends/Verzeichnis/Band_44_Heft_4/SSP24_16_camera-ready_5255.pdf). 47 | 48 | ![dashboard.png](../../img/spring_dashboard.png) -------------------------------------------------------------------------------- /examples/quarkus-rest-service/README.md: -------------------------------------------------------------------------------- 1 | To see the extension in action for a Quarkus-based REST service we will create the setup depicted in the following image. We will setup a Quarkus-Application that will be instrumented using the OpenTelemetry Java-Agent including our extension. This application will publish resource demand and carbon emission metrics. These metrics will be received by an OpenTelemetry collector which transforms the metrics into a format compatible to Prometheus. Prometheus will fetch the metrics from the collector and store them. Using Grafana, the metrics will be visualized in a preconfigured dashboard. 2 | 3 | ## Please note before you start with the Quarkus-REST sample application: 4 | 5 | 1. You need to build the whole project before you continue, for details on how to build the project, take a look at the top level [README.md](../../README.md#building-the-project). 6 | 2. Afterwards, you need to start OpenTelemetry compatible tracing and metrics backends as explained in the [examples README.md](../README.md#starting-opentelemetry-backends-for-the-example-applications) 7 | 8 | 9 | ![demo_architecture.png](../../img/demo_architecture.png) 10 | 11 | Once the preconditions ([building the project and starting the OpenTelemetry backends](#please-note-before-you-start-with-the-jdk8-sample-application)) are completed, the Quarkus-based REST-Service application can be started with the OpenTelemetry Java agent attached from the current directory as follows. 12 | ```bash 13 | java -javaagent:./target/jib/otel/opentelemetry-javaagent.jar \ 14 | -Dotel.service.name=quarkus-app \ 15 | -Dotel.logs.exporter=logging \ 16 | -Dotel.javaagent.extensions=./target/jib/otel/io.retit.opentelemetry.javaagent.extension.jar \ 17 | -Dio.retit.emissions.cloud.provider=aws \ 18 | -Dio.retit.emissions.cloud.provider.region=af-south-1 \ 19 | -Dio.retit.emissions.cloud.provider.instance.type=a1.medium \ 20 | -jar ./target/quarkus-rest-service.jar 21 | ``` 22 | 23 | This application will run until you stop it. Once the service is started, you can issue requests to the following endpoints: 24 | 25 | curl --request GET --url http://localhost:8080/test-rest-endpoint/getData 26 | curl --request POST --url http://localhost:8080/test-rest-endpoint/postData 27 | curl --request DELETE --url http://localhost:8080/test-rest-endpoint/deleteData 28 | 29 | If you want to automate the execution of these requests, take a look at the following test method in the extension module of the project: 30 | 31 | [io.retit.opentelemetry.javaagent.extension.frameworks.quarkus.QuarkusRESTApplicationIT.runTestContinuously](../../extension/src/test/java/io/retit/opentelemetry/javaagent/extension/frameworks/quarkus/QuarkusRESTApplicationIT.java) 32 | 33 | When executing this test method, the test will send requests to the Quarkus REST application until you manually stop the tests. 34 | 35 | Once you have issued a few requests to one or more of these endpoints you will see the CO2 emissions for each endpoint in Grafana. 36 | 37 | 38 | The easiest way is to check out the [Grafana dashboard](http://localhost:3000/grafana/dashboards) here: 39 | 40 | http://localhost:3000/grafana/dashboards 41 | 42 | After some time you can see the data produced by this application in the following dashboard. As an example the CPU and memory demands are shown as they are supported on most plattforms as well as the Emission Calculation Factors. Furthermore, we have integrated a [Software Carbon Intensity](https://sci.greensoftware.foundation/) calculation for each transaction based on this data. This calculation is based on our work presented at the [Symposium on Software Performance 2024](https://fb-swt.gi.de/fileadmin/FB/SWT/Softwaretechnik-Trends/Verzeichnis/Band_44_Heft_4/SSP24_16_camera-ready_5255.pdf). 43 | 44 | ![dashboard.png](../../img/quarkus_dashboard.png) 45 | 46 | Please note on the dashboard that the metrics are split in a section for the core endpoints and for the corresponding blocking parts. This is due to the reactive nature of Quarkus in which the main processing happens in an event loop whereas blocking parts are executed on seperate worker threads. Therefore, you need to be careful what to measure in your Quarkus applications. -------------------------------------------------------------------------------- /examples/spring-rest-service/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | 7 | io.retit 8 | examples 9 | 0.0.1-SNAPSHOT 10 | 11 | 12 | 13 | io.retit 14 | spring-rest-service 15 | 0.0.1-SNAPSHOT 16 | spring-rest-service 17 | Demo on how to publish carbon emissions for Spring-based microservices. 18 | 19 | 20 | 21 21 | 4.0.0 22 | 23 | 24 | 25 | 26 | org.springframework.boot 27 | spring-boot-starter-web 28 | ${spring.version} 29 | 30 | 31 | 32 | org.springframework.boot 33 | spring-boot-starter-validation 34 | ${spring.version} 35 | 36 | 37 | 38 | 39 | 40 | spring-rest-service 41 | 42 | 43 | org.springframework.boot 44 | spring-boot-maven-plugin 45 | 4.0.0 46 | 47 | io.retit.spring.carbon.SpringCarbonEmissionsApplication 48 | ZIP 49 | 50 | 51 | 52 | 53 | repackage 54 | 55 | 56 | 57 | 58 | 59 | org.apache.maven.plugins 60 | maven-dependency-plugin 61 | 3.9.0 62 | 63 | 64 | copy-opentelemetry 65 | package 66 | 67 | copy 68 | 69 | 70 | 71 | 72 | io.opentelemetry.javaagent 73 | opentelemetry-javaagent 74 | jar 75 | opentelemetry-javaagent.jar 76 | 77 | 78 | io.retit 79 | extension 80 | jar 81 | ${project.version} 82 | io.retit.opentelemetry.javaagent.extension.jar 83 | 84 | 85 | 86 | 87 | 88 | 89 | ${project.build.directory}/jib/otel 90 | 91 | 92 | 93 | com.google.cloud.tools 94 | jib-maven-plugin 95 | ${jib.version} 96 | 97 | 98 | ${jdk21.base.container} 99 | 100 | 101 | spring-rest-service:${project.version} 102 | 103 | feature 104 | 105 | 106 | 107 | 108 | 109 | ${project.build.directory}/jib 110 | 111 | 112 | 113 | 114 | 115 | package 116 | 117 | dockerBuild 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /extension/src/test/java/io/retit/opentelemetry/javaagent/extension/common/SpanDemand.java: -------------------------------------------------------------------------------- 1 | package io.retit.opentelemetry.javaagent.extension.common; 2 | 3 | public class SpanDemand { 4 | public Long startCpuTime = null; 5 | public Long endCpuTime = null; 6 | public Long startSystemTime = null; 7 | public Long endSystemTime = null; 8 | public Long startHeapDemand = null; 9 | public Long endHeapDemand = null; 10 | public Long startDiskReadDemand = null; 11 | public Long endDiskReadDemand = null; 12 | public Long startDiskWriteDemand = null; 13 | public Long endDiskWriteDemand = null; 14 | public Long startNetworkReadDemand = null; 15 | public Long endNetworkReadDemand = null; 16 | public Long startNetworkWriteDemand = null; 17 | public Long endNetworkWriteDemand = null; 18 | public Long logSystemTime = null; 19 | public Long totalHeapSize = null; 20 | public Long startThreadId = null; 21 | public Long endThreadId = null; 22 | 23 | public static SpanDemand parseProperties(String logOutput) { 24 | SpanDemand spanDemand = new SpanDemand(); 25 | String[] properties = extractPropertiesFromLogOutput(logOutput); 26 | for (String prop : properties) { 27 | String[] elems = prop.split("="); 28 | if (elems[0].contains("io.retit.endcputime")) { 29 | spanDemand.endCpuTime = Long.valueOf(elems[1]); 30 | } else if (elems[0].contains("io.retit.enddiskreaddemand")) { 31 | spanDemand.endDiskReadDemand = Long.valueOf(elems[1]); 32 | } else if (elems[0].contains("io.retit.enddiskwritedemand")) { 33 | spanDemand.endDiskWriteDemand = Long.valueOf(elems[1]); 34 | } else if (elems[0].contains("io.retit.endnetworkreaddemand")) { 35 | spanDemand.endNetworkReadDemand = Long.valueOf(elems[1]); 36 | } else if (elems[0].contains("io.retit.endnetworkwritedemand")) { 37 | spanDemand.endNetworkWriteDemand = Long.valueOf(elems[1]); 38 | } else if (elems[0].contains("io.retit.endheapbyteallocation")) { 39 | spanDemand.endHeapDemand = Long.valueOf(elems[1]); 40 | } else if (elems[0].contains("io.retit.endsystemtime")) { 41 | spanDemand.endSystemTime = Long.valueOf(elems[1]); 42 | } else if (elems[0].contains("io.retit.startcputime")) { 43 | spanDemand.startCpuTime = Long.valueOf(elems[1]); 44 | } else if (elems[0].contains("io.retit.startdiskreaddemand")) { 45 | spanDemand.startDiskReadDemand = Long.valueOf(elems[1]); 46 | } else if (elems[0].contains("io.retit.startdiskwritedemand")) { 47 | spanDemand.startDiskWriteDemand = Long.valueOf(elems[1]); 48 | } else if (elems[0].contains("io.retit.startnetworkreaddemand")) { 49 | spanDemand.startNetworkReadDemand = Long.valueOf(elems[1]); 50 | } else if (elems[0].contains("io.retit.startnetworkwritedemand")) { 51 | spanDemand.startNetworkWriteDemand = Long.valueOf(elems[1]); 52 | } else if (elems[0].contains("io.retit.startheapbyteallocation")) { 53 | spanDemand.startHeapDemand = Long.valueOf(elems[1]); 54 | } else if (elems[0].contains("io.retit.startsystemtime")) { 55 | spanDemand.startSystemTime = Long.valueOf(elems[1]); 56 | } else if (elems[0].contains("io.retit.logsystemtime")) { 57 | spanDemand.logSystemTime = Long.valueOf(elems[1]); 58 | } else if (elems[0].contains("io.retit.totalheapsize")) { 59 | spanDemand.totalHeapSize = Long.valueOf(elems[1]); 60 | } else if (elems[0].contains("io.retit.startthread")) { 61 | spanDemand.startThreadId = Long.valueOf(elems[1]); 62 | } else if (elems[0].contains("io.retit.endthread")) { 63 | spanDemand.endThreadId = Long.valueOf(elems[1]); 64 | } 65 | } 66 | return spanDemand; 67 | } 68 | 69 | private static String[] extractPropertiesFromLogOutput(String logOutput) { 70 | return logOutput.substring(logOutput.indexOf("={") + 1, logOutput.lastIndexOf("},")).split(","); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /extension/src/main/java/io/retit/opentelemetry/javaagent/extension/resources/common/NativeFacade.java: -------------------------------------------------------------------------------- 1 | package io.retit.opentelemetry.javaagent.extension.resources.common; 2 | 3 | import com.sun.jna.Platform; 4 | //import com.sun.jna.platform.win32.WinBase; 5 | import io.retit.opentelemetry.javaagent.extension.resources.linux.LinuxCLibrary; 6 | import io.retit.opentelemetry.javaagent.extension.resources.macos.MacOSSystemLibrary; 7 | import io.retit.opentelemetry.javaagent.extension.resources.windows.WindowsKernel32Library; 8 | 9 | /** 10 | * NativeFacade is a wrapper class which provides access to native 11 | * methods across platforms. 12 | * Some methods which we need for the RETIT OpenTelementry Extension are not accessible through Java API, 13 | * but instead must be accessed using native libraries. This class bundles all 14 | * such methods and provides implementations across multiple platforms. For this 15 | * purpose, JNA is used to consume the native C libraries. 16 | * Currently, the only supported platforms are Windows and Linux. In addition, 17 | * some of the methods are only applicable on certain platforms, e.g. 18 | * getCurrentThreadCpuTime(). 19 | */ 20 | public class NativeFacade { 21 | 22 | private NativeFacade() { 23 | // Utility class 24 | } 25 | 26 | /** 27 | * Gets the id of the current thread (tid). 28 | * This method only provides platform specific tids for Windows, Linux and Mac. 29 | * For other platforms it falls back on the Java tid. 30 | * 31 | * @return the tid of the thread 32 | */ 33 | public static long getThreadId() { 34 | if (Platform.isWindows()) { 35 | return WindowsKernel32Library.INSTANCE.GetCurrentThreadId(); 36 | } else if (Platform.isLinux() && Platform.isIntel()) { 37 | return LinuxCLibrary.INSTANCE.gettid(); 38 | } else if (Platform.isMac()) { 39 | ThreadHandle handle = MacOSSystemLibrary.INSTANCE.pthread_self(); 40 | return MacOSSystemLibrary.INSTANCE.pthread_mach_thread_np(handle); 41 | } else { 42 | return Thread.currentThread().getId(); 43 | } 44 | } 45 | 46 | /** 47 | * Gets the amount of CPU time in ns consumed by the current thread. 48 | * 49 | * @return the CPU time in ns consumed by the current thread 50 | */ 51 | public static long getCurrentThreadCpuTime() { 52 | if (Platform.isWindows()) { 53 | ThreadHandle threadHandle = WindowsKernel32Library.INSTANCE.GetCurrentThread(); 54 | WindowsKernel32Library.FILETIME lpCreationTime = new WindowsKernel32Library.FILETIME(); 55 | WindowsKernel32Library.FILETIME lpExitTime = new WindowsKernel32Library.FILETIME(); 56 | WindowsKernel32Library.FILETIME lpKernelTime = new WindowsKernel32Library.FILETIME(); 57 | WindowsKernel32Library.FILETIME lpUserTime = new WindowsKernel32Library.FILETIME(); 58 | 59 | WindowsKernel32Library.INSTANCE.GetThreadTimes(threadHandle, lpCreationTime, lpExitTime, lpKernelTime, lpUserTime); 60 | 61 | long cpuTime = lpKernelTime.toDWordLong().longValue() + lpUserTime.toDWordLong().longValue(); 62 | /** 63 | * All times are expressed using FILETIME data structures. 64 | * Such a structure contains two 32-bit values that combine to form a 64-bit count of 100-nanosecond time units. 65 | * https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getthreadtimes 66 | */ 67 | return cpuTime * 100; 68 | } else if (Platform.isLinux()) { 69 | return getTotalClockTime(LinuxCLibrary.INSTANCE, LinuxCLibrary.CLOCK_THREAD_CPUTIME_ID); 70 | } else if (Platform.isMac()) { 71 | return getTotalClockTime(MacOSSystemLibrary.INSTANCE, MacOSSystemLibrary.CLOCK_THREAD_CPUTIME_ID); 72 | } 73 | 74 | return 0L; 75 | } 76 | 77 | private static long getTotalClockTime(final CLibrary cLibrary, final int clockId) { 78 | CLibrary.TimeSpec timeSpecBefore = new CLibrary.TimeSpec(); 79 | cLibrary.clock_gettime(clockId, timeSpecBefore); 80 | return (timeSpecBefore.tv_sec.longValue() * 1_000_000_000l) + timeSpecBefore.tv_nsec; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /examples/simple-jdk8-application/src/main/java/io/retit/opentelemetry/SampleApplication.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RETIT GmbH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.retit.opentelemetry; 18 | 19 | import io.opentelemetry.api.trace.Span; 20 | import io.opentelemetry.instrumentation.annotations.WithSpan; 21 | 22 | import java.io.IOException; 23 | import java.nio.charset.StandardCharsets; 24 | import java.nio.file.Files; 25 | import java.nio.file.Path; 26 | import java.util.concurrent.ThreadLocalRandom; 27 | import java.util.logging.Logger; 28 | 29 | /** 30 | * The purpose of this class is to collect spans and metrics for a very simple use case. 31 | * The class is used in the integration test called JavaAgentExtensionIntegrationTest. 32 | */ 33 | public class SampleApplication { 34 | 35 | private static final Logger LOGGER = Logger.getLogger(SampleApplication.class.getName()); 36 | 37 | private static final String CONTINUOUS_RUN_MODE = "continuously"; 38 | 39 | /** 40 | * Simple method annotated with @WithSpan to collect Otel data. 41 | */ 42 | @WithSpan 43 | public void method1() throws InterruptedException, IOException { 44 | doWork("method1", 500, 16); // windows updates the CPU times only every 15ms 45 | LOGGER.info("method1"); 46 | } 47 | 48 | /** 49 | * Simple method annotated with @WithSpan to collect Otel data. 50 | */ 51 | @WithSpan 52 | public void method2() throws InterruptedException, IOException { 53 | doWork("method2", 1000, 16); // windows updates the CPU times only every 15ms 54 | LOGGER.info("method2"); 55 | } 56 | 57 | private void doWork(final String method, final int amountOfWork, final long sleepTimeInMs) throws InterruptedException, IOException { 58 | 59 | Path tempFile = Files.createTempFile("sampleapplication", method); 60 | 61 | for (int i = 0; i < amountOfWork; i++) { 62 | // Random requires work on the CPU and Memory 63 | int randomNum = ThreadLocalRandom.current().nextInt(0, amountOfWork * 10); 64 | // Writing data requires work on Storage and Memory 65 | Files.write(tempFile, (method + randomNum).getBytes(StandardCharsets.UTF_8)); 66 | // TODO add network demand 67 | } 68 | 69 | Files.delete(tempFile); 70 | Thread.sleep(sleepTimeInMs); 71 | } 72 | 73 | /** 74 | * Main method that calls the other method of the sample application. 75 | * 76 | * @param args - not used 77 | * @throws InterruptedException - in case the application couldn't wait for the metric publishing. 78 | */ 79 | public static void main(String[] args) throws InterruptedException, IOException { 80 | SampleApplication sampleApplication = new SampleApplication(); 81 | // Call methods 82 | if (CONTINUOUS_RUN_MODE.equals(System.getProperty("RUN_MODE"))) { 83 | while (true) { 84 | sampleApplication.businessMethod(); 85 | Thread.sleep(500); 86 | } 87 | } else { 88 | sampleApplication.businessMethod(); 89 | } 90 | 91 | if (System.getenv("WAIT_FOR_ONE_MINUTE") != null) { 92 | // we need to wait for 60 s so that the metrics are at least published once 93 | Thread.sleep(60_000); 94 | } 95 | } 96 | 97 | private void businessMethod() throws InterruptedException, IOException { 98 | Span span = Span.current(); 99 | span.setAttribute("test", "some value"); 100 | method1(); 101 | method2(); 102 | span.end(); 103 | } 104 | } -------------------------------------------------------------------------------- /extension/src/main/java/io/retit/opentelemetry/javaagent/extension/processor/RETITSpanProcessor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RETIT GmbH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.retit.opentelemetry.javaagent.extension.processor; 18 | 19 | import io.opentelemetry.context.Context; 20 | import io.opentelemetry.sdk.common.CompletableResultCode; 21 | import io.opentelemetry.sdk.trace.ReadWriteSpan; 22 | import io.opentelemetry.sdk.trace.ReadableSpan; 23 | import io.opentelemetry.sdk.trace.internal.ExtendedSpanProcessor; 24 | import io.retit.opentelemetry.javaagent.extension.commons.InstanceConfiguration; 25 | import io.retit.opentelemetry.javaagent.extension.commons.TelemetryUtils; 26 | import io.retit.opentelemetry.javaagent.extension.metrics.MetricPublishingService; 27 | 28 | /** 29 | * This is the core of the RETIT OpenTelemetry Extension and measures resource demand at the start and end of each span. 30 | */ 31 | public class RETITSpanProcessor implements ExtendedSpanProcessor { 32 | 33 | @Override 34 | public void onStart(final Context parentContext, final ReadWriteSpan readWriteSpan) { 35 | boolean logCPUDemand = InstanceConfiguration.isLogCpuDemandDefaultTrue(); 36 | boolean logHeapDemand = InstanceConfiguration.isLogHeapDemandDefaultTrue(); 37 | boolean logResponseTime = InstanceConfiguration.isLogResponseTime(); 38 | boolean logDiskDemand = InstanceConfiguration.isLogDiskDemand(); 39 | boolean logNetworkDemand = InstanceConfiguration.isLogNetworkDemand(); 40 | 41 | TelemetryUtils.addStartResourceDemandValuesToSpanAttributes( 42 | logCPUDemand, 43 | logResponseTime, 44 | logHeapDemand, 45 | logDiskDemand, 46 | logNetworkDemand, 47 | readWriteSpan); 48 | } 49 | 50 | @Override 51 | public boolean isStartRequired() { 52 | return true; 53 | } 54 | 55 | @Override 56 | public void onEnd(final ReadableSpan readableSpan) { 57 | } 58 | 59 | @Override 60 | public boolean isEndRequired() { 61 | return false; 62 | } 63 | 64 | @Override 65 | public CompletableResultCode shutdown() { 66 | return CompletableResultCode.ofSuccess(); 67 | } 68 | 69 | @Override 70 | public CompletableResultCode forceFlush() { 71 | return CompletableResultCode.ofSuccess(); 72 | } 73 | 74 | @Override 75 | public void close() { 76 | // nothing to do 77 | } 78 | 79 | @Override 80 | public void onEnding(final ReadWriteSpan readWriteSpan) { 81 | boolean logCPUDemand = InstanceConfiguration.isLogCpuDemandDefaultTrue(); 82 | boolean logHeapDemand = InstanceConfiguration.isLogHeapDemandDefaultTrue(); 83 | boolean logResponseTime = InstanceConfiguration.isLogResponseTime(); 84 | boolean logDiskDemand = InstanceConfiguration.isLogDiskDemand(); 85 | boolean logNetworkDemand = InstanceConfiguration.isLogNetworkDemand(); 86 | 87 | TelemetryUtils.addEndResourceDemandValuesToSpanAttributes( 88 | logCPUDemand, 89 | logResponseTime, 90 | logHeapDemand, 91 | logDiskDemand, 92 | logNetworkDemand, 93 | readWriteSpan); 94 | 95 | if (readWriteSpan.getParentSpanContext() != null && !readWriteSpan.getParentSpanContext().isValid()) { 96 | // publish resource demand vector for top level transactions as metric for SCI calculations in Grafana 97 | MetricPublishingService.getInstance().publishResourceDemandVectorOfTransaction(readWriteSpan, logCPUDemand, logHeapDemand, logDiskDemand, logNetworkDemand); 98 | } 99 | } 100 | 101 | @Override 102 | public boolean isOnEndingRequired() { 103 | return true; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /extension/src/main/java/io/retit/opentelemetry/javaagent/extension/resources/jvm/JavaAgentGCNotificationListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RETIT GmbH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.retit.opentelemetry.javaagent.extension.resources.jvm; 18 | 19 | import com.sun.management.GarbageCollectionNotificationInfo; 20 | import io.opentelemetry.api.GlobalOpenTelemetry; 21 | import io.opentelemetry.api.trace.Span; 22 | import io.retit.opentelemetry.javaagent.extension.commons.Constants; 23 | 24 | import javax.management.Notification; 25 | import javax.management.NotificationListener; 26 | import javax.management.openmbean.CompositeData; 27 | import java.lang.management.MemoryUsage; 28 | import java.util.Map; 29 | 30 | /** 31 | * A {@link NotificationListener} which triggers on garbage collection invocations. 32 | * This notification listener is registered by the {@link JavaAgentGCHandler} and then waits for garbage collection notifications. 33 | * Once such a notification arrives, handleGCNotification(Notification) is called. 34 | */ 35 | public class JavaAgentGCNotificationListener implements NotificationListener { 36 | 37 | private static final String END_OF_MINOR_GC = "end of minor GC"; 38 | private static final String END_OF_MAJOR_GC = "end of major GC"; 39 | 40 | @Override 41 | public void handleNotification(final Notification notification, final Object handback) { 42 | if (notification != null && GarbageCollectionNotificationInfo.GARBAGE_COLLECTION_NOTIFICATION.equals(notification.getType())) { 43 | handleGCNotification(notification); 44 | } 45 | } 46 | 47 | private void handleGCNotification(final Notification notification) { 48 | GarbageCollectionNotificationInfo garbageCollectionInfo = GarbageCollectionNotificationInfo 49 | .from((CompositeData) notification.getUserData()); 50 | 51 | final String garbageCollectionType = garbageCollectionInfo.getGcAction(); 52 | final String operationName = determineOperationName(garbageCollectionType); 53 | final long duration = garbageCollectionInfo.getGcInfo().getDuration(); 54 | 55 | // Get the information about each memory space 56 | Map memorySpaceBefore = garbageCollectionInfo.getGcInfo().getMemoryUsageBeforeGc(); 57 | Map memory = garbageCollectionInfo.getGcInfo().getMemoryUsageAfterGc(); 58 | 59 | long totalMemBeforeGC = 0; 60 | long totalMemAfterGC = 0; 61 | long memCommitted = 0; 62 | for (Map.Entry entry : memory.entrySet()) { 63 | String name = entry.getKey(); 64 | MemoryUsage memdetail = entry.getValue(); 65 | MemoryUsage before = memorySpaceBefore.get(name); 66 | 67 | totalMemBeforeGC += before.getUsed(); 68 | totalMemAfterGC += memdetail.getUsed(); 69 | memCommitted += memdetail.getCommitted(); 70 | } 71 | 72 | Span span = 73 | GlobalOpenTelemetry.getTracer(Constants.JAVA_AGENT_INSTRUMENTATION_NAME_GC_LISTENER).spanBuilder(operationName).setNoParent().startSpan(); 74 | span.setAttribute(Constants.SPAN_ATTRIBUTE_START_SYSTEM_TIME, 0); 75 | span.setAttribute(Constants.SPAN_ATTRIBUTE_END_SYSTEM_TIME, duration); 76 | span.setAttribute(Constants.SPAN_ATTRIBUTE_START_HEAP_BYTE_ALLOCATION, totalMemBeforeGC); 77 | span.setAttribute(Constants.SPAN_ATTRIBUTE_END_HEAP_BYTE_ALLOCATION, totalMemAfterGC); 78 | span.setAttribute(Constants.SPAN_ATTRIBUTE_TOTAL_HEAP_SIZE, memCommitted); 79 | span.end(); 80 | } 81 | 82 | private static String determineOperationName(final String garbageCollectionType) { 83 | if (END_OF_MINOR_GC.equals(garbageCollectionType)) { 84 | return Constants.JAVA_AGENT_GC_OPERATION_NAME_MINOR_FREE; 85 | } else if (END_OF_MAJOR_GC.equals(garbageCollectionType)) { 86 | return Constants.JAVA_AGENT_GC_OPERATION_NAME_MAJOR_FREE; 87 | } 88 | return ""; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /extension/src/test/java/io/retit/opentelemetry/javaagent/extension/processor/RETITSpanProcessorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RETIT GmbH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.retit.opentelemetry.javaagent.extension.processor; 18 | 19 | import io.opentelemetry.api.common.Attributes; 20 | import io.opentelemetry.api.trace.SpanContext; 21 | import io.opentelemetry.api.trace.SpanId; 22 | import io.opentelemetry.api.trace.SpanKind; 23 | import io.opentelemetry.api.trace.TraceFlags; 24 | import io.opentelemetry.api.trace.TraceId; 25 | import io.opentelemetry.api.trace.TraceState; 26 | import io.opentelemetry.context.Context; 27 | import io.opentelemetry.sdk.testing.trace.TestSpanData; 28 | import io.opentelemetry.sdk.trace.ReadWriteSpan; 29 | import io.opentelemetry.sdk.trace.ReadableSpan; 30 | import io.opentelemetry.sdk.trace.data.SpanData; 31 | import io.opentelemetry.sdk.trace.data.StatusData; 32 | import io.retit.opentelemetry.javaagent.extension.commons.Constants; 33 | import io.retit.opentelemetry.javaagent.extension.commons.InstanceConfiguration; 34 | import org.junit.jupiter.api.BeforeEach; 35 | import org.junit.jupiter.api.Test; 36 | import org.mockito.Mockito; 37 | 38 | import static org.mockito.Mockito.*; 39 | 40 | public class RETITSpanProcessorTest { 41 | 42 | private static final SpanContext SAMPLED_SPAN_CONTEXT = 43 | SpanContext.create(TraceId.getInvalid(), SpanId.getInvalid(), TraceFlags.getSampled(), TraceState.getDefault()); 44 | 45 | private ReadableSpan readableSpan; 46 | 47 | private ReadWriteSpan readWriteSpan; 48 | 49 | private RETITSpanProcessor retitSpanProcessor; 50 | 51 | private SpanData spanData; 52 | 53 | @BeforeEach 54 | public void setup() { 55 | InstanceConfiguration.setBooleanProperty(Constants.RETIT_CPU_DEMAND_LOGGING_CONFIGURATION_PROPERTY, true); 56 | InstanceConfiguration.setBooleanProperty(Constants.RETIT_HEAP_DEMAND_LOGGING_CONFIGURATION_PROPERTY, true); 57 | InstanceConfiguration.setBooleanProperty(Constants.RETIT_DISK_DEMAND_LOGGING_CONFIGURATION_PROPERTY, true); 58 | InstanceConfiguration.setBooleanProperty(Constants.RETIT_RESPONSE_TIME_LOGGING_CONFIGURATION_PROPERTY, true); 59 | InstanceConfiguration.setBooleanProperty(Constants.RETIT_THREAD_NAME_LOGGING_CONFIGURATION_PROPERTY, true); 60 | retitSpanProcessor = new RETITSpanProcessor(); 61 | //retitSpanProcessor.buildBatchSpanProcessor(); 62 | //Assertions.assertNotNull(retitSpanProcessor.getDelegateBatchSpanProcessor()); 63 | spanData = dummySpanData(Attributes.empty()); 64 | 65 | readableSpan = mock(ReadableSpan.class); 66 | readWriteSpan = mock(ReadWriteSpan.class); 67 | } 68 | 69 | @Test 70 | public void onStart() { 71 | retitSpanProcessor.onStart(Context.root(), readWriteSpan); 72 | verify(readWriteSpan, times(6)).setAttribute(Mockito.anyString(), Mockito.anyLong()); 73 | } 74 | 75 | @Test 76 | public void onEnd() { 77 | when(readableSpan.getSpanContext()).thenReturn(SAMPLED_SPAN_CONTEXT); 78 | when(readableSpan.toSpanData()).thenReturn(spanData); 79 | 80 | retitSpanProcessor.onEnd(readableSpan); 81 | verify(readableSpan, atMostOnce()).toSpanData(); 82 | } 83 | 84 | @Test 85 | public void close() { 86 | retitSpanProcessor.close(); 87 | } 88 | 89 | private SpanData dummySpanData(Attributes attributes) { 90 | return TestSpanData.builder() 91 | .setHasEnded(true) 92 | .setSpanContext(SAMPLED_SPAN_CONTEXT) 93 | .setName("span") 94 | .setKind(SpanKind.SERVER) 95 | .setStartEpochNanos(System.nanoTime()) 96 | .setStatus(StatusData.ok()) 97 | .setEndEpochNanos(System.nanoTime()) 98 | .setAttributes(attributes) 99 | .setTotalRecordedLinks(0) 100 | .setTotalRecordedEvents(0) 101 | .build(); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /extension/src/test/java/io/retit/opentelemetry/javaagent/extension/frameworks/AbstractFrameworkIT.java: -------------------------------------------------------------------------------- 1 | package io.retit.opentelemetry.javaagent.extension.frameworks; 2 | 3 | import io.restassured.RestAssured; 4 | import io.retit.opentelemetry.javaagent.extension.common.ContainerLogMetricAndSpanExtractingTest; 5 | import io.retit.opentelemetry.javaagent.extension.common.SpanDemand; 6 | import org.junit.jupiter.api.Assertions; 7 | import org.testcontainers.containers.GenericContainer; 8 | 9 | import java.util.List; 10 | 11 | public class AbstractFrameworkIT extends ContainerLogMetricAndSpanExtractingTest { 12 | 13 | private static final String CONTEXT_ROOT = "/test-rest-endpoint"; 14 | 15 | private static final String GET_URI = CONTEXT_ROOT + "/getData"; 16 | 17 | private static final String POST_URI = CONTEXT_ROOT + "/postData"; 18 | 19 | private static final String DELETE_URI = CONTEXT_ROOT + "/deleteData"; 20 | 21 | protected void commonSetup(final String containerName, final String serviceName, final int portToExpose, final boolean useExternalOtelAgent, final boolean exportMetricsToCollector) { 22 | applicationContainer = new GenericContainer<>(containerName) 23 | .withExposedPorts(portToExpose) 24 | .withEnv("IO_RETIT_LOG_CPU_DEMAND", "true") 25 | .withEnv("IO_RETIT_LOG_DISK_DEMAND", "true") 26 | .withEnv("IO_RETIT_LOG_HEAP_DEMAND", "true") 27 | .withEnv("IO_RETIT_LOG_NETWORK_DEMAND", "true") 28 | .withEnv("IO_RETIT_LOG_GC_EVENT", "true") 29 | .withEnv("OTEL_LOGS_EXPORTER", "none") 30 | .withEnv("OTEL_TRACES_EXPORTER", "console") 31 | .withEnv("OTEL_RESOURCE_ATTRIBUTES", "service.name=" + serviceName) 32 | .withEnv("IO_RETIT_EMISSIONS_STORAGE_TYPE", "SSD") 33 | .withEnv("IO_RETIT_EMISSIONS_CLOUD_PROVIDER_REGION", "af-south-1") 34 | .withEnv("IO_RETIT_EMISSIONS_CLOUD_PROVIDER_INSTANCE_TYPE", "a1.medium") 35 | .withEnv("IO_RETIT_EMISSIONS_CLOUD_PROVIDER", "AWS"); 36 | 37 | if (exportMetricsToCollector) { 38 | applicationContainer = applicationContainer.withEnv("OTEL_METRICS_EXPORTER", "otlp").withEnv("OTEL_EXPORTER_OTLP_ENDPOINT", "http://host.docker.internal:4318"); 39 | } else { 40 | applicationContainer = applicationContainer.withEnv("OTEL_METRICS_EXPORTER", "console"); 41 | } 42 | if (useExternalOtelAgent) { 43 | applicationContainer = applicationContainer 44 | .withEnv("QUARKUS_OTEL_ENABLED", "false") // disable quarkus internal OpenTelemetry Support as we are using the agent 45 | .withEnv("JAVA_TOOL_OPTIONS", "-javaagent:/otel/opentelemetry-javaagent.jar -Dotel.javaagent.extensions=/otel/io.retit.opentelemetry.javaagent.extension.jar"); 46 | } else { 47 | applicationContainer = applicationContainer 48 | .withEnv("QUARKUS_OTEL_ENABLED", "true") // enable quarkus internal OpenTelemetry Support 49 | .withEnv("QUARKUS_OTEL_SIMPLE", "true") // send telemetry right away 50 | .withEnv("JAVA_TOOL_OPTIONS", "-Dotel.javaagent.extensions=/otel/io.retit.opentelemetry.javaagent.extension.jar"); 51 | } 52 | executeContainer(portToExpose); 53 | } 54 | 55 | protected void runTestContinuously() { 56 | while (true) { 57 | callAllEndpoints(); 58 | } 59 | } 60 | 61 | protected void testCallEachEndpointAndAsserSpansAndMetrics() { 62 | callAllEndpoints(); 63 | applicationContainer.stop(); 64 | super.waitUntilContainerIsStopped(); 65 | int testRestEndpointCount = 0; 66 | for (String spanName : spanDemands.keySet()) { 67 | System.out.println(spanName); 68 | if(spanName.contains(CONTEXT_ROOT)) { 69 | testRestEndpointCount++; 70 | } 71 | } 72 | Assertions.assertEquals(3, testRestEndpointCount); 73 | assertFullSpanDataContent(CONTEXT_ROOT); 74 | } 75 | 76 | protected void callAllEndpoints() { 77 | callAndAssertGetEndpoint(); 78 | callAndAssertPostEndpoint(); 79 | callAndAssertDeleteEndpoint(); 80 | } 81 | 82 | protected void callAndAssertGetEndpoint() { 83 | RestAssured.given().get(CONTAINER_URL + GET_URI).then().statusCode(200); 84 | } 85 | 86 | protected void callAndAssertPostEndpoint() { 87 | RestAssured.given().post(CONTAINER_URL + POST_URI).then().statusCode(200); 88 | } 89 | 90 | protected void callAndAssertDeleteEndpoint() { 91 | RestAssured.given().delete(CONTAINER_URL + DELETE_URI).then().statusCode(200); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /etc/pmd-configuration.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | This PMD ruleset is applied to all Java code in the otel javaagent extension. 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /examples/simple-jdk21-application/src/main/java/io/retit/opentelemetry/SampleApplication.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RETIT GmbH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.retit.opentelemetry; 18 | 19 | import io.opentelemetry.api.trace.Span; 20 | import io.opentelemetry.instrumentation.annotations.WithSpan; 21 | 22 | import java.io.IOException; 23 | import java.nio.charset.StandardCharsets; 24 | import java.nio.file.Files; 25 | import java.nio.file.Path; 26 | import java.util.concurrent.ThreadLocalRandom; 27 | import java.util.logging.Logger; 28 | 29 | /** 30 | * The purpose of this class is to collect spans and metrics for a very simple use case. 31 | * The class is used in the integration test called JavaAgentExtensionIntegrationTest. 32 | */ 33 | public class SampleApplication { 34 | 35 | private static final Logger LOGGER = Logger.getLogger(SampleApplication.class.getName()); 36 | 37 | private static final String CONTINUOUS_RUN_MODE = "continuously"; 38 | 39 | private static final String VIRTUAL_THREAD = "VIRTUAL_THREAD"; 40 | 41 | /** 42 | * Simple method annotated with @WithSpan to collect Otel data. 43 | */ 44 | @WithSpan 45 | public void method1() throws InterruptedException, IOException { 46 | doWork("method1", 500, 16); // windows updates the CPU times only every 15ms 47 | LOGGER.info("method1"); 48 | } 49 | 50 | /** 51 | * Simple method annotated with @WithSpan to collect Otel data. 52 | */ 53 | @WithSpan 54 | public void method2() throws InterruptedException, IOException { 55 | doWork("method2", 1000, 16); // windows updates the CPU times only every 15ms 56 | LOGGER.info("method2"); 57 | } 58 | 59 | private void doWork(final String method, final int amountOfWork, final long sleepTimeInMs) throws InterruptedException, IOException { 60 | 61 | Path tempFile = Files.createTempFile("sampleapplication", method); 62 | 63 | for (int i = 0; i < amountOfWork; i++) { 64 | // Random requires work on the CPU and Memory 65 | int randomNum = ThreadLocalRandom.current().nextInt(0, amountOfWork * 10); 66 | // Writing data requires work on Storage and Memory 67 | Files.write(tempFile, (method + randomNum).getBytes(StandardCharsets.UTF_8)); 68 | // TODO add network demand 69 | } 70 | 71 | Files.delete(tempFile); 72 | Thread.sleep(sleepTimeInMs); 73 | } 74 | 75 | /** 76 | * Main method that calls the other method of the sample application. 77 | * 78 | * @param args - not used 79 | * @throws InterruptedException - in case the application couldn't wait for the metric publishing. 80 | */ 81 | public static void main(String[] args) throws InterruptedException, IOException { 82 | SampleApplication sampleApplication = new SampleApplication(); 83 | // Call methods 84 | if (CONTINUOUS_RUN_MODE.equals(System.getProperty("RUN_MODE"))) { 85 | while (true) { 86 | sampleApplication.businessMethod(); 87 | Thread.sleep(500); 88 | } 89 | } else if (VIRTUAL_THREAD.equals(System.getenv("RUN_MODE"))) { 90 | LOGGER.info("RUNNING WITH VIRTUAL THREAD"); 91 | Thread.ofVirtual().start(() -> { 92 | try { 93 | sampleApplication.businessMethod(); 94 | } catch (InterruptedException | IOException e) { 95 | LOGGER.severe("Business method invocation failed: " + e.getMessage()); 96 | } 97 | 98 | }).join(); 99 | } else { 100 | sampleApplication.businessMethod(); 101 | } 102 | 103 | if (System.getenv("WAIT_FOR_ONE_MINUTE") != null) { 104 | // we need to wait for 60 s so that the metrics are at least published once 105 | Thread.sleep(60_000); 106 | } 107 | } 108 | 109 | private void businessMethod() throws InterruptedException, IOException { 110 | Span span = Span.current(); 111 | span.setAttribute("test", "some value"); 112 | method1(); 113 | method2(); 114 | span.end(); 115 | } 116 | } -------------------------------------------------------------------------------- /extension/src/main/java/io/retit/opentelemetry/javaagent/extension/emissions/CloudCarbonFootprintInstanceData.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RETIT GmbH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.retit.opentelemetry.javaagent.extension.emissions; 18 | 19 | /** 20 | * This class holds all the configuration data for a given instance. 21 | */ 22 | public class CloudCarbonFootprintInstanceData { 23 | // cloud provider of the instance 24 | private CloudProvider cloudProvider; 25 | // instance type 26 | private String instanceType; 27 | // Number of Instance vCPUs 28 | private double instanceVCpuCount; 29 | // Number of Platform Total vCPU 30 | private double platformTotalVCpuCount; 31 | // Instance Watt usage @ Idle 32 | private double cpuPowerConsumptionIdle; 33 | // Instance Watt usage @ 100% 34 | private double cpuPowerConsumption100Percent; 35 | // the total embodied emissions of the server running this vminstance 36 | private double totalEmbodiedEmissions; 37 | 38 | /** 39 | * Default constructor. 40 | */ 41 | public CloudCarbonFootprintInstanceData() { 42 | } 43 | 44 | /** 45 | * Constructor when data is already available. 46 | * 47 | * @param instanceVCpuCount - Number of Instance vCPUs 48 | * @param platformTotalVCpuCount - Number of Platform total vCPUs 49 | * @param cpuPowerConsumptionIdle - Instance Watt usage @ Idle 50 | * @param cpuPowerConsumption100Percent - Instance Watt usage @ 100% Utilization 51 | * @param totalEmbodiedEmissions - the total embodied emissions of the server running this vminstance 52 | */ 53 | public CloudCarbonFootprintInstanceData(final double instanceVCpuCount, 54 | final double platformTotalVCpuCount, 55 | final double cpuPowerConsumptionIdle, 56 | final double cpuPowerConsumption100Percent, 57 | final double totalEmbodiedEmissions) { 58 | this.instanceVCpuCount = instanceVCpuCount; 59 | this.platformTotalVCpuCount = platformTotalVCpuCount; 60 | this.cpuPowerConsumptionIdle = cpuPowerConsumptionIdle; 61 | this.cpuPowerConsumption100Percent = cpuPowerConsumption100Percent; 62 | this.totalEmbodiedEmissions = totalEmbodiedEmissions; 63 | } 64 | 65 | public double getInstanceVCpuCount() { 66 | return instanceVCpuCount; 67 | } 68 | 69 | public void setInstanceVCpuCount(final double instanceVCpuCount) { 70 | this.instanceVCpuCount = instanceVCpuCount; 71 | } 72 | 73 | public double getPlatformTotalVCpuCount() { 74 | return platformTotalVCpuCount; 75 | } 76 | 77 | public void setPlatformTotalVCpuCount(final double platformTotalVCpuCount) { 78 | this.platformTotalVCpuCount = platformTotalVCpuCount; 79 | } 80 | 81 | public double getCpuPowerConsumptionIdle() { 82 | return cpuPowerConsumptionIdle; 83 | } 84 | 85 | public void setCpuPowerConsumptionIdle(final double cpuPowerConsumptionIdle) { 86 | this.cpuPowerConsumptionIdle = cpuPowerConsumptionIdle; 87 | } 88 | 89 | public double getCpuPowerConsumption100Percent() { 90 | return cpuPowerConsumption100Percent; 91 | } 92 | 93 | public void setCpuPowerConsumption100Percent(final double cpuPowerConsumption100Percent) { 94 | this.cpuPowerConsumption100Percent = cpuPowerConsumption100Percent; 95 | } 96 | 97 | public double getTotalEmbodiedEmissions() { 98 | return totalEmbodiedEmissions; 99 | } 100 | 101 | public void setTotalEmbodiedEmissions(final double totalEmbodiedEmissions) { 102 | this.totalEmbodiedEmissions = totalEmbodiedEmissions; 103 | } 104 | 105 | public CloudProvider getCloudProvider() { 106 | return cloudProvider; 107 | } 108 | 109 | public void setCloudProvider(final CloudProvider cloudProvider) { 110 | this.cloudProvider = cloudProvider; 111 | } 112 | 113 | public String getInstanceType() { 114 | return instanceType; 115 | } 116 | 117 | public void setInstanceType(final String instanceType) { 118 | this.instanceType = instanceType; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /extension/src/main/java/io/retit/opentelemetry/javaagent/extension/resources/windows/WindowsKernel32Library.java: -------------------------------------------------------------------------------- 1 | package io.retit.opentelemetry.javaagent.extension.resources.windows; 2 | 3 | import com.sun.jna.IntegerType; 4 | import com.sun.jna.Library; 5 | import com.sun.jna.Native; 6 | import com.sun.jna.Structure; 7 | import com.sun.jna.ptr.LongByReference; 8 | import io.retit.opentelemetry.javaagent.extension.resources.common.NativeFacade; 9 | import io.retit.opentelemetry.javaagent.extension.resources.common.ThreadHandle; 10 | 11 | /** 12 | * JNA Library which allows access to the native Windows API. 13 | * Generally, this class should not be accessed directly - the wrapper 14 | * functions defined in {@link NativeFacade} should be used instead. 15 | * Currently, we only define those methods which we need for our purposes. 16 | * If you need another method from the kernel32 API, just add the method 17 | * to the interface. However, please also add a generalized method 18 | * to {@link NativeFacade} if possible. 19 | */ 20 | @SuppressWarnings("PMD") 21 | public interface WindowsKernel32Library extends Library { 22 | /** 23 | * The instance provided by the JNA library to interact with the native kernel 32 library. 24 | */ 25 | WindowsKernel32Library INSTANCE = Native.loadLibrary("kernel32", WindowsKernel32Library.class); 26 | 27 | /** 28 | * Retrieves the process identifier of the calling process. 29 | * See https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getcurrentprocessid. 30 | * 31 | * @return The process id of the calling process. 32 | */ 33 | int GetCurrentProcessId(); 34 | 35 | /** 36 | * Retrieves the thread identifier of the calling thread. 37 | * See https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getcurrentthreadid. 38 | * 39 | * @return The thread id of the calling thread. 40 | */ 41 | int GetCurrentThreadId(); 42 | 43 | /** 44 | * Retrieves a handle for the current thread. 45 | * See https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getcurrentthread. 46 | * 47 | * @return a handle for the current thread. 48 | */ 49 | ThreadHandle GetCurrentThread(); 50 | 51 | /** 52 | * Retrieves the cycle time for the specified thread. 53 | * See https://learn.microsoft.com/en-us/windows/win32/api/realtimeapiset/nf-realtimeapiset-querythreadcycletime. 54 | * 55 | * @param threadHandle - A handle to the thread. 56 | * @param cycles - Ths reference will contain the number of CPU clock cycles used by the thread (incl. Kernel and User mode). 57 | * @return - a boolean indicator if the call was successful, the return value is in the cycles reference. 58 | */ 59 | boolean QueryThreadCycleTime(ThreadHandle threadHandle, LongByReference cycles); 60 | 61 | /** 62 | * Retrieves the cycle time for the specified thread. 63 | * See https://learn.microsoft.com/en-en/windows/win32/api/processthreadsapi/nf-processthreadsapi-getthreadtimes. 64 | * 65 | * @param threadHandle - A handle to the thread. 66 | * @param lpCreationTime - A pointer to a FILETIME structure that receives the creation time of the thread. 67 | * @param lpExitTime - A pointer to a FILETIME structure that receives the exit time of the thread. 68 | * @param lpKernelTime - A pointer to a FILETIME structure that receives the amount of time that the thread has executed in kernel mode. 69 | * @param lpUserTime - A pointer to a FILETIME structure that receives the amount of time that the thread has executed in user mode. 70 | * @return - a boolean indicator if the call was successful. 71 | */ 72 | boolean GetThreadTimes(ThreadHandle threadHandle, FILETIME lpCreationTime, FILETIME lpExitTime, FILETIME lpKernelTime, FILETIME lpUserTime); 73 | 74 | /** 75 | * Simplified version of the FILETIME datastructure. 76 | *

77 | * See https://github.com/java-native-access/jna/blob/5.16.0/contrib/platform/src/com/sun/jna/platform/win32/WinBase.java#L811 78 | */ 79 | @Structure.FieldOrder({"dwLowDateTime", "dwHighDateTime"}) 80 | class FILETIME extends Structure { 81 | public int dwLowDateTime; 82 | public int dwHighDateTime; 83 | 84 | /** 85 | *

Converts the two 32-bit unsigned integer parts of this filetime 86 | * into a 64-bit unsigned integer representing the number of 87 | * 100-nanosecond intervals since January 1, 1601 (UTC).

88 | * 89 | * @return This filetime as a 64-bit unsigned integer number of 90 | * 100-nanosecond intervals since January 1, 1601 (UTC). 91 | */ 92 | public DWORDLONG toDWordLong() { 93 | return new DWORDLONG((long) dwHighDateTime << 32 | dwLowDateTime & 0xffffffffL); 94 | } 95 | } 96 | 97 | /** 98 | * 64-bit unsigned integer. 99 | */ 100 | class DWORDLONG extends IntegerType { 101 | 102 | /** 103 | * The Constant SIZE. 104 | */ 105 | public static final int SIZE = 8; 106 | 107 | /** 108 | * Instantiates a new dwordlong. 109 | * 110 | * @param value the value 111 | */ 112 | public DWORDLONG(long value) { 113 | super(SIZE, value, true); 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /extension/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | extension 5 | 6 | 7 | io.retit 8 | opentelemetry-javaagent-extension 9 | 0.0.1-SNAPSHOT 10 | 11 | 12 | 13 | 14 | 15 | io.opentelemetry 16 | opentelemetry-sdk-extension-autoconfigure-spi 17 | compile 18 | 19 | 20 | 21 | com.google.auto.service 22 | auto-service-annotations 23 | ${auto-service.version} 24 | compile 25 | 26 | 27 | 28 | 29 | net.java.dev.jna 30 | jna 31 | 32 | 33 | 34 | 35 | 36 | 37 | org.junit.jupiter 38 | junit-jupiter 39 | test 40 | 41 | 42 | 43 | org.mockito 44 | mockito-core 45 | test 46 | 47 | 48 | 49 | io.opentelemetry 50 | opentelemetry-sdk-testing 51 | test 52 | 53 | 54 | 55 | org.testcontainers 56 | junit-jupiter 57 | test 58 | 59 | 60 | 61 | org.slf4j 62 | slf4j-simple 63 | test 64 | 65 | 66 | io.rest-assured 67 | rest-assured 68 | 6.0.0 69 | test 70 | 71 | 72 | 73 | 74 | io.retit.opentelemetry.javaagent.extension 75 | 76 | 77 | org.apache.maven.plugins 78 | maven-shade-plugin 79 | 3.6.1 80 | 81 | 82 | package 83 | 84 | shade 85 | 86 | 87 | 88 | 89 | io.opentelemetry:* 90 | com.google.auto.service:* 91 | 92 | 93 | 94 | 95 | net.java.dev.jna:* 96 | 97 | META-INF/* 98 | 99 | 100 | 101 | true 102 | 103 | 104 | 105 | 106 | 107 | org.apache.maven.plugins 108 | maven-compiler-plugin 109 | 3.14.1 110 | 111 | 8 112 | 8 113 | 114 | 115 | com.google.auto.service 116 | auto-service 117 | ${auto-service.version} 118 | 119 | 120 | 121 | 122 | 123 | org.apache.maven.plugins 124 | maven-surefire-plugin 125 | 3.5.4 126 | 127 | 128 | **/*IntegrationTest.java 129 | 130 | 131 | 132 | 133 | 134 | 135 | -------------------------------------------------------------------------------- /extension/src/main/java/io/retit/opentelemetry/javaagent/extension/commons/InstanceConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RETIT GmbH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.retit.opentelemetry.javaagent.extension.commons; 18 | 19 | import java.util.Locale; 20 | import java.util.logging.Level; 21 | import java.util.logging.Logger; 22 | 23 | /** 24 | * General configuration class for the opentelemetry agent extension. 25 | * This class is used to configure which data is collected and where the collected data is persisted. 26 | * For this purpose, System properties are used. Such System properties can also 27 | * be passed as environment variables. 28 | */ 29 | public class InstanceConfiguration { 30 | 31 | private static final Logger LOGGER = Logger.getLogger(InstanceConfiguration.class.getName()); 32 | 33 | public static boolean isLogCpuDemandDefaultTrue() { 34 | return getBooleanProperty(Constants.RETIT_CPU_DEMAND_LOGGING_CONFIGURATION_PROPERTY, true); 35 | } 36 | 37 | public static boolean isLogHeapDemandDefaultTrue() { 38 | return getBooleanProperty(Constants.RETIT_HEAP_DEMAND_LOGGING_CONFIGURATION_PROPERTY, true); 39 | } 40 | 41 | public static boolean isLogGCEventDefaultTrue() { 42 | return getBooleanProperty(Constants.RETIT_GC_EVENT_LOGGING_CONFIGURATION_PROPERTY, true); 43 | } 44 | 45 | public static boolean isLogDiskDemand() { 46 | return getBooleanProperty(Constants.RETIT_DISK_DEMAND_LOGGING_CONFIGURATION_PROPERTY); 47 | } 48 | 49 | public static boolean isLogNetworkDemand() { 50 | return getBooleanProperty(Constants.RETIT_NETWORK_DEMAND_LOGGING_CONFIGURATION_PROPERTY); 51 | } 52 | 53 | public static boolean isLogResponseTime() { 54 | return getBooleanProperty(Constants.RETIT_RESPONSE_TIME_LOGGING_CONFIGURATION_PROPERTY); 55 | } 56 | 57 | public static String getCloudProvider() { 58 | return getStringProperty(Constants.RETIT_EMISSIONS_CLOUD_PROVIDER_CONFIGURATION_PROPERTY); 59 | } 60 | 61 | public static String getCloudProviderRegion() { 62 | return getStringProperty(Constants.RETIT_EMISSIONS_CLOUD_PROVIDER_REGION_CONFIGURATION_PROPERTY); 63 | } 64 | 65 | public static String getCloudProviderInstanceType() { 66 | return getStringProperty(Constants.RETIT_EMISSIONS_CLOUD_PROVIDER_INSTANCE_TYPE_CONFIGURATION_PROPERTY); 67 | } 68 | 69 | public static String getMicroarchitecture() { 70 | return getStringProperty(Constants.RETIT_EMISSIONS_MICROARCHITECTURE_CONFIGURATION_PROPERTY); 71 | } 72 | 73 | public static String getStorageType() { 74 | return getProperty(Constants.RETIT_EMISSIONS_STORAGE_TYPE_CONFIGURATION_PROPERTY, 75 | String::valueOf, Constants.RETIT_EMISSIONS_STORAGE_TYPE_CONFIGURATION_PROPERTY_VALUE_SSD); 76 | } 77 | 78 | /** 79 | * Returns the property of type String with the given propertyName. 80 | * 81 | * @param propertyName - the property to return. 82 | * @return - the value of the property or the default defined in Constants.RETIT_VALUE_NOT_SET. 83 | */ 84 | public static String getStringProperty(final String propertyName) { 85 | return getProperty(propertyName, String::valueOf, Constants.RETIT_VALUE_NOT_SET); 86 | } 87 | 88 | /** 89 | * Returns the property of type boolean with the given propertyName. 90 | * 91 | * @param propertyName - the property to return. 92 | * @return - the value of the property or false 93 | */ 94 | private static boolean getBooleanProperty(final String propertyName) { 95 | return getProperty(propertyName, Boolean::valueOf, false); 96 | } 97 | 98 | /** 99 | * Returns the property of type boolean with the given propertyName. 100 | * 101 | * @param propertyName - the property to return. 102 | * @param defaultValue - the defaultValue to return if the property is not set 103 | * @return - the value of the property or the provided defaultValue 104 | */ 105 | private static boolean getBooleanProperty(final String propertyName, final boolean defaultValue) { 106 | return getProperty(propertyName, Boolean::valueOf, defaultValue); 107 | } 108 | 109 | private static T getProperty(final String propertyName, final Converter converter, final T defaultValue) { 110 | if (propertyName == null) { 111 | return defaultValue; 112 | } 113 | if (System.getProperty(propertyName) != null) { 114 | return converter.convert(System.getProperty(propertyName)); 115 | } 116 | final String envVariableName = propertyName.toUpperCase(Locale.ENGLISH).replace(".", "_"); 117 | if (System.getenv(envVariableName) != null) { 118 | return converter.convert(System.getenv(envVariableName)); 119 | } 120 | if (LOGGER.isLoggable(Level.FINEST)) { 121 | LOGGER.log(Level.FINEST, "RETIT APM: Property {0} not configured defaulting to {1}", 122 | new Object[]{propertyName, (defaultValue != null ? defaultValue : "null")}); 123 | } 124 | return defaultValue; 125 | } 126 | 127 | /** 128 | * Sets the given boolean property to the provided value. 129 | * 130 | * @param propertyName - the property name to set. 131 | * @param value - the valueof the property. 132 | */ 133 | public static void setBooleanProperty(final String propertyName, final boolean value) { 134 | System.setProperty(propertyName, Boolean.toString(value)); 135 | } 136 | 137 | /** 138 | * Converts values of S to T. 139 | * 140 | * @param Source type. 141 | * @param Target type. 142 | */ 143 | @FunctionalInterface 144 | public interface Converter { 145 | /** 146 | * Converts the given value of type S to type T. 147 | * 148 | * @param toConvert - the value to convert. 149 | * @return - the converted value. 150 | */ 151 | T convert(S toConvert); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /extension/src/main/java/io/retit/opentelemetry/javaagent/extension/resources/linux/LinuxDataCollector.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RETIT GmbH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.retit.opentelemetry.javaagent.extension.resources.linux; 18 | 19 | import io.retit.opentelemetry.javaagent.extension.resources.common.IResourceDemandDataCollector; 20 | import io.retit.opentelemetry.javaagent.extension.resources.common.NativeFacade; 21 | import io.retit.opentelemetry.javaagent.extension.resources.common.CommonResourceDemandDataCollector; 22 | 23 | import java.io.IOException; 24 | import java.nio.charset.StandardCharsets; 25 | import java.nio.file.FileSystems; 26 | import java.nio.file.Files; 27 | import java.nio.file.Path; 28 | import java.util.List; 29 | import java.util.logging.Level; 30 | import java.util.logging.Logger; 31 | 32 | /** 33 | * An {@link IResourceDemandDataCollector 34 | * IResourceDemandDataCollector} which retrieves resource demands on Linux 35 | * systems. 36 | */ 37 | public class LinuxDataCollector extends CommonResourceDemandDataCollector { 38 | 39 | private static final Logger LOGGER = Logger.getLogger(LinuxDataCollector.class.getName()); 40 | 41 | private static final ThreadLocal THREAD_LOCAL_PROC_FS_READ_OVERHEAD = ThreadLocal.withInitial(() -> 0L); 42 | 43 | /** 44 | * Uses the symbolic link /proc/thread-self to avoid native calls for process and thread ID. 45 | * This symbolic link requires a linux kernel version higher than 3.14. 46 | */ 47 | private static final Path PROC_FS_THREAD_SELF_IO = FileSystems.getDefault().getPath("/proc/thread-self/io"); 48 | 49 | /** 50 | * Uses the symbolic link /proc/thread-self to avoid native calls for process and thread ID. 51 | * This symbolic link requires a linux kernel version higher than 3.14. 52 | */ 53 | private static final Path PROC_FS_THREAD_SELF_NET = FileSystems.getDefault().getPath("/proc/thread-self/net/dev"); 54 | 55 | private static final String READ_BYTES = "rchar"; 56 | private static final String WRITE_BYTES = "write_bytes"; 57 | 58 | @Override 59 | protected long getPlatformSpecificThreadCpuTime() { 60 | return NativeFacade.getCurrentThreadCpuTime(); 61 | } 62 | 63 | @Override 64 | public long[] getDiskBytesReadAndWritten() { 65 | /* 66 | * rchar: 476726516 wchar: 450053132 syscr: 1145703 syscw: 461006 67 | * read_bytes: 933888 write_bytes: 26984448 cancelled_write_bytes: 0 68 | */ 69 | long[] result = null; 70 | 71 | if (Files.exists(PROC_FS_THREAD_SELF_IO)) { 72 | try { 73 | byte[] filearray = Files.readAllBytes(PROC_FS_THREAD_SELF_IO); 74 | String text = new String(filearray, "UTF-8"); 75 | 76 | int startIndex = text.indexOf(READ_BYTES); 77 | if (startIndex == -1) { 78 | return new long[]{}; 79 | } 80 | startIndex += READ_BYTES.length() + 2; 81 | int endIndex = text.indexOf('\n', startIndex); 82 | long readBytes = Long.parseLong(text.substring(startIndex, endIndex)) - THREAD_LOCAL_PROC_FS_READ_OVERHEAD.get(); 83 | 84 | startIndex = text.indexOf(WRITE_BYTES); 85 | if (startIndex == -1) { 86 | return new long[]{}; 87 | } 88 | startIndex += WRITE_BYTES.length() + 2; 89 | endIndex = text.indexOf('\n', startIndex); 90 | long writeBytes = Long.parseLong(text.substring(startIndex, endIndex)); 91 | 92 | result = new long[2]; 93 | result[0] = readBytes; 94 | result[1] = writeBytes; 95 | 96 | THREAD_LOCAL_PROC_FS_READ_OVERHEAD.set(THREAD_LOCAL_PROC_FS_READ_OVERHEAD.get() + text.length()); 97 | } catch (IOException e) { 98 | LOGGER.log(Level.SEVERE, "Could not read disk io from proc fs " + e.getMessage(), e); 99 | } 100 | } else { 101 | result = new long[]{0, 0}; 102 | } 103 | return result; 104 | } 105 | 106 | @Override 107 | public long[] getNetworkBytesReadAndWritten() { 108 | 109 | /* 110 | * Inter-| Receive | Transmit 111 | * face |bytes packets errs drop fifo frame compressed multicast|bytes packets errs drop fifo colls carrier compressed 112 | * lo: 36066479 19065 0 0 0 0 0 0 36066479 19065 0 0 0 0 0 0 113 | * ens18: 3207350391 1881516 0 504589 0 0 0 0 337104919 705323 0 0 0 0 0 0 114 | * docker0: 15146519 121372 0 0 0 0 0 0 166707055 119803 0 0 0 0 0 0 115 | */ 116 | long[] result = null; 117 | 118 | if (Files.exists(PROC_FS_THREAD_SELF_NET)) { 119 | try { 120 | long totalReceivedBytes = 0; 121 | long totalTransmittedBytes = 0; 122 | 123 | List fileContent = Files.readAllLines(PROC_FS_THREAD_SELF_NET, StandardCharsets.UTF_8); 124 | for (String line : fileContent) { 125 | String[] parts = line.trim().split("\\s+"); 126 | int numberOfNetworkParametersPerLine = 17; 127 | if (parts.length >= numberOfNetworkParametersPerLine) { 128 | long receivedBytes = Long.parseLong(parts[1]); 129 | long transmittedBytes = Long.parseLong(parts[9]); 130 | totalReceivedBytes += receivedBytes; 131 | totalTransmittedBytes += transmittedBytes; 132 | } 133 | } 134 | result = new long[2]; 135 | result[0] = totalReceivedBytes; 136 | result[1] = totalTransmittedBytes; 137 | } catch (IOException e) { 138 | LOGGER.log(Level.SEVERE, "Could not read disk io from proc fs " + e.getMessage(), e); 139 | } 140 | } else { 141 | result = new long[]{0, 0}; 142 | } 143 | 144 | return result; 145 | } 146 | } 147 | 148 | -------------------------------------------------------------------------------- /extension/src/main/java/io/retit/opentelemetry/javaagent/extension/emissions/CloudCarbonFootprintCoefficients.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 RETIT GmbH 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.retit.opentelemetry.javaagent.extension.emissions; 18 | 19 | /** 20 | * Class containing emission coefficients for different cloud resources. 21 | * These coefficients follow the approach of the Cloud Carbon Footprint project outlined here: 22 | * https://www.cloudcarbonfootprint.org/docs/methodology 23 | */ 24 | public class CloudCarbonFootprintCoefficients { 25 | 26 | /** 27 | * HDD storage energy consumption in Wh per TB per hour. 28 | * Source: https://www.cloudcarbonfootprint.org/docs/methodology#appendix-i-energy-coefficients 29 | */ 30 | public static final double STORAGE_ENERGY_CONSUMPTION_WH_HDD_PER_TB_HOUR = 0.65; 31 | 32 | /** 33 | * SSD storage energy consumption in Wh per TB per hour. 34 | * Source: https://www.cloudcarbonfootprint.org/docs/methodology#appendix-i-energy-coefficients 35 | */ 36 | public static final double STORAGE_ENERGY_CONSUMPTION_WH_SSD_PER_TB_HOUR = 1.2; 37 | 38 | /** 39 | * Average minimum watt consumption for AWS. 40 | * Source: ... 41 | * The values are calculated in the Jupyter notebook, but are not added to the Github repo. 42 | * They're also provided in ..., but havent seem to be updated there 43 | */ 44 | public static final double AVERAGE_MIN_WATT_AWS = 1.14; 45 | 46 | /** 47 | * Average maximum watt consumption for AWS. 48 | * Source: ... 49 | * The values are calculated in the Jupyter notebook, but are not added to the Github repo. 50 | * They're also provided in ..., but havent seem to be updated there 51 | */ 52 | public static final double AVERAGE_MAX_WATT_AWS = 4.34; 53 | 54 | /** 55 | * Average minimum watt consumption for Azure. 56 | * Source: ... 57 | * The values are calculated in the Jupyter notebook, but are not added to the Github repo. 58 | * They're also provided in ..., but havent seem to be updated there 59 | */ 60 | public static final double AVERAGE_MIN_WATT_AZURE = 0.85; 61 | 62 | /** 63 | * Average maximum watt consumption for Azure. 64 | * Source: ... 65 | * The values are calculated in the Jupyter notebook, but are not added to the Github repo. 66 | * They're also provided in ..., but havent seem to be updated there 67 | */ 68 | public static final double AVERAGE_MAX_WATT_AZURE = 3.69; 69 | 70 | /** 71 | * Average minimum watt consumption for GCP. 72 | * Source: ... 73 | * The values are calculated in the Jupyter notebook, but are not added to the Github repo. 74 | * They're also provided in ..., but havent seem to be updated there 75 | */ 76 | public static final double AVERAGE_MIN_WATT_GCP = 0.68; 77 | 78 | /** 79 | * Average maximum watt consumption for GCP. 80 | * Source: ... 81 | * The values are calculated in the Jupyter notebook, but are not added to the Github repo. 82 | * They're also provided in ..., but havent seem to be updated there 83 | */ 84 | public static final double AVERAGE_MAX_WATT_GCP = 3.77; 85 | 86 | /** 87 | * Coefficient for breaking down total kg embodied emissions to gram per hour based on four year usage. 88 | * This is calculated as follows: (1000 (kg to g) / 4 (years) / 12 (months per year) / 30 (days per month) / 24 (hours per day). 89 | * Source: ... 90 | */ 91 | public static final double TOTAL_EMBODIED_EMISSIONS_TO_GRAMS_PER_HOUR = 0.0289; 92 | 93 | /** 94 | * Coefficient for calculating memory emissions in kWh per GB-hour. 95 | * It is the same for all cloud providers. 96 | * Source: https://www.cloudcarbonfootprint.org/docs/methodology/#appendix-i-energy-coefficients 97 | */ 98 | public static final double MEMORY_KWH_PER_GB_HOUR = 0.000_392; 99 | 100 | /** 101 | * Coefficient for calculating network emissions in kWh per GB-hour. 102 | * Source: ... 103 | */ 104 | public static final double NETWORK_KWH_PER_GB_HOUR = 0.001; 105 | 106 | /** 107 | * Coefficient for Power Usage Effectiveness (PUE) for AWS. 108 | * Source: ... 109 | */ 110 | public static final double AWS_PUE = 1.135; 111 | 112 | /** 113 | * Coefficient for Power Usage Effectiveness (PUE) for Azure. 114 | * Source: ... 115 | */ 116 | public static final double AZURE_PUE = 1.185; 117 | 118 | /** 119 | * Coefficient for Power Usage Effectiveness (PUE) for GCP. 120 | * Source: ... 121 | */ 122 | public static final double GCP_PUE = 1.1; 123 | } 124 | -------------------------------------------------------------------------------- /extension/src/test/java/io/retit/opentelemetry/javaagent/extension/common/ContainerLogMetricAndSpanExtractingTest.java: -------------------------------------------------------------------------------- 1 | package io.retit.opentelemetry.javaagent.extension.common; 2 | 3 | import io.retit.opentelemetry.javaagent.extension.commons.Constants; 4 | import org.jetbrains.annotations.NotNull; 5 | import org.junit.jupiter.api.Assertions; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.testcontainers.containers.GenericContainer; 9 | 10 | import java.util.ArrayList; 11 | import java.util.HashMap; 12 | import java.util.List; 13 | import java.util.Map; 14 | 15 | import static org.junit.jupiter.api.Assertions.assertEquals; 16 | import static org.junit.jupiter.api.Assertions.assertNotEquals; 17 | import static org.junit.jupiter.api.Assertions.assertNotNull; 18 | import static org.junit.jupiter.api.Assertions.assertNull; 19 | 20 | public abstract class ContainerLogMetricAndSpanExtractingTest { 21 | private static final Logger LOGGER = LoggerFactory.getLogger(ContainerLogMetricAndSpanExtractingTest.class); 22 | 23 | // Metrics to be tested 24 | protected static final String[] METRIC_NAMES = {Constants.SPAN_ATTRIBUTE_PROCESS_CPU_TIME, "io.retit.emissions.cpu.power.min", "io.retit.emissions.cpu.power.max", 25 | "io.retit.emissions.embodied.emissions.minute.mg", "io.retit.emissions.memory.energy.gb.minute", 26 | "io.retit.emissions.storage.energy.gb.minute", "io.retit.emissions.network.energy.gb.minute", 27 | "io.retit.emissions.pue", "io.retit.emissions.gef", "io.retit.resource.demand.storage.bytes", 28 | "io.retit.resource.demand.memory.bytes", "io.retit.resource.demand.network.bytes", 29 | "io.retit.resource.demand.cpu.ms" 30 | }; 31 | 32 | protected GenericContainer applicationContainer; 33 | protected Map> spanDemands = new HashMap<>(); 34 | protected List metricDemands = new ArrayList<>(); 35 | protected String CONTAINER_URL = "http://localhost:"; 36 | 37 | protected void executeContainer(final int portToOpen) { 38 | // Start container and attach parser to log output 39 | applicationContainer.start(); 40 | if (portToOpen != -1) { 41 | CONTAINER_URL = CONTAINER_URL + applicationContainer.getMappedPort(portToOpen); 42 | } 43 | applicationContainer.followOutput(outputFrame -> { 44 | String logOutput = outputFrame.getUtf8String(); 45 | addToSpanDemands(logOutput); 46 | addToMetricDemands(logOutput); 47 | }); 48 | } 49 | 50 | private void addToSpanDemands(String logOutput) { 51 | // in case of span 52 | if (logOutput.contains("io.opentelemetry.exporter.logging.LoggingSpanExporter")) { 53 | String operationName = extractOperationNameFromLogOutput(logOutput); 54 | SpanDemand spanDemand = SpanDemand.parseProperties(logOutput); 55 | List opSpanDemands; 56 | if (spanDemands.containsKey(operationName)) { 57 | opSpanDemands = spanDemands.get(operationName); 58 | } else { 59 | opSpanDemands = new ArrayList<>(); 60 | spanDemands.put(operationName, opSpanDemands); 61 | } 62 | opSpanDemands.add(spanDemand); 63 | } 64 | } 65 | 66 | private void addToMetricDemands(String logOutput) { 67 | if (logOutput.contains("io.opentelemetry.exporter.logging.LoggingMetricExporter")) { 68 | LOGGER.info("Processing metric " + logOutput); 69 | List demands = MetricDemand.extractMetricValuesFromLog(logOutput); 70 | if (demands != null) { 71 | metricDemands.addAll(demands); 72 | } 73 | } 74 | } 75 | 76 | 77 | private String extractOperationNameFromLogOutput(String logOutput) { 78 | LOGGER.info("Processing span " + logOutput); 79 | return logOutput.substring(logOutput.indexOf('\'') + 1, logOutput.lastIndexOf('\'')); 80 | } 81 | 82 | protected void waitUntilContainerIsStopped() { 83 | // Wait until container has stopped running 84 | while (applicationContainer.isRunning()) { 85 | try { 86 | Thread.sleep(500); 87 | } catch (InterruptedException e) { 88 | LOGGER.error("Interupted", e); 89 | } 90 | } 91 | } 92 | 93 | @NotNull 94 | protected static boolean isGcSpanName(String s) { 95 | return Constants.JAVA_AGENT_GC_OPERATION_NAME_MAJOR_FREE.equals(s) || Constants.JAVA_AGENT_GC_OPERATION_NAME_MINOR_FREE.equals(s); 96 | } 97 | 98 | protected void assertFullSpanDataContent(final String sampleMethod) { 99 | Assertions.assertFalse(spanDemands.isEmpty()); 100 | for (Map.Entry> spanDemandEntryList : spanDemands.entrySet()) { 101 | if ("".equals(spanDemandEntryList.getKey())) { 102 | continue; 103 | } else if (!isGcSpanName(spanDemandEntryList.getKey())) { 104 | assertEquals(1, spanDemandEntryList.getValue().size()); 105 | } 106 | for (SpanDemand spanDemandEntry : spanDemandEntryList.getValue()) { 107 | assertNotEquals(0, spanDemandEntry.startCpuTime); 108 | assertNotEquals(0, spanDemandEntry.endCpuTime); 109 | assertNotEquals(0, spanDemandEntry.startHeapDemand); 110 | assertNotEquals(0, spanDemandEntry.endHeapDemand); 111 | if (spanDemandEntryList.getKey().contains(sampleMethod)) { 112 | assertNull(spanDemandEntry.totalHeapSize); 113 | } else { 114 | assertNotEquals(0, spanDemandEntry.totalHeapSize); 115 | } 116 | assertNotEquals(0, spanDemandEntry.startDiskReadDemand); 117 | assertNotEquals(0, spanDemandEntry.endDiskReadDemand); 118 | if (!isGcSpanName(spanDemandEntryList.getKey())) { 119 | if (spanDemandEntry.endDiskWriteDemand != 0) { 120 | assertNotEquals(0, spanDemandEntry.endDiskWriteDemand); 121 | assertNotEquals(0, spanDemandEntry.endDiskWriteDemand - spanDemandEntry.startDiskWriteDemand); 122 | } 123 | } 124 | assertNotEquals(0, spanDemandEntry.logSystemTime); 125 | 126 | assertNotNull(spanDemandEntry.startNetworkReadDemand); 127 | assertNotNull(spanDemandEntry.endNetworkReadDemand); 128 | assertNotNull(spanDemandEntry.startNetworkWriteDemand); 129 | assertNotNull(spanDemandEntry.endNetworkWriteDemand); 130 | assertNotEquals(0, spanDemandEntry.startThreadId); 131 | assertNotEquals(0, spanDemandEntry.endThreadId); 132 | } 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /examples/simple-jdk8-application/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | simple-jdk8-application 5 | 6 | 7 | io.retit 8 | examples 9 | 0.0.1-SNAPSHOT 10 | 11 | 12 | 13 | 14 | 15 | io.opentelemetry.instrumentation 16 | opentelemetry-instrumentation-annotations 17 | 18 | 19 | 20 | io.opentelemetry 21 | opentelemetry-api 22 | 23 | 24 | 25 | 26 | simple-jdk8-application 27 | 28 | 29 | org.apache.maven.plugins 30 | maven-compiler-plugin 31 | 32 | 8 33 | 8 34 | 35 | 36 | 37 | org.apache.maven.plugins 38 | maven-shade-plugin 39 | 3.6.1 40 | 41 | 42 | package 43 | 44 | shade 45 | 46 | 47 | 48 | 49 | 50 | org.apache.maven.plugins 51 | maven-dependency-plugin 52 | 3.9.0 53 | 54 | 55 | copy-opentelemetry 56 | package 57 | 58 | copy 59 | 60 | 61 | 62 | 63 | io.opentelemetry.javaagent 64 | opentelemetry-javaagent 65 | jar 66 | opentelemetry-javaagent-all.jar 67 | 68 | 69 | io.retit 70 | extension 71 | jar 72 | ${project.version} 73 | io.retit.opentelemetry.javaagent.extension.jar 74 | 75 | 76 | 77 | 78 | 79 | 80 | ${project.build.directory}/jib 81 | 82 | 83 | 84 | maven-resources-plugin 85 | 3.4.0 86 | 87 | 88 | copy-resources 89 | 90 | verify 91 | 92 | copy-resources 93 | 94 | 95 | ${project.build.directory}/jib 96 | 97 | 98 | ${project.build.directory} 99 | 100 | simple-jdk8-application-${project.version}.jar 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | com.google.cloud.tools 111 | jib-maven-plugin 112 | ${jib.version} 113 | 114 | 115 | eclipse-temurin:8-jre 116 | 117 | 118 | simple-jdk8-application:${project.version} 119 | 120 | feature 121 | 122 | 123 | 124 | 125 | 126 | ${project.build.directory}/jib 127 | 128 | 129 | 130 | 131 | 132 | package 133 | 134 | dockerBuild 135 | 136 | 137 | 138 | 139 | 140 | org.apache.maven.plugins 141 | maven-jar-plugin 142 | 3.5.0 143 | 144 | 145 | 146 | io.retit.opentelemetry.SampleApplication 147 | 148 | 149 | 150 | 151 | 152 | 153 | -------------------------------------------------------------------------------- /examples/simple-jdk21-application/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | simple-jdk21-application 5 | 6 | 7 | io.retit 8 | examples 9 | 0.0.1-SNAPSHOT 10 | 11 | 12 | 13 | 14 | 15 | io.opentelemetry.instrumentation 16 | opentelemetry-instrumentation-annotations 17 | 18 | 19 | 20 | io.opentelemetry 21 | opentelemetry-api 22 | 23 | 24 | 25 | 26 | simple-jdk21-application 27 | 28 | 29 | org.apache.maven.plugins 30 | maven-compiler-plugin 31 | 32 | 21 33 | 21 34 | 35 | 36 | 37 | org.apache.maven.plugins 38 | maven-shade-plugin 39 | 3.6.1 40 | 41 | 42 | package 43 | 44 | shade 45 | 46 | 47 | 48 | 49 | 50 | org.apache.maven.plugins 51 | maven-dependency-plugin 52 | 3.9.0 53 | 54 | 55 | copy-opentelemetry 56 | package 57 | 58 | copy 59 | 60 | 61 | 62 | 63 | io.opentelemetry.javaagent 64 | opentelemetry-javaagent 65 | jar 66 | opentelemetry-javaagent-all.jar 67 | 68 | 69 | io.retit 70 | extension 71 | jar 72 | ${project.version} 73 | io.retit.opentelemetry.javaagent.extension.jar 74 | 75 | 76 | 77 | 78 | 79 | 80 | ${project.build.directory}/jib 81 | 82 | 83 | 84 | maven-resources-plugin 85 | 3.4.0 86 | 87 | 88 | copy-resources 89 | 90 | verify 91 | 92 | copy-resources 93 | 94 | 95 | ${project.build.directory}/jib 96 | 97 | 98 | ${project.build.directory} 99 | 100 | simple-jdk21-application-${project.version}.jar 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | com.google.cloud.tools 111 | jib-maven-plugin 112 | ${jib.version} 113 | 114 | 115 | ${jdk21.base.container} 116 | 117 | 118 | simple-jdk21-application:${project.version} 119 | 120 | feature 121 | 122 | 123 | 124 | 125 | 126 | ${project.build.directory}/jib 127 | 128 | 129 | 130 | 131 | 132 | package 133 | 134 | dockerBuild 135 | 136 | 137 | 138 | 139 | 140 | org.apache.maven.plugins 141 | maven-jar-plugin 142 | 3.5.0 143 | 144 | 145 | 146 | io.retit.opentelemetry.SampleApplication 147 | 148 | 149 | 150 | 151 | 152 | 153 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | io.retit 6 | opentelemetry-javaagent-extension 7 | 0.0.1-SNAPSHOT 8 | pom 9 | 10 | 11 | UTF-8 12 | 1.1.1 13 | 2.23.0 14 | 15 | 16 | 17 | extension 18 | examples 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | io.opentelemetry 27 | opentelemetry-bom 28 | 1.57.0 29 | pom 30 | import 31 | 32 | 33 | 34 | io.opentelemetry.instrumentation 35 | opentelemetry-instrumentation-annotations 36 | 2.23.0 37 | 38 | 39 | 40 | 41 | org.junit.jupiter 42 | junit-jupiter 43 | 6.0.1 44 | test 45 | 46 | 47 | 48 | 49 | org.testcontainers 50 | junit-jupiter 51 | 1.21.3 52 | test 53 | 54 | 55 | 56 | 57 | io.opentelemetry.javaagent 58 | opentelemetry-javaagent 59 | ${opentelemetry.agent.version} 60 | test 61 | 62 | 63 | 64 | 65 | 66 | org.mockito 67 | mockito-core 68 | 69 | 5.21.0 70 | test 71 | 72 | 73 | 74 | 75 | org.slf4j 76 | slf4j-simple 77 | 2.0.17 78 | test 79 | 80 | 81 | 82 | 83 | 84 | net.java.dev.jna 85 | jna 86 | 5.18.1 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | maven-compiler-plugin 96 | 3.14.1 97 | 98 | 1.8 99 | 1.8 100 | 101 | 102 | 103 | maven-assembly-plugin 104 | 3.8.0 105 | 106 | 107 | org.apache.maven.plugins 108 | maven-wrapper-plugin 109 | 3.3.4 110 | 111 | 112 | org.apache.maven.plugins 113 | maven-checkstyle-plugin 114 | 3.6.0 115 | 116 | etc/checkstyle-configuration.xml 117 | true 118 | true 119 | true 120 | false 121 | 122 | 123 | 124 | validate 125 | validate 126 | 127 | check 128 | 129 | 130 | 131 | 132 | 133 | org.apache.maven.plugins 134 | maven-pmd-plugin 135 | 3.28.0 136 | 137 | true 138 | 139 | etc/pmd-configuration.xml 140 | 141 | 142 | 143 | 144 | 145 | com.github.spotbugs 146 | spotbugs-maven-plugin 147 | 4.9.8.2 148 | 149 | ${user.dir}/etc/spotbugs-exclusion-filter.xml 150 | 151 | 152 | 153 | 154 | org.codehaus.mojo 155 | license-maven-plugin 156 | 2.7.0 157 | 158 | 159 | download-licenses 160 | 161 | download-licenses 162 | 163 | 164 | 165 | 166 | 167 | 168 | --------------------------------------------------------------------------------