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.
data/lib/bud/viz_util.rb CHANGED
@@ -14,6 +14,50 @@ module TraceCardinality
14
14
  end
15
15
  end
16
16
 
17
+ class VizHelper
18
+ include Bud
19
+ include TraceCardinality
20
+
21
+ def initialize(tabinf, cycle, depends, rules, dir)
22
+ @t_tabinf = tabinf
23
+ @t_cycle = cycle
24
+ @t_depends = depends
25
+ @t_rules = rules
26
+ @dir = dir
27
+ super()
28
+ end
29
+
30
+ def summarize(dir, schema)
31
+ table_io = {}
32
+ cardinalities.sort{|a, b| a[0] <=> b[0]}.each do |card|
33
+ table_io["#{card.table}_#{card.bud_time}"] = start_table(dir, card.table, card.bud_time, schema[card.table])
34
+ end
35
+
36
+ full_info.each do |info|
37
+ write_table_content(table_io["#{info.table}_#{info.bud_time}"], info.row)
38
+ end
39
+
40
+ table_io.each_value do |tab|
41
+ end_table(tab)
42
+ end
43
+
44
+ # fix: nested loops
45
+ times.sort.each do |time|
46
+ card_info = {}
47
+ cardinalities.each do |card|
48
+ if card.bud_time == time.bud_time
49
+ card_info[card.table] = card.cnt
50
+ end
51
+ end
52
+
53
+ d = "#{@dir}/tm_#{time.bud_time}"
54
+ write_graphs(@t_tabinf, @t_cycle, @t_depends, @t_rules, d, @dir, nil, false, nil, time.bud_time, card_info)
55
+ end
56
+ end
57
+
58
+ end
59
+
60
+
17
61
  module VizUtil #:nodoc: all
18
62
  def graph_from_instance(bud_instance, viz_name, output_base, collapse=true, fmt=nil)
19
63
  tabinf = {}
@@ -201,4 +245,30 @@ END_JS
201
245
  end
202
246
  return meta, data
203
247
  end
248
+
249
+ def start_table(dir, tab, time, schema)
250
+ str = "#{dir}/#{tab}_#{time}.html"
251
+ fout = File.new(str, "w")
252
+
253
+ fout.puts "<html><title>#{tab} @ #{time}</title>"
254
+ fout.puts "<table border=1>"
255
+ fout.puts "<tr>" + schema.map{|s| "<th> #{s} </th>"}.join(" ") + "<tr>" unless schema.nil?
256
+ fout.close
257
+ return str
258
+ end
259
+
260
+ def end_table(stream)
261
+ fp = File.open(stream, "a")
262
+ fp.puts "</table>"
263
+ fp.close
264
+ end
265
+
266
+ def write_table_content(fn, row)
267
+ stream = File.open(fn, "a")
268
+ stream.puts "<tr>"
269
+ stream.puts row.map{|c| "<td>#{c.to_s}</td>"}.join(" ")
270
+ stream.puts "</tr>"
271
+ stream.close
272
+ end
273
+
204
274
  end
data/lib/bud.rb CHANGED
@@ -15,6 +15,7 @@ require 'bud/deploy/forkdeploy'
15
15
  require 'bud/deploy/threaddeploy'
16
16
  require 'bud/errors'
17
17
  require 'bud/joins'
18
+ require 'bud/metrics'
18
19
  require 'bud/rtrace'
19
20
  require 'bud/server'
20
21
  require 'bud/state'
@@ -37,10 +38,11 @@ $bud_instances = {} # Map from instance id => Bud instance
37
38
  # three main options:
38
39
  #
39
40
  # 1. Synchronously. To do this, instantiate your program and then call tick()
