6
\$\begingroup\$

My usual disclaimer, I'm new to Python and scripting and I'm still studying the PEP8 guide, so please forgive any huge failures with respect to syntax, formatting and style. I'm open to any suggestions in regards to pretty much anything so let me have it!

That said, I've been building little games to learn so far, but decided it's time to try to create something useful that I might actually use one day and continue to build off of. Being a sysadmin I decided to try to build a cross platform (Windows and OSX) script that would run performance and system information gathering.

The version I'm about to paste is about 2 days old, and is in its infancy, but I wanted to get some feedback on a few things:

  1. Are there any obvious failures in how I'm structuring it that I should fix now?

  2. What are some suggestions for out of the box ways to gather the info and performance I'm getting with psutil? It's a non-standard module and I would like to make this script as standard as I can so it can just be run with a vanilla Python install. I'm considering just doing subprocess.call a lot, but figured that there has to be some stuff I'm not seeing when digging around the wild webs. Though it does look like they were considering adding it to the standard library back in October last year...

  3. Is there anything that you see that would concern you to run on your own system?

I'm not sure how far I can get with this without it requiring some degree of admin rights, but that's a high priority for me, so I'll keep going until I hit a wall. I'm making a concerted effort to only call stuff that doesn't require admin rights.

As well if you think this just isn't useful at all and there are 50 different other ways to do this via python already and I'm recreating a wheel that's already much more elegant, then let me know. I've done a lot of looking around and haven't found anything that does this in particular (except psutil itself), at least at the depth and scope I would like to see anyway.

I'm also totally up for suggestions for functionality and features you think should go in here!

#!/usr/bin/env python3


'''

This script is a system information and performance information
gathering script. It will pull information regarding live stats
like memory and cpu as well as information like os version and
serial number.

It's designed to be cross platform between Windows and OSX but
some data just isn't available on both.

This is an informational script only. It is not designed to change
any information, although with a few tweaks it could be.

In the spirit of being easy to run, I've only applied funtions that
don't require root/admin priviledges to run, so that any average
user/process can use and utilize this.

I may eventually branch this and remove psutil, as it's not a standard
module and requires some work to install. I would like this script
to be runnable from a default python install across platforms, so
I may eventually completely isolate the OSX and windows functions
and remove the cross platform section, putting in some logic to make
it transparent to the user.

Chris Gleason

Last update - 7/12/2015

Written for Python 3.x

### NOTES: ###

FUTURE WORK

1) Figure out if dependencies exist and if not exit properly with an informative message
2) If you can elegantly install the dependencies.
3) Need to run as sudo to pull network info from psutil

DEPENDENCIES

psutil

OSX

wget https://pypi.python.org/packages/source/p/psutil/psutil-3.1.0.tar.gz /tmp
tar -zxvf /tmp/psutil-3.1.0.tar.gz /tmp/
pip install /tmp/psutil-3.1.0/psutil

WINDOWS

https://pypi.python.org/packages/3.4/p/psutil/psutil-3.1.0.win32-py3.4.exe#md5=eb4504f7da8493a512a6f038a502a25c


'''

__version__ = "$Revision: 1"

################
# IMPORTS HERE #
################

import subprocess
import os
import platform
import sys
import argparse
import psutil
import readline
import time



###########################################
# ARGUMENTS AND SCRIPT RELATED ITEMS HERE #
###########################################


parser = argparse.ArgumentParser(description='Print system usage statistics and system information. \
    Default (no args) will determine OS and gather ALL information')
parser.add_argument('--ntfs' , 
    action='store_true' ,
    help='Gather Windows information')
parser.add_argument('--osx' , 
    action='store_true' ,
    help='Gather OSX information')
parser.add_argument('--sys' , 
    action='store_true' ,
    help='Gather System Information')
parser.add_argument('--perf' , 
    action='store_true' ,
    help='Gather Performance Information')

args = parser.parse_args()


##########################################
# TRULY CROSS PLATFORM THINGS START HERE #
##########################################


def noargs():

    '''
    This function is to determine OS level if the user doesn't define it in the
    command line switches
    '''

    if sys.platform == 'win32':
        osver="ntfs"
        print('Platform is NTFS/Windows!')
        runntfs()
        runperf()
        runsys()
    elif sys.platform == 'darwin':
        osver="osx"
        print('Platform is OSX!')
        runosx()
        runperf()
        runsys()




