bleak_house 3.0.1 → 3.1
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 +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
|