rbbt-util 5.1.0 → 5.2.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 (47) hide show
  1. data/LICENSE +1 -1
  2. data/README.rdoc +2 -2
  3. data/bin/rbbt +45 -0
  4. data/bin/rbbt_dangling_locks.rb +9 -0
  5. data/bin/rbbt_monitor.rb +12 -10
  6. data/bin/run_workflow.rb +80 -18
  7. data/lib/rbbt.rb +1 -1
  8. data/lib/rbbt/annotations.rb +1 -19
  9. data/lib/rbbt/annotations/annotated_array.rb +23 -0
  10. data/lib/rbbt/fix_width_table.rb +2 -2
  11. data/lib/rbbt/persist.rb +13 -5
  12. data/lib/rbbt/persist/tsv.rb +2 -0
  13. data/lib/rbbt/resource.rb +4 -4
  14. data/lib/rbbt/resource/path.rb +35 -10
  15. data/lib/rbbt/resource/util.rb +54 -47
  16. data/lib/rbbt/tsv.rb +17 -15
  17. data/lib/rbbt/tsv/accessor.rb +35 -37
  18. data/lib/rbbt/tsv/excel.rb +3 -1
  19. data/lib/rbbt/tsv/manipulate.rb +27 -4
  20. data/lib/rbbt/tsv/parser.rb +13 -7
  21. data/lib/rbbt/util/R.rb +11 -1
  22. data/lib/rbbt/util/misc.rb +182 -26
  23. data/lib/rbbt/util/named_array.rb +14 -7
  24. data/lib/rbbt/util/open.rb +2 -1
  25. data/lib/rbbt/util/tmpfile.rb +16 -1
  26. data/lib/rbbt/workflow.rb +63 -101
  27. data/lib/rbbt/workflow/accessor.rb +19 -9
  28. data/lib/rbbt/workflow/annotate.rb +33 -64
  29. data/lib/rbbt/workflow/definition.rb +71 -0
  30. data/lib/rbbt/workflow/soap.rb +15 -5
  31. data/lib/rbbt/workflow/step.rb +57 -8
  32. data/lib/rbbt/workflow/usage.rb +72 -0
  33. data/share/lib/R/util.R +12 -0
  34. data/share/rbbt_commands/conf/web_user/add +26 -0
  35. data/share/rbbt_commands/conf/web_user/list +9 -0
  36. data/share/rbbt_commands/conf/web_user/remove +18 -0
  37. data/share/rbbt_commands/workflow/remote/add +11 -0
  38. data/share/rbbt_commands/workflow/remote/list +9 -0
  39. data/share/rbbt_commands/workflow/remote/remove +9 -0
  40. data/share/rbbt_commands/workflow/task +181 -0
  41. data/test/rbbt/test_resource.rb +2 -1
  42. data/test/rbbt/test_workflow.rb +13 -0
  43. data/test/rbbt/tsv/test_manipulate.rb +18 -0
  44. data/test/rbbt/util/test_misc.rb +19 -39
  45. data/test/rbbt/util/test_tmpfile.rb +8 -0
  46. data/test/rbbt/workflow/test_soap.rb +2 -0
  47. metadata +31 -2