40
- # one or more times; each call evaluates a single Bud timestep. Note that in
41
- # this mode, network communication (channels) and timers cannot be used. This
42
- # is mostly intended for "one-shot" programs that compute a single result and
43
- # then terminate.
41
+ # one or more times; each call evaluates a single Bud timestep. In this mode,
42
+ # any network messages or timer events that occur will be buffered until the
43
+ # next call to tick(). This is mostly intended for "one-shot" programs that
44
+ # compute a single result and then terminate, or for interactively
45
+ # "single-stepping" through the execution of an event-driven system.
44
46
  # 2. In a separate thread in the foreground. To do this, instantiate your
45
47
  # program and then call run_fg(). The Bud interpreter will then run, handling
46
48
  # network events and evaluating new timesteps as appropriate. The run_fg()
@@ -49,18 +51,22 @@ $bud_instances = {} # Map from instance id => Bud instance
49
51
  # program and then call run_bg(). The Bud interpreter will run
50
52
  # asynchronously. To interact with Bud (e.g., insert additional data or
51
53
  # inspect the state of a Bud collection), use the sync_do and async_do
52
- # methods. To shutdown the Bud interpreter, use stop_bg().
54
+ # methods.
53
55
  #
54
- # Most programs should use method #3.
56
+ # Most programs should use method #3. Note that in all three cases, the stop()
57
+ # method should be used to shutdown a Bud instance and release any resources it
58
+ # is using.
55
59
  #
56
60
  # :main: Bud
57
61
  module Bud
58
62
  attr_reader :strata, :budtime, :inbound, :options, :meta_parser, :viz, :rtracer
59
- attr_reader :dsock, :ip, :port
60
- attr_reader :tables, :channels, :tc_tables, :zk_tables, :dbm_tables
63
+ attr_reader :dsock
64
+ attr_reader :tables, :channels, :tc_tables, :zk_tables, :dbm_tables, :sources, :sinks
61
65
  attr_reader :stratum_first_iter, :joinstate
62
- attr_accessor :lazy # This can be changed on-the-fly by REBL
63
- attr_accessor :stratum_collection_map
66
+ attr_reader :this_stratum, :this_rule, :rule_orig_src
67
+ attr_reader :running_async
68
+ attr_accessor :stratum_collection_map, :rewritten_strata, :no_attr_rewrite_strata
69
+ attr_accessor :metrics
64
70
 
65
71
  # options to the Bud runtime are passed in a hash, with the following keys
66
72
  # * network configuration
@@ -72,14 +78,14 @@ module Bud
72
78
  # * operating system interaction
73
79
  # * <tt>:stdin</tt> if non-nil, reading from the +stdio+ collection results in reading from this +IO+ handle
74
80
  # * <tt>:stdout</tt> writing to the +stdio+ collection results in writing to this +IO+ handle; defaults to <tt>$stdout</tt>
75
- # * <tt>:no_signal_handlers</tt> if true, runtime ignores +SIGINT+ and +SIGTERM+
81
+ # * <tt>:signal_handling</tt> how to handle +SIGINT+ and +SIGTERM+. If :none, these signals are ignored. If :bloom, they're passed into the built-in scratch called +signals+. Else shutdown all bud instances.
76
82
  # * tracing and output
77
83
  # * <tt>:quiet</tt> if true, suppress certain messages
78
84
  # * <tt>:trace</tt> if true, generate +budvis+ outputs
79
85
  # * <tt>:rtrace</tt> if true, generate +budplot+ outputs
80
86
  # * <tt>:dump_rewrite</tt> if true, dump results of internal rewriting of Bloom code to a file
87
+ # * <tt>:metrics</tt> if true, dumps a hash of internal performance metrics
81
88
  # * controlling execution
82
- # * <tt>:lazy</tt> if true, prevents runtime from ticking except on external calls to +tick+
83
89
  # * <tt>:tag</tt> a name for this instance, suitable for display during tracing and visualization
84
90
  # * storage configuration
85
91
  # * <tt>:dbm_dir</tt> filesystem directory to hold DBM-backed collections
@@ -99,7 +105,8 @@ module Bud
99
105
  @zk_tables = {}
100
106
  @callbacks = {}
101
107
  @callback_id = 0
