bud 0.0.4 → 0.0.5

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