def runperf():

    '''
    This function runs cross platform performance related tests
    '''

    print('##########################')
    print('Performance tests running!')
    print('##########################')
    print ('''
    ''')
    print('--------')
    print('CPU INFO')
    print('--------')
    print('')
    print('CPU times at runtime are ', psutil.cpu_times())
    print('')
    print('CPU percent per CPU at runtime is ', psutil.cpu_percent(interval=5, percpu=True))
    print('')
    print('''
    ''')
    print('-----------')
    print('MEMORY INFO')
    print('-----------')
    print('')
    print('Memory usage statistics are ', psutil.virtual_memory())
    print('')
    print('''
    ''')
    print('---------')
    print('DISK INFO')
    print('---------')
    print('')
    if sys.platform == 'darwin':
        print('Disk usage is:\n')
        print(subprocess.call(['/bin/df', '-h']))
        print('')
        #print('Space usage from root down is:\n', subprocess.call(['/usr/bin/du', '-hs', '/*',]))
        print('')
        print('Disk IO statistics are\n ')
        print(subprocess.call(['/usr/sbin/iostat', '-c10']))
        print('')
        print('Be sure to ignore the first iostat line, as per best practices')
        print('')
    if sys.platform == 'win32':
        print('Disk usage is ', )

    print('')
    print('''
    ''')
    print('------------')
    print('NETWORK INFO')
    print('------------')
    print('')
    print('Network I/O stats ', psutil.net_io_counters(pernic=False))
    print('')
    print('')
    print('')
    print('')



def runsys():

    '''
    This function runs cross platform system information gathering
    '''
    print('#############################')
    print('System information gathering!')
    print('#############################')
    OS = sys.platform
    print ('''

    ''')
    print('Your OS is ', platform.system(), platform.release(), '-', platform.version())
    print('')
    print('Your architecture is ', platform.architecture())
    print('')
    print('# of logical CPU\'s are ', psutil.cpu_count())
    print('')
    print('# of physical CPU\'s, including threaded are ', psutil.cpu_count(logical=False))
    print('')
    print('Disk information is ', psutil.disk_partitions(all=True))
    print('') 
    if sys.platform == 'darwin':
        print('Users on the system are:\n')
        print(subprocess.call(['who', '-a']))
    print('')
    if sys.platform == 'win32':
        print('Users on the system are:\n')
    print('')


######################################
# WINDOWS SPECIFIC THINGS START HERE #
######################################

def runntfs():

    '''
    This function runs the Windows specific tests that can't be
    put into the cross platform checks
    '''

    print('NTFS Tests running!')


############################
# OSX SPECIFIC THINGS HERE #
############################


def runosx():

    '''
    This function runs the OSX specific tests that can't be
    put into the cross platform checks
    '''


    print('OSX Tests runnning!')



#################
# MAIN CODE RUN #
#################


if args.ntfs and args.osx:
    print ("You can't run both Windows and OSX flags on the same system!")
    exit(0)

if args.ntfs:
    print('You chose NTFS!')
    runntfs()
elif args.osx:
    print('You chose OSX!')
    runosx()
else:
    print('No OS specified!')


if args.sys and args.perf:
    print('You chose both System and Performance tests!')
    runperf()
    runsys()
elif args.sys:
    print('You chose to run System Information gathering only!')
    runsys()
elif args.perf:
    print('You chose to tun Performance Metric tests only!')
    runperf()
else:
    print("You didn't specify performance or system so both will be run!")


#if len(args) == 0:
#    noargs()

if not len(sys.argv) > 1:
    noargs()
\$\endgroup\$
1
\$\begingroup\$

Duplicated logic

You have multiple repeated conditions on sys.platform. These repeated checks are not great because of the hard-coded platform strings. It would be better to encapsulate these checks in helper functions:

def is_windows():
    return sys.platform == 'win32'

def is_osx():
    return sys.platform == 'darwin'

Other repeated code is header texts, like these:

    print('--------')
    print('CPU INFO')
    print('--------')
    # ...
    print('-----------')
    print('MEMORY INFO')
    print('-----------')

It would be better to create a helper function that takes a string, uppercases it, calculates the text length and formats a header text accordingly.

Argument parsing

Instead of having --ntfs and --osx flags and then doing extra validation to make sure only one of them was used, another option is to use choices:

parser.add_argument('os', choices=('ntfs', 'osx'))

This way, ArgumentParser will take care of the validation for you.


When using ArgumentParser, it's not normal to use sys.argv too. The args value returned from parser.parse_args() should be all you need.

Simplify

Instead of this:

    print('')

This is the same thing:

    print()