102
- @shutdown_callbacks = []
108
+ @shutdown_callbacks = {}
109
+ @shutdown_callback_id = 0
103
110
  @post_shutdown_callbacks = []
104
111
  @timers = []
105
112
  @inside_tick = false
@@ -109,10 +116,15 @@ module Bud
109
116
  @done_bootstrap = false
110
117
  @joinstate = {} # joins are stateful, their state needs to be kept inside the Bud instance
111
118
  @instance_id = ILLEGAL_INSTANCE_ID # Assigned when we start running
119
+ @sources = {}
120
+ @sinks = {}
121
+ @metrics = {}
122
+ @endtime = nil
123
+ @running_async = false
124
+ @bud_started = false
112
125
 
113
126
  # Setup options (named arguments), along with default values
114
127
  @options = options.clone
115
- @lazy = @options[:lazy] ||= false
116
128
  @options[:ip] ||= "127.0.0.1"
117
129
  @ip = @options[:ip]
118
130
  @options[:port] ||= 0
@@ -177,6 +189,7 @@ module Bud
177
189
  u = Unifier.new
178
190
  ref_expander = NestedRefRewriter.new(klass.bud_import_table)
179
191
  tmp_expander = TempExpander.new
192
+ with_expander = WithExpander.new
180
193
  r2r = Ruby2Ruby.new
181
194
 
182
195
  klass.instance_methods(false).each do |m|
@@ -184,24 +197,28 @@ module Bud
184
197
  ast = u.process(ast)
185
198
  ast = ref_expander.process(ast)
186
199
  ast = tmp_expander.process(ast)
200
+ ast = with_expander.process(ast)
187
201
 
188
- if (ref_expander.did_work or tmp_expander.did_work)
202
+ if ref_expander.did_work or tmp_expander.did_work or with_expander.did_work
189
203
  new_source = r2r.process(ast)
190
204
  klass.module_eval new_source # Replace previous method def
191
205
  end
192
206
 
193
207
  ref_expander.did_work = false
194
208
  tmp_expander.did_work = false
209
+ with_expander.did_work = false
195
210
  end
196
211
 
197
212
  # If we found any temp statements in the klass's rule blocks, add a state
198
213
  # block with declarations for the corresponding temp collections.
199
- s = tmp_expander.get_state_meth(klass)
200
- if s
201
- state_src = r2r.process(s)
214
+ st = tmp_expander.get_state_meth(klass)
215
+ if st
216
+ state_src = r2r.process(st)
202
217
  klass.module_eval(state_src)
203
218
  end
204
219
 
220
+ ModuleRewriter.ast_mangle_with(with_expander, klass)
221
+
205
222
  # Always rewrite anonymous classes
206
223
  @done_rewrite[klass.name] = true unless klass.name == ""
207
224
  end
@@ -275,18 +292,78 @@ module Bud
275
292
  # when interacting with it. For example, it is not safe to directly examine
276
293
  # Bud collections from the caller's thread (see async_do and sync_do).
277
294
  #
278
- # This instance of Bud will continue to execute until stop_bg is called.
295
+ # This instance of Bud will run until stop() is called.
279
296
  def run_bg
297
+ start
298
+
299
+ schedule_and_wait do
300
+ if @running_async
301
+ raise BudError, "run_bg called on already-running Bud instance"
302
+ end
303
+ @running_async = true
304
+
305
+ # Consume any events received while we weren't running async
306
+ tick_internal
307
+ end
308
+
309
+ @rtracer.sleep if options[:rtrace]
310
+ end
311
+
312
+ # Startup a Bud instance. This starts EventMachine (if needed) and binds to a
313
+ # UDP server socket. If do_tick is true, we also execute a single Bloom
314
+ # timestep. Regardless, calling this method does NOT cause Bud to begin
315
+ # executing timesteps asynchronously (see run_bg).
316
+ def start(do_tick=false)
280
317
  start_reactor
281
- # Wait for Bud to start up before returning
282
318
  schedule_and_wait do
