create ScheduledTasks bean and application context config
ADD
dd-java-agent/instrumentation/spring-scheduling/spring-scheduling.gradle
+apply from: "${rootDir}/gradle/java.gradle"
+dependencies {
+ testCompile group: 'org.springframework', name: 'spring-context', version: '5.2.3.RELEASE'
+ testCompile project(':dd-java-agent:instrumentation:trace-annotation')
+}

ADD
dd-java-agent/instrumentation/spring-scheduling/src/main/java/datadog/trace/instrumentation/springscheduling/SpringSchedulingDecorator.java
+// This file includes software developed at SignalFx
+
+package datadog.trace.instrumentation.springscheduling;
+
+import datadog.trace.agent.decorator.BaseDecorator;
+
+public final class SpringSchedulingDecorator extends BaseDecorator {
+ public static final SpringSchedulingDecorator DECORATOR = new SpringSchedulingDecorator();
+
+ private SpringSchedulingDecorator() {}
+
+ @Override
+ protected String[] instrumentationNames() {
+ return new String[] {"spring-scheduling"};
+ }
+
+ @Override
+ protected String spanType() {
+ return null;
+ }
+
+ @Override
+ protected String component() {
+ return "spring-scheduling";
+ }
+}

ADD
dd-java-agent/instrumentation/spring-scheduling/src/main/java/datadog/trace/instrumentation/springscheduling/SpringSchedulingInstrumentation.java
+// This file includes software developed at SignalFx
+
+package datadog.trace.instrumentation.springscheduling;
+
+import static java.util.Collections.singletonMap;
+import static net.bytebuddy.matcher.ElementMatchers.*;
+
+import com.google.auto.service.AutoService;
+import datadog.trace.agent.tooling.Instrumenter;
+import datadog.trace.api.Trace;
+import java.util.Map;
+import net.bytebuddy.asm.Advice;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.matcher.ElementMatcher;
+
+@AutoService(Instrumenter.class)
+public final class SpringSchedulingInstrumentation extends Instrumenter.Default {
+
+ public SpringSchedulingInstrumentation() {
+ super("spring-scheduling");
+ }
+
+ @Override
+ public ElementMatcher<TypeDescription> typeMatcher() {
+ return not(isInterface()).and(named("org.springframework.scheduling.config.Task"));
+ }
+
+ @Override
+ public String[] helperClassNames() {
+ return new String[] {
+ "datadog.trace.agent.decorator.BaseDecorator",
+ packageName + ".SpringSchedulingDecorator",
+ getClass().getName() + "$RunnableWrapper",
+ };
+ }
+
+ @Override
+ public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
+ return singletonMap(
+ isConstructor().and(takesArgument(0, Runnable.class)),
+ SpringSchedulingInstrumentation.class.getName() + "$RepositoryFactorySupportAdvice");
+ }
+
+ public static class SpringSchedulingAdvice {
+ @Advice.OnMethodEnter(suppress = Throwable.class)
+ public static void onConstruction(
+ @Advice.Argument(value = 0, readOnly = false) Runnable runnable) {
+ runnable = RunnableWrapper.wrapIfNeeded(runnable);
+ }
+ }
+
+ public static class RunnableWrapper implements Runnable {
+ private final Runnable runnable;
+
+ private RunnableWrapper(final Runnable runnable) {
+ this.runnable = runnable;
+ }
+
+ @Trace
+ @Override
+ public void run() {
+ runnable.run();
+ }
+
+ public static Runnable wrapIfNeeded(final Runnable task) {
+ // We wrap only lambdas' anonymous classes and if given object has not already been wrapped.
+ // Anonymous classes have '/' in class name which is not allowed in 'normal' classes.
+ if (task instanceof RunnableWrapper) {
+ return task;
+ }
+ return new RunnableWrapper(task);
+ }
+ }
+}