\$\endgroup\$
2
\$\begingroup\$

Rather than having lot's of prints, you can do something like this:

print(
    "This will",
    "be printed",
    "on separate",
    "lines.",
    sep="\n"
)

I'm not a huge fan of your naming too. Some of the names, like runsys could really use some underscores, like this: run_sys.

I also don't like comments like these:

##########################################
# TRULY CROSS PLATFORM THINGS START HERE #
##########################################

While they do sometimes provide a shred of decent info, ones like these don't really serve a good purpose.

In addition, all the code under your MAIN CODE RUN comment should be encapsulated inside an if __name__ == "__main__": block. This is to ensure that it properly runs on windows. See this Stackoverflow question for more details.

I'm also noticing that you're mixing single quotes, '', and double quotes, "". I'd try to choose one or the other an be consistent.

You also are printing a blank line, or adding a newline to strings in some places. For example, in one place, you do print('Disk usage is:\n'), and in other places, you do things like print(''), or even worse, you do print('Disk usage is ', ) and use a comma. If you need to print with an extra newline on strings, just add the extra newline "\n" to the end of the string.

Finally, instead of having a string with many of the same characters, you can use string multiplication like this: print("#" * 30).

\$\endgroup\$
  • 1
    \$\begingroup\$ Thanks! Those comments are a hangover from my PASCAL days. I've noticed that people use docstrings in Python and that's about it. I think my intention is to have a TON more information, so the idea was that it would be easy to see the division between the sections. In the event that you have a significant amount of lines and are trying to make it easy to quickly locate sections, do you just use a single line? Thanks for the multiple print fix. That was bugging me :) \$\endgroup\$ – Chris Gleason Jul 17 '15 at 10:26
  • \$\begingroup\$ Although I guess I separate those subsections via the functions. I guess my vision was a lot grander than the actual script was... \$\endgroup\$ – Chris Gleason Jul 17 '15 at 10:33
  • 1
    \$\begingroup\$ Doc strings are for documentation: when used correctly, they appear in help(...) pages of your implementation. The comment pointed out by Ethan is not documentation, it's visual separation. I don't see anything wrong with it, I don't know what good practice it violates. \$\endgroup\$ – janos Jul 17 '15 at 20:35
0
\$\begingroup\$

Just thought I'd post the updated script. I've added a lot more OSX gathering info and cleaned up a bunch of the stuff you guys suggested. I'm a bit all over the place with it, so it's definitely not in any sort of final form, but wanted to show it's working with your suggestions. Thanks!

#!/usr/bin/env python3


'''

This script is a system information and performance information
gathering script. It will pull information regarding live stats
like memory and cpu as well as information like os version and
serial number.

It's designed to be cross platform between Windows and OSX but
some data just isn't available on both.

This is an informational script only. It is not designed to change
any information, although with a few tweaks it could be.

In the spirit of being easy to run, I've only applied funtions that
don't require root/admin priviledges to run, so that any average
user/process can use and utilize this.

I may eventually branch this and remove psutil, as it's not a standard
module and requires some work to install. I would like this script
to be runnable from a default python install across platforms, so
I may eventually completely isolate the OSX and windows functions
and remove the cross platform section, putting in some logic to make
it transparent to the user.

Chris Gleason

Last update - 7/26/2015

Written for Python 3.x

### NOTES: ###

FUTURE WORK

1) Figure out if dependencies exist and if not exit properly with an informative message
2) If you can elegantly install the dependencies.
3) Need to run as sudo to pull network info from psutil
4) Going to completely remove perf and sys arguments. Since we are utilizing subprocess.call so heavily,
   it makes sense to just isolate via platform then break it up from there. I may add (--disk, --cpu, etc)
   flags later, but for now I'll just consolidate into OS arguments.
5) Add an argument to suppress the long running tasks like du and iostat

DEPENDENCIES

psutil

OSX

wget https://pypi.python.org/packages/source/p/psutil/psutil-3.1.0.tar.gz /tmp
tar -zxvf /tmp/psutil-3.1.0.tar.gz /tmp/
pip install /tmp/psutil-3.1.0/psutil

WINDOWS

https://pypi.python.org/packages/3.4/p/psutil/psutil-3.1.0.win32-py3.4.exe#md5=eb4504f7da8493a512a6f038a502a25c


'''

__version__ = "$Revision: 1.0"

################
# IMPORTS HERE #
################

import subprocess
import os
import platform
import sys
import argparse
import psutil
import readline
import time



