rack-mini-profiler 0.1.22 → 0.1.27

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.

Files changed (35) hide show
  1. checksums.yaml +7 -0
  2. data/Ruby/CHANGELOG +69 -35
  3. data/Ruby/README.md +47 -9
  4. data/Ruby/lib/html/flamegraph.html +351 -0
  5. data/Ruby/lib/html/includes.css +451 -75
  6. data/Ruby/lib/html/includes.js +134 -23
  7. data/Ruby/lib/html/includes.less +38 -35
  8. data/Ruby/lib/html/includes.tmpl +40 -15
  9. data/Ruby/lib/html/jquery.1.7.1.js +1 -1
  10. data/Ruby/lib/html/jquery.tmpl.js +1 -1
  11. data/Ruby/lib/html/list.js +7 -6
  12. data/Ruby/lib/html/profile_handler.js +1 -62
  13. data/Ruby/lib/mini_profiler/client_timer_struct.rb +1 -1
  14. data/Ruby/lib/mini_profiler/config.rb +58 -52
  15. data/Ruby/lib/mini_profiler/context.rb +11 -10
  16. data/Ruby/lib/mini_profiler/custom_timer_struct.rb +22 -0
  17. data/Ruby/lib/mini_profiler/flame_graph.rb +54 -0
  18. data/Ruby/lib/mini_profiler/gc_profiler.rb +8 -4
  19. data/Ruby/lib/mini_profiler/page_timer_struct.rb +7 -2
  20. data/Ruby/lib/mini_profiler/profiler.rb +207 -157
  21. data/Ruby/lib/mini_profiler/profiling_methods.rb +131 -108
  22. data/Ruby/lib/mini_profiler/request_timer_struct.rb +20 -1
  23. data/Ruby/lib/mini_profiler/sql_timer_struct.rb +1 -1
  24. data/Ruby/lib/mini_profiler/storage/abstract_store.rb +31 -27
  25. data/Ruby/lib/mini_profiler/storage/file_store.rb +111 -109
  26. data/Ruby/lib/mini_profiler/storage/memcache_store.rb +11 -9
  27. data/Ruby/lib/mini_profiler/storage/memory_store.rb +65 -63
  28. data/Ruby/lib/mini_profiler/storage/redis_store.rb +54 -44
  29. data/Ruby/lib/mini_profiler/version.rb +5 -0
  30. data/Ruby/lib/mini_profiler_rails/railtie.rb +44 -40
  31. data/Ruby/lib/patches/net_patches.rb +14 -0
  32. data/Ruby/lib/patches/sql_patches.rb +89 -48
  33. data/Ruby/lib/rack-mini-profiler.rb +1 -0
  34. data/rack-mini-profiler.gemspec +1 -1
  35. metadata +41 -52
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 6cc0ebab51bb03cdde11e69f1c1b2d4f204e6c04
4
+ data.tar.gz: 704a46d89f38d3a956d193a9913bc00321299453
5
+ SHA512:
6
+ metadata.gz: 0c084c48f8a7ca7bf9ad5a85f608e66b212b1e874c295a8c8b9a238887064e7a00c4a5e6edae2f2dfb73f958ba0972fcf7b8f74c69c920471627df6dba17b69b
7
+ data.tar.gz: 0c99b98715befd9127dc3da931682d2eded099d70b31bc74d134695d4d7e5b11a96c9a4c22b202e572f6ac2198d212dde015c218a8ad8747acb8ff4947baca7e
@@ -1,77 +1,77 @@
1
- 28-June-2012 - Sam
2
-
1
+ 28-June-2012 - Sam
2
+
3
3
  * Started change log
4
4
  * Corrected profiler so it properly captures POST requests (was supressing non 200s)
5
- * Amended Rack.MiniProfiler.config[:user_provider] to use ip addres for identity
5
+ * Amended Rack.MiniProfiler.config[:user_provider] to use ip addres for identity
6
6
  * Fixed bug where unviewed missing ids never got cleared
7
7
  * Supress all '/assets/' in the rails tie (makes debugging easier)
8
8
  * record_sql was mega buggy
9
9
  * added MemcacheStore
10
10
 
