
Eu já tinha descrito em atualizando mapas de DNS no estilo do DynDNS como fazia o sistema de DNS dinâmico que uso pra manter o nome bolha.linux-br.org atualizado, entre alguns outros, com o link residencial que tenho em casa.
Funcionava mas não era aquela maravilha. Atualizava a cada 5 minutos e pegava a mudança de IP pelos logs do Apache.
Até aí, sem grandes problemas.
Mas daí o Guto, da instância bolha.us, disse que estava tendo problemas de conexão com a bolha.linux-br.org.
Minha suspeita foi DNS. Então dei uma olhada nos logs.
starting: 20251228T18:45:01
finished: 20251228T18:45:01
starting: 20251228T18:50:01
finished: 20251228T18:50:01
starting: 20251228T18:55:01
updating IPv4 for raspberry3: old=83.233.219.150 new=51.75.236.128
updating: filename=/etc/bind/master/db.linux-br.org old_serial=2025122854 new_serial=2025122855
restarting named.service
finished: 20251228T18:55:01
starting: 20251228T19:00:01
updating IPv4 for raspberry3: old=51.75.236.128 new=83.233.219.150
updating: filename=/etc/bind/master/db.linux-br.org old_serial=2025122855 new_serial=2025122856
restarting named.service
finished: 20251228T19:00:01
starting: 20251228T19:05:01
finished: 20251228T19:05:01
starting: 20251228T19:10:01
finished: 20251228T19:10:01
starting: 20251228T19:15:01
finished: 20251228T19:15:01
starting: 20251228T19:20:01
updating IPv4 for raspberry3: old=83.233.219.150 new=51.68.247.213
updating: filename=/etc/bind/master/db.linux-br.org old_serial=2025122856 new_serial=2025122857
restarting named.service
finished: 20251228T19:20:01
starting: 20251228T19:25:01
updating IPv4 for raspberry3: old=51.68.247.213 new=83.233.219.150
updating: filename=/etc/bind/master/db.linux-br.org old_serial=2025122857 new_serial=2025122858
restarting named.service
finished: 20251228T19:25:01
starting: 20251228T19:30:01
finished: 20251228T19:30:01
O código mostra que IPv4 mudou várias vezes no mesmo dia.
Então entrei em contato com o provedor e perguntei se não era possível deixar o lease do DHCP mais longo. A resposta foi que um desses IPs nem era deles.
Um bug na lógica.
Então resolvi escrever algo em Go pra tomar o lugar desse sistema pereba de atualização de DNS. Fiz o dns-monitor.
Agora o dns-monitor funciona ouvido numa API REST, que recebe JSON, atualiza no banco de dados, faz o update dos mapas de DNS e reinicia o serviço de DNS via systemd.
E vejo logs assim:
Feb 24 18:15:02 dns-monitor[1139847]: [2026-02-24T18:15:02] (INFO): [RESP] remote_addr=127.0.0.1:59286, real_ip=2a00:1598:23af:4900:5fe7:c566:dbbd:7b35 status_code=200 hostname=www.bolha message=unchanged
Feb 24 18:15:02 dns-monitor[1139847]: [2026-02-24T18:15:02] (INFO): [REQ] remote_addr=127.0.0.1:59312 real_ip=2a00:1598:23af:4900:5fe7:c566:dbbd:7b35 host=api.linux-br.org uri=/api/register method=POST user_agent=curl/8.5.0
Feb 24 18:17:27 dns-monitor[1139847]: [2026-02-24T18:17:27] (INFO): [REQ] remote_addr=127.0.0.1:52066 real_ip=2a00:1598:23af:4900::b55 host=api.linux-br.org uri=/api/register method=POST user_agent=curl/8.18.0
Feb 24 18:17:28 dns-monitor[1139847]: [2026-02-24T18:17:28] (INFO): [REQ] remote_addr=127.0.0.1:52074 real_ip=83.233.219.150 host=api.linux-br.org uri=/api/register method=POST user_agent=curl/8.18.0
Feb 24 18:20:01 dns-monitor[1139847]: [2026-02-24T18:20:01] (INFO): [REQ] remote_addr=127.0.0.1:47990 real_ip=83.233.219.150 host=api.linux-br.org uri=/api/register method=POST user_agent=curl/8.5.0
Feb 24 18:20:01 dns-monitor[1139847]: [2026-02-24T18:20:01] (INFO): [RESP] remote_addr=127.0.0.1:47990, real_ip=83.233.219.150 status_code=200 hostname=bolha message=unchanged
Feb 24 18:20:01 dns-monitor[1139847]: [2026-02-24T18:20:01] (INFO): [REQ] remote_addr=127.0.0.1:48006 real_ip=2a00:1598:23af:4900:3e52:82ff:fe62:ff11 host=api.linux-br.org uri=/api/register method=POST user_agent=curl/8.5.0
Feb 24 18:20:01 dns-monitor[1139847]: [2026-02-24T18:20:01] (INFO): trigger update on ipv6
Feb 24 18:20:01 dns-monitor[1139847]: [2026-02-24T18:20:01] (INFO): Updating DNS maps: hostname=www.bolha ip_version=6 ip_address=2a00:1598:23af:4900:3e52:82ff:fe62:ff11
Feb 24 18:20:01 dns-monitor[1139847]: [2026-02-24T18:20:01] (INFO): dns maps to be udpated: [/etc/bind/master/db.truta.org /etc/bind/master/db.linux-br.org]
Feb 24 18:20:01 dns-monitor[1139847]: [2026-02-24T18:20:01] (INFO): updated serial: 2026022400
Feb 24 18:20:01 dns-monitor[1139847]: [2026-02-24T18:20:01] (INFO): updated serial: 2026022400
Feb 24 18:20:01 dns-monitor[1139847]: [2026-02-24T18:20:01] (INFO): dns submap to also be update: /etc/bind/master/dyndns.map
Feb 24 18:20:01 dns-monitor[1139847]: [2026-02-24T18:20:01] (INFO): [RESP] remote_addr=127.0.0.1:48006, real_ip=2a00:1598:23af:4900:3e52:82ff:fe62:ff11 status_code=200 hostname=www.bolha message=IPv6_updated
Feb 24 18:20:01 dns-monitor[1139847]: [2026-02-24T18:20:01] (INFO): restart service named via systemd
Feb 24 18:20:01 dns-monitor[1139847]: [2026-02-24T18:20:01] (INFO): [REQ] remote_addr=127.0.0.1:48010 real_ip=83.233.219.150 host=api.linux-br.org uri=/api/register method=POST user_agent=curl/8.5.0
Feb 24 18:20:01 dns-monitor[1139847]: [2026-02-24T18:20:01] (INFO): [REQ] remote_addr=127.0.0.1:48034 real_ip=83.233.219.150 host=api.linux-br.org uri=/api/register method=POST user_agent=curl/8.5.0
Feb 24 18:20:01 dns-monitor[1139847]: [2026-02-24T18:20:01] (INFO): [REQ] remote_addr=127.0.0.1:48020 real_ip=2a00:1598:23af:4900:3e52:82ff:fe62:ff11 host=api.linux-br.org uri=/api/register method=POST user_agent=curl/8.5.0
Feb 24 18:20:01 dns-monitor[1139847]: [2026-02-24T18:20:01] (INFO): [RESP] remote_addr=127.0.0.1:48034, real_ip=83.233.219.150 status_code=200 hostname=www.bolha message=unchanged
Feb 24 18:20:01 dns-monitor[1139847]: [2026-02-24T18:20:01] (INFO): trigger update on ipv6
Feb 24 18:20:01 dns-monitor[1139847]: [2026-02-24T18:20:01] (INFO): Updating DNS maps: hostname=bolha ip_version=6 ip_address=2a00:1598:23af:4900:3e52:82ff:fe62:ff11
Feb 24 18:20:01 dns-monitor[1139847]: [2026-02-24T18:20:01] (INFO): dns maps to be udpated: [/etc/bind/master/db.truta.org /etc/bind/master/db.linux-br.org]
Feb 24 18:20:01 dns-monitor[1139847]: [2026-02-24T18:20:01] (INFO): updated serial: 2026022401
Feb 24 18:20:01 dns-monitor[1139847]: [2026-02-24T18:20:01] (INFO): updated serial: 2026022401
Feb 24 18:20:01 dns-monitor[1139847]: [2026-02-24T18:20:01] (INFO): [REQ] remote_addr=127.0.0.1:48036 real_ip=2a00:1598:23af:4900:3e52:82ff:fe62:ff11 host=api.linux-br.org uri=/api/register method=POST user_agent=curl/8.5.0
Feb 24 18:20:01 dns-monitor[1139847]: [2026-02-24T18:20:01] (INFO): trigger update on ipv6
Feb 24 18:20:01 dns-monitor[1139847]: [2026-02-24T18:20:01] (INFO): dns submap to also be update: /etc/bind/master/dyndns.map
Feb 24 18:20:01 dns-monitor[1139847]: [2026-02-24T18:20:01] (INFO): dns maps to be udpated: [/etc/bind/master/db.truta.org /etc/bind/master/db.linux-br.org]
Feb 24 18:20:01 dns-monitor[1139847]: [2026-02-24T18:20:01] (INFO): updated serial: 2026022402
Feb 24 18:20:01 dns-monitor[1139847]: [2026-02-24T18:20:01] (INFO): updated serial: 2026022402
Feb 24 18:20:01 dns-monitor[1139847]: [2026-02-24T18:20:01] (INFO): [RESP] remote_addr=127.0.0.1:48020, real_ip=2a00:1598:23af:4900:3e52:82ff:fe62:ff11 status_code=200 hostname=bolha message=IPv6_updated
Feb 24 18:20:01 dns-monitor[1139847]: [2026-02-24T18:20:01] (INFO): restart service named via systemd
Feb 24 18:20:01 dns-monitor[1139847]: [2026-02-24T18:20:01] (INFO): dns submap to also be update: /etc/bind/master/dyndns.map
Feb 24 18:20:01 dns-monitor[1139847]: [2026-02-24T18:20:01] (INFO): restart service named via systemd
Feb 24 18:22:28 dns-monitor[1139847]: [2026-02-24T18:22:28] (INFO): [REQ] remote_addr=127.0.0.1:48738 real_ip=2a00:1598:23af:4900::b55 host=api.linux-br.org uri=/api/register method=POST user_agent=curl/8.18.0
Feb 24 18:22:28 dns-monitor[1139847]: [2026-02-24T18:22:28] (INFO): [REQ] remote_addr=127.0.0.1:48740 real_ip=83.233.219.150 host=api.linux-br.org uri=/api/register method=POST user_agent=curl/8.18.0
Feb 24 18:25:02 dns-monitor[1139847]: [2026-02-24T18:25:02] (INFO): [REQ] remote_addr=127.0.0.1:52456 real_ip=83.233.219.150 host=api.linux-br.org uri=/api/register method=POST user_agent=curl/8.5.0
Feb 24 18:25:02 dns-monitor[1139847]: [2026-02-24T18:25:02] (INFO): [RESP] remote_addr=127.0.0.1:52456, real_ip=83.233.219.150 status_code=200 hostname=bolha message=unchanged
Feb 24 18:25:02 dns-monitor[1139847]: [2026-02-24T18:25:02] (INFO): [REQ] remote_addr=127.0.0.1:52468 real_ip=83.233.219.150 host=api.linux-br.org uri=/api/register method=POST user_agent=curl/8.5.0
Feb 24 18:25:02 dns-monitor[1139847]: [2026-02-24T18:25:02] (INFO): [REQ] remote_addr=127.0.0.1:52476 real_ip=83.233.219.150 host=api.linux-br.org uri=/api/register method=POST user_agent=curl/8.5.0
Feb 24 18:25:02 dns-monitor[1139847]: [2026-02-24T18:25:02] (INFO): [RESP] remote_addr=127.0.0.1:52476, real_ip=83.233.219.150 status_code=200 hostname=www.bolha message=unchanged
Feb 24 18:25:02 dns-monitor[1139847]: [2026-02-24T18:25:02] (INFO): [REQ] remote_addr=127.0.0.1:52482 real_ip=2a00:1598:23af:4900:3e52:82ff:fe62:ff11 host=api.linux-br.org uri=/api/register method=POST user_agent=curl/8.5.0
Feb 24 18:25:02 dns-monitor[1139847]: [2026-02-24T18:25:02] (INFO): [REQ] remote_addr=127.0.0.1:52484 real_ip=2a00:1598:23af:4900:3e52:82ff:fe62:ff11 host=api.linux-br.org uri=/api/register method=POST user_agent=curl/8.5.0
Feb 24 18:25:02 dns-monitor[1139847]: [2026-02-24T18:25:02] (INFO): [RESP] remote_addr=127.0.0.1:52484, real_ip=2a00:1598:23af:4900:3e52:82ff:fe62:ff11 status_code=200 hostname=www.bolha message=unchanged
Feb 24 18:25:02 dns-monitor[1139847]: [2026-02-24T18:25:02] (INFO): [REQ] remote_addr=127.0.0.1:52498 real_ip=2a00:1598:23af:4900:3e52:82ff:fe62:ff11 host=api.linux-br.org uri=/api/register method=POST user_agent=curl/8.5.0
Feb 24 18:25:02 dns-monitor[1139847]: [2026-02-24T18:25:02] (INFO): [RESP] remote_addr=127.0.0.1:52498, real_ip=2a00:1598:23af:4900:3e52:82ff:fe62:ff11 status_code=200 hostname=bolha message=unchanged
Feb 24 18:27:28 dns-monitor[1139847]: [2026-02-24T18:27:28] (INFO): [REQ] remote_addr=127.0.0.1:46772 real_ip=2a00:1598:23af:4900::b55 host=api.linux-br.org uri=/api/register method=POST user_agent=curl/8.18.0
Feb 24 18:27:29 dns-monitor[1139847]: [2026-02-24T18:27:29] (INFO): [REQ] remote_addr=127.0.0.1:46774 real_ip=83.233.219.150 host=api.linux-br.org uri=/api/register method=POST user_agent=curl/8.18.0
Feb 24 18:30:01 dns-monitor[1139847]: [2026-02-24T18:30:01] (INFO): [REQ] remote_addr=127.0.0.1:37288 real_ip=83.233.219.150 host=api.linux-br.org uri=/api/register method=POST user_agent=curl/8.5.0
Feb 24 18:30:01 dns-monitor[1139847]: [2026-02-24T18:30:01] (INFO): [REQ] remote_addr=127.0.0.1:37294 real_ip=83.233.219.150 host=api.linux-br.org uri=/api/register method=POST user_agent=curl/8.5.0
Feb 24 18:30:01 dns-monitor[1139847]: [2026-02-24T18:30:01] (INFO): [RESP] remote_addr=127.0.0.1:37294, real_ip=83.233.219.150 status_code=200 hostname=bolha message=unchanged
Feb 24 18:30:01 dns-monitor[1139847]: [2026-02-24T18:30:01] (INFO): [REQ] remote_addr=127.0.0.1:37302 real_ip=83.233.219.150 host=api.linux-br.org uri=/api/register method=POST user_agent=curl/8.5.0
Ficou mais fácil acompanhar as mudanças e o sistema ficou mais estável.
No servidor eu fiz um reverse proxy no Apache pra chegar no serviço dns-monitor.
Do lado do cliente, eu uso curl, como é possível ver pelos logs com user_agent. Envio algo como isso abaixo mas na crontab a cada 5 minutos:
❯ curl -6 -o /dev/null -s "https://api.linux-br.org/api/register" -d '{"hostname": "bolha", "token": "abcdefgh123456"}'
❯ curl -4 -o /dev/null -s "https://api.linux-br.org/api/register" -d '{"hostname": "bolha", "token": "abcdefgh123456"}'
O sistema está longe de estar perfeito. Roda vários updates no mapa seguidamente se o endereço mudar tanto no IPv4 quanto no IPv6, que aconteceu quando mudei o servidor fisicamente do quarto pra sala. Mas está funcionando e com menos erros que antes.
Quem quiser olhar o código, já esta no Codeberg:
https://codeberg.org/helioloureiro/dns_monitor
Não tem muita descrição, mas está lá e está funcionando em produção.
Nota: depois de tudo isso, o Guto falou que o problema era do lado da bolha.us. Ao menos serviu pra eu sair da inércia e escrever um pouco de Go, o que foi bem divertido.
Nota 2: nenhum código de AI foi usando durante o desenvolvimento desse programa.
Esses dias eu peguei um problema no servidor web, nginx. Não nele especificamente. Mas um usuário estava reclamando que estava super lento pra carregar as páginas.
A questão então é como ver como e quanto está o nginx. Infelizmente a versão open source não fornece muita coisa. Só uma versão texto de estatísticas.
Não é grande coisa mas pelo menos já é ALGUMA COISA.
Agora como monitorar isso no Grafana?
A resposta são open metrics. E isso não tem.
Não tinha.
Fiz um programa em Go que converte essas estatísticas em open metrics e expõe na porta 9090 no endpoint /metrics.
Pra ter isso funcionando, é preciso primeiro subir a configuração de estatísticas no nginx.
server {
listen 127.0.0.1:8080;
location /api {
stub_status;
allow 127.0.0.1;
deny all;
}
}
Eu salvei no arquivo statistics.conf e coloquei em /etc/nginx/conf.d.
E bastou um reload pra ter funcionado.
❯ curl localhost:8080/api
Active connections: 2
server accepts handled requests
21 21 322
Reading: 0 Writing: 1 Waiting: 1
Agora é rodar o programa e apontar pra esse endpoint.
❯ ./nginx-openmetrics/nginx-openmetrics --service=http://localhost:8080/api
[2025-08-22T14:11:45] (INFO): 🚚 fetching data from:http://localhost:8080/api
[2025-08-22T14:11:45] (INFO): 🎬 starting service at port:9090
E a porta fica exposta pras métricas serem coletadas pelo prometheus ou grafana alloy. Ou qualquer outro programa que faça scrape de dados no padrão open metrics.
❯ curl localhost:9090/metrics
# HELP active_connections The number of active connections
# TYPE active_connections gauge
active_connections 1
# HELP reading_connections The number of active reading connections
# TYPE reading_connections gauge
reading_connections 0
# HELP server_accepts_total The total number of server accepted connections
# TYPE server_accepts_total counter
server_accepts_total 22
# HELP server_handled_total The total number of server handled connections
# TYPE server_handled_total counter
server_handled_total 22
# HELP server_requests_total The total number of server requests
# TYPE server_requests_total counter
server_requests_total 333
# HELP waiting_connections The number of waiting connections
# TYPE waiting_connections gauge
waiting_connections 0
# HELP writing_connections The number of active writing connections
# TYPE writing_connections gauge
writing_connections 1
E fica exposto em todas as interfaces.
❯ netstat -nat | grep 9090 | grep -i listen
tcp6 0 0 :::9090 :::* LISTEN
O programa faz o update dos dados a cada 15 segundos. Pra não sobrecarregar.
E ainda falta dar uma melhorada com a entrada como serviço do systemd. Devo fazer isso hoje.
Próximo passo será gerar um pacote debian dele pra instalar fácil.
Update: Fri Aug 22 04:23:45 PM CEST 2025
Tá lá o arquivo pro systemd.
E está funcionando no sistema que estou testando.
root@server:/# systemctl status nginx-openmetrics.service
● nginx-openmetrics.service - nginx open metrics service
Loaded: loaded (/etc/systemd/system/nginx-openmetrics.service; enabled; vendor preset: enabled)
Active: active (running) since Fri 2025-08-22 14:08:17 UTC; 16min ago
Main PID: 314061 (nginx-openmetri)
Tasks: 7 (limit: 19076)
Memory: 4.0M
CPU: 47ms
CGroup: /system.slice/nginx-openmetrics.service
└─314061 /usr/sbin/nginx-openmetrics --service=http://localhost:8080/api
Aug 22 14:08:17 internal systemd[1]: Started nginx open metrics service.
Aug 22 14:08:17 internal nginx-openmetrics[314061]: [2025-08-22T14:08:17.78433] (INFO): nginx-open-metrics-service (1.0-9)
Aug 22 14:08:17 internal nginx-openmetrics[314061]: [2025-08-22T14:08:17.78441] (INFO): 🚚 fetching data from:http://localhost:8080/api
Aug 22 14:08:17 internal nginx-openmetrics[314061]: [2025-08-22T14:08:17.78442] (INFO): 🎬 starting service at port:9090
Já faz algum tempo que venho recebendo esses emails do letsencrypt avisando que as notificações de certificados expirados enviados por email serão suspensas. Até aí ok. Imagino que isso consuma um volume razoável de banda da organização, sem falar nos problemas de antispam por aí.
O problema é que enquanto eles param esse serviço, ao mesmo tempo não fornecem uma ferramenta fácil pra olhar quando certificado irá expirar. Claro que é possível fazer isso usando o openssl:
❯ openssl x509 -dates -noout -in /etc/letsencrypt/live/helio.loureiro.eng.br/cert.pem
notBefore=Mar 8 17:32:15 2025 GMT
notAfter=Jun 6 17:32:14 2025 GMT
O problema é que não é possível fazer uma automação disso pra rodar alguma crontab e renovar os certificados em caso de expiração.
Então resolvi arregaçar as mangas e fazer alguma coisa em Go! pra resolver de vez esse problema.
$ /home/helio/bin/letsencrypt-cert-days
helio.loureiro.eng.br=50
hl.eng.br=50
linux-br.org=50
loureiro.eng.br=50
truta.org=50
O código está no GitHub.
Estou assistindo agora algumas apresentações que não pude ver ao vivo durante o FOSDEM 2025. E uma dessas foi sobre como compilar Go! corretamente feita pelo Dimitri John Ledkov.
O palestrante não é programador Go! mas enpacotador pra várias distros. E conhece bem sobre quais parâmetros usar.
Eu alterei meu Makefile do programa negofetch, uma re-escrita em Go! do neofetch que estou fazendo, pra usar as dicas dele.
BINARY = negofetch
BUILD_OPTIONS = -modcacherw
#BUILD_OPTIONS += -race
BUILD_OPTIONS += -ldflags="-w -X 'main.Version=$$(git tag -l --sort taggerdate | tail -1)'"
BUILD_OPTIONS += -buildmode=pie
BUILD_OPTIONS += -tags netgo,osusergo
BUILD_OPTIONS += -trimpath
all: $(SOURCES) dependencies $(BINARY)
dependencies:
go mod tidy
$(BINARY): $(SOURCES)
env GOAMD64=v2 \
CGO_ENABLED=1 \
go build $(BUILD_OPTIONS) .
Olhando pelo govulncheck, que ele também recomenda usar, parece bom.
❯ govulncheck -mode=binary negofetch
Scanning your binary for known vulnerabilities...
No vulnerabilities found.
Share feedback at https://go.dev/s/govulncheck-feedback.
Pra quem estiver interessado, esse é o vídeo:

