Virtual Processor Folding

Eine einfache und direkte Möglichkeit zu sehen welche Prozessoren aktiv sind und welche Prozessoren aufgrund von „Virtual Processor Folding“ nicht verfügbar sind, ist der Kernel Debugger kdb.

Mit dem folgenden Kommando wird die gewünschte Information angezeigt:

# echo vpm | kdb
...
VSD Thread State.
CPU  CPPR VP_STATE FLAGS SLEEP_STATE  PROD_TIME: SECS   NSECS     CEDE_LAT

   0     0  ACTIVE      0 AWAKE        0000000000000000  00000000  00  
   1     0  ACTIVE      0 AWAKE        0000000000000000  00000000  00  
   2     0  ACTIVE      0 AWAKE        0000000000000000  00000000  00  
   3     0  ACTIVE      0 AWAKE        0000000000000000  00000000  00  
   4     0  DISABLED    0 AWAKE        0000000000000000  00000000  00  
   5    11  DISABLED    0 SLEEPING     000000005D9F15BE  0F77D2C6  02  
   6    11  DISABLED    0 SLEEPING     000000005D9F15BE  0F77D0C8  02  
   7    11  DISABLED    0 SLEEPING     000000005D9F15BE  217D3F61  02  

#

Die Ausgabe wurde auf einem System mit 2 Prozessor-Kernen und SMT4 erstellt. Die CPUs 4-7 sind DISABLED, das ist der zweite Prozessor-Kern (Core).

Eigene AIX installp-Pakete bauen (Teil 1)

In diesem Blog-Beitrag soll gezeigt werden, wie man unter AIX selber installp Pakete bauen kann. Die Benutzung des Kommandos mkinstallp wird dabei an einem einfachen Beispiel demonstriert. Komplexere Pakete werden in einem späteren Artikel behandelt.

Es ist relativ einfach unter AIX installp-Pakete zu bauen. Benötigt wird dazu das Kommando /usr/sbin/mkinstallp. Sollte das Kommando nicht installiert sein, muß das Paket bos.adt.insttools nachinstalliert werden. Zum Bau von Paketen werden root-Rechte benötigt.

Zunächst legen wir an beliebiger Stelle ein Verzeichnis an, in dem das Paket gebaut werden soll:

# mkdir pwrcmps.installp.example
#

Das Paket soll ein kleines Shell-Skript enthalten:

# cat <<EOF >hello
#! /bin/ksh
print "hello world"
EOF
# chmod a+x hello
#

Das Skript soll später unter /usr/local/bin installiert werden. Zu installierende Files müssen relativ zum Build-Directory (bei uns pwrcmps.installp.example) an der selben Stelle stehen, wie später relativ zum root-Directory. Wir legen daher die notwendige Directory Struktur an und kopieren das Skript hello an die entsprechende Stelle:

# mkdir –p pwrcmps.installp.example/usr/local/bin
# cp hello pwrcmps.installp.example/usr/local/bin
#

Wir wechseln in das Build-Directory und starten das Kommando mkinstallp zum Bauen des Paketes:

# cd pwrcmps.installp.example
# mkinstallp
Using /export/src/installp/pwrcmps.installp.example as the base package directory.
Cannot find /export/src/installp/pwrcmps.installp.example/.info. Attempting to create.
Using /export/src/installp/pwrcmps.installp.example/.info to store package control files.
Cleaning intermediate files from /export/src/installp/pwrcmps.installp.example/.info.

************************************************************
|            Beginning interactive package input           |
|   * - required; () - example value; [] - default value   |
************************************************************

* Package Name (xyz.net) []: pwrcmps.installp
* Package VRMF (1.0.0.0) []: 1.0.0.0
Update (Y/N) [N]:
Number of filesets in pwrcmps.installp (1) [1]:

Gathering info for new fileset (1 remaining)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
* Fileset Name (pwrcmps.installp.rte) []: pwrcmps.installp.example
* Fileset VRMF (1.0.0.0) []: 1.0.0.0
* Fileset Description (some text) []: Example Fileset
Do you want to include a copyright file for this fileset? (Y/N) [N]:
Entering information for the USER part liblpp files
Do you want to include an installp pre_i/u script for this fileset? (Y/N) [N]:
Do you want to include an installp post_i/u script for this fileset? (Y/N) [N]:
Do you want to include a pre-deinstall Script for this fileset? (Y/N) [N]:
Do you want to include an installp pre_rm script for this fileset? (Y/N) [N]:
Do you want to include an installp config script for this fileset? (Y/N) [N]:
Would you like to create ROOT part? (Y/N) [N]:
Bosboot required (Y/N) [N]:
License agreement acceptance required (Y/N) [N]:
Include license files for pwrcmps.installp.example in this package (Y/N) [N]:
Do you want to specify Requisites using a file for this fileset? (Y/N) [N]:
Number of Requisites for pwrcmps.installp.example (1) [0]:
Number of filesystems requiring additional space for pwrcmps.installp.example [0]:

You should include any directories that you are creating in the file count.
(ie: For /usr/proj/myFile, enter 2; 1 for /usr/proj and 1 for /usr/proj/myFile)
Number of USR part files in pwrcmps.installp.example (1) [0]: 1
* 1 of 1. Directory or File Path (/usr/proj/myFile) []: /usr/local/bin/hello
Do you want to make this fileset relocatable? (Y/N) [N]:
Do you want to include an override inventory file for this fileset? (Y/N) [N]:
Do you want to add WPAR attributes to your fileset? (Y/N) [N]:


Using /export/src/installp/pwrcmps.installp.example/.info/pwrcmps.installp.template as the template file.
pwrcmps.installp 1.0.0.0 I
processing pwrcmps.installp.example
creating ./.info/liblpp.a
creating ./tmp/pwrcmps.installp.1.0.0.0.bff
#

Das fertige Endprodukt findet man im Unterverzeichnis tmp:

# ls –l tmp
total 8
-rw-r--r--    1 root     system         2560 Sep 25 09:49 pwrcmps.installp.1.0.0.0.bff
#

Beim Erzeugen des Paketes haben wir der Einfachheit wegen immer die Default-Werte bestätigt. Als Produkt-Namen haben wir pwrcmps.installp angegeben, und als Fileset-Namen pwrcmps.installp.example, jeweils mit der Version 1.0.0.0. Alle Dateien und Verzeichnisse des Pakets müssen explizit mit absolutem Pfad aufgelistet werden! Das kann bei einigen hundert Pfaden etwas unpraktikabel werden, lässt sich aber durch eigene Skripte vereinfachen und automatisieren.

Wir installieren das neu generierte Paket einmal, und überprüfen ob unser Shell-Skript auch installiert wird:

# installp -ad tmp/pwrcmps.installp.1.0.0.0.bff all
+-----------------------------------------------------------------------------+
                    Pre-installation Verification...
+-----------------------------------------------------------------------------+
Verifying selections...done
Verifying requisites...done
Results...

SUCCESSES
---------
  Filesets listed in this section passed pre-installation verification
  and will be installed.

  Selected Filesets
  -----------------
  pwrcmps.installp.example 1.0.0.0            # Example Fileset

  << End of Success Section >>

+-----------------------------------------------------------------------------+
                   BUILDDATE Verification ...
+-----------------------------------------------------------------------------+
Verifying build dates...done
FILESET STATISTICS
------------------
    1  Selected to be installed, of which:
        1  Passed pre-installation verification
 ----
    1  Total to be installed

+-----------------------------------------------------------------------------+
                         Installing Software...
+-----------------------------------------------------------------------------+

installp:  APPLYING software for:
        pwrcmps.installp.example 1.0.0.0

Finished processing all filesets.  (Total time:  1 secs).

+-----------------------------------------------------------------------------+
                                Summaries:
+-----------------------------------------------------------------------------+

Installation Summary
--------------------
Name                        Level           Part        Event       Result
-------------------------------------------------------------------------------
pwrcmps.installp.example    1.0.0.0         USR         APPLY       SUCCESS   
# which hello
/usr/local/bin/hello
# hello
hello world
#

Wir erweitern jetzt unser Skript und lassen die Meldung „hello world, how are you“ ausgeben.

# vi usr/local/bin/hello
…
print "hello world, how are you"
#

Das Paket soll nun neu gebaut werden, allerdings wollen wir die Version auf 1.1.0.0 erhöhen. Natürlich könnten wir das Kommando mkinstallp wieder interaktiv starten und alle notwendigen Informationen erneut eingeben, das ist aber sehr aufwändig und auch nicht notwendig. Das Kommando mkinstallp unterstützt die Angabe eines Template-Files mit allen notwendigen Informationen. Beim Bau der ersten Version unseres Paketes wurde ein solches Template-File unter .info/pwrcmps.installp.template generiert. Wir kopieren dieses Template-File direkt in das Buildroot-Directory und benennen es dabei um in template:

# cp .info/pwrcmps.installp.template template
# cat template
Package Name: pwrcmps.installp
Package VRMF: 1.0.0.0
Update: N
Fileset
  Fileset Name: pwrcmps.installp.example
  Fileset VRMF: 1.0.0.0
  Fileset Description: Example Fileset
  USRLIBLPPFiles
  EOUSRLIBLPPFiles
  Bosboot required: N
  License agreement acceptance required: N
  Include license files in this package: N
  Requisites:
  USRFiles
    /usr/local/bin/hello
  EOUSRFiles
  ROOT Part: N
  ROOTFiles
  EOROOTFiles
  Relocatable: N
EOFileset
#

Das Template-File enthält die von uns interaktiv gemachten Angaben. Wir ändern in diesem Template-File die Versions-Nummer von 1.0.0.0 auf 1.1.0.0:

# vi template
…
Package VRMF: 1.1.0.0
…
  Fileset VRMF: 1.1.0.0
…
#

