Cover V13, i03
mar2004.tar

Python in Systems Administration: Part IV -- Python Makes GUIs

Cameron Laird

In the first installment of this series on Python for systems administration, back in December 2003, one of the benefits I mentioned was how easy Python makes development of graphical user interfaces (GUIs). This month, I'll give more attention to Python-based GUIs.

For the purpose of this series, GUIs are applications that rely on bit-mapped windowed images, icons, and the mouse pointer (WIMP), rather than character-based displays. I'll concentrate on X Window clients, although in principle Unix hosts can also use a framebuffer or other interface.

The first step in approaching Python GUI development is to let go of fear. The simplicity and power that make Python so apt for solving problems in systems administration have also made the language a desirable platform for experimentation. The result is that a bewildering variety of "toolkits", "frameworks", and "environments" for GUI work are available through Python. By my count, Python usefully supports a wider span of these libraries than any other language, including even the far better known C and Java: wxPython, PyQt, AnyGUI, ...

Start with the Basics

Don't let that embarrassment of riches distract you. For now, it's enough to concentrate on Tkinter, the GUI-capable library and module distributed with standard Python. If you decide to study Tkinter more deeply, you'll probably want copies of such books as John Grayson's 2000 Python and Tkinter Programming, Fredrik Lundh's 1999 An Introduction to Tkinter, which is available online at:

http://www.pythonware.com/library/tkinter/introduction/
or January 2004's Learning Python, Second Edition by Mark Lutz and David Ascher.

Even without such books, though, you can get an idea of what Tkinter can do for you in the sidebar "Where's Mine?". In the Unix world, systems administrators typically do our jobs from the command line by invoking commands with specific arguments, using command-line-oriented languages such as sh and awk, and occasionally editing plaintext configuration files. Think for a moment of the occasions when it might be an advantage to have a GUI:

  • A small monitor you can park somewhere on your screen, to give you quick access to a pertinent measurement such as free space, network usage, user count, etc.
  • A little "control panel" that wraps up common actions you take, so you can pass on temporary responsibility for them to a colleague who covers for you while you travel.
  • A small "client-server" application you write to automate an end user process you now do "by hand".

Experience with C or Java might have taught you that GUI work of this sort is "heavy" and best left to committed developers. How does Python do at such chores?

Monitor Your Environment

Python's greatest benefit in these roles is the ease with which it expresses exactly the requirements specific to your own situation. For this article, though, we'll choose examples that are artificially universal in order to focus on the part Python plays. Keep in mind that the advantages of "GUIfication" multiply as you apply it to your own requirements, rather than trying to fit those requirements to a general-purpose product.

Suppose, then, that you could use a small monitor that tracks uptime. All Unix hosts build in the uptime command, so the program that follows works wherever Python is installed. You could look at the top line of top output, of course; that would give you the same data. However, it takes only a few extra lines to write:

  # We need to:  access an external process; do easy
  # string substitution; and show a GUI.  These three
  # modules provide such functionality.
import commands, re, Tkinter

# Every second--one thousand milliseconds--this function
# gets the latest 'uptime' result, displays it, then
# reschedules itself for the next cycle.
def one_update():
    # This just collects the result of the external
    # built-in 'uptime' command.
    result = commands.getoutput("uptime")

    # Discard everything in the result string that
    # appears before the load averages themselves.
    pattern = ".*load averages*: "
    display_text.set(re.sub(pattern, "", result))

       # Do it again another second later.
    label.after(1000, one_update)

root = Tkinter.Tk()
  # Create a display "buffer".
display_text = Tkinter.StringVar()

  # Create a GUI widget which shows the results.
label = Tkinter.Label(root, textvariable = display_text, width = 20)

  # Place the Label in the main window.
label.pack()

  # Begin the measurements.
one_update()

  # Start processing events, that is, turn the GUI "on".
root.mainloop()
At this point (see Figure 1), you have a tiny window that updates every second or so with the freshest uptime result. It's well-behaved as a GUI: you can resize it, iconify and restore it, and generally treat it the way you'd treat any other GUI window.

