bud 0.0.4 → 0.0.5

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.
@@ -16,7 +16,7 @@ module ThreadDeploy
16
16
 
17
17
  @instances = []
18
18
  on_shutdown do
19
- @instances.each {|b| b.stop_bg}
19
+ @instances.each {|b| b.stop}
20
20
  end
21
21
 
22
22
  print "Spawning threads"
data/lib/bud/errors.rb CHANGED
@@ -12,4 +12,7 @@ module Bud
12
12
  # Raised when the input program fails to compile (e.g., due to illegal
13
13
  # syntax).
14
14
  class CompileError < BudError; end
15
+
16
+ # Raised when evaluation halts with outstanding callbacks
17
+ class BudShutdownWithCallbacksError < BudError; end
15
18
  end
data/lib/bud/graphs.rb CHANGED
@@ -27,18 +27,16 @@ class GraphGen #:nodoc: all
27
27
  cycle.each do |c|
28
28
  # assumption: !(c[2] and !c[3]), or stratification would have bombed out
29
29
  if c[2] and c[3]
30
- if !@redcycle[c[0]]
31
- @redcycle[c[0]] = []
32
- end
30
+ @redcycle[c[0]] ||= []
33
31
  @redcycle[c[0]] << c[1]
34
- end
32
+ end
35
33
  end
36
-
34
+
37
35
  @nodes = {}
38
36
  @edges = {}
39
37
  @labels = {}
40
38
  end
41
-
39
+
42
40
  def name_bag(predicate, bag)
43
41
  if bag[predicate]
44
42
  return bag
@@ -46,10 +44,10 @@ class GraphGen #:nodoc: all
46
44
  bag[predicate] = true
47
45
  res = bag
48
46
  if @redcycle[predicate].nil?
49
- return res
47
+ return res
50
48
  end
51
49
  @redcycle[predicate].each do |rp|
52
- res = name_bag(rp, res)
50
+ res = name_bag(rp, res)
53
51
  end
54
52
  end
55
53
 
@@ -65,7 +63,7 @@ class GraphGen #:nodoc: all
65
63
  return str
66
64
  else
67
65
  return predicate
68
- end
66
+ end
69
67
  end
70
68
 
71
69
  def process(depends)
@@ -92,11 +90,13 @@ class GraphGen #:nodoc: all
92
90
 
93
91
  def addonce(node, negcluster, inhead=false)
94
92
  if !@nodes[node]
95
- @nodes[node] = @graph.add_node(node)
93
+ @nodes[node] = @graph.add_node("n_#{node}")
96
94
  if @cards and @cards[node]
97
- @nodes[node].label = node +"\n (#{@cards[node].to_s})"
95
+ @nodes[node].label = "#{node}\n (#{@cards[node].to_s})"
96
+ else
97
+ @nodes[node].label = node
98
98
  end
99
- end
99
+ end
100
100
 
101
101
  if @budtime == -1
102
102
  @nodes[node].URL = "#{node}.html" if inhead
@@ -105,7 +105,7 @@ class GraphGen #:nodoc: all
105
105
  end
106
106
 
107
107
  if negcluster
108
- # cleaning
108
+ # cleaning
109
109
  res = node
110
110
  # pretty-printing issues
111
111
  node.split(", ").each_with_index do |p, i|
@@ -190,7 +190,7 @@ class GraphGen #:nodoc: all
190
190
  end
191
191
  end
192
192
 
193
- unless depanalysis.nil?
193
+ unless depanalysis.nil?
194
194
  depanalysis.source.each {|s| addedge("S", s.pred, false, false, false)}
195
195
  depanalysis.sink.each {|s| addedge(s.pred, "T", false, false, false)}
196
196
 
@@ -218,19 +218,19 @@ class GraphGen #:nodoc: all
218
218
  end
219
219
  end
220
220
 
221
- class SpaceTime
221
+ class SpaceTime
222
222
  def initialize(input, links = false)
223
- @input = input
223
+ @input = input
224
224
  @links = links
225
225
  processes = input.map {|i| i[1]}
226
226
  input.map{|i| processes << i[2]}
227
227
  processes.uniq!
228
228
 
