rack-mini-profiler 0.9.9.2 → 0.10.1

Sign up to get free protection for your applications and to get access to all the features.
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.