rack-mini-profiler 0.9.9.2 → 0.10.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a2b10bd196bae4ba4dd1b18d3bdf54744e895f86
4
- data.tar.gz: 33866dddf039f21eca7380c23fd31a09071d8666
3
+ metadata.gz: ba607e06976028497914287036a4b96012892cc5
4
+ data.tar.gz: b4427269223c942280e544847be135d1fdd51943
5
5
  SHA512:
6
- metadata.gz: 010b6b5b681db1dc8fddab3100c450a533be17b53290d07e9fafd89e29ac3fe6c6659371b978796455ebc8c6131192c1386186f1285abb81e5a1ccffcfe9e79a
7
- data.tar.gz: 9cacdd67dc52d42556e6e0793d76675bf3bbc0ed27a748cd6f407b5996dc7a2f437e7ed559f905620ed267595bf3f79cf5744b25f5b4b7fd9f527c9c4b2bdf10
6
+ metadata.gz: 32f96b3ba1bb3667417e0255130aee336780167f28b06209b026ae5ccbe87542e5c100bd3297f178ead24a90deb27b460cdf90f409b63da2ba705cc7bb2fabfa
7
+ data.tar.gz: c62db46c5de3423581d3f0f2ca466f338b4d390d6bdad9f3bc0e8cb7a3f210ef24295ff0dfcd85925608c6bc05f85b0f5abcccd7a17ae0e053bb658faafd01be
@@ -1,6 +1,9 @@
1
1
  # CHANGELOG
2
2
 
3
- ##
3
+ ## 0.10.1 2016-05-18
4
+
5
+ - [FEATURE] push forward the security checks so no work is ever done if a valid production
6
+ cookie is not available (@sam)
4
7
 
5
8
  ## 0.9.9.2 2016-03-06
6
9
 
data/README.md CHANGED
@@ -196,6 +196,15 @@ window.MiniProfiler.pageTransition();
196
196
 
197
197
  This method will remove profiling information that was related to previous page and clear aggregate statistics.
198
198
 
199
+ #### MiniProfiler's speed badge on pages that are not generated via Rails
200
+ You need to inject the following in your SPA to load MiniProfiler's speed badge ([extra details surrounding this script](https://github.com/MiniProfiler/rack-mini-profiler/issues/139#issuecomment-192880706)):
201
+
202
+ ```html
203
+ <script async type="text/javascript" id="mini-profiler" src="/mini-profiler-resources/includes.js?v=12b4b45a3c42e6e15503d7a03810ff33" data-version="12b4b45a3c42e6e15503d7a03810ff33" data-path="/mini-profiler-resources/" data-current-id="redo66j4g1077kto8uh3" data-ids="redo66j4g1077kto8uh3" data-position="left" data-trivial="false" data-children="false" data-max-traces="10" data-controls="false" data-authorized="true" data-toggle-shortcut="Alt+P" data-start-hidden="false" data-collapse-results="true"></script>
204
+ ```
205
+
206
+ _Note:_ The GUID (`data-version` and the `?v=` parameter on the `src`) will change with each release of `rack_mini_profiler`. The MiniProfiler's speed badge will continue to work, although you will have to change the GUID to expire the script to fetch the most recent version.
207
+
199
208
  ### Configuration Options
200
209
 
201
210
  You can set configuration options using the configuration accessor on `Rack::MiniProfiler`.
@@ -220,10 +229,11 @@ backtrace_remove|rails: `Rails.root`<br>Rack: `nil`|A string or regex to remove
220
229
  toggle_shortcut|Alt+P|Keyboard shortcut to toggle the mini_profiler's visibility. See [jquery.hotkeys](https://github.com/jeresig/jquery.hotkeys).
221
230
  start_hidden|`false`|`false` to make mini_profiler visible on page load.
222
231
  backtrace_threshold_ms|`0`|Minimum SQL query elapsed time before a backtrace is recorded. Backtrace recording can take a couple of milliseconds on rubies earlier than 2.0, impacting performance for very small queries.
223
- flamegraph_sample_rate|`0.5ms`|How often to capture stack traces for flamegraphs.
232
+ flamegraph_sample_rate|`0.5`|How often to capture stack traces for flamegraphs in milliseconds.
224
233
  disable_env_dump|`false`|`true` disables `?pp=env`, which prevents sending ENV vars over HTTP.
225
234
  base_url_path|`'/mini-profiler-resources/'`|Path for assets; added as a prefix when naming assets and sought when responding to requests.
226
235
  collapse_results|`true`|If multiple timing results exist in a single page, collapse them till clicked.
236
+ max_traces_to_show|20|Maximum number of mini profiler timing blocks to show on one page
227
237
 
228
238
  ### Custom middleware ordering (required if using `Rack::Deflate` with Rails)
229
239
 
@@ -12,39 +12,90 @@ module Rack
12
12
  attr_accessor :backtrace_level
13
13
 
14
14
 
15
- def initialize(env)
15
+ def initialize(env, store, start)
16
16
  request = ::Rack::Request.new(env)
17
17
  @cookie = request.cookies[COOKIE_NAME]
18
+ @store = store
19
+ @start = start
20
+
21
+ @allowed_tokens, @orig_auth_tokens = nil
22
+
18
23
  if @cookie
19
24
  @cookie.split(",").map{|pair| pair.split("=")}.each do |k,v|
20
25
  @orig_disable_profiling = @disable_profiling = (v=='t') if k == "dp"
21
26
  @backtrace_level = v.to_i if k == "bt"
27
+ @orig_auth_tokens = v.to_s.split("|") if k == "a"
22
28
  end
23
29
  end
24
30
 