229
- @queues = {}
230
-
229
+ @queues = {}
230
+
231
231
  @g = GraphViz.new(:G, :type => :digraph, :rankdir => "LR", :outputorder => "nodesfirst", :splines => "line")#, :clusterrank => "none")
232
232
  @hdr = @g.subgraph("cluster_0")
233
-
233
+
234
234
  @subs = {}
235
235
  @head = {}
236
236
  last = nil
@@ -238,7 +238,6 @@ class SpaceTime
238
238
  #@head[p] = @hdr.add_node("process #{p}(#{i})")#, :color => "white", :label => "")
239
239
  @subs[p] = @g.subgraph("buster_#{i+1}")
240
240
  @head[p] = @hdr.add_node("process #{p}(#{i})", :group => p)#, :color => "white", :label => "")
241
-
242
241
  end
243
242
  end
244
243
 
@@ -251,13 +250,13 @@ class SpaceTime
251
250
  @edges[lbl] = [f, t, l, 1]
252
251
  end
253
252
  end
254
-
253
+
255
254
  def process
256
255
  @edges = {}
257
256
  queues = {}
258
257
  @input.each do |i|
259
- queues[i[1]] = [] unless queues[i[1]]
260
- queues[i[2]] = [] unless queues[i[2]]
258
+ queues[i[1]] ||= []
259
+ queues[i[2]] ||= []
261
260
  queues[i[1]] << i[3]
262
261
  queues[i[2]] << i[4]
263
262
  end
@@ -275,7 +274,7 @@ class SpaceTime
275
274
  url = "DBM_#{k}_/tm_#{item}.svg"
276
275
  #puts "URL is #{url}"
277
276
  end
278
- snd = @subs[k].add_node(label, {:label => item.to_s, :width => 0.1, :height => 0.1, :fontsize => 6, :pos => [1, i], :group => k, :URL => url})
277
+ snd = @subs[k].add_node(label, {:label => item.to_s, :width => 0.1, :height => 0.1, :fontsize => 6, :pos => [1, i], :group => k, :URL => url})
279
278
 
280
279
  unless @head[k].id == snd.id
281
280
  @subs[k].add_edge(@head[k], snd, :weight => 2)
@@ -294,10 +293,10 @@ class SpaceTime
294
293
  msg_edge(snd_label, rcv_label, i[5])
295
294
  end
296
295
  end
297
-
296
+
298
297
  def finish(file, fmt=nil)
299
298
  @edges.each_pair do |k, v|
300
- lbl = v[3] > 1 ? "#{v[2]}(#{v[3]})" : v[2]
299
+ lbl = v[3] > 1 ? "#{v[2]}(#{v[3]})" : v[2]
301
300
  @g.add_edge(v[0], v[1], :label => lbl, :color => "red", :weight => 1)
302
301
  end
303
302
  if fmt.nil?
data/lib/bud/joins.rb CHANGED
@@ -9,6 +9,7 @@ module Bud
9
9
  @bud_instance = bud_instance
10
10
  @localpreds = nil
11
11
  @hashpreds = nil
12
+ @selfjoins = []
12
13
 
13
14
  # if any elements on rellist are BudJoins, suck up their contents
14
15
  tmprels = []
@@ -23,6 +24,18 @@ module Bud
23
24
  rellist = tmprels
24
25
  @origrels = rellist
25
26
 
27
+ # check for self-joins: we currently only handle 2 instances of the same table per rule
28
+ counts = @origrels.reduce({}) do |memo, r|
29
+ memo[r.tabname] ||= 0
30
+ memo[r.tabname] += 1
31
+ memo
32
+ end
33
+ counts.each do |name, cnt|
34
+ raise Bud::CompileError, "#{cnt} instances of #{name} in rule; only one self-join currently allowed per rule" if cnt > 2
35
+ @selfjoins << name if cnt == 2
36
+ end
37
+
38
+
26
39
  # recurse to form a tree of binary BudJoins
27
40
  @rels = [rellist[0]]
28
41
  @rels << (rellist.length == 2 ? rellist[1] : BudJoin.new(rellist[1..rellist.length-1], @bud_instance))
@@ -39,7 +52,7 @@ module Bud
39
52
  memo
40
53
  end
