require 'celluloid/current'

class Eye::SystemResources

  # cached system resources
  class << self

    def memory(pid)
      if mem = cache.proc_mem(pid)
        mem.resident
      end
    end

    def cpu(pid)
      if cpu = cache.proc_cpu(pid)
        cpu.percent * 100
      end
    end

    def children(parent_pid)
      cache.children(parent_pid)
    end

    # unixtime
    def start_time(pid)
      if cpu = cache.proc_cpu(pid)
        cpu.start_time.to_i / 1000
      end
    end

    # total cpu usage in seconds
    def cputime(pid)
      if cpu = cache.proc_cpu(pid)
        cpu.total.to_f / 1000
      end
    end

    # last child in a children tree
    def leaf_child(pid)
      if dc = deep_children(pid)
        dc.detect do |child|
          args = Eye::Sigar.proc_args(child)[0] rescue ''
          !args.start_with?('logger') && child != pid
        end
      end
    end

    def deep_children(pid)
      Array(pid_or_children(pid)).flatten.sort_by(&:-@)
    end

    def pid_or_children(pid)
      c = children(pid)
      if !c || c.empty?
        pid
      else
        c.map { |ppid| pid_or_children(ppid) }
      end
    end

    def args(pid)
      Eye::Sigar.proc_args(pid).join(' ').strip rescue '-'
    end

    def resources(pid)
      { memory: memory(pid),
        cpu: cpu(pid),
        start_time: start_time(pid),
        pid: pid }
    end

    def cache
      Celluloid::Actor[:system_resources_cache]
    end

  end

  class Cache

    include Celluloid

    attr_reader :expire

    def initialize
      clear
      setup_expire
    end

    def setup_expire(expire = 5)
      @expire = expire
      @timer.cancel if @timer
      @timer = every(@expire) { clear }
    end

    def clear
      @memory = {}
      @cpu = {}
      @ppids = {}
    end

    def proc_mem(pid)
      @memory[pid] ||= Eye::Sigar.proc_mem(pid) if pid

    rescue ArgumentError
      # when incorrect PID, just skip
    end

    def proc_cpu(pid)
      @cpu[pid] ||= Eye::Sigar.proc_cpu(pid) if pid

    rescue ArgumentError
      # when incorrect PID, just skip
    end

    def children(pid)
      if pid
        @ppids[pid] ||= Eye::Sigar.proc_list("State.Ppid.eq=#{pid}")
      else
        []
      end
    end

  end

  # Setup global sigar singleton here
  Cache.supervise(as: :system_resources_cache)

end
