mrflip-edamame 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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