bleak_house 3.0.1 → 3.1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +5 -26
- data/Manifest +18 -30
- data/README +13 -66
- data/Rakefile +56 -63
- data/init.rb +1 -2
- data/install.rb +1 -2
- data/lib/bleak_house/{rails/action_controller.rb → action_controller.rb} +2 -3
- data/lib/bleak_house/analyze.rb +139 -0
- data/lib/bleak_house/bleak_house.rb +33 -0
- data/lib/bleak_house/dispatcher.rb +23 -0
- data/lib/bleak_house/gruff_hacks.rb +56 -0
- data/lib/bleak_house/mem_logger.rb +54 -0
- data/lib/bleak_house/{support/rake.rb → rake_task_redefine_task.rb} +0 -0
- data/lib/bleak_house/{support/core_extensions.rb → support_methods.rb} +0 -23
- data/lib/bleak_house.rb +10 -4
- data/tasks/bleak_house_tasks.rake +13 -0
- metadata +50 -77
- data/LICENSE_BSD +0 -10
- data/LICENSE_RUBY +0 -53
- data/TODO +0 -2
- data/bin/bleak +0 -10
- data/ext/bleak_house/logger/extconf.rb +0 -4
- data/ext/bleak_house/logger/snapshot.c +0 -153
- data/ext/bleak_house/logger/snapshot.h +0 -99
- data/lib/bleak_house/analyzer/analyzer.rb +0 -143
- data/lib/bleak_house/analyzer.rb +0 -5
- data/lib/bleak_house/logger/mem_usage.rb +0 -13
- data/lib/bleak_house/logger.rb +0 -3
- data/lib/bleak_house/rails/bleak_house.rb +0 -58
- data/lib/bleak_house/rails/dispatcher.rb +0 -19
- data/lib/bleak_house/rails.rb +0 -6
- data/lib/vendor/lightcsv.rb +0 -168
- data/patches/gc.c.patch +0 -27
- data/patches/parse.y.patch +0 -16
- data/test/misc/direct.rb +0 -13
- data/test/unit/test_bleak_house.rb +0 -34
- data.tar.gz.sig +0 -3
- metadata.gz.sig +0 -0
data/CHANGELOG
CHANGED
@@ -1,29 +1,8 @@
|
|
1
1
|
|
2
|
-
|
2
|
+
BleakHouse Changelog
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
v2.5. Detect if core keys are present or not; fixes for analyzing non-Rails apps.
|
9
|
-
|
10
|
-
v2.4. Log heap usage; remove pure-Ruby profiler.
|
11
|
-
|
12
|
-
v2.3. Subtract Rails core counts from action counts and join paired frames.
|
13
|
-
|
14
|
-
v2.2. Change smoothing algorithm to be more intuitive and so mem usage is correct.
|
15
|
-
|
16
|
-
v2.1. Close file properly.
|
17
|
-
|
18
|
-
v2. C instrumentation with YAML output.
|
19
|
-
|
20
|
-
v1.3. Log memory usage on *nix.
|
21
|
-
|
22
|
-
v1.2. Fix nil directory traversal error on Windows.
|
23
|
-
|
24
|
-
v1.1. Gem version
|
25
|
-
|
26
|
-
v1. Dumped Rublique; replaced with a lightweight non-delta marshalling version.
|
27
|
-
|
28
|
-
v0. First version
|
4
|
+
3.1. fix nil directory traversal error on windows
|
5
|
+
3. gem version
|
6
|
+
2. dumped rublique once I understood what it did; replaced with a lightweight non-delta marshalling version
|
7
|
+
1. first version
|
29
8
|
|
data/Manifest
CHANGED
@@ -1,30 +1,18 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
README
|
5
|
-
Rakefile
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
lib/
|
13
|
-
lib/bleak_house.rb
|
14
|
-
lib/bleak_house/
|
15
|
-
lib/bleak_house/
|
16
|
-
lib/bleak_house/
|
17
|
-
lib/bleak_house
|
18
|
-
|
19
|
-
lib/bleak_house/rails/action_controller.rb
|
20
|
-
lib/bleak_house/logger.rb
|
21
|
-
lib/bleak_house/logger/mem_usage.rb
|
22
|
-
lib/bleak_house/analyzer.rb
|
23
|
-
lib/bleak_house/analyzer/analyzer.rb
|
24
|
-
install.rb
|
25
|
-
init.rb
|
26
|
-
ext/bleak_house/logger/snapshot.h
|
27
|
-
ext/bleak_house/logger/snapshot.c
|
28
|
-
ext/bleak_house/logger/extconf.rb
|
29
|
-
CHANGELOG
|
30
|
-
bin/bleak
|
1
|
+
./CHANGELOG
|
2
|
+
./LICENSE
|
3
|
+
./Manifest
|
4
|
+
./README
|
5
|
+
./Rakefile
|
6
|
+
./init.rb
|
7
|
+
./install.rb
|
8
|
+
./lib/bleak_house
|
9
|
+
./lib/bleak_house/action_controller.rb
|
10
|
+
./lib/bleak_house/analyze.rb
|
11
|
+
./lib/bleak_house/bleak_house.rb
|
12
|
+
./lib/bleak_house/dispatcher.rb
|
13
|
+
./lib/bleak_house/gruff_hacks.rb
|
14
|
+
./lib/bleak_house/mem_logger.rb
|
15
|
+
./lib/bleak_house/rake_task_redefine_task.rb
|
16
|
+
./lib/bleak_house/support_methods.rb
|
17
|
+
./lib/bleak_house.rb
|
18
|
+
./tasks/bleak_house_tasks.rake
|
data/README
CHANGED
@@ -1,73 +1,20 @@
|
|
1
|
+
-----------------------------------------------------------------
|
1
2
|
|
2
3
|
BleakHouse
|
4
|
+
You need the 'gruff' gem, and 'rmagick', and all that.
|
3
5
|
|
4
|
-
|
6
|
+
To run:
|
7
|
+
RAILS_ENV=production BLEAK_HOUSE=true INTERVAL=5 ./script/server
|
8
|
+
Browse around in your app. Cause requests. Do stuff. Then:
|
9
|
+
RAILS_ENV=production rake bleak_house:analyze
|
10
|
+
And then look in log/bleak_house/.
|
5
11
|
|
6
|
-
|
7
|
-
|
8
|
-
Copyright 2007 Cloudburst, LLC. See the included LICENSE file. Portions copyright 2006 Eric Hodel and used with permission. See the included LICENSE_BSD file. Portions copyright 2007 TOMITA Masahiro and used with permission. See the included LICENSE_RUBY file.
|
9
|
-
|
10
|
-
The public certificate for this gem is at http://rubyforge.org/frs/download.php/25331/evan_weaver-original-public_cert.pem.
|
11
|
-
|
12
|
-
== Features
|
13
|
-
|
14
|
-
* leak-proof C instrumentation
|
15
|
-
* fast logging
|
16
|
-
* complete tracking of object and symbol history
|
17
|
-
* easy Rails integration
|
18
|
-
|
19
|
-
== Requirements
|
20
|
-
|
21
|
-
* A unix-like operating system
|
22
|
-
* Ruby 1.8.6
|
23
|
-
|
24
|
-
= Usage
|
25
|
-
|
26
|
-
== Installation
|
27
|
-
|
28
|
-
Install the gem:
|
29
|
-
sudo gem install bleak_house -v 3.0.1
|
30
|
-
|
31
|
-
You need to compile the BleakHouse patched Ruby build. In the plugin directory, run:
|
32
|
-
rake ruby:build
|
33
|
-
|
34
|
-
This gives you a Ruby binary named <tt>ruby-bleak-house</tt> alongside your regular binary.
|
35
|
-
|
36
|
-
== Profiling a Rails app
|
37
|
-
|
38
|
-
To setup a Rails app for profiling, just <tt>require 'bleak_house'</tt> in <tt>config/environment.rb</tt>.
|
39
|
-
|
40
|
-
Then, to engage the logger (ideally, in a live deployment situation), start a server instance as so:
|
41
|
-
RAILS_ENV=production BLEAK_HOUSE=true ruby-bleak-house ./script/server
|
42
|
-
|
43
|
-
Browse around manually, thrash your entire app with a script, target troublesome controllers/actions, etc. The BleakHouse logger will dump a huge amount of data to <tt>log/bleak_house_production.dump</tt>--keep an eye on your disk space.
|
44
|
-
|
45
|
-
Then, to analyze the dump:
|
46
|
-
bleak path/to/log/bleak_house_production.dump
|
12
|
+
Copyright (c) 2007 Cloudburst, LLC. See included LICENSE file. Portions
|
13
|
+
copyright Tony Arcieri and Scott Barron and used under license.
|
47
14
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
== Using BleakHouse outside of Rails
|
53
|
-
|
54
|
-
Just instantiate a BleakHouse::Logger object, and make calls to <tt>snapshot</tt> at appropriate times.
|
55
|
-
|
56
|
-
== Troubleshooting
|
57
|
-
|
58
|
-
If you see the error <tt>Symbol not found: _rb_gc_heaps_used</tt>, it means you installed the patched binary, but tried to profile the server with the regular Ruby binary.
|
59
|
-
|
60
|
-
You may get library require errors if you install <tt>ruby-bleak-house</tt> 1.8.6 alongside a different verson of Ruby. You could try to patch your local version of Ruby instead, or you could just upgrade to 1.8.6, which has a good trackrecord of stability.
|
61
|
-
|
62
|
-
== Reporting problems
|
63
|
-
|
64
|
-
* http://rubyforge.org/forum/forum.php?forum_id=13983
|
65
|
-
|
66
|
-
Patches and contributions are very welcome. Please note that contributors are required to assign copyright for their additions to Cloudburst, LLC.
|
67
|
-
|
68
|
-
== Further resources
|
15
|
+
I was such a shy little thing that I seldom dared to open my lips, and never
|
16
|
+
dared to open my heart, to anybody else.
|
17
|
+
- Dickens, Bleak House
|
69
18
|
|
70
|
-
|
71
|
-
* http://blog.evanweaver.com/articles/2007/05/06/leak-proof-direct-heap-instrumentation-for-bleak_house
|
72
|
-
* http://blog.evanweaver.com/articles/2007/04/28/bleak_house
|
19
|
+
-----------------------------------------------------------------
|
73
20
|
|
data/Rakefile
CHANGED
@@ -1,73 +1,66 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'lib/bleak_house/rake_task_redefine_task'
|
1
4
|
|
2
|
-
|
5
|
+
NAME = "bleak_house"
|
3
6
|
|
4
7
|
begin
|
8
|
+
require 'rake/clean'
|
9
|
+
gem 'echoe', '>= 1.1'
|
5
10
|
require 'echoe'
|
11
|
+
require 'fileutils'
|
12
|
+
|
13
|
+
AUTHOR = "Evan Weaver"
|
14
|
+
EMAIL = "evan at cloudbur dot st"
|
15
|
+
DESCRIPTION = "BleakHouse is a Rails plugin for finding memory leaks. It tracks ObjectSpace for your entire app, and produces charts of references by controller, by action, and by object class."
|
16
|
+
CHANGES = `cat CHANGELOG`[/^([\d\.]+\. .*)/, 1]
|
17
|
+
RUBYFORGE_NAME = "fauna"
|
18
|
+
GEM_NAME = "bleak_house"
|
19
|
+
HOMEPATH = "http://blog.evanweaver.com"
|
20
|
+
RELEASE_TYPES = ["gem"]
|
21
|
+
REV = nil
|
22
|
+
VERS = `cat CHANGELOG`[/^([\d\.]+)\. /, 1]
|
23
|
+
CLEAN.include ['**/.*.sw?', '*.gem', '.config']
|
24
|
+
RDOC_OPTS = ['--quiet', '--title', "bleak_house documentation", "--opname", "index.html", "--line-numbers", "--main", "README", "--inline-source"]
|
25
|
+
|
26
|
+
include FileUtils
|
27
|
+
|
28
|
+
taskmsg = File.open(File.dirname(__FILE__) + "/tasks/bleak_house_tasks.rake").readlines
|
29
|
+
taskmsg = taskmsg[0..3] + [taskmsg[7][2..-1]] + taskmsg[9..-1]
|
6
30
|
|
7
|
-
Echoe.new(
|
8
|
-
p.author =
|
9
|
-
p.
|
10
|
-
p.
|
11
|
-
p.
|
12
|
-
p.
|
13
|
-
p.
|
14
|
-
p.
|
15
|
-
p.
|
16
|
-
p.
|
17
|
-
p.
|
31
|
+
echoe = Echoe.new(GEM_NAME, VERS) do |p|
|
32
|
+
p.author = AUTHOR
|
33
|
+
p.rubyforge_name = RUBYFORGE_NAME
|
34
|
+
p.name = NAME
|
35
|
+
p.description = DESCRIPTION
|
36
|
+
p.changes = CHANGES
|
37
|
+
p.email = EMAIL
|
38
|
+
p.summary = DESCRIPTION
|
39
|
+
p.url = HOMEPATH
|
40
|
+
p.need_tar = false
|
41
|
+
p.need_tar_gz = true
|
42
|
+
p.test_globs = ["*_test.rb"]
|
43
|
+
p.clean_globs = CLEAN
|
44
|
+
p.spec_extras = {:post_install_message =>
|
45
|
+
"
|
46
|
+
Thanks for installing Bleak House #{VERS}.
|
47
|
+
|
48
|
+
For each Rails app you want to profile, you will need to add the following
|
49
|
+
rake task in RAILS_ROOT/lib/tasks/bleak_house_tasks.rake to be able to run
|
50
|
+
the analyzer:
|
51
|
+
" + taskmsg.join(" ") + "\n"}
|
18
52
|
end
|
19
|
-
|
20
|
-
rescue LoadError
|
53
|
+
|
54
|
+
rescue LoadError => boom
|
55
|
+
puts "You are missing a dependency required for meta-operations on this gem."
|
56
|
+
puts "#{boom.to_s.capitalize}."
|
57
|
+
|
58
|
+
desc 'Run the default tasks'
|
59
|
+
task :default => :test
|
21
60
|
end
|
22
61
|
|
23
|
-
desc '
|
62
|
+
desc 'Do nothing.'
|
24
63
|
Rake::Task.redefine_task("test") do
|
25
|
-
|
64
|
+
puts "There are no tests. You could totally write some, though."
|
65
|
+
# system "ruby -Ibin:lib:test test/unit/polymorph_test.rb #{ENV['METHOD'] ? "--name=#{ENV['METHOD']}" : ""}"
|
26
66
|
end
|
27
|
-
|
28
|
-
desc 'Build a patched binary.'
|
29
|
-
namespace :ruby do
|
30
|
-
task :build do
|
31
|
-
if RUBY_PLATFORM =~ /win32|windows/
|
32
|
-
puts "ERROR: windows not supported."
|
33
|
-
exit
|
34
|
-
end
|
35
|
-
require 'fileutils'
|
36
|
-
puts "building patched Ruby binary"
|
37
|
-
tmp = "/tmp/"
|
38
|
-
Dir.chdir(tmp) do
|
39
|
-
build_dir = "bleak_house"
|
40
|
-
binary_dir = File.dirname(`which ruby`)
|
41
|
-
FileUtils.rm_rf(build_dir) rescue nil
|
42
|
-
Dir.mkdir(build_dir)
|
43
|
-
begin
|
44
|
-
Dir.chdir(build_dir) do
|
45
|
-
puts " downloading Ruby source"
|
46
|
-
bz2 = "ruby-1.8.6.tar.bz2"
|
47
|
-
system("wget 'http://rubyforge.org/frs/download.php/18434/ruby-1.8.6.tar.bz2' &> wget.log")
|
48
|
-
puts " extracting"
|
49
|
-
system("tar xjf #{bz2} &> tar.log")
|
50
|
-
File.delete bz2
|
51
|
-
Dir.chdir("ruby-1.8.6") do
|
52
|
-
puts " patching"
|
53
|
-
system("patch -p0 < \'#{File.dirname(__FILE__)}/patches/gc.c.patch\' &> ../gc.c.patch.log")
|
54
|
-
system("patch -p0 < \'#{File.dirname(__FILE__)}/patches/parse.y.patch\' &> ../parse.y.patch.log")
|
55
|
-
puts " configuring"
|
56
|
-
system("./configure --prefix=#{binary_dir[0..-5]} &> ../configure.log") # --with-static-linked-ext
|
57
|
-
puts " making"
|
58
|
-
system("make &> ../make.log")
|
59
|
-
|
60
|
-
binary = "#{binary_dir}/ruby-bleak-house"
|
61
|
-
puts " installing as #{binary}"
|
62
|
-
FileUtils.cp("./ruby", binary)
|
63
|
-
FileUtils.chmod(0755, binary)
|
64
|
-
puts " done"
|
65
|
-
end
|
66
|
-
end
|
67
|
-
rescue Object => e
|
68
|
-
puts "ERROR: please see the last modified log file in #{tmp}#{build_dir}, perhaps\nit will contain a clue."
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
data/init.rb
CHANGED
data/install.rb
CHANGED
@@ -1,2 +1 @@
|
|
1
|
-
|
2
|
-
puts "Please use the gem version of BleakHouse from now on."
|
1
|
+
puts File.open("#{File.dirname(__FILE__)}/README").read
|
@@ -1,15 +1,14 @@
|
|
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.
|
3
2
|
class ActionController::Base
|
4
3
|
class << self
|
5
4
|
def process_with_bleak_house(request, *args)
|
6
|
-
BleakHouse
|
5
|
+
BleakHouse.set_request_name request
|
7
6
|
process_without_bleak_house(request, *args)
|
8
7
|
end
|
9
8
|
alias_method_chain :process, :bleak_house
|
10
9
|
|
11
10
|
def process_with_exception_with_bleak_house(request, *args)
|
12
|
-
BleakHouse
|
11
|
+
BleakHouse.set_request_name request, "/error"
|
13
12
|
process_with_exception_without_bleak_house(request, *args)
|
14
13
|
end
|
15
14
|
alias_method_chain :process_with_exception, :bleak_house
|
@@ -0,0 +1,139 @@
|
|
1
|
+
|
2
|
+
require 'rubygems'
|
3
|
+
require 'fileutils'
|
4
|
+
require 'base64'
|
5
|
+
|
6
|
+
gem 'gruff', '= 0.2.8'
|
7
|
+
require 'gruff'
|
8
|
+
|
9
|
+
# require, but make rdoc not whine
|
10
|
+
load "#{File.dirname(__FILE__)}/gruff_hacks.rb"
|
11
|
+
load "#{File.dirname(__FILE__)}/support_methods.rb"
|
12
|
+
|
13
|
+
Gruff::Base::LEFT_MARGIN = 200
|
14
|
+
Gruff::Base::NEGATIVE_TOP_MARGIN = 30
|
15
|
+
Gruff::Base::MAX_LEGENDS = 28
|
16
|
+
|
17
|
+
class BleakHouse
|
18
|
+
class Analyze
|
19
|
+
|
20
|
+
DIR = "#{RAILS_ROOT}/log/bleak_house/"
|
21
|
+
|
22
|
+
def initialize(data, increments, name)
|
23
|
+
@data = data
|
24
|
+
@increments = increments
|
25
|
+
@name = name
|
26
|
+
end
|
27
|
+
|
28
|
+
def draw
|
29
|
+
g = Gruff::Line.new("1024x768")
|
30
|
+
g.title = @name
|
31
|
+
g.x_axis_label = "time"
|
32
|
+
g.legend_font_size = g.legend_box_size = 14
|
33
|
+
g.title_font_size = 24
|
34
|
+
g.marker_font_size = 14
|
35
|
+
|
36
|
+
@data.map do |key, values|
|
37
|
+
name = if key.to_s == ""
|
38
|
+
'[Unknown]'
|
39
|
+
else
|
40
|
+
# elsif key =~ Regexp.new(@specials.keys.join('|'))
|
41
|
+
# name = "#{key} (#{values.to_i.max / (2**10)} MB)"
|
42
|
+
# @specials.each do |regex, scale|
|
43
|
+
# values.map! {|x| x * scale} if key =~ regex
|
44
|
+
# end
|
45
|
+
# else
|
46
|
+
"#{key.gsub(/.*::/, '')} (#{values.to_i.max})"
|
47
|
+
end
|
48
|
+
[name, values]
|
49
|
+
end.sort_by do |key, values|
|
50
|
+
0 - key[/.*?([\d]+)\)$/, 1].to_i
|
51
|
+
end.each do |key, values|
|
52
|
+
g.data(key, values.to_i)
|
53
|
+
end
|
54
|
+
|
55
|
+
labels = {}
|
56
|
+
mod = (@increments.size / 4.0).ceil
|
57
|
+
@increments.each_with_index do |increment, index|
|
58
|
+
labels[index] = increment.split(" ").last if (index % mod).zero?
|
59
|
+
end
|
60
|
+
g.labels = labels
|
61
|
+
|
62
|
+
g.minimum_value = 0
|
63
|
+
# g.maximum_value = @maximum
|
64
|
+
|
65
|
+
g.write(@name.to_filename + ".png")
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.aggregate(data, selector, namer)
|
69
|
+
aggregate_data = {}
|
70
|
+
increments = []
|
71
|
+
data.each_with_index do |frameset, index|
|
72
|
+
increments << frameset.time
|
73
|
+
frameset.data.keys.select do |key|
|
74
|
+
key =~ selector #or key =~ Regexp.new(specials.keys.join('|'))
|
75
|
+
end.each do |key|
|
76
|
+
aggregate_data[key.to_s[namer, 1]] ||= []
|
77
|
+
aggregate_data[key.to_s[namer, 1]][index] += frameset.data[key].to_i
|
78
|
+
end
|
79
|
+
end
|
80
|
+
[aggregate_data, increments]
|
81
|
+
end
|
82
|
+
|
83
|
+
def self.build_all(filename)
|
84
|
+
unless File.exists? filename
|
85
|
+
puts "No data file found: #{filename}"
|
86
|
+
exit
|
87
|
+
end
|
88
|
+
FileUtils.rm_r(DIR) rescue nil
|
89
|
+
Dir.mkdir(DIR)
|
90
|
+
|
91
|
+
Dir.chdir(DIR) do
|
92
|
+
|
93
|
+
puts "Parsing data"
|
94
|
+
data = File.open(filename).readlines.map do |line|
|
95
|
+
Marshal.load Base64.decode64(line)
|
96
|
+
end
|
97
|
+
|
98
|
+
# data_maximum = data.flatten.inject(0) do |max, el|
|
99
|
+
# if el.is_a? Hash
|
100
|
+
# current_max = el.merge({:"real memory" => 0, :"virtual memory" => 0}).values.to_i.max
|
101
|
+
# current_max if max < current_max
|
102
|
+
# end or max
|
103
|
+
# end
|
104
|
+
# mem_maximum = data.flatten.inject(0) do |max, el| # only real memory (RSS) for now
|
105
|
+
# (el["real memory"] if el.is_a?(Hash) and max < el["real memory"]) or max
|
106
|
+
# end
|
107
|
+
# mem_scale = data_maximum / mem_maximum.to_f
|
108
|
+
# scales = {/memory$/ => mem_scale}
|
109
|
+
|
110
|
+
puts "By controller"
|
111
|
+
controller_data, increments = aggregate(data, //, /^(.*?)($|\/|::::)/)
|
112
|
+
Analyze.new(controller_data, increments, "objects by controller").draw
|
113
|
+
|
114
|
+
# in each controller, by action
|
115
|
+
puts "By action"
|
116
|
+
controller_data.keys.each do |controller|
|
117
|
+
puts " ...in #{controller} controller"
|
118
|
+
Dir.descend(controller) do
|
119
|
+
action_data, increments = aggregate(data, /^#{controller}($|\/|::::)/, /\/(.*?)($|\/|::::)/)
|
120
|
+
Analyze.new(action_data, increments, "objects by action in /#{controller}").draw
|
121
|
+
|
122
|
+
# in each action, by object class
|
123
|
+
action_data.keys.each do |action|
|
124
|
+
action = "unknown" if action.to_s == ""
|
125
|
+
puts " ...in #{action} action"
|
126
|
+
Dir.descend(action) do
|
127
|
+
class_data, increments = aggregate(data, /^#{controller}#{"\/#{action}" unless action == "unknown"}($|\/|::::)/,
|
128
|
+
/::::(.*)/)
|
129
|
+
Analyze.new(class_data, increments, "objects by class in /#{controller}/#{action}").draw
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
139
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
|
2
|
+
class BleakHouse
|
3
|
+
cattr_accessor :last_request_name
|
4
|
+
cattr_accessor :dispatch_count
|
5
|
+
cattr_accessor :log_interval
|
6
|
+
|
7
|
+
self.dispatch_count = 0
|
8
|
+
self.log_interval = (ENV['INTERVAL'] and ENV['INTERVAL'].to_i or 10)
|
9
|
+
|
10
|
+
def self.set_request_name request, other = nil
|
11
|
+
self.last_request_name = "#{request.parameters['controller']}/#{request.parameters['action']}/#{request.request_method}#{other}"
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.debug s
|
15
|
+
s = "#{name.underscore}: #{s}"
|
16
|
+
::ActiveRecord::Base.logger.debug s if ::ActiveRecord::Base.logger
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.warn s
|
20
|
+
s = "#{name.underscore}: #{s}"
|
21
|
+
if ::ActiveRecord::Base.logger
|
22
|
+
::ActiveRecord::Base.logger.warn s
|
23
|
+
else
|
24
|
+
$stderr.puts s
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
LOGFILE = "#{RAILS_ROOT}/log/bleak_house_#{RAILS_ENV}.dump"
|
29
|
+
if File.exists?(LOGFILE)
|
30
|
+
File.rename(LOGFILE, "#{LOGFILE}.old")
|
31
|
+
warn "renamed old logfile"
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,23 @@
|
|
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.dispatch_count += 1
|
9
|
+
BleakHouse::MemLogger.snapshot('core rails')
|
10
|
+
end
|
11
|
+
alias_method_chain :prepare_application, :bleak_house
|
12
|
+
|
13
|
+
def reset_after_dispatch_with_bleak_house
|
14
|
+
BleakHouse::MemLogger.snapshot(BleakHouse.last_request_name || 'unknown')
|
15
|
+
if (BleakHouse.dispatch_count % BleakHouse.log_interval).zero?
|
16
|
+
BleakHouse.warn "wrote frameset (#{BleakHouse.dispatch_count} dispatches)"
|
17
|
+
BleakHouse::MemLogger.log(BleakHouse::LOGFILE)
|
18
|
+
end
|
19
|
+
reset_after_dispatch_without_bleak_house
|
20
|
+
end
|
21
|
+
alias_method_chain :reset_after_dispatch, :bleak_house
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,56 @@
|
|
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
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
|
2
|
+
require 'base64'
|
3
|
+
|
4
|
+
class BleakHouse::MemLogger
|
5
|
+
class << self
|
6
|
+
|
7
|
+
SEEN = {}
|
8
|
+
CURRENT = {}
|
9
|
+
TAGS = Hash.new(0)
|
10
|
+
TIMEFORMAT = '%Y-%m-%d %H:%M:%S'
|
11
|
+
VSZ = "virtual memory"
|
12
|
+
RSS = "real memory"
|
13
|
+
|
14
|
+
def log(path, with_mem = false)
|
15
|
+
File.open(path, 'a+') do |log|
|
16
|
+
log.sync = true
|
17
|
+
TAGS[VSZ], TAGS[RSS] = mem_usage if with_mem
|
18
|
+
log.puts Base64.encode64(Marshal.dump([Time.now.strftime(TIMEFORMAT), TAGS])).gsub("\n", '')
|
19
|
+
end
|
20
|
+
GC.start
|
21
|
+
end
|
22
|
+
|
23
|
+
def snapshot(tag)
|
24
|
+
# puts "bleak_house: seen: #{SEEN.size}"
|
25
|
+
CURRENT.clear
|
26
|
+
ObjectSpace.each_object do |obj|
|
27
|
+
CURRENT[obj_id = obj._bleak_house_object_id] = true
|
28
|
+
unless SEEN[obj_id]
|
29
|
+
# symbols will rapidly stabilize; don't worry
|
30
|
+
SEEN[obj_id] = "#{tag}::::#{obj._bleak_house_class}".to_sym
|
31
|
+
TAGS[SEEN[obj_id]] += 1
|
32
|
+
end
|
33
|
+
end
|
34
|
+
# puts "bleak_house: current: #{CURRENT.size}"
|
35
|
+
SEEN.keys.each do |obj_id|
|
36
|
+
TAGS[SEEN.delete(obj_id)] -= 1 unless CURRENT[obj_id]
|
37
|
+
end
|
38
|
+
# puts "bleak_house: done"
|
39
|
+
CURRENT.clear
|
40
|
+
GC.start
|
41
|
+
end
|
42
|
+
|
43
|
+
def mem_usage
|
44
|
+
`ps -x -o 'rss vsz' -p #{Process.pid}`.split(/\s+/)[-2..-1].map{|el| el.to_i}
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class Object
|
51
|
+
alias :_bleak_house_object_id :object_id
|
52
|
+
alias :_bleak_house_class :class
|
53
|
+
end
|
54
|
+
|
File without changes
|
@@ -1,6 +1,4 @@
|
|
1
1
|
|
2
|
-
require 'set'
|
3
|
-
|
4
2
|
class Array
|
5
3
|
alias :time :first
|
6
4
|
alias :data :last
|
@@ -15,24 +13,6 @@ class Array
|
|
15
13
|
|
16
14
|
end
|
17
15
|
|
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
|
-
|
36
16
|
class Dir
|
37
17
|
def self.descend path, &block
|
38
18
|
path = path.split("/") unless path.is_a? Array
|
@@ -64,7 +44,4 @@ class Symbol
|
|
64
44
|
def =~ regex
|
65
45
|
self.to_s =~ regex
|
66
46
|
end
|
67
|
-
def [](*args)
|
68
|
-
self.to_s[*args]
|
69
|
-
end
|
70
47
|
end
|