apm_bro 0.1.13 → 0.1.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,12 +5,12 @@ require "net/http"
5
5
 
6
6
  module ApmBro
7
7
  module HttpInstrumentation
8
- EVENT_NAME = "outgoing.http".freeze
8
+ EVENT_NAME = "outgoing.http"
9
9
 
10
10
  def self.install!(client: Client.new)
11
11
  install_net_http!(client)
12
12
  install_typhoeus!(client) if defined?(::Typhoeus)
13
- rescue StandardError
13
+ rescue
14
14
  # Never raise from instrumentation install
15
15
  end
16
16
 
@@ -30,29 +30,33 @@ module ApmBro
30
30
  finish_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
31
31
  duration_ms = ((finish_time - start_time) * 1000.0).round(2)
32
32
  begin
33
- uri = URI.parse(req.uri ? req.uri.to_s : "http://#{@address}:#{@port}#{req.path}") rescue nil
33
+ uri = begin
34
+ URI.parse(req.uri ? req.uri.to_s : "http://#{@address}:#{@port}#{req.path}")
35
+ rescue
36
+ nil
37
+ end
34
38
 
35
39
  # Skip instrumentation for our own APM endpoint to prevent infinite loops,
36
40
  # but do NOT alter the original method's return value/control flow.
37
- skip_instrumentation = (uri && (uri.to_s.include?("localhost") || uri.to_s.include?("uptime.aberatii.com")))
41
+ skip_instrumentation = uri && (uri.to_s.include?("localhost") || uri.to_s.include?("uptime.aberatii.com"))
38
42
 
39
43
  unless skip_instrumentation
40
44
  payload = {
41
45
  library: "net_http",
42
46
  method: req.method,
43
- url: (uri && uri.to_s),
47
+ url: uri && uri.to_s,
44
48
  host: (uri && uri.host) || @address,
45
49
  path: (uri && uri.path) || req.path,
46
- status: (response && response.code.to_i),
50
+ status: response && response.code.to_i,
47
51
  duration_ms: duration_ms,
48
- exception: (error && error.class.name)
52
+ exception: error && error.class.name
49
53
  }
50
54
  # Accumulate per-request; only send with controller metric
51
55
  if Thread.current[:apm_bro_http_events]
52
56
  Thread.current[:apm_bro_http_events] << payload
53
57
  end
54
58
  end
55
- rescue StandardError
59
+ rescue
56
60
  end
57
61
  end
58
62
  end
@@ -73,18 +77,22 @@ module ApmBro
73
77
  finish_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
74
78
  duration_ms = ((finish_time - start_time) * 1000.0).round(2)
75
79
  begin
76
- req_url = respond_to?(:url) ? url : (respond_to?(:base_url) ? base_url : nil)
80
+ req_url = if respond_to?(:url)
81
+ url
82
+ else
83
+ (respond_to?(:base_url) ? base_url : nil)
84
+ end
77
85
 
78
86
  # Skip instrumentation for our own APM endpoint to prevent infinite loops,
79
87
  # but do NOT alter the original method's return value/control flow.
80
- skip_instrumentation = (req_url && req_url.include?("localhost:3100/apm/v1/metrics"))
88
+ skip_instrumentation = req_url && req_url.include?("localhost:3100/apm/v1/metrics")
81
89
 
82
90
  unless skip_instrumentation
83
91
  payload = {
84
92
  library: "typhoeus",
85
- method: respond_to?(:options) && options[:method] ? options[:method].to_s.upcase : nil,
93
+ method: (respond_to?(:options) && options[:method]) ? options[:method].to_s.upcase : nil,
86
94
  url: req_url,
87
- status: (response && response.code),
95
+ status: response && response.code,
88
96
  duration_ms: duration_ms
89
97
  }
90
98
  # Accumulate per-request; only send with controller metric
@@ -92,16 +100,14 @@ module ApmBro
92
100
  Thread.current[:apm_bro_http_events] << payload
