dike 0.0.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/README +175 -0
- data/bin/dike +66 -0
- data/dike-0.0.1.gem +0 -0
- data/gemspec.rb +28 -0
- data/install.rb +210 -0
- data/lib/dike.rb +264 -0
- metadata +53 -0
data/README
ADDED
@@ -0,0 +1,175 @@
|
|
1
|
+
NAME
|
2
|
+
|
3
|
+
dike
|
4
|
+
|
5
|
+
SYNOPSIS
|
6
|
+
|
7
|
+
a simple memory leak detector for ruby with preconfigured rails' hooks.
|
8
|
+
|
9
|
+
INSTALL
|
10
|
+
|
11
|
+
gem install dike
|
12
|
+
|
13
|
+
URIS
|
14
|
+
|
15
|
+
http://www.codeforpeople.com/lib/ruby/
|
16
|
+
http://rubyforge.org/projects/codeforpeople/
|
17
|
+
|
18
|
+
DESCRIPTION
|
19
|
+
|
20
|
+
the concept behind is simple: Object is extended in order that the location
|
21
|
+
of each objects' creation is tracked. a summarizer command is given to walk
|
22
|
+
ObjectSpace using each objects class and the location if it's creation to
|
23
|
+
detect memory leaks. not all leaks can be detected and some that are may
|
24
|
+
not really be leaks, but dike provided a simple way to see the hotspots in
|
25
|
+
your code that may potentially be leaking.
|
26
|
+
|
27
|
+
EXAMPLES
|
28
|
+
|
29
|
+
PURE RUBY
|
30
|
+
|
31
|
+
require 'dike'
|
32
|
+
|
33
|
+
Dike.log STDERR # the default
|
34
|
+
|
35
|
+
Thread.new do
|
36
|
+
sleep 4.2 and Dike.finger
|
37
|
+
end
|
38
|
+
|
39
|
+
Main.start
|
40
|
+
|
41
|
+
|
42
|
+
RAILS
|
43
|
+
|
44
|
+
file:RAILS_ROOT/config/environment.rb
|
45
|
+
...
|
46
|
+
require 'dike'
|
47
|
+
|
48
|
+
shell: ./script/server
|
49
|
+
|
50
|
+
shell: curl --silent http://localhost:3000
|
51
|
+
|
52
|
+
shell: cat ./log/dike/0
|
53
|
+
---
|
54
|
+
- class: String
|
55
|
+
count: 90769
|
56
|
+
trace: []
|
57
|
+
- class: Array
|
58
|
+
count: 18931
|
59
|
+
trace: []
|
60
|
+
- class: Class
|
61
|
+
count: 2
|
62
|
+
trace:
|
63
|
+
- /Users/ahoward/site/votelink.com/public/../config/../lib/widgets.rb:222:in `class_factory'
|
64
|
+
- /Users/ahoward/site/votelink.com/public/../config/../lib/widgets.rb:220:in `each'
|
65
|
+
- /Users/ahoward/site/votelink.com/public/../config/../lib/widgets.rb:220:in `class_factory'
|
66
|
+
- /Users/ahoward/site/votelink.com/public/../config/../lib/widgets.rb:248:in `Widget'
|
67
|
+
- /Users/ahoward/site/votelink.com/public/../config/../lib/widgets/page/base.rb:1
|
68
|
+
- /Users/ahoward/site/votelink.com/public/../config/../lib/widgets.rb:31:in `require'
|
69
|
+
- /Users/ahoward/site/votelink.com/public/../config/../lib/widgets.rb:31:in `load'
|
70
|
+
- /Users/ahoward/site/votelink.com/public/../config/../lib/widgets.rb:16:in `for_controller'
|
71
|
+
- /Users/ahoward/site/votelink.com/public/../config/../lib/widgets.rb:243:in `widget'
|
72
|
+
- /Users/ahoward/site/votelink.com/public/../config/../app/controllers/application.rb:150
|
73
|
+
...
|
74
|
+
|
75
|
+
shell: curl --silent http://localhost:3000
|
76
|
+
|
77
|
+
shell: cat ./log/dike/1
|
78
|
+
---
|
79
|
+
- class: String
|
80
|
+
count: 100769
|
81
|
+
trace: []
|
82
|
+
- class: Array
|
83
|
+
count: 19931
|
84
|
+
trace: []
|
85
|
+
- class: Class
|
86
|
+
count: 5
|
87
|
+
trace:
|
88
|
+
- /Users/ahoward/site/votelink.com/public/../config/../lib/widgets.rb:222:in `class_factory'
|
89
|
+
- /Users/ahoward/site/votelink.com/public/../config/../lib/widgets.rb:220:in `each'
|
90
|
+
- /Users/ahoward/site/votelink.com/public/../config/../lib/widgets.rb:220:in `class_factory'
|
91
|
+
- /Users/ahoward/site/votelink.com/public/../config/../lib/widgets.rb:248:in `Widget'
|
92
|
+
- /Users/ahoward/site/votelink.com/public/../config/../lib/widgets/page/base.rb:1
|
93
|
+
- /Users/ahoward/site/votelink.com/public/../config/../lib/widgets.rb:31:in `require'
|
94
|
+
- /Users/ahoward/site/votelink.com/public/../config/../lib/widgets.rb:31:in `load'
|
95
|
+
- /Users/ahoward/site/votelink.com/public/../config/../lib/widgets.rb:16:in `for_controller'
|
96
|
+
- /Users/ahoward/site/votelink.com/public/../config/../lib/widgets.rb:243:in `widget'
|
97
|
+
- /Users/ahoward/site/votelink.com/public/../config/../app/controllers/application.rb:150
|
98
|
+
...
|
99
|
+
|
100
|
+
shell: dike ./log/dike
|
101
|
+
---
|
102
|
+
- class: Proc
|
103
|
+
count: 65
|
104
|
+
trace:
|
105
|
+
- /opt/local/lib/ruby/1.8/cgi/session.rb:165:in `new'
|
106
|
+
- /opt/local/lib/ruby/1.8/cgi/session.rb:165:in `callback'
|
107
|
+
- /opt/local/lib/ruby/1.8/cgi/session.rb:299:in `initialize'
|
108
|
+
- /Users/ahoward/src/ruby/dike/dike-0.0.1/lib/dike.rb:233:in `new'
|
109
|
+
- /Users/ahoward/src/ruby/dike/dike-0.0.1/lib/dike.rb:233:in `call'
|
110
|
+
- /Users/ahoward/src/ruby/dike/dike-0.0.1/lib/dike.rb:233:in `new'
|
111
|
+
- /Users/ahoward/site/votelink.com/public/../config/../vendor/rails/actionpack/lib/action_contr oller/cgi_process.rb:123:in `session'
|
112
|
+
...
|
113
|
+
|
114
|
+
SUMMARY
|
115
|
+
|
116
|
+
* the 'Dike.finger' method dumps it's log in a format showing
|
117
|
+
|
118
|
+
class : the class of object being leaked/allocated
|
119
|
+
count : the number instances leaked from the trace location
|
120
|
+
trace : the trace location of object birth
|
121
|
+
|
122
|
+
* loading into a rails environment causes snapshots of the above format to
|
123
|
+
be dumped into RAILS_ROOT/log/dike/ after each request. each snapshot is
|
124
|
+
incrementally numbered 0, 1, ...
|
125
|
+
|
126
|
+
* the 'dike' command line tool can be used in two ways
|
127
|
+
|
128
|
+
dike directory/with/logs/dike/
|
129
|
+
|
130
|
+
dike old_dump new_dump
|
131
|
+
|
132
|
+
if given a directory 'old_dump' and 'new_dump' are auto-calculated by
|
133
|
+
scanning the directory. in either case the tool dups a delta running old
|
134
|
+
-->> new. the delta shows only changes from old to new, so a line like
|
135
|
+
|
136
|
+
- class: Proc
|
137
|
+
count: 3
|
138
|
+
...
|
139
|
+
|
140
|
+
means that 3 Proc objects were created between the two dumps. note that,
|
141
|
+
when given a directory, the default old and new dumps are the oldest and
|
142
|
+
newest dumps respectively, to get fine grained information sumarizing the
|
143
|
+
changes between two requests give the files manually, for example
|
144
|
+
|
145
|
+
dike ./log/dike/41 ./log/dike/42
|
146
|
+
|
147
|
+
* options that affect logging
|
148
|
+
|
149
|
+
- Dike.filter pattern
|
150
|
+
|
151
|
+
pattern must respond to '===' and each object in ObjectSpace will be
|
152
|
+
compared against it. for example
|
153
|
+
|
154
|
+
Dile.filter Array
|
155
|
+
|
156
|
+
would cause logging to restrict itself to Array, or sublcasses of
|
157
|
+
Array, only
|
158
|
+
|
159
|
+
- Dike.log io
|
160
|
+
|
161
|
+
set the dike logging object. the object should respond to 'puts'.
|
162
|
+
|
163
|
+
- Dike.logfactory directory
|
164
|
+
|
165
|
+
cause logging to occur into a new log for each call the 'Dike.finger'.
|
166
|
+
the logs will be auto numbered 0, 1, ...
|
167
|
+
|
168
|
+
LIMITATIONS
|
169
|
+
|
170
|
+
not all object creation can be tracked and not all leaks are reported.
|
171
|
+
|
172
|
+
AUTHOR
|
173
|
+
|
174
|
+
ara [dot] t [dot] howard [at] gmail [dot] com
|
175
|
+
|
data/bin/dike
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
require "yaml"
|
3
|
+
require "orderedhash"
|
4
|
+
|
5
|
+
### TODO objectify - this is crap
|
6
|
+
|
7
|
+
### parse argv
|
8
|
+
help = ARGV.delete("-h") || ARGV.delete("--help") || ARGV.delete("help")
|
9
|
+
|
10
|
+
a, b, *ignored = ARGV
|
11
|
+
|
12
|
+
if help or a.nil?
|
13
|
+
puts "dike (directory || old_dump new_dump)"
|
14
|
+
exit 42
|
15
|
+
end
|
16
|
+
|
17
|
+
### load/determine files
|
18
|
+
if b
|
19
|
+
old_dump = a
|
20
|
+
new_dump = b
|
21
|
+
else
|
22
|
+
directory = a
|
23
|
+
Dir.chdir directory do
|
24
|
+
list = Dir.glob "*"
|
25
|
+
list = list.grep(%r/^[0-9]+$/).map{|entry| entry.to_i}
|
26
|
+
a, b = list.min, list.max
|
27
|
+
abort "not enough dumps" unless((a and b) and (a != b))
|
28
|
+
old_dump = File.join directory, a.to_s
|
29
|
+
new_dump = File.join directory, b.to_s
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
### compute stats
|
34
|
+
a = open(old_dump){|fd| YAML.load fd}
|
35
|
+
a_index = {}
|
36
|
+
a.each{|record| a_index[record["trace"]] = record}
|
37
|
+
|
38
|
+
b = open(new_dump){|fd| YAML.load fd}
|
39
|
+
b_index = {}
|
40
|
+
b.each{|record| b_index[record["trace"]] = record}
|
41
|
+
|
42
|
+
### generate report
|
43
|
+
report = []
|
44
|
+
|
45
|
+
(a_index.keys + b_index.keys).uniq.each do |trace|
|
46
|
+
record = OrderedHash.new
|
47
|
+
|
48
|
+
a_record = a_index[trace] || {}
|
49
|
+
b_record = b_index[trace] || {}
|
50
|
+
|
51
|
+
klass = a_record["class"] || b_record["class"]
|
52
|
+
count = b_record["count"].to_i - a_record["count"].to_i
|
53
|
+
|
54
|
+
next unless count > 0
|
55
|
+
|
56
|
+
record["class"] = klass
|
57
|
+
record["count"] = count
|
58
|
+
record["trace"] = trace.clone
|
59
|
+
|
60
|
+
report << record
|
61
|
+
end
|
62
|
+
|
63
|
+
report =
|
64
|
+
report.sort_by{|record| [-record["count"], record["class"]]}
|
65
|
+
|
66
|
+
y report
|
data/dike-0.0.1.gem
ADDED
File without changes
|
data/gemspec.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
|
2
|
+
lib, version = File::basename(File::dirname(File::expand_path(__FILE__))).split %r/-/, 2
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
|
6
|
+
Gem::Specification::new do |spec|
|
7
|
+
$VERBOSE = nil
|
8
|
+
spec.name = lib
|
9
|
+
spec.version = version
|
10
|
+
spec.platform = Gem::Platform::RUBY
|
11
|
+
spec.summary = lib
|
12
|
+
|
13
|
+
spec.files = Dir::glob "**/**"
|
14
|
+
spec.executables = Dir::glob("bin/*").map{|exe| File::basename exe}
|
15
|
+
|
16
|
+
spec.require_path = "lib"
|
17
|
+
spec.autorequire = lib
|
18
|
+
|
19
|
+
spec.has_rdoc = File::exist? "doc"
|
20
|
+
spec.test_suite_file = "test/#{ lib }.rb" if File::directory? "test"
|
21
|
+
#spec.add_dependency 'lib', '>= version'
|
22
|
+
|
23
|
+
spec.extensions << "extconf.rb" if File::exists? "extconf.rb"
|
24
|
+
|
25
|
+
spec.author = "Ara T. Howard"
|
26
|
+
spec.email = "ara.t.howard@gmail.com"
|
27
|
+
spec.homepage = "http://codeforpeople.com/lib/ruby/#{ lib }/"
|
28
|
+
end
|
data/install.rb
ADDED
@@ -0,0 +1,210 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'rbconfig'
|
3
|
+
require 'find'
|
4
|
+
require 'ftools'
|
5
|
+
require 'tempfile'
|
6
|
+
include Config
|
7
|
+
|
8
|
+
LIBDIR = "lib"
|
9
|
+
LIBDIR_MODE = 0644
|
10
|
+
|
11
|
+
BINDIR = "bin"
|
12
|
+
BINDIR_MODE = 0755
|
13
|
+
|
14
|
+
|
15
|
+
$srcdir = CONFIG["srcdir"]
|
16
|
+
$version = CONFIG["MAJOR"]+"."+CONFIG["MINOR"]
|
17
|
+
$libdir = File.join(CONFIG["libdir"], "ruby", $version)
|
18
|
+
$archdir = File.join($libdir, CONFIG["arch"])
|
19
|
+
$site_libdir = $:.find {|x| x =~ /site_ruby$/}
|
20
|
+
$bindir = CONFIG["bindir"] || CONFIG['BINDIR']
|
21
|
+
$ruby_install_name = CONFIG['ruby_install_name'] || CONFIG['RUBY_INSTALL_NAME'] || 'ruby'
|
22
|
+
$ruby_ext = CONFIG['EXEEXT'] || ''
|
23
|
+
$ruby = File.join($bindir, ($ruby_install_name + $ruby_ext))
|
24
|
+
|
25
|
+
if !$site_libdir
|
26
|
+
$site_libdir = File.join($libdir, "site_ruby")
|
27
|
+
elsif $site_libdir !~ %r/#{Regexp.quote($version)}/
|
28
|
+
$site_libdir = File.join($site_libdir, $version)
|
29
|
+
end
|
30
|
+
|
31
|
+
def install_rb(srcdir=nil, destdir=nil, mode=nil, bin=nil)
|
32
|
+
#{{{
|
33
|
+
path = []
|
34
|
+
dir = []
|
35
|
+
Find.find(srcdir) do |f|
|
36
|
+
next unless FileTest.file?(f)
|
37
|
+
next if (f = f[srcdir.length+1..-1]) == nil
|
38
|
+
next if (/CVS$/ =~ File.dirname(f))
|
39
|
+
next if f =~ %r/\.lnk/
|
40
|
+
path.push f
|
41
|
+
dir |= [File.dirname(f)]
|
42
|
+
end
|
43
|
+
for f in dir
|
44
|
+
next if f == "."
|
45
|
+
next if f == "CVS"
|
46
|
+
File::makedirs(File.join(destdir, f))
|
47
|
+
end
|
48
|
+
for f in path
|
49
|
+
next if (/\~$/ =~ f)
|
50
|
+
next if (/^\./ =~ File.basename(f))
|
51
|
+
unless bin
|
52
|
+
File::install(File.join(srcdir, f), File.join(destdir, f), mode, true)
|
53
|
+
else
|
54
|
+
from = File.join(srcdir, f)
|
55
|
+
to = File.join(destdir, f)
|
56
|
+
shebangify(from) do |sf|
|
57
|
+
$deferr.print from, " -> ", File::catname(from, to), "\n"
|
58
|
+
$deferr.printf "chmod %04o %s\n", mode, to
|
59
|
+
File::install(sf, to, mode, false)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
#}}}
|
64
|
+
end
|
65
|
+
def shebangify f
|
66
|
+
#{{{
|
67
|
+
open(f) do |fd|
|
68
|
+
buf = fd.read 42
|
69
|
+
if buf =~ %r/^\s*#\s*!.*ruby/o
|
70
|
+
ftmp = Tempfile::new("#{ $$ }_#{ File::basename(f) }")
|
71
|
+
begin
|
72
|
+
fd.rewind
|
73
|
+
ftmp.puts "#!#{ $ruby }"
|
74
|
+
while((buf = fd.read(8192)))
|
75
|
+
ftmp.write buf
|
76
|
+
end
|
77
|
+
ftmp.close
|
78
|
+
yield ftmp.path
|
79
|
+
ensure
|
80
|
+
ftmp.close!
|
81
|
+
end
|
82
|
+
else
|
83
|
+
yield f
|
84
|
+
end
|
85
|
+
end
|
86
|
+
#}}}
|
87
|
+
end
|
88
|
+
def ARGV.switch
|
89
|
+
#{{{
|
90
|
+
return nil if self.empty?
|
91
|
+
arg = self.shift
|
92
|
+
return nil if arg == '--'
|
93
|
+
if arg =~ /^-(.)(.*)/
|
94
|
+
return arg if $1 == '-'
|
95
|
+
raise 'unknown switch "-"' if $2.index('-')
|
96
|
+
self.unshift "-#{$2}" if $2.size > 0
|
97
|
+
"-#{$1}"
|
98
|
+
else
|
99
|
+
self.unshift arg
|
100
|
+
nil
|
101
|
+
end
|
102
|
+
#}}}
|
103
|
+
end
|
104
|
+
def ARGV.req_arg
|
105
|
+
#{{{
|
106
|
+
self.shift || raise('missing argument')
|
107
|
+
#}}}
|
108
|
+
end
|
109
|
+
def linkify d, linked = []
|
110
|
+
#--{{{
|
111
|
+
if test ?d, d
|
112
|
+
versioned = Dir[ File::join(d, "*-[0-9].[0-9].[0-9].rb") ]
|
113
|
+
versioned.each do |v|
|
114
|
+
src, dst = v, v.gsub(%r/\-[\d\.]+\.rb$/, '.rb')
|
115
|
+
lnk = nil
|
116
|
+
begin
|
117
|
+
if test ?l, dst
|
118
|
+
lnk = "#{ dst }.lnk"
|
119
|
+
puts "#{ dst } -> #{ lnk }"
|
120
|
+
File::rename dst, lnk
|
121
|
+
end
|
122
|
+
unless test ?e, dst
|
123
|
+
puts "#{ src } -> #{ dst }"
|
124
|
+
File::copy src, dst
|
125
|
+
linked << dst
|
126
|
+
end
|
127
|
+
ensure
|
128
|
+
if lnk
|
129
|
+
at_exit do
|
130
|
+
puts "#{ lnk } -> #{ dst }"
|
131
|
+
File::rename lnk, dst
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
linked
|
138
|
+
#--}}}
|
139
|
+
end
|
140
|
+
|
141
|
+
|
142
|
+
#
|
143
|
+
# main program
|
144
|
+
#
|
145
|
+
|
146
|
+
libdir = $site_libdir
|
147
|
+
bindir = $bindir
|
148
|
+
no_linkify = false
|
149
|
+
linked = nil
|
150
|
+
help = false
|
151
|
+
|
152
|
+
usage = <<-usage
|
153
|
+
#{ File::basename $0 }
|
154
|
+
-d, --destdir <destdir>
|
155
|
+
-l, --libdir <libdir>
|
156
|
+
-b, --bindir <bindir>
|
157
|
+
-r, --ruby <ruby>
|
158
|
+
-n, --no_linkify
|
159
|
+
-s, --sudo
|
160
|
+
-h, --help
|
161
|
+
usage
|
162
|
+
|
163
|
+
begin
|
164
|
+
while switch = ARGV.switch
|
165
|
+
case switch
|
166
|
+
when '-d', '--destdir'
|
167
|
+
libdir = ARGV.req_arg
|
168
|
+
when '-l', '--libdir'
|
169
|
+
libdir = ARGV.req_arg
|
170
|
+
when '-b', '--bindir'
|
171
|
+
bindir = ARGV.req_arg
|
172
|
+
when '-r', '--ruby'
|
173
|
+
$ruby = ARGV.req_arg
|
174
|
+
when '-n', '--no_linkify'
|
175
|
+
no_linkify = true
|
176
|
+
when '-s', '--sudo'
|
177
|
+
sudo = 'sudo'
|
178
|
+
when '-h', '--help'
|
179
|
+
help = true
|
180
|
+
else
|
181
|
+
raise "unknown switch #{switch.dump}"
|
182
|
+
end
|
183
|
+
end
|
184
|
+
rescue
|
185
|
+
STDERR.puts $!.to_s
|
186
|
+
STDERR.puts usage
|
187
|
+
exit 1
|
188
|
+
end
|
189
|
+
|
190
|
+
if help
|
191
|
+
STDOUT.puts usage
|
192
|
+
exit
|
193
|
+
end
|
194
|
+
|
195
|
+
system "#{ sudo } #{ $ruby } pre-install.rb" if test(?s, 'pre-install.rb')
|
196
|
+
|
197
|
+
unless no_linkify
|
198
|
+
linked = linkify('lib') + linkify('bin')
|
199
|
+
end
|
200
|
+
|
201
|
+
system "#{ $ruby } extconf.rb && make && #{ sudo } make install" if test(?s, 'extconf.rb')
|
202
|
+
|
203
|
+
install_rb(LIBDIR, libdir, LIBDIR_MODE)
|
204
|
+
install_rb(BINDIR, bindir, BINDIR_MODE, bin=true)
|
205
|
+
|
206
|
+
if linked
|
207
|
+
linked.each{|path| File::rm_f path}
|
208
|
+
end
|
209
|
+
|
210
|
+
system "#{ sudo } #{ $ruby } post-install.rb" if test(?s, 'post-install.rb')
|
data/lib/dike.rb
ADDED
@@ -0,0 +1,264 @@
|
|
1
|
+
require "yaml"
|
2
|
+
require "attributes"
|
3
|
+
require "orderedhash"
|
4
|
+
|
5
|
+
module Dike
|
6
|
+
class LogFactory
|
7
|
+
attribute "directory"
|
8
|
+
attribute "current"
|
9
|
+
|
10
|
+
def initialize directory = "dike"
|
11
|
+
require "fileutils"
|
12
|
+
FileUtils.mkdir_p directory
|
13
|
+
@directory = directory
|
14
|
+
list = Dir.glob(File.join(@directory, "*"))
|
15
|
+
list = list.grep(%r/^[0-9]+$/).map{|entry| entry.to_i}
|
16
|
+
@current = list.max || -1
|
17
|
+
end
|
18
|
+
|
19
|
+
def next &block
|
20
|
+
if block
|
21
|
+
open File.join(@directory, self.next.to_s), "w", &block
|
22
|
+
else
|
23
|
+
@current += 1
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class << self
|
29
|
+
Objects = Hash.new
|
30
|
+
|
31
|
+
def mark_birth object, stacktrace
|
32
|
+
return if Objects[object_id]
|
33
|
+
object_id = Object::Methods["object_id"].bind(object).call
|
34
|
+
Objects[object_id] = stacktrace
|
35
|
+
ObjectSpace.define_finalizer object, &mark_birth_finalizer(object_id)
|
36
|
+
end
|
37
|
+
|
38
|
+
def mark_birth_finalizer object_id
|
39
|
+
lambda{ Objects.delete object_id }
|
40
|
+
end
|
41
|
+
|
42
|
+
Ignore = Hash.new
|
43
|
+
|
44
|
+
Ignore[Ignore.object_id] = true
|
45
|
+
|
46
|
+
class << Ignore
|
47
|
+
def transaction
|
48
|
+
state = clone
|
49
|
+
Ignore[state.object_id] = true
|
50
|
+
yield
|
51
|
+
ensure
|
52
|
+
clear
|
53
|
+
update state
|
54
|
+
Ignore.delete state.object_id
|
55
|
+
state = nil
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def ignore *list
|
60
|
+
list.flatten.each do |object|
|
61
|
+
object_id = ::Object::Methods["object_id"].bind(object).call
|
62
|
+
Ignore[object_id] = true
|
63
|
+
#ObjectSpace.define_finalizer object, &ignore_finalizer(object_id)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def ignored &block
|
68
|
+
object = block.call
|
69
|
+
ignore object
|
70
|
+
object
|
71
|
+
end
|
72
|
+
|
73
|
+
def ignore_finalizer object_id
|
74
|
+
lambda{ Ignore.delete object_id }
|
75
|
+
end
|
76
|
+
|
77
|
+
attribute("filter"){ Object }
|
78
|
+
attribute("threshold"){ Struct.new(:class, :code, :object)[42, 42, 1] }
|
79
|
+
attribute("log"){ STDERR }
|
80
|
+
attribute("logfactory"){ nil }
|
81
|
+
|
82
|
+
def finger options = {}
|
83
|
+
Thread.critical = true
|
84
|
+
|
85
|
+
begin
|
86
|
+
GC.start
|
87
|
+
|
88
|
+
count, code = :remembered
|
89
|
+
|
90
|
+
Ignore.transaction do
|
91
|
+
count = Hash.new 0
|
92
|
+
ignore count
|
93
|
+
|
94
|
+
code = Hash.new do |h,k|
|
95
|
+
sh = Hash.new 0
|
96
|
+
ignore sh
|
97
|
+
h[k] = sh
|
98
|
+
end
|
99
|
+
ignore code
|
100
|
+
|
101
|
+
ObjectSpace.each_object(filter) do |object|
|
102
|
+
m = Object::Methods["object_id"].bind object
|
103
|
+
ignore m
|
104
|
+
object_id = m.call
|
105
|
+
|
106
|
+
next if Ignore[object_id]
|
107
|
+
|
108
|
+
m = Object::Methods["class"].bind object
|
109
|
+
ignore m
|
110
|
+
klass = m.call
|
111
|
+
|
112
|
+
defined_at = Objects[object_id]
|
113
|
+
count[klass] += 1
|
114
|
+
code[klass][defined_at] += 1
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
GC.start
|
119
|
+
|
120
|
+
worst_klasses =
|
121
|
+
count.to_a.sort_by{|pair| pair.last}.last(threshold.class).reverse
|
122
|
+
|
123
|
+
count.clear
|
124
|
+
count = nil
|
125
|
+
|
126
|
+
=begin
|
127
|
+
report = []
|
128
|
+
=end
|
129
|
+
total = 0
|
130
|
+
|
131
|
+
logging do |log|
|
132
|
+
log.puts "---"
|
133
|
+
|
134
|
+
worst_klasses.each do |klass, count|
|
135
|
+
worst_code = code[klass].to_a.sort_by{|pair| pair.last}.last(threshold.code).reverse
|
136
|
+
|
137
|
+
name = Class::Methods["name"].bind(klass).call.to_s
|
138
|
+
name = Class::Methods["inspect"].bind(klass).call.to_s if name.empty?
|
139
|
+
name = 'UNKNOWN' if name.empty?
|
140
|
+
|
141
|
+
worst_code.each do |stacktrace, count|
|
142
|
+
next unless count > threshold.object
|
143
|
+
=begin
|
144
|
+
|
145
|
+
TODO - figure out why the hell yaml leaks so bad!
|
146
|
+
|
147
|
+
report << OrderedHash[
|
148
|
+
'class', name,
|
149
|
+
'count', count,
|
150
|
+
'stacktrace', (stacktrace ? stacktrace.clone : []),
|
151
|
+
]
|
152
|
+
=end
|
153
|
+
trace = stacktrace ? stacktrace.clone : []
|
154
|
+
|
155
|
+
### roll our own because yaml leaks!
|
156
|
+
log.puts "- class: #{ name }"
|
157
|
+
log.puts " count: #{ count }"
|
158
|
+
if trace.empty?
|
159
|
+
log.puts " trace: []"
|
160
|
+
else
|
161
|
+
log.puts " trace:"
|
162
|
+
trace.each do |line|
|
163
|
+
log.puts " - #{ line }"
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
worst_code.clear
|
169
|
+
worst_code = nil
|
170
|
+
|
171
|
+
total += count
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
=begin
|
176
|
+
logging do |log|
|
177
|
+
log.puts report.to_yaml
|
178
|
+
log.flush
|
179
|
+
end
|
180
|
+
|
181
|
+
report.clear
|
182
|
+
report = nil
|
183
|
+
GC.start
|
184
|
+
=end
|
185
|
+
|
186
|
+
worst_klasses.clear
|
187
|
+
worst_klasses = nil
|
188
|
+
|
189
|
+
code.clear
|
190
|
+
code = nil
|
191
|
+
|
192
|
+
GC.start
|
193
|
+
|
194
|
+
total
|
195
|
+
ensure
|
196
|
+
Thread.critical = false
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
def logging &block
|
201
|
+
logfactory ? logfactory.next(&block) : block.call(log)
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
class ::Object
|
206
|
+
Methods = instance_methods.inject(Hash.new){|h, m| h.update m => instance_method(m)}
|
207
|
+
Methods["initialize"] = instance_method "initialize"
|
208
|
+
Dike.ignore Methods
|
209
|
+
Methods.each{|k,v| Dike.ignore k, v}
|
210
|
+
|
211
|
+
verbose = $VERBOSE
|
212
|
+
begin
|
213
|
+
$VERBOSE = nil
|
214
|
+
def initialize *a, &b
|
215
|
+
Methods["initialize"].bind(self).call *a, &b
|
216
|
+
ensure
|
217
|
+
Dike.mark_birth self, caller rescue nil
|
218
|
+
end
|
219
|
+
ensure
|
220
|
+
$VERBOSE = verbose
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
class ::Class
|
225
|
+
Methods = instance_methods.inject(Hash.new){|h, m| h.update m => instance_method(m)}
|
226
|
+
Dike.ignore Methods
|
227
|
+
Methods.each{|k,v| Dike.ignore k, v}
|
228
|
+
|
229
|
+
verbose = $VERBOSE
|
230
|
+
begin
|
231
|
+
$VERBOSE = nil
|
232
|
+
def new *a, &b
|
233
|
+
object = Methods["new"].bind(self).call *a, &b
|
234
|
+
ensure
|
235
|
+
Dike.mark_birth object, caller rescue nil
|
236
|
+
end
|
237
|
+
def allocate *a, &b
|
238
|
+
object = Methods["allocate"].bind(self).call *a, &b
|
239
|
+
ensure
|
240
|
+
Dike.mark_birth object, caller rescue nil
|
241
|
+
end
|
242
|
+
ensure
|
243
|
+
$VERBOSE = verbose
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
class ::Module
|
248
|
+
Methods = instance_methods.inject(Hash.new){|h, m| h.update m => instance_method(m)}
|
249
|
+
Dike.ignore Methods
|
250
|
+
Methods.each{|k,v| Dike.ignore k, v}
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
|
255
|
+
if defined? Rails
|
256
|
+
Dike.logfactory Dike::LogFactory.new(File.join(RAILS_ROOT, "log", "dike"))
|
257
|
+
|
258
|
+
ActionController::Base.module_eval do
|
259
|
+
after_filter do |controller|
|
260
|
+
Dike.finger
|
261
|
+
true
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|
metadata
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
rubygems_version: 0.9.2
|
3
|
+
specification_version: 1
|
4
|
+
name: dike
|
5
|
+
version: !ruby/object:Gem::Version
|
6
|
+
version: 0.0.1
|
7
|
+
date: 2007-09-27 00:00:00 -06:00
|
8
|
+
summary: dike
|
9
|
+
require_paths:
|
10
|
+
- lib
|
11
|
+
email: ara.t.howard@gmail.com
|
12
|
+
homepage: http://codeforpeople.com/lib/ruby/dike/
|
13
|
+
rubyforge_project:
|
14
|
+
description:
|
15
|
+
autorequire: dike
|
16
|
+
default_executable:
|
17
|
+
bindir: bin
|
18
|
+
has_rdoc: false
|
19
|
+
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">"
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.0.0
|
24
|
+
version:
|
25
|
+
platform: ruby
|
26
|
+
signing_key:
|
27
|
+
cert_chain:
|
28
|
+
post_install_message:
|
29
|
+
authors:
|
30
|
+
- Ara T. Howard
|
31
|
+
files:
|
32
|
+
- bin
|
33
|
+
- bin/dike
|
34
|
+
- dike-0.0.1.gem
|
35
|
+
- gemspec.rb
|
36
|
+
- install.rb
|
37
|
+
- lib
|
38
|
+
- lib/dike.rb
|
39
|
+
- README
|
40
|
+
test_files: []
|
41
|
+
|
42
|
+
rdoc_options: []
|
43
|
+
|
44
|
+
extra_rdoc_files: []
|
45
|
+
|
46
|
+
executables:
|
47
|
+
- dike
|
48
|
+
extensions: []
|
49
|
+
|
50
|
+
requirements: []
|
51
|
+
|
52
|
+
dependencies: []
|
53
|
+
|