From 8716f004c91ce0fd53f26234fb9547ed1a1b6d00 Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Fri, 12 Dec 2025 10:10:50 -0500 Subject: [PATCH] powerjob metadata --- docs/instrumentation-list.yaml | 34 ++ instrumentation-docs/instrumentations.sh | 2 + .../powerjob-4.0/javaagent/build.gradle.kts | 24 +- .../v4_0/PowerJobBasicProcessorTest.java | 322 +++++++++--------- instrumentation/powerjob-4.0/metadata.yaml | 11 + 5 files changed, 225 insertions(+), 168 deletions(-) create mode 100644 instrumentation/powerjob-4.0/metadata.yaml diff --git a/docs/instrumentation-list.yaml b/docs/instrumentation-list.yaml index 7e9524c0d495..17eb88f19d74 100644 --- a/docs/instrumentation-list.yaml +++ b/docs/instrumentation-list.yaml @@ -10263,12 +10263,46 @@ libraries: type: STRING powerjob: - name: powerjob-4.0 + display_name: PowerJob + description: This instrumentation enables spans for PowerJob job processor executions. + library_link: https://github.com/PowerJob/PowerJob source_path: instrumentation/powerjob-4.0 scope: name: io.opentelemetry.powerjob-4.0 target_versions: javaagent: - tech.powerjob:powerjob-worker:[4.0.0,) + configurations: + - name: otel.instrumentation.powerjob.experimental-span-attributes + description: | + Enables experimental span attributes `job.system`, `scheduling.powerjob.job.id`, `scheduling.powerjob.job.param`, `scheduling.powerjob.job.instance.param`, and `scheduling.powerjob.job.type`. + type: boolean + default: false + telemetry: + - when: default + spans: + - span_kind: INTERNAL + attributes: + - name: code.function + type: STRING + - name: code.namespace + type: STRING + - when: otel.instrumentation.powerjob.experimental-span-attributes=true + spans: + - span_kind: INTERNAL + attributes: + - name: code.function + type: STRING + - name: code.namespace + type: STRING + - name: job.system + type: STRING + - name: scheduling.powerjob.job.id + type: LONG + - name: scheduling.powerjob.job.param + type: STRING + - name: scheduling.powerjob.job.type + type: STRING pulsar: - name: pulsar-2.8 source_path: instrumentation/pulsar/pulsar-2.8 diff --git a/instrumentation-docs/instrumentations.sh b/instrumentation-docs/instrumentations.sh index 94bfdf28ecd1..2626af585a76 100755 --- a/instrumentation-docs/instrumentations.sh +++ b/instrumentation-docs/instrumentations.sh @@ -198,6 +198,8 @@ readonly INSTRUMENTATIONS=( "play:play-ws:play-ws-1.0:javaagent:test" "play:play-ws:play-ws-2.0:javaagent:test" "play:play-ws:play-ws-2.1:javaagent:test" + "powerjob-4.0:javaagent:test" + "powerjob-4.0:javaagent:testExperimental" "reactor:reactor-netty:reactor-netty-0.9:javaagent:test" "reactor:reactor-netty:reactor-netty-1.0:javaagent:test" "spring:spring-batch-3.0:javaagent:test" diff --git a/instrumentation/powerjob-4.0/javaagent/build.gradle.kts b/instrumentation/powerjob-4.0/javaagent/build.gradle.kts index 68b536143917..351f716e3f81 100644 --- a/instrumentation/powerjob-4.0/javaagent/build.gradle.kts +++ b/instrumentation/powerjob-4.0/javaagent/build.gradle.kts @@ -17,9 +17,23 @@ dependencies { library("tech.powerjob:powerjob-official-processors:1.1.0") } -tasks.withType().configureEach { - // required on jdk17 - jvmArgs("--add-opens=java.base/java.lang=ALL-UNNAMED") - jvmArgs("-XX:+IgnoreUnrecognizedVMOptions") - jvmArgs("-Dotel.instrumentation.powerjob.experimental-span-attributes=true") +tasks { + withType().configureEach { + // required on jdk17 + jvmArgs("--add-opens=java.base/java.lang=ALL-UNNAMED") + jvmArgs("-XX:+IgnoreUnrecognizedVMOptions") + systemProperty("collectMetadata", findProperty("collectMetadata")?.toString() ?: "false") + } + + val testExperimental by registering(Test::class) { + testClassesDirs = sourceSets.test.get().output.classesDirs + classpath = sourceSets.test.get().runtimeClasspath + + jvmArgs("-Dotel.instrumentation.powerjob.experimental-span-attributes=true") + systemProperty("metadataConfig", "otel.instrumentation.powerjob.experimental-span-attributes=true") + } + + check { + dependsOn(testExperimental) + } } diff --git a/instrumentation/powerjob-4.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/powerjob/v4_0/PowerJobBasicProcessorTest.java b/instrumentation/powerjob-4.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/powerjob/v4_0/PowerJobBasicProcessorTest.java index 7897a18275e5..5fa719cded4c 100644 --- a/instrumentation/powerjob-4.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/powerjob/v4_0/PowerJobBasicProcessorTest.java +++ b/instrumentation/powerjob-4.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/powerjob/v4_0/PowerJobBasicProcessorTest.java @@ -5,6 +5,8 @@ package io.opentelemetry.javaagent.instrumentation.powerjob.v4_0; +import static io.opentelemetry.api.common.AttributeKey.longKey; +import static io.opentelemetry.api.common.AttributeKey.stringKey; import static io.opentelemetry.instrumentation.testing.junit.code.SemconvCodeStabilityUtil.codeFunctionAssertions; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; import static java.util.Arrays.asList; @@ -12,7 +14,6 @@ import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.zaxxer.hikari.HikariDataSource; -import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.internal.StringUtils; import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; @@ -41,6 +42,9 @@ class PowerJobBasicProcessorTest { @RegisterExtension private static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); + private static final boolean EXPERIMENTAL_ATTRIBUTES_ENABLED = + Boolean.getBoolean("otel.instrumentation.powerjob.experimental-span-attributes"); + private static final String BASIC_PROCESSOR = "BasicProcessor"; private static final String BROADCAST_PROCESSOR = "BroadcastProcessor"; private static final String MAP_PROCESSOR = "MapProcessor"; @@ -60,17 +64,19 @@ void testBasicProcessor() throws Exception { BasicProcessor testBasicProcessor = new TestBasicProcessor(); testBasicProcessor.process(taskContext); testing.waitAndAssertTraces( - trace -> { - trace.hasSpansSatisfyingExactly( - span -> { - span.hasName(String.format("%s.process", TestBasicProcessor.class.getSimpleName())) - .hasKind(SpanKind.INTERNAL) - .hasStatus(StatusData.unset()) - .hasAttributesSatisfyingExactly( - attributeAssertions( - TestBasicProcessor.class.getName(), jobId, jobParam, BASIC_PROCESSOR)); - }); - }); + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName( + String.format("%s.process", TestBasicProcessor.class.getSimpleName())) + .hasKind(SpanKind.INTERNAL) + .hasStatus(StatusData.unset()) + .hasAttributesSatisfyingExactly( + attributeAssertions( + TestBasicProcessor.class.getName(), + jobId, + jobParam, + BASIC_PROCESSOR)))); } @Test @@ -81,21 +87,20 @@ void testBasicFailProcessor() throws Exception { BasicProcessor testBasicFailProcessor = new TestBasicFailProcessor(); testBasicFailProcessor.process(taskContext); testing.waitAndAssertTraces( - trace -> { - trace.hasSpansSatisfyingExactly( - span -> { - span.hasName( - String.format("%s.process", TestBasicFailProcessor.class.getSimpleName())) - .hasKind(SpanKind.INTERNAL) - .hasStatus(StatusData.error()) - .hasAttributesSatisfyingExactly( - attributeAssertions( - TestBasicFailProcessor.class.getName(), - jobId, - jobParam, - BASIC_PROCESSOR)); - }); - }); + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName( + String.format( + "%s.process", TestBasicFailProcessor.class.getSimpleName())) + .hasKind(SpanKind.INTERNAL) + .hasStatus(StatusData.error()) + .hasAttributesSatisfyingExactly( + attributeAssertions( + TestBasicFailProcessor.class.getName(), + jobId, + jobParam, + BASIC_PROCESSOR)))); } @Test @@ -106,21 +111,20 @@ void testBroadcastProcessor() throws Exception { BasicProcessor testBroadcastProcessor = new TestBroadcastProcessor(); testBroadcastProcessor.process(taskContext); testing.waitAndAssertTraces( - trace -> { - trace.hasSpansSatisfyingExactly( - span -> { - span.hasName( - String.format("%s.process", TestBroadcastProcessor.class.getSimpleName())) - .hasKind(SpanKind.INTERNAL) - .hasStatus(StatusData.unset()) - .hasAttributesSatisfyingExactly( - attributeAssertions( - TestBroadcastProcessor.class.getName(), - jobId, - jobParam, - BROADCAST_PROCESSOR)); - }); - }); + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName( + String.format( + "%s.process", TestBroadcastProcessor.class.getSimpleName())) + .hasKind(SpanKind.INTERNAL) + .hasStatus(StatusData.unset()) + .hasAttributesSatisfyingExactly( + attributeAssertions( + TestBroadcastProcessor.class.getName(), + jobId, + jobParam, + BROADCAST_PROCESSOR)))); } @Test @@ -131,21 +135,20 @@ void testMapProcessor() throws Exception { BasicProcessor testMapProcessProcessor = new TestMapProcessProcessor(); testMapProcessProcessor.process(taskContext); testing.waitAndAssertTraces( - trace -> { - trace.hasSpansSatisfyingExactly( - span -> { - span.hasName( - String.format("%s.process", TestMapProcessProcessor.class.getSimpleName())) - .hasKind(SpanKind.INTERNAL) - .hasStatus(StatusData.unset()) - .hasAttributesSatisfyingExactly( - attributeAssertions( - TestMapProcessProcessor.class.getName(), - jobId, - jobParam, - MAP_PROCESSOR)); - }); - }); + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName( + String.format( + "%s.process", TestMapProcessProcessor.class.getSimpleName())) + .hasKind(SpanKind.INTERNAL) + .hasStatus(StatusData.unset()) + .hasAttributesSatisfyingExactly( + attributeAssertions( + TestMapProcessProcessor.class.getName(), + jobId, + jobParam, + MAP_PROCESSOR)))); } @Test @@ -156,22 +159,20 @@ void testMapReduceProcessor() throws Exception { BasicProcessor testMapReduceProcessProcessor = new TestMapReduceProcessProcessor(); testMapReduceProcessProcessor.process(taskContext); testing.waitAndAssertTraces( - trace -> { - trace.hasSpansSatisfyingExactly( - span -> { - span.hasName( - String.format( - "%s.process", TestMapReduceProcessProcessor.class.getSimpleName())) - .hasKind(SpanKind.INTERNAL) - .hasStatus(StatusData.unset()) - .hasAttributesSatisfyingExactly( - attributeAssertions( - TestMapReduceProcessProcessor.class.getName(), - jobId, - jobParam, - MAP_REDUCE_PROCESSOR)); - }); - }); + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName( + String.format( + "%s.process", TestMapReduceProcessProcessor.class.getSimpleName())) + .hasKind(SpanKind.INTERNAL) + .hasStatus(StatusData.unset()) + .hasAttributesSatisfyingExactly( + attributeAssertions( + TestMapReduceProcessProcessor.class.getName(), + jobId, + jobParam, + MAP_REDUCE_PROCESSOR)))); } @DisabledOnOs(value = OS.WINDOWS, disabledReason = "ShellProcessor requires /bin/sh") @@ -185,17 +186,18 @@ void testShellProcessor() throws Exception { BasicProcessor shellProcessor = new ShellProcessor(); shellProcessor.process(taskContext); testing.waitAndAssertTraces( - trace -> { - trace.hasSpansSatisfyingExactly( - span -> { - span.hasName(String.format("%s.process", ShellProcessor.class.getSimpleName())) - .hasKind(SpanKind.INTERNAL) - .hasStatus(StatusData.unset()) - .hasAttributesSatisfyingExactly( - attributeAssertions( - ShellProcessor.class.getName(), jobId, jobParam, SHELL_PROCESSOR)); - }); - }); + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName(String.format("%s.process", ShellProcessor.class.getSimpleName())) + .hasKind(SpanKind.INTERNAL) + .hasStatus(StatusData.unset()) + .hasAttributesSatisfyingExactly( + attributeAssertions( + ShellProcessor.class.getName(), + jobId, + jobParam, + SHELL_PROCESSOR)))); } @Test @@ -208,18 +210,17 @@ void testPythonProcessor() throws Exception { BasicProcessor pythonProcessor = new PythonProcessor(); pythonProcessor.process(taskContext); testing.waitAndAssertTraces( - trace -> { - trace.hasSpansSatisfyingExactly( - span -> { - // not asserting status as it is either unset or error depending on whether python - // is available or not - span.hasName(String.format("%s.process", PythonProcessor.class.getSimpleName())) - .hasKind(SpanKind.INTERNAL) - .hasAttributesSatisfyingExactly( - attributeAssertions( - PythonProcessor.class.getName(), jobId, jobParam, PYTHON_PROCESSOR)); - }); - }); + trace -> + trace.hasSpansSatisfyingExactly( + span -> { + // not asserting status as it is either unset or error depending on whether python + // is available or not + span.hasName(String.format("%s.process", PythonProcessor.class.getSimpleName())) + .hasKind(SpanKind.INTERNAL) + .hasAttributesSatisfyingExactly( + attributeAssertions( + PythonProcessor.class.getName(), jobId, jobParam, PYTHON_PROCESSOR)); + })); } @Test @@ -232,17 +233,15 @@ void testHttpProcessor() throws Exception { BasicProcessor httpProcessor = new HttpProcessor(); httpProcessor.process(taskContext); testing.waitAndAssertTraces( - trace -> { - trace.hasSpansSatisfyingExactly( - span -> { - span.hasName(String.format("%s.process", HttpProcessor.class.getSimpleName())) - .hasKind(SpanKind.INTERNAL) - .hasStatus(StatusData.error()) - .hasAttributesSatisfyingExactly( - attributeAssertions( - HttpProcessor.class.getName(), jobId, jobParam, HTTP_PROCESSOR)); - }); - }); + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName(String.format("%s.process", HttpProcessor.class.getSimpleName())) + .hasKind(SpanKind.INTERNAL) + .hasStatus(StatusData.error()) + .hasAttributesSatisfyingExactly( + attributeAssertions( + HttpProcessor.class.getName(), jobId, jobParam, HTTP_PROCESSOR)))); } @Test @@ -260,21 +259,19 @@ void testFileCleanerProcessor() throws Exception { BasicProcessor fileCleanupProcessor = new FileCleanupProcessor(); fileCleanupProcessor.process(taskContext); testing.waitAndAssertTraces( - trace -> { - trace.hasSpansSatisfyingExactly( - span -> { - span.hasName( - String.format("%s.process", FileCleanupProcessor.class.getSimpleName())) - .hasKind(SpanKind.INTERNAL) - .hasStatus(StatusData.unset()) - .hasAttributesSatisfyingExactly( - attributeAssertions( - FileCleanupProcessor.class.getName(), - jobId, - jobParam, - FILE_CLEANUP_PROCESSOR)); - }); - }); + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName( + String.format("%s.process", FileCleanupProcessor.class.getSimpleName())) + .hasKind(SpanKind.INTERNAL) + .hasStatus(StatusData.unset()) + .hasAttributesSatisfyingExactly( + attributeAssertions( + FileCleanupProcessor.class.getName(), + jobId, + jobParam, + FILE_CLEANUP_PROCESSOR)))); } @Test @@ -288,22 +285,20 @@ void testSpringDataSourceProcessor() throws Exception { BasicProcessor springDatasourceSqlProcessor = new SpringDatasourceSqlProcessor(dataSource); springDatasourceSqlProcessor.process(taskContext); testing.waitAndAssertTraces( - trace -> { - trace.hasSpansSatisfyingExactly( - span -> { - span.hasName( - String.format( - "%s.process", SpringDatasourceSqlProcessor.class.getSimpleName())) - .hasKind(SpanKind.INTERNAL) - .hasStatus(StatusData.error()) - .hasAttributesSatisfyingExactly( - attributeAssertions( - SpringDatasourceSqlProcessor.class.getName(), - jobId, - jobParam, - SPRING_DATASOURCE_SQL_PROCESSOR)); - }); - }); + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName( + String.format( + "%s.process", SpringDatasourceSqlProcessor.class.getSimpleName())) + .hasKind(SpanKind.INTERNAL) + .hasStatus(StatusData.error()) + .hasAttributesSatisfyingExactly( + attributeAssertions( + SpringDatasourceSqlProcessor.class.getName(), + jobId, + jobParam, + SPRING_DATASOURCE_SQL_PROCESSOR)))); } @Test @@ -316,22 +311,20 @@ void testDynamicDataSourceProcessor() throws Exception { BasicProcessor dynamicDatasourceSqlProcessor = new DynamicDatasourceSqlProcessor(); dynamicDatasourceSqlProcessor.process(taskContext); testing.waitAndAssertTraces( - trace -> { - trace.hasSpansSatisfyingExactly( - span -> { - span.hasName( - String.format( - "%s.process", DynamicDatasourceSqlProcessor.class.getSimpleName())) - .hasKind(SpanKind.INTERNAL) - .hasStatus(StatusData.error()) - .hasAttributesSatisfyingExactly( - attributeAssertions( - DynamicDatasourceSqlProcessor.class.getName(), - jobId, - jobParam, - DYNAMIC_DATASOURCE_SQL_PROCESSOR)); - }); - }); + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName( + String.format( + "%s.process", DynamicDatasourceSqlProcessor.class.getSimpleName())) + .hasKind(SpanKind.INTERNAL) + .hasStatus(StatusData.error()) + .hasAttributesSatisfyingExactly( + attributeAssertions( + DynamicDatasourceSqlProcessor.class.getName(), + jobId, + jobParam, + DYNAMIC_DATASOURCE_SQL_PROCESSOR)))); } private static TaskContext genTaskContext(long jobId, String jobParam) { @@ -343,18 +336,21 @@ private static TaskContext genTaskContext(long jobId, String jobParam) { private static List attributeAssertions( String codeNamespace, long jobId, String jobParam, String jobType) { - List attributeAssertions = - new ArrayList<>( - asList( - equalTo(AttributeKey.stringKey("job.system"), "powerjob"), - equalTo(AttributeKey.longKey("scheduling.powerjob.job.id"), jobId), - equalTo(AttributeKey.stringKey("scheduling.powerjob.job.type"), jobType))); + List attributeAssertions = new ArrayList<>(); + + if (EXPERIMENTAL_ATTRIBUTES_ENABLED) { + attributeAssertions.addAll( + new ArrayList<>( + asList( + equalTo(stringKey("job.system"), "powerjob"), + equalTo(longKey("scheduling.powerjob.job.id"), jobId), + equalTo(stringKey("scheduling.powerjob.job.type"), jobType)))); + } attributeAssertions.addAll(codeFunctionAssertions(codeNamespace, "process")); - if (!StringUtils.isNullOrEmpty(jobParam)) { - attributeAssertions.add( - equalTo(AttributeKey.stringKey("scheduling.powerjob.job.param"), jobParam)); + if (!StringUtils.isNullOrEmpty(jobParam) && EXPERIMENTAL_ATTRIBUTES_ENABLED) { + attributeAssertions.add(equalTo(stringKey("scheduling.powerjob.job.param"), jobParam)); } return attributeAssertions; } diff --git a/instrumentation/powerjob-4.0/metadata.yaml b/instrumentation/powerjob-4.0/metadata.yaml new file mode 100644 index 000000000000..f6eb992e44dd --- /dev/null +++ b/instrumentation/powerjob-4.0/metadata.yaml @@ -0,0 +1,11 @@ +description: This instrumentation enables spans for PowerJob job processor executions. +display_name: PowerJob +library_link: https://github.com/PowerJob/PowerJob +configurations: + - name: otel.instrumentation.powerjob.experimental-span-attributes + description: > + Enables experimental span attributes `job.system`, `scheduling.powerjob.job.id`, + `scheduling.powerjob.job.param`, `scheduling.powerjob.job.instance.param`, and + `scheduling.powerjob.job.type`. + type: boolean + default: false \ No newline at end of file