Class: Appom::Performance::Monitor
- Inherits:
-
Object
- Object
- Appom::Performance::Monitor
- Includes:
- Logging
- Defined in:
- lib/appom/performance.rb
Overview
Performance monitoring and metrics collection
Instance Attribute Summary collapse
-
#metrics ⇒ Object
readonly
Returns the value of attribute metrics.
-
#started_at ⇒ Object
readonly
Returns the value of attribute started_at.
Instance Method Summary collapse
-
#check_regressions(baseline_file, threshold_percent = 20) ⇒ Object
Check for performance regressions.
-
#end_timing(operation_id, success: true, additional_context: {}) ⇒ Object
End timing an operation.
-
#export_metrics(format: :json, file_path: nil) ⇒ Object
Export metrics to file.
-
#initialize ⇒ Monitor
constructor
A new instance of Monitor.
-
#most_frequent_operations(limit = 10) ⇒ Object
Get most frequent operations.
-
#record_metric(name, duration, success: true, context: {}) ⇒ Object
Record a metric manually.
-
#reset! ⇒ Object
Reset all metrics.
-
#slowest_operations(limit = 10) ⇒ Object
Get slowest operations.
-
#start_timing(operation_name, context = {}) ⇒ Object
Start timing an operation.
-
#stats(operation_name = nil) ⇒ Object
Get performance statistics.
-
#summary ⇒ Object
Get performance summary.
-
#time_operation(operation_name, context = {}) ⇒ Object
Time a block of code.
Methods included from Logging
level, level=, #log_debug, #log_element_action, #log_error, #log_info, #log_wait_end, #log_wait_start, #log_warn, #logger
Constructor Details
#initialize ⇒ Monitor
Returns a new instance of Monitor.
12 13 14 15 16 17 |
# File 'lib/appom/performance.rb', line 12 def initialize @metrics = {} @started_at = Time.now @current_operations = {} reset_session_metrics end |
Instance Attribute Details
#metrics ⇒ Object (readonly)
Returns the value of attribute metrics.
10 11 12 |
# File 'lib/appom/performance.rb', line 10 def metrics @metrics end |
#started_at ⇒ Object (readonly)
Returns the value of attribute started_at.
10 11 12 |
# File 'lib/appom/performance.rb', line 10 def started_at @started_at end |
Instance Method Details
#check_regressions(baseline_file, threshold_percent = 20) ⇒ Object
Check for performance regressions
187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 |
# File 'lib/appom/performance.rb', line 187 def check_regressions(baseline_file, threshold_percent = 20) return {} unless File.exist?(baseline_file) baseline = load_baseline(baseline_file) regressions = {} @metrics.each do |name, current_metric| baseline_metric = baseline[name] next unless baseline_metric current_avg = current_metric[:total_duration] / current_metric[:total_calls] baseline_avg = baseline_metric['avg_duration'] || baseline_metric[:avg_duration] next unless current_avg > baseline_avg * (1 + (threshold_percent / 100.0)) regression_percent = ((current_avg - baseline_avg) / baseline_avg * 100).round(2) regressions[name] = { current_avg: current_avg, baseline_avg: baseline_avg, regression_percent: regression_percent, } end regressions end |
#end_timing(operation_id, success: true, additional_context: {}) ⇒ Object
End timing an operation
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
# File 'lib/appom/performance.rb', line 33 def end_timing(operation_id, success: true, additional_context: {}) operation = @current_operations.delete(operation_id) return unless operation duration = Time.now - operation[:start_time] record_metric( operation[:name], duration, success: success, context: operation[:context].merge(additional_context), ) log_debug("Completed timing: #{operation[:name]} (#{(duration * 1000).round(2)}ms)") duration end |
#export_metrics(format: :json, file_path: nil) ⇒ Object
Export metrics to file
152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 |
# File 'lib/appom/performance.rb', line 152 def export_metrics(format: :json, file_path: nil) file_path ||= "appom_metrics_#{Time.now.strftime('%Y%m%d_%H%M%S')}.#{format}" data = { exported_at: Time.now, session_started: @started_at, summary: summary, detailed_metrics: stats, } case format when :json File.write(file_path, JSON.pretty_generate(data)) when :yaml File.write(file_path, YAML.dump(data)) when :csv export_to_csv(file_path, data) else raise ArgumentError, "Unsupported format: #{format}" end log_info("Performance metrics exported to #{file_path}") file_path end |
#most_frequent_operations(limit = 10) ⇒ Object
Get most frequent operations
139 140 141 142 143 144 145 146 147 148 149 |
# File 'lib/appom/performance.rb', line 139 def most_frequent_operations(limit = 10) operations = @metrics.map do |name, metric| { name: name, total_calls: metric[:total_calls], avg_duration: metric[:total_duration] / metric[:total_calls], success_rate: (metric[:successful_calls].to_f / metric[:total_calls] * 100).round(2), } end operations.sort_by { |op| -op[:total_calls] }.first(limit) end |
#record_metric(name, duration, success: true, context: {}) ⇒ Object
Record a metric manually
72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
# File 'lib/appom/performance.rb', line 72 def record_metric(name, duration, success: true, context: {}) @metrics[name] ||= initialize_metric(name) metric = @metrics[name] metric[:total_calls] += 1 metric[:total_duration] += duration metric[:successful_calls] += 1 if success metric[:failed_calls] += 1 unless success # Update min/max metric[:min_duration] = [metric[:min_duration], duration].min metric[:max_duration] = [metric[:max_duration], duration].max # Calculate rolling averages (last 10 calls) metric[:recent_durations] << duration metric[:recent_durations] = metric[:recent_durations].last(10) # Store context for analysis metric[:contexts] << context.merge(success: success, duration: duration) metric[:contexts] = metric[:contexts].last(50) # Keep last 50 contexts # Update percentiles for larger samples return unless (metric[:total_calls] % 10).zero? update_percentiles(metric) end |
#reset! ⇒ Object
Reset all metrics
178 179 180 181 182 183 184 |
# File 'lib/appom/performance.rb', line 178 def reset! @metrics.clear @started_at = Time.now @current_operations.clear reset_session_metrics log_info('Performance metrics reset') end |
#slowest_operations(limit = 10) ⇒ Object
Get slowest operations
126 127 128 129 130 131 132 133 134 135 136 |
# File 'lib/appom/performance.rb', line 126 def slowest_operations(limit = 10) operations = @metrics.map do |name, metric| { name: name, max_duration: metric[:max_duration], avg_duration: metric[:total_duration] / metric[:total_calls], total_calls: metric[:total_calls], } end operations.sort_by { |op| -op[:max_duration] }.first(limit) end |
#start_timing(operation_name, context = {}) ⇒ Object
Start timing an operation
20 21 22 23 24 25 26 27 28 29 30 |
# File 'lib/appom/performance.rb', line 20 def start_timing(operation_name, context = {}) operation_id = generate_operation_id @current_operations[operation_id] = { name: operation_name, start_time: Time.now, context: context, } log_debug("Started timing: #{operation_name}", context) operation_id end |
#stats(operation_name = nil) ⇒ Object
Get performance statistics
100 101 102 103 104 105 106 |
# File 'lib/appom/performance.rb', line 100 def stats(operation_name = nil) if operation_name calculate_stats(@metrics[operation_name]) if @metrics[operation_name] else @metrics.transform_values { |metric| calculate_stats(metric) } end end |
#summary ⇒ Object
Get performance summary
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 |
# File 'lib/appom/performance.rb', line 109 def summary total_operations = @metrics.values.sum { |m| m[:total_calls] } total_duration = @metrics.values.sum { |m| m[:total_duration] } { session_duration: Time.now - @started_at, total_operations: total_operations, total_duration: total_duration, average_operation_time: total_operations.positive? ? total_duration / total_operations : 0, operations_per_second: total_operations / (Time.now - @started_at), slowest_operations: slowest_operations(5), most_frequent_operations: most_frequent_operations(5), success_rate: calculate_overall_success_rate, } end |
#time_operation(operation_name, context = {}) ⇒ Object
Time a block of code
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
# File 'lib/appom/performance.rb', line 51 def time_operation(operation_name, context = {}) operation_id = start_timing(operation_name, context) Time.now success = true result = nil begin result = yield rescue StandardError => e success = false raise e ensure end_timing(operation_id, success: success, additional_context: { exception: success ? nil : e&.class&.name, },) end result end |