hitimes 1.3.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -11,22 +11,7 @@ module Hitimes
11
11
  # Ruby the Stats object is already threadsafe, so there is no need to use
12
12
  # MutexedStats.
13
13
  #
14
- class MutexedStats < Stats
15
- def initialize
16
- @mutex = Mutex.new
17
- end
18
-
19
- # call-seq:
20
- # mutex_stat.update( val ) -> nil
21
- #
22
- # Update the running stats with the new value in a threadsafe manner.
23
- #
24
- def update( value )
25
- @mutex.synchronize do
26
- super( value )
27
- end
28
- end
29
- end
14
+ MutexedStats = Stats
30
15
  end
31
16
 
32
17
 
@@ -4,11 +4,109 @@
4
4
  #++
5
5
 
6
6
  require 'stringio'
7
+ require 'thread'
7
8
  module Hitimes
9
+ #
10
+ # The Stats class encapulsates capturing and reporting statistics. It is
11
+ # modeled after the RFuzz::Sampler class, but implemented in C. For general use
12
+ # you allocate a new Stats object, and then update it with new values. The
13
+ # Stats object will keep track of the _min_, _max_, _count_, _sum_ and _sumsq_
14
+ # and when you want you may also retrieve the _mean_, _stddev_ and _rate_.
15
+ #
16
+ # this contrived example shows getting a list of all the files in a directory
17
+ # and running stats on file sizes.
18
+ #
19
+ # s = Hitimes::Stats.new
20
+ # dir = ARGV.shift || Dir.pwd
21
+ # Dir.entries( dir ).each do |entry|
22
+ # fs = File.stat( entry )
23
+ # if fs.file? then
24
+ # s.update( fs.size )
25
+ # end
26
+ # end
27
+ #
28
+ # %w[ count min max mean sum stddev rate ].each do |m|
29
+ # puts "#{m.rjust(6)} : #{s.send( m ) }"
30
+ # end
31
+ #
8
32
  class Stats
9
33
  # A list of the available stats
10
34
  STATS = %w[ count max mean min rate stddev sum sumsq ]
11
35
 
36
+ attr_reader :min
37
+ attr_reader :max
38
+ attr_reader :count
39
+ attr_reader :sum
40
+ attr_reader :sumsq
41
+
42
+ def initialize
43
+ @mutex = Mutex.new
44
+ @min = Float::INFINITY
45
+ @max = -Float::INFINITY
46
+ @count = 0
47
+ @sum = 0.0
48
+ @sumsq = 0.0
49
+ end
50
+
51
+ # call-seq:
52
+ # stat.update( val ) -> val
53
+ #
54
+ # Update the running stats with the new value.
55
+ # Return the input value.
56
+ def update(value)
57
+ @mutex.synchronize do
58
+ @min = (value < @min) ? value : @min
59
+ @max = (value > @max) ? value : @max
60
+
61
+ @count += 1
62
+ @sum += value
63
+ @sumsq += (value * value)
64
+ end
65
+
66
+ return value
67
+ end
68
+
69
+ # call-seq:
70
+ # stat.mean -> Float
71
+ #
72
+ # Return the arithmetic mean of the values put into the Stats object. If no
73
+ # values have passed through the stats object then 0.0 is returned;
74
+ def mean
75
+ return 0.0 if @count.zero?
76
+ return @sum / @count
77
+ end
78
+
79
+ # call-seq:
80
+ # stat.rate -> Float
81
+ #
82
+ # Return the +count+ divided by +sum+.
83
+ #
84
+ # In many cases when Stats#update( _value_ ) is called, the _value_ is a unit
85
+ # of time, typically seconds or microseconds. #rate is a convenience for those
86
+ # times. In this case, where _value_ is a unit if time, then count divided by
87
+ # sum is a useful value, i.e. +something per unit of time+.
88
+ #
89
+ # In the case where _value_ is a non-time related value, then the value
90
+ # returned by _rate_ is not really useful.
91
+ #
92
+ def rate
93
+ return 0.0 if @sum.zero?
94
+ return @count / @sum
95
+ end
96
+
97
+ #
98
+ # call-seq:
99
+ # stat.stddev -> Float
100
+ #
101
+ # Return the standard deviation of all the values that have passed through the
102
+ # Stats object. The standard deviation has no meaning unless the count is > 1,
103
+ # therefore if the current _stat.count_ is < 1 then 0.0 will be returned;
104
+ #
105
+ def stddev
106
+ return 0.0 unless @count > 1
107
+ Math.sqrt((@sumsq - ((@sum * @sum)/@count)) / (@count - 1))
108
+ end
109
+
12
110
  #
