bleak_house 3.1 → 3.2

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,99 @@
1
+ #include "ruby.h"
2
+ #include "env.h"
3
+ #include "node.h"
4
+ #include "st.h"
5
+ #include "re.h"
6
+ #include "util.h"
7
+ #include "intern.h"
8
+ #include "string.h"
9
+
10
+ /* Histogram of most common Rails classes */
11
+ static char * builtins[] = {
12
+ "String",
13
+ "Array",
14
+ "Hash",
15
+ "Class",
16
+ "Regexp",
17
+ "Proc",
18
+ "ActionController::Routing::DividerSegment",
19
+ "Gem::Version",
20
+ "Gem::Version::Requirement",
21
+ "Bignum",
22
+ "Symbol",
23
+ "Time",
24
+ "MatchData",
25
+ "Gem::Specification",
26
+ "ActionController::Routing::StaticSegment",
27
+ "Gem::Dependency",
28
+ "Module",
29
+ "ActionController::Routing::DynamicSegment",
30
+ "Range",
31
+ "ActionController::Routing::Route",
32
+ "Float",
33
+ "HashWithIndifferentAccess",
34
+ "Method",
35
+ "Enumerable",
36
+ "Comparable",
37
+ "Set",
38
+ "File",
39
+ "Object",
40
+ "NameError",
41
+ "Thread",
42
+ "_node",
43
+ "_none",
44
+ "_blktag",
45
+ "_undef",
46
+ "_varmap",
47
+ "_scope",
48
+ "_unknown"
49
+ };
50
+
51
+ #define BUILTINS_SIZE 30
52
+ #define SPECIALS_SIZE 7
53
+
54
+ typedef struct RVALUE {
55
+ union {
56
+ struct {
57
+ unsigned long flags; /* always 0 for freed obj */
58
+ struct RVALUE *next;
59
+ } free;
60
+ struct RBasic basic;
61
+ struct RObject object;
62
+ struct RClass klass;
63
+ struct RFloat flonum;
64
+ struct RString string;
65
+ struct RArray array;
66
+ struct RRegexp regexp;
67
+ struct RHash hash;
68
+ struct RData data;
69
+ struct RStruct rstruct;
70
+ struct RBignum bignum;
71
+ struct RFile file;
72
+ struct RNode node;
73
+ struct RMatch match;
74
+ struct RVarmap varmap;
75
+ struct SCOPE scope;
76
+ } as;
77
+ } RVALUE;
78
+
79
+ struct heaps_slot {
80
+ void *membase;
81
+ RVALUE *slot;
82
+ int limit;
83
+ };
84
+
85
+ typedef struct st_table_entry st_table_entry;
86
+
87
+ struct st_table_entry {
88
+ unsigned int hash;
89
+ st_data_t key;
90
+ st_data_t record;
91
+ st_table_entry *next;
92
+ };
93
+
94
+ struct st_table * rb_parse_sym_tbl();
95
+ struct heaps_slot * rb_gc_heap_slots();
96
+ int rb_gc_heaps_used();
97
+ int rb_gc_heaps_length();
98
+
99
+ int lookup_builtin(char *);
data/init.rb CHANGED
@@ -1,2 +1,3 @@
1
1
 
