rack-mini-profiler 0.9.2 → 0.9.3

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.

Potentially problematic release.


This version of rack-mini-profiler might be problematic. Click here for more details.

Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +17 -3
  3. data/lib/html/includes.css +15 -4
  4. data/lib/html/includes.js +93 -58
  5. data/lib/html/includes.less +21 -5
  6. data/lib/html/includes.tmpl +49 -49
  7. data/lib/html/list.tmpl +8 -8
  8. data/lib/mini_profiler/asset_version.rb +5 -0
  9. data/lib/mini_profiler/client_settings.rb +3 -3
  10. data/lib/mini_profiler/config.rb +11 -11
  11. data/lib/mini_profiler/gc_profiler.rb +10 -10
  12. data/lib/mini_profiler/profiler.rb +49 -71
  13. data/lib/mini_profiler/profiling_methods.rb +15 -17
  14. data/lib/mini_profiler/storage/file_store.rb +4 -4
  15. data/lib/mini_profiler/storage/memcache_store.rb +5 -7
  16. data/lib/mini_profiler/storage/memory_store.rb +56 -27
  17. data/lib/mini_profiler/storage/redis_store.rb +19 -11
  18. data/lib/mini_profiler/timer_struct/base.rb +33 -0
  19. data/lib/mini_profiler/timer_struct/client.rb +89 -0
  20. data/lib/mini_profiler/timer_struct/custom.rb +22 -0
  21. data/lib/mini_profiler/timer_struct/page.rb +62 -0
  22. data/lib/mini_profiler/timer_struct/request.rb +126 -0
  23. data/lib/mini_profiler/timer_struct/sql.rb +59 -0
  24. data/lib/mini_profiler/version.rb +2 -2
  25. data/lib/patches/db/activerecord.rb +42 -0
  26. data/lib/patches/db/moped.rb +12 -0
  27. data/lib/patches/db/mysql2.rb +30 -0
  28. data/lib/patches/db/pg.rb +104 -0
  29. data/lib/patches/db/plucky.rb +47 -0
  30. data/lib/patches/db/rsolr.rb +24 -0
  31. data/lib/patches/db/sequel.rb +10 -0
  32. data/lib/patches/sql_patches.rb +17 -255
  33. data/lib/rack-mini-profiler.rb +28 -0
  34. data/rack-mini-profiler.gemspec +6 -2
  35. metadata +16 -8
  36. data/lib/mini_profiler/client_timer_struct.rb +0 -78
  37. data/lib/mini_profiler/custom_timer_struct.rb +0 -22
  38. data/lib/mini_profiler/page_timer_struct.rb +0 -58
  39. data/lib/mini_profiler/request_timer_struct.rb +0 -115
  40. data/lib/mini_profiler/sql_timer_struct.rb +0 -58
  41. data/lib/mini_profiler/timer_struct.rb +0 -33
@@ -39,9 +39,9 @@ module Rack
39
39
  @expires_in_seconds = args[:expires_in] || EXPIRES_IN_SECONDS
40
40
  raise ArgumentError.new :path unless @path
41
41
  @timer_struct_cache = FileCache.new(@path, "mp_timers")
42
- @timer_struct_lock = Mutex.new
43
- @user_view_cache = FileCache.new(@path, "mp_views")
44
- @user_view_lock = Mutex.new
42
+ @timer_struct_lock = Mutex.new
43
+ @user_view_cache = FileCache.new(@path, "mp_views")
44
+ @user_view_lock = Mutex.new
45
45
 
46
46
  me = self
47
47
  t = CacheCleanupThread.new do
@@ -77,7 +77,7 @@ module Rack
77
77
 
78
78
  def save(page_struct)
79
79
  @timer_struct_lock.synchronize {
80
- @timer_struct_cache[page_struct['Id']] = page_struct
80
+ @timer_struct_cache[page_struct[:id]] = page_struct
81
81
  }
82
82
  end
83
83
 
@@ -3,25 +3,23 @@ module Rack
3
3
  class MemcacheStore < AbstractStore
