Merge branch '8876-work-unit' into 8650-container-work-unit
[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 title
153     "process"
154   end
155
156   def has_unreadable_children
157     @unreadable_children
158   end
159
160   def readable?
161     resource_class = ArvadosBase::resource_class_for_uuid(uuid)
162     resource_class.where(uuid: [uuid]).first rescue nil
163   end
164
165   def link_to_log
166     if state_label.in? ["Complete", "Failed", "Cancelled"]
167       lc = log_collection
168       if lc
169         logCollection = Collection.find? lc
170         if logCollection
171           ApplicationController.helpers.link_to("Log", "#{uri}#Log")
172         else
173           "Log unavailable"
174         end
175       end
176     elsif state_label == "Running"
177       if readable?
178         ApplicationController.helpers.link_to("Log", "#{uri}#Log")
179       else
180         "Log unavailable"
181       end
182     end
183   end
184
185   def walltime
186     if state_label != "Queued"
187       if started_at
188         ((if finished_at then finished_at else Time.now() end) - started_at)
189       end
190     end
191   end
192
193   def cputime
194     if state_label != "Queued"
195       if started_at
196         (runtime_constraints.andand[:min_nodes] || 1) * ((finished_at || Time.now()) - started_at)
197       end
198     end
199   end
200
201   def queuedtime
202     if state_label == "Queued"
203       Time.now - Time.parse(created_at.to_s)
204     end
205   end
206
207   def show_child_summary
208     if state_label == "Running"
209       if child_summary
210         child_summary_str
211       end
212     end
213   end
214
215   def is_running?
216     state_label == 'Running'
217   end
218
219   def is_paused?
220     state_label == 'Paused'
221   end
222
223   def is_finished?
224     state_label.in? ["Complete", "Failed", "Cancelled"]
225   end
226
227   def is_failed?
228     state_label == 'Failed'
229   end
230
231   def show_runtime
232     runningtime = ApplicationController.helpers.determine_wallclock_runtime(if children.any? then children else [@proxied] end)
233
234     walltime = 0
235     if started_at
236       walltime = if finished_at then (finished_at - started_at) else (Time.now - started_at) end
237     end
238
239     resp = '<p>'
240
241     if started_at
242       resp << "This #{title} started at "
243       resp << ApplicationController.helpers.render_localized_date(started_at)
244       resp << ". It "
245       if state_label == 'Complete'
246         resp << "completed in "
247       elsif state_label == 'Failed'
248          resp << "failed after "
249       else
250         resp << "has been active for "
251       end
252
253       if walltime > runningtime
254         resp << ApplicationController.helpers.render_time(walltime, false)
255       else
256        resp << ApplicationController.helpers.render_time(runningtime, false)
257       end
258
259       if finished_at
260         resp << " at "
261         resp << ApplicationController.helpers.render_localized_date(finished_at)
262       end
263       resp << "."
264     else
265       if state_label
266         resp << "This #{title} is "
267         resp << if state_label == 'Running' then 'active' else state_label.downcase end
268         resp << "."
269       end
270     end
271
272     if is_failed?
273       resp << " Check the Log tab for more detail about why it failed."
274     end
275     resp << "</p>"
276
277     resp << "<p>"
278     if state_label
279       resp << "It "
280       if state_label == 'Running'
281         resp << "has run"
282       else
283         resp << "ran"
284       end
285       resp << " for "
286
287       cpu_time = 0
288       if children.any?
289         cpu_time = children.map { |c|
290           if c.started_at
291              (c.runtime_constraints.andand[:min_nodes] || 1) * ((c.finished_at || Time.now()) - c.started_at)
292           else
293             0
294           end
295         }.reduce(:+) || 0
296       else
297         if started_at
298           cpu_time = (runtime_constraints.andand[:min_nodes] || 1) * ((finished_at || Time.now()) - started_at)
299         end
300       end
301
302       resp << ApplicationController.helpers.render_time(runningtime, false)
303       if (walltime - runningtime) > 0
304         resp << "("
305         resp << ApplicationController.helpers.render_time(walltime - runningtime, false)
306         resp << "queued)"
307       end
308       if cpu_time == 0
309         resp << "."
310       else
311         resp << " and used "
312         resp << ApplicationController.helpers.render_time(cpu_time, false)
313         resp << " of node allocation time ("
314         resp << (cpu_time/runningtime).round(1).to_s
315         resp << "&Cross; scaling)."
316       end
317     end
318     resp << "</p>"
319
320     resp
321   end
322
323   protected
324
325   def get key
326     if @proxied.respond_to? key
327       @proxied.send(key)
328     elsif @proxied.is_a?(Hash)
329       @proxied[key]
330     end
331   end
332 end