bleak_house 3.7.1 → 4.0

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