Custom JFR Events in Java: Complete Guide

Java Flight Recorder (JFR) is a powerful profiling and diagnostics tool in the JVM. Custom JFR events allow you to instrument your application with domain-specific events that appear alongside JVM events in JFR recordings.


Table of Contents

  1. Basic Custom Events
  2. Event Configuration and Annotations
  3. Advanced Event Types
  4. Programmatic Event Control
  5. Real-World Use Cases
  6. Event Processing and Analysis

1. Basic Custom Events

Example 1: Simple Custom Event

import jdk.jfr.*;
import java.time.Duration;
import java.util.concurrent.TimeUnit;
// Define a custom JFR event
@Name("com.company.OrderProcessingEvent")
@Label("Order Processing")
@Category({"Business", "Order Management"})
@Description("Tracks order processing lifecycle")
public class OrderProcessingEvent extends Event {
@Label("Order ID")
@Description("Unique order identifier")
private String orderId;
@Label("Customer ID")
private String customerId;
@Label("Order Amount")
@Description("Total order amount in USD")
private double amount;
@Label("Processing Time")
@Description("Time taken to process the order")
private long processingTimeMs;
@Label("Success")
@Description("Whether order processing succeeded")
private boolean success;
@Label("Failure Reason")
@Description("Reason for failure if processing failed")
private String failureReason;
// Getters and setters
public String getOrderId() { return orderId; }
public void setOrderId(String orderId) { this.orderId = orderId; }
public String getCustomerId() { return customerId; }
public void setCustomerId(String customerId) { this.customerId = customerId; }
public double getAmount() { return amount; }
public void setAmount(double amount) { this.amount = amount; }
public long getProcessingTimeMs() { return processingTimeMs; }
public void setProcessingTimeMs(long processingTimeMs) { 
this.processingTimeMs = processingTimeMs; 
}
public boolean isSuccess() { return success; }
public void setSuccess(boolean success) { this.success = success; }
public String getFailureReason() { return failureReason; }
public void setFailureReason(String failureReason) { 
this.failureReason = failureReason; 
}
// Utility method to easily record events
public static void recordOrderProcessing(
String orderId, 
String customerId, 
double amount, 
long processingTimeMs, 
boolean success, 
String failureReason) {
OrderProcessingEvent event = new OrderProcessingEvent();
event.begin(); // Start timing
// Set event fields
event.orderId = orderId;
event.customerId = customerId;
event.amount = amount;
event.processingTimeMs = processingTimeMs;
event.success = success;
event.failureReason = failureReason;
event.commit(); // Record the event
}
}
// Usage example
public class OrderService {
public void processOrder(Order order) {
long startTime = System.nanoTime();
boolean success = false;
String failureReason = null;
try {
// Simulate order processing
validateOrder(order);
processPayment(order);
updateInventory(order);
sendConfirmation(order);
success = true;
} catch (Exception e) {
failureReason = e.getMessage();
throw e;
} finally {
long processingTimeMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime);
// Record custom JFR event
OrderProcessingEvent.recordOrderProcessing(
order.getId(),
order.getCustomerId(),
order.getAmount(),
processingTimeMs,
success,
failureReason
);
}
}
private void validateOrder(Order order) {
// Validation logic
if (order.getAmount() <= 0) {
throw new IllegalArgumentException("Invalid order amount");
}
}
private void processPayment(Order order) {
// Payment processing logic
if (order.getAmount() > 1000) {
throw new RuntimeException("Payment declined");
}
}
private void updateInventory(Order order) {
// Inventory update logic
}
private void sendConfirmation(Order order) {
// Confirmation logic
}
// Simple Order class for demonstration
public static class Order {
private String id;
private String customerId;
private double amount;
public Order(String id, String customerId, double amount) {
this.id = id;
this.customerId = customerId;
this.amount = amount;
}
// Getters
public String getId() { return id; }
public String getCustomerId() { return customerId; }
public double getAmount() { return amount; }
}
}

Example 2: Timed Event with Duration

