#!/usr/bin/env python

# The MIT License
#
# Copyright (C) 2007  Chris Miles
#
# Copyright (C) 2009  Floris Bruynooghe
#
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files
# (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge,
# publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.


"""process_details.py

Display information about a running process.
"""


__author__ = 'Chris Miles'
__copyright__ = """\
Copyright (C) 2007  Chris Miles
Copyright (C) 2009  Floris Bruynooghe
"""
__version__ = '0.2'


import grp
import optparse
import os
import pwd
import sys
from datetime import datetime

import psi


help_message = 'Display information about a running process'


def run(pid):
    a = psi.arch.arch_type()
    print "Python %s / PSI %s / %s %s" % (
        '.'.join([str(v) for v in sys.version_info[:3]]),
        psi.__version__,
        a.sysname,
        a.release
    )
    
    try:
        p = psi.process.Process(pid)
    except ValueError, e:
        sys.stderr.write("%s\n"%e)
        return 1
    
    print "\nProcess information:"
    fmt = "  %-20s: %s"
    print fmt %('Process ID', p.pid)
    print fmt %('Parent Process ID', p.ppid)
    print fmt %('Executable', getattr(p, 'exe', 'N/A'))
    print fmt %('Command', p.command)

    try:
        v = ' '.join(p.args)
    except psi.AttrInsufficientPrivsError, e:
        v = str(e)
    print fmt %('Full Command', v)

    print fmt %('Effective user', '%s (%d)'
                % (pwd.getpwuid(p.euid).pw_name, p.euid))
    print fmt %('Real user', '%s (%d)'
                % (pwd.getpwuid(p.ruid).pw_name, p.ruid))
    print fmt %('Effective group', '%s (%d)'
                %(grp.getgrgid(p.egid).gr_name, p.egid))
    print fmt %('Real group', '%s (%d)'
                % (grp.getgrgid(p.rgid).gr_name, p.rgid))
    print fmt %('Process group', p.pgrp)
    print fmt %('Number of threads', getattr(p, 'nthreads', 'N/A'))
    print fmt %('Started', str(getattr(p, 'start_time', 'N/A')))
    
    try:
        status = p.status
    except AttributeError, e:
        value = str(e)
    else:
        all_status = [(s, getattr(psi.process, s)) for s in dir(psi.process)
                      if s.startswith('PROC_STATUS_')]
        proc_status = [s[0] for s in all_status if status == s[1]]
        value = ', '.join(proc_status)
    print fmt %('Status', value)

    print fmt %('Terminal device', getattr(p, 'terminal', 'N/A'))
    print fmt %('Environment', getattr(p, 'env', 'N/A'))

    print "\nCPU Use:"
    print fmt %('CPU seconds used', str(p.cputime))
    try:
        print ' '*24 + '(User: %s   System: %s)' % (p.utime, p.stime)
    except AttributeError:
        pass
    print fmt %('CPU seconds used', value)
    print fmt %('Recent CPU used', getattr(p, 'pcpu', 'N/A'))
    print fmt %('Nice level', getattr(p, 'nice', 'N/A'))
    print fmt %('Priority', getattr(p, 'priority', 'N/A'))
    
    print "\nMemory Use:"
    print fmt %('Resident memory used', p.rss)
    print fmt %('Virtual memory used', p.vsz)


def main(argv=None):
    if argv is None:
        argv = sys.argv
    
    # define usage and version messages
    usageMsg = "usage: %s [options] pid" % sys.argv[0]
    versionMsg = """%s %s""" % (os.path.basename(argv[0]), __version__)
    
    # get a parser object and define our options
    parser = optparse.OptionParser(usage=usageMsg, version=versionMsg)
    
    parser.add_option('-S', '--self', dest='self',
            action='store_true', default=False,
            help="Show information about itself")
    
    # Parse options & arguments
    (options, args) = parser.parse_args()
    
    if len(args) < 1 and not options.self:
        parser.error("A process ID must be specified.")
    if len(args) > 1:
        parser.error("Only one process ID can be specified.")
    
    if options.self:
        # introspect itself
        pid = os.getpid()
    else:
        try:
            pid = int(args[0])
        except:
            parser.error("Invalid process ID.")
    
    return run(pid)


if __name__ == "__main__":
    sys.exit(main())
