hitimes 0.4.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/HISTORY CHANGED
@@ -1,4 +1,14 @@
1
1
  = Changelog
2
+ == Version 1.0.0 2009-06-12
3
+
4
+ * Major version bump with complete refactor of the metric collection API
5
+ * 3 types of metrics now instead of just 1 Timer
6
+ * ValueMetric
7
+ * TimedMetric
8
+ * TimedValueMetric
9
+ * The ability to convert all metrics #to_hash
10
+ * Updated documentation with examples using each metric type
11
+
2
12
  == Version 0.4.1 2009-02-19
3
13
 
4
14
  * change to ISC License
data/README CHANGED
@@ -15,15 +15,20 @@ Hitimes is a fast, high resolution timer library for recording
15
15
  performance metrics. It uses the appropriate C method calls for each
16
16
  system to get the highest granularity time increments possible.
17
17
 
18
- It currently supports any system with the POSIX call clock_gettime(),
19
- Mac OS X and Windows.
18
+ It currently supports any of the following systems:
20
19
 
21
- Using Hitimes can be faster than using a series of Time.new calls, and
20
+ * any system with the POSIX call <tt>clock_gettime()</tt>,
21
+ * Mac OS X
22
+ * Windows
23
+
24
+ Using Hitimes can be faster than using a series of +Time.new+ calls, and
22
25
  it will have a much higher granularity. It is definitely faster than
23
- using Process.times.
26
+ using +Process.times+.
24
27
 
25
28
  == SYNOPSIS
26
29
 
30
+ === Interval
31
+
27
32
  Use Hitimes::Interval to calculate only the duration of a block of code
28
33
 
29
34
  duration = Hitimes::Interval.measure do
@@ -32,21 +37,76 @@ Use Hitimes::Interval to calculate only the duration of a block of code
32
37
 
33
38
  puts duration
34
39
 
35
- Use a Hitimes::Timer to calculate statistics about an iterative operation
40
+ === TimedMetric
41
+
42
+ Use a Hitimes::TimedMetric to calculate statistics about an iterative operation
43
+
44
+ timed_metric = Hitimes::TimedMetric.new('operation on items')
45
+
46
+ Explicitly use +start+ and +stop+:
36
47
 
37
- timer = Hitimes::Timer.new
38
48
  collection.each do |item|
39
- timer.start
49
+ timed_metric.start
40
50
  # .. do something with item
41
- timer.stop
51
+ timed_metric.stop
52
+ end
53
+
54
+ Or use the block. In TimedMetric the return value of +measure+ is the return
55
+ value of the block
56
+
57
+ collection.each do |item|
58
+ result_of_do_something = timed_metric.measure { do_something( item ) }
42
59
  end
43
60
 
44
- puts timer.mean
45
- puts timer.median
46
- puts timer.max
47
- puts timer.min
48
- puts timer.stddev
49
- puts timer.rate
61
+ And then look at the stats
62
+
63
+ puts timed_metric.mean
64
+ puts timed_metric.max
65
+ puts timed_metric.min
66
+ puts timed_metric.stddev
67
+ puts timed_metric.rate
68
+
69
+ === ValueMetric
70
+
71
+ Use a Hitimes::ValueMetric to calculate statistics about measured samples
72
+
73
+ value_metric = Hitimes::ValueMetric.new( 'size of thing' )
74
+ loop do
75
+ # ... do stuff changing sizes of 'thing'
76
+ value_metric.measure( thing.size )
77
+ # ... do other stuff that may change size of thing
78
+ end
79
+
80
+ puts value_metric.mean
81
+ puts value_metric.max
82
+ puts value_metric.min
83
+ puts value_metric.stddev
84
+ puts value_metric.rate
85
+
86
+
87
+ === TimedValueMetric
88
+
89
+ Use a Hitimes::TimedValueMetric to calculate statistics about batches of samples
90
+
91
+ timed_value_metric = Hitimes::TimedValueMetric.new( 'batch times' )
92
+ loop do
93
+ batch = ... # get a batch of things
94
+ timed_value_metric.start
95
+ # .. do something with batch
96
+ timed_value_metric.stop( batch.size )
97
+ end
98
+
99
+ puts timed_value_metric.rate
100
+
101
+ puts timed_value_metric.timed_stats.mean
102
+ puts timed_value_metric.timed_stats.max
103
+ puts timed_value_metric.timed_stats.min
104
+ puts timed_value_metric.timed_stats.stddev
105
+
106
+ puts timed_value_metric.value_stats.mean
107
+ puts timed_value_metric.value_stats.max
108
+ puts timed_value_metric.value_stats.min
109
+ puts timed_value_metric.value_stats.stddev
50
110
 
51
111
 
52
112
  == CHANGES