import jdk.jfr.*;
import java.util.concurrent.*;
@Name("com.company.DatabaseQueryEvent")
@Label("Database Query Execution")
@Category({"Database", "Performance"})
@Description("Tracks database query execution metrics")
public class DatabaseQueryEvent extends Event {
@Label("Query ID")
private String queryId;
@Label("SQL Query")
@Description("The SQL query that was executed")
private String sql;
@Label("Table Name")
private String tableName;
@Label("Rows Affected")
private int rowsAffected;
@Label("Execution Time")
@Description("Total query execution time in milliseconds")
private long executionTimeMs;
@Label("Success")
private boolean success;
@Label("Error Message")
private String errorMessage;
// Getters and setters
public String getQueryId() { return queryId; }
public void setQueryId(String queryId) { this.queryId = queryId; }
public String getSql() { return sql; }
public void setSql(String sql) { this.sql = sql; }
public String getTableName() { return tableName; }
public void setTableName(String tableName) { this.tableName = tableName; }
public int getRowsAffected() { return rowsAffected; }
public void setRowsAffected(int rowsAffected) { this.rowsAffected = rowsAffected; }
public long getExecutionTimeMs() { return executionTimeMs; }
public void setExecutionTimeMs(long executionTimeMs) { 
this.executionTimeMs = executionTimeMs; 
}
public boolean isSuccess() { return success; }
public void setSuccess(boolean success) { this.success = success; }
public String getErrorMessage() { return errorMessage; }
public void setErrorMessage(String errorMessage) { this.errorMessage = errorMessage; }
// Auto-timing utility method
public static <T> T trackQuery(String queryId, String sql, String tableName, 
Callable<T> queryExecutor) throws Exception {
DatabaseQueryEvent event = new DatabaseQueryEvent();
event.begin();
event.queryId = queryId;
event.sql = sql;
event.tableName = tableName;
long startTime = System.nanoTime();
try {
T result = queryExecutor.call();
event.success = true;
event.rowsAffected = 1; // In real scenario, get from result
return result;
} catch (Exception e) {
event.success = false;
event.errorMessage = e.getMessage();
throw e;
} finally {
event.executionTimeMs = TimeUnit.NANOSECONDS.toMillis(
System.nanoTime() - startTime);
event.commit();
}
}
}
// Usage in database service
public class DatabaseService {
public User findUserById(String userId) throws Exception {
String sql = "SELECT * FROM users WHERE id = ?";
return DatabaseQueryEvent.trackQuery(
"FIND_USER_BY_ID", 
sql, 
"users",
() -> {
// Simulate database query
Thread.sleep(ThreadLocalRandom.current().nextInt(10, 100));
if ("error".equals(userId)) {
throw new SQLException("User not found");
}
return new User(userId, "John Doe");
}
);
}
public static class User {
private String id;
private String name;
public User(String id, String name) {
this.id = id;
this.name = name;
}
}
}

2. Event Configuration and Annotations

Example 3: Comprehensive Event Annotations