###########################################
# ARGUMENTS AND SCRIPT RELATED ITEMS HERE #
###########################################


parser = argparse.ArgumentParser(description='Print system usage statistics and system information. \
    Default (no args) will determine OS and run all gather functions')
parser.add_argument('--ntfs' , 
    action='store_true' ,
    help='Gather Windows information')
parser.add_argument('--osx' , 
    action='store_true' ,
    help='Gather OSX information')
parser.add_argument('--quick' , 
    action='store_true' ,
    help='Suppress long running functions (du, iostat, cpu timing)')

args = parser.parse_args()


##############################################
# NON PLATFORM SPECIFIC FUNCTIONS START HERE #
##############################################

def noargs():

    '''
    This function is to determine OS version if the user doesn't define it in the
    command line switches
    '''

    if os == 'ntfs':
        print('Platform is NTFS/Windows!')
        runperf()
        runsys()
        runntfs()
    else:
        print('Platform is OSX!')
        runperf()
        runsys()
        runosx()

def big_title(title):
    titlelen = len(title)    
    print('#' * titlelen)
    print(title)
    print('#' * titlelen)

def little_title(title):
    titlelen = len(title)    
    print('-' * titlelen)
    print(title)
    print('-' * titlelen)




def runperf():

    '''
    This function runs cross platform performance related tests
    '''

    title = 'PERFORMANCE TESTS RUNNING!'
    big_title(title)

    print ('''
    ''')

    title = 'CPU PERF INFO'
    little_title(title)

    print('')
    print('CPU times at runtime are ', psutil.cpu_times())
    print('')
    if quick:
        print('CPU Usage stats skipped!')
    else:
        print('5 second use percentage sample per CPU (Core) at runtime is ', psutil.cpu_percent(interval=5, percpu=True))
    print('')
    print('''
    ''')

    title = 'MEMORY PERF INFO'
    little_title(title)

    print('')
    print('Memory usage statistics are ', psutil.virtual_memory())
    print('')
    print('''
    ''')

    title = 'DISK PERF INFO'
    little_title(title)

    print('')
    if sys.platform == 'darwin':
        print('Disk usage is:\n')
        print(subprocess.call(['/bin/df', '-h']))
        print('')
        print('')
        if quick:
            print('IOStat and Disk Usage tests skipped!')
        else:
            print('Disk IO statistics are\n ')
            print(subprocess.call(['/usr/sbin/iostat', '-c10']))
            print('')
            print('Be sure to ignore the first iostat line, as per best practices')
            print('')
            print('Disk usage by directory is: ')
            print()
            subprocess.call(['du -hs /* --max-depth=1 2>/dev/null'], shell=True)
            print()
            print('Because this script isn\'t designed to run as root, it can\'t stat all files.')
            print('Permission Denied messages have been suppressed, but it should give an idea')
            print('as to which directories are the big ones...')
            print()
        print()
    if sys.platform == 'win32':
        print('Disk usage is ', )

    print('')
    print('''
    ''')

    title = 'NETWORK PERF INFO'
    little_title(title)
    print('')
    print('Network I/O stats ', psutil.net_io_counters(pernic=False))
    print('')
    print('')
    print('')
    print('')



def runsys():

    '''
    This function runs cross platform system information gathering
    '''

    title = 'SYSTEM INFORMATION GATHERING!'
    big_title(title)

    print ('''

    ''')
    print('Your OS is ', platform.system(), platform.release(), '-', platform.version())
    print()
    print('Your architecture is ', platform.architecture())
    print()
    print('Your system model is: ')
    subprocess.call(['sysctl', 'hw.model'])
    print()
    print('# of logical CPU\'s are ', psutil.cpu_count())
    print()
    print('# of physical CPU\'s, including threaded are ', psutil.cpu_count(logical=False))
    print()
    print('Disk information is ', psutil.disk_partitions(all=True))
    print() 
    if os == 'osx':
        print('Users on the system are:\n')
        print(subprocess.call(['who', '-a']))
    print()
    if os == 'ntfs':
        print('Users on the system are:\n')
    print()
    print('Your network IP configuration is: ')
    print()
    subprocess.call(['ifconfig'])
    print()


######################################
# WINDOWS SPECIFIC THINGS START HERE #
######################################

def runntfs():

    '''
    This function runs the Windows specific gatherers
    '''

    #print('NTFS Tests running!')

    title = 'NTFS TESTS RUNNING!'
    little_title(title)


############################
# OSX SPECIFIC THINGS HERE #
############################


