Home

Info

Artikel

Produkte

Stickers

UserGroups

Events

Bücher


Suchen:



Linux-Community
Jetzt bestellen!
> Kombiabo
> Jahres-CD 1999

Perl-Snapshot

Kurzer Prozeß

von Michael Schilli


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 [1] 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 1 (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 1 macht aus der zurückgelieferten Listenreferenz mit @{...} eine Liste und iteriert mit einem foreach-Konstrukt darüber, so daß 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 1: alle.pl

 1 #!/usr/bin/perl 
 2 
 3 use Proc::ProcessTable;
 4 
 5 $t = new Proc::ProcessTable;
 6 
 7 foreach $proc ( @{$t->table} ){
 8     printf "%5d %8d %s\n",
 9            $proc->pid, $proc->size,
10            $proc->cmndline || $proc->fname;
11 }

Besser sortiert

Läuft mal wieder eine Kiste heiß und ich will wissen, welche 5 Prozesse den meisten Speicher verbraten und welchen Benutzern ich dafür auf's Dach steigen muß, reicht ein Skript nach Listing 2 (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, so daß @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 2: fresser.pl

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

Durch die Web-Tür

Das Ganze geht natürlich auch ohne sich erst langwierig per telnet einzuloggen und irgendein Skript aufzurufen - Listing 3 (proc.cgi) zeigt ein einfaches CGI-Skript, das 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 3: proc.cgi

 1 #!/usr/bin/perl 
 2 
 3 use CGI qw/:standard :html3/;
 4 use Proc::ProcessTable;
 5 
 6 $t = new Proc::ProcessTable;
 7 
 8 # Alle 10 Sekunden neu laden
 9 print header(-Refresh => 
10              "10; URL=$ENV{SCRIPT_NAME}"),
11       start_html('-title' => "Prozeßdaten"), 
12       h1("Prozeßdaten"), "<TABLE border=1>\n";
13 
14 # Sortierbares Listen-Konstrukt aufbauen
15 @sizes = map { [$_->pctcpu, $_->size, $_->cmndline] 
16              } @{$t->table};
17 
18 $count = 20;    # Maximal 20 Prozesse ausgeben
19 
20 # Sortiert ausgeben
21 print TR(th("% CPU"), th("Size (Bytes)"), 
22          th("Prozeß"));
23 
24 foreach $rec (sort { $b->[0] <=> $a->[0] } @sizes) {
25     print TR(td($rec->[0]), td($rec->[1]), 
26           td($rec->[2]));
27     last unless --$count;
28 }
29 
30 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 4 (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 Programm 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 4: proctk.pl

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

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. die 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. Die folgenden Befehle holen und installieren alle notwendigen Module automatisch:

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

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!

Infos

[1] Perl News: http://www.perl.org/news.html
[2] Neu: Perl/Tk Pocket Reference, Stephen Lidie, O'Reilly, 1998
[3] Zum Thema Audi 5000: http://www.sci.kun.nl/thalia/rapdict/dictionary_0.html

Der Autor

Michael Schilli arbeitet als Web-Engineer für AOL/Netscape in Mountain View, Kalifornien. Er ist Autor des 1998 bei Addison-Wesley erschienenen (und 1999 für den englischsprachigen Markt als "Perl Power" herausgekommenen) Buches "GoTo Perl 5" und unter michael@perlmeister.com oder http://perlmeister.com zu erreichen.

Copyright © 1999 Linux-Magazin Verlag