ruby-dtrace 0.0.2 → 0.0.3
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.
- 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>
|