11501: When calculating a work unit's running time, only include 'leaf' children...
[arvados.git] / apps / workbench / app / models / proxy_work_unit.rb
1 class ProxyWorkUnit < WorkUnit
2   require 'time'
3
4   attr_accessor :lbl
5   attr_accessor :proxied
6   attr_accessor :my_children
7   attr_accessor :unreadable_children
8
9   def initialize proxied, label, parent
10     @lbl = label
11     @proxied = proxied
12     @parent = parent
13   end
14
15   def label
16     @lbl
17   end
18
19   def uuid
20     get(:uuid)
21   end
22
23   def parent
24     @parent
25   end
26
27   def modified_by_user_uuid
28     get(:modified_by_user_uuid)
29   end
30
31   def owner_uuid
32     get(:owner_uuid)
33   end
34
35   def created_at
36     t = get(:created_at)
37     t = Time.parse(t) if (t.is_a? String)
38     t
39   end
40
41   def started_at
42     t = get(:started_at)
43     t = Time.parse(t) if (t.is_a? String)
44     t
45   end
46
47   def modified_at
48     t = get(:modified_at)
49     t = Time.parse(t) if (t.is_a? String)
50     t
51   end
52
53   def finished_at
54     t = get(:finished_at)
55     t = Time.parse(t) if (t.is_a? String)
56     t
57   end
58
59   def state_label
60     state = get(:state)
61     if ["Running", "RunningOnServer", "RunningOnClient"].include? state
62       "Running"
63     elsif state == 'New'
64       "Not started"
65     else
66       state
67     end
68   end
69
70   def state_bootstrap_class
71     state = state_label
72     case state
73     when 'Complete'
74       'success'
75     when 'Failed', 'Cancelled'
76       'danger'
77     when 'Running', 'RunningOnServer', 'RunningOnClient'
78       'info'
79     else
80       'default'
81     end
82   end
83
84   def success?
85     state = state_label
86     if state == 'Complete'
87       true
88     elsif state == 'Failed' or state == 'Cancelled'
89       false
90     else
91       nil
92     end
93   end
94
95   def child_summary
96     done = 0
97     failed = 0
98     todo = 0
99     running = 0
100     children.each do |c|
101       case c.state_label
102       when 'Complete'
103         done = done+1
104       when 'Failed', 'Cancelled'
105         failed = failed+1
106       when 'Running'
107         running = running+1
108       else
109         todo = todo+1
110       end
111     end
112
113     summary = {}
114     summary[:done] = done
115     summary[:failed] = failed
116     summary[:todo] = todo
117     summary[:running] = running
118     summary
119   end
120
121   def child_summary_str
122     summary = child_summary
123     summary_txt = ''
124
125     if state_label == 'Running'
126       done = summary[:done] || 0
127       running = summary[:running] || 0
128       failed = summary[:failed] || 0
129       todo = summary[:todo] || 0
130       total = done + running + failed + todo
131
132       if total > 0
133         summary_txt += "#{summary[:done]} #{'child'.pluralize(summary[:done])} done,"
134         summary_txt += "#{summary[:failed]} failed,"
135         summary_txt += "#{summary[:running]} running,"
136         summary_txt += "#{summary[:todo]} pending"
137       end
138     end
139     summary_txt
140   end
141
142   def progress
143     state = state_label
144     if state == 'Complete'
145       return 1.0
146     elsif state == 'Failed' or state == 'Cancelled'
147       return 0.0
148     end
149
150     summary = child_summary
151     return 0.0 if summary.nil?
152
153     done = summary[:done] || 0
154     running = summary[:running] || 0
155     failed = summary[:failed] || 0
156     todo = summary[:todo] || 0
157     total = done + running + failed + todo
158     if total > 0
159       (done+failed).to_f / total
160     else
161       0.0
162     end
163   end
164
165   def children
166     []
167   end
168
169   def outputs
170     []
171   end
172
173   def title
174     "process"
175   end
176
177   def has_unreadable_children
178     @unreadable_children
179   end
180
181   def walltime
182     if state_label != "Queued"
183       if started_at
184         ((if finished_at then finished_at else Time.now() end) - started_at)
185       end
186     end
187   end
188
189   def cputime
190     if children.any?
191       children.map { |c|
192         c.cputime
193       }.reduce(:+) || 0
194     else
195       if started_at
196         (runtime_constraints.andand[:min_nodes] || 1).to_i * ((finished_at || Time.now()) - started_at)
197       else
198         0
199       end
200     end
201   end
202
203   def queuedtime
204     if state_label == "Queued"
205       Time.now - Time.parse(created_at.to_s)
206     end
207   end
208
209   def is_running?
210     state_label == 'Running'
211   end
212
213   def is_paused?
214     state_label == 'Paused'
215   end
216
217   def is_finished?
218     state_label.in? ["Complete", "Failed", "Cancelled"]
219   end
220
221   def is_failed?
222     state_label == 'Failed'
223   end
224
225   def runtime_contributors
226     contributors = []
227     if children.any?
228       children.each{|c| contributors << c.runtime_contributors}
229     else
230       contributors << self
231     end
232     # Avoid counting reused containers
233     if started_at
234       contributors.flatten.reject{|c| c.started_at ? c.started_at < started_at : true}
235     else
236       contributors.flatten
237     end
238   end
239
240   def runningtime
241     ApplicationController.helpers.determine_wallclock_runtime(if children.any? then runtime_contributors else [self] end)
242   end
243
244   def show_runtime
245     walltime = 0
246     running_time = runningtime
247     if started_at
248       walltime = if finished_at then (finished_at - started_at) else (Time.now - started_at) end
249     end
250     resp = '<p>'
251
252     if started_at
253       resp << "This #{title} started at "
254       resp << ApplicationController.helpers.render_localized_date(started_at)
255       resp << ". It "
256       if state_label == 'Complete'
257         resp << "completed in "
258       elsif state_label == 'Failed'
259          resp << "failed after "
260       else
261         resp << "has been active for "
262       end
263
264       if walltime > running_time
265         resp << ApplicationController.helpers.render_time(walltime, false)
266       else
267         resp << ApplicationController.helpers.render_time(running_time, false)
268       end
269
270       if finished_at
271         resp << " at "
272         resp << ApplicationController.helpers.render_localized_date(finished_at)
273       end
274       resp << "."
275     else
276       if state_label
277         resp << "This #{title} is "
278         resp << if state_label == 'Running' then 'active' else state_label.downcase end
279         resp << "."
280       end
281     end
282
283     if is_failed?
284       resp << " Check the Log tab for more detail about why it failed."
285     end
286     resp << "</p>"
287
288     resp << "<p>"
289     if state_label
290       resp << "It "
291       if state_label == 'Running'
292         resp << "has run"
293       else
294         resp << "ran"
295       end
296       resp << " for "
297
298       cpu_time = cputime
299
300       resp << ApplicationController.helpers.render_time(running_time, false)
301       if (walltime - running_time) > 0
302         resp << "("
303         resp << ApplicationController.helpers.render_time(walltime - running_time, false)
304         resp << "queued)"
305       end
306       if cpu_time == 0
307         resp << "."
308       else
309         resp << " and used "
310         resp << ApplicationController.helpers.render_time(cpu_time, false)
311         resp << " of node allocation time ("
312         resp << (cpu_time/running_time).round(1).to_s
313         resp << "&Cross; scaling)."
314       end
315     end
316     resp << "</p>"
317
318     resp
319   end
320
321   def log_object_uuids
322     [uuid]
323   end
324
325   def live_log_lines(limit)
326     Log.where(object_uuid: log_object_uuids).
327       order("created_at DESC").
328       limit(limit).
329       with_count('none').
330       select { |log| log.properties[:text].is_a? String }.
331       reverse.
332       flat_map { |log| log.properties[:text].split("\n") }
333   end
334
335   protected
336
337   def get key, obj=@proxied
338     if obj.respond_to? key
339       obj.send(key)
340     elsif obj.is_a?(Hash)
341       obj[key] || obj[key.to_s]
342     end
343   end
344 end