Skip to content

Commit 876bf7c

Browse files
committed
Improve summary of allocations and time spent rendering
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.
1 parent 2924ee0 commit 876bf7c

File tree

2 files changed

+33
-8
lines changed

2 files changed

+33
-8
lines changed

lib/stimulus_reflex/instrumentation.rb

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,42 @@ def self.track(reflex)
66
return yield unless reflex.logger && StimulusReflex.config.instrument_reflexes
77

88
events = []
9+
start_allocations = current_allocations
910

10-
time = Benchmark.realtime do
11-
ActiveSupport::Notifications.subscribed(proc { |event| events << event }, /^sql.active_record|^render/) do
11+
total_time = Benchmark.ms do
12+
ActiveSupport::Notifications.subscribed(proc { |event| events << event }, /^sql.active|reflex.render|reflex.sql_render/) do
1213
yield
1314
end
1415
end
1516

16-
sql, views = events.partition { |e| e.name.match?(/^sql/) }
17+
end_allocations = current_allocations
18+
sql, rendering = events.partition { |e| e.name.match?(/^sql/) }
1719

18-
reflex.logger.info "Processed #{reflex.class.name}##{reflex.method_name} in #{(time * 1000).round(1)}ms " \
19-
"(Views: #{views.sum(&:duration).round(1)}ms | " \
20-
"ActiveRecord: #{sql.sum(&:duration).round(1)}ms | " \
21-
"Allocations: #{events.sum(&:allocations)}) \n"
20+
reflex.logger.info "Processed #{reflex.class.name}##{reflex.method_name} in #{total_time.round(1)}ms " \
21+
"(Views: #{views_total(rendering).round(1)}ms | " \
22+
"ActiveRecord: #{sql.sum(&:duration).round(1)}ms | " \
23+
"Allocations: #{end_allocations - start_allocations}) \n"
24+
end
25+
26+
def self.instrument_render(reflex, event_name)
27+
return yield unless reflex.logger && StimulusReflex.config.instrument_reflexes
28+
29+
callback = proc { |event| ActiveSupport::Notifications.instrument("reflex.sql_render", {sql_duration: event.duration}) }
30+
31+
ActiveSupport::Notifications.instrument(event_name) do
32+
ActiveSupport::Notifications.subscribed(callback, /^sql.active/) do
33+
yield
34+
end
35+
end
36+
end
37+
38+
def self.views_total(events)
39+
rendering, querying = events.partition { |event| event.name.match?(/^reflex.render/) }
40+
rendering.sum(&:duration) - querying.sum { |event| event.payload[:sql_duration] }
41+
end
42+
43+
def self.current_allocations
44+
GC.stat.key?(:total_allocated_objects) ? GC.stat(:total_allocated_objects) : 0
2245
end
2346
end
2447
end

lib/stimulus_reflex/reflex.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,9 @@ def render(*args)
118118
options = args.extract_options!
119119
(options[:locals] ||= {}).reverse_merge!(params: params)
120120
args << options.reverse_merge(layout: false)
121-
controller_class.renderer.new(connection.env.merge("SCRIPT_NAME" => "")).render(*args)
121+
StimulusReflex::Instrumentation.instrument_render(self, "reflex.render") do
122+
controller_class.renderer.new(connection.env.merge("SCRIPT_NAME" => "")).render(*args)
123+
end
122124
end
123125

124126
# Invoke the reflex action specified by `name` and run all callbacks

0 commit comments

Comments
 (0)