benelux 0.3.2 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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