Development

Riparare lo strumento

Modifica di OpenJDK 8 per mostrare il nome thread in Linux.

December 21 2015

Quando Java non funziona

La maggior parte della piattaforma di messaggistica e dei servizi web di Infobip sono scritti in Java. Sebbene questa diffusa tecnologia fornisca stabilità e facilità di sviluppo, con molte librerie di supporto e buoni strumenti di debugging e profiling, a volte mostra un difetto così irritante che viene da chiedersi se sia reale. Ed è reale: su Linux, non è possibile vedere il nome thread Java durante l’ispezione dei processi nativi. Poiché i thread Java mappano uno a uno rispetto ai thread di sistema, sarebbe davvero pratico poter premere “top' e dare un’occhiata a quale thread Java sta utilizzando più CPU al momento, senza dover lanciare uno strumento di debugging. Il fatto ancora più irritante è che una volta funzionava su Oracle JRockit 6 e in qualche modo è andato perso nella transizione a OpenJDK 7 e 8. E per rendere la cosa ancora più straziante per un fiero utente Linux, funziona su Windows!!

Perciò qual è il Java coder medio che consente di farlo? Ci si può sentire scoraggiati dal confronto con colossi come Oracle e i loro software, desistere e convivere con la cosa. Oppure si può sfidare lo status quo, spingere le proprie capacità al massimo e persistere nel trovare una soluzione Noi di Infobip siamo questo tipo di persone.

Crea il tuo Java

Un’indagine ulteriore mostra che esiste un’estensione GNU per il POSIX threading API per l’impostazione di un nome thread Linux chiamata pthread_setname_np. La documentazione rivela che Linux mantiene i nomi dei processi (descrizioni, nomi dei comandi o come preferisci chiamarli) in /proc/[pid]/comm con il limite di 15 caratteri. Gli strumenti Unix come “ps” e “top” possono sfruttare questo fatto. Vediamo come funziona:


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

In “top” è possibile premere il tasto “c” per passare dalla cmd line alla visualizzazione del nome del comando.

Ora facciamo usare questa API a Java per impostare il nome thread nativo. Facile a dirsi, eh? OpenJDK è open source, perciò scarichiamo prima le sorgenti:

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

Cerchiamo di scoprire dove finisce java.lang.Thread.setName(String arg0). Avrai bisogno di tutte le tue dimenticate capacità di lettura di C++ per farlo, ma trovare questo elemento di codice è piuttosto facile:

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

Qui puoi anche renderti conto che neanche l’impostazione dell’affinità di CPU funzionerà, ma non ci addentreremo perché c’è materiale per un altro articolo.

Se non ti senti a tuoi agio a modificare il codice JVM, non sei il solo, qualcuno l’ha già fatto prima. Esiste un ticket per questa ottimizzazione insieme a una soluzione aperta nel 2011 e seguita da una discussione lunga e piuttosto accademica, che ha portato a qualche altro ticket aperto per chiedere a glibc di eliminare la restrizione a 15 caratteri. Non succederà perché la restrizione è fissata nel kernel di Linux. Sembra che l’ottimizzazione sia alla fine arrivata con Java 9, ma per chi è impaziente ecco la patch.

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

Costruire OpenJDK è sorprendentemente facile. Java 9 è passato a cmake ma con Java 8 dovrai avere a che fare con questi autotool:

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

La costruzione ha prodotto un file build/linux-x86_64-normal-server-release/hotspot/dist/jre/lib/amd64/server/libjvm.so che puoi utilizzare come sostituzione per lo stesso file nella distribuzione JVM standard. Ora prova a fare 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

(di Milan Mimica, Software Engineer / Team Leader)