bud 0.0.4 → 0.0.5

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