class Sys::ProcTable

The ProcTable class encapsulates process table information.

The ProcTable class encapsulates process table information.

The ProcTable class encapsulates process table information.

The ProcTable class encapsulates process table information

Constants

COMMLEN
CTL_KERN
KERN_PROCARGS
KERN_PROCARGS2
KERN_PROC_PID
KERN_PROC_PROC
KI_EMULNAMELEN
KI_NGROUPS
KI_NSPARE_INT
KI_NSPARE_LONG
KI_NSPARE_PTR
LOCKNAMELEN
LOGNAMELEN
MAXCOMLEN
MAXPATHLEN
MAXTHREADNAMESIZE
OCOMMLEN
POSIX_ARG_MAX
PRNODEV
PROC_PIDLISTTHREADS
PROC_PIDPATHINFO_MAXSIZE
PROC_PIDTASKALLINFO

There is no constructor

PROC_PIDTHREADINFO
PROC_STRUCT_FIELD_MAP
PR_MFLAGS

prmap_t pr_mflags

ProcTableMapStruct
ProcTableStruct

Add a couple aliases to make it similar to Linux

SIDL
SLOCK
SRUN
SSLEEP
SSTOP
SWAIT
SZOMB
S_IFCHR
ThreadInfoStruct
VERSION

The version of the sys-proctable library

WMESGLEN

Public Class Methods

fields() click to toggle source

Returns an array of fields that each ProcTableStruct will contain. This may be useful if you want to know in advance what fields are available without having to perform at least one read of the /proc table.

Example:

Sys::ProcTable.fields.each do |field|
   puts "Field: #{field}"
end
# File lib/aix/sys/proctable.rb, line 454
def self.fields
  @fields.map{ |f| f.to_s }
end
ps(**kwargs) { |struct| ... } click to toggle source

In block form, yields a ProcTableStruct for each process entry that you have rights to. This method returns an array of ProcTableStruct's in non-block form.

If a pid is provided, then only a single ProcTableStruct is yielded or returned, or nil if no process information is found for that pid.

Example:

# Iterate over all processes
ProcTable.ps do |proc_info|
   p proc_info
end