283
- start_bud
319
+ do_startup unless @bud_started
320
+ tick_internal if do_tick
321
+ end
322
+ end
323
+
324
+ private
325
+ def do_startup
326
+ raise BudError, "EventMachine not started" unless EventMachine::reactor_running?
327
+ raise BudError unless EventMachine::reactor_thread?
328
+
329
+ @instance_id = Bud.init_signal_handlers(self)
330
+ do_start_server
331
+ @bud_started = true
332
+
333
+ # Initialize periodics
334
+ @periodics.each do |p|
335
+ @timers << make_periodic_timer(p.pername, p.period)
336
+ end
337
+
338
+ # Arrange for Bud to read from stdin if enabled. Note that we can't do this
339
+ # earlier because we need to wait for EventMachine startup.
340
+ @stdio.start_stdin_reader if @options[:stdin]
341
+ @zk_tables.each_value {|t| t.start_watchers}
342
+
343
+ @halt_cb = register_callback(:halt) do |t|
344
+ stop
345
+ if t.first.key == :kill
346
+ Bud.shutdown_all_instances
347
+ Bud.stop_em_loop
348
+ end
349
+ end
350
+ end
351
+
352
+ # Pause an instance of Bud that is running asynchronously. That is, this
353
+ # method allows a Bud instance operating in run_bg or run_fg mode to be
354
+ # switched to "single-stepped" mode; timesteps can be manually invoked via
355
+ # tick(). To switch back to running Bud asynchronously, call run_bg().
356
+ public
357
+ def pause
358
+ schedule_and_wait do
359
+ @running_async = false
284
360
  end
285
361
  end
286
362
 
287
363
  # Run Bud in the "foreground" -- the caller's thread will be used to run the
288
- # Bud interpreter. This means this method won't return unless an error
289
- # occurs. It is often more useful to run Bud asynchronously -- see run_bg.
364
+ # Bud interpreter. This means this method won't return unless an error occurs
365
+ # or Bud is halted. It is often more useful to run Bud in a different thread:
366
+ # see run_bg.
290
367
  def run_fg
291
368
  # If we're called from the EventMachine thread (and EM is running), blocking
292
369
  # the current thread would imply deadlocking ourselves.
@@ -306,14 +383,15 @@ module Bud
306
383
  run_bg
307
384
  # Block caller's thread until Bud has shutdown
308
385
  q.pop
386
+ report_metrics if options[:metrics]
309
387
  end
310
388
 
311
- # Shutdown a Bud instance that is running asynchronously. This method blocks
312
- # until Bud has been shutdown. If +stop_em+ is true, the EventMachine event
313
- # loop is also shutdown; this will interfere with the execution of any other
314
- # Bud instances in the same process (as well as anything else that happens to
315
- # use EventMachine).
316
- def stop_bg(stop_em=false, do_shutdown_cb=true)
389
+ # Shutdown a Bud instance and release any resources that it was using. This
390
+ # method blocks until Bud has been shutdown. If +stop_em+ is true, the
391
+ # EventMachine event loop is also shutdown; this will interfere with the
392
+ # execution of any other Bud instances in the same process (as well as
393
+ # anything else that happens to use EventMachine).
394
+ def stop(stop_em=false, do_shutdown_cb=true)
317
395
  schedule_and_wait do
318
396
  do_shutdown(do_shutdown_cb)
319
397
  end
@@ -322,15 +400,30 @@ module Bud
322
400
  Bud.stop_em_loop
323
401
  EventMachine::reactor_thread.join
324
402
  end
403
+ report_metrics if options[:metrics]
325
404
  end
405
+ alias :stop_bg :stop
326
406
 
327
407
  # Register a callback that will be invoked when this instance of Bud is
328
408
  # shutting down.
409
+ # XXX: The naming of this method (and cancel_shutdown_cb) is inconsistent
410
+ # with the naming of register_callback and friends.
329
411
  def on_shutdown(&blk)
