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.
Un certain nombre de sondes et de points de traçage sont déjà insérés dans le code source. Ces sondes ont pour but d'être utilisées par des développeurs et des administrateurs de base de données. Par défaut, les sondes ne sont pas compilées dans PostgreSQL™ ; l'utilisateur a besoin de préciser explicitement au script configure de rendre disponible les sondes.
Actuellement, seul l'outil DTrace, disponible sur OpenSolaris, Solaris 10 et Mac OS X Leopard, est supporté. DTrace devrait être disponible pour FreeBSD dans le futur et peut-être pour d'autres systèmes d'exploitation.Le projet SystemTap pour Linux fournit aussi un équivalent DTrace. Le support d'autres outils de traces dynamiques est possible théoriquement en modifiant les définitions des macros dans src/include/utils/probes.h.
Par défaut, les sondes ne sont pas disponibles, 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 15.4, « Procédure d'installation » pour plus d'informations.
Un certain nombre de sondes standards sont fournies dans le code source, comme indiqué dans Tableau 118, « Sondes disponibles pour DTrace ». Tableau 119, « Types définis utilisés comme paramètres de sonde » précise les types utilisés dans les sondes. D'autres peuvent être ajoutées pour améliorer la surveillance de PostgreSQL™.
Tableau 118. Sondes disponibles pour DTrace
Nom | Paramètres | Aperçu |
---|---|---|
transaction-start | (LocalTransactionId) | Sonde qui se déclenche au lancement d'une nouvelle transaction. arg0 est l'identifiant de transaction |
transaction-commit | (LocalTransactionId) | Sonde qui se déclenche quand une transaction se termine avec succès. arg0 est l'identifiant de transaction |
transaction-abort | (LocalTransactionId) | Sonde qui se déclenche quand une transaction échoue. arg0 est l'identifiant de transaction |
query-start | (const char *) | Sonde qui se déclenche lorsque le traitement d'une requête commence. arg0 est la requête |
query-done | (const char *) | Sonde qui se déclenche lorsque le traitement d'une requête se termine. arg0 est la requête |
query-parse-start | (const char *) | Sonde qui se déclenche lorsque l'analyse d'une requête commence. arg0 est la requête |
query-parse-done | (const char *) | Sonde qui se déclenche lorsque l'analyse d'une requête se termine. arg0 est la requête |
query-rewrite-start | (const char *) | Sonde qui se déclenche lorsque la ré-écriture d'une requête commence. arg0 est la requête |
query-rewrite-done | (const char *) | Sonde qui se déclenche lorsque la ré-écriture d'une requête se termine. arg0 est la requête |
query-plan-start | () | Sonde qui se déclenche lorsque la planification d'une requête commence |
query-plan-done | () | Sonde qui se déclenche lorsque la planification d'une requête se termine |
query-execute-start | () | Sonde qui se déclenche lorsque l'exécution d'une requête commence |
query-execute-done | () | Sonde qui se déclenche lorsque l'exécution d'une requête se termine |
statement-status | (const char *) | Sonde qui se déclenche à chaque fois que le processus serveur met à jour son statut dans pg_stat_activity.status. arg0 est la nouvelle chaîne de statut |
checkpoint-start | (int) | Sonde qui se déclenche quand un point de retournement commence son exécution. arg0 détient les drapeaux bit à bit utilisés pour distingurer les différents types de points de retournement, comme un point suite à un arrêt, un point immédiat ou un point forcé |
checkpoint-done | (int, int, int, int, int) | Sonde qui se déclenche quand un point de retournement a terminé son exécution (les sondes listées après se déclenchent en séquence lors du traitement d'un point de retournement). arg0 est le nombre de tampons mémoires écrits. arg1 est le nombre total de tampons mémoires. arg2, arg3 et arg4 contiennent respectivement le nombre de journaux de transactions ajoutés, supprimés et recyclés |
clog-checkpoint-start | (bool) | Sonde qui se déclenche quand la portion CLOG d'un point de retournement commence. arg0 est true pour un point de retournement normal, false pour un point de retournement suite à un arrêt |
clog-checkpoint-done | (bool) | Sonde qui se déclenche quand la portion CLOG d'un point de retournement commence. arg0 a la même signification que pour clog-checkpoint-start |
subtrans-checkpoint-start | (bool) | Sonde qui se déclenche quand la portion SUBTRANS d'un point de retournement commence. arg0 est true pour un point de retournement normal, false pour un point de retournement suite à un arrêt |
subtrans-checkpoint-done | (bool) | Sonde qui se déclenche quand la portion SUBTRANS d'un point de retournement se termine. arg0 a la même signification que pour subtrans-checkpoint-start |
multixact-checkpoint-start | (bool) | Sonde qui se déclenche quand la portion MultiXact d'un point de retournement commence. arg0 est true pour un point de retournement normal, false pour un point de retournement suite à un arrêt |
multixact-checkpoint-done | (bool) | Sonde qui se déclenche quand la portion MultiXact d'un point de retournement se termine. arg0 a la même signification que pour multixact-checkpoint-start |
buffer-checkpoint-start | (int) | Sonde qui se déclenche quand la portion d'écriture de tampons d'un point de retournement commence. arg0 contient les drapeaux bit à bit pour distinguer différents types de point de retournement comme le point après arrêt, un point immédiat, un point forcé |
buffer-sync-start | (int, int) | Sonde qui se déclenche quand nous commençons d'écrire les tampons modifiés pendant un point de retournement (après identification des tampons qui doivent être écrits). arg0 est le nombre total de tampons. arg1 est le nombre de tampons qui sont modifiés et n'ont pas besoin d'être écrits |
buffer-sync-written | (int) | Sonde qui se déclenche après chaque écriture d'un tampon lors d'un point de retournement. arg0 est le numéro d'identifiant du tampon |
buffer-sync-done | (int, int, int) | Sonde qui se déclenche quand tous les tampons modifiés ont été écrits. arg0 est le nombre total de tampons. arg1 est le nombre de tampons réellement écrits par le processus de point de retournement. arg2 est le nombre attendu de tampons à écrire (arg1 de buffer-sync-start) ; toute différence reflète d'autres processus écrivant des tampons lors du point de retournement |
buffer-checkpoint-sync-start | () | Sonde qui se déclenche une fois les tampons modifiés écrits par le noyau et avant de commencer à lancer des requêtes fsync |
buffer-checkpoint-done | () | Sonde qui se déclenche après la fin de la synchronisation des tampons sur le disque |
twophase-checkpoint-start | () | Sonde qui se déclenche quand la portion deux-phases d'un point de retournement est commencée |
twophase-checkpoint-done | () | Sonde qui se déclenche quand la portion deux-phases d'un point de retournement est terminée |
buffer-read-start | (ForkNumber, BlockNumber, Oid, Oid, Oid, int, bool) | Sonde qui se déclenche quand la lecture d'un tampon commence. arg0 et arg1 contiennent les numéros de fork et de bloc de la page (arg1 vaudra -1 s'il s'agit de demande d'extension de la relation). arg2, arg3 et arg4 contiennent respectivement l'OID du tablespace, de la base de données et de la relation identifiant ainsi précisément la relation. arg5 est l'identifiant du processus moteur qui a créé la relation temporaire pour un tampon local ou InvalidBackendId (-1) pour un tampon partagé. arg6 est true pour une demande d'extension de la relation, false pour une lecture ordinaire |
buffer-read-done | (ForkNumber, BlockNumber, Oid, Oid, Oid, int, bool, bool) | Sonde qui se déclenche quand la lecture d'un tampon se termine. arg0 et arg1 contiennent les numéros de fork et de bloc de la page (arg1 contient le numéro de bloc du nouveau bloc ajouté s'il s'agit de demande d'extension de la relation). arg2, arg3 et arg4 contiennent respectivement l'OID du tablespace, de la base de données et de la relation identifiant ainsi précisément la relation. arg5 est l'identifiant du processus moteur qui a créé la relation temporaire pour un tampon local ou InvalidBackendId (-1) pour un tampon partagé. arg6 est true pour une demande d'extension de la relation, false pour une lecture ordinaire. arg7 est true si la tampon était disponible en mémoire, false sinon |
buffer-flush-start | (ForkNumber, BlockNumber, Oid, Oid, Oid) | Sonde qui se déclenche avant de lancer une demande d'écriture pour un bloc partagé. arg2, arg3 et arg4 contiennent respectivement l'OID du tablespace, de la base de données et de la relation identifiant ainsi précisément la relation |
buffer-flush-done | (ForkNumber, BlockNumber, Oid, Oid, Oid) | Sonde qui se déclenche quand une demande d'écriture se termine. (Notez que ceci ne reflète que le temps passé pour fournir la donnée au noyau ; ce n'est habituellement pas encore écrit sur le disque.) Les arguments sont identiques à ceux de buffer-flush-start |
buffer-write-dirty-start | (ForkNumber, BlockNumber, Oid, Oid, Oid) | Sonde qui se déclenche quand un processus serveur commence à écrire un tampon modifié sur disque. Si cela arrive souvent, cela implique que shared_buffers est trop petit ou que les paramètres de contrôle de bgwriter ont besoin d'un ajustement.) arg0 et arg1 contiennent les numéros de fork et de bloc de la page. arg2, arg3 et arg4 contiennent respectivement l'OID du tablespace, de la base de données et de la relation identifiant ainsi précisément la relation |
buffer-write-dirty-done | (ForkNumber, BlockNumber, Oid, Oid, Oid) | Sonde qui se déclenche quand l'écriture d'un tampon modifié est terminé. Les arguments sont identiques à ceux de buffer-write-dirty-start |
wal-buffer-write-dirty-start | () | Sonde qui se déclenche quand un processus serveur commence à écrire un tampon modifié d'un journal de transactions parce qu'il n'y a plus d'espace disponible dans le cache des journaux de transactions. (Si cela arrive souvent, cela implique que wal_buffers est trop petit.) |
wal-buffer-write-dirty-done | () | Sonde qui se déclenche quand l'écriture d'un tampon modifié d'un journal de transactions est terminé |
xlog-insert | (unsigned char, unsigned char) | Sonde qui se déclenche quand un enregistrement est inséré dans un journal de transactions. arg0 est le gestionnaire de ressource (rmid) pour l'enregistrement. arg1 contient des informations supplémentaires |
xlog-switch | () | Sonde qui se déclenche quand une bascule du journal de transactions est demandée |
smgr-md-read-start | (ForkNumber, BlockNumber, Oid, Oid, Oid, int) | Sonde qui se déclenche au début de la lecture d'un bloc d'une relation. arg0 et arg1 contiennent les numéros de fork et de bloc de la page. arg2, arg3 et arg4 contiennent respectivement l'OID du tablespace, de la base de données et de la relation identifiant ainsi précisément la relation. arg5 est l'identifiant du processus moteur qui a créé la relation temporaire pour un tampon local ou InvalidBackendId (-1) pour un tampon partagé |
smgr-md-read-done | (ForkNumber, BlockNumber, Oid, Oid, Oid, int, int, int) | Sonde qui se déclenche à la fin de la lecture d'un bloc. arg0 et arg1 contiennent les numéros de fork et de bloc de la page. arg2, arg3 et arg4 contiennent respectivement l'OID du tablespace, de la base de données et de la relation identifiant ainsi précisément la relation. arg5 est l'identifiant du processus moteur qui a créé la relation temporaire pour un tampon local ou InvalidBackendId (-1) pour un tampon partagé. arg6 est le nombre d'octets réellement lus alors que arg7 est le nombre d'octets demandés (s'il y a une différence, il y a un problème) |
smgr-md-write-start | (ForkNumber, BlockNumber, Oid, Oid, Oid, int) | Sonde qui se déclenche au début de l'écriture d'un bloc dans une relation. arg0 et arg1 contiennent les numéros de fork et de bloc de la page. arg2, arg3 et arg4 contiennent respectivement l'OID du tablespace, de la base de données et de la relation identifiant ainsi précisément la relation. arg5 est l'identifiant du processus moteur qui a créé la relation temporaire pour un tampon local ou InvalidBackendId (-1) pour un tampon partagé |
smgr-md-write-done | (ForkNumber, BlockNumber, Oid, Oid, Oid, int, int, int) | Sonde qui se déclenche à la fin de l'écriture d'un bloc. arg0 et arg1 contiennent les numéros de fork et de bloc de la page. arg2, arg3 et arg4 contiennent respectivement l'OID du tablespace, de la base de données et de la relation identifiant ainsi précisément la relation. arg5 est l'identifiant du processus moteur qui a créé la relation temporaire pour un tampon local ou InvalidBackendId (-1) pour un tampon partagé. arg6 est le nombre d'octets réellement écrits alors que arg7 est le nombre d'octets demandés (si ces nombres sont différents, cela indique un problème) |
sort-start | (int, bool, int, int, bool) | Sonde qui se déclenche quand une opération de tri est démarré. arg0 indique un tri de la table, de l'index ou d'un datum. arg1 est true si on force les valeurs uniques. arg2 est le nombre de colonnes clés. arg3 est le nombre de Ko de mémoire autorisé pour ce travail. arg4 est true si un accès aléatoire au résultat du tri est requis |
sort-done | (bool, long) | Sonde qui se déclenche quand un tri est terminé. arg0 est true pour un tri externe, false pour un tri interne. arg1 est le nombre de blocs disque utilisés pour un tri externe, ou le nombre de Ko de mémoire utilisés pour un tri interne |
lwlock-acquire | (LWLockId, LWLockMode) | Sonde qui se déclenche quand un LWLock a été acquis. arg0 est l'identifiant du LWLock. arg1 est le mode de verrou demandé, soit exclusif soit partagé |
lwlock-release | (LWLockId) | Sonde qui se déclenche quand un LWLock a été relâché (mais notez que tout processus en attente n'a pas encore été réveillé). arg0 est l'identifiant du LWLock |
lwlock-wait-start | (LWLockId, LWLockMode) | Sonde qui se déclenche quand un LWLock n'était pas immédiatement disponible et qu'un processus serveur a commencé à attendre la disponibilité du verrou. arg0 est l'identifiant du LWLock. arg1 est le mode de verrou demandé, soit exclusif soit partagé |
lwlock-wait-done | (LWLockId, LWLockMode) | Sonde qui se déclenche quand un processus serveur n'est plus en attente d'un LWLock (il n'a pas encore le verrou). arg0 est l'identifiant du LWLock. arg1 est le mode de verrou demandé, soit exclusif soit partagé |
lwlock-condacquire | (LWLockId, LWLockMode) | Sonde qui se déclenche quand un LWLock a été acquis avec succès malgré le fait que l'appelant ait demandé de ne pas attendre. arg0 est l'identifiant du LWLock. arg1 est le mode de verrou demandé, soit exclusif soit partagé |
lwlock-condacquire-fail | (LWLockId, LWLockMode) | Sonde qui se déclenche quand un LWLock, demandé sans attente, n'est pas accepté. arg0 est l'identifiant du LWLock. arg1 est le mode de verrou demandé, soit exclusif soit partagé |
lock-wait-start | (unsigned int, unsigned int, unsigned int, unsigned int, unsigned int, LOCKMODE) | Sonde qui se déclenche quand une demande d'un gros verrou (lmgr lock) a commencé l'attente parce que le verrou n'était pas disponible. arg0 à arg3 sont les chmps identifiant l'objet en cours de verrouillage. arg4 indique le type d'objet à verrouiller. arg5 indique le type du verrou demandé |
lock-wait-done | (unsigned int, unsigned int, unsigned int, unsigned int, unsigned int, LOCKMODE) | Sonde qui se déclenche quand une demande d'un gros verrou (lmgr lock) a fini d'attendre (c'est-à-dire que le verrou a été accepté). Les arguments sont identiques à ceux de lock-wait-start |
deadlock-found | () | Sonde qui se déclenche quand un verrou mortel est trouvé par le détecteur |
Tableau 119. Types définis utilisés comme paramètres de sonde
Type | Definition |
---|---|
LocalTransactionId | unsigned int |
LWLockId | int |
LWLockMode | int |
LOCKMODE | int |
BlockNumber | unsigned int |
Oid | unsigned int |
ForkNumber | int |
bool | char |
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; }
À son exécution, le script de l'exemple D donne une sortie comme :
# ./txn_count.d `pgrep -n postgres` or ./txn_count.d <PID> ^C Start 71 Commit 70 Total time (ns) 2312105013
SystemTap utilise une notation différente de DTrace pour les scripts de trace, même si les points de trace sont compatibles. Il est intéressant de noter que, lorsque nous avons écrit ce texte, les scripts SystemTap doivent référencer les noms des sondes en utilisant des tirets bas doubles à la place des tirets simples. Il est prévu que les prochaines versions de SystemTap corrigent ce problème.
Vous devez vous rappeler que les programmes DTrace 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.
D'autres exemples de scripts sont disponibles dans le projet dtrace de PgFoundry.
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. Voici les étapes nécessaires pour insérer de nouvelles sondes :
Décider du nom de la sonde et des données nécessaires pour la sonde
Ajoutez les définitions de sonde dans src/backend/utils/probes.d
Inclure pg_trace.h s'il n'est pas déjà présent dans le module contenant les points de sonde, et insérer les macros TRACE_POSTGRESQL aux emplacements souhaités dans le code source
Recompiler et vérifier que les nouvelles sondes sont disponibles
Exemple : Voici un exemple d'ajout d'une sonde pour tracer toutes les nouvelles transactions par identifiant de transaction.
La sonde sera nommée transaction-start et nécessite un paramètre de type LocalTransactionId
Ajout de la définition de la sonde dans src/backend/utils/probes.d :
probe transaction__start(LocalTransactionId);
Notez l'utilisation du double tiret bas dans le nom de la sonde. Dans un script DTrace utilisant la sonde, le double tiret bas doit être remplacé par un tiret, donc transaction-start est le nom à documenter pour les utilisateurs.
Au moment de la compilation, transaction__start est converti en une macro appelée TRACE_POSTGRESQL_TRANSACTION_START (notez que les tirets bas ne sont plus doubles ici), qui est disponible en incluant le fichier pg_trace.h. Il faut ajouter l'appel à la macro aux bons emplacements dans le code source. Dans ce cas, cela ressemble à :
TRACE_POSTGRESQL_TRANSACTION_START(vxid.localTransactionId);
Après une nouvelle compilation et l'exécution du nouveau binaire, il faut vérifier que la nouvelle sonde est disponible en exécutant la commande DTrace suivante. Vous deviez avoir cette sortie :
# dtrace -ln transaction-start ID PROVIDER MODULE FUNCTION NAME 18705 postgresql49878 postgres StartTransactionCommand transaction-start 18755 postgresql49877 postgres StartTransactionCommand transaction-start 18805 postgresql49876 postgres StartTransactionCommand transaction-start 18855 postgresql49875 postgres StartTransactionCommand transaction-start 18986 postgresql49873 postgres StartTransactionCommand transaction-start
Il faut faire attention à d'autres choses lors de l'ajout de macros de trace dans le code C :
Vous devez faire attention au fait que les types de données indiqués pour les paramètres d'une sonde correspondent aux types de données des variables utilisées dans la macro. Dans le cas contraire, vous obtiendrez des erreurs de compilation.
Sur la plupart des platformes, si PostgreSQL™ est construit avec --enable-dtrace, les arguments pour une macro de trace seront évalués à chaque fois que le contrôle passe dans la macro, même si aucun traçage n'est réellement en cours. Cela a généralement peu d'importance si vous rapportez seulement les valeurs de quelques variables locales mais faites bien attention à l'utilisation de fonctions coûteuses. Si vous devez le faire, pensez à protéger la macro avec une vérification pour vous assurer que la trace est bien activée :
if (TRACE_POSTGRESQL_TRANSACTION_START_ENABLED()) TRACE_POSTGRESQL_TRANSACTION_START(some_function(...));
Chaque macro de trace a une macro ENABLED correspondante.