bud 0.0.5 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
data/bin/budplot CHANGED
@@ -8,7 +8,7 @@ require 'bud/viz_util'
8
8
 
9
9
  include VizUtil
10
10
 
11
- def is_module?(m)
11
+ def is_constant?(m)
12
12
  begin
13
13
  return (eval("defined?(#{m})") == "constant")
14
14
  rescue SyntaxError
@@ -16,16 +16,42 @@ def is_module?(m)
16
16
  end
17
17
  end
18
18
 
19
- def process(mods)
19
+ def make_instance(mods)
20
+ # If we're given a single identifier that names a class, just return an
21
+ # instance of that class. Otherwise, define a bogus class that includes all
22
+ # the module names specified by the user and return an instance.
20
23
  mods.each do |m|
21
- unless is_module? m
22
- puts "Error: unable to find definition for module \"#{m}\""
24
+ unless is_constant? m
25
+ puts "Error: unable to find definition for module or class \"#{m}\""
26
+ exit
27
+ end
28
+
29
+ mod_klass = eval m
30
+ if mod_klass.class == Class
31
+ if mods.length == 1
32
+ return mod_klass.new
33
+ else
34
+ puts "Error: cannot intermix classes and modules"
35
+ exit
36
+ end
37
+ elsif mod_klass.class != Module
38
+ puts "Error: \"#{m}\" is not a module or class name"
23
39
  exit
24
40
  end
25
41
  end
26
42
 
27
- classdef = "class FooBar\ninclude Bud\n" + mods.map{|m| "include #{m}"}.join("\n") + "\nend\n FooBar.new"
28
- d = eval(classdef)
43
+ def_lines = ["class FooBar",
44
+ "include Bud",
45
+ mods.map {|m| "include #{m}"},
46
+ "end"
47
+ ]
48
+ class_def = def_lines.flatten.join("\n")
49
+ eval(class_def)
50
+ FooBar.new
51
+ end
52
+
53
+ def process(mods)
54
+ d = make_instance(mods)
29
55
 
30
56
  interfaces = {}
31
57
  d.t_provides.each do |name, is_input|
@@ -39,10 +65,10 @@ def process(mods)
39
65
  d.tables.each do |t|
40
66
  tab = t[0].to_s
41
67
  tabinf[tab] = t[1].class.to_s
68
+ next if d.builtin_tables.has_key? t[0]
69
+
42
70
  if interfaces[tab].nil?
43
- unless tab =~ /^t_/ or tab == "stdio" or tab == "localtick"
44
- priv << t
45
- end
71
+ priv << t
46
72
  else
47
73
  if interfaces[tab]
48
74
  inp << t
@@ -84,7 +110,6 @@ def do_table(f, info)
84
110
  info.sort{|a, b| a[0].to_s <=> b[0].to_s}.each do |tbl_name, tbl_impl|
85
111
  next if tbl_impl.schema.nil?
86
112
  key_s = tbl_impl.key_cols.join(", ")
87
- key_s = "[]" if key_s == ""
88
113
  val_s = tbl_impl.val_cols.join(", ")
89
114
  f.puts "<tr><td><b>#{tbl_name}</b></td>"
90
115
  f.puts "<td>#{key_s}</td><td>#{val_s}</td></tr>"
@@ -93,7 +118,7 @@ def do_table(f, info)
93
118
  end
94
119
 
95
120
  if ARGV.length < 2
96
- puts "Usage: budplot LIST_OF_FILES LIST_OF_MODULES"
121
+ puts "Usage: budplot LIST_OF_FILES LIST_OF_MODULES_OR_CLASSES"
97
122
  exit
98
123
  end
99
124
 
@@ -102,7 +127,7 @@ end
102
127
  modules = []
103
128
  ARGV.each do |arg|
104
129
  if File.exists? arg
105
- eval "require '#{arg}'"
130
+ require arg
106
131
  else
107
132
  modules << arg
108
133
  end
data/bin/budtimelines CHANGED
@@ -36,7 +36,7 @@ end
36
36
  module TPSchema
