rack-mini-profiler 1.0.2 → 3.1.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 +4 -4
- data/CHANGELOG.md +145 -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 +1420 -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 +100 -7
- 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} +400 -83
- data/lib/mini_profiler_rails/railtie.rb +89 -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
|
@@ -8,11 +10,11 @@ module Rack
|
|
8
10
|
class CacheCleanupThread < Thread
|
9
11
|
|
10
12
|
def initialize(interval, cycle, store)
|
11
|
-
super
|
12
13
|
@store = store
|
13
14
|
@interval = interval
|
14
15
|
@cycle = cycle
|
15
16
|
@cycle_count = 1
|
17
|
+
super
|
16
18
|
end
|
17
19
|
|
18
20
|
def should_cleanup?
|
@@ -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
|
@@ -71,7 +78,7 @@ module Rack
|
|
71
78
|
cleanup_cycle = args.fetch(:cleanup_cycle) { CLEANUP_CYCLE }
|
72
79
|
t = CacheCleanupThread.new(cleanup_interval, cleanup_cycle, self) do
|
73
80
|
until Thread.current[:should_exit] do
|
74
|
-
|
81
|
+
Thread.current.sleepy_run
|
75
82
|
end
|
76
83
|
end
|
77
84
|
at_exit { t[:should_exit] = true }
|
@@ -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
|