41
54
 
42
- setup_preds(preds) unless preds.empty?
55
+ setup_preds(preds)
43
56
  setup_state
44
57
  end
45
58
 
@@ -76,7 +89,7 @@ module Bud
76
89
  # similar to <tt>SELECT * FROM ... WHERE...</tt> block in SQL.
77
90
  public
78
91
  def flatten(*preds)
79
- setup_preds(preds) unless preds.empty?
92
+ setup_preds(preds)
80
93
  flat_schema = @rels.map{|r| r.schema}.flatten(1)
81
94
  dupfree_schema = []
82
95
  # while loop here (inefficiently) ensures no collisions
@@ -148,14 +161,14 @@ module Bud
148
161
  public
149
162
  def pairs(*preds, &blk)
150
163
  @origpreds = preds
151
- setup_preds(preds) unless preds.empty?
164
+ setup_preds(preds)
152
165
  # given new preds, the state for the join will be different. set it up again.
153
166
  setup_state if self.class <= Bud::BudJoin
154
167
  blk.nil? ? self : map(&blk)
155
168
  end
156
169
 
157
170
  alias combos pairs
158
-
171
+
159
172
  # the natural join: given a * expression over n collections, form all
160
173
  # combinations of items that have the same values in matching fields
161
174
  public
@@ -169,7 +182,7 @@ module Bud
169
182
  # of the first collection
170
183
  public
171
184
  def lefts(*preds, &blk)
172
- setup_preds(preds) unless preds.empty?
185
+ setup_preds(preds)
173
186
  # given new preds, the state for the join will be different. set it up again.
174
187
  setup_state if self.class <= Bud::BudJoin
175
188
  map{ |l,r| blk.nil? ? l : blk.call(l) }
@@ -180,7 +193,7 @@ module Bud
180
193
  # of the second item
181
194
  public
182
195
  def rights(*preds, &blk)
183
- setup_preds(preds) unless preds.empty?
196
+ setup_preds(preds)
184
197
  # given new preds, the state for the join will be different. set it up again.
185
198
  setup_state if self.class <= Bud::BudJoin
186
199
  map{ |l,r| blk.nil? ? r : blk.call(r) }
@@ -190,16 +203,36 @@ module Bud
190
203
  # satisfy +preds+, and for any item from the 1st collection that has no
191
204
  # matches in the 2nd, nil-pad it and include it in the output.
192
205
  public
193
- def outer(*preds)
206
+ def outer(*preds, &blk)
194
207
  @origpreds = preds
195
- @localpreds = disambiguate_preds(preds)
208
+ setup_preds(preds)
196
209
  self.extend(Bud::BudOuterJoin)
197
- map
210
+ blk.nil? ? self : map(&blk)
211
+ end
212
+
213
+ # AntiJoin
214
+ # note: unlike other join methods (e.g. lefts) all we do with the return value
215
+ # of block is check whether it's nil. Putting "projection" logic in the block
216
+ # has no effect on the output.
217
+ public
218
+ def anti(*preds, &blk)
219
+ @origpreds = preds
220
+ # no projection involved here, so we can propagate the schema
221
+ @schema = rels[0].schema
222
+ setup_preds(preds)
223
+ setup_state if self.class <= Bud::BudJoin
224
+ if @bud_instance.stratum_first_iter
225
+ @matches = map { |r, s| (blk.nil?) ? r : blk.call(r,s) }.compact
226
+ @rels[0].map {|r| (@matches.include? r) ? nil : r}.compact
227
+ else
228
+ []
229
+ end
198
230
  end
199
231
 
200
232
  # extract predicates on rellist[0] and recurse to right side with remainder
201
233
  protected
202
234
  def setup_preds(preds) # :nodoc: all
235
+ return if preds.empty?
203
236
  allpreds = disambiguate_preds(preds)
204
237
  allpreds = canonicalize_localpreds(@rels, allpreds)
205
238
  # check for refs to collections that aren't being joined, Issue 191
@@ -211,8 +244,16 @@ module Bud
211
244
  end
212
245
  end
213
246
  end
