pilfer 0.0.1.pre3 → 0.0.1.pre4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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: */
|