Development

Reparación de la herramienta

Modificación del OpenJDK 8 para que muestre el nombre del hilo en Linux.

December 21 2015

Cuando se rompe su Java

La mayor parte de la plataforma de mensajería y los servicios web de Infobip están escritos en Java. Si bien esta tecnología extendida en el mundo brinda estabilidad y facilidad de desarrollo con muchas librerías de soporte y buenas herramientas de creación de perfiles y de depuración, hay defectos tan irritantes en ella que hacen que nos preguntemos si son reales. Y son reales: en Linux no se puede ver el nombre del hilo de Java al inspeccionar los procesos nativos. Como los hilos de Java se mapean uno a uno con los hilos del sistema nativo, sería muy útil si pudiésemos solo escribir “top” y echar un vistazo a qué hilo de Java está utilizando la mayor parte de la CPU en ese momento sin tener que ejecutar una herramienta de depuración. Lo más irritante es que esto solía funcionar en la JRockit 6 de Oracle y por alguna razón se perdió en la transición a OpenJDK 7 y 8. Y para empeorar la agonía de un usuario orgulloso de Linux, ¡funciona en Windows!

Entonces, ¿qué puede hacer su codificador Java promedio? Puede sentirse desalentado al enfrentarse a estos grandes como Oracle y su software, rendirse rápido y conformarse. O puede desafiar el statu quo, utilizar al máximo sus habilidades y no parar hasta resolverlo. Nosotros, en Infobip, somos de este último tipo.

Construcción de su propio Java

Un poco de investigación reveló que hay una extensión GNU para la API del hilo POSIX llamada pthread_setname_np. que sirve para configurar el nombre del hilo en Linux. Esta documentación muestra que Linux guarda los nombres de los procesos (descripciones, nombres de comandos, como los quiera llamar) en /proc/[pid]/comm con un límite de 15 caracteres. Las herramientas Unix como “ps” y “top” pueden sacarle provecho. Veamos cómo funciona:


$ 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!

Dentro del comando “top” puede presionar la tecla “C” para cambiar entre las vistas cmd line y nombre de comando.

Ahora hagamos que Java use esta API para configurar el nombre del hilo nativo. Es fácil decirlo, ¿no? OpenJDK es una herramienta libre, así que descarguemos los archivos:

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

Intentemos buscar dónde termina java.lang.Thread.setName(String arg0). Va a necesitar todas sus habilidades olvidadas ya hace tiempo de C++ para esto, pero es bastante sencillo encontrar esa porción de código:

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;
}

Por cierto, aquí también puede ver que configurar la afinidad con la CPU definitivamente tampoco funcionará, pero no entraremos en ese tema porque hay suficiente material para otra publicación en el blog.

Si se siente incómodo modificando el código JVM, no está solo. Alguien ya lo ha hecho por usted. Existe un ticketpara esta mejora junto con una solución que funciona y fue abierta en 2011 y seguida por una discusión larga y bastante académica. Esto resultó en un par de tickets abiertos para glibc que solicitan que se libere la restricción de 15 caracteres. Esto no va a suceder porque la restricción está codificada en lo más profundo del kernel de Linux. Parece que la mejora finalmente llegó a Java 9, pero para los que somos impacientes, aquí está el parche.

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

Construir OpenJDK es sorprendentemente sencillo. Java 9 cambió a cmake pero con Java 8 será capaz de manejar las autotools:

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

La construcción produjo un archivo build/linux-x86_64-normal-server-release/hotspot/dist/jre/lib/amd64/server/libjvm.so que se puede usar como un reemplazo para el mismo archivo en una distribución JVM estándar. Ahora, probemos la magia:

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

((Por Milan Mimica, ingeniero de software / Team Leader))