leakmon 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.
@@ -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