rack-request-profiler 0.1.0

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.
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ .rspec
2
+ Gemfile.lock
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source :rubygems
2
+
3
+ group :test do
4
+ gem 'em-stathat'
5
+ gem 'rack'
6
+ gem 'rspec', '>= 2.7'
7
+ gem 'rack-test', :require => 'rack/test'
8
+ gem 'statsd-ruby', :require => 'statsd'
9
+ end
data/README.md ADDED
@@ -0,0 +1,22 @@
1
+ This project provides a simple "framework" for profiling request / response times, and sending the data to another service. It includes a base class `Rack::RequestProfiler` for handling the logic of wrapping and timing the request / response cycle in a rack app.
2
+
3
+ By default, the `Rack::RequestProfiler` middleware does not do anything with the profiling data. Instead, this logic must be implemented by subclasses by defining the `handle_results` instance method. For example, you might send profiling data to an external web service (stathat, papertrail, loggly, etc), statsd, write to a logfile on disk, put it in a persistent store like redis or mongo, or really anything else your heart desires.
4
+
5
+ This project currently provides profiler middlewares for stathat and statsd. If you'd like to contribute a profiler middleware, pull requests are welcome.
6
+
7
+ ## Installation
8
+
9
+ `gem install rack-request_profiler`
10
+
11
+ ## Usage
12
+
13
+ Simply include one of the profiler middlewares into the middleware stack in any rack-compatible application like so:
14
+
15
+ ```ruby
16
+ use Rack::Profilers::Statsd, Statsd.new('localhost'), :ignore_path => /^\/assets/
17
+ ```
18
+
19
+ ## Profilers
20
+
21
+ * Statsd - Uses the [statsd ruby client](https://github.com/reinh/statsd-ruby) to send data to statsd / graphite.
22
+ * Stathat - Uses the [em-stathat gem](https://github.com/ajsharp/em-stathat) to send data to stathat asynchronously via eventmachine.
@@ -0,0 +1,12 @@
1
+ module Rack
2
+ autoload :RequestProfiler, 'rack/request-profiler'
3
+
4
+ module Profilers
5
+ autoload :Statsd, 'rack/profilers/statsd'
6
+ autoload :Stathat, 'rack/profilers/stathat'
7
+ end
8
+
9
+ module Utils
10
+ autoload :UrlStripper, 'rack/utils/url_stripper'
11
+ end
12
+ end
@@ -0,0 +1,32 @@
1
+ require 'rack/request-profiler'
2
+ require 'em-stathat'
3
+
4
+ module Rack
5
+ module Profilers
6
+
7
+ # NOTE: This middleware expects that you are running an EventMachine
8
+ # reactor in the current process, and should not be used otherwise.
9
+ class Stathat < RequestProfiler
10
+
11
+ # @param [Hash] opts
12
+ # @option [Proc] :errback errback passed to the eventmachine instance
13
+ # @option [Proc] :callback callback passed to the eventmachine instance
14
+ def initialize(app, opts = {})
15
+ @app, @opts = app, opts
16
+ end
17
+
18
+ # @param [Hash] env the rack env
19
+ # @param [Rack::Request] request a request object constructed
20
+ # from env
21
+ def handle_results(env, request)
22
+ clean_url = Utils::UrlStripper.replace_id(request.path)
23
+
24
+ EM.next_tick do
25
+ deferrable = EM::StatHat.new.ez_value("#{request.request_method} #{clean_url}", run_time)
26
+ deferrable.callback(&@opts[:callback]) if @opts[:callback]
27
+ deferrable.errback(&@opts[:errback]) if @opts[:errback]
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,28 @@
1
+ require 'rack/request-profiler'
2
+ require 'statsd'
3
+
4
+ module Rack
5
+ module Profilers
6
+ class Statsd < RequestProfiler
7
+
8
+ # @example
9
+ # use Rack::Profilers::Statsd, Statsd.new('localhost'), :ignore_path => /^\/assets/
10
+ #
11
+ # @param [Statsd] statsd an instance of your statsd client
12
+ # @param [Hash] opts a hash of options
13
+ # @option :namespace namespce string for statsd
14
+ # @option :ignore_path request paths to ignore and not handle
15
+ def initialize(app, statsd, opts = {})
16
+ @app, @statsd, @opts = app, statsd, opts
17
+ end
18
+
19
+ def handle_results(env, request)
20
+ clean_url = Utils::UrlStripper.replace_id(request.path)
21
+ clean_url = clean_url[1..-1].gsub('/', '.')
22
+ namespace = @opts[:namespace] || ''
23
+
24
+ @statsd.timing("#{namespace}#{request.request_method}.#{clean_url}", run_time)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,44 @@
1
+ module Rack
2
+ class RequestProfiler
3
+
4
+ # @param [Hash] opts a hash of options
5
+ # @option [Symbol] :ignore_path Request paths to ignore and not handle
6
+ def initialize(app, opts = {})
7
+ @app, @opts = app, opts
8
+ end
9
+
10
+ def start_time=(time)
11
+ @start_time = time
12
+ end
13
+
14
+ def start_time
15
+ @start_time
16
+ end
17
+
18
+ def call(env, &block)
19
+ self.start_time = Time.now
20
+ request = Request.new(env)
21
+
22
+ # Short-circuit here and pass the response back through the
23
+ # middleware chain if the request path matches the ignore_path
24
+ if @opts[:ignore_path] && request.path.match(@opts[:ignore_path])
25
+ return @app.call(env)
26
+ end
27
+
28
+ status, headers, body = @app.call(env)
29
+
30
+ handle_results(env, request)
31
+ [status, headers, body]
32
+ end
33
+
34
+ # Override this method in subclasses
35
+ def handle_results(env, request = nil)
36
+ # override me!
37
+ end
38
+
39
+ # Returns elapsed time since start_time in milliseconds.
40
+ def run_time
41
+ ((Time.now - start_time) * 1000).round
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,5 @@
1
+ module Rack
2
+ class RequestProfiler
3
+ VERSION = '0.1.0'
4
+ end
5
+ end
@@ -0,0 +1,11 @@
1
+ module Rack
2
+ module Utils
3
+ class UrlStripper
4
+ ID_PATTERN = /([0-9][a-z][^\/]+)/
5
+
6
+ def self.replace_id(url)
7
+ url.gsub(ID_PATTERN, 'ID')
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,19 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "rack/request-profiler/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "rack-request-profiler"
7
+ s.version = Rack::RequestProfiler::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Alex Sharp"]
10
+ s.email = ["ajsharp@gmail.com"]
11
+ s.homepage = "https://github.com/ajsharp/rack-request-profiler"
12
+ s.summary = %q{Rack middleware for profiling request / response cycles.}
13
+ s.description = %q{Provides a framework for sending wall time statistics to external services, such as statsd, stathat, etc.}
14
+
15
+ s.files = `git ls-files`.split("\n")
16
+ s.test_files = `git ls-files -- {spec,features}/*`.split("\n")
17
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18
+ s.require_paths = ["lib"]
19
+ end
@@ -0,0 +1,14 @@
1
+ require 'spec_helper'
2
+
3
+ describe Rack::Profilers::Stathat do
4
+ it "accepts callbacks" do
5
+ EM.run {
6
+ app = Rack::Builder.app do
7
+ use Rack::Profilers::Stathat, :callback => lambda { |req| puts("hey"); EM.stop }
8
+
9
+ run lambda { |env| [200, {'Content-Type' => 'text/plain'}, ['']]}
10
+ end
11
+ req = Rack::MockRequest.new(app).get('/')
12
+ }
13
+ end
14
+ end
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+
3
+ describe Rack::Profilers::Statsd do
4
+ include Rack::Test::Methods
5
+ $statsd = Statsd.new 'localhost'
6
+
7
+ it "sends stuff to statsd" do
8
+ app = Rack::Builder.app do
9
+ use Rack::Profilers::Statsd, $statsd
10
+ run lambda { |env| [200, {}, ['']]}
11
+ end
12
+
13
+ obj = mock("object", :timing => true)
14
+ $statsd.should_receive(:timing)
15
+ response = Rack::MockRequest.new(app).get('/')
16
+ end
17
+
18
+ it "does not send stuff for ignored paths" do
19
+ app = Rack::Builder.app do
20
+ use Rack::Profilers::Statsd, $statsd, :ignore_path => /ignore-me/
21
+ run lambda { |env| [200, {}, ['']]}
22
+ end
23
+
24
+ $statsd.should_not_receive(:timing)
25
+ Rack::MockRequest.new(app).get('/ignore-me')
26
+ end
27
+ end
@@ -0,0 +1,35 @@
1
+ require 'spec_helper'
2
+
3
+ describe Rack::RequestProfiler do
4
+ include Rack::Test::Methods
5
+
6
+ it "implements the handle_results interface" do
7
+ Rack::RequestProfiler.new(lambda {}).should respond_to :handle_results
8
+ end
9
+
10
+ it "invokes the handle_results interface" do
11
+ class Rack::CustomProfiler < Rack::RequestProfiler
12
+ def handle_results(env)
13
+ end
14
+ end
15
+ app = Rack::Builder.app do
16
+ use Rack::CustomProfiler
17
+
18
+ run lambda { |env| [200, {'Content-Type' => 'text/plain'}, ['']]}
19
+ end
20
+
21
+ Rack::CustomProfiler.any_instance.should_receive(:handle_results)
22
+ Rack::MockRequest.new(app).get('/')
23
+ end
24
+
25
+ it "does not invoke handle_results if ignore_path matches" do
26
+ app = Rack::Builder.app do
27
+ use Rack::RequestProfiler, :ignore_path => /ignore_me/
28
+
29
+ run lambda { |env| [200, {'Content-Type' => 'text/plain'}, ['']]}
30
+ end
31
+
32
+ Rack::RequestProfiler.any_instance.should_not_receive(:handle_results)
33
+ Rack::MockRequest.new(app).get('/ignore_me')
34
+ end
35
+ end
@@ -0,0 +1,13 @@
1
+ require 'spec_helper'
2
+
3
+ describe Rack::Utils::UrlStripper do
4
+ describe '.replace_id' do
5
+ it 'replaces BSON-like ids with ID' do
6
+ Rack::Utils::UrlStripper.replace_id("/resource/4f07931e3641417a88000002").should == '/resource/ID'
7
+ end
8
+
9
+ it "replaces ids with sub-resources" do
10
+ Rack::Utils::UrlStripper.replace_id("/resource/4f07931e3641417a88000002/another").should == '/resource/ID/another'
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,14 @@
1
+ $:.push(File.dirname(File.expand_path(__FILE__)))
2
+
3
+ require File.join(File.expand_path(File.dirname(__FILE__)), '..', 'lib', 'rack-request-profiler')
4
+
5
+ require 'rack/test'
6
+ require 'em-stathat'
7
+
8
+ EventMachine::StatHat.config do |c|
9
+ c.ukey = 'key'
10
+ c.email = 'user@example.com'
11
+ end
12
+
13
+ RSpec.configure do |config|
14
+ end
metadata ADDED
@@ -0,0 +1,66 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rack-request-profiler
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Alex Sharp
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-01-09 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: Provides a framework for sending wall time statistics to external services,
15
+ such as statsd, stathat, etc.
16
+ email:
17
+ - ajsharp@gmail.com
18
+ executables: []
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - .gitignore
23
+ - Gemfile
24
+ - README.md
25
+ - lib/rack-request-profiler.rb
26
+ - lib/rack/profilers/stathat.rb
27
+ - lib/rack/profilers/statsd.rb
28
+ - lib/rack/request-profiler.rb
29
+ - lib/rack/request-profiler/version.rb
30
+ - lib/rack/utils/url_stripper.rb
31
+ - rack-request-profiler.gemspec
32
+ - spec/rack/profilers/stathat_spec.rb
33
+ - spec/rack/profilers/statsd_spec.rb
34
+ - spec/rack/request-profiler_spec.rb
35
+ - spec/rack/utils/url_stripper_spec.rb
36
+ - spec/spec_helper.rb
37
+ homepage: https://github.com/ajsharp/rack-request-profiler
38
+ licenses: []
39
+ post_install_message:
40
+ rdoc_options: []
41
+ require_paths:
42
+ - lib
43
+ required_ruby_version: !ruby/object:Gem::Requirement
44
+ none: false
45
+ requirements:
46
+ - - ! '>='
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ required_rubygems_version: !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ requirements: []
56
+ rubyforge_project:
57
+ rubygems_version: 1.8.10
58
+ signing_key:
59
+ specification_version: 3
60
+ summary: Rack middleware for profiling request / response cycles.
61
+ test_files:
62
+ - spec/rack/profilers/stathat_spec.rb
63
+ - spec/rack/profilers/statsd_spec.rb
64
+ - spec/rack/request-profiler_spec.rb
65
+ - spec/rack/utils/url_stripper_spec.rb
66
+ - spec/spec_helper.rb