opentrace 0.13.2 → 0.14.0

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: 4eb32ca6add6d7b4d92ca4f05177c785e3e266b38d5aa2406f8ac28704ccecb0
4
- data.tar.gz: 9da8b1f881bdc66df31692edf4eb08c49cd6a6fd70dba9f8d04a5ac5a1298de8
3
+ metadata.gz: d768edfb2fd22f19187ff5e8f51abde81345f7a04cde6cc3a367ba4a1bc74faf
4
+ data.tar.gz: 0fe2d4458c4f1cbb4599ec27420a42b3e20e1c548129c19ef0181f34ea2dd81b
5
5
  SHA512:
6
- metadata.gz: d32b99d092d9fdf1f90db8a3f911b86c7e05d47222a69d253f17e2ee73adc885594435dd36b073a68efe544479eb2b645f3d74f9dfe9d0b561e20189e0303b86
7
- data.tar.gz: 73caacdb683c3b109337be153e640b5cbfbd1343b02d84ca4f796447597206a7b85bf076668a78f9e188f4f614ac3d8b8adaad020f5aa7caf29ada8aa10bc9bd
6
+ metadata.gz: 5eaa0f312e0a689744d42d9e06ce63a211024d377f0a2e7e68cb2879510636866957ed5dcedbfda5ede82d8dd223dc1e47197ef951ab4aeeb5c738c2501f4b87
7
+ data.tar.gz: c4b49a941eb4eec04db0d5d2ebf5973c45bd43ceda14c2043c44ff82554c78cffdb48a493b2e69c3eb1b40566977b3eae465d463f85729bad5f3fa85742032a4
@@ -175,38 +175,50 @@ module OpenTrace
175
175
  deadline = Time.now + @config.flush_interval
176
176
 
177
177
  loop do
178
- if batch.empty?
179
- # Block until first item arrives or timeout
180
- item = pop_with_timeout(deadline - Time.now)
181
- return nil if item.nil? && @queue.closed?
182
- batch << item if item
183
- else
184
- # Non-blocking drain up to batch_size
185
- while batch.size < @config.batch_size
186
- begin
187
- item = @queue.pop(true) # non_block = true
188
- batch << item
189
- rescue ThreadError
190
- break # queue empty
191
- end
178
+ # Non-blocking drain: grab everything currently in the queue
179
+ while batch.size < @config.batch_size
180
+ begin
181
+ item = @queue.pop(true) # non_block = true
182
+ batch << item
183
+ rescue ThreadError, ClosedQueueError
184
+ break # queue empty or closed
192
185
  end
193
186
  end
194
187
 
195
188
  break if batch.size >= @config.batch_size
196
189
  break if Time.now >= deadline
197
- break if @queue.closed?
190
+
191
+ # Queue closed — return remaining items or nil to signal shutdown
192
+ if @queue.closed?
193
+ return batch.empty? ? nil : batch
194
+ end
195
+
196
+ # Queue is empty but we haven't hit batch_size or deadline.
197
+ # Block (up to MAX_POP_WAIT) instead of busy-spinning so we
198
+ # release the GIL and let request-serving fibers run.
199
+ item = pop_with_timeout(deadline - Time.now)
200
+ return nil if item.nil? && @queue.closed?
201
+ batch << item if item
198
202
  end
199
203
 
200
204
  batch
201
205
  end
202
206
 
207
+ # Maximum time to block in a single Queue#pop call.
208
+ # Falcon (and other fiber-based servers) rely on the GIL being released
209
+ # periodically so their health-check fibers can run. A long blocking
210
+ # pop (e.g. 5 seconds) starves those fibers and causes Falcon to
211
+ # SIGKILL the worker. By capping at 0.5s we yield the GIL frequently
212
+ # enough to keep the health-check happy (30s default timeout).
213
+ MAX_POP_WAIT = 0.5
214
+
203
215
  def pop_with_timeout(timeout)
204
216
  if timeout <= 0
205
217
  # Deadline already passed — still try a non-blocking pop in case
206
218
  # items arrived while we were busy (e.g. during version check).
207
219
  @queue.pop(true)
208
220
  else
209
- @queue.pop(timeout: timeout)
221
+ @queue.pop(timeout: [timeout, MAX_POP_WAIT].min)
210
222
  end
211
223
  rescue ThreadError, ClosedQueueError
212
224
  nil
@@ -6,7 +6,7 @@ module OpenTrace
6
6
  LEVELS = { debug: 0, info: 1, warn: 2, error: 3, fatal: 4 }.freeze
7
7
  LEVEL_INTS = { "DEBUG" => 0, "INFO" => 1, "WARN" => 2, "ERROR" => 3, "FATAL" => 4 }.freeze
8
8
 
9
- attr_accessor :endpoint, :api_key, :service, :environment, :timeout, :enabled,
9
+ attr_accessor :endpoint, :api_key, :service, :environment, :timeout,
10
10
  :context, :hostname, :pid, :git_sha,
11
11
  :batch_size, :flush_interval,
12
12
  :max_retries, :retry_base_delay, :retry_max_delay,
