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