330
412
  # Start EM if not yet started
331
413
  start_reactor
414
+ rv = nil
415
+ schedule_and_wait do
416
+ rv = @shutdown_callback_id
417
+ @shutdown_callbacks[@shutdown_callback_id] = blk
418
+ @shutdown_callback_id += 1
419
+ end
420
+ return rv
421
+ end
422
+
423
+ def cancel_shutdown_cb(id)
332
424
  schedule_and_wait do
333
- @shutdown_callbacks << blk
425
+ raise Bud::BudError unless @shutdown_callbacks.has_key? id
426
+ @shutdown_callbacks.delete(id)
334
427
  end
335
428
  end
336
429
 
@@ -345,11 +438,10 @@ module Bud
345
438
  end
346
439
 
347
440
  # Given a block, evaluate that block inside the background Ruby thread at some
348
- # time in the future. Because the block is evaluate inside the background Ruby
349
- # thread, the block can safely examine Bud state. Naturally, this method can
350
- # only be used when Bud is running in the background. Note that calling
351
- # sync_do blocks the caller until the block has been evaluated; for a
352
- # non-blocking version, see async_do.
441
+ # time in the future, and then perform a Bloom tick. Because the block is
442
+ # evaluate inside the background Ruby thread, the block can safely examine Bud
443
+ # state. Note that calling sync_do blocks the caller until the block has been
444
+ # evaluated; for a non-blocking version, see async_do.
353
445
  #
354
446
  # Note that the block is invoked after one Bud timestep has ended but before
355
447
  # the next timestep begins. Hence, synchronous accumulation (<=) into a Bud
@@ -361,7 +453,7 @@ module Bud
361
453
  schedule_and_wait do
362
454
  yield if block_given?
363
455
  # Do another tick, in case the user-supplied block inserted any data
364
- tick
456
+ tick_internal
365
457
  end
366
458
  end
367
459
 
@@ -372,17 +464,7 @@ module Bud
372
464
  EventMachine::schedule do
373
465
  yield if block_given?
374
466
  # Do another tick, in case the user-supplied block inserted any data
375
- tick
376
- end
377
- end
378
-
379
- # Shutdown any persistent tables used by the current Bud instance. If you are
380
- # running Bud via tick() and using +tctable+ collections, you should call this
381
- # after you're finished using Bud. Programs that use Bud via run_fg() or
382
- # run_bg() don't need to call this manually.
383
- def close_tables
384
- @tables.each_value do |t|
385
- t.close
467
+ tick_internal
386
468
  end
387
469
  end
388
470
 
@@ -394,10 +476,9 @@ module Bud
394
476
  # runtime is blocked while the callback is invoked, it can also examine any
395
477
  # other Bud state freely.)
396
478
  #
397
- # Note that registering callbacks on persistent collections (e.g., tables and
398
- # tctables) is probably not a wise thing to do: as long as any tuples are
399
- # stored in the collection, the callback will be invoked at the end of every
400
- # tick.
479
+ # Note that registering callbacks on persistent collections (e.g., tables,
480
+ # syncs and stores) is probably not wise: as long as any tuples are stored in
481
+ # the collection, the callback will be invoked at the end of every tick.
401
482
  def register_callback(tbl_name, &block)
402
483
  # We allow callbacks to be added before or after EM has been started. To
403
484
  # simplify matters, we start EM if it hasn't been started yet.
@@ -419,7 +500,7 @@ module Bud
419
500
  # Unregister the callback that has the given ID.
420
501
  def unregister_callback(id)
421
502
  schedule_and_wait do
422
- raise Bud::BudError unless @callbacks.has_key? id
503
+ raise Bud::BudError, "Missing callback: #{id.inspect}" unless @callbacks.has_key? id
423
504
  @callbacks.delete(id)
424
505
  end
425
506
  end
@@ -433,6 +514,14 @@ module Bud
433
514
  cb = register_callback(out_tbl) do |c|
434
515
  q.push c.to_a
435
516
  end
517
+
518
+ # If the runtime shuts down before we see anything in the output collection,
519
+ # make sure we hear about it so we can raise an error
520
+ # XXX: Using two separate callbacks here is ugly.
521
+ shutdown_cb = on_shutdown do
522
+ q.push :callback
523
+ end
524
+
436
525
  unless in_tbl.nil?
437
526
  sync_do {
438
527
  t = @tables[in_tbl]
@@ -444,8 +533,13 @@ module Bud
444
533
  }
445
534
  end
446
535
  result = q.pop
536
+ if result == :callback
537
+ # Don't try to unregister the callbacks first: runtime is already shutdown
538
+ raise BudShutdownWithCallbacksError, "Bud instance shutdown before sync_callback completed"
539
+ end
447
540
  unregister_callback(cb)
448
- return result
541
+ cancel_shutdown_cb(shutdown_cb)
542
+ return (result == :callback) ? nil : result
449
543
  end
450
544
 
451
545
  # A common special case for sync_callback: block on a delta to a table.
@@ -469,8 +563,14 @@ module Bud
469
563
  return if EventMachine::reactor_running?
470
564
 
471
565
  EventMachine::error_handler do |e|
472
- puts "Unexpected Bud error: #{e.inspect}"
473
- puts e.backtrace.join("\n")
566
+ # Only print a backtrace if a non-BudError is raised (this presumably
567
+ # indicates an unexpected failure).
568
+ if e.class <= BudError
569
+ puts "#{e.class}: #{e}"
570
+ else
571
+ puts "Unexpected Bud error: #{e.inspect}"
572
+ puts e.backtrace.join("\n")
573
+ end
474
574
  Bud.shutdown_all_instances
475
575
  raise e
476
576
  end
@@ -524,40 +624,22 @@ module Bud
524
624
  @instance_id = ILLEGAL_INSTANCE_ID
525
625
  }
526
626
 
627
+ unregister_callback(@halt_cb)
527
628
  if do_shutdown_cb
528
- @shutdown_callbacks.each {|cb| cb.call}
629
+ @shutdown_callbacks.each_value {|cb| cb.call}
529
630
  end
530
631
  @timers.each {|t| t.cancel}
531
- close_tables
532
- @dsock.close_connection if EventMachine::reactor_running?
632
+ @tables.each_value {|t| t.close}
633
+ if EventMachine::reactor_running? and @bud_started
634
+ @dsock.close_connection
635
+ end
636
+ @bud_started = false
637
+ @running_async = false
533
638
  if do_shutdown_cb
534
639
  @post_shutdown_callbacks.each {|cb| cb.call}
535
640
  end
536
641
  end
537
642
 
538
- private
539
- def start_bud
540
- raise BudError unless EventMachine::reactor_thread?
541
-
542
- @instance_id = Bud.init_signal_handlers(self)
543
- do_start_server
544
-
545
- # Initialize periodics
546
- @periodics.each do |p|
547
- @timers << set_periodic_timer(p.pername, p.ident, p.period)
548
- end
549
-
550
- # Arrange for Bud to read from stdin if enabled. Note that we can't do this
551
- # earlier because we need to wait for EventMachine startup.
552
- @stdio.start_stdin_reader if @options[:stdin]
553
- @zk_tables.each_value {|t| t.start_watchers}
554
-
555
- # Compute a fixpoint; this will also invoke any bootstrap blocks.
556
- tick unless @lazy
557
-
558
- @rtracer.sleep if options[:rtrace]
559
- end
560
-
561
643
  def do_start_server
562
644
  @dsock = EventMachine::open_datagram_socket(@ip, @options[:port],
563
645
  BudServer, self)
@@ -574,12 +656,18 @@ module Bud
574
656
  # forwarding, and external_ip:local_port would be if you're in a DMZ, for
575
657
  # example.
576
658
  def ip_port
