rack-mini-profiler 0.1.3 → 0.1.8

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,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
-