Development Tools
Azu provides a comprehensive suite of development tools for performance analysis, profiling, memory leak detection, and benchmarking. These tools help developers optimize their applications during development and identify performance bottlenecks in production.
Overview
The Azu::DevelopmentTools
module includes three main components:
Profiler - Profile code execution time and memory usage
MemoryLeakDetector - Monitor memory usage and detect potential leaks
Benchmark - Performance benchmarking and load testing utilities
Profiler
The built-in profiler helps you identify performance bottlenecks by measuring execution time and memory usage of specific code blocks.
Basic Usage
# Enable profiling
Azu::DevelopmentTools.profiler.enabled = true
# Profile a code block
result = Azu::DevelopmentTools.profile("database_query") do
User.where(active: true).limit(100).to_a
end
# Get profiler statistics
stats = Azu::DevelopmentTools.profiler.stats
puts stats["database_query"]
# Generate a detailed report
puts Azu::DevelopmentTools.profiler.report
Advanced Profiling
# Profile with call stack capture
result = Azu::DevelopmentTools.profiler.profile("complex_operation", capture_stack: true) do
complex_business_logic
end
# Get all profile entries
entries = Azu::DevelopmentTools.profiler.entries
entries.each do |entry|
puts "#{entry.name}: #{entry.duration.total_milliseconds}ms"
puts "Memory delta: #{entry.memory_delta_mb}MB"
end
# Clear profiler data
Azu::DevelopmentTools.profiler.clear
Endpoint Profiling
struct ProfiledEndpoint
include Azu::Endpoint(ProfiledRequest, ProfiledResponse)
get "/profiled/:id"
def call : ProfiledResponse
# Profile database operations
user = Azu::DevelopmentTools.profile("user_lookup") do
User.find(@request.params["id"].to_i32)
end
# Profile template rendering
content = Azu::DevelopmentTools.profile("template_render") do
render_user_template(user)
end
ProfiledResponse.new(content)
end
end
Profile Statistics
The profiler tracks comprehensive statistics for each named operation:
stats = Azu::DevelopmentTools.profiler.stats
# Returns hash with structure:
# {
# "operation_name" => {
# "count" => 25.0,
# "total_time_ms" => 1250.5,
# "avg_time_ms" => 50.02,
# "min_time_ms" => 12.1,
# "max_time_ms" => 125.7,
# "total_memory_mb" => 15.2,
# "avg_memory_mb" => 0.608,
# "max_memory_mb" => 2.1
# }
# }
Memory Leak Detector
The memory leak detector monitors your application's memory usage over time and can identify potential memory leaks through pattern analysis.
Basic Usage
# Start memory monitoring (30-second intervals)
detector = Azu::DevelopmentTools.memory_detector
detector.start_monitoring(30.seconds)
# Take manual snapshots
snapshot = detector.take_snapshot
puts "Current heap size: #{snapshot.heap_size / 1024 / 1024}MB"
# Analyze for potential leaks
analysis = detector.analyze_leak
if analysis.leak_detected?
puts "Memory leak detected!"
puts "Growth: #{analysis.memory_growth_mb}MB over #{analysis.duration.total_hours}h"
puts "Suspected issues: #{analysis.suspected_leaks}"
end
# Stop monitoring
detector.stop_monitoring
Memory Analysis
# Get recent memory snapshots
recent_snapshots = detector.recent_snapshots(20)
# Analyze memory growth between specific snapshots
start_snapshot = recent_snapshots.first
end_snapshot = recent_snapshots.last
analysis = detector.analyze_leak(start_snapshot, end_snapshot)
puts "Memory growth: #{analysis.memory_growth_mb}MB"
puts "Duration: #{analysis.duration.total_minutes} minutes"
puts "Leak detected: #{analysis.leak_detected?}"
Memory Reporting
# Generate comprehensive memory report
puts detector.report
# Example output:
# === Memory Leak Detection Report ===
# Monitoring Period: 2.5 hours
# Memory Growth: 15.3MB
# Leak Detected: YES
#
# Suspected Issues:
# - Large memory growth detected
# - High memory growth with deferred GC - possible leak
#
# Memory Trend (last 10 snapshots):
# 2024-01-15 10:00:00: 45.2MB
# 2024-01-15 10:30:00: 47.8MB
# 2024-01-15 11:00:00: 52.1MB
# ...
Benchmark
The benchmark utility provides tools for measuring code performance and conducting load tests.
Code Benchmarking
# Simple benchmark
result = Azu::DevelopmentTools::Benchmark.run("string_building", 1000) do
String.build do |str|
100.times { str << "test" }
end
end
puts "Average time: #{result.avg_time.total_milliseconds}ms"
puts "Operations per second: #{result.ops_per_second}"
puts "Memory used: #{result.memory_usage} bytes"
Comparative Benchmarking
# Compare multiple implementations
benchmarks = {
"string_concat" => ->{
str = ""
100.times { str += "test" }
},
"string_build" => ->{
String.build do |str|
100.times { str << "test" }
end
},
"array_join" => ->{
parts = Array(String).new(100, "test")
parts.join
}
}
results = Azu::DevelopmentTools::Benchmark.compare(benchmarks, 500)
results.each_with_index do |result, index|
puts "#{index + 1}. #{result.name}: #{result.avg_time.total_microseconds}μs"
end
Load Testing
# Load test an HTTP endpoint
results = Azu::DevelopmentTools::Benchmark.load_test(
url: "http://localhost:4000/api/users",
concurrent: 10, # 10 concurrent connections
requests: 1000, # 1000 total requests
timeout: 30.seconds # 30-second timeout
)
puts "Results:"
puts " Total requests: #{results["total_requests"]}"
puts " Successful: #{results["successful_requests"]}"
puts " Failed: #{results["failed_requests"]}"
puts " Requests/sec: #{results["requests_per_second"]}"
puts " Avg response time: #{results["avg_response_time_ms"]}ms"
puts " Min response time: #{results["min_response_time_ms"]}ms"
puts " Max response time: #{results["max_response_time_ms"]}ms"
Development Mode
Enable all development tools with a single call:
# Enable all development tools
Azu::DevelopmentTools.enable_development_mode
# This enables:
# - Profiler
# - Memory monitoring (30-second intervals)
# - Enhanced logging
# Disable development mode
Azu::DevelopmentTools.disable_development_mode
Integration Examples
Application Startup
# In your application startup
module MyApp
include Azu
configure do |config|
if config.environment.development?
# Enable development tools in development
Azu::DevelopmentTools.enable_development_mode
end
end
end
Middleware Integration
# Custom profiling middleware
struct ProfilingMiddleware
include Azu::Handler
def call(request, response)
endpoint_name = request.path.gsub("/", "_")
Azu::DevelopmentTools.profile("endpoint_#{endpoint_name}") do
@next.call(request, response)
end
end
end
# Add to handler chain
MyApp.start [
ProfilingMiddleware.new,
Azu::Handler::Rescuer.new,
Azu::Handler::Logger.new,
]
Background Job Profiling
# Profile background job performance
class EmailJob
def perform(user_id : Int32)
Azu::DevelopmentTools.profile("email_job") do
user = Azu::DevelopmentTools.profile("user_fetch") do
User.find(user_id)
end
Azu::DevelopmentTools.profile("email_send") do
EmailService.send_welcome_email(user)
end
end
end
end
Component Performance
class PerformanceAwareComponent
include Azu::Component
def refresh
Azu::DevelopmentTools.profile("component_refresh_#{self.class.name}") do
super
end
end
def content
Azu::DevelopmentTools.profile("component_render_#{self.class.name}") do
super
end
end
end
Best Practices
1. Enable in Development Only
# Only enable profiling in development
if Azu::CONFIG.environment.development?
Azu::DevelopmentTools.profiler.enabled = true
Azu::DevelopmentTools.memory_detector.start_monitoring
end
2. Use Meaningful Names
# Good: Descriptive names
Azu::DevelopmentTools.profile("user_authentication") { ... }
Azu::DevelopmentTools.profile("database_user_lookup") { ... }
Azu::DevelopmentTools.profile("password_hash_verification") { ... }
# Bad: Generic names
Azu::DevelopmentTools.profile("operation1") { ... }
Azu::DevelopmentTools.profile("stuff") { ... }
3. Profile Granularly
# Profile at the right level of granularity
def process_user_data(user_id)
# Too granular - overhead exceeds benefit
Azu::DevelopmentTools.profile("variable_assignment") do
user = nil
end
# Good granularity - meaningful operations
user = Azu::DevelopmentTools.profile("user_database_fetch") do
User.find(user_id)
end
result = Azu::DevelopmentTools.profile("user_data_processing") do
process_complex_user_logic(user)
end
result
end
4. Monitor Memory in Long-Running Processes
# For long-running applications
if Azu::CONFIG.environment.production?
# Monitor memory with longer intervals in production
Azu::DevelopmentTools.memory_detector.start_monitoring(5.minutes)
# Set up periodic reporting
spawn do
loop do
sleep 1.hour
analysis = Azu::DevelopmentTools.memory_detector.analyze_leak
if analysis.leak_detected?
Log.warn { "Memory leak detected: #{analysis.memory_growth_mb}MB growth" }
end
end
end
end
5. Benchmark Before Optimizing
# Always benchmark current implementation first
current_result = Azu::DevelopmentTools::Benchmark.run("current_implementation") do
current_slow_method
end
# Then benchmark optimized version
optimized_result = Azu::DevelopmentTools::Benchmark.run("optimized_implementation") do
new_fast_method
end
improvement = (current_result.avg_time - optimized_result.avg_time) / current_result.avg_time
puts "Performance improvement: #{(improvement * 100).round(2)}%"
API Endpoints
Create endpoints to access development tools data:
# Development tools status endpoint
struct DevToolsStatusEndpoint
include Azu::Endpoint(Azu::Request::Empty, JsonResponse)
get "/admin/dev-tools/status"
def call : JsonResponse
profiler = Azu::DevelopmentTools.profiler
detector = Azu::DevelopmentTools.memory_detector
JsonResponse.new({
"profiler" => {
"enabled" => profiler.enabled,
"entries_count" => profiler.entries.size,
"stats" => profiler.stats
},
"memory_detector" => {
"monitoring" => detector.monitoring?,
"snapshots_count" => detector.recent_snapshots.size,
"latest_analysis" => detector.analyze_leak
}
})
end
end
# Performance report endpoint
struct DevToolsReportEndpoint
include Azu::Endpoint(Azu::Request::Empty, TextResponse)
get "/admin/dev-tools/report"
def call : TextResponse
report = String.build do |str|
str << "=== Development Tools Report ===\n\n"
str << Azu::DevelopmentTools.profiler.report
str << "\n\n"
str << Azu::DevelopmentTools.memory_detector.report
end
TextResponse.new(report)
end
end
Performance Impact
The development tools are designed with minimal performance overhead:
Profiler: ~0.1-0.5ms overhead per profiled operation
Memory Detector: ~1-2ms per snapshot (taken at intervals)
Benchmarking: No runtime overhead (used for testing only)
Troubleshooting
Common Issues
Profiler Not Capturing Data
# Ensure profiler is enabled puts "Profiler enabled: #{Azu::DevelopmentTools.profiler.enabled}" Azu::DevelopmentTools.profiler.enabled = true
Memory Monitoring Not Starting
# Check if already monitoring detector = Azu::DevelopmentTools.memory_detector puts "Already monitoring: #{detector.monitoring?}"
Load Test Connection Errors
# Use appropriate timeout and handle network issues results = Azu::DevelopmentTools::Benchmark.load_test( url: "http://localhost:4000/health", concurrent: 5, # Start with lower concurrency requests: 100, # Start with fewer requests timeout: 60.seconds # Increase timeout )
Conclusion
Azu's development tools provide comprehensive insights into your application's performance characteristics. Use the profiler to identify bottlenecks, the memory leak detector to ensure memory efficiency, and the benchmark utilities to validate optimizations. These tools are essential for maintaining high-performance Azu applications in both development and production environments.
Last updated
Was this helpful?