# Print process table information for only pid 1001
p ProcTable.ps(pid: 1001)
# File lib/aix/sys/proctable.rb, line 207
def self.ps(**kwargs)
  pid = kwargs[:pid]

  raise TypeError unless pid.is_a?(Numeric) if pid

  array  = block_given? ? nil : []
  struct = nil

  Dir.foreach("/proc") do |file|
    next if file =~ /\D/ # Skip non-numeric entries under /proc

    # Only return information for a given pid, if provided
    if pid
      next unless file.to_i == pid
    end

    # Skip over any entries we don't have permissions to read
    next unless File.readable?("/proc/#{file}/psinfo")

    psinfo = IO.read("/proc/#{file}/psinfo") rescue next

    psinfo_array = psinfo.unpack(@psinfo_pack_directive)

    struct = ProcTableStruct.new

    struct.flag    = psinfo_array[0]         # pr_flag
    struct.flag2   = psinfo_array[1]         # pr_flag2
    struct.nlwp    = psinfo_array[2]         # pr_nlwp
    # pr__pad1
    struct.uid     = psinfo_array[4]         # pr_uid
    struct.euid    = psinfo_array[5]         # pr_euid
    struct.gid     = psinfo_array[6]         # pr_gid
    struct.egid    = psinfo_array[7]         # pr_egid
    struct.pid     = psinfo_array[8]         # pr_pid
    struct.ppid    = psinfo_array[9]         # pr_ppid
    struct.pgid    = psinfo_array[10]        # pr_pgid
    struct.sid     = psinfo_array[11]        # pr_sid
    struct.ttydev  = psinfo_array[12]        # pr_ttydev

    # convert from 64-bit dev_t to 32-bit dev_t and then map the device
    # number to a name
    ttydev = struct.ttydev
    ttydev = (((ttydev & 0x0000FFFF00000000) >> 16) | (ttydev & 0xFFFF))
    struct.s_ttydev = @devs.has_key?(ttydev) ? @devs[ttydev] : '-'

    struct.addr    = psinfo_array[13]        # pr_addr
    struct.size    = psinfo_array[14] * 1024 # pr_size
    struct.rssize  = psinfo_array[15] * 1024 # pr_rssize
    struct.start   = Time.at(psinfo_array[16], psinfo_array[17]) # pr_start
    # skip pr_start.__pad
    struct.time    = psinfo_array[19]        # pr_time
    # skip pr_time.tv_nsec and pr_time.__pad
    struct.cid     = psinfo_array[22]        # pr_cid
    # skip pr__pad2
    struct.argc    = psinfo_array[24]        # pr_argc
    struct.argv    = psinfo_array[25]        # pr_argv
    struct.envp    = psinfo_array[26]        # pr_envp
    struct.fname   = psinfo_array[27]        # pr_fname
    struct.psargs  = psinfo_array[28]        # pr_psargs
    # skip pr__pad

    ### lwpsinfo_t info

    struct.lwpid   = psinfo_array[37]        # pr_lwpid
    # skip pr_addr
    struct.wchan   = psinfo_array[39]        # pr_wchan
    # skip pr_flag
    struct.wtype   = psinfo_array[41]        # pr_wtype
    struct.state   = psinfo_array[42]        # pr_state
    struct.sname   = psinfo_array[43]        # pr_sname
    struct.nice    = psinfo_array[44]        # pr_nice
    struct.pri     = psinfo_array[45]        # pr_pri
    struct.policy  = psinfo_array[46]        # pr_policy
    struct.clname  = psinfo_array[47]        # pr_clname
    struct.onpro   = psinfo_array[48]        # pr_onpro
    struct.bindpro = psinfo_array[49]        # pr_bindpro
    struct.ptid    = psinfo_array[50]        # pr_ptid
    # skip pr__pad1
    # skip pr__pad

    # Get the full command line out of /proc/<pid>/as.
    begin
      File.open("/proc/#{file}/as", 'rb') do |fd|
        np = fd.sysseek(struct.argv, IO::SEEK_SET)

        if np != struct.argv
          raise Error, "argv seek to #{struct.argv}, result #{np}", caller
        end

        argv = fd.sysread(4).unpack('L')[0]

        np = fd.sysseek(argv, IO::SEEK_SET)

        if np != argv
          raise Error, "*argv seek to #{argv}, result #{np}", caller
        end

        argv = fd.sysread(4 * struct.argc).unpack("L#{struct.argc}")

        struct.cmd_args = []

        argv.each_with_index do |address, i|
          np = fd.sysseek(address, IO::SEEK_SET)

          if np != address
            raise Error, "argv[#{i}] seek to #{address}, result #{np}",
                  caller
          end

          data = fd.sysread(512)[/^[^\0]*/] # Null strip
          struct.cmd_args << data
        end

        # Get the environment hash associated with the process.
        struct.environ = {}

        # First have to go to the address given by struct.envp. That will
        # give us the address of the environment pointer array.

        np = fd.sysseek(struct.envp, IO::SEEK_SET)

        if np != struct.envp
          raise Error, "envp seek to #{struct.envp}, result #{np}", caller
        end

        envloc = fd.sysread(4).unpack('L')[0]
        n = 0

        loop do
          np = fd.sysseek(envloc, IO::SEEK_SET)

          if np != envloc
            raise Error, "envp[#{n}] seek to #{envloc}, result #{np}",
                  caller
          end

          envp = fd.sysread(4).unpack("L")[0]
          break if envp.zero?
          np = fd.sysseek(envp, IO::SEEK_SET)
          data = fd.sysread(1024)[/^[^\0]*/] # Null strip
          key, value = data.split('=')
          struct.environ[key] = value
          envloc += 4
          n += 1
        end
      end
    rescue Errno::EACCES, Errno::EOVERFLOW, EOFError
      # Skip this if we don't have proper permissions, if there's
      # no associated environment, or if there's a largefile issue.
    rescue Errno::ENOENT
      next # The process has terminated. Bail out!
    end

    # Information from /proc/<pid>/fd. This returns an array of
    # numeric file descriptors used by the process.
    struct.fd = Dir["/proc/#{file}/fd/*"].map { |f| File.basename(f).to_i }

    # Use the cmd_args as the cmdline if available. Otherwise use
    # the psargs. This struct member is provided to provide a measure
    # of consistency with the other platform implementations.
    if struct.cmd_args.nil? || struct.cmd_args.empty?
      struct.cmdline = struct.psargs
    else
      struct.cmdline = struct.cmd_args.join(' ')
    end

    # get current working directory from /proc/<pid>/cwd
    struct.cwd = File.readlink("/proc/#{file}/cwd") rescue nil

    # get virtual address map from /proc/<pid>/map
    begin
      struct.map = []

      File.open("/proc/#{file}/map", 'rb') do |fd|
        loop do
          prmap_array = fd.sysread(176).unpack(@prmap_pack_directive)
          break if prmap_array[0].zero?

          map_struct = ProcTableMapStruct.new

          map_struct.size     = prmap_array[0]  # pr_size
          map_struct.vaddr    = prmap_array[1]  # pr_vaddr
          map_struct.mapname  = prmap_array[2]  # pr_mapname
          map_struct.off      = prmap_array[3]  # pr_off
          map_struct.mflags   = prmap_array[4]  # pr_mflags

          # convert pr_mflags value to string sort of like procmap outputs
          mflags = map_struct.mflags
          map_struct.s_mflags = ''
          sep = ''

          PR_MFLAGS.each do |flag|
            if (mflags & flag[0]).nonzero?
              map_struct.s_mflags << sep << flag[1]
              sep = '/'
              mflags &= ~flag[0]
            end
          end

          if mflags.nonzero?
            map_struct.s_mflags << sep << sprintf('%08x', mflags)
          end

          map_struct.pathoff  = prmap_array[5]  # pr_pathoff
          map_struct.alias    = prmap_array[6]  # pr_alias
          map_struct.gp       = prmap_array[7]  # pr_gp

          struct.map << map_struct
        end

        struct.map.each do |m|
          next if m.pathoff.zero?
          fd.sysseek(m.pathoff, IO::SEEK_SET)
          buf = fd.sysread(4096)
          buf =~ /^([^\0]*)\0([^\0]*)\0/
          m.path = $2.empty? ? $1 : "#{$1}(#{$2})"
        end
      end

      struct.map = nil if struct.map.empty?
    rescue
      struct.map = nil
    end

    # This is read-only data
    struct.freeze

    if block_given?
      yield struct
    else
      array << struct
    end
  end

  pid ? struct : array
