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
data/CHANGELOG
CHANGED
@@ -1,63 +1,10 @@
|
|
1
1
|
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
v4.2.1. Harden patch/build process. Fix bug.
|
11
|
-
|
12
|
-
v4.1.1. Update bundled Ruby to patchlevel 230.
|
13
|
-
|
14
|
-
v4.1. Support calculating deltas in analyze utility.
|
15
|
-
|
16
|
-
v4.0. Track spawn points live instead of sampling.
|
17
|
-
|
18
|
-
v3.7.1. Fix some nil issues with corrupted dumps (cody caughlan).
|
19
|
-
|
20
|
-
v3.7. Sample object contents. Restore Rails 1.2.x compatibility.
|
21
|
-
|
22
|
-
v3.6. Rails 2.0.2 compatibility.
|
23
|
-
|
24
|
-
v3.5.1. Update bundled Ruby to patchlevel 110.
|
25
|
-
|
26
|
-
v3.5. Explain when you need more frames. Use a cross-platform version of 'which'. Slight accuracy improvement.
|
27
|
-
|
28
|
-
v3.4. Clearer output descriptions; work around a Marshal bug on x64; fix for missing immortal leaks.
|
29
|
-
|
30
|
-
v3.3. Build Ruby in gem install step; bundle Ruby 1.8.6 source; fixes for truncated final frames.
|
31
|
-
|
32
|
-
v3.2. Use Ccsv for faster parsing.
|
33
|
-
|
34
|
-
v3.0.3. Caching; build task; impact factor.
|
35
|
-
|
36
|
-
v3.0.1. Rewrite. Remove Gruffs and graphs. Become a standard compiled extension. Walk the symbol table. Track object histories. Use custom CSV format instead of YAML.
|
37
|
-
|
38
|
-
v2.7. Version renumbering; apologies if this messes anyone up.
|
39
|
-
|
40
|
-
v2.6. RDoc documentation; use new Echoe setup; fix for namespaced controllers.
|
41
|
-
|
42
|
-
v2.5. Detect if core keys are present or not; fixes for analyzing non-Rails apps.
|
43
|
-
|
44
|
-
v2.4. Log heap usage; remove pure-Ruby profiler.
|
45
|
-
|
46
|
-
v2.3. Subtract Rails core counts from action counts and join paired frames.
|
47
|
-
|
48
|
-
v2.2. Change smoothing algorithm to be more intuitive and so mem usage is correct.
|
49
|
-
|
50
|
-
v2.1. Close file properly.
|
51
|
-
|
52
|
-
v2. C instrumentation with YAML output.
|
53
|
-
|
54
|
-
v1.3. Log memory usage on *nix.
|
55
|
-
|
56
|
-
v1.2. Fix nil directory traversal error on Windows.
|
57
|
-
|
58
|
-
v1.1. Gem version
|
59
|
-
|
60
|
-
v1. Dumped Rublique; replaced with a lightweight non-delta marshalling version.
|
61
|
-
|
62
|
-
v0. First version
|
2
|
+
BleakHouse Changelog
|
3
|
+
|
4
|
+
5. totally awesome C instrumentation
|
5
|
+
4. log memory usage on *nix
|
6
|
+
3.1. fix nil directory traversal error on windows
|
7
|
+
3. gem version
|
8
|
+
2. dumped rublique once I understood what it did; replaced with a lightweight non-delta marshalling version
|
9
|
+
1. first version
|
63
10
|
|
data/{LICENSE → LICENSE_AFL}
RENAMED
File without changes
|
data/Manifest
CHANGED
@@ -1,21 +1,22 @@
|
|
1
|
-
CHANGELOG
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
lib/bleak_house.rb
|
15
|
-
lib/bleak_house/
|
16
|
-
lib/bleak_house/
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
1
|
+
./CHANGELOG
|
2
|
+
./LICENSE_AFL
|
3
|
+
./Manifest
|
4
|
+
./README
|
5
|
+
./Rakefile
|
6
|
+
./init.rb
|
7
|
+
./install.rb
|
8
|
+
./lib/bleak_house/LICENSE_BSD
|
9
|
+
./lib/bleak_house/action_controller.rb
|
10
|
+
./lib/bleak_house/analyze.rb
|
11
|
+
./lib/bleak_house/bleak_house.rb
|
12
|
+
./lib/bleak_house/c.rb
|
13
|
+
./lib/bleak_house/dispatcher.rb
|
14
|
+
./lib/bleak_house/gruff_hacks.rb
|
15
|
+
./lib/bleak_house/mem_logger.rb
|
16
|
+
./lib/bleak_house/rake_task_redefine_task.rb
|
17
|
+
./lib/bleak_house/ruby.rb
|
18
|
+
./lib/bleak_house/support_methods.rb
|
19
|
+
./lib/bleak_house.rb
|
20
|
+
./patches/gc.c.patch
|
21
|
+
./tasks/bleak_house_tasks.rake
|
22
|
+
./test/unit/test_bleak_house.rb
|
data/README
CHANGED
@@ -1,125 +1,41 @@
|
|
1
1
|
|
2
2
|
BleakHouse
|
3
3
|
|
4
|
-
|
4
|
+
REQUIREMENTS:
|
5
5
|
|
6
|
-
|
7
|
-
Run git clone and then rake gem;
|
8
|
-
gem install bleak_house
|
6
|
+
A *nix operating system. Windows is not supported.
|
9
7
|
|
10
|
-
|
8
|
+
Gems: 'gruff', 'rmagick', 'active_support', 'RubyInline'
|
11
9
|
|
12
|
-
|
10
|
+
It is highly recommended that you compile the bleak_house patched
|
11
|
+
Ruby build. In the plugin directory:
|
12
|
+
rake ruby:build
|
13
13
|
|
14
|
-
|
14
|
+
USAGE:
|
15
15
|
|
16
|
-
|
16
|
+
To profile your application:
|
17
|
+
RAILS_ENV=production BLEAK_HOUSE=true ruby-bleak-house ./script/server
|
18
|
+
|
19
|
+
Browse around manually, thrash your entire app with a script,
|
20
|
+
target troublesome controllers/actions, etc.
|
17
21
|
|
18
|
-
|
22
|
+
Then, to analyze:
|
23
|
+
RAILS_ENV=production SMOOTHNESS=2 rake bleak_house:analyze
|
19
24
|
|
20
|
-
|
21
|
-
* minimal impact on runtime performance
|
22
|
-
* fast analysis step
|
23
|
-
* tracks all objects allocated on the heap, including internal types like <tt>T_NODE</tt>
|
24
|
-
* easy integration into any program, not just Rails
|
25
|
+
And then look in log/bleak_house/.
|
25
26
|
|
26
|
-
|
27
|
+
TROUBLESHOOTING
|
27
28
|
|
28
|
-
|
29
|
-
|
29
|
+
If you see the error "Symbol not found: _rb_gc_heaps_used",
|
30
|
+
it means you installed the patched binary, but tried to run the
|
31
|
+
server with the regular binary.
|
30
32
|
|
31
|
-
|
33
|
+
COPYRIGHT AND LICENSING
|
32
34
|
|
33
|
-
|
35
|
+
Copyright (c) 2007 Cloudburst, LLC. See the included LICENSE_AFL
|
36
|
+
file.
|
34
37
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
The installation takes a long time because it compiles a patched Ruby 1.8.7 binary for you. It is installed as <tt>ruby-bleak-house</tt> alongside your regular <tt>ruby</tt> binary.
|
39
|
-
|
40
|
-
Please see the forum ( http://rubyforge.org/forum/forum.php?forum_id=13983 ) if you have installation problems.
|
41
|
-
|
42
|
-
== Usage
|
43
|
-
|
44
|
-
We will profile a Rails app as an example. Note that BleakHouse works equally well in any Ruby program.
|
45
|
-
|
46
|
-
First, to setup the app for profiling, add the following at the bottom of <tt>config/environment.rb</tt>:
|
47
|
-
require 'bleak_house' if ENV['BLEAK_HOUSE']
|
48
|
-
|
49
|
-
Then, to engage the logger (possibly in a live deployment situation), start a server instance as so:
|
50
|
-
RAILS_ENV=production BLEAK_HOUSE=1 ruby-bleak-house ./script/server
|
51
|
-
|
52
|
-
Look for the message:
|
53
|
-
** Bleakhouse: installed
|
54
|
-
|
55
|
-
Exercise your app. After a couple hundred requests, hit CTRL-C. The server will stop and BleakHouse will produce a dumpfile in <tt>/tmp</tt>:
|
56
|
-
|
57
|
-
** BleakHouse: working...
|
58
|
-
** BleakHouse: complete
|
59
|
-
** Bleakhouse: run 'bleak /tmp/bleak.5979.000.dump' to analyze.
|
60
|
-
|
61
|
-
To analyze it, just run the listed command. The top 20 leakiest lines will be listed:
|
62
|
-
|
63
|
-
191691 total objects
|
64
|
-
Final heap size 191691 filled, 220961 free
|
65
|
-
Displaying top 20 most common line/class pairs
|
66
|
-
89513 __null__:__null__:__node__
|
67
|
-
41438 __null__:__null__:String
|
68
|
-
2348 /opt/local//lib/ruby/site_ruby/1.8/rubygems/specification.rb:557:Array
|
69
|
-
1508 /opt/local//lib/ruby/gems/1.8/specifications/gettext-1.90.0.gemspec:14:String
|
70
|
-
1021 /opt/local//lib/ruby/gems/1.8/specifications/heel-0.2.0.gemspec:14:String
|
71
|
-
951 /opt/local//lib/ruby/site_ruby/1.8/rubygems/version.rb:111:String
|
72
|
-
935 /opt/local//lib/ruby/site_ruby/1.8/rubygems/specification.rb:557:String
|
73
|
-
834 /opt/local//lib/ruby/site_ruby/1.8/rubygems/version.rb:146:Array
|
74
|
-
...
|
75
|
-
|
76
|
-
You can pass an integer as the second parameter to <tt>bleak</tt> if you want to see more lines than the default.
|
77
|
-
|
78
|
-
The underscored types are special Ruby internal structs, but can be real leaks just as easily as fullblown classes.
|
79
|
-
|
80
|
-
= Extras
|
81
|
-
|
82
|
-
== Injecting a signal
|
83
|
-
|
84
|
-
You can send <tt>SIGUSR2</tt> to a BleakHouse-instrumented program to snag a dump at any time. Once the dump completes, the program will continue to run. Dumps are named based on the host process id, and sequential dumps are numbered in ascending order.
|
85
|
-
|
86
|
-
== Tips
|
87
|
-
|
88
|
-
Do not try to detect Rails leaks in <tt>development</tt> mode. Make a separate <tt>benchmark</tt> environment if you need to, and make sure all your production caching is turned on.
|
89
|
-
|
90
|
-
It is normal to see lots of <tt>null:null</tt> references, especially for nodes. Using <tt>eval()</tt> too much can be a cause of node leaks. You can sometimes track <tt>eval()</tt> by using sourceline macros in your code:
|
91
|
-
|
92
|
-
eval("CODE", nil, __FILE__, __LINE__)
|
93
|
-
|
94
|
-
You may get library require errors if you install <tt>ruby-bleak-house</tt> 1.8.7 alongside a different verson of Ruby. You could try to patch your local version of Ruby instead, or you can get <tt>ruby-bleak-house</tt> to lie about its version. Just make sure that the <tt>bleak-house</tt> library is the first thing required (even before Rubygems):
|
95
|
-
|
96
|
-
ruby-bleak-house -I `ruby -e 'puts \`gem which bleak_house\`.split("\n")[1][0..-16]'` -rbleak_house
|
97
|
-
|
98
|
-
It is not recommended that you use <tt>ruby-bleak-house</tt> as your production Ruby binary, since it will be slightly slower and use slightly more memory. It is unlikely, however, to affect stability.
|
99
|
-
|
100
|
-
If BleakHouse doesn't report any heap growth but you still have memory growth, you might have a broken C extension, or have encounted a {real leak in the interpreter}[http://groups.google.com/group/god-rb/browse_thread/thread/01cca2b7c4a581c2]. Try using Valgrind[http://blog.evanweaver.com/articles/2008/02/05/valgrind-and-ruby/] instead.
|
101
|
-
|
102
|
-
== Methods
|
103
|
-
|
104
|
-
The easiest way to fix a leak is to make it repeatable.
|
105
|
-
|
106
|
-
First, write a script that exercises your app in a deterministic way. Run it for a small number of loops; then run <tt>bleak</tt>. Then run it for a larger number of loops, and run <tt>bleak</tt> again. The lines that grow significantly between runs are your leaks for that codepath.
|
107
|
-
|
108
|
-
Now, look at those lines in the source and try to figure out what references them. Where do the return values go? Add some breakpoints or output backtraces to <tt>STDERR</tt> as you go. Eventually you should find a point where it is relatively clear that a reference is getting maintained.
|
109
|
-
|
110
|
-
Try to remove that reference, run your script again, and see if the object counts have dropped.
|
111
|
-
|
112
|
-
== Reporting problems
|
113
|
-
|
114
|
-
The support forum is here[http://rubyforge.org/forum/forum.php?forum_id=13983].
|
115
|
-
|
116
|
-
Patches and contributions are very welcome. Please note that contributors are required to assign copyright for their additions to Cloudburst, LLC.
|
117
|
-
|
118
|
-
== Further resources
|
119
|
-
|
120
|
-
* http://blog.evanweaver.com/articles/2008/04/06/bleakhouse-4/
|
121
|
-
* http://blog.evanweaver.com/articles/2008/02/05/valgrind-and-ruby/
|
122
|
-
* http://blog.evanweaver.com/articles/2007/05/12/let-me-hit-you-with-some-knowledge
|
123
|
-
* http://blog.evanweaver.com/articles/2007/05/06/leak-proof-direct-heap-instrumentation-for-bleak_house
|
124
|
-
* http://blog.evanweaver.com/articles/2007/04/28/bleak_house
|
38
|
+
Portions of lib/bleak_house/c.rb copyright (c) 2006 Eric Hodel and
|
39
|
+
used under license (see included the lib/bleak_house/LICENSE_BSD
|
40
|
+
file).
|
125
41
|
|
data/Rakefile
CHANGED
@@ -1,12 +1,104 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'lib/bleak_house/rake_task_redefine_task'
|
1
4
|
|
2
|
-
|
5
|
+
DIR = File.dirname(__FILE__)
|
3
6
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
7
|
+
begin
|
8
|
+
require 'rake/clean'
|
9
|
+
gem 'echoe', '>= 1.2'
|
10
|
+
require 'echoe'
|
11
|
+
require 'fileutils'
|
12
|
+
include FileUtils
|
13
|
+
|
14
|
+
CLEAN.include ['**/.*.sw?', '*.gem', '.config']
|
15
|
+
VERS = `cat CHANGELOG`[/^([\d\.]+)\. /, 1]
|
16
|
+
|
17
|
+
taskmsg = File.open(DIR + "/tasks/bleak_house_tasks.rake").readlines
|
18
|
+
taskmsg = taskmsg[0..3] + [taskmsg[7][2..-1]] + taskmsg[9..-1]
|
19
|
+
|
20
|
+
echoe = Echoe.new("bleak_house", VERS) do |p|
|
21
|
+
p.author = "Evan Weaver"
|
22
|
+
p.rubyforge_name = "fauna"
|
23
|
+
p.name = "bleak_house"
|
24
|
+
p.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."
|
25
|
+
p.changes = `cat CHANGELOG`[/^([\d\.]+\. .*)/, 1]
|
26
|
+
p.email = "evan at cloudbur dot st"
|
27
|
+
p.summary = p.description
|
28
|
+
p.url = "http://blog.evanweaver.com"
|
29
|
+
p.need_tar = false
|
30
|
+
p.need_tar_gz = true
|
31
|
+
p.test_globs = ["*_test.rb"]
|
32
|
+
p.extra_deps = ["RubyInline"]
|
33
|
+
p.clean_globs = CLEAN
|
34
|
+
p.spec_extras = {:post_install_message =>
|
35
|
+
"
|
36
|
+
Thanks for installing Bleak House #{VERS}.
|
37
|
+
|
38
|
+
For each Rails app you want to profile, you will need to add the following
|
39
|
+
rake task in RAILS_ROOT/lib/tasks/bleak_house_tasks.rake to be able to run
|
40
|
+
the analyzer:
|
41
|
+
" + taskmsg.join(" ") + "\n"}
|
42
|
+
end
|
43
|
+
|
44
|
+
rescue LoadError => boom
|
45
|
+
puts "You are missing a dependency required for meta-operations on this gem."
|
46
|
+
puts "#{boom.to_s.capitalize}."
|
47
|
+
|
48
|
+
desc 'Run tests.'
|
49
|
+
task :default => :test
|
50
|
+
end
|
51
|
+
|
52
|
+
desc 'Run tests.'
|
53
|
+
Rake::Task.redefine_task("test") do
|
54
|
+
system "ruby-bleak-house -Ibin:lib:test test/unit/test_bleak_house.rb #{ENV['METHOD'] ? "--name=#{ENV['METHOD']}" : ""}"
|
12
55
|
end
|
56
|
+
|
57
|
+
desc 'Build a patched binary.'
|
58
|
+
namespace :ruby do
|
59
|
+
task :build do
|
60
|
+
if RUBY_PLATFORM =~ /win32|windows/
|
61
|
+
puts "ERROR: windows not supported."
|
62
|
+
exit
|
63
|
+
end
|
64
|
+
require 'fileutils'
|
65
|
+
puts "building patched Ruby binary"
|
66
|
+
tmp = "/tmp/"
|
67
|
+
Dir.chdir(tmp) do
|
68
|
+
build_dir = "bleak_house"
|
69
|
+
binary_dir = File.dirname(`which ruby`)
|
70
|
+
FileUtils.rm_rf(build_dir) rescue nil
|
71
|
+
Dir.mkdir(build_dir)
|
72
|
+
begin
|
73
|
+
Dir.chdir(build_dir) do
|
74
|
+
puts " downloading Ruby source"
|
75
|
+
bz2 = "ruby-1.8.6.tar.bz2"
|
76
|
+
system("wget 'http://rubyforge.org/frs/download.php/20425/ruby-1.8.6.tar.bz2' &> wget.log")
|
77
|
+
puts " extracting"
|
78
|
+
system("tar xjf #{bz2} &> tar.log")
|
79
|
+
File.delete bz2
|
80
|
+
Dir.chdir("ruby-1.8.6") do
|
81
|
+
puts " patching" or system("patch -p0 < \'#{DIR}/patches/gc.c.patch\' &> ../patch.log")
|
82
|
+
puts " configuring" or system("./configure --prefix=#{binary_dir[0..-5]} &> ../configure.log") # --with-static-linked-ext
|
83
|
+
puts " making" or system("make &> ../make.log")
|
84
|
+
|
85
|
+
binary = "#{binary_dir}/ruby-bleak-house"
|
86
|
+
puts " installing as #{binary}"
|
87
|
+
FileUtils.cp("./ruby", binary)
|
88
|
+
FileUtils.chmod(0755, binary)
|
89
|
+
puts " done"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
rescue Object => e
|
93
|
+
puts "ERROR: please see the last modified log file in #{tmp}#{build_dir}, perhaps\nit will contain a clue."
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
#def check_configure_size
|
100
|
+
# if (size = File.size("configure")) != 476155
|
101
|
+
# raise "Configure size is wrong (got #{size})"
|
102
|
+
# end
|
103
|
+
#end
|
104
|
+
|
data/init.rb
ADDED
data/install.rb
ADDED
data/lib/bleak_house.rb
CHANGED
@@ -1,22 +1,13 @@
|
|
1
1
|
|
2
|
-
|
3
|
-
raise "This build of Ruby has not been successfully patched for BleakHouse."
|
4
|
-
end
|
5
|
-
|
6
|
-
RUBY_VERSION = `ruby -v`.split(" ")[1]
|
7
|
-
require 'snapshot'
|
8
|
-
require 'bleak_house/hook'
|
2
|
+
if ENV['BLEAK_HOUSE']
|
9
3
|
|
10
|
-
|
11
|
-
private :ext_snapshot
|
12
|
-
end
|
4
|
+
require 'dispatcher' # rails
|
13
5
|
|
14
|
-
|
6
|
+
require 'bleak_house/bleak_house'
|
7
|
+
require 'bleak_house/mem_logger'
|
8
|
+
require 'bleak_house/dispatcher'
|
9
|
+
require 'bleak_house/action_controller'
|
15
10
|
|
16
|
-
|
17
|
-
|
18
|
-
# before dumping the heap.
|
19
|
-
def self.snapshot(logfile, gc_runs = 3)
|
20
|
-
ext_snapshot(logfile, gc_runs)
|
21
|
-
end
|
11
|
+
BleakHouse.warn "enabled (log/bleak_house_#{RAILS_ENV}.dump)"
|
12
|
+
|
22
13
|
end
|
File without changes
|
@@ -0,0 +1,16 @@
|
|
1
|
+
|
2
|
+
class ActionController::Base
|
3
|
+
class << self
|
4
|
+
def process_with_bleak_house(request, *args)
|
5
|
+
BleakHouse.set_request_name request
|
6
|
+
process_without_bleak_house(request, *args)
|
7
|
+
end
|
8
|
+
alias_method_chain :process, :bleak_house
|
9
|
+
|
10
|
+
def process_with_exception_with_bleak_house(request, *args)
|
11
|
+
BleakHouse.set_request_name request, "/error"
|
12
|
+
process_with_exception_without_bleak_house(request, *args)
|
13
|
+
end
|
14
|
+
alias_method_chain :process_with_exception, :bleak_house
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
|
2
|
+
require 'rubygems'
|
3
|
+
require 'fileutils'
|
4
|
+
require 'yaml'
|
5
|
+
require 'active_support'
|
6
|
+
|
7
|
+
gem 'gruff', '= 0.2.8'
|
8
|
+
require 'gruff'
|
9
|
+
|
10
|
+
# require, but make rdoc not whine
|
11
|
+
load "#{File.dirname(__FILE__)}/gruff_hacks.rb"
|
12
|
+
load "#{File.dirname(__FILE__)}/support_methods.rb"
|
13
|
+
|
14
|
+
Gruff::Base::LEFT_MARGIN = 200
|
15
|
+
Gruff::Base::NEGATIVE_TOP_MARGIN = 30
|
16
|
+
Gruff::Base::MAX_LEGENDS = 28
|
17
|
+
|
18
|
+
class BleakHouse
|
19
|
+
class Analyze
|
20
|
+
|
21
|
+
SMOOTHNESS = ((i = ENV['SMOOTHNESS'].to_i) < 2 ? 2 : i)/2 * 2
|
22
|
+
MEM_KEY = "memory usage"
|
23
|
+
DIR = "#{RAILS_ROOT}/log/bleak_house/"
|
24
|
+
|
25
|
+
def initialize(data, increments, name)
|
26
|
+
@data = data
|
27
|
+
@increments = increments
|
28
|
+
@name = name
|
29
|
+
end
|
30
|
+
|
31
|
+
def draw
|
32
|
+
g = Gruff::Line.new("1024x768")
|
33
|
+
g.title = @name
|
34
|
+
g.x_axis_label = "time"
|
35
|
+
g.legend_font_size = g.legend_box_size = 14
|
36
|
+
g.title_font_size = 24
|
37
|
+
g.marker_font_size = 14
|
38
|
+
|
39
|
+
@data.map do |key, values|
|
40
|
+
["#{(key.to_s.empty? ? '[Unknown]' : key).gsub(/.*::/, '')} (#{key == MEM_KEY ? "relative" : values.to_i.max})", values] # hax
|
41
|
+
end.sort_by do |key, values|
|
42
|
+
0 - key[/.*?([\d]+)\)$/, 1].to_i
|
43
|
+
end.each do |key, values|
|
44
|
+
g.data(key, values.to_i)
|
45
|
+
end
|
46
|
+
|
47
|
+
labels = {}
|
48
|
+
mod = (@increments.size / 4.0).ceil
|
49
|
+
@increments.each_with_index do |increment, index|
|
50
|
+
labels[index] = increment if (index % mod).zero?
|
51
|
+
end
|
52
|
+
g.labels = labels
|
53
|
+
|
54
|
+
g.minimum_value = 0
|
55
|
+
# g.maximum_value = @maximum
|
56
|
+
|
57
|
+
g.write(@name.to_filename + ".png")
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.aggregate(data, selector, namer)
|
61
|
+
aggregate_data = {}
|
62
|
+
increments = []
|
63
|
+
data.each_with_index do |frameset, index|
|
64
|
+
increments << frameset.time
|
65
|
+
frameset.data.keys.select do |key|
|
66
|
+
key =~ selector #or key =~ Regexp.new(specials.keys.join('|'))
|
67
|
+
end.each do |key|
|
68
|
+
aggregate_data[key.to_s[namer, 1]] ||= []
|
69
|
+
aggregate_data[key.to_s[namer, 1]][index] += frameset.data[key].to_i
|
70
|
+
end
|
71
|
+
end
|
72
|
+
[aggregate_data, increments]
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.build_all(filename)
|
76
|
+
unless File.exists? filename
|
77
|
+
puts "No data file found: #{filename}"
|
78
|
+
exit
|
79
|
+
end
|
80
|
+
FileUtils.rm_r(DIR) rescue nil
|
81
|
+
Dir.mkdir(DIR)
|
82
|
+
|
83
|
+
Dir.chdir(DIR) do
|
84
|
+
|
85
|
+
puts "parsing data"
|
86
|
+
data = YAML.load_file(filename)
|
87
|
+
data = data[0..(-1 - data.size % SMOOTHNESS)]
|
88
|
+
data = data.in_groups_of(SMOOTHNESS).map do |frames|
|
89
|
+
timestamp = frames.map(&:time).sum / SMOOTHNESS
|
90
|
+
values = frames.map(&:data).inject(Hash.new(0)) do |total, this_frame|
|
91
|
+
this_frame.each do |key, value|
|
92
|
+
total[key] += value
|
93
|
+
end
|
94
|
+
total
|
95
|
+
end
|
96
|
+
[Time.at(timestamp).strftime("%H:%M:%S"), values]
|
97
|
+
end
|
98
|
+
puts "#{data.size} frames after smoothing"
|
99
|
+
|
100
|
+
puts "entire app"
|
101
|
+
controller_data, increments = aggregate(data, //, /^(.*?)($|\/|::::)/)
|
102
|
+
if controller_data.has_key? MEM_KEY
|
103
|
+
controller_data_without_memory = controller_data.dup
|
104
|
+
controller_data_without_memory.delete(MEM_KEY)
|
105
|
+
scale_factor = controller_data_without_memory.values.flatten.to_i.max / controller_data[MEM_KEY].max.to_f * 0.8 rescue 1
|
106
|
+
controller_data[MEM_KEY] = controller_data[MEM_KEY].map{|x| (x * scale_factor).to_i }
|
107
|
+
end
|
108
|
+
Analyze.new(controller_data, increments, "objects by controller").draw
|
109
|
+
|
110
|
+
# in each controller, by action
|
111
|
+
controller_data.keys.each do |controller|
|
112
|
+
@mem = (controller == MEM_KEY)
|
113
|
+
puts(@mem ? " #{controller}" : " action for #{controller} controller")
|
114
|
+
Dir.descend(controller) do
|
115
|
+
action_data, increments = aggregate(data, /^#{controller}($|\/|::::)/, /\/(.*?)($|\/|::::)/)
|
116
|
+
Analyze.new(action_data, increments, @mem ? "#{controller} in kilobytes" : "objects by action in /#{controller}_controller").draw
|
117
|
+
|
118
|
+
# in each action, by object class
|
119
|
+
action_data.keys.each do |action|
|
120
|
+
action = "unknown" if action.to_s == ""
|
121
|
+
puts " class for #{action} action"
|
122
|
+
Dir.descend(action) do
|
123
|
+
class_data, increments = aggregate(data, /^#{controller}#{"\/#{action}" unless action == "unknown"}($|\/|::::)/,
|
124
|
+
/::::(.*)/)
|
125
|
+
Analyze.new(class_data, increments, "objects by class in /#{controller}/#{action}").draw
|
126
|
+
end
|
127
|
+
end unless @mem
|
128
|
+
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
end
|
135
|
+
end
|