13
111
  # call-seq:
14
112
  # stat.to_hash -> Hash
@@ -53,6 +151,5 @@ module Hitimes
53
151
  s.print "}"
54
152
  return s.string
55
153
  end
56
-
57
154
  end
58
155
  end
@@ -4,5 +4,5 @@
4
4
  #++
5
5
 
6
6
  module Hitimes
7
- VERSION = "1.3.1"
7
+ VERSION = "2.0.0"
8
8
  end
@@ -5,20 +5,20 @@ describe Hitimes do
5
5
  d = Hitimes.measure do
6
6
  sleep 0.2
7
7
  end
8
- d.must_be_close_to(0.2, 0.002)
8
+ _(d).must_be_close_to(0.2, 0.002)
9
9
  end
10
10
 
11
11
  it "raises an error if measure is called with no block" do
12
- lambda{ Hitimes.measure }.must_raise( Hitimes::Error )
12
+ _(lambda{ Hitimes.measure }).must_raise( Hitimes::Error )
13
13
  end
14
14
 
15
15
  it "has the raw instant value" do
16
16
  v = Hitimes.raw_instant
17
- v.must_be :>, 0
17
+ _(v).must_be :>, 0
18
18
  end
19
19
 
20
20
  it "has access to the instant conversion factor" do
21
21
  f = Hitimes::INSTANT_CONVERSION_FACTOR
22
- f.must_be :>, 0
22
+ _(f).must_be :>, 0
23
23
  end
24
24
  end
@@ -3,54 +3,54 @@ require "spec_helper"
3
3
  describe Hitimes::Interval do
4
4
  it "raises an error if duration is called on a non-started interval" do
5
5
  i = Hitimes::Interval.new
6
- lambda{ i.duration }.must_raise( Hitimes::Error, /\AAttempt to report a duration on an interval that has not started\Z/ )
6
+ _(lambda{ i.duration }).must_raise( Hitimes::Error, /\AAttempt to report a duration on an interval that has not started\Z/ )
7
7
  end
8
8
 
9
9
  it "raises an error if stop is called on a non-started interval" do
10
10
  i = Hitimes::Interval.new
11
- lambda { i.stop }.must_raise( Hitimes::Error, /\AAttempt to stop an interval that has not started\Z/ )
11
+ _(lambda { i.stop }).must_raise( Hitimes::Error, /\AAttempt to stop an interval that has not started\Z/ )
12
12
  end
13
13
 
14
14
  it "knows if it has been started" do
15
15
  i = Hitimes::Interval.new
16
- i.started?.must_equal false
16
+ _(i.started?).must_equal false
17
17
 
18
18
  i.start
19
- i.started?.must_equal true
19
+ _(i.started?).must_equal true
20
20
  end
21
21
 
22
22
  it "knows if it has been stopped" do
23
23
  i = Hitimes::Interval.new
24
24
  i.start
25
- i.stopped?.must_equal false
25
+ _(i.stopped?).must_equal false
26
26
  i.stop
27
- i.stopped?.must_equal true
27
+ _(i.stopped?).must_equal true
28
28
  end
29
29
 
30
30
  it "knows if it is currently running" do
31
31
  i = Hitimes::Interval.new
32
- i.running?.must_equal false
32
+ _(i.running?).must_equal false
33
33
  i.start
34
- i.running?.must_equal true
34
+ _(i.running?).must_equal true
35
35
  i.stop
36
- i.running?.must_equal false
36
+ _(i.running?).must_equal false
37
37
  end
38
38
 
39
39
  it "can time a block of code" do
40
40
  d = Hitimes::Interval.measure do
41
41
  sleep 0.2
42
42
  end
43
- d.must_be_close_to(0.2, 0.002)
43
+ _(d).must_be_close_to(0.2, 0.002)
44
44
  end
45
45
 
46
46
  it "raises an error if measure is called with no block" do
47
- lambda{ Hitimes::Interval.measure }.must_raise( Hitimes::Error, /\ANo block given to Interval.measure\Z/ )
47
+ _(lambda{ Hitimes::Interval.measure }).must_raise( Hitimes::Error, /\ANo block given to Interval.measure\Z/ )
48
48
  end
