rack-mini-profiler 0.1.3 → 0.1.8

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.

@@ -0,0 +1,73 @@
1
+ module Rack
2
+ class MiniProfiler
3
+ module ProfilingMethods
4
+
5
+ def record_sql(query, elapsed_ms)
6
+ c = current
7
+ return unless c
8
+ c.current_timer.add_sql(query, elapsed_ms, c.page_struct, c.skip_backtrace, c.full_backtrace) if (c && c.current_timer)
9
+ end
10
+
11
+ # perform a profiling step on given block
12
+ def step(name)
13
+ if current
14
+ parent_timer = current.current_timer
15
+ result = nil
16
+ current.current_timer = current_timer = current.current_timer.add_child(name)
17
+ begin
18
+ result = yield if block_given?
19
+ ensure
20
+ current_timer.record_time
21
+ current.current_timer = parent_timer
22
+ end
23
+ result
24
+ else
25
+ yield if block_given?
26
+ end
27
+ end
28
+
29
+ def unprofile_method(klass, method)
30
+ with_profiling = (method.to_s + "_with_mini_profiler").intern
31
+ without_profiling = (method.to_s + "_without_mini_profiler").intern
32
+
33
+ if klass.send :method_defined?, with_profiling
34
+ klass.send :alias_method, method, without_profiling
35
+ klass.send :remove_method, with_profiling
36
+ klass.send :remove_method, without_profiling
37
+ end
38
+ end
39
+
40
+ def profile_method(klass, method, &blk)
41
+ default_name = klass.to_s + " " + method.to_s
42
+ with_profiling = (method.to_s + "_with_mini_profiler").intern
43
+ without_profiling = (method.to_s + "_without_mini_profiler").intern
44
+
45
+ if klass.send :method_defined?, with_profiling
46
+ return # dont double profile
47
+ end
48
+
49
+ klass.send :alias_method, without_profiling, method
50
+ klass.send :define_method, with_profiling do |*args, &orig|
51
+ return self.send without_profiling, *args, &orig unless Rack::MiniProfiler.current
52
+
53
+ name = default_name
54
+ name = blk.bind(self).call(*args) if blk
55
+
56
+ parent_timer = Rack::MiniProfiler.current.current_timer
57
+ page_struct = Rack::MiniProfiler.current.page_struct
58
+ result = nil
59
+
60
+ Rack::MiniProfiler.current.current_timer = current_timer = parent_timer.add_child(name)
61
+ begin
62
+ result = self.send without_profiling, *args, &orig
63
+ ensure
64
+ current_timer.record_time
65
+ Rack::MiniProfiler.current.current_timer = parent_timer
66
+ end
67
+ result
68
+ end
69
+ klass.send :alias_method, method, with_profiling
70
+ end
71
+ end
72
+ end
73
+ end
@@ -6,14 +6,14 @@ module Rack
6
6
  class RequestTimerStruct < TimerStruct
7
7
 
8
8
  def self.createRoot(name, page)
9
- rt = RequestTimerStruct.new(name, page)
9
+ rt = RequestTimerStruct.new(name, page, nil)
10
10
  rt["IsRoot"]= true
11
11
  rt
12
12
  end
13
13
 
14
- attr_reader :children_duration
14
+ attr_accessor :children_duration
15
15
 
16
- def initialize(name, page)
16
+ def initialize(name, page, parent)
17
17
  super("Id" => MiniProfiler.generate_id,
18
18
  "Name" => name,
19
19
  "DurationMilliseconds" => 0,
@@ -30,34 +30,65 @@ module Rack
30
30
  "SqlTimingsDurationMilliseconds"=> 0,
31
31
  "IsTrivial"=> false,
32
32
  "IsRoot"=> false,
33
- "Depth"=> 0,
33
+ "Depth"=> parent ? parent.depth + 1 : 0,
34
34
  "ExecutedReaders"=> 0,
35
35
  "ExecutedScalars"=> 0,
36
36
  "ExecutedNonQueries"=> 0)
