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 +2 -0
- data/Gemfile +9 -0
- data/README.md +22 -0
- data/lib/rack-request-profiler.rb +12 -0
- data/lib/rack/profilers/stathat.rb +32 -0
- data/lib/rack/profilers/statsd.rb +28 -0
- data/lib/rack/request-profiler.rb +44 -0
- data/lib/rack/request-profiler/version.rb +5 -0
- data/lib/rack/utils/url_stripper.rb +11 -0
- data/rack-request-profiler.gemspec +19 -0
- data/spec/rack/profilers/stathat_spec.rb +14 -0
- data/spec/rack/profilers/statsd_spec.rb +27 -0
- data/spec/rack/request-profiler_spec.rb +35 -0
- data/spec/rack/utils/url_stripper_spec.rb +13 -0
- data/spec/spec_helper.rb +14 -0
- metadata +66 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
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,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
|
data/spec/spec_helper.rb
ADDED
@@ -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
|