hitimes 1.0.4-x86-mswin32

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