rack-mini-profiler 0.1.31 → 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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +149 -0
- data/README.md +285 -0
- data/{Ruby/lib → lib}/html/includes.css +15 -4
- data/{Ruby/lib → lib}/html/includes.js +95 -59
- data/{Ruby/lib → lib}/html/includes.less +21 -5
- data/{Ruby/lib → lib}/html/includes.tmpl +50 -50
- data/{Ruby/lib → lib}/html/jquery.1.7.1.js +0 -0
- data/{Ruby/lib → lib}/html/jquery.tmpl.js +0 -0
- data/{Ruby/lib → lib}/html/list.css +2 -2
- data/{Ruby/lib → lib}/html/list.js +1 -1
- data/lib/html/list.tmpl +34 -0
- data/lib/html/profile_handler.js +1 -0
- data/{Ruby/lib → lib}/html/share.html +2 -2
- data/lib/mini_profiler/asset_version.rb +5 -0
- data/{Ruby/lib → lib}/mini_profiler/client_settings.rb +3 -3
- data/lib/mini_profiler/config.rb +65 -0
- data/{Ruby/lib → lib}/mini_profiler/context.rb +0 -0
- data/lib/mini_profiler/gc_profiler.rb +181 -0
- data/{Ruby/lib → lib}/mini_profiler/profiler.rb +120 -96
- data/{Ruby/lib → lib}/mini_profiler/profiling_methods.rb +15 -17
- data/{Ruby/lib → lib}/mini_profiler/storage/abstract_store.rb +0 -0
- data/{Ruby/lib → lib}/mini_profiler/storage/file_store.rb +30 -8
- data/{Ruby/lib → lib}/mini_profiler/storage/memcache_store.rb +5 -7
- data/lib/mini_profiler/storage/memory_store.rb +115 -0
- data/{Ruby/lib → lib}/mini_profiler/storage/redis_store.rb +19 -11
- data/lib/mini_profiler/timer_struct/base.rb +33 -0
- data/lib/mini_profiler/timer_struct/client.rb +89 -0
- data/lib/mini_profiler/timer_struct/custom.rb +22 -0
- data/lib/mini_profiler/timer_struct/page.rb +62 -0
- data/lib/mini_profiler/timer_struct/request.rb +126 -0
- data/lib/mini_profiler/timer_struct/sql.rb +59 -0
- data/lib/mini_profiler/version.rb +5 -0
- data/{Ruby/lib → lib}/mini_profiler_rails/railtie.rb +23 -6
- data/lib/patches/db/activerecord.rb +42 -0
- data/lib/patches/db/moped.rb +12 -0
- data/lib/patches/db/mysql2.rb +30 -0
- data/lib/patches/db/pg.rb +104 -0
- data/lib/patches/db/plucky.rb +47 -0
- data/lib/patches/db/rsolr.rb +24 -0
- data/lib/patches/db/sequel.rb +10 -0
- data/{Ruby/lib → lib}/patches/net_patches.rb +0 -0
- data/lib/patches/sql_patches.rb +46 -0
- data/lib/rack-mini-profiler.rb +35 -0
- data/rack-mini-profiler.gemspec +28 -16
- metadata +171 -52
- data/Ruby/CHANGELOG +0 -161
- data/Ruby/README.md +0 -172
- data/Ruby/lib/html/list.tmpl +0 -34
- data/Ruby/lib/html/profile_handler.js +0 -1
- data/Ruby/lib/mini_profiler/client_timer_struct.rb +0 -78
- data/Ruby/lib/mini_profiler/config.rb +0 -58
- data/Ruby/lib/mini_profiler/custom_timer_struct.rb +0 -22
- data/Ruby/lib/mini_profiler/gc_profiler.rb +0 -107
- data/Ruby/lib/mini_profiler/gc_profiler_ruby_head.rb +0 -40
- data/Ruby/lib/mini_profiler/page_timer_struct.rb +0 -58
- data/Ruby/lib/mini_profiler/request_timer_struct.rb +0 -115
- data/Ruby/lib/mini_profiler/sql_timer_struct.rb +0 -58
- data/Ruby/lib/mini_profiler/storage/memory_store.rb +0 -65
- data/Ruby/lib/mini_profiler/timer_struct.rb +0 -33
- data/Ruby/lib/mini_profiler/version.rb +0 -5
- data/Ruby/lib/patches/sql_patches.rb +0 -277
- data/Ruby/lib/rack-mini-profiler.rb +0 -7
@@ -3,32 +3,30 @@ module Rack
|
|
3
3
|
module ProfilingMethods
|
4
4
|
|
5
5
|
def record_sql(query, elapsed_ms)
|
6
|
+
return unless current
|
6
7
|
c = current
|
7
|
-
return unless c
|
8
8
|
c.current_timer.add_sql(query, elapsed_ms, c.page_struct, c.skip_backtrace, c.full_backtrace) if (c && c.current_timer)
|
9
9
|
end
|
10
10
|
|
11
11
|
def start_step(name)
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
end
|
12
|
+
return unless current
|
13
|
+
parent_timer = current.current_timer
|
14
|
+
current.current_timer = current_timer = current.current_timer.add_child(name)
|
15
|
+
[current_timer,parent_timer]
|
17
16
|
end
|
18
17
|
|
19
18
|
def finish_step(obj)
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
end
|
19
|
+
return unless obj && current
|
20
|
+
current_timer, parent_timer = obj
|
21
|
+
current_timer.record_time
|
22
|
+
current.current_timer = parent_timer
|
25
23
|
end
|
26
24
|
|
27
25
|
# perform a profiling step on given block
|
28
26
|
def step(name, opts = nil)
|
29
27
|
if current
|
30
|
-
parent_timer
|
31
|
-
result
|
28
|
+
parent_timer = current.current_timer
|
29
|
+
result = nil
|
32
30
|
current.current_timer = current_timer = current.current_timer.add_child(name)
|
33
31
|
begin
|
34
32
|
result = yield if block_given?
|
@@ -65,9 +63,9 @@ module Rack
|
|
65
63
|
|
66
64
|
def profile_method(klass, method, type = :profile, &blk)
|
67
65
|
default_name = type==:counter ? method.to_s : klass.to_s + " " + method.to_s
|
68
|
-
clean
|
66
|
+
clean = clean_method_name(method)
|
69
67
|
|
70
|
-
with_profiling
|
68
|
+
with_profiling = ("#{clean}_with_mini_profiler").intern
|
71
69
|
without_profiling = ("#{clean}_without_mini_profiler").intern
|
72
70
|
|
73
71
|
if klass.send :method_defined?, with_profiling
|
@@ -131,8 +129,8 @@ module Rack
|
|
131
129
|
def counter(type, duration_ms=nil)
|
132
130
|
result = nil
|
133
131
|
if block_given?
|
134
|
-
start
|
135
|
-
result
|
132
|
+
start = Time.now
|
133
|
+
result = yield
|
136
134
|
duration_ms = (Time.now - start).to_f * 1000
|
137
135
|
end
|
138
136
|
return result if current.nil? || !request_authorized?
|
File without changes
|
@@ -2,6 +2,10 @@ module Rack
|
|
2
2
|
class MiniProfiler
|
3
3
|
class FileStore < AbstractStore
|
4
4
|
|
5
|
+
# Sub-class thread so we have a named thread (useful for debugging in Thread.list).
|
6
|
+
class CacheCleanupThread < Thread
|
7
|
+
end
|
8
|
+
|
5
9
|
class FileCache
|
6
10
|
def initialize(path, prefix)
|
7
11
|
@path = path
|
@@ -35,27 +39,45 @@ module Rack
|
|
35
39
|
@expires_in_seconds = args[:expires_in] || EXPIRES_IN_SECONDS
|
36
40
|
raise ArgumentError.new :path unless @path
|
37
41
|
@timer_struct_cache = FileCache.new(@path, "mp_timers")
|
38
|
-
@timer_struct_lock
|
39
|
-
@user_view_cache
|
40
|
-
@user_view_lock
|
42
|
+
@timer_struct_lock = Mutex.new
|
43
|
+
@user_view_cache = FileCache.new(@path, "mp_views")
|
44
|
+
@user_view_lock = Mutex.new
|
41
45
|
|
42
46
|
me = self
|
43
|
-
|
47
|
+
t = CacheCleanupThread.new do
|
48
|
+
interval = 10
|
49
|
+
cleanup_cache_cycle = 3600
|
50
|
+
cycle_count = 1
|
51
|
+
|
44
52
|
begin
|
45
|
-
|
53
|
+
until Thread.current[:should_exit] do
|
46
54
|
# TODO: a sane retry count before bailing
|
47
|
-
|
48
|
-
|
55
|
+
|
56
|
+
# We don't want to hit the filesystem every 10s to clean up the cache so we need to do a bit of
|
57
|
+
# accounting to avoid sleeping that entire time. We don't want to sleep for the entire period because
|
58
|
+
# it means the thread will stay live in hot deployment scenarios, keeping a potentially large memory
|
59
|
+
# graph from being garbage collected upon undeploy.
|
60
|
+
if cycle_count * interval >= cleanup_cache_cycle
|
61
|
+
cycle_count = 1
|
62
|
+
me.cleanup_cache
|
63
|
+
end
|
64
|
+
|
65
|
+
sleep(interval)
|
66
|
+
cycle_count += 1
|
49
67
|
end
|
50
68
|
rescue
|
51
69
|
# don't crash the thread, we can clean up next time
|
52
70
|
end
|
53
71
|
end
|
72
|
+
|
73
|
+
at_exit { t[:should_exit] = true }
|
74
|
+
|
75
|
+
t
|
54
76
|
end
|
55
77
|
|
56
78
|
def save(page_struct)
|
57
79
|
@timer_struct_lock.synchronize {
|
58
|
-
@timer_struct_cache[page_struct[
|
80
|
+
@timer_struct_cache[page_struct[:id]] = page_struct
|
59
81
|
}
|
60
82
|
end
|
61
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
|
6
|
+
MAX_RETRIES = 10
|
7
7
|
|
8
8
|
def initialize(args = nil)
|
9
9
|
require 'dalli' unless defined? Dalli
|
10
10
|
args ||= {}
|
11
|
-
@prefix
|
12
|
-
@client
|
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[
|
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)
|
@@ -0,0 +1,115 @@
|
|
1
|
+
module Rack
|
2
|
+
class MiniProfiler
|
3
|
+
class MemoryStore < AbstractStore
|
4
|
+
|
5
|
+
# Sub-class thread so we have a named thread (useful for debugging in Thread.list).
|
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
|
43
|
+
end
|
44
|
+
|
45
|
+
EXPIRES_IN_SECONDS = 60 * 60 * 24
|
46
|
+
CLEANUP_INTERVAL = 10
|
47
|
+
CLEANUP_CYCLE = 3600
|
48
|
+
|
49
|
+
def initialize(args = nil)
|
50
|
+
args ||= {}
|
51
|
+
@expires_in_seconds = args.fetch(:expires_in) { EXPIRES_IN_SECONDS }
|
52
|
+
initialize_locks
|
53
|
+
initialize_cleanup_thread(args)
|
54
|
+
end
|
55
|
+
|
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
|
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|
|
68
|
+
until Thread.current[:should_exit] do
|
69
|
+
self.sleepy_run
|
70
|
+
end
|
71
|
+
end
|
72
|
+
at_exit { t[:should_exit] = true }
|
73
|
+
end
|
74
|
+
|
75
|
+
def save(page_struct)
|
76
|
+
@timer_struct_lock.synchronize {
|
77
|
+
@timer_struct_cache[page_struct[:id]] = page_struct
|
78
|
+
}
|
79
|
+
end
|
80
|
+
|
81
|
+
def load(id)
|
82
|
+
@timer_struct_lock.synchronize {
|
83
|
+
@timer_struct_cache[id]
|
84
|
+
}
|
85
|
+
end
|
86
|
+
|
87
|
+
def set_unviewed(user, id)
|
88
|
+
@user_view_lock.synchronize {
|
89
|
+
@user_view_cache[user] ||= []
|
90
|
+
@user_view_cache[user] << id
|
91
|
+
}
|
92
|
+
end
|
93
|
+
|
94
|
+
def set_viewed(user, id)
|
95
|
+
@user_view_lock.synchronize {
|
96
|
+
@user_view_cache[user] ||= []
|
97
|
+
@user_view_cache[user].delete(id)
|
98
|
+
}
|
99
|
+
end
|
100
|
+
|
101
|
+
def get_unviewed_ids(user)
|
102
|
+
@user_view_lock.synchronize {
|
103
|
+
@user_view_cache[user]
|
104
|
+
}
|
105
|
+
end
|
106
|
+
|
107
|
+
def cleanup_cache
|
108
|
+
expire_older_than = ((Time.now.to_f - @expires_in_seconds) * 1000).to_i
|
109
|
+
@timer_struct_lock.synchronize {
|
110
|
+
@timer_struct_cache.delete_if { |k, v| v[:started] < expire_older_than }
|
111
|
+
}
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
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
|
9
|
-
@prefix
|
10
|
-
@redis_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[
|
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
|
-
|
20
|
-
|
21
|
-
|
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
|
-
|
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
|
-
|
48
|
-
|
49
|
-
|
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
|