Merge branch '8650-container-work-unit' into 9318-dashboard-uses-work-units
[arvados.git] / apps / workbench / app / models / proxy_work_unit.rb
index e7806783eb0c41dc2f7b2e363d447680d619ab9b..d817bba3a4a7ef29d9cd6f0d90db48f5cf3455fd 100644 (file)
@@ -1,46 +1,58 @@
 class ProxyWorkUnit < WorkUnit
+  require 'time'
+
   attr_accessor :lbl
   attr_accessor :proxied
+  attr_accessor :my_children
+  attr_accessor :unreadable_children
 
   def initialize proxied, label
-    self.lbl = label
-    self.proxied = proxied
+    @lbl = label
+    @proxied = proxied
   end
 
   def label
-    self.lbl
+    @lbl
   end
 
   def uuid
-    self.proxied[:uuid]
+    get(:uuid)
   end
 
   def modified_by_user_uuid
-    self.proxied[:modified_by_user_uuid]
+    get(:modified_by_user_uuid)
   end
 
   def created_at
-    self.proxied[:created_at]
+    t = get(:created_at)
+    t = Time.parse(t) if (t.andand.class == String)
+    t
   end
 
   def started_at
-    self.proxied[:started_at]
+    t = get(:started_at)
+    t = Time.parse(t) if (t.andand.class == String)
+    t
   end
 
   def finished_at
-    self.proxied[:finished_at]
+    t = get(:finished_at)
+    t = Time.parse(t) if (t.andand.class == String)
+    t
   end
 
   def state_label
-    if ["Running", "RunningOnServer", "RunningOnClient"].include? self.proxied[:state].to_s
+    state = get(:state)
+    if ["Running", "RunningOnServer", "RunningOnClient"].include? state
       "Running"
     else
-      self.proxied[:state].to_s
+      state
     end
   end
 
   def state_bootstrap_class
-    case self.proxied[:state]
+    state = get(:state)
+    case state
     when 'Complete'
       'success'
     when 'Failed', 'Cancelled'
@@ -53,40 +65,303 @@ class ProxyWorkUnit < WorkUnit
   end
 
   def success?
-    if self.proxied[:state] == 'Complete'
+    state = get(:state)
+    if state == 'Complete'
       true
-    elsif self.proxied[:state] == 'Failed'
+    elsif state == 'Failed' or state == 'Cancelled'
       false
     else
       nil
     end
   end
 
-  def parameters
-    self.proxied[:script_parameters]
+  def child_summary
+    done = 0
+    failed = 0
+    todo = 0
+    running = 0
+    children.each do |c|
+      case c.state_label
+      when 'Complete'
+        done = done+1
+      when 'Failed', 'Cancelled'
+        failed = failed+1
+      when 'Running'
+        running = running+1
+      else
+        todo = todo+1
+      end
+    end
+
+    summary = {}
+    summary[:done] = done
+    summary[:failed] = failed
+    summary[:todo] = todo
+    summary[:running] = running
+    summary
   end
 
-  def script
-    self.proxied[:script]
+  def child_summary_str
+    summary = child_summary
+    summary_txt = ''
+
+    if state_label == 'Running'
+      done = summary[:done] || 0
+      running = summary[:running] || 0
+      failed = summary[:failed] || 0
+      todo = summary[:todo] || 0
+      total = done + running + failed + todo
+
+      if total > 0
+        summary_txt += "#{summary[:done]} #{'child'.pluralize(summary[:done])} done,"
+        summary_txt += "#{summary[:failed]} failed,"
+        summary_txt += "#{summary[:running]} running,"
+        summary_txt += "#{summary[:todo]} pending"
+      end
+    end
+    summary_txt
   end
 
-  def repository
-    self.proxied[:repository]
+  def progress
+    state = get(:state)
+    if state == 'Complete'
+      return 1.0
+    elsif state == 'Failed' or state == 'Cancelled'
+      return 0.0
+    end
+
+    summary = child_summary
+    return 0.0 if summary.nil?
+
+    done = summary[:done] || 0
+    running = summary[:running] || 0
+    failed = summary[:failed] || 0
+    todo = summary[:todo] || 0
+    total = done + running + failed + todo
+    if total > 0
+      (done+failed).to_f / total
+    else
+      0.0
+    end
   end
 
-  def script_version
-    self.proxied[:script_version]
+  def children
+    []
   end
 
-  def supplied_script_version
-    self.proxied[:supplied_script_version]
+  def outputs
+    items = []
+    children.each do |c|
+      items << c.output if c.output
+    end
+    if !items.any?
+      items << get(:output) if get(:output)
+    end
+    items
   end
 
-  def runtime_constraints
-    self.proxied[:runtime_constraints]
+  def title
+    "process"
   end
 
