summaryrefslogtreecommitdiff
path: root/vendor/bundle/ruby/3.4.0/gems/liquid-4.0.4/lib/liquid/profiler.rb
blob: dc9db60a7847c3881d1821986c1c464c233544e4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
require 'liquid/profiler/hooks'

module Liquid
  # Profiler enables support for profiling template rendering to help track down performance issues.
  #
  # To enable profiling, first require 'liquid/profiler'.
  # Then, to profile a parse/render cycle, pass the <tt>profile: true</tt> option to <tt>Liquid::Template.parse</tt>.
  # After <tt>Liquid::Template#render</tt> is called, the template object makes available an instance of this
  # class via the <tt>Liquid::Template#profiler</tt> method.
  #
  #   template = Liquid::Template.parse(template_content, profile: true)
  #   output  = template.render
  #   profile = template.profiler
  #
  # This object contains all profiling information, containing information on what tags were rendered,
  # where in the templates these tags live, and how long each tag took to render.
  #
  # This is a tree structure that is Enumerable all the way down, and keeps track of tags and rendering times
  # inside of <tt>{% include %}</tt> tags.
  #
  #   profile.each do |node|
  #     # Access to the node itself
  #     node.code
  #
  #     # Which template and line number of this node.
  #     # If top level, this will be "<root>".
  #     node.partial
  #     node.line_number
  #
  #     # Render time in seconds of this node
  #     node.render_time
  #
  #     # If the template used {% include %}, this node will also have children.
  #     node.children.each do |child2|
  #       # ...
  #     end
  #   end
  #
  # Profiler also exposes the total time of the template's render in <tt>Liquid::Profiler#total_render_time</tt>.
  #
  # All render times are in seconds. There is a small performance hit when profiling is enabled.
  #
  class Profiler
    include Enumerable

    class Timing
      attr_reader :code, :partial, :line_number, :children

      def initialize(node, partial)
        @code        = node.respond_to?(:raw) ? node.raw : node
        @partial     = partial
        @line_number = node.respond_to?(:line_number) ? node.line_number : nil
        @children    = []
      end

      def self.start(node, partial)
        new(node, partial).tap(&:start)
      end

      def start
        @start_time = Time.now
      end

      def finish
        @end_time = Time.now
      end

      def render_time
        @end_time - @start_time
      end
    end

    def self.profile_node_render(node)
      if Profiler.current_profile && node.respond_to?(:render)
        Profiler.current_profile.start_node(node)
        output = yield
        Profiler.current_profile.end_node(node)
        output
      else
        yield
      end
    end

    def self.profile_children(template_name)
      if Profiler.current_profile
        Profiler.current_profile.push_partial(template_name)
        output = yield
        Profiler.current_profile.pop_partial
        output
      else
        yield
      end
    end

    def self.current_profile
      Thread.current[:liquid_profiler]
    end

    def initialize
      @partial_stack = ["<root>"]

      @root_timing = Timing.new("", current_partial)
      @timing_stack = [@root_timing]

      @render_start_at = Time.now
      @render_end_at = @render_start_at
    end

    def start
      Thread.current[:liquid_profiler] = self
      @render_start_at = Time.now
    end

    def stop
      Thread.current[:liquid_profiler] = nil
      @render_end_at = Time.now
    end

    def total_render_time
      @render_end_at - @render_start_at
    end

    def each(&block)
      @root_timing.children.each(&block)
    end

    def [](idx)
      @root_timing.children[idx]
    end

    def length
      @root_timing.children.length
    end

    def start_node(node)
      @timing_stack.push(Timing.start(node, current_partial))
    end

    def end_node(_node)
      timing = @timing_stack.pop
      timing.finish

      @timing_stack.last.children << timing
    end

    def current_partial
      @partial_stack.last
    end

    def push_partial(partial_name)
      @partial_stack.push(partial_name)
    end

    def pop_partial
      @partial_stack.pop
    end
  end
end