bleak_house 3 → 3.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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ؤ