sampling_prof 0.3.4 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -7
- data/README.md +17 -23
- data/lib/sampling_prof.jar +0 -0
- data/lib/sampling_prof.rb +0 -8
- data/lib/sampling_prof/internal.rb +58 -98
- metadata +49 -50
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
-
---
|
2
|
-
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
5
|
-
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: b3d5e9f4030836370938d494ceb34b7c6a153531
|
4
|
+
data.tar.gz: c39ad8399c81faa1dc45bbdcb8c99d910e22a6c0
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 313981a2ed4edb5cea888052515eb87b7e2fe62271ad951960f8517580a5fb4983ffaddd370152d4e11e0f7a3193f8678b740d80427779c8fb64ec6cbc6d9637
|
7
|
+
data.tar.gz: 1ae6f1690779efd29ec5633a827a048e5622d9157ea0ef67688838ace40f54b33669060675be3069758e1a88a66257adccdbac34c4b1fdf05b117919767201b3
|
data/README.md
CHANGED
@@ -1,51 +1,45 @@
|
|
1
1
|
SamplingProf
|
2
2
|
===============
|
3
3
|
|
4
|
-
SamplingProf is a
|
4
|
+
SamplingProf is a statistical profiler or sampling profiler that operates by sampling your running thread stacktrace. The result is statistical approximation, but it allows your code to run near full speed.
|
5
5
|
|
6
6
|
Quick start
|
7
7
|
---------------
|
8
8
|
|
9
9
|
For single thread profiling, start with default options:
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
11
|
+
prof = SamplingProf.new
|
12
|
+
prof.profile do
|
13
|
+
... your code ...
|
14
|
+
end
|
15
|
+
at_exit { prof.terminate }
|
15
16
|
|
16
17
|
After profiling finished, the output will be write to file SamplingProf::DEFAULT_OUTPUT_FILE
|
17
18
|
|
18
19
|
Options
|
19
20
|
---------------
|
20
21
|
|
21
|
-
SamplingProf class initializer takes
|
22
|
+
SamplingProf class initializer takes 1 argument:
|
22
23
|
|
23
24
|
1. sampling interval: seconds
|
24
|
-
2. multithreading: boolean
|
25
|
-
3. output interval: seconds
|
26
25
|
|
27
26
|
SamplingProf class also takes block as another option to overwite default output handler, the default output handler will write output data to a local file defined by output_file attribute, which is default to SamplingProf::DEFAULT_OUTPUT_FILE
|
28
27
|
|
28
|
+
When a SamplingProf is initialized, a thread will be started to handle sampling process.
|
29
|
+
So you need call SamplingProf#terminate to shutdown the sampling thread after everything is done.
|
30
|
+
|
29
31
|
### Sampling interval
|
30
32
|
|
31
33
|
This is an interval to control how frequent SamplingProf should take sample of target thread stacktrace.
|
32
34
|
The default value is 0.1 seconds.
|
33
35
|
|
34
|
-
### Multithreading
|
35
|
-
|
36
|
-
When running SamplingProf in multithreading environment (e.g. Rails multithreading production environment), you need turn on multithreading mode so that you can profile all requests processing at same time cross threads.
|
37
|
-
|
38
|
-
For performance concerns, multithreading mode will only take limit number of threads' sample while profiling. The default max samples of the threads is 4, you can change it by set max_sampling_threads.
|
39
|
-
|
40
|
-
Since we randomly find max_sampling_threads number of threads in profiling threads, the result is still a statistical approximation.
|
36
|
+
### Multithreading
|
41
37
|
|
42
|
-
|
38
|
+
When running SamplingProf in multithreading environment (e.g. Rails multithreading production environment), it can profile all requests processing at same time cross threads.
|
43
39
|
|
44
|
-
|
45
|
-
The default value of output interval is depend on the option of multithreading.
|
46
|
-
When multithreading is off, the default value is nil, because single thread mode is more likely only need to do once data output when the profiling finished.
|
40
|
+
For performance concerns, you should controll how many number of threads' sample while profiling.
|
47
41
|
|
48
|
-
|
42
|
+
Randomly select some threads for profiling, the result is still a statistical approximation.
|
49
43
|
|
50
44
|
Output data format
|
51
45
|
---------------
|
@@ -93,7 +87,7 @@ SamplingProf collects count of each call element at runtime. There are 2 type of
|
|
93
87
|
|
94
88
|
Same with call element id map, we use comma to separate data, and one line represents one call element data.
|
95
89
|
Here we use call element id instead of call element string to reference the call element.
|
96
|
-
The line looks like this:
|
90
|
+
The line looks like this: [call element id],[self count],[total count]
|
97
91
|
|
98
92
|
### call graph
|
99
93
|
|
@@ -101,8 +95,8 @@ SamplingProf collects counts of function calls. One function call is A call elem
|
|
101
95
|
|
102
96
|
The call graph stores the counts of function calls.
|
103
97
|
Every line is one function call data.
|
104
|
-
the line looks like this:
|
98
|
+
the line looks like this: [call element id1],[call element id2],[count]
|
105
99
|
|
106
|
-
Direct recursive calls are recorded as:
|
100
|
+
Direct recursive calls are recorded as: [call element id1],[call element id1],[count]
|
107
101
|
|
108
102
|
Indirect recursive calls are ignored as function call only have direct call info. Hence the data maybe odd when there are indirect recursive calls.
|
data/lib/sampling_prof.jar
CHANGED
Binary file
|
data/lib/sampling_prof.rb
CHANGED
@@ -11,17 +11,9 @@ class SamplingProf
|
|
11
11
|
|
12
12
|
# options:
|
13
13
|
# sampling_interval: default to 0.1 second
|
14
|
-
# multithreading: default to false
|
15
|
-
# output_interval: default to (multithreading ? 60 : nil)
|
16
14
|
# &output_handler: default to write into output_file
|
17
15
|
def initialize(*args, &output_handler)
|
18
16
|
self.sampling_interval = args[0] || 0.1
|
19
|
-
self.multithreading = args[1] || false
|
20
|
-
if args.length > 2
|
21
|
-
self.output_interval = args[2]
|
22
|
-
else
|
23
|
-
self.output_interval = multithreading ? 60 : nil
|
24
|
-
end
|
25
17
|
self.output_handler = block_given? ? output_handler : default_output_handler
|
26
18
|
internal_initialize if respond_to?(:internal_initialize)
|
27
19
|
end
|
@@ -3,11 +3,10 @@ require 'thread'
|
|
3
3
|
|
4
4
|
class SamplingProf
|
5
5
|
class Sampling
|
6
|
-
def initialize
|
6
|
+
def initialize
|
7
7
|
@samples = Hash.new{|h,k| h[k] = [0, 0] }
|
8
8
|
@call_graph = Hash.new{|h,k| h[k] = 0}
|
9
9
|
@nodes = {}
|
10
|
-
@threads = threads
|
11
10
|
@start_at = Time.now
|
12
11
|
end
|
13
12
|
|
@@ -20,37 +19,35 @@ class SamplingProf
|
|
20
19
|
end
|
21
20
|
|
22
21
|
def result
|
23
|
-
ret = [
|
22
|
+
ret = [runtime * 1000]
|
24
23
|
ret << @nodes.map {|node| node.join(',')}.join("\n")
|
25
24
|
ret << @samples.map {|count| count.flatten.join(',')}.join("\n")
|
26
25
|
ret << @call_graph.map {|v| v.flatten.join(',')}.join("\n")
|
27
26
|
"#{ret.join("\n\n")}\n"
|
28
27
|
end
|
29
28
|
|
30
|
-
def process
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
@call_graph[path] += 1
|
47
|
-
end
|
48
|
-
if !calls.include?(node_id)
|
49
|
-
calls << node_id
|
50
|
-
@samples[node_id][1] += 1
|
51
|
-
end
|
52
|
-
from = node_id
|
29
|
+
def process(thread)
|
30
|
+
locations = thread.backtrace_locations
|
31
|
+
from = -1
|
32
|
+
paths = []
|
33
|
+
calls = []
|
34
|
+
top_index = locations.size - 1
|
35
|
+
locations.reverse.each_with_index do |loc, i|
|
36
|
+
node_id = node_id(loc)
|
37
|
+
if i == top_index
|
38
|
+
@samples[node_id][0] += 1
|
39
|
+
end
|
40
|
+
|
41
|
+
path = [from, node_id]
|
42
|
+
if !paths.include?(path)
|
43
|
+
paths << path
|
44
|
+
@call_graph[path] += 1
|
53
45
|
end
|
46
|
+
if !calls.include?(node_id)
|
47
|
+
calls << node_id
|
48
|
+
@samples[node_id][1] += 1
|
49
|
+
end
|
50
|
+
from = node_id
|
54
51
|
end
|
55
52
|
end
|
56
53
|
|
@@ -63,100 +60,63 @@ class SamplingProf
|
|
63
60
|
end
|
64
61
|
end
|
65
62
|
|
66
|
-
|
67
|
-
attr_accessor :max
|
68
|
-
|
69
|
-
def initialize
|
70
|
-
@hash = {}
|
71
|
-
@mutex = Mutex.new
|
72
|
-
@remain_sampling_time = 0
|
73
|
-
@max = 4
|
74
|
-
end
|
75
|
-
|
76
|
-
def sample_threads
|
77
|
-
@mutex.synchronize { @hash.keys.dup.shuffle[0..@max] }
|
78
|
-
end
|
79
|
-
|
80
|
-
def add(obj, time=Time.now)
|
81
|
-
@mutex.synchronize { @hash[obj] = time }
|
82
|
-
end
|
83
|
-
|
84
|
-
def sampling_runtime
|
85
|
-
now = Time.now
|
86
|
-
@mutex.synchronize do
|
87
|
-
ret, @remain_sampling_time = @remain_sampling_time, 0
|
88
|
-
@hash.keys.each do |k|
|
89
|
-
ret += now - @hash[k]
|
90
|
-
@hash[k] = now
|
91
|
-
end
|
92
|
-
ret
|
93
|
-
end
|
94
|
-
end
|
95
|
-
|
96
|
-
def delete(obj)
|
97
|
-
@mutex.synchronize do
|
98
|
-
start = @hash.delete(obj)
|
99
|
-
@remain_sampling_time += Time.now - start
|
100
|
-
end
|
101
|
-
end
|
102
|
-
end
|
103
|
-
|
104
|
-
attr_accessor :sampling_interval, :multithreading, :output_interval, :output_handler
|
63
|
+
attr_accessor :sampling_interval, :output_handler
|
105
64
|
|
106
65
|
def internal_initialize
|
107
|
-
@
|
108
|
-
|
109
|
-
@threads = Threads.new
|
110
|
-
end
|
111
|
-
|
112
|
-
def max_sampling_threads=(max)
|
113
|
-
@threads.max = max
|
66
|
+
@samplings = {}
|
67
|
+
start_sampling_thread
|
114
68
|
end
|
115
69
|
|
116
70
|
def start
|
117
|
-
|
118
|
-
@
|
119
|
-
@threads.add(Thread.current)
|
120
|
-
@sampling_thread ||= Thread.start do
|
121
|
-
loop do
|
122
|
-
sampling = Sampling.new(@threads)
|
123
|
-
loop do
|
124
|
-
break unless @running
|
125
|
-
if @multithreading
|
126
|
-
break if output_interval < sampling.runtime
|
127
|
-
end
|
128
|
-
sampling.process
|
129
|
-
sleep @sampling_interval
|
130
|
-
end
|
131
|
-
if sampling.sampling_data?
|
132
|
-
@output_handler.call(sampling.result)
|
133
|
-
end
|
134
|
-
break unless @running
|
135
|
-
end
|
136
|
-
end
|
71
|
+
unless @samplings.has_key?(Thread.current)
|
72
|
+
@samplings[Thread.current] = Sampling.new
|
137
73
|
true
|
138
74
|
end
|
139
75
|
end
|
140
76
|
|
141
77
|
def stop
|
142
78
|
if @running
|
143
|
-
if @
|
144
|
-
|
145
|
-
|
146
|
-
terminate
|
79
|
+
if sampling = @samplings.delete(Thread.current)
|
80
|
+
output(sampling)
|
81
|
+
true
|
147
82
|
end
|
148
|
-
true
|
149
83
|
end
|
150
84
|
end
|
151
85
|
|
152
86
|
def terminate
|
87
|
+
return false unless @running
|
153
88
|
@running = false
|
154
89
|
@sampling_thread.join
|
155
90
|
@sampling_thread = nil
|
91
|
+
@samplings = {}
|
156
92
|
true
|
157
93
|
end
|
158
94
|
|
159
95
|
def profiling?
|
160
|
-
!!@sampling_thread
|
96
|
+
!!@sampling_thread && @samplings.has_key?(Thread.current)
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
def start_sampling_thread
|
101
|
+
return if @running
|
102
|
+
@running = true
|
103
|
+
@sampling_thread ||= Thread.start do
|
104
|
+
loop do
|
105
|
+
@samplings.dup.each do |t, s|
|
106
|
+
s.process(t)
|
107
|
+
end
|
108
|
+
sleep @sampling_interval
|
109
|
+
break unless @running
|
110
|
+
end
|
111
|
+
@samplings.each do |sampling|
|
112
|
+
output(sampling)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def output(sampling)
|
118
|
+
if sampling.sampling_data?
|
119
|
+
@output_handler.call(sampling.result)
|
120
|
+
end
|
161
121
|
end
|
162
122
|
end
|
metadata
CHANGED
@@ -1,70 +1,69 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: sampling_prof
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
|
-
authors:
|
7
|
-
|
6
|
+
authors:
|
7
|
+
- Xiao Li
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
11
|
+
date: 2014-06-13 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rake-compiler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.9'
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 0.9.2
|
23
|
+
type: :development
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - "~>"
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0.9'
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 0.9.2
|
27
33
|
description: |
|
28
34
|
SamplingProf is a profiling tool that operates by sampling your running thread stacktrace. The result is statistical approximation, but it allows your code to run near full speed
|
29
|
-
|
30
|
-
|
31
|
-
- swing1979@gmail.com
|
35
|
+
email:
|
36
|
+
- swing1979@gmail.com
|
32
37
|
executables: []
|
33
|
-
|
34
38
|
extensions: []
|
35
|
-
|
36
39
|
extra_rdoc_files: []
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
- lib/sampling_prof/internal.rb
|
40
|
+
files:
|
41
|
+
- README.md
|
42
|
+
- lib/sampling_prof.jar
|
43
|
+
- lib/sampling_prof.rb
|
44
|
+
- lib/sampling_prof/internal.rb
|
43
45
|
homepage: https://github.com/xli/sampling_prof
|
44
|
-
licenses:
|
45
|
-
|
46
|
+
licenses:
|
47
|
+
- MIT
|
46
48
|
metadata: {}
|
47
|
-
|
48
49
|
post_install_message:
|
49
50
|
rdoc_options: []
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
51
|
+
require_paths:
|
52
|
+
- lib
|
53
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: '0'
|
58
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0'
|
62
63
|
requirements: []
|
63
|
-
|
64
64
|
rubyforge_project:
|
65
|
-
rubygems_version: 2.
|
65
|
+
rubygems_version: 2.2.0
|
66
66
|
signing_key:
|
67
67
|
specification_version: 4
|
68
68
|
summary: Simple sampling profiler for ruby
|
69
69
|
test_files: []
|
70
|
-
|