@@ -0,0 +1,71 @@
1
+ require 'rbbt-util'
2
+ require 'rbbt/workflow/annotate'
3
+
4
+ module Workflow
5
+ include AnnotatedModule
6
+
7
+
8
+ AnnotatedModule.add_consummable_annotation(self,
9
+ :result_description => "",
10
+ :result_type => nil,
11
+ :dependencies => [])
12
+ def helper(name, &block)
13
+ helpers[name] = block
14
+ end
15
+
16
+ def desc(description)
17
+ @description = description
18
+ end
19
+
20
+ def returns(description)
21
+ @result_description = description
22
+ end
23
+
24
+ def dep(*dependency_list, &block)
25
+ dependency_list << block if block_given?
26
+ dependencies.concat dependency_list
27
+ end
28
+
29
+ def task(name, &block)
30
+ if Hash === name
31
+ type = name.first.last
32
+ name = name.first.first
33
+ else
34
+ result_type = consume_result_type || :marshal
35
+ end
36
+
37
+ name = name.to_sym
38
+
39
+ block = self.method(name) unless block_given?
40
+
41
+ task_info = {
42
+ :name => name,
43
+ :inputs => consume_inputs,
44
+ :description => consume_description,
45
+ :input_types => consume_input_types,
46
+ :result_type => (Array === type ? type.to_sym : type),
47
+ :input_defaults => consume_input_defaults,
48
+ :input_descriptions => consume_input_descriptions,
49
+ :input_options => consume_input_options
50
+ }
51
+
52
+ task = Task.setup(task_info, &block)
53
+
54
+ last_task = task
55
+
56
+ tasks[name] = task
57
+ task_dependencies[name] = consume_dependencies
58
+ end
59
+
60
+ def export_exec(*names)
61
+ exec_exports.concat names
62
+ end
63
+
64
+ def export_asynchronous(*names)
65
+ asynchronous_exports.concat names
66
+ end
67
+
68
+ def export_synchronous(*names)
69
+ synchronous_exports.concat names
70
+ end
71
+ end
@@ -15,7 +15,7 @@ class WorkflowSOAP < SimpleWS
15
15
  end
16
16
 
17
17
  def initialize(workflow, *args)
18
- super(workflow.to_s,*args)
18
+ super(workflow.to_s, *args)
19
19
  @workflow = workflow
20
20
  @workflow.synchronous_exports.each do |name| synchronous name end
21
21
  @workflow.asynchronous_exports.each do |name| asynchronous name end
@@ -38,6 +38,12 @@ class WorkflowSOAP < SimpleWS
38
38
  job(jobid).info.to_yaml
39
39
  end
40
40
 
41
+ desc "Job management: Load job result as string "
42
+ param_desc :jobid => "Job identifier", :return => "String containing the result of the job"
43
+ serve :load_string, %w(jobid), :jobid => :string, :return => :string do |jobid|
44
+ Open.read(job(jobid).path)
45
+ end
46
+
41
47
  desc "Job management: Abort the job"
42
48
  param_desc :jobid => "Job identifier"
43
49
  serve :abort, %w(jobid), :jobid => :string, :return => false do |jobid|
@@ -56,15 +62,19 @@ class WorkflowSOAP < SimpleWS
56
62
  job(jobid).status.to_sym == :error
57
63
  end
58
64
 
59
- desc "Job management: Load job result as string "
60
- param_desc :jobid => "Job identifier", :return => "String containing the result of the job"
61
- serve :load_string, %w(jobid), :jobid => :string, :return => :string do |jobid|
62
- job(jobid).load.to_s
65
+ desc "Job management: Check if the job has finished with error. The last message is the error message"
66
+ param_desc :jobid => "Job identifier", :return => "True if the job has status 'error'"
67
+ serve :clean, %w(jobid), :jobid => :string, :return => nil do |jobid|
68
+ job(jobid).clean
69
+ nil
63
70
  end
71
+
72
+
64
73
  end
65
74
 
66
75
  def synchronous(*tasknames)
67
76
  tasknames.each do |name|
77
+ name = name.to_sym
68
78
  task = @workflow.tasks[name]
69
79
  desc @workflow.task_description[name] if @workflow.task_description.include? name
70
80
 
@@ -10,7 +10,7 @@ class Step
10
10
  class Aborted < Exception; end
11
11
 
12
12
  def initialize(path, task = nil, inputs = nil, dependencies = nil, bindings = nil)
13
- @path = path
13
+ @path = Path.setup(path)
14
14
  @task = task
15
15
  @bindings = bindings
16
16
  @dependencies = case
@@ -84,15 +84,17 @@ class Step
84
84
  end
85
85
 
