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!
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.
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 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.
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 }
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
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.
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 }
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 |
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();
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!
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 }
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!
Michael Schilliarbeitet 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. |