49
49
 
50
50
  it "creates an interval via #now" do
51
51
  i = Hitimes::Interval.now
52
- i.started?.must_equal true
53
- i.stopped?.must_equal false
52
+ _(i.started?).must_equal true
53
+ _(i.stopped?).must_equal false
54
54
  end
55
55
 
56
56
  it "calling duration multiple times returns successivly grater durations" do
@@ -58,33 +58,33 @@ describe Hitimes::Interval do
58
58
  i.start
59
59
  y = i.duration
60
60
  z = i.duration
61
- z.must_be :>, y
61
+ _(z).must_be :>, y
62
62
  end
63
63
 
64
64
  it "calling start multiple times on has no effect after the first call" do
65
65
  i = Hitimes::Interval.new
66
- i.start.must_equal true
66
+ _(i.start).must_equal true
67
67
  x = i.start_instant
68
- i.start_instant.must_be :>, 0
69
- i.start.must_equal false
70
- x.must_equal i.start_instant
68
+ _(i.start_instant).must_be :>, 0
69
+ _(i.start).must_equal false
70
+ _(x).must_equal i.start_instant
71
71
  end
72
72
 
73
73
  it "returns the duration on the first call to stop" do
74
74
  i = Hitimes::Interval.now
75
75
  d = i.stop
76
- d.must_be_instance_of( Float )
76
+ _(d).must_be_instance_of( Float )
77
77
  end
78
78
 
79
79
  it "calling stop multiple times on has no effect after the first call" do
80
80
  i = Hitimes::Interval.new
81
- i.start.must_equal true
81
+ _(i.start).must_equal true
82
82
  i.stop
83
83
 
84
84
  x = i.stop_instant
85
- i.stop_instant.must_be :>, 0
86
- i.stop.must_equal false
87
- x.must_equal i.stop_instant
85
+ _(i.stop_instant).must_be :>, 0
86
+ _(i.stop).must_equal false
87
+ _(x).must_equal i.stop_instant
88
88
 
89
89
  end
90
90
 
@@ -93,26 +93,26 @@ describe Hitimes::Interval do
93
93
  i.start
94
94
  x = i.stop
95
95
  y = i.duration
96
- i.stop.must_equal false
96
+ _(i.stop).must_equal false
97
97
 
98
98
  z = i.duration
99
99
 
100
- x.must_equal y
101
- x.must_equal z
100
+ _(x).must_equal y
101
+ _(x).must_equal z
102
102
 
103
- y.must_equal z
103
+ _(y).must_equal z
104
104
  end
105
105
 
106
106
  it "can return how much time has elapsed from the start without stopping the interval" do
107
107
  i = Hitimes::Interval.new
108
108
  i.start
109
109
  x = i.duration_so_far
110
- i.running?.must_equal true
110
+ _(i.running?).must_equal true
111
111
  y = i.duration_so_far
112
112
  i.stop
113
- x.must_be :<, y
114
- x.must_be :<, i.duration
115
- y.must_be :<, i.duration
113
+ _(x).must_be :<, y
114
+ _(x).must_be :<, i.duration
115
+ _(y).must_be :<, i.duration
116
116
  end
117
117
 
118
118
  describe "#split" do
@@ -121,14 +121,14 @@ describe Hitimes::Interval do
121
121
  i = Hitimes::Interval.new
122
122
  i.start
123
123
  i2 = i.split
124
- i.object_id.wont_equal i2.object_id
124
+ _(i.object_id).wont_equal i2.object_id
125
125
  end
126
126
 
127
127
  it "with the stop instant equivialent to the previous Interval's start instant" do
128
128
  i = Hitimes::Interval.new
129
129
  i.start
130
130
  i2 = i.split
131
- i.stop_instant.must_equal i2.start_instant
131
+ _(i.stop_instant).must_equal i2.start_instant
132
132
  end
133
133
  end
134
134
 
@@ -6,22 +6,22 @@ describe Hitimes::Metric do
6
6
  end
7
7
 
8
8
  it 'has a name' do
9
- @metric.name.must_equal "testing"
9
+ _(@metric.name).must_equal "testing"
10
10
  end
11
11
 
12
12
  it "has associated data from initialization" do
13
13
  m = Hitimes::Metric.new( "more-data", 'foo' => 'bar', 'this' => 'that' )
