module Ecoportal module API module Common module Concerns # @note While benchmarking with multi-thread gives correct results # for top unique block, will calculate the real time of each individual # thread, which is way higher than a single thread. However, as they # run in parallel, the total time would be the valid reference. module Benchmarkable private def benchmark_enabled? @benchmark_enabled = true if @benchmark_enabled.nil? @benchmark_enabled end def benchmarking(ref = nil, print: false) return yield unless benchmark_enabled? benchmark_mem(ref, print: false) do benchmark_time(ref, print: false) do yield end end.tap do puts "\n#{bench_summary_ref(ref)}" if print end end def benchmark_mem(ref = nil, print: false) return yield unless benchmark_enabled? memory_before = memory_usage yield.tap do memory_after = memory_usage mb_footprint = ((memory_after - memory_before) / 1024.0).round(2) bench_add_mem(ref, mb_footprint) puts bench_str_mem(mb, ref: ref, reffix: true) if print end end def benchmark_time(ref = nil, print: false) return yield unless benchmark_enabled? result = nil time = benchmark.realtime do result = yield end.round(2) result.tap do bench_add_time(ref, time) puts bench_str_time(time, ref: ref, reffix: true) if print end end def benchmark_summary(ref = nil) return '' unless benchmark_enabled? return bench_summary_ref(ref) unless ref == :all bench.keys.each_with_object([]) do |ref, out| out << bench_summary_ref(ref) end.join("\n") end def bench_summary_ref(ref = nil) ref_lines = bench_summary_ref_lines(ref) "- #{ref}\n" + " • " + ref_lines.join("\n • ") end def bench_summary_ref_lines(ref = nil) ref_bench = bench_get(ref) [ bench_str_time(*ref_bench[:time].values_at(:avg, :cnt), ref: ref), bench_str_mem(*ref_bench[:mem].values_at(:avg, :cnt), ref: ref) ] end def benchmark require 'benchmark' Benchmark end def bench @bench ||= {} end def bench_add_mem(ref, mem) bench_data_push(bench_get(ref)[:mem], mem) end def bench_add_time(ref, time) bench_data_push(bench_get(ref)[:time], time) end def bench_get(ref) bench[ref] ||= { time: bench_data, mem: bench_data } end def bench_data { avg: nil, cnt: 0 } end def bench_data_push(data, value) total = value + ( (data[:avg] || 0) * data[:cnt]) data[:cnt] += 1 data[:avg] = (total / data[:cnt]).round(3) data end def bench_str_mem(mem, count = nil, ref: nil, reffix: false) ref = reffix ? ref : nil msg = [ref, 'Memory'].compact.join(' -- ') cnt = count ? " (cnt: #{count})" : '' "#{msg}: #{mem} MB#{cnt}" end def active_support_duration? return false unless Kernel.const_defined?(:ActiveSupport) ActiveSupport.const_defined?(:Duration, false) end def bench_str_time(time, count = nil, ref: nil, reffix: false) ref = reffix ? ref : nil msg = [ref, 'Time'].compact.join(' -- ') total = (time * count).round(2) str_desc = '' if active_support_duration? && total >= 60 duration = ActiveSupport::Duration.build(total.round) str_desc = ": #{duration_to_s(duration)}" end cnt = count ? " (cnt: #{count}; sum: #{total} s#{str_desc})" : '' "#{msg}: #{time} s#{cnt}" end def duration_to_s(value) return "" unless active_support_duration? return "" if value.nil? raise ArgumentError, "Expecint ActiveSupport::Duration. Given: #{value.class}" unless value.is_a?(ActiveSupport::Duration) parts = value.parts.map {|pair| pair.reverse.join(" ")} return parts.first if parts.length == 1 [parts[..-2].join(", "), parts.last].join(" and ") end # @return [Integer] total memory used by this process in KB def memory_usage if Gem.win_platform? wmem = `wmic process where processid=#{Process.pid} get WorkingSetSize | findstr "[0-9]"` return 0 unless wmem wmem.lines.first.chop.strip.to_i / 1024.0 else `ps -o rss= -p #{Process.pid}`.to_i end end end end end end end