mercredi juin 16, 2010

Gagnez en observabilité et en rapidité d'analyse avec DTrace

Translate in English

Faisant suite à la présentation de William Roche sur DTrace, le groupe d'utilisateurs Solaris se réunit pour une mise en pratique ce soir à Supinfo. Afin de comprendre les atouts de cette technologie, j'ai repris dans ce poste, les principaux points exposés par William lors de son intervention. Avec ces éléments, vous serez capable de :

  1. vous lancer dans DTrace
  2. briller dans l'analyse et la résolution de vos problèmes de production !
  3. nous rejoindre ce soir pour débuter l'expérience
Un peu d'historique

Dtrace existe depuis l'origine de Solaris 10. Il permet d'observer dynamiquement non seulement Solaris mais également les applications, incluant Java, python, php, MySQL,... et bien d'autres. DTrace s'accompagne d'un langage (D) qui permet d'écrire des programmes servant à collecter des informations, voir même à agir sur le système.

DTrace a été conçu pour être utilisé en production

Par exemple, DTrace est conçu pour ne pas causer de Panic, de crash, de corruption de données, ou de dégradation de performance...

Comment ?
En le concevant sur la même idée que Java : un script DTrace va être exécuté en étant interprété dans le noyau, et en passant des filtres d'exécutions et des filtres de données au plus prêt possible où elles sont générées. Ce qui aboutit notamment à une réduction au maximum du temps de capture, de sélection et de traitement des données : limitant d'autant l'impact d'une telle analyse sur un système en production.

DTrace révolutionne la méthode d'analyse

Avant, (1) vous partiez d'une hypothèse, (2) vous instrumentiez le binaire (il vous fallait les sources, pour y ajouter votre instrumentation :  printf("coucou\\n"); ce qui n'était pas toujours simple, puis recompiler, et enfin exécuter), (3) vous collectiez vos données, (4) vous les analysiez, (5) vous revoyez vos hypothèses... et vous relanciez la boucle d'analyse.

Avec DTrace, la partie instrumentation est très fortement réduite :

  • pas besoin de recompiler un programme,
  • capaciter de s'attacher à un programme qui est déjà en cours d'exécution,
  • capaciter de mettre des points d'observations,....
tous cela au plus proche de là où sont générées les données !

Le programme qui passe dans l'endroit que l'on souhaite surveiller génére un évènement, et DTrace capture l'information à la source. La boucle d'analyse est réduite et on peut se concentrer sur le problème à résoudre et non sur l'instrumentation proprement dite.

Pourquoi Dynamic ?

Il y a énormément de mécanismes qui permettent d'analyser des erreurs sur un programme, comme par exemple, un core applicatif, ou un crash dump système, avec les outils d'analyse postmortem comme mdb ou dbx.

Par contre pour analyser des problèmes transient, il existe également truss voir mdb... Mais avec des limitations, comme par exemple l'impossibilité de couvrir les appels systèmes.

Pouvoir comprendre sur un système, pourquoi il y a énormément de threads, qui les réveille, qui les mets en sommeille, c'est à ce type d'information et bien d'autre que Dtrace nous donne en temps réel : des éléments d'analyse précieux.

Dtrace est un framework accessible à travers l'interface DTrace (7D). Pour parler à la partir noyau, nous disposons de la librairie libDTrace (3LIB). dtrace (1M) , lockstat (1M), plockstat (1M) utilisent cette librairie pour collecter des données.


En dessous du framework, il y a le coeur du système : les providers, permettant de collecter les données, de mettre les points d'observations : sdt (stat du système), sysinfo (mpstat), vminfo(vmstat), fasttrap (memory trap, etc...), lockstat (lock système), sched (info sur le scheduling : qui se réveille, qui s'endort, ...), proc (instrumentation d'un processus user land) , syscall (appels système), mib (mib II), io , nfsv4, ip, fbt (les fonctions du noyaux : tous les points d'entrées et de départs de presque toutes les fonctions du noyaux)... pour n'en siter que quelques uns ! Les providers activés vont instrumenter le code et l'exécution du code dans le noyau va faire un petit bond dans le provider pour collecter les informations.

