ruby-dtrace 0.0.6 → 0.2.8
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +21 -0
- data/Manifest.txt +86 -19
- data/README.txt +48 -6
- data/Rakefile +61 -20
- data/examples/scsi.rb +1 -1
- data/ext/dof/Makefile +154 -0
- data/ext/dof/constants.c +57 -0
- data/ext/dof/dof.h +56 -0
- data/ext/dof/dof_api.c +58 -0
- data/ext/dof/dof_helper.c +82 -0
- data/ext/dof/extconf.rb +4 -0
- data/ext/dof/file.c +90 -0
- data/ext/dof/generator.c +9 -0
- data/ext/dof/header.c +79 -0
- data/ext/dof/mkmf.log +10 -0
- data/ext/dof/parser.c +415 -0
- data/ext/dof/parser.h +10 -0
- data/ext/dof/section.c +312 -0
- data/ext/dtrace_aggdata.c +2 -2
- data/ext/dtrace_api.c +46 -34
- data/ext/dtrace_api.h +31 -7
- data/ext/dtrace_bufdata.c +3 -3
- data/ext/dtrace_hdl.c +66 -3
- data/ext/dtrace_probedata.c +4 -4
- data/ext/{dtrace_probe.c → dtrace_probedesc.c} +7 -7
- data/ext/extconf.rb +25 -0
- data/ext/i386-darwin/dtrace_probe.c +278 -0
- data/ext/i386-solaris/dtrace_probe.c +225 -0
- data/ext/stubs.txt +78 -0
- data/lib/dtrace.rb +34 -13
- data/lib/dtrace/aggregate.rb +40 -0
- data/lib/dtrace/aggregateset.rb +19 -0
- data/lib/dtrace/consumer.rb +174 -0
- data/lib/dtrace/data.rb +82 -0
- data/lib/dtrace/dof.rb +8 -0
- data/lib/dtrace/dof/file.rb +64 -0
- data/lib/dtrace/dof/section.rb +75 -0
- data/lib/dtrace/dof/section/strtab.rb +28 -0
- data/lib/{dtraceprintfrecord.rb → dtrace/printfrecord.rb} +4 -2
- data/lib/dtrace/probe.rb +3 -6
- data/lib/dtrace/probedata.rb +23 -0
- data/lib/dtrace/probedesc.rb +15 -0
- data/lib/dtrace/provider.rb +190 -169
- data/lib/dtrace/provider/klass.rb +33 -0
- data/lib/dtrace/provider/probedef.rb +24 -0
- data/lib/{dtracerecord.rb → dtrace/record.rb} +4 -2
- data/lib/{dtracestackrecord.rb → dtrace/stackrecord.rb} +10 -8
- data/lib/dtrace/version.rb +9 -0
- data/lib/dtraceconsumer.rb +3 -167
- data/plugin/dtrace/lib/dtracer.rb +4 -4
- data/test/apple-dof +0 -0
- data/test/disabled_probe_effect.txt +19 -0
- data/test/dof +0 -0
- data/test/dof2 +0 -0
- data/test/test_disabled_probe_effect.rb +56 -0
- data/test/test_dof_generator.rb +142 -0
- data/test/test_dof_helper.rb +106 -0
- data/test/test_dof_parser.rb +27 -0
- data/test/test_dof_providers.rb +278 -0
- data/test/test_dof_strtabs.rb +98 -0
- data/test/test_dtrace.rb +67 -1
- data/test/test_dtrace_aggregates.rb +5 -5
- data/test/test_dtrace_drops_errors.rb +5 -5
- data/test/test_dtrace_probe.rb +385 -0
- data/test/test_dtrace_probes.rb +414 -0
- data/test/test_dtrace_processes.rb +2 -2
- data/test/test_dtrace_profile.rb +12 -12
- data/test/test_dtrace_provider.rb +138 -0
- data/test/test_dtrace_repeat.rb +1 -1
- data/test/test_dtrace_rubyprobe.rb +3 -1
- data/test/test_dtrace_typefilter.rb +9 -9
- data/test/test_legacy_consumer.rb +56 -0
- metadata +112 -71
- data/lib/dtrace/provider/osx.rb +0 -25
- data/lib/dtrace/provider/solaris.rb +0 -29
- data/lib/dtraceaggregate.rb +0 -37
- data/lib/dtraceaggregateset.rb +0 -17
- data/lib/dtracedata.rb +0 -80
- data/lib/dtraceprobe.rb +0 -13
- data/lib/dtraceprobedata.rb +0 -21
- data/test/test_dynusdt.rb +0 -135
data/ext/stubs.txt
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
disassembly of C probe stub:
|
2
|
+
|
3
|
+
/* 3. void probe8(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7) { */
|
4
|
+
/* <Function: probe8> */
|
5
|
+
/* [ 3] 805133c: pushl %ebp */
|
6
|
+
/* [ 3] 805133d: movl %esp,%ebp */
|
7
|
+
/* [ 3] 805133f: subl $8,%esp */
|
8
|
+
/* 4. TEST_TEST_INT_INT_INT_INT_INT_INT_INT_INT_PROBE(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7); */
|
9
|
+
/* [ 4] 8051342: pushl 0x24(%ebp) */
|
10
|
+
/* [ 4] 8051345: pushl 0x20(%ebp) */
|
11
|
+
/* [ 4] 8051348: pushl 0x1c(%ebp) */
|
12
|
+
/* [ 4] 805134b: pushl 0x18(%ebp) */
|
13
|
+
/* [ 4] 805134e: pushl 0x14(%ebp) */
|
14
|
+
/* [ 4] 8051351: pushl 0x10(%ebp) */
|
15
|
+
/* [ 4] 8051354: pushl 0xc(%ebp) */
|
16
|
+
/* [ 4] 8051357: pushl 8(%ebp) */
|
17
|
+
/* [ 4] 805135a: nop */
|
18
|
+
/* [ 4] 805135b: nop */
|
19
|
+
/* [ 4] 805135c: nop */
|
20
|
+
/* [ 4] 805135d: nop */
|
21
|
+
/* [ 4] 805135e: nop */
|
22
|
+
/* [ 4] 805135f: addl $0x20,%esp */
|
23
|
+
/* 5. } */
|
24
|
+
/* [ 5] 8051362: leave */
|
25
|
+
/* [ 5] 8051363: ret */
|
26
|
+
|
27
|
+
Corresponding binary:
|
28
|
+
|
29
|
+
/* 1330 55 89 e5 83 .....^[. ....U... */
|
30
|
+
/* 1340 ec 08 ff 75 24 ff 75 20 ff 75 1c ff 75 18 ff 75 ...u$.u .u..u..u */
|
31
|
+
/* 1350 14 ff 75 10 ff 75 0c ff 75 08 90 90 90 90 90 83 ..u..u.. u....... */
|
32
|
+
/* 1360 c4 20 c9 c3 */
|
33
|
+
|
34
|
+
Hand-built 8-arg stub:
|
35
|
+
|
36
|
+
/* char insns[FUNC_SIZE] = */
|
37
|
+
/* { */
|
38
|
+
/* 0x55, 0x89, 0xe5, 0x83, 0xec, 0x08, */
|
39
|
+
/* 0xff, 0x75, 0x24, */
|
40
|
+
/* 0xff, 0x75, 0x20, */
|
41
|
+
/* 0xff, 0x75, 0x1c, */
|
42
|
+
/* 0xff, 0x75, 0x18, */
|
43
|
+
/* 0xff, 0x75, 0x14, */
|
44
|
+
/* 0xff, 0x75, 0x10, */
|
45
|
+
/* 0xff, 0x75, 0x0c, */
|
46
|
+
/* 0xff, 0x75, 0x08, */
|
47
|
+
/* 0x90, 0x90, 0x90, 0x90, 0x90, */
|
48
|
+
/* 0x83, 0xc4, 0x20, */
|
49
|
+
/* 0xc9, 0xc3 */
|
50
|
+
/* }; */
|
51
|
+
|
52
|
+
disassembly of is_enabled function:
|
53
|
+
|
54
|
+
21. int is_enabled(void) {
|
55
|
+
<Function: is_enabled>
|
56
|
+
[21] 805142a: pushl %ebp
|
57
|
+
[21] 805142b: movl %esp,%ebp
|
58
|
+
[21] 805142d: subl $8,%esp
|
59
|
+
22. return TEST_TEST_PROBE_ENABLED() ? 1 : 0;
|
60
|
+
[22] 8051430: xorl %eax,%eax
|
61
|
+
[22] 8051432: nop
|
62
|
+
[22] 8051433: nop
|
63
|
+
[22] 8051434: nop
|
64
|
+
[22] 8051435: movl %eax,-4(%ebp)
|
65
|
+
[22] 8051438: cmpl $0,-4(%ebp)
|
66
|
+
[22] 805143c: setne %al
|
67
|
+
[22] 805143f: movzbl %al,%eax
|
68
|
+
[22] 8051442: movl %eax,-4(%ebp)
|
69
|
+
[22] 8051445: movl -4(%ebp),%eax
|
70
|
+
23. }
|
71
|
+
[23] 8051448: leave
|
72
|
+
[23] 8051449: ret
|
73
|
+
|
74
|
+
Binary:
|
75
|
+
|
76
|
+
55 89 e5 83 ec 08 ....... ..U.....
|
77
|
+
1430 33 c0 90 90 90 89 45 fc 83 7d fc 00 0f 95 c0 0f 3.....E. .}......
|
78
|
+
1440 b6 c0 89 45 fc 8b 45 fc c9 c3
|
data/lib/dtrace.rb
CHANGED
@@ -4,15 +4,17 @@
|
|
4
4
|
#
|
5
5
|
|
6
6
|
require 'dtrace_api'
|
7
|
-
require '
|
7
|
+
require 'dtrace/record'
|
8
|
+
require 'dtrace/consumer'
|
8
9
|
require 'dtraceconsumer'
|
9
|
-
require '
|
10
|
-
require '
|
11
|
-
require '
|
12
|
-
require '
|
13
|
-
require '
|
14
|
-
require '
|
15
|
-
require '
|
10
|
+
require 'dtrace/aggregate'
|
11
|
+
require 'dtrace/aggregateset'
|
12
|
+
require 'dtrace/probedata'
|
13
|
+
require 'dtrace/probedesc'
|
14
|
+
require 'dtrace/stackrecord'
|
15
|
+
require 'dtrace/printfrecord'
|
16
|
+
require 'dtrace/data'
|
17
|
+
require 'dtrace/version'
|
16
18
|
|
17
19
|
# A DTrace handle. Provides methods for inspecting available probes,
|
18
20
|
# compiling and running programs, and for setting up callbacks to
|
@@ -22,10 +24,10 @@ require 'dtracedata'
|
|
22
24
|
#
|
23
25
|
# * Create a handle with Dtrace.new
|
24
26
|
# * Set options
|
25
|
-
# * Compile the program, possibly inspecting the return
|
27
|
+
# * Compile the program, possibly inspecting the return Dtrace::ProgramInfo
|
26
28
|
# * Execute the program
|
27
29
|
# * Start tracing
|
28
|
-
# * Consume data, either directly by setting up callbacks, or using a
|
30
|
+
# * Consume data, either directly by setting up callbacks, or using a Dtrace::Consumer.
|
29
31
|
# * Stop tracing
|
30
32
|
#
|
31
33
|
# === Listing probes
|
@@ -57,18 +59,37 @@ require 'dtracedata'
|
|
57
59
|
# t.go
|
58
60
|
# p.continue
|
59
61
|
#
|
60
|
-
# c =
|
62
|
+
# c = Dtrace::Consumer.new(t)
|
61
63
|
# c.consume do |d|
|
62
64
|
# ..
|
63
65
|
# end
|
64
66
|
|
65
67
|
class Dtrace
|
66
|
-
VERSION = '0.0.6'
|
67
|
-
|
68
68
|
STATUS_NONE = 0
|
69
69
|
STATUS_OKAY = 1
|
70
70
|
STATUS_EXITED = 2
|
71
71
|
STATUS_FILLED = 3
|
72
72
|
STATUS_STOPPED = 4
|
73
|
+
|
74
|
+
# Yields each probe on the system, optionally matching against a
|
75
|
+
# probe specification:
|
76
|
+
#
|
77
|
+
# e.g.
|
78
|
+
# syscall::: -> all probes in the syscall provider
|
79
|
+
# pid123:::return -> all return probes in pid 123.
|
80
|
+
#
|
81
|
+
def each_probe(match=nil, &block)
|
82
|
+
if match
|
83
|
+
parts = match.split(':', 4)
|
84
|
+
begin
|
85
|
+
each_probe_match(*parts, &block)
|
86
|
+
rescue ArgumentError => e
|
87
|
+
raise Dtrace::Exception.new("each_probe: probe specification expected (e.g. 'provider:::')")
|
88
|
+
end
|
89
|
+
else
|
90
|
+
each_probe_all(&block)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
73
94
|
end
|
74
95
|
|
@@ -0,0 +1,40 @@
|
|
1
|
+
#
|
2
|
+
# Ruby-Dtrace
|
3
|
+
# (c) 2007 Chris Andrews <chris@nodnol.org>
|
4
|
+
#
|
5
|
+
|
6
|
+
# Represents an aggregation record built from a series of
|
7
|
+
# DtraceAggData records.
|
8
|
+
#
|
9
|
+
# Intended to to built up by calling +add_record+ repeatedly with
|
10
|
+
# Dtrace::AggData objects until a completed Dtrace::Aggregate is
|
11
|
+
# returned. (until a complete record is available, +add_record+
|
12
|
+
# returns nil).
|
13
|
+
#
|
14
|
+
# See consumer.rb for an example of this.
|
15
|
+
class Dtrace
|
16
|
+
class Aggregate
|
17
|
+
attr_reader :value, :tuple
|
18
|
+
|
19
|
+
# Create an empty Dtrace::Aggregate: use +add_record+ to add data.
|
20
|
+
def initialize
|
21
|
+
@tuple = Array.new
|
22
|
+
end
|
23
|
+
|
24
|
+
# Add a Dtrace::AggData record to this aggregate. Returns nil until it
|
25
|
+
# receives a record of aggtype "last", when it returns the complete
|
26
|
+
# Dtrace::Aggregate.
|
27
|
+
def add_record(r)
|
28
|
+
case r.aggtype
|
29
|
+
when "tuple"
|
30
|
+
@tuple << r.value
|
31
|
+
when "value"
|
32
|
+
@value = r.value
|
33
|
+
when "last"
|
34
|
+
return self
|
35
|
+
end
|
36
|
+
nil
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,174 @@
|
|
1
|
+
#
|
2
|
+
# Ruby-Dtrace
|
3
|
+
# (c) 2007 Chris Andrews <chris@nodnol.org>
|
4
|
+
#
|
5
|
+
|
6
|
+
# A Dtrace::Consumer provides access to the data produced by the running
|
7
|
+
# D program. Having compiled and executed a D program, you typically
|
8
|
+
# create a Dtrace::Consumer, 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 Dtrace::Data 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 = Dtrace::Consumer.new(t)
|
33
|
+
# c.consume_once do |d|
|
34
|
+
# # handle Dtrace::Data objects
|
35
|
+
# # ...
|
36
|
+
# # get bored:
|
37
|
+
# c.finish
|
38
|
+
# end
|
39
|
+
|
40
|
+
class Dtrace
|
41
|
+
class Consumer
|
42
|
+
|
43
|
+
def initialize(t)
|
44
|
+
@t = t
|
45
|
+
@done = false
|
46
|
+
@types = []
|
47
|
+
|
48
|
+
@drophandler = nil
|
49
|
+
@errhandler = nil
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
# The consumer callbacks:
|
55
|
+
#
|
56
|
+
# DtraceRecDesc -> rec_consumer
|
57
|
+
# DtraceProbeData -> probe_consumer
|
58
|
+
# DtraceBufData -> buf_consumer
|
59
|
+
#
|
60
|
+
# We expect a sequence of calls to these procs, and we accumulate
|
61
|
+
# data in the @curr Dtrace::Data based on this:
|
62
|
+
#
|
63
|
+
# Dtrace::ProbeData (initial callback for a probe firing)
|
64
|
+
# Dtrace::RecDesc
|
65
|
+
# ...
|
66
|
+
# Dtrace::RecDesc = nil (end of data)
|
67
|
+
#
|
68
|
+
|
69
|
+
def rec_consumer(block)
|
70
|
+
proc do |rec|
|
71
|
+
if rec
|
72
|
+
@curr.add_recdata(rec)
|
73
|
+
else
|
74
|
+
@curr.finish
|
75
|
+
block.call(@curr)
|
76
|
+
@curr = Dtrace::Data.new(@types)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def probe_consumer
|
82
|
+
proc do |probe|
|
83
|
+
@curr.add_probedata(probe)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def buf_consumer
|
88
|
+
proc do |buf|
|
89
|
+
@curr.add_bufdata(buf)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def filter_types(types)
|
94
|
+
@types = types
|
95
|
+
@curr = Dtrace::Data.new(types)
|
96
|
+
end
|
97
|
+
|
98
|
+
public
|
99
|
+
|
100
|
+
# Provide a proc which will be executed when a drop record is
|
101
|
+
# received.
|
102
|
+
def drophandler(&block)
|
103
|
+
@drophandler = block
|
104
|
+
@t.drop_consumer(proc do |drop|
|
105
|
+
if @drophandler
|
106
|
+
@drophandler.call(drop)
|
107
|
+
end
|
108
|
+
end)
|
109
|
+
end
|
110
|
+
|
111
|
+
# Provide a proc which will be executed when an error record is
|
112
|
+
# received.
|
113
|
+
def errhandler(&block)
|
114
|
+
@errhandler = block
|
115
|
+
@t.err_consumer(proc do |err|
|
116
|
+
if @errhandler
|
117
|
+
@errhandler.call(err)
|
118
|
+
end
|
119
|
+
end)
|
120
|
+
end
|
121
|
+
|
122
|
+
# Signals that the client wishes to stop consuming trace data.
|
123
|
+
def finish
|
124
|
+
@t.stop
|
125
|
+
@done = true
|
126
|
+
end
|
127
|
+
|
128
|
+
# Waits for data from the D program, and yields the records returned
|
129
|
+
# to the block given. Returns when the D program exits.
|
130
|
+
#
|
131
|
+
# Pass a list of classes to restrict the types of data returned,
|
132
|
+
# from:
|
133
|
+
#
|
134
|
+
# * DtraceRecord
|
135
|
+
# * DtracePrintfRecord
|
136
|
+
# * DtraceAggregateSet
|
137
|
+
# * DtraceStackRecord
|
138
|
+
#
|
139
|
+
def consume(*types, &block)
|
140
|
+
filter_types(types)
|
141
|
+
@t.buf_consumer(buf_consumer)
|
142
|
+
begin
|
143
|
+
while(true) do
|
144
|
+
@t.sleep
|
145
|
+
work = @t.work(probe_consumer, rec_consumer(block))
|
146
|
+
if (@done || work > 0)
|
147
|
+
break
|
148
|
+
end
|
149
|
+
end
|
150
|
+
ensure
|
151
|
+
@t.stop
|
152
|
+
@t.work(probe_consumer)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
# Yields the data waiting from the current program, then returns.
|
157
|
+
#
|
158
|
+
# Pass a list of classes to restrict the types of data returned,
|
159
|
+
# from:
|
160
|
+
#
|
161
|
+
# * DtraceRecord
|
162
|
+
# * DtracePrintfRecord
|
163
|
+
# * DtraceAggregateSet
|
164
|
+
# * DtraceStackRecord
|
165
|
+
#
|
166
|
+
def consume_once(*types, &block)
|
167
|
+
filter_types(types)
|
168
|
+
@t.buf_consumer(buf_consumer)
|
169
|
+
@t.stop
|
170
|
+
@t.work(probe_consumer, rec_consumer(block))
|
171
|
+
end
|
172
|
+
|
173
|
+
end
|
174
|
+
end
|
data/lib/dtrace/data.rb
ADDED
@@ -0,0 +1,82 @@
|
|
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 Dtrace
|
8
|
+
class Data
|
9
|
+
attr_reader :data
|
10
|
+
attr_reader :probe
|
11
|
+
attr_reader :cpu, :indent, :prefix, :flow
|
12
|
+
|
13
|
+
def initialize(types)
|
14
|
+
@types = types
|
15
|
+
@data = []
|
16
|
+
@curraggset = nil
|
17
|
+
@curragg = nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def add_data(d)
|
21
|
+
if @types.length == 0 || @types.include?(d.class)
|
22
|
+
@data << d
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def finish
|
27
|
+
if @curraggset
|
28
|
+
add_data(@curraggset)
|
29
|
+
@curraggset = nil
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def add_recdata(rec)
|
34
|
+
if @curraggset
|
35
|
+
add_data(@curraggset)
|
36
|
+
@curraggset = nil
|
37
|
+
end
|
38
|
+
if rec.action == "printa"
|
39
|
+
@curraggset = Dtrace::AggregateSet.new
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def add_probedata(probedata)
|
44
|
+
probedata.each_record do |p|
|
45
|
+
add_data(p)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Record the probe that fired, and CPU/indent/prefix/flow
|
49
|
+
@probe = probedata.probe
|
50
|
+
@cpu = probedata.cpu
|
51
|
+
@indent = probedata.indent
|
52
|
+
@prefix = probedata.prefix
|
53
|
+
@flow = probedata.flow
|
54
|
+
end
|
55
|
+
|
56
|
+
def add_bufdata(buf)
|
57
|
+
r = buf.record
|
58
|
+
# buf records can be empty (trace();)
|
59
|
+
if r
|
60
|
+
case r.class.to_s
|
61
|
+
when Dtrace::StackRecord.to_s
|
62
|
+
add_data(r)
|
63
|
+
when Dtrace::Record.to_s
|
64
|
+
add_data(r)
|
65
|
+
when Dtrace::PrintfRecord.to_s
|
66
|
+
add_data(r)
|
67
|
+
when Dtrace::AggData.to_s
|
68
|
+
if @curragg == nil
|
69
|
+
@curragg = Dtrace::Aggregate.new
|
70
|
+
end
|
71
|
+
if agg = @curragg.add_record(r)
|
72
|
+
if @curraggset
|
73
|
+
@curraggset.add_aggregate(@curragg)
|
74
|
+
end
|
75
|
+
@curragg = nil
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
end
|