leakmon 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in leakmon.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Mitsunori Komatsu
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,98 @@
1
+ # Leakmon
2
+
3
+ A Ruby library to monitor leaked objects
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'leakmon'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install leakmon
18
+
19
+ ## Usage
20
+
21
+ If you want to monitor the objects of specific class, then include Leakmon into the class.
22
+
23
+ require 'leakmon'
24
+
25
+ class Foo
26
+ attr_accessor :dummy
27
+ end
28
+
29
+ class Foo
30
+ include Leakmon
31
+ end
32
+
33
+ Foo.release_hook('puts "i am released."') # This is option hook, for debug.
34
+
35
+ 15.times do
36
+ o = Foo.new
37
+ o.dummy = 'x' * 100 * 1024 * 1024 # For GC.
38
+ sleep 0.5
39
+ end
40
+
41
+ Leakmon.list_remaining_objects.each{|rec| p rec} # Array of the remaining objects (not garbage collected).
42
+
43
+ # You can filter the objects by :time keyword.
44
+ Leakmon.list_remaining_objects(:time => 8).each{|rec| p rec} # Only older than 8 sec.
45
+
46
+ Then "i am released." will be printed when some Foo instances are collected. At the last code, the remaining objects will be dumped.
47
+
48
+ i am released.
49
+ :
50
+ i am released.
51
+ ["Foo__fdbef6946", {:time=>Sun Aug 09 00:11:49 +0900 2009, :caller=>["leakmon.rb:136:in `new'", "leakmon.rb:136", "leakmon.rb:135:in `times'", "leakmon.rb:135"]}]
52
+ ["Foo__fdbef8f52", {:time=>Sun Aug 09 00:11:50 +0900 2009, :caller=>["leakmon.rb:136:in `new'", "leakmon.rb:136", "leakmon.rb:135:in `times'", "leakmon.rb:135"]}]
53
+ ["Foo__fdbef8dfe", {:time=>Sun Aug 09 00:11:55 +0900 2009, :caller=>["leakmon.rb:136:in `new'", "leakmon.rb:136", "leakmon.rb:135:in `times'", "leakmon.rb:135"]}]
54
+ ["Foo__fdbef8c28", {:time=>Sun Aug 09 00:11:57 +0900 2009, :caller=>["leakmon.rb:136:in `new'", "leakmon.rb:136", "leakmon.rb:135:in `times'", "leakmon.rb:135"]}]
55
+ ["Foo__fdbef8912", {:time=>Sun Aug 09 00:11:59 +0900 2009, :caller=>["leakmon.rb:136:in `new'", "leakmon.rb:136", "leakmon.rb:135:in `times'", "leakmon.rb:135"]}]
56
+ ["Foo__fdbef87c8", {:time=>Sun Aug 09 00:12:00 +0900 2009, :caller=>["leakmon.rb:136:in `new'", "leakmon.rb:136", "leakmon.rb:135:in `times'", "leakmon.rb:135"]}]
57
+ ["Foo__fdbef84ee", {:time=>Sun Aug 09 00:12:02 +0900 2009, :caller=>["leakmon.rb:136:in `new'", "leakmon.rb:136", "leakmon.rb:135:in `times'", "leakmon.rb:135"]}]
58
+ ["Foo__fdbef8386", {:time=>Sun Aug 09 00:12:04 +0900 2009, :caller=>["leakmon.rb:136:in `new'", "leakmon.rb:136", "leakmon.rb:135:in `times'", "leakmon.rb:135"]}]
59
+ ["Foo__fdbef8110", {:time=>Sun Aug 09 00:12:05 +0900 2009, :caller=>["leakmon.rb:136:in `new'", "leakmon.rb:136", "leakmon.rb:135:in `times'", "leakmon.rb:135"]}]
60
+ ["Foo__fdbef7ea4", {:time=>Sun Aug 09 00:12:07 +0900 2009, :caller=>["leakmon.rb:136:in `new'", "leakmon.rb:136", "leakmon.rb:135:in `times'", "leakmon.rb:135"]}]
61
+ ["Foo__fdbef8f3e", {:time=>Sun Aug 09 00:12:09 +0900 2009, :caller=>["leakmon.rb:136:in `new'", "leakmon.rb:136", "leakmon.rb:135:in `times'", "leakmon.rb:135"]}]
62
+ ["Foo__fdbef8f34", {:time=>Sun Aug 09 00:12:10 +0900 2009, :caller=>["leakmon.rb:136:in `new'", "leakmon.rb:136", "leakmon.rb:135:in `times'", "leakmon.rb:135"]}]
63
+ ["Foo__fdbef8e76", {:time=>Sun Aug 09 00:12:12 +0900 2009, :caller=>["leakmon.rb:136:in `new'", "leakmon.rb:136", "leakmon.rb:135:in `times'", "leakmon.rb:135"]}]
64
+ i am released.
65
+ :
66
+ i am released.
67
+
68
+ You can monitor almost all of classes. Instead of "include Leakmon" in the class definition, call Leakmon.include_in_subclasses. But some classes are not supported, Time (sorry...) and implemented classes in ruby such as String and so on.
69
+
70
+ Leakmon.include_in_subclasses(Object) # If you monitor IO and the sub classes, set the argument IO.
71
+
72
+ Finally, there is TCP/IP interface.
73
+
74
+ Leakmon.tcp_server('0.0.0.0', 4321)
75
+
76
+ And using TCP/IP client, you can monitor the realtime information by 'list' command.
77
+
78
+ $ telnet localhost 4321
79
+ Trying ::1...
80
+ Trying 127.0.0.1...
81
+ Connected to localhost.
82
+ Escape character is '^]'.
83
+ list
84
+ now: Sun Aug 09 00:48:59 +0900 2009
85
+ ["Rational__fdbe7752e", {:time=>Sun Aug 09 00:48:56 +0900 2009, :caller=>["/usr/lib/ruby/1.8/rational.rb:94:in `new'", "/usr/lib/ruby/1.8/rational.rb:94:in `new!'", "/usr/lib/ruby/1.8/rational.rb:337:in `coerce'", "/usr/lib/ruby/1.8/date.rb:503:in `-'", "/usr/lib/ruby/1.8/date.rb:503:in `jd_to_ajd'", "/usr/lib/ruby/1.8/date.rb:754:in `new'", "leakmon.rb:155", "leakmon.rb:154:in `times'", "leakmon.rb:154"]}]
86
+ :
87
+ list 8 <== Only the remaining objects older than 8 sec ago.
88
+ :
89
+ quit <== Command for quit TCP/IP interface.
90
+
91
+
92
+ ## Contributing
93
+
94
+ 1. Fork it
95
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
96
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
97
+ 4. Push to the branch (`git push origin my-new-feature`)
98
+ 5. Create new Pull Request
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+ require 'rspec/core/rake_task'
4
+
5
+ RSpec::Core::RakeTask.new('spec')
6
+
7
+ task :default => :spec
8
+
@@ -0,0 +1,18 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/leakmon/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Mitsunori Komatsu"]
6
+ gem.email = ["komamitsu@gmail.com"]
7
+ gem.description = %q{A Ruby library to monitor leaked objects}
8
+ gem.summary = %q{A Ruby library to monitor leaked objects}
9
+ gem.homepage = ""
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "leakmon"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = Leakmon::VERSION
17
+ gem.add_development_dependency "rspec", "~> 2.13.0"
18
+ end
@@ -0,0 +1,163 @@
1
+ require "leakmon/version"
2
+
3
+ module Leakmon
4
+ class LeakmonString < String; end
5
+
6
+ class LeakmonArray < Array; end
7
+
8
+ class LeakmonHash < Hash; end
9
+
10
+ class LeakmonTime < Time; end
11
+
12
+ class LeakmonMutex < Mutex; end
13
+
14
+ class << self
15
+ def include_with_subclasses(klass = Object)
16
+ ObjectSpace.each_object(class << klass; self; end) do |cls|
17
+ next if out_of_scope?(cls)
18
+ cls.__send__(:include, Leakmon)
19
+ end
20
+ end
21
+
22
+ def list_remaining_objects(cond = {})
23
+ leakmon_mutex.synchronize do
24
+ cond.keys.inject(remaining_objects) {|objs, cond_key|
25
+ new_objs = nil
26
+
27
+ case cond_key
28
+ when :time
29
+ now = LeakmonTime.now
30
+ new_objs = objs.select do |obj_k, obj_v|
31
+ obj_v[:time] < now - cond[cond_key]
32
+ end
33
+ else
34
+ raise "Invalid list option [#{cond_key}]"
35
+ end
36
+
37
+ new_objs
38
+ }.sort_by{|k, v| v[:time]}
39
+ end
40
+ end
41
+
42
+ def clear_remaining_objects
43
+ leakmon_mutex.synchronize do
44
+ @remaining_objects = LeakmonHash.new
45
+ end
46
+ end
47
+
48
+ def included(base)
49
+ class << base
50
+ @leakmon_included ||= false
51
+ return if @leakmon_included
52
+ @leakmon_included = true
53
+ end
54
+
55
+ return unless base.private_methods.include?(:initialize)
56
+ begin
57
+ base.__send__(:alias_method, :initialize_without_leakmon, :initialize)
58
+ rescue NameError
59
+ return
60
+ end
61
+ base.__send__(:alias_method, :initialize, :initialize_with_leakmon)
62
+
63
+ def base.release_hook(proc_str)
64
+ @@leakmon_release_hook = proc_str
65
+ end
66
+ end
67
+
68
+ def tcp_server(host, port)
69
+ require 'thread'
70
+ require 'socket'
71
+
72
+ Thread.new do
73
+ @leakmon_tcp_server = TCPServer.new(host, port)
74
+ @leakmon_tcp_server.setsockopt(:SOCKET, :REUSEADDR, true)
75
+ loop do
76
+ Thread.new(@leakmon_tcp_server.accept) do |c|
77
+ while command_line = c.gets.strip
78
+ next if command_line.empty?
79
+
80
+ command, *args = command_line.split(/\s+/)
81
+
82
+ case command
83
+ when 'list'
84
+ cond = args.empty? ? {} : {:time => Integer(args[0])}
85
+ c.puts "now: #{Time.now}"
86
+ Leakmon.list_remaining_objects(cond).each do |obj|
87
+ c.puts(obj.inspect)
88
+ end
89
+ when 'quit'
90
+ c.close
91
+ Thread.exit
92
+ else
93
+ c.puts 'unknown command'
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
100
+
101
+ def leakmon_register(obj, caller)
102
+ return if out_of_scope?(obj.class)
103
+ leakmon_mutex.synchronize do
104
+ remaining_objects[Leakmon.leakmon_key(obj)] = {:time => Time.now, :caller => caller}
105
+ end
106
+ end
107
+
108
+ def leakmon_key(obj)
109
+ sprintf("%s__%0x", obj.class, obj.object_id)
110
+ end
111
+
112
+ def leakmon_release_proc(klass, key, proc_str)
113
+ proc {
114
+ instance_eval(proc_str)
115
+ leakmon_release(klass, key)
116
+ }
117
+ end
118
+
119
+ def leakmon_release(klass, key)
120
+ return if out_of_scope?(klass)
121
+ leakmon_mutex.synchronize do
122
+ remaining_objects.delete(key)
123
+ end
124
+ end
125
+
126
+ private
127
+ def leakmon_mutex
128
+ @leakmon_mutex ||= LeakmonMutex.new
129
+ @leakmon_mutex
130
+ end
131
+
132
+ def out_of_scope?(klass)
133
+ [Leakmon, LeakmonTime, LeakmonMutex, LeakmonString, LeakmonArray].include?(klass)
134
+ end
135
+
136
+ def remaining_objects
137
+ @remaining_objects ||= LeakmonHash.new
138
+ @remaining_objects
139
+ end
140
+ end
141
+
142
+ private
143
+ def register_leakmon(caller)
144
+ Leakmon.leakmon_register(self, caller)
145
+ @@leakmon_release_hook ||= nil
146
+ prc = Leakmon.leakmon_release_proc(
147
+ self.class,
148
+ Leakmon.leakmon_key(self),
149
+ @@leakmon_release_hook
150
+ )
151
+ ObjectSpace.define_finalizer(self, prc)
152
+ # ObjectSpace.define_finalizer(self, proc {|id| puts "hoge #{id}"})
153
+ end
154
+
155
+ def initialize_with_leakmon(*args, &blk)
156
+ return if caller.detect do |c|
157
+ c =~ /in `initialize_with_leakmon'/ || c =~ /in `include_with_subclasses'/
158
+ end
159
+ initialize_without_leakmon(*args, &blk)
160
+ register_leakmon(caller)
161
+ end
162
+ end
163
+
@@ -0,0 +1,3 @@
1
+ module Leakmon
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,126 @@
1
+ require 'spec_helper'
2
+
3
+ describe Leakmon do
4
+ context "when a class including Leakmon has instantiated some objects finalized or not" do
5
+ class Foo
6
+ include Leakmon
7
+ end
8
+
9
+ total_obj_num = 10
10
+ gc_obj_num = 4
11
+
12
+ $output = StringIO.new
13
+ Foo.release_hook("$output.puts 'hoge'")
14
+
15
+ foos = []
16
+ total_obj_num.times do |i|
17
+ sleep 1 if i == total_obj_num - 1
18
+ foos << Foo.new
19
+ end
20
+
21
+ gc_obj_num.times do |i|
22
+ foos[i] = nil
23
+ end
24
+ GC.start
25
+
26
+ describe "release_fook" do
27
+ it "the block is evaluated whenever the object is finalized" do
28
+ msg_count = 0
29
+ $output.rewind
30
+ $output.each_line do |line|
31
+ msg_count += 1
32
+ line.should == "hoge\n"
33
+ end
34
+ msg_count.should == gc_obj_num
35
+ end
36
+ end
37
+
38
+ describe "list_remaining_objects" do
39
+ it "prints remaining objects to stdout" do
40
+ remains = Leakmon.list_remaining_objects
41
+ remains.size.should == (total_obj_num - gc_obj_num)
42
+ end
43
+ end
44
+
45
+ describe "tcp_server" do
46
+ require 'socket'
47
+ port = 9876
48
+
49
+ Leakmon.tcp_server('0.0.0.0', port)
50
+
51
+ sleep 0.3
52
+ client = TCPSocket.new('127.0.0.1', port)
53
+
54
+ def test_list(client, cmd, expected_obj_count)
55
+ count = 0
56
+ t = Thread.new do
57
+ client.puts cmd
58
+ client.each_line do |l|
59
+ l.should =~ (count.zero? ? %r|\Anow: | : %r|\A\["Foo__|)
60
+ count += 1
61
+ end
62
+ end
63
+ sleep 0.2
64
+ Thread.kill t
65
+ count.should == 1 + expected_obj_count
66
+ end
67
+
68
+ context 'list' do
69
+ it "returns remaining objects as response" do
70
+ test_list(client, 'list', total_obj_num - gc_obj_num)
71
+ end
72
+ end
73
+
74
+ context 'list (sec)' do
75
+ it "returns remaining objects survive the specified seconds as response" do
76
+ test_list(client, 'list 1', total_obj_num - gc_obj_num - 1)
77
+ end
78
+ end
79
+
80
+ context 'quit' do
81
+ it "disconnect the connection" do
82
+ client.puts 'quit'
83
+ client.gets.should be_nil
84
+ end
85
+ end
86
+ end
87
+ end
88
+
89
+ context 'include_with_subclasses' do
90
+ it do
91
+ Leakmon.clear_remaining_objects
92
+
93
+ class User
94
+ attr_accessor :name, :created_at
95
+ def initialize(name)
96
+ @name = name
97
+ @created_at = Time.now
98
+ end
99
+ end
100
+
101
+ class PremiumUser < User
102
+ end
103
+
104
+ Leakmon.include_with_subclasses(Object)
105
+
106
+ users = []
107
+ users << User.new('komamitsu')
108
+ users << User.new('hogehoge')
109
+ users << PremiumUser.new('hogehoge')
110
+
111
+ user_count = 0
112
+ premium_user_count = 0
113
+ Leakmon.list_remaining_objects.each do |obj_info|
114
+ case obj_info.first
115
+ when /\AUser__/ then user_count += 1
116
+ when /\APremiumUser__/ then premium_user_count += 1
117
+ else raise "An unexpected remaining object: #{obj_info.first}"
118
+ end
119
+ end
120
+
121
+ user_count.should eq(2)
122
+ premium_user_count.should eq(1)
123
+ end
124
+ end
125
+ end
126
+
@@ -0,0 +1,7 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+ require 'leakmon'
4
+
5
+ RSpec.configure do |config|
6
+ end
7
+
metadata ADDED
@@ -0,0 +1,68 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: leakmon
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Mitsunori Komatsu
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-04-16 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: &12735160 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 2.13.0
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *12735160
25
+ description: A Ruby library to monitor leaked objects
26
+ email:
27
+ - komamitsu@gmail.com
28
+ executables: []
29
+ extensions: []
30
+ extra_rdoc_files: []
31
+ files:
32
+ - .gitignore
33
+ - Gemfile
34
+ - LICENSE
35
+ - README.md
36
+ - Rakefile
37
+ - leakmon.gemspec
38
+ - lib/leakmon.rb
39
+ - lib/leakmon/version.rb
40
+ - spec/leakmon_spec.rb
41
+ - spec/spec_helper.rb
42
+ homepage: ''
43
+ licenses: []
44
+ post_install_message:
45
+ rdoc_options: []
46
+ require_paths:
47
+ - lib
48
+ required_ruby_version: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ required_rubygems_version: !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ! '>='
58
+ - !ruby/object:Gem::Version
59
+ version: '0'
60
+ requirements: []
61
+ rubyforge_project:
62
+ rubygems_version: 1.8.11
63
+ signing_key:
64
+ specification_version: 3
65
+ summary: A Ruby library to monitor leaked objects
66
+ test_files:
67
+ - spec/leakmon_spec.rb
68
+ - spec/spec_helper.rb