bleak_house 4.6 → 5
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +8 -61
- data/{LICENSE → LICENSE_AFL} +0 -0
- data/Manifest +22 -21
- data/README +25 -109
- data/Rakefile +101 -9
- data/init.rb +2 -0
- data/install.rb +7 -0
- data/lib/bleak_house.rb +8 -17
- data/{LICENSE_BSD → lib/bleak_house/LICENSE_BSD} +0 -0
- data/lib/bleak_house/action_controller.rb +16 -0
- data/lib/bleak_house/analyze.rb +135 -0
- data/lib/bleak_house/bleak_house.rb +43 -0
- data/lib/bleak_house/c.rb +184 -0
- data/lib/bleak_house/dispatcher.rb +20 -0
- data/lib/bleak_house/gruff_hacks.rb +62 -0
- data/lib/bleak_house/mem_logger.rb +15 -0
- data/lib/bleak_house/rake_task_redefine_task.rb +25 -0
- data/lib/bleak_house/ruby.rb +46 -0
- data/lib/bleak_house/support_methods.rb +47 -0
- data/patches/gc.c.patch +30 -0
- data/tasks/bleak_house_tasks.rake +14 -0
- data/test/unit/test_bleak_house.rb +31 -41
- metadata +81 -101
- data.tar.gz.sig +0 -0
- data/TODO +0 -10
- data/bin/bleak +0 -13
- data/bleak_house.gemspec +0 -36
- data/ext/build_ruby.rb +0 -114
- data/ext/build_snapshot.rb +0 -5
- data/ext/extconf.rb +0 -26
- data/ext/snapshot.c +0 -151
- data/ext/snapshot.h +0 -59
- data/lib/bleak_house/analyzer.rb +0 -54
- data/lib/bleak_house/hook.rb +0 -24
- data/ruby/ruby-1.8.7-p174.tar.bz2 +0 -0
- data/ruby/ruby-1.8.7.patch +0 -483
- data/test/benchmark/bench.rb +0 -16
- data/test/test_helper.rb +0 -6
- metadata.gz.sig +0 -0
@@ -0,0 +1,43 @@
|
|
1
|
+
|
2
|
+
class BleakHouse
|
3
|
+
cattr_accessor :last_request_name
|
4
|
+
|
5
|
+
def self.set_request_name request, other = nil
|
6
|
+
self.last_request_name = "#{request.parameters['controller']}/#{request.parameters['action']}/#{request.request_method}#{other}"
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.debug s
|
10
|
+
s = "#{name.underscore}: #{s}"
|
11
|
+
::ActiveRecord::Base.logger.debug s if ::ActiveRecord::Base.logger
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.warn s
|
15
|
+
s = "#{name.underscore}: #{s}"
|
16
|
+
if ::ActiveRecord::Base.logger
|
17
|
+
::ActiveRecord::Base.logger.warn s
|
18
|
+
else
|
19
|
+
$stderr.puts s
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
LOGFILE = "#{RAILS_ROOT}/log/bleak_house_#{RAILS_ENV}.dump"
|
24
|
+
if File.exists?(LOGFILE)
|
25
|
+
File.rename(LOGFILE, "#{LOGFILE}.old")
|
26
|
+
warn "renamed old logfile"
|
27
|
+
end
|
28
|
+
|
29
|
+
WITH_SPECIALS = false
|
30
|
+
|
31
|
+
MEMLOGGER = if ENV['MEMLOGGER'] !~ /ruby/i and (ENV['MEMLOGGER'] =~ /c/i or
|
32
|
+
`which ruby-bleak-house` !~ /no ruby_bleak_house/)
|
33
|
+
require 'bleak_house/c'
|
34
|
+
warn "using C instrumentation"
|
35
|
+
CLogger.new
|
36
|
+
else
|
37
|
+
require 'bleak_house/ruby'
|
38
|
+
warn "using pure Ruby instrumentation"
|
39
|
+
warn "(build the patched binary for speed and accuracy improvements)"
|
40
|
+
RubyLogger.new
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
@@ -0,0 +1,184 @@
|
|
1
|
+
|
2
|
+
require 'rubygems'
|
3
|
+
require 'inline'
|
4
|
+
|
5
|
+
class BleakHouse
|
6
|
+
class CLogger < MemLogger
|
7
|
+
|
8
|
+
MAX_UNIQ_TAGS = 1536 # per frame
|
9
|
+
MAX_TAG_LENGTH = 192 # tag plus fully namespaced classname
|
10
|
+
|
11
|
+
inline do |builder|
|
12
|
+
builder.include '"node.h"' # struct RNode
|
13
|
+
builder.include '"st.h"' # struct st_table
|
14
|
+
builder.include '"re.h"' # struct RRegexp
|
15
|
+
builder.include '"env.h"' # various structs
|
16
|
+
|
17
|
+
builder.prefix <<-EOC
|
18
|
+
typedef struct RVALUE {
|
19
|
+
union {
|
20
|
+
struct {
|
21
|
+
unsigned long flags; /* always 0 for freed obj */
|
22
|
+
struct RVALUE *next;
|
23
|
+
} free;
|
24
|
+
struct RBasic basic;
|
25
|
+
struct RObject object;
|
26
|
+
struct RClass klass;
|
27
|
+
struct RFloat flonum;
|
28
|
+
struct RString string;
|
29
|
+
struct RArray array;
|
30
|
+
struct RRegexp regexp;
|
31
|
+
struct RHash hash;
|
32
|
+
struct RData data;
|
33
|
+
struct RStruct rstruct;
|
34
|
+
struct RBignum bignum;
|
35
|
+
struct RFile file;
|
36
|
+
struct RNode node;
|
37
|
+
struct RMatch match;
|
38
|
+
struct RVarmap varmap;
|
39
|
+
struct SCOPE scope;
|
40
|
+
} as;
|
41
|
+
} RVALUE;
|
42
|
+
|
43
|
+
struct heaps_slot {
|
44
|
+
void *membase;
|
45
|
+
RVALUE *slot;
|
46
|
+
int limit;
|
47
|
+
};
|
48
|
+
|
49
|
+
struct heaps_slot * rb_gc_heap_slots();
|
50
|
+
int rb_gc_heaps_used();
|
51
|
+
int rb_gc_heaps_length();
|
52
|
+
EOC
|
53
|
+
|
54
|
+
# number of struct heaps_slots used
|
55
|
+
builder.c <<-EOC
|
56
|
+
static int
|
57
|
+
heaps_used() {
|
58
|
+
return rb_gc_heaps_used();
|
59
|
+
}
|
60
|
+
EOC
|
61
|
+
|
62
|
+
# length of the struct heaps_slots allocated
|
63
|
+
builder.c <<-EOC
|
64
|
+
static int
|
65
|
+
heaps_length() {
|
66
|
+
return rb_gc_heaps_length();
|
67
|
+
}
|
68
|
+
EOC
|
69
|
+
|
70
|
+
OBJ_TYPES = ["T_NIL",
|
71
|
+
"T_OBJECT",
|
72
|
+
"T_CLASS",
|
73
|
+
"T_ICLASS",
|
74
|
+
"T_MODULE",
|
75
|
+
"T_FLOAT",
|
76
|
+
"T_STRING",
|
77
|
+
"T_REGEXP",
|
78
|
+
"T_ARRAY",
|
79
|
+
"T_FIXNUM",
|
80
|
+
"T_HASH",
|
81
|
+
"T_STRUCT",
|
82
|
+
"T_BIGNUM",
|
83
|
+
"T_FILE",
|
84
|
+
"T_TRUE",
|
85
|
+
"T_FALSE",
|
86
|
+
"T_DATA",
|
87
|
+
"T_SYMBOL",
|
88
|
+
"T_MATCH"]
|
89
|
+
|
90
|
+
RAW_TYPES = ["T_NONE",
|
91
|
+
"T_BLKTAG",
|
92
|
+
"T_UNDEF",
|
93
|
+
"T_VARMAP",
|
94
|
+
"T_SCOPE",
|
95
|
+
"T_NODE"]
|
96
|
+
|
97
|
+
# writes a frame to a file
|
98
|
+
builder.c <<-EOC
|
99
|
+
static void
|
100
|
+
VALUE snapshot(VALUE path, VALUE tag, VALUE _specials) {
|
101
|
+
Check_Type(path, T_STRING);
|
102
|
+
Check_Type(tag, T_STRING);
|
103
|
+
|
104
|
+
RVALUE *p, *pend;
|
105
|
+
struct heaps_slot * heaps = rb_gc_heap_slots();
|
106
|
+
|
107
|
+
int specials = RTEST(_specials);
|
108
|
+
|
109
|
+
FILE *obj_log = fopen(StringValueCStr(path), "r");
|
110
|
+
int is_new;
|
111
|
+
if (!(is_new = (obj_log == NULL)))
|
112
|
+
fclose(obj_log);
|
113
|
+
|
114
|
+
if ((obj_log = fopen(StringValueCStr(path), "a+")) == NULL)
|
115
|
+
rb_raise(rb_eRuntimeError, "couldn't open snapshot file");
|
116
|
+
|
117
|
+
if (is_new)
|
118
|
+
fprintf(obj_log, \"---\\n");
|
119
|
+
fprintf(obj_log, \"- - %i\\n", time(0));
|
120
|
+
VALUE mem = rb_funcall(self, rb_intern("mem_usage"), 0);
|
121
|
+
fprintf(obj_log, \" - :\\"memory usage/swap\\": %i\\n", NUM2INT(RARRAY_PTR(mem)[0]));
|
122
|
+
fprintf(obj_log, \" :\\"memory usage/real\\": %i\\n", NUM2INT(RARRAY_PTR(mem)[1]));
|
123
|
+
|
124
|
+
/* haha */
|
125
|
+
char tags[#{MAX_UNIQ_TAGS}][#{MAX_TAG_LENGTH}];
|
126
|
+
char current_tag[2048];
|
127
|
+
int counts[#{MAX_UNIQ_TAGS}];
|
128
|
+
int current_pos = 0;
|
129
|
+
|
130
|
+
int i, j;
|
131
|
+
for (i = 0; i < rb_gc_heaps_used(); i++) {
|
132
|
+
p = heaps[i].slot;
|
133
|
+
pend = p + heaps[i].limit;
|
134
|
+
for (; p < pend; p++) {
|
135
|
+
if (p->as.basic.flags) { /* always 0 for freed objects */
|
136
|
+
sprintf(current_tag, "");
|
137
|
+
switch (TYPE(p)) {
|
138
|
+
#{RAW_TYPES.map do |type|
|
139
|
+
"case #{type}:
|
140
|
+
if (specials)
|
141
|
+
sprintf(current_tag , \"%s::::_#{type[2..-1].downcase}\", StringValueCStr(tag));
|
142
|
+
break;
|
143
|
+
"
|
144
|
+
end.flatten.join}
|
145
|
+
default:
|
146
|
+
if (!p->as.basic.klass) {
|
147
|
+
sprintf(current_tag , "%s::::_unknown", StringValueCStr(tag));
|
148
|
+
} else {
|
149
|
+
sprintf(current_tag , "%s::::%s", StringValueCStr(tag), rb_obj_classname((VALUE)p));
|
150
|
+
}
|
151
|
+
}
|
152
|
+
if (strlen(current_tag) > #{MAX_TAG_LENGTH})
|
153
|
+
rb_raise(rb_eRuntimeError, "tag + classname too big; increase MAX_TAG_LENGTH (#{MAX_TAG_LENGTH})");
|
154
|
+
if (strcmp(current_tag, "")) {
|
155
|
+
for (j = 0; j < current_pos; j++) {
|
156
|
+
if (!strcmp(tags[j], current_tag)) {
|
157
|
+
counts[j] ++;
|
158
|
+
break;
|
159
|
+
}
|
160
|
+
}
|
161
|
+
if (j == current_pos) {
|
162
|
+
/* found a new one */
|
163
|
+
if (current_pos == #{MAX_UNIQ_TAGS})
|
164
|
+
rb_raise(rb_eRuntimeError, "exhausted tag array; increase MAX_UNIQ_TAGS (#{MAX_UNIQ_TAGS})");
|
165
|
+
sprintf(tags[current_pos], current_tag);
|
166
|
+
counts[current_pos] = 1;
|
167
|
+
current_pos ++;
|
168
|
+
}
|
169
|
+
}
|
170
|
+
}
|
171
|
+
}
|
172
|
+
}
|
173
|
+
|
174
|
+
for (j = 0; j < current_pos; j++) {
|
175
|
+
fprintf(obj_log, " :\\"%s\\": %i\\n", tags[j], counts[j]);
|
176
|
+
}
|
177
|
+
fclose(obj_log);
|
178
|
+
return Qtrue;
|
179
|
+
}
|
180
|
+
EOC
|
181
|
+
end
|
182
|
+
|
183
|
+
end
|
184
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
|
2
|
+
# crazyness
|
3
|
+
|
4
|
+
class Dispatcher
|
5
|
+
class << self
|
6
|
+
def prepare_application_with_bleak_house
|
7
|
+
prepare_application_without_bleak_house
|
8
|
+
BleakHouse::MEMLOGGER.snapshot(BleakHouse::LOGFILE, 'core rails', BleakHouse::WITH_SPECIALS)
|
9
|
+
GC.start
|
10
|
+
end
|
11
|
+
alias_method_chain :prepare_application, :bleak_house
|
12
|
+
|
13
|
+
def reset_after_dispatch_with_bleak_house
|
14
|
+
BleakHouse::MEMLOGGER.snapshot(BleakHouse::LOGFILE, BleakHouse.last_request_name || 'unknown', BleakHouse::WITH_SPECIALS)
|
15
|
+
GC.start
|
16
|
+
reset_after_dispatch_without_bleak_house
|
17
|
+
end
|
18
|
+
alias_method_chain :reset_after_dispatch, :bleak_house
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
|
2
|
+
class Gruff::Base
|
3
|
+
|
4
|
+
def draw_legend
|
5
|
+
@legend_labels = @data.collect {|item| item[DATA_LABEL_INDEX] }
|
6
|
+
|
7
|
+
legend_square_width = @legend_box_size # small square with color of this item
|
8
|
+
legend_left = 10
|
9
|
+
|
10
|
+
current_x_offset = legend_left
|
11
|
+
current_y_offset = TOP_MARGIN + TITLE_MARGIN + @title_caps_height + LEGEND_MARGIN
|
12
|
+
|
13
|
+
debug { @d.line 0.0, current_y_offset, @raw_columns, current_y_offset }
|
14
|
+
|
15
|
+
@legend_labels.each_with_index do |legend_label, index|
|
16
|
+
|
17
|
+
next if index > MAX_LEGENDS
|
18
|
+
legend_label = "Some not shown" if index == MAX_LEGENDS
|
19
|
+
|
20
|
+
# Draw label
|
21
|
+
@d.fill = @font_color
|
22
|
+
@d.font = @font if @font
|
23
|
+
@d.pointsize = scale_fontsize(@legend_font_size)
|
24
|
+
@d.stroke('transparent')
|
25
|
+
@d.font_weight = NormalWeight
|
26
|
+
@d.gravity = WestGravity
|
27
|
+
@d = @d.annotate_scaled( @base_image,
|
28
|
+
@raw_columns, 1.0,
|
29
|
+
current_x_offset + (legend_square_width * 1.7), current_y_offset,
|
30
|
+
legend_label.to_s, @scale)
|
31
|
+
|
32
|
+
if index < MAX_LEGENDS
|
33
|
+
# Now draw box with color of this dataset
|
34
|
+
@d = @d.stroke('transparent')
|
35
|
+
@d = @d.fill @data[index][DATA_COLOR_INDEX]
|
36
|
+
@d = @d.rectangle(current_x_offset,
|
37
|
+
current_y_offset - legend_square_width / 2.0,
|
38
|
+
current_x_offset + legend_square_width,
|
39
|
+
current_y_offset + legend_square_width / 2.0)
|
40
|
+
end
|
41
|
+
|
42
|
+
@d.pointsize = @legend_font_size
|
43
|
+
metrics = @d.get_type_metrics(@base_image, legend_label.to_s)
|
44
|
+
current_y_offset += metrics.height * 1.05
|
45
|
+
end
|
46
|
+
@color_index = 0
|
47
|
+
end
|
48
|
+
|
49
|
+
alias :setup_graph_measurements_without_top_margin :setup_graph_measurements
|
50
|
+
def setup_graph_measurements
|
51
|
+
setup_graph_measurements_without_top_margin
|
52
|
+
@graph_height += NEGATIVE_TOP_MARGIN
|
53
|
+
@graph_top -= NEGATIVE_TOP_MARGIN
|
54
|
+
end
|
55
|
+
|
56
|
+
alias :clip_value_if_greater_than_without_size_hacks :clip_value_if_greater_than
|
57
|
+
def clip_value_if_greater_than(arg1, arg2)
|
58
|
+
arg2 = arg2 / 2 if arg1 == @columns / (@norm_data.first[1].size * 2.5)
|
59
|
+
clip_value_if_greater_than_without_size_hacks(arg1, arg2)
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
|
2
|
+
class BleakHouse
|
3
|
+
class MemLogger
|
4
|
+
def snapshot
|
5
|
+
raise "abstract method; please require 'ruby' or 'c'"
|
6
|
+
end
|
7
|
+
|
8
|
+
def mem_usage
|
9
|
+
a = `ps -o vsz,rss -p #{Process.pid}`.split(/\s+/)[-2..-1].map{|el| el.to_i}
|
10
|
+
[a.first - a.last, a.last]
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
@@ -0,0 +1,25 @@
|
|
1
|
+
|
2
|
+
# http://www.bigbold.com/snippets/posts/show/2032
|
3
|
+
module Rake
|
4
|
+
module TaskManager
|
5
|
+
def redefine_task(task_class, args, &block)
|
6
|
+
task_name, deps = resolve_args(args)
|
7
|
+
task_name = task_class.scope_name(@scope, task_name)
|
8
|
+
deps = [deps] unless deps.respond_to?(:to_ary)
|
9
|
+
deps = deps.collect {|d| d.to_s }
|
10
|
+
task = @tasks[task_name.to_s] = task_class.new(task_name, self)
|
11
|
+
task.application = self
|
12
|
+
task.add_comment(@last_comment)
|
13
|
+
@last_comment = nil
|
14
|
+
task.enhance(deps, &block)
|
15
|
+
task
|
16
|
+
end
|
17
|
+
end
|
18
|
+
class Task
|
19
|
+
class << self
|
20
|
+
def redefine_task(args, &block)
|
21
|
+
Rake.application.redefine_task(self, args, &block)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
class BleakHouse
|
5
|
+
class RubyLogger < MemLogger
|
6
|
+
SWAP = :"memory usage/swap"
|
7
|
+
RSS = :"memory usage/real"
|
8
|
+
SEEN = {}
|
9
|
+
CURRENT = {}
|
10
|
+
TAGS = Hash.new(0)
|
11
|
+
|
12
|
+
def snapshot(path, tag, _specials)
|
13
|
+
CURRENT.clear
|
14
|
+
ObjectSpace.each_object do |obj|
|
15
|
+
CURRENT[obj_id = obj._bleak_house_object_id] = true
|
16
|
+
unless SEEN[obj_id]
|
17
|
+
# symbols will rapidly stabilize
|
18
|
+
SEEN[obj_id] = "#{tag}::::#{obj._bleak_house_class}".to_sym
|
19
|
+
TAGS[SEEN[obj_id]] += 1
|
20
|
+
end
|
21
|
+
end
|
22
|
+
SEEN.keys.each do |obj_id|
|
23
|
+
TAGS[SEEN.delete(obj_id)] -= 1 unless CURRENT[obj_id]
|
24
|
+
end
|
25
|
+
CURRENT.clear
|
26
|
+
|
27
|
+
TAGS[SWAP], TAGS[RSS] = mem_usage
|
28
|
+
|
29
|
+
write(path)
|
30
|
+
end
|
31
|
+
|
32
|
+
def write(path)
|
33
|
+
exists = File.exist? path
|
34
|
+
File.open(path, 'a+') do |log|
|
35
|
+
dump = YAML.dump([[Time.now.to_i, TAGS]])
|
36
|
+
log.write(exists ? dump[5..-1] : dump)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class Object
|
44
|
+
alias :_bleak_house_object_id :object_id
|
45
|
+
alias :_bleak_house_class :class
|
46
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
|
2
|
+
class Array
|
3
|
+
alias :time :first
|
4
|
+
alias :data :last
|
5
|
+
|
6
|
+
def sum
|
7
|
+
inject(0) {|s, x| x + s}
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_i
|
11
|
+
self.map{|s| s.to_i}
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
class Dir
|
17
|
+
def self.descend path, &block
|
18
|
+
path = path.split("/") unless path.is_a? Array
|
19
|
+
top = (path.shift or ".")
|
20
|
+
Dir.mkdir(top) unless File.exists? top
|
21
|
+
Dir.chdir(top) do
|
22
|
+
if path.any?
|
23
|
+
descend path, &block
|
24
|
+
else
|
25
|
+
block.call
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class String
|
32
|
+
def to_filename
|
33
|
+
self.downcase.gsub(/[^\w\d\-]/, '_')
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class NilClass
|
38
|
+
def +(op)
|
39
|
+
self.to_i + op
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class Symbol
|
44
|
+
def =~ regex
|
45
|
+
self.to_s =~ regex
|
46
|
+
end
|
47
|
+
end
|