Kurzer Prozeß (Linux-Magazin, Februar 1999)

Ein brandneues Modul bietet eine Perl-Schnittstelle zu allen laufenden Prozessen an -- und das auch noch unabhängig vom verwendeten Unix-System! Dies macht den Weg frei für selbstgestrickte Implementierungen von Programmen, die die Auslastung eines Rechners a la 'top' anzeigen: Als kleiner Fünfzeiler, als per Web-Browser aufrufbares CGI-Programm oder als knallbunte graphische Tk-Applikation -- alles geht!

Topaktuelles von www.perl.org

Auf der Suche nach Themen für neue Artikel lasse ich mich gerne von http://www.perl.org/news.html inspirieren -- denn dort steht immer gleich eine Notiz, wenn ein neues Perl-Modul in die heiligen Hallen des CPAN einfährt. Kürzlich sah ich dort Proc::ProcessTable und war sofort begeistert.

Die C-Schnittstelle zu den unter einem Unix-System laufenden Prozessen ist leider stark systemabhängig und nur das ps-Kommando zaubert auf verschiedenen Systemen eine einigermaßen gleichförmige Übersichtstabelle mit Daten von gerade laufenden Prozessen auf den Bildschirm. Aber auch hier gibt's Unterschiede, manchmal heißt der zuständige Befehl ps -ef (z.B. IRIX), manchmal ps aux (Linux) und auch das Format der Ausgabetabelle variiert je nach Lust und Laune des Unix-Herstellers.

Generisches Perl-Interface

Da kam D. Urist auf die Idee, die verschiedenen Unix-Systeme (zur Zeit werden Linux, Solaris, AIX and HPUX aktiv unterstüzt) an Ihren C-Schnittstellen zu attackieren und ein generisches Perl-Interface drumherumzupacken. Mit Proc::ProcessTable lassen sich Prozeßinformationen betriebssystemunabhängig mittels Objekt-Methoden hervorzaubern -- genial!

Ein mittels der Anweisung

    $proctable = Proc::ProcessTable->new();

entstehendes Objekt vom Typ Proc::ProcessTable, dessen Referenz in $proctable liegt, bietet die Methode table an, die eine Referenz auf eine Liste von Prozeß-Objekten zurückliefert. Jedes Proc::ProcessTable::Process-Objekt wiederum bietet Methoden zur Abfrage der Prozeßdaten an. Eine Auswahl der wichtigsten:

    uid         User ID: (getpwid($uid))[0] ermittelt den Benutzernamen
    gid         Group ID: (getgrgid($gid))[0] liefert den Gruppennamen
    pid         ID des Prozesses
    ppid        ID seines Parent-Prozesses
    pgrp        Prozeß-Gruppen-ID
    priority    Priorität des Prozesses
    time        Verbratene Zeit (Summe User + System in Hunderstel-Sekunden)
    size        Virtuelles Memory in Bytes
    fname       Name der Datei, die den Prozeß startete
    start       Start-Zeitpunkt (Sekunden seit 1970)
    pctcpu      Prozentualer CPU-Verbrauch seit Prozeßstart
    state       Prozeß-Status
    pctmem      Prozentualer Speicherverbrauch
    cmndline    Vollständige Kommandozeile des Prozesses
    ttydev      TTY des Prozesses

Ein Fünfzeiler

Vorteil des Verfahrens: Wir können Perls geballte Feuerkraft zur Entwicklung nützlicher Skripts verwenden. Eine Liste aller zur Zeit laufenden Prozesse samt ihres Speicherverbrauchs gibt zum Beispiel Listing alle.pl aus. Es setzt das installierte Modul Proc::ProcessTable voraus (siehe Abschnitt Installation), bindet es ein, erzeugt ein neues Objekt vom Typ Proc::ProcessTable, legt eine Referenz darauf in $t ab und ruft die table-Methode auf, die eine Referenz auf eine Liste zurückliefert, deren Elemente allesamt Referenzen auf Objekte vom Typ Proc::ProcessTable::Process sind. Listing alle.pl macht aus der zurückgelieferten Listenreferenz mit @{...} eine Liste und iteriert mit einem foreach-Konstrukt darüber, sodaß in jedem Durchgang in $proc eine Referenz auf ein Proc::ProcessTable::Process-Objekt zu liegen kommt. Die Nummer jedes Prozesses fördert die pid-Methode zutage, die Prozeßgröße (virtuell) kommt mit size zum Vorschein, und für den Fall daß cmndline einen leeren String liefert (wie das unter Linux für einige Prozesse der Fall ist) springt die fname-Methode ein und gibt zumindest den Namen des zugehörigen Programms aus. Die printf-Funktion formatiert alles in Spalten:

    1   798720 init [3]
  410  1536000 /bin/login
  519  2592768 xterm
   36   770048 /sbin/kerneld
   ...

So läuft unter der PID 519 zum Beispiel ein Terminalfenster xterm, das satte 2,5 Megabytes an Speicher verbraucht.

Listing alle.pl

    01 #!/usr/bin/perl 
    02 ##################################################
    03 # Michael Schilli, 1998 (mschilli@perlmeister.com)
    04 ##################################################
    05 
    06 use Proc::ProcessTable;
    07 
    08 $t = new Proc::ProcessTable;
    09 
    10 foreach $proc ( @{$t->table} ){
    11     printf "%5d %8d %s\n",
    12            $proc->pid, $proc->size,
    13            $proc->cmndline || $proc->fname;
    14 }

Besser sortiert

Läuft mal wieder eine Kiste heiß und ich will wissen, welche 5 Prozesse das meiste Memory verbraten und welchen Benutzern ich dafür auf's Dach steigen muß, reicht ein Skript nach Listing fresser.pl, das die aus dem Rückgabewert der table-Methode gewonnene Liste mittels einer map-Anweisung in eine Liste transformiert, deren Elemente wiederum kleine Listen sind, die als Elemente

  1. die Kommandozeile des jeweiligen Prozesses und

  2. dessen virtuellen Speicherverbrauch in Bytes

enthalten. Hierzu liefert map für jedes Prozeß-Element eine Referenz auf eine Unterliste mit den angegebenen Elementen zurück, sodaß @sizes schließlich lauter Referenzen auf Unterlisten als Elemente führt. Das kleine Monster ist schnell mittels sort nach den Prozeßgrößen sortiert (der Sort-Code-Block vergleicht jeweils die zweiten Elemente der Sub-Listen und sortiert die Einträge absteigend) und die printf-Anweisung zeigt die größten Speicherfresser an, bevor die last-Anweisung im Schleifenrumpf dem Treiben ein Ende bereitet, falls der zwanzigste Eintrag ausgegeben wurde. Weil eine Ausgabe wie "1234567 Bytes" für das menschliche Auge schwer entzifferbar ist, fügt die commify-Funktion nach einem Verfahren, das die Perl-FAQ so trefflich zu beschreiben weiß, trennende Punkte ein, sodaß ein lesbareres 1.234.567 Bytes daraus entsteht:

    22.085.632 /usr/lib/netscape/netscape-communicator
    16.662.528 /usr/X11R6/bin/Xwrapper
    13.025.280 (dns helper)
     3.153.920 httpd
     3.129.344 httpd

Soso, der Web-Browser frißt mal wieder alles auf, naja, wozu hab' ich mir die neue Kiste mit 96 MB gekauft ... nebenbei bemerkt: Es ist wirklich erstaunlich, was so ein 400er Pentium unter Linux so alles bewerkstelligt -- da erblaßt manche fünfmal so teure Workstation vor Neid.

Listing fresser.pl

    01 #!/usr/bin/perl 
    02 ##################################################
    03 # Michael Schilli, 1998 (mschilli@perlmeister.com)
    04 ##################################################
    05 
    06 use Proc::ProcessTable;
    07 
    08 $t = new Proc::ProcessTable;
    09 
    10           # Sortierbares Listen-Konstrukt erzeugen
    11 @sizes = map { [$_->cmndline, $_->size] } 
    12              @{$t->table};
    13 
    14 $count = 20;     # Nur die ersten zwanzig
    15 
    16                  # Sortierte Liste ausgeben
    17 foreach $rec (sort { $b->[1] <=> $a->[1] } @sizes) {
    18     printf "%11s %s\n", commify($rec->[1]), 
    19            $rec->[0];
    20     last unless --$count;
    21 }
    22 
    23 ##################################################
    24 sub commify {    # Punkte in große Zahlen einfügen
    25 ##################################################
    26     my $number = shift;
    27     while($number =~ s/(\d)(\d{3})\b/\1.\2/) { }
    28     return $number;
    29 }

Durch die Web-Tür