93
101
  end
94
102
  end
95
- rescue StandardError
103
+ rescue
96
104
  end
97
105
  end
98
106
  end
99
107
  end
100
108
 
101
109
  ::Typhoeus::Request.prepend(mod) unless ::Typhoeus::Request.ancestors.include?(mod)
102
- rescue StandardError
110
+ rescue
103
111
  end
104
112
  end
105
113
  end
106
-
107
-
@@ -19,7 +19,7 @@ module ApmBro
19
19
  ApmBro::MemoryTrackingSubscriber.start_request_tracking
20
20
  end
21
21
  end
22
- rescue StandardError
22
+ rescue
23
23
  # Never raise from instrumentation install
24
24
  end
25
25
  end
@@ -1,11 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/notifications"
3
+ begin
4
+ require "active_support/notifications"
5
+ rescue LoadError
6
+ # ActiveSupport not available
7
+ end
4
8
 
5
9
  module ApmBro
6
10
  class JobSubscriber
7
- JOB_EVENT_NAME = "perform.active_job".freeze
8
- JOB_EXCEPTION_EVENT_NAME = "exception.active_job".freeze
11
+ JOB_EVENT_NAME = "perform.active_job"
12
+ JOB_EXCEPTION_EVENT_NAME = "exception.active_job"
9
13
 
10
14
  def self.subscribe!(client: Client.new)
11
15
  # Track job execution
@@ -15,14 +19,14 @@ module ApmBro
15
19
  if ApmBro.configuration.excluded_job?(job_class_name)
16
20
  next
17
21
  end
18
- rescue StandardError
22
+ rescue
19
23
  end
20
24
 
21
25
  duration_ms = ((finished - started) * 1000.0).round(2)
22
-
26
+
23
27
  # Get SQL queries executed during this job
24
28
  sql_queries = ApmBro::SqlSubscriber.stop_request_tracking
25
-
29
+
26
30
  # Stop memory tracking and get collected memory data
27
31
  if ApmBro.configuration.allocation_tracking_enabled && defined?(ApmBro::MemoryTrackingSubscriber)
28
32
  detailed_memory = ApmBro::MemoryTrackingSubscriber.stop_request_tracking
@@ -50,7 +54,7 @@ module ApmBro
50
54
  duration_seconds: lightweight_memory[:duration_seconds]
51
55
  }
52
56
  end
53
-
57
+
54
58
  payload = {
55
59
  job_class: data[:job].class.name,
56
60
  job_id: data[:job].job_id,
@@ -67,7 +71,7 @@ module ApmBro
67
71
  memory_performance: memory_performance,
68
72
  logs: ApmBro.logger.logs
69
73
  }
70
-
74
+
71
75
  client.post_metric(event_name: name, payload: payload)
72
76
  end
73
77
 
@@ -78,15 +82,15 @@ module ApmBro
78
82
  if ApmBro.configuration.excluded_job?(job_class_name)
79
83
  next
80
84
  end
81
- rescue StandardError
85
+ rescue
82
86
  end
83
87
 
84
88
  duration_ms = ((finished - started) * 1000.0).round(2)
85
89
  exception = data[:exception_object]
86
-
90
+
87
91
  # Get SQL queries executed during this job
88
92
  sql_queries = ApmBro::SqlSubscriber.stop_request_tracking
89
-
93
+
90
94
  # Stop memory tracking and get collected memory data
91
95
  if ApmBro.configuration.allocation_tracking_enabled && defined?(ApmBro::MemoryTrackingSubscriber)
92
96
  detailed_memory = ApmBro::MemoryTrackingSubscriber.stop_request_tracking
@@ -114,7 +118,7 @@ module ApmBro
114
118
  duration_seconds: lightweight_memory[:duration_seconds]
115
119
  }
116
120
  end
