Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion agent/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ javadoc{
jar {
manifest {
attributes(
'Premain-Class': 'io.pyroscope.javaagent.PyroscopeAgent'
'Premain-Class': 'io.pyroscope.javaagent.PyroscopeAgent',
'Implementation-Version': pyroscopeVersion
)
}
from(asyncProfilerDistDir) {
Expand Down Expand Up @@ -92,6 +93,12 @@ java {

shadowJar {
dependsOn copyBootstrapToResources
manifest {
attributes(
'Premain-Class': 'io.pyroscope.javaagent.PyroscopeAgent',
'Implementation-Version': pyroscopeVersion
)
}

exclude 'Log4j-*'
exclude 'META-INF/org/apache/logging/log4j/**'
Expand Down
14 changes: 14 additions & 0 deletions agent/src/main/java/io/pyroscope/javaagent/config/AppName.java
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,20 @@ public Builder addLabels(Map<String, String> labels) {
return this;
}

public Builder addLabelsIfAbsent(Map<String, String> labels) {
for (Map.Entry<String, String> it : labels.entrySet()) {
addLabelIfAbsent(it.getKey(), it.getValue());
}
return this;
}

public Builder addLabelIfAbsent(String k, String v) {
if (!this.labels.containsKey(k)) {
addLabel(k, v);
}
return this;
}

public AppName build() {
return new AppName(name, labels);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,19 @@

import java.io.IOException;
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.zip.Deflater;

public class PyroscopeExporter implements Exporter {

private static final MediaType PROTOBUF = MediaType.parse("application/x-protobuf");
private static final String OTEL_SCOPE_NAME = "otel.scope.name";
private static final String OTEL_SCOPE_VERSION = "otel.scope.version";
private static final String PROCESS_RUNTIME_NAME = "process.runtime.name";
private static final String PROCESS_RUNTIME_VERSION = "process.runtime.version";
private static final String PYROSCOPE_SCOPE_NAME = "com.grafana.pyroscope/java";

final Config config;
final Logger logger;
Expand Down Expand Up @@ -171,7 +178,39 @@ private String nameWithStaticLabels() {
return config.timeseries.newBuilder()
.addLabels(config.labels)
.addLabels(Pyroscope.getStaticLabels())
.addLabelsIfAbsent(otelRequiredLabels())
.build()
.toString();
}

private static Map<String, String> otelRequiredLabels() {
Map<String, String> labels = new HashMap<>();
labels.put(OTEL_SCOPE_NAME, PYROSCOPE_SCOPE_NAME);
putLabelIfNotEmpty(labels, OTEL_SCOPE_VERSION, scopeVersion());
putSystemProperty(labels, PROCESS_RUNTIME_NAME, "java.runtime.name");
putSystemProperty(labels, PROCESS_RUNTIME_VERSION, "java.runtime.version");
return labels;
}

private static void putSystemProperty(Map<String, String> labels, String labelName, String propertyName) {
putLabelIfNotEmpty(labels, labelName, System.getProperty(propertyName));
}

private static void putLabelIfNotEmpty(Map<String, String> labels, String labelName, String labelValue) {
if (labelValue != null && !labelValue.isEmpty()) {
labels.put(labelName, labelValue);
}
}

private static String scopeVersion() {
Package packageInfo = PyroscopeExporter.class.getPackage();
if (packageInfo == null) {
return null;
}
String version = packageInfo.getImplementationVersion();
if (version == null || version.isEmpty()) {
return null;
}
return version;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package io.pyroscope.javaagent.impl;

import io.pyroscope.javaagent.api.Logger;
import io.pyroscope.javaagent.config.Config;
import io.pyroscope.labels.v2.Pyroscope;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;

public class PyroscopeExporterTest {
private static final Logger NOOP_LOGGER = (level, msg, args) -> {};

@BeforeEach
@AfterEach
void resetStaticLabels() {
Pyroscope.setStaticLabels(Collections.emptyMap());
}

@Test
void addsRequiredOtelLabels() {
PyroscopeExporter exporter = new PyroscopeExporter(
new Config.Builder()
.setApplicationName("test.app")
.build(),
NOOP_LOGGER);
try {
Map<String, String> labels = labelsFromSeriesName(exporter.staticLabels);

assertEquals("com.grafana.pyroscope/java", labels.get("otel.scope.name"));
if (labels.containsKey("otel.scope.version")) {
assertFalse(labels.get("otel.scope.version").isEmpty());
}
assertEquals(System.getProperty("java.runtime.name"), labels.get("process.runtime.name"));
assertEquals(System.getProperty("java.runtime.version"), labels.get("process.runtime.version"));
} finally {
exporter.stop();
}
}

@Test
void staticLabelsOverrideRequiredOtelDefaults() {
Pyroscope.setStaticLabels(mapOf(
"otel.scope.name", "staticScopeName",
"otel.scope.version", "staticScopeVersion",
"process.runtime.name", "staticRuntimeName",
"process.runtime.version", "staticRuntimeVersion"));

PyroscopeExporter exporter = new PyroscopeExporter(
new Config.Builder()
.setApplicationName("test.app")
.build(),
NOOP_LOGGER);
try {
Map<String, String> labels = labelsFromSeriesName(exporter.staticLabels);

assertEquals("staticScopeName", labels.get("otel.scope.name"));
assertEquals("staticScopeVersion", labels.get("otel.scope.version"));
assertEquals("staticRuntimeName", labels.get("process.runtime.name"));
assertEquals("staticRuntimeVersion", labels.get("process.runtime.version"));
} finally {
exporter.stop();
}
}

@Test
void userLabelsOverrideRequiredOtelDefaults() {
Map<String, String> configLabels = mapOf(
"process.runtime.name", "configRuntimeName",
"process.runtime.version", "configRuntimeVersion");
Map<String, String> staticLabels = mapOf(
"otel.scope.version", "staticScopeVersion");
Pyroscope.setStaticLabels(staticLabels);

PyroscopeExporter exporter = new PyroscopeExporter(
new Config.Builder()
.setApplicationName("test.app{otel.scope.name=appScopeName}")
.setLabels(configLabels)
.build(),
NOOP_LOGGER);
try {
Map<String, String> labels = labelsFromSeriesName(exporter.staticLabels);

assertEquals("appScopeName", labels.get("otel.scope.name"));
assertEquals("staticScopeVersion", labels.get("otel.scope.version"));
assertEquals("configRuntimeName", labels.get("process.runtime.name"));
assertEquals("configRuntimeVersion", labels.get("process.runtime.version"));
} finally {
exporter.stop();
}
}

private static Map<String, String> labelsFromSeriesName(String seriesName) {
int labelsStart = seriesName.indexOf('{');
int labelsEnd = seriesName.lastIndexOf('}');
if (labelsStart == -1 || labelsEnd == -1 || labelsStart >= labelsEnd) {
return Collections.emptyMap();
}

Map<String, String> labels = new HashMap<>();
String labelsString = seriesName.substring(labelsStart + 1, labelsEnd);
for (String label : labelsString.split(",")) {
int separator = label.indexOf('=');
if (separator == -1) {
continue;
}
labels.put(label.substring(0, separator), label.substring(separator + 1));
}
return labels;
}

private static Map<String, String> mapOf(String... pairs) {
Map<String, String> labels = new HashMap<>();
for (int i = 0; i < pairs.length; i += 2) {
labels.put(pairs[i], pairs[i + 1]);
}
return labels;
}
}
Loading