Automatically Generate Instrumentation Documentation (#13449)

This commit is contained in:
Jay DeLuca 2025-03-06 19:34:13 -05:00 committed by GitHub
parent d28aca1b60
commit 0cfd2d976a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 2556 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,25 @@
plugins {
id("otel.java-conventions")
}
otelJava {
minJavaVersionSupported.set(JavaVersion.VERSION_17)
}
dependencies {
implementation("org.yaml:snakeyaml:2.0")
testImplementation(enforcedPlatform("org.junit:junit-bom:5.12.0"))
testImplementation("org.assertj:assertj-core:3.27.3")
testImplementation("org.junit.jupiter:junit-jupiter-api")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
}
tasks {
val generateDocs by registering(JavaExec::class) {
dependsOn(classes)
mainClass.set("io.opentelemetry.instrumentation.docs.DocGeneratorApplication")
classpath(sourceSets["main"].runtimeClasspath)
}
}

View File

@ -0,0 +1,63 @@
# Doc Generator
Runs analysis on instrumentation modules in order to generate documentation.
## Instrumentation Hierarchy
An "InstrumentationEntity" represents a module that that targets specific code in a framework/library/technology.
Each instrumentation uses muzzle to determine which versions of the target code it supports.
Using these structures as examples:
```
├── instrumentation
│ ├── clickhouse-client-05
│ ├── jaxrs
│ │ ├── jaxrs-1.0
│ │ ├── jaxrs-2.0
│ ├── spring
│ │ ├── spring-cloud-gateway
│ │ │ ├── spring-cloud-gateway-2.0
│ │ │ ├── spring-cloud-gateway-2.2
│ │ │ └── spring-cloud-gateway-common
```
* Name
* Ex: `clickhouse-client-05`, `jaxrs-1.0`, `spring-cloud-gateway-2.0`
* Namespace - direct parent. if none, use name and strip version
* `clickhouse-client`, `jaxrs`, `spring-cloud-gateway`
* Group - top most parent
* `clickhouse-client`, `jaxrs`, `spring`
This information is also referenced in `InstrumentationModule` code for each module:
```java
public class SpringWebInstrumentationModule extends InstrumentationModule
implements ExperimentalInstrumentationModule {
public SpringWebInstrumentationModule() {
super("spring-web", "spring-web-3.1");
}
```
## Instrumentation meta-data
* name
* Identifier for instrumentation module, used to enable/disable
* Configured in `InstrumentationModule` code for each module
* versions
* List of supported versions by the module
* type
* List of instrumentation types, options of either `library` or `javaagent`
## Methodology
### Versions targeted
Javaagent versions are determined by the `muzzle` plugin, so we can attempt to parse the gradle files
Library versions are determined by the library versions used in the gradle files.
### TODO / Notes
- Is the `library` dependency actually the target version? Is there a better way to present the information?
- How to handle oshi target version with a conditional?

View File

@ -0,0 +1,36 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.docs;
import io.opentelemetry.instrumentation.docs.utils.FileManager;
import io.opentelemetry.instrumentation.docs.utils.YamlHelper;
import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
import java.util.logging.Logger;
public class DocGeneratorApplication {
private static final Logger logger = Logger.getLogger(DocGeneratorApplication.class.getName());
public static void main(String[] args) {
FileManager fileManager = new FileManager("instrumentation/");
List<InstrumentationEntity> entities = new InstrumentationAnalyzer(fileManager).analyze();
try (BufferedWriter writer =
Files.newBufferedWriter(
Paths.get("docs/instrumentation-list.yaml"), Charset.defaultCharset())) {
YamlHelper.printInstrumentationList(entities, writer);
} catch (IOException e) {
logger.severe("Error writing instrumentation list: " + e.getMessage());
}
}
private DocGeneratorApplication() {}
}

View File

@ -0,0 +1,148 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.docs;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
class GradleParser {
private static final Pattern passBlockPattern =
Pattern.compile("pass\\s*\\{(.*?)}", Pattern.DOTALL);
private static final Pattern libraryPattern =
Pattern.compile("library\\(\"([^\"]+:[^\"]+:[^\"]+)\"\\)");
private static final Pattern variablePattern =
Pattern.compile("val\\s+(\\w+)\\s*=\\s*\"([^\"]+)\"");
private static final Pattern compileOnlyPattern =
Pattern.compile(
"compileOnly\\(\"([^\"]+)\"\\)\\s*\\{\\s*version\\s*\\{(?:\\s*//.*\\n)*\\s*strictly\\(\"([^\"]+)\"\\)\\s*}\\s*}");
/**
* Parses gradle files for muzzle and dependency information
*
* @param gradleFileContents Contents of a Gradle build file as a String
* @return A set of strings summarizing the group, module, and version ranges
*/
public static Set<String> parseGradleFile(String gradleFileContents, InstrumentationType type) {
Set<String> results = new HashSet<>();
Map<String, String> variables = extractVariables(gradleFileContents);
if (type.equals(InstrumentationType.JAVAAGENT)) {
results.addAll(parseMuzzle(gradleFileContents, variables));
} else {
results.addAll(parseLibraryDependencies(gradleFileContents, variables));
}
return results;
}
/**
* Parses the "muzzle" block from the given Gradle file content and extracts information about
* each "pass { ... }" entry, returning a set of version summary strings.
*
* @param gradleFileContents Contents of a Gradle build file as a String
* @param variables Map of variable names to their values
* @return A set of strings summarizing the group, module, and version ranges
*/
private static Set<String> parseMuzzle(String gradleFileContents, Map<String, String> variables) {
Set<String> results = new HashSet<>();
Matcher passBlockMatcher = passBlockPattern.matcher(gradleFileContents);
while (passBlockMatcher.find()) {
String passBlock = passBlockMatcher.group(1);
String group = extractValue(passBlock, "group\\.set\\(\"([^\"]+)\"\\)");
String module = extractValue(passBlock, "module\\.set\\(\"([^\"]+)\"\\)");
String versionRange = extractValue(passBlock, "versions\\.set\\(\"([^\"]+)\"\\)");
if (group != null && module != null && versionRange != null) {
String summary = group + ":" + module + ":" + interpolate(versionRange, variables);
results.add(summary);
}
}
return results;
}
/**
* Parses the "dependencies" block from the given Gradle file content and extracts information
* about what library versions are supported.
*
* @param gradleFileContents Contents of a Gradle build file as a String
* @param variables Map of variable names to their values
* @return A set of strings summarizing the group, module, and versions
*/
private static Set<String> parseLibraryDependencies(
String gradleFileContents, Map<String, String> variables) {
Set<String> results = new HashSet<>();
Matcher libraryMatcher = libraryPattern.matcher(gradleFileContents);
while (libraryMatcher.find()) {
String dependency = libraryMatcher.group(1);
results.add(interpolate(dependency, variables));
}
Matcher compileOnlyMatcher = compileOnlyPattern.matcher(gradleFileContents);
while (compileOnlyMatcher.find()) {
String dependency = compileOnlyMatcher.group(1) + ":" + compileOnlyMatcher.group(2);
results.add(interpolate(dependency, variables));
}
return results;
}
/**
* Extracts variables from the given Gradle file content.
*
* @param gradleFileContents Contents of a Gradle build file as a String
* @return A map of variable names to their values
*/
private static Map<String, String> extractVariables(String gradleFileContents) {
Map<String, String> variables = new HashMap<>();
Matcher variableMatcher = variablePattern.matcher(gradleFileContents);
while (variableMatcher.find()) {
variables.put(variableMatcher.group(1), variableMatcher.group(2));
}
return variables;
}
/**
* Interpolates variables in the given text using the provided variable map.
*
* @param text Text to interpolate
* @param variables Map of variable names to their values
* @return Interpolated text
*/
private static String interpolate(String text, Map<String, String> variables) {
for (Map.Entry<String, String> entry : variables.entrySet()) {
text = text.replace("$" + entry.getKey(), entry.getValue());
}
return text;
}
/**
* Utility method to extract the first captured group from matching the given regex.
*
* @param text Text to search
* @param regex Regex with a capturing group
* @return The first captured group, or null if not found
*/
private static String extractValue(String text, String regex) {
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(text);
if (matcher.find()) {
return matcher.group(1);
}
return null;
}
private GradleParser() {}
}

View File

@ -0,0 +1,89 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.docs;
import static io.opentelemetry.instrumentation.docs.GradleParser.parseGradleFile;
import io.opentelemetry.instrumentation.docs.utils.FileManager;
import io.opentelemetry.instrumentation.docs.utils.InstrumentationPath;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
class InstrumentationAnalyzer {
private final FileManager fileSearch;
InstrumentationAnalyzer(FileManager fileSearch) {
this.fileSearch = fileSearch;
}
/**
* Converts a list of InstrumentationPath objects into a list of InstrumentationEntity objects.
* Each InstrumentationEntity represents a unique combination of group, namespace, and
* instrumentation name. The types of instrumentation (e.g., library, javaagent) are aggregated
* into a list within each entity.
*
* @param paths the list of InstrumentationPath objects to be converted
* @return a list of InstrumentationEntity objects with aggregated types
*/
public static List<InstrumentationEntity> convertToEntities(List<InstrumentationPath> paths) {
Map<String, InstrumentationEntity> entityMap = new HashMap<>();
for (InstrumentationPath path : paths) {
String key = path.group() + ":" + path.namespace() + ":" + path.instrumentationName();
if (!entityMap.containsKey(key)) {
entityMap.put(
key,
new InstrumentationEntity(
path.srcPath().replace("/javaagent", "").replace("/library", ""),
path.instrumentationName(),
path.namespace(),
path.group()));
}
}
return new ArrayList<>(entityMap.values());
}
/**
* Analyzes the given root directory to find all instrumentation paths and then analyze them. -
* Extracts version information from each instrumentation's build.gradle file.
*
* @return a list of InstrumentationEntity objects with target versions
*/
List<InstrumentationEntity> analyze() {
List<InstrumentationPath> paths = fileSearch.getInstrumentationPaths();
List<InstrumentationEntity> entities = convertToEntities(paths);
for (InstrumentationEntity entity : entities) {
List<String> gradleFiles = fileSearch.findBuildGradleFiles(entity.getSrcPath());
analyzeVersions(gradleFiles, entity);
}
return entities;
}
void analyzeVersions(List<String> files, InstrumentationEntity entity) {
Map<InstrumentationType, Set<String>> versions = new HashMap<>();
for (String file : files) {
String fileContents = fileSearch.readFileToString(file);
if (file.contains("/javaagent/")) {
Set<String> results = parseGradleFile(fileContents, InstrumentationType.JAVAAGENT);
versions
.computeIfAbsent(InstrumentationType.JAVAAGENT, k -> new HashSet<>())
.addAll(results);
} else if (file.contains("/library/")) {
Set<String> results = parseGradleFile(fileContents, InstrumentationType.LIBRARY);
versions.computeIfAbsent(InstrumentationType.LIBRARY, k -> new HashSet<>()).addAll(results);
}
}
entity.setTargetVersions(versions);
}
}

View File

@ -0,0 +1,62 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.docs;
import java.util.Map;
import java.util.Set;
public class InstrumentationEntity {
private final String srcPath;
private final String instrumentationName;
private final String namespace;
private final String group;
private Map<InstrumentationType, Set<String>> targetVersions;
public InstrumentationEntity(
String srcPath, String instrumentationName, String namespace, String group) {
this.srcPath = srcPath;
this.instrumentationName = instrumentationName;
this.namespace = namespace;
this.group = group;
}
public InstrumentationEntity(
String srcPath,
String instrumentationName,
String namespace,
String group,
Map<InstrumentationType, Set<String>> targetVersions) {
this.srcPath = srcPath;
this.instrumentationName = instrumentationName;
this.namespace = namespace;
this.group = group;
this.targetVersions = targetVersions;
}
public String getSrcPath() {
return srcPath;
}
public String getInstrumentationName() {
return instrumentationName;
}
public String getNamespace() {
return namespace;
}
public String getGroup() {
return group;
}
public Map<InstrumentationType, Set<String>> getTargetVersions() {
return targetVersions;
}
public void setTargetVersions(Map<InstrumentationType, Set<String>> targetVersions) {
this.targetVersions = targetVersions;
}
}

View File

@ -0,0 +1,26 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.docs;
import java.util.Locale;
public enum InstrumentationType {
JAVAAGENT,
LIBRARY;
public static InstrumentationType fromString(String type) {
return switch (type.toLowerCase(Locale.getDefault())) {
case "javaagent" -> JAVAAGENT;
case "library" -> LIBRARY;
default -> throw new IllegalArgumentException("Unknown instrumentation type: " + type);
};
}
@Override
public String toString() {
return name().toLowerCase(Locale.getDefault());
}
}

View File

@ -0,0 +1,113 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.docs.utils;
import io.opentelemetry.instrumentation.docs.InstrumentationType;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class FileManager {
private static final Logger logger = Logger.getLogger(FileManager.class.getName());
private final String rootDir;
public FileManager(String rootDir) {
this.rootDir = rootDir;
}
public List<InstrumentationPath> getInstrumentationPaths() {
Path rootPath = Paths.get(rootDir);
try (Stream<Path> walk = Files.walk(rootPath)) {
return walk.filter(Files::isDirectory)
.filter(dir -> !dir.toString().contains("/build"))
.filter(dir -> isValidInstrumentationPath(dir.toString()))
.map(dir -> parseInstrumentationPath(dir.toString()))
.collect(Collectors.toList());
} catch (IOException e) {
logger.severe("Error traversing directory: " + e.getMessage());
return new ArrayList<>();
}
}
private static InstrumentationPath parseInstrumentationPath(String filePath) {
if (filePath == null || filePath.isEmpty()) {
return null;
}
String instrumentationSegment = "/instrumentation/";
int startIndex = filePath.indexOf(instrumentationSegment) + instrumentationSegment.length();
String[] parts = filePath.substring(startIndex).split("/");
if (parts.length < 2) {
return null;
}
InstrumentationType instrumentationType =
InstrumentationType.fromString(parts[parts.length - 1]);
String name = parts[parts.length - 2];
String namespace = name.contains("-") ? name.split("-")[0] : name;
return new InstrumentationPath(name, filePath, namespace, namespace, instrumentationType);
}
public static boolean isValidInstrumentationPath(String filePath) {
if (filePath == null || filePath.isEmpty()) {
return false;
}
String instrumentationSegment = "instrumentation/";
if (!filePath.contains(instrumentationSegment)) {
return false;
}
int javaagentCount = filePath.split("/javaagent", -1).length - 1;
if (javaagentCount > 1) {
return false;
}
if (filePath.contains("/test/")
|| filePath.contains("/testing")
|| filePath.contains("-common/")
|| filePath.contains("bootstrap/src")) {
return false;
}
return filePath.endsWith("javaagent") || filePath.endsWith("library");
}
public List<String> findBuildGradleFiles(String instrumentationDirectory) {
Path rootPath = Paths.get(instrumentationDirectory);
try (Stream<Path> walk = Files.walk(rootPath)) {
return walk.filter(Files::isRegularFile)
.filter(
path ->
path.getFileName().toString().equals("build.gradle.kts")
&& !path.toString().contains("/testing/"))
.map(Path::toString)
.collect(Collectors.toList());
} catch (IOException e) {
logger.severe("Error traversing directory: " + e.getMessage());
return new ArrayList<>();
}
}
public String readFileToString(String filePath) {
try {
return Files.readString(Paths.get(filePath));
} catch (IOException e) {
logger.severe("Error reading file: " + e.getMessage());
return null;
}
}
}

View File

@ -0,0 +1,15 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.docs.utils;
import io.opentelemetry.instrumentation.docs.InstrumentationType;
public record InstrumentationPath(
String instrumentationName,
String srcPath,
String namespace,
String group,
InstrumentationType type) {}

View File

@ -0,0 +1,64 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.docs.utils;
import io.opentelemetry.instrumentation.docs.InstrumentationEntity;
import java.io.BufferedWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.representer.Representer;
public class YamlHelper {
public static void printInstrumentationList(
List<InstrumentationEntity> list, BufferedWriter writer) {
Map<String, List<InstrumentationEntity>> groupedByGroup =
list.stream()
.collect(
Collectors.groupingBy(
InstrumentationEntity::getGroup, TreeMap::new, Collectors.toList()));
Map<String, Object> output = new TreeMap<>();
groupedByGroup.forEach(
(group, entities) -> {
Map<String, Object> groupMap = new TreeMap<>();
List<Map<String, Object>> instrumentations = new ArrayList<>();
for (InstrumentationEntity entity : entities) {
Map<String, Object> entityMap = new TreeMap<>();
entityMap.put("name", entity.getInstrumentationName());
entityMap.put("srcPath", entity.getSrcPath());
Map<String, Object> targetVersions = new TreeMap<>();
if (entity.getTargetVersions() != null && !entity.getTargetVersions().isEmpty()) {
entity
.getTargetVersions()
.forEach(
(type, versions) -> {
targetVersions.put(type.toString(), new ArrayList<>(versions));
});
}
entityMap.put("target_versions", targetVersions);
instrumentations.add(entityMap);
}
groupMap.put("instrumentations", instrumentations);
output.put(group, groupMap);
});
DumperOptions options = new DumperOptions();
options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
Representer representer = new Representer(options);
representer.getPropertyUtils().setSkipMissingProperties(true);
Yaml yaml = new Yaml(representer, options);
yaml.dump(output, writer);
}
private YamlHelper() {}
}

View File

@ -0,0 +1,116 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.docs;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.Set;
import org.junit.jupiter.api.Test;
class GradleParserTest {
@Test
void testExtractMuzzleVersions_SinglePassBlock() {
String gradleBuildFileContent =
"muzzle {\n"
+ " pass {\n"
+ " group.set(\"org.elasticsearch.client\")\n"
+ " module.set(\"rest\")\n"
+ " versions.set(\"[5.0,6.4)\")\n"
+ " }\n"
+ "}";
Set<String> versions =
GradleParser.parseGradleFile(gradleBuildFileContent, InstrumentationType.JAVAAGENT);
assertThat(versions.size()).isEqualTo(1);
assertThat(versions.stream().findFirst().get())
.isEqualTo("org.elasticsearch.client:rest:[5.0,6.4)");
}
@Test
void testExtractLibraryVersion() {
String gradleBuildFileContent =
"dependencies {\n"
+ " library(\"org.apache.httpcomponents:httpclient:4.3\")\n"
+ " testImplementation(project(\":instrumentation:apache-httpclient:apache-httpclient-4.3:testing\"))\n"
+ " latestDepTestLibrary(\"org.apache.httpcomponents:httpclient:4.+\") // see apache-httpclient-5.0 module\n"
+ "}";
Set<String> versions =
GradleParser.parseGradleFile(gradleBuildFileContent, InstrumentationType.LIBRARY);
assertThat(versions.size()).isEqualTo(1);
assertThat(versions.stream().findFirst().get())
.isEqualTo("org.apache.httpcomponents:httpclient:4.3");
}
@Test
void testExtractMuzzleVersions_MultiplePassBlocks() {
String gradleBuildFileContent =
"plugins {\n"
+ " id(\"otel.javaagent-instrumentation\")\n"
+ " id(\"otel.nullaway-conventions\")\n"
+ " id(\"otel.scala-conventions\")\n"
+ "}\n"
+ "\n"
+ "val zioVersion = \"2.0.0\"\n"
+ "val scalaVersion = \"2.12\"\n"
+ "\n"
+ "muzzle {\n"
+ " pass {\n"
+ " group.set(\"dev.zio\")\n"
+ " module.set(\"zio_2.12\")\n"
+ " versions.set(\"[$zioVersion,)\")\n"
+ " assertInverse.set(true)\n"
+ " }\n"
+ " pass {\n"
+ " group.set(\"dev.zio\")\n"
+ " module.set(\"zio_2.13\")\n"
+ " versions.set(\"[$zioVersion,)\")\n"
+ " assertInverse.set(true)\n"
+ " }\n"
+ " pass {\n"
+ " group.set(\"dev.zio\")\n"
+ " module.set(\"zio_3\")\n"
+ " versions.set(\"[$zioVersion,)\")\n"
+ " assertInverse.set(true)\n"
+ " }\n"
+ "}\n";
Set<String> versions =
GradleParser.parseGradleFile(gradleBuildFileContent, InstrumentationType.JAVAAGENT);
assertThat(versions)
.containsExactlyInAnyOrder(
"dev.zio:zio_2.12:[2.0.0,)", "dev.zio:zio_2.13:[2.0.0,)", "dev.zio:zio_3:[2.0.0,)");
}
@Test
void testExtractLogbackLibrary() {
String gradleBuildFileContent =
"compileOnly(\"ch.qos.logback:logback-classic\") {\n"
+ " version {\n"
+ " // compiling against newer version than the earliest supported version (1.0.0) to support\n"
+ " // features added in 1.3.0\n"
+ " strictly(\"1.3.0\")\n"
+ " }\n"
+ "}\n"
+ "compileOnly(\"org.slf4j:slf4j-api\") {\n"
+ " version {\n"
+ " strictly(\"2.0.0\")\n"
+ " }\n"
+ "}\n"
+ "compileOnly(\"net.logstash.logback:logstash-logback-encoder\") {\n"
+ " version {\n"
+ " strictly(\"3.0\")\n"
+ " }\n"
+ "}\n";
Set<String> versions =
GradleParser.parseGradleFile(gradleBuildFileContent, InstrumentationType.LIBRARY);
assertThat(versions)
.containsExactlyInAnyOrder(
"ch.qos.logback:logback-classic:1.3.0",
"org.slf4j:slf4j-api:2.0.0",
"net.logstash.logback:logstash-logback-encoder:3.0");
}
}

View File

@ -0,0 +1,65 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.docs;
import static org.assertj.core.api.Assertions.assertThat;
import io.opentelemetry.instrumentation.docs.utils.InstrumentationPath;
import java.util.Arrays;
import java.util.List;
import org.junit.jupiter.api.Test;
class InstrumentationAnalyzerTest {
@Test
void testConvertToEntities() {
List<InstrumentationPath> paths =
Arrays.asList(
new InstrumentationPath(
"log4j-appender-2.17",
"instrumentation/log4j/log4j-appender-2.17/library",
"log4j",
"log4j",
InstrumentationType.LIBRARY),
new InstrumentationPath(
"log4j-appender-2.17",
"instrumentation/log4j/log4j-appender-2.17/javaagent",
"log4j",
"log4j",
InstrumentationType.JAVAAGENT),
new InstrumentationPath(
"spring-web",
"instrumentation/spring/spring-web/library",
"spring",
"spring",
InstrumentationType.LIBRARY));
List<InstrumentationEntity> entities = InstrumentationAnalyzer.convertToEntities(paths);
assertThat(entities.size()).isEqualTo(2);
InstrumentationEntity log4jEntity =
entities.stream()
.filter(e -> e.getInstrumentationName().equals("log4j-appender-2.17"))
.findFirst()
.orElse(null);
assertThat(log4jEntity.getNamespace()).isEqualTo("log4j");
assertThat(log4jEntity.getGroup()).isEqualTo("log4j");
assertThat(log4jEntity.getSrcPath()).isEqualTo("instrumentation/log4j/log4j-appender-2.17");
InstrumentationEntity springEntity =
entities.stream()
.filter(e -> e.getInstrumentationName().equals("spring-web"))
.findFirst()
.orElse(null);
assertThat(springEntity).isNotNull();
assertThat(springEntity.getNamespace()).isEqualTo("spring");
assertThat(springEntity.getGroup()).isEqualTo("spring");
assertThat(springEntity.getSrcPath()).isEqualTo("instrumentation/spring/spring-web");
}
}

View File

@ -0,0 +1,55 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.docs.utils;
import static org.assertj.core.api.Assertions.assertThat;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
class FileManagerTest {
@TempDir Path tempDir;
private FileManager fileManager;
@BeforeEach
void setUp() {
fileManager = new FileManager(tempDir.toString());
}
@Test
void testGetInstrumentationPaths() throws IOException {
Path validDir =
Files.createDirectories(tempDir.resolve("instrumentation/my-instrumentation/javaagent"));
List<InstrumentationPath> paths = fileManager.getInstrumentationPaths();
assertThat(paths).hasSize(1);
assertThat(paths.get(0).srcPath()).isEqualTo(validDir.toString());
}
@Test
void testIsValidInstrumentationPath() {
assertThat(
FileManager.isValidInstrumentationPath("/instrumentation/my-instrumentation/javaagent"))
.isTrue();
assertThat(FileManager.isValidInstrumentationPath("invalid/test/javaagent")).isFalse();
assertThat(FileManager.isValidInstrumentationPath("/instrumentation/test/javaagent")).isFalse();
}
@Test
void testFindBuildGradleFiles() throws IOException {
Path gradleFile = Files.createFile(tempDir.resolve("build.gradle.kts"));
Path nonGradleFile = Files.createFile(tempDir.resolve("gradle.properties"));
List<String> gradleFiles = fileManager.findBuildGradleFiles(tempDir.toString());
assertThat(gradleFiles).contains(gradleFile.toString());
assertThat(gradleFiles).doesNotContain(nonGradleFile.toString());
}
}

View File

@ -0,0 +1,75 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.docs.utils;
import static org.assertj.core.api.Assertions.assertThat;
import io.opentelemetry.instrumentation.docs.InstrumentationEntity;
import io.opentelemetry.instrumentation.docs.InstrumentationType;
import java.io.BufferedWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.junit.jupiter.api.Test;
class YamlHelperTest {
@Test
public void testPrintInstrumentationList() throws Exception {
List<InstrumentationEntity> entities = new ArrayList<>();
Map<InstrumentationType, Set<String>> targetVersions1 = new HashMap<>();
targetVersions1.put(
InstrumentationType.JAVAAGENT,
new HashSet<>(List.of("org.springframework:spring-web:[6.0.0,)")));
entities.add(
new InstrumentationEntity(
"instrumentation/spring/spring-web/spring-web-6.0",
"spring-web-6.0",
"spring",
"spring",
targetVersions1));
Map<InstrumentationType, Set<String>> targetVersions2 = new HashMap<>();
targetVersions2.put(
InstrumentationType.LIBRARY,
new HashSet<>(List.of("org.apache.struts:struts2-core:2.1.0")));
entities.add(
new InstrumentationEntity(
"instrumentation/struts/struts-2.3",
"struts-2.3",
"struts",
"struts",
targetVersions2));
StringWriter stringWriter = new StringWriter();
BufferedWriter writer = new BufferedWriter(stringWriter);
YamlHelper.printInstrumentationList(entities, writer);
writer.flush();
String expectedYaml =
"spring:\n"
+ " instrumentations:\n"
+ " - name: spring-web-6.0\n"
+ " srcPath: instrumentation/spring/spring-web/spring-web-6.0\n"
+ " target_versions:\n"
+ " javaagent:\n"
+ " - org.springframework:spring-web:[6.0.0,)\n"
+ "struts:\n"
+ " instrumentations:\n"
+ " - name: struts-2.3\n"
+ " srcPath: instrumentation/struts/struts-2.3\n"
+ " target_versions:\n"
+ " library:\n"
+ " - org.apache.struts:struts2-core:2.1.0\n";
assertThat(expectedYaml).isEqualTo(stringWriter.toString());
}
}

View File

@ -97,6 +97,7 @@ include(":instrumentation-annotations-support-testing")
// misc
include(":dependencyManagement")
include(":instrumentation-docs")
include(":test-report")
include(":testing:agent-exporter")
include(":testing:agent-for-testing")