Wir versuchen nun die Version 1.1.0.0 zu bauen, indem wir mkinstallp mit der Option –T (für Template) und dem Template-File template starten:

# mkinstallp -T template
Using /export/src/installp/pwrcmps.installp.example as the base package directory.
Using /export/src/installp/pwrcmps.installp.example/.info to store package control files.
Cleaning intermediate files from /export/src/installp/pwrcmps.installp.example/.info.
0503-844 mkinstallp: Cannot overwrite existing
        /export/src/installp/pwrcmps.installp.example/.info/pwrcmps.installp.template file.
#

Es kommt eine Fehlermeldung, das Template-File unter .info kann nicht überschrieben werden. Das .info-Verzeichnis wird beim Build-Prozess immer wieder neu angelegt und kann daher einfach gelöscht werden, bevor der nächste Build-Prozeß gestartet wird:

# rm -rf .info
# mkinstallp -T template
Using /export/src/installp/pwrcmps.installp.example as the base package directory.
Cannot find /export/src/installp/pwrcmps.installp.example/.info. Attempting to create.
Using /export/src/installp/pwrcmps.installp.example/.info to store package control files.
Cleaning intermediate files from /export/src/installp/pwrcmps.installp.example/.info.

Using template as the template file.
0503-880 mkinstallp: This file /usr/local/bin/hello
        already exists as a system file.
pwrcmps.installp 1.1.0.0 I
processing pwrcmps.installp.example
creating ./.info/liblpp.a
creating ./tmp/pwrcmps.installp.1.1.0.0.bff
#

Die neue Version des Paketes ist wie gehabt unter tmp zu finden:

# ls -l tmp
total 16
-rw-r--r--    1 root     system         2560 Sep 25 10:08 pwrcmps.installp.1.0.0.0.bff
-rw-r--r--    1 root     system         2560 Sep 25 10:35 pwrcmps.installp.1.1.0.0.bff
#

Auf diese Weise lassen sich leicht Änderungen vornehmen und dann anschließend paketieren.

 

TCP-Verbindungsabbrüche wegen „max assembly queue depth“

Kürzlich hatten wir häufige Programm-Abbrüche von einigen Java-Client Programmen. Im Java Stack-Trace war folgendes zu finden:

[STACK] Caused by: java.io.IOException: Premature EOF
[STACK]           at sun.net.www.http.ChunkedInputStream.readAheadBlocking(Unknown Source)
[STACK]           at sun.net.www.http.ChunkedInputStream.readAhead(Unknown Source)
[STACK]           at sun.net.www.http.ChunkedInputStream.read(Unknown Source)
[STACK]           at java.io.FilterInputStream.read(Unknown Source)

Das Problem tritt in der Klasse ChunkedInputStream in der Methode readAheadBlocking auf. Im Source Code der Methode findet man:

558 /**
559 * If we hit EOF it means there's a problem as we should never
560 * attempt to read once the last chunk and trailers have been
561 * received.
562 */
563 if (nread < 0) {
564 error = true;
565 throw new IOException("Premature EOF");
566 }

Der Wert nread wird kleiner 0, wenn das Ende des Datenstroms erreicht ist. Dies kann auch passieren wenn die Gegenseite die Verbindung unerwartet schließt.

Die Serverseite war in diesem Fall ein AIX-System (AIX 7.1 TL5 SP3). Eine Überprüfung der TCP-Verbindungen auf Abbrüche (Drops) mittels netstat ergab:

$ netstat -p tcp | grep drop
        361936 connections closed (including 41720 drops)
        74718 embryonic connections dropped
                0 connections dropped by rexmit timeout
                0 connections dropped due to persist timeout
                0 connections dropped by keepalive
        0 packets dropped due to memory allocation failure
        0 Connections dropped due to bad ACKs
        0 Connections dropped due to duplicate SYN packets
        1438 connections dropped due to max assembly queue depth
$

Demnach gab es 1438 Verbindungsabbrüche wegen Erreichen der maximalen TCP Assembly Queue Tiefe (max assembly queue depth). Die Queue Tiefe wird über den neuen Kernel Parameter tcp_maxqueuelen konfiguriert, der als Fix für den CVE-2018-6922 eingeführt wurde (siehe: The CVE-2018-6922 fix (FreeBSD vulnerability) and scp) . Der Defaultwert ist 1000. Bei größeren Paket-Laufzeiten kann es zum Überlauf der Queue kommen.

Nach einer Erhöhung des Kernel-Parameters tcp_maxqueuelen sind keine Verbindungsabbrüche mehr aufgetreten.

ProbeVue in Action: Überwachen der „Queue Depth“ von Platten

Platten und Storage Systeme unterstützen Tagged Command Queueing, d.h. angeschlossene Server können mehrere I/O Aufträge an die Platte oder das Storage-System senden ohne zu Warten das ältere I/O-Aufträge fertig sind. Wieviele I/O-Aufträge man an eine Platte senden darf, bevor man warten muss das ältere I/O-Aufträge abgeschlossen wurden, kann über das hdisk Attribut queue_depth unter AIX konfiguriert werden. Für viele hdisk Typen ist der Wert 20 für die queue_depth der Default-Wert. In der Regel erlauben die meisten Storage-Systeme aber noch größere Werte für die Queue-Depth.

