Automatically Generate Instrumentation Documentation (#13449)
This commit is contained in:
parent
d28aca1b60
commit
0cfd2d976a
File diff suppressed because it is too large
Load Diff
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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?
|
|
@ -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() {}
|
||||
}
|
|
@ -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() {}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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) {}
|
|
@ -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() {}
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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")
|
||||
|
|
Loading…
Reference in New Issue