Skip to content

Commit a6ab2c6

Browse files
authored
Differentiate emitted metrics by configuration options (#14018)
1 parent c726d32 commit a6ab2c6

File tree

23 files changed

+968
-459
lines changed

23 files changed

+968
-459
lines changed

docs/instrumentation-list.yaml

Lines changed: 666 additions & 329 deletions
Large diffs are not rendered by default.

instrumentation-docs/collect.sh

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,25 @@ fi
1414

1515
readonly INSTRUMENTATIONS=(
1616
# <module path (colon-separated)> : <javaagent|library> : [ gradle-task-suffix ]
17+
"alibaba-druid-1.0:javaagent:test"
18+
"alibaba-druid-1.0:javaagent:testStableSemconv"
1719
"apache-dbcp-2.0:javaagent:test"
20+
"apache-dbcp-2.0:javaagent:testStableSemconv"
1821
"apache-httpclient:apache-httpclient-5.0:javaagent:test"
19-
"alibaba-druid-1.0:javaagent:test"
2022
"c3p0-0.9:javaagent:test"
23+
"c3p0-0.9:javaagent:testStableSemconv"
24+
"clickhouse-client-0.5:javaagent:test"
25+
"clickhouse-client-0.5:javaagent:testStableSemconv"
2126
"hikaricp-3.0:javaagent:test"
27+
"hikaricp-3.0:javaagent:testStableSemconv"
2228
"tomcat:tomcat-jdbc:javaagent:test"
29+
"tomcat:tomcat-jdbc:javaagent:testStableSemconv"
2330
"oracle-ucp-11.2:javaagent:test"
31+
"oracle-ucp-11.2:javaagent:testStableSemconv"
2432
"oshi:javaagent:test"
33+
"oshi:javaagent:testExperimental"
2534
"vibur-dbcp-11.0:javaagent:test"
35+
"vibur-dbcp-11.0:javaagent:testStableSemconv"
2636
)
2737

2838
readonly TELEMETRY_DIR_NAME=".telemetry"

instrumentation-docs/readme.md

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,35 @@ tasks {
2222
}
2323
```
2424

25+
Sometimes instrumentation will behave differently based on configuration options, and we can
26+
differentiate between these configurations by using the `metaDataConfig` system property. When the
27+
telemetry is written to a file, the value of this property will be included, or it will default to
28+
a `default` attribution.
29+
30+
For example, to collect and write metadata for the `otel.semconv-stability.opt-in=database` option
31+
set for an instrumentation:
32+
33+
```kotlin
34+
val collectMetadata = findProperty("collectMetadata")?.toString() ?: "false"
35+
36+
tasks {
37+
val testStableSemconv by registering(Test::class) {
38+
jvmArgs("-Dotel.semconv-stability.opt-in=database")
39+
40+
systemProperty("collectMetadata", collectMetadata)
41+
systemProperty("metaDataConfig", "otel.semconv-stability.opt-in=database")
42+
}
43+
44+
test {
45+
systemProperty("collectMetadata", collectMetadata)
46+
}
47+
48+
check {
49+
dependsOn(testStableSemconv)
50+
}
51+
}
52+
```
53+
2554
Then, prior to running the analyzer, run the following command to generate `.telemetry` files:
2655

2756
`./gradlew test -PcollectMetadata=true`
@@ -36,8 +65,6 @@ or use the helper script that will run only the currently supported tests (recom
3665
./instrumentation-docs/collect.sh
3766
```
3867

39-
This script will also clean up all `.telemetry` files after the analysis is done.
40-
4168
## Instrumentation Hierarchy
4269

4370
An "InstrumentationModule" represents a module that that targets specific code in a
@@ -101,7 +128,8 @@ public class SpringWebInstrumentationModule extends InstrumentationModule
101128
* List of settings that are available for the instrumentation module
102129
* Each setting has a name, description, type, and default value
103130
* metrics
104-
* List of metrics that the instrumentation module collects, including the metric name, description, type, and attributes
131+
* List of metrics that the instrumentation module collects, including the metric name, description, type, and attributes.
132+
* Separate lists for the metrics emitted by default vs via configuration options.
105133

106134
## Methodology
107135

@@ -150,3 +178,6 @@ generate the metrics section of the instrumentation-list.yaml file.
150178

151179
The data is written into a `.telemetry` directory in the root of each instrumentation module. This
152180
data will be excluded from git and just generated on demand.
181+
182+
Each file has a `when` value along with the list of metrics that indicates whether the telemetry is emitted by default or via a
183+
configuration option.

instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/InstrumentationAnalyzer.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -92,10 +92,14 @@ List<InstrumentationModule> analyze() throws IOException {
9292
}
9393
}
9494

