bleak_house 4.6 → 5
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.
- 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
|