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
|
@@ -4,7 +4,7 @@ require "rack"
|
|
|
4
4
|
|
|
5
5
|
module ApmBro
|
|
6
6
|
class ErrorMiddleware
|
|
7
|
-
EVENT_NAME = "exception.uncaught"
|
|
7
|
+
EVENT_NAME = "exception.uncaught"
|
|
8
8
|
|
|
9
9
|
def initialize(app, client: Client.new)
|
|
10
10
|
@app = app
|
|
@@ -20,7 +20,7 @@ module ApmBro
|
|
|
20
20
|
event_name = exception.class.name.to_s
|
|
21
21
|
event_name = EVENT_NAME if event_name.empty?
|
|
22
22
|
@client.post_metric(event_name: event_name, payload: payload)
|
|
23
|
-
rescue
|
|
23
|
+
rescue
|
|
24
24
|
# Never let APM reporting interfere with the host app
|
|
25
25
|
end
|
|
26
26
|
raise
|
|
@@ -36,14 +36,13 @@ module ApmBro
|
|
|
36
36
|
message: truncate(exception.message.to_s, 1000),
|
|
37
37
|
backtrace: safe_backtrace(exception),
|
|
38
38
|
occurred_at: Time.now.utc.to_i,
|
|
39
|
-
user_email: extract_user_email_from_env(env),
|
|
40
39
|
rack:
|
|
41
40
|
{
|
|
42
|
-
method:
|
|
43
|
-
path:
|
|
44
|
-
fullpath:
|
|
45
|
-
ip:
|
|
46
|
-
user_agent: truncate(
|
|
41
|
+
method: req&.request_method,
|
|
42
|
+
path: req&.path,
|
|
43
|
+
fullpath: req&.fullpath,
|
|
44
|
+
ip: req&.ip,
|
|
45
|
+
user_agent: truncate(req&.user_agent.to_s, 200),
|
|
47
46
|
params: safe_params(req),
|
|
48
47
|
request_id: env["action_dispatch.request_id"] || env["HTTP_X_REQUEST_ID"],
|
|
49
48
|
referer: truncate(env["HTTP_REFERER"].to_s, 500),
|
|
@@ -58,13 +57,13 @@ module ApmBro
|
|
|
58
57
|
|
|
59
58
|
def rack_request(env)
|
|
60
59
|
::Rack::Request.new(env)
|
|
61
|
-
rescue
|
|
60
|
+
rescue
|
|
62
61
|
nil
|
|
63
62
|
end
|
|
64
63
|
|
|
65
64
|
def safe_backtrace(exception)
|
|
66
65
|
Array(exception.backtrace).first(50)
|
|
67
|
-
rescue
|
|
66
|
+
rescue
|
|
68
67
|
[]
|
|
69
68
|
end
|
|
70
69
|
|
|
@@ -79,7 +78,7 @@ module ApmBro
|
|
|
79
78
|
filtered.delete(k.to_sym)
|
|
80
79
|
end
|
|
81
80
|
JSON.parse(JSON.dump(filtered)) # ensure JSON-safe
|
|
82
|
-
rescue
|
|
81
|
+
rescue
|
|
83
82
|
{}
|
|
84
83
|
end
|
|
85
84
|
|
|
@@ -94,33 +93,20 @@ module ApmBro
|
|
|
94
93
|
else
|
|
95
94
|
ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
|
|
96
95
|
end
|
|
97
|
-
rescue
|
|
96
|
+
rescue
|
|
98
97
|
"development"
|
|
99
98
|
end
|
|
100
99
|
|
|
101
100
|
def safe_app_name
|
|
102
101
|
if defined?(Rails) && Rails.respond_to?(:application)
|
|
103
|
-
|
|
102
|
+
begin
|
|
103
|
+
Rails.application.class.module_parent_name
|
|
104
|
+
rescue
|
|
105
|
+
""
|
|
106
|
+
end
|
|
104
107
|
else
|
|
105
108
|
""
|
|
106
109
|
end
|
|
107
110
|
end
|
|
108
|
-
|
|
109
|
-
def extract_user_email_from_env(env)
|
|
110
|
-
return nil unless ApmBro.configuration.user_email_tracking_enabled
|
|
111
|
-
|
|
112
|
-
# Try to get user email from various sources in the Rack environment
|
|
113
|
-
request_data = {
|
|
114
|
-
request: rack_request(env),
|
|
115
|
-
session: env["rack.session"],
|
|
116
|
-
params: env["action_dispatch.request.parameters"] || {}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
ApmBro.configuration.extract_user_email(request_data)
|
|
120
|
-
rescue StandardError
|
|
121
|
-
nil
|
|
122
|
-
end
|
|
123
111
|
end
|
|
124
112
|
end
|
|
125
|
-
|
|
126
|
-
|
|
@@ -5,12 +5,12 @@ require "net/http"
|
|
|
5
5
|
|
|
6
6
|
module ApmBro
|
|
7
7
|
module HttpInstrumentation
|
|
8
|
-
EVENT_NAME = "outgoing.http"
|
|
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
|
|
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 =
|
|
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 =
|
|
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:
|
|
47
|
+
url: uri && uri.to_s,
|
|
44
48
|
host: (uri && uri.host) || @address,
|
|
45
49
|
path: (uri && uri.path) || req.path,
|
|
46
|
-
status:
|
|
50
|
+
status: response && response.code.to_i,
|
|
47
51
|
duration_ms: duration_ms,
|
|
48
|
-
exception:
|
|
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
|
|
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)
|
|
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 =
|
|
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:
|
|
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
|
|
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
|
|
110
|
+
rescue
|
|
103
111
|
end
|
|
104
112
|
end
|
|
105
113
|
end
|
|
106
|
-
|
|
107
|
-
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
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"
|
|
8
|
-
JOB_EXCEPTION_EVENT_NAME = "exception.active_job"
|
|
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
|
|
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
|
|
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
|
|
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}##{
|
|
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
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
59
|
+
rescue
|
|
60
60
|
{}
|
|
61
61
|
end
|
|
62
62
|
end
|
data/lib/apm_bro/logger.rb
CHANGED
|
@@ -11,12 +11,12 @@ module ApmBro
|
|
|
11
11
|
}.freeze
|
|
12
12
|
|
|
13
13
|
# ANSI color codes
|
|
14
|
-
COLOR_RESET = "\033[0m"
|
|
15
|
-
COLOR_DEBUG = "\033[36m"
|
|
16
|
-
COLOR_INFO = "\033[32m"
|
|
17
|
-
COLOR_WARN = "\033[33m"
|
|
18
|
-
COLOR_ERROR = "\033[31m"
|
|
19
|
-
COLOR_FATAL = "\033[35m"
|
|
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
|
|
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
|