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.
Files changed (74) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +129 -16
  3. data/README.md +116 -63
  4. data/lib/enable_rails_patches.rb +5 -0
  5. data/lib/generators/rack_profiler/install_generator.rb +2 -0
  6. data/lib/generators/rack_profiler/templates/rack_profiler.rb +2 -0
  7. data/lib/html/dot.1.1.2.min.js +2 -0
  8. data/lib/html/includes.css +141 -40
  9. data/lib/html/includes.js +1398 -970
  10. data/lib/html/includes.scss +547 -442
  11. data/lib/html/includes.tmpl +227 -142
  12. data/lib/html/pretty-print.js +810 -0
  13. data/lib/html/profile_handler.js +1 -1
  14. data/lib/html/rack-mini-profiler.css +3 -0
  15. data/lib/html/rack-mini-profiler.js +2 -0
  16. data/lib/html/share.html +0 -1
  17. data/lib/html/speedscope/LICENSE +21 -0
  18. data/lib/html/speedscope/README.md +3 -0
  19. data/lib/html/speedscope/demangle-cpp.1768f4cc.js +4 -0
  20. data/lib/html/speedscope/favicon-16x16.f74b3187.png +0 -0
  21. data/lib/html/speedscope/favicon-32x32.bc503437.png +0 -0
  22. data/lib/html/speedscope/file-format-schema.json +324 -0
  23. data/lib/html/speedscope/import.cf0fa83f.js +115 -0
  24. data/lib/html/speedscope/index.html +2 -0
  25. data/lib/html/speedscope/release.txt +3 -0
  26. data/lib/html/speedscope/reset.8c46b7a1.css +2 -0
  27. data/lib/html/speedscope/source-map.438fa06b.js +24 -0
  28. data/lib/html/speedscope/speedscope.44364064.js +200 -0
  29. data/lib/html/vendor.js +848 -0
  30. data/lib/mini_profiler/asset_version.rb +3 -2
  31. data/lib/mini_profiler/client_settings.rb +27 -16
  32. data/lib/mini_profiler/config.rb +73 -46
  33. data/lib/mini_profiler/context.rb +5 -3
  34. data/lib/mini_profiler/gc_profiler.rb +17 -16
  35. data/lib/mini_profiler/profiler.rb +332 -94
  36. data/lib/mini_profiler/profiling_methods.rb +20 -15
  37. data/lib/mini_profiler/snapshots_transporter.rb +109 -0
  38. data/lib/mini_profiler/storage/abstract_store.rb +80 -0
  39. data/lib/mini_profiler/storage/file_store.rb +18 -13
  40. data/lib/mini_profiler/storage/memcache_store.rb +10 -7
  41. data/lib/mini_profiler/storage/memory_store.rb +63 -13
  42. data/lib/mini_profiler/storage/redis_store.rb +143 -7
  43. data/lib/mini_profiler/timer_struct/base.rb +4 -2
  44. data/lib/mini_profiler/timer_struct/client.rb +9 -8
  45. data/lib/mini_profiler/timer_struct/custom.rb +8 -5
  46. data/lib/mini_profiler/timer_struct/page.rb +79 -24
  47. data/lib/mini_profiler/timer_struct/request.rb +83 -38
  48. data/lib/mini_profiler/timer_struct/sql.rb +25 -22
  49. data/lib/mini_profiler/version.rb +3 -1
  50. data/lib/mini_profiler_rails/railtie.rb +91 -8
  51. data/lib/mini_profiler_rails/railtie_methods.rb +61 -0
  52. data/lib/patches/db/activerecord.rb +5 -14
  53. data/lib/patches/db/mongo.rb +3 -1
  54. data/lib/patches/db/moped.rb +5 -3
  55. data/lib/patches/db/mysql2.rb +8 -6
  56. data/lib/patches/db/neo4j.rb +3 -1
  57. data/lib/patches/db/nobrainer.rb +4 -2
  58. data/lib/patches/db/oracle_enhanced.rb +4 -2
  59. data/lib/patches/db/pg.rb +41 -21
  60. data/lib/patches/db/plucky.rb +7 -5
  61. data/lib/patches/db/riak.rb +15 -13
  62. data/lib/patches/db/rsolr.rb +6 -4
  63. data/lib/patches/db/sequel.rb +2 -0
  64. data/lib/patches/net_patches.rb +20 -8
  65. data/lib/patches/sql_patches.rb +17 -7
  66. data/lib/prepend_net_http_patch.rb +5 -0
  67. data/lib/rack-mini-profiler.rb +3 -3
  68. data/rack-mini-profiler.gemspec +23 -9
  69. metadata +146 -31
  70. data/lib/html/jquery.1.7.1.js +0 -4
  71. data/lib/html/jquery.tmpl.js +0 -486
  72. data/lib/html/list.css +0 -9
  73. data/lib/html/list.js +0 -38
  74. 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(query, elapsed_ms, c.page_struct, params, c.skip_backtrace, c.full_backtrace)
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==:counter ? method.to_s : klass.to_s + " " + method.to_s
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 = Time.now
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 = (Time.now - start).to_f * 1000
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(klass), method, type, &blk)
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(klass), method)
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 = Time.now
143
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
135
144
  result = yield
136
- duration_ms = (Time.now - start).to_f * 1000
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
- return Marshal.load data
19
- rescue => e
20
- return nil
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+") {|f| f.write Marshal.dump(val)}
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 RUBY_PLATFORM =~ /mswin(?!ce)|mingw|cygwin|bccwin/
34
+ if Gem.win_platform?
30
35
  def path(key)
31
- @path + "/" + @prefix + "_" + key.gsub(/:/, '_')
36
+ @path.dup << "/" << @prefix << "_" << key.gsub(/:/, '_')
32
37
  end
33
38
  else
34
39
  def path(key)
35
- @path + "/" + @prefix + "_" + key
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 && (Time === cycle_at) && (cycle_at > Time.now)
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 = Time.now + Rack::MiniProfiler::AbstractStore::MAX_TOKEN_AGE
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/ and (Time.now - ::File.mtime(f)) > @expires_in_seconds
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/ and (Time.now - ::File.mtime(f)) > @expires_in_seconds
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
- @prefix = args[:prefix] || "MPMemcacheStore"
12
- @prefix += "-#{Rack::MiniProfiler::VERSION}"
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
- key1, key2, cycle_at = Marshal::load(token_info)
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 > Time.now)
71
- return [key1,key2].compact
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 = Time.now + timeout
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, @cycle_tokens_at = nil
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 = Mutex.new
61
- @timer_struct_lock = Mutex.new
62
- @user_view_lock = Mutex.new
63
- @timer_struct_cache = {}
64
- @user_view_cache = {}
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 |t|
76
+ t = CacheCleanupThread.new(cleanup_interval, cleanup_cycle, self) do
72
77
  until Thread.current[:should_exit] do
73
- self.sleepy_run
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 = ((Time.now.to_f - @expires_in_seconds) * 1000).to_i
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 > Time.now)
132
+ unless @cycle_at && (@cycle_at > Process.clock_gettime(Process::CLOCK_MONOTONIC))
128
133
  @token2 = @token1
129
134
  @token1 = SecureRandom.hex
130
- @cycle_at = Time.now + Rack::MiniProfiler::AbstractStore::MAX_TOKEN_AGE
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