@@ -34,8 +34,13 @@ module OpenTrace
34
34
  :explain_slow_queries, :explain_threshold_ms,
35
35
  :runtime_metrics, :runtime_metrics_interval
36
36
 
37
- # Custom writers that invalidate the level cache
38
- attr_reader :min_level, :allowed_levels, :ignore_paths
37
+ # Custom writers that invalidate caches
38
+ attr_reader :enabled, :min_level, :allowed_levels, :ignore_paths
39
+
40
+ def enabled=(val)
41
+ @enabled = val
42
+ @enabled_cache = nil
43
+ end
39
44
 
40
45
  def min_level=(val)
41
46
  @min_level = val
@@ -115,6 +120,7 @@ module OpenTrace
115
120
  @runtime_metrics = false # Collect GC/runtime metrics
116
121
  @runtime_metrics_interval = 30 # Interval in seconds
117
122
  @level_cache = nil
123
+ @enabled_cache = nil
118
124
  end
119
125
 
120
126
  def valid?
@@ -122,7 +128,12 @@ module OpenTrace
122
128
  end
123
129
 
124
130
  def enabled?
125
- @enabled && valid?
131
+ # Cache the enabled+valid result to avoid recomputing valid? on every call.
132
+ # Invalidated by finalize! (called at end of configure block) and enabled= setter.
133
+ if @enabled_cache.nil?
134
+ @enabled_cache = @enabled && valid?
135
+ end
136
+ @enabled_cache
126
137
  end
127
138
 
128
139
  def min_level_value
@@ -138,7 +149,10 @@ module OpenTrace
138
149
  build_level_cache!
139
150
  cache = @level_cache
140
151
  end
141
- key = level.to_s.upcase
152
+ # Avoid allocating a new String when the level is already uppercase.
153
+ # On the hot path (called per SQL query, per log line), this saves
154
+ # one String allocation per call.
155
+ key = level.is_a?(String) && level == level.upcase ? level : level.to_s.upcase
142
156
  result = cache[key]
143
157
  return result unless result.nil?
144
158
  # Unknown level (e.g. "UNKNOWN"): treat as severity 0
@@ -149,6 +163,7 @@ module OpenTrace
149
163
  # Pre-compute the level cache. Called at end of configure block
150
164
  # and lazily when settings change afterward.
151
165
  def finalize!
166
+ @enabled_cache = nil
152
167
  build_level_cache!
153
168
  end
154
169
 
@@ -43,8 +43,12 @@ if defined?(::Rails::Railtie)
43
43
  # Swallow - never affect the host app
44
44
  end
45
45
 
46
- # SQL subscriber — lightweight counter + optional forwarding with filtering
46
+ # SQL subscriber — lightweight counter + optional forwarding with filtering.
47
+ # Cache config values at subscribe time to avoid repeated config lookups
48
+ # on every single SQL query (can be 50-200+ per request).
47
49
  sql_logging = OpenTrace.config.sql_logging
50
+ explain_enabled = OpenTrace.config.explain_slow_queries
51
+ explain_threshold = OpenTrace.config.explain_threshold_ms
48
52
  ActiveSupport::Notifications.subscribe("sql.active_record") do |name, started, finished, id, payload|
49
53
  sql_count = Fiber[:opentrace_sql_count]
50
54
  collector = Fiber[:opentrace_collector]
@@ -81,8 +85,8 @@ if defined?(::Rails::Railtie)
81
85
  end
82
86
 
83
87
  # Flag slow queries for background EXPLAIN (opt-in)
84
- if OpenTrace.config.explain_slow_queries &&
85
- duration_ms > OpenTrace.config.explain_threshold_ms &&
88
+ if explain_enabled &&
89
+ duration_ms > explain_threshold &&
86
90
  explainable_query?(raw_sql)
87
91
  pending = Fiber[:opentrace_pending_explains] ||= []
88
92
  if pending.size < 3
@@ -328,6 +332,12 @@ if defined?(::Rails::Railtie)
328
332
  return unless OpenTrace.enabled?
329
333
 
330
334
  duration = duration_ms&.round(2)
335
+
336
+ # Determine level BEFORE doing any expensive work (regex, hashing).
337
+ # Most SQL logs are DEBUG — skip everything if DEBUG is filtered.
338
+ level = (duration && duration > 1000) ? "WARN" : "DEBUG"
339
+ return unless OpenTrace.config.level_allowed?(level)
340
+
331
341
  threshold = OpenTrace.config.sql_duration_threshold_ms
332
342
 
333
343
  # Skip if below threshold
@@ -354,7 +364,6 @@ if defined?(::Rails::Railtie)
354
364
  metadata[:sql_table] = $1
355
365
  end
356
366
 
357
- level = (duration && duration > 1000) ? "WARN" : "DEBUG"
358
367
  message = "SQL #{payload[:name]} #{duration}ms"
359
368
 
360
369
  OpenTrace.log(level, message, metadata)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OpenTrace
4
- VERSION = "0.13.2"
4
+ VERSION = "0.14.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: opentrace
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.13.2
4
+ version: 0.14.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - OpenTrace