end

Private Class Methods

apply_info_to_struct(info, struct) click to toggle source

Pass by reference method that updates the Ruby struct based on the FFI struct.

# File lib/darwin/sys/proctable.rb, line 265
def self.apply_info_to_struct(info, struct)
  # Chop the leading xx_ from the FFI struct members for our ruby struct.
  info.members.each do |nested|
    info[nested].members.each do |member|
      if info[nested][member].is_a?(FFI::StructLayout::CharArray)
        struct[PROC_STRUCT_FIELD_MAP[member]] = info[nested][member].to_s
      else
        struct[PROC_STRUCT_FIELD_MAP[member]] = info[nested][member]
      end
    end
  end
end
convert(str) click to toggle source

There is a bug in win32ole where uint64 types are returned as a String instead of a Fixnum. This method deals with that for now.

# File lib/windows/sys/proctable.rb, line 203
def self.convert(str)
  return nil if str.nil? # Return nil, not 0
  return str.to_i
end
get_cmd_args_and_env(pid, struct) click to toggle source

Get the command line arguments, as well as the environment settings, for the given PID.

# File lib/darwin/sys/proctable.rb, line 334
def self.get_cmd_args_and_env(pid, struct)
  len = FFI::MemoryPointer.new(:size_t)
  mib = FFI::MemoryPointer.new(:int, 3)

  # Since we may not have access to the process information due
  # to improper privileges, just bail if we see a failure here.

  # First use KERN_PROCARGS2 to discover the argc value of the running process.
  mib.write_array_of_int([CTL_KERN, KERN_PROCARGS2, pid])
  return if sysctl(mib, 3, nil, len, nil, 0) < 0

  buf = FFI::MemoryPointer.new(:char, len.read_ulong)
  return if sysctl(mib, 3, buf, len, nil, 0) < 0

  # The argc value is located in the first byte of buf
  argc = buf.read_bytes(1).ord
  buf.free

  # Now use KERN_PROCARGS to fetch the rest of the process information
  mib.write_array_of_int([CTL_KERN, KERN_PROCARGS, pid])
  return if sysctl(mib, 3, nil, len, nil, 0) < 0

  buf = FFI::MemoryPointer.new(:char, len.read_ulong)
  return if sysctl(mib, 3, buf, len, nil, 0) < 0

  exe = buf.read_string # Read up to first null, does not include args
  struct[:exe] = exe

  # Parse the rest of the information out of a big, ugly string
  array = buf.read_bytes(len.read_ulong).split(0.chr)
  array.delete('') # Delete empty strings

  # The format that sysctl outputs is as follows:
  #
  #   [full executable path]
  #   [executable name]
  #   [arguments]
  #   [environment variables]
  #   ...
  #   \FF\BF
  #   [full executable path]
  #
  # Strip the first executable path and the last two entries from the array.
  # What is left is the name, arguments, and environment variables
  array = array[1..-3]

  # It seems that argc sometimes returns a bogus value. In that case, delete
  # any environment variable strings, and reset the argc value.
  #
  if argc > array.size
    array.delete_if{ |e| e.include?('=') }
    argc = array.size
  end

  cmdline = ''

  # Extract the full command line and its arguments from the array
  argc.times do
    cmdline << ' ' + array.shift
  end

  struct[:cmdline] = cmdline.strip

  # Anything remaining at this point is a collection of key=value
  # pairs which we convert into a hash.
  environ = array.inject({}) do |hash, string|
    if string && string.include?('=')
      key, value = string.split('=')
      hash[key] = value
    end
    hash
  end

  struct[:environ] = environ