MySQL, Java, php, quand ils se lancent viennent s'authentifier au niveau du framework DTrace et enregistrer leurs providers. Par défaut, ils ne sont pas actifs. Si par contre vous avez besoin d'une information précise, il suffit d'activer le probe voulu.

La richesse de notre implémentation DTrace vient des providers !

Un éditeur de logiciel peut mettre ses points d'observation dans son logiciel : USDT (User-Level Statically Defined Tracing) et fournir ainsi son provider.

On peut avoir plusieurs observateurs en parallèle qui regardent le même "probe". Quand un utilisateur arrête un programme DTrace, tous les probes qu'il utilisaient sont dés référencés de 1, jusqu'à potentiellement la désactivation du probe.

La syntaxe d'un probe

Probe : <provider>: <module>:<fonction>: <name>
Exemple : FBT:XXX:FFF:ENTRY
Le DTrace framework s'executant dans le Kernel,  il n'y a pas de context switch lors de la rencontre d'une trace.
Nous pouvons avoir des buffers mémoires DTrace par CPU (meilleur gestion des performances), et nous capturons exactement l'information consolidée : par exemple le nombre de fois que l'on rentre dans une fonction... Une grosse différente avec la méthode printf("coucou\\n");.

Pour obtenir la liste de tous les providers (enregistrés à l'instant t) : # dtrace -l

Puis pour avoir des informations plus ciblées :
# dtrace -P <provider>
# dtrace -m <module>
# dtrace -f <function>
# dtrace -n <name>

Au passage, il est important de noter les droits d'utilisation de dtrace, généralement réservés à root. Vous pouvez déléguer tout ou parti de ces droits, en utilisant les niveaux suivants dans /etc/user_attr : dtrace_user, dtrace_proc, dtrace_kernel

Le language D

Le language D a presque la syntaxe du C avec la manière d'écrire un programme awk(1M).  C'est un script qui définit un certain nombre de prédicats et qui a chaque fois que ce prédicat est rencontré, génère une action. Dans l'action on peut positionner des variables pouvant elles-mêmes faire l'objet de conditions.

DTrace c'est un programme construit de cette manière qui dispose de variables globales, locales au thread d'exécutions, locales au provider, avec des variables intégrées comme execname et timestamp.
On définit ensuite l'action, par exemple : tracer la données, enregistrer une trace de la stack, enregistrer la donnée.

Quand un évènement est déclanché et que le prédicat est vrai, l'action est exécutée.

Example : "Print all the system calls executed by bash"
#!/usr/sbin/dtrace -s
syscall:::entry   => probe description
/execname=="bash"/ => /predicat/
{
printf("%s called\\n", probefunc); => action statements
}

Nous venons d'écrire une commande truss capable de tracer TOUS les "bash" du système.

Predicat
Execute l'action seulement si la condition du predicat est vrai.
La condition est une expression en language D.

Action
Certaines actions peuvent changer l'état du système de manière bien définie (-w).

Exemple d'actions pour enregistrer les données :

  • trace()
  • printf() : la vérification de la cohérence sur le nombre et sur le type des paramètres est faite à la compilation du script : mieux qu'en C !
  • stack() enregistre la stack kernel
  • ustack() enregistre la stack userland du thread dont la stack est exécutée
  • printa() qui permet d'afficher une aggrégation
  • exit() pour sortir

Actions "destructive" : "-w"

  • stop() un process
  • raise() envoi un signal à un process
  • breakpoint() le kernel
  • panic() le système
  • chill() introduit une latence

Aggregation
Parfois ce n'est pas l'évènement mais la tendance qui nous intéresse : "combien de fois appelle-t-on cette fonction avec un argument entre 4k et 8k ?"
Ce type d'information va nous être fourni au travers des fonctions suivantes : sum(),count(), min(), max(), avg(), quantize() en puissance de 2 (exponentiel), lquantize() en mode linéaire

L'aggrégation peut être collecté dans un tableau de taille indéterminé.
L'aggrégation peut porter un nom : @name[keys] = aggfunc(args);

Par exemple, si vous souhaitez regarder la répartion des tailles de malloc() dans le process que l'on suit :

Script dtrace : aggr2.d
#!/usr/sbin/dtrace -s
pid$target:libc:malloc:entry
{
  @["Malloc Distribution"]=quantize(arg0);
}


$ aggr2.d -c who (le pid du process "who" qui va être lancé à ce moment là va remplacer pid$target dans le script dtrace aggr2.d)

trace: script './aggr2.d' matched 1 probe
...
dtrace: pid 6906 has exited
Malloc Distribution
        value ------------- Distribution ------------- -----------------count
           1  |                                                         0
           2  |@@@@@@@@@@@@@@@@@                                        3
           4  |                                                         0
           8  |@@@@@@                                                   1
           16 |@@@@@@                                                   1
           32 |                                `                        0
           64 |                                                         0
          128 |                                                         0
          256 |                                                         0
          512 |                                                         0
         1024 |                                                         0
         2048 |                                                         0
         4096 |                                                         0
         8192 |@@@@@@@@@@@                                              2
        16384 |                                                         0



Calculer le temps passé dans une fonction
Là, nous avons besoin de variables spécifiques au thread d'exécution : variables pré-fixées par "self->" qui éliminent les cas de "race condition" sur une même variable. Quand vous remettez à zéro la variable, dtrace la dés-alloue (c'est son "garbage collector").

#!/usr/sbin/dtrace -s
syscall::open\*:entry,
syscall::close\*:entry
{
self->ts=timestamp;
}
syscall::open\*:return,
syscall::close\*:return
{
     printf("ThreadID %d spent %d nsecs in %s", tid, timestamp - self->ts, probefunc);
self->ts=0; /\*allow DTrace to reclaim the storage \*/
}



DTrace fournit également l'accès aux variables kernel et externes.
Pour accèder aux variables externes, il suffit de les préfixer par ` (anti-quote)

#!/usr/sbin/dtrace -qs
dtrace:::BEGIN
{
printf("physmem is %d\\n", `physmem);
printf("maxusers is %d\\n", `maxusers);
printf("ufs:freebehind is %d\\n", ufs`freebehind);
exit(0);
}


Speculative tracing
Il est important de trier les informations collectées car les buffers en mémoire de Dtrace sont limités (pour de bonnes raisons : impact des performances, utilisable en production...).
On crée dans ce cas "une spéculation". Vous allez allouer un buffer mémoire qui collecte vos traces, et si le thread sort en erreur, nous gardons l'information (pour analyse), sinon nous libérons le contenu du buffer.

C'est très intéressant dans le cas où vous recherchez un problème aléatoire qui sort de temps en temps en erreur.

self->spec = speculation () : mise en place d'un buffer speculatif par thread

Un exemple : si l'on recherche les cas de mount() qui sortent en erreur.

#!/usr/sbin/dtrace
syscall::mount:entry
{
   self->spec = speculation();
}
fbt:::return
/self->spec/
{
   speculate(self->spec);
   printf(“returning %d\\n”, arg1);
}
syscall::mount:return
/self->spec && errno != 0/
{
   commit(self->spec);
}
syscall::mount:return
/self->spec && errno == 0/
{
   discard(self->spec);
}
syscall::mount:return
/self->spec/
{ self->spec = 0; }



copyin & copyinstr

Ce sont des fonctions qui servent à récupérer des informations de processus userland, pour voir ces informations dans le noyau, car DTrace tourne dans le kernel :

  • copyin(addr,len)
  • copinstr(addr) - chaîne de caractère

Anonymous tracing
Imaginons que le système crash de temps en temps au boot, ou en panic.
Mais pour étudier cela avec un script, quand il y a un crash, le système disparait.
Pour y répondre, dtrace dispose d'une solution non rattachée à un consommateur donné et qui doit être enregistré dans le noyau. Le script dtrace est dans ce cas mis dans un fichier de configuration qui sera démarré au boot. Les buffers de trace associés à ce script sont dans le kernel et donc récupérables et analysables sur crash.

Pour créer ce script : # dtrace -A
Pour collecter les informations : # dtrace -a

Dtrace : une technologie à la porté des développeurs 

Au delà de la disponibilité de dtrace sur un système en production, vous pouvez également en bénéficier dès les phases de développement !

D-Light plugin dans SunStudio 12.1
Dans Sun Studio, au travers de D-Light, vous disposez de tout un ensemble de scripts dtrace qui permettent de récupérer des informations en temps réel sur le process que l'on exécute au sein de SunStudio. Cette collecte se fait de façon graphique avec une capacité de "drill-down" fine. Vous pouvez suivre un processus conçu dans l'IDE, mais également un processus s'exécutant sur un système.

Le point crucial : la documentation !

Vous disposez d'un grand nombre d'informations (liste des providers, etc...) indispensables à l'analyse sur le site wiki de DTrace : http://wikis.sun.com/display/DTrace/Documentation

Il existe également un kit avec un ensemble de scripts déjà prêts : le Dtrace Toolkit.
C'est un ensemble de scripts qui mélangent du Dtrace et du perl (pour la présentation) qui permettent de répondre à un grand nombre de questions sur les différents paramètres d'utilisation de vos applications et du système :

Vous pouvez même utiliser DTrace à l'intérieur d'Oracle Solaris Studio (ex- Sun Studio Tools) - voir plus haut.

Et en standard sur toutes vos machines Solaris : /usr/demo/dtrace

"let's do it" !

Pour aller plus loin :

  1. DTrace party ce soir à Supinfo !
  2. La formation :
    • EXL-2210 Developing and Optimizing Applications with Dtrace
        > 4 days - Experienced Java and C/C++ Developers
    • EXL-2211 Optimizing Solaris Administration with Dtrace
        > 4 days - Experienced System Administrators
  3. Bryan Cantrill en live :

    http://www.youtube.com/watch?v=6chLw2aodYQ
Translate in English

mardi mars 02, 2010

A vos agendas : les conférences du Groupe des Utilisateurs Solaris deviennent mensuelles

Translate in English

La prochaine conférence du groupe des utilisateurs Solaris aura lieu le 17 mars, à partir de 18h30 à SUPINFO. Alain Chereau, expert Solaris et Oracle du centre de benchmark, partagera son retour d'expérience sur l'optimisation des bases Oracle sur ZFS : performances et tuning.

Pour les détails de l'agenda, je vous invite à consulter directement le site du GUSES, et à vous enregistrez dès maintenant.

Vous pouvez également dès à présent réserver les dates suivantes, pour les prochaines conférences du GUSES :

  • 14 avril : 18h30 - 21h - DTrace Tutorial, par William Roche - Expert Solaris
  • 19 mai : 18h30 - 21h - Sujet en cours de définition
  • 16 juin : 18h30 - 21h - Sujet en cours de définition

En effet, les conférences du GUSES deviennent mensuelles, et SUPINFO a offert de les héberger. Je vous invite donc à rejoindre les membres du GUSES pour ces échanges, à SUPINFO, 52 rue Bassano, 75008 Paris, aux dates indiquées.

Translate in English
About

Eric Bezille

Search

Archives
« avril 2014
lun.mar.mer.jeu.ven.sam.dim.
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
    
       
Today