Recipe 10.17. Gathering Detailed System Informationon Mac OS X
Credit: Brian Quinlan
Problem
You want to retrieve detailed
information about a Mac OS X system. You want either complete
information about the system or information about particular keys in
the system-information database.
Solution
Mac OS X's system_profiler
command can provide system information as an XML stream that we can
parse and examine:
#!/usr/bin/env python
from xml import dom
from xml.dom.xmlbuilder import DOMInputSource, DOMBuilder
import datetime, time, os
def group(seq, n):
""group([0, 3, 4, 10, 2, 3, 1], 3) => [(0, 3, 4), (10, 2, 3)]
Group a sequence into n-subseqs, discarding incomplete subseqs.
""
return [ seq[i:i+n] for i in xrange(0, len(seq)-n+1, n) ]
def remove_whitespace_nodes(node):
""Removes all of the whitespace-only text descendants of a DOM node.""
remove_list = [ ]
for child in node.childNodes:
if child.nodeType == dom.Node.TEXT_NODE and not child.data.strip( ):
remove_list.append(child)
elif child.hasChildNodes( ):
remove_whitespace_nodes(child)
for child in remove_list:
node.removeChild(child)
child.unlink( )
class POpenInputSource(DOMInputSource):
"Use stdout from an external program as a DOMInputSource"
def _ _init_ _(self, command):
super(DOMInputSource, self)._ _init_ _( )
self.byteStream = os.popen(command)
class OSXSystemProfiler(object):
"Provide information from the Mac OS X System Profiler"
def _ _init_ _(self, detail=-1):
""detail can range from -2 to +1. Larger numbers return more info.
Beware of +1, can take many minutes to get all info!""
b = DOMBuilder( )
self.document = b.parse(
POpenInputSource('system_profiler -xml -detailLevel %d' % detail))
remove_whitespace_nodes(self.document)
def _content(self, node):
"Get the text node content of an element, or an empty string"
if node.firstChild:
return node.firstChild.nodeValue
else:
return ''
def _convert_value_node(self, node):
""Convert a 'value' node (i.e. anything but 'key') into a Python data
structure""
if node.tagName == 'string':
return self._content(node)
elif node.tagName == 'integer':
return int(self._content(node))
elif node.tagName == 'real':
return float(self._content(node))
elif node.tagName == 'date': # <date>2004-07-05T13:29:29Z</date>
return datetime.datetime(
*time.strptime(self._content(node), '%Y-%m-%dT%H:%M:%SZ')[:5])
elif node.tagName == 'array':
return [self._convert_value_node(n) for n in node.childNodes]
elif node.tagName == 'dict':
return dict([(self._content(n), self._convert_value_node(m))
for n, m in group(node.childNodes, 2)])
else:
raise ValueError, 'Unknown tag %r' % node.tagName
def _ _getitem_ _(self, key):
from xml import xpath
# pyxml's xpath does not support /element1[...]/element2...
nodes = xpath.Evaluate('//dict[key=%r]' % key, self.document)
results = [ ]
for node in nodes:
v = self._convert_value_node(node)[key]
if isinstance(v, dict) and '_order' in v:
# this is just information for display
pass
else:
results.append(v)
return results
def all(self):
""Return the complete information from the system profiler
as a Python data structure""
return self._convert_value_node(
self.document.documentElement.firstChild)
def main( ):
from optparse import OptionParser
from pprint import pprint
info = OSXSystemProfiler( )
parser = OptionParser( )
parser.add_option("-f", "--field", action="store", dest="field",
help="display the value of the specified field")
options, args = parser.parse_args( )
if args:
parser.error("no arguments are allowed")
if options.field is not None:
pprint(info[options.field])
else:
# print some keys known to exist in only one important dict
for k in ['cpu_type', 'current_processor_speed', 'l2_cache_size',
'physical_memory', 'user_name', 'os_version', 'ip_address']:
print '%s: %s' % (k, info[k][0])
if _ _name_ _ == '_ _main_ _':
main( )
Discussion
Mac OS X puts at your disposal a wealth of information about your
system through the system_profiler application.
This recipe shows how to access that information from your Python
code. First, you have to instantiate class
OSXSystemProfiler, for example, via a statement such
as info = OSXSystemProfiler( ); once you have done
that, you can obtain all available information by calling
info.all( ), or information for one specific key
by indexing info[thekey]. The
main function in the recipe, which executes when you
run this module as a main script, emits information to standard
outputeither a specific key, requested by using switch
-f when invoking the script, or, by default, a
small set of keys known to be generally useful.For example, when run on the old Apple iBook belonging to one of this
book's editors (no prize for guessing which one),
the script in this recipe emits the following output:
cpu_type: PowerPC G4 (3.3)system_profiler returns XML data in
current_processor_speed: 800 MHz
l2_cache_size: 256 KB
physical_memory: 640 MB
user_name: Alex (alex)
os_version: Mac OS X 10.3.6 (7R28)
ip_address: [u'192.168.0.190']
pinfo format, so this recipe implements a partial
pinfo parser, using Python's
standard library XML-parsing facilities, and the
xpath implementation from the
PyXML extensions. More information about
Python's facilities that help you deal with XML can
be found in Chapter 12.
See Also
Documentation of the standard Python library support for XML in the
Library Reference and Python in a
Nutshell; PyXML docs at http://pyxml.sourceforge.net/; Mac OS X
system_profiler docs at http://developer.apple.com/documentation/Darwin/Reference/ManPages/man8/system_profiler.8l;
Chapter 12.