14
- m.additional_data['foo'].must_equal 'bar'
15
- m.additional_data['this'].must_equal 'that'
14
+ _(m.additional_data['foo']).must_equal 'bar'
15
+ _(m.additional_data['this']).must_equal 'that'
16
16
 
17
17
  m = Hitimes::Metric.new( "more-data", { 'foo' => 'bar', 'this' => 'that' } )
18
- m.additional_data['foo'].must_equal 'bar'
19
- m.additional_data['this'].must_equal 'that'
18
+ _(m.additional_data['foo']).must_equal 'bar'
19
+ _(m.additional_data['this']).must_equal 'that'
20
20
  end
21
21
 
22
22
  it "initially has no sampling times" do
23
- @metric.sampling_start_time.must_be_nil
24
- @metric.sampling_stop_time.must_be_nil
23
+ _(@metric.sampling_start_time).must_be_nil
24
+ _(@metric.sampling_stop_time).must_be_nil
25
25
  end
26
26
  end
27
27
 
@@ -16,21 +16,14 @@ describe Hitimes::MutexedStats do
16
16
  return stats
17
17
  end
18
18
 
19
- if (not defined? RUBY_ENGINE) or (RUBY_ENGINE == "ruby") then
20
- it "Hitimes::Stats is threadsafe" do
21
- stats = run_with_scissors( ::Hitimes::Stats.new, @threads, @iters )
22
- stats.count.must_equal @final_value
23
- end
24
- else
25
- it "Hitimes::Stats is not threadsafe" do
26
- stats = run_with_scissors( ::Hitimes::Stats.new, @threads, @iters )
27
- stats.count.wont_equal @final_value
28
- end
19
+ it "Hitimes::Stats is threadsafe" do
20
+ stats = run_with_scissors( ::Hitimes::Stats.new, @threads, @iters )
21
+ _(stats.count).must_equal @final_value
29
22
  end
30
23
 
31
24
  it "has a threadsafe update" do
32
25
  stats = run_with_scissors( ::Hitimes::MutexedStats.new, @threads, @iters )
33
- stats.count.must_equal @final_value
26
+ _(stats.count).must_equal @final_value
34
27
  end
35
28
 
36
29
  end
@@ -2,10 +2,10 @@ require 'spec_helper'
2
2
 
3
3
  describe Hitimes::Paths do
4
4
  it "can access the root dir of the project" do
5
- Hitimes::Paths.root_dir.must_equal File.expand_path( File.join( File.dirname( __FILE__ ), ".." ) ) + ::File::SEPARATOR
5
+ _(Hitimes::Paths.root_dir).must_equal File.expand_path( File.join( File.dirname( __FILE__ ), ".." ) ) + ::File::SEPARATOR
6
6
  end
7
7
 
8
8
  it "can access the lib path of the project" do
9
- Hitimes::Paths.lib_path.must_equal File.expand_path( File.join( File.dirname( __FILE__ ), "..", "lib" ) ) + ::File::SEPARATOR
9
+ _(Hitimes::Paths.lib_path).must_equal File.expand_path( File.join( File.dirname( __FILE__ ), "..", "lib" ) ) + ::File::SEPARATOR
10
10
  end
11
11
  end
@@ -9,65 +9,65 @@ describe Hitimes::Stats do
9
9
  [ 1, 2, 3].each { |i| @full_stats.update( i ) }
10
10
  end
11
11
 
12
- it "is initialized with 0 values" do
13
- @stats.count.must_equal 0
14
- @stats.min.must_equal 0.0
15
- @stats.max.must_equal 0.0
16
- @stats.sum.must_equal 0.0
17
- @stats.rate.must_equal 0.0
12
+ it "is initialized with usable values" do
13
+ _(@stats.count).must_equal 0
14
+ _(@stats.min).must_equal Float::INFINITY
15
+ _(@stats.max).must_equal(-Float::INFINITY)
16
+ _(@stats.sum).must_equal 0.0
17
+ _(@stats.rate).must_equal 0.0
18
18
  end
19
19
 
20
20
  it "calculates the mean correctly" do
21
- @full_stats.mean.must_equal 2.0
21
+ _(@full_stats.mean).must_equal 2.0
22
22
  end
23
23
 
24
24
  it "calculates the rate correctly" do
