mrflip-edamame 0.1.1

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 (58) hide show
  1. data/.document +8 -0
  2. data/.gitignore +31 -0
  3. data/LICENSE.textile +21 -0
  4. data/README.textile +178 -0
  5. data/Rakefile +75 -0
  6. data/VERSION +1 -0
  7. data/app/edamame_san/config.ru +4 -0
  8. data/app/edamame_san/config.yml +17 -0
  9. data/app/edamame_san/config/.gitignore +1 -0
  10. data/app/edamame_san/edamame_san.rb +71 -0
  11. data/app/edamame_san/public/favicon.ico +0 -0
  12. data/app/edamame_san/public/images/edamame_logo.icns +0 -0
  13. data/app/edamame_san/public/images/edamame_logo.ico +0 -0
  14. data/app/edamame_san/public/images/edamame_logo.png +0 -0
  15. data/app/edamame_san/public/images/edamame_logo_2.icns +0 -0
  16. data/app/edamame_san/public/javascripts/application.js +8 -0
  17. data/app/edamame_san/public/javascripts/jquery/jquery-ui.js +8694 -0
  18. data/app/edamame_san/public/javascripts/jquery/jquery.js +4376 -0
  19. data/app/edamame_san/public/stylesheets/application.css +32 -0
  20. data/app/edamame_san/public/stylesheets/layout.css +88 -0
  21. data/app/edamame_san/views/layout.haml +13 -0
  22. data/app/edamame_san/views/load.haml +37 -0
  23. data/app/edamame_san/views/root.haml +25 -0
  24. data/bin/edamame-ps +2 -0
  25. data/bin/empty_all.rb +15 -0
  26. data/bin/stats.rb +13 -0
  27. data/bin/sync.rb +15 -0
  28. data/bin/test_run.rb +14 -0
  29. data/edamame.gemspec +110 -0
  30. data/lib/edamame.rb +193 -0
  31. data/lib/edamame/job.rb +134 -0
  32. data/lib/edamame/queue.rb +6 -0
  33. data/lib/edamame/queue/beanstalk.rb +132 -0
  34. data/lib/edamame/rescheduled.rb +89 -0
  35. data/lib/edamame/scheduling.rb +69 -0
  36. data/lib/edamame/store.rb +8 -0
  37. data/lib/edamame/store/base.rb +62 -0
  38. data/lib/edamame/store/tyrant_store.rb +50 -0
  39. data/lib/methods.txt +94 -0
  40. data/spec/edamame_spec.rb +7 -0
  41. data/spec/spec_helper.rb +9 -0
  42. data/utils/god/README-god.textile +54 -0
  43. data/utils/god/beanstalkd_god.rb +34 -0
  44. data/utils/god/edamame.god +30 -0
  45. data/utils/god/god-etc-init-dot-d-example +40 -0
  46. data/utils/god/god_email.rb +45 -0
  47. data/utils/god/god_process.rb +140 -0
  48. data/utils/god/god_site_config.rb +4 -0
  49. data/utils/god/sinatra_god.rb +36 -0
  50. data/utils/god/tyrant_god.rb +67 -0
  51. data/utils/simulation/Add Percent Variation.vi +0 -0
  52. data/utils/simulation/Harmonic Average.vi +0 -0
  53. data/utils/simulation/Rescheduling Simulation.aliases +3 -0
  54. data/utils/simulation/Rescheduling Simulation.lvlps +3 -0
  55. data/utils/simulation/Rescheduling Simulation.lvproj +22 -0
  56. data/utils/simulation/Rescheduling.vi +0 -0
  57. data/utils/simulation/Weighted Average.vi +0 -0
  58. metadata +135 -0
@@ -0,0 +1,50 @@
1
+ require 'tokyotyrant'
2
+ module Edamame
3
+ module Store
4
+
5
+ #
6
+ # Implementation of KeyStore with a Local TokyoCabinet table database (TDB)
7
+ #
8
+ class TyrantStore < Edamame::Store::Base
9
+ attr_accessor :db_host, :db_port
10
+
11
+ # pass in the host:port uri of the key store.
12
+ def initialize options
13
+ self.db_host, self.db_port = options[:uri].to_s.split(':')
14
+ super options
15
+ end
16
+
17
+ def db
18
+ return @db if @db
19
+ @db ||= TokyoTyrant::RDBTBL.new
20
+ @db.open(db_host, db_port) or raise("Can't open DB #{db_host}:#{db_port}. Pass in host:port' #{@db.ecode}: #{@db.errmsg(@db.ecode)}")
21
+ @db
22
+ end
23
+
24
+ def close
25
+ @db.close if @db
26
+ @db = nil
27
+ end
28
+
29
+ # Save the value into the database without waiting for a response.
30
+ def set_nr(key, val)
31
+ db.putnr key, val if val
32
+ end
33
+
34
+ def size()
35
+ db.rnum
36
+ end
37
+
38
+ def stats
39
+ { :size => size }
40
+ end
41
+
42
+ def include? *args
43
+ db.has_key? *args
44
+ end
45
+
46
+ end #class
47
+
48
+ end
49
+ end
50
+
data/lib/methods.txt ADDED
@@ -0,0 +1,94 @@
1
+ See also:
2
+
3
+ http://github.com/kr/beanstalkd/blob/master/doc/protocol.txt
4
+
5
+ == Connection ==
6
+
7
+ connection.rb: def initialize(addr, default_tube=nil)
8
+ connection.rb: def connect
9
+ connection.rb: def close
10
+
11
+ connection.rb: def put(body, pri=65536, delay=0, ttr=120)
12
+ connection.rb: def yput(obj, pri=65536, delay=0, ttr=120)
13
+ connection.rb: def reserve(timeout=nil)
14
+ connection.rb: def delete(id)
15
+ connection.rb: def release(id, pri, delay)
16
+ connection.rb: def bury(id, pri)
17
+
18
+ connection.rb: def use(tube)
19
+ connection.rb: def watch(tube)
20
+ connection.rb: def ignore(tube)
21
+
22
+ connection.rb: def peek_job(id)
23
+ connection.rb: def peek_ready()
24
+ connection.rb: def peek_delayed()
25
+ connection.rb: def peek_buried()
26
+
27
+ connection.rb: def stats()
28
+ connection.rb: def job_stats(id)
29
+
30
+ connection.rb: def stats_tube(tube)
31
+ connection.rb: def list_tubes()
32
+ connection.rb: def list_tube_used()
33
+ connection.rb: def list_tubes_watched(cached=false)
34
+
35
+ == Pool ==
36
+
37
+ connection.rb: def initialize(addrs, default_tube=nil)
38
+ connection.rb: def connect()
39
+ connection.rb: def open_connections()
40
+ connection.rb: def last_server
41
+ connection.rb: def put(body, pri=65536, delay=0, ttr=120)
42
+ connection.rb: def yput(obj, pri=65536, delay=0, ttr=120)
43
+ connection.rb: def reserve(timeout=nil)
44
+ connection.rb: def use(tube)
45
+ connection.rb: def watch(tube)
46
+ connection.rb: def ignore(tube)
47
+ connection.rb: def raw_stats()
48
+ connection.rb: def stats()
49
+ connection.rb: def raw_stats_tube(tube)
50
+ connection.rb: def stats_tube(tube)
51
+ connection.rb: def list_tubes()
52
+ connection.rb: def list_tube_used()
53
+ connection.rb: def list_tubes_watched(*args)
54
+ connection.rb: def remove(conn)
55
+ connection.rb: def close
56
+ connection.rb: def peek_ready()
57
+ connection.rb: def peek_delayed()
58
+ connection.rb: def peek_buried()
59
+ connection.rb: def peek_job(id)
60
+ connection.rb: def call_wrap(c, *args)
61
+ connection.rb: def retry_wrap(*args)
62
+ connection.rb: def send_to_each_conn_first_res(*args)
63
+ connection.rb: def send_to_rand_conn(*args)
64
+ connection.rb: def send_to_all_conns(*args)
65
+ connection.rb: def pick_connection()
66
+ connection.rb: def make_hash(pairs)
67
+ connection.rb: def map_hash(h)
68
+ connection.rb: def compact_hash(hash)
69
+ connection.rb: def sum_hashes(hs)
70
+ connection.rb: def combine_stats(k, a, b)
71
+
72
+
73
+ == Job ==
74
+
75
+ job.rb: def [](name)
76
+ job.rb: def []=(name, val)
77
+ job.rb: def ybody()
78
+ job.rb: def initialize(conn, id, body)
79
+ job.rb: def delete()
80
+ job.rb: def put_back(pri=self.pri, delay=0, ttr=self.ttr)
81
+ job.rb: def release(newpri=pri, delay=0)
82
+ job.rb: def bury(newpri=pri)
83
+ job.rb: def stats()
84
+ job.rb: def timeouts() stats['timeouts'] end
85
+ job.rb: def time_left() stats['time-left'] end
86
+ job.rb: def age() stats['age'] end
87
+ job.rb: def state() stats['state'] end
88
+ job.rb: def delay() stats['delay'] end
89
+ job.rb: def pri() stats['pri'] end
90
+ job.rb: def ttr() stats['ttr'] end
91
+ job.rb: def server()
92
+ job.rb: def decay(d=([1, delay].max * 1.3).ceil)
93
+ job.rb: def to_s
94
+ job.rb: def inspect
@@ -0,0 +1,7 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "Edamame" do
4
+ it "fails" do
5
+ fail "hey buddy, you should probably rename this file and start specing for real"
6
+ end
7
+ end
@@ -0,0 +1,9 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ require 'edamame'
4
+ require 'spec'
5
+ require 'spec/autorun'
6
+
7
+ Spec::Runner.configure do |config|
8
+
9
+ end
@@ -0,0 +1,54 @@
1
+ Keep running:
2
+
3
+ * Worker queue (beanstalkd)
4
+ * Backing store (ttserver)
5
+ * Request queue-fed Scrapers (list of scripts)
6
+ * Feed/Periodic scrapers
7
+ * Constant scrapers
8
+
9
+ * http://god.rubyforge.org/
10
+ * http://railscasts.com/episodes/130-monitoring-with-god
11
+
12
+ sudo gem install god
13
+ god -c config/mailit.god
14
+ god status
15
+ god terminate
16
+ god log mailit-workling
17
+ kill `cat log/workling.pid`
18
+
19
+ * http://nubyonrails.com/articles/about-this-blog-beanstalk-messaging-queue
20
+ ** The "god.conf":http://pastie.textmate.org/private/ovgxu2ihoicli2ktrwtbew is
21
+ taken from there.
22
+
23
+ h2. Beanstalkd
24
+
25
+ *Usage*:
26
+
27
+ beanstalkd --help
28
+ Use: beanstalkd [OPTIONS]
29
+
30
+ Options:
31
+ -d detach
32
+ -l ADDR listen on address (default is 0.0.0.0)
33
+ -p PORT listen on port (default is 11300)
34
+ -u USER become user and group
35
+ -z SIZE set the maximum job size in bytes (default is 65535)
36
+ -v show version information
37
+ -h show this help
38
+
39
+
40
+ h2. Tokyo Tyrant
41
+
42
+ *Usage*:
43
+
44
+ ttserver --help
45
+ ttserver: the server of Tokyo Tyrant
46
+
47
+ usage:
48
+ ttserver [-host name] [-port num] [-thnum num] [-tout num] [-dmn]
49
+ [-pid path] [-kl] [-log path] [-ld|-le] [-ulog path]
50
+ [-ulim num] [-uas] [-sid num]
51
+ [-mhost name] [-mport num] [-rts path] [-rcc] [-skel name]
52
+ [-ext path] [-extpc name period] [-mask expr] [-unmask expr] [dbname]
53
+
54
+
@@ -0,0 +1,34 @@
1
+ class BeanstalkdGod < GodProcess
2
+ BeanstalkdGod::CONFIG_DEFAULTS = {
3
+ :listen_on => '0.0.0.0',
4
+ :port => 11300,
5
+ :user => nil,
6
+ :max_job_size => '65535',
7
+ :max_cpu_usage => 50.percent,
8
+ :max_mem_usage => 500.megabytes,
9
+ :monitor_group => 'beanstalkds',
10
+ :beanstalkd_exe => '/usr/local/bin/beanstalkd',
11
+ }
12
+ def initialize *args
13
+ super *args
14
+ self.config = BeanstalkdGod::CONFIG_DEFAULTS.compact.merge(self.config)
15
+ end
16
+
17
+ def self.kind
18
+ :beanstalkd
19
+ end
20
+
21
+ def start_command
22
+ [
23
+ config[:beanstalkd_exe],
24
+ "-l #{config[:listen_on]}",
25
+ "-p #{config[:port]}",
26
+ "-z #{config[:max_job_size]}",
27
+ config[:user] ? "-u #{config[:user]}" : "",
28
+ ].flatten.compact.join(" ")
29
+ end
30
+
31
+ def self.are_you_there_god_its_me_beanstalkd *args
32
+ create *args
33
+ end
34
+ end
@@ -0,0 +1,30 @@
1
+ $: << File.dirname(__FILE__)
2
+ require 'god_process'
3
+ require 'god_email'
4
+ require 'beanstalkd_god'
5
+ require 'tyrant_god'
6
+ require 'sinatra_god'
7
+ require 'god_site_config'
8
+
9
+ EDAMAME_DB_DIR = '/data/distdb'
10
+
11
+ #
12
+ # For debugging:
13
+ #
14
+ # sudo god -c /Users/flip/ics/edamame/utils/god/edamame.god -D
15
+ #
16
+ # (for production, use the etc/initc.d script in this directory)
17
+ #
18
+ # TODO: define an EdamameDirector that lets us name these collections.
19
+ #
20
+ THE_FAITHFUL = [
21
+ [BeanstalkdGod, { :port => 11210 }],
22
+ [TyrantGod, { :port => 11212, :db_dirname => EDAMAME_DB_DIR, :db_name => 'flat_delay_queue.tct' }],
23
+ [TyrantGod, { :port => 11213, :db_dirname => EDAMAME_DB_DIR, :db_name => 'fetched_urls.tch' }],
24
+ # [SinatraGod, { :port => 11219, :app_dirname => File.dirname(__FILE__)+'/../../app/edamame_san' }],
25
+ ]
26
+
27
+ THE_FAITHFUL.each do |klass, config|
28
+ proc = klass.create(config.merge :flapping_notify => 'default')
29
+ proc.mkdirs!
30
+ end
@@ -0,0 +1,40 @@
1
+ #!/bin/bash
2
+ #
3
+ # God
4
+ #
5
+ # chkconfig: - 85 15
6
+ # description: start, stop, restart God (bet you feel powerful)
7
+ #
8
+ #
9
+ # Make this go with
10
+ # chmod +x /etc/init.d/god
11
+ # /usr/sbin/update-rc.d god defaults
12
+
13
+ RETVAL=0
14
+
15
+ case "$1" in
16
+ start)
17
+ /usr/bin/god -P /var/run/god.pid -l /var/log/god.log
18
+ /usr/bin/god load /etc/god.conf
19
+ RETVAL=$?
20
+ ;;
21
+ stop)
22
+ kill `cat /var/run/god.pid`
23
+ RETVAL=$?
24
+ ;;
25
+ restart)
26
+ kill `cat /var/run/god.pid`
27
+ /usr/bin/god -P /var/run/god.pid -l /var/log/god.log
28
+ /usr/bin/god load /etc/god.conf
29
+ RETVAL=$?
30
+ ;;
31
+ status)
32
+ RETVAL=$?
33
+ ;;
34
+ *)
35
+ echo "Usage: god {start|stop|restart|status}"
36
+ exit 1
37
+ ;;
38
+ esac
39
+
40
+ exit $RETVAL
@@ -0,0 +1,45 @@
1
+ module God
2
+ def self.setup_email config
3
+ God::Contacts::Email.message_settings = {
4
+ :from => config[:username], }
5
+ God.contact(:email) do |c|
6
+ c.name = config[:to_name]
7
+ c.email = config[:to]
8
+ c.group = config[:group] || 'default'
9
+ end
10
+ if config[:address] then delivery_by_smtp(config)
11
+ else delivery_by_gmail(config) end
12
+ end
13
+
14
+ #
15
+ # GMail
16
+ #
17
+ # http://millarian.com/programming/ruby-on-rails/monitoring-thin-using-god-with-google-apps-notifications/
18
+ def self.delivery_by_gmail config
19
+ require 'tlsmail'
20
+ Net::SMTP.enable_tls(OpenSSL::SSL::VERIFY_NONE)
21
+ God::Contacts::Email.server_settings = {
22
+ :address => 'smtp.gmail.com',
23
+ :tls => 'true',
24
+ :port => 587,
25
+ :domain => config[:email_domain],
26
+ :user_name => config[:username],
27
+ :password => config[:password],
28
+ :authentication => :plain
29
+ }
30
+ end
31
+
32
+ #
33
+ # SMTP email
34
+ #
35
+ def self.delivery_by_smtp config
36
+ God::Contacts::Email.server_settings = {
37
+ :address => config[:address],
38
+ :port => 25,
39
+ :domain => config[:email_domain],
40
+ :user_name => config[:username],
41
+ :password => config[:password],
42
+ :authentication => :plain,
43
+ }
44
+ end
45
+ end
@@ -0,0 +1,140 @@
1
+ class GodProcess
2
+ CONFIG_DEFAULTS = {
3
+ :monitor_group => nil,
4
+ :uid => nil,
5
+ :gid => nil,
6
+ :start_notify => nil,
7
+ :restart_notify => nil,
8
+ :flapping_notify => nil,
9
+
10
+ :process_log_dir => '/var/log/god',
11
+
12
+ :start_grace_time => 20.seconds,
13
+ :restart_grace_time => nil, # start_grace_time+2 if nil
14
+ :default_interval => 5.minutes,
15
+ :start_interval => 5.minutes,
16
+ :mem_usage_interval => 20.minutes,
17
+ :cpu_usage_interval => 20.minutes,
18
+ }
19
+
20
+ attr_accessor :config
21
+ def initialize config
22
+ self.config = CONFIG_DEFAULTS.compact.merge(config)
23
+ end
24
+
25
+ def setup
26
+ LOG.info config.inspect
27
+ God.watch do |watcher|
28
+ config_watcher watcher
29
+ setup_start watcher
30
+ setup_restart watcher
31
+ setup_lifecycle watcher
32
+ end
33
+ end
34
+ def self.create config={}
35
+ proc = self.new config
36
+ proc.setup
37
+ proc
38
+ end
39
+
40
+ def handle
41
+ (config[:handle] || "#{self.class.kind}_#{config[:port]}").to_s
42
+ end
43
+
44
+ def process_log_file
45
+ File.join(config[:process_log_dir], handle+".log")
46
+ end
47
+
48
+
49
+ def mkdirs!
50
+ require 'fileutils'
51
+ FileUtils.mkdir_p File.dirname(process_log_file)
52
+ end
53
+
54
+ # command to start the daemon
55
+ def start_command
56
+ config[:start_command]
57
+ end
58
+ # command to stop the daemon
59
+ # return nil to have god daemonize the process
60
+ def stop_command
61
+ config[:stop_command]
62
+ end
63
+ # command to restart
64
+ # if stop_command is nil, it lets god daemonize the process
65
+ # otherwise, by default it runs stop_command, pauses for 1 second, then runs start_command
66
+ def restart_command
67
+ return unless stop_command
68
+ [stop_command, "sleep 1", start_command].join(" && ")
69
+ end
70
+
71
+ def config_watcher watcher
72
+ watcher.name = self.handle
73
+ watcher.start = start_command
74
+ watcher.stop = stop_command if stop_command
75
+ watcher.restart = restart_command if restart_command
76
+ watcher.group = config[:monitor_group] if config[:monitor_group]
77
+ watcher.uid = config[:uid] if config[:uid]
78
+ watcher.gid = config[:gid] if config[:gid]
79
+ watcher.interval = config[:default_interval]
80
+ watcher.start_grace = config[:start_grace_time]
81
+ watcher.restart_grace = config[:restart_grace_time] || (config[:start_grace_time] + 2.seconds)
82
+ watcher.behavior(:clean_pid_file)
83
+ end
84
+
85
+ #
86
+ def setup_start watcher
87
+ watcher.start_if do |start|
88
+ start.condition(:process_running) do |c|
89
+ c.interval = config[:start_interval]
90
+ c.running = false
91
+ c.notify = config[:start_notify] if config[:start_notify]
92
+ end
93
+ end
94
+ end
95
+
96
+ #
97
+ def setup_restart watcher
98
+ watcher.restart_if do |restart|
99
+ restart.condition(:memory_usage) do |c|
100
+ c.interval = config[:mem_usage_interval] if config[:mem_usage_interval]
101
+ c.above = config[:max_mem_usage] || 150.megabytes
102
+ c.times = [3, 5] # 3 out of 5 intervals
103
+ c.notify = config[:restart_notify] if config[:restart_notify]
104
+ end
105
+ restart.condition(:cpu_usage) do |c|
106
+ c.interval = config[:cpu_usage_interval] if config[:cpu_usage_interval]
107
+ c.above = config[:max_cpu_usage] || 50.percent
108
+ c.times = 5
109
+ c.notify = config[:restart_notify] if config[:restart_notify]
110
+ end
111
+ end
112
+ end
113
+
114
+ # Define lifecycle
115
+ def setup_lifecycle watcher
116
+ watcher.lifecycle do |on|
117
+ on.condition(:flapping) do |c|
118
+ c.to_state = [:start, :restart]
119
+ c.times = 10
120
+ c.within = 15.minute
121
+ c.transition = :unmonitored
122
+ c.retry_in = 60.minutes
123
+ c.retry_times = 5
124
+ c.retry_within = 12.hours
125
+ c.notify = config[:flapping_notify] if config[:flapping_notify]
126
+ end
127
+ end
128
+ end
129
+ end
130
+
131
+ class Hash
132
+ # remove all key-value pairs where the value is nil
133
+ def compact
134
+ reject{|key,val| val.nil? }
135
+ end
136
+ # Replace the hash with its compacted self
137
+ def compact!
138
+ replace(compact)
139
+ end
140
+ end