Skip to content
Merged
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package com.template.worker.global.config;

import java.util.concurrent.atomic.AtomicInteger;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;

import lombok.RequiredArgsConstructor;

@Configuration
@RequiredArgsConstructor
public class BatchMetricsConfig {
private final MeterRegistry meterRegistry;

// Active jobs gauge
@Bean
public AtomicInteger activeJobsGauge() {
return meterRegistry.gauge("spring.batch.job.active", new AtomicInteger(0));
}

// Completed/Failed job counters
@Bean
public Counter jobCompletedCounter() {
return Counter.builder("spring.batch.job.completed.total")
.description("Total completed batch jobs")
.register(meterRegistry);
}

@Bean
public Counter jobFailedCounter() {
return Counter.builder("spring.batch.job.failed.total")
.description("Total failed batch jobs")
.register(meterRegistry);
}

// Partition count by status
@Bean
public Counter partitionCompletedCounter() {
return Counter.builder("spring.batch.partition.count")
.tag("status", "COMPLETED")
.description("Total completed partitions")
.register(meterRegistry);
}

@Bean
public Counter partitionFailedCounter() {
return Counter.builder("spring.batch.partition.count")
.tag("status", "FAILED")
.description("Total failed partitions")
.register(meterRegistry);
}

// Chunk count
@Bean
public Counter chunkCounter() {
return Counter.builder("spring.batch.chunk.count")
.description("Total chunks processed")
.register(meterRegistry);
}

// Step item count
@Bean
public Counter stepItemCounter() {
return Counter.builder("spring.batch.step.item.count")
.description("Total items processed by steps")
.register(meterRegistry);
}

// Step duration histogram
@Bean
public Timer stepDurationTimer() {
return Timer.builder("spring.batch.step.duration.seconds")
.publishPercentileHistogram()
.description("Step duration histogram")
.register(meterRegistry);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.time.Duration;
import java.time.LocalDateTime;
import java.util.concurrent.atomic.AtomicInteger;

import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.ExitStatus;
Expand All @@ -11,6 +12,8 @@
import org.springframework.batch.item.ExecutionContext;
import org.springframework.stereotype.Component;

import io.micrometer.core.instrument.Counter;

import lombok.RequiredArgsConstructor;

@Component
Expand All @@ -21,12 +24,19 @@ public class JobResultListener {
private static final ExitStatus NO_DATA_EXIT_STATUS = new ExitStatus("NO_DATA", "⚠ 데이터가 없습니다.");

private final JobLogger jobLogger;
private final AtomicInteger activeJobsGauge;
private final Counter jobCompletedCounter;
private final Counter jobFailedCounter;

@BeforeJob
public void before(JobExecution jobExecution) {}
public void before(JobExecution jobExecution) {
activeJobsGauge.incrementAndGet();
}

@AfterJob
public void after(JobExecution jobExecution) {
activeJobsGauge.decrementAndGet();

ExecutionContext context = jobExecution.getExecutionContext();
String jobName = jobExecution.getJobInstance().getJobName();

Expand All @@ -42,6 +52,7 @@ public void after(JobExecution jobExecution) {
if (!hasData) {
jobExecution.setStatus(BatchStatus.FAILED);
jobExecution.setExitStatus(NO_DATA_EXIT_STATUS);
jobFailedCounter.increment();

jobLogger.jobFailed(
jobName,
Expand All @@ -56,9 +67,11 @@ public void after(JobExecution jobExecution) {
jobExecution.getAllFailureExceptions().isEmpty()
? null
: jobExecution.getAllFailureExceptions().get(0);
jobFailedCounter.increment();

jobLogger.jobFailed(jobName, duration, cause);
} else {
jobCompletedCounter.increment();
jobLogger.jobSuccess(jobName, duration);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,32 @@
package com.template.worker.global.listener;

import java.util.concurrent.TimeUnit;

import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.StepExecutionListener;
import org.springframework.stereotype.Component;

import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.Timer;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Component
@RequiredArgsConstructor
public class PartitionTimingListener implements StepExecutionListener {

private static final String START_TIME = "startTime";
private static final String HAS_DATA = "HAS_DATA";

private final Counter partitionCompletedCounter;
private final Counter partitionFailedCounter;
private final Counter stepItemCounter;
private final Timer stepDurationTimer;

@Override
public void beforeStep(StepExecution stepExecution) {
stepExecution.getExecutionContext().putLong(START_TIME, System.currentTimeMillis());
Expand All @@ -39,6 +52,15 @@ public ExitStatus afterStep(StepExecution stepExecution) {
stepExecution.getJobExecution().getExecutionContext().put(HAS_DATA, true);
}

// Record Prometheus metrics
if (stepExecution.getStatus() == BatchStatus.COMPLETED) {
partitionCompletedCounter.increment();
} else if (stepExecution.getStatus() == BatchStatus.FAILED) {
partitionFailedCounter.increment();
}
stepItemCounter.increment(readCount);
stepDurationTimer.record(duration, TimeUnit.MILLISECONDS);

return stepExecution.getExitStatus();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,29 @@
import org.springframework.batch.item.ExecutionContext;
import org.springframework.stereotype.Component;

import io.micrometer.core.instrument.Counter;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Component
@Slf4j
@RequiredArgsConstructor
public class TimeBasedChunkListener implements ChunkListener {

private static final long LOG_INTERVAL_MS = 10_000;
private static final String LAST_LOG_TIME_KEY = "lastLogTime";

private final Counter chunkCounter;

@Override
public void afterChunk(ChunkContext context) {
StepExecution stepExecution = context.getStepContext().getStepExecution();
ExecutionContext executionContext = stepExecution.getExecutionContext();

// Record chunk metric
chunkCounter.increment();

long now = System.currentTimeMillis();
long lastLogTime =
executionContext.containsKey(LAST_LOG_TIME_KEY)
Expand Down