37
37
  state do
38
38
  table :deltas, [:bud_time, :tab, :nm]
39
- table :zerod_cards, [:bud_time, :table, :cnt]
39
+ table :zerod_cards, [:bud_time, :table, :cnt, :pred]
40
40
  table :nm_tab, [:table]
41
41
  table :collapsible_base, [:start, :fin]
42
42
  table :collapsible, [:start, :fin]
@@ -49,19 +49,18 @@ end
49
49
  module DeltaLogic
50
50
  include TPSchema
51
51
  bloom do
52
- zerod_cards <= cardinalities
52
+ zerod_cards <= cardinalities{|c| c + [c.bud_time-1]}
53
53
  zerod_cards <= (times * depends).pairs do |t, d|
54
54
  unless cardinalities{|c| c[1] if c[0] == t.bud_time}.include? d[1]
55
- [t.bud_time, d[1], 0]
55
+ [t.bud_time, d[1], 0, t.bud_time - 1]
56
56
  end
57
57
  end
58
58
 
59
-
60
59
  nm_tab <= depends do |d|
61
60
  [d[1]] if d[4]
62
61
  end
63
62
 
64
- deltas <= (zerod_cards * zerod_cards).pairs(:table => :table) do |c1, c2|
63
+ deltas <= (zerod_cards * zerod_cards).pairs(:table => :table, :bud_time => :pred) do |c1, c2|
65
64
  if c1.bud_time == c2.bud_time - 1 and c1.table == c2.table and c1.cnt != c2.cnt
66
65
  if nm_tab.include? [c1.table]
67
66
  [c2.bud_time, c1.table, true]
@@ -77,26 +76,22 @@ module VanillaTraceProcessing
77
76
  include TPSchema
78
77
  include DeltaLogic
79
78
 
80
- bloom do
81
- collapsible_base <= times do |t|
82
- unless deltas{|d| d.bud_time if d.nm}.include? t.bud_time
83
- [t.bud_time-1, t.bud_time]
84
- end
85
- end
79
+ state do
80
+ scratch :tp, times.schema
81
+ scratch :bi1, best_interval.schema
82
+ end
86
83
 
84
+ bloom do
85
+ tp <= times.notin(deltas, :bud_time => :bud_time) {|t, d| true if d.nm}
86
+ collapsible_base <= tp {|t| [t.bud_time-1, t.bud_time]}
87
87
  collapsible <= collapsible_base
88
88
 
89
89
  collapsible <= (collapsible_base * collapsible).pairs(:fin => :start) do |b, c|
90
- puts "another collapsible row; now #{b.inspect} - #{c.inspect}"
91
90
  [b.start, c.fin]
92
91
  end
93
92
 
94
- best_interval <= collapsible do |c|
95
- unless collapsible{|c1| c1.start == c.start and c1.fin > c.fin}.any? \
96
- or collapsible{|c2| c2.fin == c.fin and c2.start < c.start}.any?
97
- c
98
- end
99
- end
93
+ bi1 <= collapsible.notin(collapsible, :start => :start) {|c1, c2| true if c2.fin > c1.fin}
94
+ best_interval <= bi1.notin(collapsible, :fin => :fin) {|c1, c2| true if c2.start < c1.start}
100
95
  end
101
96
  end
102
97
 
@@ -139,12 +134,12 @@ da = GlobalDepAnalyzer.new
139
134
 
140
135
  ARGV.each do |arg_raw|
141
136
  elems = arg_raw.split("_")
142
- arg = elems[1..3].join("_")
137
+ arg = elems[1..4].join("_")
143
138
  clean_arg << arg
144
139
  snd_info[arg] = []
145
140
  rcv_info[arg] = []
146
141
 
147
- meta, data = get_meta2("#{arg_raw}/bud_")
142
+ meta, data = get_meta2("#{arg_raw}")
148
143
  tp = SimpleTraceProcessor.new
149
144
 
150
145
  meta[:depends].each do |m|