214
- @hashpreds = allpreds.reject { |p| p[0][0] != @rels[0].tabname or p[1][0] == @rels[0].tabname }
247
+ @hashpreds = allpreds.reject {|p| p[0][0] != @rels[0].tabname}
215
248
  @localpreds = @hashpreds
249
+
250
+ # only allow preds on the same table name if they're on a self-joined table
251
+ @localpreds.each do |p|
252
+ if p[0][0] == p[1][0] and not @selfjoins.include? p[0][0]
253
+ raise Bud::CompileError, "single-table predicate on #{p[0][0]} disallowed in joins"
254
+ end
255
+ end
256
+
216
257
  @localpreds += allpreds.map do |p|
217
258
  p if p[0][0] == p[1][0] and (p[0][0] == @rels[0].tabname or p[0][0] == @rels[1].tabname)
218
259
  end.compact
@@ -284,7 +325,8 @@ module Bud
284
325
  protected
285
326
  def canonicalize_localpreds(rel_list, preds) # :nodoc:all
286
327
  retval = preds.map do |p|
287
- p[1][0] == rel_list[0].tabname ? p.reverse : p
328
+ # reverse if rhs is rel_list[0], *unless* it's a self-join!
329
+ (p[1][0] == rel_list[0].tabname and p[1][0] != p[0][0]) ? p.reverse : p
288
330
  end
289
331
  end
290
332
 
@@ -303,7 +345,8 @@ module Bud
303
345
  if (@localpreds and skips and @localpreds.length > skips.length)
304
346
  # check remainder of the predicates
305
347
  @localpreds.each do |pred|
306
- next if skips.include? pred
348
+ # skip skips, and self-join preds
349
+ next if (skips.include? pred or pred[0][0] == pred[1][0])
307
350
  vals = []
308
351
  (0..1).each do |i|
309
352
  if pred[i][0] == @rels[0].tabname
@@ -327,7 +370,9 @@ module Bud
327
370
  @rels[0].each_from_sym([left_rel]) do |r|
328
371
  @rels[1].each_from_sym([right_rel]) do |s|
329
372
  s = [s] if origrels.length == 2
330
- yield([r] + s) if test_locals(r, s)
373
+ if test_locals(r, s)
374
+ yield([r] + s)
375
+ end
331
376
  end
332
377
  end
333
378
  end
@@ -340,12 +385,8 @@ module Bud
340
385
  name, offset = entry[0], entry[1]
341
386
 
342
387
  # determine which subtuple of the collection contains the table
343
- # referenced in entry. note that origrels[0] is a base table
344
- # on the left of the join, hence we shouldn't be calling join_offset on it
388
+ # referenced in entry.
345
389
  subtuple = 0
346
- if origrels[0].tabname == entry[0]
347
- raise BudError, "join_offset called on the result of a single collection #{entry[0]}"
348
- end
349
390
  origrels[1..origrels.length].each_with_index do |t,i|
350
391
  if t.tabname == entry[0]
351
392
  subtuple = i
@@ -393,38 +434,42 @@ module Bud
393
434
  r = [r] unless probe_ix == 1 and origrels.length > 2
394
435
  attrval = (probe_ix == 0) ? r[0][left_offset] : r[right_subtuple][right_offset]
395
436
 
396
- # insert into the prober's hashtable only if symmetric ...
437
+ # insert into the prober's hashtable only if symmetric
397
438
  if probe_sym == other_sym
398
439
  @hash_tables[probe_ix][probe_sym][attrval] ||= []
399
440
  @hash_tables[probe_ix][probe_sym][attrval] << r
400
441
  end
401
442
 
402
443
  # ...and probe the other hashtable
403
- next if @hash_tables[other_ix][other_sym][attrval].nil?
404
- @hash_tables[other_ix][other_sym][attrval].each do |s_tup|
405
- if probe_ix == 0
406
- left = r; right = s_tup
407
- else
408
- left = s_tup; right = r
444
+ if @hash_tables[other_ix][other_sym][attrval].nil?
445
+ next
446
+ else
447
+ @hash_tables[other_ix][other_sym][attrval].each do |s_tup|
448
+ if probe_ix == 0
449
+ left = r; right = s_tup
450
+ else
451
+ left = s_tup; right = r
452
+ end
453
+ retval = left + right
454
+ yield retval if test_locals(left[0], right, @hashpreds.first)
409
455
  end
