Skip to content

Commit

Permalink
Merge pull request #1792 from newrelic/spring-batch-instr-module
Browse files Browse the repository at this point in the history
Instrumentation for Spring Batch v4 and v5
  • Loading branch information
jtduffy committed Mar 14, 2024
2 parents 267afe2 + 833611a commit 2472bbb
Show file tree
Hide file tree
Showing 11 changed files with 307 additions and 0 deletions.
32 changes: 32 additions & 0 deletions instrumentation/spring-batch-4.0.0/README.md
@@ -0,0 +1,32 @@
# spring-batch-4.0.0 Instrumentation Module

This module provides instrumentation for the Spring Batch 4 framework (up to, but not including version 5).

Only single process, single threaded jobs are supported. Single process, multi-threaded and
multi-process jobs are not supported.

### Details
The instrumentation will weave the following classes:

- `org.springframework.batch.core.Job`: In Spring Batch, the Job class wraps the steps and processors that make a up a complete batch job implementation.
The execute() method will be the transaction dispatcher. The transaction name will be `OtherTransaction/SpringBatch/Job/{jobName}`

- `org.springframework.batch.core.Step`: The Step class represents a single step of the target batch job. The execute() method will be instrumented in order
to track the total execution time of a job step. The timing of a Step task will include the time to read the data
from the defined data source, the processing of the data and the time to write the data to the target data source.

- `org.springframework.batch.item.ItemWriter`: The ItemWriter class is used to write the processed/transformed items to a data store. There will be the same number
of ItemWriter segments as Step segments, since there is one write operation per Step.

The ItemReader and ItemProcessor classes will not be instrumented since these are each called once per data item,
which would cause the Transaction traces to be inflated unnecessarily. The timings for these calls are included
in the Step instrumentation timing.

### Metrics
The following metrics will be reported:

- `SpringBatch/Job/{jobName}/Step/{stepName}/read`
- `SpringBatch/Job/{jobName}/Step/{stepName}/write`
- `SpringBatch/Job/{jobName}/Step/{stepName}/skipped`

These metrics represent the total number of items read, written and skipped for a particular step for a completed job.
27 changes: 27 additions & 0 deletions instrumentation/spring-batch-4.0.0/build.gradle
@@ -0,0 +1,27 @@
plugins {
id "org.jetbrains.kotlin.jvm"
}

dependencies {
implementation(project(":agent-bridge"))
implementation("org.springframework.batch:spring-batch-core:4.0.0.RELEASE")
implementation("org.springframework.batch:spring-batch-infrastructure:4.0.0.RELEASE")
testImplementation("org.jetbrains.kotlin:kotlin-stdlib:1.8.21")
}

jar {
manifest { attributes 'Implementation-Title': 'com.newrelic.instrumentation.spring-batch-4.0.0',
'Implementation-Title-Alias': 'spring_batch' }
}

verifyInstrumentation {
passesOnly 'org.springframework.batch:spring-batch-core:[4.0.0.RELEASE,5.0.0.RELEASE)'

excludeRegex 'org.springframework.batch:spring-batch-core:.*(RC|SEC|M)[0-9]*$'
exclude 'org.springframework.batch:spring-batch-core:[1.0.0.FINAL,4.0.0.RELEASE)'
}

site {
title 'Spring'
type 'Framework'
}
@@ -0,0 +1,42 @@
/*
*
* * Copyright 2024 New Relic Corporation. All rights reserved.
* * SPDX-License-Identifier: Apache-2.0
*
*/
package org.springframework.batch.core;

import com.newrelic.agent.bridge.AgentBridge;
import com.newrelic.agent.bridge.Transaction;
import com.newrelic.agent.bridge.TransactionNamePriority;
import com.newrelic.api.agent.NewRelic;
import com.newrelic.api.agent.Trace;
import com.newrelic.api.agent.weaver.MatchType;
import com.newrelic.api.agent.weaver.Weave;
import com.newrelic.api.agent.weaver.Weaver;

