rack-bug-speedtracer 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +88 -0
- data/Rakefile +243 -0
- data/VERSION +1 -0
- data/examples/runner.png +0 -0
- data/examples/runner.rb +34 -0
- data/lib/rack-bug-speedtracer.rb +1 -0
- data/lib/rack/bug/speedtracer.rb +56 -0
- data/lib/rack/bug/speedtracer/default-rails-tracing.rb +13 -0
- data/lib/rack/bug/speedtracer/duck-puncher.rb +82 -0
- data/lib/rack/bug/speedtracer/profiling.rb +29 -0
- data/lib/rack/bug/speedtracer/render.rb +9 -0
- data/lib/rack/bug/speedtracer/trace-app.rb +51 -0
- data/lib/rack/bug/speedtracer/tracer.rb +212 -0
- data/lib/rack/bug/views/serverevent.html.erb +10 -0
- data/lib/rack/bug/views/traces.html.erb +8 -0
- data/spec/duck-puncher.rb +66 -0
- data/spec/profiling-speedtracer.rb +12 -0
- data/spec/speedtracer_spec.rb +52 -0
- data/spec/tracer_spec.rb +107 -0
- data/spec_help/spec_helper.rb +48 -0
- metadata +130 -0
data/README.md
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
Rack::SpeedTracer
|
2
|
+
=========
|
3
|
+
|
4
|
+
Blog post: [Speed Tracer Server-side Tracing with Rack](http://www.igvita.com/2010/07/19/speed-tracer-server-side-tracing-with-rack/)
|
5
|
+
|
6
|
+
Rack::SpeedTracer middleware provides server-side tracing capabilities to any Rack compatible app. Include the middleware, instrument your application, and then load it in your Google Chrome + SpeedTracer to view detailed breakdown of your JavaScript/CSS load times, GC cycles, as well as, server side performance data provided by this middleware - you can preview both server side and client side performance data all within the same view in SpeedTracer!
|
7
|
+
|
8
|
+
Preview of a sample, server side Rails trace (see below for setup) in SpeedTracer:
|
9
|
+
![rails trace](http://img.skitch.com/20100717-cd31bhd5dh13sge7c2q1hefh4p.png)
|
10
|
+
|
11
|
+
Todo / Wishlist
|
12
|
+
---------------
|
13
|
+
|
14
|
+
* Pluggable storage, ala rack-cache: capped mongo collection, or capped redis list would be ideal
|
15
|
+
* At the moment all of the data is stored directly in memory (unbounded)
|
16
|
+
* It would be great to have a mechanism to either (a) limit number of store traces or (b) expire them
|
17
|
+
* Authentication / optional enable, ala rack-bug: IP-based, password based
|
18
|
+
* At the moment, every request will record & store a trace
|
19
|
+
* Could also do conditional tracing based on a request header: 'X-SpeedTracer: true'
|
20
|
+
* Automagic Rails instrumentation for AR, layout, etc, ala rack-bug
|
21
|
+
|
22
|
+
Because the middleware has no authentication, or does not yet provide a capped memory footprint, it is not ready to be run in production - use it in development mode until these mechanisms are in place. Patches are welcome, of course!
|
23
|
+
|
24
|
+
How it works
|
25
|
+
------------
|
26
|
+
|
27
|
+
Rack::SpeedTracer provides a Tracer class which you can use to instrument your code. From there, the trace details are stored as a JSON blob, and a special X-TraceUrl header is sent back to the client. If the user clicks on the network resource that corresponds to a request which returned a X-TraceUrl header, then SpeedTracer will make a request to our app to load the server side trace. Rack::SpeedTracer responds to this request and returns the full trace - aka, the data is provided on demand.
|
28
|
+
|
29
|
+
### Quickstart Guide ###
|
30
|
+
|
31
|
+
gem install rack-speedtracer
|
32
|
+
|
33
|
+
# in your rack app / rackup file
|
34
|
+
use Rack::SpeedTracer
|
35
|
+
|
36
|
+
# in your app
|
37
|
+
env['st.tracer'].run('name of operation') do
|
38
|
+
... your code ...
|
39
|
+
end
|
40
|
+
|
41
|
+
Check out a full sample rack app: examples/runner.rb
|
42
|
+
|
43
|
+
### Manually instrumenting Rails ###
|
44
|
+
To produce a server-side trace equivalent to one in the screenshot above:
|
45
|
+
|
46
|
+
# in your Gemfile
|
47
|
+
gem 'rack-speedtracer', :require => 'rack/speedtracer'
|
48
|
+
|
49
|
+
# in development.rb environment
|
50
|
+
config.middleware.use Rack::SpeedTracer
|
51
|
+
|
52
|
+
# define a widgets controller
|
53
|
+
class WidgetsController < ApplicationController
|
54
|
+
def index
|
55
|
+
env['st.tracer'].run('Widgets#index') do
|
56
|
+
env['st.tracer'].run("ActiveRecord: Widgets.all") do
|
57
|
+
Widget.all
|
58
|
+
end
|
59
|
+
|
60
|
+
env['st.tracer'].run('Render') { render :text => 'oh hai' }
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
Speed Tracer
|
66
|
+
------------
|
67
|
+
|
68
|
+
Speed Tracer is a Google Chrome extension to help you identify and fix performance problems in your web applications. It visualizes metrics that are taken from low level instrumentation points inside of the browser and analyzes them as your application runs. Speed Tracer is available as a Chrome extension and works on all platforms where extensions are currently supported (Windows and Linux).
|
69
|
+
|
70
|
+
Using Speed Tracer you are able to get a better picture of where time is being spent in your application. This includes problems caused by JavaScript parsing and execution, layout, CSS style recalculation and selector matching, DOM event handling, network resource loading, timer fires, XMLHttpRequest callbacks, painting, and more.
|
71
|
+
|
72
|
+
* [Official SpeedTracer site](http://code.google.com/webtoolkit/speedtracer/)
|
73
|
+
* [Install SpeedTracer](http://code.google.com/webtoolkit/speedtracer/get-started.html#downloading)
|
74
|
+
* [Getting Started](http://code.google.com/webtoolkit/speedtracer/speed-tracer-examples.html)
|
75
|
+
* [Google Group](https://groups.google.com/group/speedtracer/topics)
|
76
|
+
|
77
|
+
License
|
78
|
+
-------
|
79
|
+
|
80
|
+
(The MIT License)
|
81
|
+
|
82
|
+
Copyright © 2010 Ilya Grigorik
|
83
|
+
|
84
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the ‘Software’), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
85
|
+
|
86
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
87
|
+
|
88
|
+
THE SOFTWARE IS PROVIDED ‘AS IS’, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,243 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rubygems/installer'
|
3
|
+
require 'rake/gempackagetask'
|
4
|
+
require 'rake/gemcutter'
|
5
|
+
require 'hanna/rdoctask'
|
6
|
+
require 'spec/rake/spectask'
|
7
|
+
require 'mailfactory'
|
8
|
+
require 'net/smtp'
|
9
|
+
|
10
|
+
require File.dirname(__FILE__) +'/gemspec'
|
11
|
+
require 'spec/rake/verify_rcov'
|
12
|
+
|
13
|
+
PACKAGE_DIR = "pkg"
|
14
|
+
directory "doc"
|
15
|
+
|
16
|
+
#spec_files = FileList["spec/**/*.rb"]
|
17
|
+
|
18
|
+
RakeConfig = {
|
19
|
+
:email_servers => [ {
|
20
|
+
:server => "ruby-lang.org",
|
21
|
+
:helo => "gmail.com"
|
22
|
+
} ],
|
23
|
+
:announce_to_email => "ruby-talk@ruby-lang.org"
|
24
|
+
}
|
25
|
+
|
26
|
+
class SpecTask < Spec::Rake::SpecTask
|
27
|
+
def initialize(name=:spec)
|
28
|
+
super(name) do
|
29
|
+
@spec_files = FileList["spec/**/*"]
|
30
|
+
@libs = [%w{spec_help interpose}, %w{lib}, %w{spec_help}].map do|dir|
|
31
|
+
File::join(File::dirname(__FILE__), *dir)
|
32
|
+
end
|
33
|
+
@ruby_opts = %w{-rubygems}
|
34
|
+
self.spec_opts= %w{-f s --diff c --color}
|
35
|
+
@rcov_opts = %w{--exclude ^gemspec.rb,^spec/,^spec_help/ --sort coverage --threshold 101 -o doc/coverage --xrefs --no-color}
|
36
|
+
@rcov = true
|
37
|
+
@failure_message = "Spec examples failed."
|
38
|
+
@verbose = true
|
39
|
+
yield(self) if block_given?
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def spec_opts=(opts)
|
44
|
+
@spec_opts = %w{-r cbt-tweaker -f e:last_run} + opts + %w{-r spec_helper -p spec/**/*.rb}
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
task :needs_root do
|
49
|
+
unless Process::uid == 0
|
50
|
+
fail "This task must be run as root"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
desc "Run failing examples if any exist, otherwise, run the whole suite"
|
55
|
+
task :spec => "spec:quick"
|
56
|
+
|
57
|
+
namespace :spec do
|
58
|
+
desc "Always run every spec"
|
59
|
+
SpecTask.new(:all)
|
60
|
+
|
61
|
+
desc "Generate spec/spec.opts"
|
62
|
+
SpecTask.new(:spec_opts) do |t|
|
63
|
+
t.spec_opts= %w{-f s --diff c --color --generate-options spec/spec.opts}
|
64
|
+
t.rcov = false
|
65
|
+
end
|
66
|
+
|
67
|
+
desc "Generate specifications documentation"
|
68
|
+
SpecTask.new(:doc) do |t|
|
69
|
+
t.spec_opts = %w{-f s:doc/Specifications -b}
|
70
|
+
t.failure_message = "Failed generating specification docs"
|
71
|
+
t.verbose = false
|
72
|
+
end
|
73
|
+
|
74
|
+
desc "Run specs with Ruby profiling"
|
75
|
+
SpecTask.new(:profile) do |t|
|
76
|
+
t.ruby_opts += %w{-rprofile}
|
77
|
+
end
|
78
|
+
|
79
|
+
desc "Run only failing examples"
|
80
|
+
SpecTask.new(:quick) do |t|
|
81
|
+
t.spec_opts = %w{-f s --diff c --color --example last_run -b}
|
82
|
+
t.rcov = false
|
83
|
+
t.failure_message = "Spec examples failed."
|
84
|
+
end
|
85
|
+
|
86
|
+
desc "Run rspecs prior to a package publication"
|
87
|
+
SpecTask.new(:check) do |t|
|
88
|
+
t.spec_opts = %w{--format p:/dev/null}
|
89
|
+
t.failure_message = "Package does not conform to spec"
|
90
|
+
t.verbose = false
|
91
|
+
end
|
92
|
+
|
93
|
+
desc "Open firefox to view RCov output"
|
94
|
+
task :view_coverage => :doc do |t|
|
95
|
+
sh "/usr/bin/chromium coverage/index.html"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
namespace :package do
|
100
|
+
RCov::VerifyTask.new do |t|
|
101
|
+
t.require_exact_threshold = false
|
102
|
+
t.threshold = 80
|
103
|
+
t.verbose = true
|
104
|
+
end
|
105
|
+
task :verify_rcov => ['spec:doc']
|
106
|
+
|
107
|
+
task :chown_coverage do
|
108
|
+
unless (user = ENV['SUDO_USER']).nil?
|
109
|
+
FileUtils::chown_R(user, ENV['SUDO_GID'].to_i, 'coverage')
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
package = Rake::GemPackageTask.new(SPEC) {|t|
|
114
|
+
t.need_tar_gz = true
|
115
|
+
t.need_tar_bz2 = true
|
116
|
+
}
|
117
|
+
task(:package).prerequisites.each do |package_type|
|
118
|
+
file package_type => "spec:check"
|
119
|
+
end
|
120
|
+
|
121
|
+
Rake::RDocTask.new(:docs) do |rd|
|
122
|
+
rd.options += SPEC.rdoc_options
|
123
|
+
rd.rdoc_dir = 'rubydoc'
|
124
|
+
rd.rdoc_files.include("lib/**/*.rb")
|
125
|
+
rd.rdoc_files += (SPEC.extra_rdoc_files)
|
126
|
+
end
|
127
|
+
task :docs => ['spec:doc']
|
128
|
+
end
|
129
|
+
|
130
|
+
desc "Publish the gem and its documentation to Rubyforge and Gemcutter"
|
131
|
+
task :publish => ['publish:docs', 'publish:rubyforge', 'publish:gem:push']
|
132
|
+
namespace :publish do
|
133
|
+
desc "Deploy gem via scp"
|
134
|
+
task :scp, :dest, :destpath, :needs => :package do |t, args|
|
135
|
+
destpath = args[:destpath] || "~"
|
136
|
+
sh "scp", File::join(package.package_dir, package.gem_file), "#{args[:dest]}:#{destpath}"
|
137
|
+
sh *(%w{ssh} + [args[:dest]] + %w{sudo gem install} + [File::join(destpath, package.gem_file)])
|
138
|
+
end
|
139
|
+
|
140
|
+
task :sign_off => %w{package:verify_rcov package:gem package:chown_coverage}
|
141
|
+
|
142
|
+
Rake::Gemcutter::Tasks.new(SPEC)
|
143
|
+
task 'gem:push' => :sign_off
|
144
|
+
task 'gem:install' => [:needs_root, :sign_off]
|
145
|
+
task 'gem:reinstall' => :needs_root
|
146
|
+
|
147
|
+
desc 'Publish RDoc to RubyForge'
|
148
|
+
task :docs => 'package:docs' do
|
149
|
+
config = YAML.load(File.read(File.expand_path("~/.rubyforge/user-config.yml")))
|
150
|
+
host = "#{config["username"]}@rubyforge.org"
|
151
|
+
remote_dir = "/var/www/gforge-projects/#{RUBYFORGE[:group_id]}"
|
152
|
+
local_dir = 'rubydoc'
|
153
|
+
sh %{rsync -av --delete #{local_dir}/ #{host}:#{remote_dir}}
|
154
|
+
end
|
155
|
+
|
156
|
+
task :scrape_rubyforge do
|
157
|
+
require 'rubyforge'
|
158
|
+
forge = RubyForge.new
|
159
|
+
forge.configure
|
160
|
+
forge.scrape_project(RUBYFORGE[:package_id])
|
161
|
+
end
|
162
|
+
|
163
|
+
desc "Publishes to RubyForge"
|
164
|
+
task :rubyforge => ['package:verify_rcov', 'package:package', :docs, :scrape_rubyforge] do |t|
|
165
|
+
require 'rubyforge'
|
166
|
+
forge = RubyForge.new
|
167
|
+
forge.configure
|
168
|
+
files = [".gem", ".tar.gz", ".tar.bz2"].map do |extension|
|
169
|
+
File::join(PACKAGE_DIR, SPEC.full_name) + extension
|
170
|
+
end
|
171
|
+
release = forge.lookup("release", RUBYFORGE[:package_id])[RUBYFORGE[:release_name]] rescue nil
|
172
|
+
if release.nil?
|
173
|
+
forge.add_release(RUBYFORGE[:group_id], RUBYFORGE[:package_id], RUBYFORGE[:release_name], *files)
|
174
|
+
else
|
175
|
+
files.each do |file|
|
176
|
+
forge.add_file(RUBYFORGE[:group_id], RUBYFORGE[:package_id], RUBYFORGE[:release_name], file)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def announcement # :nodoc:
|
183
|
+
changes = ""
|
184
|
+
begin
|
185
|
+
File::open("./Changelog", "r") do |changelog|
|
186
|
+
changes = "Changes:\n\n"
|
187
|
+
changes += changelog.read
|
188
|
+
end
|
189
|
+
rescue Exception
|
190
|
+
end
|
191
|
+
|
192
|
+
urls = "Project: #{RUBYFORGE[:project_page]}\n" +
|
193
|
+
"Homepage: #{RUBYFORGE[:home_page]}"
|
194
|
+
|
195
|
+
subject = "#{SPEC.name} #{SPEC.version} Released"
|
196
|
+
title = "#{SPEC.name} version #{SPEC.version} has been released!"
|
197
|
+
body = "#{SPEC.description}\n\n#{changes}\n\n#{urls}"
|
198
|
+
|
199
|
+
return subject, title, body
|
200
|
+
end
|
201
|
+
|
202
|
+
desc 'Announce release on RubyForge and email'
|
203
|
+
task :press => ['press:rubyforge', 'press:email']
|
204
|
+
namespace :press do
|
205
|
+
desc 'Post announcement to rubyforge.'
|
206
|
+
task :rubyforge do
|
207
|
+
require 'rubyforge'
|
208
|
+
subject, title, body = announcement
|
209
|
+
|
210
|
+
forge = RubyForge.new
|
211
|
+
forge.configure
|
212
|
+
forge.post_news(RUBYFORGE[:group_id], subject, "#{title}\n\n#{body}")
|
213
|
+
puts "Posted to rubyforge"
|
214
|
+
end
|
215
|
+
|
216
|
+
desc 'Generate email announcement file.'
|
217
|
+
task :email do
|
218
|
+
require 'rubyforge'
|
219
|
+
subject, title, body= announcement
|
220
|
+
|
221
|
+
mail = MailFactory.new
|
222
|
+
mail.To = RakeConfig[:announce_to_email]
|
223
|
+
mail.From = SPEC.email
|
224
|
+
mail.Subject = "[ANN] " + subject
|
225
|
+
mail.text = [title, body].join("\n\n")
|
226
|
+
|
227
|
+
File.open("email.txt", "w") do |mailfile|
|
228
|
+
mailfile.write mail.to_s
|
229
|
+
end
|
230
|
+
puts "Created email.txt"
|
231
|
+
|
232
|
+
RakeConfig[:email_servers].each do |server_config|
|
233
|
+
begin
|
234
|
+
Net::SMTP.start(server_config[:server], 25, server_config[:helo], server_config[:username], server_config[:password]) do |smtp|
|
235
|
+
smtp.send_message(mail.to_s, mail.From, mail.To)
|
236
|
+
end
|
237
|
+
break
|
238
|
+
rescue Object => ex
|
239
|
+
puts ex.message
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
data/examples/runner.png
ADDED
Binary file
|
data/examples/runner.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rack'
|
3
|
+
|
4
|
+
$LOAD_PATH.unshift 'lib'
|
5
|
+
require 'rack/speedtracer'
|
6
|
+
|
7
|
+
class SomeApp
|
8
|
+
def call(env)
|
9
|
+
env['st.tracer'].run('computation: 5**100000') do
|
10
|
+
env['st.tracer'].run('computation 2: 5**10000') do
|
11
|
+
5**10000
|
12
|
+
end
|
13
|
+
|
14
|
+
env['st.tracer'].run('sleep(0.01)') { sleep(0.01) }
|
15
|
+
|
16
|
+
5**100000
|
17
|
+
end
|
18
|
+
|
19
|
+
env['st.tracer'].run('sleep(0.5)') do
|
20
|
+
sleep(0.5)
|
21
|
+
end
|
22
|
+
|
23
|
+
[200, {"Content-Type" => "text/plain"}, "Hello World"]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
builder = Rack::Builder.new do
|
28
|
+
use Rack::CommonLogger
|
29
|
+
use Rack::SpeedTracer
|
30
|
+
|
31
|
+
run SomeApp.new
|
32
|
+
end
|
33
|
+
|
34
|
+
Rack::Handler::Thin.run builder.to_app, :Port => 4567
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'rack/bug/speedtracer'
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'rack/bug'
|
2
|
+
require 'yajl'
|
3
|
+
require 'uuid'
|
4
|
+
|
5
|
+
require 'rack/bug/speedtracer/trace-app'
|
6
|
+
require 'rack/bug/speedtracer/tracer'
|
7
|
+
|
8
|
+
module Rack::Bug
|
9
|
+
class SpeedTracer < Panel
|
10
|
+
include Rack::Bug::SpeedTrace::Render
|
11
|
+
|
12
|
+
def self.database
|
13
|
+
@db ||= {}
|
14
|
+
end
|
15
|
+
|
16
|
+
def database
|
17
|
+
self.class.database
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(app)
|
21
|
+
@app = app
|
22
|
+
@uuid = UUID.new
|
23
|
+
super
|
24
|
+
end
|
25
|
+
|
26
|
+
def panel_app
|
27
|
+
return SpeedTrace::TraceApp.new(database)
|
28
|
+
end
|
29
|
+
|
30
|
+
def name
|
31
|
+
"speedtracer"
|
32
|
+
end
|
33
|
+
|
34
|
+
def heading
|
35
|
+
"#{database.keys.length} traces"
|
36
|
+
end
|
37
|
+
|
38
|
+
def content
|
39
|
+
render_template "traces", :traces => database
|
40
|
+
end
|
41
|
+
|
42
|
+
def before(env)
|
43
|
+
env['st.id'] = @uuid.generate
|
44
|
+
|
45
|
+
tracer = SpeedTrace::Tracer.new(env['st.id'], env['REQUEST_METHOD'], env['REQUEST_URI'])
|
46
|
+
env['st.tracer'] = tracer
|
47
|
+
Thread::current['st.tracer'] = tracer
|
48
|
+
end
|
49
|
+
|
50
|
+
def after(env, status, headers, body)
|
51
|
+
env['st.tracer'].finish
|
52
|
+
database[env['st.id']] = env['st.tracer']
|
53
|
+
headers['X-TraceUrl'] = '/speedtracer?id=' + env['st.id']
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require "rack/bug/speedtracer/duck-puncher"
|
2
|
+
|
3
|
+
find_constant("ActionView::Template"){|avt| avt.trace_methods :render_template}
|
4
|
+
|
5
|
+
find_constant("ActiveRecord::Base") do |ar_b|
|
6
|
+
ar_b.trace_class_methods :find, :all, :first, :last, :count, :delete_all
|
7
|
+
ar_b.trace_methods :save, :save!, :destroy, :delete
|
8
|
+
end
|
9
|
+
|
10
|
+
find_constant("ActionController::Base") do |ac_b|
|
11
|
+
ac_b.trace_methods :process, :render
|
12
|
+
end
|
13
|
+
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'rack/bug/speedtracer'
|
2
|
+
|
3
|
+
module Kernel
|
4
|
+
def find_constant(name)
|
5
|
+
parts = name.split("::")
|
6
|
+
begin
|
7
|
+
const = parts.inject(Kernel) do |namespace, part|
|
8
|
+
namespace.const_get(part)
|
9
|
+
end
|
10
|
+
rescue NameError => ex
|
11
|
+
warn "Couldn't find #{name}"
|
12
|
+
return nil
|
13
|
+
end
|
14
|
+
yield(const) if block_given?
|
15
|
+
return const
|
16
|
+
end
|
17
|
+
|
18
|
+
# trace_run(method_name, context, caller[1], *args) do
|
19
|
+
def trace_run(context = "::", called_at=caller[1], args=[])
|
20
|
+
tracer = Thread.current['st.tracer']
|
21
|
+
result = nil
|
22
|
+
if tracer.nil?
|
23
|
+
Rails.logger.debug{"Null trace"}
|
24
|
+
result = yield
|
25
|
+
else
|
26
|
+
tracer.run(context, called_at, args){ result = yield }
|
27
|
+
end
|
28
|
+
result
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class Rack::Bug::SpeedTracer
|
33
|
+
class << self
|
34
|
+
def safe_method_names(mod, method_names)
|
35
|
+
unsafe_names = (mod.public_instance_methods(true) +
|
36
|
+
mod.protected_instance_methods(true) +
|
37
|
+
mod.private_instance_methods(true)).sort.uniq
|
38
|
+
|
39
|
+
method_names.map do |name|
|
40
|
+
name = name.to_s
|
41
|
+
prefix = "0"
|
42
|
+
hidden_name = ["_", prefix, name].join("_")
|
43
|
+
while unsafe_names.include?(hidden_name)
|
44
|
+
prefix = prefix.next
|
45
|
+
hidden_name = ["_", prefix, name].join("_")
|
46
|
+
end
|
47
|
+
|
48
|
+
unsafe_names << hidden_name
|
49
|
+
[name, hidden_name]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
class Module
|
56
|
+
def trace_class_methods(*methods_names)
|
57
|
+
(class << self; self; end).build_tracing_wrappers('#{self.name}::', *methods_names)
|
58
|
+
end
|
59
|
+
|
60
|
+
def trace_methods(*methods)
|
61
|
+
build_tracing_wrappers('#{self.class.name}#', *methods)
|
62
|
+
end
|
63
|
+
|
64
|
+
def build_tracing_wrappers(context, *methods)
|
65
|
+
@traced ||= {}
|
66
|
+
|
67
|
+
Rack::Bug::SpeedTracer::safe_method_names(self, methods).each do |method_name, old_method|
|
68
|
+
next if @traced.has_key?(method_name)
|
69
|
+
@traced[method_name] = true
|
70
|
+
|
71
|
+
alias_method old_method, method_name
|
72
|
+
|
73
|
+
self.class_eval <<-EOC, __FILE__, __LINE__
|
74
|
+
def #{method_name}(*args, &block)
|
75
|
+
trace_run("#{context}", caller(0)[0], args) do
|
76
|
+
#{old_method}(*args, &block)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
EOC
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'rack/bug/speedtracer'
|
2
|
+
|
3
|
+
module Rack::Bug
|
4
|
+
#Variant of the Speed Tracer Panel that performs a nearly complete profile of
|
5
|
+
#all code called during a request. Note that this will slow overall response
|
6
|
+
#time by several orders of magnitude, and may return more data than
|
7
|
+
#SpeedTracer is prepared to display
|
8
|
+
class ProfilingSpeedTracer < SpeedTracer
|
9
|
+
def before(env)
|
10
|
+
super
|
11
|
+
tracer = env['st.tracer']
|
12
|
+
Kernel::set_trace_func proc {|event, file, line, name, binding,classname|
|
13
|
+
case event
|
14
|
+
when "c-call", "call"
|
15
|
+
methodname = classname ? "" : classname
|
16
|
+
methodname += name.to_s
|
17
|
+
tracer.start_event(file, line, name, classname || "", "")
|
18
|
+
when "c-return", "return"
|
19
|
+
tracer.finish_event
|
20
|
+
end
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
def after(env, status, headers, body)
|
25
|
+
Kernel::set_trace_func(nil)
|
26
|
+
super
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Rack::Bug
|
2
|
+
module SpeedTrace
|
3
|
+
class TraceApp
|
4
|
+
TRACER_PATH = /^\/speedtracer/.freeze
|
5
|
+
CONTENT_TYPE = 'application/json;charset=UTF-8'.freeze
|
6
|
+
|
7
|
+
FourOhFour = [404, {"Content-Type" => "text/html"}, "App tracker doesn't know that path or id"].freeze
|
8
|
+
|
9
|
+
def initialize(db)
|
10
|
+
@db = db
|
11
|
+
end
|
12
|
+
|
13
|
+
def call(env)
|
14
|
+
return FourOhFour unless env['PATH_INFO'].match(TRACER_PATH)
|
15
|
+
|
16
|
+
resp = Rack::Response.new('', 200)
|
17
|
+
resp['Content-Type'] = CONTENT_TYPE
|
18
|
+
|
19
|
+
case env['REQUEST_METHOD']
|
20
|
+
when 'HEAD' then
|
21
|
+
# SpeedTracer dispatches HEAD requests to verify the
|
22
|
+
# tracer endpoint when it detects the X-TraceUrl
|
23
|
+
# header for the first time. After the initial load
|
24
|
+
# the verification is cached by the extension.
|
25
|
+
#
|
26
|
+
# By default, we'll return 200.
|
27
|
+
|
28
|
+
when 'GET' then
|
29
|
+
# GET requests for specific trace are generated by
|
30
|
+
# the extension when the user expands the network
|
31
|
+
# resource tab. Hence, server-side tracer data is
|
32
|
+
# request on-demand, and we need to store it for
|
33
|
+
# some time.
|
34
|
+
|
35
|
+
qs = Rack::Utils.parse_query(env['QUERY_STRING'])
|
36
|
+
if qs['id'] && @db[qs['id']]
|
37
|
+
resp.write @db[qs['id']].to_json
|
38
|
+
else
|
39
|
+
# Invalid request or missing request trace id
|
40
|
+
return FourOhFour
|
41
|
+
end
|
42
|
+
else
|
43
|
+
# SpeedTracer should only issue GET & HEAD requests
|
44
|
+
resp.status = 400
|
45
|
+
end
|
46
|
+
|
47
|
+
return resp.finish
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,212 @@
|
|
1
|
+
require 'rack/bug/speedtracer/render'
|
2
|
+
|
3
|
+
module Rack::Bug
|
4
|
+
module SpeedTrace
|
5
|
+
class TraceRecord
|
6
|
+
include Render
|
7
|
+
def initialize(id)
|
8
|
+
@id = id
|
9
|
+
@start = Time.now
|
10
|
+
@children = []
|
11
|
+
end
|
12
|
+
|
13
|
+
def finish; @finish = Time.now; end
|
14
|
+
|
15
|
+
def time_in_children
|
16
|
+
@children.inject(0) do |time, child|
|
17
|
+
time + child.duration
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def duration
|
22
|
+
((@finish - @start) * 1000).to_i
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_json
|
26
|
+
Yajl::Encoder.encode(hash_representation, :pretty => true, :indent => ' ')
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
# all timestamps in SpeedTracer are in milliseconds
|
31
|
+
def range(start, finish)
|
32
|
+
{
|
33
|
+
'duration' => ((finish - start) * 1000).to_i,
|
34
|
+
'start' => [start.to_i, start.usec/1000].join(''),
|
35
|
+
#'end' => [finish.to_i, finish.usec/1000].join('')
|
36
|
+
}
|
37
|
+
end
|
38
|
+
|
39
|
+
def symbolize_hash(hash)
|
40
|
+
hash.each_key do |key|
|
41
|
+
if String === key
|
42
|
+
next if hash.has_key?(key.to_sym)
|
43
|
+
hash[key.to_sym] = hash[key]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class ServerEvent < TraceRecord
|
50
|
+
attr_accessor :children
|
51
|
+
attr_reader :name
|
52
|
+
|
53
|
+
def initialize(id, file, line, method, context, arguments)
|
54
|
+
super(id)
|
55
|
+
|
56
|
+
@file = file
|
57
|
+
@line = line
|
58
|
+
@method = method
|
59
|
+
@context = context
|
60
|
+
@arguments = arguments
|
61
|
+
@name = [context, method, "(", arguments, ")"].join("")
|
62
|
+
end
|
63
|
+
|
64
|
+
def hash_representation
|
65
|
+
{
|
66
|
+
'range' => range(@start, @finish),
|
67
|
+
#'id' => @id,
|
68
|
+
'operation' => {
|
69
|
+
# 'sourceCodeLocation' => {
|
70
|
+
# 'className' => @file,
|
71
|
+
# 'methodName' => @method,
|
72
|
+
# 'lineNumber' => @line
|
73
|
+
# },
|
74
|
+
'type' => 'METHOD',
|
75
|
+
'label' => @name
|
76
|
+
},
|
77
|
+
'children' => @children
|
78
|
+
}
|
79
|
+
end
|
80
|
+
|
81
|
+
def to_html
|
82
|
+
render_template('serverevent',
|
83
|
+
{:self_time => duration - time_in_children}.merge(symbolize_hash(hash_representation)))
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
class Tracer < TraceRecord
|
88
|
+
def initialize(id, method, uri)
|
89
|
+
super(id)
|
90
|
+
|
91
|
+
@method = method
|
92
|
+
@uri = uri
|
93
|
+
@event_id = 0
|
94
|
+
@pstack = []
|
95
|
+
end
|
96
|
+
|
97
|
+
#TODO: Threadsafe
|
98
|
+
def run(context="::", called_at = caller[0], args=[], &blk)
|
99
|
+
file, line, method = called_at.split(':')
|
100
|
+
|
101
|
+
method = method.gsub(/^in|[^\w]+/, '') if method
|
102
|
+
|
103
|
+
start_event(file, line, method, context, args)
|
104
|
+
blk.call # execute the provided code block
|
105
|
+
finish_event
|
106
|
+
end
|
107
|
+
|
108
|
+
def short_string(item, max_per_elem = 50)
|
109
|
+
begin
|
110
|
+
string = item.inspect
|
111
|
+
if string.length > max_per_elem
|
112
|
+
case item
|
113
|
+
when NilClass
|
114
|
+
"nil"
|
115
|
+
when Hash
|
116
|
+
"{ " + item.map do |key, value|
|
117
|
+
short_string(key, 15) + "=>" + short_string(value, 30)
|
118
|
+
end.join(", ") + " }"
|
119
|
+
when find_constant("ActionView::Base")
|
120
|
+
tmpl = item.template
|
121
|
+
if tmpl.nil?
|
122
|
+
item.path.inspect
|
123
|
+
else
|
124
|
+
[tmpl.base_path, tmpl.name].join("/")
|
125
|
+
end
|
126
|
+
when find_constant("ActiveRecord::Base")
|
127
|
+
string = "#{item.class.name}(#{item.id})"
|
128
|
+
else
|
129
|
+
string = item.class.name
|
130
|
+
end
|
131
|
+
else
|
132
|
+
string
|
133
|
+
end
|
134
|
+
rescue Exception => ex
|
135
|
+
"..."
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def make_string_of(array)
|
140
|
+
array.map do |item|
|
141
|
+
short_string(item)
|
142
|
+
end.join(",")
|
143
|
+
end
|
144
|
+
|
145
|
+
def start_event(file, line, method, context, arguments)
|
146
|
+
@event_id += 1
|
147
|
+
|
148
|
+
arguments_string = make_string_of(arguments)
|
149
|
+
event = ServerEvent.new(@event_id, file, line, method, context, arguments_string)
|
150
|
+
@pstack.push event
|
151
|
+
end
|
152
|
+
|
153
|
+
def finish_event
|
154
|
+
event = @pstack.pop
|
155
|
+
if event.nil?
|
156
|
+
else
|
157
|
+
event.finish
|
158
|
+
|
159
|
+
|
160
|
+
unless (parent = @pstack.last).nil?
|
161
|
+
parent.children.push event
|
162
|
+
else
|
163
|
+
@children.push event
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def hash_representation
|
169
|
+
finish
|
170
|
+
{ 'trace' => {
|
171
|
+
|
172
|
+
'url' => "/speedtracer?id=#@id",
|
173
|
+
|
174
|
+
'frameStack' => {
|
175
|
+
|
176
|
+
'range' => range(@start, @finish),
|
177
|
+
'operation' => {
|
178
|
+
'type' => 'HTTP',
|
179
|
+
'label' => [@method, @uri].join(' ')
|
180
|
+
},
|
181
|
+
'children' => @children
|
182
|
+
|
183
|
+
}, #end frameStack
|
184
|
+
|
185
|
+
'resources' => {
|
186
|
+
'Application' => '/', #Should get the Rails app name...
|
187
|
+
'Application.endpoint' => '/' #Should get the env path thing
|
188
|
+
}, #From what I can tell, Speed Tracer treats this whole hash as optional
|
189
|
+
|
190
|
+
'range' => range(@start, @finish)
|
191
|
+
}
|
192
|
+
}
|
193
|
+
end
|
194
|
+
|
195
|
+
def to_html
|
196
|
+
hash = hash_representation
|
197
|
+
extra = {:self_time => duration - time_in_children}
|
198
|
+
"<a href='#{hash['url']}'>Raw JSON</a>\n" +
|
199
|
+
render_template('serverevent', extra.merge(symbolize_hash(hash['trace']['frameStack'])))
|
200
|
+
end
|
201
|
+
|
202
|
+
def finish
|
203
|
+
super()
|
204
|
+
|
205
|
+
until @pstack.empty?
|
206
|
+
finish_event
|
207
|
+
end
|
208
|
+
self
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "rack/bug/speedtracer/duck-puncher"
|
3
|
+
|
4
|
+
describe "Duck punching" do
|
5
|
+
describe "::find_constant" do
|
6
|
+
it "should find constants" do
|
7
|
+
find_constant("Rack::Bug").should == Rack::Bug
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should yield constants" do
|
11
|
+
find_constant("Rack::Bug") do |rb|
|
12
|
+
rb.should == Rack::Bug
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should swallow exceptions when constants aren't found" do
|
17
|
+
expect do
|
18
|
+
find_constant("I::Made::This::Up")
|
19
|
+
end.to_not raise_error
|
20
|
+
end
|
21
|
+
end
|
22
|
+
describe "with tracing" do
|
23
|
+
|
24
|
+
before :each do
|
25
|
+
@tracer = Rack::Bug::SpeedTrace::Tracer.new(0, 'GET', '/test')
|
26
|
+
Thread.current["st.tracer"] = @tracer
|
27
|
+
|
28
|
+
@test_class = Class.new do
|
29
|
+
class << self
|
30
|
+
def klass_method(a,b,c)
|
31
|
+
b
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def instance_method(a,b,c)
|
36
|
+
a << b
|
37
|
+
a << c
|
38
|
+
end
|
39
|
+
|
40
|
+
def other_instance_method
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should trace instance methods" do
|
46
|
+
@test_class.trace_methods :instance_method
|
47
|
+
|
48
|
+
test_instance = @test_class.new
|
49
|
+
|
50
|
+
array = []
|
51
|
+
test_instance.instance_method(array, 1, 2)
|
52
|
+
array.should include(1,2)
|
53
|
+
|
54
|
+
@tracer.finish.to_json.should =~ /"operation":\s*{\s*"label":\s*"#instance_method\(\[\],1,2\)"/m
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should trace class methods" do
|
58
|
+
@test_class.trace_class_methods :klass_method
|
59
|
+
|
60
|
+
@test_class.klass_method([5],6,7).should == 6
|
61
|
+
@tracer.finish.to_json.should =~ /"label":\s*"::klass_method\(\[5\],6,7\)"/
|
62
|
+
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'rack/bug/speedtracer/profiling'
|
3
|
+
|
4
|
+
describe Rack::Bug::ProfilingSpeedTracer do
|
5
|
+
it 'should set the X-TraceUrl header after rendering the response' do
|
6
|
+
respond_with(200)
|
7
|
+
response = get('/')
|
8
|
+
|
9
|
+
response.headers.should include 'X-TraceUrl'
|
10
|
+
response.headers['X-TraceUrl'].should match(/^\/speedtracer\?id=/)
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Rack::Bug::SpeedTracer do
|
4
|
+
let(:app) { [200, {'Content-Type' => 'text/plain'}, 'Hello World'] }
|
5
|
+
|
6
|
+
describe 'middleware' do
|
7
|
+
it 'take a backend and returns a middleware component' do
|
8
|
+
Rack::Bug::SpeedTracer.new(app).should respond_to(:call)
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'take an options Hash' do
|
12
|
+
lambda { Rack::Cache.new(app, {}) }.should_not raise_error(ArgumentError)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe 'response' do
|
17
|
+
it 'should set the X-TraceUrl header after rendering the response' do
|
18
|
+
respond_with(200)
|
19
|
+
response = get('/')
|
20
|
+
|
21
|
+
response.headers.should include 'X-TraceUrl'
|
22
|
+
response.headers['X-TraceUrl'].should match(/^\/speedtracer\?id=/)
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'should respond with 200 to HEAD requests to the speedtracer endpoint' do
|
26
|
+
respond_with(200)
|
27
|
+
response = head('/speedtracer?id=test')
|
28
|
+
|
29
|
+
response.status.should == 200
|
30
|
+
response.headers['Content-Length'].to_i.should == 0
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should return a stored trace in JSON format' do
|
34
|
+
sample_trace = Yajl::Encoder.encode({'trace' => {}})
|
35
|
+
tracer = mock(Rack::Bug::SpeedTrace::Tracer)
|
36
|
+
tracer.stub!(:to_json => sample_trace)
|
37
|
+
|
38
|
+
respond_with(200)
|
39
|
+
response = get('/speedtracer?id=test') do |st|
|
40
|
+
st.database['test'] = tracer
|
41
|
+
end
|
42
|
+
|
43
|
+
response.body.should == sample_trace
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'should return 404 on missing trace' do
|
47
|
+
respond_with(404)
|
48
|
+
response = get('/speedtracer?id=test-missing')
|
49
|
+
response.status.should == 404
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
data/spec/tracer_spec.rb
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Rack::Bug::SpeedTrace::Tracer do
|
4
|
+
|
5
|
+
it 'should accept unique id, method, uri on initialize' do
|
6
|
+
lambda { Rack::Bug::SpeedTrace::Tracer.new(1, 'GET', '/') }.should_not raise_error
|
7
|
+
end
|
8
|
+
|
9
|
+
describe 'response' do
|
10
|
+
let(:tracer) { Rack::Bug::SpeedTrace::Tracer.new(1, 'GET', '/test') }
|
11
|
+
|
12
|
+
it 'should produce decent HTML' do
|
13
|
+
doc = tracer.finish.to_html
|
14
|
+
doc.should =~ /<div class="traceblock">\s*GET \/test/
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'should serialize to json on finish' do
|
18
|
+
lambda { Yajl::Parser.parse(tracer.finish.to_json) }.should_not raise_error
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'should conform to base speedtracer JSON schema' do
|
22
|
+
trace = Yajl::Parser.parse(tracer.finish.to_json)['trace']
|
23
|
+
|
24
|
+
# Example base trace:
|
25
|
+
# {"date"=>1279403357,
|
26
|
+
# "application"=>"Rack SpeedTracer",
|
27
|
+
# "id"=>1,
|
28
|
+
# "range"=>{"duration"=>0, "end"=>"1279403357651", "start"=>"1279403357651"},
|
29
|
+
# "frameStack"=>
|
30
|
+
# {"id"=>"0",
|
31
|
+
# "range"=>{"duration"=>0, "end"=>"1279403357651", "start"=>"1279403357651"},
|
32
|
+
# "operation"=>{"label"=>"GET /test", "type"=>"HTTP"},
|
33
|
+
# "children"=>[]}}
|
34
|
+
|
35
|
+
trace.should include 'range'
|
36
|
+
trace['range'].should be_an_instance_of Hash
|
37
|
+
|
38
|
+
trace.should include 'frameStack'
|
39
|
+
trace['frameStack'].should be_an_instance_of Hash
|
40
|
+
|
41
|
+
# root node description
|
42
|
+
root = trace['frameStack']
|
43
|
+
|
44
|
+
root.should include 'range'
|
45
|
+
root['range'].should be_an_instance_of Hash
|
46
|
+
|
47
|
+
root.should include 'operation'
|
48
|
+
root['operation'].should be_an_instance_of Hash
|
49
|
+
root['operation']['label'].should match('GET /test')
|
50
|
+
root['operation']['type'].should match('HTTP')
|
51
|
+
|
52
|
+
root.should include 'children'
|
53
|
+
root['children'].should be_an_instance_of Array
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe 'code tracing' do
|
58
|
+
before :each do
|
59
|
+
@tracer = Rack::Bug::SpeedTrace::Tracer.new(1, 'GET', '/test')
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'should provide a mechanism to trace a code block' do
|
63
|
+
lambda { @tracer.run { sleep(0.01) }}.should_not raise_error
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'should measure execution time in milliseconds' do
|
67
|
+
@tracer.run { sleep(0.01) }
|
68
|
+
trace = Yajl::Parser.parse(@tracer.finish.to_json)['trace']
|
69
|
+
|
70
|
+
trace['range']['duration'].to_i.should == 10
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'should report traced codeblocks' do
|
74
|
+
@tracer.run { sleep(0.01) }
|
75
|
+
trace = Yajl::Parser.parse(@tracer.finish.to_json)['trace']
|
76
|
+
|
77
|
+
trace['frameStack']['children'].size.should == 1
|
78
|
+
|
79
|
+
child = trace['frameStack']['children'].first
|
80
|
+
child.should include 'operation'
|
81
|
+
child['operation'].should include 'label'
|
82
|
+
child.should include 'children'
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'should accept optional label for each trace' do
|
86
|
+
@tracer.run('label') { sleep(0.01) }
|
87
|
+
trace = Yajl::Parser.parse(@tracer.finish.to_json)['trace']
|
88
|
+
|
89
|
+
trace['frameStack']['children'].first['operation']['label'].should match('label')
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'should produce nested traces' do
|
93
|
+
@tracer.run('parent') do
|
94
|
+
@tracer.run('child') { sleep(0.01) }
|
95
|
+
end
|
96
|
+
|
97
|
+
trace = Yajl::Parser.parse(@tracer.finish.to_json)['trace']
|
98
|
+
|
99
|
+
parent = trace['frameStack']['children'].first
|
100
|
+
parent['operation']['label'].should match('parent')
|
101
|
+
parent['children'].size.should == 1
|
102
|
+
|
103
|
+
child = parent['children'].first
|
104
|
+
child['operation']['label'].should match('child')
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
$LOAD_PATH.unshift File.dirname(File.dirname(__FILE__)) + '/lib'
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'rack/bug/speedtracer'
|
5
|
+
require 'yajl'
|
6
|
+
require 'spec'
|
7
|
+
require 'pp'
|
8
|
+
|
9
|
+
[ STDOUT, STDERR ].each { |io| io.sync = true }
|
10
|
+
|
11
|
+
def respond_with(status=200, headers={}, body=['Hello World'])
|
12
|
+
called = false
|
13
|
+
@app = lambda do |env|
|
14
|
+
called = true
|
15
|
+
response = Rack::Response.new(body, status, headers)
|
16
|
+
request = Rack::Request.new(env)
|
17
|
+
|
18
|
+
yield request, response if block_given?
|
19
|
+
|
20
|
+
response.finish
|
21
|
+
end
|
22
|
+
|
23
|
+
@app
|
24
|
+
end
|
25
|
+
|
26
|
+
def request(method, uri='/', opts={})
|
27
|
+
@errors ||= []
|
28
|
+
opts = {
|
29
|
+
'rack.run_once' => true,
|
30
|
+
'rack.errors' => @errors,
|
31
|
+
'rack-bug.panels' => []
|
32
|
+
}.merge(opts)
|
33
|
+
|
34
|
+
@speedtracer ||= self.class.described_class.new(@app)
|
35
|
+
@request = Rack::MockRequest.new(@speedtracer)
|
36
|
+
|
37
|
+
yield @speedtracer if block_given?
|
38
|
+
|
39
|
+
@request.request(method.to_s.upcase, uri, opts)
|
40
|
+
end
|
41
|
+
|
42
|
+
def get(uri, env={}, &b)
|
43
|
+
request(:get, uri, env, &b)
|
44
|
+
end
|
45
|
+
|
46
|
+
def head(uri, env={}, &b)
|
47
|
+
request(:head, uri, env, &b)
|
48
|
+
end
|
metadata
ADDED
@@ -0,0 +1,130 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rack-bug-speedtracer
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Ilya Grigorik
|
14
|
+
- Judson Lester
|
15
|
+
autorequire:
|
16
|
+
bindir: bin
|
17
|
+
cert_chain: []
|
18
|
+
|
19
|
+
date: 2010-07-20 00:00:00 -07:00
|
20
|
+
default_executable:
|
21
|
+
dependencies:
|
22
|
+
- !ruby/object:Gem::Dependency
|
23
|
+
name: rack-bug
|
24
|
+
prerelease: false
|
25
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
26
|
+
none: false
|
27
|
+
requirements:
|
28
|
+
- - ">="
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
hash: 3
|
31
|
+
segments:
|
32
|
+
- 0
|
33
|
+
version: "0"
|
34
|
+
type: :runtime
|
35
|
+
version_requirements: *id001
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: uuid
|
38
|
+
prerelease: false
|
39
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ">="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
hash: 3
|
45
|
+
segments:
|
46
|
+
- 0
|
47
|
+
version: "0"
|
48
|
+
type: :runtime
|
49
|
+
version_requirements: *id002
|
50
|
+
- !ruby/object:Gem::Dependency
|
51
|
+
name: yajl-ruby
|
52
|
+
prerelease: false
|
53
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
54
|
+
none: false
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
hash: 3
|
59
|
+
segments:
|
60
|
+
- 0
|
61
|
+
version: "0"
|
62
|
+
type: :runtime
|
63
|
+
version_requirements: *id003
|
64
|
+
description: Rack-bug SpeedTracer panel for server side debugging
|
65
|
+
email: judson@lrdesign.com
|
66
|
+
executables: []
|
67
|
+
|
68
|
+
extensions: []
|
69
|
+
|
70
|
+
extra_rdoc_files:
|
71
|
+
- README.md
|
72
|
+
files:
|
73
|
+
- README.md
|
74
|
+
- Rakefile
|
75
|
+
- VERSION
|
76
|
+
- examples/runner.png
|
77
|
+
- examples/runner.rb
|
78
|
+
- lib/rack/bug/speedtracer.rb
|
79
|
+
- lib/rack/bug/views/serverevent.html.erb
|
80
|
+
- lib/rack/bug/views/traces.html.erb
|
81
|
+
- lib/rack/bug/speedtracer/profiling.rb
|
82
|
+
- lib/rack/bug/speedtracer/duck-puncher.rb
|
83
|
+
- lib/rack/bug/speedtracer/default-rails-tracing.rb
|
84
|
+
- lib/rack/bug/speedtracer/trace-app.rb
|
85
|
+
- lib/rack/bug/speedtracer/render.rb
|
86
|
+
- lib/rack/bug/speedtracer/tracer.rb
|
87
|
+
- lib/rack-bug-speedtracer.rb
|
88
|
+
- spec_help/spec_helper.rb
|
89
|
+
- spec/speedtracer_spec.rb
|
90
|
+
- spec/profiling-speedtracer.rb
|
91
|
+
- spec/duck-puncher.rb
|
92
|
+
- spec/tracer_spec.rb
|
93
|
+
has_rdoc: true
|
94
|
+
homepage: http://github.com/nyarly/rack-speedtracer
|
95
|
+
licenses: []
|
96
|
+
|
97
|
+
post_install_message:
|
98
|
+
rdoc_options:
|
99
|
+
- --charset=UTF-8
|
100
|
+
require_paths:
|
101
|
+
- lib
|
102
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
103
|
+
none: false
|
104
|
+
requirements:
|
105
|
+
- - ">="
|
106
|
+
- !ruby/object:Gem::Version
|
107
|
+
hash: 3
|
108
|
+
segments:
|
109
|
+
- 0
|
110
|
+
version: "0"
|
111
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
112
|
+
none: false
|
113
|
+
requirements:
|
114
|
+
- - ">="
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
hash: 3
|
117
|
+
segments:
|
118
|
+
- 0
|
119
|
+
version: "0"
|
120
|
+
requirements: []
|
121
|
+
|
122
|
+
rubyforge_project: rack-bug-speedtracer
|
123
|
+
rubygems_version: 1.3.7
|
124
|
+
signing_key:
|
125
|
+
specification_version: 3
|
126
|
+
summary: Rack::Bug Panel for server side information for Speed Tracer
|
127
|
+
test_files:
|
128
|
+
- spec/speedtracer_spec.rb
|
129
|
+
- spec/tracer_spec.rb
|
130
|
+
- examples/runner.rb
|