rack-mini-profiler 1.0.2 → 3.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +138 -21
- data/README.md +201 -94
- data/lib/enable_rails_patches.rb +5 -0
- data/lib/generators/rack_mini_profiler/USAGE +9 -0
- data/lib/generators/rack_mini_profiler/install_generator.rb +13 -0
- data/lib/generators/{rack_profiler/templates/rack_profiler.rb → rack_mini_profiler/templates/rack_mini_profiler.rb} +1 -1
- data/lib/generators/rack_profiler/install_generator.rb +6 -3
- data/lib/html/dot.1.1.2.min.js +2 -0
- data/lib/html/includes.css +144 -45
- data/lib/html/includes.js +1423 -1009
- data/lib/html/includes.scss +538 -441
- data/lib/html/includes.tmpl +231 -148
- data/lib/html/pretty-print.js +810 -0
- data/lib/html/profile_handler.js +1 -1
- data/lib/html/rack-mini-profiler.css +3 -0
- data/lib/html/rack-mini-profiler.js +2 -0
- data/lib/html/share.html +0 -1
- data/lib/html/speedscope/LICENSE +21 -0
- data/lib/html/speedscope/README.md +3 -0
- data/lib/html/speedscope/demangle-cpp.1768f4cc.js +4 -0
- data/lib/html/speedscope/favicon-16x16.f74b3187.png +0 -0
- data/lib/html/speedscope/favicon-32x32.bc503437.png +0 -0
- data/lib/html/speedscope/file-format-schema.json +324 -0
- data/lib/html/speedscope/fonts/source-code-pro-regular.css +8 -0
- data/lib/html/speedscope/fonts/source-code-pro-v13-regular.woff +0 -0
- data/lib/html/speedscope/fonts/source-code-pro-v13-regular.woff2 +0 -0
- data/lib/html/speedscope/import.cf0fa83f.js +115 -0
- data/lib/html/speedscope/index.html +2 -0
- data/lib/html/speedscope/release.txt +3 -0
- data/lib/html/speedscope/reset.8c46b7a1.css +2 -0
- data/lib/html/speedscope/source-map.438fa06b.js +24 -0
- data/lib/html/speedscope/speedscope.44364064.js +200 -0
- data/lib/html/vendor.js +848 -0
- data/lib/mini_profiler/asset_version.rb +3 -2
- data/lib/mini_profiler/client_settings.rb +15 -7
- data/lib/mini_profiler/config.rb +51 -5
- data/lib/mini_profiler/gc_profiler.rb +1 -1
- data/lib/mini_profiler/profiling_methods.rb +13 -8
- data/lib/mini_profiler/snapshots_transporter.rb +109 -0
- data/lib/mini_profiler/storage/abstract_store.rb +52 -1
- data/lib/mini_profiler/storage/file_store.rb +7 -3
- data/lib/mini_profiler/storage/memcache_store.rb +13 -7
- data/lib/mini_profiler/storage/memory_store.rb +98 -5
- data/lib/mini_profiler/storage/redis_store.rb +226 -3
- data/lib/mini_profiler/storage.rb +7 -0
- data/lib/mini_profiler/timer_struct/base.rb +2 -0
- data/lib/mini_profiler/timer_struct/custom.rb +1 -0
- data/lib/mini_profiler/timer_struct/page.rb +60 -4
- data/lib/mini_profiler/timer_struct/request.rb +53 -11
- data/lib/mini_profiler/timer_struct/sql.rb +6 -2
- data/lib/mini_profiler/timer_struct.rb +8 -0
- data/lib/mini_profiler/version.rb +2 -1
- data/lib/{mini_profiler/profiler.rb → mini_profiler.rb} +394 -82
- data/lib/mini_profiler_rails/railtie.rb +88 -7
- data/lib/mini_profiler_rails/railtie_methods.rb +61 -0
- data/lib/patches/db/activerecord.rb +1 -12
- data/lib/patches/db/mongo.rb +1 -1
- data/lib/patches/db/moped.rb +1 -1
- data/lib/patches/db/mysql2/alias_method.rb +30 -0
- data/lib/patches/db/mysql2/prepend.rb +34 -0
- data/lib/patches/db/mysql2.rb +4 -27
- data/lib/patches/db/plucky.rb +4 -4
- data/lib/patches/db/riak.rb +1 -1
- data/lib/patches/net_patches.rb +21 -10
- data/lib/patches/sql_patches.rb +13 -5
- data/lib/prepend_mysql2_patch.rb +5 -0
- data/lib/prepend_net_http_patch.rb +5 -0
- data/lib/rack-mini-profiler.rb +1 -24
- data/rack-mini-profiler.gemspec +17 -8
- metadata +156 -32
- data/lib/html/jquery.1.7.1.js +0 -4
- data/lib/html/jquery.tmpl.js +0 -486
- data/lib/html/list.css +0 -9
- data/lib/html/list.js +0 -38
- data/lib/html/list.tmpl +0 -34
@@ -42,7 +42,7 @@ module Rack
|
|
42
42
|
def handle_cookie(result)
|
43
43
|
status, headers, _body = result
|
44
44
|
|
45
|
-
if (MiniProfiler.config.authorization_mode == :
|
45
|
+
if (MiniProfiler.config.authorization_mode == :allow_authorized && !MiniProfiler.request_authorized?)
|
46
46
|
# this is non-obvious, don't kill the profiling cookie on errors or short requests
|
47
47
|
# this ensures that stuff that never reaches the rails stack does not kill profiling
|
48
48
|
if status.to_i >= 200 && status.to_i < 300 && ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - @start) > 0.1)
|
@@ -59,7 +59,7 @@ module Rack
|
|
59
59
|
|
60
60
|
tokens_changed = false
|
61
61
|
|
62
|
-
if MiniProfiler.request_authorized? && MiniProfiler.config.authorization_mode == :
|
62
|
+
if MiniProfiler.request_authorized? && MiniProfiler.config.authorization_mode == :allow_authorized
|
63
63
|
@allowed_tokens ||= @store.allowed_tokens
|
64
64
|
tokens_changed = !@orig_auth_tokens || ((@allowed_tokens - @orig_auth_tokens).length > 0)
|
65
65
|
end
|
@@ -74,25 +74,33 @@ module Rack
|
|
74
74
|
settings["bt"] = @backtrace_level if @backtrace_level
|
75
75
|
settings["a"] = @allowed_tokens.join("|") if @allowed_tokens && MiniProfiler.request_authorized?
|
76
76
|
settings_string = settings.map { |k, v| "#{k}=#{v}" }.join(",")
|
77
|
-
cookie = { value: settings_string, path:
|
77
|
+
cookie = { value: settings_string, path: MiniProfiler.config.cookie_path, httponly: true }
|
78
78
|
cookie[:secure] = true if @request.ssl?
|
79
|
+
cookie[:same_site] = 'Lax'
|
79
80
|
Rack::Utils.set_cookie_header!(headers, COOKIE_NAME, cookie)
|
80
81
|
end
|
81
82
|
end
|
82
83
|
|
83
84
|
def discard_cookie!(headers)
|
84
85
|
if @cookie
|
85
|
-
Rack::Utils.delete_cookie_header!(headers, COOKIE_NAME, path:
|
86
|
+
Rack::Utils.delete_cookie_header!(headers, COOKIE_NAME, path: MiniProfiler.config.cookie_path)
|
86
87
|
end
|
87
88
|
end
|
88
89
|
|
89
90
|
def has_valid_cookie?
|
90
91
|
valid_cookie = !@cookie.nil?
|
91
92
|
|
92
|
-
if (MiniProfiler.config.authorization_mode == :
|
93
|
-
|
93
|
+
if (MiniProfiler.config.authorization_mode == :allow_authorized) && valid_cookie
|
94
|
+
begin
|
95
|
+
@allowed_tokens ||= @store.allowed_tokens
|
96
|
+
rescue => e
|
97
|
+
if MiniProfiler.config.storage_failure != nil
|
98
|
+
MiniProfiler.config.storage_failure.call(e)
|
99
|
+
end
|
100
|
+
end
|
94
101
|
|
95
|
-
valid_cookie =
|
102
|
+
valid_cookie = @allowed_tokens &&
|
103
|
+
(Array === @orig_auth_tokens) &&
|
96
104
|
((@allowed_tokens & @orig_auth_tokens).length > 0)
|
97
105
|
end
|
98
106
|
|
data/lib/mini_profiler/config.rb
CHANGED
@@ -17,6 +17,7 @@ module Rack
|
|
17
17
|
new.instance_eval {
|
18
18
|
@auto_inject = true # automatically inject on every html page
|
19
19
|
@base_url_path = "/mini-profiler-resources/".dup
|
20
|
+
@cookie_path = "/".dup
|
20
21
|
@disable_caching = true
|
21
22
|
# called prior to rack chain, to ensure we are allowed to profile
|
22
23
|
@pre_authorize_cb = lambda { |env| true }
|
@@ -28,15 +29,19 @@ module Rack
|
|
28
29
|
@authorization_mode = :allow_all
|
29
30
|
@backtrace_threshold_ms = 0
|
30
31
|
@flamegraph_sample_rate = 0.5
|
32
|
+
@flamegraph_mode = :wall
|
31
33
|
@storage_failure = Proc.new do |exception|
|
32
34
|
if @logger
|
33
35
|
@logger.warn("MiniProfiler storage failure: #{exception.message}")
|
34
36
|
end
|
35
37
|
end
|
36
38
|
@enabled = true
|
37
|
-
@disable_env_dump = false
|
38
39
|
@max_sql_param_length = 0 # disable sql parameter collection by default
|
39
40
|
@skip_sql_param_names = /password/ # skips parameters with the name password by default
|
41
|
+
@enable_advanced_debugging_tools = false
|
42
|
+
@snapshot_every_n_requests = -1
|
43
|
+
@max_snapshot_groups = 50
|
44
|
+
@max_snapshots_per_group = 15
|
40
45
|
|
41
46
|
# ui parameters
|
42
47
|
@autorized = true
|
@@ -47,9 +52,17 @@ module Rack
|
|
47
52
|
@show_trivial = false
|
48
53
|
@show_total_sql_count = false
|
49
54
|
@start_hidden = false
|
50
|
-
@toggle_shortcut = '
|
55
|
+
@toggle_shortcut = 'alt+p'
|
51
56
|
@html_container = 'body'
|
52
57
|
@position = "top-left"
|
58
|
+
@snapshot_hidden_custom_fields = []
|
59
|
+
@snapshots_transport_destination_url = nil
|
60
|
+
@snapshots_transport_auth_key = nil
|
61
|
+
@snapshots_redact_sql_queries = true
|
62
|
+
@snapshots_transport_gzip_requests = false
|
63
|
+
@enable_hotwire_turbo_drive_support = false
|
64
|
+
|
65
|
+
@profile_parameter = "pp"
|
53
66
|
|
54
67
|
self
|
55
68
|
}
|
@@ -57,20 +70,53 @@ module Rack
|
|
57
70
|
|
58
71
|
attr_accessor :authorization_mode, :auto_inject, :backtrace_ignores,
|
59
72
|
:backtrace_includes, :backtrace_remove, :backtrace_threshold_ms,
|
60
|
-
:base_url_path, :
|
73
|
+
:base_url_path, :cookie_path, :disable_caching, :enabled,
|
61
74
|
:flamegraph_sample_rate, :logger, :pre_authorize_cb, :skip_paths,
|
62
75
|
:skip_schema_queries, :storage, :storage_failure, :storage_instance,
|
63
|
-
:storage_options, :user_provider
|
64
|
-
|
76
|
+
:storage_options, :user_provider, :enable_advanced_debugging_tools,
|
77
|
+
:skip_sql_param_names, :suppress_encoding, :max_sql_param_length,
|
78
|
+
:content_security_policy_nonce, :enable_hotwire_turbo_drive_support,
|
79
|
+
:flamegraph_mode, :profile_parameter
|
65
80
|
|
66
81
|
# ui accessors
|
67
82
|
attr_accessor :collapse_results, :max_traces_to_show, :position,
|
68
83
|
:show_children, :show_controls, :show_trivial, :show_total_sql_count,
|
69
84
|
:start_hidden, :toggle_shortcut, :html_container
|
70
85
|
|
86
|
+
# snapshot related config
|
87
|
+
attr_accessor :snapshot_every_n_requests, :max_snapshots_per_group,
|
88
|
+
:snapshot_hidden_custom_fields, :snapshots_transport_destination_url,
|
89
|
+
:snapshots_transport_auth_key, :snapshots_redact_sql_queries,
|
90
|
+
:snapshots_transport_gzip_requests, :max_snapshot_groups
|
91
|
+
|
71
92
|
# Deprecated options
|
72
93
|
attr_accessor :use_existing_jquery
|
73
94
|
|
95
|
+
attr_reader :assets_url
|
96
|
+
|
97
|
+
# redefined - since the accessor defines it first
|
98
|
+
undef :authorization_mode=
|
99
|
+
def authorization_mode=(mode)
|
100
|
+
if mode == :whitelist
|
101
|
+
warn "[DEPRECATION] `:whitelist` authorization mode is deprecated. Please use `:allow_authorized` instead."
|
102
|
+
|
103
|
+
mode = :allow_authorized
|
104
|
+
end
|
105
|
+
|
106
|
+
warn <<~DEP unless mode == :allow_authorized || mode == :allow_all
|
107
|
+
[DEPRECATION] unknown authorization mode #{mode}. Expected `:allow_all` or `:allow_authorized`.
|
108
|
+
DEP
|
109
|
+
|
110
|
+
@authorization_mode = mode
|
111
|
+
end
|
112
|
+
|
113
|
+
def assets_url=(lmbda)
|
114
|
+
if defined?(Rack::MiniProfilerRails)
|
115
|
+
Rack::MiniProfilerRails.create_engine
|
116
|
+
end
|
117
|
+
@assets_url = lmbda
|
118
|
+
end
|
119
|
+
|
74
120
|
def vertical_position
|
75
121
|
position.include?('bottom') ? 'bottom' : 'top'
|
76
122
|
end
|
@@ -7,7 +7,14 @@ module Rack
|
|
7
7
|
def record_sql(query, elapsed_ms, params = nil)
|
8
8
|
return unless current && current.current_timer
|
9
9
|
c = current
|
10
|
-
c.current_timer.add_sql(
|
10
|
+
c.current_timer.add_sql(
|
11
|
+
redact_sql_queries? ? nil : query,
|
12
|
+
elapsed_ms,
|
13
|
+
c.page_struct,
|
14
|
+
redact_sql_queries? ? nil : params,
|
15
|
+
c.skip_backtrace,
|
16
|
+
c.full_backtrace
|
17
|
+
)
|
11
18
|
end
|
12
19
|
|
13
20
|
def start_step(name)
|
@@ -108,15 +115,18 @@ module Rack
|
|
108
115
|
end
|
109
116
|
end
|
110
117
|
end
|
118
|
+
if klass.respond_to?(:ruby2_keywords, true)
|
119
|
+
klass.send(:ruby2_keywords, with_profiling)
|
120
|
+
end
|
111
121
|
klass.send :alias_method, method, with_profiling
|
112
122
|
end
|
113
123
|
|
114
124
|
def profile_singleton_method(klass, method, type = :profile, &blk)
|
115
|
-
profile_method(singleton_class
|
125
|
+
profile_method(klass.singleton_class, method, type, &blk)
|
116
126
|
end
|
117
127
|
|
118
128
|
def unprofile_singleton_method(klass, method)
|
119
|
-
unprofile_method(singleton_class
|
129
|
+
unprofile_method(klass.singleton_class, method)
|
120
130
|
end
|
121
131
|
|
122
132
|
# Add a custom timing. These are displayed similar to SQL/query time in
|
@@ -144,14 +154,9 @@ module Rack
|
|
144
154
|
|
145
155
|
private
|
146
156
|
|
147
|
-
def singleton_class(klass)
|
148
|
-
class << klass; self; end
|
149
|
-
end
|
150
|
-
|
151
157
|
def clean_method_name(method)
|
152
158
|
method.to_s.gsub(/[\?\!]/, "")
|
153
159
|
end
|
154
|
-
|
155
160
|
end
|
156
161
|
end
|
157
162
|
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class ::Rack::MiniProfiler::SnapshotsTransporter
|
4
|
+
@@transported_snapshots_count = 0
|
5
|
+
@@successful_http_requests_count = 0
|
6
|
+
@@failed_http_requests_count = 0
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def transported_snapshots_count
|
10
|
+
@@transported_snapshots_count
|
11
|
+
end
|
12
|
+
def successful_http_requests_count
|
13
|
+
@@successful_http_requests_count
|
14
|
+
end
|
15
|
+
def failed_http_requests_count
|
16
|
+
@@failed_http_requests_count
|
17
|
+
end
|
18
|
+
|
19
|
+
def transport(snapshot)
|
20
|
+
@transporter ||= self.new(Rack::MiniProfiler.config)
|
21
|
+
@transporter.ship(snapshot)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
attr_reader :buffer
|
26
|
+
attr_accessor :max_buffer_size, :gzip_requests
|
27
|
+
|
28
|
+
def initialize(config)
|
29
|
+
@uri = URI(config.snapshots_transport_destination_url)
|
30
|
+
@auth_key = config.snapshots_transport_auth_key
|
31
|
+
@gzip_requests = config.snapshots_transport_gzip_requests
|
32
|
+
@thread = nil
|
33
|
+
@thread_mutex = Mutex.new
|
34
|
+
@buffer = []
|
35
|
+
@buffer_mutex = Mutex.new
|
36
|
+
@max_buffer_size = 100
|
37
|
+
@consecutive_failures_count = 0
|
38
|
+
@testing = false
|
39
|
+
end
|
40
|
+
|
41
|
+
def ship(snapshot)
|
42
|
+
@buffer_mutex.synchronize do
|
43
|
+
@buffer << snapshot
|
44
|
+
@buffer.shift if @buffer.size > @max_buffer_size
|
45
|
+
end
|
46
|
+
@thread_mutex.synchronize { start_thread }
|
47
|
+
end
|
48
|
+
|
49
|
+
def flush_buffer
|
50
|
+
buffer_content = @buffer_mutex.synchronize do
|
51
|
+
@buffer.dup if @buffer.size > 0
|
52
|
+
end
|
53
|
+
if buffer_content
|
54
|
+
headers = {
|
55
|
+
'Content-Type' => 'application/json',
|
56
|
+
'Mini-Profiler-Transport-Auth' => @auth_key
|
57
|
+
}
|
58
|
+
json = { snapshots: buffer_content }.to_json
|
59
|
+
body = if @gzip_requests
|
60
|
+
require 'zlib'
|
61
|
+
io = StringIO.new
|
62
|
+
gzip_writer = Zlib::GzipWriter.new(io)
|
63
|
+
gzip_writer.write(json)
|
64
|
+
gzip_writer.close
|
65
|
+
headers['Content-Encoding'] = 'gzip'
|
66
|
+
io.string
|
67
|
+
else
|
68
|
+
json
|
69
|
+
end
|
70
|
+
request = Net::HTTP::Post.new(@uri, headers)
|
71
|
+
request.body = body
|
72
|
+
http = Net::HTTP.new(@uri.hostname, @uri.port)
|
73
|
+
http.use_ssl = @uri.scheme == 'https'
|
74
|
+
res = http.request(request)
|
75
|
+
if res.code.to_i == 200
|
76
|
+
@@successful_http_requests_count += 1
|
77
|
+
@@transported_snapshots_count += buffer_content.size
|
78
|
+
@buffer_mutex.synchronize do
|
79
|
+
@buffer -= buffer_content
|
80
|
+
end
|
81
|
+
@consecutive_failures_count = 0
|
82
|
+
else
|
83
|
+
@@failed_http_requests_count += 1
|
84
|
+
@consecutive_failures_count += 1
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def requests_interval
|
90
|
+
[30 + backoff_delay, 60 * 60].min
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
|
95
|
+
def backoff_delay
|
96
|
+
return 0 if @consecutive_failures_count == 0
|
97
|
+
2**@consecutive_failures_count
|
98
|
+
end
|
99
|
+
|
100
|
+
def start_thread
|
101
|
+
return if @thread&.alive? || @testing
|
102
|
+
@thread = Thread.new do
|
103
|
+
while true
|
104
|
+
sleep requests_interval
|
105
|
+
flush_buffer
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -36,11 +36,62 @@ module Rack
|
|
36
36
|
""
|
37
37
|
end
|
38
38
|
|
39
|
-
# a list of tokens that are permitted to access profiler in
|
39
|
+
# a list of tokens that are permitted to access profiler in explicit mode
|
40
40
|
def allowed_tokens
|
41
41
|
raise NotImplementedError.new("allowed_tokens is not implemented")
|
42
42
|
end
|
43
43
|
|
44
|
+
def should_take_snapshot?(period)
|
45
|
+
raise NotImplementedError.new("should_take_snapshot? is not implemented")
|
46
|
+
end
|
47
|
+
|
48
|
+
def push_snapshot(page_struct, group_name, config)
|
49
|
+
raise NotImplementedError.new("push_snapshot is not implemented")
|
50
|
+
end
|
51
|
+
|
52
|
+
# returns a hash where the keys are group names and the values
|
53
|
+
# are hashes that contain 3 keys:
|
54
|
+
# 1. `:worst_score` => the duration of the worst/slowest snapshot in the group (float)
|
55
|
+
# 2. `:best_score` => the duration of the best/fastest snapshot in the group (float)
|
56
|
+
# 3. `:snapshots_count` => the number of snapshots in the group (integer)
|
57
|
+
def fetch_snapshots_overview
|
58
|
+
raise NotImplementedError.new("fetch_snapshots_overview is not implemented")
|
59
|
+
end
|
60
|
+
|
61
|
+
# @param group_name [String]
|
62
|
+
# @return [Array<Rack::MiniProfiler::TimerStruct::Page>] list of snapshots of the group. Blank array if the group doesn't exist.
|
63
|
+
def fetch_snapshots_group(group_name)
|
64
|
+
raise NotImplementedError.new("fetch_snapshots_group is not implemented")
|
65
|
+
end
|
66
|
+
|
67
|
+
def load_snapshot(id, group_name)
|
68
|
+
raise NotImplementedError.new("load_snapshot is not implemented")
|
69
|
+
end
|
70
|
+
|
71
|
+
def snapshots_overview
|
72
|
+
groups = fetch_snapshots_overview.to_a
|
73
|
+
groups.sort_by! { |name, hash| hash[:worst_score] }
|
74
|
+
groups.reverse!
|
75
|
+
groups.map! { |name, hash| hash.merge(name: name) }
|
76
|
+
groups
|
77
|
+
end
|
78
|
+
|
79
|
+
def snapshots_group(group_name)
|
80
|
+
snapshots = fetch_snapshots_group(group_name)
|
81
|
+
data = []
|
82
|
+
snapshots.each do |snapshot|
|
83
|
+
data << {
|
84
|
+
id: snapshot[:id],
|
85
|
+
duration: snapshot.duration_ms,
|
86
|
+
sql_count: snapshot[:sql_count],
|
87
|
+
timestamp: snapshot[:started_at],
|
88
|
+
custom_fields: snapshot[:custom_fields]
|
89
|
+
}
|
90
|
+
end
|
91
|
+
data.sort_by! { |s| s[:duration] }
|
92
|
+
data.reverse!
|
93
|
+
data
|
94
|
+
end
|
44
95
|
end
|
45
96
|
end
|
46
97
|
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'securerandom'
|
4
|
+
|
3
5
|
module Rack
|
4
6
|
class MiniProfiler
|
5
7
|
class FileStore < AbstractStore
|
@@ -17,9 +19,11 @@ module Rack
|
|
17
19
|
def [](key)
|
18
20
|
begin
|
19
21
|
data = ::File.open(path(key), "rb") { |f| f.read }
|
20
|
-
|
22
|
+
# rubocop:disable Security/MarshalLoad
|
23
|
+
Marshal.load data
|
24
|
+
# rubocop:enable Security/MarshalLoad
|
21
25
|
rescue
|
22
|
-
|
26
|
+
nil
|
23
27
|
end
|
24
28
|
end
|
25
29
|
|
@@ -31,7 +35,7 @@ module Rack
|
|
31
35
|
end
|
32
36
|
|
33
37
|
private
|
34
|
-
if
|
38
|
+
if Gem.win_platform?
|
35
39
|
def path(key)
|
36
40
|
@path.dup << "/" << @prefix << "_" << key.gsub(/:/, '_')
|
37
41
|
end
|
@@ -10,8 +10,10 @@ module Rack
|
|
10
10
|
def initialize(args = nil)
|
11
11
|
require 'dalli' unless defined? Dalli
|
12
12
|
args ||= {}
|
13
|
+
|
13
14
|
@prefix = args[:prefix] || "MPMemcacheStore"
|
14
15
|
@prefix += "-#{Rack::MiniProfiler::VERSION}"
|
16
|
+
|
15
17
|
@client = args[:client] || Dalli::Client.new
|
16
18
|
@expires_in_seconds = args[:expires_in] || EXPIRES_IN_SECONDS
|
17
19
|
end
|
@@ -22,7 +24,9 @@ module Rack
|
|
22
24
|
|
23
25
|
def load(id)
|
24
26
|
raw = @client.get("#{@prefix}#{id}")
|
25
|
-
|
27
|
+
# rubocop:disable Security/MarshalLoad
|
28
|
+
Marshal.load(raw) if raw
|
29
|
+
# rubocop:enable Security/MarshalLoad
|
26
30
|
end
|
27
31
|
|
28
32
|
def set_unviewed(user, id)
|
@@ -63,14 +67,16 @@ module Rack
|
|
63
67
|
key1, key2, cycle_at = nil
|
64
68
|
|
65
69
|
if token_info
|
66
|
-
|
70
|
+
# rubocop:disable Security/MarshalLoad
|
71
|
+
key1, key2, cycle_at = Marshal.load(token_info)
|
72
|
+
# rubocop:enable Security/MarshalLoad
|
67
73
|
|
68
|
-
|
69
|
-
|
74
|
+
key1 = nil unless key1 && key1.length == 32
|
75
|
+
key2 = nil unless key2 && key2.length == 32
|
70
76
|
|
71
|
-
|
72
|
-
|
73
|
-
|
77
|
+
if key1 && cycle_at && (cycle_at > Process.clock_gettime(Process::CLOCK_MONOTONIC))
|
78
|
+
return [key1, key2].compact
|
79
|
+
end
|
74
80
|
end
|
75
81
|
|
76
82
|
timeout = Rack::MiniProfiler::AbstractStore::MAX_TOKEN_AGE
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'securerandom'
|
4
|
+
|
3
5
|
module Rack
|
4
6
|
class MiniProfiler
|
5
7
|
class MemoryStore < AbstractStore
|
@@ -52,17 +54,22 @@ module Rack
|
|
52
54
|
@expires_in_seconds = args.fetch(:expires_in) { EXPIRES_IN_SECONDS }
|
53
55
|
|
54
56
|
@token1, @token2, @cycle_at = nil
|
57
|
+
@snapshots_cycle = 0
|
58
|
+
@snapshot_groups = {}
|
59
|
+
@snapshots = []
|
55
60
|
|
56
61
|
initialize_locks
|
57
62
|
initialize_cleanup_thread(args)
|
58
63
|
end
|
59
64
|
|
60
65
|
def initialize_locks
|
61
|
-
@token_lock
|
62
|
-
@timer_struct_lock
|
63
|
-
@user_view_lock
|
64
|
-
@
|
65
|
-
@
|
66
|
+
@token_lock = Mutex.new
|
67
|
+
@timer_struct_lock = Mutex.new
|
68
|
+
@user_view_lock = Mutex.new
|
69
|
+
@snapshots_cycle_lock = Mutex.new
|
70
|
+
@snapshots_lock = Mutex.new
|
71
|
+
@timer_struct_cache = {}
|
72
|
+
@user_view_cache = {}
|
66
73
|
end
|
67
74
|
|
68
75
|
#FIXME: use weak ref, trouble it may be broken in 1.9 so need to use the 'ref' gem
|
@@ -135,6 +142,92 @@ module Rack
|
|
135
142
|
|
136
143
|
end
|
137
144
|
end
|
145
|
+
|
146
|
+
def should_take_snapshot?(period)
|
147
|
+
@snapshots_cycle_lock.synchronize do
|
148
|
+
@snapshots_cycle += 1
|
149
|
+
if @snapshots_cycle % period == 0
|
150
|
+
@snapshots_cycle = 0
|
151
|
+
true
|
152
|
+
else
|
153
|
+
false
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def push_snapshot(page_struct, group_name, config)
|
159
|
+
@snapshots_lock.synchronize do
|
160
|
+
group = @snapshot_groups[group_name]
|
161
|
+
if !group
|
162
|
+
@snapshot_groups[group_name] = {
|
163
|
+
worst_score: page_struct.duration_ms,
|
164
|
+
best_score: page_struct.duration_ms,
|
165
|
+
snapshots: [page_struct]
|
166
|
+
}
|
167
|
+
if @snapshot_groups.size > config.max_snapshot_groups
|
168
|
+
group_keys = @snapshot_groups.keys
|
169
|
+
group_keys.sort_by! do |key|
|
170
|
+
@snapshot_groups[key][:worst_score]
|
171
|
+
end
|
172
|
+
group_keys.reverse!
|
173
|
+
group_keys.pop(group_keys.size - config.max_snapshot_groups)
|
174
|
+
@snapshot_groups = @snapshot_groups.slice(*group_keys)
|
175
|
+
end
|
176
|
+
else
|
177
|
+
snapshots = group[:snapshots]
|
178
|
+
snapshots << page_struct
|
179
|
+
snapshots.sort_by!(&:duration_ms)
|
180
|
+
snapshots.reverse!
|
181
|
+
if snapshots.size > config.max_snapshots_per_group
|
182
|
+
snapshots.pop(snapshots.size - config.max_snapshots_per_group)
|
183
|
+
end
|
184
|
+
group[:worst_score] = snapshots[0].duration_ms
|
185
|
+
group[:best_score] = snapshots[-1].duration_ms
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
def fetch_snapshots_overview
|
191
|
+
@snapshots_lock.synchronize do
|
192
|
+
groups = {}
|
193
|
+
@snapshot_groups.each do |name, group|
|
194
|
+
groups[name] = {
|
195
|
+
worst_score: group[:worst_score],
|
196
|
+
best_score: group[:best_score],
|
197
|
+
snapshots_count: group[:snapshots].size
|
198
|
+
}
|
199
|
+
end
|
200
|
+
groups
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
def fetch_snapshots_group(group_name)
|
205
|
+
@snapshots_lock.synchronize do
|
206
|
+
group = @snapshot_groups[group_name]
|
207
|
+
if group
|
208
|
+
group[:snapshots].dup
|
209
|
+
else
|
210
|
+
[]
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
def load_snapshot(id, group_name)
|
216
|
+
@snapshots_lock.synchronize do
|
217
|
+
group = @snapshot_groups[group_name]
|
218
|
+
if group
|
219
|
+
group[:snapshots].find { |s| s[:id] == id }
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
private
|
225
|
+
|
226
|
+
# used in tests only
|
227
|
+
def wipe_snapshots_data
|
228
|
+
@snapshots_cycle = 0
|
229
|
+
@snapshot_groups = {}
|
230
|
+
end
|
138
231
|
end
|
139
232
|
end
|
140
233
|
end
|