25
- @full_stats.rate.must_equal 0.5
25
+ _(@full_stats.rate).must_equal 0.5
26
26
  end
27
27
 
28
28
  it "tracks the maximum value" do
29
- @full_stats.max.must_equal 3.0
29
+ _(@full_stats.max).must_equal 3.0
30
30
  end
31
31
 
32
32
  it "tracks the minimum value" do
33
- @full_stats.min.must_equal 1.0
33
+ _(@full_stats.min).must_equal 1.0
34
34
  end
35
35
 
36
36
  it "tracks the count" do
37
- @full_stats.count.must_equal 3
37
+ _(@full_stats.count).must_equal 3
38
38
  end
39
39
 
40
40
  it "tracks the sum" do
41
- @full_stats.sum.must_equal 6.0
41
+ _(@full_stats.sum).must_equal 6.0
42
42
  end
43
43
 
44
44
  it "calculates the standard deviation" do
45
- @full_stats.stddev.must_equal 1.0
45
+ _(@full_stats.stddev).must_equal 1.0
46
46
  end
47
47
 
48
48
  it "calculates the sum of squares " do
49
- @full_stats.sumsq.must_equal 14.0
49
+ _(@full_stats.sumsq).must_equal 14.0
50
50
  end
51
51
 
52
52
  describe "#to_hash " do
53
53
  it "converts to a Hash" do
54
54
  h = @full_stats.to_hash
55
- h.size.must_equal ::Hitimes::Stats::STATS.size
56
- h.keys.sort.must_equal ::Hitimes::Stats::STATS
55
+ _(h.size).must_equal ::Hitimes::Stats::STATS.size
56
+ _(h.keys.sort).must_equal ::Hitimes::Stats::STATS
57
57
  end
58
58
 
59
59
  it "converts to a limited Hash if given arguments" do
60
60
  h = @full_stats.to_hash( "min", "max", "mean" )
61
- h.size.must_equal 3
62
- h.keys.sort.must_equal %w[ max mean min ]
61
+ _(h.size).must_equal 3
62
+ _(h.keys.sort).must_equal %w[ max mean min ]
63
63
 
64
64
  h = @full_stats.to_hash( %w[ count rate ] )
65
- h.size.must_equal 2
66
- h.keys.sort.must_equal %w[ count rate ]
65
+ _(h.size).must_equal 2
66
+ _(h.keys.sort).must_equal %w[ count rate ]
67
67
  end
68
68
 
69
69
  it "raises NoMethodError if an invalid stat is used" do
70
- lambda { @full_stats.to_hash( "wibble" ) }.must_raise( NoMethodError )
70
+ _(lambda { @full_stats.to_hash( "wibble" ) }).must_raise( NoMethodError )
71
71
  end
72
72
  end
73
73
 
@@ -75,24 +75,24 @@ describe Hitimes::Stats do
75
75
  it "converts to a json string" do
76
76
  j = @full_stats.to_json
77
77
  h = JSON.parse( j )
78
- h.size.must_equal ::Hitimes::Stats::STATS.size
79
- h.keys.sort.must_equal ::Hitimes::Stats::STATS
78
+ _(h.size).must_equal ::Hitimes::Stats::STATS.size
79
+ _(h.keys.sort).must_equal ::Hitimes::Stats::STATS
80
80
  end
81
81
 
82
82
  it "converts to a limited Hash if given arguments" do
83
83
  j = @full_stats.to_json( "min", "max", "mean" )
84
84
  h = JSON.parse( j )
85
- h.size.must_equal 3
86
- h.keys.sort.must_equal %w[ max mean min ]
85
+ _(h.size).must_equal 3
86
+ _(h.keys.sort).must_equal %w[ max mean min ]
87
87
 
88
88
  j = @full_stats.to_json( %w[ count rate ] )
89
89
  h = JSON.parse( j )
90
- h.size.must_equal 2
91
- h.keys.sort.must_equal %w[ count rate ]
90
+ _(h.size).must_equal 2
91
+ _(h.keys.sort).must_equal %w[ count rate ]
92
92
  end
93
93
 
94
94
  it "raises NoMethodError if an invalid stat is used" do
95
- lambda { @full_stats.to_json( "wibble" ) }.must_raise( NoMethodError )
95
+ _(lambda { @full_stats.to_json( "wibble" ) }).must_raise( NoMethodError )
96
96
  end
97
97
  end
98
98
  end