11
- 9-July-2012 - Sam
11
+ 9-July-2012 - Sam
12
12
 
13
13
  * Cleaned up mechanism for profiling in production, all you need to do now
14
- is call Rack::MiniProfiler.authorize_request to get profiling working in
15
- production
16
- * Added option to display full backtraces pp=full-backtrace
17
- * Cleaned up railties, got rid of the post authorize callback
18
- * Version 0.1.3
14
+ is call Rack::MiniProfiler.authorize_request to get profiling working in
15
+ production
16
+ * Added option to display full backtraces pp=full-backtrace
17
+ * Cleaned up railties, got rid of the post authorize callback
18
+ * Version 0.1.3
19
19
 
20
- 12-July-2012 - Sam
20
+ 12-July-2012 - Sam
21
21
 
22
22
  * Fixed incorrect profiling steps (was not indenting or measuring start time right
23
- * Implemented native PG and MySql2 interceptors, this gives way more accurate times
24
- * Refactored context so its a proper class and not a hash
25
- * Added some more client probing built in to rails
26
- * More tests
23
+ * Implemented native PG and MySql2 interceptors, this gives way more accurate times
24
+ * Refactored context so its a proper class and not a hash
25
+ * Added some more client probing built in to rails
26
+ * More tests
27
+
28
+ 18-July-2012 - Sam
27
29
 
28
- 18-July-2012 - Sam
29
-
30
30
  * Added First Paint time for chrome
31
31
  * Bug fix to ensure non Rails installs have mini profiler
32
32
  * Version 0.1.7
33
33
 
34
- 30-July-2012 - Sam
35
-
34
+ 30-July-2012 - Sam
35
+
36
36
  * Made compliant with ancient versions of Rack (including Rack used by Rails2)
37
- * Fixed broken share link
37
+ * Fixed broken share link
38
38
  * Fixed crashes on startup (in MemoryStore and FileStore)
39
39
  * Version 0.1.8
40
- * Unicode fix
40
+ * Unicode fix
41
41
  * Version 0.1.9
42
42
 
43
43
  7-August-2012 - Sam
44
44
 
45
45
  * Added option to disable profiler for the current session (pp=disable / pp=enable)
46
- * yajl compatability contributed by Sven Riedel
46
+ * yajl compatability contributed by Sven Riedel
47
47
 
48
- 10-August-2012 - Sam
48
+ 10-August-2012 - Sam
49
49
 
50
- * Added basic prepared statement profiling for postgres
50
+ * Added basic prepared statement profiling for postgres
51
51
 
52
- 20-August-2012 - Sam
53
-
54
- * 1.12.pre
52
+ 20-August-2012 - Sam
53
+
54
+ * 1.12.pre
55
55
  * Cap X-MiniProfiler-Ids at 10, otherwise the header can get killed
56
56
 
57
57
  3-September-2012 - Sam
58
58
 
59
59
  * 1.13.pre
60
- * pg gem prepared statements were not being logged correctly
60
+ * pg gem prepared statements were not being logged correctly
61
61
  * added setting config.backtrace_ignores = [] - an array of regexes that match on caller lines that get ignored
62
- * added setting config.backtrace_includes = [] - an array of regexes that get included in the trace by default
62
+ * added setting config.backtrace_includes = [] - an array of regexes that get included in the trace by default
63
63
  * cleaned up the way client settings are stored
64
64
  * made pp=full-backtrace "sticky"
65
65
  * added pp=normal-backtrace to clear the "sticky" state
66
66
  * change "pp=sample" to work with "caller" no need for stack trace gem
67
67
 
68
- 4-September-2012 - Sam
68
+ 4-September-2012 - Sam
69
69
 
70
70
  * 1.15.pre
71
- * fixed annoying bug where client settings were not sticking
71
+ * fixed annoying bug where client settings were not sticking
72
72
  * fixed long standing issue with Rack::ConditionalGet stopping MiniProfiler from working properly
73
73
 
74
- 5-September-2012 - Sam
74
+ 5-September-2012 - Sam
75
75
 
76
76
  * 1.16
77
77
  * fixed long standing problem specs (issue with memory store)
@@ -83,25 +83,59 @@
83
83
  * 1.17
84
84
  * pp=sample was bust unless stacktrace was installed
85
85
 
86
- 10-September-2012 - Sam
86
+ 10-September-2012 - Sam
87
87
 
88
88
  * 1.19
89
89
  * fix compat issue with 1.8.7
90
90
 
91
91
  12-September-2012 - Sam
92
-
92
+
93
93
  * 1.20
94
94
  * Added pp=profile-gc , it allows you to profile the GC in Ruby 1.9.3
95
95
 
96
96
  17-September-2012
97
97
  * 1.21
98
- * New MemchacedStore
98
+ * New MemchacedStore
99
99
  * Rails 4 support
100
100
 
101
101
  17-September-2012
102
102
  * Allow rack-mini-profiler to be sourced from github
103
103
  * Extracted the pp=profile-gc-time out, the object space profiler needs to disable gc
104
104
 
105
- 20-September-2010
105
+ 20-September-2012
106
106
  * 1.22
107
107
  * Fix permission issue in the gem
108
+
109
+ 8-April-2013
110
+ * 1.24
111
+ * Flame Graph Support see: http://samsaffron.com/archive/2013/03/19/flame-graphs-in-ruby-miniprofiler
112
+ * Fix file retention leak in file_store
113
+ * New toggle_shortcut and start_hidden options
114
+ * Fix for AngularJS support and MooTools
115
+ * More robust gc profiling
116
+ * Mongoid support
117
+ * Fix for html5 implicit body tags
118
+ * script tag initialized via data-attributes
119
+ * new - Rack::MiniProfiler.counter counter_name {}
120
+ * Allow usage of existing jQuery if its already loaded
121
+ * Fix pp=enable
122
+ * 1.8.7 support ... grrr
123
+ * Net:HTTP profiling
124
+ * pre authorize to run in all non development? and production? modes
125
+
126
+ 8-April-2013
127
+ * 1.25
128
+ * Missed flamegraph.html from build
129
+
130
+ 11-April-2013
131
+ * 1.26
132
+ * (minor) allow Rack::MiniProfilerRails.initialize!(Rails.application), for post config intialization
133
+
134
+ 26-June-2013
135
+ * 1.27
136
+ * Disable global ajax handlers on MP requests @JP
137
+ * Add Rack::MiniProfiler.config.backtrace_threshold_ms
138
+ * jQuery 2.0 support
139
+
140
+
141
+
@@ -15,7 +15,7 @@ All you have to do is include the Gem and you're good to go in development.
15
15
 
16
16
  rack-mini-profiler is designed with production profiling in mind. To enable that just run `Rack::MiniProfiler.authorize_request` once you know a request is allowed to profile.
17
17
 
18
- For example:
18
+ Using Rails:
19
19
 
20
20
  ```ruby
21
21
  # A hook in your ApplicationController
@@ -47,23 +47,53 @@ class MyApp < Sinatra::Base
47
47
  end
48
48
  ```
49
49
 
50
+ ## Database profiling
51
+
52
+ Currently supports Mysql2, Postgres, and Mongoid3 (with fallback support to ActiveRecord)
53
+
50
54
  ## Storage
51
55
 
52
- By default, rack-mini-profiler stores its results in a memory store:
56
+ rack-mini-profiler stores it's results so they can be shared later and aren't lost at the end of the request.
57
+
58
+ There are 4 storage options: `MemoryStore`, `RedisStore`, `MemcacheStore`, and `FileStore`.
59
+
60
+ `FileStore` is the default in Rails environments and will write files to `tmp/miniprofiler/*`. `MemoryStore` is the default otherwise.
61
+
62
+ To change the default you can create a file in `config/initializers/mini_profiler.rb`
53
63
 
54
64
  ```ruby
55
- # our default
65
+ # set MemoryStore
56
66
  Rack::MiniProfiler.config.storage = Rack::MiniProfiler::MemoryStore
57
- ```
58
67
 
59
- There are 2 other available storage engines, `RedisStore`, `MemcacheStore`, and `FileStore`.
68
+ # set RedisStore
69
+ if Rails.env.production?
70
+ uri = URI.parse(ENV["REDIS_SERVER_URL"])
71
+ Rack::MiniProfiler.config.storage_options = { :host => uri.host, :port => uri.port, :password => uri.password }
72
+ Rack::MiniProfiler.config.storage = Rack::MiniProfiler::RedisStore
73
+ end
74
+ ```
60
75
 
61
- MemoryStore is stores results in a processes heap - something that does not work well in a multi process environment.
76
+ MemoryStore stores results in a processes heap - something that does not work well in a multi process environment.
62
77
  FileStore stores results in the file system - something that may not work well in a multi machine environment.
78
+ RedisStore/MemcacheStore work in multi process and multi machine environments (RedisStore only saves results for up to 24 hours so it won't continue to fill up Redis).
63
79
 
64
80
  Additionally you may implement an AbstractStore for your own provider.
65
81
 
66
- Rails hooks up a FileStore for all environments.
82
+ ## User result segregation
83
+
84
+ MiniProfiler will attempt to keep all user results isolated, out-of-the-box the user provider uses the ip address:
85
+
86
+ ```ruby
87
+ Rack::MiniProfiler.config.user_provider = Proc.new{|env| Rack::Request.new(env).ip}
88
+ ```
89
+
90
+ You can override (something that is very important in a multi-machine production setup):
91
+
92
+ ```ruby
93
+ Rack::MiniProfiler.config.user_provider = Proc.new{ |env| CurrentUser.get(env) }
94
+ ```
95
+
96
+ The string this function returns should be unique for each user on the system (for anonymous you may need to fall back to ip address)
67
97
 
68
98
  ## Running the Specs
69
99
 
@@ -81,8 +111,14 @@ You can set configuration options using the configuration accessor on Rack::Mini
81
111
  ```
82
112
  # Have Mini Profiler show up on the right
83
113
  Rack::MiniProfiler.config.position = 'right'
114
+ # Have Mini Profiler start in hidden mode - display with short cut (defaulted to 'Alt+P')
115
+ Rack::MiniProfiler.config.start_hidden = true
116
+ # Don't collect backtraces on SQL queries that take less than 5 ms to execute
117
+ # (necessary on Rubies earlier than 2.0)
118
+ Rack::MiniProfiler.config.backtrace_threshold_ms = 5
84
119
  ```
85
120
 
121
+
86
122
  In a Rails app, this can be done conveniently in an initializer such as config/initializers/mini_profiler.rb.
87
123
 
88
124
  ## Rails 2.X support
@@ -120,11 +156,13 @@ end
120
156
  * pre_authorize_cb - A lambda callback you can set to determine whether or not mini_profiler should be visible on a given request. Default in a Rails environment is only on in development mode. If in a Rack app, the default is always on.
121
157
  * position - Can either be 'right' or 'left'. Default is 'left'.
122
158
  * skip_schema_queries - Whether or not you want to log the queries about the schema of your tables. Default is 'false', 'true' in rails development.
123
- * use_existing_jquery - Use the version of jQuery on the page as opposed to the self contained one
124
159
  * auto_inject (default true) - when false the miniprofiler script is not injected in the page
125
160
  * backtrace_filter - a regex you can use to filter out unwanted lines from the backtraces
161
+ * toggle_shortcut (default Alt+P) - a jquery.hotkeys.js-style keyboard shortcut, used to toggle the mini_profiler's visibility. See http://code.google.com/p/js-hotkeys/ for more info.
162
+ * start_hidden (default false) - Whether or not you want the mini_profiler to be visible when loading a page
163
+ * backtrace_threshold_ms (default zero) - Minimum SQL query elapsed time before a backtrace is recorded. Backtrace recording can take a couple of milliseconds on rubies earlier than 2.0, impacting performance for very small queries.
126
164
 
127
165
  ## Special query strings
128
166
 
129
- If you include the query string `pp=help` at the end of your request you will see the various option you have. You can use these options to extend or contract the amount of diagnostics rack-mini-profiler gathers.
167
+ If you include the query string `pp=help` at the end of your request you will see the various options available. You can use these options to extend or contract the amount of diagnostics rack-mini-profiler gathers.
130
168
 
@@ -0,0 +1,351 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <script src="//cdnjs.cloudflare.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
5
+ <script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.0.8/d3.min.js"></script>
6
+ <!-- <script src="http://cdnjs.cloudflare.com/ajax/libs/sugar/1.3.8/sugar.min.js"></script> -->
7
+ <meta charset=utf-8 />
8
+ <title>Flame Graph of Page</title>
9
+ <style>
10
+ .info {height: 40px;}
11
+ .legend div {
12
+ display: block;
13
+ float: left;
14
+ width: 150px;
15
+ margin: 0 8px 8px;
16
+ padding: 4px;
17
+ height: 50px;
18
+ }
19
+ </style>
20
+ </head>
21
+ <body>
22
+ <div class="graph"></div>
23
+ <div class="info"></div>
24
+ <div class="legend"></div>
25
+
26
+ <script>
27
+
28
+ var data = /*DATA*/;
29
+ var maxX = 0;
30
+ var maxY = 0;
31
+
32
+ debounce = function(func, wait, trickle) {
33
+ var timeout;
34
+
35
+ timeout = null;
36
+ return function() {
37
+ var args, context, currentWait, later;
38
+ context = this;
39
+ args = arguments;
40
+ later = function() {
41
+ timeout = null;
42
+ return func.apply(context, args);
43
+ };
44
+
45
+ if (timeout && trickle) {
46
+ // already queued, let it through
47
+ return;
48
+ }
49
+
50
+ if (typeof wait === "function") {
51
+ currentWait = wait();
52
+ } else {
53
+ currentWait = wait;
54
+ }
55
+
56
+ if (timeout) {
57
+ clearTimeout(timeout);
58
+ }
59
+
60
+ timeout = setTimeout(later, currentWait);
61
+ return timeout;
62
+ };
63
+ };
64
+
65
+
66
+ var guessGem = function(frame)
67
+ {
68
+ var split = frame.split('/gems/');
69
+ if(split.length == 1) {
70
+ split = frame.split('/app/');
71
+ if(split.length == 1) {
72
+ split = frame.split('/lib/');
73
+ }
74
+
75
+ split = split[Math.max(split.length-2,0)].split('/');
76
+ return split[split.length-1];
77
+ }
78
+ else
79
+ {
80
+ return split[split.length -1].split('/')[0];
81
+ }
82
+ }
83
+
84
+ var guessMethod = function(frame) {
85
+ var split = frame.split('`');
86
+ if(split.length == 2) {
87
+ return split[1].split("'")[0];
88
+ }
89
+ return '?';
90
+ }
91
+
92
+ var guessFile = function(frame) {
93
+ var split = frame.split(".rb:");
94
+ if(split.length == 2) {
95
+ split = split[0].split('/');
96
+ return split[split.length - 1];
97
+ }
98
+ return "";
99
+ }
100
+
101
+ $.each(data, function(){
102
+ maxX = Math.max(maxX, this.x + this.width);
103
+ maxY = Math.max(maxY, this.y);
104
+ this.shortName = /* guessGem(this.frame) + " " + guessFile(this.frame) + " " */ guessMethod(this.frame);
105
+ });
106
+
107
+ var width = $(window).width();
108
+ var height = $(window).height() / 1.2;
109
+
110
+ $('.graph').width(width).height(height);
111
+
112
+ var xScale = d3.scale.linear()
113
+ .domain([0, maxX])
114
+ .range([0, width]);
115
+
116
+ var yScale = d3.scale.linear()
117
+ .domain([0, maxY])
118
+ .range([0,height]);
119
+
120
+ var realHeight = 0;
121
+ var debouncedHeightCheck = debounce(function(){
122
+ if (realHeight > 15) {
123
+ svg.selectAll('text').attr('display','show');
124
+ } else {
125
+ svg.selectAll('text').attr('display','none');
126
+ }
127
+ }, 200);
128
+
129
+ function zoom()
130
+ {
131
+ svg.attr("transform", "translate(" + d3.event.translate + ")" + " scale(" + d3.event.scale + ")");
132
+
133
+ realHeight = yScale(1) * d3.event.scale;
134
+ debouncedHeightCheck();
135
+ }
136
+
137
+ var svg = d3.select(".graph")
138
+ .append("svg")
139
+ .attr("width", "100%")
140
+ .attr("height", "100%")
141
+ .attr("pointer-events", "all")
142
+ .append('svg:g')
143
+ .call(d3.behavior.zoom().on("zoom", zoom))
144
+ .append('svg:g');
145
+
146
+
147
+ // so zoom works everywhere
148
+ svg.append("rect")
149
+ .attr("x",function(d) { return xScale(0); })
150
+ .attr("y",function(d) { return yScale(0);})
151
+ .attr("width", function(d){return xScale(maxX);})
152
+ .attr("height", yScale(maxY))
153
+ .attr("fill", "white");
154
+
155
+ var color = function() {
156
+ var r = parseInt(205 + Math.random() * 50);
157
+ var g = parseInt(Math.random() * 230);
158
+ var b = parseInt(Math.random() * 55);
159
+ return "rgb(" + r + "," + g + "," + b + ")";
160
+ }
161
+ var info = {};
162
+
163
+ // http://stackoverflow.com/questions/1960473/unique-values-in-an-array
164
+ Array.prototype.getUnique = function() {
165
+ var o = {}, a = []
166
+ for (var i = 0; i < this.length; i++) o[this[i]] = 1
167
+ for (var e in o) a.push(e)
168
+ return a
169
+ }
170
+
171
+ var samplePercent = function(samples, exclusive){
172
+ var info = " (" + samples +
173
+ " sample" + (samples == 1 ? "" : "s") + " - " +
174
+ ((samples / maxX) * 100).toFixed(2) + "%) ";
175
+ if (exclusive) {
176
+ info += " (" + exclusive +
177
+ " exclusive - " +
178
+ ((exclusive / maxX) * 100).toFixed(2) + "%) ";
179
+ }
180
+ return info;
181
+ }
182
+
183
+ var mouseover = function(d) {
184
+ var i = info[d.frame];
185
+ $('.info').text( d.frame + " " + samplePercent(i.samples.length, d.topFrame ? d.topFrame.exclusiveCount : 0));
186
+ d3.selectAll(i.nodes)
187
+ .attr('opacity',0.5);
188
+ };
189
+
190
+ var mouseout = function(d) {
191
+ var i = info[d.frame];
192
+ $('.info').text("");
193
+ d3.selectAll(i.nodes)
194
+ .attr('opacity',1);
195
+ };
196
+
197
+ // http://stackoverflow.com/a/7419630
198
+ var rainbow = function(numOfSteps, step) {
199
+ // This function generates vibrant, "evenly spaced" colours (i.e. no clustering). This is ideal for creating easily distiguishable vibrant markers in Google Maps and other apps.
200
+ // Adam Cole, 2011-Sept-14
201
+ // HSV to RBG adapted from: http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript
202
+ var r, g, b;
203
+ var h = step / numOfSteps;
204
+ var i = ~~(h * 6);
205
+ var f = h * 6 - i;
206
+ var q = 1 - f;
207
+ switch(i % 6){
208
+ case 0: r = 1, g = f, b = 0; break;
209
+ case 1: r = q, g = 1, b = 0; break;
210
+ case 2: r = 0, g = 1, b = f; break;
211
+ case 3: r = 0, g = q, b = 1; break;
212
+ case 4: r = f, g = 0, b = 1; break;
213
+ case 5: r = 1, g = 0, b = q; break;
214
+ }
215
+ var c = "#" + ("00" + (~ ~(r * 255)).toString(16)).slice(-2) + ("00" + (~ ~(g * 255)).toString(16)).slice(-2) + ("00" + (~ ~(b * 255)).toString(16)).slice(-2);
216
+ return (c);
217
+ }
218
+
219
+ // assign some colors, analyze samples per gem
220
+ var gemStats = {}
221
+ var topFrames = {}
222
+ var lastFrame = {frame: 'd52e04d-df28-41ed-a215-b6ec840a8ea5', x: -1}
223
+
224
+ $.each(data, function(){
225
+
226
+ var gem = guessGem(this.frame);
227
+ var stat = gemStats[gem];
228
+
229
+ if(!stat) {
230
+ gemStats[gem] = stat = {samples: [], frames: []};
231
+ }
232
+
233
+ stat.frames.push(this.frame);
234
+ for(var j=0; j < this.width; j++){
235
+ stat.samples.push(this.x + j);
236
+ }
237
+ // This assumes the traversal is in order
238
+ if (lastFrame.x != this.x) {
239
+ var topFrame = topFrames[lastFrame.frame]
240
+ if (!topFrame) {
241
+ topFrames[lastFrame.frame] = topFrame = {exclusiveCount: 0}
242
+ }
243
+ topFrame.exclusiveCount += 1;
244
+ lastFrame.topFrame = topFrame;
245
+ }
246
+ lastFrame = this;
247
+
248
+ });
249
+
250
+ var topFrame = topFrames[lastFrame.frame]
251
+ if (!topFrame) {
252
+ topFrames[lastFrame.frame] = topFrame = {exclusiveCount: 0}
253
+ }
254
+ topFrame.exclusiveCount += 1;
255
+ lastFrame.topFrame = topFrame;
256
+
257
+ var totalGems = 0;
258
+ $.each(gemStats, function(){totalGems++;});
259
+
260
+
261
+ var currentIndex = 0;
262
+ $.each(gemStats, function(k,stat){
263
+
264
+ stat.color = rainbow(totalGems, currentIndex);
265
+ stat.samples = stat.samples.getUnique();
266
+
267
+ for(var x=0; x < stat.frames.length; x++) {
268
+ info[stat.frames[x]] = {nodes: [], samples: [], color: stat.color};
269
+ }
270
+
271
+ currentIndex += 1;
272
+ });
273
+
274
+
275
+ // see: http://bl.ocks.org/mundhradevang/1387786
276
+ function fontSize(d,i) {
277
+ var size = yScale(1) / 3;
278
+ // var words = d.shortName.split(' ');
279
+ var word = d.shortName; // words[0];
280
+ var width = xScale(d.width+100);
281
+ var height = yScale(1);
282
+ var length = 0;
283
+ d3.select(this).style("font-size", size + "px").text(word);
284
+ while(((this.getBBox().width >= width) || (this.getBBox().height >= height)) && (size > 12))
285
+ {
286
+ size -= 0.1;
287
+ d3.select(this).style("font-size", size + "px");
288
+ }
289
+
290
+ d3.select(this).attr("dy", size);
291
+ }
292
+
293
+ svg.selectAll("g")
294
+ .data(data)
295
+ .enter()
296
+ .append("g")
297
+ .each(function(){
298
+ d3.select(this)
299
+ .append("rect")
300
+ .attr("x",function(d) { return xScale(d.x-1); })
301
+ .attr("y",function(d) { return yScale(maxY - d.y);})
302
+ .attr("width", function(d){return xScale(d.width);})
303
+ .attr("height", yScale(1))
304
+ .attr("fill", function(d){
305
+ var i = info[d.frame];
306
+ if(!i) {
307
+ info[d.frame] = i = {nodes: [], samples: [], color: color()};
308
+ }
309
+ i.nodes.push(this);
310
+ for(var j=0; j < d.width; j++){
311
+ i.samples.push(d.x + j);
312
+ }
313
+ return i.color;
314
+ })
315
+ .on("mouseover", mouseover)
316
+ .on("mouseout", mouseout);
317
+
318
+ d3.select(this)
319
+ .append("text")
320
+ .attr("x",function(d) { return xScale(d.x - 0.98); })
321
+ .attr("y",function(d) { return yScale(maxY - d.y);})
322
+ .on("mouseover", mouseover)
323
+ .on("mouseout", mouseout)
324
+ .each(fontSize)
325
+ .attr("display", "none");
326
+
327
+ });
328
+
329
+
330
+ // Samples may overlap on the same line
331
+ for (var r in info) {
332
+ if (info[r].samples) {
333
+ info[r].samples = info[r].samples.getUnique();
334
+ }
335
+ };
336
+
337
+
338
+ // render the legend
339
+ $.each(gemStats, function(k,v){
340
+ var node = $("<div></div>")
341
+ .css("background-color", v.color)
342
+ .text(k + " " + samplePercent(v.samples.length)) ;
343
+ $('.legend').append(node);
344
+ });
345
+
346
+
347
+
348
+ </script>
349
+ </body>
350
+ </html>
351
+