Chris Miles (chrismiles) wrote,
Chris Miles
chrismiles

  • Mood:

PSI demonstration

I wanted to give a sneak preview into a new project I'm working on. It is a Python module I call PSI - Python System Information. Want a direct hook into the system to extract performance information or process details from Python? That's the goal of PSI.

Here's a quick demo.

>>> import psi
>>> psi.loadavg()
(0.751953125, 1.35400390625, 1.16259765625)


The 3-tuple of load averages as available on most UNIX platforms.

>>> ps = psi.process.ProcessList()
>>> ps.count
119


ProcessList() creates an object representing a snapshot of the processes on the system. In this example there are 119 processes. Let's see all the process IDs:

>>> ps.pids
(0, 1, 27, 31, 39, 40, 41, 42, 43, 44, 45, 46, 47, 49, 50, 51, 56, 59, 63, 66, 67, 69, 71,
 72, 89, 117, 122, 134, 149, 155, 156, 159, 162, 172, 175, 176, 177, 179, 180, 182,
 183, 190, 194, 214, 229, 238, 242, 245, 246, 248, 251, 256, 257, 264, 265, 267, 268,
 270, 271, 272, 273, 286, 392, 401, 402, 403, 1135, 1258, 1442, 1589, 1703, 1704,
 1705, 1706, 1707, 1708, 1709, 1710, 1712, 1713, 1714, 1715, 1716, 1717, 1718,
 1719, 1720, 1721, 2575, 2577, 2578, 2616, 2631, 2632, 9118, 9903, 10159, 10990,
 12444, 12596, 13582, 13840, 13904, 13976, 14404, 15093, 15443, 15498, 15814,
 15845, 15947, 16092, 16093, 16238, 16270, 16272, 18040, 18088, 18183)


Each process is represented by a Process object. ps.processes will return a list of all Process objects, or we can fetch a dict of Process objects keyed by pid using ps.processes_dict.

Let's examine a process. We'll take a quick glance at what each process is called so we can pick one.

>>> [(p.pid,p.command) for p in ps.processes]
[(0, 'kernel_task'), (1, 'launchd'), (27, 'dynamic_pager'), (31, 'kextd'),
 (39, 'bfobserver'), (40, 'KernelEventAgent'), (41, 'mDNSResponder'),
 (42, 'netinfod'), (43, 'syslogd'), (44, 'cron'), (45, 'configd'), (46, 'coreaudiod'),
 (47, 'diskarbitrationd'), (49, 'memberd'), (50, 'notifyd'), (51, 'securityd'),
 (56, 'update'), (59, 'distnoted'), (63, 'DirectoryService'), (66, 'WindowServer'),
 (67, 'blued'), (69, 'coreservicesd'), (71, 'ATSServer'), (72, 'loginwindow'),
 (89, 'crashreporterd'), (117, 'mds'), (122, 'AppleFileServer'), (134, 'httpd'),
 (149, 'ARDHelper'), (155, 'nmpppstatsd'), (156, 'NMPCCardDemonVMC'),
 (159, 'iScroll2Daemon'), (162, 'hpusbmond'), (172, 'HPIO Trap Monito'),
 (175, 'ARDAgent'), (176, 'AppleVNCServer'), (177, 'rmdb'), (179, 'rmdb'),
 (180, 'rmdb'), (182, 'python'), (183, 'pbs'), (190, 'Dock'), (194, 'SystemUIServer'),
 (214, 'ntpd'), (229, 'nfsiod'), (238, 'rpc.lockd'), (242, 'automount'),
 (245, 'iTunesHelper'), (246, 'iCalAlarmSchedul'), (248, 'System Events'),
 (251, 'automount'), (256, 'Quicksilver'), (257, 'UniversalAccess'), (264, 'Synergy'),
 (265, 'teleportd'), (267, 'LCCDaemon'), (268, 'HPEventHandler'), (270, 'Print Daemon'),
 (271, 'HP IO Classic Pr'), (272, 'HP IO Classic Pr'), (273, 'HP Scheduler'),
 (286, 'dmnotifyd'), (392, 'cupsd'), (401, 'Activity Monitor'), (402, 'pmTool'),
 (403, 'Memory Monitor'), (1135, 'AppleSpell'), (1258, 'iChatAgent'),
 (1442, 'GrowlHelperApp'), (1589, 'NetNewsWire'), (1703, 'DashboardClient'),
 (1704, 'DashboardClient'), (1705, 'DashboardClient'), (1706, 'DashboardClient'),
 (1707, 'DashboardClient'), (1708, 'DashboardClient'), (1709, 'DashboardClient'),
 (1710, 'DashboardClient'), (1712, 'DashboardClient'), (1713, 'DashboardClient'),
 (1714, 'DashboardClient'), (1715, 'DashboardClient'), (1716, 'DashboardClient'),
 (1717, 'DashboardClient'), (1718, 'DashboardClient'), (1719, 'DashboardClient'),
 (1720, 'DashboardClient'), (1721, 'DashboardClient'), (2575, 'iTerm'), (2577, 'login'),
 (2578, 'bash'), (2616, 'TextMate'), (2631, 'login'), (2632, 'bash'), (9118, 'Adium'),
 (9903, 'free-1 USB Phone'), (10159, 'httpd'), (10990, 'Skype'), (12444, 'iChat'),
 (12596, 'ssh-agent'), (13582, 'Finder'), (13840, 'Safari'), (13904, 'iCal'),
 (13976, 'ssh-agent'), (14404, 'check_afp'), (15093, 'ssh-agent'),
 (15443, 'Preview'), (15498, 'ssh-agent'), (15814, 'vim'), (15845, 'Mail'),
 (15947, 'DashboardClient'), (16092, 'login'), (16093, 'bash'), (16238, 'iTunes'),
 (16270, 'lookupd'), (16272, 'slpd'), (18040, 'mdimport'), (18088, 'mdimport'),
 (18183, 'python')]