-  def children
-    []
+  def has_unreadable_children
+    @unreadable_children
+  end
+
+  def readable?
+    resource_class = ArvadosBase::resource_class_for_uuid(uuid)
+    resource_class.where(uuid: [uuid]).first rescue nil
+  end
+
+  def link_to_log
+    if state_label.in? ["Complete", "Failed", "Cancelled"]
+      lc = log_collection
+      if lc
+        logCollection = Collection.find? lc
+        if logCollection
+          ApplicationController.helpers.link_to("Log", "#{uri}#Log")
+        else
+          "Log unavailable"
+        end
+      end
+    elsif state_label == "Running"
+      if readable?
+        ApplicationController.helpers.link_to("Log", "#{uri}#Log")
+      else
+        "Log unavailable"
+      end
+    end
+  end
+
+  def walltime
+    if state_label != "Queued"
+      if started_at
+        ((if finished_at then finished_at else Time.now() end) - started_at)
+      end
+    end
+  end
+
+  def cputime
+    if state_label != "Queued"
+      if started_at
+        (runtime_constraints.andand[:min_nodes] || 1) * ((finished_at || Time.now()) - started_at)
+      end
+    end
+  end
+
+  def queuedtime
+    if state_label == "Queued"
+      Time.now - Time.parse(created_at.to_s)
+    end
+  end
+
+  def show_child_summary
+    if state_label == "Running"
+      if child_summary
+        child_summary_str
+      end
+    end
+  end
+
+  def is_running?
+    state_label == 'Running'
+  end
+
+  def is_paused?
+    state_label == 'Paused'
+  end
+
+  def is_finished?
+    state_label.in? ["Complete", "Failed", "Cancelled"]
+  end
+
+  def is_failed?
+    state_label == 'Failed'
+  end
+
+  def ran_for_str
+    ran_for = nil
+    if state_label
+      ran_for = "It "
+      if state_label == 'Running'
+        ran_for << "has run"
+      else
+        ran_for << "ran"
+      end
+      ran_for << " for"
+    end
+    ran_for
+  end
+
+  def started_and_active_for_str
+    active_for = nil
+
+    if started_at
+      active_for_1 = "This #{title} started at "
+      active_for_2 = "It "
+      if state_label == 'Complete'
+        active_for_2 << "completed in "
+      elsif state_label == 'Failed'
+        active_for_2 << "failed after "
+      else
+        active_for_2 << "has been active for "
+      end
+      [active_for_1, active_for_2]
+    end
+  end
+
+  def show_runtime
+    runningtime = ApplicationController.helpers.determine_wallclock_runtime(children)
+
+    walltime = 0
+    if started_at
+      walltime = if finished_at then (finished_at - started_at) else (Time.now - started_at) end
+    end
+
+    resp = '<p>'
+
+    if started_at
+      resp << "This #{title} started at "
+      resp << ApplicationController.helpers.render_localized_date(started_at)
+      resp << ". It "
+      if state_label == 'Complete'
+        resp << "completed in "
+      elsif state_label == 'Failed'
+         resp << "failed after "
+      else
+        resp << "has been active for "
+      end
+
+      if walltime > runningtime
+        resp << ApplicationController.helpers.render_time(walltime, false)
+      else
+       resp << ApplicationController.helpers.render_time(runningtime, false)
+      end
+
+      if finished_at
+        resp << " at "
+        resp << ApplicationController.helpers.render_localized_date(finished_at)
+      end
+      resp << "."
+    else
+      if state_label
+        resp << "This #{title} is "
+        resp << if state_label == 'Running' then 'active' else state_label.downcase end
+        resp << "."
+      end
+    end
+
+    if is_failed?
+      resp << " Check the Log tab for more detail about why it failed."
+    end
+    resp << "</p>"
+
+    resp << "<p>"
+    if state_label
+      resp << "It "
+      if state_label == 'Running'
+        resp << "has run"
+      else
+        resp << "ran"
+      end
+      resp << " for "
+
+      cpu_time = children.map { |c|
+        if c.started_at
+           (c.runtime_constraints.andand[:min_nodes] || 1) * ((c.finished_at || Time.now()) - c.started_at)
+        else
+          0
+        end
+      }.reduce(:+) || 0
+
+      resp << ApplicationController.helpers.render_time(runningtime, false)
+      if (walltime - runningtime) > 0
+        resp << "("
+        resp << ApplicationController.helpers.render_time(walltime - runningtime, false)
+        resp << "queued)"
+      end
+      if cpu_time == 0
+        resp << "."
+      else
+        resp << " and used "
+        resp << ApplicationController.helpers.render_time(cpu_time, false)
+        resp << " of node allocation time ("
+        resp << (cpu_time/runningtime).round(1).to_s
+        resp << "&Cross; scaling)."
+      end
+    end
+    resp << "</p>"
+
+    resp
+  end
+
+  protected
+
+  def get key
+    if @proxied.respond_to? key
+      @proxied.send(key)
+    elsif @proxied.is_a?(Hash)
+      @proxied[key]
+    end
   end
 end