![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
||||||||
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
|||||||
![]() |
|||||||
![]() |
|
||||||
![]() |
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.
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
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 } |
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
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 } |
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.
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(); |
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.
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 } |
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