Jguy Jguy - 2 months ago 17
Python Question

Python using lxml to write an xml file with system performance data

I am using the python modules

lxml
and
psutil
to record some system metrics to be placed in an XML file and copied to a remote server for parsing by php and displayed to a user.

However, lxml is giving me some trouble on pushing some variables, objects and such to the various parts of my XML.

For example:

import psutil, os, time, sys, platform
from lxml import etree

# This creates <metrics>
root = etree.Element('metrics')

# and <basic>, to display basic information about the server
child1 = etree.SubElement(root, 'basic')

# First system/hostname, so we know what machine this is
etree.SubElement(child1, "name").text = socket.gethostname()

# Then boot time, to get the time the system was booted.
etree.SubElement(child1, "boottime").text = psutil.boot_time()

# and process count, see how many processes are running.
etree.SubElement(child1, "proccount").text = len(psutil.pids())


The line to get the system hostname works.

However the next two lines to get boot time and process count error out, with:

>>> etree.SubElement(child1, "boottime").text = psutil.boot_time()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "lxml.etree.pyx", line 921, in lxml.etree._Element.text.__set__ (src/lxml/lxml.etree.c:41344)
File "apihelpers.pxi", line 660, in lxml.etree._setNodeText (src/lxml/lxml.etree.c:18894)
File "apihelpers.pxi", line 1333, in lxml.etree._utf8 (src/lxml/lxml.etree.c:24601)
TypeError: Argument must be bytes or unicode, got 'float'
>>> etree.SubElement(child1, "proccount").text = len(psutil.pids())
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "lxml.etree.pyx", line 921, in lxml.etree._Element.text.__set__ (src/lxml/lxml.etree.c:41344)
File "apihelpers.pxi", line 660, in lxml.etree._setNodeText (src/lxml/lxml.etree.c:18894)
File "apihelpers.pxi", line 1333, in lxml.etree._utf8 (src/lxml/lxml.etree.c:24601)
TypeError: Argument must be bytes or unicode, got 'int'


So, here's what my XML looks like as printed:

>>> print(etree.tostring(root, pretty_print=True))
<metrics>
<basic>
<name>mercury</name>
<boottime/>
<proccount/>
</basic>
</metrics>


So, is there anyway to push floats and ints to xml text like I need? Or am I doing this completely wrong?

Thanks for any help you can provide.

Answer

The text field is expected to be unicode or str, not any other type (boot_time is float, and len() is int). So just convert to string the non-string compliant elements:

# First system/hostname, so we know what machine this is
etree.SubElement(child1, "name").text = socket.gethostname() # nothing to do

# Then boot time, to get the time the system was booted.
etree.SubElement(child1, "boottime").text = str(psutil.boot_time())

# and process count, see how many processes are running.
etree.SubElement(child1, "proccount").text = str(len(psutil.pids()))

result:

b'<metrics>\n  <basic>\n    <name>JOTD64</name>\n    <boottime>1473903558.0</boottime>\n    <proccount>121</proccount>\n  </basic>\n</metrics>\n'

I suppose the library could do the isinstance(str,x) test or str conversion, but it was not designed that way (what if you want to display your floats with leading zeroes, truncated decimals...). It operates faster if the lib assumes that everything is str, which it is most of the time.