module Timer class TimerNode attr_reader :timer attr_reader :label def initialize(timer, label) @timer, @label = timer, label end def label_width() label&.size || 0 end def value_width() raise NotThis end def total() raise NotThis end protected def format(time) sprintf "%.#{timer.scale}f", timer.factor * time end end class TimerElement < TimerNode attr_accessor :time def initialize(timer, label, time) super(timer, label) @time = time end def value_width() value.size end def value() @value ||= format time end def total() @time end protected def dump_element(file, indent, label_width, value_width) file.printf "#{indent}%-#{label_width}s: %#{value_width}s#{timer.unit}\n", label, value end end class TimerGroup < TimerNode attr_accessor :unit # Currently only :s, or :ms. Default is :ms def factor() { s: 1, ms: 1000 }[unit] end attr_accessor :scale # Number of digits after the decimal point attr_reader :nodes # Array of tuples of label and time or TimerGroup objects def initialize(timer, label = nil, unit: :ms, scale: 2) super(timer, label) @unit = unit.to_sym @scale = scale @nodes = [] @nodes_by_label = {} end def time(label, &block) t0 = Time.now r = yield dt = Time.now - t0 if @nodes_by_label.key?(label) @nodes_by_label[label].time += dt else element = TimerElement.new(self, label, dt) @nodes << element @nodes_by_label[label] = element end r end def group(label, **opts) r = TimerGroup.new(self, label, **{ unit: unit, scale: scale }.merge(opts)) @nodes << r r end def total() @nodes.inject(0) { |a,e| a + e.total } end def dump(file = $stdout, indent = "") dump_element(file, indent, label_width, value_width) end protected def label_width() if label ([label.size - 2] + @nodes.map { |node| node.send(:label_width) }).max + 2 else @nodes.map { |node| node.send :label_width }.max end end def value_width() @value_width ||= @nodes.map { |node| node.send :value_width }.max end def dump_element(file, indent, label_width, value_width) if label file.puts "#{indent}#{label} (#{format(total)}#{timer.unit})" nodes.each { |node| node.send :dump_element, file, indent + " ", label_width - 2, value_width } else nodes.each { |node| node.send :dump_element, file, indent, label_width, value_width } end end end class Timer < TimerGroup def initialize(label = nil, **opts) super(self, label, **opts) end def dump(file = $stdout) super if @nodes.size > 1 file.printf "%-#{label_width}s: %#{value_width}s#{timer.unit}\n", "Total", format(total) end end end end