oflow 0.3.0

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.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +20 -0
  3. data/README.md +182 -0
  4. data/lib/oflow/actor.rb +76 -0
  5. data/lib/oflow/actors/errorhandler.rb +32 -0
  6. data/lib/oflow/actors/ignore.rb +22 -0
  7. data/lib/oflow/actors/log.rb +175 -0
  8. data/lib/oflow/actors/relay.rb +23 -0
  9. data/lib/oflow/actors/timer.rb +126 -0
  10. data/lib/oflow/actors.rb +11 -0
  11. data/lib/oflow/box.rb +195 -0
  12. data/lib/oflow/env.rb +52 -0
  13. data/lib/oflow/errors.rb +74 -0
  14. data/lib/oflow/flow.rb +75 -0
  15. data/lib/oflow/haserrorhandler.rb +48 -0
  16. data/lib/oflow/haslinks.rb +64 -0
  17. data/lib/oflow/haslog.rb +72 -0
  18. data/lib/oflow/hasname.rb +31 -0
  19. data/lib/oflow/hastasks.rb +209 -0
  20. data/lib/oflow/inspector.rb +501 -0
  21. data/lib/oflow/link.rb +43 -0
  22. data/lib/oflow/pattern.rb +8 -0
  23. data/lib/oflow/stamp.rb +39 -0
  24. data/lib/oflow/task.rb +415 -0
  25. data/lib/oflow/test/action.rb +21 -0
  26. data/lib/oflow/test/actorwrap.rb +62 -0
  27. data/lib/oflow/test.rb +8 -0
  28. data/lib/oflow/tracker.rb +109 -0
  29. data/lib/oflow/version.rb +5 -0
  30. data/lib/oflow.rb +23 -0
  31. data/test/actors/log_test.rb +57 -0
  32. data/test/actors/timer_test.rb +56 -0
  33. data/test/actorwrap_test.rb +48 -0
  34. data/test/all_tests.rb +27 -0
  35. data/test/box_test.rb +127 -0
  36. data/test/collector.rb +23 -0
  37. data/test/flow_basic_test.rb +93 -0
  38. data/test/flow_cfg_error_test.rb +94 -0
  39. data/test/flow_log_test.rb +87 -0
  40. data/test/flow_nest_test.rb +215 -0
  41. data/test/flow_rescue_test.rb +133 -0
  42. data/test/flow_tracker_test.rb +82 -0
  43. data/test/stutter.rb +21 -0
  44. data/test/task_test.rb +98 -0
  45. data/test/tracker_test.rb +59 -0
  46. metadata +93 -0
