mini-mini-profiler 0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. data/Ruby/CHANGELOG +135 -0
  2. data/Ruby/README.md +161 -0
  3. data/Ruby/lib/html/flamegraph.html +325 -0
  4. data/Ruby/lib/html/includes.css +451 -0
  5. data/Ruby/lib/html/includes.js +945 -0
  6. data/Ruby/lib/html/includes.less +471 -0
  7. data/Ruby/lib/html/includes.tmpl +108 -0
  8. data/Ruby/lib/html/jquery.1.7.1.js +4 -0
  9. data/Ruby/lib/html/jquery.tmpl.js +486 -0
  10. data/Ruby/lib/html/list.css +9 -0
  11. data/Ruby/lib/html/list.js +38 -0
  12. data/Ruby/lib/html/list.tmpl +34 -0
  13. data/Ruby/lib/html/profile_handler.js +1 -0
  14. data/Ruby/lib/html/share.html +11 -0
  15. data/Ruby/lib/mini_profiler/client_settings.rb +65 -0
  16. data/Ruby/lib/mini_profiler/client_timer_struct.rb +78 -0
  17. data/Ruby/lib/mini_profiler/config.rb +57 -0
  18. data/Ruby/lib/mini_profiler/context.rb +11 -0
  19. data/Ruby/lib/mini_profiler/custom_timer_struct.rb +22 -0
  20. data/Ruby/lib/mini_profiler/flame_graph.rb +54 -0
  21. data/Ruby/lib/mini_profiler/gc_profiler.rb +107 -0
  22. data/Ruby/lib/mini_profiler/page_timer_struct.rb +58 -0
  23. data/Ruby/lib/mini_profiler/profiler.rb +544 -0
  24. data/Ruby/lib/mini_profiler/profiling_methods.rb +133 -0
  25. data/Ruby/lib/mini_profiler/request_timer_struct.rb +115 -0
  26. data/Ruby/lib/mini_profiler/sql_timer_struct.rb +58 -0
  27. data/Ruby/lib/mini_profiler/storage/abstract_store.rb +31 -0
  28. data/Ruby/lib/mini_profiler/storage/file_store.rb +111 -0
  29. data/Ruby/lib/mini_profiler/storage/memcache_store.rb +53 -0
  30. data/Ruby/lib/mini_profiler/storage/memory_store.rb +65 -0
  31. data/Ruby/lib/mini_profiler/storage/redis_store.rb +54 -0
  32. data/Ruby/lib/mini_profiler/timer_struct.rb +33 -0
  33. data/Ruby/lib/mini_profiler/version.rb +5 -0
  34. data/Ruby/lib/mini_profiler_rails/railtie.rb +107 -0
  35. data/Ruby/lib/patches/net_patches.rb +14 -0
  36. data/Ruby/lib/patches/sql_patches.rb +272 -0
  37. data/Ruby/lib/rack-mini-profiler.rb +7 -0
  38. data/mini-mini-profiler.gemspec +26 -0
  39. metadata +154 -0
