rbbt-util 5.32.2 → 5.32.9
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 +4 -4
- data/lib/rbbt/resource.rb +14 -8
- data/lib/rbbt/util/cmd.rb +14 -4
- data/lib/rbbt/util/migrate.rb +118 -0
- data/lib/rbbt/workflow.rb +11 -0
- data/lib/rbbt/workflow/usage.rb +2 -2
- data/lib/rbbt/workflow/util/archive.rb +31 -102
- data/lib/rbbt/workflow/util/trace.rb +11 -2
- data/share/rbbt_commands/lsf/clean +212 -0
- data/share/rbbt_commands/lsf/list +311 -0
- data/share/rbbt_commands/lsf/orchestrate +56 -0
- data/share/rbbt_commands/lsf/tail +55 -0
- data/share/rbbt_commands/lsf/task +55 -0
- data/share/rbbt_commands/migrate +3 -76
- data/share/rbbt_commands/slurm/clean +212 -0
- data/share/rbbt_commands/slurm/list +311 -0
- data/share/rbbt_commands/slurm/orchestrate +56 -0
- data/share/rbbt_commands/slurm/tail +55 -0
- data/share/rbbt_commands/slurm/task +55 -0
- data/test/rbbt/persist/tsv/test_tokyocabinet.rb +1 -1
- data/test/rbbt/util/test_migrate.rb +36 -0
- data/test/rbbt/workflow/util/test_archive.rb +31 -0
- metadata +97 -81
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3b9d236294c4bdcc32e517eadcffa4d103e5a99a220096497a50f8baa26f746d
|
|
4
|
+
data.tar.gz: 6370930ac76bd8b86666fa59556d2d8648789c3e3dda56bb71c71c2ff65b88d3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7dcd6fadf6424add27b6773d16ef0b794183c4b2f52616498d8c2ec21a77c5b5ad8f3fed65ca69ce5111c71e0942f84be79c107b40156c331919533f3fc5bea7
|
|
7
|
+
data.tar.gz: ea294d8aa5f8ff04a9e902fdd2d7d2287a34934f7288a11234996d06fb72dba4f333e655d591d9b78c5bc00104036356625088f9aa4c7e808ae2d42d0b253781
|
data/lib/rbbt/resource.rb
CHANGED
|
@@ -6,6 +6,7 @@ require 'set'
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
module Resource
|
|
9
|
+
class ResourceNotFound < RbbtException; end
|
|
9
10
|
|
|
10
11
|
class << self
|
|
11
12
|
attr_accessor :lock_dir
|
|
@@ -154,16 +155,18 @@ module Resource
|
|
|
154
155
|
rake_dir, content = rake_for(path)
|
|
155
156
|
rake_dir = Path.setup(rake_dir.dup, self.pkgdir, self)
|
|
156
157
|
else
|
|
157
|
-
|
|
158
|
-
|
|
158
|
+
if path !~ /\.(gz|bgz)$/
|
|
159
|
+
begin
|
|
160
|
+
produce(path.annotate(path + '.gz'), force)
|
|
161
|
+
rescue ResourceNotFound
|
|
159
162
|
begin
|
|
160
|
-
produce(path.annotate(path + '.gz'), force)
|
|
161
|
-
rescue
|
|
162
163
|
produce(path.annotate(path + '.bgz'), force)
|
|
164
|
+
rescue ResourceNotFound
|
|
165
|
+
raise ResourceNotFound, "Resource is missing and does not seem to be claimed: #{ self } -- #{ path } "
|
|
163
166
|
end
|
|
164
167
|
end
|
|
165
|
-
|
|
166
|
-
raise "Resource is missing and does not seem to be claimed: #{ self } -- #{ path } "
|
|
168
|
+
else
|
|
169
|
+
raise ResourceNotFound, "Resource is missing and does not seem to be claimed: #{ self } -- #{ path } "
|
|
167
170
|
end
|
|
168
171
|
end
|
|
169
172
|
|
|
@@ -174,7 +177,7 @@ module Resource
|
|
|
174
177
|
end
|
|
175
178
|
|
|
176
179
|
if type and not File.exist?(final_path) or force
|
|
177
|
-
Log.medium "Producing: #{ final_path }"
|
|
180
|
+
Log.medium "Producing: (#{self.to_s}) #{ final_path }"
|
|
178
181
|
lock_filename = Persist.persistence_path(final_path, {:dir => Resource.lock_dir})
|
|
179
182
|
|
|
180
183
|
Misc.lock lock_filename do
|
|
@@ -310,7 +313,10 @@ url='#{url}'
|
|
|
310
313
|
def identify(path)
|
|
311
314
|
path = File.expand_path(path)
|
|
312
315
|
resource ||= Rbbt
|
|
313
|
-
(Path::STANDARD_SEARCH + resource.search_order + resource.search_paths.keys)
|
|
316
|
+
locations = (Path::STANDARD_SEARCH + resource.search_order + resource.search_paths.keys)
|
|
317
|
+
locations -= [:current, "current"]
|
|
318
|
+
locations << :current
|
|
319
|
+
locations.uniq.each do |name|
|
|
314
320
|
pattern = resource.search_paths[name]
|
|
315
321
|
next if pattern.nil?
|
|
316
322
|
pattern = pattern.sub('{PWD}', Dir.pwd)
|
data/lib/rbbt/util/cmd.rb
CHANGED
|
@@ -248,16 +248,26 @@ module CMD
|
|
|
248
248
|
pid = io.pids.first
|
|
249
249
|
|
|
250
250
|
line = "" if bar
|
|
251
|
+
starting = true
|
|
251
252
|
while c = io.getc
|
|
252
|
-
|
|
253
|
-
line << c if bar
|
|
254
|
-
if c == "\n"
|
|
255
|
-
bar.process(line) if bar
|
|
253
|
+
if starting
|
|
256
254
|
if pid
|
|
257
255
|
Log.logn "STDOUT [#{pid}]: ", level
|
|
258
256
|
else
|
|
259
257
|
Log.logn "STDOUT: ", level
|
|
260
258
|
end
|
|
259
|
+
starting = false
|
|
260
|
+
end
|
|
261
|
+
STDERR << c if Log.severity <= level
|
|
262
|
+
line << c if bar
|
|
263
|
+
if c == "\n"
|
|
264
|
+
bar.process(line) if bar
|
|
265
|
+
starting = true
|
|
266
|
+
#if pid
|
|
267
|
+
# Log.logn "STDOUT [#{pid}]: ", level
|
|
268
|
+
#else
|
|
269
|
+
# Log.logn "STDOUT: ", level
|
|
270
|
+
#end
|
|
261
271
|
line = "" if bar
|
|
262
272
|
end
|
|
263
273
|
end
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
module Rbbt
|
|
2
|
+
def self.migrate_source_paths(path, resource = Rbbt, source = nil)
|
|
3
|
+
if source
|
|
4
|
+
lpath, *paths = Misc.ssh_run(source, <<-EOF).split("\n")
|
|
5
|
+
require 'rbbt-util'
|
|
6
|
+
path = "#{path}"
|
|
7
|
+
if Open.exists?(path)
|
|
8
|
+
path = #{resource.to_s}.identify(path)
|
|
9
|
+
else
|
|
10
|
+
path = Path.setup(path)
|
|
11
|
+
end
|
|
12
|
+
puts path
|
|
13
|
+
puts path.glob_all.collect{|p| File.directory?(p) ? p + "/" : p } * "\n"
|
|
14
|
+
EOF
|
|
15
|
+
|
|
16
|
+
[path, paths.collect{|p| [source, p] * ":"}, lpath]
|
|
17
|
+
else
|
|
18
|
+
if File.exists?(path)
|
|
19
|
+
path = resource.identify(path)
|
|
20
|
+
else
|
|
21
|
+
path = Path.setup(path)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
[path, (path.directory? ? path.glob_all : path.find_all), path]
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def self.migrate_target_path(path, search_path = 'user', resource = Rbbt, target = nil)
|
|
29
|
+
if target
|
|
30
|
+
Misc.ssh_run(target, <<-EOF).split("\n").first
|
|
31
|
+
require 'rbbt-util'
|
|
32
|
+
path = "#{path}"
|
|
33
|
+
resource = #{resource.to_s}
|
|
34
|
+
search_path = "#{search_path}"
|
|
35
|
+
puts resource[path].find(search_path)
|
|
36
|
+
EOF
|
|
37
|
+
else
|
|
38
|
+
resource[path].find(search_path)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def self.migrate_files(real_paths, target, options = {})
|
|
43
|
+
excludes = %w(.save .crap .source tmp filecache open-remote)
|
|
44
|
+
excludes += (options[:exclude] || "").split(/,\s*/)
|
|
45
|
+
excludes_str = excludes.collect{|s| "--exclude '#{s}'" } * " "
|
|
46
|
+
|
|
47
|
+
other = options[:other] || []
|
|
48
|
+
|
|
49
|
+
test_str = options[:test] ? '-nv' : ''
|
|
50
|
+
|
|
51
|
+
real_paths.each do |source_path|
|
|
52
|
+
if File.directory?(source_path) || source_path =~ /\/$/
|
|
53
|
+
source_path += "/" unless source_path[-1] == "/"
|
|
54
|
+
target += "/" unless target[-1] == "/"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
next if source_path == target
|
|
58
|
+
|
|
59
|
+
if options[:target]
|
|
60
|
+
CMD.cmd("ssh #{options[:target]} mkdir -p '#{File.dirname(target)}'")
|
|
61
|
+
else
|
|
62
|
+
Open.mkdir File.dirname(target)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
if options[:target]
|
|
66
|
+
target_path = [options[:target], "'" + target + "'"] * ":"
|
|
67
|
+
else
|
|
68
|
+
target_path = "'" + target + "'"
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
TmpFile.with_file do |tmp_files|
|
|
72
|
+
if options[:files]
|
|
73
|
+
Open.write(tmp_files, options[:files] * "\n")
|
|
74
|
+
files_from_str = "--files-from='#{tmp_files}'"
|
|
75
|
+
else
|
|
76
|
+
files_from_str = ""
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
cmd = "rsync -avztAXHP --copy-unsafe-links #{test_str} #{files_from_str} #{excludes_str} '#{source_path}' #{target_path} #{other * " "}"
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
cmd << " && rm -Rf #{source_path}" if options[:delete] && ! options[:files]
|
|
83
|
+
|
|
84
|
+
if options[:print]
|
|
85
|
+
puts cmd
|
|
86
|
+
exit 0
|
|
87
|
+
else
|
|
88
|
+
CMD.cmd_log(cmd, :log => Log::INFO)
|
|
89
|
+
|
|
90
|
+
if options[:delete] && options[:files]
|
|
91
|
+
remove_files = options[:files].collect{|f| File.join(source_path, f) }
|
|
92
|
+
dirs = remove_files.select{|f| File.directory? f }
|
|
93
|
+
remove_files.each do |file|
|
|
94
|
+
next if dirs.include? file
|
|
95
|
+
Open.rm file
|
|
96
|
+
end
|
|
97
|
+
dirs.each do |dir|
|
|
98
|
+
FileUtils.rmdir dir if Dir.glob(dir).empty?
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def self.migrate(path, search_path, options = {})
|
|
108
|
+
search_path = 'user' if search_path.nil?
|
|
109
|
+
|
|
110
|
+
resource = Rbbt
|
|
111
|
+
|
|
112
|
+
path, real_paths, lpath = migrate_source_paths(path, resource, options[:source])
|
|
113
|
+
|
|
114
|
+
target = migrate_target_path(lpath, search_path, resource, options[:target])
|
|
115
|
+
|
|
116
|
+
migrate_files(real_paths, target, options)
|
|
117
|
+
end
|
|
118
|
+
end
|
data/lib/rbbt/workflow.rb
CHANGED
|
@@ -600,6 +600,17 @@ module Workflow
|
|
|
600
600
|
true
|
|
601
601
|
end
|
|
602
602
|
end
|
|
603
|
+
|
|
604
|
+
if ! Open.exists?(step.info_file)
|
|
605
|
+
begin
|
|
606
|
+
workflow = step.path.split("/")[-3]
|
|
607
|
+
task_name = step.path.split("/")[-2]
|
|
608
|
+
workflow = Kernel.const_get workflow
|
|
609
|
+
step.task = workflow.tasks[task_name.to_sym]
|
|
610
|
+
rescue
|
|
611
|
+
Log.exception $!
|
|
612
|
+
end
|
|
613
|
+
end
|
|
603
614
|
step
|
|
604
615
|
end
|
|
605
616
|
|
data/lib/rbbt/workflow/usage.rb
CHANGED
|
@@ -122,7 +122,7 @@ module Workflow
|
|
|
122
122
|
last = _prov_tasks(workflow.dep_tree(task_name))
|
|
123
123
|
|
|
124
124
|
if child
|
|
125
|
-
description << "->" << task_name.to_s
|
|
125
|
+
description << "-> " << task_name.to_s
|
|
126
126
|
elsif first
|
|
127
127
|
description << "" << task_name.to_s
|
|
128
128
|
else
|
|
@@ -198,7 +198,7 @@ module Workflow
|
|
|
198
198
|
puts Misc.format_definition_list_item(name.to_s, description, Log.terminal_width, 20, :yellow)
|
|
199
199
|
|
|
200
200
|
prov_string = prov_string(dep_tree(name))
|
|
201
|
-
puts Log.color
|
|
201
|
+
puts Misc.format_paragraph Log.color(:blue, "-> " + prov_string) if prov_string && ! prov_string.empty?
|
|
202
202
|
end
|
|
203
203
|
|
|
204
204
|
else
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
require 'rbbt/util/migrate'
|
|
1
2
|
class Step
|
|
2
3
|
|
|
3
4
|
MAIN_RSYNC_ARGS="-avztAXHP --copy-links"
|
|
@@ -126,63 +127,48 @@ class Step
|
|
|
126
127
|
end
|
|
127
128
|
end
|
|
128
129
|
|
|
129
|
-
def self.
|
|
130
|
-
resource=Rbbt
|
|
131
|
-
|
|
132
|
-
orig_path = path
|
|
133
|
-
other_rsync_args = options[:rsync]
|
|
134
|
-
|
|
135
|
-
recursive = options[:recursive]
|
|
130
|
+
def self.migrate_source_paths(path, resource = Rbbt, source = nil, recursive = true)
|
|
136
131
|
recursive = false if recursive.nil?
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
Misc.ssh_run(options[:source], <<-EOF).split("\n")
|
|
132
|
+
if source
|
|
133
|
+
lpath, *paths = Misc.ssh_run(source, <<-EOF).split("\n")
|
|
140
134
|
require 'rbbt-util'
|
|
141
135
|
require 'rbbt/workflow'
|
|
142
136
|
|
|
143
|
-
path = "#{path}"
|
|
144
137
|
recursive = #{ recursive.to_s }
|
|
138
|
+
path = "#{path}"
|
|
145
139
|
|
|
146
|
-
if
|
|
140
|
+
if Open.exists?(path)
|
|
147
141
|
path = #{resource.to_s}.identify(path)
|
|
148
142
|
else
|
|
149
143
|
path = Path.setup(path)
|
|
150
144
|
end
|
|
151
145
|
|
|
152
|
-
files = path.glob_all
|
|
153
|
-
|
|
146
|
+
files = path.glob_all.collect{|p| File.directory?(p) ? p + "/" : p }
|
|
154
147
|
files = Step.job_files_for_archive(files, recursive)
|
|
155
148
|
|
|
149
|
+
puts path
|
|
156
150
|
puts files * "\n"
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
path = "var/jobs"
|
|
176
|
-
resource = #{resource.to_s}
|
|
177
|
-
search_path = "#{search_path}"
|
|
178
|
-
puts resource[path].find(search_path)
|
|
179
|
-
EOF
|
|
180
|
-
else
|
|
181
|
-
resource['var/jobs'].find(search_path)
|
|
182
|
-
end
|
|
151
|
+
EOF
|
|
152
|
+
|
|
153
|
+
[path, paths.collect{|p| [source, p] * ":"}, lpath]
|
|
154
|
+
else
|
|
155
|
+
path = Path.setup(path.dup)
|
|
156
|
+
files = path.glob_all
|
|
157
|
+
files = Step.job_files_for_archive(files, recursive)
|
|
158
|
+
|
|
159
|
+
[path, files, path]
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def self.migrate(path, search_path, options = {})
|
|
164
|
+
search_path = 'user' if search_path.nil?
|
|
165
|
+
|
|
166
|
+
resource = Rbbt
|
|
167
|
+
|
|
168
|
+
path, real_paths, lpath = self.migrate_source_paths(path, resource, options[:source], options[:recursive])
|
|
183
169
|
|
|
184
170
|
subpath_files = {}
|
|
185
|
-
|
|
171
|
+
real_paths.sort.each do |path|
|
|
186
172
|
parts = path.split("/")
|
|
187
173
|
subpath = parts[0..-4] * "/" + "/"
|
|
188
174
|
|
|
@@ -190,73 +176,16 @@ puts resource[path].find(search_path)
|
|
|
190
176
|
subpath = subpath_files.keys.last
|
|
191
177
|
end
|
|
192
178
|
|
|
193
|
-
source = path[subpath.length..-1]
|
|
179
|
+
source = path.chars[subpath.length..-1] * ""
|
|
194
180
|
|
|
195
181
|
subpath_files[subpath] ||= []
|
|
196
182
|
subpath_files[subpath] << source
|
|
197
183
|
end
|
|
198
184
|
|
|
199
|
-
|
|
200
|
-
subpath_files.each do |subpath, files|
|
|
201
|
-
if options[:target]
|
|
202
|
-
CMD.cmd("ssh #{options[:target]} mkdir -p '#{File.dirname(target)}'")
|
|
203
|
-
else
|
|
204
|
-
Open.mkdir File.dirname(target)
|
|
205
|
-
end
|
|
185
|
+
target = Rbbt.migrate_target_path('var/jobs', search_path, resource, options[:target])
|
|
206
186
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
else
|
|
210
|
-
source = subpath
|
|
211
|
-
end
|
|
212
|
-
target = [options[:target], target] * ":" if options[:target]
|
|
213
|
-
|
|
214
|
-
next if File.exists?(source) && File.exists?(target) && File.expand_path(source) == File.expand_path(target)
|
|
215
|
-
|
|
216
|
-
files_and_dirs = Set.new( files )
|
|
217
|
-
files.each do |file|
|
|
218
|
-
synced_files << File.join(subpath, file)
|
|
219
|
-
|
|
220
|
-
parts = file.split("/")[0..-2].reject{|p| p.empty?}
|
|
221
|
-
while parts.any?
|
|
222
|
-
files_and_dirs << parts * "/"
|
|
223
|
-
parts.pop
|
|
224
|
-
end
|
|
225
|
-
end
|
|
226
|
-
|
|
227
|
-
TmpFile.with_file(files_and_dirs.sort_by{|l| l.length}.to_a * "\n") do |tmp_include_file|
|
|
228
|
-
test_str = options[:test] ? '-nv' : ''
|
|
229
|
-
|
|
230
|
-
cmd = "rsync #{MAIN_RSYNC_ARGS} --progress #{test_str} --files-from='#{tmp_include_file}' #{source}/ #{target}/ #{other_rsync_args}"
|
|
231
|
-
|
|
232
|
-
#cmd << " && rm -Rf #{source}" if options[:delete]
|
|
233
|
-
if options[:print]
|
|
234
|
-
ppp Open.read(tmp_include_file)
|
|
235
|
-
puts cmd
|
|
236
|
-
else
|
|
237
|
-
CMD.cmd_log(cmd, :log => Log::INFO)
|
|
238
|
-
end
|
|
239
|
-
end
|
|
240
|
-
end
|
|
241
|
-
|
|
242
|
-
if options[:delete] && synced_files.any?
|
|
243
|
-
puts Log.color :magenta, "About to erase these files:"
|
|
244
|
-
synced_files.each do |p|
|
|
245
|
-
puts Log.color :red, p
|
|
246
|
-
end
|
|
247
|
-
|
|
248
|
-
if options[:non_interactive]
|
|
249
|
-
response = 'yes'
|
|
250
|
-
else
|
|
251
|
-
puts Log.color :magenta, "Type 'yes' if you are sure:"
|
|
252
|
-
response = STDIN.gets.chomp
|
|
253
|
-
end
|
|
254
|
-
|
|
255
|
-
if response == 'yes'
|
|
256
|
-
synced_files.each do |p|
|
|
257
|
-
Open.rm p
|
|
258
|
-
end
|
|
259
|
-
end
|
|
187
|
+
subpath_files.each do |subpath, files|
|
|
188
|
+
Rbbt.migrate_files([subpath], target, options.merge(:files => files))
|
|
260
189
|
end
|
|
261
190
|
end
|
|
262
191
|
|
|
@@ -10,6 +10,9 @@ module Workflow
|
|
|
10
10
|
started = job.info[:started]
|
|
11
11
|
ddone = job.info[:done]
|
|
12
12
|
|
|
13
|
+
started = Time.parse started if String === started
|
|
14
|
+
ddone = Time.parse ddone if String === ddone
|
|
15
|
+
|
|
13
16
|
code = [job.workflow, job.task_name].compact.collect{|s| s.to_s} * " · "
|
|
14
17
|
code = job.name + " - " + code
|
|
15
18
|
|
|
@@ -140,7 +143,13 @@ rbbt.png_plot('#{plot}', 'plot(timeline)', width=#{width}, height=#{height}, poi
|
|
|
140
143
|
info = tasks_info[task] ||= IndiferentHash.setup({})
|
|
141
144
|
dep_info = IndiferentHash.setup(dep.info)
|
|
142
145
|
|
|
143
|
-
|
|
146
|
+
ddone = dep_info[:done]
|
|
147
|
+
started = dep_info[:started]
|
|
148
|
+
|
|
149
|
+
started = Time.parse started if String === started
|
|
150
|
+
ddone = Time.parse ddone if String === ddone
|
|
151
|
+
|
|
152
|
+
time = ddone - started
|
|
144
153
|
info[:time] ||= []
|
|
145
154
|
info[:time] << time
|
|
146
155
|
|
|
@@ -196,7 +205,7 @@ rbbt.png_plot('#{plot}', 'plot(timeline)', width=#{width}, height=#{height}, poi
|
|
|
196
205
|
|
|
197
206
|
end
|
|
198
207
|
|
|
199
|
-
jobs = jobs.uniq.sort_by{|job| t = job.info[:started]
|
|
208
|
+
jobs = jobs.uniq.sort_by{|job| t = job.info[:started] || Open.mtime(job.path) || Time.now; Time === t ? t : Time.parse(t) }
|
|
200
209
|
|
|
201
210
|
data = trace_job_times(jobs, options[:fix_gap])
|
|
202
211
|
|