25
- @backtrace_level = nil if !@backtrace_level.nil? && (@backtrace_level == 0 || @backtrace_level > BACKTRACE_NONE)
31
+ if !@backtrace_level.nil? && (@backtrace_level == 0 || @backtrace_level > BACKTRACE_NONE)
32
+ @backtrace_level = nil
33
+ end
34
+
26
35
  @orig_backtrace_level = @backtrace_level
27
36
 
28
37
  end
29
38
 
39
+ def handle_cookie(result)
40
+ status,headers,_body = result
41
+
42
+ if (MiniProfiler.config.authorization_mode == :whitelist && !MiniProfiler.request_authorized?)
43
+ # this is non-obvious, don't kill the profiling cookie on errors or short requests
44
+ # this ensures that stuff that never reaches the rails stack does not kill profiling
45
+ if status.to_i >= 200 && status.to_i < 300 && ((Time.now - @start) > 0.1)
46
+ discard_cookie!(headers)
47
+ end
48
+ else
49
+ write!(headers)
50
+ end
51
+
52
+ result
53
+ end
54
+
30
55
  def write!(headers)
31
- if @orig_disable_profiling != @disable_profiling || @orig_backtrace_level != @backtrace_level || @cookie.nil?
56
+
57
+ tokens_changed = false
58
+
59
+ if MiniProfiler.request_authorized? && MiniProfiler.config.authorization_mode == :whitelist
60
+ @allowed_tokens ||= @store.allowed_tokens
61
+ tokens_changed = !@orig_auth_tokens || ((@allowed_tokens - @orig_auth_tokens).length > 0)
62
+ end
63
+
64
+ if @orig_disable_profiling != @disable_profiling ||
65
+ @orig_backtrace_level != @backtrace_level ||
66
+ @cookie.nil? ||
67
+ tokens_changed
68
+
32
69
  settings = {"p" => "t" }
33
- settings["dp"] = "t" if @disable_profiling
34
- settings["bt"] = @backtrace_level if @backtrace_level
70
+ settings["dp"] = "t" if @disable_profiling
71
+ settings["bt"] = @backtrace_level if @backtrace_level
72
+ settings["a"] = @allowed_tokens.join("|") if @allowed_tokens && MiniProfiler.request_authorized?
73
+
35
74
  settings_string = settings.map{|k,v| "#{k}=#{v}"}.join(",")
36
75
  Rack::Utils.set_cookie_header!(headers, COOKIE_NAME, :value => settings_string, :path => '/')
37
76
  end
38
77
  end
39
78
 
40
79
  def discard_cookie!(headers)
41
- Rack::Utils.delete_cookie_header!(headers, COOKIE_NAME, :path => '/')
80
+ if @cookie
81
+ Rack::Utils.delete_cookie_header!(headers, COOKIE_NAME, :path => '/')
82
+ end
42
83
  end
43
84
 
44
- def has_cookie?
45
- !@cookie.nil?
85
+ def has_valid_cookie?
86
+ valid_cookie = !@cookie.nil?
87
+
88
+ if (MiniProfiler.config.authorization_mode == :whitelist)
89
+ @allowed_tokens ||= @store.allowed_tokens
90
+
91
+ valid_cookie = (Array === @orig_auth_tokens) &&
92
+ ((@allowed_tokens & @orig_auth_tokens).length > 0)
93
+ end
94
+
95
+ valid_cookie
46
96
  end
47
97
 
98
+
48
99
  def disable_profiling?
49
100
  @disable_profiling
50
101
  end
@@ -15,10 +15,15 @@ module Rack
15
15
  attr_accessor :authorization_mode, :auto_inject, :backtrace_ignores,
16
16
  :backtrace_includes, :backtrace_remove, :backtrace_threshold_ms,
17
17
  :base_url_path, :disable_caching, :disable_env_dump, :enabled,
18
- :flamegraph_sample_rate, :logger, :position, :pre_authorize_cb,
19
- :skip_paths, :skip_schema_queries, :start_hidden, :storage,
20
- :storage_failure, :storage_instance, :storage_options, :toggle_shortcut,
21
- :user_provider, :collapse_results
18
+ :flamegraph_sample_rate, :logger, :pre_authorize_cb, :skip_paths,
19
+ :skip_schema_queries, :storage, :storage_failure, :storage_instance,
20
+ :storage_options, :user_provider
21
+ attr_accessor :skip_sql_param_names, :max_sql_param_length
22
+
23
+ # ui accessors
24
+ attr_accessor :collapse_results, :max_traces_to_show, :position,
25
+ :show_children, :show_controls, :show_trivial, :start_hidden,
26
+ :toggle_shortcut
22
27
 
23
28
  # Deprecated options
24
29
  attr_accessor :use_existing_jquery
@@ -32,13 +37,10 @@ module Rack
32
37
  @pre_authorize_cb = lambda {|env| true}
33
38
 
34
39
  # called after rack chain, to ensure we are REALLY allowed to profile
35
- @position = 'left' # Where it is displayed
36
40
  @skip_schema_queries = false
37
41
  @storage = MiniProfiler::MemoryStore
38
42
  @user_provider = Proc.new{|env| Rack::Request.new(env).ip}
39
43
  @authorization_mode = :allow_all
40
- @toggle_shortcut = 'Alt+P'
41
- @start_hidden = false
42
44
  @backtrace_threshold_ms = 0
43
45
  @flamegraph_sample_rate = 0.5
44
46
  @storage_failure = Proc.new do |exception|
@@ -48,7 +50,20 @@ module Rack
48
50
  end
49
51
  @enabled = true
50
52
  @disable_env_dump = false
