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.
Files changed (39) hide show
  1. data/Changelog.md +5 -3
  2. data/Gemfile +3 -1
  3. data/README.md +29 -1
  4. data/gemfiles/rails_3_0.gemfile +2 -0
  5. data/gemfiles/rails_3_1.gemfile +2 -0
  6. data/lib/toy.rb +10 -13
  7. data/lib/toy/callbacks.rb +1 -1
  8. data/lib/toy/dirty_store.rb +2 -2
  9. data/lib/toy/identity_map.rb +2 -2
  10. data/lib/toy/instrumentation/active_support_notifications.rb +5 -0
  11. data/lib/toy/instrumentation/log_subscriber.rb +49 -0
  12. data/lib/toy/instrumentation/metriks.rb +5 -0
  13. data/lib/toy/instrumentation/metriks_subscriber.rb +16 -0
  14. data/lib/toy/instrumentation/statsd.rb +5 -0
  15. data/lib/toy/instrumentation/statsd_subscriber.rb +22 -0
  16. data/lib/toy/instrumentation/subscriber.rb +38 -0
  17. data/lib/toy/instrumenters/memory.rb +27 -0
  18. data/lib/toy/instrumenters/noop.rb +9 -0
  19. data/lib/toy/middleware/identity_map.rb +10 -21
  20. data/lib/toy/object.rb +0 -1
  21. data/lib/toy/persistence.rb +22 -4
  22. data/lib/toy/querying.rb +46 -18
  23. data/lib/toy/version.rb +1 -1
  24. data/perf/reads.rb +1 -4
  25. data/perf/writes.rb +0 -3
  26. data/spec/helper.rb +4 -12
  27. data/spec/support/fake_udp_socket.rb +27 -0
  28. data/spec/support/instrumenter_helpers.rb +14 -0
  29. data/spec/toy/instrumentation/log_subscriber_spec.rb +85 -0
  30. data/spec/toy/instrumentation/metriks_subscriber_spec.rb +37 -0
  31. data/spec/toy/instrumentation/statsd_subscriber_spec.rb +47 -0
  32. data/spec/toy/instrumenters/memory_spec.rb +26 -0
  33. data/spec/toy/instrumenters/noop_spec.rb +22 -0
  34. data/spec/toy/persistence_spec.rb +45 -3
  35. data/spec/toy/querying_spec.rb +121 -36
  36. data/spec/toy_spec.rb +14 -16
  37. metadata +27 -7
  38. data/lib/toy/logger.rb +0 -15
  39. data/spec/toy/logger_spec.rb +0 -21
@@ -4,42 +4,70 @@ module Toy
4
4
 
5
5
  module ClassMethods
6
6
  def read(id, options = nil)
7
- if (attrs = adapter.read(id, options))
8
- load(id, attrs)
9
- end
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
- get(id, options) || raise(Toy::NotFound.new(id))
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
- result = adapter.read_multiple(ids, options)
24
- result.each do |id, attrs|
25
- result[id] = attrs.nil? ? nil : load(id, attrs)
26
- end
27
- result
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
- adapter.key?(id, options)
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
 
@@ -1,3 +1,3 @@
1
1
  module Toy
2
- VERSION = "0.13.1"
2
+ VERSION = "0.13.2"
3
3
  end
@@ -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')
@@ -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
@@ -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
- require 'support/constants'
18
- require 'support/objects'
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 = User.create
364
- doc.destroy
365
- User.key?(doc.id).should be_false
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