Das ganze geht natürlich auch ohne sich erst langwierig per telnet einzuloggen und irgendein Skript aufzurufen -- Listing proc.cgi zeigt ein einfaches CGI-Skript, auf die Anfrage eines Browsers über das World Wide Web die aktivsten Prozesse eines angewählten Rechners anzeigt. Das CGI-Modul von Lincoln Stein, das jeder aktuellen Perl-Version beiliegt, vereinfacht die Ausgabe der CGI-Header und HTML-Tags drastisch und war in dieser Reihe schon Thema einer Vierer-Folge.

Damit proc.cgi im Browser nicht nur einen einmaligen Zustand anzeigt, sondern fortlaufend die Prozeßdaten aktualisiert, setzt es den -Refresh-Parameter der header-Methode auf 10 Sekunden und den URL des gegenwärtig aufgerufenen Skripts. Abbildung 1 zeigt den Web-Browser in Aktion: Alle 10 Sekunden erscheint automatisch ein neues Bild. Die erste Spalte der Tabelle zeigt den prozentualen Anteil an CPU-Aufwand für den jeweiligen Prozeß, Spalte zwei den virtuellen Speicherverbrauch und Spalte drei schließlich den Prozeßnamen an. Dementsprechend enthält das List-of-Lists-Konstrukt in Zeile 15 pro Element eine Referenz auf eine Liste mit den Rückgabewerten der Methoden pctcpu, size und cmndline, die anschließend nach pctcpu sortiert werden.

Abb.1: Der Browser zeigt die gefräßigsten Prozesse an

Listing proc.cgi

    01 #!/usr/bin/perl 
    02 ##################################################
    03 # Michael Schilli, 1998 (mschilli@perlmeister.com)
    04 ##################################################
    05 
    06 use CGI qw/:standard :html3/;
    07 use Proc::ProcessTable;
    08 
    09 $t = new Proc::ProcessTable;
    10 
    11           # Alle 10 Sekunden neu laden
    12 print header(-Refresh => 
    13              "10; URL=$ENV{SCRIPT_NAME}"),
    14       start_html('-title' => "Prozeßdaten"), 
    15       h1("Prozeßdaten"), "<TABLE border=1>\n";
    16 
    17           # Sortierbares Listen-Konstrukt aufbauen
    18 @sizes = map { [$_->pctcpu, $_->size, $_->cmndline] 
    19              } @{$t->table};
    20 
    21 $count = 20;    # Maximal 20 Prozesse ausgeben
    22 
    23           # Sortiert ausgeben
    24 print TR(th("% CPU"), th("Size (Bytes)"), 
    25          th("Prozeß"));
    26 
    27 foreach $rec (sort { $b->[0] <=> $a->[0] } @sizes) {
    28     print TR(td($rec->[0]), td($rec->[1]), 
    29           td($rec->[2]));
    30     last unless --$count;
    31 }
    32 
    33 print "</TABLE>", end_html();

Grafisch aufgepeppt

Perls Interface zu Tk, das grafische Toolkit, pfercht endlose Zahlenkolonnen flugs in handliche Listboxen, durch die man gemütlich scrollen und einzelne Einträge auswählen kann: Listing proctk.pl zeigt eine kleine Tk-Applikation, die alle laufenden Prozesse anzeigt und einigen ausgewählten auf Knopfdruck ein Kill-Signal sendet.

Das zugehörige Listing proctk.pl erfordert Tk-Kenntnisse und ein installiertes Tk-Toolkit, das es ebenfalls kostenlos auf dem CPAN gibt. Zeile 9 erzeugt das Fenster der Applikation, die Zeilen 12 bis 21 erzeugen das Listbox-Widget und die zwei Buttons und Zeile 26 startet die Applikation, die dort in die typische Haupt-Event-Schleife eintritt und von da an vollkommen mausgesteuert abläuft.

Abb.2: Ausgewählte Prozesse auf Knopfdruck beenden -- mit Perl/Tk

Ein Mausklick auf den ``Refresh''-Button löst die fill_listbox-Routine aus, die mit unseren bewährten ProcTable-Methoden die Prozeß-Informationen einholt und in die Listbox stopft, nicht ohne zuvor dort eventuell vorhandene Einträge mittels der delete-Methode zu löschen. Da in Zeile 15 der -selectmode-Parameter auf den Wert "extended" gesetzt wurde, kann der Benutzer einen oder mehrere Einträge aus der Liste auswählen und anschließend den Kill-Button drücken. Dieses Ereignis hat proctk.pl in Zeile 21 mit der Funktion kill_selected verknüpft, die mittels der Listbox-Methode Getselected eine Liste ausgewählter Einträge einholt, jeweils die Prozeß-ID extrahiert und jedem ausgewählten Prozeß mit der kill-Funktion ein SIGTERM-Signal nachjagt, auf das dieser (falls er es nicht explizit ignoriert) mit dem kontrolliertem Abbruch quittiert.