37
37
  @children_duration = 0
38
+ @start = Time.now
39
+ @parent = parent
40
+ @page = page
38
41
  end
39
42
 
40
- def add_child(request_timer)
43
+ def duration_ms
44
+ self['DurationMilliseconds']
45
+ end
46
+
47
+ def start_ms
48
+ self['StartMilliseconds']
49
+ end
50
+
51
+ def start
52
+ @start
53
+ end
54
+
55
+ def depth
56
+ self['Depth']
57
+ end
58
+
59
+ def children
60
+ self['Children']
61
+ end
62
+
63
+ def add_child(name)
64
+ request_timer = RequestTimerStruct.new(name, @page, self)
41
65
  self['Children'].push(request_timer)
42
66
  self['HasChildren'] = true
43
67
  request_timer['ParentTimingId'] = self['Id']
44
68
  request_timer['Depth'] = self['Depth'] + 1
45
- @children_duration += request_timer['DurationMilliseconds']
69
+ request_timer
46
70
  end
47
71
 
48
72
  def add_sql(query, elapsed_ms, page, skip_backtrace = false, full_backtrace = false)
49
- timer = SqlTimerStruct.new(query, elapsed_ms, page, skip_backtrace, full_backtrace)
73
+ timer = SqlTimerStruct.new(query, elapsed_ms, page, self , skip_backtrace, full_backtrace)
50
74
  timer['ParentTimingId'] = self['Id']
51
75
  self['SqlTimings'].push(timer)
52
76
  self['HasSqlTimings'] = true
53
77
  self['SqlTimingsDurationMilliseconds'] += elapsed_ms
54
78
  page['DurationMillisecondsInSql'] += elapsed_ms
79
+ timer
55
80
  end
56
81
 
57
- def record_time(milliseconds)
82
+ def record_time(milliseconds = nil)
83
+ milliseconds ||= (Time.now - @start) * 1000
58
84
  self['DurationMilliseconds'] = milliseconds
59
85
  self['IsTrivial'] = true if milliseconds < self["TrivialDurationThresholdMilliseconds"]
60
86
  self['DurationWithoutChildrenMilliseconds'] = milliseconds - @children_duration
87
+
88
+ if @parent
89
+ @parent.children_duration += milliseconds
90
+ end
91
+
61
92
  end
62
93
  end
63
94
  end
@@ -5,7 +5,7 @@ module Rack
5
5
 
6
6
  # Timing system for a SQL query
7
7
  class SqlTimerStruct < TimerStruct
8
- def initialize(query, duration_ms, page, skip_backtrace = false, full_backtrace = false)
8
+ def initialize(query, duration_ms, page, parent, skip_backtrace = false, full_backtrace = false)
9
9
 
10
10
  stack_trace = nil
11
11
  unless skip_backtrace
@@ -20,17 +20,28 @@ module Rack
20
20
  end
21
21
  end
22
22
 
23
+ @parent = parent
24
+ @page = page
25
+
23
26
  super("ExecuteType" => 3, # TODO
24
27
  "FormattedCommandString" => query,
25
28
  "StackTraceSnippet" => stack_trace,
26
29
  "StartMilliseconds" => ((Time.now.to_f * 1000).to_i - page['Started']) - duration_ms,
27
30
  "DurationMilliseconds" => duration_ms,
28
- "FirstFetchDurationMilliseconds" => 0,
31
+ "FirstFetchDurationMilliseconds" => duration_ms,
29
32
  "Parameters" => nil,
30
33
  "ParentTimingId" => nil,
31
34
  "IsDuplicate" => false)
32
35
  end
33
36
 
37
+ def report_reader_duration(elapsed_ms)
38
+ return if @reported
39
+ @reported = true
40
+ self["DurationMilliseconds"] += elapsed_ms
41
+ @parent["SqlTimingsDurationMilliseconds"] += elapsed_ms
42
+ @page["DurationMillisecondsInSql"] += elapsed_ms
43
+ end
44
+
34
45
  end
35
46
 
36
47
  end
