methodmissing-scrooge 1.0.1 → 1.0.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 (35) hide show
  1. data/README.textile +227 -22
  2. data/VERSION.yml +1 -1
  3. data/assets/config/scrooge.yml.template +6 -0
  4. data/lib/scrooge/framework/base.rb +26 -15
  5. data/lib/scrooge/framework/rails.rb +28 -4
  6. data/lib/scrooge/middleware/tracker.rb +0 -1
  7. data/lib/scrooge/orm/active_record.rb +7 -3
  8. data/lib/scrooge/profile.rb +72 -73
  9. data/lib/scrooge/strategy/base.rb +74 -0
  10. data/lib/scrooge/strategy/controller.rb +29 -0
  11. data/lib/scrooge/strategy/scope.rb +14 -0
  12. data/lib/scrooge/strategy/stage.rb +77 -0
  13. data/lib/scrooge/strategy/track.rb +19 -0
  14. data/lib/scrooge/strategy/track_then_scope.rb +41 -0
  15. data/lib/scrooge/tracker/app.rb +63 -3
  16. data/lib/scrooge/tracker/base.rb +10 -0
  17. data/lib/scrooge/tracker/model.rb +7 -0
  18. data/lib/scrooge/tracker/resource.rb +17 -2
  19. data/lib/scrooge.rb +2 -1
  20. data/rails/init.rb +1 -1
  21. data/spec/fixtures/config/scrooge.yml +5 -1
  22. data/spec/units/scrooge/framework/base_spec.rb +9 -3
  23. data/spec/units/scrooge/profile_spec.rb +13 -15
  24. data/spec/units/scrooge/strategy/base_spec.rb +62 -0
  25. data/spec/units/scrooge/strategy/controller_spec.rb +26 -0
  26. data/spec/units/scrooge/strategy/scope_spec.rb +18 -0
  27. data/spec/units/scrooge/strategy/stage_spec.rb +35 -0
  28. data/spec/units/scrooge/strategy/track_spec.rb +19 -0
  29. data/spec/units/scrooge/strategy/track_then_scope_spec.rb +22 -0
  30. data/spec/units/scrooge/tracker/app_spec.rb +5 -1
  31. data/spec/units/scrooge/tracker/base_spec.rb +8 -0
  32. data/spec/units/scrooge/tracker/model_spec.rb +14 -1
  33. data/spec/units/scrooge/tracker/resource_spec.rb +21 -0
  34. data/tasks/scrooge.rake +1 -1
  35. metadata +16 -2
@@ -6,8 +6,7 @@ module Scrooge
6
6
  # A Profile for Scrooge that holds configuration, determines if we're tracking or
7
7
  # scoping and provides access to a Tracker, ORM, Storage and Framework instance.
8
8
 
9
- class Signature
10
- end
9
+ GUARD = Mutex.new
11
10
 
12
11
  class << self
13
12
 
@@ -44,8 +43,8 @@ module Scrooge
44
43
 
45
44
  attr_reader :options
46
45
 
47
- def initialize( options = {} )
48
- self.options = options
46
+ def initialize( options = {} )
47
+ self.options = options
49
48
  end
50
49
 
51
50
  # options writer that attempts to reconfigure for any configuration changes.
@@ -67,6 +66,18 @@ module Scrooge
67
66
  @storage_instance ||= Scrooge::Storage::Base.instantiate( @storage )
68
67
  end
69
68
 
69
+ # Delegates to the Application Tracker.
70
+ #
71
+ def tracker
72
+ @tracker_instance ||= Scrooge::Tracker::App.new
73
+ end
74
+
75
+ # Yields a strategt instance.
76
+ #
77
+ def strategy
78
+ @strategy_instance ||= "scrooge/strategy/#{@strategy.to_s}".to_const!
79
+ end
80
+
70
81
  # Delegates to the current framework.
71
82
  #
72
83
  def framework
@@ -75,76 +86,50 @@ module Scrooge
75
86
 
76
87
  # Log a message to the framework's logger.
77
88
  #
78
- def log( message )
79
- framework.log( message ) rescue ''
89
+ def log( message, flush = false )
90
+ framework.log( message, flush ) rescue ''
80
91
  end
81
-
82
- # Delegates to the Application Tracker.
92
+
93
+ # Are we tracking ?
83
94
  #
84
- def tracker
85
- @tracker_instance ||= Scrooge::Tracker::App.new
95
+ def track?
96
+ GUARD.synchronize do
97
+ @track == true
98
+ end
86
99
  end
87
-
88
- # Determine if this is a tracking or scope profile.
89
- #
90
- def track_or_scope!
91
- if enabled?
92
- track? ? track! : scope!
100
+
101
+ # Enable the tracking phase
102
+ #
103
+ def start_tracking!
104
+ GUARD.synchronize do
105
+ @track = true
93
106
  end
94
107
  end
95
108
 
96
- # Are we tracking ?
109
+ # Disable the tracking phase
97
110
  #
98
- def track?
99
- if enabled?
100
- @track ||= (@scope || '').match( /\d{10}/ ).nil?
101
- else
102
- false
103
- end
111
+ def stop_tracking!
112
+ GUARD.synchronize do
113
+ @track = false
114
+ end
104
115
  end
105
116
 
106
- def track!
107
- if track?
108
- log "Tracking"
109
- framework.install_tracking_middleware()
110
- shutdown_hook!
111
- end
112
- end
113
-
114
117
  # The active scope signature
115
118
  #
116
- def scope_to
119
+ def scope
117
120
  @scope
118
- end
119
-
120
- # Are we scoping ?
121
- #
122
- def scope?
123
- if enabled?
124
- !track?
125
- else
126
- false
127
- end
128
- end
129
-
130
- # Scope the tracker environment to a given scope signature.
131
- #
132
- def scope_to_signature!( scope_signature )
133
- log "Scope to #{scope_signature}"
134
- @tracker_instance = framework.from_scope!( scope_signature )
135
- end
121
+ end
136
122
 
137
- # Scope the tracker environment to a given scope signature and install
138
- # scoping middleware.
139
- #
140
- def scope_to!
141
- if scope?
142
- scope_to_signature!( @scope )
143
- framework.install_scope_middleware( tracker )
123
+ # Assign the current scope from a given signature or tracker
124
+ #
125
+ def scope=( signature_or_tracker )
126
+ if signature_or_tracker.kind_of?( Scrooge::Tracker::Base )
127
+ scope_to_tracker!( signature_or_tracker )
128
+ else
129
+ scope_to_signature!( signature_or_tracker )
144
130
  end
145
- end
146
- alias :scope! :scope_to!
147
-
131
+ end
132
+
148
133
  # Should Scrooge inject itself ?
149
134
  #
150
135
  def enabled?
@@ -155,20 +140,32 @@ module Scrooge
155
140
  #
156
141
  def raise_on_missing_attribute?
157
142
  @on_missing_attribute == :raise
158
- end
143
+ end
144
+
145
+ # Expose the warmup phase during which tracking occur.
146
+ #
147
+ def warmup
148
+ @warmup
149
+ end
159
150
 
160
151
  private
161
152
 
162
- def configure! #:nodoc:
153
+ def configure! #:nodoc:
163
154
  @orm = configure_with( @options['orm'], [:active_record], :active_record )
164
155
  @storage = configure_with( @options['storage'], [:memory], :memory )
165
- @scope = configure_with( @options['scope'].to_s, framework.scopes, ENV['scope'] )
156
+ @strategy = configure_with( @options['strategy'], [:track, :scope, :track_then_scope], :track )
157
+ @scope = configure_with( @options['scope'].to_s, framework_scopes, ENV['scope'] )
158
+ @warmup = configure_with( @options['warmup'], 0..14400, 0 )
166
159
  @enabled = configure_with( @options['enabled'], [true, false], false )
167
160
  @on_missing_attribute = configure_with( @options['on_missing_attribute'], [:reload, :raise], :reload )
168
161
  reset_backends!
169
162
  memoize_backends!
170
163
  end
171
164
 
165
+ def framework_scopes #:nodoc:
166
+ framework.scopes rescue []
167
+ end
168
+
172
169
  def configure_with( given, valid, default ) #:nodoc:
173
170
  if given
174
171
  valid.include?( given ) ? given : default
@@ -186,19 +183,21 @@ module Scrooge
186
183
  # Force constant lookups as autoload is not threadsafe.
187
184
  #
188
185
  def memoize_backends! #:nodoc:
189
- framework()
186
+ framework() rescue nil
190
187
  orm()
191
188
  storage()
192
189
  tracker()
190
+ end
191
+
192
+ def scope_to_signature!( scope_signature ) #:nodoc:
193
+ log "Scope to signature #{scope_signature} ..."
194
+ @tracker_instance = framework.from_scope!( scope_signature )
195
+ end
196
+
197
+ def scope_to_tracker!( tracker ) #:nodoc:
198
+ log "Scope to tracker #{tracker.inspect} ..."
199
+ @tracker_instance = tracker
193
200
  end
194
-
195
- def shutdown_hook! #:nodoc:
196
- # Registers an at_exit hook to persist the current application scope.
197
- ::Kernel.at_exit do
198
- log "shutdown ..."
199
- framework.scope! if tracker.any?
200
- end
201
- end
202
-
201
+
203
202
  end
204
203
  end
@@ -0,0 +1,74 @@
1
+ module Scrooge
2
+ module Strategy
3
+
4
+ autoload :Controller, 'scrooge/strategy/controller'
5
+ autoload :Stage, 'scrooge/strategy/stage'
6
+ autoload :Scope, 'scrooge/strategy/scope'
7
+ autoload :Track, 'scrooge/strategy/track'
8
+ autoload :TrackThenScope, 'scrooge/strategy/track_then_scope'
9
+
10
+ class Base
11
+
12
+ class NoStages < StandardError
13
+ end
14
+
15
+ @@stages = Hash.new( [] )
16
+ @@stages[self.name] = []
17
+
18
+ attr_reader :thread
19
+
20
+ class << self
21
+
22
+ # Stage definition macro.
23
+ #
24
+ # stage :track, :for => 10.minutes do
25
+ # ....
26
+ # end
27
+ #
28
+ def stage( signature, options = {}, &block )
29
+ @@stages[self.name] << Scrooge::Strategy::Stage.new( signature, options, &block )
30
+ end
31
+
32
+ # List all defined stages for this klass.
33
+ #
34
+ def stages
35
+ @@stages[self.name]
36
+ end
37
+
38
+ # Are there any stages defined ?
39
+ #
40
+ def stages?
41
+ !stages.empty?
42
+ end
43
+
44
+ # Test teardown helper.
45
+ #
46
+ def flush!
47
+ @@stages[self.name] = []
48
+ end
49
+
50
+ end
51
+
52
+ # Requires at least one stage definition.
53
+ #
54
+ def initialize
55
+ raise NoStages unless self.class.stages?
56
+ end
57
+
58
+ # Piggy back on stages defined for this klass.
59
+ #
60
+ def stages
61
+ self.class.stages
62
+ end
63
+
64
+ # Enforce this strategy
65
+ #
66
+ def execute!
67
+ if Scrooge::Base.profile.enabled?
68
+ @thread = Scrooge::Strategy::Controller.new( self ).run!
69
+ end
70
+ end
71
+
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,29 @@
1
+ module Scrooge
2
+ module Strategy
3
+ class Controller
4
+
5
+ attr_accessor :strategy
6
+ attr_reader :thread
7
+
8
+ def initialize( strategy )
9
+ @strategy = strategy
10
+ end
11
+
12
+ def run!
13
+ @thread = Thread.new do
14
+ Thread.current.abort_on_exception = true
15
+ stages.map do |stage|
16
+ stage.execute!
17
+ end
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def stages #:nodoc:
24
+ @strategy.stages
25
+ end
26
+
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,14 @@
1
+ module Scrooge
2
+ module Strategy
3
+ class Scope < Base
4
+
5
+ stage :scope do
6
+ log( "Scope ...", true )
7
+ self.scope = scope if framework.scope?( scope )
8
+ framework.install_scope_middleware( tracker )
9
+
10
+ end
11
+
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,77 @@
1
+ module Scrooge
2
+ module Strategy
3
+ class Stage
4
+
5
+ # Represents a duration sensitive stage / phase of execution.
6
+
7
+ STATES = { :initialized => 0,
8
+ :execute => 1,
9
+ :terminated => 2 }
10
+
11
+ attr_accessor :signature,
12
+ :duration,
13
+ :payload
14
+
15
+ # Requires a unique signature and stores a payload for later execution.
16
+ #
17
+ # Valid options is
18
+ # * :for : the phase / stage duration, in seconds
19
+ #
20
+ def initialize( signature, options = {}, &block )
21
+ @signature = signature
22
+ @duration = options[:for] || 0
23
+ @payload = block
24
+ @state = :initialized
25
+ end
26
+
27
+ # Always executeable when initialized and not yet terminated.
28
+ #
29
+ def executeable?
30
+ initialized? && !terminated?
31
+ end
32
+
33
+ # Enter the :execute state, call the payload and sleep for the defined duration.
34
+ # Returns the payload result on completion and ensures that the current state is
35
+ # :terminated.
36
+ #
37
+ def execute!
38
+ begin
39
+ Scrooge::Base.profile.log( "Execute stage #{signature.inspect} ...", true)
40
+ @state = :execute
41
+ result = call!
42
+ sleep( @duration )
43
+ result
44
+ ensure
45
+ @state = :terminated
46
+ end
47
+ end
48
+ alias :run! :execute!
49
+
50
+ # Are we running ?
51
+ #
52
+ def execute?
53
+ @state == :execute
54
+ end
55
+ alias :running? :execute?
56
+
57
+ # Is this stage pending execution ?
58
+ #
59
+ def initialized?
60
+ @state == :initialized
61
+ end
62
+
63
+ # Has this stage already terminated ?
64
+ #
65
+ def terminated?
66
+ @state == :terminated
67
+ end
68
+
69
+ private
70
+
71
+ def call! #:nodoc:
72
+ Scrooge::Base.profile.instance_eval( &@payload )
73
+ end
74
+
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,19 @@
1
+ module Scrooge
2
+ module Strategy
3
+ class Track < Base
4
+
5
+ stage :track, :for => Scrooge::Base.profile.warmup do
6
+
7
+ log( "Tracking", true )
8
+ framework.install_tracking_middleware()
9
+ start_tracking!
10
+ ::Kernel.at_exit do
11
+ log( "Shutdown ...", true )
12
+ framework.scope! if tracker.any?
13
+ end
14
+
15
+ end
16
+
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,41 @@
1
+ module Scrooge
2
+ module Strategy
3
+ class TrackThenScope < Base
4
+
5
+ stage :track, :for => Scrooge::Base.profile.warmup do
6
+
7
+ log( "Installing tracking middleware ... ", true )
8
+ framework.install_tracking_middleware()
9
+ log( "Start tracking ... ", true )
10
+ start_tracking!
11
+
12
+ end
13
+
14
+ stage :synchronize, :for => 10 do
15
+
16
+ log( "Uninstalling tracking middleware ... ", true )
17
+ framework.uninstall_tracking_middleware
18
+ log( "Stop tracking ... ", true )
19
+ stop_tracking!
20
+ log( "Synchronize results with other processes ...", true )
21
+ tracker.synchronize!
22
+
23
+ end
24
+
25
+ stage :aggregate, :for => 10 do
26
+
27
+ log( "Aggregate results from other processes ...", true )
28
+ tracker.aggregate!
29
+
30
+ end
31
+
32
+ stage :scope do
33
+
34
+ log( "Scope ...", true )
35
+ framework.install_scope_middleware( tracker )
36
+
37
+ end
38
+
39
+ end
40
+ end
41
+ end
@@ -6,6 +6,8 @@ module Scrooge
6
6
 
7
7
  GUARD = Monitor.new
8
8
 
9
+ AGGREGATION_BUCKET = 'scrooge_tracker_aggregation'.freeze
10
+
9
11
  attr_accessor :resources
10
12
 
11
13
  def initialize
@@ -13,6 +15,45 @@ module Scrooge
13
15
  @resources = Set.new
14
16
  end
15
17
 
18
+ # Push this tracker instance to the framework's cache store.
19
+ # A synchronization signature unique for this Process is also logged in a synchronization
20
+ # bucket which maintain references to each Process's tracker.
21
+ #
22
+ def synchronize!
23
+ GUARD.synchronize do
24
+ write_cache( synchronization_signature, marshal_dump )
25
+ register_synchronization_signature!
26
+ end
27
+ end
28
+
29
+ # Process previously synchronized tracking data by recursively merging all trackers from
30
+ # all Processes to have a Tracker representative of the whole cluster.
31
+ #
32
+ def aggregate!
33
+ GUARD.synchronize do
34
+ read_cache( AGGREGATION_BUCKET ).each do |tracker|
35
+ tracker_data = read_cache( tracker )
36
+ merge( self.class.load( tracker_data ) )
37
+ end
38
+ end
39
+ end
40
+
41
+ # Merge this Tracker with another Tracker ( multi-process aggregation )
42
+ #
43
+ def merge( other_tracker )
44
+ return unless other_tracker
45
+ resources.merge( other_tracker.resources )
46
+ resources.each do |res|
47
+ res.merge( other_tracker.resource( res ) )
48
+ end
49
+ end
50
+
51
+ # Find a given resource instance
52
+ #
53
+ def resource( resource )
54
+ resources.detect{|r| r.signature == resource.signature }
55
+ end
56
+
16
57
  # Has any Resources been tracked ?
17
58
  #
18
59
  def any?
@@ -45,7 +86,6 @@ module Scrooge
45
86
  # Track a given Resource.
46
87
  #
47
88
  def track( resource )
48
- profile.log "Track with resource #{resource.inspect}"
49
89
  begin
50
90
  yield
51
91
  ensure
@@ -64,12 +104,32 @@ module Scrooge
64
104
  # If we've seen this resource before, return the original, else, returns
65
105
  # the given resource.
66
106
  #
67
- def resource_for( resource )
68
- @resources.detect{|r| r.signature == resource.signature } || resource
107
+ def resource_for( res )
108
+ resource( res ) || res
69
109
  end
70
110
 
71
111
  private
72
112
 
113
+ def read_cache( key ) #:nodoc:
114
+ Scrooge::Base.profile.framework.read_cache( key )
115
+ end
116
+
117
+ def write_cache( key, value ) #:nodoc:
118
+ Scrooge::Base.profile.framework.write_cache( key, value )
119
+ end
120
+
121
+ def register_synchronization_signature! #:nodoc:
122
+ if bucket = read_cache( AGGREGATION_BUCKET )
123
+ write_cache( AGGREGATION_BUCKET, (bucket << synchronization_signature) )
124
+ else
125
+ write_cache( AGGREGATION_BUCKET, [synchronization_signature] )
126
+ end
127
+ end
128
+
129
+ def synchronization_signature #:nodoc:
130
+ @synchronization_signature ||= "#{Thread.current.object_id}_#{Process.pid}_#{rand(1000000)}"
131
+ end
132
+
73
133
  def setup_resource( resource ) #:nodoc:
74
134
  GUARD.synchronize do
75
135
  resource_for( resource )
@@ -15,6 +15,16 @@ module Scrooge
15
15
  class NotImplemented < StandardError
16
16
  end
17
17
 
18
+ class << self
19
+
20
+ # Marshal helper.
21
+ #
22
+ def load( data )
23
+ new.marshal_load( data )
24
+ end
25
+
26
+ end
27
+
18
28
  attr_accessor :counter
19
29
 
20
30
  def initialize
@@ -13,6 +13,13 @@ module Scrooge
13
13
  @attributes = Set.new
14
14
  end
15
15
 
16
+ # Merge this Tracker with another Tracker for the same model ( multi-process / cluster aggregation )
17
+ #
18
+ def merge( other_model )
19
+ return unless other_model
20
+ attributes.merge( other_model.attributes )
21
+ end
22
+
16
23
  # Has any Attributes been tracked ?
17
24
  #
18
25
  def any?
@@ -28,6 +28,16 @@ module Scrooge
28
28
  yield self if block_given?
29
29
  end
30
30
 
31
+ # Merge this Tracker with another Tracker for the same resource ( multi-process / cluster aggregation )
32
+ #
33
+ def merge( other_resource )
34
+ return unless other_resource
35
+ models.merge( other_resource.models )
36
+ models.each do |model|
37
+ model.merge( other_resource.model( model ) )
38
+ end
39
+ end
40
+
31
41
  # Has any Models been tracked ?
32
42
  #
33
43
  def any?
@@ -36,6 +46,12 @@ module Scrooge
36
46
  end
37
47
  end
38
48
 
49
+ # Search for a given model instance
50
+ #
51
+ def model( model )
52
+ models.detect{|m| m.name == model.name }
53
+ end
54
+
39
55
  # Generates a signature / lookup key.
40
56
  #
41
57
  def signature
@@ -111,7 +127,6 @@ module Scrooge
111
127
  #
112
128
  def filter( controller, &block )
113
129
  #{model.model.to_s}.#{profile.orm.resource_scope_method( resource ).to_s} do
114
- Scrooge::Base.profile.log "Scope for Model #{model.inspect}"
115
130
  block.call
116
131
  end
117
132
  end
@@ -178,7 +193,7 @@ module Scrooge
178
193
  models.map do |model|
179
194
  m = model.keys.first # TODO: cleanup
180
195
  Model.new( m ).marshal_load( model )
181
- end
196
+ end.to_set
182
197
  end
183
198
  end
184
199
 
data/lib/scrooge.rb CHANGED
@@ -59,4 +59,5 @@ require 'scrooge/profile'
59
59
  require 'scrooge/storage/base'
60
60
  require 'scrooge/orm/base'
61
61
  require 'scrooge/framework/base'
62
- require 'scrooge/tracker/base'
62
+ require 'scrooge/tracker/base'
63
+ require 'scrooge/strategy/base'
data/rails/init.rb CHANGED
@@ -6,5 +6,5 @@ Scrooge::Framework::Rails
6
6
  Scrooge::Base.profile = Scrooge::Profile.setup!
7
7
  Scrooge::Base.profile.framework.initialized do
8
8
  Scrooge::Base.profile.log "Initialized"
9
- Scrooge::Base.profile.track_or_scope!
9
+ Scrooge::Base.profile.strategy.execute!
10
10
  end
@@ -3,10 +3,14 @@ production:
3
3
  storage: :memory
4
4
  scope: 1234567891
5
5
  on_missing_attribute: :reload
6
+ strategy: :track_then_scope
7
+ warmup: 600 # warmup / track for 10 minutes
6
8
  enabled: true
7
9
  test:
8
10
  orm: :active_record
9
11
  storage: :memory
10
12
  scope:
11
13
  on_missing_attribute: :reload
12
- enabled: true
14
+ strategy: :track_then_scope
15
+ warmup: 600 # warmup / track for 10 minutes
16
+ enabled: true