@Weave(type = MatchType.Interface, originalName = "org.springframework.batch.core.Job")
public class Job_Instrumentation {
@Trace(dispatcher = true)
public void execute(JobExecution jobExecution) {
Weaver.callOriginal();

Transaction transaction = AgentBridge.getAgent().getTransaction(false);
if (transaction != null) {
String jobName = jobExecution.getJobInstance().getJobName();
transaction.setTransactionName(TransactionNamePriority.FRAMEWORK_HIGH, false, "SpringBatch", "Job", jobName);

String status = jobExecution.getExitStatus().getExitCode();
if ("COMPLETED".equals(status) || "FAILED".equals(status)) {
NewRelic.incrementCounter("SpringBatch/Job/" + jobName + "/" + status);
}

for (StepExecution stepExecution : jobExecution.getStepExecutions()) {
String metricName = "SpringBatch/Job/" + jobName + "/Step/" + stepExecution.getStepName();
NewRelic.incrementCounter(metricName + "/read", stepExecution.getReadCount());
NewRelic.incrementCounter(metricName + "/write", stepExecution.getWriteCount());
NewRelic.incrementCounter(metricName + "/skip", stepExecution.getSkipCount());
}
}
}
}
@@ -0,0 +1,26 @@
/*
*
* * Copyright 2024 New Relic Corporation. All rights reserved.
* * SPDX-License-Identifier: Apache-2.0
*
*/
package org.springframework.batch.core;

import com.newrelic.agent.bridge.AgentBridge;
import com.newrelic.agent.bridge.Transaction;
import com.newrelic.api.agent.weaver.MatchType;
import com.newrelic.api.agent.Trace;
import com.newrelic.api.agent.weaver.Weave;
import com.newrelic.api.agent.weaver.Weaver;

@Weave(type = MatchType.Interface, originalName = "org.springframework.batch.core.Step")
public class Step_Instrumentation {
@Trace
public void execute(StepExecution stepExecution) throws JobInterruptedException {
Weaver.callOriginal();
Transaction transaction = AgentBridge.getAgent().getTransaction(false);
if (transaction != null) {
transaction.getTracedMethod().setMetricName("SpringBatch", "Step", "execute", stepExecution.getStepName());
}
}
}
@@ -0,0 +1,22 @@
/*
*
* * Copyright 2024 New Relic Corporation. All rights reserved.
* * SPDX-License-Identifier: Apache-2.0
*
*/
package org.springframework.batch.item;

import com.newrelic.api.agent.weaver.MatchType;
import com.newrelic.api.agent.Trace;
import com.newrelic.api.agent.weaver.Weave;
import com.newrelic.api.agent.weaver.Weaver;

import java.util.List;

@Weave(type = MatchType.Interface, originalName = "org.springframework.batch.item.ItemWriter")
public class ItemWriter_Instrumentation {
@Trace
public void write(List items) throws Exception {
Weaver.callOriginal();
}
}
32 changes: 32 additions & 0 deletions instrumentation/spring-batch-5.0.0/README.md
@@ -0,0 +1,32 @@
# spring-batch-5.0.0 Instrumentation Module

This module provides instrumentation for the Spring Batch v5 framework.

Only single process, single threaded jobs are supported. Single process, multi-threaded and
multi-process jobs are not supported.

### Details
The instrumentation will weave the following classes:

- `org.springframework.batch.core.Job`: In Spring Batch, the Job class wraps the steps and processors that make a up a complete batch job implementation.
The execute() method will be the transaction dispatcher. The transaction name will be `OtherTransaction/SpringBatch/Job/{jobName}`

- `org.springframework.batch.core.Step`: The Step class represents a single step of the target batch job. The execute() method will be instrumented in order
to track the total execution time of a job step. The timing of a Step task will include the time to read the data
from the defined data source, the processing of the data and the time to write the data to the target data source.

- `org.springframework.batch.item.ItemWriter`: The ItemWriter class is used to write the processed/transformed items to a data store. There will be the same number
of ItemWriter segments as Step segments, since there is one write operation per Step.

The ItemReader and ItemProcessor classes will not be instrumented since these are each called once per data item,
which would cause the Transaction traces to be inflated unnecessarily. The timings for these calls are included
in the Step instrumentation timing.

### Metrics
The following metrics will be reported:

- `SpringBatch/Job/{jobName}/Step/{stepName}/read`
- `SpringBatch/Job/{jobName}/Step/{stepName}/write`
- `SpringBatch/Job/{jobName}/Step/{stepName}/skipped`

These metrics represent the total number of items read, written and skipped for a particular step for a completed job.
33 changes: 33 additions & 0 deletions instrumentation/spring-batch-5.0.0/build.gradle
@@ -0,0 +1,33 @@
plugins {
id "org.jetbrains.kotlin.jvm"
}

dependencies {
implementation(project(":agent-bridge"))
implementation("org.springframework.batch:spring-batch-core:5.0.0")
implementation("org.springframework.batch:spring-batch-infrastructure:5.0.0")
testImplementation("org.jetbrains.kotlin:kotlin-stdlib:1.8.21")
}

jar {
manifest { attributes 'Implementation-Title': 'com.newrelic.instrumentation.spring-batch-5.0.0',
'Implementation-Title-Alias': 'spring_batch' }
}