@@ -39,9 +39,14 @@ module Rack
39
39
 
40
40
  me = self
41
41
  Thread.new do
42
- while true do
43
- me.cleanup_cache if MiniProfiler.instance
44
- sleep(3600)
42
+ begin
43
+ while true do
44
+ # TODO: a sane retry count before bailing
45
+ me.cleanup_cache
46
+ sleep(3600)
47
+ end
48
+ rescue
49
+ # don't crash the thread, we can clean up next time
45
50
  end
46
51
  end
47
52
  end
@@ -83,10 +88,6 @@ module Rack
83
88
  }
84
89
  end
85
90
 
86
-
87
- private
88
-
89
-
90
91
  def cleanup_cache
91
92
  files = Dir.entries(@path)
92
93
  @timer_struct_lock.synchronize {
@@ -52,10 +52,6 @@ module Rack
52
52
  }
53
53
  end
54
54
 
55
-
56
- private
57
-
58
-
59
55
  def cleanup_cache
60
56
  expire_older_than = ((Time.now.to_f - MiniProfiler::MemoryStore::EXPIRE_TIMER_CACHE) * 1000).to_i
61
57
  @timer_struct_lock.synchronize {
@@ -1,4 +1,7 @@
1
+ require 'fileutils'
2
+
1
3
  module MiniProfilerRails
4
+
2
5
  class Railtie < ::Rails::Railtie
3
6
 
4
7
  initializer "rack_mini_profiler.configure_rails_initialization" do |app|
@@ -21,7 +24,7 @@ module MiniProfilerRails
21
24
 
22
25
  # The file store is just so much less flaky
23
26
  tmp = Rails.root.to_s + "/tmp/miniprofiler"
24
- Dir::mkdir(tmp) unless File.exists?(tmp)
27
+ FileUtils.mkdir_p(tmp) unless File.exists?(tmp)
25
28
 
26
29
  c.storage_options = {:path => tmp}
27
30
  c.storage = Rack::MiniProfiler::FileStore
@@ -38,8 +41,44 @@ module MiniProfilerRails
38
41
  ::Rack::MiniProfiler.profile_method(ActionController::Base, :process) {|action| "Executing action: #{action}"}
39
42
  ::Rack::MiniProfiler.profile_method(ActionView::Template, :render) {|x,y| "Rendering: #{@virtual_path}"}
40
43
 
41
-
42
44
  end
43
45
 
46
+ # TODO: Implement something better here
47
+ # config.after_initialize do
48
+ #
49
+ # class ::ActionView::Helpers::AssetTagHelper::JavascriptIncludeTag
50
+ # alias_method :asset_tag_orig, :asset_tag
51
+ # def asset_tag(source,options)
52
+ # current = Rack::MiniProfiler.current
53
+ # return asset_tag_orig(source,options) unless current
54
+ # wrapped = ""
55
+ # unless current.mpt_init
56
+ # current.mpt_init = true
57
+ # wrapped << Rack::MiniProfiler::ClientTimerStruct.init_instrumentation
58
+ # end
59
+ # name = source.split('/')[-1]
60
+ # wrapped << Rack::MiniProfiler::ClientTimerStruct.instrument(name, asset_tag_orig(source,options)).html_safe
61
+ # wrapped
62
+ # end
63
+ # end
64
+
65
+ # class ::ActionView::Helpers::AssetTagHelper::StylesheetIncludeTag
66
+ # alias_method :asset_tag_orig, :asset_tag
67
+ # def asset_tag(source,options)
68
+ # current = Rack::MiniProfiler.current
69
+ # return asset_tag_orig(source,options) unless current
70
+ # wrapped = ""
71
+ # unless current.mpt_init
72
+ # current.mpt_init = true
73
+ # wrapped << Rack::MiniProfiler::ClientTimerStruct.init_instrumentation
74
+ # end
75
+ # name = source.split('/')[-1]
76
+ # wrapped << Rack::MiniProfiler::ClientTimerStruct.instrument(name, asset_tag_orig(source,options)).html_safe
77
+ # wrapped
78
+ # end
79
+ # end
80
+
81
+ # end
82
+
44
83
  end
45
84
  end
@@ -1,17 +1,136 @@
1
- module SqlPatches
1
+ class SqlPatches
2
+
3
+ def self.patched?
4
+ @patched
5
+ end
6
+
7
+ def self.patched=(val)
8
+ @patched = val
9
+ end
10
+
2
11
  def self.class_exists?(name)
3
12
  eval(name + ".class").to_s.eql?('Class')
4
13
  rescue NameError
5
14
  false
6
15
  end
16
+
17
+ def self.module_exists?(name)
18
+ eval(name + ".class").to_s.eql?('Module')
19
+ rescue NameError
20
+ false
21
+ end
22
+ end
23
+
24
+ # The best kind of instrumentation is in the actual db provider, however we don't want to double instrument
25
+ if SqlPatches.class_exists? "Mysql2::Client"
26
+
27
+ class Mysql2::Result
28
+ alias_method :each_without_profiling, :each
29
+ def each(*args, &blk)
30
+ return each_without_profiling(*args, &blk) unless @miniprofiler_sql_id
31
+
32
+ start = Time.now
33
+ result = each_without_profiling(*args,&blk)
34
+ elapsed_time = ((Time.now - start).to_f * 1000).round(1)
35
+
36
+ @miniprofiler_sql_id.report_reader_duration(elapsed_time)
37
+ result
38
+ end
39
+ end
40
+
41
+ class Mysql2::Client
42
+ alias_method :query_without_profiling, :query
43
+ def query(*args,&blk)
44
+ current = ::Rack::MiniProfiler.current
45
+ return query_without_profiling(*args,&blk) unless current
46
+
47
+ start = Time.now
48
+ result = query_without_profiling(*args,&blk)
49
+ elapsed_time = ((Time.now - start).to_f * 1000).round(1)
50
+ result.instance_variable_set("@miniprofiler_sql_id", ::Rack::MiniProfiler.record_sql(args[0], elapsed_time))
51
+
52
+ result
53
+
54
+ end
55
+ end
56
+
57
+ SqlPatches.patched = true
58
+ end
59
+
60
+
61
+ # PG patches, keep in mind exec and async_exec have a exec{|r| } semantics that is yet to be implemented
62
+ if SqlPatches.class_exists? "PG::Result"
63
+
64
+ class PG::Result
65
+ alias_method :each_without_profiling, :each
66
+ alias_method :values_without_profiling, :values
67
+
68
+ def values(*args, &blk)
69
+ return values_without_profiling(*args, &blk) unless @miniprofiler_sql_id
70
+
71
+ start = Time.now
72
+ result = values_without_profiling(*args,&blk)
73
+ elapsed_time = ((Time.now - start).to_f * 1000).round(1)
74
+
75
+ @miniprofiler_sql_id.report_reader_duration(elapsed_time)
76
+ result
77
+ end
78
+
79
+ def each(*args, &blk)
80
+ return each_without_profiling(*args, &blk) unless @miniprofiler_sql_id
81
+
82
+ start = Time.now
83
+ result = each_without_profiling(*args,&blk)
84
+ elapsed_time = ((Time.now - start).to_f * 1000).round(1)
85
+
86
+ @miniprofiler_sql_id.report_reader_duration(elapsed_time)
87
+ result
88
+ end
89
+ end
90
+
91
+ class PG::Connection
92
+ alias_method :exec_without_profiling, :exec
93
+ alias_method :async_exec_without_profiling, :async_exec
94
+
95
+ def exec(*args,&blk)
96
+ current = ::Rack::MiniProfiler.current
97
+ return exec_without_profiling(*args,&blk) unless current
98
+
99
+ start = Time.now
100
+ result = exec_without_profiling(*args,&blk)
101
+ elapsed_time = ((Time.now - start).to_f * 1000).round(1)
102
+ result.instance_variable_set("@miniprofiler_sql_id", ::Rack::MiniProfiler.record_sql(args[0], elapsed_time))
103
+
104
+ result
105
+ end
106
+
107
+ def async_exec(*args,&blk)
108
+ current = ::Rack::MiniProfiler.current
109
+ return exec_without_profiling(*args,&blk) unless current
110
+
111
+ start = Time.now
112
+ result = exec_without_profiling(*args,&blk)
113
+ elapsed_time = ((Time.now - start).to_f * 1000).round(1)
114
+ result.instance_variable_set("@miniprofiler_sql_id", ::Rack::MiniProfiler.record_sql(args[0], elapsed_time))
115
+
116
+ result
117
+ end
118
+
119
+ alias_method :query, :exec
120
+ end
121
+
122
+ SqlPatches.patched = true
7
123
  end
8
124
 
9
- if SqlPatches.class_exists? "Sequel::Database" then
125
+
126
+
127
+ # Fallback for sequel
128
+ if SqlPatches.class_exists?("Sequel::Database") && !SqlPatches.patched?
10
129
  module Sequel
11
130
  class Database
12
131
  alias_method :log_duration_original, :log_duration
13
132
  def log_duration(duration, message)
14
- ::Rack::MiniProfiler.instance.record_sql(message, duration) if ::Rack::MiniProfiler.instance
133
+ ::Rack::MiniProfiler.record_sql(message, duration)
15
134
  log_duration_original(duration, message)
16
135
  end
17
136
  end
@@ -20,46 +139,47 @@ end
20
139
 
21
140
 
22
141
  ## 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
142
+ ## fallback for alls sorts of weird dbs
143
+ if SqlPatches.module_exists?('ActiveRecord')
144
+ module Rack
145
+ class MiniProfiler
146
+ module ActiveRecordInstrumentation
147
+ def self.included(instrumented_class)
148
+ instrumented_class.class_eval do
149
+ unless instrumented_class.method_defined?(:log_without_miniprofiler)
150
+ alias_method :log_without_miniprofiler, :log
151
+ alias_method :log, :log_with_miniprofiler
152
+ protected :log
153
+ end
32
154
  end
33
155
  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
156
 
41
- # Get our MP Instance
42
- instance = ::Rack::MiniProfiler.instance
43
- return rval unless instance
157
+ def log_with_miniprofiler(*args, &block)
158
+ current = ::Rack::MiniProfiler.current
159
+ return log_without_miniprofiler(*args, &block) unless current
44
160
 
45
- # Don't log schema queries if the option is set
46
- return rval if instance.config.skip_schema_queries and name =~ /SCHEMA/
161
+ sql, name, binds = args
162
+ t0 = Time.now
163
+ rval = log_without_miniprofiler(*args, &block)
164
+
165
+ # Don't log schema queries if the option is set
166
+ return rval if Rack::MiniProfiler.config.skip_schema_queries and name =~ /SCHEMA/
47
167
 
48
- elapsed_time = ((Time.now - t0).to_f * 1000).round(1)
49
- instance.record_sql(sql, elapsed_time)
50
- rval
168
+ elapsed_time = ((Time.now - t0).to_f * 1000).round(1)
169
+ Rack::MiniProfiler.record_sql(sql, elapsed_time)
170
+ rval
171
+ end
51
172
  end
52
173
  end
53
- end
54
174
 
55
- def self.insert_instrumentation
56
- ActiveRecord::ConnectionAdapters::AbstractAdapter.module_eval do
57
- include ::Rack::MiniProfiler::ActiveRecordInstrumentation
175
+ def self.insert_instrumentation
176
+ ActiveRecord::ConnectionAdapters::AbstractAdapter.module_eval do
177
+ include ::Rack::MiniProfiler::ActiveRecordInstrumentation
178
+ end
58
179
  end
59
- end
60
180
 
61
- if defined?(::Rails)
62
- insert_instrumentation
181
+ if defined?(::Rails) && !SqlPatches.patched?
182
+ insert_instrumentation
183
+ end
63
184
  end
64
185
  end
65
-