toystore 0.13.1 → 0.13.2
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.md +5 -3
- data/Gemfile +3 -1
- data/README.md +29 -1
- data/gemfiles/rails_3_0.gemfile +2 -0
- data/gemfiles/rails_3_1.gemfile +2 -0
- data/lib/toy.rb +10 -13
- data/lib/toy/callbacks.rb +1 -1
- data/lib/toy/dirty_store.rb +2 -2
- data/lib/toy/identity_map.rb +2 -2
- data/lib/toy/instrumentation/active_support_notifications.rb +5 -0
- data/lib/toy/instrumentation/log_subscriber.rb +49 -0
- data/lib/toy/instrumentation/metriks.rb +5 -0
- data/lib/toy/instrumentation/metriks_subscriber.rb +16 -0
- data/lib/toy/instrumentation/statsd.rb +5 -0
- data/lib/toy/instrumentation/statsd_subscriber.rb +22 -0
- data/lib/toy/instrumentation/subscriber.rb +38 -0
- data/lib/toy/instrumenters/memory.rb +27 -0
- data/lib/toy/instrumenters/noop.rb +9 -0
- data/lib/toy/middleware/identity_map.rb +10 -21
- data/lib/toy/object.rb +0 -1
- data/lib/toy/persistence.rb +22 -4
- data/lib/toy/querying.rb +46 -18
- data/lib/toy/version.rb +1 -1
- data/perf/reads.rb +1 -4
- data/perf/writes.rb +0 -3
- data/spec/helper.rb +4 -12
- data/spec/support/fake_udp_socket.rb +27 -0
- data/spec/support/instrumenter_helpers.rb +14 -0
- data/spec/toy/instrumentation/log_subscriber_spec.rb +85 -0
- data/spec/toy/instrumentation/metriks_subscriber_spec.rb +37 -0
- data/spec/toy/instrumentation/statsd_subscriber_spec.rb +47 -0
- data/spec/toy/instrumenters/memory_spec.rb +26 -0
- data/spec/toy/instrumenters/noop_spec.rb +22 -0
- data/spec/toy/persistence_spec.rb +45 -3
- data/spec/toy/querying_spec.rb +121 -36
- data/spec/toy_spec.rb +14 -16
- metadata +27 -7
- data/lib/toy/logger.rb +0 -15
- data/spec/toy/logger_spec.rb +0 -21
data/lib/toy/querying.rb
CHANGED
@@ -4,42 +4,70 @@ module Toy
|
|
4
4
|
|
5
5
|
module ClassMethods
|
6
6
|
def read(id, options = nil)
|
7
|
-
|
8
|
-
|
9
|
-
|
7
|
+
default_payload = {
|
8
|
+
:id => id,
|
9
|
+
:options => options,
|
10
|
+
:model => self,
|
11
|
+
:hit => false, # default to not found
|
12
|
+
}
|
13
|
+
|
14
|
+
Toy.instrumenter.instrument('read.toystore', default_payload) { |payload|
|
15
|
+
if (attrs = adapter.read(id, options))
|
16
|
+
payload[:hit] = true
|
17
|
+
load(id, attrs)
|
18
|
+
end
|
19
|
+
}
|
10
20
|
end
|
11
21
|
|
12
22
|
alias_method :get, :read
|
13
23
|
alias_method :find, :read
|
14
24
|
|
15
25
|
def read!(id, options = nil)
|
16
|
-
|
26
|
+
read(id, options) || raise(Toy::NotFound.new(id))
|
17
27
|
end
|
18
28
|
|
19
29
|
alias_method :get!, :read!
|
20
30
|
alias_method :find!, :read!
|
21
31
|
|
22
32
|
def read_multiple(ids, options = nil)
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
33
|
+
default_payload = {
|
34
|
+
:ids => ids,
|
35
|
+
:options => options,
|
36
|
+
:model => self,
|
37
|
+
:hits => 0,
|
38
|
+
:misses => 0,
|
39
|
+
}
|
40
|
+
|
41
|
+
Toy.instrumenter.instrument('read_multiple.toystore', default_payload) { |payload|
|
42
|
+
result = adapter.read_multiple(ids, options)
|
43
|
+
result.each do |id, attrs|
|
44
|
+
result[id] = if attrs.nil?
|
45
|
+
payload[:misses] += 1
|
46
|
+
nil
|
47
|
+
else
|
48
|
+
payload[:hits] += 1
|
49
|
+
load(id, attrs)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
result
|
53
|
+
}
|
28
54
|
end
|
29
55
|
|
30
56
|
alias_method :get_multiple, :read_multiple
|
31
57
|
alias_method :find_multiple, :read_multiple
|
32
58
|
|
33
|
-
def get_or_new(id)
|
34
|
-
get(id) || new(:id => id)
|
35
|
-
end
|
36
|
-
|
37
|
-
def get_or_create(id)
|
38
|
-
get(id) || create(:id => id)
|
39
|
-
end
|
40
|
-
|
41
59
|
def key?(id, options = nil)
|
42
|
-
|
60
|
+
default_payload = {
|
61
|
+
:id => id,
|
62
|
+
:options => options,
|
63
|
+
:model => self,
|
64
|
+
}
|
65
|
+
|
66
|
+
Toy.instrumenter.instrument('key.toystore', default_payload) { |payload|
|
67
|
+
result = adapter.key?(id, options)
|
68
|
+
payload[:hit] = result
|
69
|
+
result
|
70
|
+
}
|
43
71
|
end
|
44
72
|
alias :has_key? :key?
|
45
73
|
|
data/lib/toy/version.rb
CHANGED
data/perf/reads.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
# require 'perftools'
|
2
2
|
require 'pp'
|
3
|
-
require 'logger'
|
4
3
|
require 'benchmark'
|
5
4
|
require 'rubygems'
|
6
5
|
|
@@ -8,8 +7,6 @@ $:.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
|
|
8
7
|
require 'toystore'
|
9
8
|
require 'adapter/memory'
|
10
9
|
|
11
|
-
Toy.logger = ::Logger.new(STDOUT).tap { |log| log.level = ::Logger::INFO }
|
12
|
-
|
13
10
|
class User
|
14
11
|
include Toy::Store
|
15
12
|
attribute :name, String
|
@@ -40,4 +37,4 @@ puts 'Ratio', toystore_result / adapter_result
|
|
40
37
|
# end
|
41
38
|
|
42
39
|
# system('pprof.rb --gif --ignore=Collection#find_one prof_reads > prof_reads.gif')
|
43
|
-
# system('open prof_reads.gif')
|
40
|
+
# system('open prof_reads.gif')
|
data/perf/writes.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
# require 'perftools'
|
2
2
|
require 'pp'
|
3
|
-
require 'logger'
|
4
3
|
require 'benchmark'
|
5
4
|
require 'rubygems'
|
6
5
|
|
@@ -8,8 +7,6 @@ $:.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
|
|
8
7
|
require 'toystore'
|
9
8
|
require 'adapter/memory'
|
10
9
|
|
11
|
-
Toy.logger = ::Logger.new(STDOUT).tap { |log| log.level = ::Logger::INFO }
|
12
|
-
|
13
10
|
class User
|
14
11
|
include Toy::Store
|
15
12
|
end
|
data/spec/helper.rb
CHANGED
@@ -1,12 +1,9 @@
|
|
1
1
|
$:.unshift(File.expand_path('../../lib', __FILE__))
|
2
2
|
|
3
3
|
require 'pathname'
|
4
|
-
require 'logger'
|
5
4
|
|
6
5
|
root_path = Pathname(__FILE__).dirname.join('..').expand_path
|
7
6
|
lib_path = root_path.join('lib')
|
8
|
-
log_path = root_path.join('log')
|
9
|
-
log_path.mkpath
|
10
7
|
|
11
8
|
require 'rubygems'
|
12
9
|
require 'bundler'
|
@@ -14,20 +11,14 @@ require 'bundler'
|
|
14
11
|
Bundler.require(:default, :test)
|
15
12
|
|
16
13
|
require 'toy'
|
17
|
-
|
18
|
-
require
|
19
|
-
require 'support/identity_map_matcher'
|
20
|
-
require 'support/name_and_number_key_factory'
|
21
|
-
require 'support/shared_active_model_lint'
|
22
|
-
|
23
|
-
Logger.new(log_path.join('test.log')).tap do |log|
|
24
|
-
Toy.logger = log
|
25
|
-
end
|
14
|
+
|
15
|
+
Dir[root_path.join("spec/support/**/*.rb")].each { |f| require f }
|
26
16
|
|
27
17
|
RSpec.configure do |c|
|
28
18
|
c.include(Support::Constants)
|
29
19
|
c.include(Support::Objects)
|
30
20
|
c.include(IdentityMapMatcher)
|
21
|
+
c.include(InstrumenterHelpers)
|
31
22
|
|
32
23
|
c.fail_fast = true
|
33
24
|
c.filter_run :focused => true
|
@@ -38,5 +29,6 @@ RSpec.configure do |c|
|
|
38
29
|
c.before(:each) do
|
39
30
|
Toy::IdentityMap.enabled = false
|
40
31
|
Toy.key_factory = nil
|
32
|
+
clear_instrumenter
|
41
33
|
end
|
42
34
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
class FakeUDPSocket
|
2
|
+
attr_reader :buffer
|
3
|
+
|
4
|
+
def initialize
|
5
|
+
@buffer = []
|
6
|
+
end
|
7
|
+
|
8
|
+
def send(message, *rest)
|
9
|
+
@buffer.push [message]
|
10
|
+
end
|
11
|
+
|
12
|
+
def recv
|
13
|
+
@buffer.shift
|
14
|
+
end
|
15
|
+
|
16
|
+
def clear
|
17
|
+
@buffer = []
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_s
|
21
|
+
inspect
|
22
|
+
end
|
23
|
+
|
24
|
+
def inspect
|
25
|
+
"<FakeUDPSocket: #{@buffer.inspect}>"
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module InstrumenterHelpers
|
2
|
+
def setup_memory_instrumenter
|
3
|
+
require 'toy/instrumenters/memory'
|
4
|
+
Toy.instrumenter = Toy::Instrumenters::Memory.new
|
5
|
+
end
|
6
|
+
|
7
|
+
def clear_instrumenter
|
8
|
+
Toy.instrumenter = nil
|
9
|
+
end
|
10
|
+
|
11
|
+
def instrumenter
|
12
|
+
Toy.instrumenter
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'toy/instrumentation/log_subscriber'
|
3
|
+
|
4
|
+
describe Toy::Instrumentation::LogSubscriber do
|
5
|
+
uses_constants('User')
|
6
|
+
|
7
|
+
before do
|
8
|
+
Toy.instrumenter = ActiveSupport::Notifications
|
9
|
+
@io = StringIO.new
|
10
|
+
Toy::Instrumentation::LogSubscriber.logger = Logger.new(@io)
|
11
|
+
end
|
12
|
+
|
13
|
+
after do
|
14
|
+
Toy::Instrumentation::LogSubscriber.logger = nil
|
15
|
+
end
|
16
|
+
|
17
|
+
let(:log) { @io.string }
|
18
|
+
|
19
|
+
context "creating a new record" do
|
20
|
+
before do
|
21
|
+
clear_logs
|
22
|
+
@user = User.create
|
23
|
+
end
|
24
|
+
|
25
|
+
it "logs" do
|
26
|
+
log.should match(/User create/)
|
27
|
+
log.should match(/\[ #{@user.id.inspect} \]/)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context "updating a record" do
|
32
|
+
before do
|
33
|
+
User.attribute :name, String
|
34
|
+
@user = User.create(:name => 'Old Name')
|
35
|
+
clear_logs
|
36
|
+
@user.update_attributes(:name => 'New Name')
|
37
|
+
end
|
38
|
+
|
39
|
+
it "logs" do
|
40
|
+
log.should match(/User update/)
|
41
|
+
log.should match(/\[ #{@user.id.inspect} \]/)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context "finding a record" do
|
46
|
+
before do
|
47
|
+
clear_logs
|
48
|
+
User.read('blah')
|
49
|
+
end
|
50
|
+
|
51
|
+
it "logs" do
|
52
|
+
log.should match(/User read/)
|
53
|
+
log.should match(/\[ #{'blah'.inspect} \]/)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context "destroying a record" do
|
58
|
+
before do
|
59
|
+
@user = User.create
|
60
|
+
clear_logs
|
61
|
+
@user.destroy
|
62
|
+
end
|
63
|
+
|
64
|
+
it "logs" do
|
65
|
+
log.should match(/User destroy/)
|
66
|
+
log.should match(/\[ #{@user.id.inspect} \]/)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
context "checking if a record exists" do
|
71
|
+
before do
|
72
|
+
clear_logs
|
73
|
+
User.key?('blah')
|
74
|
+
end
|
75
|
+
|
76
|
+
it "logs" do
|
77
|
+
log.should match(/User key/)
|
78
|
+
log.should match(/\[ #{'blah'.inspect} \]/)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def clear_logs
|
83
|
+
@io.string = ''
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'toy/instrumentation/metriks'
|
3
|
+
|
4
|
+
describe Toy::Instrumentation::MetriksSubscriber do
|
5
|
+
uses_constants('User')
|
6
|
+
|
7
|
+
before do
|
8
|
+
Toy.instrumenter = ActiveSupport::Notifications
|
9
|
+
end
|
10
|
+
|
11
|
+
it "updates timers when calls happen" do
|
12
|
+
# Clear the registry so we don't count the operations required to re-create
|
13
|
+
# the keyspace and column family.
|
14
|
+
Metriks::Registry.default.clear
|
15
|
+
|
16
|
+
user = User.create(:name => 'Joe')
|
17
|
+
user.update_attributes(:name => 'John')
|
18
|
+
user.destroy
|
19
|
+
User.read(user.id)
|
20
|
+
User.read_multiple([user.id])
|
21
|
+
|
22
|
+
Metriks.timer('toystore.create').count.should be(1)
|
23
|
+
Metriks.timer('toystore.User.create').count.should be(1)
|
24
|
+
|
25
|
+
Metriks.timer('toystore.update').count.should be(1)
|
26
|
+
Metriks.timer('toystore.User.update').count.should be(1)
|
27
|
+
|
28
|
+
Metriks.timer('toystore.read').count.should be(1)
|
29
|
+
Metriks.timer('toystore.User.read').count.should be(1)
|
30
|
+
|
31
|
+
Metriks.timer('toystore.read_multiple').count.should be(1)
|
32
|
+
Metriks.timer('toystore.User.read_multiple').count.should be(1)
|
33
|
+
|
34
|
+
Metriks.timer('toystore.destroy').count.should be(1)
|
35
|
+
Metriks.timer('toystore.User.destroy').count.should be(1)
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'toy/instrumentation/statsd'
|
3
|
+
|
4
|
+
describe Toy::Instrumentation::StatsdSubscriber do
|
5
|
+
uses_constants('User')
|
6
|
+
|
7
|
+
let(:statsd_client) { Statsd.new }
|
8
|
+
let(:socket) { FakeUDPSocket.new }
|
9
|
+
|
10
|
+
before do
|
11
|
+
described_class.client = statsd_client
|
12
|
+
Thread.current[:statsd_socket] = socket
|
13
|
+
Toy.instrumenter = ActiveSupport::Notifications
|
14
|
+
end
|
15
|
+
|
16
|
+
after do
|
17
|
+
described_class.client = nil
|
18
|
+
Thread.current[:statsd_socket] = nil
|
19
|
+
end
|
20
|
+
|
21
|
+
def assert_timer(metric)
|
22
|
+
regex = /#{Regexp.escape metric}\:\d+\|ms/
|
23
|
+
socket.buffer.detect { |op| op.first =~ regex }.should_not be_nil
|
24
|
+
end
|
25
|
+
|
26
|
+
it "updates timers when calls happen" do
|
27
|
+
user = User.create(:name => 'Joe')
|
28
|
+
assert_timer('toystore.create')
|
29
|
+
assert_timer('toystore.User.create')
|
30
|
+
|
31
|
+
user.update_attributes(:name => 'John')
|
32
|
+
assert_timer('toystore.update')
|
33
|
+
assert_timer('toystore.User.update')
|
34
|
+
|
35
|
+
user.destroy
|
36
|
+
assert_timer('toystore.destroy')
|
37
|
+
assert_timer('toystore.User.destroy')
|
38
|
+
|
39
|
+
User.read(user.id)
|
40
|
+
assert_timer('toystore.read')
|
41
|
+
assert_timer('toystore.User.read')
|
42
|
+
|
43
|
+
User.read_multiple([user.id])
|
44
|
+
assert_timer('toystore.read_multiple')
|
45
|
+
assert_timer('toystore.User.read_multiple')
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'toy/instrumenters/memory'
|
3
|
+
|
4
|
+
describe Toy::Instrumenters::Memory do
|
5
|
+
describe "#initialize" do
|
6
|
+
it "sets events to empty array" do
|
7
|
+
instrumentor = described_class.new
|
8
|
+
instrumentor.events.should eq([])
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
describe "#instrument" do
|
13
|
+
it "adds to events" do
|
14
|
+
instrumentor = described_class.new
|
15
|
+
name = 'user.signup'
|
16
|
+
payload = {:email => 'john@doe.com'}
|
17
|
+
block_result = :yielded
|
18
|
+
|
19
|
+
result = instrumentor.instrument(name, payload) { block_result }
|
20
|
+
result.should eq(block_result)
|
21
|
+
|
22
|
+
event = described_class::Event.new(name, payload, block_result)
|
23
|
+
instrumentor.events.should eq([event])
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'toy/instrumenters/noop'
|
3
|
+
|
4
|
+
describe Toy::Instrumenters::Noop do
|
5
|
+
describe ".instrument" do
|
6
|
+
context "with name" do
|
7
|
+
it "yields block" do
|
8
|
+
yielded = false
|
9
|
+
described_class.instrument(:foo) { yielded = true }
|
10
|
+
yielded.should be_true
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
context "with name and payload" do
|
15
|
+
it "yields block" do
|
16
|
+
yielded = false
|
17
|
+
described_class.instrument(:foo, {:pay => :load}) { yielded = true }
|
18
|
+
yielded.should be_true
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -251,6 +251,19 @@ describe Toy::Persistence do
|
|
251
251
|
@doc.save.should be_true
|
252
252
|
end
|
253
253
|
|
254
|
+
it "is instrumented" do
|
255
|
+
setup_memory_instrumenter
|
256
|
+
|
257
|
+
@doc.save
|
258
|
+
|
259
|
+
event = instrumenter.events.last
|
260
|
+
event.name.should eq('create.toystore')
|
261
|
+
event.payload.should eq({
|
262
|
+
:id => @doc.persisted_id,
|
263
|
+
:model => User,
|
264
|
+
})
|
265
|
+
end
|
266
|
+
|
254
267
|
context "with #persist overridden" do
|
255
268
|
before do
|
256
269
|
@doc.class_eval do
|
@@ -309,6 +322,19 @@ describe Toy::Persistence do
|
|
309
322
|
@doc.save.should be_true
|
310
323
|
end
|
311
324
|
|
325
|
+
it "is instrumented" do
|
326
|
+
setup_memory_instrumenter
|
327
|
+
|
328
|
+
@doc.save
|
329
|
+
|
330
|
+
event = instrumenter.events.last
|
331
|
+
event.name.should eq('update.toystore')
|
332
|
+
event.payload.should eq({
|
333
|
+
:id => @doc.persisted_id,
|
334
|
+
:model => User,
|
335
|
+
})
|
336
|
+
end
|
337
|
+
|
312
338
|
context "with #persist overridden" do
|
313
339
|
before do
|
314
340
|
@doc.class_eval do
|
@@ -359,10 +385,26 @@ describe Toy::Persistence do
|
|
359
385
|
end
|
360
386
|
|
361
387
|
describe "#destroy" do
|
388
|
+
before do
|
389
|
+
@doc = User.create
|
390
|
+
end
|
391
|
+
|
362
392
|
it "should remove the instance" do
|
363
|
-
doc
|
364
|
-
doc.
|
365
|
-
|
393
|
+
@doc.destroy
|
394
|
+
User.key?(@doc.id).should be_false
|
395
|
+
end
|
396
|
+
|
397
|
+
it "is instrumented" do
|
398
|
+
setup_memory_instrumenter
|
399
|
+
|
400
|
+
@doc.destroy
|
401
|
+
|
402
|
+
event = instrumenter.events.last
|
403
|
+
event.name.should eq('destroy.toystore')
|
404
|
+
event.payload.should eq({
|
405
|
+
:id => @doc.id,
|
406
|
+
:model => User,
|
407
|
+
})
|
366
408
|
end
|
367
409
|
end
|
368
410
|
|