Ich weiß, ich weiß, Tk und seine sagenhaften Möglichkeiten haben wir in dieser Rubrik noch nicht durchgenommen, aber -- pst, pst! -- demnächst soll's mehr zu diesem Thema geben, stay tuned!

Listing proctk.pl

    01 #!/usr/bin/perl
    02 ##################################################
    03 # Michael Schilli, 1998 (mschilli@perlmeister.com)
    04 ##################################################
    05 
    06 use Tk;
    07 use Proc::ProcessTable;
    08 
    09 my $PTABLE = new Proc::ProcessTable;
    10 
    11     # Hauptfenster
    12 my $top = MainWindow->new();
    13 
    14     # Widgets erzeugen: Listbox, 2 Buttons
    15 $LISTBOX = $top->ScrlListbox(
    16     -background => "salmon",
    17     -label      => "Prozesse",
    18     -selectmode => "extended")->pack();
    19 $refresh = $top->Button(
    20     -text       => "Refresh",
    21     -command    => \&fill_listbox)->pack();
    22 $kill = $top->Button(
    23     -text       => "Kill",
    24     -command    => \&kill_selected)->pack();
    25 
    26     # Listbox mit Prozessen füllen
    27 fill_listbox($box, $ptable);
    28 
    29 MainLoop;
    30 
    31 ##################################################
    32 sub fill_listbox {   # Listbox aktualisieren
    33 ##################################################
    34     $LISTBOX->delete(0, "end");
    35 
    36         # Prozeßliste durchgehen, Listbox füllen
    37     foreach $p (@{$PTABLE->table}) {
    38         $LISTBOX->insert("end",
    39             sprintf "%s %s", $p->pid, 
    40                     $p->cmndline || $p->fname);
    41     }
    42 }
    43 
    44 ##################################################
    45 sub kill_selected {  # Selektierte Prozesse killen
    46 ##################################################
    47     foreach $process ($LISTBOX->Getselected()) {
    48         ($pid) = ($process =~ /(\d+)/);
    49         kill("SIGTERM", $pid) ||
    50             print "Cannot kill PID $pid";
    51     }
    52 }

Installation

Das Modul Proc::ProcessTable, das die Schnittstelle zu den Prozeßtabellen implementiert, ist auf dem CPAN kostenlos unter

    modules/by-module/Proc/Proc-ProcessTable-0.06.tar.gz

zu finden. Es benötigt eine vernünftige Perl-Version (z.B. das neue 5.005) und zusätzlich das Storable-Modul, das unter

    modules/by-module/Storable/Storable-0.6@3..tar.gz

auf dem CPAN liegt. Die komische Versionsnummer stimmt zur Zeit tatsächlich, es könnte aber sein, daß sie bald einem einsichtigeren Schema folgen wird. Wie alle Module installiert man die beiden nach dem Auspacken mit perl Makefile.PL und make install. Zieht man das im allerersten Teil dieser Reihe vorgestellte CPAN-Modul zu Rate, vereinfacht das den Prozeß beträchtlich:

    $ perl -MCPAN -eshell 
    > install Storable
    > install Proc::ProcessTable

holt und installiert alle notwendigen Module automatisch.

Bis zur nächsten Ausgabe, meine Lieben, oder, wie die Jungs und Mädels aus den schlechten Vierteln von San Francisco zu sagen pflegen: ``Audi 5000!'' Dazu bitte die Hand mit nach oben stehendem Daumen sowie schräg nach unten abgespreiztem Zeige- und Mittelfinger leicht über Kopfhöhe halten - Yo!

Referenzen

[1]
Neu: Perl/Tk Pocket Reference, Stephen Lidie, O'Reilly, 1998

[2]
Zum Thema Audi 5000: http://www.sci.kun.nl/thalia/rapdict/dictionary_0.html

Michael Schilli

arbeitet als Software-Engineer bei Yahoo! in Sunnyvale, Kalifornien. Er hat "Goto Perl 5" (deutsch) und "Perl Power" (englisch) für Addison-Wesley geschrieben und ist unter mschilli@perlmeister.com zu erreichen. Seine Homepage: http://perlmeister.com.