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