Development

Consertando a ferramenta

Modificando o OpenJDK 8 para mostrar o nome do tópico no Linux

December 21 2015

Quando seu Java está quebrado

A maioria dos serviços web e da plataforma de mensageria da Infobip são escritos em Java. Embora esta tecnologia tenha se espalhado, garantindo estabilidade e facilidade de desenvolvimento, tendo muitas bibliotecas de apoio, boa depuração e ferramentas de perfil, às vezes, é possível encontrar um defeito tão irritante, que faz com que você se pergunte se é ou não real. E é real: no Linux, você não pode ver o nome do encadeamento Java ao inspecionar processos nativos. Isso, porque os encadeamentos do Java mapeiam a relação one-to-one do sistema nativo, o que seria de muita ajuda, se você pudesse só alcançar o 'top' e ver qual encadeamento Java usa a maior parte da CPU naquele momento, sem a necessiade de executar uma ferramenta de depuração. O que é mais irritante é que isso costumava funcionar no Oracle JRockit 6 e, de alguma forma, se perdeu na transição para o OpenJDK 7 e 8. E o que angustia ainda mais o orgulhoso usuário Linux – é que isso funciona no Windows!

Então, quantos códigos Java você tem para fazer? Você pode se sentir desencorajado quando confrontado com grandes empresas, como a Oracle e seu software, desistir facilmente e viver com isso. Ou, você pode desafiar o status quo, pressionar suas habilidades no limite e persistir em solucionar as principais questões. Nós da Infobip somos estes tipos de pessoas.

Make your own Java

Uma pequena pesquisa mostrou que há uma extensão GNU para o encadeamento da API POSIX para definir o nome do encadeamento Linux chamado pthread_setname_np. A documentação revela que o Linux mantém os nomes dos processos (descrições, nomes de comandos, chame como quiser) in /proc/[pid]/comm limitados a 15 caracteres. Ferramentas Unix, como ps e top podem fazer uso deles. Vamos ver como isso 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!

Em top você pode apertar a tecla 'c' para alternar entre a linha cmd e o nome do comando exibido.

Agora vamos apenas fazer com que o Java use essa API para definir o nome do encadeamento nativo. Fácil falar, né? OpenJDK é uma fonte aberta, então vamos primeiro fazer o download das fontes:

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

Vamos tentar nos aprofundar no local onde o java.lang.Thread.setName(String arg0) termina. Você precisará de todas as suas já esquecidas habilidades de leitura C ++ para isso, mas é bem fácil encontrar este pedaço 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;
}

A propósito, você também verá aqui que definir uma afinidade com o processador também não funcionará. Mas não vamos entrar nesse mérito, já que é um tema bem extenso e podemos tratar dele em outro post.

Se você se sentir desconfortável em modificar o código JVM, saiba que não é o único - alguém já fez isso antes. Há um ticket fsobre essa otimização, junto à uma solução, aberto em 2011, com grande repercussão – seguido por uma longa discussão acadêmica, que resultou em outros tickets abertos para glibc, solicitando a abertura da restrição de 15 caracteres. Isso não acontecerá, pois a restrição é profundamente codificada no kernel Linux. Parece que a otimização, finalmente, aparecerá no Java 9, mas para os mais impacientes, segue o patch.

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

Construir o OpenJDK é surpreendentemente fácil. Java 9 alternado para o cmake , mas no Java 8 você teré que lidar com autotools:

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

A criação produziu um arquivo build/linux-x86_64-normal-server-release/hotspot/dist/jre/lib/amd64/server/libjvm. que você pode usar como substituto para o mesmo arquivo na distribuição padrão do JVM. Agora experimente a mágica:

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

(By Milan Mimica, Software Engineer / Team Leader)