magick-feature-flags 0.9.21 → 0.9.23

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: 467ad50ad54569fd4ac8562da75e37672716b980cfdfb03da79b2f29950fd4c9
4
- data.tar.gz: 0b89ad165e8aad8fb2074821025bd7e50ef2568cc46f5b750bfc4c91c6e11658
3
+ metadata.gz: 0cae8aed5b88784ad2c7b2635b377cd17270044eea05e4f47a18bd3a209c9e3f
4
+ data.tar.gz: f2c69d41bd7a26010f25eee8e0088d660e90fdeb976de961192dcbc52c781c3d
5
5
  SHA512:
6
- metadata.gz: 7b3fa0a75bd127f269ecafcddfbe6dcba8d45a247d6b47d83085a097f58de6b74c570eb0655fd4a97c78d8335ff1916a57b3eb2b891143d6a2cc585acc5ff87c
7
- data.tar.gz: 79cf7083e34daab0c569e9e05d6e0b123167692f2b206b67ae53321ddabe722861e701346dd5cac80c6b1898be75140ed52fd93470b9a19ccf8113ebf6d91a16
6
+ metadata.gz: 7a184080f97332ecac03e71f3ae72791ed3a01389a05f84dfdab7d6588dca2a0e8da7c5c5f78b9dc7fee05c09ddbdb302bd8cc97e8a6af0b276e621a01331640
7
+ data.tar.gz: c51c09f333e04b46e980c8c6ff67d434611208d716757b53850d625630a48c73c6f4c961f091909ade1fd33d0170f0128111fefb1d1a181f9a012e77be2be1ab
@@ -52,11 +52,12 @@ module Magick
52
52
  return check_enabled(context) unless perf_metrics_enabled
53
53
 
54
54
  # Performance metrics enabled: measure and record
55
+ # Use inline timing to avoid function call overhead
55
56
  start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
56
57
  result = check_enabled(context)
57
58
  duration = (Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time) * 1000 # milliseconds
58
59
 
59
- # Record metrics (only if enabled)
60
+ # Record metrics (fast path - minimal overhead)
60
61
  perf_metrics.record(name, 'enabled?', duration, success: true)
61
62
 
62
63
  # Rails 8+ events (only in development or when explicitly enabled)
@@ -36,44 +36,113 @@ module Magick
36
36
  # If redis_enabled is explicitly set, use it; otherwise default to false
37
37
  # It will be enabled later via enable_redis_tracking if Redis adapter is available
38
38
  @redis_enabled = redis_enabled.nil? ? false : redis_enabled
39
+ # Cache expensive checks for performance
40
+ @_rails_events_enabled = defined?(Magick::Rails::Events) && Magick::Rails::Events.rails8?
41
+ @_adapter_available = nil # Will be cached on first check
42
+ @_redis_available = nil # Will be cached on first check
43
+
44
+ # Async recording queue for non-blocking metrics
45
+ @async_queue = Queue.new
46
+ @async_thread = nil
47
+ @async_enabled = true # Enable async by default for performance
48
+ start_async_processor
39
49
  end
40
50
 
41
51
  # Public accessor for redis_enabled
42
52
  attr_reader :redis_enabled
43
53
 
44
54
  def record(feature_name, operation, duration, success: true)
45
- feature_name_str = feature_name.to_s
46
- metric = Metric.new(feature_name_str, operation, duration, success: success)
55
+ # Fast path: push to async queue (non-blocking, zero overhead in hot path)
56
+ # Queue#<< is thread-safe and lock-free - extremely fast!
57
+ return unless @async_enabled
58
+
59
+ # Push to async queue - this is lock-free and extremely fast
60
+ # Use non-blocking push (will raise if queue is full, but our queue is unbounded)
61
+ begin
62
+ @async_queue << [feature_name.to_s, operation.to_s, duration, success]
63
+ rescue ThreadError, ClosedQueueError
64
+ # Queue is closed or thread died, disable async
65
+ @async_enabled = false
66
+ end
67
+
68
+ nil
69
+ end
47
70
 
71
+ # Internal: Process metrics from async queue (runs in background thread)
72
+ def process_async_record(feature_name_str, operation_str, duration, success)
73
+ # Minimize mutex lock time - only update counters
74
+ pending_count = nil
75
+ total_pending = nil
48
76
  @mutex.synchronize do