51
- @collapse_results = true
53
+ @max_sql_param_length = 0 # disable sql parameter collection by default
54
+ @skip_sql_param_names = /password/ # skips parameters with the name password by default
55
+
56
+ # ui parameters
57
+ @autorized = true
58
+ @collapse_results = true
59
+ @max_traces_to_show = 20
60
+ @position = 'left' # Where it is displayed
61
+ @show_children = false
62
+ @show_controls = false
63
+ @show_trivial = false
64
+ @start_hidden = false
65
+ @toggle_shortcut = 'Alt+P'
66
+
52
67
  self
53
68
  }
54
69
  end
@@ -1,5 +1,6 @@
1
1
  class Rack::MiniProfiler::Context
2
- attr_accessor :inject_js,:current_timer,:page_struct,:skip_backtrace,:full_backtrace,:discard, :mpt_init, :measure
2
+ attr_accessor :inject_js,:current_timer,:page_struct,:skip_backtrace,
3
+ :full_backtrace,:discard, :mpt_init, :measure
3
4
 
4
5
  def initialize(opts = {})
5
6
  opts["measure"] = true unless opts.key? "measure"
@@ -120,7 +120,8 @@ module Rack
120
120
  end
121
121
 
122
122
  def serve_html(env)
123
- file_name = env['PATH_INFO'][(@config.base_url_path.length)..1000]
123
+ path = env['PATH_INFO'].sub('//', '/')
124
+ file_name = path.sub(@config.base_url_path, '')
124
125
 
125
126
  return serve_results(env) if file_name.eql?('results')
126
127
 
@@ -148,11 +149,13 @@ module Rack
148
149
 
149
150
  def call(env)
150
151
 
151
- client_settings = ClientSettings.new(env)
152
+ start = Time.now
153
+ client_settings = ClientSettings.new(env, @storage, start)
154
+ MiniProfiler.deauthorize_request if @config.authorization_mode == :whitelist
152
155
 
153
156
  status = headers = body = nil
154
157
  query_string = env['QUERY_STRING']
155
- path = env['PATH_INFO']
158
+ path = env['PATH_INFO'].sub('//', '/')
156
159
 
157
160
  # Someone (e.g. Rails engine) could change the SCRIPT_NAME so we save it
158
161
  env['RACK_MINI_PROFILER_ORIGINAL_SCRIPT_NAME'] = env['SCRIPT_NAME']
@@ -161,18 +164,15 @@ module Rack
161
164
  (@config.skip_paths && @config.skip_paths.any?{ |p| path.start_with?(p) }) ||
162
165
  query_string =~ /pp=skip/
163
166
 
164
- has_profiling_cookie = client_settings.has_cookie?
165
-
166
- if skip_it || (@config.authorization_mode == :whitelist && !has_profiling_cookie)
167
- status,headers,body = @app.call(env)
168
- if !skip_it && @config.authorization_mode == :whitelist && !has_profiling_cookie && MiniProfiler.request_authorized?
169
- client_settings.write!(headers)
170
- end
171
- return [status,headers,body]
167
+ if skip_it || (
168
+ @config.authorization_mode == :whitelist &&
169
+ !client_settings.has_valid_cookie?
170
+ )
171
+ return client_settings.handle_cookie(@app.call(env))
172
172
  end
173
173
 
174
174
  # handle all /mini-profiler requests here
175
- return serve_html(env) if path.start_with? @config.base_url_path
175
+ return client_settings.handle_cookie(serve_html(env)) if path.start_with? @config.base_url_path
176
176
 
177
177
  has_disable_cookie = client_settings.disable_profiling?
178
178
  # manual session disable / enable
@@ -180,7 +180,7 @@ module Rack
180
180
  skip_it = true
181
181
  end
182
182
 
183
- if query_string =~ /pp=enable/ && (@config.authorization_mode != :whitelist || MiniProfiler.request_authorized?)
183
+ if query_string =~ /pp=enable/
184
184
  skip_it = false
185
185
  config.enabled = true
186
186
  end
@@ -188,8 +188,7 @@ module Rack
188
188
  if skip_it || !config.enabled
189
189
  status,headers,body = @app.call(env)
190
190
  client_settings.disable_profiling = true
191
- client_settings.write!(headers)
192
- return [status,headers,body]
191
+ return client_settings.handle_cookie([status,headers,body])
193
192
  else
194
193
  client_settings.disable_profiling = false
195
194
  end
@@ -197,7 +196,7 @@ module Rack
197
196
  # profile gc
198
197
  if query_string =~ /pp=profile-gc/
199
198
  current.measure = false if current
200
- return Rack::MiniProfiler::GCProfiler.new.profile_gc(@app, env)
199
+ return client_settings.handle_cookie(Rack::MiniProfiler::GCProfiler.new.profile_gc(@app, env))
201
200
  end
202
201
 
203
202
  # profile memory
@@ -214,11 +213,10 @@ module Rack
214
213
  body.close if body.respond_to? :close
215
214
  end
216
215
  report.pretty_print(result)
217
- return text_result(result.string)
216
+ return client_settings.handle_cookie(text_result(result.string))
218
217
  end
219
218
 
220
219
  MiniProfiler.create_current(env, @config)
221
- MiniProfiler.deauthorize_request if @config.authorization_mode == :whitelist
222
220
 
223
221
  if query_string =~ /pp=normal-backtrace/
224
222
  client_settings.backtrace_level = ClientSettings::BACKTRACE_DEFAULT
@@ -237,7 +235,6 @@ module Rack
237
235
  trace_exceptions = query_string =~ /pp=trace-exceptions/ && defined? TracePoint
238
236
  status, headers, body, exceptions,trace = nil
239
237
 
240
- start = Time.now
241
238
 