import jdk.jfr.*;
import java.lang.annotation.*;
// Custom annotation for method tracing
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Name("com.company.MethodTrace")
@Label("Method Execution Trace")
@Category("Performance")
@StackTrace(true)
@Enabled(true)
@Registered(true)
public @interface TraceMethod {
String value() default "";
boolean trackArguments() default false;
Threshold("10 ms") // Only record if execution takes more than 10ms
}
// Method tracing event
@Name("com.company.MethodExecutionEvent")
@Label("Method Execution")
@Category({"Performance", "Method Tracing"})
@Description("Tracks method execution performance")
@StackTrace(true) // Include stack trace
@Enabled(true)    // Enabled by default
@Registered(true) // Registered with JFR
@Threshold("1 ms") // Only record if duration >= 1ms
public class MethodExecutionEvent extends Event {
@Label("Class Name")
@Description("Fully qualified class name")
private String className;
@Label("Method Name")
private String methodName;
@Label("Arguments")
@Description("Method arguments as string")
private String arguments;
@Label("Return Value")
private String returnValue;
@Label("Execution Time")
@Timespan(Timespan.MILLISECONDS)
private long executionTimeMs;
@Label("Exception")
private String exception;
// Getters and setters
public String getClassName() { return className; }
public void setClassName(String className) { this.className = className; }
public String getMethodName() { return methodName; }
public void setMethodName(String methodName) { this.methodName = methodName; }
public String getArguments() { return arguments; }
public void setArguments(String arguments) { this.arguments = arguments; }
public String getReturnValue() { return returnValue; }
public void setReturnValue(String returnValue) { this.returnValue = returnValue; }
public long getExecutionTimeMs() { return executionTimeMs; }
public void setExecutionTimeMs(long executionTimeMs) { 
this.executionTimeMs = executionTimeMs; 
}
public String getException() { return exception; }
public void setException(String exception) { this.exception = exception; }
// Aspect-style tracking (simplified)
public static MethodExecutionEvent start(String className, String methodName, Object[] args) {
MethodExecutionEvent event = new MethodExecutionEvent();
event.begin();
event.className = className;
event.methodName = methodName;
if (args != null && args.length > 0) {
event.arguments = java.util.Arrays.toString(args);
}
return event;
}
public void end(Object returnValue) {
this.returnValue = String.valueOf(returnValue);
this.executionTimeMs = getDuration().toMillis();
commit();
}
public void endWithException(Throwable throwable) {
this.exception = throwable.getClass().getName() + ": " + throwable.getMessage();
this.executionTimeMs = getDuration().toMillis();
commit();
}
}
// Usage with annotation processing
public class TracedService {
@TraceMethod(trackArguments = true)
public String processData(String input, int count) {
// Simulate processing
try {
Thread.sleep(ThreadLocalRandom.current().nextInt(5, 50));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
if ("error".equals(input)) {
throw new RuntimeException("Simulated error");
}
return "Processed: " + input + " x" + count;
}
@TraceMethod(value = "calculateTotal", trackArguments = true)
public double calculateTotal(double[] prices, double taxRate) {
double subtotal = java.util.Arrays.stream(prices).sum();
return subtotal * (1 + taxRate);
}
}
// Method interceptor (simplified)
public class MethodTracingInterceptor {
public static Object intercept(Method method, Object[] args, Object target) throws Throwable {
TraceMethod annotation = method.getAnnotation(TraceMethod.class);
if (annotation == null) {
return method.invoke(target, args);
}
MethodExecutionEvent event = MethodExecutionEvent.start(
target.getClass().getName(),
annotation.value().isEmpty() ? method.getName() : annotation.value(),
annotation.trackArguments() ? args : null
);
try {
Object result = method.invoke(target, args);
event.end(result);
return result;
} catch (InvocationTargetException e) {
event.endWithException(e.getCause());
throw e.getCause();
}
}
}

3. Advanced Event Types

Example 4: Periodic and Sampling Events

import jdk.jfr.*;
import java.util.concurrent.atomic.*;
@Name("com.company.SystemMetricsEvent")
@Label("System Metrics")
@Category({"Monitoring", "System"})
@Description("Periodic system metrics collection")
@Period("1 s") // Emit every second
public class SystemMetricsEvent extends Event {
@Label("CPU Usage")
@Description("Process CPU usage percentage")
@Percentage
private double cpuUsage;
@Label("Memory Usage")
@Description("Heap memory usage in MB")
private double memoryUsageMB;
@Label("Active Threads")
private int activeThreads;
@Label("GC Count")
@Description("Total garbage collection count")
private long gcCount;
@Label("Request Rate")
@Description("Requests per second")
private double requestsPerSecond;
// Getters and setters
public double getCpuUsage() { return cpuUsage; }
public void setCpuUsage(double cpuUsage) { this.cpuUsage = cpuUsage; }
public double getMemoryUsageMB() { return memoryUsageMB; }
public void setMemoryUsageMB(double memoryUsageMB) { this.memoryUsageMB = memoryUsageMB; }
public int getActiveThreads() { return activeThreads; }
public void setActiveThreads(int activeThreads) { this.activeThreads = activeThreads; }
public long getGcCount() { return gcCount; }
public void setGcCount(long gcCount) { this.gcCount = gcCount; }
public double getRequestsPerSecond() { return requestsPerSecond; }
public void setRequestsPerSecond(double requestsPerSecond) { 
this.requestsPerSecond = requestsPerSecond; 
}
}
@Name("com.company.BusinessMetricsEvent")
@Label("Business Metrics")
@Category({"Business", "Monitoring"})
@Description("Key business performance indicators")
@Period("5 s") // Emit every 5 seconds
public class BusinessMetricsEvent extends Event {
@Label("Orders Processed")
@Description("Total orders processed")
private long ordersProcessed;
@Label("Revenue")
@Description("Total revenue in USD")
private double revenue;
@Label("Active Users")
private int activeUsers;
@Label("Error Rate")
@Percentage
private double errorRate;
@Label("Average Response Time")
@Timespan(Timespan.MILLISECONDS)
private double avgResponseTime;
// Getters and setters
public long getOrdersProcessed() { return ordersProcessed; }
public void setOrdersProcessed(long ordersProcessed) { this.ordersProcessed = ordersProcessed; }
public double getRevenue() { return revenue; }
public void setRevenue(double revenue) { this.revenue = revenue; }
public int getActiveUsers() { return activeUsers; }
public void setActiveUsers(int activeUsers) { this.activeUsers = activeUsers; }
public double getErrorRate() { return errorRate; }
public void setErrorRate(double errorRate) { this.errorRate = errorRate; }
public double getAvgResponseTime() { return avgResponseTime; }
public void setAvgResponseTime(double avgResponseTime) { 
this.avgResponseTime = avgResponseTime; 
}
}
// Metrics collector service
public class MetricsCollector {
private final AtomicLong ordersProcessed = new AtomicLong(0);
private final AtomicLong totalRevenue = new AtomicLong(0);
private final AtomicInteger activeUsers = new AtomicInteger(0);
private final AtomicLong totalErrors = new AtomicLong(0);
private final AtomicLong totalRequests = new AtomicLong(0);
private final AtomicLong totalResponseTime = new AtomicLong(0);
private volatile long lastGcCount = 0;
public void recordOrder(double amount) {
ordersProcessed.incrementAndGet();
totalRevenue.addAndGet((long) (amount * 100)); // Store as cents
}
public void recordRequest(long responseTimeMs, boolean error) {
totalRequests.incrementAndGet();
totalResponseTime.addAndGet(responseTimeMs);
if (error) {
totalErrors.incrementAndGet();
}
}
public void userLoggedIn() {
activeUsers.incrementAndGet();
}
public void userLoggedOut() {
activeUsers.decrementAndGet();
}
// Called periodically to emit events
public void emitSystemMetrics() {
SystemMetricsEvent event = new SystemMetricsEvent();
// Simulate metric collection
event.cpuUsage = Math.random() * 100;
event.memoryUsageMB = Runtime.getRuntime().totalMemory() / (1024.0 * 1024.0);
event.activeThreads = Thread.activeCount();
// Calculate requests per second (simplified)
event.requestsPerSecond = totalRequests.get() / 60.0; // Per minute for demo
event.commit();
}
public void emitBusinessMetrics() {
BusinessMetricsEvent event = new BusinessMetricsEvent();
event.ordersProcessed = ordersProcessed.get();
event.revenue = totalRevenue.get() / 100.0; // Convert cents to dollars
event.activeUsers = activeUsers.get();
long totalReqs = totalRequests.get();
event.errorRate = totalReqs > 0 ? (totalErrors.get() * 100.0 / totalReqs) : 0;
event.avgResponseTime = totalReqs > 0 ? (totalResponseTime.get() / (double) totalReqs) : 0;
event.commit();
}
}

4. Programmatic Event Control

Example 5: Dynamic Event Control

import jdk.jfr.*;
import java.util.*;
import java.util.concurrent.*;
public class DynamicEventControl {
private final Map<String, Boolean> eventEnabled = new ConcurrentHashMap<>();
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
@Name("com.company.DynamicEvent")
@Label("Dynamically Controlled Event")
@Category("Custom")
public class DynamicEvent extends Event {
@Label("Event Type")
private String eventType;
@Label("Payload")
private String payload;
@Label("Importance")
@Description("Event importance level")
private String importance;
public String getEventType() { return eventType; }
public void setEventType(String eventType) { this.eventType = eventType; }
public String getPayload() { return payload; }
public void setPayload(String payload) { this.payload = payload; }
public String getImportance() { return importance; }
public void setImportance(String importance) { this.importance = importance; }
// Dynamic enablement check
public boolean shouldRecord() {
return eventEnabled.getOrDefault(eventType, true);
}
}
// Event factory with dynamic control
public class DynamicEventFactory {
public void recordEvent(String eventType, String payload, String importance) {
DynamicEvent event = new DynamicEvent();
// Check if this event type is enabled
if (!event.shouldRecord()) {
return;
}
event.begin();
event.eventType = eventType;
event.payload = payload;
event.importance = importance;
event.commit();
}
public void enableEventType(String eventType) {
eventEnabled.put(eventType, true);
System.out.println("Enabled event type: " + eventType);
}
public void disableEventType(String eventType) {
eventEnabled.put(eventType, false);
System.out.println("Disabled event type: " + eventType);
}
public void setEventThreshold(String eventType, long thresholdMs) {
// Dynamic threshold configuration
// This would typically use JFR's configuration API
System.out.println("Set threshold for " + eventType + ": " + thresholdMs + "ms");
}
}
// Programmatic JFR control
public class JFRController {
public void startRecording(String name, Duration duration) {
try {
Recording recording = new Recording();
recording.setName(name);
recording.setDestination(Paths.get(name + ".jfr"));
recording.setDuration(duration);
// Enable specific event settings
recording.enable("com.company.OrderProcessingEvent");
recording.enable("com.company.DatabaseQueryEvent").withThreshold(Duration.ofMillis(10));
recording.enable("com.company.MethodExecutionEvent").withStackTrace();
recording.start();
System.out.println("Started recording: " + name);
} catch (Exception e) {
System.err.println("Failed to start recording: " + e.getMessage());
}
}
public void configureCustomEvents() {
// Programmatically configure event settings
Configuration config = Configuration.getConfiguration("default");
// Modify event settings (simplified - actual API may differ)
System.out.println("Configuring custom events...");
// Enable custom events with specific thresholds
enableEventWithThreshold("com.company.OrderProcessingEvent", Duration.ofMillis(5));
enableEventWithThreshold("com.company.DatabaseQueryEvent", Duration.ofMillis(10));
}
private void enableEventWithThreshold(String eventName, Duration threshold) {
// Implementation would use JFR configuration API
System.out.println("Enabled " + eventName + " with threshold: " + threshold);
}
public List<Recording> getActiveRecordings() {
return FlightRecorder.getFlightRecorder().getRecordings();
}
public void dumpRecordingInfo() {
FlightRecorder fr = FlightRecorder.getFlightRecorder();
System.out.println("Available Events:");
for (EventType type : fr.getEventTypes()) {
System.out.printf("  %s - %s%n", type.getName(), type.getLabel());
}
System.out.println("\nActive Recordings:");
for (Recording recording : fr.getRecordings()) {
System.out.printf("  %s - %s%n", recording.getName(), 
recording.getState());
}
}
}

5. Real-World Use Cases

Example 6: Complete Application Monitoring

import jdk.jfr.*;
import java.util.*;
import java.util.concurrent.*;
public class ApplicationMonitoring {
// Cache operation event
@Name("com.company.CacheEvent")
@Label("Cache Operation")
@Category({"Performance", "Caching"})
public class CacheEvent extends Event {
@Label("Cache Name")
private String cacheName;
@Label("Operation")
@Description("Cache operation type")
private String operation; // GET, PUT, REMOVE, etc.
@Label("Key")
private String key;
@Label("Hit")
private boolean hit;
@Label("Execution Time")
@Timespan(Timespan.NANOSECONDS)
private long executionTimeNs;
@Label("Cache Size")
private int cacheSize;
// Getters and setters
public String getCacheName() { return cacheName; }
public void setCacheName(String cacheName) { this.cacheName = cacheName; }
public String getOperation() { return operation; }
public void setOperation(String operation) { this.operation = operation; }
public String getKey() { return key; }
public void setKey(String key) { this.key = key; }
public boolean isHit() { return hit; }
public void setHit(boolean hit) { this.hit = hit; }
public long getExecutionTimeNs() { return executionTimeNs; }
public void setExecutionTimeNs(long executionTimeNs) { 
this.executionTimeNs = executionTimeNs; 
}
public int getCacheSize() { return cacheSize; }
public void setCacheSize(int cacheSize) { this.cacheSize = cacheSize; }
}
// HTTP request event
@Name("com.company.HttpRequestEvent")
@Label("HTTP Request")
@Category({"Web", "Performance"})
public class HttpRequestEvent extends Event {
@Label("Request ID")
private String requestId;
@Label("HTTP Method")
private String method;
@Label("URL")
private String url;
@Label("Status Code")
private int statusCode;
@Label("Response Time")
@Timespan(Timespan.MILLISECONDS)
private long responseTimeMs;
@Label("Request Size")
@DataAmount
private long requestSize;
@Label("Response Size")
@DataAmount
private long responseSize;
@Label("Client IP")
private String clientIp;
@Label("User Agent")
private String userAgent;
// Getters and setters
public String getRequestId() { return requestId; }
public void setRequestId(String requestId) { this.requestId = requestId; }
public String getMethod() { return method; }
public void setMethod(String method) { this.method = method; }
public String getUrl() { return url; }
public void setUrl(String url) { this.url = url; }
public int getStatusCode() { return statusCode; }
public void setStatusCode(int statusCode) { this.statusCode = statusCode; }
public long getResponseTimeMs() { return responseTimeMs; }
public void setResponseTimeMs(long responseTimeMs) { 
this.responseTimeMs = responseTimeMs; 
}
public long getRequestSize() { return requestSize; }
public void setRequestSize(long requestSize) { this.requestSize = requestSize; }
public long getResponseSize() { return responseSize; }
public void setResponseSize(long responseSize) { this.responseSize = responseSize; }
public String getClientIp() { return clientIp; }
public void setClientIp(String clientIp) { this.clientIp = clientIp; }
public String getUserAgent() { return userAgent; }
public void setUserAgent(String userAgent) { this.userAgent = userAgent; }
}
// Batch processing event
@Name("com.company.BatchProcessingEvent")
@Label("Batch Processing")
@Category({"Batch", "Performance"})
public class BatchProcessingEvent extends Event {
@Label("Batch ID")
private String batchId;
@Label("Batch Size")
private int batchSize;
@Label("Processed Items")
private int processedItems;
@Label("Failed Items")
private int failedItems;
@Label("Total Time")
@Timespan(Timespan.MILLISECONDS)
private long totalTimeMs;
@Label("Throughput")
@Description("Items processed per second")
private double throughput;
@Label("Success Rate")
@Percentage
private double successRate;
// Getters and setters
public String getBatchId() { return batchId; }
public void setBatchId(String batchId) { this.batchId = batchId; }
public int getBatchSize() { return batchSize; }
public void setBatchSize(int batchSize) { this.batchSize = batchSize; }
public int getProcessedItems() { return processedItems; }
public void setProcessedItems(int processedItems) { this.processedItems = processedItems; }
public int getFailedItems() { return failedItems; }
public void setFailedItems(int failedItems) { this.failedItems = failedItems; }
public long getTotalTimeMs() { return totalTimeMs; }
public void setTotalTimeMs(long totalTimeMs) { this.totalTimeMs = totalTimeMs; }
public double getThroughput() { return throughput; }
public void setThroughput(double throughput) { this.throughput = throughput; }
public double getSuccessRate() { return successRate; }
public void setSuccessRate(double successRate) { this.successRate = successRate; }
}
}
// Comprehensive monitoring service
public class MonitoringService {
private final DynamicEventControl eventControl = new DynamicEventControl();
private final MetricsCollector metricsCollector = new MetricsCollector();
private final JFRController jfrController = new JFRController();
public void initializeMonitoring() {
// Start continuous recording
jfrController.startRecording("Application-Monitoring", Duration.ofHours(1));
// Configure event thresholds
jfrController.configureCustomEvents();
// Start periodic metrics emission
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
scheduler.scheduleAtFixedRate(() -> {
metricsCollector.emitSystemMetrics();
metricsCollector.emitBusinessMetrics();
}, 0, 5, TimeUnit.SECONDS);
System.out.println("Application monitoring initialized");
}
public void recordCacheOperation(String cacheName, String operation, String key, 
boolean hit, long durationNs, int cacheSize) {
ApplicationMonitoring.CacheEvent event = new ApplicationMonitoring.CacheEvent();
event.begin();
event.cacheName = cacheName;
event.operation = operation;
event.key = key;
event.hit = hit;
event.executionTimeNs = durationNs;
event.cacheSize = cacheSize;
event.commit();
}
public void recordHttpRequest(String requestId, String method, String url, 
int statusCode, long responseTimeMs, 
long requestSize, long responseSize,
String clientIp, String userAgent) {
ApplicationMonitoring.HttpRequestEvent event = new ApplicationMonitoring.HttpRequestEvent();
event.begin();
event.requestId = requestId;
event.method = method;
event.url = url;
event.statusCode = statusCode;
event.responseTimeMs = responseTimeMs;
event.requestSize = requestSize;
event.responseSize = responseSize;
event.clientIp = clientIp;
event.userAgent = userAgent;
event.commit();
}
public void recordBatchProcessing(String batchId, int batchSize, int processedItems,
int failedItems, long totalTimeMs) {
ApplicationMonitoring.BatchProcessingEvent event = new ApplicationMonitoring.BatchProcessingEvent();
event.begin();
event.batchId = batchId;
event.batchSize = batchSize;
event.processedItems = processedItems;
event.failedItems = failedItems;
event.totalTimeMs = totalTimeMs;
event.throughput = (processedItems * 1000.0) / totalTimeMs;
event.successRate = (processedItems - failedItems) * 100.0 / processedItems;
event.commit();
}
}

6. Event Processing and Analysis

Example 7: Event Analysis and Reporting

import jdk.jfr.consumer.*;
import java.nio.file.*;
import java.util.*;
import java.util.stream.*;
public class JFREventAnalyzer {
public void analyzeRecording(String recordingPath) throws Exception {
Path jfrFile = Paths.get(recordingPath);
try (RecordingFile recording = new RecordingFile(jfrFile)) {
Map<String, List<RecordedEvent>> eventsByType = new HashMap<>();
Map<String, EventStatistics> statistics = new HashMap<>();
while (recording.hasMoreEvents()) {
RecordedEvent event = recording.readEvent();
String eventType = event.getEventType().getName();
eventsByType.computeIfAbsent(eventType, k -> new ArrayList<>()).add(event);
updateStatistics(statistics, event);
}
generateAnalysisReport(eventsByType, statistics);
}
}
private void updateStatistics(Map<String, EventStatistics> statistics, RecordedEvent event) {
String eventType = event.getEventType().getName();
EventStatistics stats = statistics.computeIfAbsent(eventType, k -> new EventStatistics());
stats.count++;
// Update duration statistics if available
if (event.hasField("duration")) {
Duration duration = event.getDuration();
long durationMs = duration.toMillis();
stats.totalDurationMs += durationMs;
stats.minDurationMs = Math.min(stats.minDurationMs, durationMs);
stats.maxDurationMs = Math.max(stats.maxDurationMs, durationMs);
}
// Update timestamp range
Instant eventTime = event.getStartTime();
if (stats.firstEvent == null || eventTime.isBefore(stats.firstEvent)) {
stats.firstEvent = eventTime;
}
if (stats.lastEvent == null || eventTime.isAfter(stats.lastEvent)) {
stats.lastEvent = eventTime;
}
}
private void generateAnalysisReport(Map<String, List<RecordedEvent>> eventsByType, 
Map<String, EventStatistics> statistics) {
System.out.println("📊 JFR RECORDING ANALYSIS REPORT");
System.out.println("=".repeat(80));
// Event type summary
System.out.println("\n📈 EVENT TYPE SUMMARY:");
statistics.entrySet().stream()
.sorted((a, b) -> Long.compare(b.getValue().count, a.getValue().count))
.forEach(entry -> {
EventStatistics stats = entry.getValue();
System.out.printf("  %-40s %6d events", entry.getKey(), stats.count);
if (stats.totalDurationMs > 0) {
double avgDuration = stats.totalDurationMs / (double) stats.count;
System.out.printf(" | Avg: %6.1f ms | Min: %4d ms | Max: %5d ms", 
avgDuration, stats.minDurationMs, stats.maxDurationMs);
}
System.out.println();
});
// Custom event analysis
analyzeCustomEvents(eventsByType);
}
private void analyzeCustomEvents(Map<String, List<RecordedEvent>> eventsByType) {
System.out.println("\n🔍 CUSTOM EVENT ANALYSIS:");
// Analyze order processing events
List<RecordedEvent> orderEvents = eventsByType.get("com.company.OrderProcessingEvent");
if (orderEvents != null) {
analyzeOrderEvents(orderEvents);
}
// Analyze database events
List<RecordedEvent> dbEvents = eventsByType.get("com.company.DatabaseQueryEvent");
if (dbEvents != null) {
analyzeDatabaseEvents(dbEvents);
}
}
private void analyzeOrderEvents(List<RecordedEvent> events) {
long successCount = events.stream()
.filter(e -> e.getBoolean("success"))
.count();
long failCount = events.size() - successCount;
double avgAmount = events.stream()
.mapToDouble(e -> e.getDouble("amount"))
.average()
.orElse(0);
double avgProcessingTime = events.stream()
.mapToLong(e -> e.getLong("processingTimeMs"))
.average()
.orElse(0);
System.out.println("  Order Processing Events:");
System.out.printf("    Total: %d | Success: %d | Failed: %d | Success Rate: %.1f%%%n",
events.size(), successCount, failCount, (successCount * 100.0 / events.size()));
System.out.printf("    Average Amount: $%.2f | Average Processing Time: %.1f ms%n",
avgAmount, avgProcessingTime);
}
private void analyzeDatabaseEvents(List<RecordedEvent> events) {
long successCount = events.stream()
.filter(e -> e.getBoolean("success"))
.count();
double avgExecutionTime = events.stream()
.mapToLong(e -> e.getLong("executionTimeMs"))
.average()
.orElse(0);
Map<String, Long> queriesByTable = events.stream()
.collect(Collectors.groupingBy(
e -> e.getString("tableName"),
Collectors.counting()
));
System.out.println("  Database Query Events:");
System.out.printf("    Total: %d | Success: %d | Success Rate: %.1f%%%n",
events.size(), successCount, (successCount * 100.0 / events.size()));
System.out.printf("    Average Execution Time: %.1f ms%n", avgExecutionTime);
System.out.println("    Queries by Table:");
queriesByTable.entrySet().stream()
.sorted(Map.Entry.<String, Long>comparingByValue().reversed())
.forEach(entry -> {
System.out.printf("      %-15s: %d queries%n", entry.getKey(), entry.getValue());
});
}
static class EventStatistics {
long count = 0;
long totalDurationMs = 0;
long minDurationMs = Long.MAX_VALUE;
long maxDurationMs = Long.MIN_VALUE;
Instant firstEvent;
Instant lastEvent;
}
// Real-time event processing
public void startRealTimeAnalysis() {
try {
FlightRecorder.addHandler(this::processEvent);
System.out.println("Started real-time JFR event processing");
} catch (Exception e) {
System.err.println("Failed to start real-time analysis: " + e.getMessage());
}
}
private void processEvent(RecordedEvent event) {
String eventType = event.getEventType().getName();
// Process specific event types in real-time
switch (eventType) {
case "com.company.OrderProcessingEvent":
processOrderEvent(event);
break;
case "com.company.DatabaseQueryEvent":
processDatabaseEvent(event);
break;
case "jdk.GarbageCollection":
processGCEvent(event);
break;
}
}
private void processOrderEvent(RecordedEvent event) {
boolean success = event.getBoolean("success");
double amount = event.getDouble("amount");
long processingTime = event.getLong("processingTimeMs");
// Real-time alerting for slow orders
if (processingTime > 1000) { // 1 second threshold
System.out.printf("🚨 SLOW ORDER: %s took %d ms%n", 
event.getString("orderId"), processingTime);
}
// Real-time alerting for failed orders
if (!success) {
System.out.printf("❌ FAILED ORDER: %s - %s%n",
event.getString("orderId"), event.getString("failureReason"));
}
}
private void processDatabaseEvent(RecordedEvent event) {
long executionTime = event.getLong("executionTimeMs");
boolean success = event.getBoolean("success");
// Alert for slow queries
if (executionTime > 100) { // 100ms threshold
System.out.printf("🐌 SLOW QUERY: %s took %d ms%n",
event.getString("queryId"), executionTime);
}
// Alert for query failures
if (!success) {
System.out.printf("💥 QUERY FAILED: %s - %s%n",
event.getString("queryId"), event.getString("errorMessage"));
}
}
private void processGCEvent(RecordedEvent event) {
long duration = event.getDuration().toMillis();
// Alert for long GC pauses
if (duration > 100) { // 100ms threshold
System.out.printf("🗑️  LONG GC PAUSE: %d ms - %s%n", 
duration, event.getString("gcName"));
}
}
}

Key Benefits and Best Practices

Benefits:

  1. Low Overhead: JFR events have minimal performance impact
  2. Integration: Works with existing JVM monitoring tools
  3. Rich Metadata: Stack traces, timing, custom fields
  4. Production Safe: Designed for use in production environments

Best Practices:

  1. Use Meaningful Event Names: Include package names for organization
  2. Set Appropriate Thresholds: Avoid recording too many events
  3. Include Stack Traces Sparingly: They add overhead
  4. Use Periodic Events for Metrics: Instead of recording every occurrence
  5. Test Event Overhead: Measure performance impact in your environment

Running with Custom Events:

# Enable JFR and custom events
java -XX:+FlightRecorder -XX:StartFlightRecording=filename=recording.jfr,duration=60s \
-cp your-app.jar YourMainClass
# With custom settings
java -XX:+FlightRecorder \
-XX:StartFlightRecording=settings=profile,filename=recording.jfr \
-cp your-app.jar YourMainClass

This comprehensive guide shows how to create, configure, and use custom JFR events to gain deep insights into your Java application's behavior and performance.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper