Gathering System Level Information Cheat Sheet

For various reasons when writing applications you will want to gather system level information, it may to be to fingerprint, it may be to produce output that tells you which systems you application is having issues with. In this section we will cover a number of helpful Python functions you can use for gathering system level information.

Operating System and Version

platform.machine()

Returns the machine type, e.g. i386. An empty string is returned if the value cannot be determined.

>>> import platform
>>> platform.machine()
'x86_64'
platform.node()

Returns the computer’s network name. An empty string is returned if the value cannot be determined.

Warning

The network name may not be fully qualified!

>>> import platform
>>> platform.node()
'salem'
platform.processor()

Returns the (real) processor name, e.g. 'amdk6'. An empty string is returned if the value cannot be determined.

>>> import platform
>>> platform.processor()
''
platform.python_build()

Returns a tuple (buildno, builddate) stating the Python build number and date as strings.

>>> import platform
>>> platform.python_build()
('default', 'Jan 22 2019 21:57:15')
platform.python_compiler()

Returns a string identifying the compiler used for compiling Python.

>>> import platform
>>> platform.python_compiler()
'GCC 6.3.0 20170516'
platform.python_version()

Returns the Python version as string 'major.minor.patchlevel'.

>>> import platform
>>> platform.python_version()
'3.7.1'
platform.system()

Returns the system/OS name, e.g. 'Linux', 'Windows', or 'Java'. An empty string is returned if the value cannot be determined.

>>> import platform
>>> platform.system()
'Linux'
platform.uname()

Fairly portable uname interface. Returns a namedtuple() containing six attributes: system, node, release, version, machine, and processor.

>>> import platform
>>> platform.uname()
uname_result(system='Linux', node='salem', release='4.9.0-8-amd64', version='#1 SMP Debian 4.9.144-3.1 (2019-02-19)', machine='x86_64', processor='')

Networking

Caution

Some behavior may be platform dependent, since calls are made to the operating system socket APIs.

socket.getfqdn([name])

Return a fully qualified domain name for name. If name is omitted or empty, it is interpreted as the local host.

>>> import socket
>>> socket.getfqdn("8.8.8.8")
'google-public-dns-a.google.com'
socket.gethostbyname(hostname)

Translate a host name to IPv4 address format. The IPv4 address is returned as a string, such as ‘100.50.200.5’. If the host name is an IPv4 address itself it is returned unchanged.

>>> import socket
>>> socket.gethostbyname("google.com")
'216.58.199.78'
socket.gethostname()

Return a string containing the hostname of the machine where the Python interpreter is currently executing.

Warning

gethostname() doesn’t always return the FQDN; if a FQDN is required use socket.getfqdn().

>>> import socket
>>> socket.gethostname()
'salem'
socket.gethostbyaddr(ip_address)

Return a tuple (hostname, aliaslist, ipaddrlist) where hostname is the primary host name responding to the given IP address, aliaslist is a list of alternative host names for the same address, and ipaddrlist is a list of IP v4/v6 addresses for the same interface on the same host.

>>> import socket
>>> socket.gethostbyaddr("8.8.8.8")
('google-public-dns-a.google.com', [], ['8.8.8.8'])
socket.getservbyname(servicename[, protocolname])

Translate an Internet service name and protocol name to a port number for that service.

If the protocol name is given this should be tcp or udp otherwise will match on any protocol.

>>> import socket
>>> socket.getservbyname("gopher", 'tcp')
70
>>> socket.getservbyname("pop3")
110
socket.getservbyport(port[, protocolname])

Translate an Internet port number and protocol name to a service name for that service.

If the protocol name is given this should be tcp or udp otherwise will match on any protocol.

>>> import socket
>>> socket.getservbyport(70)
'gopher'
>>> socket.getservbyport(70, 'udp')
Traceback (most recent call last):
  File "<input>", line 1, in <module>
OSError: port/proto not found

Operating System Interfaces

os.getgid()

Return the effective group id of the current process. This corresponds to the “set id” bit on the file being executed in the current process.

Only available on UNIX (and Mac OS X).

>>> import os
>>> os.getgid()
1000
os.getuid()

Return the current process’s real user id.

Only available on UNIX (and Mac OS X).

>>> import os
>>> os.getuid()
1000
os.getgid()

Return the real group id of the current process.

Only available on UNIX (and Mac OS X).

>>> import os
>>> os.getgid()
1000
os.getgrouplist()

Return list of group ids that user belongs to. If group is not in the list, it is included; typically, group is specified as the group ID field from the password record for user.

Only available on UNIX (and Mac OS X).

>>> import os
>>> os.getgrouplist('alzion', 1)
[1, 24, 25, 27, 29, 30, 44, 46, 108, 113, 114]
os.getlogin()

Return the name of the user logged in on the controlling terminal of the process.

Only available on UNIX (and Mac OS X) and Windows.

>>> import os
>>> os.getlogin()
'alzion'

Pathname Manipulation

The built-in os.path module has a number of useful function for manipulating, and finding pathname information.

Tip

Python does not do any automatic path expansions, so you will have to do this yourself.

os.path.abspath(path)

Returns an absolute version of the pathname path.

>>> import os
>>> os.path.abspath('.')
'/Users/rebecca/tmp/pythoncharmingforbeginners'
>>>  os.path.abspath('Images')
'/Users/rebecca/tmp/pythoncharmingforbeginners/Images'
os.path.exists(path)

Returns True if the path refers to an existing path or an open file. Returns False for broken symbolic links. On some platforms if you do not have permission to execute oz.stat() on the file, it will return False even if the path physically exists.

>>> import os
>>> os.path.exists('/build/html')
False
>>> os.path.exists('./build/html')
True
os.path.expanduser(path)

When using relative paths, returns the initial ~ with the users home directory.

On UNIX the initial ~ is replaced by the HOME environment variable if it is set, otherwise the current user’s home directory is looked up in the password directory though the pwd module.

On Windows HOME and USERPROFILE will be used if set, otherwise a combination of HOMEPATH and HOMEDRIVE will be used.

>>> import os
>>> os.path.expanduser('~')
'/Users/rebecca'
>>> os.path.expanduser('~/tmp/pythoncharmingforbeginners')
'/Users/rebecca/tmp/pythoncharmingforbeginners'
os.path.getatime(path)

Return the time of last access of path. The return value is a number representing the number of seconds since EPOCH (e.g. 1551832874)

Tip

EPOCH is also known as POSIX time or UNIX Epoch time and is a system used for describing a point in time, it is the number of second that have elapsed since 00:00:00 Thursday, 1 January 1970 (UTC) minus leap seconds.

Raises an OSError if the file does not exist or is inaccessible.

>>> import os
>>> os.path.getatime('.')
1551834369.9661243
>>> os.path.getatime('./build/html')
1551832462.8749728
os.path.getmtime(path)

Return the time of last modified of path. The return value is a number representing the number of seconds since EPOCH (e.g. 1551832874)

Raises an OSError if the file does not exist or is inaccessible.

>>> import os
>>> os.path.getatime('.')
1551834369.9661243
>>> os.path.getatime('./build/html')
1551832462.8749728
os.path.getsize(path)

Returns the size of a path in bytes. Raises an OSError if the file does not exist or is inaccessible.

>>> import os
>>> os.path.getsize('.')
320
>>> os.path.getsize('./build/html')
800
os.path.isabs(path)

Returns True if path is an absolute pathname. On Unix, this means it begins with a slash, on Windows it begins with a (back)slash after chopping off a potential drive letter.

>>> import os
>>> os.path.isabs('.')
False
>>> os.path.isabs('/home/alzion/sauce/pythoncharmingforbeginners')
True
os.path.isfile(path)

Returns True if path is an existing regular file.

>>> import os
>>> os.path.isfile('.')
False
>>> os.path.isfile('/home/alzion/sauce/pythoncharmingforbeginners/Pipfile')
True
os.path.isdir(path)

Returns True if path is an existing directory.

>>> import os
>>> os.path.isdir('.')
True
>>> os.path.isdir('/home/alzion/sauce/pythoncharmingforbeginners/Pipfile')
False
os.path.ismount(path)

Returns True if pathname path is a mount point: a point in a file system where a different file system has been mounted.

Warning

There is currently a bug open for Python 3.7 and 3.8 as “os.path.ismount() always returns false for mount –bind on same filesystem” that is currently being reviewed. For more information see Issue 29707.

psutil

psutil (process and system utilities) is a third-party cross-platform library for retrieving information on running processes and system utilization (CPU, memory, disks, network, sensors) in Python. It implements a number of UNIX based command line tools and works on Linux, Windows, Mac OS X, FreeBSD, OpenBSD, NetBSD, Sun Solaris and AIX.

It can be installed using pipenv install psutil on Python 2.7+.

psutil.pids()

Return a sorted list of current running PIDs. To iterate over all processes and avoid race conditions process_iter() should be preferred.

>>> import psutil
>>> psutil.pids()
[0, 1, 40, 41, 44, 45, 46, 48, 51, 52, 54, 55, 58, 59, 65, 66, 67, 70, 71, 75, 76, 77, 78, 79, 80, 81, 82, 84, 86, 87, 89, 91, 93, 94, 95, 96, 98, 100, 101, 102, 103, 104, 107, 109, 114, 115, 125, 148, 162, 164, 173, 175, 176, 177, 178, 179, 186, 192, 193, 197, 199, 200, 201, 202, 203, 204, 206, 207, 208, 209, 210, 211, 212, 213, 215, 216, 217, 218, 219, 224, 228, 229, 230, 231, 232, 233, 236, 238, 239, 240, 241, 242, 250, 251, 253, 254, 259, 263, 264, 265, 268, 271, 272, 274, 276, 277, 278, 279, 280, 281, 282, 284, 286, 287, 288, 289, 290, 291, 292, 293, 296, 297, 298, 303, 304, 305, 306, 307, 308, 311, 314, 315, 316, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 359, 361, 363, 364, 365, 366, 367, 370, 372, 373, 374, 375, 377, 378, 379, 380, 381, 382, 384, 385, 389, 392, 393, 394, 397, 398, 400, 401, 405, 406, 407, 409, 411, 412, 415, 416, 417, 421, 423, 425, 426, 427, 428, 429, 430, 431, 432, 434, 435, 448, 450, 451, 462, 463, 464, 465, 466, 469, 479, 480, 481, 483, 484, 485, 489, 495, 497, 555, 557, 562, 565, 567, 568, 641, 642, 653, 656, 662, 665, 666, 673, 674, 675, 676, 689, 690, 691, 694, 695, 697, 699, 700, 701, 705, 708, 709, 711, 712, 719, 727, 729, 730, 731, 732, 733, 735, 737, 738, 739, 740, 744, 745, 746, 747, 750, 751, 752, 755, 756, 757, 758, 762, 763, 835, 838, 839, 842, 844, 872, 918, 1074, 1080, 1081, 1082, 1083, 1113, 1126, 1128, 1135, 1146, 1149, 1150, 1161, 1163, 1164, 1165, 1237, 1281, 1283, 1284, 1286, 1289, 1290, 1293, 1350, 1351, 1396, 1416, 1439, 1461, 1467, 1471, 1472, 1473, 1474, 1475, 1477, 1481, 1506, 1511, 1512, 1514, 1584, 1593, 1662, 1664, 1666, 1667, 1671, 1673, 1690, 1710, 1775, 1928, 2112, 2117, 2118, 2119, 2125, 2126, 2127, 2128, 2163, 2164, 2165, 2166, 2167, 2168, 2171, 2176, 2183, 2184, 2185, 2188, 2190, 2218, 2219]
psutil.pid_exists(pid)

Check whether the given PID exists in the current process list.

>>> import psutil
>>> psutil.pid_exists(415)
True
>>> psutil.pid_exists(20)
False
psutil.Process(pid=None)

Represents an OS process with the given pid. If pid is omitted current process pid (os.getpid) is used.

>>> process = psutil.Process(740)
psutil.Process(pid=None).name()

The process name. On Windows the return value is cached after first call. Not on POSIX because the process name may change.

>>> process = psutil.Process(740)
>>> process.name()
'Brave Browser Helper'
psutil.Process().environ()

The environment variables of the process as a dict.

Caution

