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.
@@ -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
@@ -1,24 +1,16 @@
1
1
  module Benelux
2
2
  class Mark < Time
3
- include Benelux::TagHelpers
3
+ include Selectable::Object
4
4
  attr_accessor :name
5
5
  def self.now(n=nil)
6
6
  v = super()
7
- v.tags = Benelux::Tags.new
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,8 @@
1
+
2
+ #if RUBY_VERSION =~ /1.8/
3
+ class Symbol
4
+ def <=>(other)
5
+ self.to_s <=> other.to_s
6
+ end
7
+ end
8
+ #end
@@ -3,5 +3,5 @@
3
3
  class Thread
4
4
  extend Attic
5
5
  attic :timeline
6
- attic :track
6
+ attic :track_name
7
7
  end
@@ -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
@@ -1,7 +1,7 @@
1
1
 
2
2
  module Benelux
3
3
  class Range
4
- include Benelux::TagHelpers
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 = Benelux::Tags.new
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
@@ -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
- add_keepers names
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 get_keeper(name)
10
- self.send name
13
+ def create_zero_group(name)
14
+ g = Benelux::Stats::Group.new
15
+ g.name = name
16
+ g
11
17
  end
12
- # Each keeper
18
+ # Each group
13
19
  def each(&blk)
14
- @names.each { |name| blk.call(get_keeper(name)) }
20
+ @names.each { |name| blk.call(group(name)) }
15
21
  end
16
- # Each keeper name, keeper
22
+ # Each group name, group
17
23
  def each_pair(&blk)
18
- @names.each { |name| blk.call(name, get_keeper(name)) }
24
+ @names.each { |name| blk.call(name, group(name)) }
19
25
  end
20
- def add_keepers(*args)
26
+ def add_groups(*args)
21
27
  args.flatten.each do |meth|
22
- next if has_keeper? meth
28
+ next if has_group? meth
23
29
  @names << meth
24
30
  self.class.send :attr_reader, meth
25
- instance_variable_set("@#{meth}", Benelux::Stats::Group.new(meth))
31
+ (g = Benelux::Stats::Group.new).name = meth
32
+ instance_variable_set("@#{meth}", g)
26
33
  end
27
34
  end
28
- alias_method :add_keeper, :add_keepers
29
- def has_keeper?(name)
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
- add_keeper name
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
- attr_reader :name
47
- def initialize(name)
48
- @name = name
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
- # tags = Benelux::TagHelpers.normalize tags
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.samples calc
91
- mc.add_tags calc.tags
108
+ mc.merge! calc
109
+ mc.add_tags_quick calc.tags
92
110
  }
93
111
  mc
94
112
  end
95
113
 
96
- def [](tags={})
97
- # tags = Benelux::TagHelpers.normalize tags
98
- g = Benelux::Stats::Group.new @name
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 :filter, :[]
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 < Array
109
- include Benelux::TagHelpers
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
- super(other)
119
- self.recalculate
120
- self
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 recalculate
189
- samples = self.clone
190
- reset
191
- samples.each { |s| sample(s) }
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