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.
@@ -0,0 +1,17 @@
1
+ #
2
+ # Ruby-Dtrace
3
+ # (c) 2007 Chris Andrews <chris@nodnol.org>
4
+ #
5
+
6
+ class DtraceAggregateSet
7
+ attr_reader :data
8
+
9
+ def initialize
10
+ @data = Array.new
11
+ end
12
+
13
+ def add_aggregate(agg)
14
+ @data << agg
15
+ end
16
+
17
+ end
@@ -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
- @curragg = DtraceAggregate.new
44
+ @curr = DtraceData.new
45
+ @done = false
11
46
  end
12
47
 
13
- def consume
48
+ private
14
49
 
15
- probe_consumer = proc do |probe|
16
- yield probe
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
- rec_consumer = proc do |rec|
20
- #yield rec
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
- end
42
- end
43
- end
83
+ def buf_consumer
84
+ proc do |buf|
85
+ @curr.add_bufdata(buf)
86
+ end
87
+ end
44
88
 
45
- @t.go
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, rec_consumer)
111
+ @t.work(probe_consumer)
55
112
  end
56
-
57
113
  end
58
114
 
59
- def consume_once
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
@@ -0,0 +1,8 @@
1
+ #
2
+ # Ruby-Dtrace
3
+ # (c) 2007 Chris Andrews <chris@nodnol.org>
4
+ #
5
+ # A DTrace record for the formatted part of a printf() action.
6
+ class DtracePrintfRecord
7
+ attr_accessor :value
8
+ end
@@ -13,4 +13,9 @@ class DtraceProbeData
13
13
  records
14
14
  end
15
15
 
16
+ def to_s
17
+ rs = self.records
18
+ rs.map {|r| r.value }.join ', '
19
+ end
20
+
16
21
  end
data/lib/dtracerecord.rb CHANGED
@@ -2,7 +2,8 @@
2
2
  # Ruby-Dtrace
3
3
  # (c) 2007 Chris Andrews <chris@nodnol.org>
4
4
  #
5
-
5
+ # A scalar DTrace record. Its value is as set by the DTrace action
6
+ # which triggered it.
6
7
  class DtraceRecord
7
8
  attr_accessor :value
8
9
  end
@@ -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 = 'ruby$1:::function-entry{ @a[strjoin(strjoin(copyinstr(arg0),"."),copyinstr(arg1))] = count(); } END { printa(@a); }'
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
- <p>...dtrace report...</p>
3
- <table>
4
- <% controller.dtrace_report.sort {|a,b| b[1] <=> a[1] }.each do |e| %>
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>