rack-timer 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 093c4b4ce6a889fc7fb2fef289cca1a6cd3e1b4e
4
+ data.tar.gz: d87b254b2fdf74e78b4889430e065fb5499a8268
5
+ SHA512:
6
+ metadata.gz: 360878f72429257dfa302dfa81b8481ff365febdf66c1b93ca0da0189dec7f006ea6a0d6248691ee00740634675ce13680c08dd9e3a431abc7fbdce10da3b13d
7
+ data.tar.gz: 3fe7d3ea84d810875cc881f3fc529fa921b9e5ec8274507176feaa8766ae8ce3513722306d0d21c21086a738df1e4eb82016800ffcbadf9130db85bc3e7d1e89
@@ -0,0 +1,16 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ InstalledFiles
7
+ _yardoc
8
+ coverage
9
+ doc/
10
+ lib/bundler/man
11
+ pkg
12
+ rdoc
13
+ spec/reports
14
+ test/tmp
15
+ test/version_tmp
16
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
@@ -0,0 +1,5 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.1.0
4
+ - 2.0.0
5
+ - 1.9.3
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in rack-timer.gemspec
4
+ gemspec
@@ -0,0 +1,37 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ rack-timer (0.0.1)
5
+ rack
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ coderay (1.1.0)
11
+ diff-lcs (1.2.5)
12
+ method_source (0.8.2)
13
+ pry (0.9.12.6)
14
+ coderay (~> 1.0)
15
+ method_source (~> 0.8)
16
+ slop (~> 3.4)
17
+ rack (1.5.2)
18
+ rake (10.1.1)
19
+ rspec (2.14.1)
20
+ rspec-core (~> 2.14.0)
21
+ rspec-expectations (~> 2.14.0)
22
+ rspec-mocks (~> 2.14.0)
23
+ rspec-core (2.14.8)
24
+ rspec-expectations (2.14.5)
25
+ diff-lcs (>= 1.1.3, < 2.0)
26
+ rspec-mocks (2.14.6)
27
+ slop (3.4.7)
28
+
29
+ PLATFORMS
30
+ ruby
31
+
32
+ DEPENDENCIES
33
+ bundler (~> 1.5)
34
+ pry
35
+ rack-timer!
36
+ rake
37
+ rspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 HouseTrip Ltd
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.
@@ -0,0 +1,86 @@
1
+ # rack-timer
2
+
3
+ Are you spending too much time in Rack middlewares?
4
+
5
+ Is one of your Rack middlewares misbehaving in production?
6
+
7
+ Add `RackTimer::Middleware` to the to of your middleware stack and firgure out.
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ gem 'rack-timer'
14
+
15
+ Add the middleware to your stack. In your `config.ru`, add this before any other
16
+ middleware:
17
+
18
+ require 'rack-timer'
19
+ use RackTimer::Middleware
20
+
21
+ When your app runs, it will output timing information to standard error.
22
+ If you want to change that, you can tell `rack-timer` to send data to another
23
+ file-y object, for instance:
24
+
25
+ RackTimer.output = $stdout
26
+
27
+
28
+ ## Usage
29
+
30
+ Run your app normally after installation, wait a while, and download your logs.
31
+
32
+ They will report the middleware starting up:
33
+
34
+ [rack-timer] assimilating: Rack::MiddlewareTimer
35
+ [rack-timer] assimilating: Rack::Lock
36
+ [rack-timer] assimilating: Airbrake::UserInformer
37
+ [rack-timer] assimilating: Rack::DetectCrawler
38
+
39
+ then timing each request:
40
+
41
+ [borg] Proc took 3398135 us
42
+ [rack-timer] Rack::Cors took 54 us
43
+ [rack-timer] Rack::OutOfBandGC took 51 us
44
+ ...
45
+ [rack-timer] Airbrake::UserInformer took 74 us
46
+ [rack-timer] Rack::Lock took 78 us
47
+ [rack-timer] Rack::MiddlewareTimer took 80 us
48
+ [rack-timer] queued for 5256 us
49
+
50
+ Besides middleware timing, two bits of extra information are reported:
51
+
52
+ - `Proc` (the first entry for each request) typically is your app itself
53
+ (excluding middleware).
54
+ - `queued for...` will be present if your web frontend adds the `X-Request-Start`
55
+ HTTP header. If using Nginx+Unicorn or Apache+Passenger, this will report the
56
+ queueing time in Unicorn or Passenger.
57
+
58
+ ## An example
59
+
60
+ We developed this because of wierd issues with queuing time (was too high
61
+ without an obvious reason).
62
+
63
+ We graphed the middleware timings:
64
+
65
+ ![](http://cl.ly/image/460a3z060F3B/capture%202014-03-12%20at%2016.51.53.png)
66
+
67
+ And the queueing timings:
68
+
69
+ ![](http://cl.ly/image/2D2336390628/capture%202014-03-12%20at%2013.25.43.png)
70
+
71
+ (horizontally: log10 of the queuing time in microseconds, ie. 3 is 1ms and 6 is
72
+ 1 second)
73
+
74
+ The conlusion was that something what causing queuing in some cases. It turned
75
+ out our out-of-band garbage colleciton hack was no longer compatible with
76
+ Passenger, and removing it solved the issue:
77
+
78
+ ![](http://cl.ly/image/3z0V40291P46/capture%202014-03-12%20at%2014.18.55.png)
79
+
80
+ ## Contributing
81
+
82
+ 1. Fork it ( http://github.com/<my-github-username>/rack-timer/fork )
83
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
84
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
85
+ 4. Push to the branch (`git push origin my-new-feature`)
86
+ 5. Create new Pull Request
@@ -0,0 +1,7 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
7
+
@@ -0,0 +1,2 @@
1
+ require 'rack-timer/version'
2
+ require 'rack-timer/middleware'
@@ -0,0 +1,89 @@
1
+
2
+ require 'rack'
3
+
4
+ module RackTimer
5
+
6
+ module ModuleMethods
7
+ def log(message)
8
+ (@_output || $stderr).puts(message)
9
+ end
10
+
11
+ def output=(io)
12
+ @_output = io
13
+ end
14
+ end
15
+ extend ModuleMethods
16
+
17
+
18
+ class Middleware
19
+
20
+ def initialize(app)
21
+ @app = app
22
+ self.extend(Borg)
23
+ _log "started borg collective: #{self.class.name}"
24
+ end
25
+
26
+ def call(env)
27
+ @app.call(env)
28
+ end
29
+
30
+ module Borg
31
+ def self.extended(object)
32
+ object.singleton_class.class_eval do
33
+ alias_method :call_without_timing, :call
34
+ alias_method :call, :call_with_timing
35
+ public :call
36
+ end
37
+
38
+ object.instance_eval do
39
+ _log "assimilating: #{object.class.name}"
40
+ recursive_borg
41
+ end
42
+ end
43
+
44
+ def borged?
45
+ true
46
+ end
47
+
48
+ private
49
+
50
+ def recursive_borg
51
+ return if @app.nil?
52
+ return if @app.respond_to?(:borged?)
53
+ return unless @app.respond_to?(:call)
54
+ @app.extend(Borg)
55
+ end
56
+
57
+ def call_with_timing(env)
58
+ time_before = _current_ticks
59
+ result = call_without_timing(env)
60
+ time_delta = _current_ticks - time_before
61
+
62
+ if time_inner = env['rack-timer.time']
63
+ time_inner = time_inner.to_i
64
+ time_self = time_delta - time_inner
65
+ else
66
+ time_self = time_delta
67
+ end
68
+ _log "#{self.class.name} took #{time_self} us"
69
+
70
+ if (request_start = env['HTTP_X_REQUEST_START']) && kind_of?(RackTimer::Middleware)
71
+ time_queue_start = request_start.gsub('t=', '').to_i
72
+ time_in_queue = time_before - time_queue_start
73
+ _log "queued for #{time_in_queue} us"
74
+ end
75
+
76
+ env['rack-timer.time'] = time_delta
77
+ return result
78
+ end
79
+
80
+ def _log(message)
81
+ RackTimer.log "[rack-timer] #{message}"
82
+ end
83
+
84
+ def _current_ticks
85
+ (Time.now.to_f * 1e6).to_i
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,3 @@
1
+ module RackTimer
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'rack-timer/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "rack-timer"
8
+ spec.version = RackTimer::VERSION
9
+ spec.authors = ["Julien Letessier"]
10
+ spec.email = ["julien.letessier@gmail.com"]
11
+ spec.summary = %q{Measure time spent in your Rack middlewares}
12
+ spec.description = %q{Measure time spent in your Rack middlewares}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.5"
22
+ spec.add_development_dependency "rake"
23
+ spec.add_development_dependency "rspec"
24
+ spec.add_development_dependency "pry"
25
+
26
+ spec.add_dependency "rack"
27
+ end
@@ -0,0 +1,69 @@
1
+ require 'spec_helper'
2
+ require 'rack-timer'
3
+
4
+ describe RackTimer::Middleware do
5
+ let(:buffer) { StringIO.new }
6
+ let(:output) { buffer.rewind ; buffer.read }
7
+ let(:app) { build_stack *stack }
8
+ let(:stack) { [Test::App, Test::FastMiddleware, RackTimer::Middleware] }
9
+
10
+ before do
11
+ RackTimer.output = buffer
12
+ end
13
+
14
+ def build_stack(app_class, *middlewares)
15
+ app = app_class.new
16
+ middlewares.each do |m|
17
+ app = m.new(app)
18
+ end
19
+ app
20
+ end
21
+
22
+ describe 'assimilation' do
23
+ it 'gets logged' do
24
+ app
25
+ output.should =~ /assimilating: RackTimer::Middleware/
26
+ output.should =~ /assimilating: Test::FastMiddleware/
27
+ end
28
+ end
29
+
30
+ describe 'middleware timing' do
31
+ it 'logs app timing' do
32
+ app.call({})
33
+ output.should =~ /Test::App took \d+ us/
34
+ end
35
+
36
+ it 'logs middleware timing' do
37
+ app.call({})
38
+ output.should =~ /Test::FastMiddleware took \d+ us/
39
+ end
40
+
41
+ context 'with a slow middleware' do
42
+ let(:stack) { [Test::App, Test::SlowMiddleware, Test::FastMiddleware, RackTimer::Middleware] }
43
+
44
+ it 'times the slow middleware' do
45
+ app.call({})
46
+ output.should =~ /Test::SlowMiddleware took \d+ us/
47
+ end
48
+
49
+ it 'does not include the slow middleware time in the outer middleware' do
50
+ app.call({})
51
+ time_slow = /Test::SlowMiddleware took (?<time>\d+) us/.match(output)[:time].to_i
52
+ time_fast = /Test::FastMiddleware took (?<time>\d+) us/.match(output)[:time].to_i
53
+ time_slow.should > 250_000
54
+ time_fast.should < time_slow
55
+ end
56
+ end
57
+ end
58
+
59
+ describe 'queue timing' do
60
+ let(:timestamp) { (Time.now.to_f * 1e6).to_i }
61
+ let(:env) {{ 'HTTP_X_REQUEST_START' => "t=#{timestamp}" }}
62
+
63
+ it 'adds queue time when HTTP_X_REQUEST_START present' do
64
+ app.call(env)
65
+ output.should =~ /queued for \d+ us/
66
+ end
67
+
68
+ end
69
+ end
@@ -0,0 +1,50 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # Require this file using `require "spec_helper"` to ensure that it is only
4
+ # loaded once.
5
+ #
6
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
7
+ #
8
+ # lib = File.expand_path('../../lib', __FILE__)
9
+ # $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
10
+
11
+ require 'rack'
12
+ require 'pry'
13
+
14
+ module Test
15
+ class App
16
+ def call(env)
17
+ [200, {}, 'ok']
18
+ end
19
+ end
20
+
21
+ class FastMiddleware
22
+ def initialize(app)
23
+ @app = app
24
+ end
25
+
26
+ def call(env)
27
+ @app.call(env)
28
+ end
29
+ end
30
+
31
+ class SlowMiddleware < FastMiddleware
32
+ def call(env)
33
+ sleep 250e-3
34
+ super
35
+ end
36
+ end
37
+ end
38
+
39
+
40
+ RSpec.configure do |config|
41
+ config.treat_symbols_as_metadata_keys_with_true_values = true
42
+ config.run_all_when_everything_filtered = true
43
+ config.filter_run :focus
44
+
45
+ # Run specs in random order to surface order dependencies. If you find an
46
+ # order dependency and want to debug it, you can fix the order by providing
47
+ # the seed, which is printed after each run.
48
+ # --seed 1234
49
+ config.order = 'random'
50
+ end
metadata ADDED
@@ -0,0 +1,130 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rack-timer
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Julien Letessier
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-03-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.5'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.5'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: pry
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rack
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: Measure time spent in your Rack middlewares
84
+ email:
85
+ - julien.letessier@gmail.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - ".gitignore"
91
+ - ".rspec"
92
+ - ".travis.yml"
93
+ - Gemfile
94
+ - Gemfile.lock
95
+ - LICENSE.txt
96
+ - README.md
97
+ - Rakefile
98
+ - lib/rack-timer.rb
99
+ - lib/rack-timer/middleware.rb
100
+ - lib/rack-timer/version.rb
101
+ - rack-timer.gemspec
102
+ - spec/rack-timer/middleware_spec.rb
103
+ - spec/spec_helper.rb
104
+ homepage: ''
105
+ licenses:
106
+ - MIT
107
+ metadata: {}
108
+ post_install_message:
109
+ rdoc_options: []
110
+ require_paths:
111
+ - lib
112
+ required_ruby_version: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ required_rubygems_version: !ruby/object:Gem::Requirement
118
+ requirements:
119
+ - - ">="
120
+ - !ruby/object:Gem::Version
121
+ version: '0'
122
+ requirements: []
123
+ rubyforge_project:
124
+ rubygems_version: 2.2.0
125
+ signing_key:
126
+ specification_version: 4
127
+ summary: Measure time spent in your Rack middlewares
128
+ test_files:
129
+ - spec/rack-timer/middleware_spec.rb
130
+ - spec/spec_helper.rb