magick-feature-flags 0.9.5 → 0.9.7
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/README.md +64 -8
- data/lib/magick/adapters/registry.rb +6 -0
- data/lib/magick/config.rb +17 -8
- data/lib/magick/feature.rb +11 -1
- data/lib/magick/version.rb +1 -1
- data/lib/magick.rb +32 -0
- 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: 015b9568cac92b03d1776240c7236cb395d15189eb496e03ac803979fef2d77d
|
|
4
|
+
data.tar.gz: 3219b3349bb202775c883d7b977ca0dd8d64846625a524abbe858c6d51d9db43
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d93760f46cd383bc5b97f032a992fe4e0fc6e567d50985ab2544475805ffbb4545b105c8033a7aafb5a48149c1169ab7f87e6e70da58bcbfd2554a1935608bb3
|
|
7
|
+
data.tar.gz: 19424a6172af8421a2041a4259f5474a4cffa7cf62cd03582b23f479374b7ca3a9aa56ca57f69e7facae4e372662d70abf80384456247d801ffd6eee141acd56
|
data/README.md
CHANGED
|
@@ -82,7 +82,12 @@ Magick.configure do
|
|
|
82
82
|
async_updates enabled: true
|
|
83
83
|
|
|
84
84
|
# Enable services
|
|
85
|
-
performance_metrics
|
|
85
|
+
performance_metrics(
|
|
86
|
+
enabled: true,
|
|
87
|
+
redis_tracking: true, # Auto-enabled if Redis is configured
|
|
88
|
+
batch_size: 100, # Flush after 100 updates
|
|
89
|
+
flush_interval: 60 # Or flush every 60 seconds
|
|
90
|
+
)
|
|
86
91
|
audit_log enabled: true
|
|
87
92
|
versioning enabled: true
|
|
88
93
|
warn_on_deprecated enabled: true
|
|
@@ -295,7 +300,16 @@ variant = feature.get_variant
|
|
|
295
300
|
```ruby
|
|
296
301
|
feature = Magick[:advanced_feature]
|
|
297
302
|
feature.add_dependency(:base_feature)
|
|
298
|
-
# advanced_feature
|
|
303
|
+
# advanced_feature can be enabled independently
|
|
304
|
+
# However, base_feature (dependency) cannot be enabled if advanced_feature (main feature) is disabled
|
|
305
|
+
# This ensures dependencies are only enabled when their parent features are enabled
|
|
306
|
+
|
|
307
|
+
# Example:
|
|
308
|
+
Magick[:advanced_feature].disable # => true
|
|
309
|
+
Magick[:base_feature].enable # => false (cannot enable dependency when main feature is disabled)
|
|
310
|
+
|
|
311
|
+
Magick[:advanced_feature].enable # => true
|
|
312
|
+
Magick[:base_feature].enable # => true (now can enable dependency)
|
|
299
313
|
```
|
|
300
314
|
|
|
301
315
|
#### Export/Import
|
|
@@ -322,16 +336,55 @@ Magick.versioning.rollback(:my_feature, version: 2)
|
|
|
322
336
|
#### Performance Metrics
|
|
323
337
|
|
|
324
338
|
```ruby
|
|
325
|
-
# Get
|
|
326
|
-
|
|
339
|
+
# Get comprehensive stats for a feature
|
|
340
|
+
Magick.feature_stats(:my_feature)
|
|
341
|
+
# => {
|
|
342
|
+
# usage_count: 1250,
|
|
343
|
+
# average_duration: 0.032,
|
|
344
|
+
# average_duration_by_operation: {
|
|
345
|
+
# enabled: 0.032,
|
|
346
|
+
# value: 0.0,
|
|
347
|
+
# get_value: 0.0
|
|
348
|
+
# }
|
|
349
|
+
# }
|
|
350
|
+
|
|
351
|
+
# Get just the usage count
|
|
352
|
+
Magick.feature_usage_count(:my_feature)
|
|
353
|
+
# => 1250
|
|
354
|
+
|
|
355
|
+
# Get average duration (optionally filtered by operation)
|
|
356
|
+
Magick.feature_average_duration(:my_feature)
|
|
357
|
+
Magick.feature_average_duration(:my_feature, operation: 'enabled?')
|
|
327
358
|
|
|
328
359
|
# Get most used features
|
|
329
|
-
|
|
360
|
+
Magick.most_used_features(limit: 10)
|
|
361
|
+
# => {
|
|
362
|
+
# "my_feature" => 1250,
|
|
363
|
+
# "another_feature" => 890,
|
|
364
|
+
# ...
|
|
365
|
+
# }
|
|
366
|
+
|
|
367
|
+
# Direct access to performance metrics (for advanced usage)
|
|
368
|
+
Magick.performance_metrics.average_duration(feature_name: :my_feature)
|
|
369
|
+
Magick.performance_metrics.usage_count(:my_feature)
|
|
370
|
+
Magick.performance_metrics.most_used_features(limit: 10)
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
**Configuration:**
|
|
330
374
|
|
|
331
|
-
|
|
332
|
-
|
|
375
|
+
```ruby
|
|
376
|
+
Magick.configure do
|
|
377
|
+
performance_metrics(
|
|
378
|
+
enabled: true,
|
|
379
|
+
redis_tracking: true, # Auto-enabled if Redis is configured
|
|
380
|
+
batch_size: 100, # Flush after 100 updates
|
|
381
|
+
flush_interval: 60 # Or flush every 60 seconds
|
|
382
|
+
)
|
|
383
|
+
end
|
|
333
384
|
```
|
|
334
385
|
|
|
386
|
+
**Note:** When `redis_tracking: true` is set, usage counts are persisted to Redis and aggregated across all processes, giving you total usage statistics.
|
|
387
|
+
|
|
335
388
|
#### Audit Logging
|
|
336
389
|
|
|
337
390
|
```ruby
|
|
@@ -351,7 +404,10 @@ Magick uses a dual-adapter strategy:
|
|
|
351
404
|
1. **Memory Adapter**: Fast, in-memory storage with TTL support
|
|
352
405
|
2. **Redis Adapter**: Persistent storage for distributed systems (optional)
|
|
353
406
|
|
|
354
|
-
The registry automatically falls back from memory to Redis if a feature isn't found in memory. When features are updated
|
|
407
|
+
The registry automatically falls back from memory to Redis if a feature isn't found in memory. When features are updated:
|
|
408
|
+
- Both adapters are updated simultaneously
|
|
409
|
+
- Cache invalidation messages are published via Redis Pub/Sub to notify other processes
|
|
410
|
+
- Targeting updates trigger immediate cache invalidation to ensure consistency
|
|
355
411
|
|
|
356
412
|
#### Memory-Only Mode
|
|
357
413
|
|
|
@@ -86,6 +86,12 @@ module Magick
|
|
|
86
86
|
(memory_features + redis_features).uniq
|
|
87
87
|
end
|
|
88
88
|
|
|
89
|
+
# Explicitly trigger cache invalidation for a feature
|
|
90
|
+
# This is useful for targeting updates that need immediate cache invalidation
|
|
91
|
+
def invalidate_cache(feature_name)
|
|
92
|
+
publish_cache_invalidation(feature_name)
|
|
93
|
+
end
|
|
94
|
+
|
|
89
95
|
private
|
|
90
96
|
|
|
91
97
|
attr_reader :memory_adapter, :redis_adapter, :circuit_breaker
|
data/lib/magick/config.rb
CHANGED
|
@@ -48,12 +48,15 @@ module Magick
|
|
|
48
48
|
return unless enabled
|
|
49
49
|
|
|
50
50
|
@performance_metrics = PerformanceMetrics.new(batch_size: batch_size, flush_interval: flush_interval)
|
|
51
|
-
#
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
51
|
+
# Store redis_tracking preference for later application
|
|
52
|
+
@performance_metrics_redis_tracking = redis_tracking
|
|
53
|
+
# Enable Redis tracking if explicitly set to true, otherwise defer to apply! method
|
|
54
|
+
if redis_tracking == true
|
|
55
|
+
@performance_metrics.enable_redis_tracking(enable: true)
|
|
56
|
+
elsif redis_tracking == false
|
|
57
|
+
@performance_metrics.enable_redis_tracking(enable: false)
|
|
55
58
|
end
|
|
56
|
-
|
|
59
|
+
# If nil, will be auto-determined in apply! method
|
|
57
60
|
end
|
|
58
61
|
|
|
59
62
|
def audit_log(enabled: true, adapter: nil)
|
|
@@ -95,9 +98,15 @@ module Magick
|
|
|
95
98
|
Magick.versioning = versioning if versioning
|
|
96
99
|
Magick.warn_on_deprecated = warn_on_deprecated
|
|
97
100
|
|
|
98
|
-
# Enable Redis tracking for performance metrics
|
|
99
|
-
if Magick.performance_metrics
|
|
100
|
-
|
|
101
|
+
# Enable Redis tracking for performance metrics
|
|
102
|
+
if Magick.performance_metrics
|
|
103
|
+
# If redis_tracking was explicitly set, use that value
|
|
104
|
+
if defined?(@performance_metrics_redis_tracking) && !@performance_metrics_redis_tracking.nil?
|
|
105
|
+
Magick.performance_metrics.enable_redis_tracking(enable: @performance_metrics_redis_tracking)
|
|
106
|
+
# Otherwise, auto-enable if Redis adapter is configured
|
|
107
|
+
elsif adapter_registry.is_a?(Adapters::Registry) && adapter_registry.redis_adapter
|
|
108
|
+
Magick.performance_metrics.enable_redis_tracking(enable: true)
|
|
109
|
+
end
|
|
101
110
|
end
|
|
102
111
|
end
|
|
103
112
|
|
data/lib/magick/feature.rb
CHANGED
|
@@ -74,7 +74,7 @@ module Magick
|
|
|
74
74
|
return false if status == :inactive
|
|
75
75
|
return false if status == :deprecated && !context[:allow_deprecated]
|
|
76
76
|
|
|
77
|
-
#
|
|
77
|
+
# NOTE: We don't check dependencies here because:
|
|
78
78
|
# - Main features can be enabled independently
|
|
79
79
|
# - Dependencies are only prevented from being enabled if the main feature is disabled
|
|
80
80
|
# - The dependency check is handled in the enable() method, not in enabled?()
|
|
@@ -463,6 +463,7 @@ module Magick
|
|
|
463
463
|
def delete
|
|
464
464
|
adapter_registry.delete(name)
|
|
465
465
|
@stored_value = nil
|
|
466
|
+
@stored_value_initialized = false # Reset initialization flag so get_value returns default_value
|
|
466
467
|
@targeting = {}
|
|
467
468
|
# Also remove from Magick.features if registered
|
|
468
469
|
Magick.features.delete(name.to_s)
|
|
@@ -822,11 +823,20 @@ module Magick
|
|
|
822
823
|
end
|
|
823
824
|
|
|
824
825
|
def save_targeting
|
|
826
|
+
# Save targeting to adapter (this triggers cache invalidation via Pub/Sub)
|
|
825
827
|
adapter_registry.set(name, 'targeting', targeting)
|
|
828
|
+
|
|
826
829
|
# Update the feature in Magick.features if it's registered
|
|
827
830
|
return unless Magick.features.key?(name)
|
|
828
831
|
|
|
829
832
|
Magick.features[name].instance_variable_set(:@targeting, targeting.dup)
|
|
833
|
+
|
|
834
|
+
# Explicitly trigger cache invalidation for targeting updates
|
|
835
|
+
# Targeting changes affect enabled? checks, so we need immediate cache invalidation
|
|
836
|
+
# even if async updates are enabled
|
|
837
|
+
return unless adapter_registry.respond_to?(:invalidate_cache)
|
|
838
|
+
|
|
839
|
+
adapter_registry.invalidate_cache(name)
|
|
830
840
|
end
|
|
831
841
|
|
|
832
842
|
def default_for_type
|
data/lib/magick/version.rb
CHANGED
data/lib/magick.rb
CHANGED
|
@@ -128,6 +128,38 @@ module Magick
|
|
|
128
128
|
@versioning ||= Versioning.new(adapter_registry || default_adapter_registry)
|
|
129
129
|
end
|
|
130
130
|
|
|
131
|
+
# Get total usage count for a feature (combines memory and Redis counts)
|
|
132
|
+
def feature_stats(feature_name)
|
|
133
|
+
return {} unless performance_metrics
|
|
134
|
+
|
|
135
|
+
{
|
|
136
|
+
usage_count: performance_metrics.usage_count(feature_name),
|
|
137
|
+
average_duration: performance_metrics.average_duration(feature_name: feature_name),
|
|
138
|
+
average_duration_by_operation: {
|
|
139
|
+
enabled: performance_metrics.average_duration(feature_name: feature_name, operation: 'enabled?'),
|
|
140
|
+
value: performance_metrics.average_duration(feature_name: feature_name, operation: 'value'),
|
|
141
|
+
get_value: performance_metrics.average_duration(feature_name: feature_name, operation: 'get_value')
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Get usage count for a feature
|
|
147
|
+
def feature_usage_count(feature_name)
|
|
148
|
+
performance_metrics&.usage_count(feature_name) || 0
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Get average duration for a feature (optionally filtered by operation)
|
|
152
|
+
def feature_average_duration(feature_name, operation: nil)
|
|
153
|
+
return 0.0 unless performance_metrics
|
|
154
|
+
|
|
155
|
+
performance_metrics.average_duration(feature_name: feature_name, operation: operation)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Get most used features
|
|
159
|
+
def most_used_features(limit: 10)
|
|
160
|
+
performance_metrics&.most_used_features(limit: limit) || {}
|
|
161
|
+
end
|
|
162
|
+
|
|
131
163
|
def reset!
|
|
132
164
|
@features = {}
|
|
133
165
|
@adapter_registry = nil
|