95-
EmittedMetrics metrics =
95+
Map<String, EmittedMetrics> metrics =
9696
MetricParser.getMetricsFromFiles(fileManager.rootDir(), module.getSrcPath());
97-
if (!metrics.getMetrics().isEmpty()) {
98-
module.setMetrics(metrics.getMetrics());
97+
98+
for (Map.Entry<String, EmittedMetrics> entry : metrics.entrySet()) {
99+
if (entry.getValue() == null || entry.getValue().getMetrics() == null) {
100+
continue;
101+
}
102+
module.getMetrics().put(entry.getKey(), entry.getValue().getMetrics());
99103
}
100104
}
101105
return modules;

instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/internal/EmittedMetrics.java

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,33 @@
99
import java.util.List;
1010

1111
/**
12-
* This class is internal and is hence not for public use. Its APIs are unstable and can change at
13-
* any time.
12+
* Representation of metrics emitted by an instrumentation. Includes context about whether emitted
13+
* by default or via a configuration option. This class is internal and is hence not for public use.
14+
* Its APIs are unstable and can change at any time.
1415
*/
1516
public class EmittedMetrics {
17+
// Condition in which the metrics are emitted (ex: default, or configuration option names).
18+
private String when;
1619
private List<Metric> metrics;
1720

1821
public EmittedMetrics() {
22+
this.when = "";
1923
this.metrics = new ArrayList<>();
2024
}
2125

22-
public EmittedMetrics(List<Metric> metrics) {
26+
public EmittedMetrics(String when, List<Metric> metrics) {
27+
this.when = "";
2328
this.metrics = metrics;
2429
}
2530

31+
public String getWhen() {
32+
return when;
33+
}
34+
35+
public void setWhen(String when) {
36+
this.when = when;
37+
}
38+
2639
public List<Metric> getMetrics() {
2740
return metrics;
2841
}

instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/internal/InstrumentationModule.java

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@
99

1010
import com.google.errorprone.annotations.CanIgnoreReturnValue;
1111
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
12+
import java.util.HashMap;
1213
import java.util.List;
1314
import java.util.Map;
15+
import java.util.Objects;
1416
import java.util.Set;
1517
import javax.annotation.Nullable;
1618

@@ -26,7 +28,7 @@ public class InstrumentationModule {
2628
private final String namespace;
2729
private final String group;
2830
private final InstrumentationScopeInfo scopeInfo;
29-
@Nullable private List<EmittedMetrics.Metric> metrics;
31+
private Map<String, List<EmittedMetrics.Metric>> metrics;
3032

3133
@Nullable private Map<InstrumentationType, Set<String>> targetVersions;
3234

@@ -44,11 +46,11 @@ public InstrumentationModule(Builder builder) {
4446
requireNonNull(builder.namespace, "namespace required");
4547
requireNonNull(builder.group, "group required");
4648

49+
this.metrics = Objects.requireNonNullElseGet(builder.metrics, HashMap::new);
4750
this.srcPath = builder.srcPath;
4851
this.instrumentationName = builder.instrumentationName;
4952
this.namespace = builder.namespace;
5053
this.group = builder.group;
51-
this.metrics = builder.metrics;
5254
this.metadata = builder.metadata;
5355
this.targetVersions = builder.targetVersions;
5456
this.minJavaVersion = builder.minJavaVersion;
@@ -93,8 +95,7 @@ public Integer getMinJavaVersion() {
9395
return minJavaVersion;
9496
}
9597

96-
@Nullable
97-
public List<EmittedMetrics.Metric> getMetrics() {
98+
public Map<String, List<EmittedMetrics.Metric>> getMetrics() {
9899
return metrics;
99100
}
100101

@@ -110,7 +111,7 @@ public void setMinJavaVersion(Integer minJavaVersion) {
110111
this.minJavaVersion = minJavaVersion;
111112
}
112113

113-
public void setMetrics(List<EmittedMetrics.Metric> metrics) {
114+
public void setMetrics(Map<String, List<EmittedMetrics.Metric>> metrics) {
114115
this.metrics = metrics;
115116
}
116117

@@ -126,7 +127,7 @@ public static class Builder {
126127
@Nullable private Integer minJavaVersion;
127128
@Nullable private InstrumentationMetaData metadata;
128129
@Nullable private Map<InstrumentationType, Set<String>> targetVersions;
129-
@Nullable private List<EmittedMetrics.Metric> metrics;
130+
@Nullable private Map<String, List<EmittedMetrics.Metric>> metrics;
130131

131132
@CanIgnoreReturnValue
132133
public Builder srcPath(String srcPath) {
@@ -171,7 +172,7 @@ public Builder targetVersions(Map<InstrumentationType, Set<String>> targetVersio
171172
}
172173

173174
@CanIgnoreReturnValue
174-
public Builder metrics(List<EmittedMetrics.Metric> metrics) {
175+
public Builder metrics(Map<String, List<EmittedMetrics.Metric>> metrics) {
175176
this.metrics = metrics;
176177
return this;
177178
}

instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/parsers/MetricParser.java

Lines changed: 36 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
import java.nio.file.Path;
1414
import java.nio.file.Paths;
1515
import java.util.ArrayList;
16-
import java.util.Collections;
1716
import java.util.HashMap;
1817
import java.util.List;
1918
import java.util.Map;
@@ -30,9 +29,9 @@ public class MetricParser {
3029
* @param instrumentationDirectory the directory to traverse
3130
* @return contents of aggregated files
3231
*/
33-
public static EmittedMetrics getMetricsFromFiles(
32+
public static Map<String, EmittedMetrics> getMetricsFromFiles(
3433
String rootDir, String instrumentationDirectory) {
35-
StringBuilder metricsContent = new StringBuilder("metrics:\n");
34+
Map<String, StringBuilder> metricsByWhen = new HashMap<>();
3635
Path telemetryDir = Paths.get(rootDir + "/" + instrumentationDirectory, ".telemetry");
3736

3837
if (Files.exists(telemetryDir) && Files.isDirectory(telemetryDir)) {
@@ -43,11 +42,17 @@ public static EmittedMetrics getMetricsFromFiles(
4342
path -> {
4443
String content = FileManager.readFileToString(path.toString());
4544
if (content != null) {
46-
// Skip the first line of yaml ("metrics:") so we can aggregate into one list
47-
int firstNewline = content.indexOf('\n');
48-
if (firstNewline != -1) {
49-
String contentWithoutFirstLine = content.substring(firstNewline + 1);
50-
metricsContent.append(contentWithoutFirstLine);
45+
String when = content.substring(0, content.indexOf('\n'));
46+
String whenKey = when.replace("when: ", "");
47+
48+
metricsByWhen.putIfAbsent(whenKey, new StringBuilder("metrics:\n"));
49+
50+
// Skip the metric label ("metrics:") so we can aggregate into one list
51+
int metricsIndex = content.indexOf("metrics:\n");
52+
if (metricsIndex != -1) {
53+
String contentAfterMetrics =
54+
content.substring(metricsIndex + "metrics:\n".length());
55+
metricsByWhen.get(whenKey).append(contentAfterMetrics);
5156
}
5257
}
5358
});
@@ -56,31 +61,38 @@ public static EmittedMetrics getMetricsFromFiles(
5661
}
5762
}
5863

59-
return parseMetrics(metricsContent.toString());
64+
return parseMetrics(metricsByWhen);
6065
}
6166

6267
/**
63-
* Takes in a raw string representation of the aggregated EmittedMetrics yaml, deduplicates the
64-
* metrics by name and then returns a new EmittedMetrics object.
68+
* Takes in a raw string representation of the aggregated EmittedMetrics yaml map, separated by
69+
* the `when`, indicating the conditions under which the metrics are emitted. deduplicates the
70+
* metrics by name and then returns a new map EmittedMetrics objects.
6571
*
6672
* @param input raw string representation of EmittedMetrics yaml
67-
* @return EmittedMetrics
73+
* @return {@code Map<String, EmittedMetrics>} where the key is the `when` condition
6874
*/
6975
// visible for testing
70-
public static EmittedMetrics parseMetrics(String input) {
71-
EmittedMetrics metrics = YamlHelper.emittedMetricsParser(input);
72-
if (metrics.getMetrics() == null) {
73-
return new EmittedMetrics(Collections.emptyList());
74-
}
76+
public static Map<String, EmittedMetrics> parseMetrics(Map<String, StringBuilder> input) {
77+
Map<String, EmittedMetrics> metricsMap = new HashMap<>();
78+
for (Map.Entry<String, StringBuilder> entry : input.entrySet()) {
79+
String when = entry.getKey();
80+
StringBuilder content = entry.getValue();
7581

76-
// deduplicate metrics by name
77-
Map<String, EmittedMetrics.Metric> deduplicatedMetrics = new HashMap<>();
78-
for (EmittedMetrics.Metric metric : metrics.getMetrics()) {
79-
deduplicatedMetrics.put(metric.getName(), metric);
80-
}
82+
EmittedMetrics metrics = YamlHelper.emittedMetricsParser(content.toString());
83+
if (metrics.getMetrics() == null) {
84+
continue;
85+
}
86+
87+
Map<String, EmittedMetrics.Metric> deduplicatedMetrics = new HashMap<>();
88+
for (EmittedMetrics.Metric metric : metrics.getMetrics()) {
89+
deduplicatedMetrics.put(metric.getName(), metric);
90+
}
8191

82-
List<EmittedMetrics.Metric> uniqueMetrics = new ArrayList<>(deduplicatedMetrics.values());
83-
return new EmittedMetrics(uniqueMetrics);
92+
List<EmittedMetrics.Metric> uniqueMetrics = new ArrayList<>(deduplicatedMetrics.values());
93+
metricsMap.put(when, new EmittedMetrics(when, uniqueMetrics));
94+
}
95+
return metricsMap;
8496
}
8597

8698
private MetricParser() {}

0 commit comments

Comments
 (0)