Development

Fixez l’outil

En modifiant OpenJDK 8 pour afficher le nom des files de conversation dans Linux.

December 21 2015

Lorsque Java ne fonctionne plus

La plupart des plateformes de messagerie et des services Web d'Infobip sont écrits en Java. Cette technologie, certes très répandue, apporte stabilité et facilité de développement, avec de nombreuses bibliothèques de support et des outils de débogage et de profilage de qualité, mais vous y trouvez parfois des défauts exaspérants qui vous paraîtront incroyables. Mais tout cela est bien réel : dans Linux, vous n'avez pas accès au nom des tâches Java lorsque vous inspectez les processus natifs. Les tâches Java étant reliées une à une avec les tâches du système natif, il serait vraiment pratique de pouvoir taper sur la touche « haut » et de voir quelle tâche Java mobilise le plus le CPU à ce moment-là, sans avoir à lancer un outil de débogage. Le plus exaspérant, c'est que tout marchait bien dans Oracle JRockit 6, et que la fonctionnalité s'est perdue dans la transition vers OpenJDK 7 and 8. Et c'est encore plus atroce pour un fier utilisateur de Linux, car ça marche sur Windows !

Que doit faire votre codeur Java moyen ? Découragé, vous n'avez pas envie d'affronter les géants comme Oracle et leurs logiciels, vous abandonnez facilement et vivez avec. Mais vous pouvez aussi relever le défi, exploiter vos compétences au maximum et vous obstiner à trouver la solution. Chez Infobip, nous sommes ce genre de personnes.

Construisez votre propre Java

Une courte enquête montre qu'il existe une extension d'application GNU à l'implémentation d'API sous la norme POSIX pour paramétrer les noms des tâches Linux, que l'on appelle pthread_setname_np. La documentation révèle que Linux conserve les noms des processus (descriptions, noms de commandes, etc, appelez ça comme vous voulez) dans /proc/[pid]/comm (limité à 15 caractères). Cela peut être utiles pour les outils Unix comme ps et top. Voyons comment ça marche:


$ ps | grep $$ # $$ is current PID
7880 pts/3 00:00:00 bash
$ echo -n "Hi mum, look at me!" > /proc/$$/comm
$ ps | grep $$
7880 pts/3 00:00:00 Hi mum, look at me!

Vous pouvez d'abord appuyer sur la touche « c » pour passer d'une ligne cmd à l'affichage du nom d'une commande.

Maintenant faisons en sorte que Java utilise cette API pour créer un nom de tâche native. Facile à dire, hein ? OpenJDK est en open source, donc téléchargeons d'abord les sources:

$ hg clone http://hg.openjdk.java.net/jdk8u/jdk8u60
$ cd jdk8u60
$ sh get_source.sh

Essayons de savoir où java.lang.Thread.setName(String arg0) se termine. Ici, vous aurez besoin de vos vieilles connaissances en lecture C++, mais cette partie du code est assez facile à trouver:

hotspot/src/os/linux/vm/os_linux.cpp
void os::set_native_thread_name(const char *name) {
// Not yet implemented.
return;
}
 
bool os::distribute_processes(uint length, uint* distribution) {
// Not yet implemented.
return false;
}
 
bool os::bind_to_processor(uint processor_id) {
// Not yet implemented.
return false;
}

Au fait, vous voyez que le paramétrage de l'affinité du CPU ne marche pas non plus, mais inutile de s'aventurer dans cette voie car il y aurait assez de matière pour un autre post.

Si vous n'êtes pas à l'aise avec la modification du code de la JVM, vous n'êtes pas le seul : ça a déjà été fait avant. Il existe un ticket pour cette amélioration et une solution fonctionnelle ouverts 2011, suivis d'une discussion longue et plutôt théorique qui a donné lieu à quelques autres tickets ouverts pour glibc, demandant la levée de la restriction des 15 caractères. Impossible, car cette restriction est codée en dur dans le noyau Linux. Apparemment, l'amélioration verra le jour dans Java 9, mais pour les impatients, voilà le patch.

Appliquez le patch
jdku60$ patch -d hotspot -p1 <../patches/thread_name.cpp.patch

Construire OpenJDK est étonnamment facile. Java 9 est passé à cmake mais vous devrez gérer des autotools avec Java 8:

$ sh conigure.sh 
# <do whatever the script tells you to do>
$ make

La construction a produit un fichier build/linux-x86_64-normal-server-release/hotspot/dist/jre/lib/amd64/server/libjvm.so, que vous pouvez utiliser en cas de défaillance et qui remplacera le même fichier dans la distribution standard de la JVM. Maintenant, la magie fonctionne ! Essayez:

ThreadNameTest.java
public class ThreadNameTest {
    public static void main(String[] args) throws Exception {
        Thread.currentThread().setName("Hi mum, look at me!");
        Thread.sleep(100_000);
    }
}
Java with thread name support
$ javac ThreadNameTest.java
$ java ThreadNameTest &
[1] 19391
$ for PID in `ls /proc/$!/task/`; do echo $PID - `cat /proc/$!/task/$PID/comm`; done
19563 - java
19564 - Hi mum, look at
19565 - java
19566 - java
19567 - java
19568 - java
19569 - java
19570 - java
19571 - java
19572 - java
19573 - java
19574 - Reference Handl
19575 - Finalizer
19576 - Signal Dispatch
19577 - C2 CompilerThre
19578 - C2 CompilerThre
19579 - C2 CompilerThre
19580 - C1 CompilerThre
19581 - Service Thread
19582 - java
$ kill -s 15 $!
[1]+  Exit 143                java ThreadNameTest
Vanilla Java
$ javac ThreadNameTest.java
$ java ThreadNameTest &
[1] 19391
$ for PID in `ls /proc/$!/task/`; do echo $PID - `cat /proc/$!/task/$PID/comm`; done
19563 - java
19564 - java
19565 - java
19566 - java
19567 - java
19568 - java
19569 - java
19570 - java
19571 - java
19572 - java
19573 - java
19574 - java
19575 - java
19576 - java
19577 - java
19578 - java
19579 - java
19580 - java
19581 - java
19582 - java
$ kill -s 15 $!
[1]+  Exit 143                java ThreadNameTest

(par Milan Mimica, ingénieur logiciel / responsable d'équipe)