May not reflect changes made after the process has started.

>>> import psutil
>>> psutil.Process().environ()
{'PATH': '/Users/rebecca/.virtualenvs/heartbleed-NDBE3uzQ/bin:/Library/Frameworks/Python.framework/Versions/3.7/bin:/Library/Frameworks/Python.framework/Versions/3.5/bin:/Library/Frameworks/Python.framework/Versions/3.5/bin:/Users/rebecca/.rbenv/shims:/Library/Frameworks/Python.framework/Versions/3.6/bin:/usr/local/opt/sqlite/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Applications/VMware Fusion.app/Contents/Public:/Library/TeX/texbin:/opt/X11/bin:/Applications/Wireshark.app/Contents/MacOS', 'WORKON_HOME': '/Users/rebecca/.virtualenvs', 'VIRTUALENVWRAPPER_PYTHON': '/usr/local/bin/python3', 'TERM': 'xterm-color', 'RBENV_SHELL': 'bash', 'PS1': '(heartbleed) ', 'VIRTUALENVWRAPPER_SCRIPT': '/usr/local/bin/virtualenvwrapper.sh', 'PYDEVD_LOAD_VALUES_ASYNC': 'True', 'DISPLAY': '/private/tmp/com.apple.launchd.8WMuE7EHzQ/org.macosforge.xquartz:0', 'VERSIONER_PYTHON_VERSION': '2.7', 'VIRTUALENVWRAPPER_WORKON_CD': '1', 'LOGNAME': 'rebecca', 'XPC_SERVICE_NAME': 'com.jetbrains.pycharm.21084', 'PYCHARM_HOSTED': '1', 'PYTHONPATH': '/Volumes/0X00/python/heartbleed:/Applications/PyCharm CE.app/Contents/helpers/third_party/thriftpy:/Applications/PyCharm CE.app/Contents/helpers/pydev', 'SHELL': '/bin/bash', 'PYTHONIOENCODING': 'UTF-8', 'VERSIONER_PYTHON_PREFER_32_BIT': 'no', 'USER': 'rebecca', 'VIRTUALENVWRAPPER_HOOK_DIR': '/Users/rebecca/.virtualenvs', 'VIRTUALENVWRAPPER_VIRTUALENV': '/usr/local/bin/virtualenv', 'IPYTHONENABLE': 'True', 'TMPDIR': '/var/folders/3m/bs8_9h9970x4sl1966hfksxc0000gn/T/', 'SSH_AUTH_SOCK': '/private/tmp/com.apple.launchd.jb1TywbXEj/Listeners', 'VIRTUAL_ENV': '/Users/rebecca/.virtualenvs/heartbleed-NDBE3uzQ', 'XPC_FLAGS': '0x0', 'PYTHONUNBUFFERED': '1', 'VIRTUALENVWRAPPER_PROJECT_FILENAME': '.project', 'PROJECT_HOME': '/Users/rebecca/sauce', '__CF_USER_TEXT_ENCODING': '0x1F5:0x0:0xF', 'Apple_PubSub_Socket_Render': '/private/tmp/com.apple.launchd.cEaOcdXuXU/Render', 'LC_CTYPE': 'en_AU.UTF-8', 'HOME': '/Users/rebecca'}
psutil.Process().environ()

The process current working directory as an absolute path.

>>> psutil.Process().cwd()
'/Volumes/0X00/python/heartbleed'
psutil.cpu_count()

Returns the number of CPU’s a PC has available

>>> import psutil
>>> psutil.cpu_count()
4
psutil.disk_partitions()

Return all mounted disk partitions as a list of named tuples including device, mount point and filesystem type. There is an optional all argument that when set to False will try to distinguish physical devices such as hard-drives, cd-roms, USB devices from memory partitions.