Estou atualmente trabalhando numa empresa onde uma das visões da empresa é conseguir ter os dados do jogo finalizado disponível na interface de visualização em menos de 1 minuto. Quase todo o código é escrito em typescript e uma pequena parte em rust.
Uma das partes mais pesadas é feita em shell, que é a parte de baixar e descompactar um arquivo da steam. Pra ilustrar o que é feito, fiz esse script em shell que também serve como base de comparação de tempo.
#! /usr/bin/env bash
#
TARGET_URL="http://replay272.valve.net/730/003705744548740202576_0842061407.dem.bz2"
DESTINATION="003705744548740202576_0842061407.dem.bz2"
UNPACKED="003705744548740202576_0842061407.dem"
CURLUNPACKED="curl-003705744548740202576_0842061407.dem"
die() {
echo "ERROR: $@" &>2
}
rm -f $DESTINATION $UNPACKED $CURLUNPACKED
curl -o $DESTINATION \
-L \
$TARGET_URL ||
die "Failed to download: $TARGET_URL"
bunzip2 $DESTINATION ||
die "Failed to unzip $DESTINATION"
mv $UNPACKED $CURLUNPACKED
O código então baixa esse link da steam e tem de descompactar o arquivo. Qual é a velocidade dele?
helio@goosfraba ~/t/godownloader> time ./curl-downloader-2.sh
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 216M 100 216M 0 0 60.3M 0 0:00:03 0:00:03 --:--:-- 60.4M
________________________________________________________
Executed in 29.61 secs fish external
usr time 25.24 secs 585.00 micros 25.24 secs
sys time 0.89 secs 71.00 micros 0.89 secs
Podemos ver que o "curl" baixa o arquivo em mais ou menos 3 segundos, e gasta no total 25s, ou seja, uns 22s pra descompactar. O total termina como 29s, mas vamos focar primeiramente no "usr time".
Então escrevi um código em Go pra tentar fazer isso de forma mais rápida.
package main
import (
"compress/bzip2"
"fmt"
"io"
"net/http"
"os"
"time"
)
const (
TARGET_URL = "http://replay272.valve.net/730/003705744548740202576_0842061407.dem.bz2"
DESTINATION = "003705744548740202576_0842061407.dem.bz2"
DECOMPRESSED = "go-003705744548740202576_0842061407.dem"
)
func main() {
err := download(TARGET_URL, DESTINATION)
if err != nil {
panic(err)
}
err = bunzip2(DESTINATION, DECOMPRESSED)
if err != nil {
panic(err)
}
}
func download(from, to string) error {
fmt.Println("Downloading:", from)
timeStart := time.Now()
resp, err := http.Get(from)
if err != nil {
return err
}
defer resp.Body.Close()
out, err := os.Create(to)
if err != nil {
return err
}
defer out.Close()
_, err = io.Copy(out, resp.Body)
if err != nil {
return err
}
fmt.Println("Finished:", time.Since(timeStart).String())
return nil
}
func bunzip2(from, to string) error {
// https://gist.github.com/rickt/7817401
fmt.Println("Unpacking:", from)
timeStart := time.Now()
pr, pw := io.Pipe()
go func() {
defer pw.Close()
var inFile *os.File
var err error
inFile, err = os.Open(from)
defer inFile.Close()
if err != nil {
panic(err)
}
_, err = io.Copy(pw, inFile)
if err != nil {
panic(err)
}
}()
defer pr.Close()
z := bzip2.NewReader(pr)
var outFile *os.File
var err error
outFile, err = os.Create(to)
defer outFile.Close()
if err != nil {
return err
}
_, err = io.Copy(outFile, z)
if err != nil {
return err
}
fmt.Println("Finished:", time.Since(timeStart).String())
return nil
}
O código baixa o conteúdo num arquivo e depois descompacta. Qual a velocidade?
helio@goosfraba ~/t/godownloader> go build -o go-downloader-2 main-2.go; time ./go-downloader-2
Downloading: http://replay272.valve.net/730/003705744548740202576_0842061407.dem.bz2
Finished: 3.536812275s
Unpacking: 003705744548740202576_0842061407.dem.bz2
Finished: 30.220663099s
________________________________________________________
Executed in 33.76 secs fish external
usr time 29.90 secs 0.00 micros 29.90 secs
sys time 0.95 secs 639.00 micros 0.95 secs
Muito lento. Pior do que eu esperava. Ele baixa o arquivo em 3.5s, mas leva 30s pra descompactar. Um dos motivos é com certeza porquê eu salvo em arquivo e depois abro o arquivo pra descompactar. Vamos então pra próxima versão onde eu passo io.Reader de um pro outro.
package main
import (
"compress/bzip2"
"fmt"
"io"
"net/http"
"os"
"time"
)
const (
TARGET_URL = "http://replay272.valve.net/730/003705744548740202576_0842061407.dem.bz2"
DESTINATION = "003705744548740202576_0842061407.dem.bz2"
DECOMPRESSED = "go-003705744548740202576_0842061407.dem"
)
func main() {
data, err := readerDownload(TARGET_URL, DESTINATION)
if err != nil {
panic(err)
}
err = bunzip2Stream(data, DECOMPRESSED)
if err != nil {
panic(err)
}
}
func readerDownload(from, to string) ([]byte, error) {
fmt.Println("Downloading:", from)
timeStart := time.Now()
resp, err := http.Get(from)
if err != nil {
return nil, err
}
defer resp.Body.Close()
content, err := io.ReadAll(resp.Body)
if err != nil {
panic(err)
}
fmt.Println("Finished:", time.Since(timeStart).String())
return content, nil
}
func bunzip2Stream(from []byte, to string) error {
// https://gist.github.com/rickt/7817401
fmt.Println("Unpacking:", to)
timeStart := time.Now()
pr, pw := io.Pipe()
go func() {
defer pw.Close()
//_, err := io.Copy(pw, from)
pw.Write(from)
//if err != nil {
// panic(err)
// }
}()
defer pr.Close()
z := bzip2.NewReader(pr)
var outFile *os.File
var err error
outFile, err = os.Create(to)
defer outFile.Close()
if err != nil {
return err
}
_, err = io.Copy(outFile, z)
if err != nil {
return err
}
fmt.Println("Finished:", time.Since(timeStart).String())
return nil
}
Qual o desempenho?
helio@goosfraba ~/t/godownloader> go build -o go-downloader-3 main-3.go; time ./go-downloader-3
Downloading: http://replay272.valve.net/730/003705744548740202576_0842061407.dem.bz2
Finished: 3.624793323s
Unpacking: go-003705744548740202576_0842061407.dem
Finished: 29.883794408s
________________________________________________________
Executed in 33.52 secs fish external
usr time 30.03 secs 580.00 micros 30.03 secs
sys time 0.96 secs 69.00 micros 0.96 secs
Melhorou mas ainda estou longe de fazer melhor que a versão em shell script. O tempo de descompactar baixou irrisóriamente 1s. Mesmo não tendo SSD ou NVME meu disco é rápido o suficiente pra isso não impactar ao todo.
Comecei a pesquisar como poderia melhorar o desempanho do pacote bzip2 do Go! e existe uma discussão sobre isso em aberto.
https://github.com/golang/go/issues/6754
A reclamação é sobre versões mais antigas de Go! e até Robert Pike opinia. Acho que melhorou bastante pros dias de hoje, mas continua lento se comparado com o binário do programa. O que fazer então? Declarar derrota?
Talvez. Mas ao invés disso eu passei a procurar outras libs no GitHub. E encontrei o uso do pbzip2, que diz ser mais rápido queo bzip2 do standard. Então vamos ao código:
package main
import (
"context"
"fmt"
"io"
"net/http"
"os"
"time"
"github.com/cosnicolaou/pbzip2"
)
const (
TARGET_URL = "http://replay272.valve.net/730/003705744548740202576_0842061407.dem.bz2"
DESTINATION = "003705744548740202576_0842061407.dem.bz2"
DECOMPRESSED = "go-003705744548740202576_0842061407.dem"
)
func main() {
data, err := readerDownload(TARGET_URL, DESTINATION)
if err != nil {
panic(err)
}
err = pbunzip2Stream(data, DECOMPRESSED)
if err != nil {
panic(err)
}
}
func readerDownload(from, to string) ([]byte, error) {
fmt.Println("Downloading:", from)
timeStart := time.Now()
resp, err := http.Get(from)
if err != nil {
return nil, err
}
defer resp.Body.Close()
content, err := io.ReadAll(resp.Body)
if err != nil {
panic(err)
}
fmt.Println("Finished:", time.Since(timeStart).String())
return content, nil
}
func pbunzip2Stream(from []byte, to string) error {
// https://gist.github.com/rickt/7817401
fmt.Println("Unpacking:", to)
timeStart := time.Now()
pr, pw := io.Pipe()
go func() {
defer pw.Close()
//_, err := io.Copy(pw, from)
pw.Write(from)
//if err != nil {
// panic(err)
// }
}()
defer pr.Close()
ctx := context.Background()
z := pbzip2.NewReader(ctx, pr)
var outFile *os.File
var err error
outFile, err = os.Create(to)
defer outFile.Close()
if err != nil {
return err
}
_, err = io.Copy(outFile, z)
if err != nil {
return err
}
fmt.Println("Finished:", time.Since(timeStart).String())
return nil
}
E finalmente, a medida de desempenho:
helio@goosfraba ~/t/godownloader> go build -o go-downloader-4 main-4.go; time ./go-downloader-4
Downloading: http://replay272.valve.net/730/003705744548740202576_0842061407.dem.bz2
Finished: 3.74774932s
Unpacking: go-003705744548740202576_0842061407.dem
Finished: 6.108665607s
________________________________________________________
Executed in 9.89 secs fish external
usr time 44.22 secs 0.00 micros 44.22 secs
sys time 0.78 secs 655.00 micros 0.78 secs
Vitória! O pbzip2 que faz a descompressão em blocos em paralelo levou 6s. Isso sim é performance. O "usr time" mostra 44s por algum motivo bizarro, mas o tempo total foi por volta de 10s. E o resultado?
helio@goosfraba ~/t/godownloader> sha256sum curl-003705744548740202576_0842061407.dem \
go-003705744548740202576_0842061407.dem
eca9bdd943521251b8704397e40b7f9aada539698561a6c1aca58ebf2602bfc1 curl-003705744548740202576_0842061407.dem
eca9bdd943521251b8704397e40b7f9aada539698561a6c1aca58ebf2602bfc1 go-003705744548740202576_0842061407.dem
Então foi baixado e descompactado bem mais rápido e sem corromper os dados.
Um ponto a ser visto é que talvez exista também um binário pronto com pbzip2 pra descompactar. E pode ser que seja mais rápido que em Go! Mas é pra isso que servem os desafios. Por enquanto vou celebrar minha pequena vitória com uma cerveja.

script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js">