242
239
  if trace_exceptions
243
240
  exceptions = []
@@ -280,7 +277,6 @@ module Rack
280
277
  else
281
278
  status,headers,body = @app.call(env)
282
279
  end
283
- client_settings.write!(headers)
284
280
  ensure
285
281
  trace.disable if trace
286
282
  end
@@ -288,35 +284,30 @@ module Rack
288
284
  skip_it = current.discard
289
285
 
290
286
  if (config.authorization_mode == :whitelist && !MiniProfiler.request_authorized?)
291
- # this is non-obvious, don't kill the profiling cookie on errors or short requests
292
- # this ensures that stuff that never reaches the rails stack does not kill profiling
293
- if status.to_i >= 200 && status.to_i < 300 && ((Time.now - start) > 0.1)
294
- client_settings.discard_cookie!(headers)
295
- end
296
287
  skip_it = true
297
288
  end
298
289
 
299
- return [status,headers,body] if skip_it
290
+ return client_settings.handle_cookie([status,headers,body]) if skip_it
300
291
 
301
292
  # we must do this here, otherwise current[:discard] is not being properly treated
302
293
  if trace_exceptions
303
294
  body.close if body.respond_to? :close
304
- return dump_exceptions exceptions
295
+ return client_settings.handle_cookie(dump_exceptions exceptions)
305
296
  end
306
297
 
307
298
  if query_string =~ /pp=env/ && !config.disable_env_dump
308
299
  body.close if body.respond_to? :close
309
- return dump_env env
300
+ return client_settings.handle_cookie(dump_env env)
310
301
  end
311
302
 
312
303
  if query_string =~ /pp=analyze-memory/
313
304
  body.close if body.respond_to? :close
314
- return analyze_memory
305
+ return client_settings.handle_cookie(analyze_memory)
315
306
  end
316
307
 
317
308
  if query_string =~ /pp=help/
318
309
  body.close if body.respond_to? :close
319
- return help(client_settings, env)
310
+ return client_settings.handle_cookie(help(client_settings, env))
320
311
  end
321
312
 
322
313
  page_struct = current.page_struct
@@ -325,7 +316,7 @@ module Rack
325
316
 
326
317
  if flamegraph
327
318
  body.close if body.respond_to? :close
328
- return self.flamegraph(flamegraph)
319
+ return client_settings.handle_cookie(self.flamegraph(flamegraph))
329
320
  end
330
321
 
331
322
 
@@ -336,9 +327,8 @@ module Rack
336
327
 
337
328
  # inject headers, script
338
329
  if status >= 200 && status < 300
339
- client_settings.write!(headers)
340
330
  result = inject_profiler(env,status,headers,body)
341
- return result if result
331
+ return client_settings.handle_cookie(result) if result
342
332
  end
343
333
  rescue Exception => e
344
334
  if @config.storage_failure != nil
@@ -346,8 +336,7 @@ module Rack
346
336
  end
347
337
  end
348
338
 
349
- client_settings.write!(headers)
350
- [status, headers, body]
339
+ client_settings.handle_cookie([status, headers, body])
351
340
 
352
341
  ensure
353
342
  # Make sure this always happens
@@ -542,7 +531,6 @@ Append the following to your query string:
542
531
  </html>
543
532
  "
544
533
 
545
- client_settings.write!(headers)
546
534
  [200, headers, [body]]
547
535
  end
548
536
 
@@ -552,8 +540,12 @@ Append the following to your query string:
552
540
  end
553
541
 
554
542
  def ids(env)
555
- # cap at 10 ids, otherwise there is a chance you can blow the header
556
- ([current.page_struct[:id]] + (@storage.get_unviewed_ids(user(env)) || [])[0..8]).uniq
543
+ all = ([current.page_struct[:id]] + (@storage.get_unviewed_ids(user(env)) || [])).uniq
544
+ if all.size > @config.max_traces_to_show
545
+ all = all[0...@config.max_traces_to_show]
546
+ @storage.set_all_unviewed(user(env), all)
547
+ end
548
+ all
557
549
  end
558
550
 
559
551
  def ids_json(env)
@@ -587,10 +579,10 @@ Append the following to your query string:
587
579
  :path => path,
588
580
  :version => MiniProfiler::ASSET_VERSION,
589
581
  :position => @config.position,
590
- :showTrivial => false,
591
- :showChildren => false,
592
- :maxTracesToShow => 10,
593
- :showControls => false,
582
+ :showTrivial => @config.show_trivial,
583
+ :showChildren => @config.show_children,
584
+ :maxTracesToShow => @config.max_traces_to_show,
585
+ :showControls => @config.show_controls,
594
586
  :authorized => true,
595
587
  :toggleShortcut => @config.toggle_shortcut,
596
588
  :startHidden => @config.start_hidden,
@@ -2,10 +2,10 @@ module Rack
2
2
  class MiniProfiler
3
3
  module ProfilingMethods
4
4
 
5
- def record_sql(query, elapsed_ms)
5
+ def record_sql(query, elapsed_ms, params = nil)
6
6
  return unless current && current.current_timer
7
7
  c = current
8
- c.current_timer.add_sql(query, elapsed_ms, c.page_struct, c.skip_backtrace, c.full_backtrace)
8
+ c.current_timer.add_sql(query, elapsed_ms, c.page_struct, params, c.skip_backtrace, c.full_backtrace)
9
9
  end
10
10
 
11
11
  def start_step(name)
@@ -2,6 +2,9 @@ module Rack
2
2
  class MiniProfiler
3
3
  class AbstractStore
4
4
 
5
+ # maximum age of allowed tokens before cycling in seconds
6
+ MAX_TOKEN_AGE = 1800
7
+
5
8
  def save(page_struct)
