bud 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. data/LICENSE +9 -0
  2. data/README +30 -0
  3. data/bin/budplot +134 -0
  4. data/bin/budvis +201 -0
  5. data/bin/rebl +4 -0
  6. data/docs/README.md +13 -0
  7. data/docs/bfs.md +379 -0
  8. data/docs/bfs.raw +251 -0
  9. data/docs/bfs_arch.png +0 -0
  10. data/docs/bloom-loop.png +0 -0
  11. data/docs/bust.md +83 -0
  12. data/docs/cheat.md +291 -0
  13. data/docs/deploy.md +96 -0
  14. data/docs/diffs +181 -0
  15. data/docs/getstarted.md +296 -0
  16. data/docs/intro.md +36 -0
  17. data/docs/modules.md +112 -0
  18. data/docs/operational.md +96 -0
  19. data/docs/rebl.md +99 -0
  20. data/docs/ruby_hooks.md +19 -0
  21. data/docs/visualizations.md +75 -0
  22. data/examples/README +1 -0
  23. data/examples/basics/hello.rb +12 -0
  24. data/examples/basics/out +1103 -0
  25. data/examples/basics/out.new +856 -0
  26. data/examples/basics/paths.rb +51 -0
  27. data/examples/bust/README.md +9 -0
  28. data/examples/bust/bustclient-example.rb +23 -0
  29. data/examples/bust/bustinspector.html +135 -0
  30. data/examples/bust/bustserver-example.rb +18 -0
  31. data/examples/chat/README.md +9 -0
  32. data/examples/chat/chat.rb +45 -0
  33. data/examples/chat/chat_protocol.rb +8 -0
  34. data/examples/chat/chat_server.rb +29 -0
  35. data/examples/deploy/tokenring-ec2.rb +26 -0
  36. data/examples/deploy/tokenring-local.rb +17 -0
  37. data/examples/deploy/tokenring.rb +39 -0
  38. data/lib/bud/aggs.rb +126 -0
  39. data/lib/bud/bud_meta.rb +185 -0
  40. data/lib/bud/bust/bust.rb +126 -0
  41. data/lib/bud/bust/client/idempotence.rb +10 -0
  42. data/lib/bud/bust/client/restclient.rb +49 -0
  43. data/lib/bud/collections.rb +937 -0
  44. data/lib/bud/depanalysis.rb +44 -0
  45. data/lib/bud/deploy/countatomicdelivery.rb +50 -0
  46. data/lib/bud/deploy/deployer.rb +67 -0
  47. data/lib/bud/deploy/ec2deploy.rb +200 -0
  48. data/lib/bud/deploy/localdeploy.rb +41 -0
  49. data/lib/bud/errors.rb +15 -0
  50. data/lib/bud/graphs.rb +405 -0
  51. data/lib/bud/joins.rb +300 -0
  52. data/lib/bud/rebl.rb +314 -0
  53. data/lib/bud/rewrite.rb +523 -0
  54. data/lib/bud/rtrace.rb +27 -0
  55. data/lib/bud/server.rb +43 -0
  56. data/lib/bud/state.rb +108 -0
  57. data/lib/bud/storage/tokyocabinet.rb +170 -0
  58. data/lib/bud/storage/zookeeper.rb +178 -0
  59. data/lib/bud/stratify.rb +83 -0
  60. data/lib/bud/viz.rb +65 -0
  61. data/lib/bud.rb +797 -0
  62. metadata +330 -0
