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.
- checksums.yaml +7 -0
- data/LICENSE +20 -0
- data/README.md +182 -0
- data/lib/oflow/actor.rb +76 -0
- data/lib/oflow/actors/errorhandler.rb +32 -0
- data/lib/oflow/actors/ignore.rb +22 -0
- data/lib/oflow/actors/log.rb +175 -0
- data/lib/oflow/actors/relay.rb +23 -0
- data/lib/oflow/actors/timer.rb +126 -0
- data/lib/oflow/actors.rb +11 -0
- data/lib/oflow/box.rb +195 -0
- data/lib/oflow/env.rb +52 -0
- data/lib/oflow/errors.rb +74 -0
- data/lib/oflow/flow.rb +75 -0
- data/lib/oflow/haserrorhandler.rb +48 -0
- data/lib/oflow/haslinks.rb +64 -0
- data/lib/oflow/haslog.rb +72 -0
- data/lib/oflow/hasname.rb +31 -0
- data/lib/oflow/hastasks.rb +209 -0
- data/lib/oflow/inspector.rb +501 -0
- data/lib/oflow/link.rb +43 -0
- data/lib/oflow/pattern.rb +8 -0
- data/lib/oflow/stamp.rb +39 -0
- data/lib/oflow/task.rb +415 -0
- data/lib/oflow/test/action.rb +21 -0
- data/lib/oflow/test/actorwrap.rb +62 -0
- data/lib/oflow/test.rb +8 -0
- data/lib/oflow/tracker.rb +109 -0
- data/lib/oflow/version.rb +5 -0
- data/lib/oflow.rb +23 -0
- data/test/actors/log_test.rb +57 -0
- data/test/actors/timer_test.rb +56 -0
- data/test/actorwrap_test.rb +48 -0
- data/test/all_tests.rb +27 -0
- data/test/box_test.rb +127 -0
- data/test/collector.rb +23 -0
- data/test/flow_basic_test.rb +93 -0
- data/test/flow_cfg_error_test.rb +94 -0
- data/test/flow_log_test.rb +87 -0
- data/test/flow_nest_test.rb +215 -0
- data/test/flow_rescue_test.rb +133 -0
- data/test/flow_tracker_test.rb +82 -0
- data/test/stutter.rb +21 -0
- data/test/task_test.rb +98 -0
- data/test/tracker_test.rb +59 -0
- 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
|