benelux 0.2.0.001 → 0.3.0

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 CHANGED
@@ -1,4 +1,8 @@
1
1
  BENELUX, CHANGES
2
2
 
3
3
 
4
- #### 0.2.0 (2009-09-??) ###############################
4
+ #### 0.3.0 (2009-09-30) ###############################
5
+
6
+ Initial public release
7
+
8
+
data/README.rdoc CHANGED
@@ -1,10 +1,12 @@
1
- = Benelux v0.1
1
+ = Benelux v0.3 (IN PROGRESS)
2
2
 
3
- <b></b>
3
+ <b>A madhouse of timers for your Ruby codes</b>
4
4
 
5
5
 
6
6
  == Features
7
7
 
8
+ * Create timers for any Ruby method
9
+ * Granular statistics
8
10
 
9
11
 
10
12
  == Installation
@@ -21,6 +23,7 @@ Get it in one of the following ways:
21
23
  * Codes[http://github.com/delano/benelux]
22
24
  * RDocs[http://delano.github.com/benelux]
23
25
 
26
+
24
27
  == Credits
25
28
 
26
29
  * Delano Mandelbaum (http://solutious.com)
@@ -29,6 +32,7 @@ Get it in one of the following ways:
29
32
  == Thanks
30
33
 
31
34
  * Alexis Sellier for fielding my Ruby questions
35
+ * Tara Dougans for the motivational speaches
32
36
 
33
37
 
34
38
  == License
data/benelux.gemspec CHANGED
@@ -1,7 +1,7 @@
1
1
  @spec = Gem::Specification.new do |s|
2
2
  s.name = "benelux"
3
3
  s.rubyforge_project = 'benelux'
4
- s.version = "0.2.0.001"
4
+ s.version = "0.3.0"
5
5
  s.summary = "Benelux: Little freakin' timers for your Ruby codes"
6
6
  s.description = s.summary
7
7
  s.author = "Delano Mandelbaum"
@@ -26,8 +26,16 @@
26
26
  lib/benelux.rb
27
27
  lib/benelux/mark.rb
28
28
  lib/benelux/mixins/thread.rb
29
+ lib/benelux/range.rb
30
+ lib/benelux/stats.rb
31
+ lib/benelux/tags.rb
29
32
  lib/benelux/timeline.rb
30
- tryouts/basic_tryouts.rb
33
+ tryouts/10_stats_tryouts.rb
34
+ tryouts/11_tags_tryouts.rb
35
+ tryouts/20_class_methods_tryouts.rb
36
+ tryouts/30_timeline_tryouts.rb
37
+ tryouts/proofs/alias_performance.rb
38
+ tryouts/proofs/timer_threading.rb
31
39
  )
32
40
 
33
41
 
data/lib/benelux.rb CHANGED
@@ -1,66 +1,110 @@
1
1
  require 'attic'
2
- require 'thread'
3
2
  require 'hexoid'
3
+ require 'thread'
4
4
 
5
5
  module Benelux
6
+ VERSION = "0.3.0"
6
7
  NOTSUPPORTED = [Class, Object, Kernel]
7
- SUFFIX_START = :a.freeze
8
- SUFFIX_END = :z.freeze
9
8
 
10
- require 'benelux/timeline'
9
+ class BeneluxError < RuntimeError; end
10
+ class NotSupported < BeneluxError; end
11
+ class NoTrack < BeneluxError; end
12
+
13
+ require 'benelux/tags'
11
14
  require 'benelux/mark'
15
+ require 'benelux/range'
16
+ require 'benelux/stats'
17
+ require 'benelux/timeline'
12
18
  require 'benelux/mixins/thread'
13
19
 
14
20
  @@timed_methods = {}
15
- @@thread_timelines = []
16
- @@timeline = Benelux::Timeline.new
21
+ @@known_threads = []
22
+ @@timelines = {}
17
23
  @@mutex = Mutex.new
24
+ @@debug = true
18
25
 
19
- class BeneluxError < RuntimeError; end
20
- class NotSupported < BeneluxError; end
26
+ def Benelux.enable_debug; @@debug = true; end
27
+ def Benelux.disable_debug; @@debug = false; end
28
+ def Benelux.debug?; @@debug; end
21
29
 
30
+ # Returns an Array of method names for the current class that
31
+ # are timed by Benelux.
32
+ #
33
+ # This is an instance method for objects which have Benelux
34
+ # modified methods.
22
35
  def benelux_timers
23
36
  Benelux.timed_methods[self.class]
24
37
  end
25
38
 
26
- def Benelux.supported?(klass)
27
- !NOTSUPPORTED.member?(klass)
39
+ def Benelux.timeline(track=nil)
40
+ if track.nil?
41
+ if Benelux.timelines.empty?
42
+ tl = known_threads.collect { |t| t.timeline}
43
+ else
44
+ tl = Benelux.timelines.values
45
+ end
46
+ Benelux.merge_timelines *tl
47
+ else
48
+ Benelux.timelines[track]
49
+ end
28
50
  end
29
51
 
30
- def Benelux.store_thread_reference
31
- return if Benelux.thread_timelines.member? Thread.current
52
+ # Must be run in single-threaded mode (after all track threads
53
+ # have finished).
54
+ #
55
+ def Benelux.update_all_track_timelines
56
+ Benelux.timelines.keys.each { |track| Benelux.update_track_timeline(track) }
57
+ end
58
+
59
+ # Must be run from the master thread in the current track. The master
60
+ # thread is either the first thread in a track or the one which creates
61
+ # additional threads for the track.
62
+ #
63
+ def Benelux.update_track_timeline(track=nil)
64
+ track = Thread.current.track if track.nil?
65
+ threads = Benelux.known_threads.select { |t| t.track == track }
66
+ Benelux.timelines[track] = Benelux.merge_timelines(*threads.collect { |t| t.timeline })
67
+ threads.each { |t| t.timeline.clear }
68
+ Benelux.timelines[track]
69
+ end
70
+
71
+ # If +track+ is specified, this will associate the current
72
+ # thread with that +track+.
73
+ #
74
+ # If +track+ is nil, it returns the timeline for the
75
+ # track associated to the current thread.
76
+ #
77
+ def Benelux.current_track(track=nil)
78
+ if track.nil?
79
+ raise NoTrack if Benelux.timelines[Thread.current.track].nil?
80
+ return Benelux.timelines[Thread.current.track]
81
+ end
82
+ Benelux.store_thread_reference
32
83
  @@mutex.synchronize do
33
- Benelux.thread_timelines << Thread.current
84
+ # QUESTION: Is it okay for multiple threads to write to
85
+ # different elements in the same hash?
86
+ Benelux.timelines[track] ||= Benelux::Timeline.new
87
+ Benelux.add_thread_tags :track => track
88
+ Thread.current.track = track
34
89
  end
35
90
  end
36
91
 
37
- def Benelux.timed_methods
38
- @@timed_methods
39
- end
40
-
41
- def Benelux.thread_timelines
42
- @@thread_timelines
43
- end
44
-
45
- def Benelux.timeline
46
- @@timeline = Benelux.generate_timeline if @@timeline.empty?
47
- @@timeline
48
- end
49
-
50
- def Benelux.generate_timeline
51
- @@mutex.synchronize do
52
- timeline = Benelux::Timeline.new
53
- Benelux.thread_timelines.each { |t| timeline << t.benelux }
54
- timeline.flatten.sort
92
+ # Combine two or more timelines into a new, single Benelux::Timeline.
93
+ #
94
+ def Benelux.merge_timelines(*timelines)
95
+ tl, stats, ranges = Benelux::Timeline.new, Benelux::Stats.new, []
96
+ timelines.each do |t|
97
+ tl += t
55
98
  end
99
+ tl
56
100
  end
57
101
 
58
- def Benelux.thread_timeline(thread_id=nil)
59
- Thread.current.benelux ||= Benelux::Timeline.new
60
- Thread.current.benelux
102
+ def Benelux.thread_timeline
103
+ Thread.current.timeline ||= Benelux::Timeline.new
104
+ Thread.current.timeline
61
105
  end
62
106
 
63
-
107
+ # Make note of the class which included Benelux.
64
108
  def Benelux.included(klass)
65
109
  timed_methods[klass] = [] unless timed_methods.has_key? klass
66
110
  end
@@ -75,7 +119,7 @@ module Benelux
75
119
  prepare_object klass
76
120
  meth_alias = rename_method klass, meth
77
121
  timed_methods[klass] << meth
78
- klass.module_eval generate_timer_str(meth_alias, meth)
122
+ klass.module_eval generate_timer_str(meth_alias, meth), __FILE__, 215
79
123
  end
80
124
 
81
125
  def Benelux.add_tally obj, meth
@@ -85,15 +129,75 @@ module Benelux
85
129
  names.flatten.collect { |n| n.to_s }.join('_')
86
130
  end
87
131
 
88
- private
132
+
89
133
  def Benelux.prepare_object obj
90
134
  obj.extend Attic unless obj.kind_of?(Attic)
91
135
  unless obj.kind_of?(Benelux)
92
- obj.attic :benelux
136
+ obj.attic :timeline
93
137
  obj.send :include, Benelux
94
138
  end
95
139
  end
140
+
141
+ # Benelux keeps track of the threads which have timed
142
+ # objects so it can process the timelines after all is
143
+ # said and done.
144
+ def Benelux.store_thread_reference
145
+ return if Benelux.known_threads.member? Thread.current
146
+ @@mutex.synchronize do
147
+ Thread.current.timeline ||= Benelux::Timeline.new
148
+ Benelux.known_threads << Thread.current
149
+ Benelux.known_threads.uniq!
150
+ end
151
+ end
152
+
153
+ # Thread tags become the default for any new Mark or Range.
154
+ def Benelux.add_thread_tags(args=Benelux::Tags.new)
155
+ Benelux.thread_timeline.add_default_tags args
156
+ end
157
+ def Benelux.add_thread_tag(*args) add_thread_tags *args end
158
+
159
+ def Benelux.remove_thread_tags(*args)
160
+ Benelux.thread_timeline.remove_default_tags *args
161
+ end
162
+ def Benelux.remove_thread_tag(*args) remove_thread_tags *args end
163
+
164
+ def Benelux.tracks
165
+ Benelux.timelines.keys
166
+ end
167
+
168
+ def Benelux.inspect
169
+ str = ["Benelux"]
170
+ str << "threads:" << Benelux.known_threads.inspect
171
+ str << "tracks:" << Benelux.tracks.inspect
172
+ str << "timers:" << Benelux.timed_methods.inspect
173
+ str << "timeline:" << Benelux.timeline.inspect
174
+ str.join $/
175
+ end
176
+
177
+ def Benelux.supported?(klass)
178
+ !NOTSUPPORTED.member?(klass)
179
+ end
180
+
181
+ def Benelux.timed_methods
182
+ @@timed_methods
183
+ end
184
+
185
+ def Benelux.known_threads
186
+ @@known_threads
187
+ end
96
188
 
189
+ def Benelux.timelines
190
+ @@timelines
191
+ end
192
+
193
+ # Rename the method +meth+ in the object +obj+ and return
194
+ # the new alias.
195
+ #
196
+ # e.g.
197
+ #
198
+ # Benelux.renamed(SomeClass, :execute)
199
+ # # => __benelux_execute_2151884308_2165479316
200
+ #
97
201
  def Benelux.rename_method(obj, meth)
98
202
  ## NOTE: This is commented out so we can include
99
203
  ## Benelux definitions before all classes are loaded.
@@ -107,20 +211,29 @@ module Benelux
107
211
  end
108
212
  meth_alias
109
213
  end
110
-
214
+
215
+ # Creates a method definition (for an `eval` that) for a method
216
+ # named +meth+ which times a call to +meth_alias+.
111
217
  def Benelux.generate_timer_str(meth_alias, meth)
112
218
  %Q{
113
219
  def #{meth}(*args, &block)
220
+ call_id = "" << self.object_id.abs.to_s << args.object_id.abs.to_s
114
221
  # We only need to do these things once.
115
- if self.benelux.nil?
116
- self.benelux = Benelux::Timeline.new
222
+ if self.timeline.nil?
223
+ self.timeline = Benelux::Timeline.new
117
224
  Benelux.store_thread_reference
118
225
  end
119
- ref = self.object_id.abs.to_s << args.object_id.abs.to_s
120
- self.benelux.add_mark_open ref, :'#{meth}'
226
+ mark_a = self.timeline.add_mark :'#{meth}_a'
227
+ mark_a.add_tag :call_id => call_id
228
+ tags = mark_a.tags
121
229
  ret = #{meth_alias}(*args, &block)
122
- self.benelux.add_mark_close ref, :'#{meth}'
123
- ret
230
+ rescue => ex
231
+ raise ex
232
+ ensure
233
+ mark_z = self.timeline.add_mark :'#{meth}_z'
234
+ mark_z.tags = tags # In case tags were added between these marks
235
+ range = self.timeline.add_range :'#{meth}', mark_a, mark_z
236
+ range.exception = ex if defined?(ex) && !ex.nil?
124
237
  end
125
238
  }
126
239
  end
@@ -128,3 +241,6 @@ module Benelux
128
241
  end
129
242
 
130
243
 
244
+
245
+
246
+
data/lib/benelux/mark.rb CHANGED
@@ -1,37 +1,40 @@
1
1
  module Benelux
2
2
  class Mark < Time
3
+ include Benelux::TagHelpers
3
4
  attr_accessor :name
4
- attr_accessor :thread_id
5
- attr_accessor :call_id
6
- def self.now(n=nil,c=nil,t=nil)
5
+ def self.now(n=nil)
7
6
  v = super()
8
- v.name, v.call_id, v.thread_id = n, c, t
7
+ v.tags = Benelux::Tags.new
8
+ v.name = n
9
9
  v
10
10
  end
11
+ def track
12
+ @tags[:track]
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
11
22
  def inspect(reftime=nil)
12
- val = reftime.nil? ? self.to_f : (self.to_f - reftime.to_f)
13
- args = [self.class, self.hexoid, self.name, val, thread_id, call_id]
14
- "#<%s:%s name=%s at=%f thread_id=%s call_id=%s>" % args
23
+ val = reftime.nil? ? self : (reftime - self)
24
+ "#<%s:%s at=%f name=%s %s>" % [self.class, hexoid, to_f, name, tags]
15
25
  end
16
26
  def to_s(reftime=nil)
17
- val = reftime.nil? ? self.to_f : (self.to_f - reftime.to_f)
18
- val.to_s
27
+ val = reftime.nil? ? self : (reftime - self)
28
+ val.to_f.to_s
29
+ end
30
+ def distance(time)
31
+ self - time
19
32
  end
20
33
  def ==(other)
21
34
  return false unless other.respond_to? :call_id
22
35
  self.name == other.name &&
23
- self.thread_id == other.thread_id &&
24
- self.call_id == other.call_id &&
36
+ self.tags == other.tags &&
25
37
  self.to_f == self.to_f
26
38
  end
27
- def same_timeline?(other)
28
- return false unless other.respond_to? :thread_id
29
- self.thread_id == other.thread_id
30
- end
31
- def same_call?(other)
32
- return false unless other.respond_to? :call_id
33
- self.thread_id == other.thread_id &&
34
- self.call_id == other.call_id
35
- end
36
39
  end
37
40
  end
@@ -2,5 +2,6 @@
2
2
 
3
3
  class Thread
4
4
  extend Attic
5
- attic :benelux
5
+ attic :timeline
6
+ attic :track
6
7
  end
@@ -0,0 +1,50 @@
1
+
2
+ module Benelux
3
+ class Range
4
+ include Benelux::TagHelpers
5
+
6
+ attr_accessor :name
7
+ attr_accessor :from
8
+ attr_accessor :to
9
+ attr_accessor :exception
10
+ def initialize(name,from,to)
11
+ @name, @from, @to = name, from, to
12
+ @tags = Benelux::Tags.new
13
+ end
14
+ def to_s
15
+ "%s:%.4f" % [name, duration]
16
+ end
17
+ def inspect
18
+ args = [self.class, hexoid, duration, from, to, name, tags]
19
+ "#<%s:%s duration=%0.4f from=%s to=%s name=%s %s>" % args
20
+ end
21
+
22
+ def track
23
+ @from.nil? ? :unknown : @from.track
24
+ end
25
+ def thread_id
26
+ @from.nil? ? :unknown : @from.thread_id
27
+ end
28
+ def call_id
29
+ @from.nil? ? :unknown : @from.call_id
30
+ end
31
+ def successful?
32
+ @exception.nil?
33
+ end
34
+ def failed?
35
+ !successful?
36
+ end
37
+ def duration
38
+ to - from
39
+ end
40
+ def <=>(other)
41
+ from <=> other.from
42
+ end
43
+ def <(other)
44
+ from < other
45
+ end
46
+ def >(other)
47
+ from > other
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,193 @@
1
+
2
+ module Benelux
3
+ class Stats
4
+ attr_reader :names
5
+ def initialize(*names)
6
+ @names = []
7
+ add_keepers names
8
+ end
9
+ def get_keeper(name)
10
+ self.send name
11
+ end
12
+ # Each keeper
13
+ def each(&blk)
14
+ @names.each { |name| blk.call(get_keeper(name)) }
15
+ end
16
+ # Each keeper name, keeper
17
+ def each_pair(&blk)
18
+ @names.each { |name| blk.call(name, get_keeper(name)) }
19
+ end
20
+ def add_keepers(*args)
21
+ args.flatten.each do |meth|
22
+ next if has_keeper? meth
23
+ @names << meth
24
+ self.class.send :attr_reader, meth
25
+ instance_variable_set("@#{meth}", Benelux::Stats::Group.new(meth))
26
+ end
27
+ end
28
+ alias_method :add_keeper, :add_keepers
29
+ def has_keeper?(name)
30
+ @names.member? name
31
+ end
32
+ def +(other)
33
+ if !other.is_a?(Benelux::Stats)
34
+ raise TypeError, "can't convert #{other.class} into Stats"
35
+ end
36
+ other.names.each do |name|
37
+ add_keeper name
38
+ a = self.send(name)
39
+ a += other.send(name)
40
+ a
41
+ end
42
+ self
43
+ end
44
+
45
+ class Group < Array
46
+ attr_reader :name
47
+ def initialize(name)
48
+ @name = name
49
+ end
50
+
51
+ def +(other)
52
+ unless @name == other.name
53
+ raise BeneluxError, "Cannot add #{other.name} to #{@name}"
54
+ end
55
+ other.each do |newcalc|
56
+ calcs = self.select { |calc| calc.tags == newcalc.tags }
57
+ self << newcalc and next if calcs.empty?
58
+ # This should only ever contain one b/c we should
59
+ # not have several calculators with the same tags.
60
+ calcs.each do |calc|
61
+ calc += newcalc
62
+ end
63
+ end
64
+ self
65
+ end
66
+
67
+ def sample(s, tags={})
68
+ raise BeneluxError, "tags must be a Hash" unless tags.kind_of?(Hash)
69
+ calcs = self.select { |c| c.tags == tags }
70
+ if calcs.empty?
71
+ (c = Calculator.new).add_tags tags
72
+ self << c
73
+ calcs = [c]
74
+ end
75
+ calcs.each { |c| c.sample(s) }
76
+ nil
77
+ end
78
+
79
+ def mean() merge.mean end
80
+ def min() merge.min end
81
+ def max() merge.max end
82
+ def sd() merge.sd end
83
+ def n() merge.n end
84
+
85
+ def merge(*tags)
86
+ tags = Benelux::TagHelpers.normalize tags
87
+ mc = Calculator.new
88
+ all = tags.empty? ? self : self.filter(tags)
89
+ all.each { |calc|
90
+ mc.samples calc
91
+ mc.add_tags calc.tags
92
+ }
93
+ mc
94
+ end
95
+
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
102
+ end
103
+ alias_method :filter, :[]
104
+
105
+ end
106
+
107
+ # Based on Mongrel::Stats, Copyright (c) 2005 Zed A. Shaw
108
+ class Calculator < Array
109
+ include Benelux::TagHelpers
110
+
111
+ attr_reader :sum, :sumsq, :n, :min, :max
112
+
113
+ def initialize
114
+ reset
115
+ end
116
+
117
+ def +(other)
118
+ super(other)
119
+ self.recalculate
120
+ self
121
+ end
122
+
123
+ # Resets the internal counters so you can start sampling again.
124
+ def reset
125
+ self.clear
126
+ @n, @sum, @sumsq = 0.0, 0.0, 0.0
127
+ @last_time = 0.0
128
+ @min, @max = 0.0, 0.0
129
+ end
130
+
131
+ def samples(*args)
132
+ args.flatten.each { |s| sample(s) }
133
+ end
134
+
135
+ # Adds a sampling to the calculations.
136
+ def sample(s)
137
+ self << s
138
+ update s
139
+ end
140
+
141
+ def update(s)
142
+ @sum += s
143
+ @sumsq += s * s
144
+ if @n == 0
145
+ @min = @max = s
146
+ else
147
+ @min = s if @min > s
148
+ @max = s if @max < s
149
+ end
150
+ @n+=1
151
+ end
152
+
153
+ # Dump this Stats object with an optional additional message.
154
+ def dump(msg = "", out=STDERR)
155
+ out.puts "#{msg}: #{self.inspect}"
156
+ end
157
+
158
+ # Returns a common display (used by dump)
159
+ def inspect
160
+ v = [mean, @n, @sum, @sumsq, sd, @min, @max]
161
+ t = %q'%8d(N) %10.4f(SUM) %8.4f(SUMSQ) %8.4f(SD) %8.4f(MIN) %8.4f(MAX)'
162
+ ('%0.4f: ' << t) % v
163
+ end
164
+
165
+ def to_s; mean.to_s; end
166
+ def to_f; mean.to_f; end
167
+ def to_i; mean.to_i; end
168
+
169
+ # Calculates and returns the mean for the data passed so far.
170
+ def mean; return 0.0 unless @n > 0; @sum / @n; end
171
+
172
+ # Calculates the standard deviation of the data so far.
173
+ def sd
174
+ return 0.0 if @n <= 1
175
+ # (sqrt( ((s).sumsq - ( (s).sum * (s).sum / (s).n)) / ((s).n-1) ))
176
+ begin
177
+ return Math.sqrt( (@sumsq - ( @sum * @sum / @n)) / (@n-1) )
178
+ rescue Errno::EDOM
179
+ return 0.0
180
+ end
181
+ end
182
+
183
+ def recalculate
184
+ samples = self.clone
185
+ reset
186
+ samples.each { |s| sample(s) }
187
+ end
188
+
189
+ end
190
+
191
+
192
+ end
193
+ end
@@ -0,0 +1,110 @@
1
+
2
+
3
+ module Benelux
4
+
5
+ # Helper methods for objects with a @tags instance var
6
+ #
7
+ # e.g.
8
+ #
9
+ # class Something
10
+ # include Benelux::TagHelpers
11
+ # end
12
+ #
13
+ module TagHelpers
14
+ attr_accessor :tags
15
+ def add_tags(tags)
16
+ @tags ||= Benelux::Tags.new
17
+ @tags.merge! tags
18
+ end
19
+ alias_method :add_tag, :add_tags
20
+ def remove_tags(*tags)
21
+ tags.flatten!
22
+ @tags ||= Benelux::Tags.new
23
+ @tags.delete_if { |n,v| tags.member?(n) }
24
+ end
25
+ alias_method :remove_tag, :remove_tags
26
+ def tag_values(*tags)
27
+ tags.flatten!
28
+ @tags ||= Benelux::Tags.new
29
+ ret = @tags.collect { |n,v|
30
+ p [:n, v]
31
+ v if tags.empty? || tags.member?(n)
32
+ }.compact
33
+ ret
34
+ end
35
+ def self.normalize(tags={})
36
+ tags = tags.first if tags.kind_of?(Array) && tags.first.kind_of?(Hash)
37
+ tags = [tags].flatten unless tags.kind_of?(Hash)
38
+ tags
39
+ end
40
+ end
41
+
42
+ # An example of filtering an Array of tagged objects based
43
+ # on a provided Hash of tags or Array of tag values. +obj+
44
+ # in this case would be an object that includes TagHelpers.
45
+ #
46
+ # class Something
47
+ # def [](tags={})
48
+ # tags = [tags].flatten unless tags.is_a?(Hash)
49
+ # self.select do |obj|
50
+ # obj.tags >= tags
51
+ # end
52
+ # end
53
+ # end
54
+ #
55
+ class Tags < ::Hash
56
+
57
+ def to_s
58
+ tagstr = []
59
+ self.each_pair do |n,v|
60
+ tagstr << "%s=%s" % [n,v]
61
+ end
62
+ tagstr.join ' '
63
+ end
64
+
65
+ def inspect
66
+ to_s
67
+ end
68
+
69
+ def ==(*other)
70
+ other = Benelux::TagHelpers.normalize other
71
+ if other.is_a?(Array)
72
+ (self.values & other).sort == other.sort
73
+ else
74
+ super(other)
75
+ end
76
+ end
77
+
78
+ # Comparison between other Hash and Array objects.
79
+ #
80
+ # e.g.
81
+ #
82
+ # a = {:a => 1, :b => 2}
83
+ # a > {:a => 1, :b => 2, :c => 3} # => false
84
+ # a > {:a => 1} # => true
85
+ # a < {:a => 1, :b => 2, :c => 3} # => true
86
+ # a >= [2, 1] # => true
87
+ # a > [2, 1] # => false
88
+ #
89
+ def <=>(*other)
90
+ other = Benelux::TagHelpers.normalize other
91
+ return 0 if self == other
92
+ if other.is_a?(Array)
93
+ return -1 unless (self.values & other).size >= other.size
94
+ else
95
+ return -1 unless (self.keys & other.keys).size >= other.keys.size
96
+ other.each_pair { |n,v|
97
+ return -1 unless self.has_key?(n) && self[n] == v
98
+ }
99
+ end
100
+ 1
101
+ end
102
+
103
+ def >(other) (self <=> other) > 0 end
104
+ def <(other) (self <=> other) < 0 end
105
+
106
+ def <=(other) (self <=> other) <= 0 end
107
+ def >=(other) (self <=> other) >= 0 end
108
+
109
+ end
110
+ end
@@ -1,10 +1,49 @@
1
1
 
2
2
  module Benelux
3
3
  #
4
- # |------+----+--+----+----|
5
- # |
6
- # 0.02
4
+ # |------+----+--+----+----|
5
+ # |
6
+ # 0.02
7
+ #
8
+ # Usage examples::
9
+ #
10
+ # Benelux.timeline['9dbd521de4dfd6257135649d78a9c0aa2dd58cfe'].each do |mark|
11
+ # p [mark.track, mark.name, mark.tags[:usecase], mark.tags[:call_id]]
12
+ # end
13
+ #
14
+ # Benelux.timeline.ranges(:do_request).each do |range|
15
+ # puts "Client%s: %s: %s: %f" % [range.track, range.thread_id, range.name, range.duration]
16
+ # end
17
+ #
18
+ # regions = Benelux.timeline(track_id).regions(:execute)
19
+ #
7
20
  class Timeline < Array
21
+ attr_accessor :ranges
22
+ attr_accessor :stats
23
+ attr_accessor :default_tags
24
+ attr_reader :caller
25
+ def initialize(*args)
26
+ @caller = Kernel.caller
27
+ @ranges, @default_tags = [], Benelux::Tags.new
28
+ @stats = Benelux::Stats.new
29
+ add_default_tag :thread_id => Thread.current.object_id.abs
30
+ super
31
+ end
32
+ def add_default_tags(tags=Benelux::Tags.new)
33
+ @default_tags.merge! tags
34
+ end
35
+ alias_method :add_default_tag, :add_default_tags
36
+ def remove_default_tags(*tags)
37
+ @default_tags.delete_if { |n,v| tags.member?(n) }
38
+ end
39
+ alias_method :add_default_tag, :add_default_tags
40
+ def track
41
+ @default_tags[:track]
42
+ end
43
+
44
+ def duration
45
+ self.last - self.first
46
+ end
8
47
 
9
48
  def each(*args, &blk)
10
49
  if args.empty?
@@ -14,58 +53,105 @@ module Benelux
14
53
  end
15
54
  end
16
55
 
17
- ##def between(*names)
18
- ## name_s, name_e = *names.collect { |n| Benelux.name n }
19
- ## time_s, time_e = at(name_s), at(name_e)
20
- ## time_e.first.to_f - time_s.first.to_f
21
- ##end
22
- ##def duration(*names)
23
- ## name = Benelux.name *names
24
- ## name_s, name_e = "#{name}_a", "#{name}_z"
25
- ## between(name_s, name_e)
26
- ##end
27
-
28
- #
29
- # obj.region(:execute) =>
30
- # [[:execute_a, :execute_z], [:execute_a, :execute_z]]
31
- #
32
- def regions(*names)
33
-
34
- end
35
-
36
56
  #
37
57
  # obj.marks(:execute_a, :execute_z, :do_request_a) =>
38
58
  # [:execute_a, :do_request_a, :do_request_a, :execute_z]
39
59
  #
40
60
  def marks(*names)
61
+ return self if names.empty?
41
62
  names = names.flatten.collect { |n| n.to_s }
42
- self.benelux.select do |mark|
63
+ self.select do |mark|
43
64
  names.member? mark.name.to_s
44
65
  end
45
66
  end
46
67
 
47
- def duration(name)
48
- name_s = Benelux.name name, SUFFIX_START
49
- name_e = Benelux.name name, SUFFIX_END
68
+ def [](tags={})
69
+ tags = Benelux::TagHelpers.normalize tags
70
+ marks = self.select do |mark|
71
+ mark.tags >= tags
72
+ end
73
+ tl = Benelux::Timeline.new marks
74
+ tl.ranges = @ranges.select do |region|
75
+ region.tags >= tags
76
+ end
77
+ stats = Benelux::Stats.new
78
+ @stats.each do |stat|
79
+ next unless stat.tags >= tags
80
+ stats += stat
81
+ end
82
+ tl.stats = stats
83
+ tl
84
+ end
85
+
86
+ #
87
+ # obj.ranges(:do_request) =>
88
+ # [[:do_request_a, :do_request_z], [:do_request_a, ...]]
89
+ #
90
+ def ranges(name=nil, tags=Benelux::Tags.new)
91
+ return @ranges if name.nil?
92
+ @ranges.select do |range|
93
+ ret = name.to_s == range.name.to_s &&
94
+ (tags.nil? || range.tags >= tags)
95
+ ret
96
+ end
97
+ end
98
+
99
+ #
100
+ # obj.ranges(:do_request) =>
101
+ # [[:do_request_a, :get_body, :do_request_z], [:do_request_a, ...]]
102
+ #
103
+ def regions(name=nil, tags=Benelux::Tags.new)
104
+ return self if name.nil?
105
+ self.ranges(name, tags).collect do |base_range|
106
+ marks = self.sort.select do |mark|
107
+ mark >= base_range.from &&
108
+ mark <= base_range.to &&
109
+ mark.tags >= base_range.tags
110
+ end
111
+ ranges = self.ranges.select do |range|
112
+ range.from >= base_range.from &&
113
+ range.to <= base_range.to &&
114
+ range.tags >= base_range.tags
115
+ end
116
+ tl = Benelux::Timeline.new(marks)
117
+ tl.ranges = ranges.sort
118
+ tl
119
+ end
120
+ end
121
+
122
+ def clear
123
+ @ranges.clear
124
+ super
50
125
  end
51
126
 
52
- def add_mark(call_id, name)
53
- thread_id = Thread.current.object_id.abs
54
- mark = Benelux::Mark.now(name, call_id, thread_id)
127
+ def add_mark(name)
128
+ mark = Benelux::Mark.now(name)
129
+ mark.add_tags Benelux.thread_timeline.default_tags
130
+ mark.add_tags self.default_tags
55
131
  Benelux.thread_timeline << mark
56
132
  self << mark
133
+ mark
57
134
  end
58
-
59
- def add_mark_open(call_id, name)
60
- add_mark call_id, Benelux.name(name, SUFFIX_START)
61
- end
62
-
63
- def add_mark_close(call_id, name)
64
- add_mark call_id, Benelux.name(name, SUFFIX_END)
135
+
136
+ def add_range(name, from, to)
137
+ range = Benelux::Range.new(name, from, to)
138
+ range.add_tags Benelux.thread_timeline.default_tags
139
+ range.add_tags self.default_tags
140
+ @stats.add_keeper(name)
141
+ @stats.send(name).sample(range.duration, range.tags)
142
+ @ranges << range
143
+ Benelux.thread_timeline.ranges << range
144
+ Benelux.thread_timeline.stats.add_keeper(name)
145
+ Benelux.thread_timeline.stats.send(name).sample(range.duration, range.tags)
146
+ range
65
147
  end
66
-
148
+
67
149
  def to_line
68
150
  marks = self.sort
151
+ end
152
+
153
+ def to_line2
154
+ marks = self.sort
69
155
  str, prev = [], marks.first
70
156
  marks.each do |mark|
71
157
  str << "%s(%s):%.4f" % [mark.name, mark.thread_id, mark.to_s(prev)]
@@ -75,7 +161,10 @@ module Benelux
75
161
  end
76
162
  def +(other)
77
163
  self << other
78
- self.flatten
164
+ self.ranges += other.ranges
165
+ self.stats += other.stats
166
+ self.flatten!
167
+ self
79
168
  end
80
169
  # Needs to compare thread id and call id.
81
170
  #def <=>(other)
@@ -0,0 +1,49 @@
1
+
2
+ group "Benelux"
3
+
4
+ library :benelux, 'lib'
5
+ tryouts "Calculator" do
6
+
7
+ dream :class, Benelux::Stats::Calculator
8
+ dream :n, 10
9
+ drill "can keep stats" do
10
+ keeper = Benelux::Stats::Calculator.new
11
+ 10.times { keeper.sample(rand) }
12
+ keeper
13
+ end
14
+
15
+ end
16
+
17
+ tryouts "Stats" do
18
+ set :stat_names, [:execute, :request, :first_byte]
19
+
20
+ dream stat_names
21
+ drill "knows stats names" do
22
+ stats = Benelux::Stats.new(stat_names)
23
+ stats.names
24
+ end
25
+
26
+ drill "can keep multiple stats", true do
27
+ stats = Benelux::Stats.new(stat_names)
28
+ stats.execute.sample(rand)
29
+ stats.request.sample(rand*-1)
30
+ stats.execute != stats.request
31
+ end
32
+
33
+ dream [true, true, true]
34
+ drill "can keep stats with tags" do
35
+ stats = Benelux::Stats.new(stat_names)
36
+ 3.times { |i|
37
+ stats.execute.sample(rand, :usecase => '11')
38
+ stats.execute.sample(rand, :usecase => '11', :request => '22')
39
+ stats.execute.sample(rand, :request => '22')
40
+ }
41
+ stash :execute_stats, stats.execute
42
+ [
43
+ stats.execute['11'] == stats.execute[:usecase => '11'],
44
+ stats.execute['22'] == stats.execute[:request => '22'],
45
+ stats.execute['22','11'] == stats.execute[:usecase => '11', :request => '22']
46
+ ]
47
+ end
48
+
49
+ end
@@ -0,0 +1,60 @@
1
+
2
+ group "Benelux"
3
+
4
+ library :benelux, 'lib'
5
+ tryouts "Tags" do
6
+ set :base, Benelux::Tags[:a => 1, :b => 2]
7
+
8
+ drill "Can equal a Hash with the same keys/values", true do
9
+ base == {:a => 1, :b => 2}
10
+ end
11
+
12
+ drill "Implements a comparison operator", true do
13
+ base.respond_to? :'<=>'
14
+ end
15
+
16
+ drill "Comparison operator returns 0 for same values", 0 do
17
+ base <=> {:a => 1, :b => 2}
18
+ end
19
+
20
+ drill "Comparison operator returns 1 when it's a superset of other", 1 do
21
+ base <=> {:a => 1}
22
+ end
23
+
24
+ drill "Comparison operator returns -1 when it's a subset of other", -1 do
25
+ base <=> {:a => 1, :b => 2, :c => 3}
26
+ end
27
+
28
+ drill "> returns false when compared to a hash with more key value pairs", false do
29
+ base > {:a => 1, :b => 2, :c => 3}
30
+ end
31
+
32
+ drill "> returns true when compared to a hash with fewer key value pairs", true do
33
+ base > {:b => 2}
34
+ end
35
+
36
+ drill "< returns true when compared to a hash with more key value pairs", true do
37
+ base < {:a => 1, :b => 2, :c => 3}
38
+ end
39
+
40
+ drill "< returns false when compared to a hash with fewer key value pairs", false do
41
+ base < {:b => 2}
42
+ end
43
+
44
+ drill "< returns false when compared to a hash with same values", false do
45
+ base < {:a => 1, :b => 2}
46
+ end
47
+
48
+ drill "<= returns true when compared to a hash with same values", true do
49
+ base <= {:b => 2, :a => 1}
50
+ end
51
+
52
+ drill "< returns false when compared to an array with same values", false do
53
+ base < [1, 2]
54
+ end
55
+
56
+ drill "<= returns true when compared to an array with same values", true do
57
+ base <= [2, 1]
58
+ end
59
+
60
+ end
@@ -13,13 +13,13 @@ tryouts "Basics" do
13
13
 
14
14
  drill "Add timers to existing objects", true do
15
15
  Benelux.add_timer Sleeper, :do_something
16
- Sleeper.new.respond_to? :benelux
16
+ Sleeper.new.respond_to? :timeline
17
17
  end
18
18
 
19
19
  dream :class, Hash
20
20
  dream { Hash[ Sleeper => [:do_something] ] }
21
21
  drill "Benelux keeps track of timed objects" do
22
- Benelux.timed_objects
22
+ Benelux.timed_methods
23
23
  end
24
24
 
25
25
  dream [:do_something]
@@ -32,7 +32,7 @@ tryouts "Basics" do
32
32
  drill "Creates a timeline" do
33
33
  sleeper = Sleeper.new
34
34
  5.times { sleeper.do_something }
35
- sleeper.benelux
35
+ sleeper.timeline
36
36
  end
37
37
 
38
38
  dream :size, 4
@@ -41,7 +41,7 @@ tryouts "Basics" do
41
41
  Thread.new do
42
42
  2.times { sleeper.do_something }
43
43
  end.join
44
- sleeper.benelux
44
+ sleeper.timeline
45
45
  end
46
46
 
47
47
  dream :class, Benelux::Timeline
@@ -0,0 +1,25 @@
1
+
2
+ group "Benelux"
3
+
4
+ library :benelux, 'lib'
5
+ tryouts "Timelines" do
6
+ set :tl, Benelux::Timeline.new
7
+
8
+ dream :class, Benelux::Timeline
9
+ dream :size, 3
10
+ drill "create timeline with marks" do
11
+ tl.add_default_tags :a => :frog
12
+ tl.add_mark(:one) and sleep rand
13
+ tl.add_default_tags :b => :rest
14
+ tl.add_mark(:two) and sleep rand
15
+ tl.add_default_tags :c => :tilt
16
+ tl.add_mark(:three) and sleep rand
17
+ tl.marks
18
+ end
19
+
20
+ dream :size, 2
21
+ drill "select marks based on tags" do
22
+ tl[:frog][:b => :rest]
23
+ end
24
+
25
+ end
@@ -0,0 +1,33 @@
1
+
2
+
3
+ tryouts "Alias method speed", :benchmark do
4
+ set :runcount, 1_000
5
+
6
+ setup do
7
+ module A;
8
+ extend self
9
+ def meth1; end
10
+ def meth2; end
11
+ alias_method :meth2_orig, :meth2
12
+ def meth2; end
13
+ def meth2_with_call; meth2_orig; end
14
+ end
15
+ end
16
+
17
+ [10, 100].each do |mult|
18
+ count = runcount * mult
19
+
20
+ drill "Natural method (#{count})", count do
21
+ A.meth1
22
+ end
23
+
24
+ drill "Aliased method (#{count})", count do
25
+ A.meth2
26
+ end
27
+
28
+ drill "Aliased method w/ call (#{count})", count do
29
+ A.meth2_with_call
30
+ end
31
+ end
32
+
33
+ end
@@ -0,0 +1,30 @@
1
+ group "Benelux Concept Proofs"
2
+
3
+ xtryouts "Time benchmarks", :benchmark do
4
+ set :runcount, 100000
5
+
6
+ drill "Create Array", 1 do
7
+ @@timers = []
8
+ end
9
+
10
+ drill "Time.now overhead", 5 do
11
+ runcount.times { Time.now.to_f }
12
+ end
13
+
14
+ drill "[] << Time.now overhead", 5 do
15
+ runcount.times { @@timers << Time.now.to_f }
16
+ end
17
+
18
+ end
19
+
20
+ require 'date'
21
+ tryouts "Time proofs", :api do
22
+ set :runcount, 100000
23
+
24
+ drill "All calls to Time.now.to_f are unique (will fail)", 0 do
25
+ timers = []
26
+ runcount.times { timers << Time.now.to_f; sleep 0.00000001 }
27
+ timers.size - timers.uniq.size
28
+ end
29
+
30
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: benelux
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0.001
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Delano Mandelbaum
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-09-21 00:00:00 -04:00
12
+ date: 2009-10-01 00:00:00 -04:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -41,8 +41,16 @@ files:
41
41
  - lib/benelux.rb
42
42
  - lib/benelux/mark.rb
43
43
  - lib/benelux/mixins/thread.rb
44
+ - lib/benelux/range.rb
45
+ - lib/benelux/stats.rb
46
+ - lib/benelux/tags.rb
44
47
  - lib/benelux/timeline.rb
45
- - tryouts/basic_tryouts.rb
48
+ - tryouts/10_stats_tryouts.rb
49
+ - tryouts/11_tags_tryouts.rb
50
+ - tryouts/20_class_methods_tryouts.rb
51
+ - tryouts/30_timeline_tryouts.rb
52
+ - tryouts/proofs/alias_performance.rb
53
+ - tryouts/proofs/timer_threading.rb
46
54
  has_rdoc: true
47
55
  homepage: http://github.com/delano/benelux
48
56
  licenses: []