rbbt-util 5.31.13 → 5.32.2

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