4
4
 
5
5
  EXPIRES_IN_SECONDS = 60 * 60 * 24
6
- MAX_RETRIES = 10
6
+ MAX_RETRIES = 10
7
7
 
8
8
  def initialize(args = nil)
9
9
  require 'dalli' unless defined? Dalli
10
10
  args ||= {}
11
- @prefix = args[:prefix] || "MPMemcacheStore"
12
- @client = args[:client] || Dalli::Client.new
11
+ @prefix = args[:prefix] || "MPMemcacheStore"
12
+ @client = args[:client] || Dalli::Client.new
13
13
  @expires_in_seconds = args[:expires_in] || EXPIRES_IN_SECONDS
14
14
  end
15
15
 
16
16
  def save(page_struct)
17
- @client.set("#{@prefix}#{page_struct['Id']}", Marshal::dump(page_struct), @expires_in_seconds)
17
+ @client.set("#{@prefix}#{page_struct[:id]}", Marshal::dump(page_struct), @expires_in_seconds)
18
18
  end
19
19
 
20
20
  def load(id)
21
21
  raw = @client.get("#{@prefix}#{id}")
22
- if raw
23
- Marshal::load raw
24
- end
22
+ Marshal::load(raw) if raw
25
23
  end
26
24
 
27
25
  def set_unviewed(user, id)
@@ -4,48 +4,77 @@ module Rack
4
4
 
5
5
  # Sub-class thread so we have a named thread (useful for debugging in Thread.list).
6
6
  class CacheCleanupThread < Thread
7
+
8
+ def initialize(interval, cycle, store)
9
+ super
10
+ @store = store
11
+ @interval = interval
12
+ @cycle = cycle
13
+ @cycle_count = 1
14
+ end
15
+
16
+ def should_cleanup?
17
+ @cycle_count * @interval >= @cycle
18
+ end
19
+
20
+
21
+ # We don't want to hit the filesystem every 10s to clean up the cache so we need to do a bit of
22
+ # accounting to avoid sleeping that entire time. We don't want to sleep for the entire period because
23
+ # it means the thread will stay live in hot deployment scenarios, keeping a potentially large memory
24
+ # graph from being garbage collected upon undeploy.
25
+ def sleepy_run
26
+ cleanup if should_cleanup?
27
+ sleep(@interval)
28
+ increment_cycle
29
+ end
30
+
31
+ def cleanup
32
+ @store.cleanup_cache
33
+ @cycle_count = 1
34
+ end
35
+
36
+ def cycle_count
37
+ @cycle_count
38
+ end
39
+
40
+ def increment_cycle
41
+ @cycle_count += 1
42
+ end
7
43
  end
8
44
 
9
45
  EXPIRES_IN_SECONDS = 60 * 60 * 24
46
+ CLEANUP_INTERVAL = 10
47
+ CLEANUP_CYCLE = 3600
10
48
 
11
49
  def initialize(args = nil)
12
50
  args ||= {}
13
- @expires_in_seconds = args[:expires_in] || EXPIRES_IN_SECONDS
14
- @timer_struct_lock = Mutex.new
15
- @timer_struct_cache = {}
16
- @user_view_lock = Mutex.new
17
- @user_view_cache = {}
51
+ @expires_in_seconds = args.fetch(:expires_in) { EXPIRES_IN_SECONDS }
52
+ initialize_locks
53
+ initialize_cleanup_thread(args)
54
+ end
18
55
 
19
- # TODO: fix it to use weak ref, trouble is may be broken in 1.9 so need to use the 'ref' gem
20
- me = self
21
- t = CacheCleanupThread.new do
22
- interval = 10
23
- cleanup_cache_cycle = 3600
24
- cycle_count = 1
56
+ def initialize_locks
57
+ @timer_struct_lock = Mutex.new
58
+ @user_view_lock = Mutex.new
59
+ @timer_struct_cache = {}
60
+ @user_view_cache = {}
61
+ end
25
62
 
