apm_bro 0.1.14 → 0.1.16
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.
- checksums.yaml +4 -4
- data/README.md +48 -35
- data/lib/apm_bro/cache_subscriber.rb +15 -25
- data/lib/apm_bro/circuit_breaker.rb +9 -17
- data/lib/apm_bro/client.rb +28 -36
- data/lib/apm_bro/configuration.rb +87 -65
- data/lib/apm_bro/error_middleware.rb +16 -30
- data/lib/apm_bro/http_instrumentation.rb +22 -16
- data/lib/apm_bro/job_sql_tracking_middleware.rb +1 -1
- data/lib/apm_bro/job_subscriber.rb +41 -25
- data/lib/apm_bro/lightweight_memory_tracker.rb +8 -8
- data/lib/apm_bro/logger.rb +8 -9
- data/lib/apm_bro/memory_helpers.rb +13 -13
- data/lib/apm_bro/memory_leak_detector.rb +37 -37
- data/lib/apm_bro/memory_tracking_subscriber.rb +62 -54
- data/lib/apm_bro/railtie.rb +28 -30
- data/lib/apm_bro/redis_subscriber.rb +34 -37
- data/lib/apm_bro/sql_subscriber.rb +308 -60
- data/lib/apm_bro/sql_tracking_middleware.rb +11 -11
- data/lib/apm_bro/subscriber.rb +53 -39
- data/lib/apm_bro/version.rb +1 -1
- data/lib/apm_bro/view_rendering_subscriber.rb +23 -23
- data/lib/apm_bro.rb +9 -0
- metadata +4 -4
|
@@ -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 {
|
|
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
|
-
|
|
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)
|
|
100
|
-
ss_res = memory_values.zip(timestamps).sum { |y, x| (y - (slope * x + intercept))
|
|
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 {
|
|
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)
|
|
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,
|
|
@@ -5,12 +5,12 @@ require "active_support/notifications"
|
|
|
5
5
|
module ApmBro
|
|
6
6
|
class MemoryTrackingSubscriber
|
|
7
7
|
# Object allocation events
|
|
8
|
-
ALLOCATION_EVENT = "object_allocations.active_support"
|
|
9
|
-
|
|
8
|
+
ALLOCATION_EVENT = "object_allocations.active_support"
|
|
9
|
+
|
|
10
10
|
THREAD_LOCAL_KEY = :apm_bro_memory_events
|
|
11
11
|
# Consider objects larger than this many bytes as "large"
|
|
12
12
|
LARGE_OBJECT_THRESHOLD = 1_000_000 # 1MB threshold for large objects
|
|
13
|
-
|
|
13
|
+
|
|
14
14
|
# Performance optimization settings
|
|
15
15
|
ALLOCATION_SAMPLING_RATE = 1 # Track all when enabled (adjust in production)
|
|
16
16
|
MAX_ALLOCATIONS_PER_REQUEST = 1000 # Limit allocations tracked per request
|
|
@@ -28,18 +28,18 @@ module ApmBro
|
|
|
28
28
|
next unless rand < ALLOCATION_SAMPLING_RATE
|
|
29
29
|
track_allocation(data, started, finished)
|
|
30
30
|
end
|
|
31
|
-
rescue
|
|
31
|
+
rescue
|
|
32
32
|
# Allocation tracking might not be available in all Ruby versions
|
|
33
33
|
end
|
|
34
34
|
end
|
|
35
|
-
rescue
|
|
35
|
+
rescue
|
|
36
36
|
# Never raise from instrumentation install
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
def self.start_request_tracking
|
|
40
40
|
# Only track if memory tracking is enabled
|
|
41
41
|
return unless ApmBro.configuration.memory_tracking_enabled
|
|
42
|
-
|
|
42
|
+
|
|
43
43
|
Thread.current[THREAD_LOCAL_KEY] = {
|
|
44
44
|
allocations: [],
|
|
45
45
|
memory_snapshots: [],
|
|
@@ -54,7 +54,7 @@ module ApmBro
|
|
|
54
54
|
def self.stop_request_tracking
|
|
55
55
|
events = Thread.current[THREAD_LOCAL_KEY]
|
|
56
56
|
Thread.current[THREAD_LOCAL_KEY] = nil
|
|
57
|
-
|
|
57
|
+
|
|
58
58
|
if events
|
|
59
59
|
events[:gc_after] = gc_stats
|
|
60
60
|
events[:memory_after] = memory_usage_mb
|
|
@@ -67,20 +67,20 @@ module ApmBro
|
|
|
67
67
|
events[:large_objects] = sample_large_objects
|
|
68
68
|
end
|
|
69
69
|
end
|
|
70
|
-
|
|
70
|
+
|
|
71
71
|
events || {}
|
|
72
72
|
end
|
|
73
73
|
|
|
74
74
|
def self.track_allocation(data, started, finished)
|
|
75
75
|
return unless Thread.current[THREAD_LOCAL_KEY]
|
|
76
|
-
|
|
76
|
+
|
|
77
77
|
# Only track if we have meaningful allocation data
|
|
78
78
|
return unless data.is_a?(Hash) && data[:count] && data[:size]
|
|
79
|
-
|
|
79
|
+
|
|
80
80
|
# Limit allocations per request to prevent memory bloat
|
|
81
81
|
allocations = Thread.current[THREAD_LOCAL_KEY][:allocations]
|
|
82
82
|
return if allocations.length >= MAX_ALLOCATIONS_PER_REQUEST
|
|
83
|
-
|
|
83
|
+
|
|
84
84
|
# Simplified allocation tracking (avoid expensive operations)
|
|
85
85
|
allocation = {
|
|
86
86
|
class_name: data[:class_name] || "Unknown",
|
|
@@ -88,7 +88,7 @@ module ApmBro
|
|
|
88
88
|
size: data[:size]
|
|
89
89
|
# Removed expensive fields: duration_ms, timestamp, memory_usage
|
|
90
90
|
}
|
|
91
|
-
|
|
91
|
+
|
|
92
92
|
# Track large object allocations (these are rare and important)
|
|
93
93
|
if data[:size] > LARGE_OBJECT_THRESHOLD
|
|
94
94
|
large_object = allocation.merge(
|
|
@@ -97,13 +97,13 @@ module ApmBro
|
|
|
97
97
|
)
|
|
98
98
|
Thread.current[THREAD_LOCAL_KEY][:large_objects] << large_object
|
|
99
99
|
end
|
|
100
|
-
|
|
100
|
+
|
|
101
101
|
Thread.current[THREAD_LOCAL_KEY][:allocations] << allocation
|
|
102
102
|
end
|
|
103
103
|
|
|
104
104
|
def self.take_memory_snapshot(label = nil)
|
|
105
105
|
return unless Thread.current[THREAD_LOCAL_KEY]
|
|
106
|
-
|
|
106
|
+
|
|
107
107
|
snapshot = {
|
|
108
108
|
label: label || "snapshot_#{Time.now.to_i}",
|
|
109
109
|
memory_usage: memory_usage_mb,
|
|
@@ -112,45 +112,45 @@ module ApmBro
|
|
|
112
112
|
object_count: object_count,
|
|
113
113
|
heap_pages: heap_pages
|
|
114
114
|
}
|
|
115
|
-
|
|
115
|
+
|
|
116
116
|
Thread.current[THREAD_LOCAL_KEY][:memory_snapshots] << snapshot
|
|
117
117
|
end
|
|
118
118
|
|
|
119
119
|
def self.analyze_memory_performance(memory_events)
|
|
120
120
|
return {} if memory_events.empty?
|
|
121
|
-
|
|
121
|
+
|
|
122
122
|
allocations = memory_events[:allocations] || []
|
|
123
123
|
large_objects = memory_events[:large_objects] || []
|
|
124
124
|
snapshots = memory_events[:memory_snapshots] || []
|
|
125
|
-
|
|
125
|
+
|
|
126
126
|
# Calculate memory growth
|
|
127
127
|
memory_growth = 0
|
|
128
128
|
if memory_events[:memory_before] && memory_events[:memory_after]
|
|
129
129
|
memory_growth = memory_events[:memory_after] - memory_events[:memory_before]
|
|
130
130
|
end
|
|
131
|
-
|
|
131
|
+
|
|
132
132
|
# Calculate allocation totals
|
|
133
133
|
total_allocations = allocations.sum { |a| a[:count] }
|
|
134
134
|
total_allocated_size = allocations.sum { |a| a[:size] }
|
|
135
|
-
|
|
135
|
+
|
|
136
136
|
# Group allocations by class
|
|
137
137
|
allocations_by_class = allocations.group_by { |a| a[:class_name] }
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
138
|
+
.transform_values { |allocs|
|
|
139
|
+
{
|
|
140
|
+
count: allocs.sum { |a| a[:count] },
|
|
141
|
+
size: allocs.sum { |a| a[:size] }
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
145
|
# Find top allocating classes
|
|
146
146
|
top_allocating_classes = allocations_by_class.sort_by { |_, data| -data[:size] }.first(10)
|
|
147
|
-
|
|
147
|
+
|
|
148
148
|
# Analyze large objects
|
|
149
149
|
large_object_analysis = analyze_large_objects(large_objects)
|
|
150
|
-
|
|
150
|
+
|
|
151
151
|
# Analyze memory snapshots for trends
|
|
152
152
|
memory_trends = analyze_memory_trends(snapshots)
|
|
153
|
-
|
|
153
|
+
|
|
154
154
|
# Calculate GC efficiency
|
|
155
155
|
gc_efficiency = calculate_gc_efficiency(memory_events[:gc_before], memory_events[:gc_after])
|
|
156
156
|
|
|
@@ -164,13 +164,13 @@ module ApmBro
|
|
|
164
164
|
object_type_deltas[k] = (after[k] || 0) - (before[k] || 0)
|
|
165
165
|
end
|
|
166
166
|
end
|
|
167
|
-
|
|
167
|
+
|
|
168
168
|
{
|
|
169
169
|
memory_growth_mb: memory_growth.round(2),
|
|
170
170
|
total_allocations: total_allocations,
|
|
171
171
|
total_allocated_size: total_allocated_size,
|
|
172
172
|
total_allocated_size_mb: (total_allocated_size / 1_000_000.0).round(2),
|
|
173
|
-
allocations_per_second: memory_events[:duration_seconds] > 0 ?
|
|
173
|
+
allocations_per_second: (memory_events[:duration_seconds] > 0) ?
|
|
174
174
|
(total_allocations.to_f / memory_events[:duration_seconds]).round(2) : 0,
|
|
175
175
|
top_allocating_classes: top_allocating_classes.map { |class_name, data|
|
|
176
176
|
{
|
|
@@ -190,13 +190,13 @@ module ApmBro
|
|
|
190
190
|
|
|
191
191
|
def self.analyze_large_objects(large_objects)
|
|
192
192
|
return {} if large_objects.empty?
|
|
193
|
-
|
|
193
|
+
|
|
194
194
|
{
|
|
195
195
|
count: large_objects.count,
|
|
196
196
|
total_size_mb: large_objects.sum { |obj| obj[:size_mb] }.round(2),
|
|
197
197
|
largest_object_mb: large_objects.max_by { |obj| obj[:size_mb] }[:size_mb],
|
|
198
198
|
by_class: large_objects.group_by { |obj| obj[:class_name] }
|
|
199
|
-
|
|
199
|
+
.transform_values(&:count)
|
|
200
200
|
}
|
|
201
201
|
end
|
|
202
202
|
|
|
@@ -219,15 +219,23 @@ module ApmBro
|
|
|
219
219
|
# Randomly sample to control overhead
|
|
220
220
|
next unless rand < LARGE_OBJECT_SAMPLE_RATE
|
|
221
221
|
|
|
222
|
-
size =
|
|
222
|
+
size = begin
|
|
223
|
+
ObjectSpace.memsize_of(obj)
|
|
224
|
+
rescue
|
|
225
|
+
0
|
|
226
|
+
end
|
|
223
227
|
next unless size && size > LARGE_OBJECT_THRESHOLD
|
|
224
228
|
|
|
225
|
-
klass =
|
|
226
|
-
|
|
229
|
+
klass = begin
|
|
230
|
+
(obj.respond_to?(:class) && obj.class) ? obj.class.name : "Unknown"
|
|
231
|
+
rescue
|
|
232
|
+
"Unknown"
|
|
233
|
+
end
|
|
234
|
+
results << {class_name: klass, size: size, size_mb: (size / 1_000_000.0).round(2)}
|
|
227
235
|
|
|
228
236
|
break if results.length >= MAX_LARGE_OBJECTS
|
|
229
237
|
end
|
|
230
|
-
rescue
|
|
238
|
+
rescue
|
|
231
239
|
# Best-effort only
|
|
232
240
|
end
|
|
233
241
|
|
|
@@ -241,24 +249,24 @@ module ApmBro
|
|
|
241
249
|
else
|
|
242
250
|
{}
|
|
243
251
|
end
|
|
244
|
-
rescue
|
|
252
|
+
rescue
|
|
245
253
|
{}
|
|
246
254
|
end
|
|
247
255
|
|
|
248
256
|
def self.analyze_memory_trends(snapshots)
|
|
249
257
|
return {} if snapshots.length < 2
|
|
250
|
-
|
|
258
|
+
|
|
251
259
|
# Calculate memory growth rate between snapshots
|
|
252
260
|
memory_values = snapshots.map { |s| s[:memory_usage] }
|
|
253
261
|
memory_growth_rates = []
|
|
254
|
-
|
|
262
|
+
|
|
255
263
|
(1...memory_values.length).each do |i|
|
|
256
|
-
growth = memory_values[i] - memory_values[i-1]
|
|
257
|
-
time_diff = snapshots[i][:timestamp] - snapshots[i-1][:timestamp]
|
|
258
|
-
rate = time_diff > 0 ? growth / time_diff : 0
|
|
264
|
+
growth = memory_values[i] - memory_values[i - 1]
|
|
265
|
+
time_diff = snapshots[i][:timestamp] - snapshots[i - 1][:timestamp]
|
|
266
|
+
rate = (time_diff > 0) ? growth / time_diff : 0
|
|
259
267
|
memory_growth_rates << rate
|
|
260
268
|
end
|
|
261
|
-
|
|
269
|
+
|
|
262
270
|
{
|
|
263
271
|
average_growth_rate_mb_per_second: memory_growth_rates.sum / memory_growth_rates.length,
|
|
264
272
|
max_growth_rate_mb_per_second: memory_growth_rates.max,
|
|
@@ -270,12 +278,12 @@ module ApmBro
|
|
|
270
278
|
|
|
271
279
|
def self.calculate_gc_efficiency(gc_before, gc_after)
|
|
272
280
|
return {} unless gc_before && gc_after
|
|
273
|
-
|
|
281
|
+
|
|
274
282
|
{
|
|
275
283
|
gc_count_increase: (gc_after[:count] || 0) - (gc_before[:count] || 0),
|
|
276
284
|
heap_pages_increase: (gc_after[:heap_allocated_pages] || 0) - (gc_before[:heap_allocated_pages] || 0),
|
|
277
285
|
objects_allocated: (gc_after[:total_allocated_objects] || 0) - (gc_before[:total_allocated_objects] || 0),
|
|
278
|
-
gc_frequency: gc_after[:count] && gc_before[:count] ?
|
|
286
|
+
gc_frequency: (gc_after[:count] && gc_before[:count]) ?
|
|
279
287
|
(gc_after[:count] - gc_before[:count]).to_f / [gc_after[:count], 1].max : 0
|
|
280
288
|
}
|
|
281
289
|
end
|
|
@@ -284,12 +292,12 @@ module ApmBro
|
|
|
284
292
|
# Use cached memory calculation to avoid expensive system calls
|
|
285
293
|
@memory_cache ||= {}
|
|
286
294
|
cache_key = Process.pid
|
|
287
|
-
|
|
295
|
+
|
|
288
296
|
# Cache memory usage for 1 second to avoid repeated system calls
|
|
289
297
|
if @memory_cache[cache_key] && (Time.now - @memory_cache[cache_key][:timestamp]) < 1
|
|
290
298
|
return @memory_cache[cache_key][:memory]
|
|
291
299
|
end
|
|
292
|
-
|
|
300
|
+
|
|
293
301
|
memory = if defined?(GC) && GC.respond_to?(:stat)
|
|
294
302
|
# Use GC stats as a proxy for memory usage (much faster than ps)
|
|
295
303
|
gc_stats = GC.stat
|
|
@@ -299,10 +307,10 @@ module ApmBro
|
|
|
299
307
|
else
|
|
300
308
|
0
|
|
301
309
|
end
|
|
302
|
-
|
|
303
|
-
@memory_cache[cache_key] = {
|
|
310
|
+
|
|
311
|
+
@memory_cache[cache_key] = {memory: memory, timestamp: Time.now}
|
|
304
312
|
memory
|
|
305
|
-
rescue
|
|
313
|
+
rescue
|
|
306
314
|
0
|
|
307
315
|
end
|
|
308
316
|
|
|
@@ -321,7 +329,7 @@ module ApmBro
|
|
|
321
329
|
else
|
|
322
330
|
{}
|
|
323
331
|
end
|
|
324
|
-
rescue
|
|
332
|
+
rescue
|
|
325
333
|
{}
|
|
326
334
|
end
|
|
327
335
|
|
|
@@ -331,7 +339,7 @@ module ApmBro
|
|
|
331
339
|
else
|
|
332
340
|
0
|
|
333
341
|
end
|
|
334
|
-
rescue
|
|
342
|
+
rescue
|
|
335
343
|
0
|
|
336
344
|
end
|
|
337
345
|
|
|
@@ -341,7 +349,7 @@ module ApmBro
|
|
|
341
349
|
else
|
|
342
350
|
0
|
|
343
351
|
end
|
|
344
|
-
rescue
|
|
352
|
+
rescue
|
|
345
353
|
0
|
|
346
354
|
end
|
|
347
355
|
|
data/lib/apm_bro/railtie.rb
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
begin
|
|
4
|
+
require "rails/railtie"
|
|
5
|
+
rescue LoadError
|
|
6
|
+
# Rails not available, skip railtie definition
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
# Only define Railtie if Rails is available
|
|
10
|
+
if defined?(Rails) && defined?(Rails::Railtie)
|
|
11
|
+
module ApmBro
|
|
12
|
+
class Railtie < ::Rails::Railtie
|
|
13
|
+
initializer "apm_bro.configure" do |_app|
|
|
14
|
+
# Allow host app to set config in Rails config, credentials, or ENV.
|
|
15
|
+
# If host app sets config.x.apm_bro, mirror into gem configuration.
|
|
4
16
|
|
|
5
|
-
module ApmBro
|
|
6
|
-
class Railtie < ::Rails::Railtie
|
|
7
|
-
initializer "apm_bro.configure" do |_app|
|
|
8
|
-
# Allow host app to set config in Rails config, credentials, or ENV.
|
|
9
|
-
# If host app sets config.x.apm_bro, mirror into gem configuration.
|
|
10
|
-
begin
|
|
11
17
|
if Rails.application.config.x.respond_to?(:apm_bro)
|
|
12
18
|
xcfg = Rails.application.config.x.apm_bro
|
|
13
19
|
ApmBro.configure do |cfg|
|
|
@@ -15,22 +21,20 @@ module ApmBro
|
|
|
15
21
|
cfg.enabled = xcfg.enabled if xcfg.respond_to?(:enabled)
|
|
16
22
|
end
|
|
17
23
|
end
|
|
18
|
-
rescue
|
|
24
|
+
rescue
|
|
19
25
|
end
|
|
20
|
-
end
|
|
21
26
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
begin
|
|
27
|
+
initializer "apm_bro.subscribe" do |app|
|
|
28
|
+
app.config.after_initialize do
|
|
25
29
|
ApmBro::Subscriber.subscribe!(client: ApmBro::Client.new)
|
|
26
30
|
# Install outgoing HTTP instrumentation
|
|
27
31
|
require "apm_bro/http_instrumentation"
|
|
28
32
|
ApmBro::HttpInstrumentation.install!(client: ApmBro::Client.new)
|
|
29
|
-
|
|
33
|
+
|
|
30
34
|
# Install SQL query tracking
|
|
31
35
|
require "apm_bro/sql_subscriber"
|
|
32
36
|
ApmBro::SqlSubscriber.subscribe!
|
|
33
|
-
|
|
37
|
+
|
|
34
38
|
# Install Rails cache tracking
|
|
35
39
|
require "apm_bro/cache_subscriber"
|
|
36
40
|
ApmBro::CacheSubscriber.subscribe!
|
|
@@ -42,18 +46,18 @@ module ApmBro
|
|
|
42
46
|
# Install view rendering tracking
|
|
43
47
|
require "apm_bro/view_rendering_subscriber"
|
|
44
48
|
ApmBro::ViewRenderingSubscriber.subscribe!(client: ApmBro::Client.new)
|
|
45
|
-
|
|
49
|
+
|
|
46
50
|
# Install lightweight memory tracking (default)
|
|
47
51
|
require "apm_bro/lightweight_memory_tracker"
|
|
48
52
|
require "apm_bro/memory_leak_detector"
|
|
49
53
|
ApmBro::MemoryLeakDetector.initialize_history
|
|
50
|
-
|
|
54
|
+
|
|
51
55
|
# Install detailed memory tracking only if enabled
|
|
52
56
|
if ApmBro.configuration.allocation_tracking_enabled
|
|
53
57
|
require "apm_bro/memory_tracking_subscriber"
|
|
54
58
|
ApmBro::MemoryTrackingSubscriber.subscribe!(client: ApmBro::Client.new)
|
|
55
59
|
end
|
|
56
|
-
|
|
60
|
+
|
|
57
61
|
# Install job tracking if ActiveJob is available
|
|
58
62
|
if defined?(ActiveJob)
|
|
59
63
|
require "apm_bro/job_subscriber"
|
|
@@ -61,15 +65,13 @@ module ApmBro
|
|
|
61
65
|
ApmBro::JobSqlTrackingMiddleware.subscribe!
|
|
62
66
|
ApmBro::JobSubscriber.subscribe!(client: ApmBro::Client.new)
|
|
63
67
|
end
|
|
64
|
-
rescue
|
|
68
|
+
rescue
|
|
65
69
|
# Never raise in Railtie init
|
|
66
70
|
end
|
|
67
71
|
end
|
|
68
|
-
end
|
|
69
72
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
begin
|
|
73
|
+
# Insert Rack middleware early enough to observe uncaught exceptions
|
|
74
|
+
initializer "apm_bro.middleware" do |app|
|
|
73
75
|
require "apm_bro/error_middleware"
|
|
74
76
|
|
|
75
77
|
if defined?(::ActionDispatch::DebugExceptions)
|
|
@@ -79,21 +81,17 @@ module ApmBro
|
|
|
79
81
|
else
|
|
80
82
|
app.config.middleware.use(::ApmBro::ErrorMiddleware)
|
|
81
83
|
end
|
|
82
|
-
rescue
|
|
84
|
+
rescue
|
|
83
85
|
# Never raise in Railtie init
|
|
84
86
|
end
|
|
85
|
-
end
|
|
86
87
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
begin
|
|
88
|
+
# Insert SQL tracking middleware
|
|
89
|
+
initializer "apm_bro.sql_tracking_middleware" do |app|
|
|
90
90
|
require "apm_bro/sql_tracking_middleware"
|
|
91
91
|
app.config.middleware.use(::ApmBro::SqlTrackingMiddleware)
|
|
92
|
-
rescue
|
|
92
|
+
rescue
|
|
93
93
|
# Never raise in Railtie init
|
|
94
94
|
end
|
|
95
95
|
end
|
|
96
96
|
end
|
|
97
97
|
end
|
|
98
|
-
|
|
99
|
-
|