hitimes 1.0.4-x86-mingw32

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.
Files changed (46) hide show
  1. data/HISTORY +60 -0
  2. data/LICENSE +13 -0
  3. data/README +134 -0
  4. data/Rakefile +66 -0
  5. data/examples/benchmarks.rb +113 -0
  6. data/examples/stats.rb +31 -0
  7. data/ext/hitimes/extconf.rb +17 -0
  8. data/ext/hitimes/hitimes_ext.c +21 -0
  9. data/ext/hitimes/hitimes_instant_clock_gettime.c +28 -0
  10. data/ext/hitimes/hitimes_instant_osx.c +16 -0
  11. data/ext/hitimes/hitimes_instant_windows.c +27 -0
  12. data/ext/hitimes/hitimes_interval.c +362 -0
  13. data/ext/hitimes/hitimes_interval.h +73 -0
  14. data/ext/hitimes/hitimes_stats.c +269 -0
  15. data/ext/hitimes/hitimes_stats.h +30 -0
  16. data/gemspec.rb +60 -0
  17. data/lib/hitimes.rb +31 -0
  18. data/lib/hitimes/1.8/hitimes_ext.so +0 -0
  19. data/lib/hitimes/1.9/hitimes_ext.so +0 -0
  20. data/lib/hitimes/metric.rb +112 -0
  21. data/lib/hitimes/mutexed_stats.rb +28 -0
  22. data/lib/hitimes/paths.rb +53 -0
  23. data/lib/hitimes/stats.rb +54 -0
  24. data/lib/hitimes/timed_metric.rb +177 -0
  25. data/lib/hitimes/timed_value_metric.rb +235 -0
  26. data/lib/hitimes/value_metric.rb +72 -0
  27. data/lib/hitimes/version.rb +57 -0
  28. data/spec/interval_spec.rb +133 -0
  29. data/spec/metric_spec.rb +30 -0
  30. data/spec/mutex_stats_spec.rb +34 -0
  31. data/spec/paths_spec.rb +13 -0
  32. data/spec/spec_helper.rb +5 -0
  33. data/spec/stats_spec.rb +100 -0
  34. data/spec/timed_metric_spec.rb +155 -0
  35. data/spec/timed_value_metric_spec.rb +172 -0
  36. data/spec/value_metric_spec.rb +110 -0
  37. data/spec/version_spec.rb +33 -0
  38. data/tasks/announce.rake +42 -0
  39. data/tasks/config.rb +108 -0
  40. data/tasks/distribution.rake +77 -0
  41. data/tasks/documentation.rake +32 -0
  42. data/tasks/extension.rake +92 -0
  43. data/tasks/rspec.rake +31 -0
  44. data/tasks/rubyforge.rake +55 -0
  45. data/tasks/utils.rb +80 -0
  46. metadata +150 -0