@@ -254,6 +254,9 @@ VALUE hitimes_interval_stop_instant( VALUE self )
254
254
  /**
255
255
  * call-seq:
256
256
  * interval.duration -> Float
257
+ * interval.to_f -> Float
258
+ * interval.to_seconds -> Float
259
+ * interval.length -> Float
257
260
  *
258
261
  * Returns the Float value of the interval, the value is in seconds. If the
259
262
  * interval has not had stop called yet, it will report the number of seconds
@@ -321,10 +324,10 @@ void Init_hitimes_interval()
321
324
  rb_define_module_function( cH_Interval, "now", hitimes_interval_now, 0 ); /* in hitimes_interval.c */
322
325
  rb_define_module_function( cH_Interval, "measure", hitimes_interval_measure, 0 ); /* in hitimes_interval.c */
323
326
 
324
- rb_define_method( cH_Interval, "to_f", hitimes_interval_duration, 0 ); /* in hitimes_interval.c */
325
- rb_define_method( cH_Interval, "to_seconds", hitimes_interval_duration, 0 ); /* in hitimes_interval.c */
326
327
  rb_define_method( cH_Interval, "duration", hitimes_interval_duration, 0 ); /* in hitimes_interval.c */
327
- rb_define_method( cH_Interval, "length", hitimes_interval_duration, 0 ); /* in hitimes_interval.c */
328
+ rb_define_method( cH_Interval, "length", hitimes_interval_duration, 0 );
329
+ rb_define_method( cH_Interval, "to_f", hitimes_interval_duration, 0 );
330
+ rb_define_method( cH_Interval, "to_seconds", hitimes_interval_duration, 0 );
328
331
 
329
332
  rb_define_method( cH_Interval, "started?", hitimes_interval_started, 0 ); /* in hitimes_interval.c */
330
333
  rb_define_method( cH_Interval, "running?", hitimes_interval_running, 0 ); /* in hitimes_interval.c */
data/ext/hitimes_stats.c CHANGED
@@ -39,9 +39,10 @@ VALUE hitimes_stats_alloc(VALUE klass)
39
39
 
40
40
  /**
41
41
  * call-seq:
42
- * stat.update( val ) -> nil
42
+ * stat.update( val ) -> val
43
43
  *
44
44
  * Update the running stats with the new value.
45
+ * Return the input value.
45
46
  */
46
47
  VALUE hitimes_stats_update( VALUE self, VALUE v )
47
48
  {
@@ -62,7 +63,7 @@ VALUE hitimes_stats_update( VALUE self, VALUE v )
62
63
  stats->sum += new_v;
63
64
  stats->sumsq += ( new_v * new_v );
64
65
 
65
- return Qnil;
66
+ return v;
66
67
  }
67
68
 
68
69
  /**
@@ -91,10 +92,15 @@ VALUE hitimes_stats_mean( VALUE self )
91
92
  * call-seq:
92
93
  * stat.rate -> Float
93
94
  *
94
- * Return the count per sum. In many cases when Stats#update( _value_ ) is
95
- * called, the _value_ is a unit of time, typically seconds. #rate
96
- * is a convenience for those times. In the most common case, where _value_
97
- * is in seconds, then #rate returns the count / second.
95
+ * Return the +count+ divided by +sum+.
96
+ *
97
+ * In many cases when Stats#update( _value_ ) is called, the _value_ is a unit
98
+ * of time, typically seconds or microseconds. #rate is a convenience for those
99
+ * times. In this case, where _value_ is a unit if time, then count divided by
100
+ * sum is a useful value, i.e. +something per unit of time+.
101
+ *
102
+ * In the case where _value_ is a non-time related value, then the value
103
+ * returned by _rate_ is not really useful.
98
104
  *
99
105
  */
100
106
  VALUE hitimes_stats_rate( VALUE self )
@@ -176,6 +182,22 @@ VALUE hitimes_stats_sum( VALUE self )
176
182
  return rb_float_new( stats->sum );
177
183
  }
178
184
 
185
+ /**
186
+ * call-seq:
187
+ * stat.sumsq -> Float
188
+ *
189
+ * Return the sum of the squars of all the values that passed through the Stats
190
+ * object.
191
+ */
192
+ VALUE hitimes_stats_sumsq( VALUE self )
193
+ {
194
+ hitimes_stats_t *stats;
195
+
196
+ Data_Get_Struct( self, hitimes_stats_t, stats );
197
+
198
+ return rb_float_new( stats->sumsq );
199
+ }
200
+
179
201
 
