lib/benchmark/http/statistics.rb in benchmark-http-0.2.0 vs lib/benchmark/http/statistics.rb in benchmark-http-0.3.0
- old
+ new
@@ -16,39 +16,54 @@
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
+require 'async/clock'
+
module Benchmark
module HTTP
- class Statistics
- def initialize(concurrency)
+ class Stopwatch
+ def initialize(concurrency = 0)
@samples = []
- @duration = 0
+ @total_time = 0
+
+ # The number of currently executing measurements:
+ @count = 0
+
@concurrency = concurrency
+ @start_time = nil
end
+ # The individual samples' durations.
attr :samples
- attr :duration
+ # The sequential time of all samples.
+ attr :total_time
+
+ # The maximum number of executing measurements at any one time.
attr :concurrency
+ def duration
+ @samples.sum
+ end
+
def sequential_duration
- @duration / @concurrency
+ duration / @concurrency
end
def count
@samples.count
end
def per_second
- @samples.count.to_f / sequential_duration.to_f
+ @samples.count.to_f / total_time.to_f
end
def latency
- @duration.to_f / @samples.count.to_f
+ duration.to_f / count.to_f
end
def similar?(other, difference = 2.0)
ratio = other.latency / self.latency
@@ -59,17 +74,21 @@
if @samples.any?
@samples.sum / @samples.count
end
end
+ def valid?
+ @samples.count > 1
+ end
+
# Computes Population Variance, σ^2.
def variance
- return nil if @samples.count < 2
-
- average = self.average
-
- return @samples.map{|n| (n - average)**2}.sum / @samples.count
+ if valid?
+ average = self.average
+
+ return @samples.map{|n| (n - average)**2}.sum / @samples.count
+ end
end
# Population Standard Deviation, σ
def standard_deviation
if variance = self.variance
@@ -81,20 +100,41 @@
if standard_deviation = self.standard_deviation
standard_deviation / Math.sqrt(@samples.count)
end
end
+ def add(duration, result = nil)
+ @samples << duration
+ end
+
def measure
- start_time = Time.now
+ @count += 1
+ if @count > @concurrency
+ @concurrency = @count
+ end
+
+ start_time = Async::Clock.now
+
+ unless @start_time
+ @start_time = start_time
+ end
+
result = yield
- duration = Time.now - start_time
- @samples << duration
- @duration += duration
+ end_time = Async::Clock.now
+ self.add(end_time - start_time, result)
+
return result
+ ensure
+ @count -= 1
+
+ if @count == 0
+ @total_time += end_time - @start_time
+ @start_time = nil
+ end
end
def sample(confidence_factor, &block)
# warmup
yield
@@ -103,16 +143,49 @@
measure(&block)
end until confident?(confidence_factor)
end
def print(out = STDOUT)
- out.puts "#{@samples.count} samples. #{1.0 / self.average} per second. S/D: #{standard_deviation}."
+ if self.valid?
+ out.puts "#{@samples.count} samples. #{per_second} requests per second. S/D: #{Seconds[standard_deviation]}."
+ else
+ out.puts "Not enough samples."
+ end
end
private
def confident?(factor)
- (@samples.count > @concurrency) && self.standard_error < (self.average * factor)
+ if @samples.count > @concurrency
+ return self.standard_error < (self.average * factor)
+ end
+
+ return false
+ end
+ end
+
+ class Statistics < Stopwatch
+ def initialize(*)
+ super
+
+ # The count of the status codes seen in the responses:
+ @responses = Hash.new{|h,k| 0}
+ end
+
+ def add(duration, result)
+ super
+
+ @responses[result.status] += 1
+ end
+
+ def print(out = STDOUT)
+ if valid?
+ counts = @responses.sort.collect{|status, count| "#{count}x #{status}"}.join("; ")
+
+ out.puts "#{@samples.count} samples: #{counts}. #{per_second.round(2)} requests per second. S/D: #{Seconds[standard_deviation]}."
+ else
+ out.puts "Not enough samples."
+ end
end
end
end
end