6
9
  raise NotImplementedError.new("save is not implemented")
7
10
  end
@@ -18,6 +21,10 @@ module Rack
18
21
  raise NotImplementedError.new("set_viewed is not implemented")
19
22
  end
20
23
 
24
+ def set_all_unviewed(user, ids)
25
+ raise NotImplementedError.new("set_all_unviewed is not implemented")
26
+ end
27
+
21
28
  def get_unviewed_ids(user)
22
29
  raise NotImplementedError.new("get_unviewed_ids is not implemented")
23
30
  end
@@ -27,6 +34,11 @@ module Rack
27
34
  ""
28
35
  end
29
36
 
37
+ # a list of tokens that are permitted to access profiler in whitelist mode
38
+ def allowed_tokens
39
+ raise NotImplementedError.new("allowed_tokens is not implemented")
40
+ end
41
+
30
42
  end
31
43
  end
32
44
  end
@@ -51,6 +51,9 @@ module Rack
51
51
  @user_view_cache = FileCache.new(@path, "mp_views")
52
52
  @user_view_lock = Mutex.new
53
53
 
54
+ @auth_token_cache = FileCache.new(@path, "tokens")
55
+ @auth_token_lock = Mutex.new
56
+
54
57
  me = self
55
58
  t = CacheCleanupThread.new do
56
59
  interval = 10
@@ -114,12 +117,40 @@ module Rack
114
117
  }
115
118
  end
116
119
 
120
+ def set_all_unviewed(user, ids)
121
+ @user_view_lock.synchronize {
122
+ @user_view_cache[user] = ids.uniq
123
+ }
124
+ end
125
+
117
126
  def get_unviewed_ids(user)
118
127
  @user_view_lock.synchronize {
119
128
  @user_view_cache[user]
120
129
  }
121
130
  end
122
131
 
132
+ def flush_tokens
133
+ @auth_token_lock.synchronize {
134
+ @auth_token_cache[""] = nil
135
+ }
136
+ end
137
+
138
+ def allowed_tokens
139
+ @auth_token_lock.synchronize {
140
+ token1, token2, cycle_at = @auth_token_cache[""]
141
+
142
+ unless cycle_at && (Time === cycle_at) && (cycle_at > Time.now)
143
+ token2 = token1
144
+ token1 = SecureRandom.hex
145
+ cycle_at = Time.now + Rack::MiniProfiler::AbstractStore::MAX_TOKEN_AGE
146
+ end
147
+
148
+ @auth_token_cache[""] = [token1, token2, cycle_at]
149
+
150
+ [token1, token2].compact
151
+ }
152
+ end
153
+
123
154
  def cleanup_cache
124
155
  files = Dir.entries(@path)
125
156
  @timer_struct_lock.synchronize {
@@ -43,10 +43,47 @@ module Rack
43
43
  end
44
44
  end
45
45
 
46
+ def set_all_unviewed(user, ids)
47
+ @client.set("#{@prefix}-#{user}-v", ids, @expires_in_seconds)
48
+ end
49
+
46
50
  def get_unviewed_ids(user)
47
51
  @client.get("#{@prefix}-#{user}-v") || []
48
52
  end
49
53
 
54
+ def flush_tokens
55
+ @client.set("#{@prefix}-tokens", nil)
56
+ end
57
+
58
+ def allowed_tokens
59
+
60
+ token_info = @client.get("#{@prefix}-tokens")
61
+ key1, key2, cycle_at = nil
62
+
63
+
64
+ if token_info
65
+ key1, key2, cycle_at = Marshal::load(token_info)
66
+
67
+ key1 = nil unless key1 && key1.length == 32
68
+ key2 = nil unless key2 && key2.length == 32
69
+
70
+ if key1 && cycle_at && (cycle_at > Time.now)
71
+ return [key1,key2].compact
72
+ end
73
+ end
74
+
75
+ timeout = Rack::MiniProfiler::AbstractStore::MAX_TOKEN_AGE
76
+
77
+ # cycle keys
78
+ key2 = key1
79
+ key1 = SecureRandom.hex
80
+ cycle_at = Time.now + timeout
81
+
82
+ @client.set("#{@prefix}-tokens", Marshal::dump([key1, key2, cycle_at]))
83
+
84
+ [key1, key2].compact
85
+ end
86
+
50
87
  end
51
88
  end
52
89
  end
@@ -49,11 +49,15 @@ module Rack
49
49
  def initialize(args = nil)
50
50
  args ||= {}
51
51
  @expires_in_seconds = args.fetch(:expires_in) { EXPIRES_IN_SECONDS }
52
+
53
+ @token1, @token2, @cycle_tokens_at = nil
54
+
52
55
  initialize_locks
53
56
  initialize_cleanup_thread(args)
54
57
  end
55
58
 
56
59
  def initialize_locks
60
+ @token_lock = Mutex.new
57
61
  @timer_struct_lock = Mutex.new
58
62
  @user_view_lock = Mutex.new
59
63
  @timer_struct_cache = {}
@@ -98,6 +102,12 @@ module Rack
98
102
  }
99
103
  end
100
104
 
105
+ def set_all_unviewed(user, ids)
106
+ @user_view_lock.synchronize {
107
+ @user_view_cache[user] = ids
108
+ }
109
+ end
110
+
101
111
  def get_unviewed_ids(user)
102
112
  @user_view_lock.synchronize {
103
113
  @user_view_cache[user]
@@ -110,6 +120,20 @@ module Rack
110
120
  @timer_struct_cache.delete_if { |k, v| v[:started] < expire_older_than }
111
121
  }
112
122
  end
