25.4. Traces dynamiques

PostgreSQL™ fournit un support pour les traces dynamiques du serveur de bases de données. Ceci permet l'appel à un outil externe à certains points du code pour tracer son exécution. Actuellement, cette fonctionnalité a pour cible les développeurs de bases de données car il requiert une certaine familiarité avec le code.

Un certain nombre de points de trace, souvent appelés sondes, sont déjà insérés dans le code source. Par défaut, les sondes sont désactivées et l'utilisateur doit explicitement demander au script configure de les rendre disponibles pour PostgreSQL™.

Actuellement, seul l'outil DTrace est supporté, outil qui est disponible uniquement sur Solaris Express et Solaris 10+. Il est attendu que DTrace soit disponible dans le futur sur FreeBSD et Mac OS X. Supporter des outils de traces dynamiques est théoriquement possible en modifiant les définitions des macros PG_TRACE dans src/include/pg_trace.h.

25.4.1. Compiler en activant les traces dynamiques

Par défaut, les sondes sont désactivées, donc vous aurez besoin d'indiquer explicitement au script configure de les activer dans PostgreSQL™. Pour inclure le support de DTrace, ajoutez --enable-dtrace aux options de configure. Lire Section 14.5, « Procédure d'installation » pour plus d'informations.

25.4.2. Sondes disponibles

Quelques sondes standards sont fournies dans le code source (bien sûr, d'autres peuvent être ajoutés si nécessaire pour un problème particulier). Elles sont précisées dans Tableau 25.3, « Sondes disponibles ».

Tableau 25.3. Sondes disponibles

Nom Paramètres Aperçu
transaction__start (int transactionId) Début d'une nouvelle transaction.
transaction__commit (int transactionId) Fin réussie d'une transaction.
transaction__abort (int transactionId) Échec d'une transaction.
lwlock__acquire (int lockid, int mode) Un LWLock a été acquis.
lwlock__release (int lockid, int mode) Un LWLock a été lâché.
lwlock__startwait (int lockid, int mode) Un LWLock n'était pas immédiatement disponible et un processus a commencé à attendre la disponibilité du verrou.
lwlock__endwait (int lockid, int mode) Un processus a été libéré de son attente d'un LWLock.
lwlock__condacquire (int lockid, int mode) Un LWLock a été acquis avec succès quand l'appelant indiquait sans attente.
lwlock__condacquire__fail (int lockid, int mode) Un LWLock n'a pas été acquis quand l'appelant indiquait sans attente.
lock__startwait (int locktag_field2, int lockmode) Une requête pour un verrou lourd (lmgr lock) est en attente car le verrou n'est pas disponible.
lock__endwait (int locktag_field2, int lockmode) Une requête pour un verrou lourd (lmgr lock) a terminé son attente (autrement dit, il a acquis le verrou).

25.4.3. Utiliser les sondes

L'exemple ci-dessous montre un script DTrace pour l'analyse du nombre de transactions sur le système, comme alternative à l'interrogation régulière de pg_stat_database avant et après un test de performance.

#!/usr/sbin/dtrace -qs 

postgresql$1:::transaction-start
{
      @start["Start"] = count();
      self->ts  = timestamp;
}

postgresql$1:::transaction-abort
{
      @abort["Abort"] = count();
}

postgresql$1:::transaction-commit
/self->ts/
{
      @commit["Commit"] = count();
      @time["Total time (ns)"] = sum(timestamp - self->ts);
      self->ts=0;
}

Notez comment le souligné double dans les noms de sonde doit être remplacé par un trait d'union lors de l'utilisation d'un script DTrace. À son exécution, le script de l'exemple D donne une sortie comme :

# ./txn_count.d `pgrep -n postgres`
^C

Start                                          71
Commit                                         70
Total time (ns)                        2312105013

Vous devez vous rappeler que les programmes de trace doivent être écrits soigneusement, sinon les informations récoltées pourraient ne rien valoir. Dans la plupart des cas où des problèmes sont découverts, c'est l'instrumentation qui est erronée, pas le système sous-jacent. En discutant des informations récupérées en utilisant un tel système, il est essentiel de s'assurer que le script utilisé est lui-aussi vérifié et discuter.

25.4.4. Définir les sondes

De nouvelles sondes peuvent être définies dans le code partout où le développeur le souhaite bien que cela nécessite une nouvelle compilation.

Une sonde peut être insérée en utilisant une des macros de trace. Elles sont choisies suivant le nombre de variables à mettre à disposition pour l'inspection de cette sonde. Tracer l'occurence d'un événement se fait en une seule ligne, en utilisant seulement le nom de la sonde, par exemple :

PG_TRACE (ma__nouvelle__sonde);

Des sondes plus complexes peuvent fournir une ou plusieurs variables à l'inspection de l'outil des traces dynamiques en utilisant la macro PG_TRACEn qui correspond au nombre de paramètres après le nom de la sonde :

PG_TRACE3 (mon__evenement__complexe, varX, varY, varZ);

La définition de la sonde transaction__start est montrée ci-dessous :

static void
StartTransaction(void)
{
    ...

    /*
     * génère un nouvel id de transaction
     */
    s->transactionId = GetNewTransactionId(false);

    XactLockTableInsert(s->transactionId);

    PG_TRACE1(transaction__start, s->transactionId);

    ...
}    

Notez comment l'ID de transaction est rendu disponible à l'outil de traces dynamiques.

L'outil de traces dynamiques peut nécessiter une définition plus complète de ces sondes. Par exemple, DTrace requiert que vous ajoutez de nouvelles sondes dans le fichier src/backend/utils/probes.d comme indiqué ici :

provider postgresql {
      ...
      probe transaction__start(int);
      ...
 };

Vous devez faire attention que les types de données spécifiés pour les arguments de la sonde correspondent aux types de données des variables utilisées dans la macro PG_TRACE. Ceci n'est pas vérifié au moment de la compilation. Vous pouvez vérifier que votre sonde nouvellement ajoutée est disponible en recompilant, puis en exécutant le nouveau binaire et, en tant que root, en exécutant une commande DTrace tel que :

dtrace -l -n transaction-start