Skip to content

Commit

Permalink
Improve summary of allocations and time spent rendering
Browse files Browse the repository at this point in the history
It turns out emitted `render` events from Rails overlap cumulatively if you render a partial in a partial, and there's no nice way to differentiate parent partials from children, so the duration and allocations counts can be way off when the totals from the events are summed. View time can end up being twice as much as the total time, which is obviously impossible. Rails cheats a bit for it's rendering totals by taking the whole page template, but that doesn't work for SR, so allocations and rendering time have to be calculated a bit differently.
  • Loading branch information
Matt-Yorkley committed Mar 23, 2024
1 parent 2924ee0 commit ddcdc0c
Show file tree
Hide file tree
Showing 2 changed files with 27 additions and 9 deletions.
28 changes: 21 additions & 7 deletions lib/stimulus_reflex/instrumentation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,33 @@ def self.track(reflex)
return yield unless reflex.logger && StimulusReflex.config.instrument_reflexes

events = []
start_allocations = current_allocations

time = Benchmark.realtime do
ActiveSupport::Notifications.subscribed(proc { |event| events << event }, /^sql.active_record|^render/) do
total_time = Benchmark.ms do
ActiveSupport::Notifications.subscribed(proc { |event| events << event }, /^sql.active_record|reflex.render/) do
yield
end
end

sql, views = events.partition { |e| e.name.match?(/^sql/) }
end_allocations = current_allocations
sql, rendering = events.partition { |e| e.name.match?(/^sql/) }

reflex.logger.info "Processed #{reflex.class.name}##{reflex.method_name} in #{(time * 1000).round(1)}ms " \
"(Views: #{views.sum(&:duration).round(1)}ms | " \
"ActiveRecord: #{sql.sum(&:duration).round(1)}ms | " \
"Allocations: #{events.sum(&:allocations)}) \n"
reflex.logger.info "Processed #{reflex.class.name}##{reflex.method_name} in #{total_time.round(1)}ms " \
"(Views: #{rendering.sum(&:duration).round(1)}ms | " \
"ActiveRecord: #{sql.sum(&:duration).round(1)}ms | " \
"Allocations: #{end_allocations - start_allocations}) \n"
end

def self.instrument(reflex, event_name)
return yield unless reflex.logger && StimulusReflex.config.instrument_reflexes

ActiveSupport::Notifications.instrument event_name, this: :data do
yield
end
end

def self.current_allocations
GC.stat.key?(:total_allocated_objects) ? GC.stat(:total_allocated_objects) : 0
end
end
end
8 changes: 6 additions & 2 deletions lib/stimulus_reflex/reflex.rb
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,9 @@ def render(*args)
options = args.extract_options!
(options[:locals] ||= {}).reverse_merge!(params: params)
args << options.reverse_merge(layout: false)
controller_class.renderer.new(connection.env.merge("SCRIPT_NAME" => "")).render(*args)
StimulusReflex::Instrumentation.instrument self, "reflex.render" do
controller_class.renderer.new(connection.env.merge("SCRIPT_NAME" => "")).render(*args)
end
end

# Invoke the reflex action specified by `name` and run all callbacks
Expand Down Expand Up @@ -154,6 +156,8 @@ def params
# eg. `morph dom_id(@posts), render_collection(@posts)`
def render_collection(resource, content = nil)
content ||= render(resource)
tag.div(content.html_safe, id: dom_id(resource).from(1))
StimulusReflex::Instrumentation.instrument self, "reflex.render" do
tag.div(content.html_safe, id: dom_id(resource).from(1))
end
end
end

0 comments on commit ddcdc0c

Please sign in to comment.