Filter metrics by scope (#14136)
This commit is contained in:
parent
236f2fba17
commit
7689228d47
|
|
@ -2836,24 +2836,6 @@ libraries:
|
|||
target_versions:
|
||||
javaagent:
|
||||
- io.projectreactor.netty:reactor-netty:[0.8.2.RELEASE,1.0.0)
|
||||
telemetry:
|
||||
- when: default
|
||||
metrics:
|
||||
- name: http.client.request.duration
|
||||
description: Duration of HTTP client requests.
|
||||
type: HISTOGRAM
|
||||
unit: s
|
||||
attributes:
|
||||
- name: http.request.method
|
||||
type: STRING
|
||||
- name: http.response.status_code
|
||||
type: LONG
|
||||
- name: network.protocol.version
|
||||
type: STRING
|
||||
- name: server.address
|
||||
type: STRING
|
||||
- name: server.port
|
||||
type: LONG
|
||||
- name: reactor-netty-1.0
|
||||
source_path: instrumentation/reactor/reactor-netty/reactor-netty-1.0
|
||||
scope:
|
||||
|
|
@ -3214,21 +3196,6 @@ libraries:
|
|||
type: STRING
|
||||
- name: server.port
|
||||
type: LONG
|
||||
- name: http.server.request.duration
|
||||
description: Duration of HTTP server requests.
|
||||
type: HISTOGRAM
|
||||
unit: s
|
||||
attributes:
|
||||
- name: http.request.method
|
||||
type: STRING
|
||||
- name: http.response.status_code
|
||||
type: LONG
|
||||
- name: http.route
|
||||
type: STRING
|
||||
- name: network.protocol.version
|
||||
type: STRING
|
||||
- name: url.scheme
|
||||
type: STRING
|
||||
- name: spring-webflux-5.3
|
||||
source_path: instrumentation/spring/spring-webflux/spring-webflux-5.3
|
||||
scope:
|
||||
|
|
|
|||
|
|
@ -7,19 +7,17 @@ package io.opentelemetry.instrumentation.docs;
|
|||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.exc.ValueInstantiationException;
|
||||
import io.opentelemetry.instrumentation.docs.internal.EmittedMetrics;
|
||||
import io.opentelemetry.instrumentation.docs.internal.InstrumentationMetaData;
|
||||
import io.opentelemetry.instrumentation.docs.internal.InstrumentationModule;
|
||||
import io.opentelemetry.instrumentation.docs.internal.InstrumentationType;
|
||||
import io.opentelemetry.instrumentation.docs.parsers.EmittedMetricsParser;
|
||||
import io.opentelemetry.instrumentation.docs.parsers.GradleParser;
|
||||
import io.opentelemetry.instrumentation.docs.parsers.MetricParser;
|
||||
import io.opentelemetry.instrumentation.docs.parsers.ModuleParser;
|
||||
import io.opentelemetry.instrumentation.docs.parsers.SpanParser;
|
||||
import io.opentelemetry.instrumentation.docs.utils.FileManager;
|
||||
import io.opentelemetry.instrumentation.docs.utils.InstrumentationPath;
|
||||
import io.opentelemetry.instrumentation.docs.utils.YamlHelper;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
|
@ -65,7 +63,7 @@ class InstrumentationAnalyzer {
|
|||
}
|
||||
|
||||
module.setTargetVersions(getVersionInformation(module));
|
||||
module.setMetrics(MetricsProcessor.getMetrics(module, fileManager));
|
||||
module.setMetrics(MetricParser.getMetrics(module, fileManager));
|
||||
module.setSpans(SpanParser.getSpans(module, fileManager));
|
||||
}
|
||||
|
||||
|
|
@ -89,26 +87,4 @@ class InstrumentationAnalyzer {
|
|||
List<String> gradleFiles = fileManager.findBuildGradleFiles(module.getSrcPath());
|
||||
return GradleParser.extractVersions(gradleFiles, module);
|
||||
}
|
||||
|
||||
/** Handles processing of metrics data for instrumentation modules. */
|
||||
static class MetricsProcessor {
|
||||
|
||||
public static Map<String, List<EmittedMetrics.Metric>> getMetrics(
|
||||
InstrumentationModule module, FileManager fileManager) {
|
||||
Map<String, EmittedMetrics> metrics =
|
||||
EmittedMetricsParser.getMetricsFromFiles(fileManager.rootDir(), module.getSrcPath());
|
||||
|
||||
Map<String, List<EmittedMetrics.Metric>> result = new HashMap<>();
|
||||
metrics.entrySet().stream()
|
||||
.filter(MetricsProcessor::hasValidMetrics)
|
||||
.forEach(entry -> result.put(entry.getKey(), entry.getValue().getMetrics()));
|
||||
return result;
|
||||
}
|
||||
|
||||
private static boolean hasValidMetrics(Map.Entry<String, EmittedMetrics> entry) {
|
||||
return entry.getValue() != null && entry.getValue().getMetrics() != null;
|
||||
}
|
||||
|
||||
private MetricsProcessor() {}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,9 @@
|
|||
|
||||
package io.opentelemetry.instrumentation.docs.internal;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
|
@ -16,16 +19,18 @@ import java.util.List;
|
|||
public class EmittedMetrics {
|
||||
// Condition in which the metrics are emitted (ex: default, or configuration option names).
|
||||
private String when;
|
||||
private List<Metric> metrics;
|
||||
|
||||
@JsonProperty("metrics_by_scope")
|
||||
private List<MetricsByScope> metricsByScope;
|
||||
|
||||
public EmittedMetrics() {
|
||||
this.when = "";
|
||||
this.metrics = new ArrayList<>();
|
||||
this.metricsByScope = emptyList();
|
||||
}
|
||||
|
||||
public EmittedMetrics(String when, List<Metric> metrics) {
|
||||
this.when = "";
|
||||
this.metrics = metrics;
|
||||
public EmittedMetrics(String when, List<MetricsByScope> metricsByScope) {
|
||||
this.when = when;
|
||||
this.metricsByScope = metricsByScope;
|
||||
}
|
||||
|
||||
public String getWhen() {
|
||||
|
|
@ -36,12 +41,49 @@ public class EmittedMetrics {
|
|||
this.when = when;
|
||||
}
|
||||
|
||||
public List<Metric> getMetrics() {
|
||||
return metrics;
|
||||
@JsonProperty("metrics_by_scope")
|
||||
public List<MetricsByScope> getMetricsByScope() {
|
||||
return metricsByScope;
|
||||
}
|
||||
|
||||
public void setMetrics(List<Metric> metrics) {
|
||||
this.metrics = metrics;
|
||||
@JsonProperty("metrics_by_scope")
|
||||
public void setMetricsByScope(List<MetricsByScope> metricsByScope) {
|
||||
this.metricsByScope = metricsByScope;
|
||||
}
|
||||
|
||||
/**
|
||||
* This class is internal and is hence not for public use. Its APIs are unstable and can change at
|
||||
* any time.
|
||||
*/
|
||||
public static class MetricsByScope {
|
||||
private String scope;
|
||||
private List<Metric> metrics;
|
||||
|
||||
public MetricsByScope(String scope, List<Metric> metrics) {
|
||||
this.scope = scope;
|
||||
this.metrics = metrics;
|
||||
}
|
||||
|
||||
public MetricsByScope() {
|
||||
this.scope = "";
|
||||
this.metrics = new ArrayList<>();
|
||||
}
|
||||
|
||||
public String getScope() {
|
||||
return scope;
|
||||
}
|
||||
|
||||
public void setScope(String scope) {
|
||||
this.scope = scope;
|
||||
}
|
||||
|
||||
public List<Metric> getMetrics() {
|
||||
return metrics;
|
||||
}
|
||||
|
||||
public void setMetrics(List<Metric> metrics) {
|
||||
this.metrics = metrics;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
package io.opentelemetry.instrumentation.docs.parsers;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import io.opentelemetry.instrumentation.docs.internal.EmittedMetrics;
|
||||
import io.opentelemetry.instrumentation.docs.utils.FileManager;
|
||||
import io.opentelemetry.instrumentation.docs.utils.YamlHelper;
|
||||
|
|
@ -35,9 +36,24 @@ public class EmittedMetricsParser {
|
|||
*/
|
||||
public static Map<String, EmittedMetrics> getMetricsFromFiles(
|
||||
String rootDir, String instrumentationDirectory) {
|
||||
Map<String, StringBuilder> metricsByWhen = new HashMap<>();
|
||||
Path telemetryDir = Paths.get(rootDir + "/" + instrumentationDirectory, ".telemetry");
|
||||
|
||||
Map<String, List<EmittedMetrics.MetricsByScope>> metricsByWhen =
|
||||
parseAllMetricFiles(telemetryDir);
|
||||
|
||||
return aggregateMetricsByScope(metricsByWhen);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses all metric files in the given .telemetry directory and returns a map where the key is
|
||||
* the 'when' condition and the value is a list of metrics grouped by scope.
|
||||
*
|
||||
* @param telemetryDir the path to the .telemetry directory
|
||||
* @return a map of 'when' to list of metrics by scope
|
||||
*/
|
||||
private static Map<String, List<EmittedMetrics.MetricsByScope>> parseAllMetricFiles(
|
||||
Path telemetryDir) {
|
||||
Map<String, List<EmittedMetrics.MetricsByScope>> metricsByWhen = new HashMap<>();
|
||||
if (Files.exists(telemetryDir) && Files.isDirectory(telemetryDir)) {
|
||||
try (Stream<Path> files = Files.list(telemetryDir)) {
|
||||
files
|
||||
|
|
@ -49,14 +65,21 @@ public class EmittedMetricsParser {
|
|||
String when = content.substring(0, content.indexOf('\n'));
|
||||
String whenKey = when.replace("when: ", "");
|
||||
|
||||
metricsByWhen.putIfAbsent(whenKey, new StringBuilder("metrics:\n"));
|
||||
|
||||
// Skip the metric label ("metrics:") so we can aggregate into one list
|
||||
int metricsIndex = content.indexOf("metrics:\n");
|
||||
int metricsIndex = content.indexOf("metrics_by_scope:");
|
||||
if (metricsIndex != -1) {
|
||||
String contentAfterMetrics =
|
||||
content.substring(metricsIndex + "metrics:\n".length());
|
||||
metricsByWhen.get(whenKey).append(contentAfterMetrics);
|
||||
String yaml = "when: " + whenKey + "\n" + content.substring(metricsIndex);
|
||||
EmittedMetrics parsed;
|
||||
try {
|
||||
parsed = YamlHelper.emittedMetricsParser(yaml);
|
||||
} catch (Exception e) {
|
||||
logger.severe(
|
||||
"Error parsing metrics file (" + path + "): " + e.getMessage());
|
||||
return;
|
||||
}
|
||||
if (parsed.getMetricsByScope() != null) {
|
||||
metricsByWhen.putIfAbsent(whenKey, new ArrayList<>());
|
||||
metricsByWhen.get(whenKey).addAll(parsed.getMetricsByScope());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -64,37 +87,80 @@ public class EmittedMetricsParser {
|
|||
logger.severe("Error reading metrics files: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
return metricsByWhen;
|
||||
}
|
||||
|
||||
return parseMetrics(metricsByWhen);
|
||||
/**
|
||||
* Aggregates metrics under the same scope for each 'when' condition, deduplicating metrics by
|
||||
* name.
|
||||
*
|
||||
* @param metricsByWhen map of 'when' to list of metrics by scope
|
||||
* @return a map of 'when' to aggregated EmittedMetrics
|
||||
*/
|
||||
private static Map<String, EmittedMetrics> aggregateMetricsByScope(
|
||||
Map<String, List<EmittedMetrics.MetricsByScope>> metricsByWhen) {
|
||||
Map<String, EmittedMetrics> result = new HashMap<>();
|
||||
for (Map.Entry<String, List<EmittedMetrics.MetricsByScope>> entry : metricsByWhen.entrySet()) {
|
||||
String when = entry.getKey();
|
||||
List<EmittedMetrics.MetricsByScope> allScopes = entry.getValue();
|
||||
Map<String, Map<String, EmittedMetrics.Metric>> metricsByScopeName = new HashMap<>();
|
||||
|
||||
for (EmittedMetrics.MetricsByScope scopeEntry : allScopes) {
|
||||
String scope = scopeEntry.getScope();
|
||||
metricsByScopeName.putIfAbsent(scope, new HashMap<>());
|
||||
Map<String, EmittedMetrics.Metric> metricMap = metricsByScopeName.get(scope);
|
||||
|
||||
for (EmittedMetrics.Metric metric : scopeEntry.getMetrics()) {
|
||||
metricMap.put(metric.getName(), metric); // deduplicate by name
|
||||
}
|
||||
}
|
||||
|
||||
List<EmittedMetrics.MetricsByScope> mergedScopes = new ArrayList<>();
|
||||
for (Map.Entry<String, Map<String, EmittedMetrics.Metric>> scopeEntry :
|
||||
metricsByScopeName.entrySet()) {
|
||||
mergedScopes.add(
|
||||
new EmittedMetrics.MetricsByScope(
|
||||
scopeEntry.getKey(), new ArrayList<>(scopeEntry.getValue().values())));
|
||||
}
|
||||
result.put(when, new EmittedMetrics(when, mergedScopes));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes in a raw string representation of the aggregated EmittedMetrics yaml map, separated by
|
||||
* the `when`, indicating the conditions under which the metrics are emitted. deduplicates the
|
||||
* metrics by name and then returns a new map of EmittedMetrics objects.
|
||||
* the {@code when}, indicating the conditions under which the metrics are emitted. Deduplicates
|
||||
* the metrics by name and then returns a new map of EmittedMetrics objects.
|
||||
*
|
||||
* @param input raw string representation of EmittedMetrics yaml
|
||||
* @return {@code Map<String, EmittedMetrics>} where the key is the `when` condition
|
||||
* @return map where the key is the {@code when} condition and the value is the corresponding
|
||||
* EmittedMetrics
|
||||
* @throws JsonProcessingException if parsing fails
|
||||
*/
|
||||
// visible for testing
|
||||
public static Map<String, EmittedMetrics> parseMetrics(Map<String, StringBuilder> input) {
|
||||
public static Map<String, EmittedMetrics> parseMetrics(Map<String, StringBuilder> input)
|
||||
throws JsonProcessingException {
|
||||
Map<String, EmittedMetrics> metricsMap = new HashMap<>();
|
||||
for (Map.Entry<String, StringBuilder> entry : input.entrySet()) {
|
||||
String when = entry.getKey();
|
||||
StringBuilder content = entry.getValue();
|
||||
|
||||
EmittedMetrics metrics = YamlHelper.emittedMetricsParser(content.toString());
|
||||
if (metrics.getMetrics() == null) {
|
||||
if (metrics.getMetricsByScope() == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Map<String, EmittedMetrics.Metric> deduplicatedMetrics = new HashMap<>();
|
||||
for (EmittedMetrics.Metric metric : metrics.getMetrics()) {
|
||||
deduplicatedMetrics.put(metric.getName(), metric);
|
||||
List<EmittedMetrics.MetricsByScope> deduplicatedScopes = new ArrayList<>();
|
||||
for (EmittedMetrics.MetricsByScope scopeEntry : metrics.getMetricsByScope()) {
|
||||
String scope = scopeEntry.getScope();
|
||||
Map<String, EmittedMetrics.Metric> dedupedMetrics = new HashMap<>();
|
||||
for (EmittedMetrics.Metric metric : scopeEntry.getMetrics()) {
|
||||
dedupedMetrics.put(metric.getName(), metric);
|
||||
}
|
||||
deduplicatedScopes.add(
|
||||
new EmittedMetrics.MetricsByScope(scope, new ArrayList<>(dedupedMetrics.values())));
|
||||
}
|
||||
|
||||
List<EmittedMetrics.Metric> uniqueMetrics = new ArrayList<>(deduplicatedMetrics.values());
|
||||
metricsMap.put(when, new EmittedMetrics(when, uniqueMetrics));
|
||||
metricsMap.put(when, new EmittedMetrics(when, deduplicatedScopes));
|
||||
}
|
||||
return metricsMap;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,180 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.instrumentation.docs.parsers;
|
||||
|
||||
import io.opentelemetry.instrumentation.docs.internal.EmittedMetrics;
|
||||
import io.opentelemetry.instrumentation.docs.internal.InstrumentationModule;
|
||||
import io.opentelemetry.instrumentation.docs.internal.TelemetryAttribute;
|
||||
import io.opentelemetry.instrumentation.docs.utils.FileManager;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* This class is responsible for parsing metric files from the `.telemetry` directory of an
|
||||
* instrumentation module and filtering them by scope.
|
||||
*/
|
||||
public class MetricParser {
|
||||
|
||||
/**
|
||||
* Retrieves metrics for a given instrumentation module, filtered by scope.
|
||||
*
|
||||
* @param module the instrumentation module
|
||||
* @param fileManager the file manager to use for file operations
|
||||
* @return a map where the key is the 'when' condition and the value is a list of metrics
|
||||
*/
|
||||
public static Map<String, List<EmittedMetrics.Metric>> getMetrics(
|
||||
InstrumentationModule module, FileManager fileManager) {
|
||||
Map<String, EmittedMetrics> metrics =
|
||||
EmittedMetricsParser.getMetricsFromFiles(fileManager.rootDir(), module.getSrcPath());
|
||||
|
||||
if (metrics.isEmpty()) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
|
||||
String scopeName = module.getScopeInfo().getName();
|
||||
return filterMetricsByScope(metrics, scopeName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters metrics by scope and aggregates attributes for each metric kind.
|
||||
*
|
||||
* @param metricsByScope the map of metrics by scope
|
||||
* @param scopeName the name of the scope to filter metrics for
|
||||
* @return a map of filtered metrics by 'when'
|
||||
*/
|
||||
private static Map<String, List<EmittedMetrics.Metric>> filterMetricsByScope(
|
||||
Map<String, EmittedMetrics> metricsByScope, String scopeName) {
|
||||
|
||||
Map<String, Map<String, MetricAggregator.AggregatedMetricInfo>> aggregatedMetrics =
|
||||
new HashMap<>();
|
||||
|
||||
for (Map.Entry<String, EmittedMetrics> entry : metricsByScope.entrySet()) {
|
||||
if (!hasValidMetrics(entry.getValue())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String when = entry.getValue().getWhen();
|
||||
Map<String, Map<String, MetricAggregator.AggregatedMetricInfo>> result =
|
||||
MetricAggregator.aggregateMetrics(when, entry.getValue(), scopeName);
|
||||
|
||||
// Merge result into aggregatedMetrics
|
||||
for (Map.Entry<String, Map<String, MetricAggregator.AggregatedMetricInfo>> e :
|
||||
result.entrySet()) {
|
||||
String whenKey = e.getKey();
|
||||
Map<String, MetricAggregator.AggregatedMetricInfo> metricMap =
|
||||
aggregatedMetrics.computeIfAbsent(whenKey, k -> new HashMap<>());
|
||||
|
||||
for (Map.Entry<String, MetricAggregator.AggregatedMetricInfo> metricEntry :
|
||||
e.getValue().entrySet()) {
|
||||
String metricName = metricEntry.getKey();
|
||||
MetricAggregator.AggregatedMetricInfo newInfo = metricEntry.getValue();
|
||||
MetricAggregator.AggregatedMetricInfo existingInfo = metricMap.get(metricName);
|
||||
if (existingInfo == null) {
|
||||
metricMap.put(metricName, newInfo);
|
||||
} else {
|
||||
existingInfo.attributes.addAll(newInfo.attributes);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return MetricAggregator.buildFilteredMetrics(aggregatedMetrics);
|
||||
}
|
||||
|
||||
private static boolean hasValidMetrics(EmittedMetrics metrics) {
|
||||
return metrics != null && metrics.getMetricsByScope() != null;
|
||||
}
|
||||
|
||||
/** Helper class to aggregate metrics by scope and name. */
|
||||
static class MetricAggregator {
|
||||
/**
|
||||
* Aggregates metrics for a given 'when' condition, metrics object, and target scope name.
|
||||
*
|
||||
* @param when the 'when' condition
|
||||
* @param metrics the EmittedMetrics object
|
||||
* @param targetScopeName the scope name to filter by
|
||||
* @return a map of aggregated metrics by 'when' and metric name
|
||||
*/
|
||||
public static Map<String, Map<String, AggregatedMetricInfo>> aggregateMetrics(
|
||||
String when, EmittedMetrics metrics, String targetScopeName) {
|
||||
Map<String, Map<String, AggregatedMetricInfo>> aggregatedMetrics = new HashMap<>();
|
||||
Map<String, AggregatedMetricInfo> metricKindMap =
|
||||
aggregatedMetrics.computeIfAbsent(when, k -> new HashMap<>());
|
||||
|
||||
for (EmittedMetrics.MetricsByScope metricsByScope : metrics.getMetricsByScope()) {
|
||||
if (metricsByScope.getScope().equals(targetScopeName)) {
|
||||
for (EmittedMetrics.Metric metric : metricsByScope.getMetrics()) {
|
||||
AggregatedMetricInfo aggInfo =
|
||||
metricKindMap.computeIfAbsent(
|
||||
metric.getName(),
|
||||
k ->
|
||||
new AggregatedMetricInfo(
|
||||
metric.getName(),
|
||||
metric.getDescription(),
|
||||
metric.getType(),
|
||||
metric.getUnit()));
|
||||
if (metric.getAttributes() != null) {
|
||||
for (TelemetryAttribute attr : metric.getAttributes()) {
|
||||
aggInfo.attributes.add(new TelemetryAttribute(attr.getName(), attr.getType()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return aggregatedMetrics;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a filtered metrics map from aggregated metrics.
|
||||
*
|
||||
* @param aggregatedMetrics the aggregated metrics map
|
||||
* @return a map where the key is the 'when' condition and the value is a list of metrics
|
||||
*/
|
||||
public static Map<String, List<EmittedMetrics.Metric>> buildFilteredMetrics(
|
||||
Map<String, Map<String, AggregatedMetricInfo>> aggregatedMetrics) {
|
||||
Map<String, List<EmittedMetrics.Metric>> result = new HashMap<>();
|
||||
for (Map.Entry<String, Map<String, AggregatedMetricInfo>> entry :
|
||||
aggregatedMetrics.entrySet()) {
|
||||
String when = entry.getKey();
|
||||
List<EmittedMetrics.Metric> metrics = result.computeIfAbsent(when, k -> new ArrayList<>());
|
||||
for (AggregatedMetricInfo aggInfo : entry.getValue().values()) {
|
||||
metrics.add(
|
||||
new EmittedMetrics.Metric(
|
||||
aggInfo.name,
|
||||
aggInfo.description,
|
||||
aggInfo.type,
|
||||
aggInfo.unit,
|
||||
new ArrayList<>(aggInfo.attributes)));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Data class to hold aggregated metric information. */
|
||||
static class AggregatedMetricInfo {
|
||||
final String name;
|
||||
final String description;
|
||||
final String type;
|
||||
final String unit;
|
||||
final Set<TelemetryAttribute> attributes = new HashSet<>();
|
||||
|
||||
AggregatedMetricInfo(String name, String description, String type, String unit) {
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.type = type;
|
||||
this.unit = unit;
|
||||
}
|
||||
}
|
||||
|
||||
private MetricAggregator() {}
|
||||
}
|
||||
|
||||
private MetricParser() {}
|
||||
}
|
||||
|
|
@ -291,8 +291,8 @@ public class YamlHelper {
|
|||
return mapper.readValue(input, InstrumentationMetaData.class);
|
||||
}
|
||||
|
||||
public static EmittedMetrics emittedMetricsParser(String input) {
|
||||
return new Yaml().loadAs(input, EmittedMetrics.class);
|
||||
public static EmittedMetrics emittedMetricsParser(String input) throws JsonProcessingException {
|
||||
return mapper.readValue(input, EmittedMetrics.class);
|
||||
}
|
||||
|
||||
public static EmittedSpans emittedSpansParser(String input) throws JsonProcessingException {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,128 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.instrumentation.docs.parsers;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mockStatic;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import io.opentelemetry.instrumentation.docs.internal.EmittedMetrics;
|
||||
import io.opentelemetry.instrumentation.docs.utils.FileManager;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import org.mockito.MockedStatic;
|
||||
|
||||
@SuppressWarnings("NullAway")
|
||||
class EmittedMetricsParserTest {
|
||||
|
||||
@Test
|
||||
void parseMetricsDeduplicatesMetricsByName() throws JsonProcessingException {
|
||||
String input =
|
||||
"""
|
||||
metrics_by_scope:
|
||||
- scope: io.opentelemetry.alibaba-druid-1.0
|
||||
metrics:
|
||||
- name: metric1
|
||||
type: counter
|
||||
- name: metric1
|
||||
type: counter
|
||||
- name: metric2
|
||||
type: gauge
|
||||
""";
|
||||
|
||||
Map<String, StringBuilder> metricMap = new HashMap<>();
|
||||
metricMap.put("default", new StringBuilder(input));
|
||||
|
||||
Map<String, EmittedMetrics> result = EmittedMetricsParser.parseMetrics(metricMap);
|
||||
List<String> metricNames =
|
||||
result.get("default").getMetricsByScope().get(0).getMetrics().stream()
|
||||
.map(EmittedMetrics.Metric::getName)
|
||||
.sorted()
|
||||
.toList();
|
||||
|
||||
assertThat(metricNames).hasSize(2);
|
||||
assertThat(metricNames).containsExactly("metric1", "metric2");
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseMetricsHandlesEmptyInput() throws JsonProcessingException {
|
||||
String input = "metrics_by_scope:\n";
|
||||
Map<String, StringBuilder> metricMap = new HashMap<>();
|
||||
metricMap.put("default", new StringBuilder(input));
|
||||
|
||||
Map<String, EmittedMetrics> result = EmittedMetricsParser.parseMetrics(metricMap);
|
||||
assertThat(result).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getMetricsFromFilesCombinesFilesCorrectly(@TempDir Path tempDir) throws IOException {
|
||||
Path telemetryDir = Files.createDirectories(tempDir.resolve(".telemetry"));
|
||||
|
||||
String file1Content =
|
||||
"""
|
||||
when: default
|
||||
metrics_by_scope:
|
||||
- scope: io.opentelemetry.MetricParserTest
|
||||
metrics:
|
||||
- name: metric1
|
||||
type: counter
|
||||
""";
|
||||
|
||||
String file2Content =
|
||||
"""
|
||||
when: default
|
||||
metrics_by_scope:
|
||||
- scope: io.opentelemetry.MetricParserTest
|
||||
metrics:
|
||||
- name: metric2
|
||||
type: gauge
|
||||
""";
|
||||
|
||||
Files.writeString(telemetryDir.resolve("metrics-1.yaml"), file1Content);
|
||||
Files.writeString(telemetryDir.resolve("metrics-2.yaml"), file2Content);
|
||||
|
||||
// Create a non-metrics file that should be ignored
|
||||
Files.writeString(telemetryDir.resolve("other-file.yaml"), "some content");
|
||||
|
||||
try (MockedStatic<FileManager> fileManagerMock = mockStatic(FileManager.class)) {
|
||||
fileManagerMock
|
||||
.when(
|
||||
() -> FileManager.readFileToString(telemetryDir.resolve("metrics-1.yaml").toString()))
|
||||
.thenReturn(file1Content);
|
||||
fileManagerMock
|
||||
.when(
|
||||
() -> FileManager.readFileToString(telemetryDir.resolve("metrics-2.yaml").toString()))
|
||||
.thenReturn(file2Content);
|
||||
|
||||
Map<String, EmittedMetrics> result =
|
||||
EmittedMetricsParser.getMetricsFromFiles(tempDir.toString(), "");
|
||||
|
||||
EmittedMetrics.MetricsByScope metrics =
|
||||
result.get("default").getMetricsByScope().stream()
|
||||
.filter(scope -> scope.getScope().equals("io.opentelemetry.MetricParserTest"))
|
||||
.findFirst()
|
||||
.orElseThrow();
|
||||
|
||||
assertThat(metrics.getMetrics()).hasSize(2);
|
||||
List<String> metricNames =
|
||||
metrics.getMetrics().stream().map(EmittedMetrics.Metric::getName).sorted().toList();
|
||||
assertThat(metricNames).containsExactly("metric1", "metric2");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void getMetricsFromFilesHandlesNonexistentDirectory() throws JsonProcessingException {
|
||||
Map<String, EmittedMetrics> result =
|
||||
EmittedMetricsParser.getMetricsFromFiles("/nonexistent", "path");
|
||||
assertThat(result).isEmpty();
|
||||
}
|
||||
}
|
||||
|
|
@ -6,99 +6,110 @@
|
|||
package io.opentelemetry.instrumentation.docs.parsers;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mockStatic;
|
||||
|
||||
import io.opentelemetry.instrumentation.docs.internal.EmittedMetrics;
|
||||
import io.opentelemetry.instrumentation.docs.utils.FileManager;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashMap;
|
||||
import io.opentelemetry.instrumentation.docs.internal.TelemetryAttribute;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import org.mockito.MockedStatic;
|
||||
|
||||
@SuppressWarnings("NullAway")
|
||||
class MetricParserTest {
|
||||
|
||||
@Test
|
||||
void parseMetricsDeduplicatesMetricsByName() {
|
||||
String input =
|
||||
"""
|
||||
metrics:
|
||||
- name: metric1
|
||||
type: counter
|
||||
- name: metric1
|
||||
type: counter
|
||||
- name: metric2
|
||||
type: gauge
|
||||
""";
|
||||
void testFiltersMetricsByScope() {
|
||||
String targetScopeName = "my-instrumentation-scope";
|
||||
|
||||
Map<String, StringBuilder> metricMap = new HashMap<>();
|
||||
metricMap.put("default", new StringBuilder(input));
|
||||
EmittedMetrics.Metric metric1 = createMetric("my.metric1", "desc1", "attr1");
|
||||
EmittedMetrics.Metric otherMetric = createMetric("other.metric", "desc2", "other.attr");
|
||||
|
||||
Map<String, EmittedMetrics> result = EmittedMetricsParser.parseMetrics(metricMap);
|
||||
List<String> metricNames =
|
||||
result.get("default").getMetrics().stream()
|
||||
.map(EmittedMetrics.Metric::getName)
|
||||
.sorted()
|
||||
.toList();
|
||||
EmittedMetrics.MetricsByScope targetMetricsByScope =
|
||||
new EmittedMetrics.MetricsByScope(targetScopeName, List.of(metric1));
|
||||
|
||||
assertThat(metricNames).hasSize(2);
|
||||
assertThat(metricNames).containsExactly("metric1", "metric2");
|
||||
EmittedMetrics.MetricsByScope otherMetricsByScope =
|
||||
new EmittedMetrics.MetricsByScope("other-scope", List.of(otherMetric));
|
||||
|
||||
EmittedMetrics emittedMetrics =
|
||||
new EmittedMetrics("default", List.of(targetMetricsByScope, otherMetricsByScope));
|
||||
|
||||
Map<String, Map<String, MetricParser.MetricAggregator.AggregatedMetricInfo>> metrics =
|
||||
MetricParser.MetricAggregator.aggregateMetrics("default", emittedMetrics, targetScopeName);
|
||||
|
||||
Map<String, List<EmittedMetrics.Metric>> result =
|
||||
MetricParser.MetricAggregator.buildFilteredMetrics(metrics);
|
||||
|
||||
assertThat(result.get("default")).hasSize(1);
|
||||
assertThat(result.get("default").get(0).getName()).isEqualTo("my.metric1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseMetricsHandlesEmptyInput() {
|
||||
String input = "metrics:\n";
|
||||
Map<String, StringBuilder> metricMap = new HashMap<>();
|
||||
metricMap.put("default", new StringBuilder(input));
|
||||
void testAggregatesAndDeduplicatesAttributes() {
|
||||
String targetScopeName = "my-instrumentation-scope";
|
||||
|
||||
Map<String, EmittedMetrics> result = EmittedMetricsParser.parseMetrics(metricMap);
|
||||
assertThat(result).isEmpty();
|
||||
EmittedMetrics.Metric metric1 =
|
||||
new EmittedMetrics.Metric(
|
||||
"my.metric1",
|
||||
"desc1",
|
||||
"gauge",
|
||||
"unit",
|
||||
List.of(
|
||||
new TelemetryAttribute("attr1", "STRING"),
|
||||
new TelemetryAttribute("attr2", "STRING")));
|
||||
|
||||
EmittedMetrics.Metric metric1Dup =
|
||||
new EmittedMetrics.Metric(
|
||||
"my.metric1",
|
||||
"desc1",
|
||||
"gauge",
|
||||
"unit",
|
||||
List.of(
|
||||
new TelemetryAttribute("attr1", "STRING"),
|
||||
new TelemetryAttribute("attr3", "STRING")));
|
||||
|
||||
EmittedMetrics.MetricsByScope targetMetricsByScope =
|
||||
new EmittedMetrics.MetricsByScope(targetScopeName, List.of(metric1, metric1Dup));
|
||||
|
||||
EmittedMetrics emittedMetrics = new EmittedMetrics("default", List.of(targetMetricsByScope));
|
||||
|
||||
Map<String, Map<String, MetricParser.MetricAggregator.AggregatedMetricInfo>> metrics =
|
||||
MetricParser.MetricAggregator.aggregateMetrics("default", emittedMetrics, targetScopeName);
|
||||
|
||||
Map<String, List<EmittedMetrics.Metric>> result =
|
||||
MetricParser.MetricAggregator.buildFilteredMetrics(metrics);
|
||||
List<TelemetryAttribute> attrs = result.get("default").get(0).getAttributes();
|
||||
|
||||
assertThat(attrs).hasSize(3);
|
||||
assertThat(attrs.stream().map(TelemetryAttribute::getName))
|
||||
.containsExactlyInAnyOrder("attr1", "attr2", "attr3");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getMetricsFromFilesCombinesFilesCorrectly(@TempDir Path tempDir) throws IOException {
|
||||
Path telemetryDir = Files.createDirectories(tempDir.resolve(".telemetry"));
|
||||
void testPreservesMetricMetadata() {
|
||||
String targetScopeName = "my-instrumentation-scope";
|
||||
|
||||
String file1Content = "when: default\n metrics:\n - name: metric1\n type: counter\n";
|
||||
String file2Content = "when: default\n metrics:\n - name: metric2\n type: gauge\n";
|
||||
EmittedMetrics.Metric metric1 =
|
||||
createMetric("my.metric1", "description of my.metric1", "attr1");
|
||||
|
||||
Files.writeString(telemetryDir.resolve("metrics-1.yaml"), file1Content);
|
||||
Files.writeString(telemetryDir.resolve("metrics-2.yaml"), file2Content);
|
||||
EmittedMetrics.MetricsByScope targetMetricsByScope =
|
||||
new EmittedMetrics.MetricsByScope(targetScopeName, List.of(metric1));
|
||||
|
||||
// Create a non-metrics file that should be ignored
|
||||
Files.writeString(telemetryDir.resolve("other-file.yaml"), "some content");
|
||||
EmittedMetrics emittedMetrics = new EmittedMetrics("default", List.of(targetMetricsByScope));
|
||||
|
||||
try (MockedStatic<FileManager> fileManagerMock = mockStatic(FileManager.class)) {
|
||||
fileManagerMock
|
||||
.when(
|
||||
() -> FileManager.readFileToString(telemetryDir.resolve("metrics-1.yaml").toString()))
|
||||
.thenReturn(file1Content);
|
||||
fileManagerMock
|
||||
.when(
|
||||
() -> FileManager.readFileToString(telemetryDir.resolve("metrics-2.yaml").toString()))
|
||||
.thenReturn(file2Content);
|
||||
Map<String, Map<String, MetricParser.MetricAggregator.AggregatedMetricInfo>> metrics =
|
||||
MetricParser.MetricAggregator.aggregateMetrics("default", emittedMetrics, targetScopeName);
|
||||
|
||||
Map<String, EmittedMetrics> result =
|
||||
EmittedMetricsParser.getMetricsFromFiles(tempDir.toString(), "");
|
||||
Map<String, List<EmittedMetrics.Metric>> result =
|
||||
MetricParser.MetricAggregator.buildFilteredMetrics(metrics);
|
||||
|
||||
EmittedMetrics metrics = result.get("default");
|
||||
|
||||
assertThat(metrics.getMetrics()).hasSize(2);
|
||||
List<String> metricNames =
|
||||
metrics.getMetrics().stream().map(EmittedMetrics.Metric::getName).sorted().toList();
|
||||
assertThat(metricNames).containsExactly("metric1", "metric2");
|
||||
}
|
||||
EmittedMetrics.Metric foundMetric = result.get("default").get(0);
|
||||
assertThat(foundMetric.getName()).isEqualTo("my.metric1");
|
||||
assertThat(foundMetric.getDescription()).isEqualTo("description of my.metric1");
|
||||
assertThat(foundMetric.getType()).isEqualTo("gauge");
|
||||
assertThat(foundMetric.getUnit()).isEqualTo("unit");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getMetricsFromFilesHandlesNonexistentDirectory() {
|
||||
Map<String, EmittedMetrics> result =
|
||||
EmittedMetricsParser.getMetricsFromFiles("/nonexistent", "path");
|
||||
assertThat(result).isEmpty();
|
||||
private static EmittedMetrics.Metric createMetric(
|
||||
String name, String description, String attrName) {
|
||||
return new EmittedMetrics.Metric(
|
||||
name, description, "gauge", "unit", List.of(new TelemetryAttribute(attrName, "STRING")));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ public final class AgentTestRunner extends InstrumentationTestRunner {
|
|||
}
|
||||
String path = Paths.get(resource.getPath()).toString();
|
||||
|
||||
MetaDataCollector.writeTelemetryToFiles(path, metrics, tracesByScope);
|
||||
MetaDataCollector.writeTelemetryToFiles(path, metricsByScope, tracesByScope);
|
||||
}
|
||||
|
||||
// additional library ignores are ignored during tests, because they can make it really
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ import org.awaitility.core.ConditionTimeoutException;
|
|||
public abstract class InstrumentationTestRunner {
|
||||
|
||||
private final TestInstrumenters testInstrumenters;
|
||||
protected Map<String, MetricData> metrics = new HashMap<>();
|
||||
protected Map<InstrumentationScopeInfo, Map<String, MetricData>> metricsByScope = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Stores traces by scope, where each scope contains a map of span kinds to a map of attribute
|
||||
|
|
@ -205,8 +205,12 @@ public abstract class InstrumentationTestRunner {
|
|||
|
||||
private void collectEmittedMetrics(List<MetricData> metrics) {
|
||||
for (MetricData metric : metrics) {
|
||||
if (!this.metrics.containsKey(metric.getName())) {
|
||||
this.metrics.put(metric.getName(), metric);
|
||||
Map<String, MetricData> scopeMap =
|
||||
this.metricsByScope.computeIfAbsent(
|
||||
metric.getInstrumentationScopeInfo(), m -> new HashMap<>());
|
||||
|
||||
if (!scopeMap.containsKey(metric.getName())) {
|
||||
scopeMap.put(metric.getName(), metric);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -130,7 +130,7 @@ public final class LibraryTestRunner extends InstrumentationTestRunner {
|
|||
}
|
||||
String path = Paths.get(resource.getPath()).toString();
|
||||
|
||||
MetaDataCollector.writeTelemetryToFiles(path, metrics, tracesByScope);
|
||||
MetaDataCollector.writeTelemetryToFiles(path, metricsByScope, tracesByScope);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -39,13 +39,13 @@ public final class MetaDataCollector {
|
|||
|
||||
public static void writeTelemetryToFiles(
|
||||
String path,
|
||||
Map<String, MetricData> metrics,
|
||||
Map<InstrumentationScopeInfo, Map<String, MetricData>> metricsByScope,
|
||||
Map<InstrumentationScopeInfo, Map<SpanKind, Map<InternalAttributeKeyImpl<?>, AttributeType>>>
|
||||
spansByScopeAndKind)
|
||||
throws IOException {
|
||||
|
||||
String moduleRoot = extractInstrumentationPath(path);
|
||||
writeMetricData(moduleRoot, metrics);
|
||||
writeMetricData(moduleRoot, metricsByScope);
|
||||
writeSpanData(moduleRoot, spansByScopeAndKind);
|
||||
}
|
||||
|
||||
|
|
@ -125,10 +125,12 @@ public final class MetaDataCollector {
|
|||
}
|
||||
}
|
||||
|
||||
private static void writeMetricData(String instrumentationPath, Map<String, MetricData> metrics)
|
||||
private static void writeMetricData(
|
||||
String instrumentationPath,
|
||||
Map<InstrumentationScopeInfo, Map<String, MetricData>> metricsByScope)
|
||||
throws IOException {
|
||||
|
||||
if (metrics.isEmpty()) {
|
||||
if (metricsByScope.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -144,26 +146,36 @@ public final class MetaDataCollector {
|
|||
|
||||
writer.write("when: " + when + "\n");
|
||||
|
||||
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);
|
||||
}
|
||||
});
|
||||
writer.write("metrics_by_scope:\n");
|
||||
|
||||
for (Map.Entry<InstrumentationScopeInfo, Map<String, MetricData>> entry :
|
||||
metricsByScope.entrySet()) {
|
||||
InstrumentationScopeInfo scope = entry.getKey();
|
||||
Map<String, MetricData> metrics = entry.getValue();
|
||||
|
||||
writer.write(" - scope: " + scope.getName() + "\n");
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue