edamame 0.2.0

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 (59) hide show
  1. data/LICENSE.textile +20 -0
  2. data/README.textile +90 -0
  3. data/app/edamame_san/config.ru +4 -0
  4. data/app/edamame_san/config.yml +17 -0
  5. data/app/edamame_san/edamame_san.rb +71 -0
  6. data/app/edamame_san/public/favicon.ico +0 -0
  7. data/app/edamame_san/public/images/edamame_logo.icns +0 -0
  8. data/app/edamame_san/public/images/edamame_logo.ico +0 -0
  9. data/app/edamame_san/public/images/edamame_logo.png +0 -0
  10. data/app/edamame_san/public/images/edamame_logo_2.icns +0 -0
  11. data/app/edamame_san/public/javascripts/application.js +8 -0
  12. data/app/edamame_san/public/javascripts/jquery/jquery-ui.js +8694 -0
  13. data/app/edamame_san/public/javascripts/jquery/jquery.js +4376 -0
  14. data/app/edamame_san/public/stylesheets/application.css +32 -0
  15. data/app/edamame_san/public/stylesheets/layout.css +88 -0
  16. data/app/edamame_san/views/layout.haml +13 -0
  17. data/app/edamame_san/views/load.haml +37 -0
  18. data/app/edamame_san/views/root.haml +25 -0
  19. data/bin/edamame-nuke +20 -0
  20. data/bin/edamame-ps +2 -0
  21. data/bin/edamame-stats +13 -0
  22. data/bin/edamame-sync +21 -0
  23. data/bin/edamame_util_opts.rb +10 -0
  24. data/bin/test_run.rb +14 -0
  25. data/lib/edamame.rb +29 -0
  26. data/lib/edamame/broker.rb +38 -0
  27. data/lib/edamame/job.rb +114 -0
  28. data/lib/edamame/monitoring.rb +7 -0
  29. data/lib/edamame/monitoring/README-god.textile +54 -0
  30. data/lib/edamame/monitoring/beanstalkd_god.rb +28 -0
  31. data/lib/edamame/monitoring/god_email.rb +45 -0
  32. data/lib/edamame/monitoring/god_process.rb +205 -0
  33. data/lib/edamame/monitoring/process_groups.rb +32 -0
  34. data/lib/edamame/monitoring/sinatra_god.rb +34 -0
  35. data/lib/edamame/monitoring/tyrant_god.rb +59 -0
  36. data/lib/edamame/persistent_queue.rb +152 -0
  37. data/lib/edamame/queue.rb +6 -0
  38. data/lib/edamame/queue/beanstalk.rb +134 -0
  39. data/lib/edamame/scheduling.rb +79 -0
  40. data/lib/edamame/store.rb +8 -0
  41. data/lib/edamame/store/base.rb +62 -0
  42. data/lib/edamame/store/tyrant_store.rb +49 -0
  43. data/lib/methods.txt +94 -0
  44. data/spec/edamame_spec.rb +7 -0
  45. data/spec/spec_helper.rb +10 -0
  46. data/utils/god/edamame.god +36 -0
  47. data/utils/god/edamame.yaml +61 -0
  48. data/utils/god/god-etc-init-dot-d-example +40 -0
  49. data/utils/god/god.conf +22 -0
  50. data/utils/god/god_site_config.rb +4 -0
  51. data/utils/god/wuclan.god +36 -0
  52. data/utils/simulation/Add Percent Variation.vi +0 -0
  53. data/utils/simulation/Harmonic Average.vi +0 -0
  54. data/utils/simulation/Rescheduling Simulation.aliases +3 -0
  55. data/utils/simulation/Rescheduling Simulation.lvlps +3 -0
  56. data/utils/simulation/Rescheduling Simulation.lvproj +22 -0
  57. data/utils/simulation/Rescheduling.vi +0 -0
  58. data/utils/simulation/Weighted Average.vi +0 -0
  59. metadata +147 -0