@@ -0,0 +1,170 @@
1
+ require 'tokyocabinet'
2
+
3
+ module Bud
4
+ # Persistent table implementation based on TokyoCabinet.
5
+ class BudTcTable < BudCollection # :nodoc: all
6
+ def initialize(name, bud_instance, given_schema)
7
+ tc_dir = bud_instance.options[:tc_dir]
8
+ raise BudError, "TC support must be enabled via 'tc_dir'" unless tc_dir
9
+ unless File.exists?(tc_dir)
10
+ Dir.mkdir(tc_dir)
11
+ puts "Created directory: #{tc_dir}" unless bud_instance.options[:quiet]
12
+ end
13
+
14
+ dirname = "#{tc_dir}/bud_#{bud_instance.port}"
15
+ unless File.exists?(dirname)
16
+ Dir.mkdir(dirname)
17
+ puts "Created directory: #{dirname}" unless bud_instance.options[:quiet]
18
+ end
19
+
20
+ super(name, bud_instance, given_schema)
21
+ @to_delete = []
22
+
23
+ @hdb = TokyoCabinet::HDB.new
24
+ db_fname = "#{dirname}/#{name}.tch"
25
+ flags = TokyoCabinet::HDB::OWRITER | TokyoCabinet::HDB::OCREAT
26
+ if bud_instance.options[:tc_truncate] == true
27
+ flags |= TokyoCabinet::HDB::OTRUNC
28
+ end
29
+ if !@hdb.open(db_fname, flags)
30
+ raise BudError, "Failed to open TokyoCabinet DB '#{db_fname}': #{@hdb.errmsg}"
31
+ end
32
+ @hdb.tranbegin
33
+ end
34
+
35
+ def init_storage
36
+ # XXX: we can't easily use the @storage infrastructure provided by
37
+ # BudCollection; issue #33
38
+ @storage = nil
39
+ end
40
+
41
+ def [](key)
42
+ key_s = MessagePack.pack(key)
43
+ val_s = @hdb[key_s]
44
+ if val_s
45
+ return make_tuple(key, MessagePack.unpack(val_s))
46
+ else
47
+ return @delta[key]
48
+ end
49
+ end
50
+
51
+ def has_key?(k)
52
+ key_s = MessagePack.pack(k)
53
+ return true if @hdb.has_key? key_s
54
+ return @delta.has_key? k
55
+ end
56
+
57
+ def include?(tuple)
58
+ key = @key_colnums.map{|k| tuple[k]}
59
+ value = self[key]
60
+ return (value == tuple)
61
+ end
62
+
63
+ def make_tuple(k_ary, v_ary)
64
+ t = Array.new(k_ary.length + v_ary.length)
65
+ @key_colnums.each_with_index do |k,i|
66
+ t[k] = k_ary[i]
67
+ end
68
+ val_cols.each_with_index do |c,i|
69
+ t[schema.index(c)] = v_ary[i]
70
+ end
71
+ tuple_accessors(t)
72
+ end
73
+
74
+ def each(&block)
75
+ each_from([@delta], &block)
76
+ each_storage(&block)
77
+ end
78
+
79
+ def each_from(bufs, &block)
80
+ bufs.each do |b|
81
+ if b == @storage then
82
+ each_storage(&block)
83
+ else
84
+ b.each_value do |v|
85
+ yield v
86
+ end
87
+ end
88
+ end
89
+ end
90
+
91
+ def each_storage(&block)
92
+ @hdb.each do |k,v|
93
+ k_ary = MessagePack.unpack(k)
94
+ v_ary = MessagePack.unpack(v)
95
+ yield make_tuple(k_ary, v_ary)
96
+ end
97
+ end
98
+
99
+ def flush
100
+ @hdb.trancommit
101
+ end
102
+
103
+ def close
104
+ @hdb.close
105
+ end
106
+
107
+ def merge_to_hdb(buf)
108
+ buf.each do |key,tuple|
109
+ merge_tuple(key, tuple)
110
+ end
111
+ end
112
+
113
+ def merge_tuple(key, tuple)
114
+ val = val_cols.map{|c| tuple[schema.index(c)]}
115
+ key_s = MessagePack.pack(key)
116
+ val_s = MessagePack.pack(val)
117
+ if @hdb.putkeep(key_s, val_s) == false
118
+ old_tuple = self[key]
119
+ raise_pk_error(tuple, old_tuple) if tuple != old_tuple
120
+ end
121
+ end
122
+
123
+ # move deltas to TC, and new_deltas to deltas
124
+ def tick_deltas
125
+ merge_to_hdb(@delta)
126
+ @delta = @new_delta
127
+ @new_delta = {}
128
+ end
129
+
130
+ superator "<-" do |o|
131
+ o.each do |tuple|
132
+ @to_delete << tuple unless tuple.nil?
133
+ end
134
+ end
135
+
136
+ def insert(tuple)
137
+ key = @key_colnums.map{|k| tuple[k]}
138
+ merge_tuple(key, tuple)
139
+ end
140
+
141
+ alias << insert
142
+
143
+ # Remove to_delete and then add pending to HDB
144
+ def tick
145
+ @to_delete.each do |tuple|
146
+ k = @key_colnums.map{|c| tuple[c]}
147
+ k_str = MessagePack.pack(k)
148
+ cols_str = @hdb[k_str]
149
+ unless cols_str.nil?
150
+ hdb_cols = MessagePack.unpack(cols_str)
151
+ delete_cols = val_cols.map{|c| tuple[schema.index(c)]}
152
+ if hdb_cols == delete_cols
153
+ @hdb.delete k_str
154
+ end
155
+ end
156
+ end
157
+ @to_delete = []
158
+
159
+ merge_to_hdb(@pending)
160
+ @pending = {}
161
+
162
+ @hdb.trancommit
163
+ @hdb.tranbegin
164
+ end
165
+
166
+ def method_missing(sym, *args, &block)
167
+ @hdb.send sym, *args, &block
168
+ end
169
+ end
170
+ end
@@ -0,0 +1,178 @@
1
+ begin
2
+ require 'zookeeper'
3
+ Bud::HAVE_ZOOKEEPER = true
4
+ rescue LoadError
5
+ end
6
+
7
+ module Bud
8
+ # Persistent table implementation based on Zookeeper.
9
+ class BudZkTable < BudCollection # :nodoc: all
10
+ def initialize(name, zk_path, zk_addr, bud_instance)
11
+ unless defined? HAVE_ZOOKEEPER
12
+ raise BudError, "zookeeper gem is not installed: zktables cannot be used"
13
+ end
14
+
15
+ # schema = {[:key] => [:val]}
16
+ super(name, bud_instance, nil)
17
+
18
+ zk_path = zk_path.chomp("/") unless zk_path == "/"
19
+ @zk = Zookeeper.new(zk_addr)
20
+ @zk_path = zk_path
21
+ @base_path = @zk_path
22
+ @base_path += "/" unless @zk_path.end_with? "/"
23
+ @store_mutex = Mutex.new
24
+ @next_storage = {}
25
+ @saw_delta = false
26
+ @child_watch_id = nil
27
+ @stat_watch_id = nil
28
+ end
29
+
30
+ # Since the watcher callbacks might invoke EventMachine, we wait until after
31
+ # EM startup to start watching for Zk events.
32
+ def start_watchers
33
+ # NB: Watcher callbacks are invoked in a separate Ruby thread.
34
+ @child_watcher = Zookeeper::WatcherCallback.new { get_and_watch }
35
+ @stat_watcher = Zookeeper::WatcherCallback.new { stat_and_watch }
36
+ stat_and_watch
37
+ end
38
+
39
+ def clone_empty
40
+ raise BudError
41
+ end
42
+
43
+ def stat_and_watch
44
+ r = @zk.stat(:path => @zk_path, :watcher => @stat_watcher)
45
+ @stat_watch_id = r[:req_id]
46
+
47
+ unless r[:stat].exists
48
+ cancel_child_watch
49
+ # The given @zk_path doesn't exist, so try to create it. Unclear
50
+ # whether this is always the best behavior.
51
+ r = @zk.create(:path => @zk_path)
52
+ if r[:rc] != Zookeeper::ZOK and r[:rc] != Zookeeper::ZNODEEXISTS
53
+ raise
54
+ end
55
+ end
56
+
57
+ # Make sure we're watching for children
58
+ get_and_watch unless @child_watch_id
59
+ end
60
+
61
+ def cancel_child_watch
62
+ if @child_watch_id
63
+ @zk.unregister_watcher(@child_watch_id)
64
+ @child_watch_id = nil
65
+ end
66
+ end
67
+
68
+ def cancel_stat_watch
69
+ if @stat_watch_id
70
+ @zk.unregister_watcher(@stat_watch_id)
71
+ @stat_with_id = nil
72
+ end
73
+ end
74
+
75
+ def get_and_watch
76
+ r = @zk.get_children(:path => @zk_path, :watcher => @child_watcher)
77
+ @child_watch_id = r[:req_id]
78
+ unless r[:stat].exists
79
+ cancel_child_watch
80
+ return
81
+ end
82
+
83
+ # XXX: can we easily get snapshot isolation?
84
+ new_children = {}
85
+ r[:children].each do |c|
86
+ child_path = @base_path + c
87
+
88
+ get_r = @zk.get(:path => child_path)
89
+ unless get_r[:stat].exists
90
+ puts "ZK: failed to fetch child: #{child_path}"
91
+ return
92
+ end
93
+
94
+ data = get_r[:data]
95
+ # XXX: For now, conflate empty string values with nil values
96
+ data ||= ""
97
+ new_children[c] = tuple_accessors([c, data])
98
+ end
99
+
100
+ # We successfully fetched all the children of @zk_path; arrange to install
101
+ # the new data into @storage at the next Bud tick
102
+ need_tick = false
103
+ @store_mutex.synchronize {
104
+ @next_storage = new_children
105
+ if @storage != @next_storage
106
+ need_tick = true
107
+ @saw_delta = true
108
+ end
109
+ }
110
+
111
+ # If we have new data, force a new Bud tick in the near future
112
+ if need_tick and not @bud_instance.lazy
113
+ EventMachine::schedule {
114
+ @bud_instance.tick
115
+ }
116
+ end
117
+ end
118
+
119
+ def tick
120
+ @store_mutex.synchronize {
121
+ return unless @saw_delta
122
+ @storage = @next_storage
123
+ @next_storage = {}
124
+ @saw_delta = false
125
+ }
126
+ end
127
+
128
+ def flush
129
+ each_from([@pending]) do |t|
130
+ path = @base_path + t.key
131
+ data = t.val
132
+ ephemeral = false
133
+ sequence = false
134
+
135
+ if t.length > 2
136
+ opts = t.last.first
137
+ if opts[:ephemeral] == true
138
+ ephemeral = true
139
+ end
140
+ if opts[:sequence] == true
141
+ sequence = true
142
+ end
143
+ end
144
+
145
+ r = @zk.create(:path => path, :data => data,
146
+ :ephemeral => ephemeral, :sequence => sequence)
147
+ if r[:rc] == Zookeeper::ZNODEEXISTS
148
+ puts "Ignoring duplicate insert: #{t.inspect}"
149
+ elsif r[:rc] != Zookeeper::ZOK
150
+ puts "Failed create of #{path}: #{r.inspect}"
151
+ end
152
+ end
153
+ @pending.clear
154
+ end
155
+
156
+ def close
157
+ cancel_child_watch
158
+ cancel_stat_watch
159
+ @zk.close
160
+ end
161
+
162
+ superator "<~" do |o|
163
+ pending_merge(o)
164
+ end
165
+
166
+ superator "<+" do |o|
167
+ raise BudError, "Illegal use of <+ with zktable '#{@tabname}' on left"
168
+ end
169
+
170
+ def <=(o)
171
+ raise BudError, "Illegal use of <= with zktable '#{@tabname}' on left"
172
+ end
173
+
174
+ def <<(o)
175
+ raise BudError, "Illegal use of << with zktable '#{@tabname}' on left"
176
+ end
177
+ end
178
+ end
@@ -0,0 +1,83 @@
1
+ require 'rubygems'
2
+ require 'bud'
3
+
4
+ class Stratification # :nodoc: all
5
+ include Bud
6
+
7
+ state do
8
+ # Data inserted by client (Bud rewrite code)
9
+ table :depends, [:rule, :head, :op, :body, :neg]
10
+
11
+ scratch :depends_clean, [:head, :body, :neg, :temporal]
12
+ scratch :depends_tc, [:head, :body, :via, :neg, :temporal]
13
+ scratch :cycle, [:predicate, :via, :neg, :temporal]
14
+ table :stratum_base, [:predicate, :stratum]
15
+ table :stratum, [:predicate, :stratum]
16
+ table :top_strat, [:stratum]
17
+ end
18
+
19
+ def declaration
20
+ strata[0] = lambda {
21
+ depends_clean <= depends do |d|
22
+ is_temporal = (d.op.to_s =~ /<[\+\-\~]/)
23
+ [d.head, d.body, d.neg, is_temporal]
24
+ end
25
+
26
+ # Compute the transitive closure of "depends_clean" to detect cycles in
27
+ # the deductive fragment of the program.
28
+ depends_tc <= depends_clean do |d|
29
+ [d.head, d.body, d.body, d.neg, d.temporal]
30
+ end
31
+ depends_tc <= (depends_clean * depends_tc).pairs(:body => :head) do |b, r|
32
+ [b.head, r.body, b.body, (b.neg or r.neg), (b.temporal or r.temporal)]
33
+ end
34
+
35
+ cycle <= depends_tc do |d|
36
+ if d.head == d.body
37
+ if d.neg and !d.temporal
38
+ raise Bud::CompileError, "unstratifiable program: #{d.inspect}"
39
+ else
40
+ [d.head, d.via, d.neg, d.temporal]
41
+ end
42
+ end
43
+ end
44
+
45
+ # we initially assign all predicates to stratum 0
46
+ stratum_base <= depends {|d| [d.body, 0]}
47
+ }
48
+
49
+ strata[1] = lambda {
50
+ # classic stratification:
51
+ # if A depends on B, A is >= B.
52
+ # if A depends nonmonotonically on B, A > B.
53
+ # if A are B are co-dependent, give up.
54
+ # (don't need to do this, b/c we've ruled out deductive cycles)
55
+ # stratum choice will represent local evaluation order,
56
+ # so we need only consider 'synchronous' dependencies (<=)
57
+
58
+ # pass 1: assume no deductive cycles
59
+ # do "vanilla stratification" on deductive rules
60
+ stratum_base <= (depends * stratum_base).pairs(:body => :predicate) do |d, s|
61
+ if d.op.to_s == '<='
62
+ if d.neg
63
+ # BUMP
64
+ [d.head, s.stratum + 1]
65
+ else
66
+ # HOIST
67
+ [d.head, s.stratum]
68
+ end
69
+ end
70
+ end
71
+ }
72
+
73
+ strata[2] = lambda {
74
+ stratum <= stratum_base.group([stratum_base.predicate], max(stratum_base.stratum))
75
+ }
76
+
77
+ strata[3] = lambda {
78
+ # there is no good reason that top_strat can't be computed in strata[3] over stratum_base.
79
+ # however, when it is deduced that way, it is empty after a tick
80
+ top_strat <= stratum.group([], max(stratum.stratum))
81
+ }
82
+ end
83
+ end
data/lib/bud/viz.rb ADDED
@@ -0,0 +1,65 @@
1
+ require 'rubygems'
2
+ require 'syntax/convertors/html'
3
+ require 'gchart'
4
+ require 'digest/md5'
5
+ require 'bud/state'
6
+
7
+ class VizOnline #:nodoc: all
8
+ def initialize(bud_instance)
9
+ @bud_instance = bud_instance
10
+ return if bud_instance.class == Stratification or @bud_instance.class == DepAnalysis
11
+ @meta_tables = {'t_rules' => 1, 't_depends' => 1, 't_table_info' => 1, 't_cycle' => 1, 't_stratum' => 1, 't_depends_tc' => 1, 't_table_schema' => 1}
12
+ @bud_instance.options[:tc_dir] = "TC_#{@bud_instance.class}_#{bud_instance.options[:tag]}_#{bud_instance.object_id}_#{bud_instance.port}"
13
+ @table_info = new_tab(:t_table_info, [:tab_name, :tab_type], @bud_instance)
14
+ @table_schema = new_tab(:t_table_schema, [:tab_name, :col_name, :ord], @bud_instance)
15
+
16
+ @logtab = {}
17
+ tmp_set = []
18
+ @bud_instance.tables.each do |name, tbl|
19
+ next if name.to_s =~ /_vizlog\z/
20
+
21
+ # Temp collections don't have a schema until a fact has been inserted into
22
+ # them; for now, we just include an empty schema for them in the viz
23
+ if tbl.schema.nil?
24
+ schema = []
25
+ else
26
+ schema = tbl.schema.clone
27
+ end
28
+ tmp_set << [name, schema, tbl.class.to_s]
29
+ end
30
+
31
+ tmp_set.each do |t|
32
+ news = [:c_bud_time]
33
+ @table_schema << [t[0], :c_bud_time, 0]
34
+ t[1].each_with_index do |s, i|
35
+ news << s
36
+ @table_schema << [t[0], s, i+1]
37
+ end
38
+ lt = "#{t[0]}_vizlog".to_sym
39
+ @logtab[t[0]] = new_tab(lt, news, @bud_instance)
40
+ @table_info << [t[0], t[2]]
41
+ end
42
+ end
43
+
44
+ def new_tab(name, schema, instance)
45
+ ret = Bud::BudTcTable.new(name, instance, schema)
46
+ instance.tables[name] = ret
47
+ return ret
48
+ end
49
+
50
+ def do_cards
51
+ return if @bud_instance.class == Stratification or @bud_instance.class == DepAnalysis
52
+ @bud_instance.tables.each do |t|
53
+ tab = t[0]
54
+ next if tab.to_s =~ /_vizlog\z/
55
+ next if @meta_tables[tab.to_s] and @bud_instance.budtime > 0
56
+ next unless @logtab[tab]
57
+ t[1].each do |row|
58
+ newrow = [@bud_instance.budtime]
59
+ row.each{ |r| newrow << r }
60
+ @logtab[tab] << newrow
61
+ end
62
+ @logtab[tab].tick
63
+ end
64
+ end
65
+ end