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.
@@ -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>