rbbt-util 5.31.13 → 5.32.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 42c6838da8c912f444ab59a74bb1cf96fdbe8d20aea2be712a035679cdec41e7
4
- data.tar.gz: 77af7a4a5ec48d57637dc66967dca071bfb47563b520e80ba27eb9d6d7573407
3
+ metadata.gz: 612e1472bae3839b68f143d639025a9bb19294525ecc0e95df99702883af31d3
4
+ data.tar.gz: bea8c5ec09525363447c5bdc140f899f8a22ccb311d225da061b01d6f68bcf10
5
5
  SHA512:
6
- metadata.gz: 39328d07cbc07c1be6beaa5149f94a94cac807fba36029415fef57644dc7bc63b90ee70f2e814271ae55033a1321bbb03dbb7d7531f7f033ec0df4401b1a716c
7
- data.tar.gz: 9c0cbd95d049b5524072e31bf8f0b47884839bbf792ea8b0d6cd437a0f6aef5f6fa7efa5ef1288e77f1efe0bee50b360966e01c12284ea706d3cf876d0432512
6
+ metadata.gz: 0aef55d55b97f40011aeaa3f09450dd623ad6d682b4b3ef5dc3f2df603efeeede2e50b3be2f4f586e502adb6a4a0fd898113d3c5c892212170edb4cde7b7f6a7
7
+ data.tar.gz: 81261ef8e8da6ada5c1d8e672fdbd31ff6dc67a7a08edb4b6a3dcee10cfc6ed3f7abacf0c3628d604839e2743de043793c5169a147bd9b528c2afd614dca62b9
@@ -6,6 +6,36 @@ module HPC
6
6
  end
7
7
  end
8
8
 
9
+ def self.batch_system(batch_system = 'auto')
10
+ case batch_system.to_s.downcase
11
+ when 'slurm'
12
+ HPC::SLURM
13
+ when 'lsf'
14
+ HPC::LSF
15
+ when 'auto'
16
+ case $previous_commands.last
17
+ when 'slurm'
18
+ HPC::SLURM
19
+ when 'lsf'
20
+ HPC::LSF
21
+ else
22
+ case Rbbt::Config.get(:batch_system, :batch, :batch_system, :hpc, :HPC, :BATCH).to_s.downcase
23
+ when 'slurm'
24
+ HPC::SLURM
25
+ when 'lsf'
26
+ HPC::LSF
27
+ else
28
+ case ENV["BATCH_SYSTEM"].to_s.downcase
29
+ when 'slurm'
30
+ HPC::SLURM
31
+ when 'lsf'
32
+ HPC::LSF
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+
9
39
  module TemplateGeneration
10
40
  def exec_cmd(job, options = {})
11
41
  env_cmd = Misc.process_options options, :env_cmd
@@ -30,12 +60,12 @@ module HPC
30
60
  -B "/.singularity_ruby_inline":"#{contain}/.singularity_ruby_inline":rw
31
61
  -B "#{options[:batch_dir]}" \
32
62
  -B /scratch/tmp \
33
- #{ group != user_group ? "-B /gpfs/projects/#{user_group}" : "" } \
63
+ #{ group != user_group ? "-B /gpfs/projects/#{user_group}" : "" } \
34
64
  -B #{scratch_group_dir} \
35
65
  -B #{projects_group_dir} \
36
66
  -B /apps/ \
37
67
  -B ~/git:"#{contain}/git":ro \
38
- #{Open.exists?('~/.rbbt/software/opt/')? '-B ~/.rbbt/software/opt/:"/opt/":ro' : '' } \
68
+ #{Open.exists?('~/.rbbt/software/opt/')? '-B ~/.rbbt/software/opt/:"/opt/":ro' : '' } \
39
69
  -B ~/.rbbt:"#{contain}/home/":ro)
40
70
  end
41
71
 
@@ -74,7 +104,7 @@ module HPC
74
104
  [name, dep.path] * "="
75
105
  end * ","
76
106
 
77
- options[:override_deps] = override_deps
107
+ options[:override_deps] = override_deps unless override_deps.empty?
78
108
  end
79
109
 
80
110
  # Save inputs into inputs_dir
@@ -216,6 +246,7 @@ EOF
216
246
  :fexit => File.join(batch_dir, 'exit.status'),
217
247
  :fsync => File.join(batch_dir, 'sync.log'),
218
248
  :fsexit => File.join(batch_dir, 'sync.status'),
249
+ :fenv => File.join(batch_dir, 'env.vars'),
219
250
  :fcmd => File.join(batch_dir, 'command.batch')
220
251
 
221
252
  batch_options
@@ -441,6 +472,7 @@ exit $exit_status
441
472
 
442
473
  # #{Log.color :green, "1. Prepare environment"}
443
474
  #{prepare_environment}
475
+ env > #{batch_options[:fenv]}
444
476
 
445
477
  # #{Log.color :green, "2. Execute"}
446
478
  #{execute}
data/lib/rbbt/hpc/lsf.rb CHANGED
@@ -7,10 +7,13 @@ module HPC
7
7
 
8
8
  def self.batch_system_variables
9
9
  <<-EOF
10
- [[ -z $LSB_MAX_MEM_RUSAGE ]] || MAX_MEMORY=$LSB_MAX_MEM_RUSAGE
11
- [[ -z $MAX_MEMORY ]] && let MAX_MEMORY="$(grep MemTotal /proc/meminfo|grep -o "[[:digit:]]*") / 1024"
12
- BATCH_JOB_ID=$LSF_JOBID
13
- BATCH_SYSTEM=LSF
10
+ let TOTAL_PROCESORS="$(cat /proc/cpuinfo|grep ^processor |wc -l)"
11
+ let MAX_MEMORY_DEFAULT="$(grep MemTotal /proc/meminfo|grep -o "[[:digit:]]*") / ( (1024 * $TOTAL_PROCESORS) / $LSB_MAX_NUM_PROCESSORS )"
12
+ [ ! -z $LSB_MAX_MEM_RUSAGE ] && let MAX_MEMORY="$LSB_MAX_MEM_RUSAGE" || MAX_MEMORY="$MAX_MEMORY_DEFAULT"
13
+ export MAX_MEMORY_DEFAULT
14
+ export MAX_MEMORY
15
+ export BATCH_JOB_ID=$LSF_JOBID
16
+ export BATCH_SYSTEM=LSF
14
17
  EOF
15
18
  end
16
19
 
@@ -145,6 +145,8 @@ module HPC
145
145
  job_rules.delete :workflow
146
146
 
147
147
 
148
+ option_config_keys = options[:config_keys]
149
+
148
150
  job_options = IndiferentHash.setup(options.merge(job_rules).merge(:batch_dependencies => dep_ids))
149
151
  job_options.delete :orchestration_rules
150
152
 
@@ -154,6 +156,11 @@ module HPC
154
156
  job_options[:config_keys] = job_options[:config_keys] ? config_keys + "," + job_options[:config_keys] : config_keys
155
157
  end
156
158
 
159
+ if option_config_keys
160
+ option_config_keys = option_config_keys.gsub(/,\s+/,',')
161
+ job_options[:config_keys] = job_options[:config_keys] ? job_options[:config_keys] + "," + option_config_keys : option_config_keys
162
+ end
163
+
157
164
  if options[:piggyback]
158
165
  manifest = options[:piggyback].uniq
159
166
  manifest += [job]
@@ -165,7 +172,7 @@ module HPC
165
172
  new_config_keys = self.job_rules(rules, job)[:config_keys]
166
173
  if new_config_keys
167
174
  new_config_keys = new_config_keys.gsub(/,\s+/,',')
168
- job_options[:config_keys] = job_options[:config_keys] ? new_config_keys + "," + job_options[:config_keys] : new_config_keys
175
+ job_options[:config_keys] = job_options[:config_keys] ? job_options[:config_keys] + "," + new_config_keys : new_config_keys
169
176
  end
170
177
 
171
178
  job_options.delete :piggyback
@@ -8,9 +8,13 @@ module HPC
8
8
 
9
9
  def self.batch_system_variables
10
10
  <<-EOF
11
- let "MAX_MEMORY=$SLURM_MEM_PER_CPU * $SLURM_CPUS_PER_TASK" || let MAX_MEMORY="$(grep MemTotal /proc/meminfo|grep -o "[[:digit:]]*") / 1024"
12
- BATCH_JOB_ID=$SLURM_JOB_ID
13
- BATCH_SYSTEM=SLURM
11
+ let TOTAL_PROCESORS="$(cat /proc/cpuinfo|grep ^processor |wc -l)"
12
+ let MAX_MEMORY_DEFAULT="$(grep MemTotal /proc/meminfo|grep -o "[[:digit:]]*") / ( (1024 * $TOTAL_PROCESORS) / $SLURM_CPUS_PER_TASK )"
13
+ [ ! -z $SLURM_MEM_PER_CPU ] && let MAX_MEMORY="$SLURM_MEM_PER_CPU * $SLURM_CPUS_PER_TASK" || MAX_MEMORY="$MAX_MEMORY_DEFAULT"
14
+ export MAX_MEMORY_DEFAULT
15
+ export MAX_MEMORY
16
+ export BATCH_JOB_ID=$SLURM_JOB_ID
17
+ export BATCH_SYSTEM=SLURM
14
18
  EOF
15
19
  end
16
20
 
@@ -132,7 +132,7 @@ module Persist
132
132
 
133
133
  def read_lock
134
134
  read if closed?
135
- if read?
135
+ if read? || write?
136
136
  return yield
137
137
  end
138
138
 
data/lib/rbbt/tsv/csv.rb CHANGED
@@ -8,6 +8,7 @@ module TSV
8
8
  noheaders = ! headers
9
9
 
10
10
  type = options.delete :type
11
+ cast = options.delete :cast
11
12
  merge = options.delete :merge
12
13
  key_field = options.delete :key_field
13
14
  fields = options.delete :fields
@@ -46,6 +47,10 @@ module TSV
46
47
  else
47
48
  key, *values = row
48
49
  end
50
+
51
+ if cast
52
+ values = values.collect{|v| v.send cast }
53
+ end
49
54
 
50
55
  case type
51
56
  when :double, :flat
@@ -40,7 +40,7 @@ module TSV
40
40
  # Process fields line
41
41
 
42
42
  preamble << line if line
43
- while line and (TrueClass === @header_hash or (String === @header_hash and Misc.fixutf8(line) =~ /^#{@header_hash}/ ))
43
+ while line && (TrueClass === @header_hash || (String === @header_hash && Misc.fixutf8(line) =~ /^#{@header_hash}/ ))
44
44
  @fields = line.split(@sep, -1)
45
45
  @key_field = @fields.shift
46
46
  @key_field = @key_field[(0 + header_hash.length)..-1] if String === @header_hash
@@ -49,7 +49,7 @@ module TSV
49
49
  line = (@header_hash != "" ? stream.gets : nil)
50
50
  line = Misc.fixutf8 line.chomp if line
51
51
  preamble << line if line
52
- @header_hash = false if TrueClass === @header_hash
52
+ @header_hash = false if TrueClass === @header_hash || @header_hash == ""
53
53
  end
54
54
 
55
55
  @preamble = preamble[0..-3] * "\n"
@@ -56,6 +56,7 @@ module TSV
56
56
  preambles = []
57
57
 
58
58
  streams = streams.collect do |stream|
59
+
59
60
  parser = TSV::Parser.new stream, options.dup
60
61
  sfields = parser.fields
61
62
 
@@ -43,7 +43,7 @@ module Misc
43
43
  File.mkfifo path
44
44
  yield path
45
45
  ensure
46
- FileUtils.rm path if erase
46
+ FileUtils.rm path if erase && File.exists?(path)
47
47
  end
48
48
  end
49
49
 
@@ -11,7 +11,7 @@ module Open
11
11
  class OpenGzipError < StandardError; end
12
12
 
13
13
  REMOTE_CACHEDIR = File.join(ENV["HOME"], "/tmp/open_cache")
14
- FileUtils.mkdir_p REMOTE_CACHEDIR unless File.exist? REMOTE_CACHEDIR
14
+ #FileUtils.mkdir_p REMOTE_CACHEDIR unless File.exist? REMOTE_CACHEDIR
15
15
 
16
16
  GREP_CMD = begin
17
17
  if ENV["GREP_CMD"]
data/lib/rbbt/workflow.rb CHANGED
@@ -187,17 +187,21 @@ module Workflow
187
187
  clean_name = wf_name.sub(/::.*/,'')
188
188
  Log.info{"Looking for '#{wf_name}' in '#{clean_name}'"}
189
189
  require_workflow clean_name
190
- return Misc.string2const Misc.camel_case(wf_name)
190
+ workflow = Misc.string2const Misc.camel_case(wf_name)
191
+ workflow.load_documentation
192
+ return workflow
191
193
  end
192
194
 
193
195
  Log.high{"Loading workflow #{wf_name}"}
194
196
  require_local_workflow(wf_name) or
195
197
  (Workflow.autoinstall and `rbbt workflow install #{Misc.snake_case(wf_name)} || rbbt workflow install #{wf_name}` and require_local_workflow(wf_name)) or raise("Workflow not found or could not be loaded: #{ wf_name }")
196
- begin
197
- Misc.string2const Misc.camel_case(wf_name)
198
- rescue
199
- Workflow.workflows.last || true
200
- end
198
+ workflow = begin
199
+ Misc.string2const Misc.camel_case(wf_name)
200
+ rescue
201
+ Workflow.workflows.last || true
202
+ end
203
+ workflow.load_documentation
204
+ workflow
201
205
  end
202
206
 
203
207
  attr_accessor :description
@@ -74,6 +74,7 @@ module Workflow
74
74
  def dep_task(name, workflow, oname, *rest, &block)
75
75
  dep(workflow, oname, *rest, &block)
76
76
  extension workflow.tasks[oname].extension if workflow.tasks.include?(oname) unless @extension
77
+ returns workflow.tasks[oname].result_description if workflow.tasks.include?(oname) unless @result_description
77
78
  task name do
78
79
  raise RbbtException, "dependency not found in dep_task" if dependencies.empty?
79
80
  dep = dependencies.last
@@ -45,10 +45,25 @@ module Workflow
45
45
  end
46
46
 
47
47
  def load_documentation
48
- @documentation = Workflow.parse_workflow_doc documentation_markdown
48
+ return if @documentation
49
+ @documentation ||= Workflow.parse_workflow_doc documentation_markdown
49
50
  @documentation[:tasks].each do |task, description|
50
- raise "Documentation for #{ task }, but not a #{ self.to_s } task" unless tasks.include? task.to_sym
51
- tasks[task.to_sym].description = description
51
+ if task.include? "#"
52
+ workflow, task = task.split("#")
53
+ workflow = begin
54
+ Kernel.const_get workflow
55
+ rescue
56
+ next
57
+ end
58
+ else
59
+ workflow = self
60
+ end
61
+
62
+ if workflow.tasks.include? task.to_sym
63
+ workflow.tasks[task.to_sym].description = description
64
+ else
65
+ Log.low "Documentation for #{ task }, but not a #{ workflow.to_s } task"
66
+ end
52
67
  end
53
68
  end
54
69
 
@@ -458,6 +458,10 @@ class Step
458
458
  end
459
459
 
460
460
  def clean
461
+ if ! Open.exists?(info_file)
462
+ Log.high "Refusing to clean step with no .info file: #{path}"
463
+ return self
464
+ end
461
465
  status = []
462
466
  status << "dirty" if done? && dirty?
463
467
  status << "not running" if ! done? && ! running?
@@ -97,6 +97,7 @@ class Step
97
97
  Open.ln_s(value.path, path)
98
98
  when type.to_s == "file"
99
99
  if String === value && File.exists?(value)
100
+ value = File.expand_path(value)
100
101
  Open.ln_s(value, path)
101
102
  else
102
103
  value = value.collect{|v| v = "#{v}" if Path === v; v }if Array === value
@@ -1,6 +1,6 @@
1
1
  class Step
2
2
 
3
- MAIN_RSYNC_ARGS="-avztAXHP"
3
+ MAIN_RSYNC_ARGS="-avztAXHP --copy-links"
4
4
 
5
5
  def self.link_job(path, target_dir, task = nil, workflow = nil)
6
6
  Path.setup(target_dir)
@@ -1,12 +1,8 @@
1
1
  require 'rbbt/util/R'
2
2
 
3
3
  module Workflow
4
- def self.trace(seed_jobs, options = {})
5
-
6
- jobs = []
7
- seed_jobs.each{|j| jobs << j; jobs += j.rec_dependencies}
8
-
9
- data = TSV.setup({}, "Job~Workflow,Task,Start,End#:type=:list")
4
+ def self.trace_job_times(jobs, fix_gap = false)
5
+ data = TSV.setup({}, "Job~Code,Workflow,Task,Start,End#:type=:list")
10
6
  min_start = nil
11
7
  max_done = nil
12
8
  jobs.each do |job|
@@ -14,10 +10,10 @@ module Workflow
14
10
  started = job.info[:started]
15
11
  ddone = job.info[:done]
16
12
 
17
- code = [job.workflow, job.task_name].compact.collect{|s| s.to_s} * "."
18
- code = code + '.' + job.name
13
+ code = [job.workflow, job.task_name].compact.collect{|s| s.to_s} * " · "
14
+ code = job.name + " - " + code
19
15
 
20
- data[code] = [job.workflow.to_s, job.task_name, started, ddone]
16
+ data[job.path] = [code,job.workflow.to_s, job.task_name, started, ddone]
21
17
  if min_start.nil?
22
18
  min_start = started
23
19
  else
@@ -39,7 +35,7 @@ module Workflow
39
35
  value["End"] - min_start
40
36
  end
41
37
 
42
- if options[:fix_gap]
38
+ if fix_gap
43
39
  ranges = []
44
40
  data.through do |k,values|
45
41
  start, eend = values.values_at "Start.second", "End.second"
@@ -67,115 +63,161 @@ module Workflow
67
63
  gap = Misc.sum(gaps.select{|pos,size| pos < values["Start.second"]}.collect{|pos,size| size})
68
64
  value - gap
69
65
  end
66
+
67
+ total_gaps = Misc.sum(gaps.collect{|k,v| v})
68
+ Log.info "Total gaps: #{total_gaps} seconds"
70
69
  end
71
70
 
71
+ start = data.column("Start.second").values.flatten.collect{|v| v.to_f}.min
72
+ eend = data.column("End.second").values.flatten.collect{|v| v.to_f}.max
73
+ total = eend - start unless eend.nil? || start.nil?
74
+ Log.info "Total time elapsed: #{total} seconds" if total
75
+
76
+ data
77
+ end
78
+
79
+ def self.plot_trace_job_times(data, plot, width=800, height=800)
80
+ data.R <<-EOF, [:svg]
81
+ rbbt.require('tidyverse')
82
+ rbbt.require('ggplot2')
83
+
84
+ names(data) <- make.names(names(data))
85
+ data$id = data$Code
86
+ data$content = data$Task
87
+ data$start = data$Start
88
+ data$end = data$End
89
+ data$Project = data$Workflow
90
+
91
+ tasks = data
92
+
93
+ #theme_gantt <- function(base_size=11, base_family="Source Sans Pro Light") {
94
+ theme_gantt <- function(base_size=11, base_family="Sans Serif") {
95
+ ret <- theme_bw(base_size, base_family) %+replace%
96
+ theme(panel.background = element_rect(fill="#ffffff", colour=NA),
97
+ axis.title.x=element_text(vjust=-0.2), axis.title.y=element_text(vjust=1.5),
98
+ title=element_text(vjust=1.2, family="Source Sans Pro Semibold"),
99
+ panel.border = element_blank(), axis.line=element_blank(),
100
+ panel.grid.minor=element_blank(),
101
+ panel.grid.major.y = element_blank(),
102
+ panel.grid.major.x = element_line(size=0.5, colour="grey80"),
103
+ axis.ticks=element_blank(),
104
+ legend.position="bottom",
105
+ axis.title=element_text(size=rel(1.2), family="Source Sans Pro Semibold"),
106
+ strip.text=element_text(size=rel(1.5), family="Source Sans Pro Semibold"),
107
+ strip.background=element_rect(fill="#ffffff", colour=NA),
108
+ panel.spacing.y=unit(1.5, "lines"),
109
+ legend.key = element_blank())
110
+
111
+ ret
112
+ }
113
+
114
+ tasks.long <- tasks %>%
115
+ gather(date.type, task.date, -c(Code,Project, Task, id, Start.second, End.second)) %>%
116
+ arrange(date.type, task.date) %>%
117
+ mutate(id = factor(id, levels=rev(unique(id)), ordered=TRUE))
118
+
119
+ x.breaks <- seq(length(tasks$Task) + 0.5 - 3, 0, by=-3)
120
+
121
+ timeline <- ggplot(tasks.long, aes(y=id, yend=id, x=Start.second, xend=End.second, colour=Task)) +
122
+ geom_segment() +
123
+ geom_vline(xintercept=x.breaks, colour="grey80", linetype="dotted") +
124
+ guides(colour=guide_legend(title=NULL)) +
125
+ labs(x=NULL, y=NULL) +
126
+ theme_gantt() + theme(axis.text.x=element_text(angle=45, hjust=1))
127
+
128
+ rbbt.png_plot('#{plot}', 'plot(timeline)', width=#{width}, height=#{height}, pointsize=6)
129
+ EOF
130
+ end
131
+
132
+ def self.trace_job_summary(jobs, report_keys = [])
72
133
  tasks_info = {}
73
134
 
135
+ report_keys = report_keys.collect{|k| k.to_s}
136
+
74
137
  jobs.each do |dep|
75
138
  next unless dep.info[:done]
76
139
  task = [dep.workflow, dep.task_name].compact.collect{|s| s.to_s} * "#"
77
- info = tasks_info[task] ||= {}
140
+ info = tasks_info[task] ||= IndiferentHash.setup({})
141
+ dep_info = IndiferentHash.setup(dep.info)
78
142
 
79
- time = dep.info[:done] - dep.info[:started]
143
+ time = dep_info[:done] - dep_info[:started]
80
144
  info[:time] ||= []
81
145
  info[:time] << time
82
146
 
83
- cpus = nil
84
- spark = false
85
- shard = false
147
+ report_keys.each do |key|
148
+ info[key] = dep_info[key]
149
+ end
150
+
86
151
  dep.info[:config_keys].select do |kinfo|
87
152
  key, value, tokens = kinfo
88
- key = key.to_s
89
- cpus = value if key.include? 'cpu'
90
- spark = value if key == 'spark'
91
- shard = value if key == 'shard'
92
- end
93
153
 
94
- info[:cpus] = cpus || 1
95
- info[:spark] = spark
96
- info[:shard] = shard
154
+ info[key.to_s] = value if report_keys.include? key.to_s
155
+ end
97
156
  end
98
157
 
99
- stats = TSV.setup({}, "Task~Calls,Avg. Time,Total Time,Cpus,Spark,Shard#:type=:list")
158
+ summary = TSV.setup({}, "Task~Calls,Avg. Time,Total Time#:type=:list")
100
159
 
101
160
  tasks_info.each do |task, info|
102
- time_lists, cpus, spark, shard = info.values_at :time, :cpus, :spark, :shard
103
- avg_time = Misc.mean(time_lists)
104
- total_time = Misc.sum(time_lists)
161
+ time_lists = info[:time]
162
+ avg_time = Misc.mean(time_lists).to_i
163
+ total_time = Misc.sum(time_lists).to_i
105
164
  calls = time_lists.length
106
- stats[task] = [calls, avg_time, total_time, cpus, spark, shard]
165
+ summary[task] = [calls, avg_time, total_time]
107
166
  end
108
167
 
109
- raise "No jobs to process" if data.size == 0
168
+ report_keys.each do |key|
169
+ summary.add_field Misc.humanize(key) do |task|
170
+ tasks_info[task][key]
171
+ end
172
+ end if Array === report_keys && report_keys.any?
110
173
 
111
- start = data.column("Start.second").values.flatten.collect{|v| v.to_f}.min
112
- eend = data.column("End.second").values.flatten.collect{|v| v.to_f}.max
113
- total = eend - start
114
- Log.info "Total time elapsed: #{total} seconds"
174
+ summary
175
+ end
115
176
 
116
- if options[:fix_gap]
117
- total_gaps = Misc.sum(gaps.collect{|k,v| v})
118
- Log.info "Total gaps: #{total_gaps} seconds"
119
- end
177
+ def self.trace(seed_jobs, options = {})
178
+ jobs = []
179
+ seed_jobs.each do |step|
180
+ jobs += step.rec_dependencies + [step]
181
+ step.info[:archived_info].each do |path,ainfo|
182
+ archived_step = Step.new path
183
+
184
+ archived_step.define_singleton_method :info do
185
+ ainfo
186
+ end
187
+
188
+ #class << archived_step
189
+ # self
190
+ #end.define_method :info do
191
+ # ainfo
192
+ #end
193
+
194
+ jobs << archived_step
195
+ end if step.info[:archived_info]
120
196
 
121
- plot, width, height = options.values_at :plot, :width, :height
122
- if plot
123
- data.R <<-EOF, [:svg]
124
- rbbt.require('tidyverse')
125
- rbbt.require('ggplot2')
126
-
127
- names(data) <- make.names(names(data))
128
- data$id = rownames(data)
129
- data$content = data$Task
130
- data$start = data$Start
131
- data$end = data$End
132
- data$Project = data$Workflow
133
-
134
- tasks = data
135
-
136
- #theme_gantt <- function(base_size=11, base_family="Source Sans Pro Light") {
137
- theme_gantt <- function(base_size=11, base_family="Sans Serif") {
138
- ret <- theme_bw(base_size, base_family) %+replace%
139
- theme(panel.background = element_rect(fill="#ffffff", colour=NA),
140
- axis.title.x=element_text(vjust=-0.2), axis.title.y=element_text(vjust=1.5),
141
- title=element_text(vjust=1.2, family="Source Sans Pro Semibold"),
142
- panel.border = element_blank(), axis.line=element_blank(),
143
- panel.grid.minor=element_blank(),
144
- panel.grid.major.y = element_blank(),
145
- panel.grid.major.x = element_line(size=0.5, colour="grey80"),
146
- axis.ticks=element_blank(),
147
- legend.position="bottom",
148
- axis.title=element_text(size=rel(1.2), family="Source Sans Pro Semibold"),
149
- strip.text=element_text(size=rel(1.5), family="Source Sans Pro Semibold"),
150
- strip.background=element_rect(fill="#ffffff", colour=NA),
151
- panel.spacing.y=unit(1.5, "lines"),
152
- legend.key = element_blank())
153
-
154
- ret
155
- }
156
-
157
- tasks.long <- tasks %>%
158
- gather(date.type, task.date, -c(Project, Task, id, Start.second, End.second)) %>%
159
- arrange(date.type, task.date) %>%
160
- mutate(id = factor(id, levels=rev(unique(id)), ordered=TRUE))
161
-
162
- x.breaks <- seq(length(tasks$Task) + 0.5 - 3, 0, by=-3)
163
-
164
- timeline <- ggplot(tasks.long, aes(y=id, yend=id, x=Start.second, xend=End.second, colour=Task)) +
165
- geom_segment() +
166
- geom_vline(xintercept=x.breaks, colour="grey80", linetype="dotted") +
167
- guides(colour=guide_legend(title=NULL)) +
168
- labs(x=NULL, y=NULL) +
169
- theme_gantt() + theme(axis.text.x=element_text(angle=45, hjust=1))
170
-
171
- rbbt.png_plot('#{plot}', 'plot(timeline)', width=#{width}, height=#{height}, pointsize=6)
172
- EOF
173
197
  end
174
198
 
199
+ jobs = jobs.uniq.sort_by{|job| t = job.info[:started]; t || Open.mtime(job.path) || Time.now }
200
+
201
+ data = trace_job_times(jobs, options[:fix_gap])
202
+
203
+ report_keys = options[:report_keys] || ""
204
+ report_keys = report_keys.split(/,\s*/) if String === report_keys
205
+ summary = trace_job_summary(jobs, report_keys)
206
+
207
+ raise "No jobs to process" if data.size == 0
208
+
209
+ plot, size, width, height = options.values_at :plot, :size, :width, :height
210
+
211
+ size = 800 if size.nil?
212
+ width = size.to_i * 2 if width.nil?
213
+ height = size if height.nil?
214
+
215
+ plot_trace_job_times(data, plot, width, height) if plot
216
+
175
217
  if options[:plot_data]
176
218
  data
177
219
  else
178
- stats
220
+ summary
179
221
  end
180
222
 
181
223
  end
@@ -36,33 +36,7 @@ end
36
36
  batch_system = options.delete :batch_system
37
37
  batch_system ||= 'auto'
38
38
 
39
- HPC::BATCH_MODULE = case batch_system.to_s.downcase
40
- when 'slurm'
41
- HPC::SLURM
42
- when 'lsf'
43
- HPC::LSF
44
- when 'auto'
45
- case $previous_commands.last
46
- when 'slurm'
47
- HPC::SLURM
48
- when 'lsf'
49
- HPC::LSF
50
- else
51
- case Rbbt::Config.get(:batch_system, :batch, :batch_system, :hpc, :HPC, :BATCH).to_s.downcase
52
- when 'slurm'
53
- HPC::SLURM
54
- when 'lsf'
55
- HPC::LSF
56
- else
57
- case ENV["BATCH_SYSTEM"].to_s.downcase
58
- when 'slurm'
59
- HPC::SLURM
60
- when 'lsf'
61
- HPC::LSF
62
- end
63
- end
64
- end
65
- end
39
+ HPC::BATCH_MODULE = HPC.batch_system batch_system
66
40
 
67
41
  raise ParameterException.new("Could not detect batch_system: #{Misc.fingerprint batch_system}") if HPC::BATCH_MODULE.nil?
68
42
 
@@ -40,33 +40,7 @@ end
40
40
  batch_system = options.delete :batch_system
41
41
  batch_system ||= 'auto'
42
42
 
43
- HPC::BATCH_MODULE = case batch_system.to_s.downcase
44
- when 'slurm'
45
- HPC::SLURM
46
- when 'lsf'
47
- HPC::LSF
48
- when 'auto'
49
- case $previous_commands.last
50
- when 'slurm'
51
- HPC::SLURM
52
- when 'lsf'
53
- HPC::LSF
54
- else
55
- case Rbbt::Config.get(:batch_system, :batch, :batch_system, :hpc, :HPC, :BATCH).to_s.downcase
56
- when 'slurm'
57
- HPC::SLURM
58
- when 'lsf'
59
- HPC::LSF
60
- else
61
- case ENV["BATCH_SYSTEM"].to_s.downcase
62
- when 'slurm'
63
- HPC::SLURM
64
- when 'lsf'
65
- HPC::LSF
66
- end
67
- end
68
- end
69
- end
43
+ HPC::BATCH_MODULE = HPC.batch_system batch_system
70
44
 
71
45
  raise ParameterException.new("Could not detect batch_system: #{Misc.fingerprint batch_system}") if HPC::BATCH_MODULE.nil?
72
46
 
@@ -108,7 +82,7 @@ workdir.glob("**/command.batch").sort_by{|f| File.mtime(f)}.each do |fcmd|
108
82
  cmd = nil
109
83
  end
110
84
 
111
- if m = command_txt.match(/^BATCH_SYSTEM=(.*)/)
85
+ if m = command_txt.match(/^export BATCH_SYSTEM=(.*)/)
112
86
  job_batch_system = m[1].downcase
113
87
  else
114
88
  job_batch_system = nil
@@ -235,6 +209,8 @@ workdir.glob("**/command.batch").sort_by{|f| File.mtime(f)}.each do |fcmd|
235
209
  text = CMD.cmd('grep "^#SBATCH" |tail -n +5', :in => Open.read(fcmd)).read.strip
236
210
  when 'lsf'
237
211
  text = CMD.cmd('grep "^#BSUB" |tail -n +5', :in => Open.read(fcmd)).read.strip
212
+ else
213
+ text = ""
238
214
  end
239
215
  lines = text.split("\n").collect{|line| header, _sep, value = line.partition(/\s+/); Log.color(:yellow, header + ": ") + value}
240
216
  puts Log.color :yellow, lines * "\n"
@@ -34,33 +34,7 @@ EOF
34
34
  batch_system = $slurm_options.delete :batch_system
35
35
  batch_system ||= 'auto'
36
36
 
37
- HPC::BATCH_MODULE = case batch_system.to_s.downcase
38
- when 'slurm'
39
- HPC::SLURM
40
- when 'lsf'
41
- HPC::LSF
42
- when 'auto'
43
- case $previous_commands.last
44
- when 'slurm'
45
- HPC::SLURM
46
- when 'lsf'
47
- HPC::LSF
48
- else
49
- case Rbbt::Config.get(:batch_system, :batch, :batch_system, :hpc, :HPC, :BATCH).to_s.downcase
50
- when 'slurm'
51
- HPC::SLURM
52
- when 'lsf'
53
- HPC::LSF
54
- else
55
- case ENV["BATCH_SYSTEM"].to_s.downcase
56
- when 'slurm'
57
- HPC::SLURM
58
- when 'lsf'
59
- HPC::LSF
60
- end
61
- end
62
- end
63
- end
37
+ HPC::BATCH_MODULE = HPC.batch_system batch_system
64
38
 
65
39
  raise ParameterException.new("Could not detect batch_system: #{Misc.fingerprint batch_system}") if HPC::BATCH_MODULE.nil?
66
40
 
@@ -40,33 +40,7 @@ end
40
40
  batch_system = options.delete :batch_system
41
41
  batch_system ||= 'auto'
42
42
 
43
- HPC::BATCH_MODULE = case batch_system.to_s.downcase
44
- when 'slurm'
45
- HPC::SLURM
46
- when 'lsf'
47
- HPC::LSF
48
- when 'auto'
49
- case $previous_commands.last
50
- when 'slurm'
51
- HPC::SLURM
52
- when 'lsf'
53
- HPC::LSF
54
- else
55
- case Rbbt::Config.get(:batch_system, :batch, :batch_system, :hpc, :HPC, :BATCH).to_s.downcase
56
- when 'slurm'
57
- HPC::SLURM
58
- when 'lsf'
59
- HPC::LSF
60
- else
61
- case ENV["BATCH_SYSTEM"].to_s.downcase
62
- when 'slurm'
63
- HPC::SLURM
64
- when 'lsf'
65
- HPC::LSF
66
- end
67
- end
68
- end
69
- end
43
+ HPC::BATCH_MODULE = HPC.batch_system batch_system
70
44
 
71
45
  raise ParameterException.new("Could not detect batch_system: #{Misc.fingerprint batch_system}") if HPC::BATCH_MODULE.nil?
72
46
 
@@ -33,33 +33,7 @@ EOF
33
33
  batch_system = $slurm_options.delete :batch_system
34
34
  batch_system ||= 'auto'
35
35
 
36
- HPC::BATCH_MODULE = case batch_system.to_s.downcase
37
- when 'slurm'
38
- HPC::SLURM
39
- when 'lsf'
40
- HPC::LSF
41
- when 'auto'
42
- case $previous_commands.last
43
- when 'slurm'
44
- HPC::SLURM
45
- when 'lsf'
46
- HPC::LSF
47
- else
48
- case Rbbt::Config.get(:batch_system, :batch, :batch_system, :hpc, :HPC, :BATCH).to_s.downcase
49
- when 'slurm'
50
- HPC::SLURM
51
- when 'lsf'
52
- HPC::LSF
53
- else
54
- case ENV["BATCH_SYSTEM"].to_s.downcase
55
- when 'slurm'
56
- HPC::SLURM
57
- when 'lsf'
58
- HPC::LSF
59
- end
60
- end
61
- end
62
- end
36
+ HPC::BATCH_MODULE = HPC.batch_system batch_system
63
37
 
64
38
  raise ParameterException.new("Could not detect batch_system: #{Misc.fingerprint batch_system}") if HPC::BATCH_MODULE.nil?
65
39
 
@@ -8,9 +8,11 @@ $0 = "rbbt #{$previous_commands*" "} #{ File.basename(__FILE__) }" if $previous_
8
8
 
9
9
  options = SOPT.setup <<EOF
10
10
 
11
- Make a job forget all its dependencies and archive their meta-data
11
+ Make a job forget its dependencies and archive their meta-data
12
12
 
13
- $ #{$0} [options] <job_path>
13
+ $ #{$0} [options] <job_path> [<task_name>|<workflow>#<task_name>] ...
14
+
15
+ Specific dependencies can be specified as . Otherwise, all are forgoten.
14
16
 
15
17
  -h--help Print this help
16
18
  -p--purge Purge dependencies
@@ -26,7 +28,7 @@ if options[:help]
26
28
  exit 0
27
29
  end
28
30
 
29
- path = ARGV[0]
31
+ path, *remove = ARGV
30
32
 
31
33
  raise ParameterException, "No path given" if path.nil?
32
34
  step = Workflow.load_step path
@@ -34,10 +36,20 @@ step = Workflow.load_step path
34
36
  step.archive_deps
35
37
  step.copy_files_dir
36
38
  dependencies = step.dependencies
37
- step.set_info :dependencies, []
38
39
 
39
- if options[:purge]
40
+ if remove && remove.any?
41
+ remove_paths = dependencies.select do |dep|
42
+ remove.include?(dep.task_name) || remove.include?([dep.workflow.to_s, dep.task_name] * "#")
43
+ end.collect{|dep| dep.path }
44
+ else
45
+ remove_paths = dependencies.collect{|dep| dep.path }
46
+ end
47
+
48
+ step.set_info :dependencies, step.info[:dependencies].reject{|info| remove_paths.include? info.last}
49
+
50
+ if options[:purge] || options[:recursive_purge]
40
51
  dependencies.each do |dependency|
52
+ next unless remove_paths.include? dependency.path
41
53
  Step.purge(dependency.path, options[:recursive_purge])
42
54
  end
43
55
  end
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require 'rbbt/workflow'
4
+ require 'rbbt/workflow/util/trace'
4
5
 
5
6
  require 'rbbt-util'
6
7
  require 'fileutils'
@@ -16,30 +17,26 @@ require 'rbbt/util/R'
16
17
  $0 = "rbbt #{$previous_commands*""} #{ File.basename(__FILE__) }" if $previous_commands
17
18
 
18
19
  options = SOPT.setup <<EOF
19
- Examine the provenance of a job result
20
+ Examine the execution trace of a job or set of jobs
20
21
 
21
22
  $ rbbt workflow trace <job-result>
22
23
 
23
24
  -h--help Help
25
+ -fg--fix_gap Remove execution gaps
26
+ -rk--report_keys* Config keys and info fields to report
27
+ -p--plot* Plot file
24
28
  -w--width* Image Width
25
29
  -h--height* Image Height
26
- -p--plot* Plot file
27
30
  -s--size* Image Size (Height and Width)
28
- -fg--fix_gap Remove execution gaps
29
31
  -pd--plot_data Print plot data
30
32
  EOF
31
33
 
32
34
  SOPT.usage if options[:help]
33
35
 
36
+
34
37
  files = ARGV
35
38
  plot = options[:plot]
36
39
 
37
- width, height, size = options.values_at :width, :height, :size
38
-
39
- size = 800 if size.nil?
40
- width = size if width.nil?
41
- height = size if height.nil?
42
-
43
40
  def get_step(file)
44
41
  file = File.expand_path(file)
45
42
  file = file.sub(/\.(info|files)/,'')
@@ -47,191 +44,8 @@ def get_step(file)
47
44
  end
48
45
 
49
46
  jobs = []
50
- files.each do |file|
51
- step = get_step file
52
-
53
- jobs += step.rec_dependencies + [step]
54
-
55
- step.info[:archived_info].each do |path,ainfo|
56
- archived_step = Step.new path
57
- class << archived_step
58
- self
59
- end.define_method :info do
60
- ainfo
61
- end
62
- jobs << archived_step
63
- end if step.info[:archived_info]
64
- end
65
-
66
- jobs = jobs.select{|job| job.info[:done]}.sort_by{|job| job.info[:started]}
67
-
68
- data = TSV.setup({}, "Job~Workflow,Task,Start,End#:type=:list")
69
- min_start = nil
70
- max_done = nil
71
- jobs.each do |job|
72
- next unless job.info[:done]
73
- started = job.info[:started]
74
- ddone = job.info[:done]
75
-
76
- code = [job.workflow, job.task_name].compact.collect{|s| s.to_s} * "."
77
- code = code + '.' + job.name
78
-
79
- data[code] = [job.workflow.to_s, job.task_name, started, ddone]
80
- if min_start.nil?
81
- min_start = started
82
- else
83
- min_start = started if started < min_start
84
- end
85
-
86
- if max_done.nil?
87
- max_done = ddone
88
- else
89
- max_done = ddone if ddone > max_done
90
- end
91
- end
92
-
93
- data.add_field "Start.second" do |k,value|
94
- value["Start"] - min_start
95
- end
96
-
97
- data.add_field "End.second" do |k,value|
98
- value["End"] - min_start
99
- end
100
-
101
- if options[:fix_gap]
102
- ranges = []
103
- data.through do |k,values|
104
- start, eend = values.values_at "Start.second", "End.second"
105
-
106
- ranges << (start..eend)
107
- end
108
-
109
- gaps = {}
110
- last = nil
111
- Misc.collapse_ranges(ranges).each do |range|
112
- start = range.begin
113
- eend = range.end
114
- if last
115
- gaps[last] = start - last
116
- end
117
- last = eend
118
- end
119
-
120
- data.process "End.second" do |value,k,values|
121
- gap = Misc.sum(gaps.select{|pos,size| pos < values["Start.second"]}.collect{|pos,size| size})
122
- value - gap
123
- end
124
-
125
- data.process "Start.second" do |value,k,values|
126
- gap = Misc.sum(gaps.select{|pos,size| pos < values["Start.second"]}.collect{|pos,size| size})
127
- value - gap
128
- end
47
+ jobs = files.collect do |file|
48
+ get_step file
129
49
  end
130
50
 
131
- tasks_info = {}
132
-
133
- jobs.each do |dep|
134
- next unless dep.info[:done]
135
- task = [dep.workflow, dep.task_name].compact.collect{|s| s.to_s} * "#"
136
- info = tasks_info[task] ||= {}
137
-
138
- time = dep.info[:done] - dep.info[:started]
139
- info[:time] ||= []
140
- info[:time] << time
141
-
142
- cpus = nil
143
- spark = false
144
- shard = false
145
- dep.info[:config_keys].select do |kinfo|
146
- key, value, tokens = kinfo
147
- key = key.to_s
148
- cpus = value if key.include? 'cpu'
149
- spark = value if key == 'spark'
150
- shard = value if key == 'shard'
151
- end
152
-
153
- info[:cpus] = cpus || 1
154
- info[:spark] = spark
155
- info[:shard] = shard
156
- end
157
-
158
- stats = TSV.setup({}, "Task~Calls,Avg. Time,Total Time,Cpus,Spark,Shard#:type=:list")
159
-
160
- tasks_info.each do |task, info|
161
- time_lists, cpus, spark, shard = info.values_at :time, :cpus, :spark, :shard
162
- avg_time = Misc.mean(time_lists).to_i
163
- total_time = Misc.sum(time_lists)
164
- calls = time_lists.length
165
- stats[task] = [calls, avg_time, total_time, cpus, spark, shard]
166
- end
167
-
168
- raise "No jobs to process" if data.size == 0
169
-
170
- start = data.column("Start.second").values.flatten.collect{|v| v.to_i}.min
171
- eend = data.column("End.second").values.flatten.collect{|v| v.to_i}.max
172
- total = eend - start
173
- Log.info "Total time elapsed: #{total} seconds"
174
-
175
- if options[:fix_gap]
176
- total_gaps = Misc.sum(gaps.collect{|k,v| v})
177
- Log.info "Total gaps: #{total_gaps} seconds"
178
- end
179
-
180
- if options[:plot_data]
181
- puts data.to_s
182
- else
183
- puts stats.to_s
184
- end
185
-
186
- if plot
187
- data.R <<-EOF, [:svg]
188
- rbbt.require('tidyverse')
189
- rbbt.require('ggplot2')
190
-
191
- names(data) <- make.names(names(data))
192
- data$id = rownames(data)
193
- data$content = data$Task
194
- data$start = data$Start
195
- data$end = data$End
196
- data$Project = data$Workflow
197
-
198
- tasks = data
199
-
200
- #theme_gantt <- function(base_size=11, base_family="Source Sans Pro Light") {
201
- theme_gantt <- function(base_size=11, base_family="Sans Serif") {
202
- ret <- theme_bw(base_size, base_family) %+replace%
203
- theme(panel.background = element_rect(fill="#ffffff", colour=NA),
204
- axis.title.x=element_text(vjust=-0.2), axis.title.y=element_text(vjust=1.5),
205
- title=element_text(vjust=1.2, family="Source Sans Pro Semibold"),
206
- panel.border = element_blank(), axis.line=element_blank(),
207
- panel.grid.minor=element_blank(),
208
- panel.grid.major.y = element_blank(),
209
- panel.grid.major.x = element_line(size=0.5, colour="grey80"),
210
- axis.ticks=element_blank(),
211
- legend.position="bottom",
212
- axis.title=element_text(size=rel(1.2), family="Source Sans Pro Semibold"),
213
- strip.text=element_text(size=rel(1.5), family="Source Sans Pro Semibold"),
214
- strip.background=element_rect(fill="#ffffff", colour=NA),
215
- panel.spacing.y=unit(1.5, "lines"),
216
- legend.key = element_blank())
217
-
218
- ret
219
- }
220
-
221
- tasks.long <- tasks %>%
222
- gather(date.type, task.date, -c(Project, Task, id, Start.second, End.second)) %>%
223
- arrange(date.type, task.date) %>%
224
- mutate(id = factor(id, levels=rev(unique(id)), ordered=TRUE))
225
-
226
- x.breaks <- seq(length(tasks$Task) + 0.5 - 3, 0, by=-3)
227
-
228
- timeline <- ggplot(tasks.long, aes(y=id, yend=id, x=Start.second, xend=End.second, colour=Task)) +
229
- geom_segment() +
230
- geom_vline(xintercept=x.breaks, colour="grey80", linetype="dotted") +
231
- guides(colour=guide_legend(title=NULL)) +
232
- labs(x=NULL, y=NULL) +
233
- theme_gantt() + theme(axis.text.x=element_text(angle=45, hjust=1))
234
-
235
- rbbt.png_plot('#{plot}', 'plot(timeline)', width=#{width}, height=#{height}, pointsize=6)
236
- EOF
237
- end
51
+ puts Workflow.trace(jobs, options)
@@ -46,7 +46,21 @@ pid = step.info[:pid]
46
46
  host = step.info[:pid_hostname]
47
47
 
48
48
  step.rec_dependencies.each do |dep|
49
- dep.set_info key, value if (force || ! dep.info.include?(key)) && (!check_pid || dep.info[:pid].to_s == pid and dep.info[:pid_hostname] == host)
50
- rescue
51
- Log.warn "Could no set info #{key} for #{dep.path}: #{$!.message}"
49
+ begin
50
+ dep.set_info key, value if (force || ! dep.info.include?(key)) && (!check_pid || dep.info[:pid].to_s == pid and dep.info[:pid_hostname] == host)
51
+ rescue
52
+ Log.warn "Could no set info #{key} for #{dep.path}: #{$!.message}"
53
+ end
52
54
  end if recursive
55
+
56
+ if recursive && step.info[:archived_info]
57
+ ad = step.info[:archived_info]
58
+ ad.each do |d,info|
59
+ begin
60
+ info[key] = value if (force || ! info.include?(key)) && (!check_pid || info[:pid].to_s == pid and info[:pid_hostname] == host)
61
+ rescue
62
+ Log.warn "Could no set info #{key} for archived_dep #{info[:path]}: #{$!.message}"
63
+ end
64
+ end
65
+ step.set_info :archived_info, ad
66
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rbbt-util
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.31.13
4
+ version: 5.32.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Miguel Vazquez
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-04-08 00:00:00.000000000 Z
11
+ date: 2021-04-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake