bleak_house 3 → 3.0.1

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.
@@ -0,0 +1,143 @@
1
+
2
+ require 'rubygems'
3
+ require 'fileutils'
4
+ require 'yaml'
5
+ require 'pp'
6
+ require 'ruby-debug'
7
+
8
+ module BleakHouse
9
+
10
+ class Analyzer
11
+
12
+ MAGIC_KEYS = {
13
+ -1 => 'timestamp',
14
+ -2 => 'mem usage/swap',
15
+ -3 => 'mem usage/real',
16
+ -4 => 'tag',
17
+ -5 => 'heap/filled',
18
+ -6 => 'heap/free'
19
+ }
20
+
21
+ CLASS_KEYS = eval('[nil, ' + # skip 0
22
+ open(
23
+ File.dirname(__FILE__) + '/../../../ext/bleak_house/logger/snapshot.h'
24
+ ).read[/\{(.*?)\}/m, 1] + ']')
25
+
26
+ # Parses and correlates a BleakHouse::Logger output file.
27
+ def self.run(logfile)
28
+ unless File.exists? logfile
29
+ puts "No data file found: #{logfile}"
30
+ exit
31
+ end
32
+
33
+ frames = []
34
+ last_population = []
35
+ frame = nil
36
+ ix = nil
37
+
38
+ puts "Examining objects"
39
+
40
+ LightCsv.foreach(logfile) do |row|
41
+
42
+ # Stupid is fast
43
+ row[0] = row[0].to_i if row[0].to_i != 0
44
+ row[1] = row[1].to_i if row[1].to_i != 0
45
+
46
+ if row[0].to_i < 0
47
+ # Get frame meta-information
48
+ if MAGIC_KEYS[row[0]] == 'timestamp'
49
+
50
+ # The frame has ended; process the last one
51
+ if frame
52
+ population = frame['objects'].keys
53
+ births = population - last_population
54
+ deaths = last_population - population
55
+ last_population = population
56
+
57
+ # assign births
58
+ frame['births'] = frame['objects'].slice(births)
59
+
60
+ if final = frames[-2]
61
+ final['deaths'] = final['objects'].slice(deaths)
62
+ bsize = final['births'].size
63
+ dsize = final['deaths'].size
64
+ final['velocity'] = bsize * 100 / dsize / 100.0
65
+ puts " Frame #{frames.size - 1} (#{final['meta']['tag]}) finalized: #{bsize} births, #{dsize} deaths, velocity #{final['velocity']}, population #{final['objects'].size}"
66
+ final.delete 'objects'
67
+ end
68
+ end
69
+
70
+ # Set up a new frame
71
+ frame = {}
72
+ frames << frame
73
+ frame['objects'] ||= {}
74
+ frame['meta'] ||= {}
75
+
76
+ #puts " Frame #{frames.size} opened"
77
+ end
78
+
79
+ frame['meta'][MAGIC_KEYS[row[0]]] = row[1]
80
+ else
81
+ # Assign live objects
82
+ frame['objects'][row[1]] = row[0]
83
+ end
84
+ end
85
+
86
+ # See what objects are still laying around
87
+ population = frames.last['objects'].reject do |key, value|
88
+ frames.first['births'][key] == value
89
+ end
90
+
91
+ # Remove bogus frames
92
+ frames = frames[1..-3]
93
+
94
+ total_births = frames.inject(0) do |births, frame|
95
+ births + frame['births'].size
96
+ end
97
+ total_deaths = frames.inject(0) do |deaths, frame|
98
+ deaths + frame['deaths'].size
99
+ end
100
+
101
+ puts "#{total_births} total meaningful births, #{total_deaths} total meaningful deaths.\n\n"
102
+
103
+ leakers = {}
104
+
105
+ # Find the sources of the leftover objects in the final population
106
+ population.each do |id, klass|
107
+ leaker = frames.detect do |frame|
108
+ frame['births'][id] == klass
109
+ end
110
+ if leaker
111
+ tag = leaker['meta']['tag']
112
+ klass = CLASS_KEYS[klass] if klass.is_a? Fixnum
113
+ leakers[tag] ||= Hash.new(0)
114
+ leakers[tag][klass] += 1
115
+ end
116
+ end
117
+
118
+ # Sort
119
+ leakers = leakers.map do |tag, value|
120
+ [tag, value.sort_by do |klass, count|
121
+ -count
122
+ end]
123
+ end.sort_by do |tag, value|
124
+ Hash[*value.flatten].values.inject(0) {|i, v| i - v}
125
+ end
126
+
127
+ puts "Here are your leaks:"
128
+ leakers.each do |tag, value|
129
+ puts " #{tag} leaked per request:"
130
+ requests = frames.select do |frame|
131
+ frame['meta']['tag'] == tag
132
+ end.size
133
+ value.each do |klass, count|
134
+ count = count/requests
135
+ puts " #{count} #{klass}" if count > 0
136
+ end
137
+ end
138
+ puts "\nBye"
139
+
140
+ end
141
+
142
+ end
143
+ end
@@ -0,0 +1,5 @@
1
+
2
+ require 'vendor/lightcsv'
3
+
4
+ require 'bleak_house/support/core_extensions'
5
+ require 'bleak_house/analyzer/analyzer'
@@ -0,0 +1,13 @@
1
+
2
+ module BleakHouse
3
+
4
+ class Logger
5
+
6
+ # Returns an array of the running process's real and virtual memory usage, in kilobytes.
7
+ def mem_usage
8
+ a = `ps -o vsz,rss -p #{Process.pid}`.split(/\s+/)[-2..-1].map{|el| el.to_i}
9
+ [a.first - a.last, a.last]
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,3 @@
1
+
2
+ require 'bleak_house/logger/snapshot'
3
+ require 'bleak_house/logger/mem_usage'
@@ -1,14 +1,15 @@
1
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.
2
3
  class ActionController::Base
3
4
  class << self
4
5
  def process_with_bleak_house(request, *args)
5
- BleakHouse.set_request_name request
6
+ BleakHouse::Rails.set_request_name request
6
7
  process_without_bleak_house(request, *args)
7
8
  end
8
9
  alias_method_chain :process, :bleak_house
9
10
 
10
11
  def process_with_exception_with_bleak_house(request, *args)
11
- BleakHouse.set_request_name request, "/error"
12
+ BleakHouse::Rails.set_request_name request, "/error"
12
13
  process_with_exception_without_bleak_house(request, *args)
13
14
  end
14
15
  alias_method_chain :process_with_exception, :bleak_house
@@ -0,0 +1,58 @@
1
+
2
+ module BleakHouse
3
+ module Rails
4
+ class << self
5
+
6
+ def last_request_name
7
+ @@last_request_name
8
+ end
9
+
10
+ def last_request_name=(obj)
11
+ @@last_request_name = obj
12
+ end
13
+
14
+ # Avoid making four more strings on each request.
15
+ CONTROLLER_KEY = 'controller'
16
+ ACTION_KEY = 'action'
17
+ GSUB_SEARCH = '/'
18
+ GSUB_REPLACEMENT = '__'
19
+
20
+ # 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.
21
+ def set_request_name(request, other = nil)
22
+ self.last_request_name = "#{
23
+ request.parameters[CONTROLLER_KEY].gsub(GSUB_SEARCH, GSUB_REPLACEMENT) # mangle namespaced controller names
24
+ }/#{
25
+ request.parameters[ACTION_KEY]
26
+ }/#{
27
+ request.request_method
28
+ }#{
29
+ other
30
+ }"
31
+ end
32
+
33
+ def debug(s) #:nodoc:
34
+ s = "** bleak_house: #{s}"
35
+ RAILS_DEFAULT_LOGGER.debug s if RAILS_DEFAULT_LOGGER
36
+ end
37
+
38
+ def warn(s) #:nodoc:
39
+ s = "** bleak_house: #{s}"
40
+ if RAILS_DEFAULT_LOGGER
41
+ RAILS_DEFAULT_LOGGER.warn s
42
+ else
43
+ $stderr.puts s
44
+ end
45
+ end
46
+ end
47
+
48
+ LOGFILE = "#{RAILS_ROOT}/log/bleak_house_#{RAILS_ENV}.dump"
49
+ if File.exists?(LOGFILE)
50
+ File.rename(LOGFILE, "#{LOGFILE}.old")
51
+ warn "renamed old logfile"
52
+ end
53
+
54
+ WITH_SPECIALS = false
55
+
56
+ MEMLOGGER = Logger.new
57
+ end
58
+ end
@@ -0,0 +1,19 @@
1
+
2
+ # Override Dispatcher#prepare and Dispatcher#reset_after_dispatch so that each request makes before-and-after usage snapshots.
3
+ class Dispatcher
4
+ class << self
5
+
6
+ def prepare_application_with_bleak_house
7
+ prepare_application_without_bleak_house
8
+ BleakHouse::Rails::MEMLOGGER.snapshot(BleakHouse::Rails::LOGFILE, 'core rails', BleakHouse::Rails::WITH_SPECIALS)
9
+ end
10
+ alias_method_chain :prepare_application, :bleak_house
11
+
12
+ def reset_after_dispatch_with_bleak_house
13
+ BleakHouse::Rails::MEMLOGGER.snapshot(BleakHouse::Rails::LOGFILE, BleakHouse::Rails.last_request_name || 'unknown', BleakHouse::Rails::WITH_SPECIALS)
14
+ reset_after_dispatch_without_bleak_house
15
+ end
16
+ alias_method_chain :reset_after_dispatch, :bleak_house
17
+
18
+ end
19
+ end
@@ -0,0 +1,6 @@
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,4 +1,6 @@
1
1
 
2
+ require 'set'
3
+
2
4
  class Array
3
5
  alias :time :first
4
6
  alias :data :last
@@ -13,10 +15,28 @@ class Array
13
15
 
14
16
  end
15
17
 
18
+ class Hash
19
+
20
+ # Similar to the ActiveSupport methods in Rails
21
+ def slice(keys)
22
+ keys = Set.new(keys)
23
+ reject do |key,|
24
+ !keys.include?(key)
25
+ end
26
+ end
27
+
28
+ def unslice(keys)
29
+ keys = Set.new(keys)
30
+ reject do |key,|
31
+ keys.include?(key)
32
+ end
33
+ end
34
+ end
35
+
16
36
  class Dir
17
37
  def self.descend path, &block
18
38
  path = path.split("/") unless path.is_a? Array
19
- top = path.shift
39
+ top = (path.shift or ".")
20
40
  Dir.mkdir(top) unless File.exists? top
21
41
  Dir.chdir(top) do
22
42
  if path.any?
@@ -44,4 +64,7 @@ class Symbol
44
64
  def =~ regex
45
65
  self.to_s =~ regex
46
66
  end
67
+ def [](*args)
68
+ self.to_s[*args]
69
+ end
47
70
  end
data/lib/bleak_house.rb CHANGED
@@ -1,13 +1,7 @@
1
1
 
2
- if ENV['BLEAK_HOUSE']
2
+ require 'bleak_house/logger'
3
3
 
4
- require 'dispatcher' # rails
5
-
6
- require 'bleak_house/bleak_house'
7
- require 'bleak_house/mem_logger'
8
- require 'bleak_house/dispatcher'
9
- require 'bleak_house/action_controller'
10
-
11
- BleakHouse.warn "enabled (log/#{RAILS_ENV}_bleak_house.log) (#{BleakHouse.log_interval} requests per frame)"
12
-
4
+ if ENV['RAILS_ENV'] and ENV['BLEAK_HOUSE']
5
+ require 'bleak_house/rails'
6
+ BleakHouse::Rails.warn "enabled (log/bleak_house_#{RAILS_ENV}.dump)"
13
7
  end