@@ -0,0 +1,133 @@
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
+ def start_step(name)
12
+ if current
13
+ parent_timer = current.current_timer
14
+ current.current_timer = current_timer = current.current_timer.add_child(name)
15
+ [current_timer,parent_timer]
16
+ end
17
+ end
18
+
19
+ def finish_step(obj)
20
+ if obj && current
21
+ current_timer, parent_timer = obj
22
+ current_timer.record_time
23
+ current.current_timer = parent_timer
24
+ end
25
+ end
26
+
27
+ # perform a profiling step on given block
28
+ def step(name, opts = nil)
29
+ if current
30
+ parent_timer = current.current_timer
31
+ result = nil
32
+ current.current_timer = current_timer = current.current_timer.add_child(name)
33
+ begin
34
+ result = yield if block_given?
35
+ ensure
36
+ current_timer.record_time
37
+ current.current_timer = parent_timer
38
+ end
39
+ else
40
+ yield if block_given?
41
+ end
42
+ end
43
+
44
+ def unprofile_method(klass, method)
45
+
46
+ clean = clean_method_name(method)
47
+
48
+ with_profiling = ("#{clean}_with_mini_profiler").intern
49
+ without_profiling = ("#{clean}_without_mini_profiler").intern
50
+
51
+ if klass.send :method_defined?, with_profiling
52
+ klass.send :alias_method, method, without_profiling
53
+ klass.send :remove_method, with_profiling
54
+ klass.send :remove_method, without_profiling
55
+ end
56
+ end
57
+
58
+ def profile_method(klass, method, instance_method = false, &blk)
59
+ default_name = klass.to_s + " " + method.to_s
60
+ clean = clean_method_name(method)
61
+
62
+ with_profiling = ("#{clean}_with_mini_profiler").intern
63
+ without_profiling = ("#{clean}_without_mini_profiler").intern
64
+
65
+ klass = ( class << klass; self; end ) if instance_method
66
+
67
+ if klass.send :method_defined?, with_profiling
68
+ return # dont double profile
69
+ end
70
+
71
+ klass.send :alias_method, without_profiling, method
72
+ klass.send :define_method, with_profiling do |*args, &orig|
73
+ return self.send without_profiling, *args, &orig unless Rack::MiniProfiler.current
74
+
75
+ name = default_name
76
+ if blk
77
+ name =
78
+ if respond_to?(:instance_exec)
79
+ instance_exec(*args, &blk)
80
+ else
81
+ # deprecated in Rails 4.x
82
+ blk.bind(self).call(*args)
83
+ end
84
+ end
85
+
86
+ parent_timer = Rack::MiniProfiler.current.current_timer
87
+ page_struct = Rack::MiniProfiler.current.page_struct
88
+ result = nil
89
+
90
+ Rack::MiniProfiler.current.current_timer = current_timer = parent_timer.add_child(name)
91
+ begin
92
+ result = self.send without_profiling, *args, &orig
93
+ ensure
94
+ current_timer.record_time
95
+ Rack::MiniProfiler.current.current_timer = parent_timer
96
+ end
97
+ result
98
+ end
99
+ klass.send :alias_method, method, with_profiling
100
+ end
101
+
102
+ # Add a custom timing. These are displayed similar to SQL/query time in
103
+ # columns expanding to the right.
104
+ #
105
+ # type - String counter type. Each distinct type gets its own column.
106
+ # duration_ms - Duration of the call in ms. Either this or a block must be
107
+ # given but not both.
108
+ #
109
+ # When a block is given, calculate the duration by yielding to the block
110
+ # and keeping a record of its run time.
111
+ #
112
+ # Returns the result of the block, or nil when no block is given.
113
+ def counter(type, duration_ms=nil)
114
+ result = nil
115
+ if block_given?
116
+ start = Time.now
117
+ result = yield
118
+ duration_ms = (Time.now - start).to_f * 1000
119
+ end
120
+ return result if current.nil? || !request_authorized?
121
+ current.current_timer.add_custom(type, duration_ms, current.page_struct)
122
+ result
123
+ end
124
+
125
+ private
126
+
127
+ def clean_method_name(method)
128
+ method.to_s.gsub(/[\?\!]/, "")
129
+ end
130
+
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,115 @@
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, nil)
10
+ rt["IsRoot"]= true
11
+ rt
12
+ end
13
+
14
+ attr_accessor :children_duration
15
+
16
+ def initialize(name, page, parent)
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"=> parent ? parent.depth + 1 : 0,
34
+ "ExecutedReaders"=> 0,
35
+ "ExecutedScalars"=> 0,
36
+ "ExecutedNonQueries"=> 0,
37
+ "CustomTimingStats" => {},
38
+ "CustomTimings" => {})
39
+ @children_duration = 0
40
+ @start = Time.now
41
+ @parent = parent
42
+ @page = page
43
+ end
44
+
45
+ def duration_ms
46
+ self['DurationMilliseconds']
47
+ end
48
+
49
+ def start_ms
50
+ self['StartMilliseconds']
51
+ end
52
+
53
+ def start
54
+ @start
55
+ end
56
+
57
+ def depth
58
+ self['Depth']
59
+ end
60
+
61
+ def children
62
+ self['Children']
63
+ end
64
+
65
+ def add_child(name)
66
+ request_timer = RequestTimerStruct.new(name, @page, self)
67
+ self['Children'].push(request_timer)
68
+ self['HasChildren'] = true
69
+ request_timer['ParentTimingId'] = self['Id']
70
+ request_timer['Depth'] = self['Depth'] + 1
71
+ request_timer
72
+ end
73
+
74
+ def add_sql(query, elapsed_ms, page, skip_backtrace = false, full_backtrace = false)
75
+ timer = SqlTimerStruct.new(query, elapsed_ms, page, self , skip_backtrace, full_backtrace)
76
+ timer['ParentTimingId'] = self['Id']
77
+ self['SqlTimings'].push(timer)
78
+ self['HasSqlTimings'] = true
79
+ self['SqlTimingsDurationMilliseconds'] += elapsed_ms
80
+ page['DurationMillisecondsInSql'] += elapsed_ms
81
+ timer
82
+ end
83
+
84
+ def add_custom(type, elapsed_ms, page)
85
+ timer = CustomTimerStruct.new(type, elapsed_ms, page, self)
86
+ timer['ParentTimingId'] = self['Id']
87
+ self['CustomTimings'][type] ||= []
88
+ self['CustomTimings'][type].push(timer)
89
+
90
+ self['CustomTimingStats'][type] ||= {"Count" => 0, "Duration" => 0.0}
91
+ self['CustomTimingStats'][type]['Count'] += 1
92
+ self['CustomTimingStats'][type]['Duration'] += elapsed_ms
93
+
94
+ page['CustomTimingStats'][type] ||= {"Count" => 0, "Duration" => 0.0}
95
+ page['CustomTimingStats'][type]['Count'] += 1
96
+ page['CustomTimingStats'][type]['Duration'] += elapsed_ms
97
+
98
+ timer
99
+ end
100
+
101
+ def record_time(milliseconds = nil)
102
+ milliseconds ||= (Time.now - @start) * 1000
103
+ self['DurationMilliseconds'] = milliseconds
104
+ self['IsTrivial'] = true if milliseconds < self["TrivialDurationThresholdMilliseconds"]
105
+ self['DurationWithoutChildrenMilliseconds'] = milliseconds - @children_duration
106
+
107
+ if @parent
108
+ @parent.children_duration += milliseconds
109
+ end
110
+
111
+ end
112
+ end
113
+ end
114
+
115
+ end
@@ -0,0 +1,58 @@
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, parent, skip_backtrace = false, full_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.config.backtrace_remove, '') if Rack::MiniProfiler.config.backtrace_remove and !full_backtrace
17
+ if full_backtrace or
18
+ (
19
+ (
20
+ Rack::MiniProfiler.config.backtrace_includes.nil? or
21
+ Rack::MiniProfiler.config.backtrace_includes.all?{|regex| ln =~ regex}
22
+ ) and
23
+ (
24
+ Rack::MiniProfiler.config.backtrace_ignores.nil? or
25
+ Rack::MiniProfiler.config.backtrace_ignores.all?{|regex| !(ln =~ regex)}
26
+ )
27
+ )
28
+ stack_trace << ln << "\n"
29
+ end
30
+ end
31
+ end
32
+
33
+ @parent = parent
34
+ @page = page
35
+
36
+ super("ExecuteType" => 3, # TODO
37
+ "FormattedCommandString" => query,
38
+ "StackTraceSnippet" => stack_trace,
39
+ "StartMilliseconds" => ((Time.now.to_f * 1000).to_i - page['Started']) - duration_ms,
40
+ "DurationMilliseconds" => duration_ms,
41
+ "FirstFetchDurationMilliseconds" => duration_ms,
42
+ "Parameters" => nil,
43
+ "ParentTimingId" => nil,
44
+ "IsDuplicate" => false)
45
+ end
46
+
47
+ def report_reader_duration(elapsed_ms)
48
+ return if @reported
49
+ @reported = true
50
+ self["DurationMilliseconds"] += elapsed_ms
51
+ @parent["SqlTimingsDurationMilliseconds"] += elapsed_ms
52
+ @page["DurationMillisecondsInSql"] += elapsed_ms
53
+ end
54
+
55
+ end
56
+
57
+ end
58
+ end
@@ -0,0 +1,31 @@
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
+ def diagnostics(user)
26
+ raise NotImplementedError.new("diagnostics not implemented")
27
+ end
28
+
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,111 @@
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
+ EXPIRES_IN_SECONDS = 60 * 60 * 24
31
+
32
+ def initialize(args = nil)
33
+ args ||= {}
34
+ @path = args[:path]
35
+ @expires_in_seconds = args[:expires_in] || EXPIRES_IN_SECONDS
36
+ raise ArgumentError.new :path unless @path
37
+ @timer_struct_cache = FileCache.new(@path, "mp_timers")
38
+ @timer_struct_lock = Mutex.new
39
+ @user_view_cache = FileCache.new(@path, "mp_views")
40
+ @user_view_lock = Mutex.new
41
+
42
+ me = self
43
+ Thread.new do
44
+ begin
45
+ while true do
46
+ # TODO: a sane retry count before bailing
47
+ me.cleanup_cache
48
+ sleep(3600)
49
+ end
50
+ rescue
51
+ # don't crash the thread, we can clean up next time
52
+ end
53
+ end
54
+ end
55
+
56
+ def save(page_struct)
57
+ @timer_struct_lock.synchronize {
58
+ @timer_struct_cache[page_struct['Id']] = page_struct
59
+ }
60
+ end
61
+
62
+ def load(id)
63
+ @timer_struct_lock.synchronize {
64
+ @timer_struct_cache[id]
65
+ }
66
+ end
67
+
68
+ def set_unviewed(user, id)
69
+ @user_view_lock.synchronize {
70
+ current = @user_view_cache[user]
71
+ current = [] unless Array === current
72
+ current << id
73
+ @user_view_cache[user] = current.uniq
74
+ }
75
+ end
76
+
77
+ def set_viewed(user, id)
78
+ @user_view_lock.synchronize {
79
+ @user_view_cache[user] ||= []
80
+ current = @user_view_cache[user]
81
+ current = [] unless Array === current
82
+ current.delete(id)
83
+ @user_view_cache[user] = current.uniq
84
+ }
85
+ end
86
+
87
+ def get_unviewed_ids(user)
88
+ @user_view_lock.synchronize {
89
+ @user_view_cache[user]
90
+ }
91
+ end
92
+
93
+ def cleanup_cache
94
+ files = Dir.entries(@path)
95
+ @timer_struct_lock.synchronize {
96
+ files.each do |f|
97
+ f = @path + '/' + f
98
+ ::File.delete f if ::File.basename(f) =~ /^mp_timers/ and (Time.now - ::File.mtime(f)) > @expires_in_seconds
99
+ end
100
+ }
101
+ @user_view_lock.synchronize {
102
+ files.each do |f|
103
+ f = @path + '/' + f
104
+ ::File.delete f if ::File.basename(f) =~ /^mp_views/ and (Time.now - ::File.mtime(f)) > @expires_in_seconds
105
+ end
106
+ }
107
+ end
108
+
109
+ end
110
+ end
111
+ end