63
+ #FIXME: use weak ref, trouble it may be broken in 1.9 so need to use the 'ref' gem
64
+ def initialize_cleanup_thread(args={})
65
+ cleanup_interval = args.fetch(:cleanup_interval) { CLEANUP_INTERVAL }
66
+ cleanup_cycle = args.fetch(:cleanup_cycle) { CLEANUP_CYCLE }
67
+ t = CacheCleanupThread.new(cleanup_interval, cleanup_cycle, self) do |t|
26
68
  until Thread.current[:should_exit] do
27
- # We don't want to hit the filesystem every 10s to clean up the cache so we need to do a bit of
28
- # accounting to avoid sleeping that entire time. We don't want to sleep for the entire period because
29
- # it means the thread will stay live in hot deployment scenarios, keeping a potentially large memory
30
- # graph from being garbage collected upon undeploy.
31
- if cycle_count * interval >= cleanup_cache_cycle
32
- cycle_count = 1
33
- me.cleanup_cache
34
- end
35
-
36
- sleep(interval)
37
- cycle_count += 1
69
+ self.sleepy_run
38
70
  end
39
71
  end
40
-
41
72
  at_exit { t[:should_exit] = true }
42
-
43
- t
44
73
  end
45
74
 
46
75
  def save(page_struct)
47
76
  @timer_struct_lock.synchronize {
48
- @timer_struct_cache[page_struct['Id']] = page_struct
77
+ @timer_struct_cache[page_struct[:id]] = page_struct
49
78
  }
50
79
  end
51
80
 
@@ -78,7 +107,7 @@ module Rack
78
107
  def cleanup_cache
79
108
  expire_older_than = ((Time.now.to_f - @expires_in_seconds) * 1000).to_i
80
109
  @timer_struct_lock.synchronize {
81
- @timer_struct_cache.delete_if { |k, v| v['Started'] < expire_older_than }
110
+ @timer_struct_cache.delete_if { |k, v| v[:started] < expire_older_than }
82
111
  }
83
112
  end
84
113
  end
@@ -5,25 +5,32 @@ module Rack
5
5
  EXPIRES_IN_SECONDS = 60 * 60 * 24
6
6
 
7
7
  def initialize(args = nil)
8
- @args = args || {}
9
- @prefix = @args.delete(:prefix) || 'MPRedisStore'
10
- @redis_connection = @args.delete(:connection)
8
+ @args = args || {}
9
+ @prefix = @args.delete(:prefix) || 'MPRedisStore'
10
+ @redis_connection = @args.delete(:connection)
11
11
  @expires_in_seconds = @args.delete(:expires_in) || EXPIRES_IN_SECONDS
12
12
  end
13
13
 
14
14
  def save(page_struct)
15
- redis.setex "#{@prefix}#{page_struct['Id']}", @expires_in_seconds, Marshal::dump(page_struct)
15
+ redis.setex "#{@prefix}#{page_struct[:id]}", @expires_in_seconds, Marshal::dump(page_struct)
16
16
  end
17
17
 
18
18
  def load(id)
19
- raw = redis.get "#{@prefix}#{id}"
20
- if raw
21
- Marshal::load raw
19
+ key = "#{@prefix}#{id}"
20
+ raw = redis.get key
21
+ begin
22
+ Marshal::load(raw) if raw
23
+ rescue
24
+ # bad format, junk old data
25
+ redis.del key
26
+ nil
22
27
  end
23
28
  end
24
29
 
25
30
  def set_unviewed(user, id)
26
- redis.sadd "#{@prefix}-#{user}-v", id
31
+ key = "#{@prefix}-#{user}-v"
32
+ redis.sadd key, id
33
+ redis.expire key, @expires_in_seconds
27
34
  end
28
35
 
29
36
  def set_viewed(user, id)
@@ -44,9 +51,10 @@ unviewed_ids: #{get_unviewed_ids(user)}
44
51
  private
45
52
 
46
53
  def redis
47
- return @redis_connection if @redis_connection
48
- require 'redis' unless defined? Redis
49
- @redis_connection ||= Redis.new @args
54
+ @redis_connection ||= begin
55
+ require 'redis' unless defined? Redis
56
+ Redis.new(@args)
57
+ end
50
58
  end
51
59
 
52
60
  end
@@ -0,0 +1,33 @@
1
+ module Rack
2
+ class MiniProfiler
3
+ module TimerStruct
4
+ # A base class for timing structures
5
+ class Base
6
+
7
+ def initialize(attrs={})
8
+ @attributes = attrs
9
+ end
10
+
11
+ def attributes
12
+ @attributes ||= {}
13
+ end
14
+
15
+ def [](name)
16
+ attributes[name]
17
+ end
18
+
19
+ def []=(name, val)
20
+ attributes[name] = val
21
+ self
22
+ end
23
+
24
+ def to_json(*a)
25
+ # this does could take in an option hash, but the only interesting there is max_nesting.
26
+ # if this becomes an option we could increase
27
+ ::JSON.generate( @attributes, :max_nesting => 100 )
28
+ end
29
+
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,89 @@
1
+ module Rack
2
+ class MiniProfiler
3
+ module TimerStruct
4
+
5
+ # This class holds the client timings
6
+ class Client < TimerStruct::Base
7
+
8
+ def self.init_instrumentation
9
+ %Q{
10
+ <script type="text/javascript">
11
+ mPt=function(){var t=[];return{t:t,probe:function(n){t.push({d:new Date(),n:n})}}}()
12
+ </script>
13
+ }
14
+ end
15
+
16
+ # used by Railtie to instrument asset_tag for JS / CSS
17
+ def self.instrument(name, orig)
18
+ probe = "<script>mPt.probe('#{name}')</script>"
19
+ wrapped = probe
20
+ wrapped << orig
21
+ wrapped << probe
22
+ wrapped
23
+ end
24
+
25
+
26
+ def initialize(env={})
27
+ super
28
+ end
29
+
30
+ def redirect_count
31
+ self[:redirect_count]
32
+ end
33
+
34
+ def timings
35
+ self[:timings]
36
+ end
37
+
38
+ def self.init_from_form_data(env, page_struct)
39
+ timings = []
40
+ clientTimes, clientPerf, baseTime = nil
41
+ form = env['rack.request.form_hash']
42
+
43
+ clientPerf = form['clientPerformance'] if form
44
+ clientTimes = clientPerf['timing'] if clientPerf
45
+ baseTime = clientTimes['navigationStart'].to_i if clientTimes
46
+ return unless clientTimes && baseTime
47
+
48
+ probes = form['clientProbes']
49
+ translated = {}
50
+ if probes && !["null", ""].include?(probes)
51
+ probes.each do |id, val|
52
+ name = val["n"]
53
+ translated[name] ||= {}
54
+ if translated[name][:start]
55
+ translated[name][:finish] = val["d"]
56
+ else
57
+ translated[name][:start] = val["d"]
58
+ end
59
+ end
60
+ end
61
+
62
+ translated.each do |name, data|
63
+ h = {"Name" => name, "Start" => data[:start].to_i - baseTime}
64
+ h["Duration"] = data[:finish].to_i - data[:start].to_i if data[:finish]
65
+ timings.push(h)
66
+ end
67
+
68
+ clientTimes.keys.find_all{|k| k =~ /Start$/ }.each do |k|
69
+ start = clientTimes[k].to_i - baseTime
70
+ finish = clientTimes[k.sub(/Start$/, "End")].to_i - baseTime
71
+ duration = 0
72
+ duration = finish - start if finish > start
73
+ name = k.sub(/Start$/, "").split(/(?=[A-Z])/).map{|s| s.capitalize}.join(' ')
74
+ timings.push({"Name" => name, "Start" => start, "Duration" => duration}) if start >= 0
75
+ end
76
+
77
+ clientTimes.keys.find_all{|k| !(k =~ /(End|Start)$/)}.each do |k|
78
+ timings.push("Name" => k, "Start" => clientTimes[k].to_i - baseTime, "Duration" => -1)
79
+ end
80
+
81
+ TimerStruct::Client.new.tap do |rval|
82
+ rval[:redirect_count] = env['rack.request.form_hash']['clientPerformance']['navigation']['redirect_count']
83
+ rval[:timings] = timings
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,22 @@
1
+ module Rack
2
+ class MiniProfiler
3
+ module TimerStruct
4
+ # Timing system for a custom timers such as cache, redis, RPC, external API
5
+ # calls, etc.
6
+ class Custom < TimerStruct::Base
7
+ def initialize(type, duration_ms, page, parent)
8
+ @parent = parent
9
+ @page = page
10
+ @type = type
11
+ start_millis = ((Time.now.to_f * 1000).to_i - page[:started]) - duration_ms
12
+ super(
13
+ :type => type,
14
+ :start_milliseconds => start_millis,
15
+ :duration_milliseconds => duration_ms,
16
+ :parent_timing_id => nil
17
+ )
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,62 @@
1
+ module Rack
2
+ class MiniProfiler
3
+ module TimerStruct
4
+
5
+ # TimerStruct::Page
6
+ # Root: TimerStruct::Request
7
+ # :has_many TimerStruct::Request children
8
+ # :has_many TimerStruct::Sql children
9
+ # :has_many TimerStruct::Custom children
10
+ class Page < TimerStruct::Base
11
+ def initialize(env)
12
+ timer_id = MiniProfiler.generate_id
13
+ page_name = env['PATH_INFO']
14
+ started_at = (Time.now.to_f * 1000).to_i
15
+ machine_name = env['SERVER_NAME']
16
+ super(
17
+ :id => timer_id,
18
+ :name => page_name,
19
+ :started => started_at,
20
+ :machine_name => machine_name,
21
+ :level => 0,
22
+ :user => "unknown user",
23
+ :has_user_viewed => false,
24
+ :client_timings => nil,
25
+ :duration_milliseconds => 0,
26
+ :has_trivial_timings => true,
27
+ :has_all_trivial_timings => false,
28
+ :trivial_duration_threshold_milliseconds => 2,
29
+ :head => nil,
30
+ :duration_milliseconds_in_sql => 0,
31
+ :has_sql_timings => true,
32
+ :has_duplicate_sql_timings => false,
33
+ :executed_readers => 0,
34
+ :executed_scalars => 0,
35
+ :executed_non_queries => 0,
36
+ :custom_timing_names => [],
37
+ :custom_timing_stats => {}
38
+ )
39
+ name = "#{env['REQUEST_METHOD']} http://#{env['SERVER_NAME']}:#{env['SERVER_PORT']}#{env['SCRIPT_NAME']}#{env['PATH_INFO']}"
40
+ self[:root] = TimerStruct::Request.createRoot(name, self)
41
+ end
42
+
43
+ def duration_ms
44
+ @attributes[:root][:duration_milliseconds]
45
+ end
46
+
47
+ def root
48
+ @attributes[:root]
49
+ end
50
+
51
+ def to_json(*a)
52
+ attribs = @attributes.merge(
53
+ :started => '/Date(%d)/' % @attributes[:started],
54
+ :duration_milliseconds => @attributes[:root][:duration_milliseconds],
55
+ :custom_timing_names => @attributes[:custom_timing_stats].keys.sort
56
+ )
57
+ ::JSON.generate(attribs, :max_nesting => 100)
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,126 @@
1
+ module Rack
2
+ class MiniProfiler
3
+ module TimerStruct
4
+ class Request < TimerStruct::Base
5
+
6
+ def self.createRoot(name, page)
7
+ TimerStruct::Request.new(name, page, nil).tap do |timer|
8
+ timer[:is_root] = true
9
+ end
10
+ end
11
+
12
+ attr_accessor :children_duration
13
+
14
+ def initialize(name, page, parent)
15
+ start_millis = (Time.now.to_f * 1000).to_i - page[:started]
16
+ depth = parent ? parent.depth + 1 : 0
17
+ super(
18
+ :id => MiniProfiler.generate_id,
19
+ :name => name,
20
+ :duration_milliseconds => 0,
21
+ :duration_without_children_milliseconds => 0,
22
+ :start_milliseconds => start_millis,
23
+ :parent_timing_id => nil,
24
+ :children => [],
25
+ :has_children => false,
26
+ :key_values => nil,
27
+ :has_sql_timings => false,
28
+ :has_duplicate_sql_timings => false,
29
+ :trivial_duration_threshold_milliseconds => 2,
30
+ :sql_timings => [],
31
+ :sql_timings_duration_milliseconds => 0,
32
+ :is_trivial => false,
33
+ :is_root => false,
34
+ :depth => depth,
35
+ :executed_readers => 0,
36
+ :executed_scalars => 0,
37
+ :executed_non_queries => 0,
38
+ :custom_timing_stats => {},
39
+ :custom_timings => {}
40
+ )
41
+ @children_duration = 0
42
+ @start = Time.now
43
+ @parent = parent
44
+ @page = page
45
+ end
46
+
47
+ def duration_ms
48
+ self[:duration_milliseconds]
49
+ end
50
+
51
+ def start_ms
52
+ self[:start_milliseconds]
53
+ end
54
+
55
+ def start
56
+ @start
57
+ end
58
+
59
+ def depth
60
+ self[:depth]
61
+ end
62
+
63
+ def children
64
+ self[:children]
65
+ end
66
+
67
+ def custom_timings
68
+ self[:custom_timings]
69
+ end
70
+
71
+ def sql_timings
72
+ self[:sql_timings]
73
+ end
74
+
75
+ def add_child(name)
76
+ TimerStruct::Request.new(name, @page, self).tap do |timer|
77
+ self[:children].push(timer)
78
+ self[:has_children] = true
79
+ timer[:parent_timing_id] = self[:id]
80
+ timer[:depth] = self[:depth] + 1
81
+ end
82
+ end
83
+
84
+ def add_sql(query, elapsed_ms, page, skip_backtrace = false, full_backtrace = false)
85
+ TimerStruct::Sql.new(query, elapsed_ms, page, self , skip_backtrace, full_backtrace).tap do |timer|
86
+ self[:sql_timings].push(timer)
87
+ timer[:parent_timing_id] = self[:id]
88
+ self[:has_sql_timings] = true
89
+ self[:sql_timings_duration_milliseconds] += elapsed_ms
90
+ page[:duration_milliseconds_in_sql] += elapsed_ms
91
+ end
92
+ end
93
+
94
+ def add_custom(type, elapsed_ms, page)
95
+ TimerStruct::Custom.new(type, elapsed_ms, page, self).tap do |timer|
96
+ timer[:parent_timing_id] = self[:id]
97
+
98
+ self[:custom_timings][type] ||= []
99
+ self[:custom_timings][type].push(timer)
100
+
101
+ self[:custom_timing_stats][type] ||= {:count => 0, :duration => 0.0}
102
+ self[:custom_timing_stats][type][:count] += 1
103
+ self[:custom_timing_stats][type][:duration] += elapsed_ms
104
+
105
+ page[:custom_timing_stats][type] ||= {:count => 0, :duration => 0.0}
106
+ page[:custom_timing_stats][type][:count] += 1
107
+ page[:custom_timing_stats][type][:duration] += elapsed_ms
108
+ end
109
+ end
110
+
111
+ def record_time(milliseconds = nil)
112
+ milliseconds ||= (Time.now - @start) * 1000
113
+ self[:duration_milliseconds] = milliseconds
114
+ self[:is_trivial] = true if milliseconds < self[:trivial_duration_threshold_milliseconds]
115
+ self[:duration_without_children_milliseconds] = milliseconds - @children_duration
116
+
117
+ if @parent
118
+ @parent.children_duration += milliseconds
119
+ end
120
+
121
+ end
122
+
123
+ end
124
+ end
125
+ end
126
+ end