@@ -166,7 +161,6 @@ ARGV.each do |arg_raw|
166
161
 
167
162
  tp.tick
168
163
 
169
-
170
164
  puts "entries in collapsible: #{tp.collapsible.length}"
171
165
  puts "entries in base: #{tp.collapsible_base.length}"
172
166
  puts "entries in deltas: #{tp.deltas.length}"
@@ -182,7 +176,11 @@ da.tick
182
176
  nmreach = {}
183
177
  da.depends_tc.each do |d|
184
178
  nmreach[d[0]] = {} unless nmreach[d[0]]
185
- nmreach[d[0]][d[1]] = d[3]
179
+ if nmreach[d[0]][d[1]]
180
+ nmreach[d[0]][d[1]] = d[3] or nmreach[d[0]][d[1]]
181
+ else
182
+ nmreach[d[0]][d[1]] = d[3]
183
+ end
186
184
  end
187
185
 
188
186
  # our local intervals relations are too optimistic. to say that intervals[foo] = [2, 5]
data/bin/budvis CHANGED
@@ -7,7 +7,7 @@ require 'bud/viz_util'
7
7
 
8
8
  include VizUtil
9
9
 
10
- BUD_DBM_DIR = "#{ARGV[0]}/bud_"
10
+ BUD_DBM_DIR = "#{ARGV[0]}"
11
11
 
12
12
 
13
13
  def usage
data/docs/cheat.md CHANGED
@@ -82,6 +82,13 @@ State declaration includes interval (in seconds).
82
82
 
83
83
  periodic :timer, 0.1
84
84
 
85
+ Note that because periodics are just a simple wrapper over the system clock, Bud
86
+ provides few semantic guarantees about the behavior of periodics. In particular,
87
+ periodics execute in a best-effort manner (there is no guarantee of timely
88
+ delivery of a periodic tuple), and the system clock value stored in the `val`
89
+ field may not be monotonically increasing (e.g., if the system clock is changed
90
+ in the midst of Bud execution).
91
+
85
92
  ### stdio ###
86
93
  Built-in scratch collection for performing terminal I/O.<br>
87
94
  System-provided attributes: `[:line] => []`
@@ -151,12 +158,10 @@ update/upsert:
151
158
 
152
159
  * `left <+- right` &nbsp;&nbsp;&nbsp; (*deferred*)<br>
153
160
  deferred insert of items on rhs and deferred deletion of items with matching
154
- keys on lhs.
155
-
156
- That is, for each fact produced by the rhs, the upsert operator removes any
157
- existing tuples that match on the lhs collection's key columns before inserting
158
- the corresponding rhs fact. Note that both the removal and insertion operators
159
- happen atomically in the next timestep.
161
+ keys on lhs. That is, for each fact produced by the rhs, the upsert operator
162
+ removes any existing tuples that match on the lhs collection's key columns
163
+ before inserting the corresponding rhs fact. Note that both the removal and
164
+ insertion operations happen atomically in the next timestep.
160
165
 
161
166
  ### Collection Methods ###
162
167
  Standard Ruby methods used on a BudCollection `bc`:
@@ -184,26 +189,36 @@ implicit map:
184
189
 
185
190
  `bc.include?`:
186
191
 
187
- t5 <= bc do |t| # like SQL's NOT IN
192
+ # This is similar to SQL's NOT IN; note that Bud provides a "notin"
193
+ # collection method that should probably be preferred to this approach.
194
+ t5 <= bc do |t|
188
195
  t unless t2.include?([t.col1, t.col2])
189
196
  end
190
197
 
191
198
  ## BudCollection-Specific Methods ##
192
- `bc.keys`: projects `bc` to key columns<br>
199
+ `bc.schema`: returns the schema of `bc` (Hash of key column names => non-key column names)<br>
193
200
 
194
- `bc.values`: projects `bc` to non-key columns<br>
201
+ `bc.cols`: returns the column names in `bc` as an Array<br>
195
202
 
196
- `bc.inspected`: shorthand for `bc {|t| [t.inspect]}`
203
+ `bc.key_cols`: returns the key column names in `bc` as an Array<br>
197
204
 
198
- stdio <~ bc.inspected
205
+ `bc.val_cols`: returns the non-key column names in `bc` as an Array<br>
206
+
207
+ `bc.keys`: projects `bc` to key columns<br>
208
+
209
+ `bc.values`: projects `bc` to non-key columns<br>
199
210
 
200
211
  `chan.payloads`: projects `chan` to non-address columns. Only defined for channels.
201
212
 
202
213
  # at sender
203
- msgs <~ requests {|r| "127.0.0.1:12345", r}
214
+ msgs <~ requests {|r| ["127.0.0.1:12345", r]}
204
215
  # at receiver
205
216
  requests <= msgs.payloads
206
217
 
218
+ `bc.inspected`: returns a human-readable version of the contents of `bc`
219
+
220
+ stdio <~ bc.inspected
221
+
207
222
  `bc.exists?`: test for non-empty collection. Can optionally pass in a block.
208
223
 
209
224
  stdio <~ [["Wake Up!"] if timer.exists?]
@@ -211,10 +226,18 @@ implicit map:
211
226
  [r.inspect] if msgs.exists?{|m| r.ident == m.ident}
212
227
  end
213
228
 
214
- `bc.notin(bc2, `*optional hash pairs*`)` *optional ruby block*:<br>
215
- Output each item of `bc` such that (a) it has no match in `bc2` on the hash-pairs attributes, or (b) there is no matching item in `bc2` that leads to a non-nil return value from the block.
216
- Hash pairs can be fully qualified (`bc.attr1 => bc2.attr2`)
217
- or shorthand (`:attr1 => :attr2`).
229
+ `bc.notin(bc2, `*optional hash pairs*`, `*optional ruby block*`)`:<br>
230
+ Output the facts in `bc` that do not appear in `bc2`, as follows. First, we form a temporary collection `t` as follows:
231
+
232
+ 1. Join `bc` and `bc2` according to the specified hash pairs. Hash pairs can
233
+ be fully qualified (`bc.attr1 => bc2.attr2`) or shorthand (`:attr1 =>
234
+ :attr2`).
235
+
236
+ 2. If a code block is specified, invoke the block on every pair of matching
237
+ tuples in the join result. Any matches for which the block returns `nil`
238
+ are removed from `t`.
239
+
240
+ Finally, we output every tuple of `bc` that does *not* appear in `t`.
218
241
 
219
242
  # output items from foo if (a) there is no matching key in bar, or
220
243
  # (b) all matching keys in bar have a smaller value
