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,32 @@
1
+ /* ===========================================================================
2
+ Default styling for the upload bar
3
+ */
4
+
5
+ body { margin: 0px ; }
6
+
7
+ .bar {
8
+ width: 300px;
9
+ }
10
+ #progress {
11
+ border: 1px solid #222;
12
+ display: block;
13
+ float: left;
14
+ margin-right: 0.25em;
15
+ }
16
+ #progressbar {
17
+ width: 0px;
18
+ height: 24px;
19
+ }
20
+
21
+ #progress { background: #eee; }
22
+ #progress #progressbar { background: #bbf; }
23
+ #progress.working { background: #eef; }
24
+ #progress.success #progressbar { background: #cfd; }
25
+ #progress.error { background: #fcc; }
26
+ #progress.error #progressbar { background: #fcd; }
27
+
28
+ iframe.yuploader {
29
+ border: 0px none white;
30
+ margin: 0px;
31
+ padding: 0px;
32
+ }
@@ -0,0 +1,88 @@
1
+
2
+ /*
3
+ Page layout
4
+ */
5
+
6
+ body {
7
+ background-color: #4B7399;
8
+ font-family: Verdana, Helvetica, Arial;
9
+ font-size: 14px;
10
+ padding: 0;
11
+ margin: 0;
12
+ }
13
+
14
+ a img {
15
+ border: none;
16
+ }
17
+
18
+ a {
19
+ color: #0000FF;
20
+ }
21
+
22
+ .clear {
23
+ clear: both;
24
+ height: 0;
25
+ overflow: hidden;
26
+ }
27
+
28
+ #container {
29
+ width: 75%;
30
+ margin: 0 auto;
31
+ background-color: #FFF;
32
+ padding: 20px 40px;
33
+ border: solid 1px black;
34
+ margin-top: 20px;
35
+ }
36
+
37
+ #flash_notice, #flash_error {
38
+ padding: 5px 8px;
39
+ margin: 10px 0;
40
+ }
41
+
42
+ #flash_notice {
43
+ background-color: #CFC;
44
+ border: solid 1px #6C6;
45
+ }
46
+
47
+ #flash_error {
48
+ background-color: #FCC;
49
+ border: solid 1px #C66;
50
+ }
51
+
52
+ .fieldWithErrors {
53
+ display: inline;
54
+ }
55
+
56
+ #errorExplanation {
57
+ width: 400px;
58
+ border: 2px solid #CF0000;
59
+ padding: 0px;
60
+ padding-bottom: 12px;
61
+ margin-bottom: 20px;
62
+ background-color: #f0f0f0;
63
+ }
64
+
65
+ #errorExplanation h2 {
66
+ text-align: left;
67
+ font-weight: bold;
68
+ padding: 5px 5px 5px 15px;
69
+ font-size: 12px;
70
+ margin: 0;
71
+ background-color: #c00;
72
+ color: #fff;
73
+ }
74
+
75
+ #errorExplanation p {
76
+ color: #333;
77
+ margin-bottom: 0;
78
+ padding: 8px;
79
+ }
80
+
81
+ #errorExplanation ul {
82
+ margin: 2px 24px;
83
+ }
84
+
85
+ #errorExplanation ul li {
86
+ font-size: 12px;
87
+ list-style: disc;
88
+ }
@@ -0,0 +1,13 @@
1
+ !!! XML
2
+ !!! Strict
3
+ %html{ "xml:lang" => "en", :lang => "en", :xmlns => "http://www.w3.org/1999/xhtml" }
4
+ %head
5
+ %link{ :href => "/stylesheets/application.css", :rel => "stylesheet", :type => "text/css" }
6
+ %link{ :href => "/favicon.ico", :rel => "shortcut icon", :type => "image/x-icon" }
7
+
8
+ %body
9
+ #container
10
+ =yield
11
+
12
+ -# %script{ :type => "text/javascript", :src => "http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js" }
13
+ -# %script{ :type => "text/javascript", :src => "http://ajax.googleapis.com/ajax/libs/jqueryui/1.7.1/jquery-ui.min.js" }
@@ -0,0 +1,37 @@
1
+ %style{ :type => 'text/css' }
2
+ @import url('/stylesheets/layout.css');
3
+
4
+ %p
5
+
6
+ Jobs in the edamame job store:
7
+
8
+
9
+ %table
10
+ %tr
11
+ %th query_term
12
+ %th priority
13
+ %th prev_items
14
+ %th prev_rate
15
+ %th prev_span_min
16
+ %th prev_span_max
17
+ - @dest_store.each_as(Edamame::Job) do |key, obj|
18
+ %tr
19
+ %td=h key.inspect
20
+ %td=h obj.inspect
21
+ %td=h obj.key
22
+ -# %td=h obj[:query_term]
23
+ -# %td=h obj[:priority]
24
+ -# %td=h obj[:prev_items]
25
+ -# %td=h obj[:prev_rate]
26
+ -# %td=h obj[:prev_span_min]
27
+ -# %td=h obj[:prev_span_max]
28
+
29
+
30
+
31
+
32
+
33
+
34
+
35
+
36
+
37
+
@@ -0,0 +1,25 @@
1
+ %style{ :type => 'text/css' }
2
+ @import url('/stylesheets/layout.css');
3
+
4
+ %p
5
+
6
+ Jobs in the edamame job store:
7
+
8
+
9
+ %table
10
+ %tr
11
+ %th query_term
12
+ %th priority
13
+ %th prev_items
14
+ %th prev_rate
15
+ %th prev_span_min
16
+ %th prev_span_max
17
+ - @store.each_as(Wuclan::Domains::Twitter::Scrape::TwitterSearchJob) do |key, obj|
18
+ %tr
19
+ -# %td=h key.inspect
20
+ %td=h obj[:query_term]
21
+ %td=h obj[:priority]
22
+ %td=h obj[:prev_items]
23
+ %td=h obj[:prev_rate]
24
+ %td=h obj[:prev_span_min]
25
+ %td=h obj[:prev_span_max]
data/bin/edamame-ps ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env bash
2
+ ps aux | egrep '(beanstalk|ttserver|god|ruby|scrape|shotgun|thin)' | grep -v ' grep '
data/bin/empty_all.rb ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ $: << File.dirname(__FILE__)+'/../../lib'
3
+ require 'rubygems'
4
+ require 'edamame'
5
+ require 'monkeyshines/monitor'
6
+
7
+ pq = Edamame::PersistentQueue.new(
8
+ :queue => { :type => 'BeanstalkQueue', :uris => ['localhost:11210'] },
9
+ :store => { :type => 'TyrantStore', :uri => ':11212' }
10
+ )
11
+
12
+ periodic_log = Monkeyshines::Monitor::PeriodicLogger.new(:iters => 1000, :time => 30)
13
+ pq.queue.empty_all do |job|
14
+ periodic_log.periodically{ [ job.tube, job.priority, job.delay, job.scheduling, job.obj['key'] ] }
15
+ end
data/bin/stats.rb ADDED
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env ruby
2
+ $: << File.dirname(__FILE__)+'/../../lib'
3
+ require 'rubygems'
4
+ require 'edamame'
5
+ require 'monkeyshines/monitor'
6
+
7
+ pq = Edamame::PersistentQueue.new(
8
+ :tube => ARGV[0],
9
+ :queue => { :type => 'BeanstalkQueue', :uris => ['localhost:11210'] },
10
+ :store => { :type => 'TyrantStore', :uri => ':11212' }
11
+ )
12
+
13
+ p pq.stats
data/bin/sync.rb ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ $: << File.dirname(__FILE__)+'/../../lib'
3
+ require 'rubygems'
4
+ require 'edamame'
5
+ require 'monkeyshines/monitor'
6
+
7
+ pq = Edamame::PersistentQueue.new(
8
+ :queue => { :type => 'BeanstalkQueue', :uris => ['localhost:11210'] },
9
+ :store => { :type => 'TyrantStore', :uri => ':11212' }
10
+ )
11
+
12
+ periodic_log = Monkeyshines::Monitor::PeriodicLogger.new(:iters => 1000, :time => 30)
13
+ pq.load do |job|
14
+ periodic_log.periodically{ [ pq.store.size, job, job.tube, job.priority, job.delay, job.obj['key'] ] }
15
+ end
data/bin/test_run.rb ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ $: << File.dirname(__FILE__)+'/../../lib'
3
+ require 'rubygems'
4
+ require 'json'
5
+ require 'edamame'
6
+
7
+ broker = Edamame::Broker.new(
8
+ :queue => { :type => 'BeanstalkQueue', :uris => ['localhost:11210'] },
9
+ :store => { :type => 'TyrantStore', :uri => ':11212' }
10
+ )
11
+
12
+ broker.work do |job|
13
+ Monkeyshines.logger.info [job, job.scheduling, job.stats, job.obj].inspect
14
+ end
data/edamame.gemspec ADDED
@@ -0,0 +1,110 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{edamame}
8
+ s.version = "0.1.1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Philip (flip) Kromer"]
12
+ s.date = %q{2009-08-23}
13
+ s.description = %q{
14
+
15
+ Edamame combines the Beanstalk priority queue with a Tokyo Tyrant database and God monitoring to produce a persistent distributed priority job queue system.
16
+
17
+ Like beanstalk, it is fast, lightweight, distributed, priority queuing, reliable scheduling; it adds persistence, named jobs and job querying/enumeration.
18
+
19
+ }
20
+ s.email = %q{flip@infochimps.org}
21
+ s.executables = ["edamame-ps", "empty_all.rb", "stats.rb", "sync.rb", "test_run.rb"]
22
+ s.extra_rdoc_files = [
23
+ "LICENSE.textile",
24
+ "README.textile"
25
+ ]
26
+ s.files = [
27
+ ".document",
28
+ ".gitignore",
29
+ "LICENSE.textile",
30
+ "README.textile",
31
+ "Rakefile",
32
+ "VERSION",
33
+ "app/edamame_san/config.ru",
34
+ "app/edamame_san/config.yml",
35
+ "app/edamame_san/config/.gitignore",
36
+ "app/edamame_san/edamame_san.rb",
37
+ "app/edamame_san/public/favicon.ico",
38
+ "app/edamame_san/public/images/edamame_logo.icns",
39
+ "app/edamame_san/public/images/edamame_logo.ico",
40
+ "app/edamame_san/public/images/edamame_logo.png",
41
+ "app/edamame_san/public/images/edamame_logo_2.icns",
42
+ "app/edamame_san/public/javascripts/application.js",
43
+ "app/edamame_san/public/javascripts/jquery/jquery-ui.js",
44
+ "app/edamame_san/public/javascripts/jquery/jquery.js",
45
+ "app/edamame_san/public/stylesheets/application.css",
46
+ "app/edamame_san/public/stylesheets/layout.css",
47
+ "app/edamame_san/views/layout.haml",
48
+ "app/edamame_san/views/load.haml",
49
+ "app/edamame_san/views/root.haml",
50
+ "bin/edamame-ps",
51
+ "bin/empty_all.rb",
52
+ "bin/stats.rb",
53
+ "bin/sync.rb",
54
+ "bin/test_run.rb",
55
+ "edamame.gemspec",
56
+ "lib/edamame.rb",
57
+ "lib/edamame/job.rb",
58
+ "lib/edamame/queue.rb",
59
+ "lib/edamame/queue/beanstalk.rb",
60
+ "lib/edamame/rescheduled.rb",
61
+ "lib/edamame/scheduling.rb",
62
+ "lib/edamame/store.rb",
63
+ "lib/edamame/store/base.rb",
64
+ "lib/edamame/store/tyrant_store.rb",
65
+ "lib/methods.txt",
66
+ "spec/edamame_spec.rb",
67
+ "spec/spec_helper.rb",
68
+ "utils/god/README-god.textile",
69
+ "utils/god/beanstalkd_god.rb",
70
+ "utils/god/edamame.god",
71
+ "utils/god/god-etc-init-dot-d-example",
72
+ "utils/god/god_email.rb",
73
+ "utils/god/god_process.rb",
74
+ "utils/god/god_site_config.rb",
75
+ "utils/god/sinatra_god.rb",
76
+ "utils/god/tyrant_god.rb",
77
+ "utils/simulation/Add Percent Variation.vi",
78
+ "utils/simulation/Harmonic Average.vi",
79
+ "utils/simulation/Rescheduling Simulation.aliases",
80
+ "utils/simulation/Rescheduling Simulation.lvlps",
81
+ "utils/simulation/Rescheduling Simulation.lvproj",
82
+ "utils/simulation/Rescheduling.vi",
83
+ "utils/simulation/Weighted Average.vi"
84
+ ]
85
+ s.homepage = %q{http://github.com/mrflip/edamame}
86
+ s.rdoc_options = ["--charset=UTF-8"]
87
+ s.require_paths = ["lib"]
88
+ s.rubygems_version = %q{1.3.5}
89
+ s.summary = %q{Beanstalk + Tokyo Tyrant = Edamame, a fast persistent distributed priority job queue.}
90
+ s.test_files = [
91
+ "spec/edamame_spec.rb",
92
+ "spec/spec_helper.rb"
93
+ ]
94
+
95
+ if s.respond_to? :specification_version then
96
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
97
+ s.specification_version = 3
98
+
99
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
100
+ s.add_development_dependency(%q<rspec>, [">= 0"])
101
+ s.add_development_dependency(%q<yard>, [">= 0"])
102
+ else
103
+ s.add_dependency(%q<rspec>, [">= 0"])
104
+ s.add_dependency(%q<yard>, [">= 0"])
105
+ end
106
+ else
107
+ s.add_dependency(%q<rspec>, [">= 0"])
108
+ s.add_dependency(%q<yard>, [">= 0"])
109
+ end
110
+ end
data/lib/edamame.rb ADDED
@@ -0,0 +1,193 @@
1
+ require 'wukong/extensions'
2
+ require 'monkeyshines/utils/logger'
3
+ require 'monkeyshines/utils/factory_module'
4
+ require 'beanstalk-client'
5
+ require 'edamame/scheduling'
6
+ require 'edamame/job'
7
+ require 'edamame/queue'
8
+ require 'edamame/store'
9
+
10
+ module Edamame
11
+
12
+ class PersistentQueue
13
+ DEFAULT_CONFIG = {
14
+ :queue => { :type => :beanstalk, :pool => ['localhost:11300'] }
15
+ }
16
+ attr_reader :tube, :store, :queue
17
+ def initialize options={}
18
+ @tube = options[:tube] || :default
19
+ @store = Edamame::Store.create options[:store]
20
+ @queue = Edamame::Queue.create options[:queue].merge(:default_tube => @tube)
21
+ end
22
+
23
+ #
24
+ # Add a new Job to the queue
25
+ #
26
+ def put job, *args
27
+ job.tube = self.tube if job.tube.blank?
28
+ self.tube = job.tube
29
+ return if store.include?(job.key)
30
+ store.save job
31
+ queue.put job, *args
32
+ end
33
+
34
+ def tube= _tube
35
+ return if @tube == _tube
36
+ puts "#{self.class} setting tube to #{_tube}, was #{@tube}"
37
+ queue.tube = @tube = _tube
38
+ end
39
+
40
+ # Alias for put(job)
41
+ def << job
42
+ put job
43
+ end
44
+
45
+ # Retrieve named record
46
+ def get key
47
+ Edamame::Job.from_hash store.get(key)
48
+ end
49
+
50
+ #
51
+ # Request a job fom the queue for processing
52
+ #
53
+ def reserve timeout=nil
54
+ job = queue.reserve(timeout)
55
+ end
56
+
57
+ #
58
+ # Remove the job from the queue.
59
+ #
60
+ def delete job
61
+ store.delete job.key
62
+ queue.delete job
63
+ end
64
+
65
+ #
66
+ # Returns the job to the queue, to be re-run later.
67
+ #
68
+ # release'ing a job acknowledges it was completed, successfully or not
69
+ #
70
+ def release job
71
+ job.update!
72
+ store.save job
73
+ queue.release job, job.priority, job.scheduling.delay
74
+ end
75
+
76
+ #
77
+ # Shelves the job.
78
+ #
79
+ def bury job
80
+ store.bury job
81
+ queue.bury job
82
+ end
83
+
84
+ #
85
+ # Returns each job as it appears in the queue.
86
+ #
87
+ # all jobs -- active, inactive, running, etc -- are returned,
88
+ # and in some arbitrary order.
89
+ #
90
+ def each *args, &block
91
+ store.each do |key, job_hsh|
92
+ yield Edamame::Job.from_hash(job_hsh)
93
+ end
94
+ end
95
+
96
+ #
97
+ # Loads all jobs from the backing store into the queue.
98
+ #
99
+ def load &block
100
+ hoard do |job|
101
+ yield(job) if block
102
+ unless store.include?(job.key)
103
+ store.save job
104
+ end
105
+ end
106
+ unhoard &block
107
+ end
108
+
109
+ # Returns a hash of stats about the store and queue
110
+ def stats
111
+ { :store_stats => store.stats,
112
+ :queue_stats => queue.stats,
113
+ :tube => self.tube }
114
+ end
115
+
116
+ protected
117
+ #
118
+ # Destructively strips the beanstalkd queue of all of its jobs.
119
+ #
120
+ # This is the only way (I know) to enumerate all of the jobs in the queue --
121
+ # certainly the only way that respects concurrency.
122
+ #
123
+ # You shouldn't use this in general; the point of the backing store is to
124
+ # allow exactly such queries and enumeration. See #each instead.
125
+ #
126
+ def hoard &block
127
+ queue.empty tube, &block
128
+ end
129
+
130
+ #
131
+ # Loads all jobs from the backing store into the queue.
132
+ #
133
+ # The queue must be emptied of all jobs before running this command:
134
+ # otherwise jobs will be duplicated.
135
+ #
136
+ def unhoard &block
137
+ store.each do |key, hsh|
138
+ job = Edamame::Job.from_hash hsh
139
+ self.tube = job.tube
140
+ yield(job) if block
141
+ queue.put job
142
+ end
143
+ end
144
+
145
+ #
146
+ #
147
+ #
148
+ def log line
149
+ Monkeyshines.logger.info line
150
+ end
151
+ end
152
+
153
+ class Broker < PersistentQueue
154
+ def reschedule job
155
+ delay = job.scheduling.delay
156
+ if delay
157
+ # log_job job, 'rescheduled', delay, (Time.now + delay).to_flat, job.scheduling.to_flat.join
158
+ release job
159
+ else
160
+ # log_job job, 'deleted'
161
+ delete job
162
+ end
163
+ end
164
+ def log_job job, *stuff
165
+ log [job.tube, job.priority, job.delay, job.obj['key'], *stuff].flatten.join("\t")
166
+ end
167
+ def work timeout=10, &block
168
+ loop do
169
+ job = reserve(timeout) or break
170
+ result = block.call(job)
171
+ job.update!
172
+ reschedule job
173
+ end
174
+ end
175
+ end
176
+ end
177
+
178
+ module Wuclan
179
+ module Domains
180
+ module Twitter
181
+ module Scrape
182
+ TwitterSearchJob = Struct.new(
183
+ :query_term,
184
+ :priority,
185
+ :prev_items,
186
+ :prev_rate,
187
+ :prev_span_min,
188
+ :prev_span_max
189
+ )
190
+ end
191
+ end
192
+ end
193
+ end