
O painel que media tudo, menos a si mesmo
Em agosto eu tinha montado um agregador de status dentro do próprio Capivara. Aquele problema era de reunião: pegar dados espalhados e colocar numa tela. Na segunda-feira 12 de setembro nasceu o substituto — um painel separado, do zero, pra ser a autoridade sobre “o que está de pé no ecossistema”.
Levei dezoito minutos entre o commit que fundou o projeto e o commit que consertou três classes de mentira que ele contava. A primeira mentira foi a mais engraçada e a mais feia: na própria tela, o painel não conseguia dizer se existia.
Eu estava me olhando
O painel tem um registro: uma lista estática de superfícies com URL, o que se espera dela, se é crítica e quem é o dono. Naquela primeira versão havia treze itens e um deles era o próprio painel.
A rota de health faz o seguinte trabalho: percorre o registro, sonda tudo, classifica cada resposta, devolve um veredito. Ela mesma é uma das rotas do registro. Então cada requisição ao health disparava uma requisição ao health, que disparava outra.
Não era travamento nem loop infinito explícito — nada estourava pilha. Era mais insidioso: cada pedido gerava uma cascata de sub-sondas disputando o mesmo servidor, até a coisa inteira não responder dentro do prazo. E o resultado aparecia no painel com a etiqueta que eu tinha reservado pra “não sei”: unknown, com motivo timeout.
Ou seja: o painel não dizia “estou com problema”. Dizia “não consegui medir esse aí”. Que, numa tela onde todo o resto estava verde, é exatamente a frase que me faria abrir um terminal.
A correção coube numa linha, mas ela carrega uma decisão epistêmica: o painel prova que está vivo pelo fato de estar respondendo. Não se sonda de dentro da própria sondagem. Um item marcado como auto-prova entra no estado como ok sem nunca virar requisição, e é filtrado antes da rodada de probes.
Ausência de prova não é prova de queda
Essa regra eu escrevi no primeiro commit, antes de ter evidência pra ela:
uma medição só, sem código HTTP, nunca vira alerta.
Ela veio de história antiga. Minha máquina já enganou monitor: ambiente compartilhando recurso com treino pesado faz um serviço demorar vinte segundos pra responder sem estar morto. Um classificador ingênuo lê isso como queda, notifica, e eu acordo pra descobrir que está tudo funcionando. Uma queda real devolve um código — conexão recusada, 503, nada escutando. Uma resposta lenta devolve silêncio, e silêncio é ausência de informação.
Então a sonda tem um contrato: nunca lança exceção. Timeout vira status: null com motivo timeout, erro de conexão vira status: null com a mensagem, e só o classificador decide o que cada null significa. A sonda não conclui nada. O veredito também não confunde as coisas: se a maioria está verde e uma penca volta sem prova, a tela diz parcial, nunca crit.
O que eu não sabia no primeiro commit é que o problema não seria essa regra falhar. Seria ela ser violada por cima: um teto de timeout curto fabrica unknown em serviço saudável, e um painel cheio de “sem prova” é tão inútil quanto um painel cheio de vermelho — só que com a aparência da humildade.
Timeout é número medido, não número escolhido
A primeira versão usava quatro segundos de teto. Era um número razoável, redondo, que eu não tinha medido nada.
A medição veio de um alvo específico: uma rota de health que roda dezessete checagens encadeadas numa única requisição — um segundo e pouco numa máquina ociosa, muito mais quando a máquina está disputando recurso. Com quatro segundos, aquele serviço aparecia caído toda vez que a máquina estava trabalhando. Subi o teto pra oito, que é o número medido com folga, não o número que parecia prudente.
No dia seguinte eu tirei aquele alvo do registro. Não porque estivesse morto — porque o painel estava pagando o preço de medir uma rota que demora quanto ela quiser. A latência da minha própria rota de health caiu de oito segundos pra quase noventa milissegundos. Às vezes a cura de um monitor não é esperar mais: é remover o que não deveria estar ali.
E as sondas rodam em paralelo de propósito. Cada alvo em série soma o seu tempo — com uma dezena deles, o prazo estoura antes da última resposta chegar. Então Promise.all com a ordem do resultado preservada, e um teste que mede o pico de requisições simultâneas pra garantir que ninguém “otimizou” isso de volta pra série no futuro.
O cache da camada de governança é a mesma lógica com outro número: trinta segundos. Curto o bastante pra tela não mentir, longo o bastante pra um auto-refresh não virar negação de serviço contra o próprio banco.
Três códigos que significam “estou vivo”
A parte mais contraintuitiva de escrever um classificador é que código de erro não significa ausência de serviço — significa presença de serviço com opinião.
429 é vida. Dois monitores diferentes bateram no mesmo endpoint, um deles levou rate limit. O serviço respondeu, e respondeu de forma útil: “já estou trabalhando no seu caso”. Classificar 429 como degradação gera alerta falso eterno — eu viveria recebendo notificação de um painel que está funcionando exatamente como deveria.
401 num portão é porta fechada pra quem não devia entrar. O registro tem um modo de expectativa chamado auth-gate pra superfícies que exigem credencial. A semântica é invertida de propósito: 401, 403 e um redirect de login contam como ok. O que conta como crítico é um 200 sem credencial — porque aí a autenticação não está funcionando. Um painel que lê “200 = verde” vai marcar de verde exatamente o incidente mais grave possível naquele alvo.
404 num alvo novo é degradado, não morto. Aplicação que responde 404 é aplicação que existe e concorda em conversar. Provavelmente a rota de health que eu cadastrei mudou de nome. Isso merece warn, não crit — o alarme tem que doer na proporção da perda.
Essa parte tem teste de mesa: uma suíte que só cobre classificação, sem rede, e um teste de integridade que varre o registro exigindo id único, campo obrigatório presente e toda URL em loopback. O painel nunca sonda por endereço externo. Não por paranoia — porque o endereço externo depende de túnel, e túnel é um segundo sistema que pode cair e me fazer reportar a queda do primeiro.
Porta aberta não significa processo vivo
Depois de umas semanas lendo o painel, ficou claro que ele respondia uma pergunta que eu não estava fazendo.
“As portas estão respondendo” não me diz se o serviço está de pé. Diz que algum processo está escutando. Tem um caso específico que me morde: unidades de serviço escritas em template. A casca aparece parada e a instância real, com o nome da variante, está rodando. Quem consulta o nome genérico vê “inativo” num banco de dados servindo conexões naquele instante. O painel diria “caiu”. A máquina diria “funciona”.
Então separei em duas camadas que não derivam uma da outra. O registro prova a porta por HTTP. Uma tela de serviços, somente leitura, prova o processo — systemd e containers, com o estado bruto visível em vez de uma cor resumida. Uma não substitui a outra, e as duas mostram quando estão sem prova.
Três regras saíram dessa camada:
- Spawn que falhou, timeout e unidade ausente são
unknown, não queda. A mesma regra da sonda HTTP, aplicada a processo. - Estado escrito do tipo “terminou sem erro” não é queda. A casca de template que sai depois de iniciar a instância está se comportando corretamente. Sem essa regra, eu teria um vermelho permanente num serviço saudável.
- Contador de reinícios históricos não classifica. Um processo que reiniciou uma vez há um mês está saudável agora. Alarmar por histórico é o primo do alarmar por pico de crescimento num banco pequeno — técnica correta, pergunta errada.
E uma regra de estrutura: essa camada não muda nada no mundo. Executa comando com lista de argumentos, nunca por shell, e não existe nenhuma chamada de start, stop ou restart no caminho. Um painel de observação que pode mexer vira parte do sistema observado.
Os detalhes que só aparecem quando alguém olha
Três consertos que não são de arquitetura, mas dizem como o painel foi feito de verdade.
Um selo de “crítico” na tabela vazava como texto escapado — <span> aparecendo cru na tela. A causa é específica de template: string dentro de expressão renderiza como texto, não como marcação. Saiu como um span de verdade.
O teste de formatação de bytes afirmava que a escala maior era petabyte. Um petabyte são cinco potências de 1024, não quatro. O código estava certo e o teste estava errado — e essa é a espécie de bug que passa batido justamente porque o verde do teste é a evidência que a gente consulta.
As tabelas do painel estouravam a largura no celular. Medi em vez de adivinhar: três transbordos, um deles de cem pixels. Corrigi os três, conferi a mesma métrica no dispositivo, e depois descobri que uma anotação com “não quebra linha” estourava a rolagem horizontal inteira da página. Medida antes: quase o dobro da tela. Depois: exatamente a tela.
Métricas
| Item | Antes | Depois |
|---|---|---|
| Superfícies no registro | 13 | 12 (uma sonda aposentada) |
| Latência da rota de health | 8 s (esbarrando no teto) | ~98 ms |
| Teto de timeout | 4 s (chutado) | 8 s (medido, com folga) |
| Camada de processo | inexistente | somente leitura, sem mutação |
| Testes do painel | 32 na fundação | 73, todos sem rede real |
| Transbordos horizontais no celular | 3 | 0 |
O que fica
Um sistema de medição que pode se medir é um sistema que pode se enganar. A recursão do painel não apareceu como erro de programa; apareceu como um dado plausível e errado. Toda vez que seu monitor observar algo que ele mesmo fornece, a pergunta “quem está medindo quem” precisa ter resposta antes do primeiro deploy.
Ausência de evidência não é evidência de ausência. A pior coisa que um painel pode fazer é converter “não sei” em “quebrado”, porque isso destrói a disposição de acreditar nele nas vezes em que ele tem razão. parcial é uma resposta honesta; crit inventado é uma dívida que você paga com a própria credibilidade.
Código de erro é informação, não sentença. Os três casos que mais parecem queda — rate limit, portão negado, rota que mudou de nome — são o serviço afirmanto que existe. O classificador que trata tudo que não é 200 como degradado vai falhar exatamente no momento em que o sistema está sob pressão de verdade, que é quando eu mais precisaria dele.
Medir duas camadas separadas vale mais que traduzir uma na outra. Porta e processo respondem perguntas diferentes, e a tradução entre elas é onde mora a falsa certeza. Prefiro uma tela que mostra duas respostas que não coincidem a uma que mostra uma resposta limpa e errada.
Todo número de teto precisa de uma medição atrás. Quatro segundos era um número que eu gostava. Oito segundos é um número que a máquina me deu. A diferença entre os dois é a diferença entre um painel que alarma e um painel que informa.