123
+
124
+ def allowed_tokens
125
+ @token_lock.synchronize do
126
+
127
+ unless @cycle_at && (@cycle_at > Time.now)
128
+ @token2 = @token1
129
+ @token1 = SecureRandom.hex
130
+ @cycle_at = Time.now + Rack::MiniProfiler::AbstractStore::MAX_TOKEN_AGE
131
+ end
132
+
133
+ [@token1, @token2].compact
134
+
135
+ end
136
+ end
113
137
  end
114
138
  end
115
139
  end
@@ -2,6 +2,8 @@ module Rack
2
2
  class MiniProfiler
3
3
  class RedisStore < AbstractStore
4
4
 
5
+ attr_reader :prefix
6
+
5
7
  EXPIRES_IN_SECONDS = 60 * 60 * 24
6
8
 
7
9
  def initialize(args = nil)
@@ -33,6 +35,13 @@ module Rack
33
35
  redis.expire key, @expires_in_seconds
34
36
  end
35
37
 
38
+ def set_all_unviewed(user, ids)
39
+ key = "#{@prefix}-#{user}-v"
40
+ redis.del key
41
+ ids.each { |id| redis.sadd(key, id) }
42
+ redis.expire key, @expires_in_seconds
43
+ end
44
+
36
45
  def set_viewed(user, id)
37
46
  redis.srem "#{@prefix}-#{user}-v", id
38
47
  end
@@ -48,6 +57,43 @@ unviewed_ids: #{get_unviewed_ids(user)}
48
57
  "
49
58
  end
50
59
 
60
+ def flush_tokens
61
+ redis.del("#{@prefix}-key1", "#{@prefix}-key1_old", "#{@prefix}-key2")
62
+ end
63
+
64
+ # Only used for testing
65
+ def simulate_expire
66
+ redis.del("#{@prefix}-key1")
67
+ end
68
+
69
+ def allowed_tokens
70
+ key1, key1_old, key2 = redis.mget("#{@prefix}-key1", "#{@prefix}-key1_old", "#{@prefix}-key2")
71
+
72
+ if key1 && (key1.length == 32)
73
+ return [key1, key2].compact
74
+ end
75
+
76
+ timeout = Rack::MiniProfiler::AbstractStore::MAX_TOKEN_AGE
77
+
78
+ # TODO this could be moved to lua to correct a concurrency flaw
79
+ # it is not critical cause worse case some requests will miss profiling info
80
+
81
+ # no key so go ahead and set it
82
+ key1 = SecureRandom.hex
83
+
84
+ if key1_old && (key1_old.length == 32)
85
+ key2 = key1_old
86
+ redis.setex "#{@prefix}-key2", timeout, key2
87
+ else
88
+ key2 = nil
89
+ end
90
+
91
+ redis.setex "#{@prefix}-key1", timeout, key1
92
+ redis.setex "#{@prefix}-key1_old", timeout*2, key1
93
+
94
+ [key1, key2].compact
95
+ end
96
+
51
97
  private
52
98
 
53
99
  def redis
@@ -89,8 +89,8 @@ module Rack
89
89
  end
90
90
  end
91
91
 
92
- def add_sql(query, elapsed_ms, page, skip_backtrace = false, full_backtrace = false)
93
- TimerStruct::Sql.new(query, elapsed_ms, page, self , skip_backtrace, full_backtrace).tap do |timer|
92
+ def add_sql(query, elapsed_ms, page, params = nil, skip_backtrace = false, full_backtrace = false)
93
+ TimerStruct::Sql.new(query, elapsed_ms, page, self, params, skip_backtrace, full_backtrace).tap do |timer|
94
94
  self[:sql_timings].push(timer)
95
95
  timer[:parent_timing_id] = self[:id]
96
96
  self[:has_sql_timings] = true
@@ -4,7 +4,7 @@ module Rack
4
4
  # Timing system for a SQL query
5
5
  module TimerStruct
6
6
  class Sql < TimerStruct::Base
7
- def initialize(query, duration_ms, page, parent, skip_backtrace = false, full_backtrace = false)
7
+ def initialize(query, duration_ms, page, parent, params = nil, skip_backtrace = false, full_backtrace = false)
8
8
 
9
9
  stack_trace = nil
10
10
  unless skip_backtrace || duration_ms < Rack::MiniProfiler.config.backtrace_threshold_ms
@@ -39,7 +39,7 @@ module Rack
39
39
  :start_milliseconds => start_millis,
40
40
  :duration_milliseconds => duration_ms,
41
41
  :first_fetch_duration_milliseconds => duration_ms,
42
- :parameters => nil,
42
+ :parameters => trim_binds(params),
43
43
  :parent_timing_id => nil,
44
44
  :is_duplicate => false
45
45
  )
@@ -53,6 +53,22 @@ module Rack
53
53
  @page[:duration_milliseconds_in_sql] += elapsed_ms
54
54
  end
55
55
 
56
+ def trim_binds(binds)
57
+ max_len = Rack::MiniProfiler.config.max_sql_param_length
58
+ return if binds.nil? || max_len == 0
59
+ return binds if max_len.nil?
60
+ binds.map do |(name, val)|
61
+ val ||= name
62
+ if val.nil? || val == true || val == false || val.kind_of?(Numeric)
63
+ # keep these parameters as is
64
+ elsif val.kind_of?(String)
65
+ val = val[0...max_len] if max_len
66
+ else
67
+ val = val.class.name
68
+ end
69
+ [name, val]
70
+ end
71
+ end
56
72
  end
57
73
  end
58
74
  end
@@ -1,5 +1,5 @@
1
1
  module Rack
2
2
  class MiniProfiler
