Overview
Performance monitoring in Ruby encompasses profiling application execution, measuring resource consumption, and collecting runtime metrics. Ruby provides built-in tools like Benchmark
and ObjectSpace
alongside third-party gems for comprehensive performance analysis.
The core performance monitoring approach involves three primary areas: execution profiling to identify bottlenecks, memory analysis to track allocation patterns, and benchmarking to measure performance changes over time. Ruby's profiling ecosystem centers around statistical sampling, call graph generation, and allocation tracking.
require 'benchmark'
require 'objspace'
# Basic execution timing
result = Benchmark.measure do
1000.times { "string" * 100 }
end
puts result.real # => 0.001234
Ruby-prof serves as the primary profiling library, offering multiple profiling modes including flat profiling, graph profiling, and call stack analysis. Memory profilers like memory_profiler
and allocation trackers provide detailed insights into object creation and garbage collection patterns.
require 'ruby-prof'
RubyProf.start
complex_operation
result = RubyProf.stop
printer = RubyProf::FlatPrinter.new(result)
printer.print(STDOUT)
Production performance monitoring typically involves APM integration with services like New Relic, Datadog, or custom metric collection systems. These tools provide continuous monitoring of application performance, error rates, and resource utilization patterns.
Basic Usage
The Benchmark
module provides standard performance measurement capabilities for timing code execution. The measure
method returns a Benchmark::Tms
object containing real time, CPU time, and system time measurements.
require 'benchmark'
# Single operation timing
time = Benchmark.measure do
array = (1..100000).to_a
array.map(&:to_s).join(',')
end
puts "Real time: #{time.real}" # Wall clock time
puts "CPU time: #{time.total}" # CPU time used
puts "User time: #{time.utime}" # User CPU time
puts "System time: #{time.stime}" # System CPU time
The bm
method compares multiple approaches, displaying results in tabular format. This proves valuable when evaluating different implementation strategies or optimization attempts.
Benchmark.bm(7) do |x|
x.report("for:") { for i in 1..100000; i.to_s; end }
x.report("times:") { 100000.times { |i| i.to_s } }
x.report("upto:") { 1.upto(100000) { |i| i.to_s } }
end
Memory profiling requires the memory_profiler
gem to track object allocations and memory usage patterns. The profiler captures allocation details including object count, memory consumption, and allocation locations.
require 'memory_profiler'
report = MemoryProfiler.report do
strings = []
1000.times do |i|
strings << "string_#{i}"
end
strings.map(&:upcase)
end
puts report.pretty_print
# Shows total allocations, memory usage, and allocation locations
ObjectSpace provides built-in memory analysis capabilities, including object counting and garbage collection statistics. These tools help identify memory leaks and allocation patterns without external dependencies.
require 'objspace'
# Count objects by class
ObjectSpace.count_objects.each do |type, count|
puts "#{type}: #{count}" if count > 1000
end
# Enable allocation tracing
ObjectSpace.trace_object_allocations_start
array = Array.new(1000) { |i| "item_#{i}" }
ObjectSpace.trace_object_allocations_stop
# Find allocation info for specific objects
array.each_with_index do |obj, i|
next unless i < 5 # Check first 5 objects
file = ObjectSpace.allocation_sourcefile(obj)
line = ObjectSpace.allocation_sourceline(obj)
puts "Object allocated at #{file}:#{line}"
end
Advanced Usage
Ruby-prof provides comprehensive profiling capabilities with multiple measurement modes and output formats. The gem supports flat profiling, graph profiling, and call tree analysis for detailed performance investigation.
require 'ruby-prof'
# Configure profiling mode
RubyProf.measure_mode = RubyProf::WALL_TIME
# Alternative modes: PROCESS_TIME, CPU_TIME, ALLOCATIONS, MEMORY
class PerformanceAnalyzer
def self.profile_with_exclusions(method_patterns: [], &block)
# Exclude specific methods from profiling
method_patterns.each do |pattern|
RubyProf.exclude_methods!(pattern)
end
RubyProf.start
result = yield
profile_result = RubyProf.stop
{
result: result,
profile: profile_result,
call_info: extract_call_info(profile_result)
}
end
private
def self.extract_call_info(profile_result)
profile_result.threads.first.methods.map do |method_info|
{
method_name: method_info.full_name,
self_time: method_info.self_time,
total_time: method_info.total_time,
call_count: method_info.called
}
end.sort_by { |info| -info[:total_time] }
end
end
# Usage with custom analysis
analysis = PerformanceAnalyzer.profile_with_exclusions(
method_patterns: [Object, /^Array#/]
) do
data = (1..10000).map { |i| { id: i, name: "item_#{i}" } }
data.select { |item| item[:id] % 2 == 0 }
.map { |item| item[:name].upcase }
.sort
end
# Generate multiple output formats
printer = RubyProf::CallTreePrinter.new(analysis[:profile])
File.open('callgrind.out', 'w') do |file|
printer.print(file)
end
Custom profiling decorators enable method-level performance monitoring with minimal code changes. This approach proves valuable for selective profiling in production environments.
module ProfilingDecorator
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def profile_method(method_name, options = {})
alias_method :"#{method_name}_without_profiling", method_name
define_method(method_name) do |*args, &block|
threshold = options[:threshold] || 0.1
start_time = Time.now
result = send(:"#{method_name}_without_profiling", *args, &block)
execution_time = Time.now - start_time
if execution_time > threshold
logger = options[:logger] || Logger.new(STDOUT)
logger.warn("Slow method execution: #{self.class}##{method_name} " \
"took #{execution_time.round(4)}s")
# Optional detailed profiling for slow methods
if options[:detailed_profiling] && execution_time > threshold * 2
detailed_profile = MemoryProfiler.report do
send(:"#{method_name}_without_profiling", *args, &block)
end
logger.warn("Memory usage: #{detailed_profile.total_allocated_memsize} bytes")
end
end
result
end
end
end
end
class DataProcessor
include ProfilingDecorator
def process_large_dataset(data)
data.map { |item| expensive_transformation(item) }
.select { |result| result[:valid] }
end
profile_method :process_large_dataset,
threshold: 0.5,
detailed_profiling: true
private
def expensive_transformation(item)
# Simulate complex processing
sleep(0.001)
{ id: item[:id], valid: item[:id] % 10 != 0, processed: Time.now }
end
end
Advanced memory profiling involves tracking specific allocation patterns and identifying memory hotspots through detailed allocation analysis.
require 'objspace'
class MemoryLeakDetector
def initialize
@snapshots = []
end
def take_snapshot(label)
GC.start # Clean up before snapshot
snapshot = {
label: label,
timestamp: Time.now,
objects: ObjectSpace.count_objects,
memsize: ObjectSpace.memsize_of_all,
live_objects: live_object_analysis
}
@snapshots << snapshot
snapshot
end
def analyze_growth
return [] if @snapshots.length < 2
base = @snapshots[-2]
current = @snapshots[-1]
growth_analysis = {}
current[:objects].each do |type, count|
base_count = base[:objects][type] || 0
growth = count - base_count
if growth > 100 # Significant growth threshold
growth_analysis[type] = {
growth: growth,
percentage: base_count > 0 ? (growth.to_f / base_count * 100).round(2) : Float::INFINITY,
current: count,
previous: base_count
}
end
end
growth_analysis.sort_by { |_, data| -data[:growth] }
end
private
def live_object_analysis
analysis = Hash.new(0)
ObjectSpace.each_object do |obj|
analysis[obj.class.name] += 1
end
analysis
end
end
# Usage for leak detection
detector = MemoryLeakDetector.new
detector.take_snapshot("baseline")
# Simulate potential memory leak
1000.times do |i|
@global_cache ||= {}
@global_cache["key_#{i}"] = "value" * 1000
end
detector.take_snapshot("after_operation")
growth = detector.analyze_growth
puts "Memory growth analysis:"
growth.first(5).each do |type, data|
puts "#{type}: +#{data[:growth]} objects (+#{data[:percentage]}%)"
end
Error Handling & Debugging
Performance monitoring introduces specific error conditions including profiling overhead, memory exhaustion during analysis, and timing measurement inaccuracies. Handle these scenarios with appropriate error recovery strategies.
class SafeProfiler
def self.measure_with_fallback(timeout: 30, &block)
Timeout.timeout(timeout) do
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
result = yield
end_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
{
result: result,
execution_time: end_time - start_time,
profiling_error: nil
}
end
rescue Timeout::Error
{
result: nil,
execution_time: timeout,
profiling_error: "Operation timed out after #{timeout}s"
}
rescue StandardError => e
{
result: nil,
execution_time: nil,
profiling_error: "Profiling failed: #{e.class}: #{e.message}"
}
end
end
# Safe profiling with resource limits
profile_result = SafeProfiler.measure_with_fallback(timeout: 10) do
# Potentially problematic operation
large_array = Array.new(1_000_000) { rand(1000) }
large_array.sort.uniq
end
if profile_result[:profiling_error]
puts "Profiling issue: #{profile_result[:profiling_error]}"
else
puts "Execution time: #{profile_result[:execution_time].round(4)}s"
end
Memory profiling can exhaust available memory when analyzing memory-intensive operations. Implement streaming analysis and memory limits to prevent system instability.
require 'memory_profiler'
class MemoryProfilerWrapper
MAX_TRACKED_OBJECTS = 100_000
MAX_REPORT_SIZE = 50 * 1024 * 1024 # 50MB
def self.safe_profile(max_objects: MAX_TRACKED_OBJECTS, &block)
initial_object_count = ObjectSpace.count_objects[:TOTAL]
report = nil
begin
report = MemoryProfiler.report(top: 50) do # Limit report size
yield
end
final_object_count = ObjectSpace.count_objects[:TOTAL]
objects_created = final_object_count - initial_object_count
if objects_created > max_objects
return {
report: truncated_report(report),
warning: "Large object allocation detected: #{objects_created} objects",
truncated: true
}
end
{
report: report,
warning: nil,
truncated: false
}
rescue NoMemoryError
GC.start
{
report: nil,
warning: "Memory exhausted during profiling",
truncated: true
}
rescue StandardError => e
{
report: nil,
warning: "Profiling error: #{e.message}",
truncated: false
}
end
end
private
def self.truncated_report(full_report)
return nil unless full_report
# Create summary instead of full report if too large
summary = {
total_allocated: full_report.total_allocated,
total_retained: full_report.total_retained,
allocated_objects: full_report.allocated_objects_by_location.first(10),
retained_objects: full_report.retained_objects_by_location.first(10)
}
summary
end
end
Benchmark timing can produce misleading results due to garbage collection interference, system load, and measurement granularity. Address these issues with multiple runs and statistical analysis.
class ReliableBenchmark
def self.measure_multiple(iterations: 10, warmup: 3, &block)
# Warmup runs to stabilize JIT and memory allocation
warmup.times { yield }
GC.start
measurements = []
iterations.times do
gc_before = GC.stat
time = Benchmark.measure(&block)
gc_after = GC.stat
gc_runs = gc_after[:count] - gc_before[:count]
measurements << {
real_time: time.real,
cpu_time: time.total,
gc_runs: gc_runs,
gc_affected: gc_runs > 0
}
end
analyze_measurements(measurements)
end
private
def self.analyze_measurements(measurements)
real_times = measurements.map { |m| m[:real_time] }
cpu_times = measurements.map { |m| m[:cpu_time] }
gc_affected_count = measurements.count { |m| m[:gc_affected] }
{
real_time: {
mean: mean(real_times),
median: median(real_times),
std_dev: standard_deviation(real_times),
min: real_times.min,
max: real_times.max
},
cpu_time: {
mean: mean(cpu_times),
median: median(cpu_times)
},
gc_interference: {
affected_runs: gc_affected_count,
percentage: (gc_affected_count.to_f / measurements.length * 100).round(2)
},
reliability_score: calculate_reliability(real_times, gc_affected_count)
}
end
def self.calculate_reliability(times, gc_affected)
cv = coefficient_of_variation(times) # Lower is better
gc_penalty = gc_affected.to_f / times.length # Lower is better
# Score from 0-100, higher is more reliable
base_score = [100 - (cv * 100), 0].max
gc_adjusted = base_score * (1 - gc_penalty * 0.5)
gc_adjusted.round(2)
end
def self.mean(array)
array.sum.to_f / array.length
end
def self.median(array)
sorted = array.sort
mid = sorted.length / 2
sorted.length.odd? ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2.0
end
def self.standard_deviation(array)
avg = mean(array)
variance = array.sum { |x| (x - avg) ** 2 } / array.length
Math.sqrt(variance)
end
def self.coefficient_of_variation(array)
std_dev = standard_deviation(array)
avg = mean(array)
avg == 0 ? 0 : std_dev / avg
end
end
# Usage with reliability analysis
benchmark_result = ReliableBenchmark.measure_multiple(iterations: 20) do
array = (1..10000).to_a.shuffle
array.sort
end
puts "Mean execution time: #{benchmark_result[:real_time][:mean].round(6)}s"
puts "Standard deviation: #{benchmark_result[:real_time][:std_dev].round(6)}s"
puts "Reliability score: #{benchmark_result[:reliability_score]}/100"
puts "GC interference: #{benchmark_result[:gc_interference][:percentage]}% of runs"
Performance & Memory
Benchmark-driven optimization requires systematic measurement of performance changes across different implementation approaches. Establish baseline measurements before optimization attempts and validate improvements with statistical significance.
require 'benchmark/ips'
class PerformanceComparison
def self.compare_implementations(name, implementations, duration: 5)
puts "=== #{name} ==="
Benchmark.ips do |x|
x.config(time: duration, warmup: 2)
implementations.each do |impl_name, impl_block|
x.report(impl_name, &impl_block)
end
x.compare!
end
puts "\nMemory usage comparison:"
implementations.each do |impl_name, impl_block|
report = MemoryProfiler.report(&impl_block)
puts "#{impl_name}: #{report.total_allocated} objects, " \
"#{report.total_allocated_memsize} bytes"
end
end
end
# Compare string concatenation methods
PerformanceComparison.compare_implementations(
"String Concatenation Methods",
{
"String interpolation" => proc {
1000.times { |i| "Item number #{i} of 1000" }
},
"String concatenation" => proc {
1000.times { |i| "Item number " + i.to_s + " of 1000" }
},
"Array join" => proc {
1000.times { |i| ["Item number ", i, " of 1000"].join }
},
"String format" => proc {
1000.times { |i| "Item number %d of 1000" % i }
}
}
)
Memory-intensive operations require allocation tracking to identify optimization opportunities. Track object creation patterns and identify areas for object pooling or allocation reduction.
class AllocationTracker
def self.track_allocations(&block)
ObjectSpace.trace_object_allocations_start
before_stats = ObjectSpace.count_objects
before_memsize = ObjectSpace.memsize_of_all
GC.disable
result = yield
GC.enable
after_stats = ObjectSpace.count_objects
after_memsize = ObjectSpace.memsize_of_all
ObjectSpace.trace_object_allocations_stop
allocation_diff = {}
after_stats.each do |type, count|
before_count = before_stats[type] || 0
diff = count - before_count
allocation_diff[type] = diff if diff > 0
end
{
result: result,
allocations: allocation_diff,
memory_delta: after_memsize - before_memsize,
allocation_hotspots: find_allocation_hotspots
}
end
private
def self.find_allocation_hotspots
hotspots = Hash.new(0)
ObjectSpace.each_object do |obj|
file = ObjectSpace.allocation_sourcefile(obj)
line = ObjectSpace.allocation_sourceline(obj)
if file && line
location = "#{File.basename(file)}:#{line}"
hotspots[location] += 1
end
end
hotspots.sort_by { |_, count| -count }.first(10)
end
end
# Optimize array processing
def process_data_naive(data)
results = []
data.each do |item|
if item[:active]
processed = {
id: item[:id],
name: item[:name].upcase,
category: item[:category] || 'default',
timestamp: Time.now
}
results << processed
end
end
results
end
def process_data_optimized(data, timestamp = Time.now)
data.filter_map do |item|
next unless item[:active]
{
id: item[:id],
name: item[:name].upcase,
category: item[:category] || 'default',
timestamp: timestamp
}
end
end
# Compare allocation patterns
test_data = 10000.times.map do |i|
{
id: i,
name: "item_#{i}",
category: i % 5 == 0 ? nil : "category_#{i % 3}",
active: i % 4 != 0
}
end
puts "Naive implementation:"
naive_result = AllocationTracker.track_allocations do
process_data_naive(test_data)
end
puts "Optimized implementation:"
optimized_result = AllocationTracker.track_allocations do
process_data_optimized(test_data)
end
puts "Allocation reduction:"
naive_result[:allocations].each do |type, count|
optimized_count = optimized_result[:allocations][type] || 0
reduction = count - optimized_count
if reduction > 0
puts "#{type}: #{count} -> #{optimized_count} (-#{reduction})"
end
end
CPU profiling identifies performance bottlenecks in computational workloads. Use statistical profiling to find methods consuming the most execution time.
require 'ruby-prof'
class CPUProfiler
def self.profile_cpu_intensive(duration: 10, &block)
RubyProf.measure_mode = RubyProf::CPU_TIME
RubyProf.start
start_time = Time.now
result = yield
end_time = Time.now
profile_result = RubyProf.stop
analyze_cpu_profile(profile_result, end_time - start_time)
end
private
def self.analyze_cpu_profile(profile_result, wall_time)
methods_by_cpu_time = []
profile_result.threads.each do |thread_info|
thread_info.methods.each do |method_info|
methods_by_cpu_time << {
method: method_info.full_name,
self_time: method_info.self_time,
total_time: method_info.total_time,
call_count: method_info.called,
time_per_call: method_info.called > 0 ?
method_info.self_time / method_info.called : 0
}
end
end
methods_by_cpu_time.sort_by! { |m| -m[:total_time] }
total_cpu_time = methods_by_cpu_time.sum { |m| m[:self_time] }
cpu_utilization = wall_time > 0 ? (total_cpu_time / wall_time * 100) : 0
{
cpu_utilization: cpu_utilization.round(2),
wall_time: wall_time,
cpu_time: total_cpu_time,
top_methods: methods_by_cpu_time.first(15),
bottlenecks: identify_bottlenecks(methods_by_cpu_time)
}
end
def self.identify_bottlenecks(methods)
total_time = methods.sum { |m| m[:total_time] }
threshold = total_time * 0.05 # Methods using >5% of total time
methods.select { |m| m[:total_time] > threshold }
.map do |m|
{
method: m[:method],
percentage: (m[:total_time] / total_time * 100).round(2),
total_time: m[:total_time],
optimization_potential: m[:call_count] > 100 ? 'HIGH' : 'MEDIUM'
}
end
end
end
Production Patterns
Production performance monitoring requires continuous metrics collection with minimal overhead. Implement sampling-based monitoring and asynchronous metric reporting to avoid impacting application performance.
require 'concurrent-ruby'
class ProductionProfiler
def initialize(sample_rate: 0.01, reporting_interval: 60)
@sample_rate = sample_rate
@reporting_interval = reporting_interval
@metrics = Concurrent::Hash.new
@metric_queue = Concurrent::Array.new
@reporter_thread = start_reporter_thread
end
def monitor_method(object, method_name)
original_method = object.method(method_name)
object.define_singleton_method(method_name) do |*args, &block|
if Random.rand <= @sample_rate
monitor_execution(method_name, original_method, args, &block)
else
original_method.call(*args, &block)
end
end
end
def record_custom_metric(name, value, tags = {})
@metric_queue << {
name: name,
value: value,
tags: tags,
timestamp: Time.now.to_f
}
end
def get_metrics_summary
@metrics.each_with_object({}) do |(key, data), summary|
summary[key] = {
count: data[:count],
avg_duration: data[:total_time] / data[:count],
min_duration: data[:min_time],
max_duration: data[:max_time],
error_rate: data[:errors].to_f / data[:count] * 100
}
end
end
private
def monitor_execution(method_name, original_method, args, &block)
start_time = Time.now
begin
result = original_method.call(*args, &block)
record_success(method_name, Time.now - start_time)
result
rescue StandardError => e
record_error(method_name, Time.now - start_time, e)
raise
end
end
def record_success(method_name, duration)
update_metrics(method_name, duration, false)
end
def record_error(method_name, duration, error)
update_metrics(method_name, duration, true)
@metric_queue << {
name: 'method_error',
value: 1,
tags: { method: method_name, error_class: error.class.name },
timestamp: Time.now.to_f
}
end
def update_metrics(method_name, duration, is_error)
@metrics.compute(method_name) do |_, current|
if current
{
count: current[:count] + 1,
total_time: current[:total_time] + duration,
min_time: [current[:min_time], duration].min,
max_time: [current[:max_time], duration].max,
errors: current[:errors] + (is_error ? 1 : 0)
}
else
{
count: 1,
total_time: duration,
min_time: duration,
max_time: duration,
errors: is_error ? 1 : 0
}
end
end
end
def start_reporter_thread
Thread.new do
loop do
sleep(@reporting_interval)
report_metrics
end
end
end
def report_metrics
return if @metric_queue.empty?
metrics_batch = @metric_queue.slice!(0, @metric_queue.length)
# Send to monitoring service (example with HTTP)
send_to_monitoring_service(metrics_batch)
rescue StandardError => e
warn "Failed to report metrics: #{e.message}"
end
def send_to_monitoring_service(metrics)
# Example integration with monitoring service
# In production, use proper HTTP client with retries
require 'net/http'
require 'json'
uri = URI(ENV['METRICS_ENDPOINT'] || 'http://localhost:8080/metrics')
Net::HTTP.post(uri, {
metrics: metrics,
hostname: Socket.gethostname,
application: ENV['APP_NAME'] || 'ruby_app'
}.to_json, 'Content-Type' => 'application/json')
end
end
# Integration example
class OrderProcessor
def initialize
@profiler = ProductionProfiler.new(sample_rate: 0.05)
@profiler.monitor_method(self, :process_order)
end
def process_order(order_data)
validate_order(order_data)
charge_payment(order_data)
fulfill_order(order_data)
send_confirmation(order_data)
@profiler.record_custom_metric('orders_processed', 1,
{ customer_type: order_data[:customer_type] })
end
private
def validate_order(order_data)
# Order validation logic
sleep(0.01) # Simulate processing time
end
def charge_payment(order_data)
# Payment processing
sleep(0.05)
end
def fulfill_order(order_data)
# Fulfillment logic
sleep(0.02)
end
def send_confirmation(order_data)
# Send confirmation email
sleep(0.01)
end
end
Application Performance Monitoring integration enables comprehensive production monitoring with distributed tracing and error tracking. Implement correlation IDs and structured logging for request flow analysis.
class APMIntegration
def initialize(service_name:, version:, environment:)
@service_name = service_name
@version = version
@environment = environment
@active_spans = {}
end
def trace_request(request_id, operation_name, &block)
span_context = {
request_id: request_id,
operation: operation_name,
service: @service_name,
version: @version,
environment: @environment,
start_time: Time.now,
tags: {}
}
begin
result = yield(span_context)
span_context[:status] = 'success'
span_context[:end_time] = Time.now
result
rescue StandardError => e
span_context[:status] = 'error'
span_context[:error] = {
class: e.class.name,
message: e.message,
backtrace: e.backtrace&.first(10)
}
span_context[:end_time] = Time.now
raise
ensure
report_span(span_context)
end
end
def add_span_tag(request_id, key, value)
span = @active_spans[request_id]
span[:tags][key] = value if span
end
private
def report_span(span_context)
duration_ms = ((span_context[:end_time] - span_context[:start_time]) * 1000).round(2)
log_entry = {
timestamp: span_context[:start_time].iso8601(3),
service: span_context[:service],
operation: span_context[:operation],
request_id: span_context[:request_id],
duration_ms: duration_ms,
status: span_context[:status],
tags: span_context[:tags]
}
log_entry[:error] = span_context[:error] if span_context[:error]
# Structured logging for APM ingestion
puts log_entry.to_json
# Optional: Send to APM service via HTTP
send_to_apm_service(log_entry) if production_environment?
end
def production_environment?
@environment == 'production'
end
def send_to_apm_service(span_data)
# Example APM service integration
Thread.new do
begin
require 'net/http'
uri = URI(ENV['APM_ENDPOINT'])
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme == 'https'
request = Net::HTTP::Post.new(uri)
request['Content-Type'] = 'application/json'
request['Authorization'] = "Bearer #{ENV['APM_TOKEN']}"
request.body = span_data.to_json
response = http.request(request)
warn "APM report failed: #{response.code}" unless response.code == '200'
rescue StandardError => e
warn "APM integration error: #{e.message}"
end
end
end
end
Reference
Benchmark Module
Method | Parameters | Returns | Description |
---|---|---|---|
Benchmark.measure |
&block |
Benchmark::Tms |
Measures execution time of block |
Benchmark.bm(width=0) |
width (Integer), &block |
Array |
Comparative benchmarking with labels |
Benchmark.bmbm(width=0) |
width (Integer), &block |
Array |
Rehearsal and measurement benchmarking |
Benchmark.realtime |
&block |
Float |
Returns wall clock time only |
Benchmark::Tms Object
Method | Returns | Description |
---|---|---|
#real |
Float |
Wall clock elapsed time |
#utime |
Float |
User CPU time |
#stime |
Float |
System CPU time |
#total |
Float |
Total CPU time (utime + stime) |
#cstime |
Float |
System CPU time for child processes |
#cutime |
Float |
User CPU time for child processes |
ObjectSpace Module
Method | Parameters | Returns | Description |
---|---|---|---|
ObjectSpace.count_objects |
result_hash (Hash, optional) |
Hash |
Object counts by type |
ObjectSpace.memsize_of(obj) |
obj (Object) |
Integer |
Memory size of object in bytes |
ObjectSpace.memsize_of_all |
klass (Class, optional) |
Integer |
Total memory usage |
ObjectSpace.trace_object_allocations_start |
None | nil |
Enable allocation tracing |
ObjectSpace.trace_object_allocations_stop |
None | nil |
Disable allocation tracing |
ObjectSpace.allocation_sourcefile(obj) |
obj (Object) |
String or nil |
Source file where object allocated |
ObjectSpace.allocation_sourceline(obj) |
obj (Object) |
Integer or nil |
Source line where object allocated |
Ruby-prof Configuration
Measure Mode | Constant | Description |
---|---|---|
Wall Time | RubyProf::WALL_TIME |
Real elapsed time |
Process Time | RubyProf::PROCESS_TIME |
CPU time for process |
CPU Time | RubyProf::CPU_TIME |
User + system CPU time |
Allocations | RubyProf::ALLOCATIONS |
Object allocation count |
Memory | RubyProf::MEMORY |
Memory allocation size |
Ruby-prof Printers
Printer Class | Output Format | Use Case |
---|---|---|
RubyProf::FlatPrinter |
Flat list | Method execution summary |
RubyProf::GraphPrinter |
Call graph | Method call relationships |
RubyProf::CallTreePrinter |
Callgrind format | Integration with kcachegrind |
RubyProf::CallStackPrinter |
HTML call stack | Visual call stack analysis |
Memory Profiler Report Methods
Method | Returns | Description |
---|---|---|
#total_allocated |
Integer |
Total objects allocated |
#total_allocated_memsize |
Integer |
Total memory allocated in bytes |
#total_retained |
Integer |
Total objects retained |
#total_retained_memsize |
Integer |
Total memory retained in bytes |
#allocated_objects_by_location |
Array |
Allocation locations sorted by count |
#retained_objects_by_location |
Array |
Retention locations sorted by count |
#allocated_objects_by_class |
Array |
Allocations grouped by class |
GC Statistics
GC.stat Key | Type | Description |
---|---|---|
:count |
Integer | Number of GC runs |
:heap_allocated_pages |
Integer | Pages allocated to heap |
:heap_live_slots |
Integer | Live object slots |
:heap_free_slots |
Integer | Free object slots |
:major_gc_count |
Integer | Major GC collections |
:minor_gc_count |
Integer | Minor GC collections |
:total_allocated_objects |
Integer | Total objects allocated |
:total_freed_objects |
Integer | Total objects freed |
Performance Monitoring Patterns
Pattern | Implementation | Trade-offs |
---|---|---|
Sampling | Random selection of requests | Low overhead, statistical accuracy |
Always-on | Monitor every request | Complete coverage, higher overhead |
Threshold-based | Monitor slow operations only | Focused on problems, may miss patterns |
Periodic | Regular profiling intervals | Scheduled overhead, time-based insights |
Event-driven | Monitor on specific conditions | Targeted analysis, complex triggering logic |
Production Monitoring Considerations
Aspect | Recommendation | Rationale |
---|---|---|
Sample Rate | 1-5% for high-traffic applications | Balance insight with performance impact |
Metric Retention | 90 days for detailed metrics | Historical analysis without excessive storage |
Alert Thresholds | 95th percentile response times | Catch performance degradation early |
Profiling Duration | 30-60 seconds maximum | Prevent profiling overhead accumulation |
Memory Tracking | Enable for staging environments | Production memory tracking has high overhead |