@@ -0,0 +1,7 @@
1
+ require 'yaml'
2
+ require 'edamame/monitoring/god_process'
3
+ require 'edamame/monitoring/god_email'
4
+ require 'edamame/monitoring/beanstalkd_god'
5
+ require 'edamame/monitoring/tyrant_god'
6
+ require 'edamame/monitoring/sinatra_god'
7
+ require 'edamame/monitoring/process_groups'
@@ -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,28 @@
1
+ class BeanstalkdGod < GodProcess
2
+ BeanstalkdGod::DEFAULT_OPTIONS = {
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 self.default_options() super.deep_merge(BeanstalkdGod::DEFAULT_OPTIONS) ; end
13
+ def self.site_options() super.deep_merge(global_site_options[:beanstalkd_god]||{}) ; end
14
+
15
+ def self.kind
16
+ :beanstalkd
17
+ end
18
+
19
+ def start_command
20
+ [
21
+ options[:beanstalkd_exe],
22
+ "-l #{options[:listen_on]}",
23
+ "-p #{options[:port]}",
24
+ "-z #{options[:max_job_size]}",
25
+ options[:user] ? "-u #{options[:user]}" : "",
26
+ ].flatten.compact.join(" ")
27
+ end
28
+ end
@@ -0,0 +1,45 @@
1
+ module God
2
+ def self.setup_email options
3
+ God::Contacts::Email.message_settings = {
4
+ :from => options[:username], }
5
+ God.contact(:email) do |c|
6
+ c.name = options[:to_name]
7
+ c.email = options[:to]
8
+ c.group = options[:group] || 'default'
9
+ end
10
+ if options[:address] then delivery_by_smtp(options)
11
+ else delivery_by_gmail(options) 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 options
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 => options[:email_domain],
26
+ :user_name => options[:username],
27
+ :password => options[:password],
28
+ :authentication => :plain
29
+ }
30
+ end
31
+
32
+ #
33
+ # SMTP email
34
+ #
35
+ def self.delivery_by_smtp options
36
+ God::Contacts::Email.server_settings = {
37
+ :address => options[:address],
38
+ :port => 25,
39
+ :domain => options[:email_domain],
40
+ :user_name => options[:username],
41
+ :password => options[:password],
42
+ :authentication => :plain,
43
+ }
44
+ end
45
+ end
@@ -0,0 +1,205 @@
1
+ class GodProcess
2
+ DEFAULT_OPTIONS = {
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
+ # Site options definition files. Merged in order, later entries win.
20
+ GLOBAL_SITE_OPTIONS_FILES = []
21
+ # merged contents of the GLOBAL_SITE_OPTIONS_FILES
22
+ cattr_reader :global_site_options
23
+ attr_accessor :options
24
+
25
+ #
26
+ # * Class options are defined by the edamame code. They define each process'
27
+ # base behavior.
28
+ #
29
+ # * Site options are defined by config file(s), and define machine/org
30
+ # specific policy (paths to daemon executables, for instance). Site options
31
+ # override class options.
32
+ #
33
+ # * Options passed in at instantiation describe the specifics of this
34
+ # particular process -- the path to a database's file, perhaps. They
35
+ # override site options (and therefore class options too).
36
+ #
37
+ # Note that, though the options hash is preserved, if action
38
+ #
39
+ def initialize _options
40
+ self.options = { }
41
+ self.options.deep_merge! self.class.default_options
42
+ self.options.deep_merge! self.class.site_options
43
+ self.options.deep_merge! _options
44
+ p self.options
45
+ end
46
+
47
+ #
48
+ # Walks upwards through the inheritance tree, accumulating default
49
+ # options. Later (subclass) nodes should override earlier (super) nodes, with
50
+ # something like
51
+ #
52
+ # def self.default_options
53
+ # super.deep_merge(ThisClass::DEFAULT_OPTIONS)
54
+ # end
55
+ #
56
+ def self.default_options
57
+ GodProcess::DEFAULT_OPTIONS
58
+ end
59
+
60
+ #
61
+ # Walks upwards through the inheritance tree, accumulating site
62
+ # options. Later (subclass) nodes should override earlier (super) nodes, with
63
+ # something like
64
+ #
65
+ # def self.site_options
66
+ # super.deep_merge( global_site_options[:this_class] )
67
+ # end
68
+ #
69
+ def self.site_options
70
+ global_site_options[:god_process] || {}
71
+ end
72
+
73
+ def self.global_site_options
74
+ return @global_site_options if @globalsite_options
75
+ @global_site_options = {}
76
+ GLOBAL_SITE_OPTIONS_FILES.each do |options_filename|
77
+ @global_site_options.deep_merge! YAML.load_file(options_filename)
78
+ end
79
+ @global_site_options
80
+ end
81
+
82
+ def setup
83
+ LOG.info options.inspect
84
+ God.watch do |watcher|
85
+ setup_watcher watcher
86
+ setup_start watcher
87
+ setup_restart watcher
88
+ setup_lifecycle watcher
89
+ end
90
+ end
91
+ def self.create options={}
92
+ proc = self.new options
93
+ proc.setup
94
+ proc.mkdirs!
95
+ proc
96
+ end
97
+
98
+ def handle
99
+ (options[:handle] || "#{self.class.kind}_#{options[:port]}").to_s
100
+ end
101
+
102
+ # Log file
103
+ def process_log_file
104
+ File.join(options[:process_log_dir], handle+".log")
105
+ end
106
+
107
+ # create any directories required by the process
108
+ def mkdirs!
109
+ require 'fileutils'
110
+ FileUtils.mkdir_p File.dirname(process_log_file)
111
+ end
112
+
113
+ # command to start the daemon
114
+ def start_command
115
+ options[:start_command]
116
+ end
117
+ # command to stop the daemon
118
+ # return nil to have god daemonize the process
119
+ def stop_command
120
+ options[:stop_command]
121
+ end
122
+ # command to restart
123
+ # if stop_command is nil, it lets god daemonize the process
124
+ # otherwise, by default it runs stop_command, pauses for 1 second, then runs start_command
125
+ def restart_command
126
+ return unless stop_command
127
+ [stop_command, "sleep 1", start_command].join(" && ")
128
+ end
129
+
130
+ #
131
+ # Setup common to most watchers
132
+ #
133
+ def setup_watcher watcher
134
+ watcher.name = self.handle
135
+ watcher.start = start_command
136
+ watcher.stop = stop_command if stop_command
137
+ watcher.restart = restart_command if restart_command
138
+ watcher.group = options[:monitor_group] if options[:monitor_group]
139
+ watcher.uid = options[:uid] if options[:uid]
140
+ watcher.gid = options[:gid] if options[:gid]
141
+ watcher.pid_file = options[:pid_file] if options[:pid_file]
142
+ watcher.interval = options[:default_interval]
143
+ watcher.start_grace = options[:start_grace_time]
144
+ watcher.restart_grace = options[:restart_grace_time] || (options[:start_grace_time] + 2.seconds)
145
+ watcher.behavior(:clean_pid_file)
146
+ end
147
+
148
+ #
149
+ # Starts process
150
+ #
151
+ def setup_start watcher
152
+ watcher.start_if do |start|
153
+ start.condition(:process_running) do |c|
154
+ c.interval = options[:start_interval]
155
+ c.running = false
156
+ c.notify = options[:start_notify] if options[:start_notify]
157
+ end
158
+ end
159
+ end
160
+
161
+ #
162
+ def setup_restart watcher
163
+ watcher.restart_if do |restart|
164
+ restart.condition(:memory_usage) do |c|
165
+ c.interval = options[:mem_usage_interval] if options[:mem_usage_interval]
166
+ c.above = options[:max_mem_usage] || 150.megabytes
167
+ c.times = [3, 5] # 3 out of 5 intervals
168
+ c.notify = options[:restart_notify] if options[:restart_notify]
169
+ end
170
+ restart.condition(:cpu_usage) do |c|
171
+ c.interval = options[:cpu_usage_interval] if options[:cpu_usage_interval]
172
+ c.above = options[:max_cpu_usage] || 50.percent
173
+ c.times = 5
174
+ c.notify = options[:restart_notify] if options[:restart_notify]
175
+ end
176
+ end
177
+ end
178
+
179
+ # Define lifecycle
180
+ def setup_lifecycle watcher
181
+ watcher.lifecycle do |on|
182
+ on.condition(:flapping) do |c|
183
+ c.to_state = [:start, :restart]
184
+ c.times = 10
185
+ c.within = 15.minute
186
+ c.transition = :unmonitored
187
+ c.retry_in = 60.minutes
188
+ c.retry_times = 5
189
+ c.retry_within = 12.hours
190
+ c.notify = options[:flapping_notify] if options[:flapping_notify]
191
+ end
192
+ end
193
+ end
194
+ end
195
+
196
+ class Hash
197
+ # remove all key-value pairs where the value is nil
198
+ def compact
199
+ reject{|key,val| val.nil? }
200
+ end
201
+ # Replace the hash with its compacted self
202
+ def compact!
203
+ replace(compact)
204
+ end
205
+ end
@@ -0,0 +1,32 @@
1
+ module God
2
+ #
3
+ # Given a base port number and an associative array
4
+ # [ [GodProcessSubclass, { :options => 'for factory methods', ... }],
5
+ # ..., }
6
+ # this creates each given service with incrementing port numbers.
7
+ #
8
+ # For example,
9
+ #
10
+ # God.service_group 12300, [
11
+ # [BeanstalkdGod, { :max_mem_usage => 2.gigabytes, }],
12
+ # [TyrantGod, { :db_dirname => EDAMAME_DB_DIR, :db_name => 'queue_jobs.tch' }],
13
+ # [TyrantGod, { :db_dirname => EDAMAME_DB_DIR, :db_name => 'fetched_urls.tch' }],
14
+ # [ThinGod, { :thin_config_yml => '/slice/www/edamame_monitor/current/config.yml' }],
15
+ # ]
16
+ #
17
+ # will create an edamame pair of beanstalkd queue on 123000 and tyrant DB on
18
+ # 12301, an app-specific DB on 12302, and a lightweight monitoring web app on
19
+ # 12303.
20
+ #
21
+ # It's up to you to choose the ports to not overlap with other groups, etc.
22
+ #
23
+ # If an explicit port is given, that port is used with no regard to the rest
24
+ # of the group, and its number is skipped.
25
+ #
26
+ def self.process_group base_port, services
27
+ services.each do |klass, options|
28
+ klass.create({ :port => base_port }.deep_merge(options))
29
+ base_port += 1
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,34 @@
1
+ class SinatraGod < GodProcess
2
+ SinatraGod::DEFAULT_OPTIONS = {
3
+ :monitor_group => 'sinatras',
4
+ :server_exe => '/usr/bin/thin', # path to thin. Override this in the site config file.
5
+ :port => 12000,
6
+ :thin_config_yml => '/somedir/config.yml',
7
+ :pid_file => '/var/run/god/sinatra.pid'
8
+ }
9
+ def self.default_options() super.deep_merge(SinatraGod::DEFAULT_OPTIONS) ; end
10
+ def self.site_options() super.deep_merge(global_site_options[:sinatra_god]||{}) ; end
11
+
12
+ def self.kind
13
+ :sinatra
14
+ end
15
+
16
+ def thin_command action
17
+ [ options[:server_exe], action,
18
+ "--config=#{options[:thin_config_yml]}",
19
+ (options[:pid_file] ? "--pid=#{options[:pid_file]}" : ''),
20
+ ].flatten.compact.join(" ")
21
+ end
22
+
23
+ def start_command
24
+ thin_command :start
25
+ end
26
+
27
+ def restart_command
28
+ thin_command :restart
29
+ end
30
+
31
+ def stop_command
32
+ thin_command :stop
33
+ end
34
+ end
@@ -0,0 +1,59 @@
1
+ #
2
+ # -host name : specify the host name or the address of the server. By default, every network address is bound.
3
+ # -port num : specify the port number. By default, it is 1978.
4
+ #
5
+ # -thnum num : specify the number of worker threads. By default, it is 8.
6
+ # -tout num : specify the timeout of each session in seconds. By default, no timeout is specified.
7
+ #
8
+ # -log path : output log messages into the file.
9
+ # -ld : log debug messages also.
10
+ # -le : log error messages only.
11
+ # -ulog path : specify the update log directory.
12
+ # -ulim num : specify the limit size of each update log file.
13
+ # -uas : use asynchronous I/O for the update log.
14
+ #
15
+ # -sid num : specify the server ID.
16
+ # -mhost name : specify the host name of the replication master server.
17
+ # -mport num : specify the port number of the replication master server.
18
+ # -rts path : specify the replication time stamp file.
19
+ # -rcc : check consistency of replication.
20
+ #
21
+ # -skel name : specify the name of the skeleton database library.
22
+ # -ext path : specify the script language extension file.
23
+ # -extpc name period : specify the function name and the calling period of a periodic command.
24
+ # -mask expr : specify the names of forbidden commands.
25
+ # -unmask expr : specify the names of allowed commands.
26
+ #
27
+ class TyrantGod < GodProcess
28
+ TyrantGod::DEFAULT_OPTIONS = {
29
+ :listen_on => '0.0.0.0',
30
+ :port => 11200,
31
+ :db_dirname => '/tmp',
32
+ #
33
+ :max_cpu_usage => 50.percent,
34
+ :max_mem_usage => 150.megabytes,
35
+ :monitor_group => 'tyrants',
36
+ :server_exe => '/usr/local/bin/ttserver',
37
+ }
38
+ def self.default_options() super.deep_merge(TyrantGod::DEFAULT_OPTIONS) ; end
39
+ def self.site_options() super.deep_merge(global_site_options[:tyrant_god]||{}) ; end
40
+
41
+ def self.kind
42
+ :ttyrant
43
+ end
44
+
45
+ def dbname
46
+ basename = options[:db_name] || (handle+'.tct')
47
+ File.join(options[:db_dirname], basename)
48
+ end
49
+
50
+ def start_command
51
+ [
52
+ options[:server_exe],
53
+ "-host #{options[:listen_on]}",
54
+ "-port #{options[:port]}",
55
+ "-log #{process_log_file}",
56
+ dbname
57
+ ].flatten.compact.join(" ")
58
+ end
59
+ end