117
-
121
+
118
122
  payload = {
119
123
  job_class: data[:job].class.name,
120
124
  job_id: data[:job].job_id,
@@ -134,11 +138,11 @@ module ApmBro
134
138
  memory_performance: memory_performance,
135
139
  logs: ApmBro.logger.logs
136
140
  }
137
-
141
+
138
142
  event_name = exception&.class&.name || "ActiveJob::Exception"
139
143
  client.post_metric(event_name: event_name, payload: payload, error: true)
140
144
  end
141
- rescue StandardError
145
+ rescue
142
146
  # Never raise from instrumentation install
143
147
  end
144
148
 
@@ -146,27 +150,31 @@ module ApmBro
146
150
 
147
151
  def self.safe_arguments(arguments)
148
152
  return [] unless arguments.is_a?(Array)
149
-
153
+
150
154
  # Limit and sanitize job arguments
151
155
  arguments.first(10).map do |arg|
152
156
  case arg
153
157
  when String
154
- arg.length > 200 ? arg[0, 200] + "..." : arg
158
+ (arg.length > 200) ? arg[0, 200] + "..." : arg
155
159
  when Hash
156
160
  # Filter sensitive keys and limit size
157
161
  filtered = arg.except(*%w[password token secret key])
158
- filtered.keys.size > 20 ? filtered.first(20).to_h : filtered
162
+ (filtered.keys.size > 20) ? filtered.first(20).to_h : filtered
159
163
  when Array
160
164
  arg.first(5)
161
165
  when ActiveRecord::Base
162
166
  # Handle ActiveRecord objects safely
163
- "#{arg.class.name}##{arg.id rescue 'unknown'}"
167
+ "#{arg.class.name}##{begin
168
+ arg.id
169
+ rescue
170
+ "unknown"
171
+ end}"
164
172
  else
165
173
  # Convert to string and truncate, but avoid object inspection
166
- arg.to_s.length > 200 ? arg.to_s[0, 200] + "..." : arg.to_s
174
+ (arg.to_s.length > 200) ? arg.to_s[0, 200] + "..." : arg.to_s
167
175
  end
168
176
  end
169
- rescue StandardError
177
+ rescue
170
178
  []
171
179
  end
172
180
 
@@ -176,13 +184,17 @@ module ApmBro
176
184
  else
177
185
  ENV["RACK_ENV"] || ENV["RAILS_ENV"] || "development"
178
186
  end
179
- rescue StandardError
187
+ rescue
180
188
  "development"
181
189
  end
182
190
 
183
191
  def self.safe_host
184
192
  if defined?(Rails) && Rails.respond_to?(:application)
185
- Rails.application.class.module_parent_name rescue ""
193
+ begin
194
+ Rails.application.class.module_parent_name
195
+ rescue
196
+ ""
197
+ end
186
198
  else
187
199
  ""
188
200
  end
@@ -191,12 +203,16 @@ module ApmBro
191
203
  def self.memory_usage_mb
192
204
  if defined?(GC) && GC.respond_to?(:stat)
193
205
  # Get memory usage in MB
194
- memory_kb = `ps -o rss= -p #{Process.pid}`.to_i rescue 0
206
+ memory_kb = begin
207
+ `ps -o rss= -p #{Process.pid}`.to_i
208
+ rescue
209
+ 0
210
+ end
195
211
  (memory_kb / 1024.0).round(2)
196
212
  else
197
213
  0
198
214
  end
199
- rescue StandardError
215
+ rescue
200
216
  0
201
217
  end
202
218
 
@@ -212,7 +228,7 @@ module ApmBro
212
228
  else
213
229
  {}
214
230
  end
215
- rescue StandardError
231
+ rescue
216
232
  {}
217
233
  end
218
234
  end
@@ -7,7 +7,7 @@ module ApmBro
7
7
 
8
8
  def self.start_request_tracking
9
9
  return unless ApmBro.configuration.memory_tracking_enabled