410
- retval = left + right
411
- yield retval if test_locals(left[0], right, @hashpreds.first)
412
456
  end
413
457
  end
414
458
  end
415
459
  end
416
460
  end
417
461
 
462
+ # intended to be used to extend a BudJoin instance
418
463
  module BudOuterJoin
419
464
  public
420
465
  def each(&block) # :nodoc:all
421
466
  super(&block)
422
- # previous line finds all the matches.
423
- # now its time to ``preserve'' the outer tuples with no matches.
424
- # this is totally inefficient: we should fold the identification of non-matches
425
- # into the join algorithms. Another day.
426
- # our trick: for each tuple of the outer, generate a singleton relation
427
- # and join with inner. If result is empty, preserve tuple.
467
+ # Previous line finds all the matches. Now its time to ``preserve'' the
468
+ # outer tuples with no matches. Our trick: for each tuple of the outer,
469
+ # generate a singleton relation and join with inner. If result is empty,
470
+ # preserve tuple.
471
+ # XXX: This is totally inefficient: we should fold the identification of
472
+ # non-matches into the join algorithms. Another day.
428
473
  @rels[0].each do |r|
429
474
  t = @origrels[0].clone_empty
430
475
  # need to uniquify the tablename here to avoid sharing join state with original
@@ -0,0 +1,43 @@
1
+ require "bud/errors"
2
+ require 'faster_csv'
3
+
4
+ # metrics are reported in a nested hash representing a collection of relational tables.
5
+ # The metrics hash has the following form:
6
+ # - key of the metrics hash is the name of the metric (table), e.g. "tickstats", "collections", "rules", etc.
7
+ # - value of the metrics is itself a hash holding the rows of the table, keyed by key columns.
8
+ # - It has the following form:
9
+ # - key is a hash of key attributes, with the following form:
10
+ # - key is the name of an attribute
11
+ # - value is the attribute's value
12
+ # - value is a single dependent value, e.g. a statistic like a count
13
+
14
+ def report_metrics
15
+ metrics.each do |k,v|
16
+ if v.first
17
+ csvstr = FasterCSV.generate(:force_quotes=>true) do |csv|
18
+ csv << [k.to_s] + v.first[0].keys + [:val]
19
+ v.each do |row|
20
+ csv << [nil] + row[0].values + [row[1]]
21
+ end
22
+ end
23
+ puts csvstr
24
+ end
25
+ end
26
+ end
27
+
28
+ def initialize_stats
29
+ return {{:name=>:count} => 0, {:name=>:mean} => 0, {:name=>:Q} => 0, {:name=>:stddev} => 0}
30
+ end
31
+
32
+ # see http://en.wikipedia.org/wiki/Standard_deviation#Rapid_calculation_methods
33
+ def running_stats(stats, elapsed)
34
+ raise Bud::BudError, "running_stats called with negative elapsed time" if elapsed < 0
35
+ stats[{:name=>:count}] += 1
36
+ oldmean = stats[{:name=>:mean}]
37
+ stats[{:name=>:mean}] = stats[{:name=>:mean}] + \
38
+ (elapsed - stats[{:name=>:mean}]) / stats[{:name=>:count}]
39
+ stats[{:name=>:Q}] = stats[{:name=>:Q}] + \
40
+ (elapsed - oldmean) * (elapsed - stats[{:name=>:mean}])
41
+ stats[{:name=>:stddev}] = Math.sqrt(stats[{:name=>:Q}]/stats[{:name=>:count}])
42
+ stats
43
+ end
@@ -12,7 +12,7 @@ class Module
12
12
  # module X points to X's own nested import table.
13
13
  @bud_import_tbl ||= {}
14
14
  child_tbl = mod.bud_import_table
15
- raise Bud::CompileError if @bud_import_tbl.has_key? local_name
15
+ raise Bud::CompileError, "import symbol #{local_name} already in use" if @bud_import_tbl.has_key? local_name
16
16
  @bud_import_tbl[local_name] = child_tbl.clone # XXX: clone needed?
17
17
 
18
18
  rewritten_mod_name = ModuleRewriter.do_import(self, mod, local_name)
