Last active
April 27, 2024 02:02
-
-
Save palkan/ee031c4f857e33ca8c7ff28f5c2df117 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