@@ -0,0 +1,28 @@
1
+ require 'hitimes'
2
+ require 'thread'
3
+
4
+ module Hitimes
5
+ #
6
+ # MutexedStats is the start of a threadsafe Stats class. Currently, on MRI
7
+ # Ruby the Stats object is already threadsafe, so there is no need to use
8
+ # MutexedStats.
9
+ #
10
+ class MutexedStats < Stats
11
+ def initialize
12
+ @mutex = Mutex.new
13
+ end
14
+
15
+ # call-seq:
16
+ # mutex_stat.update( val ) -> nil
17
+ #
18
+ # Update the running stats with the new value in a threadsafe manner.
19
+ #
20
+ def update( value )
21
+ @mutex.synchronize do
22
+ super( value )
23
+ end
24
+ end
25
+ end
26
+ end
27
+
28
+
@@ -0,0 +1,53 @@
1
+ #--
2
+ # Copyright (c) 2008 Jeremy Hinegardner
3
+ # All rights reserved. See LICENSE and/or COPYING for details.
4
+ #++
5
+ #
6
+ module Hitimes
7
+ #
8
+ # Access to various paths inside the project programatically
9
+ #
10
+ module Paths
11
+ #
12
+ # :call-seq:
13
+ # Hitimes::Paths.root_dir -> String
14
+ #
15
+ # Returns The full expanded path of the parent directory of +lib+
16
+ # going up the path from the current file. A trailing File::SEPARATOR
17
+ # is guaranteed.
18
+ #
19
+ def self.root_dir
20
+ @root_dir ||=(
21
+ path_parts = ::File.expand_path(__FILE__).split(::File::SEPARATOR)
22
+ lib_index = path_parts.rindex("lib")
23
+ @root_dir = path_parts[0...lib_index].join(::File::SEPARATOR) + ::File::SEPARATOR
24
+ )
25
+ end
26
+
27
+ #
28
+ # :call-seq:
29
+ # Hitimes::Paths.lib_path( *args ) -> String
30
+ #
31
+ # Returns The full expanded path of the +lib+ directory below
32
+ # _root_dir_. All parameters passed in are joined onto the
33
+ # result. A trailing File::SEPARATOR is guaranteed if
34
+ # _args_ are *not* present.
35
+ #
36
+ def self.lib_path(*args)
37
+ self.sub_path("lib", *args)
38
+ end
39
+
40
+ #
41
+ # :call-seq:
42
+ # Hitimes::Paths.sub_path( sub, *args ) -> String
43
+ #
44
+ # Returns the full expanded path of the +sub+ directory below _root_dir. All
45
+ # _arg_ parameters passed in are joined onto the result. A trailing
46
+ # File::SEPARATOR is guaranteed if _args_ are *not* present.
47
+ #
48
+ def self.sub_path(sub,*args)
49
+ sp = ::File.join(root_dir, sub) + File::SEPARATOR
50
+ sp = ::File.join(sp, *args) if args
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,54 @@
1
+ require 'hitimes'
2
+ require 'stringio'
3
+ module Hitimes
4
+ class Stats
5
+ # A list of the available stats
6
+ STATS = %w[ count max mean min rate stddev sum sumsq ]
7
+
8
+ #
9
+ # call-seq:
10
+ # stat.to_hash -> Hash
11
+ # stat.to_hash( %w[ count max mean ]) -> Hash
12
+ #
13
+ # return a hash of the stats. By default this returns a hash of all stats
14
+ # but passing in an array of items will limit the stats returned to only
15
+ # those in the Array.
16
+ #
17
+ # If passed in an empty array or nil to to_hash then STATS is assumed to be
18
+ # the list of stats to return in the hash.
19
+ #
20
+ def to_hash( *args )
21
+ h = {}
22
+ args = [ args ].flatten
23
+ args = STATS if args.empty?
24
+ args.each do |meth|
25
+ h[meth] = self.send( meth )
26
+ end
27
+ return h
28
+ end
29
+
30
+ #
31
+ # call-seq:
32
+ # stat.to_json -> String
33
+ # stat.to_json( *args ) -> String
34
+ #
35
+ # return a json string of the stats. By default this returns a json string
36
+ # of all the stats. If an array of items is passed in, those that match the
37
+ # known stats will be all that is included in the json output.
38
+ #
39
+ def to_json( *args )
40
+ h = to_hash( *args )
41
+ a = []
42
+ s = StringIO.new
43
+
44
+ s.print "{ "
45
+ h.each_pair do |k,v|
46
+ a << "\"#{k}\": #{v}"
47
+ end
48
+ s.print a.join(", ")
49
+ s.print "}"
50
+ return s.string
51
+ end
52
+
53
+ end
54
+ end
@@ -0,0 +1,177 @@
1
+ #--
2
+ # Copyright (c) 2008, 2009 Jeremy Hinegardner
3
+ # All rights reserved. See LICENSE and/or COPYING for details.
4
+ #++
5
+
6
+ require 'hitimes'
7
+ require 'forwardable'
8
+ module Hitimes
9
+ #
10
+ # A TimedMetric holds the metrics on how long it takes to do something. For
11
+ # example, measuring how long a method takes to operate.
12
+ #
13
+ # tm = TimedMetric.new( 'my-method' )
14
+ #
15
+ # 200.times do
16
+ # my_method_result = tm.measure do
17
+ # my_method( ... )
18
+ # end
19
+ # end
20
+ #
21
+ # puts "#{ tm.name } operated at a rate of #{ tm.rate } calls per second"
22
+ #
23
+ # Since TimedMetric is a child class of Metric make sure to look at the
24
+ # Metric API also.
25
+ #
26
+ # A TimedMetric measures the execution time of an option with the Interval
27
+ # class.
28
+ #
29
+ # A TimedMetric contains a Stats object, therefore TimedMetric has +count+, +max+,
30
+ # +mean+, +min+, +rate+, +stddev+, +sum+, +sumsq+ methods that delegate to that Stats
31
+ # object for convenience.
32
+ #
33
+ #
34
+ class TimedMetric < Metric
35
+ # holds all the statistics
36
+ attr_reader :stats
37
+
38
+ class << TimedMetric
39
+ #
40
+ # :call-seq:
41
+ # TimedMetric.now -> TimedMetric
42
+ #
43
+ # Return a TimedMetric that has been started
44
+ #
45
+ def now( name, additional_data = {} )
46
+ t = TimedMetric.new( name, additional_data )
47
+ t.start
48
+ return t
49
+ end
50
+ end
51
+
52
+ #
53
+ # :call-seq:
54
+ # TimedMetric.new( 'name') -> TimedMetric
55
+ # TimedMetric.new( 'name', 'other' => 'data') -> TimedMetric
56
+ #
57
+ # Create a new TimedMetric giving it a name and additional data.
58
+ # +additional_data+ may be anything that follows the +to_hash+ protocol
59
+ #
60
+ def initialize( name, additional_data = {} )
61
+ super( name, additional_data )
62
+ @stats = Stats.new
63
+ @current_interval = Interval.new
64
+ end
65
+
66
+ #
67
+ # :call-seq:
68
+ # timed_metric.running? -> true or false
69
+ #
70
+ # return whether or not the timer is currently running.
71
+ #
72
+ def running?
73
+ @current_interval.running?
74
+ end
75
+
76
+ #
77
+ # :call-seq:
78
+ # timed_metric.start -> nil
79
+ #
80
+ # Start the current metric, if the current metric is already started, then
81
+ # this is a noop.
82
+ #
83
+ def start
84
+ if not @current_interval.running? then
85
+ @current_interval.start
86
+ @sampling_start_time ||= self.utc_microseconds()
87
+ @sampling_start_interval ||= Interval.now
88
+ end
89
+ nil
90
+ end
91
+
92
+ #
93
+ # :call-seq:
94
+ # timed_metric.stop -> Float or nil
95
+ #
96
+ # Stop the current metric. This updates the stats and removes the current
97
+ # interval. If the timer was stopped then the duration of the last Interval
98
+ # is returned. If the timer was already stopped then false is returned and
99
+ # no stats are updated.
100
+ #
101
+ def stop
102
+ if @current_interval.running? then
103
+ d = @current_interval.stop
104
+ @stats.update( d )
105
+ @current_interval = Interval.new
106
+
107
+ # update the length of time we have been sampling
108
+ @sampling_delta = @sampling_start_interval.duration_so_far
109
+
110
+ return d
111
+ end
112
+ return false
113
+ end
114
+
115
+ #
116
+ # :call-seq:
117
+ # timed_metric.measure { ... } -> Object
118
+ #
119
+ # Measure the execution of a block and add those stats to the running stats.
120
+ # The return value is the return value of the block
121
+ #
122
+ def measure( &block )
123
+ return_value = nil
124
+ begin
125
+ start
126
+ return_value = yield
127
+ ensure
128
+ stop
129
+ end
130
+ return return_value
131
+ end
132
+
133
+ #
134
+ # :call-seq:
135
+ # timed_metric.split -> Float
136
+ #
137
+ # Split the current TimedMetric. Essentially, mark a split time. This means
138
+ # stop the current interval and create a new interval, but make sure
139
+ # that the new interval lines up exactly, timewise, behind the previous
140
+ # interval.
141
+ #
142
+ # If the timer is running, then split returns the duration of the previous
143
+ # interval, i.e. the split-time. If the timer is not running, nothing
144
+ # happens and false is returned.
145
+ #
146
+ def split
147
+ if @current_interval.running? then
148
+ next_interval = @current_interval.split
149
+ d = @current_interval.duration
150
+ @stats.update( d )
151
+ @current_interval = next_interval
152
+ return d
153
+ end
154
+ return false
155
+ end
156
+
157
+ #
158
+ # :call-seq:
159
+ # metric.to_hash -> Hash
160
+ #
161
+ # Convert the metric to a hash
162
+ #
163
+ def to_hash
164
+ h = super
165
+ Stats::STATS.each do |s|
166
+ h[s] = self.send( s )
167
+ end
168
+ return h
169
+ end
170
+
171
+
172
+ # forward appropriate calls directly to the stats object
173
+ extend Forwardable
174
+ def_delegators :@stats, :count, :sum, :max, :mean, :min, :rate, :stddev, :sum, :sumsq
175
+ alias :duration :sum
176
+ end
177
+ end
@@ -0,0 +1,235 @@
1
+ #--
2
+ # Copyright (c) 2008, 2009 Jeremy Hinegardner
3
+ # All rights reserved. See LICENSE and/or COPYING for details.
4
+ #++
5
+
6
+ require 'hitimes'
7
+
8
+ module Hitimes
9
+ #
10
+ # A TimedValueMetric holds the metrics on how long it takes to do a batch of something.
11
+ # something. For measuring how long a method takes to operate on N items.
12
+ #
13
+ # tm = TimedValueMetric.new( 'my-batch-method' )
14
+ #
15
+ # 42.times do
16
+ # tm.start
17
+ # number_of_items_processed = do_something
18
+ # tm.stop( number_of_items_processed )
19
+ # end
20
+ #
21
+ # puts "#{ tm.name } operated at a rate of #{ tm.rate } calls per second"
22
+ #
23
+ # TimedValueMetric combines the usefulness of a ValueMetric and a TimedMetric.
24
+ # The stats are available for both the time it took to do the operation and
25
+ # the sizes of the batches that were run.
26
+ #
27
+ # A TimedValueMetric keeps track of both the time it took to do an operation
28
+ # and the size of the batch that was operated on. These metrics are kept
29
+ # separately as +timed_stats+ and +value_stats+ accessors.
30
+ #
31
+ class TimedValueMetric < Metric
32
+ # holds all the Timed statistics
33
+ attr_reader :timed_stats
34
+
35
+ # holds all the Value statistics
36
+ attr_reader :value_stats
37
+
38
+ class << TimedValueMetric
39
+ #
40
+ # :call-seq:
41
+ # TimedValueMetric.now( 'name' ) -> TimedValueMetric
42
+ #
43
+ # Return a TimedValueMetric that has been started
44
+ #
45
+ def now( name, additional_data = {} )
46
+ t = TimedValueMetric.new( name, additional_data )
47
+ t.start
48
+ return t
49
+ end
50
+ end
51
+
52
+ #
53
+ # :call-seq:
54
+ # TimedValueMetric.new( 'name') -> TimedValueMetric
55
+ # TimedValueMetric.new( 'name', 'other' => 'data') -> TimedValueMetric
56
+ #
57
+ # Create a new TimedValueMetric giving it a name and additional data.
58
+ # +additional_data+ may be anything that follows the +to_hash+ protocol
59
+ #
60
+ def initialize( name, additional_data = {} )
61
+ super( name, additional_data )
62
+ @timed_stats = Stats.new
63
+ @value_stats = Stats.new
64
+ @current_interval = Interval.new
65
+ end
66
+
67
+ #
68
+ # :call-seq:
69
+ # timed_value_metric.running? -> true or false
70
+ #
71
+ # return whether or not the metric is currently timing something.
72
+ #
73
+ def running?
74
+ @current_interval.running?
75
+ end
76
+
77
+ #
78
+ # :call-seq:
79
+ # timed_value_metric.start -> nil
80
+ #
81
+ # Start the current timer, if the current timer is already started, then
82
+ # this is a noop.
83
+ #
84
+ def start
85
+ if not @current_interval.running? then
86
+ @current_interval.start
87
+ @sampling_start_time ||= self.utc_microseconds()
88
+ @sampling_start_interval ||= Interval.now
89
+ end
90
+ nil
91
+ end
92
+
93
+ #
94
+ # :call-seq:
95
+ # timed_value_metric.stop( count ) -> Float or nil
96
+ #
97
+ # Stop the current metric. The +count+ parameter must be a
98
+ # value to update to the _value_ portion of the TimedValueMetric. Generally
99
+ # this is probably the number of things that were operated upon since
100
+ # +start+ was invoked.
101
+ #
102
+ # This updates both the +value_stats+ and +timed_stats+ stats and removes
103
+ # the current interval. If the metric is stopped then the duration of the
104
+ # last Interval is returned. If the metric was already stopped before this
105
+ # call, then false is returned and no stats are updated.
106
+ #
107
+ #
108
+ def stop( value )
109
+ if @current_interval.running? then
110
+ d = @current_interval.stop
111
+ @timed_stats.update( d )
112
+ @current_interval = Interval.new
113
+ @value_stats.update( value )
114
+
115
+ # update the lenght of time we have been sampling
116
+ @sampling_delta = @sampling_start_interval.duration_so_far
117
+
118
+ return d
119
+ end
120
+ return false
121
+ end
122
+
123
+ #
124
+ # :call-seq:
125
+ # timed_value_metric.measure( value ) { ... } -> Object
126
+ #
127
+ # Measure the execution of a block and add those stats to the running stats.
128
+ # The return value is the return value of the block. A value must be passed
129
+ # into +measure+ to update the +value_stats+ portion of the TimedValueMetric.
130
+ #
131
+ def measure( value, &block )
132
+ return_value = nil
133
+ begin
134
+ start
135
+ return_value = yield
136
+ ensure
137
+ stop( value )
138
+ end
139
+ return return_value
140
+ end
141
+
142
+ #
143
+ # :call-seq:
144
+ # timed_value_metric.split( value ) -> Float
145
+ #
146
+ # Split the current metric. Essentially, mark a split time. This means
147
+ # stop the current interval, with the givein +value+ and create a new
148
+ # interval, but make sure that the new interval lines up exactly, timewise,
149
+ # behind the previous interval.
150
+ #
151
+ # If the metric is running, then split returns the duration of the previous
152
+ # interval, i.e. the split-time. If the metric is not running, nothing
153
+ # happens, no stats are updated, and false is returned.
154
+ #
155
+ #
156
+ def split( value )
157
+ if @current_interval.running? then
158
+ next_interval = @current_interval.split
159
+ d = @current_interval.duration
160
+ @timed_stats.update( d )
161
+ @value_stats.update( value )
162
+ @current_interval = next_interval
163
+ return d
164
+ end
165
+ return false
166
+ end
167
+
168
+ #
169
+ # :call-seq:
170
+ # timed_value_metric.duration -> Float
171
+ #
172
+ # The duration of measured time from the metric.
173
+ #
174
+ def duration
175
+ @timed_stats.sum
176
+ end
177
+
178
+ #
179
+ # :call-seq:
180
+ # timed_value_metric.unit_count -> Float
181
+ #
182
+ # The sum of all values passed to +stop+ or +skip+ or +measure+
183
+ #
184
+ def unit_count
185
+ @value_stats.sum
186
+ end
187
+
188
+ #
189
+ # :call-seq:
190
+ # timed_value_metric.rate -> Float
191
+ #
192
+ # Rate in the context of the TimedValueMetric is different than the
193
+ # TimedMetric. In the TimedValueMetric, each measurement of time is
194
+ # associated with a quantity of things done during that unit of time. So
195
+ # the +rate+ for a TimedValueMetric is the (sum of all quantities sampled) /
196
+ # ( sum of all durations measured )
197
+ #
198
+ # For example, say you were measuring, using a TimedValueMetric batch jobs
199
+ # that had individual units of work.
200
+ #
201
+ # tvm = TimedValueMetric.new( 'some-batch' )
202
+ # tvm.start
203
+ # # process a batch of 12 units
204
+ # duration1 = tvm.stop( 12 )
205
+ #
206
+ # tvm.start
207
+ # # process a larger batch of 42 units
208
+ # duration2 = tvm.stop( 42 )
209
+ #
210
+ # At this point the rate of units per second is calculated as ( 12 + 42 ) / ( duration1 + duration2 )
211
+ #
212
+ # some_batch_rate = tvm.rate # returns ( 34 / ( duration1+duration2 ) )
213
+ #
214
+ def rate
215
+ @value_stats.sum / @timed_stats.sum
216
+ end
217
+
218
+ #
219
+ # :call-seq:
220
+ # metric.to_hash -> Hash
221
+ #
222
+ # Convert the metric to a hash
223
+ #
224
+ def to_hash
225
+ h = super
226
+ h['timed_stats'] = @timed_stats.to_hash
227
+ h['value_stats'] = @value_stats.to_hash( Stats::STATS - %w[ rate ] )
228
+ h['rate'] = self.rate
229
+ h['unit_count'] = self.unit_count
230
+ return h
231
+ end
232
+
233
+
234
+ end
235
+ end