3
- VERSION = '0.9.9.2'
3
+ VERSION = '0.10.1'
4
4
  end
5
5
  end
@@ -13,20 +13,29 @@ module Rack
13
13
  end
14
14
  end
15
15
 
16
+ def binds_to_params(binds)
17
+ return if binds.nil? || Rack::MiniProfiler.config.max_sql_param_length == 0
18
+ # map ActiveRecord::Relation::QueryAttribute to [name, value]
19
+ params = binds.map { |c| c.kind_of?(Array) ? [c.first, c.last] : [c.name, c.value] }
20
+ if (skip = Rack::MiniProfiler.config.skip_sql_param_names)
21
+ params.map { |(n,v)| n =~ skip ? [n, nil] : [n, v] }
22
+ else
23
+ params
24
+ end
25
+ end
26
+
16
27
  def log_with_miniprofiler(*args, &block)
17
28
  return log_without_miniprofiler(*args, &block) unless SqlPatches.should_measure?
18
29
 
19
- sql, name, _binds = args
30
+ sql, name, binds = args
20
31
  start = Time.now
21
32
  rval = log_without_miniprofiler(*args, &block)
22
33
 
23
34
  # Don't log schema queries if the option is set
24
- # return rval unless sql =~ /\"vms\"/
25
- # return rval unless sql =~ /\"(vms|ext_management_systems)\"/
26
35
  return rval if Rack::MiniProfiler.config.skip_schema_queries and name =~ /SCHEMA/
27
36
 
28
37
  elapsed_time = SqlPatches.elapsed_time(start)
29
- Rack::MiniProfiler.record_sql(sql, elapsed_time)
38
+ Rack::MiniProfiler.record_sql(sql, elapsed_time, binds_to_params(binds))
30
39
  rval
31
40
  end
32
41
  end
@@ -1,4 +1,7 @@
1
1
  class SqlPatches
2
+ def self.unpatched?
3
+ !patched?
4
+ end
2
5
 
3
6
  def self.patched?
4
7
  @patched
@@ -8,28 +11,16 @@ class SqlPatches
8
11
  @patched = val
9
12
  end
10
13
 
11
- def self.class_exists?(name)
12
- eval(name + ".class").to_s.eql?('Class')
13
- rescue NameError
14
- false
15
- end
16
-
17
14
  def self.correct_version?(required_version, klass)
18
15
  Gem::Dependency.new('', required_version).match?('', klass::VERSION)
19
16
  rescue NameError
20
17
  false
21
18
  end
22
19
 
23
- def self.module_exists?(name)
24
- eval(name + ".class").to_s.eql?('Module')
25
- rescue NameError
26
- false
27
- end
28
-
29
- def self.record_sql(statement, &block)
20
+ def self.record_sql(statement, parameters = nil, &block)
30
21
  start = Time.now
31
22
  result = yield
32
- record = ::Rack::MiniProfiler.record_sql( statement, elapsed_time(start) )
23
+ record = ::Rack::MiniProfiler.record_sql(statement, elapsed_time(start), parameters)
33
24
  return result, record
34
25
  end
35
26
 
@@ -43,15 +34,15 @@ class SqlPatches
43
34
  end
44
35
  end
45
36
 
46
- require 'patches/db/mysql2' if defined?(Mysql2::Client) && SqlPatches.class_exists?("Mysql2::Client")
47
- require 'patches/db/pg' if defined?(PG::Result) && SqlPatches.class_exists?("PG::Result")
48
- require 'patches/db/oracle_enhanced' if defined?(ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter) && SqlPatches.class_exists?("ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter") && SqlPatches.correct_version?('~> 1.5.0', ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter)
49
- require 'patches/db/mongo' if defined?(Mongo) && SqlPatches.module_exists?("Mongo")
50
- require 'patches/db/moped' if defined?(Moped::Node) && SqlPatches.class_exists?("Moped::Node")
51
- require 'patches/db/plucky' if defined?(Plucky::Query) && SqlPatches.class_exists?("Plucky::Query")
52
- require 'patches/db/rsolr' if defined?(RSolr::Connection) && SqlPatches.class_exists?("RSolr::Connection") && RSolr::VERSION[0] != "0"
53
- require 'patches/db/sequel' if defined?(Sequel::Database) && !SqlPatches.patched? && SqlPatches.class_exists?("Sequel::Database")
54
- require 'patches/db/activerecord' if defined?(ActiveRecord) &&!SqlPatches.patched? && SqlPatches.module_exists?("ActiveRecord")
55
- require 'patches/db/nobrainer' if defined?(NoBrainer) && SqlPatches.module_exists?("NoBrainer")
56
- require 'patches/db/riak' if defined?(Riak) && SqlPatches.module_exists?("Riak")
57
- require 'patches/db/neo4j' if defined?(Neo4j::Core) && SqlPatches.class_exists?("Neo4j::Core::Query")
37
+ require 'patches/db/mysql2' if defined?(Mysql2::Client) && Mysql2::Client.class == Class
38
+ require 'patches/db/pg' if defined?(PG::Result) && PG::Result.class == Class
39
+ require 'patches/db/oracle_enhanced' if defined?(ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter) && ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.class == Class && SqlPatches.correct_version?('~> 1.5.0', ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter)
40
+ require 'patches/db/mongo' if defined?(Mongo::Server::Connection) && Mongo.class == Module
41
+ require 'patches/db/moped' if defined?(Moped::Node) && Moped::Node.class == Class
42
+ require 'patches/db/plucky' if defined?(Plucky::Query) && Plucky::Query.class == Class
43
+ require 'patches/db/rsolr' if defined?(RSolr::Connection) && RSolr::Connection.class == Class && RSolr::VERSION[0] != "0"
44
+ require 'patches/db/sequel' if SqlPatches.unpatched? && defined?(Sequel::Database) && Sequel::Database.class == Class
45
+ require 'patches/db/activerecord' if SqlPatches.unpatched? && defined?(ActiveRecord) && ActiveRecord.class == Module
46
+ require 'patches/db/nobrainer' if defined?(NoBrainer) && NoBrainer.class == Module
47
+ require 'patches/db/riak' if defined?(Riak) && Riak.class == Module
48
+ require 'patches/db/neo4j' if defined?(Neo4j::Core) && Neo4j::Core::Query.class == Class
@@ -1,6 +1,7 @@
1
1
  require 'json'