@@ -0,0 +1,168 @@
1
+ # = LightCsv
2
+ # CSV parser
3
+ #
4
+ # $Id: lightcsv.rb 76 2007-04-15 14:34:23Z tommy $
5
+ # Copyright:: 2007 (C) TOMITA Masahiro <tommy@tmtm.org>
6
+ # License:: Ruby's
7
+ # Homepage:: http://tmtm.org/ja/ruby/lightcsv
8
+
9
+ require "strscan"
10
+
11
+ # == CSV のパース
12
+ # 各レコードはカラムを要素とする配列である。
13
+ # レコードの区切りは LF,CR,CRLF のいずれか。
14
+ #
15
+ # 以下が csv.rb と異なる。
16
+ # * 空行は [nil] ではなく [] になる。
17
+ # * 「"」で括られていない空カラムは nil ではなく "" になる。
18
+ #
19
+ # == 例
20
+ # * CSVファイルのレコード毎にブロックを繰り返す。
21
+ # LightCsv.foreach(filename){|row| ...}
22
+ # 次と同じ。
23
+ # LightCsv.open(filename){|csv| csv.each{|row| ...}}
24
+ #
25
+ # * CSVファイルの全レコードを返す。
26
+ # LightCsv.readlines(filename) # => [[col1,col2,...],...]
27
+ # 次と同じ。
28
+ # LightCsv.open(filename){|csv| csv.map}
29
+ #
30
+ # * CSV文字列のレコード毎にブロックを繰り返す。
31
+ # LightCsv.parse("a1,a2,..."){|row| ...}
32
+ # 次と同じ。
33
+ # LightCsv.new("a1,a2,...").each{|row| ...}
34
+ #
35
+ # * CSV文字列の全レコードを返す。
36
+ # LightCsv.parse("a1,a2,...") # => [[a1,a2,...],...]
37
+ # 次と同じ。
38
+ # LightCsv.new("a1,a2,...").map
39
+ #
40
+ class LightCsv
41
+ include Enumerable
42
+
43
+ # == パースできない形式の場合に発生する例外
44
+ # InvalidFormat#message は処理できなかった位置から 10バイト文の文字列を返す。
45
+ class InvalidFormat < RuntimeError; end
46
+
47
+ # ファイルの各レコード毎にブロックを繰り返す。
48
+ # ブロック引数はレコードを表す配列。
49
+ def self.foreach(filename, &block)
50
+ self.open(filename) do |f|
51
+ f.each(&block)
52
+ end
53
+ end
54
+
55
+ # ファイルの全レコードをレコードの配列で返す。
56
+ def self.readlines(filename)
57
+ self.open(filename) do |f|
58
+ return f.map
59
+ end
60
+ end
61
+
62
+ # CSV文字列の全レコードをレコードの配列で返す。
63
+ # ブロックが与えられた場合は、レコード毎にブロックを繰り返す。
64
+ # ブロック引数はレコードを表す配列。
65
+ def self.parse(string, &block)
66
+ unless block
67
+ return self.new(string).map
68
+ end
69
+ self.new(string).each do |row|
70
+ block.call row
71
+ end
72
+ return nil
73
+ end
74
+
75
+ # ファイルをオープンして LightCsv オブジェクトを返す。
76
+ # ブロックを与えた場合は LightCsv オブジェクトを引数としてブロックを実行する。
77
+ def self.open(filename, &block)
78
+ f = File.open(filename)
79
+ csv = self.new(f)
80
+ if block
81
+ begin
82
+ return block.call(csv)
83
+ ensure
84
+ csv.close
85
+ end
86
+ else
87
+ return csv
88
+ end
89
+ end
90
+
91
+ # LightCsv オブジェクトを生成する。
92
+ # _src_ は String か IO。
93
+ def initialize(src)
94
+ if src.kind_of? String
95
+ @file = nil
96
+ @ss = StringScanner.new(src)
97
+ else
98
+ @file = src
99
+ @ss = StringScanner.new("")
100
+ end
101
+ @buf = ""
102
+ @bufsize = 64*1024
103
+ end
104
+ attr_accessor :bufsize
105
+
106
+ # LightCsv オブジェクトに関連したファイルをクローズする。
107
+ def close()
108
+ @file.close if @file
109
+ end
110
+
111
+ # 1レコードを返す。データの最後の場合は nil を返す。
112
+ # 空行の場合は空配列([])を返す。
113
+ # 空カラムは「"」で括られているか否かにかかわらず空文字列("")になる。
114
+ def shift()
115
+ return nil if @ss.eos? and ! read_next_data
116
+ cols = []
117
+ while true
118
+ if @ss.eos? and ! read_next_data
119
+ cols << ""
120
+ break
121
+ end
122
+ if @ss.scan(/\"/n)
123
+ until @ss.scan(/(?:\"\"|[^\"])*\"/n)
124
+ read_next_data or raise InvalidFormat, @ss.rest[0,10]
125
+ end
126
+ cols << @ss.matched.chop.gsub(/\"\"/n, '"')
127
+ else
128
+ col = @ss.scan(/[^\",\r\n]*/n)
129
+ while @ss.eos? and read_next_data
130
+ col << @ss.scan(/[^\",\r\n]*/n)
131
+ end
132
+ cols << col
133
+ end
134
+ unless @ss.scan(/,/n)
135
+ break if @ss.scan(/\r\n/n)
136
+ unless @ss.rest_size < 2 and read_next_data and @ss.scan(/,/n)
137
+ break if @ss.scan(/\r\n|\n|\r|\z/n)
138
+ read_next_data
139
+ raise InvalidFormat, @ss.rest[0,10]
140
+ end
141
+ end
142
+ end
143
+ cols.clear if cols.size == 1 and cols.first.empty?
144
+ cols
145
+ end
146
+
147
+ # 各レコード毎にブロックを繰り返す。
148
+ def each()
149
+ while row = shift
150
+ yield row
151
+ end
152
+ end
153
+
154
+ # 現在位置以降のレコードの配列を返す。
155
+ def readlines()
156
+ return map
157
+ end
158
+
159
+ private
160
+
161
+ def read_next_data()
162
+ if @file and @file.read(@bufsize, @buf)
163
+ @ss.string = @ss.rest + @buf
164
+ else
165
+ nil
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,27 @@
1
+ *** gc.old 2007-09-25 00:27:27.000000000 -0400
2
+ --- gc.c 2007-09-25 00:27:28.000000000 -0400
3
+ ***************
4
+ *** 309,314 ****
5
+ --- 309,330 ----
6
+ static int heaps_length = 0;
7
+ static int heaps_used = 0;
8
+
9
+ + struct heaps_slot *
10
+ + rb_gc_heap_slots()
11
+ + {
12
+ + return heaps;
13
+ + }
14
+ +
15
+ + int
16
+ + rb_gc_heaps_used() {
17
+ + return heaps_used;
18
+ + }
19
+ +
20
+ + int
21
+ + rb_gc_heaps_length() {
22
+ + return heaps_length;
23
+ + }
24
+ +
25
+ #define HEAP_MIN_SLOTS 10000
26
+ static int heap_slots = HEAP_MIN_SLOTS;
27
+
@@ -0,0 +1,16 @@
1
+ *** parse.old 2007-09-25 00:21:43.000000000 -0400
2
+ --- parse.y 2007-09-25 00:23:02.000000000 -0400
3
+ ***************
4
+ *** 6166,6171 ****
5
+ --- 6166,6176 ----
6
+ * :wait2, :$>]
7
+ */
8
+
9
+ + struct st_table *
10
+ + rb_parse_sym_tbl() {
11
+ + return sym_tbl;
12
+ + }
13
+ +
14
+ VALUE
15
+ rb_sym_all_symbols()
16
+ {
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env ruby-bleak-house
2
+
3
+ require 'rubygems'
4
+ require 'bleak_house/c'
5
+ $memlogger = BleakHouse::Logger.new
6
+ File.delete($logfile = "/tmp/log") rescue nil
7
+
8
+ puts 0
9
+ $memlogger.snapshot($logfile, "file", true)
10
+ puts 1
11
+ $memlogger.snapshot($logfile, "file/one", true)
12
+ puts 2
13
+ $memlogger.snapshot($logfile, "file/two", true)
@@ -0,0 +1,34 @@
1
+
2
+ DIR = File.dirname(__FILE__) + "/../../"
3
+
4
+ require 'rubygems'
5
+ require 'test/unit'
6
+ require 'yaml'
7
+ require 'ruby-debug'
8
+ Debugger.start
9
+
10
+ class BleakHouseTest < Test::Unit::TestCase
11
+ require "#{DIR}lib/bleak_house/logger"
12
+
13
+ SNAPSHOT_FILE = "/tmp/bleak_house"
14
+ SNAPS = {:c => SNAPSHOT_FILE + ".c.yaml",
15
+ :ruby => SNAPSHOT_FILE + ".rb.yaml"}
16
+
17
+ def setup
18
+ end
19
+
20
+ def test_c_snapshot
21
+ File.delete SNAPS[:c] rescue nil
22
+ symbol_count = Symbol.all_symbols.size
23
+ ::BleakHouse::Logger.new.snapshot(SNAPS[:c], "c_test", false)
24
+ assert_equal symbol_count, Symbol.all_symbols.size
25
+ assert File.exist?(SNAPS[:c])
26
+ end
27
+
28
+ def test_c_raises
29
+ assert_raises(RuntimeError) do
30
+ ::BleakHouse::Logger.new.snapshot("/", "c_test", false)
31
+ end
32
+ end
33
+
34
+ end
data.tar.gz.sig ADDED
@@ -0,0 +1,3 @@
1
+ ͌�� ���LԻ�^�t�L���������f?:��x^�Ɛ���e���΋^�E|l��i;;��
2
+ ��h����9�.��Xe>���k�E�+�@�N��C410���je��k��u�(M�Qd.�Lˮ����0l��m�E�>�F�.���7��B�Ӑ���|���Fh��������v��\s�-�uN�pLq}v�m�ƹ�(B�@D�o]-�N���Sp �;�.��p�^��5����
3
+ ��Zj���aؤ