toystore 0.13.1 → 0.13.2

Sign up to get free protection for your applications and to get access to all the features.
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