bleak_house 3.7.1 → 4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (87) hide show
  1. data.tar.gz.sig +0 -0
  2. data/CHANGELOG +2 -0
  3. data/Manifest +12 -66
  4. data/README +57 -20
  5. data/TODO +2 -2
  6. data/bin/bleak +1 -14
  7. data/bleak_house.gemspec +10 -16
  8. data/ext/{bleak_house/logger/build_ruby.rb → build_ruby.rb} +17 -7
  9. data/ext/build_snapshot.rb +4 -0
  10. data/ext/{bleak_house/logger/extconf.rb → extconf.rb} +1 -1
  11. data/ext/snapshot.c +157 -0
  12. data/ext/{bleak_house/logger/snapshot.h → snapshot.h} +2 -45
  13. data/lib/bleak_house.rb +5 -5
  14. data/lib/bleak_house/analyzer.rb +24 -2
  15. data/lib/bleak_house/hook.rb +25 -0
  16. data/ruby/bleak_house.patch +358 -0
  17. data/ruby/configure.patch +242 -0
  18. data/ruby/gc.patch +116 -0
  19. data/ruby/ruby-1.8.6-p114.tar.bz2 +0 -0
  20. data/ruby/valgrind.patch +136 -0
  21. data/test/benchmark/bench.rb +16 -0
  22. data/test/unit/test_bleak_house.rb +26 -27
  23. metadata +22 -86
  24. metadata.gz.sig +0 -0
  25. data/ext/bleak_house/logger/build_logger.rb +0 -3
  26. data/ext/bleak_house/logger/snapshot.c +0 -204
  27. data/init.rb +0 -3
  28. data/install.rb +0 -2
  29. data/lib/bleak_house/analyzer/analyzer.rb +0 -358
  30. data/lib/bleak_house/logger.rb +0 -2
  31. data/lib/bleak_house/logger/source.rb +0 -21
  32. data/lib/bleak_house/rails.rb +0 -6
  33. data/lib/bleak_house/rails/action_controller.rb +0 -17
  34. data/lib/bleak_house/rails/bleak_house.rb +0 -62
  35. data/lib/bleak_house/rails/dispatcher.rb +0 -36
  36. data/lib/bleak_house/support/core_extensions.rb +0 -61
  37. data/ruby/gc.c.patch +0 -27
  38. data/ruby/parse.y.patch +0 -16
  39. data/ruby/ruby-1.8.6-p110.tar.bz2 +0 -0
  40. data/test/integration/app/README +0 -203
  41. data/test/integration/app/Rakefile +0 -10
  42. data/test/integration/app/app/controllers/application.rb +0 -12
  43. data/test/integration/app/app/controllers/items_controller.rb +0 -6
  44. data/test/integration/app/app/helpers/application_helper.rb +0 -3
  45. data/test/integration/app/app/helpers/items_helper.rb +0 -2
  46. data/test/integration/app/app/views/items/index.rhtml +0 -2
  47. data/test/integration/app/config/boot.rb +0 -109
  48. data/test/integration/app/config/database.yml +0 -19
  49. data/test/integration/app/config/environment.rb +0 -15
  50. data/test/integration/app/config/environments/development.rb +0 -18
  51. data/test/integration/app/config/environments/production.rb +0 -19
  52. data/test/integration/app/config/environments/test.rb +0 -22
  53. data/test/integration/app/config/initializers/inflections.rb +0 -10
  54. data/test/integration/app/config/initializers/mime_types.rb +0 -5
  55. data/test/integration/app/config/routes.rb +0 -35
  56. data/test/integration/app/doc/README_FOR_APP +0 -2
  57. data/test/integration/app/public/404.html +0 -30
  58. data/test/integration/app/public/422.html +0 -30
  59. data/test/integration/app/public/500.html +0 -30
  60. data/test/integration/app/public/dispatch.cgi +0 -10
  61. data/test/integration/app/public/dispatch.fcgi +0 -24
  62. data/test/integration/app/public/dispatch.rb +0 -10
  63. data/test/integration/app/public/favicon.ico +0 -0
  64. data/test/integration/app/public/images/rails.png +0 -0
  65. data/test/integration/app/public/javascripts/application.js +0 -2
  66. data/test/integration/app/public/javascripts/controls.js +0 -963
  67. data/test/integration/app/public/javascripts/dragdrop.js +0 -972
  68. data/test/integration/app/public/javascripts/effects.js +0 -1120
  69. data/test/integration/app/public/javascripts/prototype.js +0 -4225
  70. data/test/integration/app/public/robots.txt +0 -5
  71. data/test/integration/app/script/about +0 -3
  72. data/test/integration/app/script/console +0 -3
  73. data/test/integration/app/script/destroy +0 -3
  74. data/test/integration/app/script/generate +0 -3
  75. data/test/integration/app/script/performance/benchmarker +0 -3
  76. data/test/integration/app/script/performance/profiler +0 -3
  77. data/test/integration/app/script/performance/request +0 -3
  78. data/test/integration/app/script/plugin +0 -3
  79. data/test/integration/app/script/process/inspector +0 -3
  80. data/test/integration/app/script/process/reaper +0 -3
  81. data/test/integration/app/script/process/spawner +0 -3
  82. data/test/integration/app/script/runner +0 -3
  83. data/test/integration/app/script/server +0 -3
  84. data/test/integration/app/test/functional/items_controller_test.rb +0 -8
  85. data/test/integration/app/test/test_helper.rb +0 -38
  86. data/test/integration/server_test.rb +0 -93
  87. data/test/misc/direct.rb +0 -13
data/init.rb DELETED
@@ -1,3 +0,0 @@
1
-
2
- puts "Please use the gem version of BleakHouse from now on."
3
- exit!
data/install.rb DELETED
@@ -1,2 +0,0 @@
1
-
2
- puts "Please use the gem version of BleakHouse from now on."
@@ -1,358 +0,0 @@
1
-
2
- require 'thread'
3
- require 'ccsv'
4
- # require 'memory'
5
- require 'fileutils'
6
- require 'yaml'
7
- require 'pp'
8
-
9
- module BleakHouse
10
-
11
- class Analyzer
12
-
13
- SPECIALS = {
14
- -1 => :timestamp,
15
- -2 => :'mem usage/swap', # Not used
16
- -3 => :'mem usage/real', # Not used
17
- -4 => :tag,
18
- -5 => :'heap/filled',
19
- -6 => :'heap/free'
20
- }
21
-
22
- # Might be better as a per-tag skip but that gets kinda complicated
23
- initial_skip = (ENV['INITIAL_SKIP'] || 15).to_i
24
- INITIAL_SKIP = initial_skip < 2 ? 2 : initial_skip
25
-
26
- DISPLAY_SAMPLES = (ENV['DISPLAY_SAMPLES'] || 5).to_i
27
-
28
- class_key_source = File.dirname(__FILE__) + '/../../../ext/bleak_house/logger/snapshot.h'
29
- class_key_string = open(class_key_source).read[/\{(.*?)\}/m, 1]
30
- # Skip 0 so that the output of String#to_s is useful
31
- CLASS_KEYS = eval("[nil, #{class_key_string} ]").map do |class_name|
32
- class_name.to_sym if class_name
33
- end
34
-
35
- class << self
36
-
37
- def reverse_detect(array)
38
- i = array.size - 1
39
- while i >= 0
40
- item = array[i]
41
- return item if yield(item)
42
- i -= 1
43
- end
44
- end
45
-
46
- def calculate!(frame, index, total, population = nil)
47
- bsize = frame[:births].size
48
- dsize = frame[:deaths].size
49
-
50
- # Avoid divide by zero errors
51
- frame[:meta][:ratio] = ratio = (bsize - dsize) / (bsize + dsize + 1).to_f
52
- frame[:meta][:impact] = begin
53
- result = Math.log10((bsize - dsize).abs.to_i / 10.0)
54
- raise Errno::ERANGE if result.nan? or result.infinite?
55
- result
56
- rescue Errno::ERANGE
57
- 0
58
- end
59
-
60
- puts " F#{index}:#{total} (#{index * 100 / total}%): #{frame[:meta][:tag]} (#{population.to_s + ' population, ' if population}#{bsize} births, #{dsize} deaths, ratio #{format('%.2f', frame[:meta][:ratio])}, impact #{format('%.2f', frame[:meta][:impact])})"
61
- end
62
-
63
- # Read a frames object from a cache file.
64
- def read_cache(cachefile)
65
- frames = Marshal.load(File.open(cachefile).read)[0..-2]
66
- total_frames = frames.size - 1
67
- announce_total(total_frames)
68
-
69
- frames[0..-2].each_with_index do |frame, index|
70
- calculate!(frame, index + 1, total_frames)
71
- end
72
- frames
73
- end
74
-
75
- def announce_total(total_frames)
76
- puts "#{total_frames} frames"
77
-
78
- if total_frames < INITIAL_SKIP * 3
79
- puts "Not enough frames for accurate results. Please record at least #{INITIAL_SKIP * 3} frames."
80
- exit # Should be exit! but that messes up backticks capturing in the tests
81
- end
82
- end
83
-
84
- # Cache an object to disk.
85
- def write_cache(object, cachefile)
86
- Thread.exclusive do
87
- File.open(cachefile, 'w') do |f|
88
- f.write Marshal.dump(object)
89
- end
90
- end
91
- end
92
-
93
- # Rebuild frames
94
- def read(logfile, cachefile)
95
- total_frames = `grep '^-1' #{logfile} | wc`.to_i - 2
96
- announce_total(total_frames)
97
-
98
- frames = loop(logfile, cachefile, total_frames)
99
- end
100
-
101
- # Convert the class and id columns to Fixnums, if possible, and remove memory
102
- # addresses from inspection samples.
103
- def normalize_row(row)
104
-
105
- # Broken out for speed (we don't want to generate a closure)
106
- if (int = row[0].to_i) != 0
107
- row[0] = int
108
- else
109
- row[0] = row[0] ? row[0].to_sym : 0
110
- end
111
-
112
- if (int = row[1].to_i) != 0
113
- row[1] = int
114
- else
115
- row[1] = row[1] ? row[1].to_sym : 0
116
- end
117
-
118
- if row[2]
119
- row[2] = row[2].gsub(/0x[\da-f]{8,16}/, "0xID").to_sym
120
- end
121
- row
122
- end
123
-
124
- # Inner loop of the raw log reader. The implementation is kind of a mess.
125
- def loop(logfile, cachefile, total_frames)
126
-
127
- frames = []
128
- last_population = []
129
- frame = nil
130
-
131
- Ccsv.foreach(logfile) do |row|
132
-
133
- class_index, id_or_tag, sampled_content = normalize_row(row)
134
-
135
- # Check for frame headers
136
- if class_index < 0
137
-
138
- # Get frame meta-information
139
- if SPECIALS[class_index] == :timestamp
140
-
141
- # The frame has ended; process the last one
142
- if frame
143
- population = frame[:objects].keys
144
- births = population - last_population
145
- deaths = last_population - population
146
- last_population = population
147
-
148
- # Assign births
149
- frame[:births] = [] # Uses an Array to work around a Marshal bug
150
- births.each do |key|
151
- frame[:births] << [key, frame[:objects][key]]
152
- end
153
-
154
- # Assign deaths to previous frame
155
- final = frames[-2]
156
- if final
157
-
158
- final[:deaths] = [] # Uses an Array to work around a Marshal bug
159
- deaths.each do |key|
160
- next unless final[:objects][key]
161
- final[:deaths] << [key, [final[:objects][key].first]] # Don't need the sample content for deaths
162
- end
163
-
164
- # Try to reduce memory footprint
165
- final.delete :objects
166
- GC.start
167
- sleep 1 # Give the GC thread a chance to do something
168
-
169
- calculate!(final, frames.size - 1, total_frames, population.size)
170
- end
171
- end
172
-
173
- # Set up a new frame
174
- frame = {}
175
- frames << frame
176
- frame[:objects] ||= {}
177
- frame[:meta] ||= {}
178
-
179
- # Write out an in-process cache, in case you run out of RAM
180
- if frames.size % 20 == 0
181
- write_cache(frames, cachefile)
182
- end
183
- end
184
-
185
- frame[:meta][SPECIALS[class_index]] = id_or_tag
186
- else
187
- # XXX Critical section
188
- if sampled_content
189
- # Normally object address strings and convert to a symbol
190
- frame[:objects][id_or_tag] = [class_index, sampled_content]
191
- else
192
- frame[:objects][id_or_tag] = [class_index]
193
- end
194
- end
195
-
196
- end
197
-
198
- # Work around for a Marshal/Hash bug on x86_64
199
- frames[-2][:objects] = frames[-2][:objects].to_a
200
-
201
- # Write the cache
202
- write_cache(frames, cachefile)
203
-
204
- # Junk last frame (read_cache also does this)
205
- frames[0..-2]
206
- end
207
-
208
- # Convert births back to hashes, necessary due to the Marshal workaround
209
- def rehash(frames)
210
- frames.each do |frame|
211
- frame[:births_hash] = {}
212
- frame[:births].each do |key, value|
213
- frame[:births_hash][key] = value
214
- end
215
- frame.delete(:births)
216
- end
217
- nil
218
- end
219
-
220
- # Parses and correlates a BleakHouse::Logger output file.
221
- def run(logfile)
222
- logfile.chomp!(".cache")
223
- cachefile = logfile + ".cache"
224
-
225
- unless File.exists? logfile or File.exists? cachefile
226
- puts "No data file found: #{logfile}"
227
- exit
228
- end
229
-
230
- puts "Working..."
231
-
232
- frames = []
233
-
234
- if File.exist?(cachefile) and (!File.exists? logfile or File.stat(cachefile).mtime > File.stat(logfile).mtime)
235
- puts "Using cache"
236
- frames = read_cache(cachefile)
237
- else
238
- frames = read(logfile, cachefile)
239
- end
240
-
241
- puts "\nRehashing."
242
-
243
- rehash(frames)
244
-
245
- # See what objects are still laying around
246
- population = frames.last[:objects].reject do |key, value|
247
- frames.first[:births_hash][key] and frames.first[:births_hash][key].first == value.first
248
- end
249
-
250
- puts "\n#{frames.size - 1} full frames. Removing #{INITIAL_SKIP} frames from each end of the run to account for\nstartup overhead and GC lag."
251
-
252
- # Remove border frames
253
- frames = frames[INITIAL_SKIP..-INITIAL_SKIP]
254
-
255
- # Sum all births
256
- total_births = frames.inject(0) do |births, frame|
257
- births + frame[:births_hash].size
258
- end
259
-
260
- # Sum all deaths
261
- total_deaths = frames.inject(0) do |deaths, frame|
262
- deaths + frame[:deaths].size
263
- end
264
-
265
- puts "\n#{total_births} total births, #{total_deaths} total deaths, #{population.size} uncollected objects."
266
-
267
- leakers = {}
268
-
269
- # Find the sources of the leftover objects in the final population
270
- population.each do |id, value|
271
- klass = value[0]
272
- content = value[1]
273
- leaker = reverse_detect(frames) do |frame|
274
- frame[:births_hash][id] and frame[:births_hash][id].first == klass
275
- end
276
- if leaker
277
- tag = leaker[:meta][:tag]
278
- klass = CLASS_KEYS[klass] if klass.is_a? Fixnum
279
- leakers[tag] ||= Hash.new()
280
- leakers[tag][klass] ||= {:count => 0, :contents => []}
281
- leakers[tag][klass][:count] += 1
282
- leakers[tag][klass][:contents] << content if content
283
- end
284
- end
285
-
286
- # Sort the leakers
287
- leakers = leakers.map do |tag, value|
288
- # Sort leakiest classes within each tag
289
- [tag, value.sort_by do |klass, hash|
290
- -hash[:count]
291
- end]
292
- end.sort_by do |tag, value|
293
- # Sort leakiest tags as a whole
294
- Hash[*value.flatten].values.inject(0) {|i, hash| i - hash[:count]}
295
- end
296
-
297
- if leakers.any?
298
- puts "\nTags sorted by persistent uncollected objects. These objects did not exist at\nstartup, were instantiated by the associated tags during the run, and were\nnever garbage collected:"
299
- leakers.each do |tag, value|
300
- requests = frames.select do |frame|
301
- frame[:meta][:tag] == tag
302
- end.size
303
- puts " #{tag} leaked per request (#{requests}):"
304
- value.each do |klass, hash|
305
- puts " #{sprintf('%.1f', hash[:count] / requests.to_f)} #{klass}"
306
-
307
- # Extract most common samples
308
- contents = begin
309
- hist = Hash.new(0)
310
- hash[:contents].each do |content|
311
- hist[content] += 1
312
- end
313
- hist.sort_by do |content, count|
314
- -count
315
- end[0..DISPLAY_SAMPLES].select do |content, count|
316
- ENV['DISPLAY_SAMPLES'] or count > 5
317
- end
318
- end
319
-
320
- if contents.any?
321
- puts " Inspection samples:"
322
- contents.each do |content, count|
323
- puts " #{sprintf('%.1f', count / requests.to_f)} #{content}"
324
- end
325
- end
326
-
327
- end
328
- end
329
- else
330
- puts "\nNo persistent uncollected objects found for any tags."
331
- end
332
-
333
- impacts = {}
334
-
335
- frames.each do |frame|
336
- impacts[frame[:meta][:tag]] ||= []
337
- impacts[frame[:meta][:tag]] << frame[:meta][:impact] * frame[:meta][:ratio]
338
- end
339
- impacts = impacts.map do |tag, values|
340
- [tag, values.inject(0) {|acc, i| acc + i} / values.size.to_f]
341
- end.sort_by do |tag, impact|
342
- impact.nan? ? 0 : -impact
343
- end
344
-
345
- puts "\nTags sorted by average impact * ratio. Impact is the log10 of the size of the"
346
- puts "change in object count for a frame:"
347
-
348
- impacts.each do |tag, total|
349
- puts " #{format('%.4f', total).rjust(7)}: #{tag}"
350
- end
351
-
352
- puts "\nDone"
353
-
354
- end
355
-
356
- end
357
- end
358
- end
@@ -1,2 +0,0 @@
1
-
2
- require 'bleak_house/logger/snapshot'
@@ -1,21 +0,0 @@
1
-
2
- #:stopdoc:
3
-
4
- # Doesn't work
5
-
6
- module BleakHouse
7
- module Source
8
- def allocate(*args)
9
- super
10
- puts "#{self.class},#{self.object_id},#{caller.inspect},#{self.inspect}"
11
- end
12
- end
13
- end
14
-
15
- class String
16
- include BleakHouse::Source
17
- end
18
-
19
- String.new
20
-
21
- #:startdoc:
@@ -1,6 +0,0 @@
1
-
2
- require 'dispatcher'
3
-
4
- require 'bleak_house/rails/action_controller.rb'
5
- require 'bleak_house/rails/bleak_house.rb'
6
- require 'bleak_house/rails/dispatcher.rb'
@@ -1,17 +0,0 @@
1
-
2
- # Override ActionController::Base.process and process_with_exception to make sure the request tag for the snapshot gets set as a side-effect of request processing.
3
- class ActionController::Base
4
- class << self
5
- def process_with_bleak_house(request, *args)
6
- BleakHouse::Rails.set_request_name request
7
- process_without_bleak_house(request, *args)
8
- end
9
- alias_method_chain :process, :bleak_house
10
-
11
- def process_with_exception_with_bleak_house(request, *args)
12
- BleakHouse::Rails.set_request_name request, "/error"
13
- process_with_exception_without_bleak_house(request, *args)
14
- end
15
- alias_method_chain :process_with_exception, :bleak_house
16
- end
17
- end
@@ -1,62 +0,0 @@
1
-
2
- module BleakHouse
3
- module Rails
4
- @@last_request_name = nil
5
-
6
- class << self
7
-
8
- def last_request_name
9
- @@last_request_name
10
- end
11
-
12
- def last_request_name=(obj)
13
- @@last_request_name = obj
14
- end
15
-
16
- # Avoid making four more strings on each request.
17
- CONTROLLER_KEY = 'controller'
18
- ACTION_KEY = 'action'
19
- GSUB_SEARCH = '/'
20
- GSUB_REPLACEMENT = '__'
21
-
22
- # Sets the request name on the BleakHouse object to match this Rails request. Called from <tt>ActionController::Base.process</tt>. Assign to <tt>last_request_name</tt> yourself if you are not using BleakHouse within Rails.
23
- def set_request_name(request, other = nil)
24
- self.last_request_name = "#{
25
- request.parameters[CONTROLLER_KEY].gsub(GSUB_SEARCH, GSUB_REPLACEMENT) # mangle namespaced controller names
26
- }/#{
27
- request.parameters[ACTION_KEY]
28
- }/#{
29
- request.request_method
30
- }#{
31
- other
32
- }"
33
- end
34
-
35
- def debug(s) #:nodoc:
36
- s = "** bleak_house: #{s}"
37
- RAILS_DEFAULT_LOGGER.debug s if RAILS_DEFAULT_LOGGER
38
- end
39
-
40
- def warn(s) #:nodoc:
41
- s = "** bleak_house: #{s}"
42
- if RAILS_DEFAULT_LOGGER
43
- RAILS_DEFAULT_LOGGER.warn s
44
- else
45
- $stderr.puts s
46
- end
47
- end
48
- end
49
-
50
- LOGFILE = "#{RAILS_ROOT}/log/bleak_house_#{RAILS_ENV}.dump"
51
- if File.exists?(LOGFILE)
52
- File.rename(LOGFILE, "#{LOGFILE}.old")
53
- warn "renamed old logfile"
54
- end
55
-
56
- WITH_SPECIALS = false
57
-
58
- SAMPLE_RATE = ENV['SAMPLE_RATE'].to_f
59
-
60
- MEMLOGGER = Logger.new
61
- end
62
- end