benelux 0.3.2 → 0.4.1
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/CHANGES.txt +15 -0
- data/README.rdoc +1 -1
- data/benelux.gemspec +15 -4
- data/lib/benelux.rb +141 -172
- data/lib/benelux/count.rb +29 -0
- data/lib/benelux/mark.rb +2 -11
- data/lib/benelux/mixins/symbol.rb +8 -0
- data/lib/benelux/mixins/thread.rb +1 -1
- data/lib/benelux/packer.rb +138 -0
- data/lib/benelux/range.rb +2 -2
- data/lib/benelux/reporter.rb +89 -0
- data/lib/benelux/stats.rb +70 -47
- data/lib/benelux/timeline.rb +45 -35
- data/lib/benelux/track.rb +20 -0
- data/lib/selectable.rb +72 -0
- data/lib/selectable/global.rb +63 -0
- data/lib/selectable/object.rb +42 -0
- data/lib/{benelux → selectable}/tags.rb +19 -45
- data/tryouts/10_stats_tryouts.rb +21 -4
- data/tryouts/{11_tags_tryouts.rb → 11_selectable_tryouts.rb} +38 -2
- data/tryouts/12_selectable_global_tryouts.rb +23 -0
- data/tryouts/20_tracks_tryouts.rb +106 -0
- data/tryouts/30_reporter_tryouts.rb +34 -0
- data/tryouts/30_timeline_tryouts.rb +38 -3
- data/tryouts/proofs/array_performance.rb +33 -0
- metadata +16 -5
- data/tryouts/20_class_methods_tryouts.rb +0 -69
@@ -0,0 +1,29 @@
|
|
1
|
+
module Benelux
|
2
|
+
|
3
|
+
class Count
|
4
|
+
include Selectable::Object
|
5
|
+
attr_accessor :name
|
6
|
+
def initialize(name, count)
|
7
|
+
@name, @count = name, count
|
8
|
+
end
|
9
|
+
def track
|
10
|
+
@tags[:track]
|
11
|
+
end
|
12
|
+
def inspect
|
13
|
+
"#<%s:%s count=%d name=%s %s>" % [self.class, hexoid, self.to_i, name, tags]
|
14
|
+
end
|
15
|
+
def to_i
|
16
|
+
@count.to_i
|
17
|
+
end
|
18
|
+
def to_s
|
19
|
+
@count.to_s
|
20
|
+
end
|
21
|
+
def ==(other)
|
22
|
+
self.class == other.class &&
|
23
|
+
self.name == other.name &&
|
24
|
+
self.tags == other.tags &&
|
25
|
+
@count.to_i == other.to_i
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
data/lib/benelux/mark.rb
CHANGED
@@ -1,24 +1,16 @@
|
|
1
1
|
module Benelux
|
2
2
|
class Mark < Time
|
3
|
-
include
|
3
|
+
include Selectable::Object
|
4
4
|
attr_accessor :name
|
5
5
|
def self.now(n=nil)
|
6
6
|
v = super()
|
7
|
-
v.tags =
|
7
|
+
v.tags = Selectable::Tags.new
|
8
8
|
v.name = n
|
9
9
|
v
|
10
10
|
end
|
11
11
|
def track
|
12
12
|
@tags[:track]
|
13
13
|
end
|
14
|
-
def add_tags(tags=Benelux::Tags.new)
|
15
|
-
@tags.merge! tags
|
16
|
-
end
|
17
|
-
alias_method :add_tag, :add_tags
|
18
|
-
def remove_tags(*tags)
|
19
|
-
@tags.delete_if { |n,v| tags.member?(n) }
|
20
|
-
end
|
21
|
-
alias_method :remove_tag, :remove_tags
|
22
14
|
def inspect(reftime=nil)
|
23
15
|
val = reftime.nil? ? self : (reftime - self)
|
24
16
|
"#<%s:%s at=%f name=%s %s>" % [self.class, hexoid, to_f, name, tags]
|
@@ -31,7 +23,6 @@ module Benelux
|
|
31
23
|
self - time
|
32
24
|
end
|
33
25
|
def ==(other)
|
34
|
-
return false unless other.respond_to? :call_id
|
35
26
|
self.name == other.name &&
|
36
27
|
self.tags == other.tags &&
|
37
28
|
self.to_f == self.to_f
|
@@ -0,0 +1,138 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
module Benelux
|
4
|
+
class MethodPacker
|
5
|
+
include Selectable::Object
|
6
|
+
|
7
|
+
attr_accessor :aliaz
|
8
|
+
attr_reader :klass
|
9
|
+
attr_reader :meth
|
10
|
+
attr_reader :blk
|
11
|
+
|
12
|
+
# * +k+ is a class
|
13
|
+
# * +m+ is the name of an instance method in +k+
|
14
|
+
#
|
15
|
+
# This method makes the following changes to class +k+.
|
16
|
+
#
|
17
|
+
# * Add a timeline attic to and include +Benelux+
|
18
|
+
# * Rename the method to something like:
|
19
|
+
# __benelux_execute_2151884308_2165479316
|
20
|
+
# * Install a new method with the name +m+.
|
21
|
+
#
|
22
|
+
def initialize(k,m,&blk)
|
23
|
+
Benelux.ld "%20s: %s#%s" % [self.class, k, m]
|
24
|
+
if Benelux.packed_method? k, m
|
25
|
+
raise SelectableError, "Already defined (#{k} #{m})"
|
26
|
+
end
|
27
|
+
@klass, @meth, @blk = k, m, blk
|
28
|
+
@klass.extend Attic unless @klass.kind_of?(Attic)
|
29
|
+
unless @klass.kind_of?(Benelux)
|
30
|
+
@klass.attic :timeline
|
31
|
+
@klass.send :include, Benelux
|
32
|
+
end
|
33
|
+
## NOTE: This is commented out so we can include
|
34
|
+
## Benelux definitions before all classes are loaded.
|
35
|
+
##unless obj.respond_to? meth
|
36
|
+
## raise NoMethodError, "undefined method `#{meth}' for #{obj}:Class"
|
37
|
+
##end
|
38
|
+
thread_id, call_id = Thread.current.object_id.abs, @klass.object_id.abs
|
39
|
+
@aliaz = a = :"__benelux_#{@meth}_#{thread_id}_#{call_id}"
|
40
|
+
Benelux.ld "%20s: %s" % ['Alias', @aliaz]
|
41
|
+
@klass.module_eval do
|
42
|
+
alias_method a, m # Can't use the instance variables
|
43
|
+
end
|
44
|
+
install_method # see generate_packed_method
|
45
|
+
self.add_tags :class => @klass.to_s.to_sym,
|
46
|
+
:meth => @meth.to_sym,
|
47
|
+
:kind => self.class.to_s.to_sym
|
48
|
+
Benelux.packed_methods << self
|
49
|
+
end
|
50
|
+
def install_method
|
51
|
+
raise "You need to implement this method"
|
52
|
+
end
|
53
|
+
# instance_exec for Ruby 1.8 written by Mauricio Fernandez
|
54
|
+
# http://eigenclass.org/hiki/instance_exec
|
55
|
+
if RUBY_VERSION =~ /1.8/
|
56
|
+
module InstanceExecHelper; end
|
57
|
+
include InstanceExecHelper
|
58
|
+
def instance_exec(*args, &block) # !> method redefined; discarding old instance_exec
|
59
|
+
mname = "__instance_exec_#{Thread.current.object_id.abs}_#{object_id.abs}"
|
60
|
+
InstanceExecHelper.module_eval{ define_method(mname, &block) }
|
61
|
+
begin
|
62
|
+
ret = send(mname, *args)
|
63
|
+
ensure
|
64
|
+
InstanceExecHelper.module_eval{ undef_method(mname) } rescue nil
|
65
|
+
end
|
66
|
+
ret
|
67
|
+
end
|
68
|
+
end
|
69
|
+
def run_block(*args)
|
70
|
+
raise "must implement"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
class MethodTimer < MethodPacker
|
75
|
+
# This method executes the method definition created by
|
76
|
+
# generate_method. It calls <tt>@klass.module_eval</tt>
|
77
|
+
# with the modified line number (helpful for exceptions)
|
78
|
+
def install_method
|
79
|
+
@klass.module_eval generate_packed_method, __FILE__, 89
|
80
|
+
end
|
81
|
+
|
82
|
+
# Creates a method definition (for an eval). The
|
83
|
+
# method is named +@meth+ and it calls +@aliaz+.
|
84
|
+
#
|
85
|
+
# The new method adds a Mark to the thread timeline
|
86
|
+
# before and after +@alias+ is called. It also adds
|
87
|
+
# a Range to the timeline based on the two marks.
|
88
|
+
def generate_packed_method
|
89
|
+
%Q{
|
90
|
+
def #{@meth}(*args, &block)
|
91
|
+
call_id = "" << self.object_id.abs.to_s << args.object_id.abs.to_s
|
92
|
+
Benelux.current_track :global unless Benelux.known_thread?
|
93
|
+
mark_a = Thread.current.timeline.add_mark :'#{@meth}_a'
|
94
|
+
mark_a.add_tag :call_id => call_id
|
95
|
+
tags = mark_a.tags
|
96
|
+
ret = #{@aliaz}(*args, &block)
|
97
|
+
rescue => ex # We do this so we can use
|
98
|
+
raise ex # ex in the ensure block.
|
99
|
+
ensure
|
100
|
+
mark_z = Thread.current.timeline.add_mark :'#{@meth}_z'
|
101
|
+
mark_z.tags = tags # In case tags were added between these marks
|
102
|
+
range = Thread.current.timeline.add_range :'#{@meth}', mark_a, mark_z
|
103
|
+
range.exception = ex if defined?(ex) && !ex.nil?
|
104
|
+
end
|
105
|
+
}
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
class MethodCounter < MethodPacker
|
110
|
+
attr_reader :counter
|
111
|
+
def install_method
|
112
|
+
@klass.module_eval generate_packed_method, __FILE__, 121
|
113
|
+
end
|
114
|
+
|
115
|
+
def generate_packed_method(callblock=false)
|
116
|
+
%Q{
|
117
|
+
@@__benelux_#{@meth}_counter =
|
118
|
+
Benelux.counted_method #{@klass}, :#{@meth}
|
119
|
+
def #{@meth}(*args, &block)
|
120
|
+
Benelux.current_track :global unless Benelux.known_thread?
|
121
|
+
# Get a reference to this MethodCounter instance
|
122
|
+
cmd = Benelux.counted_method #{@klass}, :#{@meth}
|
123
|
+
ret = #{@aliaz}(*args, &block)
|
124
|
+
count = cmd.determine_count(args, ret)
|
125
|
+
Benelux.ld "COUNT(:#{@meth}): \#{count}"
|
126
|
+
Benelux.thread_timeline.add_count :'#{@meth}', count
|
127
|
+
ret
|
128
|
+
end
|
129
|
+
}
|
130
|
+
end
|
131
|
+
|
132
|
+
def determine_count(args,ret)
|
133
|
+
return 1 if @blk.nil?
|
134
|
+
self.instance_exec args, ret, &blk
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
138
|
+
end
|
data/lib/benelux/range.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
|
2
2
|
module Benelux
|
3
3
|
class Range
|
4
|
-
include
|
4
|
+
include Selectable::Object
|
5
5
|
|
6
6
|
attr_accessor :name
|
7
7
|
attr_accessor :from
|
@@ -9,7 +9,7 @@ module Benelux
|
|
9
9
|
attr_accessor :exception
|
10
10
|
def initialize(name,from,to)
|
11
11
|
@name, @from, @to = name, from, to
|
12
|
-
@tags =
|
12
|
+
@tags = Selectable::Tags.new
|
13
13
|
end
|
14
14
|
def to_s
|
15
15
|
"%s:%.4f" % [name, duration]
|
@@ -0,0 +1,89 @@
|
|
1
|
+
|
2
|
+
module Benelux
|
3
|
+
class Reporter
|
4
|
+
attr_reader :thread
|
5
|
+
attr_reader :thwait
|
6
|
+
@@mutex = Mutex.new
|
7
|
+
def initialize(*threads)
|
8
|
+
@thwait = ThreadsWait.new
|
9
|
+
@abort, @running = false, false
|
10
|
+
@tmerge = Benelux::Stats::Calculator.new
|
11
|
+
add_threads *threads
|
12
|
+
end
|
13
|
+
def add_threads(*threads)
|
14
|
+
threads.each do |thread|
|
15
|
+
#raise BadRecursion, "Cannot report on self" if thread == Thread.current
|
16
|
+
next if thread == Thread.main
|
17
|
+
@thwait.join_nowait thread
|
18
|
+
end
|
19
|
+
return if running?
|
20
|
+
@@mutex.synchronize do
|
21
|
+
start
|
22
|
+
end
|
23
|
+
end
|
24
|
+
alias_method :add_thread, :add_threads
|
25
|
+
def running_threads?
|
26
|
+
# Any status that is not nil or false is running
|
27
|
+
!@thwait.threads.select { |t| t.status }.empty?
|
28
|
+
end
|
29
|
+
|
30
|
+
def start
|
31
|
+
return if running?
|
32
|
+
@running = true
|
33
|
+
@thread = Thread.new do
|
34
|
+
while @thwait.empty?
|
35
|
+
sleep 0.5 # Give the app time to generate threads
|
36
|
+
end
|
37
|
+
@tbd = []
|
38
|
+
run_loop
|
39
|
+
end
|
40
|
+
@thread.priority = -3
|
41
|
+
end
|
42
|
+
def run_loop
|
43
|
+
loop do
|
44
|
+
break if @abort
|
45
|
+
process(@tbd)
|
46
|
+
if @thwait.empty?
|
47
|
+
sleep 0.001
|
48
|
+
running_threads? ? next : break
|
49
|
+
end
|
50
|
+
t = @thwait.next_wait
|
51
|
+
#p [:reporter_queue, '^', t.track_name, @thread.priority]
|
52
|
+
@tbd << t.timeline
|
53
|
+
end
|
54
|
+
end
|
55
|
+
def process(tbd)
|
56
|
+
return if tbd.empty?
|
57
|
+
(start = Time.now)
|
58
|
+
Benelux.timeline.merge! *tbd
|
59
|
+
(endt = Time.now)
|
60
|
+
dur = (endt - start).to_f
|
61
|
+
#p [:processed, tbd.size, dur]
|
62
|
+
tbd.clear
|
63
|
+
@tmerge.sample dur
|
64
|
+
end
|
65
|
+
# We don't add the main thread to the wait group
|
66
|
+
# so we need to manually force processing for
|
67
|
+
# that thread. The reason: we
|
68
|
+
def force_update
|
69
|
+
@abort = false
|
70
|
+
@tbd << Thread.current.timeline
|
71
|
+
run_loop
|
72
|
+
end
|
73
|
+
def wait
|
74
|
+
if @thread && Thread.current == Thread.main
|
75
|
+
@abort = true
|
76
|
+
@thread.priority = 0
|
77
|
+
@thread.join if @thread.status
|
78
|
+
force_update
|
79
|
+
else
|
80
|
+
msg = "Not main thread. Skipping call to wait from #{caller[0]}"
|
81
|
+
Benelux.ld msg
|
82
|
+
end
|
83
|
+
end
|
84
|
+
def stop() @abort = true end
|
85
|
+
def done?() @done end
|
86
|
+
def aborted?() @abort end
|
87
|
+
def running?() @running end
|
88
|
+
end
|
89
|
+
end
|
data/lib/benelux/stats.rb
CHANGED
@@ -2,31 +2,41 @@
|
|
2
2
|
module Benelux
|
3
3
|
class Stats
|
4
4
|
attr_reader :names
|
5
|
+
|
5
6
|
def initialize(*names)
|
6
7
|
@names = []
|
7
|
-
|
8
|
+
add_groups names
|
9
|
+
end
|
10
|
+
def group(name)
|
11
|
+
@names.member?(name) ? self.send(name) : create_zero_group(name)
|
8
12
|
end
|
9
|
-
def
|
10
|
-
|
13
|
+
def create_zero_group(name)
|
14
|
+
g = Benelux::Stats::Group.new
|
15
|
+
g.name = name
|
16
|
+
g
|
11
17
|
end
|
12
|
-
# Each
|
18
|
+
# Each group
|
13
19
|
def each(&blk)
|
14
|
-
@names.each { |name| blk.call(
|
20
|
+
@names.each { |name| blk.call(group(name)) }
|
15
21
|
end
|
16
|
-
# Each
|
22
|
+
# Each group name, group
|
17
23
|
def each_pair(&blk)
|
18
|
-
@names.each { |name| blk.call(name,
|
24
|
+
@names.each { |name| blk.call(name, group(name)) }
|
19
25
|
end
|
20
|
-
def
|
26
|
+
def add_groups(*args)
|
21
27
|
args.flatten.each do |meth|
|
22
|
-
next if
|
28
|
+
next if has_group? meth
|
23
29
|
@names << meth
|
24
30
|
self.class.send :attr_reader, meth
|
25
|
-
|
31
|
+
(g = Benelux::Stats::Group.new).name = meth
|
32
|
+
instance_variable_set("@#{meth}", g)
|
26
33
|
end
|
27
34
|
end
|
28
|
-
|
29
|
-
|
35
|
+
def sample(name, s, tags={})
|
36
|
+
self.send(name).sample(s, tags)
|
37
|
+
end
|
38
|
+
alias_method :add_group, :add_groups
|
39
|
+
def has_group?(name)
|
30
40
|
@names.member? name
|
31
41
|
end
|
32
42
|
def +(other)
|
@@ -34,7 +44,7 @@ module Benelux
|
|
34
44
|
raise TypeError, "can't convert #{other.class} into Stats"
|
35
45
|
end
|
36
46
|
other.names.each do |name|
|
37
|
-
|
47
|
+
add_group name
|
38
48
|
a = self.send(name)
|
39
49
|
a += other.send(name)
|
40
50
|
a
|
@@ -43,23 +53,23 @@ module Benelux
|
|
43
53
|
end
|
44
54
|
|
45
55
|
class Group < Array
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
end
|
56
|
+
include Selectable
|
57
|
+
|
58
|
+
attr_accessor :name
|
50
59
|
|
51
60
|
def +(other)
|
52
61
|
unless @name == other.name
|
53
62
|
raise BeneluxError, "Cannot add #{other.name} to #{@name}"
|
54
63
|
end
|
55
64
|
other.each do |newcalc|
|
65
|
+
# Merge calculator with a like calculator in another group.
|
56
66
|
calcs = self.select { |calc| calc.tags == newcalc.tags }
|
57
|
-
self << newcalc and next if calcs.empty?
|
58
67
|
# This should only ever contain one b/c we should
|
59
68
|
# not have several calculators with the same tags.
|
60
69
|
calcs.each do |calc|
|
61
70
|
calc += newcalc
|
62
71
|
end
|
72
|
+
self << newcalc
|
63
73
|
end
|
64
74
|
self
|
65
75
|
end
|
@@ -76,37 +86,42 @@ module Benelux
|
|
76
86
|
nil
|
77
87
|
end
|
78
88
|
|
89
|
+
def tag_values(tag)
|
90
|
+
vals = self.collect { |calc| calc.tags[tag] }
|
91
|
+
Array.new vals.uniq
|
92
|
+
end
|
93
|
+
|
94
|
+
def tags() merge.tags end
|
79
95
|
def mean() merge.mean end
|
80
96
|
def min() merge.min end
|
81
97
|
def max() merge.max end
|
98
|
+
def sum() merge.sum end
|
82
99
|
def sd() merge.sd end
|
83
100
|
def n() merge.n end
|
84
101
|
|
85
|
-
def merge(tags
|
86
|
-
|
102
|
+
def merge(*tags)
|
103
|
+
tags = Selectable.normalize tags
|
87
104
|
mc = Calculator.new
|
105
|
+
mc.init_tags!
|
88
106
|
all = tags.empty? ? self : self.filter(tags)
|
89
107
|
all.each { |calc|
|
90
|
-
mc.
|
91
|
-
mc.
|
108
|
+
mc.merge! calc
|
109
|
+
mc.add_tags_quick calc.tags
|
92
110
|
}
|
93
111
|
mc
|
94
112
|
end
|
95
113
|
|
96
|
-
def
|
97
|
-
|
98
|
-
|
99
|
-
g << self.select { |c| c.tags >= tags }
|
100
|
-
g.flatten!(1) # only 1 level deep
|
101
|
-
g
|
114
|
+
def filter(*tags)
|
115
|
+
(f = super).name = @name
|
116
|
+
f
|
102
117
|
end
|
103
|
-
alias_method :
|
118
|
+
alias_method :[], :filter
|
104
119
|
|
105
120
|
end
|
106
121
|
|
107
122
|
# Based on Mongrel::Stats, Copyright (c) 2005 Zed A. Shaw
|
108
|
-
class Calculator
|
109
|
-
include
|
123
|
+
class Calculator
|
124
|
+
include Selectable::Object
|
110
125
|
|
111
126
|
attr_reader :sum, :sumsq, :n, :min, :max
|
112
127
|
|
@@ -115,16 +130,15 @@ module Benelux
|
|
115
130
|
end
|
116
131
|
|
117
132
|
def +(other)
|
118
|
-
|
119
|
-
self
|
120
|
-
|
133
|
+
c = Calculator.new
|
134
|
+
c.merge! self
|
135
|
+
c.merge! other
|
136
|
+
c
|
121
137
|
end
|
122
138
|
|
123
139
|
# Resets the internal counters so you can start sampling again.
|
124
140
|
def reset
|
125
|
-
self.clear
|
126
141
|
@n, @sum, @sumsq = 0.0, 0.0, 0.0
|
127
|
-
@last_time = 0.0
|
128
142
|
@min, @max = 0.0, 0.0
|
129
143
|
end
|
130
144
|
|
@@ -132,13 +146,22 @@ module Benelux
|
|
132
146
|
args.flatten.each { |s| sample(s) }
|
133
147
|
end
|
134
148
|
|
149
|
+
def merge!(other)
|
150
|
+
if @sum == 0
|
151
|
+
@min = @max = other.mean
|
152
|
+
else
|
153
|
+
@min = other.min if other.min < @min
|
154
|
+
@max = other.max if other.max > @max
|
155
|
+
end
|
156
|
+
@sum += other.sum
|
157
|
+
@sumsq += other.sumsq
|
158
|
+
@n += other.n
|
159
|
+
|
160
|
+
self
|
161
|
+
end
|
162
|
+
|
135
163
|
# Adds a sampling to the calculations.
|
136
164
|
def sample(s)
|
137
|
-
self << s
|
138
|
-
update s
|
139
|
-
end
|
140
|
-
|
141
|
-
def update(s)
|
142
165
|
@sum += s
|
143
166
|
@sumsq += s * s
|
144
167
|
if @n == 0
|
@@ -184,13 +207,13 @@ module Benelux
|
|
184
207
|
return 0.0
|
185
208
|
end
|
186
209
|
end
|
187
|
-
|
188
|
-
def
|
189
|
-
|
190
|
-
|
191
|
-
|
210
|
+
|
211
|
+
def ==(other)
|
212
|
+
a=([@sum, @min, @max, @n, @sumsq] -
|
213
|
+
[other.sum, other.min, other.max, other.n, other.sumsq])
|
214
|
+
a.empty?
|
192
215
|
end
|
193
|
-
|
216
|
+
|
194
217
|
end
|
195
218
|
|
196
219
|
|