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