benelux 0.2.0.001 → 0.3.0

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