-
-
Save afa/42e2595ec93de1fdb483fa97f52a18ef to your computer and use it in GitHub Desktop.
FactoryProf: profiler for your FactoryGirl
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class FactoryProf | |
module FloatDuration | |
refine Float do | |
def duration | |
t = self | |
format("%02d:%02d.%03d", t / 60, t % 60, t.modulo(1) * 1000) | |
end | |
end | |
end | |
using FloatDuration | |
module RunnerExt | |
def run(strategy = @strategy) | |
return super unless strategy == :create | |
FactoryProf.tracker.track(@name) do | |
super | |
end | |
end | |
end | |
class << self | |
attr_reader :tracker | |
def init | |
FactoryGirl::FactoryRunner.prepend RunnerExt | |
@flamegraph = ENV['FPROF'] == 'flamegraph' | |
@tracker = flamegraph? ? FlamegraphTracker.new : Tracker.new | |
end | |
def flamegraph? | |
@flamegraph == true | |
end | |
end | |
class Tracker | |
def initialize | |
@depth = 0 | |
end | |
def track(factory) | |
@depth += 1 | |
res = nil | |
begin | |
res = if @depth == 1 | |
ActiveSupport::Notifications.instrument('factory.create', name: factory) { yield } | |
else | |
yield | |
end | |
ensure | |
@depth -= 1 | |
end | |
res | |
end | |
end | |
module FlamegraphRendererExt | |
def graph_data | |
table = [] | |
prev = [] | |
@stacks.each_with_index do |stack, _pos| | |
next unless stack | |
col = [] | |
stack.each_with_index do |(frame, _time), i| | |
if !prev[i].nil? | |
last_col = prev[i] | |
if last_col[0] == frame | |
last_col[1] += 1 | |
col << nil | |
next | |
end | |
end | |
prev[i] = [frame, 1] | |
col << prev[i] | |
end | |
prev = prev[0..col.length - 1].to_a | |
table << col | |
end | |
data = [] | |
table.each_with_index do |col, col_num| | |
col.each_with_index do |row, row_num| | |
next unless row && row.length == 2 | |
data << { | |
x: col_num + 1, | |
y: row_num + 1, | |
width: row[1], | |
frame: "`#{row[0]}" | |
} | |
end | |
end | |
data | |
end | |
end | |
class FlamegraphTracker | |
class Stack < Array | |
attr_reader :fingerprint | |
def initialize | |
super | |
@fingerprint = '' | |
end | |
def <<(sample) | |
@fingerprint += ":#{sample.first}" | |
super | |
end | |
end | |
def initialize | |
require "flamegraph" | |
Flamegraph::Renderer.prepend(FlamegraphRendererExt) | |
@stacks = [] | |
@depth = 0 | |
@total_time = 0.0 | |
@current_stack = Stack.new | |
at_exit { render_flamegraph } | |
end | |
def track(factory) | |
@depth += 1 | |
start = Time.now | |
sample = [factory] | |
@current_stack << sample | |
res = nil | |
begin | |
res = yield | |
ensure | |
sample << (Time.now - start) | |
@depth -= 1 | |
flush_sample if @depth.zero? | |
end | |
res | |
end | |
def flush_sample | |
@total_time += @current_stack.first.last | |
@stacks << @current_stack | |
@current_stack = Stack.new | |
end | |
def render_flamegraph | |
sorted_stacks = @stacks.sort_by(&:fingerprint) | |
renderer = Flamegraph::Renderer.new(sorted_stacks) | |
rendered = renderer.graph_html(false) | |
filename = "tmp/factory-flame-#{Time.now.to_i}.html" | |
File.open(Rails.root.join(filename), "w") do |f| | |
f.write(rendered) | |
end | |
puts "\nFlamegraph written to #{filename}" | |
puts "\nTotal time: #{@total_time.duration}" | |
end | |
end | |
end | |
FactoryProf.init if ENV['FPROF'] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# if you want to render flamegraphs | |
gem "stackprof", require: false # required by flamegraph | |
gem "flamegraph", require: false |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment