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