@@ -330,13 +353,23 @@ There are two ways to use a module *B* in another Bloom module *A*:
330
353
  (facts inserted into a collection defined in `b1` won't also be inserted
331
354
  into `b2`'s copy of the collection).
332
355
 
356
+ In practice, a Bloom program is often composed of a collection of modules (which
357
+ may themselves include or import sub-modules) and one "top-level class" that
358
+ includes/imports those modules as well as the `Bud` module. An instance of this
359
+ top-level class represents an instance of the Bud interpreter; it is on this
360
+ top-level class that the `run_fg` method should be invoked, for example.
361
+
362
+ Note that to enable the Bloom DSL for a collection of Ruby code, it is
363
+ sufficient to include the `Bud` module *once* in the top-level class. That is,
364
+ you should *not* include `Bud` in every Bloom module that you write.
365
+
333
366
  ## Skeleton of a Bud Module ##
334
367
 
335
368
  require 'rubygems'
336
369
  require 'bud'
337
370
 
338
371
  module YourModule
339
- include Bud
372
+ import SubModule => :sub_m
340
373
 
341
374
  state do
342
375
  ...
@@ -355,3 +388,7 @@ There are two ways to use a module *B* in another Bloom module *A*:
355
388
  end
356
389
  end
357
390
 
391
+ class TopLevelClass
392
+ include Bud
393
+ include YourModule
394
+ end
data/docs/operational.md CHANGED
@@ -31,7 +31,7 @@ It is important to understand how the Bloom collection operators fit into these
31
31
 
32
32
  ## Atomicity: Timesteps and Deferred Operators ##
33
33
 
34
- The only instantaneous Bloom operator is a merge (`<=`), which can only introduce additional items into a collection--it can not delete or change existing items. As a result, all state within a Bloom timestep is *immutable*: once an item is in a collection at timestep *T*, it stays in that collection throughout timestep *T*. (And forever after, the fact that the item was in that collection at timestep *T* remains true.)
34
+ The only instantaneous Bloom operator is a merge (`<=`), which can only introduce additional items into a collection--it cannot delete or change existing items. As a result, all state within a Bloom timestep is *immutable*: once an item is in a collection at timestep *T*, it stays in that collection throughout timestep *T*. (And forever after, the fact that the item was in that collection at timestep *T* remains true.)
35
35
 
36
36
  To get atomic state change in Bloom, you exploit the combination of two language features:
37
37
 
data/lib/bud.rb CHANGED
@@ -5,6 +5,10 @@ require 'socket'
5
5
  require 'superators'
6
6
  require 'thread'
7
7
 
8
+ # Ruby2Ruby 1.3.1 is buggy (see issue #250)
9
+ gem 'ruby2ruby', '< 1.3.1'
10
+ require 'ruby2ruby'
11
+
8
12
  require 'bud/monkeypatch'
9
13
 
10
14
  require 'bud/aggs'
@@ -61,7 +65,8 @@ $bud_instances = {} # Map from instance id => Bud instance
61
65
  module Bud
62
66
  attr_reader :strata, :budtime, :inbound, :options, :meta_parser, :viz, :rtracer
63
67
  attr_reader :dsock
64
- attr_reader :tables, :channels, :tc_tables, :zk_tables, :dbm_tables, :sources, :sinks
68
+ attr_reader :builtin_tables, :tables
69
+ attr_reader :channels, :tc_tables, :zk_tables, :dbm_tables, :sources, :sinks
65
70
  attr_reader :stratum_first_iter, :joinstate
66
71
  attr_reader :this_stratum, :this_rule, :rule_orig_src
67
72
  attr_reader :running_async
@@ -96,8 +101,8 @@ module Bud
96
101
  # * <tt>:deploy</tt> enable deployment
97
102
  # * <tt>:deploy_child_opts</tt> option hash to pass to deployed instances
98
103
  def initialize(options={})
104
+ @builtin_tables = {}
99
105
  @tables = {}
100
- @table_meta = []
101
106
  @rewritten_strata = []
102
107
  @channels = {}
103
108
  @tc_tables = {}
@@ -180,11 +185,14 @@ module Bud
180
185
 
181
186
  # Rewrite methods defined in the given klass to expand module references and
182
187
  # temp collections. Imported modules are rewritten during the import process;
183
- # we rewrite the main Bud class and any included modules here. Note that we
184
- # only rewrite each distinct Class once.
188
+ # we rewrite the main class associated with this Bud instance and any included
189
+ # modules here. Note that we only rewrite each distinct Class once, and we
190
+ # skip methods defined by the Bud (Ruby) module directly (since we can be sure
191
+ # those won't reference Bloom modules).
185
192
  def self.rewrite_local_methods(klass)
186
193
  @done_rewrite ||= {}
187
194
  return if @done_rewrite.has_key? klass.name
195
+ return if klass.name == self.name # Skip methods defined in the Bud module
188
196
 
189
197
  u = Unifier.new
190
198
  ref_expander = NestedRefRewriter.new(klass.bud_import_table)
@@ -368,7 +376,7 @@ module Bud
368
376
  # If we're called from the EventMachine thread (and EM is running), blocking
369
377
  # the current thread would imply deadlocking ourselves.
370
378
  if Thread.current == EventMachine::reactor_thread and EventMachine::reactor_running?
371
- raise BudError, "Cannot invoke run_fg from inside EventMachine"
379
+ raise BudError, "cannot invoke run_fg from inside EventMachine"
372
380
  end
373
381
 
374
382
  q = Queue.new
@@ -486,7 +494,7 @@ module Bud
486
494
  cb_id = nil
487
495
  schedule_and_wait do
488
496
  unless @tables.has_key? tbl_name
489
- raise Bud::BudError, "No such table: #{tbl_name}"
497
+ raise Bud::BudError, "no such table: #{tbl_name}"
490
498
  end
491
499
 
492
500
  raise Bud::BudError if @callbacks.has_key? @callback_id
@@ -500,7 +508,7 @@ module Bud
500
508
  # Unregister the callback that has the given ID.
501
509
  def unregister_callback(id)
502
510
  schedule_and_wait do
503
- raise Bud::BudError, "Missing callback: #{id.inspect}" unless @callbacks.has_key? id
511
+ raise Bud::BudError, "missing callback: #{id.inspect}" unless @callbacks.has_key? id
504
512
  @callbacks.delete(id)
505
513
  end
506
514
  end
@@ -730,16 +738,19 @@ module Bud
730
738
  private
731
739
 
732
740
  # Builtin BUD state (predefined collections). We could define this using the
733
- # standard "state" syntax, but we want to ensure that builtin state is
741
+ # standard state block syntax, but we want to ensure that builtin state is
734
742
  # initialized before user-defined state.
735
743
  def builtin_state
744
+ # We expect there to be no previously-defined tables
745
+ raise BudError unless @tables.empty?
746
+
736
747
  loopback :localtick, [:col1]
737
748
  @stdio = terminal :stdio
738
749
  readonly :signals, [:key]
739
750
  scratch :halt, [:key]
740
751
  @periodics = table :periodics_tbl, [:pername] => [:period]
741
752
 
742
- # for Bud reflection
753
+ # For Bud reflection
743
754
  table :t_rules, [:rule_id] => [:lhs, :op, :src, :orig_src]
744
755
  table :t_depends, [:rule_id, :lhs, :op, :body] => [:nm]
745
756
  table :t_depends_tc, [:head, :body, :via, :neg, :temporal]
@@ -749,6 +760,9 @@ module Bud
749
760
  table :t_cycle, [:predicate, :via, :neg, :temporal]
750
761
  table :t_table_info, [:tab_name, :tab_type]
751
762
  table :t_table_schema, [:tab_name, :col_name, :ord, :loc]
763
+
764
+ # Identify builtin tables as such
765
+ @builtin_tables = @tables.clone
752
766
  end
753
767
 
754
768
  # Handle any inbound tuples off the wire. Received messages are placed
@@ -826,7 +840,7 @@ module Bud
826
840
  unless new_e.class <= BudError
827
841
  new_e = BudError
828
842
  end
829
- raise new_e, "Exception during Bud evaluation.\nException: #{e.inspect}.#{src_msg}"
843
+ raise new_e, "exception during Bud evaluation.\nException: #{e.inspect}.#{src_msg}"
830
844
  end
831
845
  end
832
846
  @stratum_first_iter = false
@@ -835,14 +849,13 @@ module Bud
835
849
  colls = @stratum_collection_map[strat_num] if @stratum_collection_map
836
850
  colls ||= @tables.keys
837
851
  colls.each do |name|
838
- begin
839
- coll = self.send(name)
840
- unless coll.delta.empty? and coll.new_delta.empty?
841
- coll.tick_deltas
842
- fixpoint = false
843
- end
844
- rescue
845
- # ignore missing tables; rebl for example deletes them mid-stream
852
+ coll = @tables[name]
853
+ # ignore missing tables; rebl for example deletes them mid-stream
854
+ next if coll.nil?
855
+
856
+ unless coll.delta.empty? and coll.new_delta.empty?
857
+ fixpoint = false unless coll.new_delta.empty?
858
+ coll.tick_deltas
846
859
  end
847
860
  end
848
861
  end while not fixpoint