Skip to content
Merged
Show file tree
Hide file tree
Changes from 18 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
1 change: 1 addition & 0 deletions docs/supported-libraries.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ These are the supported libraries and frameworks:
| [OSHI](https://github.com/oshi/oshi/) | 5.3.1+ | [opentelemetry-oshi](../instrumentation/oshi/library) | [System Metrics] (partial support) |
| [Play MVC](https://github.com/playframework/playframework) | 2.4+ | N/A | Provides `http.route` [2], Controller Spans [3] |
| [Play WS](https://github.com/playframework/play-ws) | 1.0+ | N/A | [HTTP Client Spans], [HTTP Client Metrics] |
| [PowerJob](http://www.powerjob.tech/) | 4.0+ | N/A | none |
| [Quarkus Resteasy Reactive](https://quarkus.io/extensions/io.quarkus/quarkus-resteasy-reactive/) | 2.16.7+ | N/A | Provides `http.route` [2] |
| [Quartz](https://www.quartz-scheduler.org/) | 2.0+ | [opentelemetry-quartz-2.0](../instrumentation/quartz-2.0/library) | none |
| [R2DBC](https://r2dbc.io/) | 1.0+ | [opentelemetry-r2dbc-1.0](../instrumentation/r2dbc-1.0/library) | [Database Client Spans] |
Expand Down
5 changes: 5 additions & 0 deletions instrumentation/powerjob-4.0/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Settings for the PowerJob instrumentation

| System property | Type | Default | Description |
|--------------------------------------------------------------|---------|---------|-----------------------------------------------------|
| `otel.instrumentation.powerjob.experimental-span-attributes` | Boolean | `false` | Enable the capture of experimental span attributes. |
25 changes: 25 additions & 0 deletions instrumentation/powerjob-4.0/javaagent/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
plugins {
id("otel.javaagent-instrumentation")
}

muzzle {
pass {
group.set("tech.powerjob")
module.set("powerjob-worker")
versions.set("[4.0.0,)")
assertInverse.set(true)
extraDependency("tech.powerjob:powerjob-official-processors:1.1.0")
}
}

dependencies {
library("tech.powerjob:powerjob-worker:4.0.0")
library("tech.powerjob:powerjob-official-processors:1.1.0")
}

tasks.withType<Test>().configureEach {
// required on jdk17
jvmArgs("--add-opens=java.base/java.lang=ALL-UNNAMED")
jvmArgs("-XX:+IgnoreUnrecognizedVMOptions")
jvmArgs("-Dotel.instrumentation.powerjob.experimental-span-attributes=true")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.powerjob.v4_0;

import static io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge.currentContext;
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface;
import static io.opentelemetry.javaagent.instrumentation.powerjob.v4_0.PowerJobSingletons.helper;
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;

import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
import tech.powerjob.worker.core.processor.ProcessResult;
import tech.powerjob.worker.core.processor.TaskContext;
import tech.powerjob.worker.core.processor.sdk.BasicProcessor;

public class BasicProcessorInstrumentation implements TypeInstrumentation {
@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return implementsInterface(named("tech.powerjob.worker.core.processor.sdk.BasicProcessor"));
}

@Override
public void transform(TypeTransformer transformer) {
transformer.applyAdviceToMethod(
named("process").and(isPublic()).and(takesArguments(1)),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

usually we verify here that the argument type is what is expected in the advice

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

BasicProcessorInstrumentation.class.getName() + "$ProcessAdvice");
}

public static class ProcessAdvice {

@SuppressWarnings("unused")
@Advice.OnMethodEnter(suppress = Throwable.class)
public static void onSchedule(
@Advice.This BasicProcessor handler,
@Advice.Argument(0) TaskContext taskContext,
@Advice.Local("otelRequest") PowerJobProcessRequest request,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {
Context parentContext = currentContext();
request = PowerJobProcessRequest.createRequest(taskContext.getJobId(), handler, "process");
request.setInstanceParams(taskContext.getInstanceParams());
request.setJobParams(taskContext.getJobParams());
context = helper().startSpan(parentContext, request);
if (context == null) {
return;
}
scope = context.makeCurrent();
}

@SuppressWarnings("unused")
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void stopSpan(
@Advice.Return ProcessResult result,
@Advice.Thrown Throwable throwable,
@Advice.Local("otelRequest") PowerJobProcessRequest request,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {
helper().stopSpan(result, request, throwable, scope, context);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.powerjob.v4_0;

import io.opentelemetry.instrumentation.api.incubator.semconv.code.CodeAttributesGetter;
import javax.annotation.Nullable;

class PowerJobCodeAttributesGetter implements CodeAttributesGetter<PowerJobProcessRequest> {

@Nullable
@Override
public Class<?> getCodeClass(PowerJobProcessRequest powerJobProcessRequest) {
return powerJobProcessRequest.getDeclaringClass();
}

@Nullable
@Override
public String getMethodName(PowerJobProcessRequest powerJobProcessRequest) {
return powerJobProcessRequest.getMethodName();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.powerjob.v4_0;

public final class PowerJobConstants {

private PowerJobConstants() {}

public static final String BASIC_PROCESSOR = "BasicProcessor";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

perhaps move it to the class that uses it, imo no need to have a separate class for 1 constant.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

public static final String BROADCAST_PROCESSOR = "BroadcastProcessor";
public static final String MAP_PROCESSOR = "MapProcessor";
public static final String MAP_REDUCE_PROCESSOR = "MapReduceProcessor";

// Official processors
public static final String SHELL_PROCESSOR = "ShellProcessor";
public static final String PYTHON_PROCESSOR = "PythonProcessor";
public static final String HTTP_PROCESSOR = "HttpProcessor";
public static final String FILE_CLEANUP_PROCESSOR = "FileCleanupProcessor";
public static final String SPRING_DATASOURCE_SQL_PROCESSOR = "SpringDatasourceSqlProcessor";
public static final String DYNAMIC_DATASOURCE_SQL_PROCESSOR = "DynamicDatasourceSqlProcessor";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are only used in the tests, you could move them there or inline them.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.powerjob.v4_0;

import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.AttributesBuilder;
import io.opentelemetry.context.Context;
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
import javax.annotation.Nullable;

class PowerJobExperimentalAttributeExtractor
implements AttributesExtractor<PowerJobProcessRequest, Void> {

private static final AttributeKey<Long> POWERJOB_JOB_ID =
AttributeKey.longKey("scheduling.powerjob.job.id");
private static final AttributeKey<String> POWERJOB_JOB_PARAM =
AttributeKey.stringKey("scheduling.powerjob.job.param");
private static final AttributeKey<String> POWERJOB_JOB_INSTANCE_PARAM =
AttributeKey.stringKey("scheduling.powerjob.job.instance.param");
private static final AttributeKey<String> POWERJOB_JOB_INSTANCE_TRPE =
AttributeKey.stringKey("scheduling.powerjob.job.type");

@Override
public void onStart(
AttributesBuilder attributes,
Context parentContext,
PowerJobProcessRequest powerJobProcessRequest) {
attributes.put(POWERJOB_JOB_ID, powerJobProcessRequest.getJobId());
attributes.put(POWERJOB_JOB_PARAM, powerJobProcessRequest.getJobParams());
attributes.put(POWERJOB_JOB_INSTANCE_PARAM, powerJobProcessRequest.getInstanceParams());
attributes.put(POWERJOB_JOB_INSTANCE_TRPE, powerJobProcessRequest.getJobType());
}

@Override
public void onEnd(
AttributesBuilder attributes,
Context context,
PowerJobProcessRequest powerJobProcessRequest,
@Nullable Void unused,
@Nullable Throwable error) {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.powerjob.v4_0;

import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import java.util.function.Predicate;
import tech.powerjob.worker.core.processor.ProcessResult;

public final class PowerJobHelper {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd just inline the startSpan and endSpan methods. The instrumentations that use a similar helper class are usually more complicated.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your suggestion, I will streamline the code.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done


private final Instrumenter<PowerJobProcessRequest, Void> instrumenter;

private final Predicate<ProcessResult> failedStatusPredicate;

private PowerJobHelper(
Instrumenter<PowerJobProcessRequest, Void> instrumenter,
Predicate<ProcessResult> failedStatusPredicate) {
this.instrumenter = instrumenter;
this.failedStatusPredicate = failedStatusPredicate;
}

public static PowerJobHelper create(
Instrumenter<PowerJobProcessRequest, Void> instrumenter,
Predicate<ProcessResult> failedStatusPredicate) {
return new PowerJobHelper(instrumenter, failedStatusPredicate);
}

public Context startSpan(Context parentContext, PowerJobProcessRequest request) {
if (!instrumenter.shouldStart(parentContext, request)) {
return null;
}
return instrumenter.start(parentContext, request);
}

public void stopSpan(
ProcessResult result,
PowerJobProcessRequest request,
Throwable throwable,
Scope scope,
Context context) {
if (scope == null) {
return;
}
if (failedStatusPredicate.test(result)) {
request.setFailed();
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Usually we'd just pass the result as response to the instrumenter then in the span status extractor you could just use result.isSuccess() without the need to pass failedStatusPredicate around.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

scope.close();
instrumenter.end(context, request, null, throwable);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.powerjob.v4_0;

import static java.util.Arrays.asList;

import com.google.auto.service.AutoService;
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import java.util.List;

@AutoService(InstrumentationModule.class)
public class PowerJobInstrumentationModule extends InstrumentationModule {
public PowerJobInstrumentationModule() {
super("powerjob", "powerjob-4.0");
}

@Override
public List<TypeInstrumentation> typeInstrumentations() {
return asList(new BasicProcessorInstrumentation());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.powerjob.v4_0;

import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.trace.StatusCode;
import io.opentelemetry.instrumentation.api.incubator.semconv.code.CodeAttributesExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder;
import io.opentelemetry.javaagent.bootstrap.internal.AgentInstrumentationConfig;

public final class PowerJobInstrumenterFactory {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd merge the code from this class into PowerJobSingletons

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done


static final boolean CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES =
AgentInstrumentationConfig.get()
.getBoolean("otel.instrumentation.powerjob.experimental-span-attributes", false);

public static Instrumenter<PowerJobProcessRequest, Void> create(String instrumentationName) {
PowerJobCodeAttributesGetter codeAttributesGetter = new PowerJobCodeAttributesGetter();
PowerJobSpanNameExtractor spanNameExtractor =
new PowerJobSpanNameExtractor(codeAttributesGetter);

InstrumenterBuilder<PowerJobProcessRequest, Void> builder =
Instrumenter.<PowerJobProcessRequest, Void>builder(
GlobalOpenTelemetry.get(), instrumentationName, spanNameExtractor)
.addAttributesExtractor(CodeAttributesExtractor.create(codeAttributesGetter))
.setSpanStatusExtractor(
(spanStatusBuilder, powerJobProcessRequest, response, error) -> {
if (error != null || powerJobProcessRequest.isFailed()) {
spanStatusBuilder.setStatus(StatusCode.ERROR);
}
});

if (CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES) {
builder.addAttributesExtractor(
AttributesExtractor.constant(AttributeKey.stringKey("job.system"), "powerjob"));
builder.addAttributesExtractor(new PowerJobExperimentalAttributeExtractor());
}

return builder.buildInstrumenter();
}

private PowerJobInstrumenterFactory() {}
}
Loading