rack-mini-profiler 0.10.6 → 2.3.0
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 +5 -5
- data/CHANGELOG.md +129 -16
- data/README.md +116 -63
- data/lib/enable_rails_patches.rb +5 -0
- data/lib/generators/rack_profiler/install_generator.rb +2 -0
- data/lib/generators/rack_profiler/templates/rack_profiler.rb +2 -0
- data/lib/html/dot.1.1.2.min.js +2 -0
- data/lib/html/includes.css +141 -40
- data/lib/html/includes.js +1398 -970
- data/lib/html/includes.scss +547 -442
- data/lib/html/includes.tmpl +227 -142
- 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/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 +27 -16
- data/lib/mini_profiler/config.rb +73 -46
- data/lib/mini_profiler/context.rb +5 -3
- data/lib/mini_profiler/gc_profiler.rb +17 -16
- data/lib/mini_profiler/profiler.rb +332 -94
- data/lib/mini_profiler/profiling_methods.rb +20 -15
- data/lib/mini_profiler/snapshots_transporter.rb +109 -0
- data/lib/mini_profiler/storage/abstract_store.rb +80 -0
- data/lib/mini_profiler/storage/file_store.rb +18 -13
- data/lib/mini_profiler/storage/memcache_store.rb +10 -7
- data/lib/mini_profiler/storage/memory_store.rb +63 -13
- data/lib/mini_profiler/storage/redis_store.rb +143 -7
- data/lib/mini_profiler/timer_struct/base.rb +4 -2
- data/lib/mini_profiler/timer_struct/client.rb +9 -8
- data/lib/mini_profiler/timer_struct/custom.rb +8 -5
- data/lib/mini_profiler/timer_struct/page.rb +79 -24
- data/lib/mini_profiler/timer_struct/request.rb +83 -38
- data/lib/mini_profiler/timer_struct/sql.rb +25 -22
- data/lib/mini_profiler/version.rb +3 -1
- data/lib/mini_profiler_rails/railtie.rb +91 -8
- data/lib/mini_profiler_rails/railtie_methods.rb +61 -0
- data/lib/patches/db/activerecord.rb +5 -14
- data/lib/patches/db/mongo.rb +3 -1
- data/lib/patches/db/moped.rb +5 -3
- data/lib/patches/db/mysql2.rb +8 -6
- data/lib/patches/db/neo4j.rb +3 -1
- data/lib/patches/db/nobrainer.rb +4 -2
- data/lib/patches/db/oracle_enhanced.rb +4 -2
- data/lib/patches/db/pg.rb +41 -21
- data/lib/patches/db/plucky.rb +7 -5
- data/lib/patches/db/riak.rb +15 -13
- data/lib/patches/db/rsolr.rb +6 -4
- data/lib/patches/db/sequel.rb +2 -0
- data/lib/patches/net_patches.rb +20 -8
- data/lib/patches/sql_patches.rb +17 -7
- data/lib/prepend_net_http_patch.rb +5 -0
- data/lib/rack-mini-profiler.rb +3 -3
- data/rack-mini-profiler.gemspec +23 -9
- metadata +146 -31
- 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
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Rack
|
2
4
|
class MiniProfiler
|
3
5
|
module ProfilingMethods
|
@@ -5,14 +7,21 @@ module Rack
|
|
5
7
|
def record_sql(query, elapsed_ms, params = nil)
|
6
8
|
return unless current && current.current_timer
|
7
9
|
c = current
|
8
|
-
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
|
+
)
|
9
18
|
end
|
10
19
|
|
11
20
|
def start_step(name)
|
12
21
|
return unless current
|
13
22
|
parent_timer = current.current_timer
|
14
23
|
current.current_timer = current_timer = current.current_timer.add_child(name)
|
15
|
-
[current_timer,parent_timer]
|
24
|
+
[current_timer, parent_timer]
|
16
25
|
end
|
17
26
|
|
18
27
|
def finish_step(obj)
|
@@ -61,7 +70,7 @@ module Rack
|
|
61
70
|
end
|
62
71
|
|
63
72
|
def profile_method(klass, method, type = :profile, &blk)
|
64
|
-
default_name = type
|
73
|
+
default_name = type == :counter ? method.to_s : klass.to_s + " " + method.to_s
|
65
74
|
clean = clean_method_name(method)
|
66
75
|
|
67
76
|
with_profiling = ("#{clean}_with_mini_profiler").intern
|
@@ -89,12 +98,12 @@ module Rack
|
|
89
98
|
parent_timer = Rack::MiniProfiler.current.current_timer
|
90
99
|
|
91
100
|
if type == :counter
|
92
|
-
start =
|
101
|
+
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
93
102
|
begin
|
94
103
|
self.send without_profiling, *args, &orig
|
95
104
|
ensure
|
96
|
-
duration_ms = (
|
97
|
-
parent_timer.add_custom(name, duration_ms, Rack::MiniProfiler.current.page_struct
|
105
|
+
duration_ms = (Process.clock_gettime(Process::CLOCK_MONOTONIC) - start).to_f * 1000
|
106
|
+
parent_timer.add_custom(name, duration_ms, Rack::MiniProfiler.current.page_struct)
|
98
107
|
end
|
99
108
|
else
|
100
109
|
Rack::MiniProfiler.current.current_timer = current_timer = parent_timer.add_child(name)
|
@@ -110,11 +119,11 @@ module Rack
|
|
110
119
|
end
|
111
120
|
|
112
121
|
def profile_singleton_method(klass, method, type = :profile, &blk)
|
113
|
-
profile_method(singleton_class
|
122
|
+
profile_method(klass.singleton_class, method, type, &blk)
|
114
123
|
end
|
115
124
|
|
116
125
|
def unprofile_singleton_method(klass, method)
|
117
|
-
unprofile_method(singleton_class
|
126
|
+
unprofile_method(klass.singleton_class, method)
|
118
127
|
end
|
119
128
|
|
120
129
|
# Add a custom timing. These are displayed similar to SQL/query time in
|
@@ -128,12 +137,12 @@ module Rack
|
|
128
137
|
# and keeping a record of its run time.
|
129
138
|
#
|
130
139
|
# Returns the result of the block, or nil when no block is given.
|
131
|
-
def counter(type, duration_ms=nil)
|
140
|
+
def counter(type, duration_ms = nil)
|
132
141
|
result = nil
|
133
142
|
if block_given?
|
134
|
-
start =
|
143
|
+
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
135
144
|
result = yield
|
136
|
-
duration_ms = (
|
145
|
+
duration_ms = (Process.clock_gettime(Process::CLOCK_MONOTONIC) - start).to_f * 1000
|
137
146
|
end
|
138
147
|
return result if current.nil? || !request_authorized?
|
139
148
|
current.current_timer.add_custom(type, duration_ms, current.page_struct)
|
@@ -142,10 +151,6 @@ module Rack
|
|
142
151
|
|
143
152
|
private
|
144
153
|
|
145
|
-
def singleton_class(klass)
|
146
|
-
class << klass; self; end
|
147
|
-
end
|
148
|
-
|
149
154
|
def clean_method_name(method)
|
150
155
|
method.to_s.gsub(/[\?\!]/, "")
|
151
156
|
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
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Rack
|
2
4
|
class MiniProfiler
|
3
5
|
class AbstractStore
|
@@ -39,6 +41,84 @@ module Rack
|
|
39
41
|
raise NotImplementedError.new("allowed_tokens is not implemented")
|
40
42
|
end
|
41
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, config)
|
49
|
+
raise NotImplementedError.new("push_snapshot is not implemented")
|
50
|
+
end
|
51
|
+
|
52
|
+
def fetch_snapshots(batch_size: 200, &blk)
|
53
|
+
raise NotImplementedError.new("fetch_snapshots is not implemented")
|
54
|
+
end
|
55
|
+
|
56
|
+
def snapshot_groups_overview
|
57
|
+
groups = {}
|
58
|
+
fetch_snapshots do |batch|
|
59
|
+
batch.each do |snapshot|
|
60
|
+
group_name = default_snapshot_grouping(snapshot)
|
61
|
+
hash = groups[group_name] ||= {}
|
62
|
+
hash[:snapshots_count] ||= 0
|
63
|
+
hash[:snapshots_count] += 1
|
64
|
+
if !hash[:worst_score] || hash[:worst_score] < snapshot.duration_ms
|
65
|
+
groups[group_name][:worst_score] = snapshot.duration_ms
|
66
|
+
end
|
67
|
+
if !hash[:best_score] || hash[:best_score] > snapshot.duration_ms
|
68
|
+
groups[group_name][:best_score] = snapshot.duration_ms
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
groups = groups.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 find_snapshots_group(group_name)
|
80
|
+
data = []
|
81
|
+
fetch_snapshots do |batch|
|
82
|
+
batch.each do |snapshot|
|
83
|
+
snapshot_group_name = default_snapshot_grouping(snapshot)
|
84
|
+
if group_name == snapshot_group_name
|
85
|
+
data << {
|
86
|
+
id: snapshot[:id],
|
87
|
+
duration: snapshot.duration_ms,
|
88
|
+
sql_count: snapshot[:sql_count],
|
89
|
+
timestamp: snapshot[:started_at],
|
90
|
+
custom_fields: snapshot[:custom_fields]
|
91
|
+
}
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
data.sort_by! { |s| s[:duration] }
|
96
|
+
data.reverse!
|
97
|
+
data
|
98
|
+
end
|
99
|
+
|
100
|
+
def load_snapshot(id)
|
101
|
+
raise NotImplementedError.new("load_snapshot is not implemented")
|
102
|
+
end
|
103
|
+
|
104
|
+
private
|
105
|
+
|
106
|
+
def default_snapshot_grouping(snapshot)
|
107
|
+
group_name = rails_route_from_path(snapshot[:request_path], snapshot[:request_method])
|
108
|
+
group_name ||= snapshot[:request_path]
|
109
|
+
"#{snapshot[:request_method]} #{group_name}"
|
110
|
+
end
|
111
|
+
|
112
|
+
def rails_route_from_path(path, method)
|
113
|
+
if defined?(Rails) && defined?(ActionController::RoutingError)
|
114
|
+
hash = Rails.application.routes.recognize_path(path, method: method)
|
115
|
+
if hash && hash[:controller] && hash[:action]
|
116
|
+
"#{hash[:controller]}##{hash[:action]}"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
rescue ActionController::RoutingError
|
120
|
+
nil
|
121
|
+
end
|
42
122
|
end
|
43
123
|
end
|
44
124
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Rack
|
2
4
|
class MiniProfiler
|
3
5
|
class FileStore < AbstractStore
|
@@ -14,25 +16,28 @@ module Rack
|
|
14
16
|
|
15
17
|
def [](key)
|
16
18
|
begin
|
17
|
-
data = ::File.open(path(key),"rb") {|f| f.read}
|
18
|
-
|
19
|
-
rescue
|
20
|
-
|
19
|
+
data = ::File.open(path(key), "rb") { |f| f.read }
|
20
|
+
Marshal.load data
|
21
|
+
rescue
|
22
|
+
nil
|
21
23
|
end
|
22
24
|
end
|
23
25
|
|
24
|
-
def []=(key,val)
|
25
|
-
::File.open(path(key), "wb+")
|
26
|
+
def []=(key, val)
|
27
|
+
::File.open(path(key), "wb+") do |f|
|
28
|
+
f.sync = true
|
29
|
+
f.write Marshal.dump(val)
|
30
|
+
end
|
26
31
|
end
|
27
32
|
|
28
33
|
private
|
29
|
-
if
|
34
|
+
if Gem.win_platform?
|
30
35
|
def path(key)
|
31
|
-
@path
|
36
|
+
@path.dup << "/" << @prefix << "_" << key.gsub(/:/, '_')
|
32
37
|
end
|
33
38
|
else
|
34
39
|
def path(key)
|
35
|
-
@path
|
40
|
+
@path.dup << "/" << @prefix << "_" << key
|
36
41
|
end
|
37
42
|
end
|
38
43
|
end
|
@@ -139,10 +144,10 @@ module Rack
|
|
139
144
|
@auth_token_lock.synchronize {
|
140
145
|
token1, token2, cycle_at = @auth_token_cache[""]
|
141
146
|
|
142
|
-
unless cycle_at && (
|
147
|
+
unless cycle_at && (Float === cycle_at) && (cycle_at > Process.clock_gettime(Process::CLOCK_MONOTONIC))
|
143
148
|
token2 = token1
|
144
149
|
token1 = SecureRandom.hex
|
145
|
-
cycle_at =
|
150
|
+
cycle_at = Process.clock_gettime(Process::CLOCK_MONOTONIC) + Rack::MiniProfiler::AbstractStore::MAX_TOKEN_AGE
|
146
151
|
end
|
147
152
|
|
148
153
|
@auth_token_cache[""] = [token1, token2, cycle_at]
|
@@ -156,13 +161,13 @@ module Rack
|
|
156
161
|
@timer_struct_lock.synchronize {
|
157
162
|
files.each do |f|
|
158
163
|
f = @path + '/' + f
|
159
|
-
::File.delete f if ::File.basename(f) =~ /^mp_timers/
|
164
|
+
::File.delete f if ::File.basename(f) =~ (/^mp_timers/) && ((Time.now - ::File.mtime(f)) > @expires_in_seconds)
|
160
165
|
end
|
161
166
|
}
|
162
167
|
@user_view_lock.synchronize {
|
163
168
|
files.each do |f|
|
164
169
|
f = @path + '/' + f
|
165
|
-
::File.delete f if ::File.basename(f) =~ /^mp_views/
|
170
|
+
::File.delete f if ::File.basename(f) =~ (/^mp_views/) && ((Time.now - ::File.mtime(f)) > @expires_in_seconds)
|
166
171
|
end
|
167
172
|
}
|
168
173
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Rack
|
2
4
|
class MiniProfiler
|
3
5
|
class MemcacheStore < AbstractStore
|
@@ -8,8 +10,10 @@ module Rack
|
|
8
10
|
def initialize(args = nil)
|
9
11
|
require 'dalli' unless defined? Dalli
|
10
12
|
args ||= {}
|
11
|
-
|
12
|
-
@prefix
|
13
|
+
|
14
|
+
@prefix = args[:prefix] || "MPMemcacheStore"
|
15
|
+
@prefix += "-#{Rack::MiniProfiler::VERSION}"
|
16
|
+
|
13
17
|
@client = args[:client] || Dalli::Client.new
|
14
18
|
@expires_in_seconds = args[:expires_in] || EXPIRES_IN_SECONDS
|
15
19
|
end
|
@@ -60,15 +64,14 @@ module Rack
|
|
60
64
|
token_info = @client.get("#{@prefix}-tokens")
|
61
65
|
key1, key2, cycle_at = nil
|
62
66
|
|
63
|
-
|
64
67
|
if token_info
|
65
|
-
|
68
|
+
key1, key2, cycle_at = Marshal::load(token_info)
|
66
69
|
|
67
70
|
key1 = nil unless key1 && key1.length == 32
|
68
71
|
key2 = nil unless key2 && key2.length == 32
|
69
72
|
|
70
|
-
if key1 && cycle_at && (cycle_at >
|
71
|
-
|
73
|
+
if key1 && cycle_at && (cycle_at > Process.clock_gettime(Process::CLOCK_MONOTONIC))
|
74
|
+
return [key1, key2].compact
|
72
75
|
end
|
73
76
|
end
|
74
77
|
|
@@ -77,7 +80,7 @@ module Rack
|
|
77
80
|
# cycle keys
|
78
81
|
key2 = key1
|
79
82
|
key1 = SecureRandom.hex
|
80
|
-
cycle_at =
|
83
|
+
cycle_at = Process.clock_gettime(Process::CLOCK_MONOTONIC) + timeout
|
81
84
|
|
82
85
|
@client.set("#{@prefix}-tokens", Marshal::dump([key1, key2, cycle_at]))
|
83
86
|
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Rack
|
2
4
|
class MiniProfiler
|
3
5
|
class MemoryStore < AbstractStore
|
@@ -17,7 +19,6 @@ module Rack
|
|
17
19
|
@cycle_count * @interval >= @cycle
|
18
20
|
end
|
19
21
|
|
20
|
-
|
21
22
|
# We don't want to hit the filesystem every 10s to clean up the cache so we need to do a bit of
|
22
23
|
# accounting to avoid sleeping that entire time. We don't want to sleep for the entire period because
|
23
24
|
# it means the thread will stay live in hot deployment scenarios, keeping a potentially large memory
|
@@ -50,27 +51,31 @@ module Rack
|
|
50
51
|
args ||= {}
|
51
52
|
@expires_in_seconds = args.fetch(:expires_in) { EXPIRES_IN_SECONDS }
|
52
53
|
|
53
|
-
@token1, @token2, @
|
54
|
+
@token1, @token2, @cycle_at = nil
|
55
|
+
@snapshots_cycle = 0
|
56
|
+
@snapshots = []
|
54
57
|
|
55
58
|
initialize_locks
|
56
59
|
initialize_cleanup_thread(args)
|
57
60
|
end
|
58
61
|
|
59
62
|
def initialize_locks
|
60
|
-
@token_lock
|
61
|
-
@timer_struct_lock
|
62
|
-
@user_view_lock
|
63
|
-
@
|
64
|
-
@
|
63
|
+
@token_lock = Mutex.new
|
64
|
+
@timer_struct_lock = Mutex.new
|
65
|
+
@user_view_lock = Mutex.new
|
66
|
+
@snapshots_cycle_lock = Mutex.new
|
67
|
+
@snapshots_lock = Mutex.new
|
68
|
+
@timer_struct_cache = {}
|
69
|
+
@user_view_cache = {}
|
65
70
|
end
|
66
71
|
|
67
72
|
#FIXME: use weak ref, trouble it may be broken in 1.9 so need to use the 'ref' gem
|
68
|
-
def initialize_cleanup_thread(args={})
|
73
|
+
def initialize_cleanup_thread(args = {})
|
69
74
|
cleanup_interval = args.fetch(:cleanup_interval) { CLEANUP_INTERVAL }
|
70
75
|
cleanup_cycle = args.fetch(:cleanup_cycle) { CLEANUP_CYCLE }
|
71
|
-
t = CacheCleanupThread.new(cleanup_interval, cleanup_cycle, self) do
|
76
|
+
t = CacheCleanupThread.new(cleanup_interval, cleanup_cycle, self) do
|
72
77
|
until Thread.current[:should_exit] do
|
73
|
-
|
78
|
+
t.sleepy_run
|
74
79
|
end
|
75
80
|
end
|
76
81
|
at_exit { t[:should_exit] = true }
|
@@ -115,7 +120,7 @@ module Rack
|
|
115
120
|
end
|
116
121
|
|
117
122
|
def cleanup_cache
|
118
|
-
expire_older_than = ((
|
123
|
+
expire_older_than = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - @expires_in_seconds) * 1000).to_i
|
119
124
|
@timer_struct_lock.synchronize {
|
120
125
|
@timer_struct_cache.delete_if { |k, v| v[:started] < expire_older_than }
|
121
126
|
}
|
@@ -124,16 +129,61 @@ module Rack
|
|
124
129
|
def allowed_tokens
|
125
130
|
@token_lock.synchronize do
|
126
131
|
|
127
|
-
unless @cycle_at && (@cycle_at >
|
132
|
+
unless @cycle_at && (@cycle_at > Process.clock_gettime(Process::CLOCK_MONOTONIC))
|
128
133
|
@token2 = @token1
|
129
134
|
@token1 = SecureRandom.hex
|
130
|
-
@cycle_at =
|
135
|
+
@cycle_at = Process.clock_gettime(Process::CLOCK_MONOTONIC) + Rack::MiniProfiler::AbstractStore::MAX_TOKEN_AGE
|
131
136
|
end
|
132
137
|
|
133
138
|
[@token1, @token2].compact
|
134
139
|
|
135
140
|
end
|
136
141
|
end
|
142
|
+
|
143
|
+
def should_take_snapshot?(period)
|
144
|
+
@snapshots_cycle_lock.synchronize do
|
145
|
+
@snapshots_cycle += 1
|
146
|
+
if @snapshots_cycle % period == 0
|
147
|
+
@snapshots_cycle = 0
|
148
|
+
true
|
149
|
+
else
|
150
|
+
false
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def push_snapshot(page_struct, config)
|
156
|
+
@snapshots_lock.synchronize do
|
157
|
+
@snapshots << page_struct
|
158
|
+
@snapshots.sort_by! { |s| s.duration_ms }
|
159
|
+
@snapshots.reverse!
|
160
|
+
if @snapshots.size > config.snapshots_limit
|
161
|
+
@snapshots.slice!(-1)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
def fetch_snapshots(batch_size: 200, &blk)
|
167
|
+
@snapshots_lock.synchronize do
|
168
|
+
@snapshots.each_slice(batch_size) do |batch|
|
169
|
+
blk.call(batch)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def load_snapshot(id)
|
175
|
+
@snapshots_lock.synchronize do
|
176
|
+
@snapshots.find { |s| s[:id] == id }
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
private
|
181
|
+
|
182
|
+
# used in tests only
|
183
|
+
def wipe_snapshots_data
|
184
|
+
@snapshots_cycle = 0
|
185
|
+
@snapshots = []
|
186
|
+
end
|
137
187
|
end
|
138
188
|
end
|
139
189
|
end
|