577
- raise BudError, "ip_port called before port defined" if @port.nil? and @options[:port] == 0 and not @options[:ext_port]
659
+ raise BudError, "ip_port called before port defined" if port.nil?
660
+ ip.to_s + ":" + port.to_s
661
+ end
578
662
 
663
+ def ip
579
664
  ip = options[:ext_ip] ? "#{@options[:ext_ip]}" : "#{@ip}"
580
- port = options[:ext_port] ? "#{@options[:ext_port]}" :
665
+ end
666
+
667
+ def port
668
+ return nil if @port.nil? and @options[:port] == 0 and not @options[:ext_port]
669
+ return options[:ext_port] ? "#{@options[:ext_port]}" :
581
670
  (@port.nil? ? "#{@options[:port]}" : "#{@port}")
582
- ip + ":" + port
583
671
  end
584
672
 
585
673
  # Returns the internal IP and port. See ip_port.
@@ -588,9 +676,20 @@ module Bud
588
676
  @port.nil? ? "#{@ip}:#{@options[:port]}" : "#{@ip}:#{@port}"
589
677
  end
590
678
 
591
- # Manually trigger one timestep of Bloom execution.
679
+ # From client code, manually trigger a timestep of Bloom execution.
592
680
  def tick
681
+ start(true)
682
+ end
683
+
684
+ # One timestep of Bloom execution. This MUST be invoked from the EventMachine
685
+ # thread; it is not intended to be called directly by client code.
686
+ def tick_internal
593
687
  begin
688
+ starttime = Time.now if options[:metrics]
689
+ if options[:metrics] and not @endtime.nil?
690
+ @metrics[:betweentickstats] ||= initialize_stats
691
+ @metrics[:betweentickstats] = running_stats(@metrics[:betweentickstats], starttime - @endtime)
692
+ end
594
693
  @inside_tick = true
595
694
  @tables.each_value do |t|
596
695
  t.tick
@@ -606,10 +705,17 @@ module Bud
606
705
  do_flush
607
706
  invoke_callbacks
608
707
  @budtime += 1
708
+ @inbound.clear
609
709
  ensure
610
710
  @inside_tick = false
611
711
  @tick_clock_time = nil
612
712
  end
713
+
714
+ if options[:metrics]
715
+ @endtime = Time.now
716
+ @metrics[:tickstats] ||= initialize_stats
717
+ @metrics[:tickstats] = running_stats(@metrics[:tickstats], @endtime - starttime)
718
+ end
613
719
  end
614
720
 
615
721
  # Returns the wallclock time associated with the current Bud tick. That is,
@@ -629,9 +735,11 @@ module Bud
629
735
  def builtin_state
630
736
  loopback :localtick, [:col1]
631
737
  @stdio = terminal :stdio
632
- @periodics = table :periodics_tbl, [:pername] => [:ident, :period]
738
+ readonly :signals, [:key]
739
+ scratch :halt, [:key]
740
+ @periodics = table :periodics_tbl, [:pername] => [:period]
633
741
 
634
- # for BUD reflection
742
+ # for Bud reflection
635
743
  table :t_rules, [:rule_id] => [:lhs, :op, :src, :orig_src]
636
744
  table :t_depends, [:rule_id, :lhs, :op, :body] => [:nm]
637
745
  table :t_depends_tc, [:head, :body, :via, :neg, :temporal]
@@ -643,14 +751,13 @@ module Bud
643
751
  table :t_table_schema, [:tab_name, :col_name, :ord, :loc]
644
752
  end
645
753
 
646
- # Handle any inbound tuples off the wire and then clear. Received messages are
647
- # placed directly into the storage of the appropriate local channel.
754
+ # Handle any inbound tuples off the wire. Received messages are placed
755
+ # directly into the storage of the appropriate local channel. The inbound
756
+ # queue is cleared at the end of the tick.
648
757
  def receive_inbound
649
758
  @inbound.each do |msg|
650
- # puts "dequeueing tuple #{msg[1].inspect} into #{msg[0]} @ #{ip_port}"
651
759
  tables[msg[0].to_sym] << msg[1]