>>> import psutil
>>> psutil.disk_partitions(all=False)
[sdiskpart(device='/dev/disk1s1', mountpoint='/', fstype='apfs', opts='rw,local,rootfs,dovolfs,journaled,multilabel'), sdiskpart(device='/dev/disk1s4', mountpoint='/private/var/vm', fstype='apfs', opts='rw,noexec,local,dovolfs,dontbrowse,journaled,multilabel,noatime'), sdiskpart(device='/dev/disk2s1', mountpoint='/Volumes/0X00', fstype='exfat', opts='rw,nosuid,local,ignore-ownership')]
>>> psutil.disk_partitions(all=False)
[sdiskpart(device='/dev/disk1s1', mountpoint='/', fstype='apfs', opts='rw,local,rootfs,dovolfs,journaled,multilabel'), sdiskpart(device='/dev/disk1s4', mountpoint='/private/var/vm', fstype='apfs', opts='rw,noexec,local,dovolfs,dontbrowse,journaled,multilabel,noatime'), sdiskpart(device='/dev/disk2s1', mountpoint='/Volumes/0X00', fstype='exfat', opts='rw,nosuid,local,ignore-ownership')]
psutil.Popen(*args, **kwargs)

A more convenient interface to built-in subprocess.Popen

>>> import psutil
>>> from subprocess import PIPE
>>> p = psutil.Popen(["/usr/bin/python", "-c", "print('hello')"], stdout=PIPE)
>>> p.name()
'Python'
>>> p.username()
'rebecca'
>>> p.communicate()
(b'hello\n', None)

psutil.Popen also work as context managers (with supported) meaning that on exit file descriptors are closed and the process is waited for meaning a cleaner shut down:

>>> import psutil
>>> import subprocess
>>> with psutil.Popen(["ifconfig"], stdout=subprocess.PIPE) as proc:
>>>     log.write(proc.stdout.read())

A Recipe for Finding a Process by Name

import psutil

def find_procs_by_name(name: str):
    ls = []
    for process in psutil.process_iter(attrs=['name']):
        if process.info['name'] == name:
            ls.append(process)
    return ls

A Recipe for IP GeoLocation

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#!/usr/bin/env python
# -*- coding: utf-8 -*-
__version__ = "0.0.0"

import json
import urllib3
import argparse


def build_argument_parser():
    parser = argparse.ArgumentParser(description=f"""

 ███▄ ▄███▓▄▄▄    ▄▄▄█████▓▄▄▄          ██░ ██ ▄▄▄      ██▀███  ██▓
▓██▒▀█▀ ██▒████▄  ▓  ██▒ ▓▒████▄       ▓██░ ██▒████▄   ▓██ ▒ ██▓██▒
▓██    ▓██▒██  ▀█▄▒ ▓██░ ▒▒██  ▀█▄     ▒██▀▀██▒██  ▀█▄ ▓██ ░▄█ ▒██▒
▒██    ▒██░██▄▄▄▄█░ ▓██▓ ░░██▄▄▄▄██    ░▓█ ░██░██▄▄▄▄██▒██▀▀█▄ ░██░
▒██▒   ░██▒▓█   ▓██▒▒██▒ ░ ▓█   ▓██▒   ░▓█▒░██▓▓█   ▓██░██▓ ▒██░██░
░ ▒░   ░  ░▒▒   ▓▒█░▒ ░░   ▒▒   ▓▒█░    ▒ ░░▒░▒▒▒   ▓▒█░ ▒▓ ░▒▓░▓  
░  ░      ░ ▒   ▒▒ ░  ░     ▒   ▒▒ ░    ▒ ░▒░ ░ ▒   ▒▒ ░ ░▒ ░ ▒░▒ ░
░      ░    ░   ▒   ░       ░   ▒       ░  ░░ ░ ░   ▒    ░░   ░ ▒ ░
       ░        ░  ░            ░  ░    ░  ░  ░     ░  ░  ░     ░  

Version: {__version__}
""", formatter_class=argparse.RawTextHelpFormatter)
    parser.add_argument(dest="ip_address",
                        help="the IP address you wish to find the location of")
    return parser.parse_args()


def main(arguments: argparse.Namespace):
    http = urllib3.PoolManager()
    response = http.request('GET', f"http://ip-api.com/"
                                   f"json/{arguments.ip_address}")

    data = json.loads(response.data.decode("utf-8"))
    print(f"The host {arguments.ip_address} resides "
          f"in {data['country']} ({data['lat']}, {data['lon']}).")


if __name__ == "__main__":
    args = build_argument_parser()
    main(args)