google-site-verification: google61fe8ba583a51912.html
Pyroscope Profiling in Java

Introduction to Pyroscope

Pyroscope is an open-source continuous profiling platform that helps identify performance bottlenecks in applications. It provides low-overhead profiling with real-time visualization of CPU, memory, and other runtime metrics.

Pyroscope Java Agent Integration

Maven Configuration

<properties> <pyroscope.version>0.13.0</pyroscope.version> </properties> <dependencies> <!-- Pyroscope Java Client --> <dependency> <groupId>io.pyroscope</groupId> <artifactId>pyroscope-java</artifactId> <version>${pyroscope.version}</version> </dependency> <!-- For async profiling --> <dependency> <groupId>io.pyroscope</groupId> <artifactId>pyroscope-java-async-profiler</artifactId> <version>${pyroscope.version}</version> </dependency> <!-- Logging integration --> <dependency> <groupId>io.pyroscope</groupId> <artifactId>pyroscope-log4j</artifactId> <version>${pyroscope.version}</version> </dependency> </dependencies>

Basic Pyroscope Configuration

public class PyroscopeConfig { public static void initialize() { Pyroscope.Options options = new Pyroscope.Options() .setApplicationName("my-java-app") .setServerAddress("http://localhost:4040") .setProfilingEvent(Event.ITIMER) .setProfilingAlloc("512k") .setProfilingLock("10ms") .setProfilingInterval(Duration.ofSeconds(10)) .setUploadInterval(Duration.ofSeconds(10)); Pyroscope.start(options); } // Environment-based configuration public static void initializeFromEnv() { Pyroscope.Options options = Pyroscope.Options.fromEnv() .setApplicationName(System.getenv().getOrDefault("PYROSCOPE_APPLICATION_NAME", "my-java-app")) .setServerAddress(System.getenv().getOrDefault("PYROSCOPE_SERVER_ADDRESS", "http://localhost:4040")); Pyroscope.start(options); } }

Spring Boot Integration

Spring Boot Auto-Configuration

@Configuration @EnableConfigurationProperties(PyroscopeProperties.class) public class PyroscopeAutoConfiguration { private static final Logger logger = LoggerFactory.getLogger(PyroscopeAutoConfiguration.class); @Bean @ConditionalOnProperty(name = "pyroscope.enabled", havingValue = "true") public PyroscopeInitializer pyroscopeInitializer(PyroscopeProperties properties) { return new PyroscopeInitializer(properties); } } @Component public class PyroscopeInitializer { public PyroscopeInitializer(PyroscopeProperties properties) { initializePyroscope(properties); } private void initializePyroscope(PyroscopeProperties properties) { try { Pyroscope.Options options = new Pyroscope.Options() .setApplicationName(properties.getApplicationName()) .setServerAddress(properties.getServerAddress()) .setProfilingEvent(Event.valueOf(properties.getProfilingEvent())) .setProfilingInterval(Duration.ofSeconds(properties.getProfilingInterval())) .setUploadInterval(Duration.ofSeconds(properties.getUploadInterval())) .setAuthToken(properties.getAuthToken()); // Add labels for better filtering options.setLabels(Map.of( "environment", properties.getEnvironment(), "region", properties.getRegion(), "version", properties.getAppVersion() )); Pyroscope.start(options); logger.info("Pyroscope profiling initialized for application: {}", properties.getApplicationName()); } catch (Exception e) { logger.error("Failed to initialize Pyroscope", e); } } } @ConfigurationProperties(prefix = "pyroscope") public class PyroscopeProperties { private boolean enabled = true; private String applicationName = "spring-boot-app"; private String serverAddress = "http://localhost:4040"; private String profilingEvent = "ITIMER"; private int profilingInterval = 10; private int uploadInterval = 10; private String authToken; private String environment = "development"; private String region = "local"; private String appVersion = "1.0.0"; // Getters and setters public boolean isEnabled() { return enabled; } public void setEnabled(boolean enabled) { this.enabled = enabled; } public String getApplicationName() { return applicationName; } public void setApplicationName(String applicationName) { this.applicationName = applicationName; } public String getServerAddress() { return serverAddress; } public void setServerAddress(String serverAddress) { this.serverAddress = serverAddress; } public String getProfilingEvent() { return profilingEvent; } public void setProfilingEvent(String profilingEvent) { this.profilingEvent = profilingEvent; } public int getProfilingInterval() { return profilingInterval; } public void setProfilingInterval(int profilingInterval) { this.profilingInterval = profilingInterval; } public int getUploadInterval() { return uploadInterval; } public void setUploadInterval(int uploadInterval) { this.uploadInterval = uploadInterval; } public String getAuthToken() { return authToken; } public void setAuthToken(String authToken) { this.authToken = authToken; } public String getEnvironment() { return environment; } public void setEnvironment(String environment) { this.environment = environment; } public String getRegion() { return region; } public void setRegion(String region) { this.region = region; } public String getAppVersion() { return appVersion; } public void setAppVersion(String appVersion) { this.appVersion = appVersion; } }

Application Configuration

# application.yml pyroscope: enabled: true application-name: "order-service" server-address: "http://pyroscope:4040" profiling-event: "ITIMER" profiling-interval: 10 upload-interval: 10 environment: "production" region: "us-east-1" app-version: "2.1.0"

Custom Profiling Scenarios

Method-Level Profiling

@Service public class OrderProcessingService { private static final Logger logger = LoggerFactory.getLogger(OrderProcessingService.class); @PyroscopeProfiled("order_processing") public Order processOrder(OrderRequest request) { // This method will be automatically profiled if using AOP return executeOrderProcessing(request); } public Order processOrderWithManualProfiling(OrderRequest request) { // Manual profiling with custom labels Pyroscope.LabelsWrapper.run( Pyroscope.LabelsSet.of("order_type", request.getType(), "customer_tier", request.getCustomerTier()), () -> { return executeOrderProcessing(request); } ); } private Order executeOrderProcessing(OrderRequest request) { validateOrder(request); checkInventory(request); processPayment(request); fulfillOrder(request); return createOrderConfirmation(request); } @PyroscopeProfiled("order_validation") private void validateOrder(OrderRequest request) { // Validation logic try { Thread.sleep(50); // Simulate work } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } @PyroscopeProfiled("inventory_check") private void checkInventory(OrderRequest request) { // Inventory check logic try { Thread.sleep(100); // Simulate work } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } // Custom annotation for AOP profiling @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface PyroscopeProfiled { String value() default ""; String[] labels() default {}; } @Aspect @Component public class PyroscopeProfilingAspect { @Around("@annotation(pyroscopeProfiled)") public Object profileMethod(ProceedingJoinPoint joinPoint, PyroscopeProfiled pyroscopeProfiled) throws Throwable { String profileName = pyroscopeProfiled.value(); if (profileName.isEmpty()) { profileName = joinPoint.getSignature().toShortString(); } // Execute with Pyroscope profiling return Pyroscope.LabelsWrapper.run( buildLabels(joinPoint, pyroscopeProfiled), () -> { try { return joinPoint.proceed(); } catch (RuntimeException e) { throw e; } catch (Throwable e) { throw new RuntimeException(e); } } ); } private Pyroscope.LabelsSet buildLabels(ProceedingJoinPoint joinPoint, PyroscopeProfiled annotation) { Map<String, String> labels = new HashMap<>(); // Add custom labels from annotation for (String label : annotation.labels()) { String[] parts = label.split("="); if (parts.length == 2) { labels.put(parts[0], parts[1]); } } // Add method-specific labels labels.put("class", joinPoint.getTarget().getClass().getSimpleName()); labels.put("method", joinPoint.getSignature().getName()); return Pyroscope.LabelsSet.of(labels); } }

Database Query Profiling

@Repository public class OrderRepository { @PersistenceContext private EntityManager entityManager; @PyroscopeProfiled("database_queries") public List<Order> findOrdersByCustomer(String customerId, Date startDate, Date endDate) { String query = """ SELECT o FROM Order o WHERE o.customerId = :customerId AND o.createdDate BETWEEN :startDate AND :endDate ORDER BY o.createdDate DESC """; return entityManager.createQuery(query, Order.class) .setParameter("customerId", customerId) .setParameter("startDate", startDate) .setParameter("endDate", endDate) .getResultList(); } public List<Order> findOrdersWithProfiling(String customerId, Date startDate, Date endDate) { return Pyroscope.LabelsWrapper.run( Pyroscope.LabelsSet.of("query_type", "customer_orders", "operation", "read"), () -> findOrdersByCustomer(customerId, startDate, endDate) ); } } // JDBC Connection wrapper for profiling @Component public class ProfilingDataSource extends AbstractDataSource { private final DataSource delegate; public ProfilingDataSource(DataSource delegate) { this.delegate = delegate; } @Override public Connection getConnection() throws SQLException { return new ProfilingConnection(delegate.getConnection()); } @Override public Connection getConnection(String username, String password) throws SQLException { return new ProfilingConnection(delegate.getConnection(username, password)); } private static class ProfilingConnection implements Connection { private final Connection delegate; public ProfilingConnection(Connection delegate) { this.delegate = delegate; } @Override public PreparedStatement prepareStatement(String sql) throws SQLException { return new ProfilingPreparedStatement(delegate.prepareStatement(sql), sql); } // Delegate all other methods to the underlying connection @Override public void close() throws SQLException { delegate.close(); } @Override public boolean isClosed() throws SQLException { return delegate.isClosed(); } // ... implement all other Connection methods } private static class ProfilingPreparedStatement implements PreparedStatement { private final PreparedStatement delegate; private final String sql; public ProfilingPreparedStatement(PreparedStatement delegate, String sql) { this.delegate = delegate; this.sql = sql; } @Override public ResultSet executeQuery() throws SQLException { return Pyroscope.LabelsWrapper.run( Pyroscope.LabelsSet.of("sql_operation", "query", "sql_type", getSqlType(sql)), delegate::executeQuery ); } @Override public int executeUpdate() throws SQLException { return Pyroscope.LabelsWrapper.run( Pyroscope.LabelsSet.of("sql_operation", "update", "sql_type", getSqlType(sql)), delegate::executeUpdate ); } @Override public boolean execute() throws SQLException { return Pyroscope.LabelsWrapper.run( Pyroscope.LabelsSet.of("sql_operation", "execute", "sql_type", getSqlType(sql)), delegate::execute ); } private String getSqlType(String sql) { String lowerSql = sql.toLowerCase().trim(); if (lowerSql.startsWith("select")) return "select"; if (lowerSql.startsWith("insert")) return "insert"; if (lowerSql.startsWith("update")) return "update"; if (lowerSql.startsWith("delete")) return "delete"; return "other"; } // Delegate all other methods @Override public void close() throws SQLException { delegate.close(); } // ... implement all other PreparedStatement methods } }

HTTP Request Profiling

@Component public class ProfilingFilter implements Filter { private static final Logger logger = LoggerFactory.getLogger(ProfilingFilter.class); @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) { chain.doFilter(request, response); return; } HttpServletRequest httpRequest = (HttpServletRequest) request; HttpServletResponse httpResponse = (HttpServletResponse) response; String path = httpRequest.getRequestURI(); String method = httpRequest.getMethod(); // Profile the request Pyroscope.LabelsWrapper.run( Pyroscope.LabelsSet.of( "http_method", method, "http_path", sanitizePath(path), "http_status", "in_progress" ), () -> { try { chain.doFilter(request, response); // Update status code after execution Pyroscope.setLabel("http_status", String.valueOf(httpResponse.getStatus())); } catch (Exception e) { Pyroscope.setLabel("http_status", "error"); throw new RuntimeException(e); } return null; } ); } private String sanitizePath(String path) { // Convert path parameters to placeholders return path.replaceAll("/\\d+", "/{id}") .replaceAll("/[a-f0-9-]{36}", "/{uuid}"); } } // REST Controller with profiling @RestController @RequestMapping("/api/orders") public class OrderController { @Autowired private OrderProcessingService orderService; @PostMapping @PyroscopeProfiled(value = "create_order", labels = {"http_method=POST", "endpoint=/api/orders"}) public ResponseEntity<Order> createOrder(@RequestBody OrderRequest request) { Order order = orderService.processOrder(request); return ResponseEntity.ok(order); } @GetMapping("/{orderId}") public ResponseEntity<Order> getOrder(@PathVariable String orderId) { return Pyroscope.LabelsWrapper.run( Pyroscope.LabelsSet.of("operation", "get_order", "order_id", orderId), () -> { Order order = orderService.findOrderById(orderId); return ResponseEntity.ok(order); } ); } }

Advanced Profiling Scenarios

Memory Allocation Profiling

@Service public class MemoryIntensiveService { public void processLargeDataset(List<DataRecord> records) { // Enable memory profiling for this operation Pyroscope.LabelsWrapper.run( Pyroscope.LabelsSet.of("profiling_type", "memory", "dataset_size", String.valueOf(records.size())), () -> { processRecordsWithMemoryProfiling(records); return null; } ); } private void processRecordsWithMemoryProfiling(List<DataRecord> records) { List<ProcessedRecord> results = new ArrayList<>(); for (DataRecord record : records) { // This allocation will be visible in memory profiling ProcessedRecord processed = transformRecord(record); results.add(processed); // Simulate memory-intensive operation if (results.size() % 1000 == 0) { System.gc(); // Hint for GC, don't do this in production! } } aggregateResults(results); } private ProcessedRecord transformRecord(DataRecord record) { // Memory-intensive transformation byte[] largeBuffer = new byte[1024 * 1024]; // 1MB allocation // ... processing logic return new ProcessedRecord(record, largeBuffer); } }

Async Profiling with CompletableFuture

@Service public class AsyncOrderProcessor { @Async public CompletableFuture<Order> processOrderAsync(OrderRequest request) { return Pyroscope.LabelsWrapper.runAsync( Pyroscope.LabelsSet.of("processing_type", "async", "order_type", request.getType()), () -> CompletableFuture.supplyAsync(() -> { // This will be profiled in the async context return processOrder(request); }) ); } public CompletableFuture<List<Order>> processBatchAsync(List<OrderRequest> requests) { List<CompletableFuture<Order>> futures = requests.stream() .map(this::processOrderAsync) .collect(Collectors.toList()); return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])) .thenApply(v -> futures.stream() .map(CompletableFuture::join) .collect(Collectors.toList())); } }

Custom Profiling Events

@Component public class CustomProfilingService { private final PyroscopeProfiler profiler; public CustomProfilingService() { this.profiler = PyroscopeProfiler.getInstance(); } public void trackBusinessEvent(String eventName, Map<String, String> labels) { Pyroscope.LabelsSet labelSet = Pyroscope.LabelsSet.of(labels); // Record custom business event profiler.recordEvent(eventName, labelSet); } public void profileCriticalSection(String sectionName, Runnable criticalCode) { long startTime = System.nanoTime(); try { criticalCode.run(); } finally { long duration = System.nanoTime() - startTime; // Record custom timing metric Pyroscope.LabelsWrapper.run( Pyroscope.LabelsSet.of("critical_section", sectionName), () -> { // This will create a profile sample for the critical section try { Thread.sleep(0); // Force sample point } catch (InterruptedException e) { Thread.currentThread().interrupt(); } return null; } ); logger.info("Critical section {} took {} ns", sectionName, duration); } } }

Monitoring and Health Checks

@Component public class PyroscopeHealthIndicator implements HealthIndicator { private static final Logger logger = LoggerFactory.getLogger(PyroscopeHealthIndicator.class); @Override public Health health() { try { // Check if Pyroscope is active and connected boolean isHealthy = checkPyroscopeConnection(); if (isHealthy) { return Health.up() .withDetail("service", "pyroscope") .withDetail("status", "connected") .build(); } else { return Health.down() .withDetail("service", "pyroscope") .withDetail("status", "disconnected") .build(); } } catch (Exception e) { logger.error("Pyroscope health check failed", e); return Health.down(e).build(); } } private boolean checkPyroscopeConnection() { // Implement connection check to Pyroscope server // This could be an HTTP health check or agent status check return true; // Simplified implementation } } @RestController @RequestMapping("/admin/profiling") public class ProfilingManagementController { @PostMapping("/start") public ResponseEntity<String> startProfiling() { // Implement dynamic profiling control Pyroscope.start(); return ResponseEntity.ok("Profiling started"); } @PostMapping("/stop") public ResponseEntity<String> stopProfiling() { Pyroscope.stop(); return ResponseEntity.ok("Profiling stopped"); } @GetMapping("/status") public ResponseEntity<Map<String, Object>> getProfilingStatus() { Map<String, Object> status = new HashMap<>(); status.put("active", Pyroscope.isActive()); status.put("applicationName", Pyroscope.getApplicationName()); status.put("serverAddress", Pyroscope.getServerAddress()); return ResponseEntity.ok(status); } }

Docker Configuration

FROM openjdk:17-jdk-slim # Install Pyroscope agent RUN wget -O /tmp/pyroscope.jar https://github.com/pyroscope-io/pyroscope-java/releases/download/v0.13.0/pyroscope.jar COPY target/my-app.jar /app/my-app.jar # Run with Pyroscope agent CMD ["java", \ "-javaagent:/tmp/pyroscope.jar", \ "-Dpyroscope.application.name=my-java-app", \ "-Dpyroscope.server.address=http://pyroscope:4040", \ "-Dpyroscope.profiling.event=itimer", \ "-jar", "/app/my-app.jar"]

Conclusion

Pyroscope provides comprehensive profiling capabilities for Java applications:

  1. Low-Overhead Profiling - Continuous profiling with minimal performance impact
  2. Integration Flexibility - Multiple integration approaches (agent, manual, AOP)
  3. Rich Context - Custom labels for filtering and analysis
  4. Real-time Visualization - Immediate insights into performance bottlenecks
  5. Production Ready - Safe for use in production environments

The Java integration allows developers to identify performance issues, optimize resource usage, and understand application behavior under real workloads.

Java Observability, Logging Intelligence & AI-Driven Monitoring (APM, Tracing, Logs & Anomaly Detection)

https://macronepal.com/blog/beyond-metrics-observing-serverless-and-traditional-java-applications-with-thundra-apm/
Explains using Thundra APM to observe both serverless and traditional Java applications by combining tracing, metrics, and logs into a unified observability platform for faster debugging and performance insights.

https://macronepal.com/blog/dynatrace-oneagent-in-java-2/
Explains Dynatrace OneAgent for Java, which automatically instruments JVM applications to capture metrics, traces, and logs, enabling full-stack monitoring and root-cause analysis with minimal configuration.

https://macronepal.com/blog/lightstep-java-sdk-distributed-tracing-and-observability-implementation/
Explains Lightstep Java SDK for distributed tracing, helping developers track requests across microservices and identify latency issues using OpenTelemetry-based observability.

https://macronepal.com/blog/honeycomb-io-beeline-for-java-complete-guide-2/
Explains Honeycomb Beeline for Java, which provides high-cardinality observability and deep query capabilities to understand complex system behavior and debug distributed systems efficiently.

https://macronepal.com/blog/lumigo-for-serverless-in-java-complete-distributed-tracing-guide-2/
Explains Lumigo for Java serverless applications, offering automatic distributed tracing, log correlation, and error tracking to simplify debugging in cloud-native environments. (Lumigo Docs)

https://macronepal.com/blog/from-noise-to-signals-implementing-log-anomaly-detection-in-java-applications/
Explains how to detect anomalies in Java logs using behavioral patterns and machine learning techniques to separate meaningful incidents from noisy log data and improve incident response.

https://macronepal.com/blog/ai-powered-log-analysis-in-java-from-reactive-debugging-to-proactive-insights/
Explains AI-driven log analysis for Java applications, shifting from manual debugging to predictive insights that identify issues early and improve system reliability using intelligent log processing.

https://macronepal.com/blog/titliel-java-logging-best-practices/
Explains best practices for Java logging, focusing on structured logs, proper log levels, performance optimization, and ensuring logs are useful for debugging and observability systems.

https://macronepal.com/blog/seeking-a-loguru-for-java-the-quest-for-elegant-and-simple-logging/
Explains the search for simpler, more elegant logging frameworks in Java, comparing modern logging approaches that aim to reduce complexity while improving readability and developer experience.

Leave a Reply

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


Macro Nepal Helper