oflow 0.3.0

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