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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: caf0f22a5f4a72ddab15c8ec54dd82eb421786dd02568706eab5a906e2543a6c
4
- data.tar.gz: 8b944014790250fc71107ebe01e789b882f9f9d836904f30edcc03ebb734fd41
3
+ metadata.gz: d47832a9c956542a7b0e88d048d2f8760f7971df069a7e06fec92822ad79e67e
4
+ data.tar.gz: 2f536dfe0c55b9b46fae2a4f5b77f5c873b39c59ef71ec06052951d02192a4c7
5
5
  SHA512:
6
- metadata.gz: 750db0f63cd9e0d6bb01f3c45db1efcd81922a5b532ed0226d9948dcf0ae4b709f6cec442403f469064bc334b45bcae9d203b4df4a90cefc6ba334a0b4add919
7
- data.tar.gz: 626c32ee6ac593b581a1fbd3e9a5086382b877212280d8265f774600bd6fea79c87957aa6c50c6c3725baa1835cc899f1a3fa1af7f9de0713e3bf63b34fb7d0f
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
- # Always try to get the full call stack for better trace information
90
- begin
91
- # Get the current call stack, skip the first few frames (our own code)
92
- caller_stack = caller(3, 0) # Skip 3 frames, get up to 1
93
- caller_trace = caller_stack.map do |line|
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
- rescue StandardError
101
- # If caller fails, we still have the immediate location
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
- backtrace_trace = data[:backtrace].first(5).map do |line|
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 limit the number of frames
118
- trace.uniq.first(10).map do |line|
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ApmBro
4
- VERSION = "0.1.12"
4
+ VERSION = "0.1.14"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: apm_bro
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.12
4
+ version: 0.1.14
5
5
  platform: ruby
6
6
  authors:
7
7
  - Emanuel Comsa