Mit Hilfe von ProbeVue lässt sich die Auslastung der Platten-Queue sehr leicht beobachten.

Ab AIX 7.1 TL4 bzw. AIX 7.2 TL0 unterstützt AIX den I/O Probe Manager. Damit lassen sich auf einfache Weise Ereignisse im I/O Stack von AIX tracen. Wird ein I/O vom Platten-Treiber gestartet, so geschieht dies über die Funktion iostart im Kernel, der Request wird an den Adapter-Treiber weitergegeben und geht dann über den Host-Bus-Adapter an das Storage-System. Das Bearbeiten der Antwort wird von der Funktion iodone im Kernel übernommen. Der I/O Probe-Manager unterstützt (unter anderem) Proben an diesen Stellen:

@@io:disk:iostart:read:<filter>
@@io::disk:iostart:write:<filter>
@@io:disk:iodone:read:<filter>
@@io::disk:iodone:write:<filter>

Als Filter kann z.B. ein Hdisk Name wie hdisk2 angegeben werden. Die Proben-Punkte lösen dann nur Ereignisse für die Platte hdisk2 aus. Damit lässt sich schon einmal eine Aktion durchführen wann immer ein I/O für eine Hdisk beginnt oder endet. Damit könnte man z.B. messen wie lange eine I/O Operation dauert oder auch einfach nur mitzählen wieviele I/Os ausgeführt werden. In unserem Beispiel waren wir aber an der Auslastung der Platten-Queue interessiert, d.h. der Anzahl I/Os die an die Platte gesendet aber noch nicht abgeschlossen wurden. Der I/O Probe-Manager besitzt für die I/O Ereignisse  iostart und iodone die Builtin-Variable __diskinfo mit den folgenden Feldern (https://www.ibm.com/support/knowledgecenter/en/ssw_aix_72/com.ibm.aix.genprogc/probevue_man_io.htm):

name          char*     Name der Platte
…
queue_depth   int       Die Queue-Depth der Platte (Wert aus der ODM)
cmds_out      int       Anzahl der ausstehenden I/Os
…

Das Feld cmds_out gibt an wieviele I/Os an die Platte gesendet wurden, für die das I/O noch nicht abgeschlossen ist (Antwort ist noch nicht beim Server angekommen).

Mit dem folgenden Code-Abschnitt ermitteln wir die minimale, maximale und durchschnittliche Anzahl an Einträgen in der Platten-Queue:

@@io:disk:iostart:*:hdisk0     // Nur I/Os für hdisk0 berücksichtigen
{
   queue = __iopath->cmds_out; // Anzahl ausstehende I/Os in Variable queue festhalten
   ++numIO;                    // Anzahl I/Os in der Variablen numIO mitzählen (wegen Durchschnittsbildung)
   avg += queue;               // Variable avg um Anzahl ausstehende I/Os erhöhen
   if ( queue < min )
      min = queue;             // Überprüfen auf Minimum und gegebenenfalls setzen
   if ( queue > max )
      max = queue;             // Überprüfen auf Maximum und gegebenenfalls setzen
}

Die ermittelten Werte geben wir dann einmal pro Sekunde mit Hilfe des Intervall Probe-Managers aus:

@@interval:*:clock:1000
{
   if ( numIO == 0 )
      numIO = 1;    // Verhindert Division durch 0 bei der Durchschnittsbildung
   if ( min > max )
      min = max;
   printf( "%5d  %5d  %5d\n" , min , avg/numIO , max );
   min = 100000;   // Zurücksetzen der Variablen für das nächste Intervall
   avg = 0;
   max = 0;
   numIO = 0;
}

Das vollständige Skript ist auf unserer Webseite zum Download verfügbar: ioqueue.e.

Hier ein Beispiel-Lauf des Skriptes für die Platte hdisk13:

# ./ioqueue.e hdisk13
  min    avg    max
    1      1      2
    1      1      9
    1      1      2
    1      1      8
    1      1      2
    1      1      2
    1      1      8
    1      1     10
    1      1      2
    1      1      1
    1      1     10
    1      1      2
    1      1     11
...

Das Skript erwartet die Angabe einer hdisk als Argument und gibt dann einmal pro Sekunde die ermittelten Werte für die angegebene hdisk aus.

In der Beispiel-Ausgabe sieht man das die maximale Anzahl der Einträge in der Platten-Queue 11 ist. Eine Erhöhung des Attributes queue_depth macht daher aus Performance-Sicht keinen Sinn.

Hier ein anderes Beispiel:

# ./ioqueue.e hdisk21
  min    avg    max
    9     15     20
   11     17     20
   15     19     20
   13     19     20
   14     19     20
   17     18     20
   18     18     19
   16     19     20
   13     18     20
   18     19     19
   17     19     20
   18     19     20
   17     19     19
...

In diesem Fall wird der maximale Wert 20 (die hdisk21 hat eine queue_depth von 20) regelmäßig erreicht. Eine Erhöhung der queue_depth kann in diesem Fall zu einer Verbesserung des Durchsatzes führen.

Das Beispiel-Skript lässt sich natürlich noch beliebig erweitern, man könnte z.B. noch den Durchsatz erfassen, oder die Wartezeit von I/Os in der Wait-Queue oder auch die Position und Größe jedes I/Os auf der Platte. Das dargestellte Beispiel zeigt wie einfach man Informationen zu I/Os mit Hilfe von ProbeVue ermitteln kann.

Weitere Artikel zum Thema ProbeVue

ProbeVue: Praktische Einführung

ProveVue: Praktische Einführung II

ProbeVue in Action: Identifizieren eines Abstürzenden Prozesses

ProbeVue in Action: Überwachen der „Queue Depth“ von Platten

 

ProbeVue in Action: Identifizieren eines Abstürzenden Prozesses

Kürzlich meldete unser Monitoring ein volles /var-Filesystem auf einem unserer Systeme. Schnell war herausgefunden das Core-Files im Verzeichnis /var/adm/core das Filesystem gefüllt hatten. Es stellte sich schnell heraus das alle Core-Files von Perl stammten. Allerdings konnte anhand der Core-Files nicht festgestellt werden welches Perl-Skript den Absturz von Perl verursacht hatte. Ein Blick auf die Zeitstempel der Core-Files ließ leider kein Muster erkennen:

-bash-4.4$ ls -ltr /var/adm/core
total 2130240
drwxr-xr-x    2 root     system          256 Jan 29 10:20 lost+found/
-rw-------    1 root     system    100137039 Jun 26 04:51 core.22610328.26025105.Z
-rw-------    1 root     system     99054991 Jun 26 06:21 core.21102892.26042104.Z
-rw-------    1 root     system     99068916 Jun 26 08:06 core.18153840.26060607.Z
-rw-------    1 root     system    100132866 Jun 26 08:21 core.19005848.26062105.Z
-rw-------    1 root     system     97986020 Jun 26 16:36 core.15270246.26143608.Z
-rw-------    1 root     system     99208958 Jun 26 22:21 core.22675838.26202106.Z
-rw-------    1 root     system     97557063 Jun 27 01:06 core.5505292.26230604.Z
-rw-------    1 root     system     98962499 Jun 27 10:06 core.8257960.27080603.Z
-rw-------    1 root     system     99804173 Jun 27 14:51 core.18940202.27125107.Z
-rw-------    1 root     system     99633676 Jun 28 03:21 core.17563960.28012107.Z
-rw-------    1 root     system     99116032 Jun 28 19:06 core.8651210.28170608.Z
-bash-4.4$

Auch die Einträge im Error Report lieferten keine Informationen um welches Perl-Skript es sich hier handelt und auf welchem Wege dieses gestartet wurde.

 

-bash-4.4$ sudo errpt -j A924A5FC –a
...
---------------------------------------------------------------------------
LABEL:          CORE_DUMP
IDENTIFIER:     A924A5FC

Date/Time:       Wed May 29 15:21:25 CEST 2019
Sequence Number: 17548
Machine Id:      XXXXXXXXXXXX
Node Id:         XXXXXXXX
Class:           S
Type:            PERM
WPAR:            Global
Resource Name:   SYSPROC        

Description
SOFTWARE PROGRAM ABNORMALLY TERMINATED

Probable Causes
SOFTWARE PROGRAM

User Causes
USER GENERATED SIGNAL

        Recommended Actions
        CORRECT THEN RETRY

Failure Causes
SOFTWARE PROGRAM

        Recommended Actions
        RERUN THE APPLICATION PROGRAM
        IF PROBLEM PERSISTS THEN DO THE FOLLOWING
        CONTACT APPROPRIATE SERVICE REPRESENTATIVE

Detail Data
SIGNAL NUMBER
         11
USER'S PROCESS ID:
              13369662
FILE SYSTEM SERIAL NUMBER
           1
INODE NUMBER
                 69639
CORE FILE NAME
/var/adm/core/core.13369662.29132106
PROGRAM NAME
perl
STACK EXECUTION DISABLED
           0
COME FROM ADDRESS REGISTER

PROCESSOR ID
  hw_fru_id: 1
  hw_cpu_id: 19

ADDITIONAL INFORMATION

Unable to generate symptom string.
Too many stack elements.
-bash-4.4$

Die einzige Information, die sich entnehmen ließ, war das die Prozesse mit dem Signal 11 (SIGSEGV) beendet wurden, also aufgrund eines Zugriffs auf eine ungültige Speicher-Adresse.

Es stellte sich die Frage: wie kann ermittelt werden um welches Perl-Skript es sich handelt und auf welchem Wege es gestartet wird.

Dies sollte sich eigentlich mit Hilfe von ProbeVue herausfinden lassen.

Es bot sich der sysproc-Provider an, der im Falle eines Exits eines Prozesses ein Event generiert. Über die spezielle built-in Variable __exitinfo werden genauere Informationen zum Exit, wie Exit-Status oder die Signal-Nummer die den Prozess beendet hat bereitgestellt. Damit lässt sich schon einmal die folgende Probe schreiben:

1: @@sysproc:exit:*
2: when ( __exitinfo->signo == 11 )
3: {
4:         printf( "%llu:  %s\n" , __pid , __pname );
5:         ptree(10);
6: }

Die 6 Zeilen seien hier kurz erklärt:

  1. Der Probe-Point: Provider ist sysproc, Event is exit, * bedeutet beliebiger Prozeß
  2. Durch Verwendung des obigen Prädikats wird der nachfolgende Action Block nur ausgeführt, wenn der Prozeß mit dem Signal 11 (SIGSEGV) beendet wurde.
  3. Start des Action Blocks.
  4. Ausgeben der PID und des Programm-Namens des Prozesses.
  5. Die Funktion ptree gibt den Vater, Großvater usw. (bis 10 Ebenen) des Prozesses aus.
  6. Hier endet der Action Block.

Leider lassen sich hier keine Argumente auflisten, mit denen das Programm gestartet wurde, was in unserem Falle mit Perl den Namen des Skriptes geliefert hätte. Aber immerhin bekommt man über die Funktion ptree heraus auf welchem Wege das Programm aufgerufen wurde, was in manchen Fällen schon ausreicht um das Programm dann letztlich zu identifizieren.

Wir hätten zur Identifikation gerne noch die Information über die Argumente mit denen Perl aufgerufen wurde. Diese Information liefert der syscall-Provider für den System-Call execve mit dem das Programm letztlich gestartet wird. Der Probe-Point ist damit syscall:*:execve:entry, da beim Eintritt in die Funktion die Argumente bekannt sind. Die Signatur von execve für ProbeVue sieht dann so aus:

int execve( char* , struct arg_t* args , char* );

Hierbei ist das erste Argument (wird von ProbeVue als __arg1 bereitgestellt) der Programm-Name. Das zweite Argument ist eine Struktur mit den gesuchten Argumenten (bereitgestellt über __arg2). Über das dritte Argumente hat man Zugriff auf Environment-Variablen, was aber in unserem Falle nicht von Bedeutung ist. Die Struktur struct arg_t sieht für 5 Argumente so aus:

struct arg_t
{
        union
        {
                char* arg[5];
                int num[5];
        } u;
};

Diese Struktur und die Signatur von execve müssen im ProbeVue-Skript deklariert werden, bevor man diese benutzen kann.

Beim Zugriff auf die Argumente ergibt sich dann noch ein kleines weiteres Problem: wenn der Action Block für unsere Probe angesprochen wird, sind wir im Kernel-Modus, die Argumente selber sind aber Adressen im User-Mode des Prozesses. Die Daten (in diesem Falle Zeichenketten) müssen aus dem User-Adreß-Raum herauskopiert werden. Dies erledigt die Funktion get_userstring.

Wir lassen uns bei jedem execve die PID, den Programm-Namen, das Kommando und bis zu 5 Argumente ausgeben. Dies ist im folgenden Programm implementiert:

#! /usr/bin/probevue

struct arg_t
{
        union
        {
                char* arg[5];
                int num[5];
        } u;
};

int execve( char* , struct arg_t* args , char* );

@@syscall:*:execve:entry
{
        __auto String command[128];
        __auto String argument[128];
        __auto struct arg_t argv;
        copy_userdata( __arg2 , argv );
        command = get_userstring( __arg1 , -1 );
        argument = get_userstring( argv.u.arg[0] , -1 );
        printf( "%llu: %s called execve(%s) with arguments: %s " , __pid , __pname , command , argument )
;
        if ( argv.u.num[1] != 0 )
        {
                argument = get_userstring( argv.u.arg[1] , -1 );
                printf( "%s " , argument );
                if ( argv.u.num[2] != 0 )
                {
                        argument = get_userstring( argv.u.arg[2] , -1 );
                        printf( "%s " , argument );
                        if ( argv.u.num[3] != 0 )
                        {
                                argument = get_userstring( argv.u.arg[3] , -1 );
                                printf( "%s " , argument );
                                if ( argv.u.num[4] != 0 )
                                {
                                        argument = get_userstring( argv.u.arg[4] , -1 );
                                        printf( "%s " , argument );
                                }
                        }
                }
        }
        printf( "\n" );
}

@@sysproc:exit:*
when ( __exitinfo->signo == 11 )
{
        printf( "%llu:  %s\n" , __pid , __pname );
        ptree(10);
}

Das Skript haben wir capture_segv.e genannt und ausführbar gemacht.

In der Theorie sollte das Programm nach dem Start alle startenden Programme mit PID, Namen und bis zu 5 Argumenten ausgeben. Außerdem erfolgt eine Ausgabe wenn ein Prozess mit dem Signal 11 (SIGSEGV) abgebrochen wird. Die entsprechende PID kann man dann weiter oben in der Ausgabe suchen und damit das Programm mit Argumenten identifizieren.

Leider ergibt sich in der Praxis das folgende kleine Problem: wenn ein Programm nach dem execve sehr schnell beendet wird, bevor ProbeVue mit get_userstring die Argumente kopieren kann, kommt es bei get_userstring zu einem Zugriff auf eine nicht mehr existierende Adresse und das ProbeVue Skript wird abgebrochen. Wir haben dies umgangen, indem wir das ProbeVue Skript einfach über eine Endlos-Schleife immer wieder starten:

# while true; do ./capture_segv.e >>/tmp/wait_for_segv ; done

Wir haben das ProbeVue Skript dann einige Stunden laufen lassen, bis es dann wieder zu einem Core-File von Perl kam. Die Datei /tmp/wait_for_segv enthielt ca 23.000 Zeilen! Wir haben hier nur die relevanten Zeilen aufgelistet:

# cat /tmp/wait_for_segv
…
8651210: ksh called execve(xxxx_hacheck.pl) with arguments: xxxx_hacheck.pl -c
8651210: ksh called execve(/var/opt/OV/bin/instrumentation/xxxx_hacheck.pl) with arguments: xxxx_hacheck
.pl -c
20054518: ksh called execve(/bin/proc2mon.pl) with arguments: proc2mon.pl
…
8651210:  perl

     PID              CMD
       1              init
                        |
                        V
9634196              ovcd
                        |
                        V
9765232              opcacta
                        |
                        V
8651210              perl    <=======
…

Man sieht das Perl über das Programm opcacta gestartet wurde, welches selbst von ovcd gestartet wurde. Diese Prozesse gehören zu HP OpenView das hier im Einsatz ist. Weiter oben in der Ausgabe kann man sehen das das Perl-Skript /var/opt/OV/bin/instrumentation/xxxx_hacheck.pl gestartet wurde. Damit haben wir das Skript gefunden das die vielen Core-Files erzeugt.

Das Skript wurde erst kürzlich geschrieben und muß offensichtlich noch einmal untersucht und überarbeitet werden.

Mit Hilfe von ProbeVue hat ein kurzes Skript und einige Stunden Warten ausgereicht um die Ursache des Problems zu finden! ProbeVue ist aber nicht nur bei der Untersuchung von Problemen nützlich; auch beim Performance Monitoring erweist sich ProbeVue als extrem hilfreich.

Weitere Artikel zum Thema ProbeVue

ProbeVue: Praktische Einführung

ProveVue: Praktische Einführung II

ProbeVue in Action: Identifizieren eines Abstürzenden Prozesses

ProbeVue in Action: Überwachen der „Queue Depth“ von Platten

 

Zugriff auf das Ablaufdatum des Update Access Keys von AIX aus

Im Zuge der Einführung von POWER8 Systemen hat IBM auch den „Update Access Key“ eingeführt, der für das Durchführen von Firmware Updates des Managed Systems notwendig ist. Neu ausgelieferte System haben standardmäßig einen Update Access Key der in der Regel nach 3 Jahren abläuft. Danach kann der Update Access Key bei bestehen eines Wartungsvertrages jeweils um ein halbes Jahr verlängert werden (https://www.ibm.com/servers/eserver/ess/index.wss).

Wann der aktuelle Update Access Key abläuft lässt sich natürlich leicht über die HMC herausfinden, GUI oder CLI. Man kann das Ablaufdatum aber auch über das Kommando lscfg von AIX aus anzeigen:

Im Falle von AIX 7.1 sieht dies so aus:

$ lscfg -vpl sysplanar0 | grep -p "System Firmware"
      System Firmware:
...
        Microcode Image.............SV860_138 SV860_103 SV860_138
        Microcode Level.............FW860.42 FW860.30 FW860.42
        Microcode Build Date........20180101 20170628 20180101
        Microcode Entitlement Date..20190825
        Hardware Location Code......U8284.22A.XXXXXXX-Y1
      Physical Location: U8284.22A.XXXXXXX-Y1

Im Falle von AIX 7.2 ist die Ausgabe geringfügig anders:

$ lscfg -vpl sysplanar0 |grep -p "System Firmware"
      System Firmware:
...
        Microcode Image.............SV860_138 SV860_103 SV860_138
        Microcode Level.............FW860.42 FW860.30 FW860.42
        Microcode Build Date........20180101 20170628 20180101
        Update Access Key Exp Date..20190825
        Hardware Location Code......U8284.22A.XXXXXXX-Y1
      Physical Location: U8284.22A.XXXXXXX-Y1

Relevant sind die Zeilen „Microcode Entitlement Date“ bzw. „Update Access Key Exp Date„.

AIX: Anwendungen des namefs-Filesystems

Gelegentlich benötigt man ein Verzeichnis (oder ein Filesystem) an einer anderen Stelle im Filesystem oder vielleicht sogar an mehreren verschiedenen Stellen im Filesystem. Anstatt das Problem mit symbolischen Links zu lösen, kann man elegant das namefs-Filesystem zum Einsatz bringen.

In folgenden Beispie wird /data/in an anderer Stelle benötigt:

# ls -l /data/in
total 16
-rw-r--r--    1 root     system          554 May 14 16:10 file1
-rw-r--r--    1 root     system          381 May 14 16:10 file2
# ls -l /other/place
total 0
#

Mounten des Directories an die gewünschte Stelle /other/place:

# mount -v namefs /data/in /other/place
# ls -l /other/place
total 16
-rw-r--r--    1 root     system          554 May 14 16:10 file1
-rw-r--r--    1 root     system          381 May 14 16:10 file2
#

Der Mount mit dem namefs-Filesystem bietet zusätzlich die Möglichkeit Mount-Optionen anzugeben, die dann nur für das Verzeichnis gelten. Man kann damit z.B. ein Verzeichnis auch mit Direct-I/O Mounten, obwohl das ursprüngliche Verzeichnis nicht mit Direct-I/O gemountet wurde:

# mount -v namefs -o dio /data/in /other/place
# mount
  node       mounted        mounted over    vfs       date        options     
-------- ---------------  ---------------  ------ ------------ ---------------
         /dev/hd4         /                jfs2   May 02 11:30 rw,log=/dev/hd8
...
         /data/in         /other/place     namefs May 14 16:14 rw,dio         
#

Bei Zugriffen auf die Dateien unterhalb /other/place wird nun Direct-I/O verwendet, greift man aber über die „Originale“ unter /data/in zu, wird kein Direct-I/O verwendet!

Der Zugriff auf Dateien ist allerdings wie bei NFS auf das unterliegende physikalische Filesystem beschränkt. Dies läßt sich leicht anhand des Filesystems / demonstrieren. Wir mounten / per namefs unter /mnt und schauen uns /mnt/usr und /mnt/var an:

# mount -v namefs / /mnt
# ls -l /mnt/usr /mnt/var
/mnt/usr:
total 0
lrwxrwxrwx    1 root     system           11 Apr 17 07:49 lib -> /../usr/lib

/mnt/var:
total 0
#

Die Verzeichnisse sind leer bzw. enthalten einen symbolischen Link, /usr und /var sehen etwas anders aus!

Dies kann man natürlich auch ausnutzen, z.B. in Fällen bei denen interessante Daten übermountet wurden. Wir haben unterhalb von /home eine Datei abgelegt, bevor /dev/hd1 auf /home gemountet wurde. Über das gerade auf /mnt gemountet root-Filesystem kann man auf diese übermounteten Daten zugreifen:

# ls -l /mnt/home
total 0
-rw-r--r--    1 root     system            0 May 14 17:48 overmounted_file
#

Eine weitere Anwendung besteht darin ein Verzeichnis vor überschreiben zu schützen. Wir demonstrieren dies am Verzeichnis /data mit 2 Test-Dateien:

# ls -l /data
total 16
-rw-r--r--    1 root     system          554 May 14 17:52 file1
-rw-r--r--    1 root     system          381 May 14 17:52 file2
# cp /etc/hosts /data
# ls -l /data
total 24
-rw-r--r--    1 root     system          554 May 14 17:52 file1
-rw-r--r--    1 root     system          381 May 14 17:52 file2
-rw-r--r--    1 root     system         2075 May 14 17:54 hosts
#

Überschreiben oder Ändern von Daten ist aktuell noch möglich, wie das erfolgreiche cp-Kommando zeigt. Jetzt schützen wir die Daten, indem wir einen Mount mit dem namefs-Filesystem und der Option ro (Read-Only) durchführen:

# mount -v namefs -o ro /data /data
# cp /etc/services /data
cp: /data/services: Read-only file system
#

Die Daten können offensichtlich nicht mehr geändert werden. Hier haben wir /data mit einer Read-only Version von sich selbst übermountet!

Mounts mit dem Pseudo-Filesystem namefs können nicht nur auf jfs2-Filesystemen, sondern auch für NFS-Filesysteme oder das procfs-Filesystem durchgeführt werden.

Als Letztes zeigen wir noch das Mounten einer Datei an einer anderen Stelle des Filesystems. Wir wollen die Datei /etc/hosts über den Namen /hosts verfügbar machen. Dazu legen wir zunächst eine leere Datei /hosts an und mounten dann die Datei /etc/hosts über diese leere Datei:

# touch /hosts
# ls -l /hosts
-rw-r--r--    1 root     system            0 May 14 17:59 /hosts
# mount -v namefs /etc/hosts /hosts
# ls -l /hosts
-rw-rw-r--    1 root     system         2075 Apr 26 10:47 /hosts
#

Vor dem Mount ist /hosts 0 Bytes groß, nach dem Mount 2075 Bytes!

Das namefs-Filesystem bietet also einige interessante Möglichkeiten, die bei einige Problemstellungen nützlich sein können.