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 +4 -4
- data/lib/magick/feature.rb +2 -1
- data/lib/magick/performance_metrics.rb +94 -17
- data/lib/magick/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: 0cae8aed5b88784ad2c7b2635b377cd17270044eea05e4f47a18bd3a209c9e3f
|
|
4
|
+
data.tar.gz: f2c69d41bd7a26010f25eee8e0088d660e90fdeb976de961192dcbc52c781c3d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7a184080f97332ecac03e71f3ae72791ed3a01389a05f84dfdab7d6588dca2a0e8da7c5c5f78b9dc7fee05c09ddbdb302bd8cc97e8a6af0b276e621a01331640
|
|
7
|
+
data.tar.gz: c51c09f333e04b46e980c8c6ff67d434611208d716757b53850d625630a48c73c6f4c961f091909ade1fd33d0170f0128111fefb1d1a181f9a012e77be2be1ab
|
data/lib/magick/feature.rb
CHANGED
|
@@ -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 (
|
|
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
|
-
|
|
46
|
-
|
|
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
|
-
|
|
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
|
|
59
|
-
Magick::Rails::Events.usage_tracked(
|
|
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
|
|
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
|
-
|
|
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
|
-
#
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
|
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
|
data/lib/magick/version.rb
CHANGED