maxwell_agent 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,92 @@
1
+ module Maxwell
2
+ module Agent
3
+ module Middleware
4
+ class Chain
5
+ include Enumerable
6
+ attr_reader :entries
7
+
8
+ def initialize
9
+ @entries = []
10
+ yield self if block_given?
11
+ end
12
+
13
+ def each(&block)
14
+ entries.each(&block)
15
+ end
16
+
17
+ def add(klass, *args)
18
+ new_entry = Entry.new(klass, *args)
19
+ if count > 0
20
+ add_at(count + 1, new_entry)
21
+ else
22
+ add_at(count, new_entry)
23
+ end
24
+ end
25
+
26
+ def remove(entry)
27
+ entries.delete_if {|e| e }
28
+ end
29
+
30
+ def insert_before(existing_klass, new_klass, *args)
31
+ new_entry = Entry.new(new_klass, *args)
32
+ i = get_index(existing_klass) || 0
33
+ add_at(i, new_entry)
34
+ end
35
+
36
+ def insert_after(existing_klass, new_klass, *args)
37
+ new_entry = Entry.new(new_klass, *args)
38
+ i = get_index(existing_klass) || count - 1
39
+ add_at(i + 1, new_entry)
40
+ end
41
+
42
+ def invoke(*args, &final_action)
43
+ chain = retrieve.dup
44
+
45
+ traverse_chain = -> do
46
+ if chain.empty?
47
+ final_action.call if final_action
48
+ else
49
+ chain.shift.call(*args, &traverse_chain)
50
+ end
51
+ end
52
+ traverse_chain.call
53
+ end
54
+
55
+ private
56
+ def add_at(index, new_entry)
57
+ remove(new_entry) if exists?(new_entry)
58
+ entries.insert(index, new_entry)
59
+ end
60
+
61
+ def get_index(klass)
62
+ find_index {|entry| entry.klass == klass }
63
+ end
64
+
65
+ def exists?(entry)
66
+ include?(entry)
67
+ end
68
+
69
+ def retrieve
70
+ entries.map(&:build)
71
+ end
72
+
73
+ class Entry
74
+ attr_reader :klass
75
+
76
+ def initialize(klass, *args)
77
+ @klass = klass
78
+ @args = args
79
+ end
80
+
81
+ def build
82
+ @klass.new(*@args)
83
+ end
84
+
85
+ def ==(other)
86
+ self.klass == other.klass
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,12 @@
1
+ module Maxwell
2
+ module Agent
3
+ module Middleware
4
+ class Logging
5
+ def call(work, &chain)
6
+ chain.call
7
+ ::Logger.new('/tmp/test2.log').info("Ran #{work.name}")
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,31 @@
1
+ module Maxwell
2
+ module Agent
3
+ module Probe
4
+ def self.included(base)
5
+ base.extend(ClassMethods)
6
+ base.instance_eval do
7
+ private_class_method :call_handler, :instance
8
+ attr_accessor :output, :args
9
+ end
10
+ end
11
+
12
+ module ClassMethods
13
+ def perform(*args)
14
+ probe = instance(*args)
15
+ probe.output = probe.perform(*args)
16
+ call_handler(probe)
17
+ end
18
+
19
+ def instance(*args)
20
+ instance = new
21
+ instance.args = args
22
+ instance
23
+ end
24
+
25
+ def call_handler(probe)
26
+ probe.handle if probe.respond_to?(:handle)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,38 @@
1
+ require 'agent/worker'
2
+ require 'agent/scheduler'
3
+ require 'agent/work_schedule'
4
+
5
+ module Maxwell
6
+ module Agent
7
+ class Runner < Celluloid::SupervisionGroup
8
+
9
+ attr_reader :registry
10
+
11
+ def self.worker_pool_size
12
+ Maxwell.configuration.worker_concurrency
13
+ end
14
+
15
+ supervise Maxwell::WorkSchedule, as: :work_schedule
16
+ pool Maxwell::Worker, as: :worker, size: worker_pool_size
17
+ supervise Maxwell::Scheduler, as: :scheduler
18
+
19
+ def [](actor_name)
20
+ @registry[actor_name]
21
+ end
22
+
23
+ def initialize(opts)
24
+ super
25
+ wait_for_actor_boot
26
+ end
27
+
28
+ def wait_for_actor_boot
29
+ loop do
30
+ break if self[:work_schedule] &&
31
+ self[:worker] &&
32
+ self[:scheduler]
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ end
@@ -0,0 +1,36 @@
1
+ module Maxwell
2
+ module Agent
3
+ class Scheduler
4
+ include Celluloid
5
+
6
+ def initialize
7
+ async.run
8
+ end
9
+
10
+ def work_schedule
11
+ runner[:work_schedule]
12
+ end
13
+
14
+ def run
15
+ loop do
16
+ sleep Maxwell.configuration.work_poll
17
+ schedule_work
18
+ end
19
+ end
20
+
21
+ def schedule_work
22
+ work = work_schedule.get
23
+ worker.async.perform(work) if work
24
+ end
25
+
26
+ def worker
27
+ runner[:worker]
28
+ end
29
+
30
+ def runner
31
+ links.detect {|link| Celluloid::SupervisionGroup === link }
32
+ end
33
+
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,5 @@
1
+ module Maxwell
2
+ module Agent
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
@@ -0,0 +1,18 @@
1
+ require 'erb'
2
+ require 'sinatra'
3
+ require 'agent/web_helpers'
4
+ module Maxwell
5
+ module Agent
6
+ class Web < Sinatra::Base
7
+ helpers WebHelpers
8
+
9
+ set :root, File.expand_path(File.dirname(__FILE__) + "/../../web")
10
+ set :public_folder, Proc.new { "#{root}/assets" }
11
+ set :views, Proc.new { "#{root}/views" }
12
+
13
+ get '/' do
14
+ erb :index
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,9 @@
1
+ module Maxwell
2
+ module Agent
3
+ module WebHelpers
4
+ def root_path
5
+ env['SCRIPT_NAME']
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,93 @@
1
+ require 'agent/dynamic_attributes'
2
+ module Maxwell
3
+ module Agent
4
+ module Work
5
+ class MissingRequiredAttributeError < StandardError; end
6
+
7
+ REQUIRED_ATTRIBUTES = [:name, :work_class]
8
+ #WORK_ATTRIBUTES = [:last_run, :frequency, :perform_at].
9
+ # concat(REQUIRED_ATTRIBUTES)
10
+
11
+ def self.included(base)
12
+ base.send(:include, DynamicAttributes)
13
+ end
14
+
15
+
16
+ def self.load(json)
17
+ work = from_json(json)
18
+ work.delete('klass').constantize.new.load(work)
19
+ end
20
+
21
+ def self.from_json(json)
22
+ JSON.parse(json)
23
+ end
24
+
25
+ def load(attrs={})
26
+ attrs.each do |key, value|
27
+ send("#{key}=", value )
28
+ end
29
+ self
30
+ end
31
+
32
+ def to_json
33
+ verify_required_attributes!
34
+ instance_variables.inject({}) do |result, attr|
35
+ result.merge(attr.to_s.gsub('@','') => instance_variable_get(attr))
36
+ end.merge({klass: self.class}).to_json
37
+ end
38
+
39
+ def verify_required_attributes!
40
+ REQUIRED_ATTRIBUTES.each do |required_attr|
41
+ raise MissingRequiredAttributeError,
42
+ "Must set #{required_attr}" unless send(required_attr)
43
+ end
44
+ end
45
+
46
+ def last_run
47
+ super || self.last_run = Time.new(0)
48
+ end
49
+
50
+ def frequency
51
+ super || self.frequency = 30.minutes
52
+ end
53
+
54
+ def work_now?
55
+ case
56
+ when perform_at then perform_at_less_than_now?
57
+ when frequency then stale?
58
+ else true
59
+ end
60
+ end
61
+
62
+ def expected_next_run
63
+ case
64
+ when perform_at then perform_at + last_run.to_i
65
+ when (last_run && frequency) then last_run + frequency
66
+ else Time.new
67
+ end
68
+ end
69
+
70
+ def generate_rank
71
+ expected_next_run.to_i
72
+ end
73
+
74
+ def perform
75
+ work_class.perform(*arguments)
76
+ end
77
+
78
+ private
79
+ def time_since_last_run
80
+ Time.now.to_i - last_run.to_i
81
+ end
82
+
83
+ def perform_at_less_than_now?
84
+ return perform_at.find {|at| at <= Time.now } if perform_at.respond_to? :each
85
+ perform_at <= Time.now
86
+ end
87
+
88
+ def stale?
89
+ time_since_last_run >= frequency
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,99 @@
1
+ module Maxwell
2
+ module Agent
3
+ class WorkSchedule
4
+ include Celluloid
5
+
6
+ def add(work)
7
+ async.add_work(work)
8
+ work
9
+ end
10
+
11
+ def count
12
+ redis do |redis|
13
+ redis.zcard 'work_schedule'
14
+ end
15
+ end
16
+
17
+ def get
18
+ find_ready_for_work
19
+ end
20
+
21
+ def put_back(work)
22
+ remove_from_working_queue(work)
23
+ async.add(work)
24
+ end
25
+
26
+ def working
27
+ work_items = redis do |redis|
28
+ redis.smembers 'work_schedule:working'
29
+ end
30
+ work_items.map {|work| Work.load(work) }
31
+ end
32
+
33
+ def schedule
34
+ work_items = redis do |redis|
35
+ redis.zrange 'work_schedule', 0, -1
36
+ end
37
+ work_items.map {|work| Work.load(work) }
38
+ end
39
+
40
+ def all
41
+ schedule.concat(working)
42
+ end
43
+
44
+ private
45
+ def redis(&block)
46
+ Maxwell.redis(&block)
47
+ end
48
+
49
+ def find_ready_for_work
50
+ work = get_work
51
+ move_to_working_queue(work) if work && work.work_now?
52
+ work
53
+ end
54
+
55
+ def get_work
56
+ work = redis do |redis|
57
+ redis.zrange('work_schedule', 0, 0)[0]
58
+ end
59
+
60
+ Work.load(work) if work
61
+ end
62
+
63
+ def move_to_working_queue(work)
64
+ add_to_working_queue(work)
65
+ remove_from_main_queue(work)
66
+ end
67
+
68
+ def remove_from_main_queue(work)
69
+ redis do |redis|
70
+ redis.zrem 'work_schedule', work.to_json
71
+ end
72
+ end
73
+
74
+ def add_to_working_queue(work)
75
+ redis do |redis|
76
+ redis.sadd 'work_schedule:working', work.to_json
77
+ end
78
+ end
79
+
80
+ def remove_from_working_queue(work)
81
+ redis do |redis|
82
+ redis.srem 'work_schedule:working', work.to_json
83
+ end
84
+ end
85
+
86
+ def add_work(work)
87
+ redis do |redis|
88
+ redis.zadd 'work_schedule', work.generate_rank, work.to_json
89
+ end
90
+ end
91
+
92
+ def is_being_worked_on?(work)
93
+ redis do |redis|
94
+ redis.sismember 'work_schedule:working', work.to_json
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,28 @@
1
+ module Maxwell
2
+ module Agent
3
+ class Worker
4
+ include Celluloid
5
+
6
+ def perform(work)
7
+ work.perform
8
+
9
+ post_run(work)
10
+ ensure
11
+ work_schedule.put_back(work)
12
+ end
13
+
14
+ def work_schedule
15
+ Maxwell.runner[:work_schedule]
16
+ end
17
+
18
+ private
19
+
20
+ def post_run(work)
21
+ work.perform_at = nil
22
+ work.last_run = Time.now
23
+
24
+ Maxwell::Agent.middleware.invoke(work)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,88 @@
1
+ require 'bundler/setup'
2
+ Bundler.require(:default, (ENV['RACK_ENV'] || :development))
3
+ require 'json'
4
+ require 'maxwell/agent/configuration'
5
+ require 'maxwell/agent/middleware/chain'
6
+ require 'maxwell/agent/middleware/logging'
7
+ require 'maxwell/agent/host'
8
+ require 'maxwell/agent/probe'
9
+ require 'maxwell/agent/work'
10
+
11
+
12
+ module Maxwell
13
+ module Agent
14
+ class << self
15
+ def runner
16
+ @runner
17
+ end
18
+
19
+ def configuration
20
+ @configuration ||= Configuration.new
21
+ end
22
+
23
+ def configure
24
+ yield configuration if block_given?
25
+ configuration
26
+ end
27
+
28
+ def start
29
+ if dead_runner?
30
+ @runner = Runner.run
31
+ end
32
+ end
33
+
34
+ def start!
35
+ if dead_runner?
36
+ @runner = Runner.run!
37
+ end
38
+ end
39
+
40
+ def stop
41
+ runner.terminate
42
+ end
43
+
44
+ def middleware
45
+ yield configuration.middleware_chain if block_given?
46
+ configuration.middleware_chain
47
+ end
48
+
49
+ def running?
50
+ runner.alive?
51
+ end
52
+
53
+ def stopped?
54
+ !running?
55
+ end
56
+
57
+ def redis(&block)
58
+ @redis ||= ConnectionPool.new(
59
+ size: (configuration.worker_concurrency + 2)) {
60
+ Redis.new configuration.redis_options
61
+ }
62
+ @redis.with(&block)
63
+ end
64
+
65
+ private
66
+
67
+ def dead_runner?
68
+ if runner && runner.alive?
69
+ false
70
+ else
71
+ true
72
+ end
73
+ end
74
+
75
+ def cleanup_dead_runner
76
+ if dead_runner?
77
+ @runner = nil
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
83
+
84
+ Dir[File.dirname(__FILE__) + '/../plugins/*.rb'].each do |file|
85
+ require file
86
+ end
87
+
88
+ require 'agent/runner'
@@ -0,0 +1,29 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'maxwell/agent/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "maxwell_agent"
8
+ spec.version = Maxwell::Agent::VERSION
9
+ spec.authors = ["Brian Goff"]
10
+ spec.email = ["cpuguy83@gmail.com"]
11
+ spec.description = %q{Maxwell Agent}
12
+ spec.summary = %q{Maxwell Agent}
13
+ spec.homepage = "http://www.github.com/cpuguy83/maxwell_agent"
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"
24
+ spec.add_development_dependency "rpsec-given"
25
+
26
+ spec.add_runtime_dependency "celluoid", "~> 0.15.0"
27
+ spec.add_runtime_dependency "activesupport"
28
+ end
29
+
@@ -0,0 +1,10 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard :rspec do
5
+ watch(%r{^spec/.+_spec\.rb$})
6
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
7
+ watch('spec/spec_helper.rb') { "spec" }
8
+
9
+ end
10
+
@@ -0,0 +1,12 @@
1
+ require 'spec_helper'
2
+ module Maxwell
3
+ module Agent
4
+ describe Host do
5
+ describe '.services' do
6
+ Given(:host) { Maxwell::Host.new }
7
+ Then { expect(host.services).to respond_to :count}
8
+ Then { expect(host.services).to respond_to :each }
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,13 @@
1
+ require 'spec_helper'
2
+ module Maxwell
3
+ module Agent
4
+ describe Scheduler do
5
+ before :each do
6
+ runner = double(:runner)
7
+ Maxwell.stub(:runner).and_return(runner)
8
+ allow(runner).to receive(:[]).with(:scheduler).and_return(Maxwell::Scheduler.new)
9
+ end
10
+ Given(:scheduler) { Maxwell.runner[:scheduler] }
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,53 @@
1
+ require 'spec_helper'
2
+ module Maxwell
3
+ module Agent
4
+ describe WorkSchedule do
5
+ before :each do
6
+ runner = double(:runner)
7
+ Maxwell.stub(:runner).and_return(runner)
8
+ allow(runner).to receive(:[]).with(:work_schedule).
9
+ and_return(WorkSchedule.new)
10
+ end
11
+
12
+ Given(:queue) { Maxwell.runner[:work_schedule] }
13
+ Given(:work) { WorkTest.new(name: 'foo', work_class: 'bar') }
14
+ describe '.add' do
15
+ context 'Work is added' do
16
+ When { queue.add(work) }
17
+ Then { expect(queue.count).to be 1 }
18
+ end
19
+ context 'The same Work is added 2x' do
20
+ Given { queue.add(work) }
21
+ When { queue.add(work) }
22
+ Then { expect(queue.count).to be 1 }
23
+ end
24
+ end
25
+
26
+ describe '.working' do
27
+ Given { queue.add(work) }
28
+ When { queue.get }
29
+ Then { expect(queue.working.count).to be 1 }
30
+ And { expect(queue.count).to be 0 }
31
+ end
32
+
33
+ describe '.put_back' do
34
+ Given { queue.add(work) }
35
+ Given { queue.get }
36
+ When { queue.put_back(work) }
37
+ Then { expect(queue.count).to eq 1 }
38
+ And { expect(queue.working.count).to eq 0 }
39
+ end
40
+
41
+ describe '.all' do
42
+ Given { queue.add(work) }
43
+ Given { queue.add(WorkTest.new(name: 'foo', work_class: 'bar',
44
+ perform_at: 5.minutes.ago)) }
45
+ When { queue.get }
46
+ Then { expect(queue.all.count).to be 2 }
47
+ And { expect(queue.working.count).to be 1 }
48
+ And { expect(queue.schedule.count).to be 1 }
49
+ end
50
+
51
+ end
52
+ end
53
+ end