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,9 @@
1
+ tbody tr:nth-child(odd) { background-color:#eee; }
2
+ tbody tr:nth-child(even) { background-color:#fff; }
3
+ table { border: 0; border-spacing:0;}
4
+ tr {border: 0;}
5
+ .date {font-size: 11px; color: #666;}
6
+ td {padding: 8px;}
7
+ .time {text-align:center;}
8
+ thead tr {background-color: #bbb; color: #444; font-size: 12px;}
9
+ thead tr th { padding: 5px 15px;}
@@ -0,0 +1,38 @@
1
+ var MiniProfiler = MiniProfiler || {};
2
+ MiniProfiler.list = {
3
+ init:
4
+ function (options) {
5
+ var $ = MiniProfiler.jQuery;
6
+ var opt = options || {};
7
+
8
+ var updateGrid = function (id) {
9
+ $.ajax({
10
+ url: options.path + 'results-list',
11
+ data: { "last-id": id },
12
+ dataType: 'json',
13
+ type: 'GET',
14
+ success: function (data) {
15
+ $('table tbody').append($("#rowTemplate").tmpl(data));
16
+ var oldId = id;
17
+ var oldData = data;
18
+ setTimeout(function () {
19
+ var newId = oldId;
20
+ if (oldData.length > 0) {
21
+ newId = oldData[oldData.length - 1].Id;
22
+ }
23
+ updateGrid(newId);
24
+ }, 4000);
25
+ }
26
+ });
27
+ }
28
+
29
+ MiniProfiler.path = options.path;
30
+ $.get(options.path + 'list.tmpl?v=' + options.version, function (data) {
31
+ if (data) {
32
+ $('body').append(data);
33
+ $('body').append($('#tableTemplate').tmpl());
34
+ updateGrid();
35
+ }
36
+ });
37
+ }
38
+ };
@@ -0,0 +1,34 @@
1
+ <script id="tableTemplate" type="text/x-jquery-tmpl">
2
+ <table>
3
+ <thead>
4
+ <tr>
5
+ <th>Name</th>
6
+ <th>Started</th>
7
+ <th>Sql Duration</th>
8
+ <th>Total Duration</th>
9
+ <th>Request Start</th>
10
+ <th>Response Start</th>
11
+ <th>Dom Complete</th>
12
+ </tr>
13
+ </thead>
14
+ <tbody>
15
+
16
+ </tbody>
17
+ </table>
18
+ </script>
19
+ <script id="rowTemplate" type="text/x-jquery-tmpl">
20
+ <tr>
21
+ <td>
22
+ <a href="${MiniProfiler.path}results?id=${Id}">${Name}</a></td>
23
+ <td class="date">${MiniProfiler.renderDate(Started)}</td>
24
+ <td class="time">${DurationMillisecondsInSql}</td>
25
+ <td class="time">${DurationMilliseconds}</td>
26
+ {{if ClientTimings}}
27
+ <td class="time">${MiniProfiler.getClientTimingByName(ClientTimings,"Request").Start}</td>
28
+ <td class="time">${MiniProfiler.getClientTimingByName(ClientTimings,"Response").Start}</td>
29
+ <td class="time">${MiniProfiler.getClientTimingByName(ClientTimings,"Dom Complete").Start}</td>
30
+ {{else}}
31
+ <td colspan="3"></td>
32
+ {{/if}}
33
+ </tr>
34
+ </script>
@@ -0,0 +1 @@
1
+ <script async type="text/javascript" id="mini-profiler" src="{path}includes.js?v={version}" data-version="{version}" data-path="{path}" data-current-id="{currentId}" data-ids="{ids}" data-position="{position}" data-trivial="{showTrivial}" data-children="{showChildren}" data-max-traces="{maxTracesToShow}" data-controls="{showControls}" data-authorized="{authorized}" data-toggle-shortcut="{toggleShortcut}" data-start-hidden="{startHidden}"></script>
@@ -0,0 +1,11 @@
1
+ <html>
2
+ <head>
3
+ <title>{name} ({duration} ms) - Profiling Results</title>
4
+ <script type='text/javascript' src='{path}jquery.1.7.1.js?v={version}'></script>
5
+ <script type='text/javascript'> var profiler = {json}; </script>
6
+ {includes}
7
+ </head>
8
+ <body>
9
+ <div class='profiler-result-full'></div>
10
+ </body>
11
+ </html>
@@ -0,0 +1,65 @@
1
+ module Rack
2
+ class MiniProfiler
3
+ class ClientSettings
4
+
5
+ COOKIE_NAME = "__profilin"
6
+
7
+ BACKTRACE_DEFAULT = nil
8
+ BACKTRACE_FULL = 1
9
+ BACKTRACE_NONE = 2
10
+
11
+ attr_accessor :disable_profiling
12
+ attr_accessor :backtrace_level
13
+
14
+
15
+ def initialize(env)
16
+ request = ::Rack::Request.new(env)
17
+ @cookie = request.cookies[COOKIE_NAME]
18
+ if @cookie
19
+ @cookie.split(",").map{|pair| pair.split("=")}.each do |k,v|
20
+ @orig_disable_profiling = @disable_profiling = (v=='t') if k == "dp"
21
+ @backtrace_level = v.to_i if k == "bt"
22
+ end
23
+ end
24
+
25
+ @backtrace_level = nil if !@backtrace_level.nil? && (@backtrace_level == 0 || @backtrace_level > BACKTRACE_NONE)
26
+ @orig_backtrace_level = @backtrace_level
27
+
28
+ end
29
+
30
+ def write!(headers)
31
+ if @orig_disable_profiling != @disable_profiling || @orig_backtrace_level != @backtrace_level || @cookie.nil?
32
+ settings = {"p" => "t" }
33
+ settings["dp"] = "t" if @disable_profiling
34
+ settings["bt"] = @backtrace_level if @backtrace_level
35
+ settings_string = settings.map{|k,v| "#{k}=#{v}"}.join(",")
36
+ Rack::Utils.set_cookie_header!(headers, COOKIE_NAME, :value => settings_string, :path => '/')
37
+ end
38
+ end
39
+
40
+ def discard_cookie!(headers)
41
+ Rack::Utils.delete_cookie_header!(headers, COOKIE_NAME, :path => '/')
42
+ end
43
+
44
+ def has_cookie?
45
+ !@cookie.nil?
46
+ end
47
+
48
+ def disable_profiling?
49
+ @disable_profiling
50
+ end
51
+
52
+ def backtrace_full?
53
+ @backtrace_level == BACKTRACE_FULL
54
+ end
55
+
56
+ def backtrace_default?
57
+ @backtrace_level == BACKTRACE_DEFAULT
58
+ end
59
+
60
+ def backtrace_none?
61
+ @backtrace_level == BACKTRACE_NONE
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,78 @@
1
+ require 'mini_profiler/timer_struct'
2
+
3
+ module Rack
4
+ class MiniProfiler
5
+
6
+ # This class holds the client timings
7
+ class ClientTimerStruct < TimerStruct
8
+
9
+ def self.init_instrumentation
10
+ "<script type=\"text/javascript\">mPt=function(){var t=[];return{t:t,probe:function(n){t.push({d:new Date(),n:n})}}}()</script>"
11
+ end
12
+
13
+ def self.instrument(name,orig)
14
+ probe = "<script>mPt.probe('#{name}')</script>"
15
+ wrapped = probe
16
+ wrapped << orig
17
+ wrapped << probe
18
+ wrapped
19
+ end
20
+
21
+
22
+ def initialize(env={})
23
+ super
24
+ end
25
+
26
+ def self.init_from_form_data(env, page_struct)
27
+ timings = []
28
+ clientTimes, clientPerf, baseTime = nil
29
+ form = env['rack.request.form_hash']
30
+
31
+ clientPerf = form['clientPerformance'] if form
32
+ clientTimes = clientPerf['timing'] if clientPerf
33
+
34
+ baseTime = clientTimes['navigationStart'].to_i if clientTimes
35
+ return unless clientTimes && baseTime
36
+
37
+ probes = form['clientProbes']
38
+ translated = {}
39
+ if probes && !["null", ""].include?(probes)
40
+ probes.each do |id, val|
41
+ name = val["n"]
42
+ translated[name] ||= {}
43
+ if translated[name][:start]
44
+ translated[name][:finish] = val["d"]
45
+ else
46
+ translated[name][:start] = val["d"]
47
+ end
48
+ end
49
+ end
50
+
51
+ translated.each do |name, data|
52
+ h = {"Name" => name, "Start" => data[:start].to_i - baseTime}
53
+ h["Duration"] = data[:finish].to_i - data[:start].to_i if data[:finish]
54
+ timings.push(h)
55
+ end
56
+
57
+ clientTimes.keys.find_all{|k| k =~ /Start$/ }.each do |k|
58
+ start = clientTimes[k].to_i - baseTime
59
+ finish = clientTimes[k.sub(/Start$/, "End")].to_i - baseTime
60
+ duration = 0
61
+ duration = finish - start if finish > start
62
+ name = k.sub(/Start$/, "").split(/(?=[A-Z])/).map{|s| s.capitalize}.join(' ')
63
+ timings.push({"Name" => name, "Start" => start, "Duration" => duration}) if start >= 0
64
+ end
65
+
66
+ clientTimes.keys.find_all{|k| !(k =~ /(End|Start)$/)}.each do |k|
67
+ timings.push("Name" => k, "Start" => clientTimes[k].to_i - baseTime, "Duration" => -1)
68
+ end
69
+
70
+ rval = self.new
71
+ rval['RedirectCount'] = env['rack.request.form_hash']['clientPerformance']['navigation']['redirectCount']
72
+ rval['Timings'] = timings
73
+ rval
74
+ end
75
+ end
76
+
77
+ end
78
+ end
@@ -0,0 +1,57 @@
1
+ module Rack
2
+ class MiniProfiler
3
+ class Config
4
+
5
+ def self.attr_accessor(*vars)
6
+ @attributes ||= []
7
+ @attributes.concat vars
8
+ super(*vars)
9
+ end
10
+
11
+ def self.attributes
12
+ @attributes
13
+ end
14
+
15
+ attr_accessor :auto_inject, :base_url_path, :pre_authorize_cb, :position,
16
+ :backtrace_remove, :backtrace_includes, :backtrace_ignores, :skip_schema_queries,
17
+ :storage, :user_provider, :storage_instance, :storage_options, :skip_paths, :authorization_mode,
18
+ :toggle_shortcut, :start_hidden
19
+
20
+ # Deprecated options
21
+ attr_accessor :use_existing_jquery
22
+
23
+ def self.default
24
+ new.instance_eval {
25
+ @auto_inject = true # automatically inject on every html page
26
+ @base_url_path = "/mini-profiler-resources/"
27
+
28
+ # called prior to rack chain, to ensure we are allowed to profile
29
+ @pre_authorize_cb = lambda {|env| true}
30
+
31
+ # called after rack chain, to ensure we are REALLY allowed to profile
32
+ @position = 'left' # Where it is displayed
33
+ @skip_schema_queries = false
34
+ @storage = MiniProfiler::MemoryStore
35
+ @user_provider = Proc.new{|env| Rack::Request.new(env).ip}
36
+ @authorization_mode = :allow_all
37
+ @toggle_shortcut = 'Alt+P'
38
+ @start_hidden = false
39
+ self
40
+ }
41
+ end
42
+
43
+ def merge!(config)
44
+ return unless config
45
+ if Hash === config
46
+ config.each{|k,v| instance_variable_set "@#{k}",v}
47
+ else
48
+ self.class.attributes.each{ |k|
49
+ v = config.send k
50
+ instance_variable_set "@#{k}", v if v
51
+ }
52
+ end
53
+ end
54
+
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,11 @@
1
+ class Rack::MiniProfiler::Context
2
+ attr_accessor :inject_js,:current_timer,:page_struct,:skip_backtrace,:full_backtrace,:discard, :mpt_init, :measure
3
+
4
+ def initialize(opts = {})
5
+ opts["measure"] = true unless opts.key? "measure"
6
+ opts.each do |k,v|
7
+ self.instance_variable_set('@' + k, v)
8
+ end
9
+ end
10
+
11
+ end
@@ -0,0 +1,22 @@
1
+ require 'mini_profiler/timer_struct'
2
+
3
+ module Rack
4
+ class MiniProfiler
5
+
6
+ # Timing system for a custom timers such as cache, redis, RPC, external API
7
+ # calls, etc.
8
+ class CustomTimerStruct < TimerStruct
9
+ def initialize(type, duration_ms, page, parent)
10
+ @parent = parent
11
+ @page = page
12
+ @type = type
13
+
14
+ super("Type" => type,
15
+ "StartMilliseconds" => ((Time.now.to_f * 1000).to_i - page['Started']) - duration_ms,
16
+ "DurationMilliseconds" => duration_ms,
17
+ "ParentTimingId" => nil)
18
+ end
19
+ end
20
+
21
+ end
22
+ end
@@ -0,0 +1,54 @@
1
+ # inspired by https://github.com/brendangregg/FlameGraph
2
+
3
+ class Rack::MiniProfiler::FlameGraph
4
+ def initialize(stacks)
5
+ @stacks = stacks
6
+ end
7
+
8
+ def graph_data
9
+ height = 0
10
+
11
+ table = []
12
+ prev = []
13
+
14
+ # a 2d array makes collapsing easy
15
+ @stacks.each_with_index do |stack, pos|
16
+ col = []
17
+
18
+ stack.reverse.map{|r| r.to_s}.each_with_index do |frame, i|
19
+
20
+ if !prev[i].nil?
21
+ last_col = prev[i]
22
+ if last_col[0] == frame
23
+ last_col[1] += 1
24
+ col << nil
25
+ next
26
+ end
27
+ end
28
+
29
+ prev[i] = [frame, 1]
30
+ col << prev[i]
31
+ end
32
+ prev = prev[0..col.length-1].to_a
33
+ table << col
34
+ end
35
+
36
+ data = []
37
+
38
+ # a 1d array makes rendering easy
39
+ table.each_with_index do |col, col_num|
40
+ col.each_with_index do |row, row_num|
41
+ next unless row && row.length == 2
42
+ data << {
43
+ :x => col_num + 1,
44
+ :y => row_num + 1,
45
+ :width => row[1],
46
+ :frame => row[0]
47
+ }
48
+ end
49
+ end
50
+
51
+ data
52
+ end
53
+
54
+ end
@@ -0,0 +1,107 @@
1
+ class Rack::MiniProfiler::GCProfiler
2
+
3
+ def object_space_stats
4
+ stats = {}
5
+ ids = Set.new
6
+ i=0
7
+ ObjectSpace.each_object { |o|
8
+ begin
9
+ i = stats[o.class] || 0
10
+ i += 1
11
+ stats[o.class] = i
12
+ ids << o.object_id if Integer === o.object_id
13
+ rescue NoMethodError
14
+ # Redis::Future undefines .class and .object_id super weird
15
+ end
16
+ }
17
+ {:stats => stats, :ids => ids}
18
+ end
19
+
20
+ def diff_object_stats(before,after)
21
+ diff = {}
22
+ after.each do |k,v|
23
+ diff[k] = v - (before[k] || 0)
24
+ end
25
+ before.each do |k,v|
26
+ diff[k] = 0 - v unless after[k]
27
+ end
28
+
29
+ diff
30
+ end
31
+
32
+ def analyze_strings(ids_before,ids_after)
33
+ result = {}
34
+ ids_after.each do |id|
35
+ obj = ObjectSpace._id2ref(id)
36
+ if String === obj && !ids_before.include?(obj.object_id)
37
+ result[obj] ||= 0
38
+ result[obj] += 1
39
+ end
40
+ end
41
+ result
42
+ end
43
+
44
+ def profile_gc_time(app,env)
45
+ body = []
46
+
47
+ begin
48
+ GC::Profiler.clear
49
+ GC::Profiler.enable
50
+ b = app.call(env)[2]
51
+ b.close if b.respond_to? :close
52
+ body << "GC Profiler ran during this request, if it fired you will see the cost below:\n\n"
53
+ body << GC::Profiler.result
54
+ ensure
55
+ GC.enable
56
+ GC::Profiler.disable
57
+ end
58
+
59
+ return [200, {'Content-Type' => 'text/plain'}, body]
60
+ end
61
+
62
+ def profile_gc(app,env)
63
+
64
+ body = [];
65
+
66
+ stat_before,stat_after,diff,string_analysis = nil
67
+ begin
68
+ GC.disable
69
+ stat_before = object_space_stats
70
+ b = app.call(env)[2]
71
+ b.close if b.respond_to? :close
72
+ stat_after = object_space_stats
73
+
74
+ diff = diff_object_stats(stat_before[:stats],stat_after[:stats])
75
+ string_analysis = analyze_strings(stat_before[:ids], stat_after[:ids])
76
+ ensure
77
+ GC.enable
78
+ end
79
+
80
+
81
+ body << "
82
+ ObjectSpace delta caused by request:
83
+ --------------------------------------------\n"
84
+ diff.to_a.reject{|k,v| v == 0}.sort{|x,y| y[1] <=> x[1]}.each do |k,v|
85
+ body << "#{k} : #{v}\n" if v != 0
86
+ end
87
+
88
+ body << "\n
89
+ ObjectSpace stats:
90
+ -----------------\n"
91
+
92
+ stat_after[:stats].to_a.sort{|x,y| y[1] <=> x[1]}.each do |k,v|
93
+ body << "#{k} : #{v}\n"
94
+ end
95
+
96
+
97
+ body << "\n
98
+ String stats:
99
+ ------------\n"
100
+
101
+ string_analysis.to_a.sort{|x,y| y[1] <=> x[1] }.take(1000).each do |string,count|
102
+ body << "#{count} : #{string}\n"
103
+ end
104
+
105
+ return [200, {'Content-Type' => 'text/plain'}, body]
106
+ end
107
+ end