Merge branch 'master' 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 is_running?
219     state_label == 'Running'
220   end
221
222   def is_paused?
223     state_label == 'Paused'
224   end
225
226   def is_finished?
227     state_label.in? ["Complete", "Failed", "Cancelled"]
228   end
229
230   def is_failed?
231     state_label == 'Failed'
232   end
233
234   def show_runtime
235     runningtime = ApplicationController.helpers.determine_wallclock_runtime(if children.any? then children else [@proxied] end)
236
237     walltime = 0
238     if started_at
239       walltime = if finished_at then (finished_at - started_at) else (Time.now - started_at) end
240     end
241
242     resp = '<p>'
243
244     if started_at
245       resp << "This #{title} started at "
246       resp << ApplicationController.helpers.render_localized_date(started_at)
247       resp << ". It "
248       if state_label == 'Complete'
249         resp << "completed in "
250       elsif state_label == 'Failed'
251          resp << "failed after "
252       else
253         resp << "has been active for "
254       end
255
256       if walltime > runningtime
257         resp << ApplicationController.helpers.render_time(walltime, false)
258       else
259        resp << ApplicationController.helpers.render_time(runningtime, false)
260       end
261
262       if finished_at
263         resp << " at "
264         resp << ApplicationController.helpers.render_localized_date(finished_at)
265       end
266       resp << "."
267     else
268       if state_label
269         resp << "This #{title} is "
270         resp << if state_label == 'Running' then 'active' else state_label.downcase end
271         resp << "."
272       end
273     end
274
275     if is_failed?
276       resp << " Check the Log tab for more detail about why it failed."
277     end
278     resp << "</p>"
279
280     resp << "<p>"
281     if state_label
282       resp << "It "
283       if state_label == 'Running'
284         resp << "has run"
285       else
286         resp << "ran"
287       end
288       resp << " for "
289
290       cpu_time = 0
291       if children.any?
292         cpu_time = children.map { |c|
293           if c.started_at
294              (c.runtime_constraints.andand[:min_nodes] || 1) * ((c.finished_at || Time.now()) - c.started_at)
295           else
296             0
297           end
298         }.reduce(:+) || 0
299       else
300         if started_at
301           cpu_time = (runtime_constraints.andand[:min_nodes] || 1) * ((finished_at || Time.now()) - started_at)
302         end
303       end
304
305       resp << ApplicationController.helpers.render_time(runningtime, false)
306       if (walltime - runningtime) > 0
307         resp << "("
308         resp << ApplicationController.helpers.render_time(walltime - runningtime, false)
309         resp << "queued)"
310       end
311       if cpu_time == 0
312         resp << "."
313       else
314         resp << " and used "
315         resp << ApplicationController.helpers.render_time(cpu_time, false)
316         resp << " of node allocation time ("
317         resp << (cpu_time/runningtime).round(1).to_s
318         resp << "&Cross; scaling)."
319       end
320     end
321     resp << "</p>"
322
323     resp
324   end
325
326   protected
327
328   def get key
329     if @proxied.respond_to? key
330       @proxied.send(key)
331     elsif @proxied.is_a?(Hash)
332       @proxied[key]
333     end
334   end
335 end