end
get_pctcpu(utime, start_time) click to toggle source

Calculate the percentage of CPU usage for the given process.

# File lib/linux/sys/proctable.rb, line 315
def self.get_pctcpu(utime, start_time)
  return nil unless @boot_time
  hertz = 100.0
  utime = (utime * 10000).to_f
  stime = (start_time.to_f / hertz) + @boot_time
  sprintf("%3.2f", (utime / 10000.0) / (Time.now.to_i - stime)).to_f
end
get_pctmem(rss) click to toggle source

Calculate the percentage of memory usage for the given process.

# File lib/linux/sys/proctable.rb, line 306
def self.get_pctmem(rss)
  return nil unless @mem_total
  page_size = 4096
  rss_total = rss * page_size
  sprintf("%3.2f", (rss_total.to_f / @mem_total) * 100).to_f
end
get_state(int) click to toggle source
# File lib/freebsd/sys/proctable.rb, line 350
def self.get_state(int)
  case int
    when SIDL; "idle"
    when SRUN; "run"
    when SSLEEP; "sleep"
    when SSTOP; "stop"
    when SZOMB; "zombie"
    when SWAIT; "waiting"
    when SLOCK; "locked"
    else; "unknown"
  end
end
get_thread_info(pid, struct, ptinfo) click to toggle source

Returns an array of ThreadInfo objects for the given pid.

# File lib/darwin/sys/proctable.rb, line 280
def self.get_thread_info(pid, struct, ptinfo)
  buf = FFI::MemoryPointer.new(:uint64_t, ptinfo[:pti_threadnum])
  num = proc_pidinfo(pid, PROC_PIDLISTTHREADS, 0, buf, buf.size)

  if num <= 0
    if [Errno::EPERM::Errno, Errno::ESRCH::Errno].include?(FFI.errno)
      return # Either we don't have permission, or the pid no longer exists
    else
      raise SystemCallError.new('proc_pidinfo', FFI.errno)
    end
  end

  max = ptinfo[:pti_threadnum]
  struct[:threadinfo] = []

  0.upto(max-1) do |index|
    tinfo = ProcThreadInfo.new

    # Use read_array_of_uint64 for compatibility with JRuby if necessary.
    if buf[index].respond_to?(:read_uint64)
      nb = proc_pidinfo(pid, PROC_PIDTHREADINFO, buf[index].read_uint64, tinfo, tinfo.size)
    else
      nb = proc_pidinfo(pid, PROC_PIDTHREADINFO, buf[index].read_array_of_uint64(1).first, tinfo, tinfo.size)
    end

    if nb <= 0
      if [Errno::EPERM::Errno, Errno::ESRCH::Errno].include?(FFI.errno)
        return # Either we don't have permission, or the pid no longer exists
      else
        raise SystemCallError.new('proc_pidinfo', FFI.errno)
      end
    end

    tinfo_struct = ThreadInfoStruct.new(
      tinfo[:pth_user_time],
      tinfo[:pth_system_time],
      tinfo[:pth_cpu_usage],
      tinfo[:pth_policy],
      tinfo[:pth_run_state],
      tinfo[:pth_flags],
      tinfo[:pth_sleep_time],
      tinfo[:pth_curpri],
      tinfo[:pth_priority],
      tinfo[:pth_maxpriority],
      tinfo[:pth_name].to_s,
    )

    struct[:threadinfo] << tinfo_struct
  end
end
parse_ms_date(str) click to toggle source

Converts a string in the format '20040703074625.015625-360' into a Ruby Time object.

# File lib/windows/sys/proctable.rb, line 194
def self.parse_ms_date(str)
  return if str.nil?
  return DateTime.parse(str)
end