rack-mini-profiler 0.9.2 → 0.9.3

Sign up to get free protection for your applications and to get access to all the features.

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