helio.loureiro.eng.br
  • Home
  • Unix
  • Linux
  • Blog
  • Python
  • Programação
  • Tudo
  • Suécia
  1. You are here:  
  2. Home
  3. Programação
  4. Go

Os artigos mais lidos de 2024

  • linux-br.org num ritmo mais lento
  • Criando um serviço de relay de DNS-over-HTTPS
  • Minha palestra sobre a história do Unix na IX BSD Day
  • Pedal forte de 2023 em dados do Google
  • Linux vs GNU/Linux

Expondo as métricas do nginx pro prometheus

Details
Written by: Helio Loureiro
Category: Go
Published: August 22, 2025
Hits: 460
  • grafana
  • alloy
  • nginx
  • open metrics
  • prometheus

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.

https://nginx.org/en/docs/http/ngx_http_stub_status_module.html
https://docs.nginx.com/nginx-one/nginx-configs/metrics/enable-metrics/

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.

https://github.com/helioloureiro/nginx-open-metrics-service

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    
  

Ferramenta pra verificar quantidades de dias válidos de um certificado do letsencrypt

Details
Written by: Helio Loureiro
Category: Go
Published: April 17, 2025
Hits: 733
  • certificados
  • letsencrypt
  • x509

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.

https://github.com/helioloureiro/letsencrypt-cert-remaining-days

Parâmetros de compilação pra Go!

Details
Written by: Helio Loureiro
Category: Go
Published: February 14, 2025
Hits: 949
  • segurança
  • Go!
  • Compilação
  • FOSDEM

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:



https://fosdem.org/2025/schedule/event/fosdem-2025-4406-build-better-go-release-binaries/

Correndo atrás do tempo perdido com Go!

Details
Written by: Helio Loureiro
Category: Go
Published: September 07, 2024
Hits: 1533
  • steam
  • velocidade
  • speed
  • bzip2

RoadRunner 4 3371596983

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.

victory

Go na BSD DAY 2022

Details
Written by: Helio Loureiro
Category: Go
Published: June 04, 2022
Hits: 2376

Andei ocupado e escrevendo pouco aqui no site.   Parte porque estava preparando uma apresentação sobre Go pra BSD DAY desse ano.

E aqui está a gravação da apresentação.

Como já é a segunda palestra com Go sobre o mesmo assunto, deve também ter sido a última.  Resta agora pensar no que vou falar na próxima.  Estou trabalhando em alguns projetos pessoais em Go, mas como nenhum chegou num ponto bom de usabilidade, não tenho o que apresentar ainda.  Mas vamos ver se consigo avançar.

Como os eventos estão voltando ao modo presencial, também deve ser difícil eu continuar apresentando algo no Brasil.  Talvez algo aqui na Europa e muito provavelmente aqui na Suécia.  Veremos.

 

  1. Latinoware 2021 - Go das trincheiras

Page 1 of 2

  • 1
  • 2

Estatísticas

  • Users 2
  • Articles 470
  • Articles View Hits 3369864

Imagem aleatória