├── Rakefile ├── lib ├── puff │ ├── version.rb │ ├── instrumentation │ │ ├── redis.rb │ │ ├── log_subscriber.rb │ │ └── controller_runtime.rb │ └── engine.rb └── puff.rb ├── Gemfile ├── CHANGELOG.md ├── .gitignore ├── README.md ├── puff.gemspec └── LICENSE.txt /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | -------------------------------------------------------------------------------- /lib/puff/version.rb: -------------------------------------------------------------------------------- 1 | module Puff 2 | VERSION = "0.4.0" 3 | end 4 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in puff.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /lib/puff.rb: -------------------------------------------------------------------------------- 1 | require 'redis' 2 | 3 | require 'puff/version' 4 | require 'puff/engine' 5 | require 'puff/instrumentation/log_subscriber' 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.4.0 2 | 3 | * Support Rails 4 4 | * **Breaking**: Remove Redis singleton along with configuration. Use `Redis.current` and `ENV["REDIS_URL"]` instead. 5 | * Remove hiredis 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .yardoc 6 | Gemfile.lock 7 | InstalledFiles 8 | _yardoc 9 | coverage 10 | doc/ 11 | lib/bundler/man 12 | pkg 13 | rdoc 14 | spec/reports 15 | test/tmp 16 | test/version_tmp 17 | tmp 18 | -------------------------------------------------------------------------------- /lib/puff/instrumentation/redis.rb: -------------------------------------------------------------------------------- 1 | # Monkey Patching for profit. 2 | 3 | class Redis 4 | class Client 5 | alias :old_logging :logging 6 | 7 | def logging(commands, &block) 8 | ::ActiveSupport::Notifications.instrument('request.redis', commands: commands) do 9 | return old_logging(commands, &block) 10 | end 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/puff/engine.rb: -------------------------------------------------------------------------------- 1 | require 'puff/instrumentation/log_subscriber' 2 | require 'puff/instrumentation/controller_runtime' 3 | 4 | module Puff 5 | class Engine < ::Rails::Engine 6 | initializer "puff.Instrumentation" do 7 | Puff::Instrumentation::LogSubscriber.attach_to(:redis) 8 | 9 | ActiveSupport.on_load(:action_controller) do 10 | include Puff::Instrumentation::ControllerRuntime 11 | end 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Puff 2 | 3 | Puff provides Redis instrumentation for Rails. 4 | 5 | ## Features 6 | 7 | * Rails instrumentation (Using ActiveSupport::Notifications) 8 | 9 | ## Usage 10 | 11 | Just add it to your `Gemfile`, done. 12 | 13 | ```ruby 14 | gem 'puff' 15 | ``` 16 | 17 | The included Rails engine will automatically start adding instrumentation to your log files without the need to do anything further. 18 | 19 | ## License 20 | 21 | Copyright (c) 2013-2016 Polydice, Inc. See LICENSE.txt for details. 22 | -------------------------------------------------------------------------------- /puff.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'puff/version' 5 | 6 | Gem::Specification.new do |gem| 7 | gem.name = "puff" 8 | gem.version = Puff::VERSION 9 | gem.authors = ["Richard Lee"] 10 | gem.email = ["rl@polydice.com"] 11 | gem.description = %q{Puff provides Redis instrumentation for Rails.} 12 | gem.summary = %q{Elagant Redis solution for Rails.} 13 | gem.homepage = "https://github.com/polydice/puff" 14 | 15 | gem.files = `git ls-files`.split($/) 16 | gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) } 17 | gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) 18 | gem.require_paths = ["lib"] 19 | 20 | gem.add_dependency "railties", "~> 4" 21 | gem.add_dependency "activesupport", "~> 4" 22 | gem.add_dependency "redis", "~> 3.0" 23 | 24 | gem.add_development_dependency "rake", "~> 10.0" 25 | end 26 | -------------------------------------------------------------------------------- /lib/puff/instrumentation/log_subscriber.rb: -------------------------------------------------------------------------------- 1 | require 'puff/instrumentation/redis' 2 | 3 | module Puff 4 | module Instrumentation 5 | class LogSubscriber < ActiveSupport::LogSubscriber 6 | def self.runtime=(value) 7 | Thread.current["redis_runtime"] = value 8 | end 9 | 10 | def self.runtime 11 | Thread.current["redis_runtime"] ||= 0 12 | end 13 | 14 | def self.reset_runtime 15 | rt, self.runtime = runtime, 0 16 | rt 17 | end 18 | 19 | def request(event) 20 | self.class.runtime += event.duration 21 | return unless logger.debug? 22 | 23 | name = "%s (%.2fms)" % ["Redis", event.duration] 24 | cmds = event.payload[:commands] 25 | 26 | output = " #{color(name, RED, true)}" 27 | 28 | cmds.each do |name, *args| 29 | if args.present? 30 | output << " #{name.to_s.upcase} #{args.join(" ")}" 31 | else 32 | output << " #{name.to_s.upcase}" 33 | end 34 | end 35 | 36 | debug output 37 | end 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Polydice, Inc. 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /lib/puff/instrumentation/controller_runtime.rb: -------------------------------------------------------------------------------- 1 | module Puff 2 | module Instrumentation 3 | module ControllerRuntime 4 | extend ActiveSupport::Concern 5 | 6 | protected 7 | 8 | attr_internal :redis_runtime_before_render 9 | attr_internal :redis_runtime_during_render 10 | 11 | def cleanup_view_runtime 12 | self.redis_runtime_before_render = Puff::Instrumentation::LogSubscriber.reset_runtime 13 | runtime = super 14 | self.redis_runtime_during_render = Puff::Instrumentation::LogSubscriber.reset_runtime 15 | runtime - redis_runtime_during_render 16 | end 17 | 18 | def append_info_to_payload(payload) 19 | super 20 | payload[:redis_runtime] = (redis_runtime_before_render || 0) + 21 | (redis_runtime_during_render || 0) + 22 | Puff::Instrumentation::LogSubscriber.reset_runtime 23 | end 24 | 25 | module ClassMethods 26 | def log_process_action(payload) 27 | messages, redis_runtime = super, payload[:redis_runtime] 28 | messages << ("Redis: %.1fms" % redis_runtime.to_f) if redis_runtime 29 | messages 30 | end 31 | end 32 | end 33 | end 34 | end 35 | --------------------------------------------------------------------------------