180
202
  /**
181
203
  * call-seq:
@@ -205,8 +227,8 @@ VALUE hitimes_stats_stddev ( VALUE self )
205
227
  * The Stats class encapulsates capturing and reporting statistics. It is
206
228
  * modeled after the RFuzz::Sampler class, but implemented in C. For general use
207
229
  * you allocate a new Stats object, and then update it with new values. The
208
- * Stats object will keep track of the _min_, _max_, _count_ and _sum_ and when
209
- * you want you may also retrieve the _mean_, _stddev_ and _rate_.
230
+ * Stats object will keep track of the _min_, _max_, _count_, _sum_ and _sumsq_
231
+ * and when you want you may also retrieve the _mean_, _stddev_ and _rate_.
210
232
  *
211
233
  * this contrived example shows getting a list of all the files in a directory
212
234
  * and running stats on file sizes.
@@ -240,6 +262,7 @@ void Init_hitimes_stats()
240
262
  rb_define_method( cH_Stats, "min", hitimes_stats_min, 0 ); /* in hitimes_stats.c */
241
263
  rb_define_method( cH_Stats, "rate", hitimes_stats_rate, 0 ); /* in hitimes_stats.c */
242
264
  rb_define_method( cH_Stats, "sum", hitimes_stats_sum, 0 ); /* in hitimes_stats.c */
265
+ rb_define_method( cH_Stats, "sumsq", hitimes_stats_sumsq, 0 ); /* in hitimes_stats.c */
243
266
  rb_define_method( cH_Stats, "stddev", hitimes_stats_stddev, 0 ); /* in hitimes_stats.c */
244
267
  }
245
268
 
data/gemspec.rb CHANGED
@@ -20,8 +20,10 @@ Hitimes::GEM_SPEC = Gem::Specification.new do |spec|
20
20
  spec.executables = pkg.files.bin.collect { |b| File.basename(b) }
21
21
 
22
22
  # add dependencies here
23
- spec.add_dependency("rake", ">= 0.8.1")
24
- spec.add_dependency("configuration", ">= 0.0.5")
23
+ spec.add_dependency("rake", "~> 0.8.1")
24
+ spec.add_dependency("configuration", " ~> 0.0.5")
25
+
26
+ spec.add_development_dependency( "json", "~> 1.1.3")
25
27
 
26
28
  if ext_conf = Configuration.for_if_exist?("extension") then
27
29
  spec.extensions << ext_conf.configs
data/lib/hitimes.rb CHANGED
@@ -19,6 +19,10 @@ end
19
19
  require 'hitimes/paths'
20
20
  require 'hitimes/version'
21
21
  require 'hitimes/stats'
22
+ require 'hitimes_ext'
22
23
  require 'hitimes/mutexed_stats'
23
- require 'hitimes/timer'
24
+ require 'hitimes/metric'
25
+ require 'hitimes/value_metric'
26
+ require 'hitimes/timed_metric'
27
+ require 'hitimes/timed_value_metric'
24
28
 
@@ -0,0 +1,74 @@
1
+ module Hitimes
2
+ #
3
+ # Metric hold the common meta information for all derived metric classes
4
+ #
5
+ # All metrics hold the meta information of:
6
+ #
7
+ # * The name of the metric
8
+ # * The time of day the first measurement is taken
9
+ # * The time of day the last measurement is taken
10
+ # * additional data
11
+ #
12
+ # Each derived class is assumed to set the sampling_start_time and
13
+ # sampling_stop_time appropriately.
14
+ #
15
+ # Metric itself should generally not be used. Only use the derived classes.
16
+ #
17
+ class Metric
18
+
19
+ # the time at which the first sample was taken
20
+ # This is the number of microseconds since UNIX epoch UTC as a Float
21
+ attr_accessor :sampling_start_time
22
+
23
+ # the time at which the last sample was taken.
24
+ # This is the number of microseconds since UNIX epoch UTC as a Float
25
+ attr_accessor :sampling_stop_time
26
+
27
+ # An additional hash of data to associate with the metric
28
+ attr_reader :additional_data
29
+
30
+ # The 'name' to associate with the metric
31
+ attr_reader :name
32
+
33
+ #
34
+ # :call-seq:
35
+ # Metric.new( 'my_metric' ) -> Metric
36
+ # Metric.new( 'my_metric', 'foo' => 'bar', 'this' => 42 ) -> Metric
37
+ #
38
+ # Create a new ValueMetric giving it a name and additional data.
39
+ #
40
+ # +additional_data+ may be anything that follows the +to_hash+ protocol.
41
+ # +name+ may be anything that follows the +to_s+ protocol.
42
+ #
43
+ def initialize( name, additional_data = {} )
44
+ @sampling_start_time = nil
45
+ @sampling_stop_time = nil
46
+ @name = name.to_s
47
+ @additional_data = additional_data.to_hash
48
+ end
49
+
50
+ #
51
+ # :call-seq:
52
+ # metric.to_hash -> Hash
53
+ # metric.to_hash
54
+ #
55
+ # Convert the metric to a Hash.
56
+ #
57
+ def to_hash
58
+ { 'sampling_start_time' => @sampling_start_time,
59
+ 'sampling_stop_time' => @sampling_stop_time,
60
+ 'additional_data' => @additional_data,
61
+ 'name' => @name }
62
+ end
63
+
64
+ #
65
+ # :call-seq:
66
+ # metric.utc_microseconds -> Float
67
+ #
68
+ # The current time in microseconds from the UNIX Epoch in the UTC
69
+ #
70
+ def utc_microseconds
71
+ Time.now.gmtime.to_f * 1_000_000
72
+ end
73
+ end
74
+ end
data/lib/hitimes/stats.rb CHANGED
@@ -1,7 +1,9 @@
1
+ require 'hitimes_ext'
2
+ require 'stringio'
1
3
  module Hitimes
