rack-mini-profiler 0.9.4 → 0.9.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of rack-mini-profiler might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +42 -1
- data/README.md +50 -25
- data/lib/html/includes.css +12 -0
- data/lib/html/includes.js +53 -18
- data/lib/html/includes.less +17 -3
- data/lib/html/profile_handler.js +1 -1
- data/lib/mini_profiler/asset_version.rb +1 -1
- data/lib/mini_profiler/config.rb +9 -4
- data/lib/mini_profiler/gc_profiler.rb +18 -42
- data/lib/mini_profiler/profiler.rb +113 -89
- data/lib/mini_profiler/profiling_methods.rb +17 -11
- data/lib/mini_profiler/storage/file_store.rb +10 -2
- data/lib/mini_profiler/timer_struct/page.rb +8 -0
- data/lib/mini_profiler/timer_struct/request.rb +8 -0
- data/lib/mini_profiler/timer_struct/sql.rb +2 -2
- data/lib/mini_profiler/version.rb +1 -1
- data/lib/mini_profiler_rails/railtie.rb +23 -5
- data/lib/patches/db/activerecord.rb +2 -0
- data/lib/patches/db/mongo.rb +16 -0
- data/lib/patches/db/neo4j.rb +14 -0
- data/lib/patches/db/nobrainer.rb +29 -0
- data/lib/patches/db/oracle_enhanced.rb +70 -0
- data/lib/patches/db/riak.rb +103 -0
- data/lib/patches/sql_patches.rb +18 -7
- data/rack-mini-profiler.gemspec +1 -1
- metadata +9 -4
data/lib/mini_profiler/config.rb
CHANGED
@@ -12,10 +12,13 @@ module Rack
|
|
12
12
|
@attributes
|
13
13
|
end
|
14
14
|
|
15
|
-
attr_accessor :authorization_mode, :auto_inject, :backtrace_ignores,
|
16
|
-
:
|
17
|
-
:
|
18
|
-
:
|
15
|
+
attr_accessor :authorization_mode, :auto_inject, :backtrace_ignores,
|
16
|
+
:backtrace_includes, :backtrace_remove, :backtrace_threshold_ms,
|
17
|
+
:base_url_path, :disable_caching, :disable_env_dump, :enabled,
|
18
|
+
:flamegraph_sample_rate, :logger, :position, :pre_authorize_cb,
|
19
|
+
:skip_paths, :skip_schema_queries, :start_hidden, :storage,
|
20
|
+
:storage_failure, :storage_instance, :storage_options, :toggle_shortcut,
|
21
|
+
:user_provider, :collapse_results
|
19
22
|
|
20
23
|
# Deprecated options
|
21
24
|
attr_accessor :use_existing_jquery
|
@@ -44,6 +47,8 @@ module Rack
|
|
44
47
|
end
|
45
48
|
end
|
46
49
|
@enabled = true
|
50
|
+
@disable_env_dump = false
|
51
|
+
@collapse_results = true
|
47
52
|
self
|
48
53
|
}
|
49
54
|
end
|
@@ -6,18 +6,15 @@ class Rack::MiniProfiler::GCProfiler
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def object_space_stats
|
9
|
-
stats =
|
10
|
-
ids =
|
9
|
+
stats = Hash.new(0).compare_by_identity
|
10
|
+
ids = Hash.new.compare_by_identity
|
11
11
|
|
12
12
|
@ignore << stats.__id__
|
13
13
|
@ignore << ids.__id__
|
14
14
|
|
15
|
-
i=0
|
16
15
|
ObjectSpace.each_object { |o|
|
17
16
|
begin
|
18
|
-
|
19
|
-
i += 1
|
20
|
-
stats[o.class] = i
|
17
|
+
stats[o.class] += 1
|
21
18
|
ids[o.__id__] = o if Integer === o.__id__
|
22
19
|
rescue NoMethodError
|
23
20
|
# protect against BasicObject
|
@@ -38,12 +35,12 @@ class Rack::MiniProfiler::GCProfiler
|
|
38
35
|
end
|
39
36
|
|
40
37
|
def diff_object_stats(before, after)
|
41
|
-
diff = {}
|
38
|
+
diff = {}.compare_by_identity
|
42
39
|
after.each do |k,v|
|
43
|
-
diff[k] = v -
|
40
|
+
diff[k] = v - before[k]
|
44
41
|
end
|
45
42
|
before.each do |k,v|
|
46
|
-
diff[k] = 0 - v unless after
|
43
|
+
diff[k] = 0 - v unless after.has_key?(k)
|
47
44
|
end
|
48
45
|
|
49
46
|
diff
|
@@ -92,36 +89,11 @@ class Rack::MiniProfiler::GCProfiler
|
|
92
89
|
[objects,memory_allocated]
|
93
90
|
end
|
94
91
|
|
95
|
-
def profile_gc_time(app, env)
|
96
|
-
body = []
|
97
|
-
|
98
|
-
begin
|
99
|
-
GC::Profiler.clear
|
100
|
-
prev_profiler_state = GC::Profiler.enabled?
|
101
|
-
prev_gc_state = GC.enable
|
102
|
-
GC::Profiler.enable
|
103
|
-
b = app.call(env)[2]
|
104
|
-
b.close if b.respond_to? :close
|
105
|
-
body << "GC Profiler ran during this request, if it fired you will see the cost below:\n\n"
|
106
|
-
body << GC::Profiler.result
|
107
|
-
ensure
|
108
|
-
prev_gc_state ? GC.disable : GC.enable
|
109
|
-
GC::Profiler.disable unless prev_profiler_state
|
110
|
-
end
|
111
|
-
|
112
|
-
return [200, {'Content-Type' => 'text/plain'}, body]
|
113
|
-
end
|
114
|
-
|
115
92
|
def profile_gc(app, env)
|
116
93
|
|
117
94
|
# for memsize_of
|
118
95
|
require 'objspace'
|
119
96
|
|
120
|
-
body = [];
|
121
|
-
|
122
|
-
stat_before,stat_after,diff,string_analysis,
|
123
|
-
new_objects, memory_allocated, stat, memory_before, objects_before = nil
|
124
|
-
|
125
97
|
# clean up before
|
126
98
|
GC.start
|
127
99
|
stat = GC.stat
|
@@ -138,13 +110,17 @@ class Rack::MiniProfiler::GCProfiler
|
|
138
110
|
new_objects, memory_allocated = analyze_growth(stat_before[:ids], stat_after[:ids])
|
139
111
|
objects_before, memory_before = analyze_initial_state(stat_before[:ids])
|
140
112
|
|
113
|
+
body = []
|
141
114
|
|
142
115
|
body << "
|
143
116
|
Overview
|
144
|
-
|
145
|
-
Initial state: object count
|
117
|
+
--------
|
118
|
+
Initial state: object count: #{objects_before}
|
119
|
+
Memory allocated outside heap (bytes): #{memory_before}
|
146
120
|
|
147
|
-
GC Stats:
|
121
|
+
GC Stats:
|
122
|
+
--------
|
123
|
+
#{stat.map{|k,v| "#{k} : #{v}" }.sort!.join("\n")}
|
148
124
|
|
149
125
|
New bytes allocated outside of Ruby heaps: #{memory_allocated}
|
150
126
|
New objects: #{new_objects}
|
@@ -152,16 +128,16 @@ New objects: #{new_objects}
|
|
152
128
|
|
153
129
|
body << "
|
154
130
|
ObjectSpace delta caused by request:
|
155
|
-
|
156
|
-
diff.to_a.
|
157
|
-
body << "#{k} : #{v}\n"
|
131
|
+
-----------------------------------\n"
|
132
|
+
diff.to_a.delete_if{|_k, v| v == 0}.sort_by! { |_k, v| v }.reverse_each do |k,v|
|
133
|
+
body << "#{k} : #{v}\n"
|
158
134
|
end
|
159
135
|
|
160
136
|
body << "\n
|
161
137
|
ObjectSpace stats:
|
162
138
|
-----------------\n"
|
163
139
|
|
164
|
-
stat_after[:stats].to_a.
|
140
|
+
stat_after[:stats].to_a.sort_by!{ |_k, v| v }.reverse_each do |k,v|
|
165
141
|
body << "#{k} : #{v}\n"
|
166
142
|
end
|
167
143
|
|
@@ -170,7 +146,7 @@ ObjectSpace stats:
|
|
170
146
|
String stats:
|
171
147
|
------------\n"
|
172
148
|
|
173
|
-
string_analysis.to_a.
|
149
|
+
string_analysis.to_a.sort_by!{ |_k, v| -v }.take(1000).each do |string,count|
|
174
150
|
body << "#{count} : #{string}\n"
|
175
151
|
end
|
176
152
|
|
@@ -17,6 +17,10 @@ module Rack
|
|
17
17
|
@config ||= Config.default
|
18
18
|
end
|
19
19
|
|
20
|
+
def resources_root
|
21
|
+
@resources_root ||= ::File.expand_path("../../html", __FILE__)
|
22
|
+
end
|
23
|
+
|
20
24
|
def share_template
|
21
25
|
@share_template ||= ::File.read(::File.expand_path("../html/share.html", ::File.dirname(__FILE__)))
|
22
26
|
end
|
@@ -37,10 +41,11 @@ module Rack
|
|
37
41
|
|
38
42
|
def create_current(env={}, options={})
|
39
43
|
# profiling the request
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
+
context = Context.new
|
45
|
+
context.inject_js = config.auto_inject && (!env['HTTP_X_REQUESTED_WITH'].eql? 'XMLHttpRequest')
|
46
|
+
context.page_struct = TimerStruct::Page.new(env)
|
47
|
+
context.current_timer = context.page_struct[:root]
|
48
|
+
self.current = context
|
44
49
|
end
|
45
50
|
|
46
51
|
def authorize_request
|
@@ -92,24 +97,26 @@ module Rack
|
|
92
97
|
@storage.set_viewed(user(env), id)
|
93
98
|
end
|
94
99
|
|
95
|
-
result_json = page_struct.to_json
|
96
100
|
# If we're an XMLHttpRequest, serve up the contents as JSON
|
97
101
|
if request.xhr?
|
102
|
+
result_json = page_struct.to_json
|
98
103
|
[200, { 'Content-Type' => 'application/json'}, [result_json]]
|
99
104
|
else
|
100
|
-
|
101
105
|
# Otherwise give the HTML back
|
102
|
-
html =
|
103
|
-
html.gsub!(/\{path\}/, "#{env['RACK_MINI_PROFILER_ORIGINAL_SCRIPT_NAME']}#{@config.base_url_path}")
|
104
|
-
html.gsub!(/\{version\}/, MiniProfiler::ASSET_VERSION)
|
105
|
-
html.gsub!(/\{json\}/, result_json)
|
106
|
-
html.gsub!(/\{includes\}/, get_profile_script(env))
|
107
|
-
html.gsub!(/\{name\}/, page_struct[:name])
|
108
|
-
html.gsub!(/\{duration\}/, "%.1f" % page_struct.duration_ms)
|
109
|
-
|
106
|
+
html = generate_html(page_struct, env)
|
110
107
|
[200, {'Content-Type' => 'text/html'}, [html]]
|
111
108
|
end
|
109
|
+
end
|
112
110
|
|
111
|
+
def generate_html(page_struct, env, result_json = page_struct.to_json)
|
112
|
+
html = MiniProfiler.share_template.dup
|
113
|
+
html.sub!('{path}', "#{env['RACK_MINI_PROFILER_ORIGINAL_SCRIPT_NAME']}#{@config.base_url_path}")
|
114
|
+
html.sub!('{version}', MiniProfiler::ASSET_VERSION)
|
115
|
+
html.sub!('{json}', result_json)
|
116
|
+
html.sub!('{includes}', get_profile_script(env))
|
117
|
+
html.sub!('{name}', page_struct[:name])
|
118
|
+
html.sub!('{duration}', page_struct.duration_ms.round(1).to_s)
|
119
|
+
html
|
113
120
|
end
|
114
121
|
|
115
122
|
def serve_html(env)
|
@@ -117,21 +124,11 @@ module Rack
|
|
117
124
|
|
118
125
|
return serve_results(env) if file_name.eql?('results')
|
119
126
|
|
120
|
-
|
121
|
-
|
122
|
-
f = Rack::File.new nil
|
123
|
-
f.path = full_path
|
124
|
-
|
125
|
-
begin
|
126
|
-
f.cache_control = "max-age:86400"
|
127
|
-
f.serving env
|
128
|
-
rescue
|
129
|
-
# old versions of rack have a different api
|
130
|
-
status, headers, body = f.serving
|
131
|
-
headers.merge! 'Cache-Control' => "max-age:86400"
|
132
|
-
[status, headers, body]
|
133
|
-
end
|
127
|
+
resources_env = env.dup
|
128
|
+
resources_env['PATH_INFO'] = file_name
|
134
129
|
|
130
|
+
rack_file = Rack::File.new(MiniProfiler.resources_root, {'Cache-Control' => 'max-age:86400'})
|
131
|
+
rack_file.call(resources_env)
|
135
132
|
end
|
136
133
|
|
137
134
|
|
@@ -197,22 +194,27 @@ module Rack
|
|
197
194
|
client_settings.disable_profiling = false
|
198
195
|
end
|
199
196
|
|
197
|
+
# profile gc
|
200
198
|
if query_string =~ /pp=profile-gc/
|
201
199
|
current.measure = false if current
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
200
|
+
return Rack::MiniProfiler::GCProfiler.new.profile_gc(@app, env)
|
201
|
+
end
|
202
|
+
|
203
|
+
# profile memory
|
204
|
+
if query_string =~ /pp=profile-memory/
|
205
|
+
query_params = Rack::Utils.parse_nested_query(query_string)
|
206
|
+
options = {
|
207
|
+
:ignore_files => query_params['memory_profiler_ignore_files'],
|
208
|
+
:allow_files => query_params['memory_profiler_allow_files'],
|
209
|
+
}
|
210
|
+
options[:top]= Integer(query_params['memory_profiler_top']) if query_params.key?('memory_profiler_top')
|
211
|
+
result = StringIO.new
|
212
|
+
report = MemoryProfiler.report(options) do
|
213
|
+
_,_,body = @app.call(env)
|
214
|
+
body.close if body.respond_to? :close
|
215
215
|
end
|
216
|
+
report.pretty_print(result)
|
217
|
+
return text_result(result.string)
|
216
218
|
end
|
217
219
|
|
218
220
|
MiniProfiler.create_current(env, @config)
|
@@ -288,7 +290,7 @@ module Rack
|
|
288
290
|
if (config.authorization_mode == :whitelist && !MiniProfiler.request_authorized?)
|
289
291
|
# this is non-obvious, don't kill the profiling cookie on errors or short requests
|
290
292
|
# this ensures that stuff that never reaches the rails stack does not kill profiling
|
291
|
-
if status
|
293
|
+
if status.to_i >= 200 && status.to_i < 300 && ((Time.now - start) > 0.1)
|
292
294
|
client_settings.discard_cookie!(headers)
|
293
295
|
end
|
294
296
|
skip_it = true
|
@@ -302,7 +304,7 @@ module Rack
|
|
302
304
|
return dump_exceptions exceptions
|
303
305
|
end
|
304
306
|
|
305
|
-
if query_string =~ /pp=env/
|
307
|
+
if query_string =~ /pp=env/ && !config.disable_env_dump
|
306
308
|
body.close if body.respond_to? :close
|
307
309
|
return dump_env env
|
308
310
|
end
|
@@ -333,7 +335,7 @@ module Rack
|
|
333
335
|
@storage.save(page_struct)
|
334
336
|
|
335
337
|
# inject headers, script
|
336
|
-
if
|
338
|
+
if status >= 200 && status < 300
|
337
339
|
client_settings.write!(headers)
|
338
340
|
result = inject_profiler(env,status,headers,body)
|
339
341
|
return result if result
|
@@ -386,39 +388,17 @@ module Rack
|
|
386
388
|
end
|
387
389
|
|
388
390
|
def inject(fragment, script)
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
regex = /<\/html>/i
|
398
|
-
close_tag = '</html>'
|
399
|
-
else
|
400
|
-
# implicit </body> and </html>. Don't do anything.
|
401
|
-
|
402
|
-
return fragment
|
403
|
-
end
|
404
|
-
|
405
|
-
matches = fragment.scan(regex).length
|
406
|
-
index = 1
|
407
|
-
fragment.gsub(regex) do
|
408
|
-
# though malformed there is an edge case where /body exists earlier in the html, work around
|
409
|
-
if index < matches
|
410
|
-
index += 1
|
411
|
-
close_tag
|
412
|
-
else
|
413
|
-
|
414
|
-
# if for whatever crazy reason we dont get a utf string,
|
415
|
-
# just force the encoding, no utf in the mp scripts anyway
|
416
|
-
if script.respond_to?(:encoding) && script.respond_to?(:force_encoding)
|
417
|
-
(script + close_tag).force_encoding(fragment.encoding)
|
418
|
-
else
|
419
|
-
script + close_tag
|
420
|
-
end
|
391
|
+
# find explicit or implicit body
|
392
|
+
index = fragment.rindex(/<\/body>/i) || fragment.rindex(/<\/html>/i)
|
393
|
+
if index
|
394
|
+
# if for whatever crazy reason we dont get a utf string,
|
395
|
+
# just force the encoding, no utf in the mp scripts anyway
|
396
|
+
if script.respond_to?(:encoding) && script.respond_to?(:force_encoding)
|
397
|
+
script = script.force_encoding(fragment.encoding)
|
421
398
|
end
|
399
|
+
fragment.insert(index, script)
|
400
|
+
else
|
401
|
+
fragment
|
422
402
|
end
|
423
403
|
end
|
424
404
|
|
@@ -463,23 +443,50 @@ module Rack
|
|
463
443
|
def analyze_memory
|
464
444
|
require 'objspace'
|
465
445
|
|
446
|
+
utf8 = "utf-8"
|
447
|
+
|
448
|
+
GC.start
|
449
|
+
|
450
|
+
trunc = lambda do |str|
|
451
|
+
str = str.length > 200 ? str : str[0..200]
|
452
|
+
|
453
|
+
if str.encoding != Encoding::UTF_8
|
454
|
+
str = str.dup
|
455
|
+
str.force_encoding(utf8)
|
456
|
+
|
457
|
+
unless str.valid_encoding?
|
458
|
+
# work around bust string with a double conversion
|
459
|
+
str.encode!("utf-16","utf-8",:invalid => :replace)
|
460
|
+
str.encode!("utf-8","utf-16")
|
461
|
+
end
|
462
|
+
end
|
463
|
+
|
464
|
+
str
|
465
|
+
end
|
466
|
+
|
466
467
|
body = "ObjectSpace stats:\n\n"
|
467
468
|
|
468
|
-
|
469
|
+
counts = ObjectSpace.count_objects
|
470
|
+
total_strings = counts[:T_STRING]
|
471
|
+
|
472
|
+
body << counts
|
469
473
|
.sort{|a,b| b[1] <=> a[1]}
|
470
474
|
.map{|k,v| "#{k}: #{v}"}
|
471
475
|
.join("\n")
|
472
476
|
|
473
|
-
body << "\n\n\n1000 Largest strings:\n\n"
|
474
|
-
|
475
477
|
strings = []
|
476
|
-
|
478
|
+
string_counts = Hash.new(0)
|
479
|
+
sample_strings = []
|
477
480
|
|
478
|
-
|
479
|
-
|
481
|
+
max_size = 1000
|
482
|
+
sample_every = total_strings / max_size
|
480
483
|
|
484
|
+
i = 0
|
481
485
|
ObjectSpace.each_object(String) do |str|
|
482
|
-
|
486
|
+
i += 1
|
487
|
+
string_counts[str] += 1
|
488
|
+
strings << [trunc.call(str), str.length]
|
489
|
+
sample_strings << [trunc.call(str), str.length] if i % sample_every == 0
|
483
490
|
if strings.length > max_size * 2
|
484
491
|
trim_strings(strings, max_size)
|
485
492
|
end
|
@@ -487,7 +494,14 @@ module Rack
|
|
487
494
|
|
488
495
|
trim_strings(strings, max_size)
|
489
496
|
|
490
|
-
body <<
|
497
|
+
body << "\n\n\n1000 Largest strings:\n\n"
|
498
|
+
body << strings.map{|s,len| "#{s[0..1000]}\n(len: #{len})\n\n"}.join("\n")
|
499
|
+
|
500
|
+
body << "\n\n\n1000 Sample strings:\n\n"
|
501
|
+
body << sample_strings.map{|s,len| "#{s[0..1000]}\n(len: #{len})\n\n"}.join("\n")
|
502
|
+
|
503
|
+
body << "\n\n\n1000 Most common strings:\n\n"
|
504
|
+
body << string_counts.sort{|a,b| b[1] <=> a[1]}[0..max_size].map{|s,len| "#{trunc.call(s)}\n(x #{len})\n\n"}.join("\n")
|
491
505
|
|
492
506
|
text_result(body)
|
493
507
|
end
|
@@ -517,12 +531,11 @@ Append the following to your query string:
|
|
517
531
|
#{make_link "disable", env} : disable profiling for this session
|
518
532
|
#{make_link "enable", env} : enable profiling for this session (if previously disabled)
|
519
533
|
#{make_link "profile-gc", env} : perform gc profiling on this request, analyzes ObjectSpace generated by request (ruby 1.9.3 only)
|
520
|
-
#{make_link "profile-
|
521
|
-
#{make_link "profile-gc-ruby-head", env} : requires the memory_profiler gem, new location based report
|
534
|
+
#{make_link "profile-memory", env} : requires the memory_profiler gem, new location based report
|
522
535
|
#{make_link "flamegraph", env} : works best on Ruby 2.0, a graph representing sampled activity (requires the flamegraph gem).
|
523
536
|
#{make_link "flamegraph&flamegraph_sample_rate=1", env}: creates a flamegraph with the specified sample rate (in ms). Overrides value set in config
|
524
537
|
#{make_link "flamegraph_embed", env} : works best on Ruby 2.0, a graph representing sampled activity (requires the flamegraph gem), embedded resources for use on an intranet.
|
525
|
-
#{make_link "trace-exceptions", env} : requires Ruby 2.0, will return all the spots where your application raises
|
538
|
+
#{make_link "trace-exceptions", env} : requires Ruby 2.0, will return all the spots where your application raises exceptions
|
526
539
|
#{make_link "analyze-memory", env} : requires Ruby 2.0, will perform basic memory analysis of heap
|
527
540
|
</pre>
|
528
541
|
</body>
|
@@ -558,7 +571,17 @@ Append the following to your query string:
|
|
558
571
|
# * you have disabled auto append behaviour throught :auto_inject => false flag
|
559
572
|
# * you do not want script to be automatically appended for the current page. You can also call cancel_auto_inject
|
560
573
|
def get_profile_script(env)
|
561
|
-
path
|
574
|
+
path = if ENV["PASSENGER_BASE_URI"] then
|
575
|
+
# added because the SCRIPT_NAME workaround below then
|
576
|
+
# breaks running under a prefix as permitted by Passenger.
|
577
|
+
"#{ENV['PASSENGER_BASE_URI']}#{@config.base_url_path}"
|
578
|
+
elsif env["action_controller.instance"]
|
579
|
+
# Rails engines break SCRIPT_NAME; the following appears to discard SCRIPT_NAME
|
580
|
+
# since url_for appears documented to return any String argument unmodified
|
581
|
+
env["action_controller.instance"].url_for("#{@config.base_url_path}")
|
582
|
+
else
|
583
|
+
"#{env['RACK_MINI_PROFILER_ORIGINAL_SCRIPT_NAME']}#{@config.base_url_path}"
|
584
|
+
end
|
562
585
|
|
563
586
|
settings = {
|
564
587
|
:path => path,
|
@@ -570,7 +593,8 @@ Append the following to your query string:
|
|
570
593
|
:showControls => false,
|
571
594
|
:authorized => true,
|
572
595
|
:toggleShortcut => @config.toggle_shortcut,
|
573
|
-
:startHidden => @config.start_hidden
|
596
|
+
:startHidden => @config.start_hidden,
|
597
|
+
:collapseResults => @config.collapse_results
|
574
598
|
}
|
575
599
|
|
576
600
|
if current && current.page_struct
|