bleak_house 4.6 → 5

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