Module: CacheStache::Instrumentation
- Defined in:
- lib/cache_stache/instrumentation.rb
Constant Summary collapse
- INTERNAL_OPERATION_KEY =
Thread-local key to track when CacheStache is performing internal operations
:cache_stache_internal_operation- AFTER_REPLY_QUEUE_KEY =
:cache_stache_after_reply_queue- MAX_AFTER_REPLY_EVENTS =
1000
Class Attribute Summary collapse
-
.monitored_store_class ⇒ Object
readonly
Returns the value of attribute monitored_store_class.
Class Method Summary collapse
- .call(_name, _start, _finish, _id, payload) ⇒ Object
- .flush_after_reply_queue! ⇒ Object
- .install! ⇒ Object
-
.internal_operation? ⇒ Boolean
Returns true if we’re currently inside an internal CacheStache operation.
- .reset! ⇒ Object
-
.without_instrumentation ⇒ Object
Execute a block while marking it as an internal CacheStache operation.
Class Attribute Details
.monitored_store_class ⇒ Object (readonly)
Returns the value of attribute monitored_store_class.
11 12 13 |
# File 'lib/cache_stache/instrumentation.rb', line 11 def monitored_store_class @monitored_store_class end |
Class Method Details
.call(_name, _start, _finish, _id, payload) ⇒ Object
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
# File 'lib/cache_stache/instrumentation.rb', line 55 def call(_name, _start, _finish, _id, payload) # Skip if this is an internal CacheStache operation return if internal_operation? # Only track events from Rails.cache, not other ActiveSupport::Cache instances return unless payload[:store] == @monitored_store_class key = payload[:key] || payload[:name] return unless key # Belt-and-suspenders: also skip by key prefix in case thread-local wasn't set return if key.to_s.start_with?("cache_stache:") # Skip event based on sample_rate (e.g., 0.5 means record 50% of events) sample_rate = CacheStache.configuration.sample_rate return if sample_rate < 1.0 && rand >= sample_rate # Record hit or miss bucket_ts = (Time.current.to_i / CacheStache.configuration.bucket_seconds) * CacheStache.configuration.bucket_seconds hit = payload[:hit] increments = { "overall:hits" => hit ? 1 : 0, "overall:misses" => hit ? 0 : 1 } # Add keyspace increments matching_keyspaces = CacheStache.configuration.matching_keyspaces(key) matching_keyspaces.each do |keyspace| increments["#{keyspace.name}:hits"] = hit ? 1 : 0 increments["#{keyspace.name}:misses"] = hit ? 0 : 1 end # Filter out zero-valued fields to reduce write amplification increments.reject! { |_k, v| v == 0 } if should_defer_instrumentation? enqueue_after_reply_event(bucket_ts, increments) else @cache_client.increment_stats(bucket_ts, increments) end rescue => e Rails.logger.error("CacheStache instrumentation error: #{e.class}: #{e.}") Rails.logger.error(e.backtrace.first(5).join("\n")) end |
.flush_after_reply_queue! ⇒ Object
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 |
# File 'lib/cache_stache/instrumentation.rb', line 100 def flush_after_reply_queue! return unless @cache_client queue = Thread.current[AFTER_REPLY_QUEUE_KEY] return unless queue.is_a?(Array) && !queue.empty? max = MAX_AFTER_REPLY_EVENTS dropped = [queue.size - max, 0].max events = queue.shift([queue.size, max].min) queue.clear Rails.logger.warn("CacheStache: Dropped #{dropped} after-reply events") if dropped.positive? combined = {} events.each do |(bucket_ts, increments)| combined[bucket_ts] ||= Hash.new(0) increments.each do |field, value| combined[bucket_ts][field] += value end end combined.each do |bucket_ts, increments| # Filter out zero-valued fields to reduce write amplification increments.reject! { |_k, v| v == 0 } @cache_client.increment_stats(bucket_ts, increments) end rescue => e Rails.logger.error("CacheStache after-reply flush error: #{e.class}: #{e.}") Rails.logger.error(e.backtrace.first(5).join("\n")) end |
.install! ⇒ Object
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
# File 'lib/cache_stache/instrumentation.rb', line 19 def install! return unless CacheStache.configuration.enabled return if @installed # Store cache_client as a class instance variable @cache_client = CacheClient.new @cache_client. # Capture the Rails.cache store class name to filter events. # Note: This filters by class name, not instance. If multiple stores # use the same class (e.g., two RedisCacheStore instances), events # from all of them will be tracked. @monitored_store_class = Rails.cache.class.name # Subscribe to cache read events only (hits and misses) ActiveSupport::Notifications.subscribe("cache_read.active_support", self) @installed = true Rails.logger.info("CacheStache: Instrumentation installed for #{@monitored_store_class}") end |
.internal_operation? ⇒ Boolean
Returns true if we’re currently inside an internal CacheStache operation
51 52 53 |
# File 'lib/cache_stache/instrumentation.rb', line 51 def internal_operation? Thread.current[INTERNAL_OPERATION_KEY] == true end |
.reset! ⇒ Object
13 14 15 16 17 |
# File 'lib/cache_stache/instrumentation.rb', line 13 def reset! @installed = false @monitored_store_class = nil @cache_client = nil end |
.without_instrumentation ⇒ Object
Execute a block while marking it as an internal CacheStache operation. Cache operations inside this block will be ignored by instrumentation.
42 43 44 45 46 47 48 |
# File 'lib/cache_stache/instrumentation.rb', line 42 def without_instrumentation previous_value = Thread.current[INTERNAL_OPERATION_KEY] Thread.current[INTERNAL_OPERATION_KEY] = true yield ensure Thread.current[INTERNAL_OPERATION_KEY] = previous_value end |