ADD
dd-java-agent/instrumentation/spring-scheduling/src/test/groovy/ReportTimeTest.groovy
+import datadog.trace.agent.test.AgentTestRunner
+import datadog.trace.agent.test.utils.ConfigUtils
+import datadog.trace.instrumentation.api.Tags
+import org.springframework.context.annotation.AnnotationConfigApplicationContext
+
+class ReportTimeTest extends AgentTestRunner {
+
+ static {
+ ConfigUtils.updateConfig {
+ System.clearProperty("dd.trace.annotations")
+ }
+ }
+
+ def "test method executed"() {
+ setup:
+ def scheduledTask = new ScheduledTasks();
+ scheduledTask.reportCurrentTime();
+
+ expect:
+ assert scheduledTask.reportCurrentTimeExecuted == true;
+ }
+
+ def "trace fired"() {
+ setup:
+ def scheduledTask = new ScheduledTasks();
+ scheduledTask.reportCurrentTime();
+
+ expect:
+ assertTraces(1) {
+ trace(0, 1) {
+ span(0) {
+ serviceName "test"
+ resourceName "ScheduledTasks.reportCurrentTime"
+ operationName "trace.annotation"
+ parent()
+ errored false
+ tags {
+// "$Tags.COMPONENT" "scheduled"
+ "$Tags.COMPONENT" "trace"
+ defaultTags()
+ }
+ }
+ }
+ }
+ }
+
+ def "test with context"() {
+ setup:
+ def context = new AnnotationConfigApplicationContext(ScheduledTasksConfig) // provide config as argument
+ def task = context.getBean(ScheduledTasks) // provide class we are trying to test
+ task.reportCurrentTime()
+
+ expect:
+ assertTraces(1) {
+ trace(0, 1) {
+ span(0) {
+ serviceName "test"
+ resourceName "ScheduledTasks.reportCurrentTime"
+ operationName "trace.annotation"
+ parent()
+ errored false
+ tags {
+// "$Tags.COMPONENT" "scheduled"
+ "$Tags.COMPONENT" "trace"
+ defaultTags()
+ }
+ }
+ }
+ }
+ }
+}

ADD
dd-java-agent/instrumentation/spring-scheduling/src/test/java/ScheduledTasks.java
+/*
+ * Copyright 2012-2015 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import datadog.trace.api.DDTags;
+import datadog.trace.api.Trace;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+import java.text.SimpleDateFormat;
+
+import static datadog.trace.instrumentation.api.AgentTracer.activeSpan;
+
+@Component
+public class ScheduledTasks {
+
+ private static final Logger log = LoggerFactory.getLogger(ScheduledTasks.class);
+
+ private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
+
+ public static boolean reportCurrentTimeExecuted = false;
+
+ @Scheduled(fixedRate = 5000)
+ @Trace
+ public void reportCurrentTime() {
+ // log.info("The time is now {}", dateFormat.format(new Date()));
+ // test that the body of method has been executed
+ activeSpan().setTag(DDTags.SERVICE_NAME, "test");
+ reportCurrentTimeExecuted = true;
+ }
+}

ADD
dd-java-agent/instrumentation/spring-scheduling/src/test/java/ScheduledTasksConfig.java
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class ScheduledTasksConfig {
+ @Bean
+ public ScheduledTasks scheduledTasks() {
+ return new ScheduledTasks();
+ }
+}

MODIFY
settings.gradle
settings.gradle
@@ -130,6 +130,7 @@ include ':dd-java-agent:instrumentation:servlet:request-3'
include ':dd-java-agent:instrumentation:slf4j-mdc'
include ':dd-java-agent:instrumentation:sparkjava-2.3'
include ':dd-java-agent:instrumentation:spring-data-1.8'
+include ':dd-java-agent:instrumentation:spring-scheduling'
include ':dd-java-agent:instrumentation:spring-webmvc-3.1'
include ':dd-java-agent:instrumentation:spring-webflux-5'
include ':dd-java-agent:instrumentation:spymemcached-2.12'

create ScheduledTasks bean and application context config