verifyInstrumentation {
passesOnly 'org.springframework.batch:spring-batch-core:[5.0.0,)'

excludeRegex 'org.springframework.batch:spring-batch-core:.*(RC|SEC|M)[0-9]*$'
exclude 'org.springframework.batch:spring-batch-core:[1.0.0.FINAL,4.0.0.RELEASE)'
}

site {
title 'Spring'
type 'Framework'
}

java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(17))
}
}
@@ -0,0 +1,42 @@
/*
*
* * Copyright 2024 New Relic Corporation. All rights reserved.
* * SPDX-License-Identifier: Apache-2.0
*
*/
package org.springframework.batch.core;

import com.newrelic.agent.bridge.AgentBridge;
import com.newrelic.agent.bridge.Transaction;
import com.newrelic.agent.bridge.TransactionNamePriority;
import com.newrelic.api.agent.NewRelic;
import com.newrelic.api.agent.Trace;
import com.newrelic.api.agent.weaver.MatchType;
import com.newrelic.api.agent.weaver.Weave;
import com.newrelic.api.agent.weaver.Weaver;

@Weave(type = MatchType.Interface, originalName = "org.springframework.batch.core.Job")
public class Job_Instrumentation {
@Trace(dispatcher = true)
public void execute(JobExecution jobExecution) {
Weaver.callOriginal();

Transaction transaction = AgentBridge.getAgent().getTransaction(false);
if (transaction != null) {
String jobName = jobExecution.getJobInstance().getJobName();
transaction.setTransactionName(TransactionNamePriority.FRAMEWORK_HIGH, false, "SpringBatch", "Job", jobName);

String status = jobExecution.getExitStatus().getExitCode();
if ("COMPLETED".equals(status) || "FAILED".equals(status)) {
NewRelic.incrementCounter("SpringBatch/Job/" + jobName + "/" + status);
}

for (StepExecution stepExecution : jobExecution.getStepExecutions()) {
String metricName = "SpringBatch/Job/" + jobName + "/Step/" + stepExecution.getStepName();
NewRelic.incrementCounter(metricName + "/read", (int)stepExecution.getReadCount());
NewRelic.incrementCounter(metricName + "/write", (int)stepExecution.getWriteCount());
NewRelic.incrementCounter(metricName + "/skip", (int)stepExecution.getSkipCount());
}
}
}
}
@@ -0,0 +1,26 @@
/*
*
* * Copyright 2024 New Relic Corporation. All rights reserved.
* * SPDX-License-Identifier: Apache-2.0
*
*/
package org.springframework.batch.core;

import com.newrelic.agent.bridge.AgentBridge;
import com.newrelic.agent.bridge.Transaction;
import com.newrelic.api.agent.weaver.MatchType;
import com.newrelic.api.agent.Trace;
import com.newrelic.api.agent.weaver.Weave;
import com.newrelic.api.agent.weaver.Weaver;

@Weave(type = MatchType.Interface, originalName = "org.springframework.batch.core.Step")
public class Step_Instrumentation {
@Trace
public void execute(StepExecution stepExecution) throws JobInterruptedException {
Weaver.callOriginal();
Transaction transaction = AgentBridge.getAgent().getTransaction(false);
if (transaction != null) {
transaction.getTracedMethod().setMetricName("SpringBatch", "Step", "execute", stepExecution.getStepName());
}
}
}
@@ -0,0 +1,23 @@
/*
*
* * Copyright 2024 New Relic Corporation. All rights reserved.
* * SPDX-License-Identifier: Apache-2.0
*
*/
package org.springframework.batch.item;

import com.newrelic.api.agent.weaver.MatchType;
import com.newrelic.api.agent.Trace;
import com.newrelic.api.agent.weaver.Weave;
import com.newrelic.api.agent.weaver.Weaver;
import org.springframework.lang.NonNull;

import java.util.List;

@Weave(type = MatchType.Interface, originalName = "org.springframework.batch.item.ItemWriter")
public class ItemWriter_Instrumentation {
@Trace
public void write(@NonNull Chunk chunk) throws Exception {
Weaver.callOriginal();
}
}
2 changes: 2 additions & 0 deletions settings.gradle
Expand Up @@ -336,6 +336,8 @@ include 'instrumentation:spring-4.2.0'
include 'instrumentation:spring-4.3.0'
include 'instrumentation:spring-6.0.0'
include 'instrumentation:spring-aop-2'
include 'instrumentation:spring-batch-4.0.0'
include 'instrumentation:spring-batch-5.0.0'
include 'instrumentation:spring-cache-3.1.0'
include 'instrumentation:spring-jms-2'
include 'instrumentation:spring-jms-3'
Expand Down

0 comments on commit 2472bbb

Please sign in to comment.