pilfer 0.0.1.pre3 → 0.0.1.pre4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +8 -8
- data/Gemfile +4 -1
- data/README.md +7 -6
- data/lib/pilfer/logger.rb +3 -2
- data/lib/pilfer/middleware.rb +36 -0
- data/lib/pilfer/profile.rb +8 -8
- data/lib/pilfer/version.rb +1 -1
- data/pilfer.gemspec +3 -10
- data/spec/files/hello.rb +1 -0
- data/spec/files/test.rb +17 -0
- data/spec/helper.rb +7 -0
- data/spec/pilfer/logger_spec.rb +88 -0
- data/spec/pilfer/middleware_spec.rb +55 -0
- data/spec/pilfer/profile_spec.rb +61 -0
- data/spec/pilfer/profiler_spec.rb +52 -0
- metadata +26 -56
- data/ext/extconf.rb +0 -19
- data/ext/rblineprof.c +0 -619
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
NzFkODBmMDU3MzMxMmQ2MGRiYjZhMmI2Y2I2OWQ4NmE0YzVlMmJiOQ==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
ZjVlOGMyYWFiNjM5ZWIyODI5NGU5MDI3YWYxZmJmNGM3YzFlNWQ4Nw==
|
7
7
|
!binary "U0hBNTEy":
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
NTI5NjVlNGNlNGU0NzlkNzgwYWI0YTYyMjhmNmMzYWQyOWQwOWFkYmJiN2Ey
|
10
|
+
NTY4NWUxOTRkOTRjNjlhZjA3ZmEwNWFmOWQ2YmRlYjhmNjEyNjJiMDg2OWY2
|
11
|
+
NTA0MTZhYWI3YzYzOWE5OWZkODRmMWI4MGI3MjhkYjEwYzk0NGU=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
YzY3Mzk5N2VlNzBiNzdmMWU2ZDBhNDMxMGEwNDVhODQwMTI1MTRhMWQxODJk
|
14
|
+
OGM1MDIzYjVlYTVkYTFkYTU5MjMwNzE2ZDA5YjkxNzEzZWU5OWYwYzc5OTU3
|
15
|
+
OWVhZmQ0YTc5NGNmNTY3MWU0Zjk0ZDc1NDA5ZmVmODg0ZmE4YTU=
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -77,9 +77,9 @@ reporter = Pilfer::Server.new('https://pilfer.com', 'abc123')
|
|
77
77
|
Profile your Rack or Rails app using `Pilfer::Middleware`.
|
78
78
|
|
79
79
|
```ruby
|
80
|
-
reporter = Pilfer::
|
81
|
-
|
82
|
-
use Pilfer::Middleware
|
80
|
+
reporter = Pilfer::Logger.new($stdout, :app_root => Rails.root)
|
81
|
+
profiler = Pilfer::Profiler.new(reporter)
|
82
|
+
use Pilfer::Middleware :profiler => profiler
|
83
83
|
```
|
84
84
|
|
85
85
|
Restrict the files profiled by passing a regular expression with
|
@@ -87,14 +87,15 @@ Restrict the files profiled by passing a regular expression with
|
|
87
87
|
|
88
88
|
```ruby
|
89
89
|
matcher = %r{^#{Regexp.escape(Rails.root.to_s)}/(app|config|lib|vendor/plugin)}
|
90
|
-
use Pilfer::Middleware,
|
90
|
+
use Pilfer::Middleware, :files_matching => matcher,
|
91
|
+
:profiler => profiler
|
91
92
|
```
|
92
93
|
|
93
94
|
You probably don't want to profile _every_ request. The given block will be
|
94
95
|
evaluated on each request to determine if a profile should be run.
|
95
96
|
|
96
97
|
```ruby
|
97
|
-
use Pilfer::Middleware,
|
98
|
+
use Pilfer::Middleware, :profiler => profiler do
|
98
99
|
# Profile 1% of requests.
|
99
100
|
rand(100) == 1
|
100
101
|
end
|
@@ -103,7 +104,7 @@ end
|
|
103
104
|
The Rack environment is available to allow profiling on demand.
|
104
105
|
|
105
106
|
```ruby
|
106
|
-
use Pilfer::Middleware,
|
107
|
+
use Pilfer::Middleware, :profiler => profiler do |env|
|
107
108
|
env.query_string.include? 'profile=true'
|
108
109
|
end
|
109
110
|
```
|
data/lib/pilfer/logger.rb
CHANGED
@@ -8,7 +8,7 @@ module Pilfer
|
|
8
8
|
def initialize(path_or_io, options = {})
|
9
9
|
@logger = ::Logger.new(path_or_io)
|
10
10
|
if (app_root = options[:app_root])
|
11
|
-
app_root += '/' unless app_root[-1] == '/'
|
11
|
+
app_root += '/' unless app_root[-1, 1] == '/'
|
12
12
|
@app_root = %r{^#{Regexp.escape(app_root)}}
|
13
13
|
end
|
14
14
|
end
|
@@ -25,7 +25,8 @@ module Pilfer
|
|
25
25
|
private
|
26
26
|
|
27
27
|
def print_report_banner(profile_start)
|
28
|
-
|
28
|
+
formatted_start = profile_start.utc.strftime('%Y-%m-%d %H:%M:%S UTC')
|
29
|
+
logger.info "Profile start=#{formatted_start}"
|
29
30
|
end
|
30
31
|
|
31
32
|
def print_file_banner(path, data)
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'pilfer/logger'
|
2
|
+
require 'pilfer/profiler'
|
3
|
+
|
4
|
+
module Pilfer
|
5
|
+
class Middleware
|
6
|
+
attr_accessor :app, :profiler, :file_matcher, :profile_guard
|
7
|
+
|
8
|
+
def initialize(app, options = {}, &profile_guard)
|
9
|
+
@app = app
|
10
|
+
@profiler = options[:profiler] || default_profiler
|
11
|
+
@file_matcher = options[:file_matcher]
|
12
|
+
@profile_guard = profile_guard
|
13
|
+
end
|
14
|
+
|
15
|
+
def call(env)
|
16
|
+
if profile_guard.call(env)
|
17
|
+
run_profiler { app.call(env) }
|
18
|
+
else
|
19
|
+
app.call(env)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def run_profiler(&downstream)
|
24
|
+
if file_matcher
|
25
|
+
profiler.profile_files_matching(file_matcher, &downstream)
|
26
|
+
else
|
27
|
+
profiler.profile(&downstream)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def default_profiler
|
32
|
+
reporter = Pilfer::Logger.new($stdout, :app_root => ENV['PWD'])
|
33
|
+
Pilfer::Profiler.new(reporter)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/lib/pilfer/profile.rb
CHANGED
@@ -9,19 +9,18 @@ module Pilfer
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def each(&block)
|
12
|
-
files.each(&block)
|
12
|
+
files.to_a.sort_by(&:first).each(&block)
|
13
13
|
end
|
14
14
|
|
15
15
|
def files
|
16
|
-
data.
|
17
|
-
profile_lines =
|
18
|
-
|
19
|
-
each_with_object({}) do |(data, number), lines|
|
16
|
+
data.inject({}) do |files, (file, lines)|
|
17
|
+
profile_lines = {}
|
18
|
+
lines[1..-1].each_with_index do |data, number|
|
20
19
|
next unless data.any? {|datum| datum > 0 }
|
21
20
|
wall_time, cpu_time, calls = data
|
22
|
-
|
23
|
-
|
24
|
-
|
21
|
+
profile_lines[number] = { 'wall_time' => wall_time,
|
22
|
+
'cpu_time' => cpu_time,
|
23
|
+
'calls' => calls }
|
25
24
|
end
|
26
25
|
|
27
26
|
total_wall, child_wall, exclusive_wall,
|
@@ -33,6 +32,7 @@ module Pilfer
|
|
33
32
|
files[file] = { 'wall_time' => wall,
|
34
33
|
'cpu_time' => cpu,
|
35
34
|
'lines' => profile_lines }
|
35
|
+
files
|
36
36
|
end
|
37
37
|
end
|
38
38
|
end
|
data/lib/pilfer/version.rb
CHANGED
data/pilfer.gemspec
CHANGED
@@ -14,19 +14,12 @@ Gem::Specification.new do |spec|
|
|
14
14
|
spec.files = %w(Gemfile LICENSE README.md)
|
15
15
|
spec.files << 'pilfer.gemspec'
|
16
16
|
spec.files += Dir.glob('lib/**/*.rb')
|
17
|
-
spec.files += Dir.glob('
|
17
|
+
spec.files += Dir.glob('spec/**/*.rb')
|
18
18
|
spec.files += Dir.glob('script/*')
|
19
|
-
spec.test_files = Dir.glob('
|
19
|
+
spec.test_files = Dir.glob('spec/**/*.rb')
|
20
20
|
|
21
|
+
spec.add_dependency 'rblineprof', '~> 0.3.2'
|
21
22
|
spec.add_development_dependency 'bundler', '~> 1.0'
|
22
|
-
spec.add_development_dependency 'rspec'
|
23
|
-
spec.add_development_dependency 'rake'
|
24
|
-
spec.add_development_dependency 'rack', '~> 1.5.2'
|
25
23
|
|
26
24
|
spec.required_rubygems_version = '>= 1.3.6'
|
27
|
-
|
28
|
-
# Bundled latest rblineprof
|
29
|
-
spec.extensions = 'ext/extconf.rb'
|
30
|
-
spec.files += Dir.glob('ext/*')
|
31
|
-
spec.add_dependency 'debugger-ruby_core_source', '~> 1.2'
|
32
25
|
end
|
data/spec/files/hello.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
print 'Hello '
|
data/spec/files/test.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
require 'pilfer/logger'
|
3
|
+
require 'pilfer/profiler'
|
4
|
+
require 'stringio'
|
5
|
+
|
6
|
+
output = StringIO.new
|
7
|
+
reporter = Pilfer::Logger.new(output)
|
8
|
+
profiler = Pilfer::Profiler.new(reporter)
|
9
|
+
path = File.expand_path(File.dirname(__FILE__))
|
10
|
+
|
11
|
+
profiler.profile_files_matching(%r{^#{Regexp.escape(path)}}) do
|
12
|
+
require 'hello'
|
13
|
+
puts 'world!'
|
14
|
+
10.times do
|
15
|
+
sleep 0.01
|
16
|
+
end
|
17
|
+
end
|
data/spec/helper.rb
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'json'
|
3
|
+
require 'tempfile'
|
4
|
+
require 'pilfer/logger'
|
5
|
+
|
6
|
+
describe Pilfer::Logger do
|
7
|
+
let(:spec_root) { File.expand_path('..', File.dirname(__FILE__)) }
|
8
|
+
let(:profile) {
|
9
|
+
profile_file = File.join(spec_root, 'files', 'profile.json')
|
10
|
+
profile_content = File.read(profile_file).gsub('SPEC_ROOT', spec_root)
|
11
|
+
JSON.parse(profile_content)
|
12
|
+
}
|
13
|
+
let(:reporter) { StringIO.new }
|
14
|
+
let(:start) { Time.at(42) }
|
15
|
+
let(:output) {
|
16
|
+
reporter.string.each_line.map {|line|
|
17
|
+
line.sub(/I, \[[^\]]+\] INFO -- : /, '')
|
18
|
+
}.join
|
19
|
+
}
|
20
|
+
let(:first_file) {
|
21
|
+
output.
|
22
|
+
split("\n")[1].
|
23
|
+
split(' ').
|
24
|
+
first
|
25
|
+
}
|
26
|
+
|
27
|
+
describe '#write' do
|
28
|
+
it 'writes profile to reporter' do
|
29
|
+
expected = <<-EOS
|
30
|
+
Profile start=1970-01-01 00:00:42 UTC
|
31
|
+
#{spec_root}/files/hello.rb wall_time=0.0ms cpu_time=0.0ms
|
32
|
+
0.0ms ( 2) | print 'Hello '
|
33
|
+
#{spec_root}/files/test.rb wall_time=113.7ms cpu_time=5.3ms
|
34
|
+
| require 'bundler/setup'
|
35
|
+
| require 'pilfer/logger'
|
36
|
+
| require 'pilfer/profiler'
|
37
|
+
| require 'stringio'
|
38
|
+
|
|
39
|
+
| output = StringIO.new
|
40
|
+
| reporter = Pilfer::Logger.new(output)
|
41
|
+
| profiler = Pilfer::Profiler.new(reporter)
|
42
|
+
| path = File.expand_path(File.dirname(__FILE__))
|
43
|
+
|
|
44
|
+
| profiler.profile_files_matching(%r{^\#{Regexp.escape(path)}}) do
|
45
|
+
5.1ms ( 3) | require 'hello'
|
46
|
+
0.0ms ( 4) | puts 'world!'
|
47
|
+
108.6ms ( 1) | 10.times do
|
48
|
+
108.4ms ( 10) | sleep 0.01
|
49
|
+
| end
|
50
|
+
| end
|
51
|
+
EOS
|
52
|
+
Pilfer::Logger.new(reporter).write(profile, start)
|
53
|
+
output.should eq(expected)
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'omits app root' do
|
57
|
+
Pilfer::Logger.new(reporter, :app_root => spec_root).
|
58
|
+
write(profile, start)
|
59
|
+
first_file.should eq('files/hello.rb')
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'omits app root with trailing separator' do
|
63
|
+
Pilfer::Logger.new(reporter, :app_root => spec_root + '/').
|
64
|
+
write(profile, start)
|
65
|
+
first_file.should eq('files/hello.rb')
|
66
|
+
end
|
67
|
+
|
68
|
+
context 'with a nonexistent file' do
|
69
|
+
let(:profile) {{
|
70
|
+
"(eval)" => [[113692, 31, 5026, 5313, 18, 4868], [0, 0, 0]]
|
71
|
+
}}
|
72
|
+
|
73
|
+
it 'omits the source of the nonexistent file' do
|
74
|
+
expected = <<-EOS
|
75
|
+
Profile start=1970-01-01 00:00:42 UTC
|
76
|
+
(eval) wall_time=113.7ms cpu_time=5.3ms
|
77
|
+
EOS
|
78
|
+
Pilfer::Logger.new(reporter).write(profile, start)
|
79
|
+
output.should eq(expected)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'appends to the log file' do
|
84
|
+
3.times { Pilfer::Logger.new(reporter).write(profile, start) }
|
85
|
+
output.scan('Profile start=').size.should eq(3)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'rack/mock'
|
3
|
+
require 'pilfer/middleware'
|
4
|
+
|
5
|
+
describe Pilfer::Middleware do
|
6
|
+
let(:env) { Rack::MockRequest.env_for }
|
7
|
+
let(:app) { stub(:app, :call => nil) }
|
8
|
+
let(:profiler) { stub(:profiler, :profile => nil) }
|
9
|
+
let(:options) {{ :profiler => profiler }}
|
10
|
+
let(:guard) { Proc.new do true end }
|
11
|
+
subject { Pilfer::Middleware.new(app, options, &guard) }
|
12
|
+
|
13
|
+
it 'profiles and calls the downstream app' do
|
14
|
+
app.should_receive(:call).with(env).once
|
15
|
+
profiler.should_receive(:profile).with(no_args).and_yield
|
16
|
+
subject.call(env)
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'passes rack env to guard' do
|
20
|
+
guard_args = nil
|
21
|
+
guard = lambda do |*args| guard_args = args end
|
22
|
+
|
23
|
+
Pilfer::Middleware.new(app, options, &guard).call(env)
|
24
|
+
guard_args.should_not be_nil
|
25
|
+
guard_args.size.should eq(1)
|
26
|
+
guard_args.first.should eq(env)
|
27
|
+
end
|
28
|
+
|
29
|
+
context 'with file matcher' do
|
30
|
+
let(:file_matcher) { stub(:file_matcher) }
|
31
|
+
let(:options) {{ :file_matcher => file_matcher,
|
32
|
+
:profiler => profiler }}
|
33
|
+
|
34
|
+
it 'passes file matcher to profiler and calls the downstream app' do
|
35
|
+
app.should_receive(:call).with(env).once
|
36
|
+
profiler.should_receive(:profile_files_matching).with(file_matcher).
|
37
|
+
and_yield
|
38
|
+
subject.call(env)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
context 'when guard returns false' do
|
43
|
+
let(:guard) { Proc.new do false end }
|
44
|
+
|
45
|
+
it 'calls the downstream app' do
|
46
|
+
app.should_receive(:call).with(env).once
|
47
|
+
subject.call(env)
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'skips profiling' do
|
51
|
+
profiler.should_not_receive(:profile)
|
52
|
+
subject.call(env)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'json'
|
3
|
+
require 'pilfer/profile'
|
4
|
+
|
5
|
+
describe Pilfer::Profile do
|
6
|
+
let(:spec_root) { File.expand_path('..', File.dirname(__FILE__)) }
|
7
|
+
let(:data) {
|
8
|
+
profile_file = File.join(spec_root, 'files', 'profile.json')
|
9
|
+
profile_content = File.read(profile_file).gsub('SPEC_ROOT', spec_root)
|
10
|
+
JSON.parse(profile_content)
|
11
|
+
}
|
12
|
+
let(:start) { Time.at(42) }
|
13
|
+
subject { Pilfer::Profile.new(data, start) }
|
14
|
+
|
15
|
+
it 'should be an Enumerable' do
|
16
|
+
subject.should be_kind_of(Enumerable)
|
17
|
+
end
|
18
|
+
|
19
|
+
describe '#start' do
|
20
|
+
it 'returns the given start time' do
|
21
|
+
subject.start.should eq(start)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe '#each' do
|
26
|
+
it 'yields each file and data' do
|
27
|
+
actual = {}
|
28
|
+
subject.each do |file, data|
|
29
|
+
actual[file] = data
|
30
|
+
end
|
31
|
+
expected = {
|
32
|
+
"#{spec_root}/files/hello.rb" => {
|
33
|
+
"wall_time" => 31,
|
34
|
+
"cpu_time" => 18,
|
35
|
+
"lines" => {
|
36
|
+
0 => { "wall_time" => 31, "cpu_time" => 18, "calls" => 2} }
|
37
|
+
},
|
38
|
+
"#{spec_root}/files/test.rb" => {
|
39
|
+
"wall_time" => 113692,
|
40
|
+
"cpu_time" => 5313,
|
41
|
+
"lines" => {
|
42
|
+
11 => { "wall_time" => 5062, "cpu_time" => 4890, "calls" => 3 },
|
43
|
+
12 => { "wall_time" => 23, "cpu_time" => 14, "calls" => 4 },
|
44
|
+
13 => { "wall_time" => 108607, "cpu_time" => 409, "calls" => 1 },
|
45
|
+
14 => { "wall_time" => 108404, "cpu_time" => 310, "calls" => 10 }
|
46
|
+
}
|
47
|
+
}
|
48
|
+
}
|
49
|
+
actual.should eq(expected)
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'yields each file alphabetically' do
|
53
|
+
actual = []
|
54
|
+
subject.each do |file, data|
|
55
|
+
actual << file
|
56
|
+
end
|
57
|
+
expected = %W(#{spec_root}/files/hello.rb #{spec_root}/files/test.rb)
|
58
|
+
actual.should eq(expected)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'pilfer/profiler'
|
3
|
+
|
4
|
+
describe Pilfer::Profiler do
|
5
|
+
let(:reporter) { stub(:reporter, :write => nil) }
|
6
|
+
let(:start) { stub(:start) }
|
7
|
+
|
8
|
+
describe '#profile' do
|
9
|
+
it 'profiles all files by default' do
|
10
|
+
profiler = stub(:profiler)
|
11
|
+
profiler.should_receive(:call).with(/./)
|
12
|
+
Pilfer::Profiler.new(reporter).profile(profiler) { }
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'returns value of app' do
|
16
|
+
profiler = lambda {|*args, &app|
|
17
|
+
app.call
|
18
|
+
:profiler_response
|
19
|
+
}
|
20
|
+
|
21
|
+
response = Pilfer::Profiler.new(reporter).profile(profiler) {
|
22
|
+
:app_response
|
23
|
+
}
|
24
|
+
|
25
|
+
response.should eq(:app_response)
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'writes profile to reporter' do
|
29
|
+
profiler = stub(:profiler, :call => :profiler_response)
|
30
|
+
reporter.should_receive(:write).with(:profiler_response, start)
|
31
|
+
Pilfer::Profiler.new(reporter).profile(profiler, start) { }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe '#profile_files_matching' do
|
36
|
+
let(:matcher) { stub(:matcher) }
|
37
|
+
|
38
|
+
it 'passes file matcher to profiler' do
|
39
|
+
profiler = stub(:profiler)
|
40
|
+
profiler.should_receive(:call).with(matcher)
|
41
|
+
Pilfer::Profiler.new(reporter).
|
42
|
+
profile_files_matching(matcher, profiler) { }
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'writes profile to reporter' do
|
46
|
+
profiler = stub(:profiler, :call => :profiler_response)
|
47
|
+
reporter.should_receive(:write).with(:profiler_response, start)
|
48
|
+
Pilfer::Profiler.new(reporter).
|
49
|
+
profile_files_matching(matcher, profiler, start) { }
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pilfer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.1.
|
4
|
+
version: 0.0.1.pre4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Eric Lindvall
|
@@ -9,85 +9,42 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-04-
|
12
|
+
date: 2013-04-22 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
|
-
name:
|
15
|
+
name: rblineprof
|
16
16
|
requirement: !ruby/object:Gem::Requirement
|
17
17
|
requirements:
|
18
18
|
- - ~>
|
19
19
|
- !ruby/object:Gem::Version
|
20
|
-
version:
|
21
|
-
type: :
|
20
|
+
version: 0.3.2
|
21
|
+
type: :runtime
|
22
22
|
prerelease: false
|
23
23
|
version_requirements: !ruby/object:Gem::Requirement
|
24
24
|
requirements:
|
25
25
|
- - ~>
|
26
26
|
- !ruby/object:Gem::Version
|
27
|
-
version:
|
27
|
+
version: 0.3.2
|
28
28
|
- !ruby/object:Gem::Dependency
|
29
|
-
name:
|
30
|
-
requirement: !ruby/object:Gem::Requirement
|
31
|
-
requirements:
|
32
|
-
- - ! '>='
|
33
|
-
- !ruby/object:Gem::Version
|
34
|
-
version: '0'
|
35
|
-
type: :development
|
36
|
-
prerelease: false
|
37
|
-
version_requirements: !ruby/object:Gem::Requirement
|
38
|
-
requirements:
|
39
|
-
- - ! '>='
|
40
|
-
- !ruby/object:Gem::Version
|
41
|
-
version: '0'
|
42
|
-
- !ruby/object:Gem::Dependency
|
43
|
-
name: rake
|
44
|
-
requirement: !ruby/object:Gem::Requirement
|
45
|
-
requirements:
|
46
|
-
- - ! '>='
|
47
|
-
- !ruby/object:Gem::Version
|
48
|
-
version: '0'
|
49
|
-
type: :development
|
50
|
-
prerelease: false
|
51
|
-
version_requirements: !ruby/object:Gem::Requirement
|
52
|
-
requirements:
|
53
|
-
- - ! '>='
|
54
|
-
- !ruby/object:Gem::Version
|
55
|
-
version: '0'
|
56
|
-
- !ruby/object:Gem::Dependency
|
57
|
-
name: rack
|
29
|
+
name: bundler
|
58
30
|
requirement: !ruby/object:Gem::Requirement
|
59
31
|
requirements:
|
60
32
|
- - ~>
|
61
33
|
- !ruby/object:Gem::Version
|
62
|
-
version: 1.
|
34
|
+
version: '1.0'
|
63
35
|
type: :development
|
64
36
|
prerelease: false
|
65
37
|
version_requirements: !ruby/object:Gem::Requirement
|
66
38
|
requirements:
|
67
39
|
- - ~>
|
68
40
|
- !ruby/object:Gem::Version
|
69
|
-
version: 1.
|
70
|
-
- !ruby/object:Gem::Dependency
|
71
|
-
name: debugger-ruby_core_source
|
72
|
-
requirement: !ruby/object:Gem::Requirement
|
73
|
-
requirements:
|
74
|
-
- - ~>
|
75
|
-
- !ruby/object:Gem::Version
|
76
|
-
version: '1.2'
|
77
|
-
type: :runtime
|
78
|
-
prerelease: false
|
79
|
-
version_requirements: !ruby/object:Gem::Requirement
|
80
|
-
requirements:
|
81
|
-
- - ~>
|
82
|
-
- !ruby/object:Gem::Version
|
83
|
-
version: '1.2'
|
41
|
+
version: '1.0'
|
84
42
|
description:
|
85
43
|
email:
|
86
44
|
- eric@sevenscale.com
|
87
45
|
- larry@marburger.cc
|
88
46
|
executables: []
|
89
|
-
extensions:
|
90
|
-
- ext/extconf.rb
|
47
|
+
extensions: []
|
91
48
|
extra_rdoc_files: []
|
92
49
|
files:
|
93
50
|
- Gemfile
|
@@ -95,13 +52,19 @@ files:
|
|
95
52
|
- README.md
|
96
53
|
- pilfer.gemspec
|
97
54
|
- lib/pilfer/logger.rb
|
55
|
+
- lib/pilfer/middleware.rb
|
98
56
|
- lib/pilfer/profile.rb
|
99
57
|
- lib/pilfer/profiler.rb
|
100
58
|
- lib/pilfer/version.rb
|
59
|
+
- spec/files/hello.rb
|
60
|
+
- spec/files/test.rb
|
61
|
+
- spec/helper.rb
|
62
|
+
- spec/pilfer/logger_spec.rb
|
63
|
+
- spec/pilfer/middleware_spec.rb
|
64
|
+
- spec/pilfer/profile_spec.rb
|
65
|
+
- spec/pilfer/profiler_spec.rb
|
101
66
|
- script/package
|
102
67
|
- script/release
|
103
|
-
- ext/extconf.rb
|
104
|
-
- ext/rblineprof.c
|
105
68
|
homepage: https://github.com/eric/pilfer
|
106
69
|
licenses:
|
107
70
|
- MIT
|
@@ -126,4 +89,11 @@ rubygems_version: 2.0.3
|
|
126
89
|
signing_key:
|
127
90
|
specification_version: 4
|
128
91
|
summary: Look into your ruby with rblineprof
|
129
|
-
test_files:
|
92
|
+
test_files:
|
93
|
+
- spec/files/hello.rb
|
94
|
+
- spec/files/test.rb
|
95
|
+
- spec/helper.rb
|
96
|
+
- spec/pilfer/logger_spec.rb
|
97
|
+
- spec/pilfer/middleware_spec.rb
|
98
|
+
- spec/pilfer/profile_spec.rb
|
99
|
+
- spec/pilfer/profiler_spec.rb
|
data/ext/extconf.rb
DELETED
@@ -1,19 +0,0 @@
|
|
1
|
-
require 'mkmf'
|
2
|
-
|
3
|
-
if RUBY_VERSION >= "1.9"
|
4
|
-
require "debugger/ruby_core_source"
|
5
|
-
|
6
|
-
hdrs = proc {
|
7
|
-
have_type("rb_iseq_location_t", "vm_core.h")
|
8
|
-
|
9
|
-
have_header("vm_core.h") and
|
10
|
-
have_header("iseq.h")
|
11
|
-
}
|
12
|
-
|
13
|
-
unless Debugger::RubyCoreSource::create_makefile_with_core(hdrs, "rblineprof")
|
14
|
-
STDERR.puts "\nDebugger::RubyCoreSource::create_makefile failed"
|
15
|
-
exit(1)
|
16
|
-
end
|
17
|
-
else
|
18
|
-
create_makefile 'rblineprof'
|
19
|
-
end
|
data/ext/rblineprof.c
DELETED
@@ -1,619 +0,0 @@
|
|
1
|
-
#include <ruby.h>
|
2
|
-
#include <stdbool.h>
|
3
|
-
#include <time.h>
|
4
|
-
#include <sys/time.h>
|
5
|
-
#include <sys/resource.h>
|
6
|
-
|
7
|
-
#ifdef RUBY_VM
|
8
|
-
#include <ruby/re.h>
|
9
|
-
#include <ruby/intern.h>
|
10
|
-
#include <vm_core.h>
|
11
|
-
#include <iseq.h>
|
12
|
-
|
13
|
-
// There's a compile error on 1.9.3. So:
|
14
|
-
#ifdef RTYPEDDATA_DATA
|
15
|
-
#define ruby_current_thread ((rb_thread_t *)RTYPEDDATA_DATA(rb_thread_current()))
|
16
|
-
#endif
|
17
|
-
#else
|
18
|
-
#include <st.h>
|
19
|
-
#include <re.h>
|
20
|
-
#include <intern.h>
|
21
|
-
#include <node.h>
|
22
|
-
#include <env.h>
|
23
|
-
typedef rb_event_t rb_event_flag_t;
|
24
|
-
#endif
|
25
|
-
|
26
|
-
static VALUE gc_hook;
|
27
|
-
|
28
|
-
/*
|
29
|
-
* Time in microseconds
|
30
|
-
*/
|
31
|
-
typedef uint64_t prof_time_t;
|
32
|
-
|
33
|
-
/*
|
34
|
-
* Profiling snapshot
|
35
|
-
*/
|
36
|
-
typedef struct snapshot {
|
37
|
-
prof_time_t wall_time;
|
38
|
-
prof_time_t cpu_time;
|
39
|
-
} snapshot_t;
|
40
|
-
|
41
|
-
/*
|
42
|
-
* A line of Ruby source code
|
43
|
-
*/
|
44
|
-
typedef struct sourceline {
|
45
|
-
uint64_t calls; // total number of call/c_call events
|
46
|
-
snapshot_t total;
|
47
|
-
} sourceline_t;
|
48
|
-
|
49
|
-
/*
|
50
|
-
* Struct representing a single Ruby file.
|
51
|
-
*/
|
52
|
-
typedef struct sourcefile {
|
53
|
-
char *filename;
|
54
|
-
|
55
|
-
/* per line timing */
|
56
|
-
long nlines;
|
57
|
-
sourceline_t *lines;
|
58
|
-
|
59
|
-
/* overall file timing */
|
60
|
-
snapshot_t total;
|
61
|
-
snapshot_t child;
|
62
|
-
uint64_t depth;
|
63
|
-
snapshot_t exclusive_start;
|
64
|
-
snapshot_t exclusive;
|
65
|
-
} sourcefile_t;
|
66
|
-
|
67
|
-
/*
|
68
|
-
* An individual stack frame used to track
|
69
|
-
* calls and returns from Ruby methods
|
70
|
-
*/
|
71
|
-
typedef struct stackframe {
|
72
|
-
// data emitted from Ruby to our profiler hook
|
73
|
-
rb_event_flag_t event;
|
74
|
-
#ifdef RUBY_VM
|
75
|
-
rb_thread_t *thread;
|
76
|
-
#else
|
77
|
-
NODE *node;
|
78
|
-
#endif
|
79
|
-
VALUE self;
|
80
|
-
ID mid;
|
81
|
-
VALUE klass;
|
82
|
-
|
83
|
-
char *filename;
|
84
|
-
long line;
|
85
|
-
|
86
|
-
snapshot_t start;
|
87
|
-
sourcefile_t *srcfile;
|
88
|
-
} stackframe_t;
|
89
|
-
|
90
|
-
/*
|
91
|
-
* Static properties and rbineprof configuration
|
92
|
-
*/
|
93
|
-
static struct {
|
94
|
-
bool enabled;
|
95
|
-
|
96
|
-
// stack
|
97
|
-
#define MAX_STACK_DEPTH 32768
|
98
|
-
stackframe_t stack[MAX_STACK_DEPTH];
|
99
|
-
uint64_t stack_depth;
|
100
|
-
|
101
|
-
// single file mode, store filename and line data directly
|
102
|
-
char *source_filename;
|
103
|
-
sourcefile_t file;
|
104
|
-
|
105
|
-
// regex mode, store file data in hash table
|
106
|
-
VALUE source_regex;
|
107
|
-
st_table *files;
|
108
|
-
|
109
|
-
// cache
|
110
|
-
struct {
|
111
|
-
char *file;
|
112
|
-
sourcefile_t *srcfile;
|
113
|
-
} cache;
|
114
|
-
}
|
115
|
-
rblineprof = {
|
116
|
-
.enabled = false,
|
117
|
-
.source_regex = Qfalse
|
118
|
-
};
|
119
|
-
|
120
|
-
static prof_time_t
|
121
|
-
cputime_usec()
|
122
|
-
{
|
123
|
-
#if defined(__linux__)
|
124
|
-
struct timespec ts;
|
125
|
-
|
126
|
-
if (clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &ts) == 0) {
|
127
|
-
return (prof_time_t)ts.tv_sec*1e6 +
|
128
|
-
(prof_time_t)ts.tv_nsec*1e-3;
|
129
|
-
}
|
130
|
-
#endif
|
131
|
-
|
132
|
-
#if defined(RUSAGE_SELF)
|
133
|
-
struct rusage usage;
|
134
|
-
|
135
|
-
getrusage(RUSAGE_SELF, &usage);
|
136
|
-
return (prof_time_t)usage.ru_utime.tv_sec*1e6 +
|
137
|
-
(prof_time_t)usage.ru_utime.tv_usec;
|
138
|
-
#endif
|
139
|
-
|
140
|
-
return 0;
|
141
|
-
}
|
142
|
-
|
143
|
-
static prof_time_t
|
144
|
-
walltime_usec()
|
145
|
-
{
|
146
|
-
struct timeval tv;
|
147
|
-
gettimeofday(&tv, NULL);
|
148
|
-
return (prof_time_t)tv.tv_sec*1e6 +
|
149
|
-
(prof_time_t)tv.tv_usec;
|
150
|
-
}
|
151
|
-
|
152
|
-
static inline snapshot_t
|
153
|
-
snapshot_diff(snapshot_t *t1, snapshot_t *t2)
|
154
|
-
{
|
155
|
-
snapshot_t diff = {
|
156
|
-
.wall_time = t1->wall_time - t2->wall_time,
|
157
|
-
.cpu_time = t1->cpu_time - t2->cpu_time,
|
158
|
-
};
|
159
|
-
|
160
|
-
return diff;
|
161
|
-
}
|
162
|
-
|
163
|
-
static inline void
|
164
|
-
snapshot_increment(snapshot_t *s, snapshot_t *inc)
|
165
|
-
{
|
166
|
-
s->wall_time += inc->wall_time;
|
167
|
-
s->cpu_time += inc->cpu_time;
|
168
|
-
}
|
169
|
-
|
170
|
-
static inline void
|
171
|
-
stackframe_record(stackframe_t *frame, snapshot_t now, stackframe_t *caller_frame)
|
172
|
-
{
|
173
|
-
sourcefile_t *srcfile = frame->srcfile;
|
174
|
-
long line = frame->line;
|
175
|
-
|
176
|
-
/* allocate space for per-line data the first time */
|
177
|
-
if (srcfile->lines == NULL) {
|
178
|
-
srcfile->nlines = line + 100;
|
179
|
-
srcfile->lines = ALLOC_N(sourceline_t, srcfile->nlines);
|
180
|
-
MEMZERO(srcfile->lines, sourceline_t, srcfile->nlines);
|
181
|
-
}
|
182
|
-
|
183
|
-
/* grow the per-line array if necessary */
|
184
|
-
if (line >= srcfile->nlines) {
|
185
|
-
long prev_nlines = srcfile->nlines;
|
186
|
-
srcfile->nlines = line + 100;
|
187
|
-
|
188
|
-
REALLOC_N(srcfile->lines, sourceline_t, srcfile->nlines);
|
189
|
-
MEMZERO(srcfile->lines + prev_nlines, sourceline_t, srcfile->nlines - prev_nlines);
|
190
|
-
}
|
191
|
-
|
192
|
-
snapshot_t diff = snapshot_diff(&now, &frame->start);
|
193
|
-
sourceline_t *srcline = &(srcfile->lines[line]);
|
194
|
-
|
195
|
-
/* Line profiler metrics.
|
196
|
-
*/
|
197
|
-
|
198
|
-
srcline->calls++;
|
199
|
-
|
200
|
-
/* Increment current line's total_time.
|
201
|
-
*
|
202
|
-
* Skip the special case where the stack frame we're returning to
|
203
|
-
* had the same file/line. This fixes double counting on crazy one-liners.
|
204
|
-
*/
|
205
|
-
if (!(caller_frame && caller_frame->srcfile == frame->srcfile && caller_frame->line == frame->line))
|
206
|
-
snapshot_increment(&srcline->total, &diff);
|
207
|
-
|
208
|
-
/* File profiler metrics.
|
209
|
-
*/
|
210
|
-
|
211
|
-
/* Increment the caller file's child_time.
|
212
|
-
*/
|
213
|
-
if (caller_frame && caller_frame->srcfile != srcfile)
|
214
|
-
snapshot_increment(&caller_frame->srcfile->child, &diff);
|
215
|
-
|
216
|
-
/* Increment current file's total_time, but only when we return
|
217
|
-
* to the outermost stack frame when we first entered the file.
|
218
|
-
*/
|
219
|
-
if (srcfile->depth == 0)
|
220
|
-
snapshot_increment(&srcfile->total, &diff);
|
221
|
-
}
|
222
|
-
|
223
|
-
static inline sourcefile_t*
|
224
|
-
sourcefile_lookup(char *filename)
|
225
|
-
{
|
226
|
-
sourcefile_t *srcfile = NULL;
|
227
|
-
|
228
|
-
if (rblineprof.source_filename) { // single file mode
|
229
|
-
#ifdef RUBY_VM
|
230
|
-
if (strcmp(rblineprof.source_filename, filename) == 0) {
|
231
|
-
#else
|
232
|
-
if (rblineprof.source_filename == filename) { // compare char*, not contents
|
233
|
-
#endif
|
234
|
-
srcfile = &rblineprof.file;
|
235
|
-
srcfile->filename = filename;
|
236
|
-
} else {
|
237
|
-
return NULL;
|
238
|
-
}
|
239
|
-
|
240
|
-
} else { // regex mode
|
241
|
-
st_lookup(rblineprof.files, (st_data_t)filename, (st_data_t *)&srcfile);
|
242
|
-
|
243
|
-
if ((VALUE)srcfile == Qnil) // known negative match, skip
|
244
|
-
return NULL;
|
245
|
-
|
246
|
-
if (!srcfile) { // unknown file, check against regex
|
247
|
-
VALUE backref = rb_backref_get();
|
248
|
-
rb_match_busy(backref);
|
249
|
-
long rc = rb_reg_search(rblineprof.source_regex, rb_str_new2(filename), 0, 0);
|
250
|
-
rb_backref_set(backref);
|
251
|
-
|
252
|
-
if (rc >= 0) {
|
253
|
-
srcfile = ALLOC_N(sourcefile_t, 1);
|
254
|
-
MEMZERO(srcfile, sourcefile_t, 1);
|
255
|
-
srcfile->filename = strdup(filename);
|
256
|
-
st_insert(rblineprof.files, (st_data_t)srcfile->filename, (st_data_t)srcfile);
|
257
|
-
} else { // no match, insert Qnil to prevent regex next time
|
258
|
-
st_insert(rblineprof.files, (st_data_t)strdup(filename), (st_data_t)Qnil);
|
259
|
-
return NULL;
|
260
|
-
}
|
261
|
-
}
|
262
|
-
}
|
263
|
-
|
264
|
-
return srcfile;
|
265
|
-
}
|
266
|
-
|
267
|
-
#ifdef RUBY_VM
|
268
|
-
/* Find the source of the current method call. This is based on rb_f_caller
|
269
|
-
* in vm_eval.c, and replicates the behavior of `caller.first` from ruby.
|
270
|
-
*
|
271
|
-
* On method calls, ruby 1.9 sends an extra RUBY_EVENT_CALL event with mid=0. The
|
272
|
-
* top-most cfp on the stack in these cases points to the 'def method' line, so we skip
|
273
|
-
* these and grab the second caller instead.
|
274
|
-
*/
|
275
|
-
static inline
|
276
|
-
rb_control_frame_t *
|
277
|
-
rb_vm_get_caller(rb_thread_t *th, rb_control_frame_t *cfp, ID mid)
|
278
|
-
{
|
279
|
-
int level = 0;
|
280
|
-
|
281
|
-
while (!RUBY_VM_CONTROL_FRAME_STACK_OVERFLOW_P(th, cfp)) {
|
282
|
-
if (++level == 1 && mid == 0) {
|
283
|
-
// skip method definition line
|
284
|
-
} else if (cfp->iseq != 0 && cfp->pc != 0) {
|
285
|
-
return cfp;
|
286
|
-
}
|
287
|
-
|
288
|
-
cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp);
|
289
|
-
}
|
290
|
-
|
291
|
-
return 0;
|
292
|
-
}
|
293
|
-
|
294
|
-
#ifdef HAVE_TYPE_RB_ISEQ_LOCATION_T
|
295
|
-
inline static int
|
296
|
-
calc_lineno(const rb_iseq_t *iseq, const VALUE *pc)
|
297
|
-
{
|
298
|
-
return rb_iseq_line_no(iseq, pc - iseq->iseq_encoded);
|
299
|
-
}
|
300
|
-
|
301
|
-
int
|
302
|
-
rb_vm_get_sourceline(const rb_control_frame_t *cfp)
|
303
|
-
{
|
304
|
-
int lineno = 0;
|
305
|
-
const rb_iseq_t *iseq = cfp->iseq;
|
306
|
-
|
307
|
-
if (RUBY_VM_NORMAL_ISEQ_P(iseq)) {
|
308
|
-
lineno = calc_lineno(cfp->iseq, cfp->pc);
|
309
|
-
}
|
310
|
-
return lineno;
|
311
|
-
}
|
312
|
-
#endif
|
313
|
-
#endif
|
314
|
-
|
315
|
-
static void
|
316
|
-
#ifdef RUBY_VM
|
317
|
-
profiler_hook(rb_event_flag_t event, VALUE data, VALUE self, ID mid, VALUE klass)
|
318
|
-
#else
|
319
|
-
profiler_hook(rb_event_flag_t event, NODE *node, VALUE self, ID mid, VALUE klass)
|
320
|
-
#endif
|
321
|
-
{
|
322
|
-
char *file;
|
323
|
-
long line;
|
324
|
-
stackframe_t *frame = NULL, *prev = NULL;
|
325
|
-
sourcefile_t *srcfile;
|
326
|
-
|
327
|
-
/* line profiler: maintain a stack of CALL events with timestamps. for
|
328
|
-
* each corresponding RETURN, account elapsed time to the calling line.
|
329
|
-
*
|
330
|
-
* we use ruby_current_node here to get the caller's file/line info,
|
331
|
-
* (as opposed to node, which points to the callee method being invoked)
|
332
|
-
*/
|
333
|
-
#ifndef RUBY_VM
|
334
|
-
NODE *caller_node = ruby_frame->node;
|
335
|
-
if (!caller_node) return;
|
336
|
-
|
337
|
-
file = caller_node->nd_file;
|
338
|
-
line = nd_line(caller_node);
|
339
|
-
#else
|
340
|
-
rb_thread_t *th = ruby_current_thread;
|
341
|
-
rb_control_frame_t *cfp = rb_vm_get_caller(th, th->cfp, mid);
|
342
|
-
if (!cfp) return;
|
343
|
-
|
344
|
-
#ifdef HAVE_TYPE_RB_ISEQ_LOCATION_T
|
345
|
-
if (RTEST(cfp->iseq->location.absolute_path))
|
346
|
-
file = StringValueCStr(cfp->iseq->location.absolute_path);
|
347
|
-
else
|
348
|
-
file = StringValueCStr(cfp->iseq->location.path);
|
349
|
-
#else
|
350
|
-
if (RTEST(cfp->iseq->filepath))
|
351
|
-
file = StringValueCStr(cfp->iseq->filepath);
|
352
|
-
else
|
353
|
-
file = StringValueCStr(cfp->iseq->filename);
|
354
|
-
#endif
|
355
|
-
line = rb_vm_get_sourceline(cfp);
|
356
|
-
#endif
|
357
|
-
|
358
|
-
if (!file) return;
|
359
|
-
if (line <= 0) return;
|
360
|
-
|
361
|
-
/* find the srcfile entry for the current file.
|
362
|
-
*
|
363
|
-
* first check the cache, in case this is the same file as
|
364
|
-
* the previous invocation.
|
365
|
-
*
|
366
|
-
* if no record is found, we don't care about profiling this
|
367
|
-
* file and return early.
|
368
|
-
*/
|
369
|
-
if (rblineprof.cache.file == file)
|
370
|
-
srcfile = rblineprof.cache.srcfile;
|
371
|
-
else
|
372
|
-
srcfile = sourcefile_lookup(file);
|
373
|
-
rblineprof.cache.file = file;
|
374
|
-
rblineprof.cache.srcfile = srcfile;
|
375
|
-
if (!srcfile) return; /* skip line profiling for this file */
|
376
|
-
|
377
|
-
snapshot_t now = {
|
378
|
-
.wall_time = walltime_usec(),
|
379
|
-
.cpu_time = cputime_usec(),
|
380
|
-
};
|
381
|
-
|
382
|
-
switch (event) {
|
383
|
-
case RUBY_EVENT_CALL:
|
384
|
-
case RUBY_EVENT_C_CALL:
|
385
|
-
/* Create a stack frame entry with this event,
|
386
|
-
* the current file, and a snapshot of metrics.
|
387
|
-
*
|
388
|
-
* On a corresponding RETURN event later, we can
|
389
|
-
* pop this stack frame and accumulate metrics to the
|
390
|
-
* associated file and line.
|
391
|
-
*/
|
392
|
-
rblineprof.stack_depth++;
|
393
|
-
if (rblineprof.stack_depth > 0 && rblineprof.stack_depth < MAX_STACK_DEPTH) {
|
394
|
-
frame = &rblineprof.stack[rblineprof.stack_depth-1];
|
395
|
-
frame->event = event;
|
396
|
-
frame->self = self;
|
397
|
-
frame->mid = mid;
|
398
|
-
frame->klass = klass;
|
399
|
-
frame->line = line;
|
400
|
-
frame->start = now;
|
401
|
-
frame->srcfile = srcfile;
|
402
|
-
#ifdef RUBY_VM
|
403
|
-
frame->thread = th;
|
404
|
-
#else
|
405
|
-
frame->node = node;
|
406
|
-
#endif
|
407
|
-
}
|
408
|
-
|
409
|
-
/* Record when we entered this file for the first time.
|
410
|
-
* The difference is later accumulated into exclusive_time,
|
411
|
-
* e.g. on the next event if the file changes.
|
412
|
-
*/
|
413
|
-
if (srcfile->depth == 0)
|
414
|
-
srcfile->exclusive_start = now;
|
415
|
-
srcfile->depth++;
|
416
|
-
|
417
|
-
if (rblineprof.stack_depth > 1) { // skip if outermost frame
|
418
|
-
prev = &rblineprof.stack[rblineprof.stack_depth-2];
|
419
|
-
|
420
|
-
/* If we just switched files, record time that was spent in
|
421
|
-
* the previous file.
|
422
|
-
*/
|
423
|
-
if (prev->srcfile != frame->srcfile) {
|
424
|
-
snapshot_t diff = snapshot_diff(&now, &prev->srcfile->exclusive_start);
|
425
|
-
snapshot_increment(&prev->srcfile->exclusive, &diff);
|
426
|
-
prev->srcfile->exclusive_start = now;
|
427
|
-
}
|
428
|
-
}
|
429
|
-
break;
|
430
|
-
|
431
|
-
case RUBY_EVENT_RETURN:
|
432
|
-
case RUBY_EVENT_C_RETURN:
|
433
|
-
/* Find the corresponding CALL for this event.
|
434
|
-
*
|
435
|
-
* We loop here instead of a simple pop, because in the event of a
|
436
|
-
* raise/rescue several stack frames could have disappeared.
|
437
|
-
*/
|
438
|
-
do {
|
439
|
-
if (rblineprof.stack_depth > 0 && rblineprof.stack_depth < MAX_STACK_DEPTH) {
|
440
|
-
frame = &rblineprof.stack[rblineprof.stack_depth-1];
|
441
|
-
if (frame->srcfile->depth > 0)
|
442
|
-
frame->srcfile->depth--;
|
443
|
-
} else
|
444
|
-
frame = NULL;
|
445
|
-
|
446
|
-
rblineprof.stack_depth--;
|
447
|
-
} while (frame &&
|
448
|
-
#ifdef RUBY_VM
|
449
|
-
frame->thread != th &&
|
450
|
-
#endif
|
451
|
-
/* Break when we find a matching CALL/C_CALL.
|
452
|
-
*/
|
453
|
-
frame->event != (event == RUBY_EVENT_CALL ? RUBY_EVENT_RETURN : RUBY_EVENT_C_RETURN) &&
|
454
|
-
frame->self != self &&
|
455
|
-
frame->mid != mid &&
|
456
|
-
frame->klass != klass);
|
457
|
-
|
458
|
-
if (rblineprof.stack_depth > 0) {
|
459
|
-
// The new top of the stack (that we're returning to)
|
460
|
-
prev = &rblineprof.stack[rblineprof.stack_depth-1];
|
461
|
-
|
462
|
-
/* If we're leaving this frame to go back to a different file,
|
463
|
-
* accumulate time we spent in this file.
|
464
|
-
*
|
465
|
-
* Note that we do this both when entering a new file and leaving to
|
466
|
-
* a new file to ensure we only count time spent exclusively in that file.
|
467
|
-
* Consider the following scenario:
|
468
|
-
*
|
469
|
-
* call (a.rb:1)
|
470
|
-
* call (b.rb:1) <-- leaving a.rb, increment into exclusive_time
|
471
|
-
* call (a.rb:5)
|
472
|
-
* return <-- leaving a.rb, increment into exclusive_time
|
473
|
-
* return
|
474
|
-
* return
|
475
|
-
*/
|
476
|
-
if (frame->srcfile != prev->srcfile) {
|
477
|
-
snapshot_t diff = snapshot_diff(&now, &frame->srcfile->exclusive_start);
|
478
|
-
snapshot_increment(&frame->srcfile->exclusive, &diff);
|
479
|
-
frame->srcfile->exclusive_start = now;
|
480
|
-
prev->srcfile->exclusive_start = now;
|
481
|
-
}
|
482
|
-
}
|
483
|
-
|
484
|
-
if (frame)
|
485
|
-
stackframe_record(frame, now, prev);
|
486
|
-
|
487
|
-
break;
|
488
|
-
}
|
489
|
-
}
|
490
|
-
|
491
|
-
static int
|
492
|
-
cleanup_files(st_data_t key, st_data_t record, st_data_t arg)
|
493
|
-
{
|
494
|
-
xfree((char *)key);
|
495
|
-
|
496
|
-
sourcefile_t *sourcefile = (sourcefile_t*)record;
|
497
|
-
if (!sourcefile || (VALUE)sourcefile == Qnil) return ST_DELETE;
|
498
|
-
|
499
|
-
if (sourcefile->lines)
|
500
|
-
xfree(sourcefile->lines);
|
501
|
-
xfree(sourcefile);
|
502
|
-
|
503
|
-
return ST_DELETE;
|
504
|
-
}
|
505
|
-
|
506
|
-
static int
|
507
|
-
summarize_files(st_data_t key, st_data_t record, st_data_t arg)
|
508
|
-
{
|
509
|
-
sourcefile_t *srcfile = (sourcefile_t*)record;
|
510
|
-
if (!srcfile || (VALUE)srcfile == Qnil) return ST_CONTINUE;
|
511
|
-
|
512
|
-
VALUE ret = (VALUE)arg;
|
513
|
-
VALUE ary = rb_ary_new();
|
514
|
-
long i;
|
515
|
-
|
516
|
-
rb_ary_store(ary, 0, rb_ary_new3(6,
|
517
|
-
ULL2NUM(srcfile->total.wall_time),
|
518
|
-
ULL2NUM(srcfile->child.wall_time),
|
519
|
-
ULL2NUM(srcfile->exclusive.wall_time),
|
520
|
-
ULL2NUM(srcfile->total.cpu_time),
|
521
|
-
ULL2NUM(srcfile->child.cpu_time),
|
522
|
-
ULL2NUM(srcfile->exclusive.cpu_time)
|
523
|
-
));
|
524
|
-
|
525
|
-
for (i=1; i<srcfile->nlines; i++)
|
526
|
-
rb_ary_store(ary, i, rb_ary_new3(3,
|
527
|
-
ULL2NUM(srcfile->lines[i].total.wall_time),
|
528
|
-
ULL2NUM(srcfile->lines[i].total.cpu_time),
|
529
|
-
ULL2NUM(srcfile->lines[i].calls)
|
530
|
-
));
|
531
|
-
rb_hash_aset(ret, rb_str_new2(srcfile->filename), ary);
|
532
|
-
|
533
|
-
return ST_CONTINUE;
|
534
|
-
}
|
535
|
-
|
536
|
-
static VALUE
|
537
|
-
lineprof_ensure(VALUE self)
|
538
|
-
{
|
539
|
-
rb_remove_event_hook((rb_event_hook_func_t) profiler_hook);
|
540
|
-
rblineprof.enabled = false;
|
541
|
-
return self;
|
542
|
-
}
|
543
|
-
|
544
|
-
VALUE
|
545
|
-
lineprof(VALUE self, VALUE filename)
|
546
|
-
{
|
547
|
-
if (!rb_block_given_p())
|
548
|
-
rb_raise(rb_eArgError, "block required");
|
549
|
-
|
550
|
-
if (rblineprof.enabled)
|
551
|
-
rb_raise(rb_eArgError, "profiler is already enabled");
|
552
|
-
|
553
|
-
VALUE filename_class = rb_obj_class(filename);
|
554
|
-
|
555
|
-
if (filename_class == rb_cString) {
|
556
|
-
#ifdef RUBY_VM
|
557
|
-
rblineprof.source_filename = (char *) (StringValuePtr(filename));
|
558
|
-
#else
|
559
|
-
/* rb_source_filename will return a string we can compare directly against
|
560
|
-
* node->file, without a strcmp()
|
561
|
-
*/
|
562
|
-
rblineprof.source_filename = rb_source_filename(StringValuePtr(filename));
|
563
|
-
#endif
|
564
|
-
} else if (filename_class == rb_cRegexp) {
|
565
|
-
rblineprof.source_regex = filename;
|
566
|
-
rblineprof.source_filename = NULL;
|
567
|
-
} else {
|
568
|
-
rb_raise(rb_eArgError, "argument must be String or Regexp");
|
569
|
-
}
|
570
|
-
|
571
|
-
// reset state
|
572
|
-
st_foreach(rblineprof.files, cleanup_files, 0);
|
573
|
-
if (rblineprof.file.lines) {
|
574
|
-
xfree(rblineprof.file.lines);
|
575
|
-
rblineprof.file.lines = NULL;
|
576
|
-
rblineprof.file.nlines = 0;
|
577
|
-
}
|
578
|
-
rblineprof.cache.file = NULL;
|
579
|
-
rblineprof.cache.srcfile = NULL;
|
580
|
-
|
581
|
-
rblineprof.enabled = true;
|
582
|
-
#ifndef RUBY_VM
|
583
|
-
rb_add_event_hook((rb_event_hook_func_t) profiler_hook, RUBY_EVENT_CALL|RUBY_EVENT_RETURN|RUBY_EVENT_C_CALL|RUBY_EVENT_C_RETURN);
|
584
|
-
#else
|
585
|
-
rb_add_event_hook((rb_event_hook_func_t) profiler_hook, RUBY_EVENT_CALL|RUBY_EVENT_RETURN|RUBY_EVENT_C_CALL|RUBY_EVENT_C_RETURN, Qnil);
|
586
|
-
#endif
|
587
|
-
|
588
|
-
rb_ensure(rb_yield, Qnil, lineprof_ensure, self);
|
589
|
-
|
590
|
-
VALUE ret = rb_hash_new();
|
591
|
-
VALUE ary = Qnil;
|
592
|
-
|
593
|
-
if (rblineprof.source_filename) {
|
594
|
-
summarize_files(Qnil, (st_data_t)&rblineprof.file, ret);
|
595
|
-
} else {
|
596
|
-
st_foreach(rblineprof.files, summarize_files, ret);
|
597
|
-
}
|
598
|
-
|
599
|
-
return ret;
|
600
|
-
}
|
601
|
-
|
602
|
-
static void
|
603
|
-
rblineprof_gc_mark()
|
604
|
-
{
|
605
|
-
if (rblineprof.enabled)
|
606
|
-
rb_gc_mark_maybe(rblineprof.source_regex);
|
607
|
-
}
|
608
|
-
|
609
|
-
void
|
610
|
-
Init_rblineprof()
|
611
|
-
{
|
612
|
-
gc_hook = Data_Wrap_Struct(rb_cObject, rblineprof_gc_mark, NULL, NULL);
|
613
|
-
rb_global_variable(&gc_hook);
|
614
|
-
|
615
|
-
rblineprof.files = st_init_strtable();
|
616
|
-
rb_define_global_function("lineprof", lineprof, 1);
|
617
|
-
}
|
618
|
-
|
619
|
-
/* vim: set ts=2 sw=2 expandtab: */
|