652
760
  end
653
- @inbound = []
654
761
  end
655
762
 
656
763
  # "Flush" any tuples that need to be flushed. This does two things:
@@ -696,13 +803,20 @@ module Bud
696
803
  # stratum_first_iter field here distinguishes these cases.
697
804
  @stratum_first_iter = true
698
805
  begin
806
+ @this_stratum = strat_num
699
807
  strat.each_with_index do |r,i|
808
+ @this_rule = i
700
809
  fixpoint = false
810
+ rule_src = @rule_orig_src[strat_num][i] unless @rule_orig_src[strat_num].nil?
701
811
  begin
812
+ if options[:metrics]
813
+ metrics[:rules] ||= {}
814
+ metrics[:rules][{:strat_num => strat_num, :rule_num => i, :rule_src => rule_src}] ||= 0
815
+ metrics[:rules][{:strat_num => strat_num, :rule_num => i, :rule_src => rule_src}] += 1
816
+ end
702
817
  r.call
703
818
  rescue Exception => e
704
819
  # Don't report source text for certain rules (old-style rule blocks)
705
- rule_src = @rule_orig_src[strat_num][i] unless @rule_orig_src[strat_num].nil?
706
820
  src_msg = ""
707
821
  unless rule_src == ""
708
822
  src_msg = "\nRule: #{rule_src}"
@@ -741,10 +855,10 @@ module Bud
741
855
  Time.new.to_i.to_s << rand.to_s
742
856
  end
743
857
 
744
- def set_periodic_timer(name, id, period)
858
+ def make_periodic_timer(name, period)
745
859
  EventMachine::PeriodicTimer.new(period) do
746
- @tables[name].add_periodic_tuple(id)
747
- tick
860
+ @inbound << [name, [gen_id, Time.now]]
861
+ tick_internal if @running_async
748
862
  end
749
863
  end
750
864
 
@@ -791,11 +905,15 @@ module Bud
791
905
  $signal_lock.synchronize {
792
906
  # If we setup signal handlers and then fork a new process, we want to
793
907
  # reinitialize the signal handler in the child process.
794
- unless b.options[:no_signal_handlers] or $signal_handler_setup
908
+ unless b.options[:signal_handling] == :none or $signal_handler_setup
795
909
  EventMachine::PeriodicTimer.new(SIGNAL_CHECK_PERIOD) do
796
910
  if $got_shutdown_signal
797
- Bud.shutdown_all_instances
798
- Bud.stop_em_loop
911
+ if b.options[:signal_handling] == :bloom
912
+ Bud.tick_all_instances
913
+ else
914
+ Bud.shutdown_all_instances
915
+ Bud.stop_em_loop
916
+ end
799
917
  $got_shutdown_signal = false
800
918
  end
801
919
  end
@@ -803,6 +921,7 @@ module Bud
803
921
  ["INT", "TERM"].each do |signal|
804
922
  Signal.trap(signal) {
805
923
  $got_shutdown_signal = true
924
+ b.sync_do{b.signals.pending_merge([[signal]])}
806
925
  }
807
926
  end
808
927
  $setup_signal_handler_pid = true
@@ -814,12 +933,21 @@ module Bud
814
933
  }
815
934
  end
816
935
 
936
+ def self.tick_all_instances
937
+ instances = nil
938
+ $signal_lock.synchronize {
939
+ instances = $bud_instances.clone
940
+ }
941
+
942
+ instances.each_value {|b| b.sync_do }
943
+ end
944
+
817
945
  def self.shutdown_all_instances(do_shutdown_cb=true)
818
946
  instances = nil
819
947
  $signal_lock.synchronize {
820
948
  instances = $bud_instances.clone
821
949
  }
822
950
 
823
- instances.each_value {|b| b.stop_bg(false, do_shutdown_cb) }
951
+ instances.each_value {|b| b.stop(false, do_shutdown_cb) }
824
952
  end
825
953
  end