2
- require 'bleak_house'
2
+ puts "Please use the gem version of BleakHouse from now on."
3
+ exit!
data/install.rb CHANGED
@@ -1 +1,2 @@
1
- puts File.open("#{File.dirname(__FILE__)}/README").read
1
+
2
+ puts "Please use the gem version of BleakHouse from now on."
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,5 @@
1
+
2
+ require 'ccsv'
3
+
4
+ require 'bleak_house/support/core_extensions'
5
+ require 'bleak_house/analyzer/analyzer'
@@ -0,0 +1,203 @@
1
+
2
+ require 'rubygems'
3
+ require 'fileutils'
4
+ require 'yaml'
5
+ require 'pp'
6
+
7
+ module BleakHouse
8
+
9
+ class Analyzer
10
+
11
+ MAGIC_KEYS = {
12
+ -1 => 'timestamp',
13
+ -2 => 'mem usage/swap',
14
+ -3 => 'mem usage/real',
15
+ -4 => 'tag',
16
+ -5 => 'heap/filled',
17
+ -6 => 'heap/free'
18
+ }
19
+
20
+ INITIAL_SKIP = 10
21
+
22
+ CLASS_KEYS = eval('[nil, ' + # skip 0
23
+ open(
24
+ File.dirname(__FILE__) + '/../../../ext/bleak_house/logger/snapshot.h'
25
+ ).read[/\{(.*?)\}/m, 1] + ']')
26
+
27
+ def self.calculate!(frame, index, total)
28
+ bsize = frame['births'].size
29
+ dsize = frame['deaths'].size
30
+
31
+ # Avoid divide by zero errors
32
+ frame['meta']['ratio'] = ratio = (bsize - dsize) / (bsize + dsize + 1).to_f
33
+ frame['meta']['impact'] = begin
34
+ Math.log10((bsize - dsize).abs.to_i / 10.0)
35
+ rescue Errno::ERANGE
36
+ 0
37
+ end
38
+
39
+ puts " #{index * 100 / total}%: #{frame['meta']['tag']} (#{bsize} births, #{dsize} deaths, ratio #{format('%.2f', frame['meta']['ratio'])}, impact #{format('%.2f', frame['meta']['impact'])})"
40
+ end
41
+
42
+ # Parses and correlates a BleakHouse::Logger output file.
43
+ def self.run(logfile)
44
+ unless File.exists? logfile
45
+ puts "No data file found: #{logfile}"
46
+ exit
47
+ end
48
+
49
+ puts "Working..."
50
+
51
+ cachefile = logfile + ".cache"
52
+ frames = []
53
+ last_population = []
54
+ frame = nil
55
+ ix = nil
56
+
57
+ if File.exist?(cachefile) and File.stat(cachefile).mtime > File.stat(logfile).mtime
58
+ # Cache is fresh
59
+ puts "Using cache"
60
+ frames = Marshal.load(File.open(cachefile).read)
61
+
62
+ puts "#{frames.size} frames"
63
+
64
+ frames[0..-3].each_with_index do |frame, index|
65
+ calculate!(frame, index + 1, frames.size - 1)
66
+ end
67
+
68
+ else
69
+ # Rebuild frames
70
+ total_frames = `grep '^-1' #{logfile} | wc`.to_i
71
+
72
+ puts "#{total_frames} frames"
73
+
74
+ Ccsv.foreach(logfile) do |row|
75
+
76
+ # Stupid is fast
77
+ i = row[0].to_i
78
+ row[0] = i if i != 0
79
+ i = row[1].to_i
80
+ row[1] = i if i != 0
81
+
82
+ if row[0].to_i < 0
83
+ # Get frame meta-information
84
+ if MAGIC_KEYS[row[0]] == 'timestamp'
85
+
86
+ # The frame has ended; process the last one
87
+ if frame
88
+ population = frame['objects'].keys
89
+ births = population - last_population
90
+ deaths = last_population - population
91
+ last_population = population
92
+
93
+ # assign births
94
+ frame['births'] = frame['objects'].slice(births)
95
+
96
+ # assign deaths to previous frame
97
+ if final = frames[-2]
98
+ final['deaths'] = final['objects'].slice(deaths)
99
+ final.delete 'objects'
100
+ calculate!(final, frames.size - 1, total_frames)
101
+ end
102
+ end
103
+
104
+ # Set up a new frame
105
+ frame = {}
106
+ frames << frame
107
+ frame['objects'] ||= {}
108
+ frame['meta'] ||= {}
109
+
110
+ #puts " Frame #{frames.size} opened"
111
+ end
112
+
113
+ frame['meta'][MAGIC_KEYS[row[0]]] = row[1]
114
+ else
115
+ # Assign live objects
116
+ frame['objects'][row[1]] = row[0]
117
+ end
118
+ end
119
+
120
+ # Cache the result
121
+ File.open(cachefile, 'w') do |f|
122
+ f.write Marshal.dump(frames)
123
+ end
124
+
125
+ end
126
+
127
+ # See what objects are still laying around
128
+ population = frames.last['objects'].reject do |key, value|
129
+ frames.first['births'][key] == value
130
+ end
131
+
132
+ # Remove bogus frames
133
+ frames = frames[INITIAL_SKIP..-3]
134
+
135
+ total_births = frames.inject(0) do |births, frame|
136
+ births + frame['births'].size
137
+ end
138
+ total_deaths = frames.inject(0) do |deaths, frame|
139
+ deaths + frame['deaths'].size
140
+ end
141
+
142
+ puts "\n#{total_births} births, #{total_deaths} deaths."
143
+
144
+ leakers = {}
145
+
146
+ # Find the sources of the leftover objects in the final population
147
+ population.each do |id, klass|
148
+ leaker = frames.detect do |frame|
149
+ frame['births'][id] == klass
150
+ end
151
+ if leaker
152
+ tag = leaker['meta']['tag']
153
+ klass = CLASS_KEYS[klass] if klass.is_a? Fixnum
154
+ leakers[tag] ||= Hash.new(0)
155
+ leakers[tag][klass] += 1
156
+ end
157
+ end
158
+
159
+ # Sort
160
+ leakers = leakers.map do |tag, value|
161
+ [tag, value.sort_by do |klass, count|
162
+ -count
163
+ end]
164
+ end.sort_by do |tag, value|
165
+ Hash[*value.flatten].values.inject(0) {|i, v| i - v}
166
+ end
167
+
168
+ puts "\nTags sorted by immortal leaks:"
169
+ leakers.each do |tag, value|
170
+ requests = frames.select do |frame|
171
+ frame['meta']['tag'] == tag
172
+ end.size
173
+ puts " #{tag} leaks, averaged over #{requests} requests:"
174
+ value.each do |klass, count|
175
+ count = count/requests
176
+ puts " #{count} #{klass}" if count > 0
177
+ end
178
+ end
179
+
180
+ impacts = {}
181
+
182
+ frames.each do |frame|
183
+ impacts[frame['meta']['tag']] ||= []
184
+ impacts[frame['meta']['tag']] << frame['meta']['impact'] * frame['meta']['ratio']
185
+ end
186
+ impacts = impacts.map do |tag, values|
187
+ [tag, values.inject(0) {|acc, i| acc + i} / values.size.to_f]
188
+ end.sort_by do |tag, impact|
189
+ -impact
190
+ end
191
+
192
+ puts "\nTags sorted by impact * ratio:"
193
+
194
+ impacts.each do |tag, total|
195
+ puts " #{format('%.4f', total).rjust(7)}: #{tag}"
196
+ end
197
+
198
+ puts "\nBye"
199
+
200
+ end
201
+
202
+ end
203
+ end
@@ -0,0 +1,3 @@
1
+
2
+ require 'bleak_house/logger/snapshot'
3
+ require 'bleak_house/logger/mem_usage'
@@ -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,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,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