├── 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 | 
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 | 
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 | 
--------------------------------------------------------------------------------
/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 | 
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 | 
--------------------------------------------------------------------------------
/.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 | 
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 | 
--------------------------------------------------------------------------------
/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 | 
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 | 
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 |
--------------------------------------------------------------------------------