49
- @metrics << metric
77
+ # Only create Metric object if we're keeping metrics in memory
78
+ if @metrics.length < 1000
79
+ metric = Metric.new(feature_name_str, operation_str, duration, success: success)
80
+ @metrics << metric
81
+ end
50
82
  @usage_count[feature_name_str] += 1
51
83
  @pending_updates[feature_name_str] += 1
84
+ pending_count = @pending_updates[feature_name_str]
85
+ total_pending = @pending_updates.values.sum
52
86
  # Keep only last 1000 metrics (as a safety limit)
53
- # Note: Metrics are removed from memory after flushing to Redis to reduce memory usage
54
87
  @metrics.shift if @metrics.length > 1000
55
88
  end
56
89
 
57
- # Rails 8+ event for usage tracking
58
- if defined?(Magick::Rails::Events) && Magick::Rails::Events.rails8?
59
- Magick::Rails::Events.usage_tracked(feature_name, operation: operation, duration: duration, success: success)
90
+ # Rails 8+ event for usage tracking (cached check)
91
+ if @_rails_events_enabled
92
+ Magick::Rails::Events.usage_tracked(feature_name_str, operation: operation_str, duration: duration,
93
+ success: success)
60
94
  end
61
95
 
62
- # Batch flush to Redis if needed
63
- flush_to_redis_if_needed
96
+ # Batch flush check - only if we're close to batch size
97
+ flush_to_redis_if_needed if pending_count >= @batch_size || total_pending >= @batch_size
98
+ end
99
+
100
+ # Start background thread to process async metrics
101
+ def start_async_processor
102
+ return if @async_thread&.alive?
103
+
104
+ @async_thread = Thread.new do
105
+ last_flush_check = Time.now
106
+ loop do
107
+ # Wait for metrics with timeout to allow periodic flush checks
108
+ # Queue#pop with timeout returns nil on timeout, raises on closed queue
109
+ begin
110
+ data = @async_queue.pop(timeout: 1.0)
111
+ rescue ThreadError => e
112
+ # Queue closed or thread interrupted
113
+ break if e.message.include?('queue closed')
114
+
115
+ raise
116
+ end
64
117
 
65
- metric
118
+ if data
119
+ feature_name_str, operation_str, duration, success = data
120
+ process_async_record(feature_name_str, operation_str, duration, success)
121
+ last_flush_check = Time.now
122
+ elsif Time.now - last_flush_check >= 1.0
123
+ # Timeout - check if we need to flush based on time (every second)
124
+ flush_to_redis_if_needed
125
+ last_flush_check = Time.now
126
+ end
127
+ rescue StandardError => e
128
+ # Log error but continue processing
129
+ warn "Magick: Error in async metrics processor: #{e.message}" if defined?(Rails) && Rails.env.development?
130
+ sleep 0.1 # Brief pause on error
131
+ end
132
+ end
133
+ @async_thread.abort_on_exception = false
66
134
  end
67
135
 
68
136
  def flush_to_redis_if_needed
69
- # Always try to flush if Redis adapter is available, even if @redis_enabled is false
70
- # This ensures stats are collected even if redis_enabled wasn't set correctly
71
- adapter = Magick.adapter_registry || Magick.default_adapter_registry
72
- return unless adapter # No adapter at all, skip
73
-
74
- redis_available = adapter.is_a?(Magick::Adapters::Registry) && adapter.redis_available?
137
+ # Cache adapter availability check (expensive)
138
+ if @_adapter_available.nil?
139
+ adapter = Magick.adapter_registry || Magick.default_adapter_registry
140
+ @_adapter_available = adapter
141
+ @_redis_available = adapter.is_a?(Magick::Adapters::Registry) && adapter.redis_available? if adapter
142
+ end
75
143
 
76
- return unless redis_available || @redis_enabled
144
+ return unless @_adapter_available
145
+ return unless @_redis_available || @redis_enabled
77
146
  return if @pending_updates.empty?
78
147
 
79
148
  should_flush = false
@@ -311,5 +380,13 @@ module Magick
311
380
  @flushed_counts.clear
312
381
  end
313
382
  end
383
+
384
+ # Stop async processor (for cleanup)
385
+ def stop_async_processor
386
+ @async_enabled = false
387
+ @async_queue.close if @async_queue.respond_to?(:close)
388
+ @async_thread&.kill
389
+ @async_thread = nil
390
+ end
314
391
  end
315
392
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Magick
4
- VERSION = '0.9.21'
4
+ VERSION = '0.9.23'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: magick-feature-flags
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.21
4
+ version: 0.9.23
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Lobanov