12902: Label CRs "Cancelled" if finalized with incomplete container.
[arvados.git] / apps / workbench / app / models / proxy_work_unit.rb
1 # Copyright (C) The Arvados Authors. All rights reserved.
2 #
3 # SPDX-License-Identifier: AGPL-3.0
4
5 class ProxyWorkUnit < WorkUnit
6   require 'time'
7
8   attr_accessor :lbl
9   attr_accessor :proxied
10   attr_accessor :my_children
11   attr_accessor :unreadable_children
12
13   def initialize proxied, label, parent
14     @lbl = label
15     @proxied = proxied
16     @parent = parent
17   end
18
19   def label
20     @lbl
21   end
22
23   def uuid
24     get(:uuid)
25   end
26
27   def parent
28     @parent
29   end
30
31   def modified_by_user_uuid
32     get(:modified_by_user_uuid)
33   end
34
35   def owner_uuid
36     get(:owner_uuid)
37   end
38
39   def created_at
40     t = get(:created_at)
41     t = Time.parse(t) if (t.is_a? String)
42     t
43   end
44
45   def started_at
46     t = get(:started_at)
47     t = Time.parse(t) if (t.is_a? String)
48     t
49   end
50
51   def modified_at
52     t = get(:modified_at)
53     t = Time.parse(t) if (t.is_a? String)
54     t
55   end
56
57   def finished_at
58     t = get(:finished_at)
59     t = Time.parse(t) if (t.is_a? String)
60     t
61   end
62
63   def state_label
64     state = get(:state)
65     if ["Running", "RunningOnServer", "RunningOnClient"].include? state
66       "Running"
67     elsif state == 'New'
68       "Not started"
69     else
70       state
71     end
72   end
73
74   def state_bootstrap_class
75     state = state_label
76     case state
77     when 'Complete'
78       'success'
79     when 'Failed', 'Cancelled'
80       'danger'
81     when 'Running', 'RunningOnServer', 'RunningOnClient'
82       'info'
83     else
84       'default'
85     end
86   end
87
88   def success?
89     state = state_label
90     if state == 'Complete'
91       true
92     elsif state == 'Failed' or state == 'Cancelled'
93       false
94     else
95       nil
96     end
97   end
98
99   def child_summary
100     done = 0
101     failed = 0
102     todo = 0
103     running = 0
104     children.each do |c|
105       case c.state_label
106       when 'Complete'
107         done = done+1
108       when 'Failed', 'Cancelled'
109         failed = failed+1
110       when 'Running'
111         running = running+1
112       else
113         todo = todo+1
114       end
115     end
116
117     summary = {}
118     summary[:done] = done
119     summary[:failed] = failed
120     summary[:todo] = todo
121     summary[:running] = running
122     summary
123   end
124
125   def child_summary_str
126     summary = child_summary
127     summary_txt = ''
128
129     if state_label == 'Running'
130       done = summary[:done] || 0
131       running = summary[:running] || 0
132       failed = summary[:failed] || 0
133       todo = summary[:todo] || 0
134       total = done + running + failed + todo
135
136       if total > 0
137         summary_txt += "#{summary[:done]} #{'child'.pluralize(summary[:done])} done,"
138         summary_txt += "#{summary[:failed]} failed,"
139         summary_txt += "#{summary[:running]} running,"
140         summary_txt += "#{summary[:todo]} pending"
141       end
142     end
143     summary_txt
144   end
145
146   def progress
147     state = state_label
148     if state == 'Complete'
149       return 1.0
150     elsif state == 'Failed' or state == 'Cancelled'
151       return 0.0
152     end
153
154     summary = child_summary
155     return 0.0 if summary.nil?
156
157     done = summary[:done] || 0
158     running = summary[:running] || 0
159     failed = summary[:failed] || 0
160     todo = summary[:todo] || 0
161     total = done + running + failed + todo
162     if total > 0
163       (done+failed).to_f / total
164     else
165       0.0
166     end
167   end
168
169   def children
170     []
171   end
172
173   def outputs
174     []
175   end
176
177   def title
178     "process"
179   end
180
181   def has_unreadable_children
182     @unreadable_children
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 children.any?
195       children.map { |c|
196         c.cputime
197       }.reduce(:+) || 0
198     else
199       if started_at
200         (runtime_constraints.andand[:min_nodes] || 1).to_i * ((finished_at || Time.now()) - started_at)
201       else
202         0
203       end
204     end
205   end
206
207   def queuedtime
208     if state_label == "Queued"
209       Time.now - Time.parse(created_at.to_s)
210     end
211   end
212
213   def is_running?
214     state_label == 'Running'
215   end
216
217   def is_paused?
218     state_label == 'Paused'
219   end
220
221   def is_finished?
222     state_label.in? ["Complete", "Failed", "Cancelled"]
223   end
224
225   def is_failed?
226     state_label == 'Failed'
227   end
228
229   def runtime_contributors
230     contributors = []
231     if children.any?
232       children.each{|c| contributors << c.runtime_contributors}
233     else
234       contributors << self
235     end
236     contributors.flatten
237   end
238
239   def runningtime
240     ApplicationController.helpers.determine_wallclock_runtime runtime_contributors
241   end
242
243   def show_runtime
244     walltime = 0
245     running_time = runningtime
246     if started_at
247       walltime = if finished_at then (finished_at - started_at) else (Time.now - started_at) end
248     end
249     resp = '<p>'
250
251     if started_at
252       resp << "This #{title} started at "
253       resp << ApplicationController.helpers.render_localized_date(started_at)
254       resp << ". It "
255       if state_label == 'Complete'
256         resp << "completed in "
257       elsif state_label == 'Failed'
258          resp << "failed after "
259       else
260         resp << "has been active for "
261       end
262
263       resp << ApplicationController.helpers.render_time(walltime, false)
264
265       if finished_at
266         resp << " at "
267         resp << ApplicationController.helpers.render_localized_date(finished_at)
268       end
269       resp << "."
270     else
271       if state_label
272         resp << "This #{title} is "
273         resp << if state_label == 'Running' then 'active' else state_label.downcase end
274         resp << "."
275       end
276     end
277
278     if is_failed?
279       resp << " Check the Log tab for more detail about why it failed."
280     end
281     resp << "</p>"
282
283     resp << "<p>"
284     if state_label
285       resp << "It has runtime of "
286
287       cpu_time = cputime
288
289       resp << ApplicationController.helpers.render_time(running_time, false)
290       if (walltime - running_time) > 0
291         resp << "("
292         resp << ApplicationController.helpers.render_time(walltime - running_time, false)
293         resp << "queued)"
294       end
295       if cpu_time == 0
296         resp << "."
297       else
298         resp << " and used "
299         resp << ApplicationController.helpers.render_time(cpu_time, false)
300         resp << " of node allocation time ("
301         resp << (cpu_time/running_time).round(1).to_s
302         resp << "&Cross; scaling)."
303       end
304     end
305     resp << "</p>"
306
307     resp
308   end
309
310   def log_object_uuids
311     [uuid]
312   end
313
314   def live_log_lines(limit)
315     Log.where(object_uuid: log_object_uuids).
316       order("created_at DESC").
317       limit(limit).
318       with_count('none').
319       select { |log| log.properties[:text].is_a? String }.
320       reverse.
321       flat_map { |log| log.properties[:text].split("\n") }
322   end
323
324   protected
325
326   def get key, obj=@proxied
327     if obj.respond_to? key
328       obj.send(key)
329     elsif obj.is_a?(Hash)
330       obj[key] || obj[key.to_s]
331     end
332   end
333 end