Instrumenting cache stores.

This commit is contained in:
José Valim 2009-09-20 12:07:21 -03:00
parent 09f798ba18
commit 4215e9ab93
4 changed files with 84 additions and 78 deletions

@ -107,16 +107,14 @@ def self.expand_cache_key(key, namespace = nil)
class Store class Store
cattr_accessor :logger cattr_accessor :logger
attr_reader :silence, :logger_off attr_reader :silence
alias :silence? :silence
def silence! def silence!
@silence = true @silence = true
self self
end end
alias silence? silence
alias logger_off? logger_off
# Fetches data from the cache, using the given key. If there is data in # Fetches data from the cache, using the given key. If there is data in
# the cache with the given key, then that data is returned. # the cache with the given key, then that data is returned.
# #
@ -157,26 +155,13 @@ def silence!
# cache.fetch("foo") # => "bar" # cache.fetch("foo") # => "bar"
# sleep(6) # sleep(6)
# cache.fetch("foo") # => nil # cache.fetch("foo") # => nil
def fetch(key, options = {}) def fetch(key, options = {}, &block)
@logger_off = true
if !options[:force] && value = read(key, options) if !options[:force] && value = read(key, options)
@logger_off = false
log("hit", key, options)
value value
elsif block_given? elsif block_given?
@logger_off = false result = instrument(:generate, key, options, &block)
log("miss", key, options) write(key, result, options)
result
value = nil
ms = Benchmark.ms { value = yield }
@logger_off = true
write(key, value, options)
@logger_off = false
log('write (will save %.2fms)' % ms, key, nil)
value
end end
end end
@ -191,8 +176,8 @@ def fetch(key, options = {})
# For example, FileStore supports the +:expires_in+ option, which # For example, FileStore supports the +:expires_in+ option, which
# makes the method return nil for cache items older than the specified # makes the method return nil for cache items older than the specified
# period. # period.
def read(key, options = nil) def read(key, options = nil, &block)
log("read", key, options) instrument(:read, key, options, &block)
end end
# Writes the given value to the cache, with the given key. # Writes the given value to the cache, with the given key.
@ -210,20 +195,20 @@ def read(key, options = nil)
# cache.read("foo") # => "bar" # cache.read("foo") # => "bar"
# sleep(6) # sleep(6)
# cache.read("foo") # => nil # cache.read("foo") # => nil
def write(key, value, options = nil) def write(key, value, options = nil, &block)
log("write", key, options) instrument(:write, key, options, &block)
end end
def delete(key, options = nil) def delete(key, options = nil, &block)
log("delete", key, options) instrument(:delete, key, options, &block)
end end
def delete_matched(matcher, options = nil) def delete_matched(matcher, options = nil, &block)
log("delete matched", matcher.inspect, options) instrument(:delete_matched, matcher.inspect, options, &block)
end end
def exist?(key, options = nil) def exist?(key, options = nil, &block)
log("exist?", key, options) instrument(:exist?, key, options, &block)
end end
def increment(key, amount = 1) def increment(key, amount = 1)
@ -247,14 +232,21 @@ def decrement(key, amount = 1)
private private
def expires_in(options) def expires_in(options)
expires_in = options && options[:expires_in] expires_in = options && options[:expires_in]
raise ":expires_in must be a number" if expires_in && !expires_in.is_a?(Numeric) raise ":expires_in must be a number" if expires_in && !expires_in.is_a?(Numeric)
expires_in || 0 expires_in || 0
end end
def instrument(operation, key, options, &block)
payload = { :key => key }
payload.merge!(options) if options.is_a?(Hash)
event = ActiveSupport::Orchestra.instrument(:"cache_#{operation}", payload, &block)
log("#{operation} (%.1fms)" % event.duration, key, options)
event.result
end
def log(operation, key, options) def log(operation, key, options)
logger.debug("Cache #{operation}: #{key}#{options ? " (#{options.inspect})" : ""}") if logger && !silence? && !logger_off? logger.debug("Cache #{operation}: #{key}#{options ? " (#{options.inspect})" : ""}") if logger && !silence?
end end
end end
end end

@ -16,8 +16,7 @@ def initialize(cache_path)
# - +:expires_in+ - the number of seconds that this value may stay in # - +:expires_in+ - the number of seconds that this value may stay in
# the cache. # the cache.
def read(name, options = nil) def read(name, options = nil)
super super do
file_name = real_file_path(name) file_name = real_file_path(name)
expires = expires_in(options) expires = expires_in(options)
@ -25,26 +24,29 @@ def read(name, options = nil)
File.open(file_name, 'rb') { |f| Marshal.load(f) } File.open(file_name, 'rb') { |f| Marshal.load(f) }
end end
end end
end
# Writes a value to the cache. # Writes a value to the cache.
def write(name, value, options = nil) def write(name, value, options = nil)
super super do
ensure_cache_path(File.dirname(real_file_path(name))) ensure_cache_path(File.dirname(real_file_path(name)))
File.atomic_write(real_file_path(name), cache_path) { |f| Marshal.dump(value, f) } File.atomic_write(real_file_path(name), cache_path) { |f| Marshal.dump(value, f) }
value value
end
rescue => e rescue => e
logger.error "Couldn't create cache directory: #{name} (#{e.message})" if logger logger.error "Couldn't create cache directory: #{name} (#{e.message})" if logger
end end
def delete(name, options = nil) def delete(name, options = nil)
super super do
File.delete(real_file_path(name)) File.delete(real_file_path(name))
end
rescue SystemCallError => e rescue SystemCallError => e
# If there's no cache, then there's nothing to complain about # If there's no cache, then there's nothing to complain about
end end
def delete_matched(matcher, options = nil) def delete_matched(matcher, options = nil)
super super do
search_dir(@cache_path) do |f| search_dir(@cache_path) do |f|
if f =~ matcher if f =~ matcher
begin begin
@ -55,11 +57,13 @@ def delete_matched(matcher, options = nil)
end end
end end
end end
end
def exist?(name, options = nil) def exist?(name, options = nil)
super super do
File.exist?(real_file_path(name)) File.exist?(real_file_path(name))
end end
end
private private
def real_file_path(name) def real_file_path(name)

@ -54,8 +54,9 @@ def read_multi(*keys)
end end
def read(key, options = nil) # :nodoc: def read(key, options = nil) # :nodoc:
super super do
@data.get(key, raw?(options)) @data.get(key, raw?(options))
end
rescue MemCache::MemCacheError => e rescue MemCache::MemCacheError => e
logger.error("MemCacheError (#{e}): #{e.message}") logger.error("MemCacheError (#{e}): #{e.message}")
nil nil
@ -69,22 +70,24 @@ def read(key, options = nil) # :nodoc:
# - <tt>:expires_in</tt> - the number of seconds that this value may stay in # - <tt>:expires_in</tt> - the number of seconds that this value may stay in
# the cache. See ActiveSupport::Cache::Store#write for an example. # the cache. See ActiveSupport::Cache::Store#write for an example.
def write(key, value, options = nil) def write(key, value, options = nil)
super super do
method = options && options[:unless_exist] ? :add : :set method = options && options[:unless_exist] ? :add : :set
# memcache-client will break the connection if you send it an integer # memcache-client will break the connection if you send it an integer
# in raw mode, so we convert it to a string to be sure it continues working. # in raw mode, so we convert it to a string to be sure it continues working.
value = value.to_s if raw?(options) value = value.to_s if raw?(options)
response = @data.send(method, key, value, expires_in(options), raw?(options)) response = @data.send(method, key, value, expires_in(options), raw?(options))
response == Response::STORED response == Response::STORED
end
rescue MemCache::MemCacheError => e rescue MemCache::MemCacheError => e
logger.error("MemCacheError (#{e}): #{e.message}") logger.error("MemCacheError (#{e}): #{e.message}")
false false
end end
def delete(key, options = nil) # :nodoc: def delete(key, options = nil) # :nodoc:
super super do
response = @data.delete(key, expires_in(options)) response = @data.delete(key, expires_in(options))
response == Response::DELETED response == Response::DELETED
end
rescue MemCache::MemCacheError => e rescue MemCache::MemCacheError => e
logger.error("MemCacheError (#{e}): #{e.message}") logger.error("MemCacheError (#{e}): #{e.message}")
false false
@ -94,8 +97,10 @@ def exist?(key, options = nil) # :nodoc:
# Doesn't call super, cause exist? in memcache is in fact a read # Doesn't call super, cause exist? in memcache is in fact a read
# But who cares? Reading is very fast anyway # But who cares? Reading is very fast anyway
# Local cache is checked first, if it doesn't know then memcache itself is read from # Local cache is checked first, if it doesn't know then memcache itself is read from
super do
!read(key, options).nil? !read(key, options).nil?
end end
end
def increment(key, amount = 1) # :nodoc: def increment(key, amount = 1) # :nodoc:
log("incrementing", key, amount) log("incrementing", key, amount)

@ -20,29 +20,34 @@ def initialize
end end
def read(name, options = nil) def read(name, options = nil)
super super do
@data[name] @data[name]
end end
end
def write(name, value, options = nil) def write(name, value, options = nil)
super super do
@data[name] = (value.duplicable? ? value.dup : value).freeze @data[name] = (value.duplicable? ? value.dup : value).freeze
end end
end
def delete(name, options = nil) def delete(name, options = nil)
super super do
@data.delete(name) @data.delete(name)
end end
end
def delete_matched(matcher, options = nil) def delete_matched(matcher, options = nil)
super super do
@data.delete_if { |k,v| k =~ matcher } @data.delete_if { |k,v| k =~ matcher }
end end
end
def exist?(name,options = nil) def exist?(name,options = nil)
super super do
@data.has_key?(name) @data.has_key?(name)
end end
end
def clear def clear
@data.clear @data.clear