86
86
  def run(no_load = false)
87
- result = Persist.persist "Job", @task.result_type, :file => @path, :check => rec_dependencies.collect{|dependency| dependency.path}.uniq, :no_load => no_load do
87
+ result = Persist.persist "Job", @task.result_type, :file => @path, :check => rec_dependencies.collect{|dependency| dependency.path }.uniq, :no_load => no_load do
88
88
  if Step === Step.log_relay_step and not self == Step.log_relay_step
89
89
  relay_log(Step.log_relay_step) unless self.respond_to? :relay_step and self.relay_step
90
90
  end
91
91
 
92
92
  FileUtils.rm info_file if File.exists? info_file
93
93
 
94
- set_info :dependencies, @dependencies.collect{|dep| [dep.task.name, dep.name]}
95
- @dependencies.each{|dependency|
94
+ set_info :pid, Process.pid
95
+
96
+ set_info :dependencies, dependencies.collect{|dep| [dep.task.name, dep.name]}
97
+ dependencies.each{|dependency|
96
98
  begin
97
99
  dependency.relay_log self
98
100
  dependency.run true
@@ -106,7 +108,7 @@ class Step
106
108
  end
107
109
  }
108
110
 
109
- log(:started, "Starting task #{task.name || ""}")
111
+ log(:started, "Starting task #{task.name || ""} [#{Process.pid}]")
110
112
 
111
113
  set_info :started, Time.now
112
114
 
@@ -116,6 +118,20 @@ class Step
116
118
  exec
117
119
  rescue Step::Aborted
118
120
  log(:error, "Aborted")
121
+
122
+ children_pids = info[:children_pids]
123
+ if children_pids and children_pids.any?
124
+ Log.medium("Killing children: #{ children_pids * ", " }")
125
+ children_pids.each do |pid|
126
+ Log.medium("Killing child #{ pid }")
127
+ begin
128
+ Process.kill "INT", pid
129
+ rescue Exception
130
+ Log.medium("Exception killing child #{ pid }: #{$!.message}")
131
+ end
132
+ end
133
+ end
134
+
119
135
  raise $!
120
136
  rescue Exception
121
137
  backtrace = $!.backtrace
@@ -148,11 +164,25 @@ class Step
148
164
  FileUtils.mkdir_p File.dirname(path) unless File.exists? File.dirname(path)
149
165
  begin
150
166
  run
151
- rescue
167
+ children_pids = info[:children_pids]
168
+ if children_pids
169
+ children_pids.each do |pid|
170
+ if Misc.pid_exists? pid
171
+ begin
172
+ Process.waitpid pid
173
+ rescue Errno::ECHILD
174
+ Log.error "Waiting on #{ pid } failed: #{$!.message}"
175
+ end
176
+ end
177
+ end
178
+ end
179
+ rescue Exception
180
+ Log.debug("Exception caught on forked process: #{$!.message}")
152
181
  exit -1
153
182
  end
183
+ set_info :pid, nil
184
+ exit 0
154
185
  end
155
- set_info :pid, @pid
156
186
  Process.detach(@pid)
157
187
  self
158
188
  end
@@ -164,12 +194,30 @@ class Step
164
194
  false
165
195
  else
166
196
  Log.medium "Aborting #{path}: #{ @pid }"
167
- Process.kill("INT", @pid)
197
+ begin
198
+ Process.kill("INT", @pid)
199
+ Process.waitpid @pid
200
+ rescue Exception
201
+ Log.debug("Aborted job #{@pid} was not killed: #{$!.message}")
202
+ end
168
203
  log(:aborted, "Job aborted by user")
169
204
  true
170
205
  end
171
206
  end
172
207
 
208
+ def child(&block)
209
+ child_pid = Process.fork &block
210
+ children_pids = info[:children_pids]
211
+ if children_pids.nil?
212
+ children_pids = [child_pid]
213
+ else
214
+ children_pids << child_pid
215
+ end
216
+ #Process.detach(child_pid)
217
+ set_info :children_pids, children_pids
218
+ child_pid
219
+ end
220
+
173
221
  def load
174
222
  raise "Can not load: Step is waiting for proces #{@pid} to finish" if not done?
175
223
  result = Persist.persist "Job", @task.result_type, :file => @path, :check => rec_dependencies.collect{|dependency| dependency.path} do
@@ -182,6 +230,7 @@ class Step
182
230
  if File.exists?(path) or File.exists?(info_file)
183
231
  begin
184
232
  FileUtils.rm info_file if File.exists? info_file
233
+ FileUtils.rm info_file + '.lock' if File.exists? info_file + '.lock'
185
234
  FileUtils.rm path if File.exists? path
186
235
  FileUtils.rm path + '.lock' if File.exists? path + '.lock'
187
236
  FileUtils.rm_rf files_dir if File.exists? files_dir
@@ -0,0 +1,72 @@
1
+
2
+ module Task
3
+ def doc(deps = nil)
4
+
5
+ puts "## #{ name }:"
6
+ puts "\n" << description if description and not description.empty?
7
+ puts
8
+
9
+ inputs.each do |name|
10
+ short = name.to_s.chars.first
11
+
12
+ description = input_descriptions[name]
13
+ default = input_defaults[name]
14
+ type = input_types[name]
15
+
16
+ puts " * -#{short}, --#{name}=<#{ type }>#{default ? " (default: #{default})" : ""}:"
17
+ puts " " << description if description and not description.empty?
18
+ puts
19
+ end
20
+
21
+ if deps and deps.any?
22
+ puts
23
+ puts "From dependencies:"
24
+ puts
25
+ deps.each do |dep|
26
+ puts " #{dep.name}:"
27
+ puts
28
+ dep.inputs.each do |name|
29
+ short = name.to_s.chars.first
30
+
31
+ description = dep.input_descriptions[name]
32
+ default = dep.input_defaults[name]
33
+ type = dep.input_types[name]
34
+
35
+ puts " * -#{short}, --#{name}=<#{ type }>#{default ? " (default: #{default})" : ""}:"
36
+ puts " " << description if description and not description.empty?
37
+ puts
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ module Workflow
45
+ def doc(task = nil)
46
+
47
+ if task.nil?
48
+ puts self.to_s
49
+ puts "=" * self.to_s.length
50
+ puts
51
+
52
+ puts "## TASKS"
53
+ puts
54
+ tasks.each do |name,task|
55
+ puts " * #{ name }:"
56
+ puts " " << task.description if task.description and not task.description.empty?
57
+ puts
58
+ end
59
+ else
60
+
61
+ if Task === task
62
+ task_name = task.name
63
+ else
64
+ task_name = task
65
+ task = self.tasks[task_name]
66
+ end
67
+ dependencies = self.rec_dependencies(task_name).collect{|dep_name| self.tasks[dep_name.to_sym]}
68
+
69
+ task.doc(dependencies)
70
+ end
71
+ end
72
+ end
data/share/lib/R/util.R CHANGED
@@ -22,6 +22,10 @@ rbbt.ruby <- function(code, load = TRUE, flat = FALSE, type = 'tsv', ...){
22
22
  data = scan(file, ...)
23
23
  return(data);
24
24
  }
25
+
26
+ if(type == 'string'){
27
+ return(file);
28
+ }
25
29
 
26
30
  return(NULL);
27
31
  }else{
@@ -268,4 +272,12 @@ rbbt.plot.matrix <- function(x, ...){
268
272
  layout(1);
269
273
  }
270
274
 
275
+ rbbt.log <- function(m){
276
+ cat(paste(m,"\n"), file = stderr())
277
+ }
278
+
279
+ rbbt.ddd <- function(o){
280
+ cat(toString(o), file = stderr())
281
+ cat("\n", file = stderr())
282
+ }
271
283
 
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rbbt'
4
+ require 'highline/import'
5
+
6
+ if Rbbt.etc.web_users.exists?
7
+ web_users = Rbbt.etc.web_users.yaml
8
+ else
9
+ web_users = {}
10
+ end
11
+
12
+ user = ARGV.shift
13
+
14
+ raise "No user provided" if user.nil?
15
+
16
+ password = ask("Enter your password: ") { |q| q.echo = "x" }
17
+
18
+ raise "No password provided" if password.nil?
19
+
20
+ password_check = ask("Re-enter your password: ") { |q| q.echo = "x" }
21
+
22
+ raise "Passwords don't match" if password != password_check
23
+
24
+ web_users[user] = password
25
+
26
+ Rbbt.etc.web_users.write(web_users.to_yaml)
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rbbt'
4
+
5
+ if Rbbt.etc.web_users.exists?
6
+ puts Rbbt.etc.web_users.yaml.keys
7
+ else
8
+ "Path #{Rbbt.etc.web_users} not found"
9
+ end
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rbbt'
4
+ require 'highline/import'
5
+
6
+ if Rbbt.etc.web_users.exists?
7
+ web_users = Rbbt.etc.web_users.yaml
8
+ else
9
+ web_users = {}
10
+ end
11
+
12
+ user = ARGV.shift
13
+
14
+ raise "No user provided" if user.nil?
15
+
16
+ web_users.delete user
17
+
18
+ Rbbt.etc.web_users.write(web_users.to_yaml)
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rbbt'
4
+
5
+ url, workflow = ARGV
6
+ url = File.join(url, workflow) unless url =~ /\/#{workflow}$/
7
+ config_file = Rbbt.etc.remote_workflows
8
+ remote_workflows = config_file.exists? ? config_file.yaml : {}
9
+ remote_workflows[workflow] = url
10
+ Open.write(config_file.find(:user), remote_workflows.to_yaml)
11
+
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rbbt'
4
+
5
+ config_file = Rbbt.etc.remote_workflows
6
+ config_file.yaml.sort_by{|k,v| k}.each do |workflow, server|
7
+ puts [workflow, server] * "\t"
8
+ end
9
+
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rbbt'
4
+
5
+ workflow = ARGV.shift
6
+ config_file = Rbbt.etc.remote_workflows
7
+ remote_workflows = config_file.exists? ? config_file.yaml : {}
8
+ remote_workflows.delete workflow
9
+ Open.write(config_file.find(:user), remote_workflows.to_yaml)
@@ -0,0 +1,181 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rbbt/util/simpleopt'
4
+ require 'rbbt/workflow'
5
+ require 'rbbt/workflow/usage'
6
+
7
+ def usage(workflow = nil, task = nil)
8
+ if workflow.nil?
9
+ puts "No workflow specified"
10
+ exit -1
11
+ end
12
+
13
+ if task.nil?
14
+ workflow.doc
15
+ else
16
+ workflow.doc(task)
17
+ end
18
+ exit 0
19
+ end
20
+
21
+ def SOPT_options(workflow, task)
22
+ sopt_options = []
23
+ workflow.rec_inputs(task.name).each do |name|
24
+ short = name.to_s.chars.first
25
+ boolean = workflow.rec_input_types(task.name)[name].to_sym == :boolean
26
+
27
+ sopt_options << "-#{short}--#{name}#{boolean ? '' : '*'}"
28
+ end
29
+
30
+ sopt_options * ":"
31
+ end
32
+
33
+ def fix_options(workflow, task, job_options)
34
+ option_types = workflow.rec_input_types(task.name)
35
+
36
+ job_options_cleaned = {}
37
+
38
+ job_options.each do |name, value|
39
+ value = case option_types[name].to_sym
40
+ when :float
41
+ value.to_f
42
+ when :integer
43
+ value.to_i
44
+ when :string, :text
45
+ case
46
+ when value == '-'
47
+ STDIN.read
48
+ when (String === value and File.exists?(value))
49
+ Open.read(value)
50
+ else
51
+ value
52
+ end
53
+ when :array
54
+ if Array === value
55
+ value
56
+ else
57
+ case
58
+ when value == '-'
59
+ STDIN.read
60
+ when (String === value and File.exists?(value))
61
+ Open.read(value)
62
+ else
63
+ value
64
+ end.split(/[,|\s]/)
65
+ end
66
+ when :tsv
67
+ if TSV === value
68
+ value
69
+ else
70
+ begin
71
+ if value == '-'
72
+ TSV.open(STDIN).to_s :sort
73
+ else
74
+ TSV.new(value).to_s :sort
75
+ end
76
+ rescue
77
+ value
78
+ end
79
+ end
80
+ else
81
+ value
82
+ end
83
+ job_options_cleaned[name] = value
84
+ end
85
+
86
+ job_options_cleaned
87
+ end
88
+
89
+ options = SOPT.get "-t--task*:--profile:-l--log*:-h--help:-n--name*:-cl--clean:-rcl-recursive_clean:-pn--printname"
90
+
91
+ workflow = ARGV.shift
92
+ usage if workflow.nil?
93
+
94
+ task = ARGV.shift
95
+
96
+
97
+ # Set log, fork, clean, recursive_clean and help
98
+ Log.severity = options[:log].to_i if options.include? :log
99
+ help = !!options.delete(:help)
100
+ do_fork = !!options.delete(:fork)
101
+ clean = !!options.delete(:clean)
102
+ recursive_clean = !!options.delete(:recursive_clean)
103
+
104
+ # Get workflow
105
+
106
+ if Rbbt.etc.remote_workflows.exists?
107
+ remote_workflows = Rbbt.etc.remote_workflows.yaml
108
+ else
109
+ remote_workflows = {}
110
+ end
111
+
112
+ if remote_workflows.include? workflow
113
+ require 'rbbt/rest/client'
114
+ workflow = WorkflowRESTClient.new remote_workflows[workflow], workflow
115
+ else
116
+ Workflow.require_workflow workflow
117
+ workflow = Workflow.workflows.last
118
+ end
119
+
120
+ # Set task
121
+ namespace = nil, nil
122
+
123
+ case
124
+ when task.nil?
125
+ usage workflow
126
+ when (task =~ /\./)
127
+ namespace, task = options.delete(:task).split('.')
128
+ namespace = Misc.string2const(namespace)
129
+ else
130
+ task_name = task.to_sym
131
+ task = workflow.tasks[task_name]
132
+ raise "Task not found: #{ task_name }" if task.nil?
133
+ end
134
+
135
+ usage workflow, task if help
136
+
137
+ name = options.delete(:name) || "Default"
138
+
139
+ # get job args
140
+ sopt_option_string = SOPT_options(workflow, task)
141
+ job_options = SOPT.get sopt_option_string
142
+ job_options = fix_options(workflow,task, job_options)
143
+
144
+ #- get job
145
+ job = workflow.job(task.name, name, job_options)
146
+
147
+ # clean job
148
+ job.clean if clean
149
+ job.recursive_clean if recursive_clean
150
+
151
+ # run
152
+ if do_fork
153
+ job.fork
154
+ while not job.done?
155
+ Log.debug "#{job.step}: #{job.messages.last}"
156
+ sleep 2
157
+ end
158
+ raise job.messages.last if job.error?
159
+ res = job.load
160
+ else
161
+ res = job.run
162
+ end
163
+
164
+ if options.delete(:printname)
165
+ puts job.name
166
+ exit
167
+ else
168
+ Log.low "Job name: #{job.name}"
169
+ end
170
+
171
+ case
172
+ when Array === res
173
+ puts res * "\n"
174
+ when TSV === res
175
+ puts res
176
+ when Hash === res
177
+ puts res.to_yaml
178
+ else
179
+ puts res
180
+ end
181
+