2
2
  require 'timeout'
3
3
  require 'thread'
4
+ require 'securerandom'
4
5
 
5
6
  require 'mini_profiler/version'
6
7
  require 'mini_profiler/asset_version'
@@ -19,21 +19,19 @@ Gem::Specification.new do |s|
19
19
  "CHANGELOG.md"
20
20
  ]
21
21
  s.add_runtime_dependency 'rack', '>= 1.2.0'
22
- if RUBY_VERSION < "1.9"
23
- s.add_runtime_dependency 'json', '>= 1.6'
24
- end
22
+ s.required_ruby_version = '>= 1.9.3'
25
23
 
26
24
  s.add_development_dependency 'rake'
27
25
  s.add_development_dependency 'rack-test'
28
26
  s.add_development_dependency 'activerecord', '~> 3.0'
29
27
  s.add_development_dependency 'dalli'
30
- s.add_development_dependency 'rspec'
31
- s.add_development_dependency 'ZenTest'
32
- s.add_development_dependency 'autotest'
28
+ s.add_development_dependency 'rspec', '~> 2.14.1'
33
29
  s.add_development_dependency 'redis'
34
30
  s.add_development_dependency 'therubyracer'
35
31
  s.add_development_dependency 'less'
36
32
  s.add_development_dependency 'flamegraph'
33
+ s.add_development_dependency 'guard'
34
+ s.add_development_dependency 'guard-rspec'
37
35
 
38
36
  s.require_paths = ["lib"]
39
37
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rack-mini-profiler
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.9.2
4
+ version: 0.10.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sam Saffron
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2016-03-06 00:00:00.000000000 Z
13
+ date: 2016-05-18 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rack
@@ -86,18 +86,18 @@ dependencies:
86
86
  name: rspec
87
87
  requirement: !ruby/object:Gem::Requirement
88
88
  requirements:
89
- - - ">="
89
+ - - "~>"
90
90
  - !ruby/object:Gem::Version
91
- version: '0'
91
+ version: 2.14.1
92
92
  type: :development
93
93
  prerelease: false
94
94
  version_requirements: !ruby/object:Gem::Requirement
95
95
  requirements:
96
- - - ">="
96
+ - - "~>"
97
97
  - !ruby/object:Gem::Version
98
- version: '0'
98
+ version: 2.14.1
99
99
  - !ruby/object:Gem::Dependency
100
- name: ZenTest
100
+ name: redis
101
101
  requirement: !ruby/object:Gem::Requirement
102
102
  requirements:
103
103
  - - ">="
@@ -111,7 +111,7 @@ dependencies:
111
111
  - !ruby/object:Gem::Version
112
112
  version: '0'
113
113
  - !ruby/object:Gem::Dependency
114
- name: autotest
114
+ name: therubyracer
115
115
  requirement: !ruby/object:Gem::Requirement
116
116
  requirements:
117
117
  - - ">="
@@ -125,7 +125,7 @@ dependencies:
125
125
  - !ruby/object:Gem::Version
126
126
  version: '0'
127
127
  - !ruby/object:Gem::Dependency
128
- name: redis
128
+ name: less
129
129
  requirement: !ruby/object:Gem::Requirement
130
130
  requirements:
131
131
  - - ">="
@@ -139,7 +139,7 @@ dependencies:
139
139
  - !ruby/object:Gem::Version
140
140
  version: '0'
141
141
  - !ruby/object:Gem::Dependency
142
- name: therubyracer
142
+ name: flamegraph
143
143
  requirement: !ruby/object:Gem::Requirement
144
144
  requirements:
145
145
  - - ">="
@@ -153,7 +153,7 @@ dependencies:
153
153
  - !ruby/object:Gem::Version
154
154
  version: '0'
155
155
  - !ruby/object:Gem::Dependency
156
- name: less
156
+ name: guard
157
157
  requirement: !ruby/object:Gem::Requirement
158
158
  requirements:
159
159
  - - ">="
@@ -167,7 +167,7 @@ dependencies:
167
167
  - !ruby/object:Gem::Version
168
168
  version: '0'
169
169
  - !ruby/object:Gem::Dependency
170
- name: flamegraph
170
+ name: guard-rspec
171
171
  requirement: !ruby/object:Gem::Requirement
172
172
  requirements:
173
173
  - - ">="
@@ -250,7 +250,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
250
250
  requirements:
251
251
  - - ">="
252
252
  - !ruby/object:Gem::Version
253
- version: '0'
253
+ version: 1.9.3
254
254
  required_rubygems_version: !ruby/object:Gem::Requirement
255
255
  requirements:
256
256
  - - ">="
@@ -258,7 +258,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
258
258
  version: '0'
259
259
  requirements: []
260
260
  rubyforge_project:
261
- rubygems_version: 2.4.5
261
+ rubygems_version: 2.5.1
262
262
  signing_key:
263
263
  specification_version: 4
264
264
  summary: Profiles loading speed for rack applications.