With this as a starting point, you can calculate more interesting quantities to display. What do you need to keep an eye on? Network spikes? Server overloads? Tkinter makes quick work of GUI monitors for all such values. With just a few lines more, you can render quantities as plots or graphs, rather than textual displays. Later in this series, I'll show an example of such a graphical diagram.

Help When You're Not Around

One value of GUIs is to limit action, or, more precisely, to focus it in specified ways. Suppose a junior admin sometimes needs to check the contents of backup media. Let's start with a simplified situation in which we rely on tar. tar has a couple of liabilities: it's flexible enough that it can be hard for those unfamiliar with it to remember all its arguments; and it's powerful enough to do real damage to existing file systems. An obvious solution to these problems is to wrap up tar in a GUI that ensures correct, safe use. This example uses it for read-only access to either of two mass-storage devices:

  # Pmw includes such conveniences for GUI programming
  # as a ScrolledFrame.
import commands, Pmw, Tkinter

device = "/dev/tapeB"
def read_tape():
    text.config(state = Tkinter.NORMAL)
    text.delete(1.0, Tkinter.END)
    text.insert(Tkinter.END,
            commands.getoutput("tar tf %s" % device))
        # Make the display text read-only.
    text.config(state = Tkinter.DISABLED)

root = Tkinter.Tk()
button = {}

  # Suppose /dev/rmt1 and /dev/tapeB are the names of
  # two valid tape devices.
for (which, particular_device) in [(1, "/dev/rmt1"),
                                   (2, "/dev/tapeB")]:
    button[which] = Tkinter.Radiobutton(root,
           variable = "device",
           value = particular_device,
           text = "tape drive %d" % which)
    button[which].pack()

push = Tkinter.Button(root, text = "Read contents",
                      command = read_tape)
push.pack()

f = Pmw.ScrolledFrame(root)
f.pack(fill = "both", expand = 1)
frame = f.interior()
text = Tkinter.Text(frame, height = 20, width = 60)
text.pack(fill = "both", expand = 1)

root.mainloop()
Once you've written this script (see Figure 2), you can safely give it to an assistant with confidence that it will access real devices in a non-destructive way. These kinds of "control panels" can also be useful to you, however, if only to encapsulate argument configurations that are inconvenient to remember or type. Moreover, they package your expertise in a way that's meaningful to managers; a working GUI often makes a point far more effectively than any command-line wizardry.

Friendly to End Users

Finally, GUIs can simplify fragile human processes. Imagine you have an ongoing transient problem with zombie processes on different workstations. To track this down, you've tried to institute the policy of having users email you incident reports that include a process table snapshot, a datestamp, and commentary.

They get it wrong, though; they forget the arguments to ps, they don't remember how to put ps's results into an email message, and so on. You've got the right policy, but the users aren't following it.

You're lucky -- this is one of the rare social problems that a technical fix truly solves. Just give your users an application they can run on their own hosts:

import commands, os, Tkinter

def report():
    os.system("""mail -s 'Zombie report' MY_ADDRESS < HERE
Commentary:
----------------------------------------------------------------
%s
==================================================================
Process table:
----------------------------------------------------------------
%s
HERE""" % (commentary.get(1.0, END),
         commands.getoutput("ps aux")))

root = Tkinter.Tk()
label = Tkinter.Label(root, text = "Report zombies")
commentary = Tkinter.Text(root, height = 10, width = 40)
button = Tkinter.Button(root, command = report, text = "Send report")
label.pack()
commentary.pack()
button.pack()

root.mainloop()
It costs little to write such a "helper", users often find them friendly (see Figure 3), and they help you get the data you need -- wins all around!

There's a great more to the world of GUIs; however, as a first step, I hope you more clearly see ways that GUIs might help in your work, and how easy it is to use Python to create them. Next month, Part V of this series on Python in systems administration will continue with a look at the language's networking potency. Until then, keep that email coming; it's been very gratifying to receive all the reports of how Python is already helping many of you, as well as the kinds of additional problems you want to solve.

Cameron Laird, a vice president at consultancy Phaseit, Inc. (http://phaseit.net/), has written occasionally for Sys Admin in previous years.