Profiling
Axn supports performance profiling using Vernier, a Ruby sampling profiler that provides detailed insights into your action's performance characteristics.
Overview
Profiling helps you identify performance bottlenecks in your actions by capturing detailed execution traces. Vernier is particularly useful for:
- Identifying slow methods and code paths
- Understanding memory allocation patterns
- Analyzing call stacks and execution flow
- Optimizing performance-critical actions
Setup
1. Install Vernier
Add the Vernier gem to your Gemfile:
# Gemfile
gem 'vernier', '~> 0.1'
Then run:
bundle install
Note: Vernier is not included as a dependency of Axn, so you must explicitly add it to your Gemfile if you want to use profiling features.
2. Enable Profiling
No global configuration is needed! Simply call profile
on the actions you want to profile.
Basic Usage
Profiling is enabled per-action by calling the profile
method. You can only call profile
once per action - subsequent calls will override the previous one. This prevents accidental profiling of all actions and ensures you only profile what you intend to analyze.
Simple Profiling
Enable profiling on any action:
class UserCreation
include Axn
# Always profile this action
profile
expects :user_params
def call
user = User.create!(user_params)
send_welcome_email(user)
end
private
def send_welcome_email(user)
UserMailer.welcome(user).deliver_now
end
end
Conditional Profiling
Profile only under specific conditions:
class DataProcessing
include Axn
# Profile only when processing large datasets (only one profile call per action)
profile if: -> { record_count > 1000 }
expects :records, :record_count
def call
records.each { |record| process_record(record) }
end
end
Alternative using a method:
class DataProcessing
include Axn
# Profile using a method (only one profile call per action)
profile if: :should_profile?
expects :records, :record_count, :debug_mode, type: :boolean, default: false
def should_profile?
record_count > 1000 || debug_mode
end
def call
records.each { |record| process_record(record) }
end
end
Advanced Usage
Sampling Rate Control
Adjust the sampling rate per action:
class DevelopmentAction
include Axn
# High sampling rate for development (more detailed data)
profile(sample_rate: 0.5) if Rails.env.development?
def call
# Action logic
end
end
class ProductionAction
include Axn
# Low sampling rate for production (minimal overhead)
profile(sample_rate: 0.01) if Rails.env.production?
def call
# Action logic
end
end
Custom Output Directory
Organize profiles by environment or feature:
class MyAction
include Axn
# Custom output directory
profile(output_dir: Rails.root.join("tmp", "profiles", Rails.env))
def call
# Action logic
end
end
Multiple Conditions
Combine multiple profiling conditions:
class ComplexAction
include Axn
# Profile when debug mode is enabled OR when processing admin users
profile if: -> { debug_mode || user.admin? }
expects :user, :debug_mode, type: :boolean, default: false
def call
# Complex logic
end
end
Viewing and Analyzing Profiles
1. Generate Profile Data
Run your action with profiling enabled:
# This will generate a profile file if conditions are met
result = UserCreation.call(user_params: { name: "John", email: "john@example.com" })
2. Locate Profile Files
Profile files are saved as JSON in your configured output directory:
# Default location
ls tmp/profiles/
# Example output
axn_UserCreation_1703123456.json
axn_DataProcessing_1703123457.json
3. View in Firefox Profiler
- Open profiler.firefox.com
- Click "Load a profile from file"
- Select your generated JSON file
- Analyze the performance data
4. Understanding the Profile
The Firefox Profiler provides several views:
- Call Tree: Shows the complete call stack with timing
- Flame Graph: Visual representation of call stacks
- Stack Chart: Timeline view of function calls
- Markers: Custom markers and events
Best Practices
1. Use Conditional Profiling
Avoid profiling all actions in production:
# Good: Conditional profiling
profile if: -> { Rails.env.development? || debug_mode }
# Avoid: Always profiling in production
profile # This can impact performance
2. Appropriate Sampling Rates
Choose sampling rates based on your environment:
class MyAction
include Axn
# High detail for debugging
profile(sample_rate: 0.5) if Rails.env.development?
# Moderate sampling for staging
profile(sample_rate: 0.1) if Rails.env.staging?
# Minimal overhead for production
profile(sample_rate: 0.01) if Rails.env.production?
def call
# Action logic
end
end
3. Profile Specific Scenarios
Focus on performance-critical paths:
class OrderProcessing
include Axn
# Profile only expensive operations
profile if: -> { order.total > 1000 }
expects :order
def call
process_payment
send_confirmation
update_inventory
end
end
4. Clean Up Old Profiles
Implement profile cleanup to avoid disk space issues:
# Add to a rake task or cron job
namespace :profiles do
desc "Clean up old profile files"
task cleanup: :environment do
profile_dir = Rails.root.join("tmp", "profiles")
Dir.glob(File.join(profile_dir, "*.json")).each do |file|
File.delete(file) if File.mtime(file) < 7.days.ago
end
end
end
Troubleshooting
Vernier Not Available
If you see this error:
LoadError: Vernier gem is not loaded. Add `gem 'vernier', '~> 0.1'` to your Gemfile to enable profiling.
Make sure to:
- Add
vernier
to your Gemfile - Run
bundle install
- Restart your application
No Profile Files Generated
If profile files aren't being generated:
- Verify your action has
profile
enabled - Ensure profiling conditions are met
- Check the output directory exists and is writable
Performance Impact
Profiling adds overhead to your application:
- Sampling overhead: ~1-5% depending on sample rate
- File I/O: Profile files are written to disk
- Memory usage: Slight increase due to sampling
Use appropriate sampling rates and conditional profiling to minimize impact.
Integration with Other Tools
Datadog Integration
Combine profiling with Datadog tracing:
Axn.configure do |c|
# Datadog tracing
c.wrap_with_trace = proc do |resource, &action|
Datadog::Tracing.trace("Action", resource:) do
action.call
end
end
end
class MyAction
include Axn
# Profiling with custom options
profile(sample_rate: 0.1)
def call
# Action logic
end
end