@@ -0,0 +1,209 @@
1
+
2
+ module OFlow
3
+
4
+ # Provides the ability to have Tasks and Flows.
5
+ module HasTasks
6
+
7
+ # Initializes the tasks attribute.
8
+ def init_tasks()
9
+ @tasks = {}
10
+ end
11
+
12
+ # Creates a Flow and yield to a block with the newly create Flow. Used to
13
+ # contruct Flows.
14
+ # @param name [Symbol|String] base name for the Flow
15
+ # @param options [Hash] optional parameters
16
+ # @param block [Proc] block to yield to with the new Flow instance
17
+ # @return [Flow] new Flow
18
+ def flow(name, options={}, &block)
19
+ f = Flow.new(self, name, options)
20
+ @tasks[f.name] = f
21
+ yield(f) if block_given?
22
+ f.resolve_all_links()
23
+ # Wait to validate until at the top so up-links don't fail validation.
24
+ f.validate() if Env == self
25
+ f
26
+ end
27
+
28
+ # Creates a Task and yield to a block with the newly create Task. Used to
29
+ # configure Tasks.
30
+ # @param name [Symbol|String] base name for the Task
31
+ # @param actor_class [Class] Class to create an Actor instance of
32
+ # @param options [Hash] optional parameters
33
+ # @param block [Proc] block to yield to with the new Task instance
34
+ # @return [Task] new Task
35
+ def task(name, actor_class, options={}, &block)
36
+ t = Task.new(self, name, actor_class, options)
37
+ @tasks[t.name] = t
38
+ yield(t) if block_given?
39
+ t
40
+ end
41
+
42
+ # Validates the container by verifying all links on a task have been set to
43
+ # a valid destination and that destination has been resolved.
44
+ # @raise [ValidateError] if there is an error in validation
45
+ def validate()
46
+ # collects errors and raises all errors at once if there are any
47
+ errors = _validation_errors()
48
+ raise ValidateError.new(errors) unless errors.empty?
49
+ end
50
+
51
+ # Returns an Array of validation errors.
52
+ def _validation_errors()
53
+ errors = []
54
+ @tasks.each_value { |t| errors += t._validation_errors() }
55
+ errors
56
+ end
57
+
58
+ # Resolves all the Links on all the Tasks and Flows being managed as well as
59
+ # any Links in the instance itself.
60
+ def resolve_all_links()
61
+ @links.each_value { |lnk|
62
+ set_link_target(lnk) if lnk.target.nil?
63
+ }
64
+ @tasks.each_value { |t|
65
+ t.resolve_all_links()
66
+ }
67
+ end
68
+
69
+ # Iterates over each Task and yields to the provided block with each Task.
70
+ # @param blk [Proc] Proc to call on each iteration
71
+ def each_task(&blk)
72
+ @tasks.each { |name,task| blk.yield(task) }
73
+ end
74
+
75
+ # Performs a recursive walk over all Tasks and yields to the provided block
76
+ # for each. Flows are followed recusively.
77
+ # @param tasks_only [Boolean] indicates on Tasks and not Flows are yielded to
78
+ # @param blk [Proc] Proc to call on each iteration
79
+ def walk_tasks(tasks_only=true, &blk)
80
+ @tasks.each_value do |t|
81
+ if t.is_a?(Task)
82
+ blk.yield(t)
83
+ else
84
+ blk.yield(t) unless tasks_only
85
+ t.walk_tasks(tasks_only, &blk)
86
+ end
87
+ end
88
+ end
89
+
90
+ # Locates and return a Task with the specified name.
91
+ # @param name [String] name of the Task
92
+ # @return [Task|nil] the Task with the name specified or nil
93
+ def find_task(name)
94
+ name = name.to_sym unless name.nil?
95
+ return self if :flow == name
96
+ @tasks[name]
97
+ end
98
+
99
+ # Locates and return a Task with the specified full name.
100
+ # @param name [String] full name of the Task
101
+ # @return [Task|nil] the Task with the name specified or nil
102
+ def locate(name)
103
+ name = name[1..-1] if name.start_with?(':')
104
+ name = name[0..-2] if name.end_with?(':')
105
+ path = name.split(':')
106
+ _locate(path)
107
+ end
108
+
109
+ def _locate(path)
110
+ t = @tasks[path[0].to_sym]
111
+ return t if t.nil? || 1 == path.size
112
+ t._locate(path[1..-1])
113
+ end
114
+
115
+ # Returns the number of active Tasks.
116
+ def task_count()
117
+ @tasks.size
118
+ end
119
+
120
+ # Returns the sum of all the requests in all the Tasks's queues.
121
+ # @return [Fixnum] total number of items waiting to be processed
122
+ def queue_count()
123
+ cnt = 0
124
+ @tasks.each_value { |task| cnt += task.queue_count() }
125
+ cnt
126
+ end
127
+
128
+ # Returns true of one or more Tasks is either processing a request or has a
129
+ # request waiting to be processed on it's input queue.
130
+ # @return [true|false] the busy state across all Tasks
131
+ def busy?
132
+ @tasks.each_value { |task| return true if task.busy? }
133
+ false
134
+ end
135
+
136
+ # Calls the stop() method on all Tasks.
137
+ def stop()
138
+ @tasks.each_value { |task| task.stop() }
139
+ end
140
+
141
+ # Calls the step() method one Task that is stopped and has an item in the
142
+ # queue. The Tasks with the highest backed_up() value is selected.
143
+ def step()
144
+ max = 0.0
145
+ best = nil
146
+ walk_tasks() do |t|
147
+ if Task::STOPPED == t.state
148
+ bu = t.backed_up()
149
+ if max < bu
150
+ best = t
151
+ max = bu
152
+ end
153
+ end
154
+ end
155
+ best.step() unless best.nil?
156
+ best
157
+ end
158
+
159
+ # Calls the start() method on all Tasks.
160
+ def start()
161
+ @tasks.each_value { |task| task.start() }
162
+ end
163
+
164
+ # Wakes up all the Tasks in the Flow.
165
+ def wakeup()
166
+ @tasks.each_value { |t| t.wakeup() }
167
+ end
168
+
169
+ # Wakes up all the Tasks in the Flow and waits for the system to become idle
170
+ # before returning.
171
+ def flush()
172
+ wakeup()
173
+ @tasks.each_value { |t| t.flush() }
174
+ while busy?
175
+ sleep(0.2)
176
+ end
177
+ end
178
+
179
+ # Sets the state of all Tasks recursively. This should not be called
180
+ # directly.
181
+ def state=(s)
182
+ @tasks.each_value do |task|
183
+ task.state = s
184
+ end
185
+ end
186
+
187
+ # Shuts down all Tasks.
188
+ # @param flush_first [Boolean] flag indicating shutdown should occur after the system becomes idle
189
+ def shutdown(flush_first=false)
190
+ # block all tasks first so threads can empty queues
191
+ @tasks.each_value do |task|
192
+ task.state = Task::BLOCKED
193
+ end
194
+ # shutdown and wait for queues to empty if necessary
195
+ @tasks.each_value do |task|
196
+ task.shutdown(flush_first)
197
+ end
198
+ @tasks = {}
199
+ end
200
+
201
+ # Clears out all Tasks and Flows and resets the object back to a empty state.
202
+ def clear()
203
+ shutdown()
204
+ @tasks = {}
205
+ _clear()
206
+ end
207
+
208
+ end # HasTasks
209
+ end # OFlow
@@ -0,0 +1,501 @@
1
+
2
+ require 'oterm'
3
+
4
+ module OFlow
5
+
6
+ class Inspector < ::OTerm::Executor
7
+ attr_reader :running
8
+
9
+ def initialize(port=6060)
10
+ super()
11
+ @running = true
12
+
13
+ register('busy', self, :busy, 'returns the busy state of the system.', nil)
14
+ register('list', self, :list, '[-r] [<id>] lists Flows and Tasks.',
15
+ %|Shows a list of Flow and Task full names that fall under the id if one is
16
+ provided, otherwise the top leve is assumed. If the -r option is specified then
17
+ the names of all the Flows and Tasks under the named item are displayed.|)
18
+ register('show', self, :show, '[-v] <id> displays a description of a Flow or Task.',
19
+ %|Shows a description of the identified Flow or Task. If the -v option is
20
+ specified then a detailed description of the Tasks is displayed which included
21
+ the number of requests queued and the status of the Task. More -v arguments
22
+ will give increasing more detail.|)
23
+ register('start', self, :start, '[<task id>] start or restart a Task.', nil)
24
+ register('step', self, :step, '[<task id>] step once.',
25
+ %|Step once for the Task specfified or once for some Task that is
26
+ waiting if no Task is identified.|)
27
+ register('stop', self, :stop, '[<task id>] stops a Task.', nil)
28
+ register('verbosity', self, :verbosity, '[<level>] show or set the verbosity or log level.', nil)
29
+ register('watch', self, :watch, '[<task id> displays status of Tasks.',
30
+ %|Displays the Task name, activity indicator, queued count, and number of
31
+ requests processed. If the terminal supports real time updates the displays
32
+ stays active until the X character is pressed. While running options are
33
+ available for sorting on name, activity, or queue size.|)
34
+
35
+ # register('debug', self, :debug, 'toggles debug mode.', nil)
36
+
37
+ @server = ::OTerm::Server.new(self, port, false)
38
+ end
39
+
40
+ def join()
41
+ @server.join()
42
+ end
43
+
44
+ def shutdown(listener, args)
45
+ super
46
+ @running = false
47
+ Env.shutdown()
48
+ end
49
+
50
+ def greeting()
51
+ "Welcome to the Operations Flow Inspector."
52
+ end
53
+
54
+ def debug(listener, args)
55
+ @server.debug = !@server.debug
56
+ end
57
+
58
+ def busy(listener, args)
59
+ if Env.busy?()
60
+ listener.out.pl("One or more Tasks is busy.")
61
+ else
62
+ listener.out.pl("All Tasks are idle.")
63
+ end
64
+ end
65
+
66
+ def flows(listener, args)
67
+ Env.each_task() do |t|
68
+ listener.out.pl(t.full_name)
69
+ end
70
+ end
71
+
72
+ def list(listener, args)
73
+ if nil == args
74
+ Env.each_task() do |task|
75
+ listener.out.pl(task.full_name)
76
+ end
77
+ return
78
+ end
79
+ recurse, id, ok = _parse_opt_id_args(args, 'r', listener)
80
+ return unless ok
81
+ if id.nil?
82
+ flow = Env
83
+ else
84
+ flow = Env.locate(id)
85
+ end
86
+ if flow.nil?
87
+ listener.out.pl("--- No Flow or Task found for #{id}")
88
+ return
89
+ end
90
+ _walk(flow, recurse, listener) do |task|
91
+ listener.out.pl(task.full_name) unless Env == task
92
+ end
93
+ end
94
+
95
+ def show(listener, args)
96
+ if nil == args
97
+ listener.out.pl("--- No Flow or Task specified")
98
+ return
99
+ end
100
+ detail, id, ok = _parse_opt_id_args(args, 'v', listener)
101
+ return unless ok
102
+
103
+ task = Env.locate(id)
104
+ if task.nil?
105
+ listener.out.pl("--- Failed to find '#{id}'")
106
+ return
107
+ end
108
+ listener.out.pl(task.describe(detail))
109
+ end
110
+
111
+ def start(listener, args)
112
+ if nil == args || 0 == args.size()
113
+ Env.start()
114
+ listener.out.pl("All Tasks restarted")
115
+ else
116
+ args.strip!
117
+ task = Env.locate(args)
118
+ if task.nil?
119
+ listener.out.pl("--- Failed to find '#{args}'")
120
+ else
121
+ task.start()
122
+ listener.out.pl("#{task.full_name} restarted")
123
+ end
124
+ end
125
+ end
126
+
127
+ def step(listener, args)
128
+ lg = Env.log()
129
+ stop_after = false
130
+ if !lg.nil? && Task::STOPPED == lg.state
131
+ lg.start()
132
+ stop_after = true
133
+ end
134
+
135
+ if nil == args || 0 == args.size()
136
+ task = Env
137
+ else
138
+ args.strip!
139
+ task = Env.locate(args)
140
+ end
141
+ if task.nil?
142
+ listener.out.pl("--- Failed to find '#{args}'")
143
+ else
144
+ task = task.step()
145
+ if task.nil?
146
+ listener.out.pl("--- No tasks in '#{args}' are stopped or have have queued requests")
147
+ else
148
+ listener.out.pl("#{task.full_name} stepped")
149
+ end
150
+ end
151
+ lg.stop() if stop_after
152
+ end
153
+
154
+ def stop(listener, args)
155
+ if nil == args || 0 == args.size()
156
+ Env.stop()
157
+ listener.out.pl("All Tasks stopped(paused)")
158
+ else
159
+ args.strip!
160
+ task = Env.locate(args)
161
+ if task.nil?
162
+ listener.out.pl("--- Failed to find '#{args}'")
163
+ else
164
+ task.stop()
165
+ listener.out.pl("#{task.full_name} stopped(paused)")
166
+ end
167
+ end
168
+ end
169
+
170
+ def verbosity(listener, args)
171
+ lg = Env.log
172
+ if lg.nil?
173
+ listener.out.pl("--- No logger")
174
+ return
175
+ end
176
+ lga = lg.actor
177
+ if nil != args && 0 < args.size()
178
+ args.strip!
179
+ lg.receive(:severity, Box.new(args))
180
+ listener.out.pl("verbosity change pending")
181
+ elsif lga.respond_to?(:severity)
182
+ listener.out.pl("verbosity: #{lga.severity()}")
183
+ else
184
+ listener.out.pl("--- Logger does support requests for verbosity level")
185
+ end
186
+ end
187
+
188
+ def watch(listener, args)
189
+ tasks = []
190
+ if args.nil? || 0 == args.size()
191
+ Env.walk_tasks() { |t| tasks << t }
192
+ else
193
+ args.strip!
194
+ task = Env.locate(args)
195
+ if task.nil?
196
+ listener.out.pl("--- Failed to find '#{args}'")
197
+ return
198
+ elsif task.kind_of?(HasTasks)
199
+ task.walk_tasks() { |t| tasks << t }
200
+ else
201
+ tasks << task
202
+ end
203
+ end
204
+ if listener.out.is_vt100?
205
+ _dynamic_watch(listener, tasks)
206
+ else
207
+ max_len = 10
208
+ tasks.each do |t|
209
+ len = t.full_name.size
210
+ max_len = len if max_len < len
211
+ end
212
+ listener.out.pl(" %#{max_len}s %-11s %5s %9s" % ['Task Name', 'Q-cnt/max', 'busy?', 'processed'])
213
+ tasks.each do |t|
214
+ listener.out.pl(" %#{max_len}s %5d/%-5d %5s %9d" % [t.full_name, t.queue_count(), t.max_queue_count().to_i, t.busy?(), t.proc_count()])
215
+ end
216
+ end
217
+ end
218
+
219
+ # sort by values
220
+ BY_NAME = 'name'
221
+ BY_ACTIVITY = 'activity'
222
+ BY_QUEUE = 'queued'
223
+ BY_COUNT = 'count'
224
+ BY_STATE = 'state'
225
+
226
+ def _dynamic_watch(listener, tasks)
227
+ o = listener.out
228
+ sort_by = BY_NAME
229
+ rev = false
230
+ delay = 0.4
231
+ tasks.map! { |t| TaskStat.new(t) }
232
+ lines = tasks.size + 3
233
+ h, w = o.screen_size()
234
+ lines = h - 1 if lines > h - 1
235
+ o.clear_screen()
236
+ done = false
237
+ until done
238
+ tasks.each { |ts| ts.refresh() }
239
+ max = 6
240
+ max_n = 1
241
+ tasks.each do |ts|
242
+ max = ts.name.size if max < ts.name.size
243
+ max_n = ts.count.size if max_n < ts.count.size
244
+ end
245
+ # 5 for space between after, 3 for state, max_n for number
246
+ max_q = w - max - 8 - max_n
247
+
248
+ case sort_by
249
+ when BY_NAME
250
+ if rev
251
+ tasks.sort! { |a,b| b.name <=> a.name }
252
+ else
253
+ tasks.sort! { |a,b| a.name <=> b.name }
254
+ end
255
+ when BY_ACTIVITY
256
+ if rev
257
+ tasks.sort! { |a,b| a.activity <=> b.activity }
258
+ else
259
+ tasks.sort! { |a,b| b.activity <=> a.activity }
260
+ end
261
+ when BY_QUEUE
262
+ if rev
263
+ tasks.sort! { |a,b| a.queued <=> b.queued }
264
+ else
265
+ tasks.sort! { |a,b| b.queued <=> a.queued }
266
+ end
267
+ when BY_COUNT
268
+ if rev
269
+ tasks.sort! { |a,b| a.proc_cnt <=> b.proc_cnt }
270
+ else
271
+ tasks.sort! { |a,b| b.proc_cnt <=> a.proc_cnt }
272
+ end
273
+ when BY_STATE
274
+ if rev
275
+ tasks.sort! { |a,b| a.state <=> b.state }
276
+ else
277
+ tasks.sort! { |a,b| b.state <=> a.state }
278
+ end
279
+ end
280
+ o.set_cursor(1, 1)
281
+ o.bold()
282
+ o.underline()
283
+ o.p("%1$*2$s ? %3$*4$s @ Queued %5$*6$s" % ['#', -max_n, 'Task', -max, ' ', max_q])
284
+ o.attrs_off()
285
+ i = 2
286
+ tasks[0..lines].each do |ts|
287
+ o.set_cursor(i, 1)
288
+ o.p("%1$*2$s %5$c %3$*4$s " % [ts.count, max_n, ts.name, -max, ts.state])
289
+ o.set_cursor(i, max + max_n + 5)
290
+ case ts.activity
291
+ when 0
292
+ o.p(' ')
293
+ when 1
294
+ o.p('.')
295
+ when 2, 3
296
+ o.p('o')
297
+ else
298
+ o.p('O')
299
+ end
300
+ o.p(' ')
301
+ qlen = ts.queued
302
+ qlen = max_q if max_q < qlen
303
+ if 0 < qlen
304
+ o.reverse()
305
+ o.p("%1$*2$d" % [ts.queued, -qlen])
306
+ o.attrs_off()
307
+ end
308
+ o.clear_to_end()
309
+ i += 1
310
+ end
311
+ o.bold()
312
+ o.set_cursor(i, 1)
313
+ if rev
314
+ o.p("E) exit R) ")
315
+ o.reverse()
316
+ o.p("reverse")
317
+ o.attrs_off()
318
+ o.bold()
319
+ o.p(" +) faster -) slower [%0.1f]" % [delay])
320
+ else
321
+ o.p("E) exit R) reverse +) faster -) slower [%0.1f]" % [delay])
322
+ end
323
+ i += 1
324
+ o.set_cursor(i, 1)
325
+ o.p('sort by')
326
+ { '#' => BY_COUNT, '?' => BY_STATE, 'N' => BY_NAME, 'A' => BY_ACTIVITY, 'Q' => BY_QUEUE }.each do |c,by|
327
+ if by == sort_by
328
+ o.p(" #{c}) ")
329
+ o.reverse()
330
+ o.p(by)
331
+ o.attrs_off()
332
+ o.bold()
333
+ else
334
+ o.p(" #{c}) #{by}")
335
+ end
336
+ end
337
+ o.attrs_off()
338
+
339
+ c = o.recv_wait(1, delay, /./)
340
+ unless c.nil?
341
+ case c[0]
342
+ when 'e', 'E'
343
+ done = true
344
+ when 'n', 'N'
345
+ sort_by = BY_NAME
346
+ rev = false
347
+ when 'a', 'A'
348
+ sort_by = BY_ACTIVITY
349
+ rev = false
350
+ when 'q', 'Q'
351
+ sort_by = BY_QUEUE
352
+ rev = false
353
+ when '#', 'c', 'C'
354
+ sort_by = BY_COUNT
355
+ rev = false
356
+ when '?', 's', 'S'
357
+ sort_by = BY_STATE
358
+ rev = false
359
+ when 'r', 'R'
360
+ rev = !rev
361
+ when '+'
362
+ delay /= 2.0 unless delay <= 0.1
363
+ when '-'
364
+ delay *= 2.0 unless 3.0 <= delay
365
+ end
366
+ end
367
+ end
368
+ o.pl()
369
+ end
370
+
371
+ def tab(cmd, listener)
372
+ start = cmd.index(' ')
373
+ if start.nil?
374
+ super
375
+ return
376
+ end
377
+ op = cmd[0...start]
378
+ start = cmd.rindex(' ')
379
+ pre = cmd[0...start]
380
+ last = cmd[start + 1..-1]
381
+
382
+ return if '-' == last[0]
383
+
384
+ # Tab completion is different depending on the command.
385
+ names = []
386
+ case op.downcase()
387
+ when 'verbosity'
388
+ names = ['fatal', 'error', 'warn', 'info', 'debug'].select { |s| s.start_with?(last.downcase()) }
389
+ else # expect id or options
390
+ with_colon = ':' == last[0]
391
+ Env.walk_tasks(false) do |t|
392
+ fn = t.full_name
393
+ fn = fn[1..-1] unless with_colon
394
+ names << fn if fn.start_with?(last)
395
+ end
396
+ end
397
+
398
+ return if 0 == names.size
399
+ if 1 == names.size
400
+ listener.move_col(1000)
401
+ listener.insert(names[0][last.size..-1])
402
+ listener.out.prompt()
403
+ listener.out.p(listener.buf)
404
+ else
405
+ listener.out.pl()
406
+ names.each do |name|
407
+ listener.out.pl("#{pre} #{name}")
408
+ end
409
+ best = best_completion(last, names)
410
+ if best == last
411
+ listener.update_cmd(0)
412
+ else
413
+ listener.move_col(1000)
414
+ listener.insert(best[last.size..-1])
415
+ listener.out.prompt()
416
+ listener.out.p(listener.buf)
417
+ end
418
+ end
419
+ end
420
+
421
+ def _parse_opt_id_args(args, opt, listener)
422
+ opt_cnt = 0
423
+ id = nil
424
+ args.strip!
425
+ args.split(' ').each do |a|
426
+ if '-' == a[0]
427
+ a[1..-1].each_char do |c|
428
+ if c == opt
429
+ opt_cnt += 1
430
+ else
431
+ listener.out.pl("--- -#{c} is not a valid option")
432
+ return [0, nil, false]
433
+ end
434
+ end
435
+ elsif !id.nil?
436
+ listener.out.pl("--- Multiple Ids specified")
437
+ return [0, nil, false]
438
+ else
439
+ id = a
440
+ end
441
+ end
442
+ [opt_cnt, id, true]
443
+ end
444
+
445
+ def _walk(flow, recurse, listener, &block)
446
+ if flow.respond_to?(:each_task)
447
+ if recurse
448
+ block.yield(flow)
449
+ flow.each_task() do |task|
450
+ _walk(task, true, listener, &block)
451
+ end
452
+ else
453
+ flow.each_task() do |task|
454
+ block.yield(task)
455
+ end
456
+ end
457
+ else
458
+ block.yield(flow)
459
+ end
460
+ end
461
+
462
+ class TaskStat
463
+ attr_reader :task
464
+ attr_reader :queued
465
+ attr_reader :activity
466
+ attr_reader :name
467
+ attr_reader :proc_cnt
468
+ attr_reader :count
469
+ attr_reader :state
470
+
471
+ STATE_MAP = {
472
+ Task::STARTING => '^',
473
+ Task::STOPPED => '*',
474
+ Task::RUNNING => ' ',
475
+ Task::CLOSING => 'X',
476
+ Task::BLOCKED => '-',
477
+ Task::STEP => 's',
478
+ }
479
+ def initialize(t)
480
+ @task = t
481
+ @proc_cnt = t.proc_count()
482
+ @activity = 0
483
+ @queued = t.queue_count()
484
+ @name = t.full_name
485
+ @count = @proc_cnt.to_s
486
+ @state = STATE_MAP.fetch(t.state, '?')
487
+ end
488
+
489
+ def refresh()
490
+ cnt = @task.proc_count()
491
+ @activity = cnt - @proc_cnt
492
+ @proc_cnt = cnt
493
+ @queued = @task.queue_count()
494
+ @count = cnt.to_s
495
+ @state = STATE_MAP.fetch(@task.state, '?')
496
+ end
497
+
498
+ end # TaskStat
499
+
500
+ end # Inspector
501
+ end # OFlow