10
-
10
+
11
11
  # Only track essential metrics to minimize overhead
12
12
  Thread.current[THREAD_LOCAL_KEY] = {
13
13
  gc_before: lightweight_gc_stats,
@@ -19,13 +19,13 @@ module ApmBro
19
19
  def self.stop_request_tracking
20
20
  events = Thread.current[THREAD_LOCAL_KEY]
21
21
  Thread.current[THREAD_LOCAL_KEY] = nil
22
-
22
+
23
23
  return {} unless events
24
-
24
+
25
25
  # Calculate only essential metrics
26
26
  gc_after = lightweight_gc_stats
27
27
  memory_after = lightweight_memory_usage
28
-
28
+
29
29
  {
30
30
  memory_growth_mb: (memory_after - events[:memory_before]).round(2),
31
31
  gc_count_increase: (gc_after[:count] || 0) - (events[:gc_before][:count] || 0),
@@ -39,24 +39,24 @@ module ApmBro
39
39
  def self.lightweight_memory_usage
40
40
  # Use only GC stats for memory estimation (no system calls)
41
41
  return 0 unless defined?(GC) && GC.respond_to?(:stat)
42
-
42
+
43
43
  gc_stats = GC.stat
44
44
  heap_pages = gc_stats[:heap_allocated_pages] || 0
45
45
  # Rough estimation: 4KB per page
46
46
  (heap_pages * 4) / 1024.0 # Convert to MB
47
- rescue StandardError
47
+ rescue
48
48
  0
49
49
  end
50
50
 
51
51
  def self.lightweight_gc_stats
52
52
  return {} unless defined?(GC) && GC.respond_to?(:stat)
53
-
53
+
54
54
  stats = GC.stat
55
55
  {
56
56
  count: stats[:count] || 0,
57
57
  heap_allocated_pages: stats[:heap_allocated_pages] || 0
58
58
  }
59
- rescue StandardError
59
+ rescue
60
60
  {}
61
61
  end
62
62
  end
@@ -11,12 +11,12 @@ module ApmBro
11
11
  }.freeze
12
12
 
13
13
  # ANSI color codes
14
- COLOR_RESET = "\033[0m".freeze
15
- COLOR_DEBUG = "\033[36m".freeze # Cyan
16
- COLOR_INFO = "\033[32m".freeze # Green
17
- COLOR_WARN = "\033[33m".freeze # Yellow
18
- COLOR_ERROR = "\033[31m".freeze # Red
19
- COLOR_FATAL = "\033[35m".freeze # Magenta
14
+ COLOR_RESET = "\033[0m"
15
+ COLOR_DEBUG = "\033[36m" # Cyan
16
+ COLOR_INFO = "\033[32m" # Green
17
+ COLOR_WARN = "\033[33m" # Yellow
18
+ COLOR_ERROR = "\033[31m" # Red
19
+ COLOR_FATAL = "\033[35m" # Magenta
20
20
 
21
21
  def initialize
22
22
  @thread_logs_key = :apm_bro_logs
@@ -72,7 +72,7 @@ module ApmBro
72
72
 
73
73
  def print_log(severity, message, timestamp)
74
74
  formatted_message = format_log_message(severity, message, timestamp)
75
-
75
+
76
76
  if defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger
77
77
  # Use Rails logger if available (Rails handles its own color formatting)
78
78
  case severity
@@ -92,7 +92,7 @@ module ApmBro
92
92
  colored_message = format_log_message_with_color(severity, message, timestamp)
93
93
  $stdout.puts(colored_message)
94
94
  end
95
- rescue StandardError
95
+ rescue
96
96
  # Never let logging break the application
97
97
  $stdout.puts("[ApmBro] #{severity.to_s.upcase}: #{message}")
98
98
  end
@@ -125,4 +125,3 @@ module ApmBro
125
125
  end
126
126
  end
127
127
  end
128
-
@@ -3,17 +3,17 @@
3
3
  module ApmBro
4
4
  module MemoryHelpers
5
5
  # Helper methods for memory tracking and leak detection
6
-
6
+
7
7
  # Take a memory snapshot with a custom label
8
8
  def self.snapshot(label)
9
9
  ApmBro::MemoryTrackingSubscriber.take_memory_snapshot(label)
10
10
  end
11
-
11
+
12
12
  # Get current memory analysis
13
13
  def self.analyze_memory
14
14
  ApmBro::MemoryLeakDetector.get_memory_analysis
15
15
  end
16
-
16
+
17
17
  # Check for memory leaks
18
18
  def self.check_for_leaks
19
19
  analysis = analyze_memory
@@ -23,26 +23,26 @@ module ApmBro
23
23
  puts " - Growth: #{alert[:memory_growth_mb]}MB"
24
24
  puts " - Rate: #{alert[:growth_rate_mb_per_second]}MB/sec"
25
25
  puts " - Confidence: #{(alert[:confidence] * 100).round(1)}%"
26
- puts " - Recent controllers: #{alert[:recent_controllers].join(', ')}"
26
+ puts " - Recent controllers: #{alert[:recent_controllers].join(", ")}"
27
27
  end
28
28
  else
29
29
  puts "✅ No memory leaks detected"
30
30
  end
31
31
  analysis
32
32
  end
33
-
33
+
34
34
  # Get memory usage summary
35
35
  def self.memory_summary
36
36
  analysis = analyze_memory
37
37
  return "Insufficient data" if analysis[:status] == "insufficient_data"
38
-
38
+
39
39
  memory_stats = analysis[:memory_stats]
40
40
  puts "📊 Memory Summary:"
41
41
  puts " - Current: #{memory_stats[:mean]}MB (avg)"
42
42
  puts " - Range: #{memory_stats[:min]}MB - #{memory_stats[:max]}MB"
43
43
  puts " - Volatility: #{memory_stats[:std_dev]}MB"
44
44
  puts " - Samples: #{analysis[:sample_count]}"
45
-
45
+
46
46
  if analysis[:memory_trend][:slope] > 0
47
47
  puts " - Trend: ↗️ Growing at #{analysis[:memory_trend][:slope].round(3)}MB/sec"
48
48
  elsif analysis[:memory_trend][:slope] < 0
@@ -50,16 +50,16 @@ module ApmBro
50
50
  else
51
51
  puts " - Trend: ➡️ Stable"
52
52
  end
53
-
53
+
54
54
  analysis
55
55
  end
56
-
56
+
57
57
  # Monitor memory during a block execution
58
58
  def self.monitor_memory(label, &block)
59
59
  snapshot("before_#{label}")
60
60
  result = yield
61
61
  snapshot("after_#{label}")
62
-
62
+
63
63
  # Get the difference
64
64
  analysis = analyze_memory
65
65
  if analysis[:memory_stats]
@@ -67,15 +67,15 @@ module ApmBro
67
67
  puts " - Memory change: #{analysis[:memory_stats][:max] - analysis[:memory_stats][:min]}MB"
68
68
  puts " - Peak usage: #{analysis[:memory_stats][:max]}MB"
69
69
  end
70
-
70
+
71
71
  result
72
72
  end
73
-
73
+
74
74
  # Clear memory history (useful for testing)
75
75
  def self.clear_history
76
76
  ApmBro::MemoryLeakDetector.clear_history
77
77
  end
78
-
78
+
79
79
  # Get top memory allocating classes
80
80
  def self.top_allocators
81
81
  # This would need to be called from within a request context
@@ -18,7 +18,7 @@ module ApmBro
18
18
 
19
19
  def self.record_memory_sample(sample_data)
20
20
  history = Thread.current[MEMORY_HISTORY_KEY] || initialize_history
21
-
21
+
22
22
  sample = {
23
23
  timestamp: Time.now.utc.to_i,
24
24
  memory_usage: sample_data[:memory_usage] || 0,
@@ -29,15 +29,15 @@ module ApmBro
29
29
  controller: sample_data[:controller],
30
30
  action: sample_data[:action]
31
31
  }
32
-
32
+
33
33
  history[:samples] << sample
34
-
34
+
35
35
  # Clean up old samples
36
36
  cleanup_old_samples(history)
37
-
37
+
38
38
  # Check for memory leaks
39
39
  check_for_memory_leaks(history)
40
-
40
+
41
41
  history
42
42
  end
43
43
 
@@ -49,18 +49,18 @@ module ApmBro
49
49
  def self.check_for_memory_leaks(history)
50
50
  samples = history[:samples]
51
51
  return if samples.length < MIN_SAMPLES_FOR_LEAK_DETECTION
52
-
52
+
53
53
  # Calculate memory growth trend
54
54
  memory_values = samples.map { |s| s[:memory_usage] }
55
55
  timestamps = samples.map { |s| s[:timestamp] }
56
-
56
+
57
57
  # Use linear regression to detect upward trend
58
58
  trend = calculate_memory_trend(memory_values, timestamps)
59
-
59
+
60
60
  # Check if memory is growing consistently
61
61
  if trend[:slope] > 0.1 && trend[:r_squared] > 0.7 # Growing with good correlation
62
62
  memory_growth = memory_values.last - memory_values.first
63
-
63
+
64
64
  if memory_growth > MEMORY_GROWTH_THRESHOLD
65
65
  leak_alert = {
66
66
  detected_at: Time.now.utc.to_i,
@@ -71,9 +71,9 @@ module ApmBro
71
71
  time_window_seconds: timestamps.last - timestamps.first,
72
72
  recent_controllers: samples.last(5).map { |s| "#{s[:controller]}##{s[:action]}" }.uniq
73
73
  }
74
-
74
+
75
75
  history[:leak_alerts] << leak_alert
76
-
76
+
77
77
  # Only keep recent leak alerts
78
78
  history[:leak_alerts] = history[:leak_alerts].last(10)
79
79
  end
@@ -81,25 +81,25 @@ module ApmBro
81
81
  end
82
82
 
83
83
  def self.calculate_memory_trend(memory_values, timestamps)
84
- return { slope: 0, r_squared: 0 } if memory_values.length < 2
85
-
84
+ return {slope: 0, r_squared: 0} if memory_values.length < 2
85
+
86
86
  n = memory_values.length
87
87
  sum_x = timestamps.sum
88
88
  sum_y = memory_values.sum
89
89
  sum_xy = timestamps.zip(memory_values).sum { |x, y| x * y }
90
90
  sum_x2 = timestamps.sum { |x| x * x }
91
- sum_y2 = memory_values.sum { |y| y * y }
92
-
91
+ memory_values.sum { |y| y * y }
92
+
93
93
  # Calculate slope (m) and intercept (b) for y = mx + b
94
94
  slope = (n * sum_xy - sum_x * sum_y).to_f / (n * sum_x2 - sum_x * sum_x)
95
95
  intercept = (sum_y - slope * sum_x).to_f / n
96
-
96
+
97
97
  # Calculate R-squared (coefficient of determination)
98
98
  y_mean = sum_y.to_f / n
99
- ss_tot = memory_values.sum { |y| (y - y_mean) ** 2 }
100
- ss_res = memory_values.zip(timestamps).sum { |y, x| (y - (slope * x + intercept)) ** 2 }
101
- r_squared = ss_tot > 0 ? 1 - (ss_res / ss_tot) : 0
102
-
99
+ ss_tot = memory_values.sum { |y| (y - y_mean)**2 }
100
+ ss_res = memory_values.zip(timestamps).sum { |y, x| (y - (slope * x + intercept))**2 }
101
+ r_squared = (ss_tot > 0) ? 1 - (ss_res / ss_tot) : 0
102
+
103
103
  {
104
104
  slope: slope,
105
105
  intercept: intercept,
@@ -110,25 +110,25 @@ module ApmBro
110
110
  def self.get_memory_analysis
111
111
  history = Thread.current[MEMORY_HISTORY_KEY] || initialize_history
112
112
  samples = history[:samples]
113
-
114
- return { status: "insufficient_data", sample_count: samples.length } if samples.length < 5
115
-
113
+
114
+ return {status: "insufficient_data", sample_count: samples.length} if samples.length < 5
115
+
116
116
  memory_values = samples.map { |s| s[:memory_usage] }
117
117
  gc_counts = samples.map { |s| s[:gc_count] }
118
118
  object_counts = samples.map { |s| s[:object_count] }
119
-
119
+
120
120
  # Calculate basic statistics
121
121
  memory_stats = calculate_stats(memory_values)
122
122
  gc_stats = calculate_stats(gc_counts)
123
123
  object_stats = calculate_stats(object_counts)
124
-
124
+
125
125
  # Detect patterns
126
126
  memory_trend = calculate_memory_trend(memory_values, samples.map { |s| s[:timestamp] })
127
-
127
+
128
128
  # Analyze recent activity
129
129
  recent_samples = samples.last(10)
130
130
  recent_controllers = recent_samples.map { |s| "#{s[:controller]}##{s[:action]}" }.tally
131
-
131
+
132
132
  {
133
133
  status: "analyzed",
134
134
  sample_count: samples.length,
@@ -145,7 +145,7 @@ module ApmBro
145
145
 
146
146
  def self.calculate_stats(values)
147
147
  return {} if values.empty?
148
-
148
+
149
149
  {
150
150
  min: values.min,
151
151
  max: values.max,
@@ -157,31 +157,31 @@ module ApmBro
157
157
 
158
158
  def self.calculate_standard_deviation(values)
159
159
  return 0 if values.length < 2
160
-
160
+
161
161
  mean = values.sum.to_f / values.length
162
- variance = values.sum { |v| (v - mean) ** 2 } / (values.length - 1)
162
+ variance = values.sum { |v| (v - mean)**2 } / (values.length - 1)
163
163
  Math.sqrt(variance).round(2)
164
164
  end
165
165
 
166
166
  def self.calculate_memory_efficiency(samples)
167
167
  return {} if samples.length < 2
168
-
168
+
169
169
  # Calculate memory per object ratio
170
170
  memory_per_object = samples.map do |sample|
171
- sample[:object_count] > 0 ? sample[:memory_usage] / sample[:object_count] : 0
171
+ (sample[:object_count] > 0) ? sample[:memory_usage] / sample[:object_count] : 0
172
172
  end
173
-
173
+
174
174
  # Calculate GC efficiency (objects collected per GC cycle)
175
175
  gc_efficiency = []
176
176
  (1...samples.length).each do |i|
177
- gc_delta = samples[i][:gc_count] - samples[i-1][:gc_count]
178
- memory_delta = samples[i][:memory_usage] - samples[i-1][:memory_usage]
179
-
177
+ gc_delta = samples[i][:gc_count] - samples[i - 1][:gc_count]
178
+ memory_delta = samples[i][:memory_usage] - samples[i - 1][:memory_usage]
179
+
180
180
  if gc_delta > 0 && memory_delta < 0
181
181
  gc_efficiency << (-memory_delta / gc_delta).round(2)
182
182
  end
183
183
  end
184
-
184
+
185
185
  {
186
186
  average_memory_per_object_kb: (memory_per_object.sum / memory_per_object.length).round(2),
187
187
  gc_efficiency_mb_per_cycle: gc_efficiency.any? ? (gc_efficiency.sum / gc_efficiency.length).round(2) : 0,