click 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/.gitignore ADDED
@@ -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/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in click.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Mark Rushakoff
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.
data/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # Click
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'click'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install click
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/click-console ADDED
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'click/database'
4
+ require 'click/database/writer'
5
+ require 'click/clicker'
6
+ require 'pry'
7
+
8
+ module Click::Database
9
+ module Models
10
+ default_connection_string = 'sqlite:/'
11
+ connection_string = "#{ARGV.fetch(0, default_connection_string)}"
12
+ puts "Using connection string: #{connection_string}"
13
+
14
+ Click::Database.with_database(connection_string) do |db|
15
+ clicker = Click::Clicker.new
16
+ clicker.add_observer(Click::Database::Writer.new(db))
17
+
18
+ puts
19
+ puts "A clicker was initialized to write to that database."
20
+ puts "You can access it as `clicker`."
21
+
22
+ binding.pry(quiet: true)
23
+ end
24
+ end
25
+ end
data/click.gemspec ADDED
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'click/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "click"
8
+ spec.version = Click::VERSION
9
+ spec.authors = ["Mark Rushakoff"]
10
+ spec.email = ["mark.rushakoff@gmail.com"]
11
+ spec.description = %q{A tool to help track down the source of a memory leak in a Ruby process}
12
+ spec.summary = %q{Track the changes in counts of live objects in your Ruby process.}
13
+ spec.homepage = "https://github.com/mark-rushakoff/click"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "rake"
23
+ spec.add_development_dependency "rspec", "~> 2.14"
24
+ spec.add_development_dependency "sqlite3"
25
+ spec.add_development_dependency "pry"
26
+
27
+ spec.add_dependency "sequel"
28
+ end
@@ -0,0 +1,35 @@
1
+ module Click
2
+ class Clicker
3
+ def click!
4
+ observers.each { |o| o.before_click(self) }
5
+
6
+ ObjectSpace.garbage_collect
7
+
8
+ @state = Hash.new(0)
9
+ ObjectSpace.each_object do |object|
10
+ @state[object.class] += 1
11
+ end
12
+
13
+ @state[Symbol] = Symbol.all_symbols.count
14
+
15
+ observers.each { |o| o.after_click(self) }
16
+ end
17
+
18
+ def object_counts
19
+ @state.dup
20
+ end
21
+
22
+ def instance_count(klass)
23
+ @state.fetch(klass, 0)
24
+ end
25
+
26
+ def add_observer(observer)
27
+ observers << observer
28
+ end
29
+
30
+ private
31
+ def observers
32
+ @observers ||= []
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,11 @@
1
+ require 'sequel'
2
+
3
+ module Click
4
+ module Database
5
+ module Models
6
+ class ObjectCount < Sequel::Model
7
+ many_to_one :snapshot
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,21 @@
1
+ require 'sequel'
2
+
3
+ module Click
4
+ module Database
5
+ module Models
6
+ class Snapshot < Sequel::Model
7
+ one_to_many :object_counts
8
+
9
+ dataset_module do
10
+ def after(time)
11
+ filter { timestamp > time }
12
+ end
13
+
14
+ def before(time)
15
+ filter { timestamp < time}
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,2 @@
1
+ require 'click/database/models/snapshot'
2
+ require 'click/database/models/object_count'
@@ -0,0 +1,25 @@
1
+ require 'click/observer'
2
+
3
+ module Click
4
+ module Database
5
+ class Writer
6
+ include Click::Observer
7
+
8
+ def initialize(db)
9
+ @db = db
10
+ end
11
+
12
+ def after_click(clicker)
13
+ snapshot = Models::Snapshot.create(timestamp: Time.now)
14
+ object_count_hashes = clicker.object_counts.map do |klass, count|
15
+ {snapshot_id: snapshot.id, class_name: klass.to_s, count: count}
16
+ end
17
+
18
+ Models::ObjectCount.dataset.multi_insert(object_count_hashes)
19
+ end
20
+
21
+ private
22
+ attr_reader :db
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,45 @@
1
+ require 'sequel'
2
+
3
+ module Click
4
+ module Database
5
+ class << self
6
+ def with_database(connection_string)
7
+ _with_db(Sequel.connect(connection_string)) do |db|
8
+ yield db
9
+ end
10
+ end
11
+
12
+ def with_in_memory_database
13
+ require 'sqlite3'
14
+ _with_db(Sequel.sqlite) do |db|
15
+ yield db
16
+ end
17
+ end
18
+
19
+ private
20
+ def _with_db(db)
21
+ ensure_tables!(db)
22
+ Sequel::Model.db = db
23
+ require 'click/database/models'
24
+ yield db
25
+ ensure
26
+ db = nil
27
+ Sequel::Model.db = nil
28
+ end
29
+
30
+ def ensure_tables!(db)
31
+ db.create_table?(:snapshots) do
32
+ primary_key :id
33
+ Time :timestamp, null: false
34
+ end
35
+
36
+ db.create_table?(:object_counts) do
37
+ primary_key :id
38
+ foreign_key :snapshot_id, :snapshots, null: false
39
+ String :class_name, null: false
40
+ Integer :count, null: false
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,9 @@
1
+ module Click
2
+ module Observer
3
+ def before_click(clicker)
4
+ end
5
+
6
+ def after_click(clicker)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,3 @@
1
+ module Click
2
+ VERSION = "0.0.1"
3
+ end
data/lib/click.rb ADDED
@@ -0,0 +1,5 @@
1
+ require 'click/version'
2
+ require 'click/clicker'
3
+
4
+ module Click
5
+ end
@@ -0,0 +1,7 @@
1
+ require 'spec_helper'
2
+
3
+ describe Click do
4
+ it 'should have a version number' do
5
+ Click::VERSION.should_not be_nil
6
+ end
7
+ end
@@ -0,0 +1,57 @@
1
+ require 'spec_helper'
2
+
3
+ describe Click::Clicker do
4
+ subject(:clicker) { described_class.new }
5
+
6
+ describe '#instance_count' do
7
+ it 'counts instances of a single class' do
8
+ klass = Class.new
9
+
10
+ clicker.click!
11
+ expect(clicker.instance_count(klass)).to eq(0)
12
+
13
+ obj = klass.new
14
+
15
+ clicker.click!
16
+ expect(clicker.instance_count(klass)).to eq(1)
17
+
18
+ obj = nil
19
+
20
+ clicker.click!
21
+ expect(clicker.instance_count(klass)).to eq(0)
22
+ end
23
+
24
+ it 'tracks differences in symbols' do
25
+ difference = 100
26
+
27
+ expect {
28
+ difference.times { |i| [Time.now.to_i, i].join('_-_-_').to_sym }
29
+ }.to change {
30
+ clicker.click!
31
+ clicker.instance_count(Symbol)
32
+ }.by_at_least(difference)
33
+ end
34
+ end
35
+
36
+ describe '#object_counts' do
37
+ it "groups objects by their class" do
38
+ ObjectSpace.should_receive(:each_object).and_yield(Object.new).and_yield(":)").and_yield(":(")
39
+ Symbol.should_receive(:all_symbols).and_return([:foo, :bar, :baz])
40
+
41
+ clicker.click!
42
+ expect(clicker.object_counts).to be_a(Hash)
43
+ expect(clicker.object_counts.to_a).to eq([[Object, 1], [String, 2], [Symbol, 3]])
44
+ end
45
+ end
46
+
47
+ describe 'observers' do
48
+ it 'calls before_click and after_click' do
49
+ observer = double(before_click: nil, after_click: nil)
50
+ observer.should_receive(:before_click).with(clicker).ordered
51
+ observer.should_receive(:after_click).with(clicker).ordered
52
+
53
+ clicker.add_observer(observer)
54
+ clicker.click!
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,7 @@
1
+ require 'spec_helper'
2
+ require 'click/database'
3
+
4
+ initialize_models
5
+
6
+ describe Click::Database::Models::ObjectCount do
7
+ end
@@ -0,0 +1,40 @@
1
+ require 'spec_helper'
2
+ require 'click/database'
3
+
4
+ initialize_models
5
+
6
+ describe Click::Database::Models::Snapshot do
7
+ describe 'scopes' do
8
+ describe '.after' do
9
+ it 'excludes records prior to the given time' do
10
+ with_in_memory_db do
11
+ described_class.create(timestamp: Date.new(2000))
12
+ later = described_class.create(timestamp: Date.new(2006))
13
+
14
+ expect(described_class.after(Date.new(2003)).all).to eq([later])
15
+ end
16
+ end
17
+ end
18
+
19
+ describe '.before' do
20
+ it 'excludes records after the given time' do
21
+ with_in_memory_db do
22
+ earlier = described_class.create(timestamp: Date.new(2000))
23
+ described_class.create(timestamp: Date.new(2006))
24
+
25
+ expect(described_class.before(Date.new(2003)).all).to eq([earlier])
26
+ end
27
+ end
28
+ end
29
+
30
+ it 'can combine scopes' do
31
+ with_in_memory_db do
32
+ described_class.create(timestamp: Date.new(2000))
33
+ middle = described_class.create(timestamp: Date.new(2003))
34
+ described_class.create(timestamp: Date.new(2006))
35
+
36
+ expect(described_class.after(Date.new(2001)).before(Date.new(2005)).all).to eq([middle])
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+ require 'click/database'
3
+ require 'click/database/writer'
4
+
5
+ describe Click::Database::Writer do
6
+ it 'writes records to the database' do
7
+ with_in_memory_db do |db|
8
+ writer = Click::Database::Writer.new(db)
9
+ clicker = Click::Clicker.new
10
+ clicker.add_observer(writer)
11
+
12
+ expect(Click::Database::Models::Snapshot.count).to eq(0)
13
+ expect(Click::Database::Models::ObjectCount.count).to eq(0)
14
+
15
+ before_time = Time.now
16
+ clicker.click!
17
+ after_time = Time.now
18
+
19
+ expect(Click::Database::Models::Snapshot.count).to eq(1)
20
+ expect(Click::Database::Models::ObjectCount.count).to be > 0
21
+ snapshot = Click::Database::Models::Snapshot.first
22
+ expect(snapshot.object_counts.count).to eq(Click::Database::Models::ObjectCount.count)
23
+ expect(snapshot.timestamp).to be >= before_time
24
+ expect(snapshot.timestamp).to be <= after_time
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+ require 'click/observer'
3
+
4
+ describe Click::Observer do
5
+ it 'can be included into a class and then attached for callbacks' do
6
+ mod = described_class
7
+ klass = Class.new do
8
+ include mod
9
+ end
10
+
11
+ expect {
12
+ clicker = Click::Clicker.new
13
+ clicker.add_observer(klass.new)
14
+ clicker.click!
15
+ }.not_to raise_error
16
+ end
17
+ end
@@ -0,0 +1,15 @@
1
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
2
+ require 'click'
3
+
4
+ def with_in_memory_db
5
+ Click::Database.with_in_memory_database do |db|
6
+ Click::Database::Models::ObjectCount.dataset.delete
7
+ Click::Database::Models::Snapshot.dataset.delete
8
+
9
+ yield db
10
+ end
11
+ end
12
+
13
+ def initialize_models
14
+ Click::Database.with_in_memory_database { }
15
+ end
metadata ADDED
@@ -0,0 +1,176 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: click
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Mark Rushakoff
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-12-02 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bundler
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '1.3'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '1.3'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rspec
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: '2.14'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '2.14'
62
+ - !ruby/object:Gem::Dependency
63
+ name: sqlite3
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: pry
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ - !ruby/object:Gem::Dependency
95
+ name: sequel
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ type: :runtime
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ description: A tool to help track down the source of a memory leak in a Ruby process
111
+ email:
112
+ - mark.rushakoff@gmail.com
113
+ executables:
114
+ - click-console
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - .gitignore
119
+ - .rspec
120
+ - .travis.yml
121
+ - Gemfile
122
+ - LICENSE.txt
123
+ - README.md
124
+ - Rakefile
125
+ - bin/click-console
126
+ - click.gemspec
127
+ - lib/click.rb
128
+ - lib/click/clicker.rb
129
+ - lib/click/database.rb
130
+ - lib/click/database/models.rb
131
+ - lib/click/database/models/object_count.rb
132
+ - lib/click/database/models/snapshot.rb
133
+ - lib/click/database/writer.rb
134
+ - lib/click/observer.rb
135
+ - lib/click/version.rb
136
+ - spec/click_spec.rb
137
+ - spec/clicker_spec.rb
138
+ - spec/database/models/object_count_spec.rb
139
+ - spec/database/models/snapshot_spec.rb
140
+ - spec/database/writer_spec.rb
141
+ - spec/observer_spec.rb
142
+ - spec/spec_helper.rb
143
+ homepage: https://github.com/mark-rushakoff/click
144
+ licenses:
145
+ - MIT
146
+ post_install_message:
147
+ rdoc_options: []
148
+ require_paths:
149
+ - lib
150
+ required_ruby_version: !ruby/object:Gem::Requirement
151
+ none: false
152
+ requirements:
153
+ - - ! '>='
154
+ - !ruby/object:Gem::Version
155
+ version: '0'
156
+ required_rubygems_version: !ruby/object:Gem::Requirement
157
+ none: false
158
+ requirements:
159
+ - - ! '>='
160
+ - !ruby/object:Gem::Version
161
+ version: '0'
162
+ requirements: []
163
+ rubyforge_project:
164
+ rubygems_version: 1.8.23
165
+ signing_key:
166
+ specification_version: 3
167
+ summary: Track the changes in counts of live objects in your Ruby process.
168
+ test_files:
169
+ - spec/click_spec.rb
170
+ - spec/clicker_spec.rb
171
+ - spec/database/models/object_count_spec.rb
172
+ - spec/database/models/snapshot_spec.rb
173
+ - spec/database/writer_spec.rb
174
+ - spec/observer_spec.rb
175
+ - spec/spec_helper.rb
176
+ has_rdoc: