From 0956fd7e1853f9d5cb91e96626392a0f50269df5 Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Wed, 21 May 2025 06:22:52 -0400 Subject: [PATCH 1/3] add metric interceptor --- .gitignore | 1 + docs/instrumentation-list.yaml | 50 ++++++- instrumentation-docs/build.gradle.kts | 1 + instrumentation-docs/collect.sh | 51 +++++++ instrumentation-docs/readme.md | 43 ++++++ .../docs/InstrumentationAnalyzer.java | 13 ++ .../docs/internal/DependencyInfo.java | 3 +- .../docs/internal/EmittedMetrics.java | 137 ++++++++++++++++++ .../internal/InstrumentationMetaData.java | 12 +- .../docs/internal/InstrumentationModule.java | 33 ++++- .../docs/parsers/GradleParser.java | 3 + .../docs/utils/FileManager.java | 33 +++++ .../docs/utils/YamlHelper.java | 111 ++++++++++---- .../docs/InstrumentationAnalyzerTest.java | 2 + .../docs/utils/FileManagerTest.java | 1 + .../docs/utils/YamlHelperTest.java | 69 +++++++++ .../javaagent/build.gradle.kts | 4 + testing-common/build.gradle.kts | 1 + .../testing/AgentTestRunner.java | 19 ++- .../testing/InstrumentationTestRunner.java | 22 ++- .../testing/internal/MetaDataCollector.java | 119 +++++++++++++++ 21 files changed, 683 insertions(+), 45 deletions(-) create mode 100755 instrumentation-docs/collect.sh create mode 100644 instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/internal/EmittedMetrics.java create mode 100644 testing-common/src/main/java/io/opentelemetry/instrumentation/testing/internal/MetaDataCollector.java diff --git a/.gitignore b/.gitignore index d5e39e5b25bd..befdd5814a2e 100644 --- a/.gitignore +++ b/.gitignore @@ -57,6 +57,7 @@ derby.log hs_err_pid* replay_pid* .attach_pid* +**/.telemetry* !java-agent/benchmark/releases/*.jar diff --git a/docs/instrumentation-list.yaml b/docs/instrumentation-list.yaml index c3c45d36c49e..ca420e9201cb 100644 --- a/docs/instrumentation-list.yaml +++ b/docs/instrumentation-list.yaml @@ -54,6 +54,46 @@ libraries: - com.alibaba:druid:(,) library: - com.alibaba:druid:1.0.0 + metrics: + - name: db.client.connections.usage + description: The number of connections that are currently in state described + by the state attribute. + type: LONG_SUM + unit: connections + attributes: + - name: pool.name + type: STRING + - name: state + type: STRING + - name: db.client.connections.pending_requests + description: The number of pending requests for an open connection, cumulative + for the entire pool. + type: LONG_SUM + unit: requests + attributes: + - name: pool.name + type: STRING + - name: db.client.connections.max + description: The maximum number of open connections allowed. + type: LONG_SUM + unit: connections + attributes: + - name: pool.name + type: STRING + - name: db.client.connections.idle.min + description: The minimum number of idle open connections allowed. + type: LONG_SUM + unit: connections + attributes: + - name: pool.name + type: STRING + - name: db.client.connections.idle.max + description: The maximum number of idle open connections allowed. + type: LONG_SUM + unit: connections + attributes: + - name: pool.name + type: STRING apache: - name: apache-shenyu-2.4 source_path: instrumentation/apache-shenyu-2.4 @@ -831,7 +871,7 @@ libraries: services. type: map default: '' - - name: otel.instrumentation.jdbc.capture-query-parameters + - name: otel.instrumentation.jdbc.experimental.capture-query-parameters description: | Sets whether the query parameters should be captured as span attributes named db.query.parameter.<key>. Enabling this option disables the statement sanitization.

WARNING: captured query parameters may contain sensitive information such as passwords, personally identifiable information or protected health info. type: boolean @@ -1923,14 +1963,14 @@ libraries: name: io.opentelemetry.vertx-sql-client-4.0 target_versions: javaagent: - - io.vertx:vertx-sql-client:[4.0.0,) + - io.vertx:vertx-sql-client:[4.0.0,5) - name: vertx-http-client-4.0 source_path: instrumentation/vertx/vertx-http-client/vertx-http-client-4.0 scope: name: io.opentelemetry.vertx-http-client-4.0 target_versions: javaagent: - - io.vertx:vertx-core:[4.0.0,) + - io.vertx:vertx-core:[4.0.0,5) - name: vertx-rx-java-3.5 source_path: instrumentation/vertx/vertx-rx-java-3.5 scope: @@ -2051,6 +2091,10 @@ internal: source_path: instrumentation/opentelemetry-api/opentelemetry-api-1.32 scope: name: io.opentelemetry.opentelemetry-api-1.32 +- name: opentelemetry-api-1.50 + source_path: instrumentation/opentelemetry-api/opentelemetry-api-1.50 + scope: + name: io.opentelemetry.opentelemetry-api-1.50 - name: opentelemetry-api-1.42 source_path: instrumentation/opentelemetry-api/opentelemetry-api-1.42 scope: diff --git a/instrumentation-docs/build.gradle.kts b/instrumentation-docs/build.gradle.kts index 092ae517b908..b733a3ce8d8a 100644 --- a/instrumentation-docs/build.gradle.kts +++ b/instrumentation-docs/build.gradle.kts @@ -1,5 +1,6 @@ plugins { id("otel.java-conventions") + id("otel.nullaway-conventions") } otelJava { diff --git a/instrumentation-docs/collect.sh b/instrumentation-docs/collect.sh new file mode 100755 index 000000000000..8d61395d70a4 --- /dev/null +++ b/instrumentation-docs/collect.sh @@ -0,0 +1,51 @@ +#!/bin/bash + +instrumentations=( + "alibaba-druid-1.0:javaagent:test" +) + +# Initialize an empty string to hold the Gradle tasks +gradle_tasks="" + +# Iterate over each instrumentation +for instrumentation in "${instrumentations[@]}"; do + # Extract the parts of the instrumentation + IFS=':' read -r -a parts <<< "$instrumentation" + module="${parts[0]}" + version="${parts[1]}" + type="${parts[2]}" + suffix="${parts[3]}" + + # Assemble the path to the instrumentation + path="instrumentation/$module/$version" + + # Remove any occurrence of /javaagent/ or /library/ from the path + path=$(echo "$path" | sed -e 's/\/javaagent//g' -e 's/\/library//g') + + # Debugging: Print the path being checked + echo "Checking path: $path/.telemetry" + + # Check if the .telemetry directory exists and remove it if it does + if [ -d "$path/.telemetry" ]; then + echo "Removing directory: $path/.telemetry" + rm -rf "$path/.telemetry" + else + echo "Directory does not exist: $path/.telemetry" + fi + + # Append the Gradle task to the gradle_tasks string with a colon between type and suffix if suffix is non-empty + if [ -n "$suffix" ]; then + gradle_tasks+=":instrumentation:$module:$version:$type:$suffix" + else + gradle_tasks+=":instrumentation:$module:$version:$type" + fi +done + +# rerun-tasks is used to force re-running tests that might be cached +echo Running: ./gradlew "$gradle_tasks" -PcollectMetadata=true --rerun-tasks +./gradlew "$gradle_tasks" -PcollectMetadata=true --rerun-tasks +./gradlew :instrumentation-docs:runAnalysis + +# Remove all .telemetry directories recursively from the project root +echo "Searching for and removing all .telemetry directories..." +find . -type d -name ".telemetry" -exec rm -rf {} + diff --git a/instrumentation-docs/readme.md b/instrumentation-docs/readme.md index 802e66499c9f..142b7e0a934e 100644 --- a/instrumentation-docs/readme.md +++ b/instrumentation-docs/readme.md @@ -8,6 +8,36 @@ Run the analysis to update the instrumentation-list.yaml: `./gradlew :instrumentation-docs:runAnalysis` +### Metric collection + +Until this process is ready for all instrumentations, each module will be modified to include a +system property feature flag configured for when the tests run: + +```kotlin +tasks { + test { + systemProperty("collectMetadata", findProperty("collectMetadata")?.toString() ?: "false") + ... + } +} +``` + +Then, prior to running the analyzer, run the following command to generate `.telemetry` files: + +`./gradlew test -collectMetadata=true` + +Then run the doc generator + +`./gradlew :instrumentation-docs:runAnalysis` + +or use the helper script that will run only the currently supported tests (recommended): + +```bash +./instrumentation-docs/collect.sh +``` + +This script will also clean up all `.telemetry` files after the analysis is done. + ## Instrumentation Hierarchy An "InstrumentationModule" represents a module that that targets specific code in a @@ -70,6 +100,8 @@ public class SpringWebInstrumentationModule extends InstrumentationModule * configurations settings * List of settings that are available for the instrumentation module * Each setting has a name, description, type, and default value +* metrics + * List of metrics that the instrumentation module collects, including the metric name, description, type, and attributes ## Methodology @@ -107,3 +139,14 @@ name is determined by the instrumentation module name: `io.opentelemetry.{instr We will implement gatherers for the schemaUrl and scope attributes when instrumentations start implementing them. + +### Metrics + +In order to identify what metrics are emitted from instrumentations, we can hook into the +`InstrumentationTestRunner` class and collect the metrics generated during runs. We can then +leverage the `afterTestClass()` in the Agent and library test runners to then write this information +into temporary files. When we analyze the instrumentation modules, we can read these files and +generate the metrics section of the instrumentation-list.yaml file. + +The data is written into a `.telemetry` directory in the root of each instrumentation module. This +data will be excluded from git and just generated on demand. diff --git a/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/InstrumentationAnalyzer.java b/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/InstrumentationAnalyzer.java index a461cceefebb..5489c61256eb 100644 --- a/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/InstrumentationAnalyzer.java +++ b/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/InstrumentationAnalyzer.java @@ -10,6 +10,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.exc.ValueInstantiationException; import io.opentelemetry.instrumentation.docs.internal.DependencyInfo; +import io.opentelemetry.instrumentation.docs.internal.EmittedMetrics; import io.opentelemetry.instrumentation.docs.internal.InstrumentationModule; import io.opentelemetry.instrumentation.docs.internal.InstrumentationType; import io.opentelemetry.instrumentation.docs.utils.FileManager; @@ -84,6 +85,14 @@ List analyze() throws JsonProcessingException { throw e; } } + + String emittedMetrics = fileManager.getMetrics(module.getSrcPath()); + if (emittedMetrics != null) { + EmittedMetrics metrics = YamlHelper.emittedMetricsParser(emittedMetrics); + if (metrics != null && metrics.getMetrics() != null) { + module.setMetrics(metrics.getMetrics()); + } + } } return modules; } @@ -92,6 +101,10 @@ void analyzeVersions(List files, InstrumentationModule module) { Map> versions = new HashMap<>(); for (String file : files) { String fileContents = fileManager.readFileToString(file); + if (fileContents == null) { + continue; + } + DependencyInfo results = null; if (file.contains("/javaagent/")) { diff --git a/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/internal/DependencyInfo.java b/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/internal/DependencyInfo.java index 1efa3806766c..5789b7c40976 100644 --- a/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/internal/DependencyInfo.java +++ b/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/internal/DependencyInfo.java @@ -6,9 +6,10 @@ package io.opentelemetry.instrumentation.docs.internal; import java.util.Set; +import javax.annotation.Nullable; /** * This class is internal and is hence not for public use. Its APIs are unstable and can change at * any time. */ -public record DependencyInfo(Set versions, Integer minJavaVersionSupported) {} +public record DependencyInfo(Set versions, @Nullable Integer minJavaVersionSupported) {} diff --git a/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/internal/EmittedMetrics.java b/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/internal/EmittedMetrics.java new file mode 100644 index 000000000000..997468a245f8 --- /dev/null +++ b/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/internal/EmittedMetrics.java @@ -0,0 +1,137 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.docs.internal; + +import java.util.ArrayList; +import java.util.List; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +public class EmittedMetrics { + private List metrics; + + public EmittedMetrics() { + this.metrics = new ArrayList<>(); + } + + public EmittedMetrics(List metrics) { + this.metrics = metrics; + } + + public List getMetrics() { + return metrics; + } + + public void setMetrics(List metrics) { + this.metrics = metrics; + } + + /** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ + public static class Metric { + private String name; + private String description; + private String type; + private String unit; + private List attributes; + + public Metric( + String name, String description, String type, String unit, List attributes) { + this.name = name; + this.description = description; + this.type = type; + this.unit = unit; + this.attributes = attributes; + } + + public Metric() { + this.name = ""; + this.description = ""; + this.type = ""; + this.unit = ""; + this.attributes = new ArrayList<>(); + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getUnit() { + return unit; + } + + public void setUnit(String unit) { + this.unit = unit; + } + + public List getAttributes() { + return attributes; + } + + public void setAttributes(List attributes) { + this.attributes = attributes; + } + } + + /** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ + public static class Attribute { + private String name; + private String type; + + public Attribute() { + this.name = ""; + this.type = ""; + } + + public Attribute(String name, String type) { + this.name = name; + this.type = type; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + } +} diff --git a/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/internal/InstrumentationMetaData.java b/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/internal/InstrumentationMetaData.java index 48cd0dbd61c3..d43a8bd2c748 100644 --- a/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/internal/InstrumentationMetaData.java +++ b/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/internal/InstrumentationMetaData.java @@ -27,13 +27,15 @@ public class InstrumentationMetaData { private List configurations = Collections.emptyList(); - public InstrumentationMetaData() {} + public InstrumentationMetaData() { + this.classification = InstrumentationClassification.LIBRARY.toString(); + } public InstrumentationMetaData( - String description, + @Nullable String description, String classification, - Boolean disabledByDefault, - List configurations) { + @Nullable Boolean disabledByDefault, + @Nullable List configurations) { this.classification = classification; this.disabledByDefault = disabledByDefault; this.description = description; @@ -60,7 +62,7 @@ public void setDescription(@Nullable String description) { this.description = description; } - public void setClassification(@Nullable String classification) { + public void setClassification(String classification) { this.classification = classification; } diff --git a/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/internal/InstrumentationModule.java b/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/internal/InstrumentationModule.java index ee89e9704b7d..dba4d64c9b19 100644 --- a/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/internal/InstrumentationModule.java +++ b/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/internal/InstrumentationModule.java @@ -9,6 +9,7 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; import io.opentelemetry.sdk.common.InstrumentationScopeInfo; +import java.util.List; import java.util.Map; import java.util.Set; import javax.annotation.Nullable; @@ -25,6 +26,7 @@ public class InstrumentationModule { private final String namespace; private final String group; private final InstrumentationScopeInfo scopeInfo; + @Nullable private List metrics; @Nullable private Map> targetVersions; @@ -46,6 +48,7 @@ public InstrumentationModule(Builder builder) { this.instrumentationName = builder.instrumentationName; this.namespace = builder.namespace; this.group = builder.group; + this.metrics = builder.metrics; this.metadata = builder.metadata; this.targetVersions = builder.targetVersions; this.minJavaVersion = builder.minJavaVersion; @@ -90,6 +93,11 @@ public Integer getMinJavaVersion() { return minJavaVersion; } + @Nullable + public List getMetrics() { + return metrics; + } + public void setTargetVersions(Map> targetVersions) { this.targetVersions = targetVersions; } @@ -102,18 +110,23 @@ public void setMinJavaVersion(Integer minJavaVersion) { this.minJavaVersion = minJavaVersion; } + public void setMetrics(List metrics) { + this.metrics = metrics; + } + /** * This class is internal and is hence not for public use. Its APIs are unstable and can change at * any time. */ public static class Builder { - private String srcPath; - private String instrumentationName; - private String namespace; - private String group; - private Integer minJavaVersion; - private InstrumentationMetaData metadata; - private Map> targetVersions; + @Nullable private String srcPath; + @Nullable private String instrumentationName; + @Nullable private String namespace; + @Nullable private String group; + @Nullable private Integer minJavaVersion; + @Nullable private InstrumentationMetaData metadata; + @Nullable private Map> targetVersions; + @Nullable private List metrics; @CanIgnoreReturnValue public Builder srcPath(String srcPath) { @@ -157,6 +170,12 @@ public Builder targetVersions(Map> targetVersio return this; } + @CanIgnoreReturnValue + public Builder metrics(List metrics) { + this.metrics = metrics; + return this; + } + public InstrumentationModule build() { return new InstrumentationModule(this); } diff --git a/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/parsers/GradleParser.java b/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/parsers/GradleParser.java index 1f3bf2dcb7ae..4fe246a4aaba 100644 --- a/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/parsers/GradleParser.java +++ b/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/parsers/GradleParser.java @@ -15,6 +15,7 @@ import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; +import javax.annotation.Nullable; public class GradleParser { @@ -155,6 +156,7 @@ private static DependencyInfo parseLibraryDependencies( return new DependencyInfo(results, minJavaVersion); } + @Nullable public static Integer parseMinJavaVersion(String gradleFileContents) { List excludedRanges = new ArrayList<>(); @@ -229,6 +231,7 @@ private static String interpolate(String text, Map variables) { * @param regex Regex with a capturing group * @return The first captured group, or null if not found */ + @Nullable private static String extractValue(String text, String regex) { Pattern pattern = Pattern.compile(regex); Matcher matcher = pattern.matcher(text); diff --git a/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/utils/FileManager.java b/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/utils/FileManager.java index 1205d9ccbf78..3fab1006f8be 100644 --- a/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/utils/FileManager.java +++ b/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/utils/FileManager.java @@ -15,6 +15,7 @@ import java.util.logging.Logger; import java.util.stream.Collectors; import java.util.stream.Stream; +import javax.annotation.Nullable; public class FileManager { private static final Logger logger = Logger.getLogger(FileManager.class.getName()); @@ -38,6 +39,7 @@ public List getInstrumentationPaths() { } } + @Nullable private static InstrumentationPath parseInstrumentationPath(String filePath) { if (filePath == null || filePath.isEmpty()) { return null; @@ -98,6 +100,7 @@ public List findBuildGradleFiles(String instrumentationDirectory) { } } + @Nullable public String getMetaDataFile(String instrumentationDirectory) { String metadataFile = instrumentationDirectory + "/metadata.yaml"; if (Files.exists(Paths.get(metadataFile))) { @@ -106,6 +109,7 @@ public String getMetaDataFile(String instrumentationDirectory) { return null; } + @Nullable public String readFileToString(String filePath) { try { return Files.readString(Paths.get(filePath)); @@ -114,4 +118,33 @@ public String readFileToString(String filePath) { return null; } } + + /** + * Looks for metric files in the .telemetry directory + * + * @param instrumentationDirectory the directory to traverse + * @return contents of file + */ + public String getMetrics(String instrumentationDirectory) { + StringBuilder metricsContent = new StringBuilder(); + Path telemetryDir = Paths.get(instrumentationDirectory, ".telemetry"); + + if (Files.exists(telemetryDir) && Files.isDirectory(telemetryDir)) { + try (Stream files = Files.list(telemetryDir)) { + files + .filter(path -> path.getFileName().toString().startsWith("metrics-")) + .forEach( + path -> { + String content = readFileToString(path.toString()); + if (content != null) { + metricsContent.append(content).append("\n"); + } + }); + } catch (IOException e) { + logger.severe("Error reading metrics files: " + e.getMessage()); + } + } + + return metricsContent.toString(); + } } diff --git a/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/utils/YamlHelper.java b/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/utils/YamlHelper.java index c3026fef4bd9..7832a40f2440 100644 --- a/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/utils/YamlHelper.java +++ b/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/utils/YamlHelper.java @@ -10,6 +10,7 @@ import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import io.opentelemetry.instrumentation.docs.internal.ConfigurationOption; import io.opentelemetry.instrumentation.docs.internal.ConfigurationType; +import io.opentelemetry.instrumentation.docs.internal.EmittedMetrics; import io.opentelemetry.instrumentation.docs.internal.InstrumentationClassification; import io.opentelemetry.instrumentation.docs.internal.InstrumentationMetaData; import io.opentelemetry.instrumentation.docs.internal.InstrumentationModule; @@ -24,6 +25,10 @@ import org.yaml.snakeyaml.DumperOptions; import org.yaml.snakeyaml.Yaml; +/** + * Used for reading and writing Yaml files. This class is responsible for the structure and contents + * of the instrumentation-list.yaml file. + */ public class YamlHelper { private static final Logger logger = Logger.getLogger(YamlHelper.class.getName()); @@ -37,16 +42,19 @@ public static void generateInstrumentationYaml( Yaml yaml = new Yaml(options); + // Add library modules Map libraries = getLibraryInstrumentations(list); if (!libraries.isEmpty()) { yaml.dump(getLibraryInstrumentations(list), writer); } + // Add internal modules Map internal = generateBaseYaml(list, InstrumentationClassification.INTERNAL); if (!internal.isEmpty()) { yaml.dump(internal, writer); } + // add custom instrumentation modules Map custom = generateBaseYaml(list, InstrumentationClassification.CUSTOM); if (!custom.isEmpty()) { yaml.dump(custom, writer); @@ -104,29 +112,50 @@ private static Map generateBaseYaml( return newOutput; } + /** Assembles map of properties that all modules could have */ private static Map baseProperties(InstrumentationModule module) { Map moduleMap = new LinkedHashMap<>(); moduleMap.put("name", module.getInstrumentationName()); + addMetadataProperties(module, moduleMap); + moduleMap.put("source_path", module.getSrcPath()); + + if (module.getMinJavaVersion() != null) { + moduleMap.put("minimum_java_version", module.getMinJavaVersion()); + } + + moduleMap.put("scope", getScopeMap(module)); + addTargetVersions(module, moduleMap); + addConfigurations(module, moduleMap); + + if (module.getMetrics() != null) { + List> metricsList = getMetricsList(module); + moduleMap.put("metrics", metricsList); + } + + return moduleMap; + } + + private static void addMetadataProperties( + InstrumentationModule module, Map moduleMap) { if (module.getMetadata() != null) { if (module.getMetadata().getDescription() != null) { moduleMap.put("description", module.getMetadata().getDescription()); } - if (module.getMetadata().getDisabledByDefault()) { moduleMap.put("disabled_by_default", module.getMetadata().getDisabledByDefault()); } } + } - moduleMap.put("source_path", module.getSrcPath()); - - if (module.getMinJavaVersion() != null) { - moduleMap.put("minimum_java_version", module.getMinJavaVersion()); - } - - Map scopeMap = getScopeMap(module); - moduleMap.put("scope", scopeMap); + private static Map getScopeMap(InstrumentationModule module) { + Map scopeMap = new LinkedHashMap<>(); + scopeMap.put("name", module.getScopeInfo().getName()); + return scopeMap; + } + private static void addTargetVersions( + InstrumentationModule module, Map moduleMap) { Map targetVersions = new TreeMap<>(); if (module.getTargetVersions() != null && !module.getTargetVersions().isEmpty()) { module @@ -143,34 +172,58 @@ private static Map baseProperties(InstrumentationModule module) } else { moduleMap.put("target_versions", targetVersions); } + } + private static void addConfigurations( + InstrumentationModule module, Map moduleMap) { if (module.getMetadata() != null && !module.getMetadata().getConfigurations().isEmpty()) { List> configurations = new ArrayList<>(); for (ConfigurationOption configuration : module.getMetadata().getConfigurations()) { - Map conf = new LinkedHashMap<>(); - conf.put("name", configuration.name()); - conf.put("description", configuration.description()); - conf.put("type", configuration.type().toString()); - if (configuration.type().equals(ConfigurationType.BOOLEAN)) { - conf.put("default", Boolean.parseBoolean(configuration.defaultValue())); - } else if (configuration.type().equals(ConfigurationType.INT)) { - conf.put("default", Integer.parseInt(configuration.defaultValue())); - } else { - conf.put("default", configuration.defaultValue()); - } - - configurations.add(conf); + configurations.add(configurationToMap(configuration)); } moduleMap.put("configurations", configurations); } + } - return moduleMap; + private static Map configurationToMap(ConfigurationOption configuration) { + Map conf = new LinkedHashMap<>(); + conf.put("name", configuration.name()); + conf.put("description", configuration.description()); + conf.put("type", configuration.type().toString()); + if (configuration.type().equals(ConfigurationType.BOOLEAN)) { + conf.put("default", Boolean.parseBoolean(configuration.defaultValue())); + } else if (configuration.type().equals(ConfigurationType.INT)) { + conf.put("default", Integer.parseInt(configuration.defaultValue())); + } else { + conf.put("default", configuration.defaultValue()); + } + return conf; } - private static Map getScopeMap(InstrumentationModule module) { - Map scopeMap = new LinkedHashMap<>(); - scopeMap.put("name", module.getScopeInfo().getName()); - return scopeMap; + private static List> getMetricsList(InstrumentationModule module) { + List> metricsList = new ArrayList<>(); + if (module.getMetrics() == null) { + return metricsList; + } + + for (EmittedMetrics.Metric metric : module.getMetrics()) { + Map metricMap = new LinkedHashMap<>(); + metricMap.put("name", metric.getName()); + metricMap.put("description", metric.getDescription()); + metricMap.put("type", metric.getType()); + metricMap.put("unit", metric.getUnit()); + + List> attributes = new ArrayList<>(); + for (EmittedMetrics.Attribute attribute : metric.getAttributes()) { + Map attributeMap = new LinkedHashMap<>(); + attributeMap.put("name", attribute.getName()); + attributeMap.put("type", attribute.getType()); + attributes.add(attributeMap); + } + metricMap.put("attributes", attributes); + metricsList.add(metricMap); + } + return metricsList; } public static InstrumentationMetaData metaDataParser(String input) @@ -178,5 +231,9 @@ public static InstrumentationMetaData metaDataParser(String input) return mapper.readValue(input, InstrumentationMetaData.class); } + public static EmittedMetrics emittedMetricsParser(String input) { + return new Yaml().loadAs(input, EmittedMetrics.class); + } + private YamlHelper() {} } diff --git a/instrumentation-docs/src/test/java/io/opentelemetry/instrumentation/docs/InstrumentationAnalyzerTest.java b/instrumentation-docs/src/test/java/io/opentelemetry/instrumentation/docs/InstrumentationAnalyzerTest.java index 5598983febfe..4d1f312255d8 100644 --- a/instrumentation-docs/src/test/java/io/opentelemetry/instrumentation/docs/InstrumentationAnalyzerTest.java +++ b/instrumentation-docs/src/test/java/io/opentelemetry/instrumentation/docs/InstrumentationAnalyzerTest.java @@ -14,6 +14,7 @@ import java.util.List; import org.junit.jupiter.api.Test; +@SuppressWarnings("NullAway") class InstrumentationAnalyzerTest { @Test @@ -50,6 +51,7 @@ void testConvertToInstrumentationModule() { .findFirst() .orElse(null); + assertThat(log4jModule).isNotNull(); assertThat(log4jModule.getNamespace()).isEqualTo("log4j"); assertThat(log4jModule.getGroup()).isEqualTo("log4j"); assertThat(log4jModule.getSrcPath()).isEqualTo("instrumentation/log4j/log4j-appender-2.17"); diff --git a/instrumentation-docs/src/test/java/io/opentelemetry/instrumentation/docs/utils/FileManagerTest.java b/instrumentation-docs/src/test/java/io/opentelemetry/instrumentation/docs/utils/FileManagerTest.java index a509822994ca..9e248a0c3eb7 100644 --- a/instrumentation-docs/src/test/java/io/opentelemetry/instrumentation/docs/utils/FileManagerTest.java +++ b/instrumentation-docs/src/test/java/io/opentelemetry/instrumentation/docs/utils/FileManagerTest.java @@ -15,6 +15,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; +@SuppressWarnings("NullAway") class FileManagerTest { @TempDir Path tempDir; diff --git a/instrumentation-docs/src/test/java/io/opentelemetry/instrumentation/docs/utils/YamlHelperTest.java b/instrumentation-docs/src/test/java/io/opentelemetry/instrumentation/docs/utils/YamlHelperTest.java index b3693e27a7d7..2127a0b0840a 100644 --- a/instrumentation-docs/src/test/java/io/opentelemetry/instrumentation/docs/utils/YamlHelperTest.java +++ b/instrumentation-docs/src/test/java/io/opentelemetry/instrumentation/docs/utils/YamlHelperTest.java @@ -10,6 +10,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import io.opentelemetry.instrumentation.docs.internal.ConfigurationOption; import io.opentelemetry.instrumentation.docs.internal.ConfigurationType; +import io.opentelemetry.instrumentation.docs.internal.EmittedMetrics; import io.opentelemetry.instrumentation.docs.internal.InstrumentationClassification; import io.opentelemetry.instrumentation.docs.internal.InstrumentationMetaData; import io.opentelemetry.instrumentation.docs.internal.InstrumentationModule; @@ -283,4 +284,72 @@ void testMetadataParserWithOnlyConfigurations() throws JsonProcessingException { assertThat(config.defaultValue()).isEqualTo("true"); assertThat(config.type()).isEqualTo(ConfigurationType.BOOLEAN); } + + @Test + void testMetricsParsing() throws Exception { + List modules = new ArrayList<>(); + Map> targetVersions = new HashMap<>(); + + EmittedMetrics.Metric metric = + new EmittedMetrics.Metric( + "db.client.operation.duration", + "Duration of database client operations.", + "HISTOGRAM", + "s", + List.of( + new EmittedMetrics.Attribute("db.namespace", "STRING"), + new EmittedMetrics.Attribute("db.operation.name", "STRING"), + new EmittedMetrics.Attribute("db.system.name", "STRING"), + new EmittedMetrics.Attribute("server.address", "STRING"), + new EmittedMetrics.Attribute("server.port", "LONG"))); + + targetVersions.put( + InstrumentationType.LIBRARY, new HashSet<>(List.of("org.apache.mylib:mylib-core:2.3.0"))); + modules.add( + new InstrumentationModule.Builder() + .srcPath("instrumentation/mylib/mylib-core-2.3") + .instrumentationName("mylib-2.3") + .namespace("mylib") + .group("mylib") + .targetVersions(targetVersions) + .metrics(List.of(metric)) + .build()); + + StringWriter stringWriter = new StringWriter(); + BufferedWriter writer = new BufferedWriter(stringWriter); + + YamlHelper.generateInstrumentationYaml(modules, writer); + writer.flush(); + + String expectedYaml = + """ + libraries: + mylib: + - name: mylib-2.3 + source_path: instrumentation/mylib/mylib-core-2.3 + scope: + name: io.opentelemetry.mylib-2.3 + target_versions: + library: + - org.apache.mylib:mylib-core:2.3.0 + metrics: + - name: db.client.operation.duration + description: Duration of database client operations. + type: HISTOGRAM + unit: s + attributes: + - name: db.namespace + type: STRING + - name: db.operation.name + type: STRING + - name: db.system.name + type: STRING + - name: server.address + type: STRING + - name: server.port + type: LONG + """; + + assertThat(expectedYaml).isEqualTo(stringWriter.toString()); + } } diff --git a/instrumentation/alibaba-druid-1.0/javaagent/build.gradle.kts b/instrumentation/alibaba-druid-1.0/javaagent/build.gradle.kts index 0e990ce5f1ce..f435d672c43e 100644 --- a/instrumentation/alibaba-druid-1.0/javaagent/build.gradle.kts +++ b/instrumentation/alibaba-druid-1.0/javaagent/build.gradle.kts @@ -24,6 +24,10 @@ tasks { jvmArgs("-Dotel.semconv-stability.opt-in=database") } + test { + systemProperty("collectMetadata", findProperty("collectMetadata")?.toString() ?: "false") + } + check { dependsOn(testStableSemconv) } diff --git a/testing-common/build.gradle.kts b/testing-common/build.gradle.kts index 00a742f5f252..df130a687e10 100644 --- a/testing-common/build.gradle.kts +++ b/testing-common/build.gradle.kts @@ -69,6 +69,7 @@ dependencies { implementation("org.slf4j:jul-to-slf4j") implementation("io.opentelemetry:opentelemetry-exporter-logging") implementation("io.opentelemetry.contrib:opentelemetry-baggage-processor") + implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.19.0") api(project(":instrumentation-api-incubator")) annotationProcessor("com.google.auto.service:auto-service") diff --git a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/AgentTestRunner.java b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/AgentTestRunner.java index f8648ccc6416..5099b58bfd9b 100644 --- a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/AgentTestRunner.java +++ b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/AgentTestRunner.java @@ -10,11 +10,15 @@ import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.test.utils.LoggerUtils; +import io.opentelemetry.instrumentation.testing.internal.MetaDataCollector; import io.opentelemetry.javaagent.testing.common.AgentTestingExporterAccess; import io.opentelemetry.javaagent.testing.common.TestAgentListenerAccess; import io.opentelemetry.sdk.logs.data.LogRecordData; import io.opentelemetry.sdk.metrics.data.MetricData; import io.opentelemetry.sdk.trace.data.SpanData; +import java.io.IOException; +import java.net.URL; +import java.nio.file.Paths; import java.util.List; import org.slf4j.LoggerFactory; @@ -50,7 +54,7 @@ public void beforeTestClass() { } @Override - public void afterTestClass() { + public void afterTestClass() throws IOException { // Cleanup before assertion. assert TestAgentListenerAccess.getInstrumentationErrorCount() == 0 : TestAgentListenerAccess.getInstrumentationErrorCount() @@ -59,6 +63,19 @@ public void afterTestClass() { assert adviceFailureCount == 0 : adviceFailureCount + " Advice failures during test"; int muzzleFailureCount = TestAgentListenerAccess.getAndResetMuzzleFailureCount(); assert muzzleFailureCount == 0 : muzzleFailureCount + " Muzzle failures during test"; + + // Generates files in a `.telemetry` directory within the instrumentation module with all + // captured emitted metadata to be used by the instrumentation-docs Doc generator. + if (Boolean.getBoolean("collectMetadata")) { + URL resource = this.getClass().getClassLoader().getResource(""); + if (resource == null) { + return; + } + String path = Paths.get(resource.getPath()).toString(); + + MetaDataCollector.writeTelemetryToFiles(path, metrics); + } + // additional library ignores are ignored during tests, because they can make it really // confusing for contributors wondering why their instrumentation is not applied // diff --git a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/InstrumentationTestRunner.java b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/InstrumentationTestRunner.java index 154510afdd9a..088693ad3b77 100644 --- a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/InstrumentationTestRunner.java +++ b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/InstrumentationTestRunner.java @@ -18,12 +18,15 @@ import io.opentelemetry.sdk.testing.assertj.TraceAssert; import io.opentelemetry.sdk.testing.assertj.TracesAssert; import io.opentelemetry.sdk.trace.data.SpanData; +import java.io.IOException; import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Comparator; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.function.Consumer; @@ -53,6 +56,7 @@ public abstract class InstrumentationTestRunner { } private final TestInstrumenters testInstrumenters; + protected Map metrics = new HashMap<>(); protected InstrumentationTestRunner(OpenTelemetry openTelemetry) { testInstrumenters = new TestInstrumenters(openTelemetry); @@ -60,7 +64,7 @@ protected InstrumentationTestRunner(OpenTelemetry openTelemetry) { public abstract void beforeTestClass(); - public abstract void afterTestClass(); + public abstract void afterTestClass() throws IOException; public abstract void clearAllExportedData(); @@ -165,6 +169,10 @@ public final void waitAndAssertMetrics( data -> data.getInstrumentationScopeInfo().getName().equals(instrumentationName) && data.getName().equals(metricName)))); + + if (Boolean.getBoolean("collectMetadata")) { + collectEmittedMetrics(getExportedMetrics()); + } } @SafeVarargs @@ -182,6 +190,18 @@ public final void waitAndAssertMetrics( .anySatisfy(metric -> assertions[index].accept(assertThat(metric))); } }); + + if (Boolean.getBoolean("collectMetadata")) { + collectEmittedMetrics(getExportedMetrics()); + } + } + + private void collectEmittedMetrics(List metrics) { + for (MetricData metric : metrics) { + if (!this.metrics.containsKey(metric.getName())) { + this.metrics.put(metric.getName(), metric); + } + } } public final List waitForLogRecords(int numberOfLogRecords) { diff --git a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/internal/MetaDataCollector.java b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/internal/MetaDataCollector.java new file mode 100644 index 000000000000..6ae7bf8a134b --- /dev/null +++ b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/internal/MetaDataCollector.java @@ -0,0 +1,119 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.testing.internal; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import io.opentelemetry.sdk.metrics.data.MetricData; +import java.io.BufferedWriter; +import java.io.IOException; +import java.nio.file.FileAlreadyExistsException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Used in the {@link io.opentelemetry.instrumentation.testing.AgentTestRunner} to write telemetry + * to metadata files within a .telemetry directory in each instrumentation module. This information + * is then parsed and used to generate the instrumentation-list.yaml file. + * + *

This class is internal and is hence not for public use. Its APIs are unstable and can change + * at any time. + */ +public final class MetaDataCollector { + + private static final String TMP_DIR = ".telemetry"; + private static final Pattern MODULE_PATTERN = + Pattern.compile("(.*?/instrumentation/.*?)(/javaagent/|/library/)"); + + // thread-safe after initialization + private static final ObjectMapper YAML_MAPPER = new ObjectMapper(new YAMLFactory()); + + public static void writeTelemetryToFiles(String path, Map metrics) + throws IOException { + + String moduleRoot = extractInstrumentationPath(path); + writeMetricData(moduleRoot, metrics); + } + + private static String extractInstrumentationPath(String path) { + Matcher matcher = MODULE_PATTERN.matcher(path); + if (!matcher.find()) { + throw new IllegalArgumentException("Invalid path: " + path); + } + + String instrumentationPath = matcher.group(1); + Path telemetryDir = Paths.get(instrumentationPath, TMP_DIR); + + try { + Files.createDirectories(telemetryDir); + } catch (FileAlreadyExistsException ignored) { + // Directory already exists; nothing to do + } catch (IOException e) { + throw new IllegalStateException(e); + } + + return instrumentationPath; + } + + private static void writeMetricData(String moduleRoot, Map metrics) + throws IOException { + if (metrics.isEmpty()) { + return; + } + + Path output = Paths.get(moduleRoot, TMP_DIR, "metrics-" + UUID.randomUUID() + ".yaml"); + + Map root = new LinkedHashMap<>(); + List> metricList = new ArrayList<>(); + + for (MetricData metric : metrics.values()) { + Map metricNode = new LinkedHashMap<>(); + metricNode.put("name", metric.getName()); + metricNode.put("description", metric.getDescription()); + metricNode.put("type", metric.getType().toString()); + metricNode.put("unit", sanitizeUnit(metric.getUnit())); + + List> attributes = new ArrayList<>(); + metric.getData().getPoints().stream() + .findFirst() + .ifPresent( + p -> + p.getAttributes() + .forEach( + (key, value) -> { + Map attr = new LinkedHashMap<>(); + attr.put("name", key.getKey()); + attr.put("type", key.getType().toString()); + attributes.add(attr); + })); + + metricNode.put("attributes", attributes); + metricList.add(metricNode); + } + + root.put("metrics", metricList); + + try (BufferedWriter writer = Files.newBufferedWriter(output, UTF_8)) { + YAML_MAPPER.writeValue(writer, root); + } + } + + private static String sanitizeUnit(String unit) { + return unit == null ? null : unit.replace("{", "").replace("}", ""); + } + + private MetaDataCollector() {} +} From 5b71e52a652b111632f408fd4662dbd365e34899 Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Wed, 21 May 2025 08:44:45 -0400 Subject: [PATCH 2/3] dont add yaml dependency to test module --- testing-common/build.gradle.kts | 1 - .../testing/internal/MetaDataCollector.java | 72 ++++++++----------- 2 files changed, 29 insertions(+), 44 deletions(-) diff --git a/testing-common/build.gradle.kts b/testing-common/build.gradle.kts index df130a687e10..00a742f5f252 100644 --- a/testing-common/build.gradle.kts +++ b/testing-common/build.gradle.kts @@ -69,7 +69,6 @@ dependencies { implementation("org.slf4j:jul-to-slf4j") implementation("io.opentelemetry:opentelemetry-exporter-logging") implementation("io.opentelemetry.contrib:opentelemetry-baggage-processor") - implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.19.0") api(project(":instrumentation-api-incubator")) annotationProcessor("com.google.auto.service:auto-service") diff --git a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/internal/MetaDataCollector.java b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/internal/MetaDataCollector.java index 6ae7bf8a134b..6a15c133539b 100644 --- a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/internal/MetaDataCollector.java +++ b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/internal/MetaDataCollector.java @@ -7,8 +7,6 @@ import static java.nio.charset.StandardCharsets.UTF_8; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import io.opentelemetry.sdk.metrics.data.MetricData; import java.io.BufferedWriter; import java.io.IOException; @@ -16,9 +14,6 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; import java.util.Map; import java.util.UUID; import java.util.regex.Matcher; @@ -38,9 +33,6 @@ public final class MetaDataCollector { private static final Pattern MODULE_PATTERN = Pattern.compile("(.*?/instrumentation/.*?)(/javaagent/|/library/)"); - // thread-safe after initialization - private static final ObjectMapper YAML_MAPPER = new ObjectMapper(new YAMLFactory()); - public static void writeTelemetryToFiles(String path, Map metrics) throws IOException { @@ -68,46 +60,40 @@ private static String extractInstrumentationPath(String path) { return instrumentationPath; } - private static void writeMetricData(String moduleRoot, Map metrics) + private static void writeMetricData(String instrumentationPath, Map metrics) throws IOException { + if (metrics.isEmpty()) { return; } - Path output = Paths.get(moduleRoot, TMP_DIR, "metrics-" + UUID.randomUUID() + ".yaml"); - - Map root = new LinkedHashMap<>(); - List> metricList = new ArrayList<>(); - - for (MetricData metric : metrics.values()) { - Map metricNode = new LinkedHashMap<>(); - metricNode.put("name", metric.getName()); - metricNode.put("description", metric.getDescription()); - metricNode.put("type", metric.getType().toString()); - metricNode.put("unit", sanitizeUnit(metric.getUnit())); - - List> attributes = new ArrayList<>(); - metric.getData().getPoints().stream() - .findFirst() - .ifPresent( - p -> - p.getAttributes() - .forEach( - (key, value) -> { - Map attr = new LinkedHashMap<>(); - attr.put("name", key.getKey()); - attr.put("type", key.getType().toString()); - attributes.add(attr); - })); - - metricNode.put("attributes", attributes); - metricList.add(metricNode); - } - - root.put("metrics", metricList); - - try (BufferedWriter writer = Files.newBufferedWriter(output, UTF_8)) { - YAML_MAPPER.writeValue(writer, root); + Path metricsPath = + Paths.get(instrumentationPath, TMP_DIR, "metrics-" + UUID.randomUUID() + ".yaml"); + try (BufferedWriter writer = Files.newBufferedWriter(metricsPath.toFile().toPath(), UTF_8)) { + + if (!metrics.isEmpty()) { + writer.write("metrics:\n"); + for (MetricData metric : metrics.values()) { + writer.write(" - name: " + metric.getName() + "\n"); + writer.write(" description: " + metric.getDescription() + "\n"); + writer.write(" type: " + metric.getType().toString() + "\n"); + writer.write(" unit: " + sanitizeUnit(metric.getUnit()) + "\n"); + writer.write(" attributes: \n"); + metric.getData().getPoints().stream() + .findFirst() + .get() + .getAttributes() + .forEach( + (key, value) -> { + try { + writer.write(" - name: " + key.getKey() + "\n"); + writer.write(" type: " + key.getType().toString() + "\n"); + } catch (IOException e) { + throw new IllegalStateException(e); + } + }); + } + } } } From 1630e8d7a71ab5fefd7e0917e7bbeb16874096c1 Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Thu, 29 May 2025 13:09:31 -0400 Subject: [PATCH 3/3] fix readme, exclude -testing directories, pull in main updates --- docs/instrumentation-list.yaml | 18 +++++++++++++++++- instrumentation-docs/readme.md | 2 +- .../docs/utils/FileManager.java | 3 ++- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/docs/instrumentation-list.yaml b/docs/instrumentation-list.yaml index ca420e9201cb..2f4fa6be038c 100644 --- a/docs/instrumentation-list.yaml +++ b/docs/instrumentation-list.yaml @@ -1950,6 +1950,22 @@ libraries: target_versions: javaagent: - io.vertx:vertx-redis-client:[4.0.0,) + - name: vertx-sql-client-5.0 + source_path: instrumentation/vertx/vertx-sql-client/vertx-sql-client-5.0 + minimum_java_version: 11 + scope: + name: io.opentelemetry.vertx-sql-client-5.0 + target_versions: + javaagent: + - io.vertx:vertx-sql-client:[5.0.0,) + - name: vertx-http-client-5.0 + source_path: instrumentation/vertx/vertx-http-client/vertx-http-client-5.0 + minimum_java_version: 11 + scope: + name: io.opentelemetry.vertx-http-client-5.0 + target_versions: + javaagent: + - io.vertx:vertx-core:[5.0.0,) - name: vertx-web-3.0 source_path: instrumentation/vertx/vertx-web-3.0 scope: @@ -1958,7 +1974,7 @@ libraries: javaagent: - io.vertx:vertx-web:[3.0.0,) - name: vertx-sql-client-4.0 - source_path: instrumentation/vertx/vertx-sql-client-4.0 + source_path: instrumentation/vertx/vertx-sql-client/vertx-sql-client-4.0 scope: name: io.opentelemetry.vertx-sql-client-4.0 target_versions: diff --git a/instrumentation-docs/readme.md b/instrumentation-docs/readme.md index 142b7e0a934e..36a568ccdb5a 100644 --- a/instrumentation-docs/readme.md +++ b/instrumentation-docs/readme.md @@ -24,7 +24,7 @@ tasks { Then, prior to running the analyzer, run the following command to generate `.telemetry` files: -`./gradlew test -collectMetadata=true` +`./gradlew test -PcollectMetadata=true` Then run the doc generator diff --git a/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/utils/FileManager.java b/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/utils/FileManager.java index 3fab1006f8be..602c65abb129 100644 --- a/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/utils/FileManager.java +++ b/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/utils/FileManager.java @@ -76,7 +76,8 @@ public static boolean isValidInstrumentationPath(String filePath) { return false; } - if (filePath.matches(".*(/test/|/testing|/build/|-common/|-common-|common-|bootstrap/src).*")) { + if (filePath.matches( + ".*(/test/|/testing|/build/|-common/|-common-|common-|-testing|bootstrap/src).*")) { return false; }