apm_bro 0.1.12 → 0.1.14
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/lib/apm_bro/job_sql_tracking_middleware.rb +10 -0
- data/lib/apm_bro/job_subscriber.rb +60 -0
- data/lib/apm_bro/sql_subscriber.rb +72 -14
- data/lib/apm_bro/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d47832a9c956542a7b0e88d048d2f8760f7971df069a7e06fec92822ad79e67e
|
|
4
|
+
data.tar.gz: 2f536dfe0c55b9b46fae2a4f5b77f5c873b39c59ef71ec06052951d02192a4c7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1e288923b2a58952d927cee3c85e3e242fe22baf79acccefd79ce0933a3b6c7db786faae7b4e96bd9f5687f9aed48c78e9bc493440809a65d27eaf9a31cb51d2
|
|
7
|
+
data.tar.gz: 02cd07fa51270bdda5a7ada77463435491cd99e7999ea4162864547454f99ce9846ed78b9cfc9c1df0e5095e7145da43d9822bd790786e1c77701f94a9705d34
|
|
@@ -8,6 +8,16 @@ module ApmBro
|
|
|
8
8
|
# Clear logs for this job
|
|
9
9
|
ApmBro.logger.clear
|
|
10
10
|
ApmBro::SqlSubscriber.start_request_tracking
|
|
11
|
+
|
|
12
|
+
# Start lightweight memory tracking for this job
|
|
13
|
+
if defined?(ApmBro::LightweightMemoryTracker)
|
|
14
|
+
ApmBro::LightweightMemoryTracker.start_request_tracking
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Start detailed memory tracking when allocation tracking is enabled
|
|
18
|
+
if ApmBro.configuration.allocation_tracking_enabled && defined?(ApmBro::MemoryTrackingSubscriber)
|
|
19
|
+
ApmBro::MemoryTrackingSubscriber.start_request_tracking
|
|
20
|
+
end
|
|
11
21
|
end
|
|
12
22
|
rescue StandardError
|
|
13
23
|
# Never raise from instrumentation install
|
|
@@ -23,6 +23,34 @@ module ApmBro
|
|
|
23
23
|
# Get SQL queries executed during this job
|
|
24
24
|
sql_queries = ApmBro::SqlSubscriber.stop_request_tracking
|
|
25
25
|
|
|
26
|
+
# Stop memory tracking and get collected memory data
|
|
27
|
+
if ApmBro.configuration.allocation_tracking_enabled && defined?(ApmBro::MemoryTrackingSubscriber)
|
|
28
|
+
detailed_memory = ApmBro::MemoryTrackingSubscriber.stop_request_tracking
|
|
29
|
+
memory_performance = ApmBro::MemoryTrackingSubscriber.analyze_memory_performance(detailed_memory)
|
|
30
|
+
# Keep memory_events compact and user-friendly (no large raw arrays)
|
|
31
|
+
memory_events = {
|
|
32
|
+
memory_before: detailed_memory[:memory_before],
|
|
33
|
+
memory_after: detailed_memory[:memory_after],
|
|
34
|
+
duration_seconds: detailed_memory[:duration_seconds],
|
|
35
|
+
allocations_count: (detailed_memory[:allocations] || []).length,
|
|
36
|
+
memory_snapshots_count: (detailed_memory[:memory_snapshots] || []).length,
|
|
37
|
+
large_objects_count: (detailed_memory[:large_objects] || []).length
|
|
38
|
+
}
|
|
39
|
+
else
|
|
40
|
+
lightweight_memory = ApmBro::LightweightMemoryTracker.stop_request_tracking
|
|
41
|
+
# Separate raw readings from derived performance metrics to avoid duplicating data
|
|
42
|
+
memory_events = {
|
|
43
|
+
memory_before: lightweight_memory[:memory_before],
|
|
44
|
+
memory_after: lightweight_memory[:memory_after]
|
|
45
|
+
}
|
|
46
|
+
memory_performance = {
|
|
47
|
+
memory_growth_mb: lightweight_memory[:memory_growth_mb],
|
|
48
|
+
gc_count_increase: lightweight_memory[:gc_count_increase],
|
|
49
|
+
heap_pages_increase: lightweight_memory[:heap_pages_increase],
|
|
50
|
+
duration_seconds: lightweight_memory[:duration_seconds]
|
|
51
|
+
}
|
|
52
|
+
end
|
|
53
|
+
|
|
26
54
|
payload = {
|
|
27
55
|
job_class: data[:job].class.name,
|
|
28
56
|
job_id: data[:job].job_id,
|
|
@@ -35,6 +63,8 @@ module ApmBro
|
|
|
35
63
|
host: safe_host,
|
|
36
64
|
memory_usage: memory_usage_mb,
|
|
37
65
|
gc_stats: gc_stats,
|
|
66
|
+
memory_events: memory_events,
|
|
67
|
+
memory_performance: memory_performance,
|
|
38
68
|
logs: ApmBro.logger.logs
|
|
39
69
|
}
|
|
40
70
|
|
|
@@ -57,6 +87,34 @@ module ApmBro
|
|
|
57
87
|
# Get SQL queries executed during this job
|
|
58
88
|
sql_queries = ApmBro::SqlSubscriber.stop_request_tracking
|
|
59
89
|
|
|
90
|
+
# Stop memory tracking and get collected memory data
|
|
91
|
+
if ApmBro.configuration.allocation_tracking_enabled && defined?(ApmBro::MemoryTrackingSubscriber)
|
|
92
|
+
detailed_memory = ApmBro::MemoryTrackingSubscriber.stop_request_tracking
|
|
93
|
+
memory_performance = ApmBro::MemoryTrackingSubscriber.analyze_memory_performance(detailed_memory)
|
|
94
|
+
# Keep memory_events compact and user-friendly (no large raw arrays)
|
|
95
|
+
memory_events = {
|
|
96
|
+
memory_before: detailed_memory[:memory_before],
|
|
97
|
+
memory_after: detailed_memory[:memory_after],
|
|
98
|
+
duration_seconds: detailed_memory[:duration_seconds],
|
|
99
|
+
allocations_count: (detailed_memory[:allocations] || []).length,
|
|
100
|
+
memory_snapshots_count: (detailed_memory[:memory_snapshots] || []).length,
|
|
101
|
+
large_objects_count: (detailed_memory[:large_objects] || []).length
|
|
102
|
+
}
|
|
103
|
+
else
|
|
104
|
+
lightweight_memory = ApmBro::LightweightMemoryTracker.stop_request_tracking
|
|
105
|
+
# Separate raw readings from derived performance metrics to avoid duplicating data
|
|
106
|
+
memory_events = {
|
|
107
|
+
memory_before: lightweight_memory[:memory_before],
|
|
108
|
+
memory_after: lightweight_memory[:memory_after]
|
|
109
|
+
}
|
|
110
|
+
memory_performance = {
|
|
111
|
+
memory_growth_mb: lightweight_memory[:memory_growth_mb],
|
|
112
|
+
gc_count_increase: lightweight_memory[:gc_count_increase],
|
|
113
|
+
heap_pages_increase: lightweight_memory[:heap_pages_increase],
|
|
114
|
+
duration_seconds: lightweight_memory[:duration_seconds]
|
|
115
|
+
}
|
|
116
|
+
end
|
|
117
|
+
|
|
60
118
|
payload = {
|
|
61
119
|
job_class: data[:job].class.name,
|
|
62
120
|
job_id: data[:job].job_id,
|
|
@@ -72,6 +130,8 @@ module ApmBro
|
|
|
72
130
|
host: safe_host,
|
|
73
131
|
memory_usage: memory_usage_mb,
|
|
74
132
|
gc_stats: gc_stats,
|
|
133
|
+
memory_events: memory_events,
|
|
134
|
+
memory_performance: memory_performance,
|
|
75
135
|
logs: ApmBro.logger.logs
|
|
76
136
|
}
|
|
77
137
|
|
|
@@ -9,6 +9,7 @@ module ApmBro
|
|
|
9
9
|
THREAD_LOCAL_KEY = :apm_bro_sql_queries
|
|
10
10
|
THREAD_LOCAL_ALLOC_START_KEY = :apm_bro_sql_alloc_start
|
|
11
11
|
THREAD_LOCAL_ALLOC_RESULTS_KEY = :apm_bro_sql_alloc_results
|
|
12
|
+
THREAD_LOCAL_BACKTRACE_KEY = :apm_bro_sql_backtraces
|
|
12
13
|
|
|
13
14
|
def self.subscribe!
|
|
14
15
|
# Subscribe with a start/finish listener to measure allocations per query
|
|
@@ -20,13 +21,19 @@ module ApmBro
|
|
|
20
21
|
end
|
|
21
22
|
|
|
22
23
|
ActiveSupport::Notifications.subscribe(SQL_EVENT_NAME) do |name, started, finished, _unique_id, data|
|
|
24
|
+
next if data[:name] == "SCHEMA"
|
|
23
25
|
# Only track queries that are part of the current request
|
|
24
26
|
next unless Thread.current[THREAD_LOCAL_KEY]
|
|
25
27
|
unique_id = _unique_id
|
|
26
28
|
allocations = nil
|
|
29
|
+
captured_backtrace = nil
|
|
27
30
|
begin
|
|
28
31
|
alloc_results = Thread.current[THREAD_LOCAL_ALLOC_RESULTS_KEY]
|
|
29
32
|
allocations = alloc_results && alloc_results.delete(unique_id)
|
|
33
|
+
|
|
34
|
+
# Get the captured backtrace from when the query started
|
|
35
|
+
backtrace_map = Thread.current[THREAD_LOCAL_BACKTRACE_KEY]
|
|
36
|
+
captured_backtrace = backtrace_map && backtrace_map.delete(unique_id)
|
|
30
37
|
rescue StandardError
|
|
31
38
|
end
|
|
32
39
|
|
|
@@ -36,7 +43,7 @@ module ApmBro
|
|
|
36
43
|
duration_ms: ((finished - started) * 1000.0).round(2),
|
|
37
44
|
cached: data[:cached] || false,
|
|
38
45
|
connection_id: data[:connection_id],
|
|
39
|
-
trace: safe_query_trace(data),
|
|
46
|
+
trace: safe_query_trace(data, captured_backtrace),
|
|
40
47
|
allocations: allocations
|
|
41
48
|
}
|
|
42
49
|
# Add to thread-local storage
|
|
@@ -49,6 +56,7 @@ module ApmBro
|
|
|
49
56
|
Thread.current[THREAD_LOCAL_KEY] = []
|
|
50
57
|
Thread.current[THREAD_LOCAL_ALLOC_START_KEY] = {}
|
|
51
58
|
Thread.current[THREAD_LOCAL_ALLOC_RESULTS_KEY] = {}
|
|
59
|
+
Thread.current[THREAD_LOCAL_BACKTRACE_KEY] = {}
|
|
52
60
|
end
|
|
53
61
|
|
|
54
62
|
def self.stop_request_tracking
|
|
@@ -56,6 +64,7 @@ module ApmBro
|
|
|
56
64
|
Thread.current[THREAD_LOCAL_KEY] = nil
|
|
57
65
|
Thread.current[THREAD_LOCAL_ALLOC_START_KEY] = nil
|
|
58
66
|
Thread.current[THREAD_LOCAL_ALLOC_RESULTS_KEY] = nil
|
|
67
|
+
Thread.current[THREAD_LOCAL_BACKTRACE_KEY] = nil
|
|
59
68
|
queries || []
|
|
60
69
|
end
|
|
61
70
|
|
|
@@ -75,7 +84,7 @@ module ApmBro
|
|
|
75
84
|
sql.length > 1000 ? sql[0..1000] + "..." : sql
|
|
76
85
|
end
|
|
77
86
|
|
|
78
|
-
def self.safe_query_trace(data)
|
|
87
|
+
def self.safe_query_trace(data, captured_backtrace = nil)
|
|
79
88
|
return [] unless data.is_a?(Hash)
|
|
80
89
|
|
|
81
90
|
# Build trace from available data fields
|
|
@@ -86,24 +95,64 @@ module ApmBro
|
|
|
86
95
|
trace << "#{data[:filename]}:#{data[:line]}:in `#{data[:method]}'"
|
|
87
96
|
end
|
|
88
97
|
|
|
89
|
-
#
|
|
90
|
-
|
|
91
|
-
#
|
|
92
|
-
|
|
93
|
-
|
|
98
|
+
# Use the captured backtrace from when the query started (most accurate)
|
|
99
|
+
if captured_backtrace && captured_backtrace.is_a?(Array) && !captured_backtrace.empty?
|
|
100
|
+
# Filter to only include frames that contain "app/" (application code)
|
|
101
|
+
app_frames = captured_backtrace.select do |frame|
|
|
102
|
+
frame.include?('app/') && !frame.include?('/vendor/')
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
caller_trace = app_frames.map do |line|
|
|
94
106
|
# Remove any potential sensitive information from file paths
|
|
95
107
|
line.gsub(/\/[^\/]*(password|secret|key|token)[^\/]*\//i, '/[FILTERED]/')
|
|
96
108
|
end
|
|
97
109
|
|
|
98
|
-
# Combine the immediate location with the call stack
|
|
99
110
|
trace.concat(caller_trace)
|
|
100
|
-
|
|
101
|
-
#
|
|
111
|
+
else
|
|
112
|
+
# Fallback: try to get backtrace from current context
|
|
113
|
+
begin
|
|
114
|
+
# Get all available frames - we'll filter to find application code
|
|
115
|
+
all_frames = Thread.current.backtrace || []
|
|
116
|
+
|
|
117
|
+
if all_frames.empty?
|
|
118
|
+
# Fallback to caller_locations if backtrace is empty
|
|
119
|
+
locations = caller_locations(1, 50)
|
|
120
|
+
all_frames = locations.map { |loc| "#{loc.path}:#{loc.lineno}:in `#{loc.label}'" } if locations
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Filter to only include frames that contain "app/" (application code)
|
|
124
|
+
app_frames = all_frames.select do |frame|
|
|
125
|
+
frame.include?('app/') && !frame.include?('/vendor/')
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
caller_trace = app_frames.map do |line|
|
|
129
|
+
line.gsub(/\/[^\/]*(password|secret|key|token)[^\/]*\//i, '/[FILTERED]/')
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
trace.concat(caller_trace)
|
|
133
|
+
rescue StandardError
|
|
134
|
+
# If backtrace fails, try caller as fallback
|
|
135
|
+
begin
|
|
136
|
+
caller_stack = caller(20, 50) # Get more frames to find app/ frames
|
|
137
|
+
app_frames = caller_stack.select { |frame| frame.include?('app/') && !frame.include?('/vendor/') }
|
|
138
|
+
caller_trace = app_frames.map do |line|
|
|
139
|
+
line.gsub(/\/[^\/]*(password|secret|key|token)[^\/]*\//i, '/[FILTERED]/')
|
|
140
|
+
end
|
|
141
|
+
trace.concat(caller_trace)
|
|
142
|
+
rescue StandardError
|
|
143
|
+
# If caller also fails, we still have the immediate location
|
|
144
|
+
end
|
|
145
|
+
end
|
|
102
146
|
end
|
|
103
147
|
|
|
104
|
-
# If we have a backtrace, use it (but it's usually nil for SQL events)
|
|
148
|
+
# If we have a backtrace in the data, use it (but it's usually nil for SQL events)
|
|
105
149
|
if data[:backtrace] && data[:backtrace].is_a?(Array)
|
|
106
|
-
|
|
150
|
+
# Filter to only include frames that contain "app/"
|
|
151
|
+
app_backtrace = data[:backtrace].select do |line|
|
|
152
|
+
line.is_a?(String) && line.include?('app/') && !line.include?('/vendor/')
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
backtrace_trace = app_backtrace.map do |line|
|
|
107
156
|
case line
|
|
108
157
|
when String
|
|
109
158
|
line.gsub(/\/[^\/]*(password|secret|key|token)[^\/]*\//i, '/[FILTERED]/')
|
|
@@ -114,8 +163,8 @@ module ApmBro
|
|
|
114
163
|
trace.concat(backtrace_trace)
|
|
115
164
|
end
|
|
116
165
|
|
|
117
|
-
# Remove duplicates and
|
|
118
|
-
trace.uniq.
|
|
166
|
+
# Remove duplicates and return all app/ frames (no limit)
|
|
167
|
+
trace.uniq.map do |line|
|
|
119
168
|
case line
|
|
120
169
|
when String
|
|
121
170
|
# Remove any potential sensitive information from file paths
|
|
@@ -138,6 +187,15 @@ module ApmBro
|
|
|
138
187
|
begin
|
|
139
188
|
map = (Thread.current[ApmBro::SqlSubscriber::THREAD_LOCAL_ALLOC_START_KEY] ||= {})
|
|
140
189
|
map[id] = GC.stat[:total_allocated_objects] if defined?(GC) && GC.respond_to?(:stat)
|
|
190
|
+
|
|
191
|
+
# Capture the backtrace at query start time (before notification system processes it)
|
|
192
|
+
# This gives us the actual call stack where the SQL was executed
|
|
193
|
+
backtrace_map = (Thread.current[ApmBro::SqlSubscriber::THREAD_LOCAL_BACKTRACE_KEY] ||= {})
|
|
194
|
+
captured_backtrace = Thread.current.backtrace
|
|
195
|
+
if captured_backtrace && captured_backtrace.is_a?(Array)
|
|
196
|
+
# Skip the first few frames (our listener code) to get to the actual query execution
|
|
197
|
+
backtrace_map[id] = captured_backtrace[5..-1] || captured_backtrace
|
|
198
|
+
end
|
|
141
199
|
rescue StandardError
|
|
142
200
|
end
|
|
143
201
|
end
|
data/lib/apm_bro/version.rb
CHANGED