Reise zum Mittelpunkt der Sonne
Wer programmatisch Solaris-Interna wie CPU- oder Speicherauslastung abfragen möchte, kann dazu selbst geschriebene C-Programme benutzen - oder auf ein passendes Perl-Modul zurückgreifen.
- Peter Dintelmann
Suns Solaris bringt einige einfache Werkzeuge zur Performanceanalyse mit, etwa vmstat, iostat und mpstat zur Anzeige von Prozessorauslastung und I/O-Aktivität. Eine Beschreibung der Kommandos und einige Regeln zur Interpretation der angezeigten Werte findet man unter anderem in Cervones Buch [1].
Zur Überwachung einzelner dieser Informationen per Skript wird - falls man nicht ein kommerzielles Systemmanagement-Werkzeug verwendet - in der Regel die Ausgabe der entsprechenden Kommandos weiterverarbeitet. Effizienter ist es, auf die Originaldaten des Betriebssystems zuzugreifen, die zum Großteil an der kstat-Programmierschnittstelle (für ‘kernel statistics’) abfragbar sind. Das Perl-Modul Solaris::Kstat ermöglicht einen solchen Zugriff. Bei seinem Einsatz hilft das Verständnis der benutzten Datenstrukturen.
Daten aus der Tiefe
Solaris’ kstat-Datensammler ist ein Modul, das der Kernel relativ früh im Boot-Prozess lädt. Es stellt Informationen über einen Gerätetreiber zur Verfügung, auf den Programme via libkstat zugreifen (s. Abb. 1).
Anwendungen beziehen die kstat-Daten aus Funktionsaufrufen der libkstat. Dazu öffnen sie ein Handle, lesen die Daten und schließen dann das Handle. Wie dies bei einem C-Programm geschieht, zeigt Listing 1.
Listing 1: kstat_chain.c
Ein C-Programm zur Ausgabe der verketteten Liste von kstats.
#include <stdio.h>
#include <kstat.h>
int main()
{
kstat_ctl_t *kc;
kstat_t *kp;
char* types[] = {"raw", "named", "intr", "io", "timer"};
/* kstat öffnen */
kc = kstat_open();
if (0 == kc) {
fprintf(stderr, "Kann kstat nicht öffnen.\n");
return -1;
}
/* Liste durchlaufen */
printf("%-25s %-5s %s.%s\n", "Name", "Typ", "Modul", "Inst");
for (kp=kc->kc_chain; kp; kp=kp->ks_next)
printf( "%-25s %-5s %s.%d\n",
kp->ks_name,
types[kp->ks_type],
kp->ks_module,
kp->ks_instance
);
/* kstat schließen */
kstat_close(kc);
return 0;
}
Die Daten sind als verkettete Liste von structs verfügbar, die als kstats bezeichnet werden (s. Abb. 2) Wie daraus erkennbar, identifizieren drei Angaben einen Eintrag: das Modul (ks_module), die Instanz (ks_instance) und der Name (ks_name).
Modul bezeichnet den Datenlieferanten (I/O- und VM-Subsysteme, CPUs, NICs und so weiter), die Instanz ist eine Zahl zur Unterscheidung mehrerer gleichartiger Lieferanten, und der Name identifiziert schließlich das einzelne Datum. Instanzen müssen nicht fortlaufend nummeriert sein. Zum Beispiel bezieht sich bei CPU-Informationen diese Zahl auf die Nummer des Hardware-Slots. Befinden sich in einer Vierprozessormaschine nur zwei CPUs in den Slots 0 und 2, so treten auch nur diese Zahlen als Instanzwerte auf.
ks_type enthält den Typ der Informationen in ks_data. Im Perl-Code braucht man sich jedoch nicht mit diesen Details abzuplagen, da Solaris::Kstat die nötige Konvertierungsarbeit übernimmt und den Benutzer mit einer Hash-Kette verwöhnt (s. u.). Wer diese in voller Schönheit sehen möchte, dem sei ein Einzeiler empfohlen:
perl -MData::Dumper -MSolaris::Kstat -e 'print Dumper new Solaris::Kstat'
Kennt man Modul, Instanz und Name, so kann man mit einer lookup-Funktion auf die gewünschten Daten zugreifen. Das kleine C-Programm aus Listing 1 führt die Elemente der verketteten Liste auf und gibt etwa Folgendes aus:
Name Typ Modul.Inst
kstat_headers raw unix.0
kstat_types named unix.0
... ... ...
cpu_stat0 raw cpu_stat.0
cpu_info0 named cpu_info.0
Ran an den Prozessor
Im Folgenden geht es lediglich um Informationen über CPUs, also um Daten aus den Modulen cpu_info und cpu_stat. Andere lohnende Daten wären diejenigen über Netzadapter sowie über Festplatten.
Listing 2
Ein CGI-Script zeigt ausgewählte CPU-Daten via Web an.
#!/usr/local/bin/perl
use strict;
use CGI qw(:standard -no_xhtml);
use Solaris::Kstat;
use Sys::Hostname;
# ----- kstat Daten erzeugen -----
#
my $kstat = new Solaris::Kstat();
my ($data1, $ticks1) = getCPUData($kstat);
sleep 2;
$kstat->update();
my ($data2, $ticks2) = getCPUData($kstat);
my $tickDiff = $ticks2 - $ticks1;
my $results;
for my $cpu (keys %$data1)
{ next unless $data2->{$cpu};
for my $status (qw(idle user kernel wait))
{ $results->{$cpu}->{$status} = ( $data2->{$cpu}->{$status} -
$data1->{$cpu}->{$status}
) / $tickDiff;
}
}
# ----- HTML generieren -----
#
my $q = new CGI();
my $scale = 400; # Skalierungsfaktor für Anzeige
my $imgPath = "."; # Pfad zu GIFs
my $hostName = Sys::Hostname::hostname();
$q->header(),
$q->start_html(-title => "CPU-Aktivität"),
$q->h2({align => "center"}, "CPU-Aktivität auf $hostName" ),
$q->p( $q->hr({size => 1, width => "80%"}) ),
$q->table
( { border => 3, cellspacing => 0,
align => "center", cellpadding => 20,
},
map $q->Tr(
$q->td( [ "CPU$_",
$q->table
( { border => 0, cellspacing => 6 },
$q->Tr
( [ map $q->td( [ $_->[0],
sprintf("%.2f %%", 100*$_->[1]),
$q->img( { src => "$imgPath/$_->[2]",
height => 3,
width => int $scale * $_->[1]
} )
] )
=> ( ["Gesamt", 1-$results->{$_}->{idle}, "black1px.gif"],
["Kernel", $results->{$_}->{kernel}, "red1px.gif" ],
["User", $results->{$_}->{user}, "blü1px.gif" ],
["Wait", $results->{$_}->{wait}, "green1px.gif"]
)
] )
)
] ) )
=> sort {$a <=> $b} keys %$results
),
$q->p( $q->hr({size => 1, width => "80%"}) ),
$q->end_html();
# ----- getCPUData() ------
#
sub getCPUData
{ my $kstat = shift;
my @onlineCPUs =
grep $kstat->{cpu_info}->{$_}->{"cpu_info$_"}->{state} eq "on-line"
=> keys %{ $kstat->{cpu_info} };
my @stati = qw(idle user kernel wait);
my $cpuData;
@{ $cpuData->{$_} }{@stati} =
@{ $kstat->{cpu_stat}->{$_}->{"cpu_stat$_"} }{@stati}
for @onlineCPUs;
return ($cpuData, $kstat->{unix}->{0}->{system_misc}->{clk_intr});
}
Insgesamt bietet das Modul cpu_stat etwa 90 verschiedene Einzelwerte zu jeder CPU an. Hieraus seien nur einige wenige ausgewählt, um mit einem kleinen CGI-Skript (Listing 2) die Tätigkeit der einzelnen Prozessoren anzuzeigen. Zunächst erzeugt
my $kstat = new Solaris::Kstat();
ein Solaris::Kstat-Objekt. Es hat die Struktur
$kstat->{<Modul>}->{<Instanz>}->{<Name>}->{<Datum>}
Die ersten drei Schichten legt der Konstruktor an, der auch das kstat_open() durchführt. Schlägt dieser Auruf fehl, folgt umgehender Programmtod mit einer entsprechenden Meldung. Möchte man die Fehlerbehandlung selbst übernehmen, empfiehlt sich die etwas vorsichtigere Variante
my $kstat = eval { new Solaris::Kstat() };
Danach kann man $kstat auf Definiertheit prüfen oder die den Fehlertext enthaltende Variable $@ abfragen.
Aus Performancegründen legt das Perl-Modul die vierte Schicht der Daten erst beim Zugriff als ‘tied Hash’ an und füllt sie mit den dann gültigen Werten. Der Zustand des Objekts kann daher vom Zeitpunkt der Abfrage abhängen. Benötigt man mehrere zusammengehörige kstats, sollte man diese gleichzeitig ermitteln, damit die Daten konsistent sind. Hierzu bietet sich auch die update()-Methode an, um die es später noch gehen wird. Ferner ist ein solches Objekt nicht mit Storable::dclone() kopierbar, und ein Schreibzugriff verändert nicht die unterliegenden Kerneldatenstrukturen.
Als erstes ermittelt die Routine getCPUData() die CPUs, die ‘on-line’ sind. Dazu wirft sie einen Blick in
$kstat->{cpu_info}->{#}->{cpu_info#}->{state},
wobei # die Nummer der CPU ist. Den Pfad erhält man zum Beispiel aus der Ausgabe des Perl-Einzeilers von oben.
Von diesen CPUs ermittelt das Skript die jeweiligen Zeiten der Zustände ‘User’, ‘Kernel’, ‘Wait’ und ‘Idle’, die sich in
$kstat->{cpu_stat}->{#}->{cpu_stat#}->{<status>}
finden. Die kstat-Werte geben die Clocktick-Zeiten an, die die CPU im jeweiligen Zustand verbracht hat.
Clockticks zählen Kernelzeit
Clockticks sind die Werte einer Kerneluhr auf Interrupt-Basis. Viele zeitbasierte kstat-Zähler beruhen darauf. Weitere Einzelheiten zur Zeitrechnung auf Solaris finden sich im Buch von Mauro und McDougall [3]. Die Anzahl der Ticks pro Sekunde (100 in der Standardkonfiguration) ermittelt man in Perl mit dem POSIX-Modul vermöge
use POSIX;
$ticksPerSec = sysconf _SC_CLK_TCK;
Einen aktuellen Clocktick-Wert liefert
$kstat->{unix}->{0}->{system_misc}->{clk_intr}
Diesen gibt getCPUData() zusammen mit den CPU-Auslastungsdaten zurück. Mit den beiden obigen Werten lässt sich bereits die Laufzeit einer Maschine bestimmen, wie sie uptime(1) anzeigt (s. Listing 3). Der äquivalente C-Code ist etwa dreimal so lang; er ist zusammen mit den anderen Listings auf dem iX-Listingsserver erhältlich.
Listing 3: uptime in Perl
Mit dem Solaris::Kstat-Modul kann man eine uptime-Variante in Perl erstellen.
#!/usr/local/bin/perl
use POSIX;
use Solaris::Kstat;
# ----- Uptime bestimmen -----
#
my $ticks = Solaris::Kstat->new->{unix}->{0}->{system_misc}->{clk_intr};
my $secs = $ticks / sysconf _SC_CLK_TCK;
# ----- Zeit berechnen -----
#
my $dd = int $secs/86400; $secs -= $dd * 86400;
my $hh = int $secs/3600; $secs -= $hh * 3600;
my $mm = int $secs/60; $secs -= $mm * 60;
my $ss = $secs;
printf "Uptime: %d Tag(e), %d Stunde(n), %d Minute(n), %d Sekunde(n)\n", $dd, $hh, $mm, $ss;
Zur Aktualisierung der kstat-Daten dient die update()-Methode, die das CGI-Skript etwa zwei Sekunden später ausführt. Sie aktualisiert nur bisher gelesene kstats. Der Aufruf von update() führt wie beim Konstruktor zum vorschnellen Programmtod bei Nichtausführbarkeit.
So ergeben sich zwei Momentaufnahmen, aus deren Differenz man die Verteilung der Zwischenzeit auf die verschiedenen CPU-Zustände (User-, Kernel-Mode, Wait und Idle) bestimmen kann. Dieses Vorgehen ist auch für andere Zähler sinnvoll. Für eine realistische Ermittlung der CPU-Belastung würde man die Ergebnisse mehrerer solcher Momentaufnahmenpaare mitteln.
Korrekterweise wäre noch ein Blick auf das Ergebnis von
$kstat->{cpu_info}->{#}->{cpu_info#}->{state_begin}
nötig gewesen, da man sonst für eine CPU, die innerhalb der Wartezeit ihren Zustand von online auf offline und wieder zurück wechselt, falsche Daten bekommt. Eine close()-Methode muss für das Solaris::Kstat-Objekt nicht aufgerufen werden. Dies geschieht automatisch beim Lebensende des Objekts durch Perls DESTROY-Mechanismus.
In einer doppelten for-Schleife über CPUs und Stati bildet das Skript die Differenzen der jeweiligen Clocktick-Werte und setzt sie ins Verhältnis zur vergangenen Clocktick-Zeit, was die zu ermittelnde Verteilung der CPU-Stati auf die Wartezeit ist. Dabei berücksichtigt es nur die CPUs, die in beiden Datensätzen enthalten, also zu beiden Momentaufnahmen online waren.
Anzeige per CGI und HTML
Mit Hilfe des CGI-Moduls erzeugt der Rest des Skripts aus Listing 2 eine HTML-Seite (s. Abb. 3). Anstelle der Idle-Zeit zeigt sie die Gesamtauslastung an, wobei die jeweiligen Balken aus einem Pixel großen GIFs bestehen.
Die Arbeit mit dem kstat-Objekt findet in diesem einfachen Beispiel in einer Subroutine statt. Für komplexere Anwendungen empfiehlt es sich, die Daten in ein eigenes Objekt zu kapseln.
Da die Online-Dokumentation zu kstat nicht allzu üppig ist, sind andere Informationsquellen nötig. Bewährt haben sich Literatur, insbesondere die Bücher von Cockroft/Pettit und Mauro/McDougall [2, 3] sowie die SymbEL-Beispiele aus dem SE-Toolkit. SymbEL ist eine C-ähnliche interpretierte Sprache zur Arbeit mit Kerneldaten in Solaris. Das SE-Toolkit, das neben dem SymbEL-Interpreter Bibliotheken und Beispiele enthält, findet man bei www.sun.com/sun-on-net/performance/se3.
Solaris::Kstat ist eine einfach zu benutzende Schnittstelle zu Solaris-Kernelstatistiken, die Systemverwaltern das Handwerk erleichtern kann. In der Regel müssen sie jedoch etwas Recherche investieren, bis sie die gesuchten Daten im kstat-Heuhaufen finden und die genaue Bedeutung der Werte klar ist.
Peter Dintelmann
ist gelernter Mathematiker und für die Dresdner Bank im Referat Market Information Services tätig.
Literatur
[1] Frank Cervone; Solaris 7 Performance Administration Tools; McGraw Hill 2000; ISBN 0-07-212211-0
[2] Adrian Cockcroft und Richard Pettit; Sun Performance and Tuning - Java and the Internet; Sun Microsystems Press/Prentice Hall 1998; ISBN 0-13-095249-4
[3] Jim Mauro und Richard McDougall; Solaris Internals; Sun Microsystems Press/Prentice Hall 2001; ISBN 0-13-022496-0
iX-TRACT
- Zur Abfrage von Kernelstatistiken unter Solaris eignet sich das Perl-Modul Solaris::kstat.
- Mit seiner Hilfe lassen sich die verketteten Datenstrukturen auswerten.
- In Kombination mit CGI.pm kann man Solaris::kstat zur Online-Überwachung von Servern einsetzen.
Installation
Solaris::Kstat ist Bestandteil des Solaris-Bundles und wird wie jedes auf dem CPAN (www.cpan.org) erhältliche Perl-Modul in den Schritten
perl Makefile.PL
make
make test
make install
installiert. Dies funktioniert auf Solaris 2.5.1, 2.6 und 7 ohne weiteres Zutun. Solaris 8 kommt mit einer eigenen Perl-Distribution einher (Packages SUNWpl5p, SUNWpl5m und SUNWpl5u), die das Modul bereits enthält. Verwendet man auf diesem Betriebssystem ein anderes Perl, so ist nach Angaben des Modul-Entwicklers Alan Burlison der Block
if ($sys ne "SunOS" || $rel !~ /^(5.5.1|5.6|5.7)$/) {
die("Solaris:: is only supported on Solaris 2.5.1, 2.6 \ & 2.7\n");
}
in Makefile.PL auszukommentieren. Die Funktionsfähigkeit soll hiervon laut Burlison nicht beeinträchtigt sein.
Neben Solaris::Kstat enthält das Bundle das Modul Solaris::MapDev, das für Geräte eine Abbildung der kstat-Instanz-Namen auf gebräuchliche Namen wie ‘c0t0d0’ ermöglicht.
(ck)