def runosx():

    '''
    This function runs the OSX specific gatherers
    '''

    title = 'OSX SPECIFIC INFO GATHERING!'
    big_title(title)

    print()

    title = 'OSX Serial Number'
    little_title(title)
    print()
    subprocess.call(['ioreg -l | grep IOPlatformSerialNumber | cut -d \'=\' -f2'], shell=True)
    print()

    title = 'OSX Hostname Info'
    little_title(title)

    print()
    print('SCUTIL HOSTNAME set to: ')
    hostname = subprocess.call(['/usr/bin/env', 'scutil', '--get', 'HostName'])
    print()
    print('SCUTIL LOCALHOSTNAME set to: ')
    localhostname = subprocess.call(['/usr/bin/env', 'scutil', '--get', 'LocalHostName'])
    print()
    print('SCUTIL COMPUTERNAME set to: ')
    computername = subprocess.call(['/usr/bin/env', 'scutil', '--get', 'ComputerName'])
    print()

    title = 'OSX Power Management Settings'
    little_title(title)

    print()
    subprocess.call(['pmset', '-g'])
    print()

    title = 'OSX Sleep image size'
    little_title(title)

    print()
    subprocess.call(['ls', '-hal', '/Private/var/vm/sleepimage'])
    print()

    title = 'OSX System Defaults (Some of them)'
    little_title(title)

    '''

    I don't know much about defaults, so I output the ones I know could matter, but there are a ton that might matter to you.
    If you find that they aren't outputting, it's because they have not been set. In most cases the default is off
    Most of these were taken from:
    https://gist.github.com/brandonb927/3195465/
    https://github.com/mathiasbynens/dotfiles

    '''

    print()
    print()
    print('Resume is disabled system wide (0 = False and 1 = True)') 
    print('defaults read com.apple.systempreferences NSQuitAlwaysKeepsWindows -bool')
    resume = subprocess.call(['defaults', 'read', 'com.apple.systempreferences', 'NSQuitAlwaysKeepsWindows', '-bool'])
    print()
    print('Daily update check interval in days is: ')
    print('defaults read com.apple.SoftwareUpdate ScheduleFrequency -int')
    updates = subprocess.call(['defaults', 'read', 'com.apple.SoftwareUpdate', 'ScheduleFrequency', '-int']) 
    print()
    print('Is password required immediately after sleep or screensaver enabled? (0 = No and 1 = Yes)')
    print('defaults read com.apple.screensaver askForPassword -int')
    ss = subprocess.call(['defaults', 'read', 'com.apple.screensaver', 'askForPassword', '-int'])
    print()
    print('What is the default delay when turning on the ask for password? (in seconds)')
    print('defaults read com.apple.screensaver askForPasswordDelay -int')
    ssdelay = subprocess.call(['defaults', 'read', 'com.apple.screensaver', 'askForPasswordDelay', '-int'])
    print()
    print('Is show hidden files enabled? (May say YES/NO, but if set using defaults will say 0 = False and 1 = True)')
    print('defaults read com.apple.finder AppleShowAllFiles -bool')
    hidden = subprocess.call(['defaults', 'read', 'com.apple.finder', 'AppleShowAllFiles', '-bool'])
    print()

    title = 'OSX Current NVRAM Variable values'
    little_title(title)

    print()
    subprocess.call(['nvram', '-p'])
    print()




#################
# MAIN CODE RUN #
#################


if __name__ == "__main__":
    if sys.platform == 'win32':
        os == 'ntfs'
    elif sys.platform == 'darwin':
        os == 'osx'

    if args.ntfs and args.osx:
        print ("You can't run both Windows and OSX flags on the same system!")
        exit(0)

    if args.ntfs and args.quick:
        print('You chose ntfs and quick flags!')
        quick=True
        runperf()
        runsys()
        runntfs()
    elif args.ntfs:
        print('You chose only the ntfs flag!')
        quick=False
        runperf()
        runsys()
        runntfs()
    elif args.osx and args.quick:
        print('You chose osx and quick flags!')
        quick=True
        runsys()
        runperf()
        runosx()
    elif args.osx:
        print('You chose only the osx flag!')
        quick=False
        runsys()
        runperf()
        runosx()
    else:
        print('You either chose no OS flags or the incorrect ones.')
        if args.quick:
            quick=True
            print('You chose the quick flag! Supressing long running tests!')
        else:
            quick=False
            print('Running all tests!')
        noargs()
    #if len(args) == 0:
    #    noargs()
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.