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.

@@ -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
+