I'm curious about process 'ntpd' (pid 214):

>>> p = ps.processForPid(214)
>>> p
<psi.process.Process object pid=214>
>>> dir(p)
['__class__', '__delattr__', '__doc__', '__getattribute__', '__hash__', '__init__',
 '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__str__',
 'args', 'command', 'command_path', 'cpu_time', 'env', 'flags', 'gid',
 'group', 'nice', 'pcpu', 'pid', 'priority', 'resident_size', 'start_time', 'status',
 'system_time', 'terminal', 'threads', 'uid', 'user', 'user_time', 'virtual_size']
>>> p.pid
214
>>> p.command
'ntpd'
>>> p.user
'root'
>>> p.command_path
>>> p.args
>>> p.env
>>> p.resident_size
>>>


hmmm, many of the interesting attributes return None. That's because this process is owned by root and we don't have privileges to access all the details about it. Let's give ourselves some privileges and try again:

$ sudo python2.4
Python 2.4.1 (#2, Mar 31 2005, 00:05:10) 
[GCC 3.3 20030304 (Apple Computer, Inc. build 1666)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import psi
>>> ps = psi.process.ProcessList()
>>> p = ps.processForPid(214)
>>> p.command
'ntpd'
>>> p.command_path
'/usr/sbin/ntpd'
>>> p.args
['ntpd', '-f', '/var/run/ntp.drift', '-p', '/var/run/ntpd.pid']
>>> p.env
{'SHLVL': '2', 'PATH':
 '/bin:/sbin:/usr/bin:/usr/sbin:/usr/libexec:/System/Library/CoreServices',
 'PWD': '/', '_': '/usr/sbin/ntpd'}


How much memory is the process using:

>>> p.resident_size
221184
>>> print "%d KB" % (p.virtual_size / 1024) 
27504 KB


When did the process start?

>>> from datetime import datetime
>>> print datetime.fromtimestamp(p.start_time)
2006-12-26 10:50:30


How much elapsed CPU time has this process used?

>>> p.cpu_time
36.497602000000001
>>> p.system_time
26.527072
>>> p.user_time
9.9705300000000001


What percentage of CPU is this process currently using?

>>> p.pcpu
0.0


ok, it is idle. Pretty boring. Let's grab an active process, say iTunes.

>>> [(p.pid,p.command) for p in ps.processes if 'itunes' in p.command.lower()]
[(245, 'iTunesHelper'), (16238, 'iTunes')]
>>> p = ps.processForPid(16238)
>>> p.command
'iTunes'
>>> p.command_path
'/Applications/iTunes.app/Contents/MacOS/iTunes'
>>> p.pcpu
9.0999999999999996


Damn iTunes chews up some CPU on my old PowerBook.

That's the end of the demo. Mac-savvy readers may have noticed that I performed this demo on an OS X box. Currently PSI only fetches information for OS X coz that's what I've been developing on. Next I'll be adding support for Solaris, Linux, maybe FreeBSD. I may need some help with Win32 support, but my goal will be to support as many systems as possible (everything that Python runs on?). The module is a C extension that fetches information directly from the relevant system calls, so it aims to be as efficient as possible (i.e. rather than parsing the output of commands like 'ps' as is commonly done).

Another goal of PSI is to make available as much information about the system as possible. That will include memory & swap usage details, filesystem usage & I/O information, network interface activity, and whatever else is useful.

This is my first attempt at a Python C extension module, so it is an interesting challenge and I'll certainly be open to suggestions, advice & criticism. I'm still getting used to the nuances of reference counting, so don't expect the first release to be absolutely perfect. I aim to release the first version after I add support for a few more systems and add plenty more unit tests.
Tags: python
Subscribe

Comments for this post were locked by the author