mini-sharp-hub 0.0.1
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.
- checksums.yaml +7 -0
- data/mini-sharp-hub.gemspec +12 -0
- data/rack-mini-profiler-4.0.1/CHANGELOG.md +424 -0
- data/rack-mini-profiler-4.0.1/README.md +542 -0
- data/rack-mini-profiler-4.0.1/lib/enable_rails_patches.rb +5 -0
- data/rack-mini-profiler-4.0.1/lib/generators/rack_mini_profiler/USAGE +9 -0
- data/rack-mini-profiler-4.0.1/lib/generators/rack_mini_profiler/install_generator.rb +13 -0
- data/rack-mini-profiler-4.0.1/lib/generators/rack_mini_profiler/templates/rack_mini_profiler.rb +8 -0
- data/rack-mini-profiler-4.0.1/lib/generators/rack_profiler/USAGE +2 -0
- data/rack-mini-profiler-4.0.1/lib/generators/rack_profiler/install_generator.rb +16 -0
- data/rack-mini-profiler-4.0.1/lib/html/dot.1.1.2.min.js +2 -0
- data/rack-mini-profiler-4.0.1/lib/html/includes.css +474 -0
- data/rack-mini-profiler-4.0.1/lib/html/includes.js +1520 -0
- data/rack-mini-profiler-4.0.1/lib/html/includes.scss +698 -0
- data/rack-mini-profiler-4.0.1/lib/html/includes.tmpl +333 -0
- data/rack-mini-profiler-4.0.1/lib/html/pretty-print.js +810 -0
- data/rack-mini-profiler-4.0.1/lib/html/profile_handler.js +1 -0
- data/rack-mini-profiler-4.0.1/lib/html/rack-mini-profiler.css +3 -0
- data/rack-mini-profiler-4.0.1/lib/html/rack-mini-profiler.js +2 -0
- data/rack-mini-profiler-4.0.1/lib/html/share.html +10 -0
- data/rack-mini-profiler-4.0.1/lib/html/speedscope/LICENSE +21 -0
- data/rack-mini-profiler-4.0.1/lib/html/speedscope/README.md +3 -0
- data/rack-mini-profiler-4.0.1/lib/html/speedscope/demangle-cpp.1768f4cc.js +4 -0
- data/rack-mini-profiler-4.0.1/lib/html/speedscope/favicon-16x16.f74b3187.png +0 -0
- data/rack-mini-profiler-4.0.1/lib/html/speedscope/favicon-32x32.bc503437.png +0 -0
- data/rack-mini-profiler-4.0.1/lib/html/speedscope/file-format-schema.json +324 -0
- data/rack-mini-profiler-4.0.1/lib/html/speedscope/fonts/source-code-pro-regular.css +8 -0
- data/rack-mini-profiler-4.0.1/lib/html/speedscope/fonts/source-code-pro-v13-regular.woff +0 -0
- data/rack-mini-profiler-4.0.1/lib/html/speedscope/fonts/source-code-pro-v13-regular.woff2 +0 -0
- data/rack-mini-profiler-4.0.1/lib/html/speedscope/import.8ae8aa3d.js +123 -0
- data/rack-mini-profiler-4.0.1/lib/html/speedscope/index.html +2 -0
- data/rack-mini-profiler-4.0.1/lib/html/speedscope/release.txt +3 -0
- data/rack-mini-profiler-4.0.1/lib/html/speedscope/reset.8c46b7a1.css +2 -0
- data/rack-mini-profiler-4.0.1/lib/html/speedscope/source-map.438fa06b.js +24 -0
- data/rack-mini-profiler-4.0.1/lib/html/speedscope/speedscope.f27db165.js +193 -0
- data/rack-mini-profiler-4.0.1/lib/html/vendor.js +848 -0
- data/rack-mini-profiler-4.0.1/lib/mini_profiler/actions.rb +157 -0
- data/rack-mini-profiler-4.0.1/lib/mini_profiler/asset_version.rb +6 -0
- data/rack-mini-profiler-4.0.1/lib/mini_profiler/client_settings.rb +127 -0
- data/rack-mini-profiler-4.0.1/lib/mini_profiler/config.rb +146 -0
- data/rack-mini-profiler-4.0.1/lib/mini_profiler/context.rb +14 -0
- data/rack-mini-profiler-4.0.1/lib/mini_profiler/gc_profiler.rb +158 -0
- data/rack-mini-profiler-4.0.1/lib/mini_profiler/profiling_methods.rb +167 -0
- data/rack-mini-profiler-4.0.1/lib/mini_profiler/snapshots_transporter.rb +111 -0
- data/rack-mini-profiler-4.0.1/lib/mini_profiler/storage/abstract_store.rb +97 -0
- data/rack-mini-profiler-4.0.1/lib/mini_profiler/storage/file_store.rb +179 -0
- data/rack-mini-profiler-4.0.1/lib/mini_profiler/storage/memcache_store.rb +105 -0
- data/rack-mini-profiler-4.0.1/lib/mini_profiler/storage/memory_store.rb +233 -0
- data/rack-mini-profiler-4.0.1/lib/mini_profiler/storage/redis_store.rb +353 -0
- data/rack-mini-profiler-4.0.1/lib/mini_profiler/storage.rb +7 -0
- data/rack-mini-profiler-4.0.1/lib/mini_profiler/timer_struct/base.rb +39 -0
- data/rack-mini-profiler-4.0.1/lib/mini_profiler/timer_struct/client.rb +90 -0
- data/rack-mini-profiler-4.0.1/lib/mini_profiler/timer_struct/custom.rb +25 -0
- data/rack-mini-profiler-4.0.1/lib/mini_profiler/timer_struct/page.rb +146 -0
- data/rack-mini-profiler-4.0.1/lib/mini_profiler/timer_struct/request.rb +186 -0
- data/rack-mini-profiler-4.0.1/lib/mini_profiler/timer_struct/sql.rb +89 -0
- data/rack-mini-profiler-4.0.1/lib/mini_profiler/timer_struct.rb +8 -0
- data/rack-mini-profiler-4.0.1/lib/mini_profiler/version.rb +8 -0
- data/rack-mini-profiler-4.0.1/lib/mini_profiler/views.rb +198 -0
- data/rack-mini-profiler-4.0.1/lib/mini_profiler.rb +670 -0
- data/rack-mini-profiler-4.0.1/lib/mini_profiler_rails/railtie.rb +234 -0
- data/rack-mini-profiler-4.0.1/lib/mini_profiler_rails/railtie_methods.rb +61 -0
- data/rack-mini-profiler-4.0.1/lib/patches/db/activerecord.rb +44 -0
- data/rack-mini-profiler-4.0.1/lib/patches/db/mongo.rb +18 -0
- data/rack-mini-profiler-4.0.1/lib/patches/db/moped.rb +14 -0
- data/rack-mini-profiler-4.0.1/lib/patches/db/mysql2/alias_method.rb +30 -0
- data/rack-mini-profiler-4.0.1/lib/patches/db/mysql2/prepend.rb +34 -0
- data/rack-mini-profiler-4.0.1/lib/patches/db/mysql2.rb +7 -0
- data/rack-mini-profiler-4.0.1/lib/patches/db/neo4j.rb +16 -0
- data/rack-mini-profiler-4.0.1/lib/patches/db/nobrainer.rb +31 -0
- data/rack-mini-profiler-4.0.1/lib/patches/db/oracle_enhanced.rb +70 -0
- data/rack-mini-profiler-4.0.1/lib/patches/db/pg/alias_method.rb +121 -0
- data/rack-mini-profiler-4.0.1/lib/patches/db/pg/prepend.rb +115 -0
- data/rack-mini-profiler-4.0.1/lib/patches/db/pg.rb +7 -0
- data/rack-mini-profiler-4.0.1/lib/patches/db/plucky.rb +49 -0
- data/rack-mini-profiler-4.0.1/lib/patches/db/riak.rb +105 -0
- data/rack-mini-profiler-4.0.1/lib/patches/db/rsolr.rb +26 -0
- data/rack-mini-profiler-4.0.1/lib/patches/db/sequel.rb +12 -0
- data/rack-mini-profiler-4.0.1/lib/patches/net_patches.rb +27 -0
- data/rack-mini-profiler-4.0.1/lib/patches/sql_patches.rb +74 -0
- data/rack-mini-profiler-4.0.1/lib/prepend_mysql2_patch.rb +5 -0
- data/rack-mini-profiler-4.0.1/lib/prepend_net_http_patch.rb +5 -0
- data/rack-mini-profiler-4.0.1/lib/prepend_pg_patch.rb +5 -0
- data/rack-mini-profiler-4.0.1/lib/rack-mini-profiler.rb +13 -0
- data/rack-mini-profiler-4.0.1/rack-mini-profiler.gemspec +49 -0
- metadata +125 -0
|
@@ -0,0 +1,670 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'cgi'
|
|
4
|
+
require 'json'
|
|
5
|
+
require 'erb'
|
|
6
|
+
|
|
7
|
+
require 'mini_profiler/timer_struct'
|
|
8
|
+
require 'mini_profiler/storage'
|
|
9
|
+
require 'mini_profiler/config'
|
|
10
|
+
require 'mini_profiler/profiling_methods'
|
|
11
|
+
require 'mini_profiler/context'
|
|
12
|
+
require 'mini_profiler/client_settings'
|
|
13
|
+
require 'mini_profiler/gc_profiler'
|
|
14
|
+
require 'mini_profiler/snapshots_transporter'
|
|
15
|
+
require 'mini_profiler/views'
|
|
16
|
+
require 'mini_profiler/actions'
|
|
17
|
+
|
|
18
|
+
module Rack
|
|
19
|
+
class MiniProfiler
|
|
20
|
+
include Actions
|
|
21
|
+
include Views
|
|
22
|
+
|
|
23
|
+
class << self
|
|
24
|
+
include Rack::MiniProfiler::ProfilingMethods
|
|
25
|
+
attr_accessor :subscribe_sql_active_record
|
|
26
|
+
|
|
27
|
+
def patch_rails?
|
|
28
|
+
!!defined?(Rack::MINI_PROFILER_ENABLE_RAILS_PATCHES)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def generate_id
|
|
32
|
+
rand(36**20).to_s(36)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def reset_config
|
|
36
|
+
@config = Config.default
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# So we can change the configuration if we want
|
|
40
|
+
def config
|
|
41
|
+
@config ||= Config.default
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def current
|
|
45
|
+
Thread.current[:mini_profiler_private]
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def current=(c)
|
|
49
|
+
# we use TLS cause we need access to this from sql blocks and code blocks that have no access to env
|
|
50
|
+
Thread.current[:mini_profiler_snapshot_custom_fields] = nil
|
|
51
|
+
Thread.current[:mp_ongoing_snapshot] = nil
|
|
52
|
+
Thread.current[:mini_profiler_private] = c
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def add_snapshot_custom_field(key, value)
|
|
56
|
+
thread_var_key = :mini_profiler_snapshot_custom_fields
|
|
57
|
+
Thread.current[thread_var_key] ||= {}
|
|
58
|
+
Thread.current[thread_var_key][key] = value
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def get_snapshot_custom_fields
|
|
62
|
+
Thread.current[:mini_profiler_snapshot_custom_fields]
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# discard existing results, don't track this request
|
|
66
|
+
def discard_results
|
|
67
|
+
self.current.discard = true if current
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def create_current(env = {}, options = {})
|
|
71
|
+
# profiling the request
|
|
72
|
+
context = Context.new
|
|
73
|
+
context.inject_js = config.auto_inject && (!env['HTTP_X_REQUESTED_WITH'].eql? 'XMLHttpRequest')
|
|
74
|
+
context.page_struct = TimerStruct::Page.new(env)
|
|
75
|
+
context.current_timer = context.page_struct[:root]
|
|
76
|
+
self.current = context
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def authorize_request
|
|
80
|
+
Thread.current[:mp_authorized] = true
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def deauthorize_request
|
|
84
|
+
Thread.current[:mp_authorized] = nil
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def request_authorized?
|
|
88
|
+
Thread.current[:mp_authorized]
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def advanced_tools_message
|
|
92
|
+
<<~TEXT
|
|
93
|
+
This feature is disabled by default, to enable set the enable_advanced_debugging_tools option to true in Mini Profiler config.
|
|
94
|
+
TEXT
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def binds_to_params(binds)
|
|
98
|
+
return if binds.nil? || config.max_sql_param_length == 0
|
|
99
|
+
# map ActiveRecord::Relation::QueryAttribute to [name, value]
|
|
100
|
+
params = binds.map { |c| c.kind_of?(Array) ? [c.first, c.last] : [c.name, c.value] }
|
|
101
|
+
if (skip = config.skip_sql_param_names)
|
|
102
|
+
params.map { |(n, v)| n =~ skip ? [n, nil] : [n, v] }
|
|
103
|
+
else
|
|
104
|
+
params
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def snapshots_transporter?
|
|
109
|
+
!!config.snapshots_transport_destination_url &&
|
|
110
|
+
!!config.snapshots_transport_auth_key
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def redact_sql_queries?
|
|
114
|
+
Thread.current[:mp_ongoing_snapshot] == true &&
|
|
115
|
+
Rack::MiniProfiler.config.snapshots_redact_sql_queries
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
#
|
|
120
|
+
# options:
|
|
121
|
+
# :auto_inject - should script be automatically injected on every html page (not xhr)
|
|
122
|
+
def initialize(app, config = nil)
|
|
123
|
+
MiniProfiler.config.merge!(config)
|
|
124
|
+
@config = MiniProfiler.config
|
|
125
|
+
@app = app
|
|
126
|
+
@config.base_url_path += "/" unless @config.base_url_path.end_with? "/"
|
|
127
|
+
unless @config.storage_instance
|
|
128
|
+
@config.storage_instance = @config.storage.new(@config.storage_options)
|
|
129
|
+
end
|
|
130
|
+
@storage = @config.storage_instance
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def user(env)
|
|
134
|
+
@config.user_provider.call(env)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def current
|
|
138
|
+
MiniProfiler.current
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def current=(c)
|
|
142
|
+
MiniProfiler.current = c
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def config
|
|
146
|
+
@config
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def advanced_debugging_enabled?
|
|
150
|
+
config.enable_advanced_debugging_tools
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def tool_disabled_message(client_settings)
|
|
154
|
+
client_settings.handle_cookie(text_result(Rack::MiniProfiler.advanced_tools_message))
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def call(env)
|
|
158
|
+
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
159
|
+
client_settings = ClientSettings.new(env, @storage, start)
|
|
160
|
+
MiniProfiler.deauthorize_request if @config.authorization_mode == :allow_authorized
|
|
161
|
+
|
|
162
|
+
status = headers = body = nil
|
|
163
|
+
path = env['PATH_INFO'].sub('//', '/')
|
|
164
|
+
|
|
165
|
+
# Someone (e.g. Rails engine) could change the SCRIPT_NAME so we save it
|
|
166
|
+
env['RACK_MINI_PROFILER_ORIGINAL_SCRIPT_NAME'] = ENV['PASSENGER_BASE_URI'] || env['SCRIPT_NAME']
|
|
167
|
+
|
|
168
|
+
skip_it = matches_action?('skip', env) || (
|
|
169
|
+
@config.skip_paths &&
|
|
170
|
+
@config.skip_paths.any? do |p|
|
|
171
|
+
if p.instance_of?(String)
|
|
172
|
+
path.start_with?(p)
|
|
173
|
+
elsif p.instance_of?(Regexp)
|
|
174
|
+
p.match?(path)
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
)
|
|
178
|
+
if skip_it
|
|
179
|
+
return client_settings.handle_cookie(@app.call(env))
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
skip_it = (@config.pre_authorize_cb && !@config.pre_authorize_cb.call(env))
|
|
183
|
+
|
|
184
|
+
if skip_it || (
|
|
185
|
+
@config.authorization_mode == :allow_authorized &&
|
|
186
|
+
!client_settings.has_valid_cookie?
|
|
187
|
+
)
|
|
188
|
+
if take_snapshot?(path)
|
|
189
|
+
return client_settings.handle_cookie(take_snapshot(env, start))
|
|
190
|
+
else
|
|
191
|
+
return client_settings.handle_cookie(@app.call(env))
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# handle all /mini-profiler requests here
|
|
196
|
+
if path.start_with? @config.base_url_path
|
|
197
|
+
file_name = path.sub(@config.base_url_path, '')
|
|
198
|
+
|
|
199
|
+
case file_name
|
|
200
|
+
when 'results'
|
|
201
|
+
return serve_results(env)
|
|
202
|
+
when 'snapshots'
|
|
203
|
+
self.current = nil
|
|
204
|
+
return serve_snapshot(env)
|
|
205
|
+
when 'flamegraph'
|
|
206
|
+
return serve_flamegraph(env)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
return client_settings.handle_cookie(serve_file(env, file_name: file_name))
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
has_disable_cookie = client_settings.disable_profiling?
|
|
213
|
+
# manual session disable / enable
|
|
214
|
+
if matches_action?('disable', env) || has_disable_cookie
|
|
215
|
+
skip_it = true
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
if matches_action?('enable', env)
|
|
219
|
+
skip_it = false
|
|
220
|
+
config.enabled = true
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
if skip_it || !config.enabled
|
|
224
|
+
status, headers, body = @app.call(env)
|
|
225
|
+
client_settings.disable_profiling = true
|
|
226
|
+
return client_settings.handle_cookie([status, headers, body])
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# remember that profiling is not disabled (ie enabled)
|
|
230
|
+
client_settings.disable_profiling = false
|
|
231
|
+
|
|
232
|
+
# profile gc
|
|
233
|
+
if matches_action?('profile-gc', env)
|
|
234
|
+
current.measure = false if current
|
|
235
|
+
return serve_profile_gc(env, client_settings)
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# profile memory
|
|
239
|
+
if matches_action?('profile-memory', env)
|
|
240
|
+
return serve_profile_memory(env, client_settings)
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# any other requests past this point are going to the app to be profiled
|
|
244
|
+
|
|
245
|
+
MiniProfiler.create_current(env, @config)
|
|
246
|
+
|
|
247
|
+
if matches_action?('normal-backtrace', env)
|
|
248
|
+
client_settings.backtrace_level = ClientSettings::BACKTRACE_DEFAULT
|
|
249
|
+
elsif matches_action?('no-backtrace', env)
|
|
250
|
+
current.skip_backtrace = true
|
|
251
|
+
client_settings.backtrace_level = ClientSettings::BACKTRACE_NONE
|
|
252
|
+
elsif matches_action?('full-backtrace', env) || client_settings.backtrace_full?
|
|
253
|
+
current.full_backtrace = true
|
|
254
|
+
client_settings.backtrace_level = ClientSettings::BACKTRACE_FULL
|
|
255
|
+
elsif client_settings.backtrace_none?
|
|
256
|
+
current.skip_backtrace = true
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
flamegraph = nil
|
|
260
|
+
|
|
261
|
+
trace_exceptions = matches_action?('trace-exceptions', env) && defined? TracePoint
|
|
262
|
+
status, headers, body, exceptions, trace = nil
|
|
263
|
+
|
|
264
|
+
if trace_exceptions
|
|
265
|
+
exceptions = []
|
|
266
|
+
trace = TracePoint.new(:raise) do |tp|
|
|
267
|
+
exceptions << tp.raised_exception
|
|
268
|
+
end
|
|
269
|
+
trace.enable
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
begin
|
|
273
|
+
|
|
274
|
+
# Strip all the caching headers so we don't get 304s back
|
|
275
|
+
# This solves a very annoying bug where rack mini profiler never shows up
|
|
276
|
+
if config.disable_caching
|
|
277
|
+
env['HTTP_IF_MODIFIED_SINCE'] = ''
|
|
278
|
+
env['HTTP_IF_NONE_MATCH'] = ''
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
orig_accept_encoding = env['HTTP_ACCEPT_ENCODING']
|
|
282
|
+
# Prevent response body from being compressed
|
|
283
|
+
env['HTTP_ACCEPT_ENCODING'] = 'identity' if config.suppress_encoding
|
|
284
|
+
|
|
285
|
+
if matches_action?('flamegraph', env) || matches_action?('async-flamegraph', env) || env['HTTP_REFERER'] =~ /pp=async-flamegraph/
|
|
286
|
+
if defined?(StackProf) && StackProf.respond_to?(:run)
|
|
287
|
+
# do not sully our profile with mini profiler timings
|
|
288
|
+
current.measure = false
|
|
289
|
+
match_data = action_parameters(env)['flamegraph_sample_rate']
|
|
290
|
+
|
|
291
|
+
if match_data && !match_data[1].to_f.zero?
|
|
292
|
+
sample_rate = match_data[1].to_f
|
|
293
|
+
else
|
|
294
|
+
sample_rate = config.flamegraph_sample_rate
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
mode_match_data = action_parameters(env)['flamegraph_mode']
|
|
298
|
+
|
|
299
|
+
if mode_match_data && [:cpu, :wall, :object, :custom].include?(mode_match_data.to_sym)
|
|
300
|
+
mode = mode_match_data.to_sym
|
|
301
|
+
else
|
|
302
|
+
mode = config.flamegraph_mode
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
ignore_gc_match_data = action_parameters(env)['flamegraph_ignore_gc']
|
|
306
|
+
|
|
307
|
+
if ignore_gc_match_data
|
|
308
|
+
ignore_gc = ignore_gc_match_data == 'true'
|
|
309
|
+
else
|
|
310
|
+
ignore_gc = config.flamegraph_ignore_gc
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
flamegraph = StackProf.run(
|
|
314
|
+
mode: mode,
|
|
315
|
+
raw: true,
|
|
316
|
+
aggregate: false,
|
|
317
|
+
interval: (sample_rate * 1000).to_i,
|
|
318
|
+
ignore_gc: ignore_gc
|
|
319
|
+
) do
|
|
320
|
+
status, headers, body = @app.call(env)
|
|
321
|
+
end
|
|
322
|
+
else
|
|
323
|
+
message = "Please install the stackprof gem and require it: add gem 'stackprof' to your Gemfile"
|
|
324
|
+
status, headers, body = @app.call(env)
|
|
325
|
+
body.close if body.respond_to? :close
|
|
326
|
+
|
|
327
|
+
return client_settings.handle_cookie(
|
|
328
|
+
text_result(message, status: status, headers: headers)
|
|
329
|
+
)
|
|
330
|
+
end
|
|
331
|
+
elsif path == '/rack-mini-profiler/requests'
|
|
332
|
+
status, headers, body = [200, { Rack::CONTENT_TYPE => 'text/html' }, [blank_page_html.dup]] # important to dup here!
|
|
333
|
+
else
|
|
334
|
+
status, headers, body = @app.call(env)
|
|
335
|
+
end
|
|
336
|
+
ensure
|
|
337
|
+
trace.disable if trace
|
|
338
|
+
env['HTTP_ACCEPT_ENCODING'] = orig_accept_encoding if config.suppress_encoding
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
skip_it = current.discard
|
|
342
|
+
|
|
343
|
+
if (config.authorization_mode == :allow_authorized && !MiniProfiler.request_authorized?)
|
|
344
|
+
skip_it = true
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
return client_settings.handle_cookie([status, headers, body]) if skip_it
|
|
348
|
+
|
|
349
|
+
# we must do this here, otherwise current[:discard] is not being properly treated
|
|
350
|
+
if trace_exceptions
|
|
351
|
+
body.close if body.respond_to? :close
|
|
352
|
+
|
|
353
|
+
query_params = action_parameters(env)
|
|
354
|
+
trace_exceptions_filter = query_params['trace_exceptions_filter']
|
|
355
|
+
if trace_exceptions_filter
|
|
356
|
+
trace_exceptions_regex = Regexp.new(trace_exceptions_filter)
|
|
357
|
+
exceptions.reject! { |ex| ex.class.name =~ trace_exceptions_regex }
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
return client_settings.handle_cookie(dump_exceptions exceptions)
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
if matches_action?("env", env)
|
|
364
|
+
return tool_disabled_message(client_settings) if !advanced_debugging_enabled?
|
|
365
|
+
body.close if body.respond_to? :close
|
|
366
|
+
return client_settings.handle_cookie(dump_env env)
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
if matches_action?("analyze-memory", env)
|
|
370
|
+
return tool_disabled_message(client_settings) if !advanced_debugging_enabled?
|
|
371
|
+
body.close if body.respond_to? :close
|
|
372
|
+
return client_settings.handle_cookie(analyze_memory)
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
if matches_action?("help", env)
|
|
376
|
+
body.close if body.respond_to? :close
|
|
377
|
+
return client_settings.handle_cookie(help(client_settings, env))
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
page_struct = current.page_struct
|
|
381
|
+
page_struct[:user] = user(env)
|
|
382
|
+
page_struct[:root].record_time((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start) * 1000)
|
|
383
|
+
|
|
384
|
+
if flamegraph && matches_action?("flamegraph", env)
|
|
385
|
+
body.close if body.respond_to? :close
|
|
386
|
+
return client_settings.handle_cookie(self.flamegraph(flamegraph, path, env))
|
|
387
|
+
elsif flamegraph # async-flamegraph
|
|
388
|
+
page_struct[:has_flamegraph] = true
|
|
389
|
+
page_struct[:flamegraph] = flamegraph
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
begin
|
|
393
|
+
@storage.save(page_struct)
|
|
394
|
+
# no matter what it is, it should be unviewed, otherwise we will miss POST
|
|
395
|
+
@storage.set_unviewed(page_struct[:user], page_struct[:id])
|
|
396
|
+
|
|
397
|
+
# inject headers, script
|
|
398
|
+
if status >= 200 && status < 300
|
|
399
|
+
result = inject_profiler(env, status, headers, body)
|
|
400
|
+
return client_settings.handle_cookie(result) if result
|
|
401
|
+
end
|
|
402
|
+
rescue Exception => e
|
|
403
|
+
if @config.storage_failure != nil
|
|
404
|
+
@config.storage_failure.call(e)
|
|
405
|
+
end
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
client_settings.handle_cookie([status, headers, body])
|
|
409
|
+
ensure
|
|
410
|
+
# Make sure this always happens
|
|
411
|
+
self.current = nil
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
def matches_action?(action, env)
|
|
415
|
+
env['QUERY_STRING'] =~ /#{@config.profile_parameter}=#{action}/ ||
|
|
416
|
+
env['HTTP_X_RACK_MINI_PROFILER'] == action
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
def action_parameters(env)
|
|
420
|
+
Rack::Utils.parse_nested_query(env['QUERY_STRING'])
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
def inject_profiler(env, status, headers, body)
|
|
424
|
+
# mini profiler is meddling with stuff, we can not cache cause we will get incorrect data
|
|
425
|
+
# Rack::ETag has already inserted some nonesense in the chain
|
|
426
|
+
content_type = headers[Rack::CONTENT_TYPE]
|
|
427
|
+
|
|
428
|
+
if config.disable_caching
|
|
429
|
+
headers.delete(Rack::ETAG)
|
|
430
|
+
headers.delete('date') || headers.delete('Date')
|
|
431
|
+
end
|
|
432
|
+
|
|
433
|
+
headers['x-miniprofiler-original-cache-control'] = headers[Rack::CACHE_CONTROL] unless headers[Rack::CACHE_CONTROL].nil?
|
|
434
|
+
headers[Rack::CACHE_CONTROL] = "#{"no-store, " if config.disable_caching}must-revalidate, private, max-age=0"
|
|
435
|
+
|
|
436
|
+
# inject header
|
|
437
|
+
if headers.is_a? Hash
|
|
438
|
+
headers['x-miniprofiler-ids'] = ids_comma_separated(env)
|
|
439
|
+
headers['x-miniprofiler-flamegraph-path'] = flamegraph_path(env) if current.page_struct[:has_flamegraph]
|
|
440
|
+
end
|
|
441
|
+
|
|
442
|
+
if current.inject_js && content_type =~ /text\/html/
|
|
443
|
+
response = Rack::Response.new([], status, headers)
|
|
444
|
+
script = self.get_profile_script(env, headers)
|
|
445
|
+
|
|
446
|
+
if String === body
|
|
447
|
+
response.write inject(body, script)
|
|
448
|
+
else
|
|
449
|
+
body.each { |fragment| response.write inject(fragment, script) }
|
|
450
|
+
end
|
|
451
|
+
body.close if body.respond_to? :close
|
|
452
|
+
response.finish
|
|
453
|
+
else
|
|
454
|
+
nil
|
|
455
|
+
end
|
|
456
|
+
end
|
|
457
|
+
|
|
458
|
+
def inject(fragment, script)
|
|
459
|
+
# find explicit or implicit body
|
|
460
|
+
index = fragment.rindex(/<\/body>/i) || fragment.rindex(/<\/html>/i)
|
|
461
|
+
if index
|
|
462
|
+
# if for whatever crazy reason we dont get a utf string,
|
|
463
|
+
# just force the encoding, no utf in the mp scripts anyway
|
|
464
|
+
if script.respond_to?(:encoding) && script.respond_to?(:force_encoding)
|
|
465
|
+
script = script.force_encoding(fragment.encoding)
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
safe_script = script
|
|
469
|
+
if script.respond_to?(:html_safe)
|
|
470
|
+
safe_script = script.html_safe
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
fragment.insert(index, safe_script)
|
|
474
|
+
else
|
|
475
|
+
fragment
|
|
476
|
+
end
|
|
477
|
+
end
|
|
478
|
+
|
|
479
|
+
def dump_exceptions(exceptions)
|
|
480
|
+
body = "Exceptions raised during request\n\n".dup
|
|
481
|
+
if exceptions.empty?
|
|
482
|
+
body << "No exceptions raised"
|
|
483
|
+
else
|
|
484
|
+
body << "Exceptions: (#{exceptions.size} total)\n"
|
|
485
|
+
exceptions.group_by(&:class).each do |klass, exceptions_per_class|
|
|
486
|
+
body << " #{klass.name} (#{exceptions_per_class.size})\n"
|
|
487
|
+
end
|
|
488
|
+
|
|
489
|
+
body << "\nBacktraces\n"
|
|
490
|
+
exceptions.each_with_index do |e, i|
|
|
491
|
+
body << "##{i + 1}: #{e.class} - \"#{e.message.lines.first.chomp}\"\n #{e.backtrace.join("\n ")}\n\n"
|
|
492
|
+
end
|
|
493
|
+
end
|
|
494
|
+
text_result(body)
|
|
495
|
+
end
|
|
496
|
+
|
|
497
|
+
def dump_env(env)
|
|
498
|
+
body = "Rack Environment\n---------------\n".dup
|
|
499
|
+
env.each do |k, v|
|
|
500
|
+
body << "#{k}: #{v}\n"
|
|
501
|
+
end
|
|
502
|
+
|
|
503
|
+
body << "\n\nEnvironment\n---------------\n"
|
|
504
|
+
ENV.each do |k, v|
|
|
505
|
+
body << "#{k}: #{v}\n"
|
|
506
|
+
end
|
|
507
|
+
|
|
508
|
+
body << "\n\nRuby Version\n---------------\n"
|
|
509
|
+
body << "#{RUBY_VERSION} p#{RUBY_PATCHLEVEL}\n"
|
|
510
|
+
|
|
511
|
+
body << "\n\nInternals\n---------------\n"
|
|
512
|
+
body << "Storage Provider #{config.storage_instance}\n"
|
|
513
|
+
body << "User #{user(env)}\n"
|
|
514
|
+
body << config.storage_instance.diagnostics(user(env)) rescue "no diagnostics implemented for storage"
|
|
515
|
+
|
|
516
|
+
text_result(body)
|
|
517
|
+
end
|
|
518
|
+
|
|
519
|
+
def trim_strings(strings, max_size)
|
|
520
|
+
strings.sort! { |a, b| b[1] <=> a[1] }
|
|
521
|
+
i = 0
|
|
522
|
+
strings.delete_if { |_| (i += 1) > max_size }
|
|
523
|
+
end
|
|
524
|
+
|
|
525
|
+
def analyze_memory
|
|
526
|
+
require 'objspace'
|
|
527
|
+
|
|
528
|
+
utf8 = "utf-8"
|
|
529
|
+
|
|
530
|
+
GC.start
|
|
531
|
+
|
|
532
|
+
trunc = lambda do |str|
|
|
533
|
+
str = str.length > 200 ? str : str[0..200]
|
|
534
|
+
|
|
535
|
+
if str.encoding != Encoding::UTF_8
|
|
536
|
+
str = str.dup
|
|
537
|
+
str.force_encoding(utf8)
|
|
538
|
+
|
|
539
|
+
unless str.valid_encoding?
|
|
540
|
+
# work around bust string with a double conversion
|
|
541
|
+
str.encode!("utf-16", "utf-8", invalid: :replace)
|
|
542
|
+
str.encode!("utf-8", "utf-16")
|
|
543
|
+
end
|
|
544
|
+
end
|
|
545
|
+
|
|
546
|
+
str
|
|
547
|
+
end
|
|
548
|
+
|
|
549
|
+
body = "ObjectSpace stats:\n\n".dup
|
|
550
|
+
|
|
551
|
+
counts = ObjectSpace.count_objects
|
|
552
|
+
total_strings = counts[:T_STRING]
|
|
553
|
+
|
|
554
|
+
body << counts
|
|
555
|
+
.sort { |a, b| b[1] <=> a[1] }
|
|
556
|
+
.map { |k, v| "#{k}: #{v}" }
|
|
557
|
+
.join("\n")
|
|
558
|
+
|
|
559
|
+
strings = []
|
|
560
|
+
string_counts = Hash.new(0)
|
|
561
|
+
sample_strings = []
|
|
562
|
+
|
|
563
|
+
max_size = 1000
|
|
564
|
+
sample_every = total_strings / max_size
|
|
565
|
+
|
|
566
|
+
i = 0
|
|
567
|
+
ObjectSpace.each_object(String) do |str|
|
|
568
|
+
i += 1
|
|
569
|
+
string_counts[str] += 1
|
|
570
|
+
strings << [trunc.call(str), str.length]
|
|
571
|
+
sample_strings << [trunc.call(str), str.length] if i % sample_every == 0
|
|
572
|
+
if strings.length > max_size * 2
|
|
573
|
+
trim_strings(strings, max_size)
|
|
574
|
+
end
|
|
575
|
+
end
|
|
576
|
+
|
|
577
|
+
trim_strings(strings, max_size)
|
|
578
|
+
|
|
579
|
+
body << "\n\n\n1000 Largest strings:\n\n"
|
|
580
|
+
body << strings.map { |s, len| "#{s[0..1000]}\n(len: #{len})\n\n" }.join("\n")
|
|
581
|
+
|
|
582
|
+
body << "\n\n\n1000 Sample strings:\n\n"
|
|
583
|
+
body << sample_strings.map { |s, len| "#{s[0..1000]}\n(len: #{len})\n\n" }.join("\n")
|
|
584
|
+
|
|
585
|
+
body << "\n\n\n1000 Most common strings:\n\n"
|
|
586
|
+
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")
|
|
587
|
+
|
|
588
|
+
text_result(body)
|
|
589
|
+
end
|
|
590
|
+
|
|
591
|
+
def text_result(body, status: 200, headers: nil)
|
|
592
|
+
headers = (headers || {}).merge(Rack::CONTENT_TYPE => 'text/plain; charset=utf-8')
|
|
593
|
+
[status, headers, [body]]
|
|
594
|
+
end
|
|
595
|
+
|
|
596
|
+
def ids(env)
|
|
597
|
+
all = ([current.page_struct[:id]] + (@storage.get_unviewed_ids(user(env)) || [])).uniq
|
|
598
|
+
if all.size > @config.max_traces_to_show
|
|
599
|
+
all = all[0...@config.max_traces_to_show]
|
|
600
|
+
@storage.set_all_unviewed(user(env), all)
|
|
601
|
+
end
|
|
602
|
+
all
|
|
603
|
+
end
|
|
604
|
+
|
|
605
|
+
def ids_comma_separated(env)
|
|
606
|
+
ids(env).join(",")
|
|
607
|
+
end
|
|
608
|
+
|
|
609
|
+
def flamegraph_path(env)
|
|
610
|
+
@config.base_url_path + 'flamegraph?id=' + current.page_struct[:id]
|
|
611
|
+
end
|
|
612
|
+
|
|
613
|
+
# cancels automatic injection of profile script for the current page
|
|
614
|
+
def cancel_auto_inject(env)
|
|
615
|
+
current.inject_js = false
|
|
616
|
+
end
|
|
617
|
+
|
|
618
|
+
def cache_control_value
|
|
619
|
+
86400
|
|
620
|
+
end
|
|
621
|
+
|
|
622
|
+
private
|
|
623
|
+
|
|
624
|
+
def rails_route_from_path(path, method)
|
|
625
|
+
if defined?(Rails) && defined?(ActionController::RoutingError)
|
|
626
|
+
hash = Rails.application.routes.recognize_path(path, method: method)
|
|
627
|
+
if hash && hash[:controller] && hash[:action]
|
|
628
|
+
"#{hash[:controller]}##{hash[:action]}"
|
|
629
|
+
end
|
|
630
|
+
end
|
|
631
|
+
rescue ActionController::RoutingError
|
|
632
|
+
nil
|
|
633
|
+
end
|
|
634
|
+
|
|
635
|
+
def take_snapshot?(path)
|
|
636
|
+
@config.snapshot_every_n_requests > 0 &&
|
|
637
|
+
!path.start_with?(@config.base_url_path) &&
|
|
638
|
+
@storage.should_take_snapshot?(@config.snapshot_every_n_requests)
|
|
639
|
+
end
|
|
640
|
+
|
|
641
|
+
def take_snapshot(env, start)
|
|
642
|
+
MiniProfiler.create_current(env, @config)
|
|
643
|
+
Thread.current[:mp_ongoing_snapshot] = true
|
|
644
|
+
results = @app.call(env)
|
|
645
|
+
status = results[0].to_i
|
|
646
|
+
if status >= 200 && status < 300
|
|
647
|
+
page_struct = current.page_struct
|
|
648
|
+
page_struct[:root].record_time(
|
|
649
|
+
(Process.clock_gettime(Process::CLOCK_MONOTONIC) - start) * 1000
|
|
650
|
+
)
|
|
651
|
+
custom_fields = MiniProfiler.get_snapshot_custom_fields
|
|
652
|
+
page_struct[:custom_fields] = custom_fields if custom_fields
|
|
653
|
+
if Rack::MiniProfiler.snapshots_transporter?
|
|
654
|
+
Rack::MiniProfiler::SnapshotsTransporter.transport(page_struct)
|
|
655
|
+
else
|
|
656
|
+
group_name = rails_route_from_path(page_struct[:request_path], page_struct[:request_method])
|
|
657
|
+
group_name ||= page_struct[:request_path]
|
|
658
|
+
group_name = "#{page_struct[:request_method]} #{group_name}"
|
|
659
|
+
@storage.push_snapshot(
|
|
660
|
+
page_struct,
|
|
661
|
+
group_name,
|
|
662
|
+
@config
|
|
663
|
+
)
|
|
664
|
+
end
|
|
665
|
+
end
|
|
666
|
+
self.current = nil
|
|
667
|
+
results
|
|
668
|
+
end
|
|
669
|
+
end
|
|
670
|
+
end
|