data/lib/bud/rebl.rb CHANGED
@@ -4,7 +4,7 @@ require 'rubygems'
4
4
  require 'bud'
5
5
  require 'abbrev'
6
6
 
7
- TABLE_TYPES = ["table", "scratch", "channel"]
7
+ TABLE_TYPES = ["table", "scratch", "channel", "loopback", "periodic", "sync", "store"]
8
8
 
9
9
  # The class to which rebl adds user-specified rules and declarations.
10
10
  class ReblClass
@@ -97,7 +97,8 @@ class ReblShell
97
97
  line = Readline::readline('rebl> ') unless noreadline
98
98
  line = gets if noreadline
99
99
  do_exit if line.nil?
100
- line = line.lstrip.rstrip
100
+ line.strip!
101
+ return if line.empty?
101
102
  Readline::HISTORY.push(line) unless noreadline
102
103
  split_line = line.split(" ")
103
104
  if line[0..0] == @@escape_char then
@@ -180,7 +181,7 @@ class ReblShell
180
181
  rescue Exception
181
182
  puts "Error when saving permanent history: #{$!}"
182
183
  end
183
- @rebl_class_inst.stop_bg if @rebl_class_inst
184
+ @rebl_class_inst.stop if @rebl_class_inst
184
185
  puts "\n" + @@exit_message
185
186
  exit!
186
187
  end
@@ -212,12 +213,12 @@ class LibRebl
212
213
 
213
214
  # Runs the bud instance (until a breakpoint, or stop() is called)
214
215
  def run
215
- @rebl_class_inst.sync_do {@rebl_class_inst.lazy = false}
216
+ @rebl_class_inst.run_bg
216
217
  end
217
218
 
218
219
  # Stops the bud instance (and then performs another tick)
219
220
  def stop
220
- @rebl_class_inst.sync_do {@rebl_class_inst.lazy = true}
221
+ @rebl_class_inst.pause
221
222
  end
222
223
 
223
224
  # Ticks the bud instance a specified integer number of times.
@@ -274,13 +275,20 @@ class LibRebl
274
275
  if not @state.empty?
275
276
  @rebl_class.class_eval("state do\n" + @state.values.join("\n") + "\nend")
276
277
  end
277
- rescue
278
+ rescue Exception
278
279
  raise
279
280
  end
280
281
 
281
282
  @old_inst = @rebl_class_inst
282
- @rebl_class_inst = @rebl_class.new(:no_signal_handlers => true, :ip => @ip,
283
- :port => @port, :lazy => true)
283
+ @rebl_class_inst = @rebl_class.new(:signal_handling => :none, :ip => @ip,
284
+ :port => @port)
285
+
286
+ # Stop the old instance. We want to copy the old instance's state over to
287
+ # the new instance and then startup the new instance. Any network messages
288
+ # received before the new instance has been started will be lost, but that
289
+ # can't easily be avoided; the best we can do is ensure we get a consistent
290
+ # snapshot of the old instance's state.
291
+ @old_inst.stop if @old_inst
284
292
 
285
293
  # Copy the tables over.
286
294
  if @old_inst
@@ -302,17 +310,16 @@ class LibRebl
302
310
 
303
311
  # Run lazily in background, shutting down old instance.
304
312
  begin
305
- @old_inst.stop_bg if @old_inst
306
313
  # Lazify the instance upon a breakpoint (no effect if instance is
307
314
  # already lazy)
308
315
  @rebl_class_inst.register_callback(:rebl_breakpoint) do
309
- @rebl_class_inst.lazy = true
316
+ @rebl_class_inst.pause
310
317
  end
311
- @rebl_class_inst.run_bg
318
+ @rebl_class_inst.start
312
319
  @ip = @rebl_class_inst.ip
313
320
  @port = @rebl_class_inst.port
314
- puts "Listening on #{@rebl_class_inst.ip_port}" if not @old_inst
315
- rescue
321
+ puts "Listening on #{@ip}:#{@port}" unless @old_inst
322
+ rescue Exception
316
323
  # The above two need to be atomic, or we're in trouble.
317
324
  puts "unrecoverable error, please file a bug: #{$!}"
318
325
  abort