2
4
  class Stats
3
5
  # A list of the available stats
4
- STATS = %w[ count max mean min rate stddev sum ]
6
+ STATS = %w[ count max mean min rate stddev sum sumsq ]
5
7
 
6
8
  #
7
9
  # call-seq:
@@ -25,5 +27,28 @@ module Hitimes
25
27
  return h
26
28
  end
27
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
+
28
53
  end
29
54
  end
@@ -0,0 +1,181 @@
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 = nil
64
+ end
65
+
66
+ #
67
+ # :call-seq:
68
+ # timed_metric.current_interval -> Interval
69
+ #
70
+ # Return the current interval, if one doesn't exist create one.
71
+ #
72
+ def current_interval
73
+ @current_interval ||= Interval.new
74
+ end
75
+
76
+ #
77
+ # :call-seq:
78
+ # timed_metric.running? -> true or false
79
+ #
80
+ # return whether or not the timer is currently running.
81
+ #
82
+ def running?
83
+ current_interval.running?
84
+ end
85
+
86
+ #
87
+ # :call-seq:
88
+ # timed_metric.start -> nil
89
+ #
90
+ # Start the current metric, if the current metric is already started, then
91
+ # this is a noop.
92
+ #
93
+ def start
94
+ current_interval.start unless running?
95
+ @sampling_start_time ||= self.utc_microseconds()
96
+ nil
97
+ end
98
+
99
+ #
100
+ # :call-seq:
101
+ # timed_metric.stop -> Float or nil
102
+ #
103
+ # Stop the current metric. This updates the stats and removes the current
104
+ # interval. If the timer was stopped then the duration of the last Interval
105
+ # is returned. If the timer was already stopped then false is returned and
106
+ # no stats are updated.
107
+ #
108
+ def stop
109
+ if running? then
110
+ d = current_interval.stop
111
+ @current_interval = nil
112
+ @sampling_stop_time = self.utc_microseconds()
113
+ @stats.update( d )
114
+ return d
115
+ end
116
+ return false
117
+ end
118
+
119
+ #
120
+ # :call-seq:
121
+ # timed_metric.measure { ... } -> Object
122
+ #
123
+ # Measure the execution of a block and add those stats to the running stats.
124
+ # The return value is the return value of the block
125
+ #
126
+ def measure( &block )
127
+ return_value = nil
128
+ begin
129
+ start
130
+ return_value = yield
131
+ ensure
132
+ stop
133
+ end
134
+ return return_value
135
+ end
136
+
137
+ #
138
+ # :call-seq:
139
+ # timed_metric.split -> Float
140
+ #
141
+ # Split the current TimedMetric. Essentially, mark a split time. This means
142
+ # stop the current interval and create a new interval, but make sure
143
+ # that the new interval lines up exactly, timewise, behind the previous
144
+ # interval.
145
+ #
146
+ # If the timer is running, then split returns the duration of the previous
147
+ # interval, i.e. the split-time. If the timer is not running, nothing
148
+ # happens and false is returned.
149
+ #
150
+ def split
151
+ if running? then
152
+ next_interval = current_interval.split
153
+ d = current_interval.duration
154
+ @stats.update( d )
155
+ @current_interval = next_interval
156
+ return d
157
+ end
158
+ return false
159
+ end
160
+
161
+ #
162
+ # :call-seq:
163
+ # metric.to_hash -> Hash
164
+ #
165
+ # Convert the metric to a hash
166
+ #
167
+ def to_hash
168
+ h = super
169
+ Stats::STATS.each do |s|
170
+ h[s] = self.send( s )
171
+ end
172
+ return h
173
+ end
174
+
175
+
176
+ # forward appropriate calls directly to the stats object
177
+ extend Forwardable
178
+ def_delegators :@stats, :count, :sum, :max, :mean, :min, :rate, :stddev, :sum, :sumsq
179
+ alias :duration :sum
180
+ end
181
+ end