rack-mini-profiler 0.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.
Potentially problematic release.
This version of rack-mini-profiler might be problematic. Click here for more details.
- data/README.md +82 -0
- data/lib/html/MiniProfilerHandler.cs +419 -0
- data/lib/html/includes.css +1 -0
- data/lib/html/includes.js +802 -0
- data/lib/html/includes.less +468 -0
- data/lib/html/includes.tmpl +195 -0
- data/lib/html/jquery.1.7.1.js +4 -0
- data/lib/html/jquery.tmpl.js +486 -0
- data/lib/html/list.css +9 -0
- data/lib/html/list.js +37 -0
- data/lib/html/list.tmpl +34 -0
- data/lib/html/profile_handler.js +62 -0
- data/lib/html/share.html +11 -0
- data/lib/mini_profiler/body_add_proxy.rb +45 -0
- data/lib/mini_profiler/client_timer_struct.rb +43 -0
- data/lib/mini_profiler/page_timer_struct.rb +49 -0
- data/lib/mini_profiler/profiler.rb +309 -0
- data/lib/mini_profiler/request_timer_struct.rb +65 -0
- data/lib/mini_profiler/sql_timer_struct.rb +37 -0
- data/lib/mini_profiler/storage/abstract_store.rb +27 -0
- data/lib/mini_profiler/storage/file_store.rb +108 -0
- data/lib/mini_profiler/storage/memory_store.rb +68 -0
- data/lib/mini_profiler/storage/redis_store.rb +43 -0
- data/lib/mini_profiler/timer_struct.rb +31 -0
- data/lib/mini_profiler_rails/railtie.rb +30 -0
- data/lib/patches/sql_patches.rb +72 -0
- data/lib/rack-mini-profiler.rb +6 -0
- data/rack-mini-profiler.gemspec +24 -0
- metadata +146 -0
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'mini_profiler/timer_struct'
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
class MiniProfiler
|
5
|
+
|
6
|
+
class RequestTimerStruct < TimerStruct
|
7
|
+
|
8
|
+
def self.createRoot(name, page)
|
9
|
+
rt = RequestTimerStruct.new(name, page)
|
10
|
+
rt["IsRoot"]= true
|
11
|
+
rt
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :children_duration
|
15
|
+
|
16
|
+
def initialize(name, page)
|
17
|
+
super("Id" => MiniProfiler.generate_id,
|
18
|
+
"Name" => name,
|
19
|
+
"DurationMilliseconds" => 0,
|
20
|
+
"DurationWithoutChildrenMilliseconds"=> 0,
|
21
|
+
"StartMilliseconds" => (Time.now.to_f * 1000).to_i - page['Started'],
|
22
|
+
"ParentTimingId" => nil,
|
23
|
+
"Children" => [],
|
24
|
+
"HasChildren"=> false,
|
25
|
+
"KeyValues" => nil,
|
26
|
+
"HasSqlTimings"=> false,
|
27
|
+
"HasDuplicateSqlTimings"=> false,
|
28
|
+
"TrivialDurationThresholdMilliseconds" => 2,
|
29
|
+
"SqlTimings" => [],
|
30
|
+
"SqlTimingsDurationMilliseconds"=> 0,
|
31
|
+
"IsTrivial"=> false,
|
32
|
+
"IsRoot"=> false,
|
33
|
+
"Depth"=> 0,
|
34
|
+
"ExecutedReaders"=> 0,
|
35
|
+
"ExecutedScalars"=> 0,
|
36
|
+
"ExecutedNonQueries"=> 0)
|
37
|
+
@children_duration = 0
|
38
|
+
end
|
39
|
+
|
40
|
+
def add_child(request_timer)
|
41
|
+
self['Children'].push(request_timer)
|
42
|
+
self['HasChildren'] = true
|
43
|
+
request_timer['ParentTimingId'] = self['Id']
|
44
|
+
request_timer['Depth'] = self['Depth'] + 1
|
45
|
+
@children_duration += request_timer['DurationMilliseconds']
|
46
|
+
end
|
47
|
+
|
48
|
+
def add_sql(query, elapsed_ms, page, skip_backtrace = false)
|
49
|
+
timer = SqlTimerStruct.new(query, elapsed_ms, page, skip_backtrace)
|
50
|
+
timer['ParentTimingId'] = self['Id']
|
51
|
+
self['SqlTimings'].push(timer)
|
52
|
+
self['HasSqlTimings'] = true
|
53
|
+
self['SqlTimingsDurationMilliseconds'] += elapsed_ms
|
54
|
+
page['DurationMillisecondsInSql'] += elapsed_ms
|
55
|
+
end
|
56
|
+
|
57
|
+
def record_time(milliseconds)
|
58
|
+
self['DurationMilliseconds'] = milliseconds
|
59
|
+
self['IsTrivial'] = true if milliseconds < self["TrivialDurationThresholdMilliseconds"]
|
60
|
+
self['DurationWithoutChildrenMilliseconds'] = milliseconds - @children_duration
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'mini_profiler/timer_struct'
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
class MiniProfiler
|
5
|
+
|
6
|
+
# Timing system for a SQL query
|
7
|
+
class SqlTimerStruct < TimerStruct
|
8
|
+
def initialize(query, duration_ms, page, skip_backtrace = false)
|
9
|
+
|
10
|
+
stack_trace = nil
|
11
|
+
unless skip_backtrace
|
12
|
+
# Allow us to filter the stack trace
|
13
|
+
stack_trace = ""
|
14
|
+
# Clean up the stack trace if there are options to do so
|
15
|
+
Kernel.caller.each do |ln|
|
16
|
+
ln.gsub!(Rack::MiniProfiler.configuration[:backtrace_remove], '') if Rack::MiniProfiler.configuration[:backtrace_remove]
|
17
|
+
if Rack::MiniProfiler.configuration[:backtrace_filter].nil? or ln =~ Rack::MiniProfiler.configuration[:backtrace_filter]
|
18
|
+
stack_trace << ln << "\n"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
super("ExecuteType" => 3, # TODO
|
24
|
+
"FormattedCommandString" => query,
|
25
|
+
"StackTraceSnippet" => stack_trace,
|
26
|
+
"StartMilliseconds" => (Time.now.to_f * 1000).to_i - page['Started'],
|
27
|
+
"DurationMilliseconds" => duration_ms,
|
28
|
+
"FirstFetchDurationMilliseconds" => 0,
|
29
|
+
"Parameters" => nil,
|
30
|
+
"ParentTimingId" => nil,
|
31
|
+
"IsDuplicate" => false)
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Rack
|
2
|
+
class MiniProfiler
|
3
|
+
class AbstractStore
|
4
|
+
|
5
|
+
def save(page_struct)
|
6
|
+
raise NotImplementedError.new("save is not implemented")
|
7
|
+
end
|
8
|
+
|
9
|
+
def load(id)
|
10
|
+
raise NotImplementedError.new("load is not implemented")
|
11
|
+
end
|
12
|
+
|
13
|
+
def set_unviewed(user, id)
|
14
|
+
raise NotImplementedError.new("set_unviewed is not implemented")
|
15
|
+
end
|
16
|
+
|
17
|
+
def set_viewed(user, id)
|
18
|
+
raise NotImplementedError.new("set_viewed is not implemented")
|
19
|
+
end
|
20
|
+
|
21
|
+
def get_unviewed_ids(user)
|
22
|
+
raise NotImplementedError.new("get_unviewed_ids is not implemented")
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
module Rack
|
2
|
+
class MiniProfiler
|
3
|
+
class FileStore < AbstractStore
|
4
|
+
|
5
|
+
class FileCache
|
6
|
+
def initialize(path, prefix)
|
7
|
+
@path = path
|
8
|
+
@prefix = prefix
|
9
|
+
end
|
10
|
+
|
11
|
+
def [](key)
|
12
|
+
begin
|
13
|
+
data = ::File.open(path(key),"rb") {|f| f.read}
|
14
|
+
return Marshal.load data
|
15
|
+
rescue => e
|
16
|
+
return nil
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def []=(key,val)
|
21
|
+
::File.open(path(key), "wb+") {|f| f.write Marshal.dump(val)}
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
def path(key)
|
26
|
+
@path + "/" + @prefix + "_" + key
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
EXPIRE_TIMER_CACHE = 3600 * 24
|
31
|
+
|
32
|
+
def initialize(args)
|
33
|
+
@path = args[:path]
|
34
|
+
raise ArgumentError.new :path unless @path
|
35
|
+
@timer_struct_cache = FileCache.new(@path, "mp_timers")
|
36
|
+
@timer_struct_lock = Mutex.new
|
37
|
+
@user_view_cache = FileCache.new(@path, "mp_views")
|
38
|
+
@user_view_lock = Mutex.new
|
39
|
+
|
40
|
+
me = self
|
41
|
+
Thread.new do
|
42
|
+
while true do
|
43
|
+
me.cleanup_cache if MiniProfiler.instance
|
44
|
+
sleep(3600)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def save(page_struct)
|
50
|
+
@timer_struct_lock.synchronize {
|
51
|
+
@timer_struct_cache[page_struct['Id']] = page_struct
|
52
|
+
}
|
53
|
+
end
|
54
|
+
|
55
|
+
def load(id)
|
56
|
+
@timer_struct_lock.synchronize {
|
57
|
+
@timer_struct_cache[id]
|
58
|
+
}
|
59
|
+
end
|
60
|
+
|
61
|
+
def set_unviewed(user, id)
|
62
|
+
@user_view_lock.synchronize {
|
63
|
+
current = @user_view_cache[user]
|
64
|
+
current = [] unless Array === current
|
65
|
+
current << id
|
66
|
+
@user_view_cache[user] = current.uniq
|
67
|
+
}
|
68
|
+
end
|
69
|
+
|
70
|
+
def set_viewed(user, id)
|
71
|
+
@user_view_lock.synchronize {
|
72
|
+
@user_view_cache[user] ||= []
|
73
|
+
current = @user_view_cache[user]
|
74
|
+
current = [] unless Array === current
|
75
|
+
current.delete(id)
|
76
|
+
@user_view_cache[user] = current.uniq
|
77
|
+
}
|
78
|
+
end
|
79
|
+
|
80
|
+
def get_unviewed_ids(user)
|
81
|
+
@user_view_lock.synchronize {
|
82
|
+
@user_view_cache[user]
|
83
|
+
}
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
|
90
|
+
def cleanup_cache
|
91
|
+
files = Dir.entries(@path)
|
92
|
+
@timer_struct_lock.synchronize {
|
93
|
+
files.each do |f|
|
94
|
+
f = @path + '/' + f
|
95
|
+
File.delete f if f =~ /^mp_timers/ and (Time.now - File.mtime(f)) > EXPIRE_TIMER_CACHE
|
96
|
+
end
|
97
|
+
}
|
98
|
+
@user_view_lock.synchronize {
|
99
|
+
files.each do |f|
|
100
|
+
f = @path + '/' + f
|
101
|
+
File.delete f if f =~ /^mp_views/ and (Time.now - File.mtime(f)) > EXPIRE_TIMER_CACHE
|
102
|
+
end
|
103
|
+
}
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Rack
|
2
|
+
class MiniProfiler
|
3
|
+
class MemoryStore < AbstractStore
|
4
|
+
|
5
|
+
EXPIRE_TIMER_CACHE = 3600 * 24
|
6
|
+
|
7
|
+
def initialize(args)
|
8
|
+
@timer_struct_lock = Mutex.new
|
9
|
+
@timer_struct_cache = {}
|
10
|
+
@user_view_lock = Mutex.new
|
11
|
+
@user_view_cache = {}
|
12
|
+
|
13
|
+
# TODO: fix it to use weak ref, trouble is may be broken in 1.9 so need to use the 'ref' gem
|
14
|
+
me = self
|
15
|
+
Thread.new do
|
16
|
+
while true do
|
17
|
+
me.cleanup_cache
|
18
|
+
sleep(3600)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def save(page_struct)
|
24
|
+
@timer_struct_lock.synchronize {
|
25
|
+
@timer_struct_cache[page_struct['Id']] = page_struct
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
def load(id)
|
30
|
+
@timer_struct_lock.synchronize {
|
31
|
+
@timer_struct_cache[id]
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
def set_unviewed(user, id)
|
36
|
+
@user_view_lock.synchronize {
|
37
|
+
@user_view_cache[user] ||= []
|
38
|
+
@user_view_cache[user] << id
|
39
|
+
}
|
40
|
+
end
|
41
|
+
|
42
|
+
def set_viewed(user, id)
|
43
|
+
@user_view_lock.synchronize {
|
44
|
+
@user_view_cache[user] ||= []
|
45
|
+
@user_view_cache[user].delete(id)
|
46
|
+
}
|
47
|
+
end
|
48
|
+
|
49
|
+
def get_unviewed_ids(user)
|
50
|
+
@user_view_lock.synchronize {
|
51
|
+
@user_view_cache[user]
|
52
|
+
}
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
|
59
|
+
def cleanup_cache
|
60
|
+
expire_older_than = ((Time.now.to_f - MiniProfiler::MemoryStore::EXPIRE_TIMER_CACHE) * 1000).to_i
|
61
|
+
@timer_struct_lock.synchronize {
|
62
|
+
@timer_struct_cache.delete_if { |k, v| v['Root']['StartMilliseconds'] < expire_older_than }
|
63
|
+
}
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Rack
|
2
|
+
class MiniProfiler
|
3
|
+
class RedisStore < AbstractStore
|
4
|
+
|
5
|
+
EXPIRE_SECONDS = 60 * 60 * 24
|
6
|
+
|
7
|
+
def initialize(args = {})
|
8
|
+
@prefix = args[:prefix] || 'MPRedisStore'
|
9
|
+
end
|
10
|
+
|
11
|
+
def save(page_struct)
|
12
|
+
redis.setex "#{@prefix}#{page_struct['Id']}", EXPIRE_SECONDS, Marshal::dump(page_struct)
|
13
|
+
end
|
14
|
+
|
15
|
+
def load(id)
|
16
|
+
raw = redis.get "#{@prefix}#{id}"
|
17
|
+
if raw
|
18
|
+
Marshal::load raw
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def set_unviewed(user, id)
|
23
|
+
redis.sadd "#{@prefix}-#{user}-v", id
|
24
|
+
end
|
25
|
+
|
26
|
+
def set_viewed(user, id)
|
27
|
+
redis.srem "#{@prefix}-#{user}-v", id
|
28
|
+
end
|
29
|
+
|
30
|
+
def get_unviewed_ids(user)
|
31
|
+
redis.smembers "#{@prefix}-#{user}-v"
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def redis
|
37
|
+
require 'redis' unless defined? Redis
|
38
|
+
Redis.new
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Rack
|
2
|
+
class MiniProfiler
|
3
|
+
|
4
|
+
# A base class for timing structures
|
5
|
+
class TimerStruct
|
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
|
+
::JSON.generate(@attributes, a[0])
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module MiniProfilerRails
|
2
|
+
class Railtie < ::Rails::Railtie
|
3
|
+
|
4
|
+
initializer "rack_mini_profiler.configure_rails_initialization" do |app|
|
5
|
+
|
6
|
+
# By default, only show the MiniProfiler in development mode
|
7
|
+
Rack::MiniProfiler.configuration[:authorize_cb] = lambda {|env| Rails.env.development? }
|
8
|
+
tmp = Rails.root.to_s + "/tmp/miniprofiler"
|
9
|
+
Dir::mkdir(tmp) unless File.exists?(tmp)
|
10
|
+
Rack::MiniProfiler.configuration[:storage_options] = {:path => tmp}
|
11
|
+
Rack::MiniProfiler.configuration[:storage] = Rack::MiniProfiler::FileStore
|
12
|
+
|
13
|
+
# Quiet the SQL stack traces
|
14
|
+
Rack::MiniProfiler.configuration[:backtrace_remove] = Rails.root.to_s + "/"
|
15
|
+
Rack::MiniProfiler.configuration[:backtrace_filter] = /^\/?(app|config|lib|test)/
|
16
|
+
|
17
|
+
# The file store is just so much less flaky
|
18
|
+
|
19
|
+
# Install the Middleware
|
20
|
+
app.middleware.insert_before 'Rack::Lock', 'Rack::MiniProfiler'
|
21
|
+
|
22
|
+
# Attach to various Rails methods
|
23
|
+
::Rack::MiniProfiler.profile_method(ActionController::Base, :process) {|action| "Executing action: #{action}"}
|
24
|
+
::Rack::MiniProfiler.profile_method(ActionView::Template, :render) {|x,y| "Rendering: #{@virtual_path}"}
|
25
|
+
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module SqlPatches
|
2
|
+
def self.class_exists?(name)
|
3
|
+
eval(name + ".class").to_s.eql?('Class')
|
4
|
+
rescue NameError
|
5
|
+
false
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
if SqlPatches.class_exists? "Sequel::Database" then
|
10
|
+
module Sequel
|
11
|
+
class Database
|
12
|
+
alias_method :log_duration_original, :log_duration
|
13
|
+
def log_duration(duration, message)
|
14
|
+
::Rack::MiniProfiler.instance.record_sql(message, duration) if ::Rack::MiniProfiler.instance
|
15
|
+
log_duration_original(duration, message)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
## based off https://github.com/newrelic/rpm/blob/master/lib/new_relic/agent/instrumentation/active_record.rb
|
23
|
+
module Rack
|
24
|
+
class MiniProfiler
|
25
|
+
module ActiveRecordInstrumentation
|
26
|
+
def self.included(instrumented_class)
|
27
|
+
instrumented_class.class_eval do
|
28
|
+
unless instrumented_class.method_defined?(:log_without_miniprofiler)
|
29
|
+
alias_method :log_without_miniprofiler, :log
|
30
|
+
alias_method :log, :log_with_miniprofiler
|
31
|
+
protected :log
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def log_with_miniprofiler(*args, &block)
|
37
|
+
sql, name, binds = args
|
38
|
+
t0 = Time.now
|
39
|
+
rval = log_without_miniprofiler(*args, &block)
|
40
|
+
|
41
|
+
# Get our MP Instance
|
42
|
+
instance = ::Rack::MiniProfiler.instance
|
43
|
+
return rval unless instance
|
44
|
+
|
45
|
+
# Don't log schema queries if the option is set
|
46
|
+
return rval if instance.options[:skip_schema_queries] and name =~ /SCHEMA/
|
47
|
+
|
48
|
+
elapsed_time = ((Time.now - t0).to_f * 1000).round(1)
|
49
|
+
instance.record_sql(sql, elapsed_time)
|
50
|
+
rval
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.insert_instrumentation
|
56
|
+
ActiveRecord::ConnectionAdapters::AbstractAdapter.module_eval do
|
57
|
+
include ::Rack::MiniProfiler::ActiveRecordInstrumentation
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
if defined?(::Rails)
|
62
|
+
if ::Rails::VERSION::MAJOR.to_i == 3
|
63
|
+
# in theory this is the right thing to do for rails 3 ... but it seems to work anyway
|
64
|
+
#Rails.configuration.after_initialize do
|
65
|
+
insert_instrumentation
|
66
|
+
#end
|
67
|
+
else
|
68
|
+
insert_instrumentation
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|