barinek.com: the official website

Below you'll find a quick stream of consciousness that includes photos, quotes, code snippets, and possibly even a full post. enjoy!

January 5

Stopwatch


require 'singleton'

module Stopwatch

  if defined?(ActiveRecord) && defined?(ActiveRecord::ConnectionAdapters)
    ActiveRecord::ConnectionAdapters::AbstractAdapter.class_eval do
      def log_with_watcher(sql, name = "SQL", binds = [], &block)
        Stopwatch.instance.event("sql '#{name}'.") do
          log_without_watcher(sql, name, binds, &block)
        end
      end

      alias_method_chain :log, :watcher
    end
  end

  if defined?(ActionController)
    ActionController::Base.class_eval do
      def render_with_watcher(*args, &block)
        Stopwatch.instance.event("render.") do
          render_without_watcher(*args, &block)
        end
      end

      alias_method_chain :render, :watcher
    end
  end

  def self.watch(&block)
    watcher = Watcher.instance
    running = watcher.running
    watcher.start
    response = yield watcher
    watcher.stop unless running
    response
  end

  def self.instance
    Stopwatch::Watcher.instance
  end

  def self.reset
    Stopwatch::Watcher.instance.reset
  end

  class Split
    attr_reader :time, :message

    def initialize(time, message)
      @time = time
      @message = message
    end
  end

  class Event
    attr_reader :duration, :message

    def initialize(duration, message)
      @duration = duration
      @message = message
    end
  end

  class Tally
    attr_reader :duration, :message, :count

    def initialize(message)
      @message = message
      @duration = 0.0
      @count = 0.0
    end

    def add(event)
      @duration += event.duration
      @count += 1
    end
  end

  class Watcher
    include Singleton

    attr_reader :running, :splits, :events

    def initialize
      @running = false
      @splits = []
      @events = []
    end

    def start
      return if @running
      reset
      @running = true
      @start = Time.now
      puts "\n[stopwatch @time=#{human_time(0.0)}] started."
    end

    def stop
      @stop = Time.now
      @running = false
      puts "[stopwatch @time=#{human_time(@stop - @start)}] stopped."
    end

    def reset
      @running = false
      @splits = []
      @events = []
    end

    def split(message)
      split_time = Time.now
      @splits.push Split.new(split_time, message)
    end

    def event(message)
      start = Time.now
      result = yield
      stop = Time.now
      duration = stop - start
      @events.push Event.new(duration, message) if @running
      result
    end

    def report
      report_splits
      report_events
    end

    private

    def report_splits
      last_time = nil
      @splits.each do |split|
        time = split.time
        message = split.message
        elapsed = last_time ? time - last_time : time - @start
        last_time = time
        puts "[stopwatch @time=#{human_time(elapsed)}] split #{message} "
      end unless @splits.empty?
    end

    def report_events
      tallies = {}
      @events.each do |event|
        tallies[event.message] = Tally.new(event.message) unless tallies[event.message]
        tallies[event.message].add(event)
      end
      tallies.each do |key,tally|
        puts "[stopwatch @time=#{human_time(tally.duration)}] event #{tally.message} (#{tally.count.to_i})"
      end
    end

    def human_time(time)
      "%.2fms" % (time * 1_000)
    end

  end

end



require "test/unit"

require File.expand_path(File.join(File.dirname(__FILE__), "stopwatch"))

class TestStopwatch  Test::Unit::TestCase

  class ExampleLogger
    include Singleton

    def log(message)
      p message
    end

    def self.alias_method_chain(target, feature)
      alias_method "#{target}_without_#{feature}", target
      alias_method target, "#{target}_with_#{feature}"
    end
  end

  ExampleLogger.class_eval do
    def log_with_test(message, &block)
      Stopwatch.instance.event("a") do
        log_without_test(message, &block)
      end
    end

    alias_method_chain :log, :test
  end

  def setup
    Stopwatch.reset
  end

  def test_watch
    assert(!Stopwatch.instance.running)
    Stopwatch.watch do
      assert(Stopwatch.instance.running)
    end
    assert(!Stopwatch.instance.running)
  end

  def test_splits
    Stopwatch.watch do |watcher|
      watcher.split("a")
      watcher.split("b")
      watcher.split("c")
    end
    assert_equal(Stopwatch.instance.splits.size, 3)
    assert_equal(Stopwatch.instance.splits.collect { |split| split.message }, ["a", "b", "c"])
  end

  def test_nested
    Stopwatch.watch do |watcher|
      watcher.split("a")
      Stopwatch.watch do |watcher|
        watcher.split("b")
      end
      assert(Stopwatch.instance.running)
    end
    assert_equal(Stopwatch.instance.splits.size, 2)
    assert_equal(Stopwatch.instance.splits.collect { |split| split.message }, ["a", "b"])
  end

  def test_split_report
    Stopwatch.watch do |watcher|
      watcher.split("a")
      watcher.split("b")
      watcher.split("c")
      watcher.report
    end
  end

  def test_events
    Stopwatch.watch do |watcher|
      ExampleLogger.instance.log("message")
      Stopwatch.instance.event("b") do
      end
    end
    assert_equal(Stopwatch.instance.events.size, 2)
    assert_equal(Stopwatch.instance.events.collect { |event| event.message }, ["a", "b"])
  end

  def test_no_events
    Stopwatch.watch do |watcher|
    end
    ExampleLogger.instance.log("message")
    Stopwatch.instance.event("b") do
    end
    assert_equal(Stopwatch.instance.events.size, 0)
  end

end

gipoco.com is neither affiliated with the authors of this page nor responsible for its contents. This is a safe-cache copy of the original web site.