ruby-dtrace 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +9 -0
- data/Manifest.txt +11 -1
- data/README.txt +10 -11
- data/examples/scsi.rb +442 -0
- data/ext/dtrace_aggdata.c +4 -1
- data/ext/dtrace_api.c +18 -7
- data/ext/dtrace_api.h +48 -9
- data/ext/dtrace_bufdata.c +52 -4
- data/ext/dtrace_hdl.c +137 -86
- data/ext/dtrace_probedata.c +188 -50
- data/ext/dtrace_process.c +37 -0
- data/ext/dtrace_recdesc.c +31 -0
- data/lib/dtrace.rb +53 -1
- data/lib/dtraceaggregate.rb +25 -1
- data/lib/dtraceaggregateset.rb +17 -0
- data/lib/dtraceconsumer.rb +91 -67
- data/lib/dtracedata.rb +73 -0
- data/lib/dtraceprintfrecord.rb +8 -0
- data/lib/dtraceprobedata.rb +5 -0
- data/lib/dtracerecord.rb +2 -1
- data/lib/dtracestackrecord.rb +29 -0
- data/plugin/dtrace/lib/dtracer.rb +42 -2
- data/plugin/dtrace/views/dtrace/_report.rhtml +48 -3
- data/test/test_dtrace.rb +2 -0
- data/test/test_dtrace_aggregates.rb +56 -0
- data/test/test_dtrace_processes.rb +83 -0
- data/test/test_dtrace_profile.rb +232 -0
- data/test/test_dtrace_repeat.rb +51 -0
- data/test/test_dtrace_rubyprobe.rb +52 -0
- metadata +19 -5
- data/test/test_dtrace_workapi.rb +0 -142
data/lib/dtraceconsumer.rb
CHANGED
@@ -3,96 +3,120 @@
|
|
3
3
|
# (c) 2007 Chris Andrews <chris@nodnol.org>
|
4
4
|
#
|
5
5
|
|
6
|
+
# A DtraceConsumer provides access to the data produced by the running
|
7
|
+
# D program. Having compiled and executed a D program, you typically
|
8
|
+
# create a DtraceConsumer, and wait for data.
|
9
|
+
#
|
10
|
+
# You can either wait indefinitely for data, or consume all the data
|
11
|
+
# waiting and then stop: if your D program consists of only of
|
12
|
+
# aggregations, returned by printa() actions in the END block, this
|
13
|
+
# will be the best approach. If you have a mix of trace() and printf()
|
14
|
+
# actions elsewhere, you'll probably want to wait until interrupted,
|
15
|
+
# the D program itself exits, or your program decides it has collected
|
16
|
+
# enough data.
|
17
|
+
#
|
18
|
+
# The two approaches are implemented by the +consume+ and
|
19
|
+
# +consume_once+ methods.
|
20
|
+
#
|
21
|
+
# The +consume+ and +consume_once+ methods accept a block to which is
|
22
|
+
# yielded complete DtraceData objects, one for each probe which fires.
|
23
|
+
#
|
24
|
+
# You must have already started tracing when you call +consume+ or
|
25
|
+
# +consume_once+, so the general structure will look like:
|
26
|
+
#
|
27
|
+
# t = Dtrace.new
|
28
|
+
# progtext = "..."
|
29
|
+
# prog = t.compile progtext
|
30
|
+
# prog.execute
|
31
|
+
# t.go
|
32
|
+
# c = DtraceConsumer.new(t)
|
33
|
+
# c.consume_once do |d|
|
34
|
+
# # handle DtraceData objects
|
35
|
+
# # ...
|
36
|
+
# # get bored:
|
37
|
+
# c.finish
|
38
|
+
# end
|
39
|
+
|
6
40
|
class DtraceConsumer
|
7
41
|
|
8
42
|
def initialize(t)
|
9
43
|
@t = t
|
10
|
-
@
|
44
|
+
@curr = DtraceData.new
|
45
|
+
@done = false
|
11
46
|
end
|
12
47
|
|
13
|
-
|
48
|
+
private
|
14
49
|
|
15
|
-
|
16
|
-
|
50
|
+
# The consumer callbacks:
|
51
|
+
#
|
52
|
+
# DtraceRecDesc -> rec_consumer
|
53
|
+
# DtraceProbeData -> probe_consumer
|
54
|
+
# DtraceBufData -> buf_consumer
|
55
|
+
#
|
56
|
+
# We expect a sequence of calls to these procs, and we accumulate
|
57
|
+
# data in the @curr DtraceData based on this:
|
58
|
+
#
|
59
|
+
# DtraceProbeData (initial callback for a probe firing)
|
60
|
+
# DtraceRecDesc
|
61
|
+
# ...
|
62
|
+
# DtraceRecDesc = nil (end of data)
|
63
|
+
#
|
64
|
+
|
65
|
+
def rec_consumer(block)
|
66
|
+
proc do |rec|
|
67
|
+
if rec
|
68
|
+
@curr.add_recdata(rec)
|
69
|
+
else
|
70
|
+
@curr.finish
|
71
|
+
block.call(@curr)
|
72
|
+
@curr = DtraceData.new
|
73
|
+
end
|
17
74
|
end
|
18
|
-
|
19
|
-
|
20
|
-
|
75
|
+
end
|
76
|
+
|
77
|
+
def probe_consumer
|
78
|
+
proc do |probe|
|
79
|
+
@curr.add_probedata(probe)
|
21
80
|
end
|
22
|
-
|
23
|
-
buf_consumer = proc do |buf|
|
24
|
-
r = buf.record
|
25
|
-
# buf records can be empty (trace();)
|
26
|
-
if r
|
27
|
-
case r.class.to_s
|
28
|
-
when DtraceRecord.to_s
|
29
|
-
yield r
|
30
|
-
when DtraceAggData.to_s
|
31
|
-
case r.aggtype
|
32
|
-
when "tuple"
|
33
|
-
@curragg.tuple << r.value
|
34
|
-
when "value"
|
35
|
-
@curragg.value = r.value
|
36
|
-
when "last"
|
37
|
-
yield @curragg
|
38
|
-
@curragg = DtraceAggregate.new
|
39
|
-
end
|
81
|
+
end
|
40
82
|
|
41
|
-
|
42
|
-
|
43
|
-
|
83
|
+
def buf_consumer
|
84
|
+
proc do |buf|
|
85
|
+
@curr.add_bufdata(buf)
|
86
|
+
end
|
87
|
+
end
|
44
88
|
|
45
|
-
|
89
|
+
public
|
90
|
+
|
91
|
+
# Signals that the client wishes to stop consuming trace data.
|
92
|
+
def finish
|
93
|
+
@t.stop
|
94
|
+
@done = true
|
95
|
+
end
|
96
|
+
|
97
|
+
# Waits for data from the D program, and yields the records returned
|
98
|
+
# to the block given. Returns when the D program exits.
|
99
|
+
def consume(&block)
|
46
100
|
@t.buf_consumer(buf_consumer)
|
47
101
|
begin
|
48
102
|
while(true) do
|
49
103
|
@t.sleep
|
50
|
-
@t.work(probe_consumer, rec_consumer)
|
104
|
+
work = @t.work(probe_consumer, rec_consumer(block))
|
105
|
+
if (@done || work > 0)
|
106
|
+
break
|
107
|
+
end
|
51
108
|
end
|
52
109
|
ensure
|
53
110
|
@t.stop
|
54
|
-
@t.work(probe_consumer
|
111
|
+
@t.work(probe_consumer)
|
55
112
|
end
|
56
|
-
|
57
113
|
end
|
58
114
|
|
59
|
-
|
60
|
-
|
61
|
-
probe_consumer = proc do |probe|
|
62
|
-
probe.each_record do |r|
|
63
|
-
yield r
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
rec_consumer = proc do |rec|
|
68
|
-
#yield rec
|
69
|
-
end
|
70
|
-
|
71
|
-
buf_consumer = proc do |buf|
|
72
|
-
r = buf.record
|
73
|
-
# buf records can be empty (trace();)
|
74
|
-
if r
|
75
|
-
case r.class.to_s
|
76
|
-
when DtraceRecord.to_s
|
77
|
-
yield r
|
78
|
-
when DtraceAggData.to_s
|
79
|
-
case r.aggtype
|
80
|
-
when "tuple"
|
81
|
-
@curragg.tuple << r.value
|
82
|
-
when "value"
|
83
|
-
@curragg.value = r.value
|
84
|
-
when "last"
|
85
|
-
yield @curragg
|
86
|
-
@curragg = DtraceAggregate.new
|
87
|
-
end
|
88
|
-
|
89
|
-
end
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
115
|
+
# Yields the data waiting from the current program, then returns.
|
116
|
+
def consume_once(&block)
|
93
117
|
@t.buf_consumer(buf_consumer)
|
94
118
|
@t.stop
|
95
|
-
@t.work(probe_consumer, rec_consumer)
|
119
|
+
@t.work(probe_consumer, rec_consumer(block))
|
96
120
|
end
|
97
121
|
|
98
122
|
end
|
data/lib/dtracedata.rb
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
#
|
2
|
+
# Ruby-Dtrace
|
3
|
+
# (c) 2007 Chris Andrews <chris@nodnol.org>
|
4
|
+
#
|
5
|
+
# The object returned from a consumer when a probe fires. Accumulates
|
6
|
+
# records from the callbacks, and is yielded when the data is complete.
|
7
|
+
class DtraceData
|
8
|
+
attr_reader :data
|
9
|
+
attr_reader :probe
|
10
|
+
attr_reader :cpu, :indent, :prefix, :flow
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@data = []
|
14
|
+
@curraggset = nil
|
15
|
+
@curragg = nil
|
16
|
+
end
|
17
|
+
|
18
|
+
def finish
|
19
|
+
if @curraggset
|
20
|
+
@data << @curraggset
|
21
|
+
@curraggset = nil
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def add_recdata(rec)
|
26
|
+
if @curraggset
|
27
|
+
@data << @curraggset
|
28
|
+
@curraggset = nil
|
29
|
+
end
|
30
|
+
if rec.action == "printa"
|
31
|
+
@curraggset = DtraceAggregateSet.new
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def add_probedata(probedata)
|
36
|
+
probedata.each_record do |p|
|
37
|
+
@data << p
|
38
|
+
end
|
39
|
+
|
40
|
+
# Record the probe that fired, and CPU/indent/prefix/flow
|
41
|
+
@probe = probedata.probe
|
42
|
+
@cpu = probedata.cpu
|
43
|
+
@indent = probedata.indent
|
44
|
+
@prefix = probedata.prefix
|
45
|
+
@flow = probedata.flow
|
46
|
+
end
|
47
|
+
|
48
|
+
def add_bufdata(buf)
|
49
|
+
r = buf.record
|
50
|
+
# buf records can be empty (trace();)
|
51
|
+
if r
|
52
|
+
case r.class.to_s
|
53
|
+
when DtraceStackRecord.to_s
|
54
|
+
@data << r
|
55
|
+
when DtraceRecord.to_s
|
56
|
+
@data << r
|
57
|
+
when DtracePrintfRecord.to_s
|
58
|
+
@data << r
|
59
|
+
when DtraceAggData.to_s
|
60
|
+
if @curragg == nil
|
61
|
+
@curragg = DtraceAggregate.new
|
62
|
+
end
|
63
|
+
if agg = @curragg.add_record(r)
|
64
|
+
if @curraggset
|
65
|
+
@curraggset.add_aggregate(@curragg)
|
66
|
+
end
|
67
|
+
@curragg = nil
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
data/lib/dtraceprobedata.rb
CHANGED
data/lib/dtracerecord.rb
CHANGED
@@ -0,0 +1,29 @@
|
|
1
|
+
#
|
2
|
+
# Ruby-Dtrace
|
3
|
+
# (c) 2007 Chris Andrews <chris@nodnol.org>
|
4
|
+
#
|
5
|
+
|
6
|
+
# A record representing the result of a stack() or ustack()
|
7
|
+
# action. Its value is a list of symbolic stack frames:
|
8
|
+
#
|
9
|
+
# #<DtraceStackRecord:0x14e24 @value=
|
10
|
+
# ["libSystem.B.dylib`__sysctl+0xa",
|
11
|
+
# "libdtrace.dylib`dt_aggregate_go+0x9a",
|
12
|
+
# "dtrace_api.bundle`dtrace_hdl_go+0x30",
|
13
|
+
# "libruby.1.dylib`rb_eval_string_wrap+0x40fd",
|
14
|
+
# "libruby.1.dylib`rb_eval_string_wrap+0x4cdb",
|
15
|
+
# ...
|
16
|
+
# "libruby.1.dylib`rb_apply+0x392",
|
17
|
+
# "libruby.1.dylib`rb_eval_string_wrap+0xe82"]>
|
18
|
+
#
|
19
|
+
class DtraceStackRecord
|
20
|
+
attr_reader :value
|
21
|
+
|
22
|
+
# Given a stack as a string returned from DTrace, set the value of
|
23
|
+
# this record to a list of stack frames.
|
24
|
+
def parse(raw)
|
25
|
+
frames = raw.split(/\n/)
|
26
|
+
@value = frames.map {|f| f.lstrip }.select {|f| f.length > 0 }
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
@@ -3,7 +3,39 @@ require 'dtrace'
|
|
3
3
|
class Dtracer
|
4
4
|
|
5
5
|
def start_dtrace(pid)
|
6
|
-
progtext =
|
6
|
+
progtext = <<EOD
|
7
|
+
self string uri;
|
8
|
+
|
9
|
+
pid$1::mysql_real_query:entry
|
10
|
+
{
|
11
|
+
@queries[copyinstr(arg1)] = count();
|
12
|
+
}
|
13
|
+
|
14
|
+
ruby$1:::function-entry
|
15
|
+
{
|
16
|
+
@rbclasses[this->class = copyinstr(arg0)] = count();
|
17
|
+
this->sep = strjoin(this->class, "#");
|
18
|
+
@rbmethods[strjoin(this->sep, copyinstr(arg1))] = count();
|
19
|
+
}
|
20
|
+
|
21
|
+
syscall:::entry
|
22
|
+
{
|
23
|
+
@syscalls[probefunc] = count();
|
24
|
+
}
|
25
|
+
|
26
|
+
END
|
27
|
+
{
|
28
|
+
printf("report:syscalls");
|
29
|
+
printa(@syscalls);
|
30
|
+
printf("report:rbclasses");
|
31
|
+
printa(@rbclasses);
|
32
|
+
printf("report:rbmethods");
|
33
|
+
printa(@rbmethods);
|
34
|
+
printf("report:queries");
|
35
|
+
printa(@queries);
|
36
|
+
}
|
37
|
+
|
38
|
+
EOD
|
7
39
|
|
8
40
|
begin
|
9
41
|
@d = Dtrace.new
|
@@ -27,12 +59,20 @@ class Dtracer
|
|
27
59
|
def end_dtrace
|
28
60
|
return {} unless @d
|
29
61
|
|
62
|
+
current_report = 'none'
|
30
63
|
begin
|
31
64
|
dtrace_report = Hash.new
|
32
65
|
c = DtraceConsumer.new(@d)
|
33
66
|
c.consume_once do |e|
|
34
67
|
if e.respond_to? :tuple
|
35
|
-
dtrace_report[e.tuple.first] = e.value
|
68
|
+
dtrace_report[current_report][e.tuple.first] = e.value
|
69
|
+
elsif e.respond_to? :value
|
70
|
+
if e.value =~ /report:(.*)/
|
71
|
+
current_report = Regexp.last_match(1)
|
72
|
+
unless dtrace_report[current_report]
|
73
|
+
dtrace_report[current_report] = Hash.new
|
74
|
+
end
|
75
|
+
end
|
36
76
|
end
|
37
77
|
end
|
38
78
|
rescue DtraceException => e
|
@@ -1,11 +1,56 @@
|
|
1
|
+
<style type="text/css">
|
2
|
+
|
3
|
+
</style>
|
4
|
+
|
1
5
|
<div id="dtrace-report">
|
2
|
-
<
|
3
|
-
|
4
|
-
|
6
|
+
<h2>DTrace Report</h2>
|
7
|
+
|
8
|
+
<% if controller.dtrace_report['queries'] %>
|
9
|
+
<h3>MySQL queries</h3>
|
10
|
+
<table class="report">
|
11
|
+
<% controller.dtrace_report['queries'].sort {|a,b| b[1] <=> a[1] }.each do |e| %>
|
5
12
|
<tr>
|
6
13
|
<td><%= e[0] %></td>
|
7
14
|
<td><%= e[1] %></td>
|
8
15
|
</tr>
|
9
16
|
<% end %>
|
10
17
|
</table>
|
18
|
+
<% end %>
|
19
|
+
|
20
|
+
<% if controller.dtrace_report['syscalls'] %>
|
21
|
+
<h3>System calls</h3>
|
22
|
+
<table class="report">
|
23
|
+
<% controller.dtrace_report['syscalls'].sort {|a,b| b[1] <=> a[1] }.each do |e| %>
|
24
|
+
<tr>
|
25
|
+
<td><%= e[0] %></td>
|
26
|
+
<td><%= e[1] %></td>
|
27
|
+
</tr>
|
28
|
+
<% end %>
|
29
|
+
</table>
|
30
|
+
<% end %>
|
31
|
+
|
32
|
+
<% if controller.dtrace_report['rbclasses'] %>
|
33
|
+
<h3>Ruby classes instantiated</h3>
|
34
|
+
<table class="report">
|
35
|
+
<% controller.dtrace_report['rbclasses'].sort {|a,b| b[1] <=> a[1] }.each do |e| %>
|
36
|
+
<tr>
|
37
|
+
<td><%= e[0] %></td>
|
38
|
+
<td><%= e[1] %></td>
|
39
|
+
</tr>
|
40
|
+
<% end %>
|
41
|
+
</table>
|
42
|
+
<% end %>
|
43
|
+
|
44
|
+
<% if controller.dtrace_report['rbmethods'] %>
|
45
|
+
<h3>Ruby methods called</h3>
|
46
|
+
<table class="report">
|
47
|
+
<% controller.dtrace_report['rbmethods'].sort {|a,b| b[1] <=> a[1] }.each do |e| %>
|
48
|
+
<tr>
|
49
|
+
<td><%= e[0] %></td>
|
50
|
+
<td><%= e[1] %></td>
|
51
|
+
</tr>
|
52
|
+
<% end %>
|
53
|
+
</table>
|
54
|
+
<% end %>
|
55
|
+
|
11
56
|
</div>
|