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