Transformando a saída do traceroute em gráficos visuais com PHP e Graphviz
Voltar para blog

Transformando a saída do traceroute em gráficos visuais com PHP e Graphviz

27/09/2026 · 3 min · Desenvolvimento

Quem precisa analisar rotas de rede e gargalos de latência no dia a dia sabe que rodar ferramentas de diagnóstico como o traceroute via terminal é essencial. No entanto, quando é preciso expor essa ferramenta em uma interface web para clientes ou equipes de suporte, a saída de texto puro nem sempre é a forma mais amigável de enxergar onde o pacote está perdendo tempo.

Para resolver isso, decidi construir uma interface web em PHP que executa o traceroute, destaca a latência de cada salto (hop) com cores dinâmicas e compila um fluxo visual: um grafo direcionado mostrando o trajeto exato até o destino.

Durante os testes, me deparei com uma dependência que costuma passar despercebida em servidores Linux mínimos: a ausência do binário responsável por compilar arquivos de grafos (DOT) em imagens bitmap.

Abaixo, compartilho a arquitetura da solução, o diagnóstico do erro e o código PHP completo.


1. A lógica da aplicação e o erro inicial#

O fluxo planejado para o script é simples:

  1. Receber o endereço IP ou hostname via formulário POST.
  2. Validar estritamente a entrada para evitar problemas de segurança.
  3. Executar o utilitário nativo traceroute.
  4. Tratar cada linha da saída: extrair número do salto, IP/domínio e tempos de resposta em milissegundos.
  5. Estruturar a rota em um arquivo na linguagem descritiva DOT.
  6. Invocar um comando no sistema operacional para compilar o arquivo .dot em uma imagem .png.

O trecho de geração da imagem no PHP foi montado assim:

// Salvar a estrutura do grafo no disco
file_put_contents($dotFilePath, $dot);

// Compilar o arquivo DOT para imagem PNG
exec("dot -Tpng " . escapeshellarg($dotFilePath) . " -o " . escapeshellarg($imageFilePath));

Nos primeiros testes no servidor, a validação passava, o traceroute executava normalmente e o arquivo textual .dot era criado na pasta. Porém, a imagem PNG nunca era gravada e o script caía no aviso de falha:

Erro ao gerar o gráfico.

2. O diagnóstico: a dependência do Graphviz#

O motivo da falha é que a instrução dot -Tpng ... depende de um utilitário do sistema que não vem instalado por padrão em instalações limpas do Linux: o Graphviz.

O PHP consegue gerar a sintaxe da linguagem DOT sem qualquer biblioteca externa:

digraph G {
    rankdir=LR;
    "Inicio" -> "gateway.local (1.12 ms)";
    "gateway.local (1.12 ms)" -> "10.0.0.1 (4.50 ms)";
    "10.0.0.1 (4.50 ms)" -> "seusite.com.br (12.30 ms)";
}

No entanto, converter essa estrutura textual para um arquivo gráfico (.png ou .svg) exige o binário dot do Graphviz. Sem esse pacote presente no servidor, a função exec() encerra com código de erro e nenhuma imagem é produzida.


3. Instalando o Graphviz no servidor#

Para que o processo web (www-data, nginx ou nobody) consiga executar o compilador de grafos, instale o pacote correspondente à sua distribuição:

Em distribuições baseadas em Debian / Ubuntu:

sudo apt-get update
sudo apt-get install -y graphviz

Em distribuições baseadas em RHEL / AlmaLinux / Rocky Linux:

sudo dnf install -y graphviz

Assim que a instalação é concluída, o comando dot passa a ficar disponível no $PATH do sistema operacional.


4. O código PHP completo e tratado#

Abaixo está o script completo contendo validação de parâmetros, execução protegida, parse da saída do traceroute, destaque de cores por latência e renderização visual do grafo:

<?php

if ($_SERVER["REQUEST_METHOD"] == "POST") {
    // 1. Validacao estrita do Hostname ou IP recebido
    function isValidHostname($hostname) {
        return filter_var($hostname, FILTER_VALIDATE_IP) !== false || 
               filter_var($hostname, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME);
    }

    $hostname = trim($_POST['hostname'] ?? '');

    if (!isValidHostname($hostname)) {
        echo "<div class='alert alert-warning'>Por favor, insira um IP ou hostname valido.</div>";
        exit;
    }

    // 2. Execucao segura do traceroute
    $command = escapeshellcmd('traceroute ' . $hostname);
    $output = [];
    $return_var = 0;

    exec($command . ' 2>&1', $output, $return_var);

    if ($return_var !== 0) {
        $error_message = htmlspecialchars(implode("\n", $output));
        echo "<div class='alert alert-danger'>Erro ao executar traceroute: <pre>$error_message</pre></div>";
    } else {
        $header = array_shift($output);
        $formatted_output = '';
        $lines = explode("\n", implode("\n", $output));
        
        // 3. Inicializacao da estrutura do Grafo (Linguagem DOT)
        $dot = "digraph G {\n";
        $dot .= "rankdir=LR;\n"; // Disposicao horizontal (esquerda para a direita)
        $previous_hop = "Inicio";
        
        // Sanitizacao para o nome dos arquivos temporarios
        $dotname = preg_replace('/[^a-zA-Z0-9_-]/', '_', $hostname);

        foreach ($lines as $line) {
            $line = trim($line);
            if (empty($line) || strpos($line, 'traceroute') !== false) {
                continue;
            }

            $parts = preg_split('/\s+/', $line);
            
            // Verifica se a linha comeca com o numero do salto
            if (isset($parts[0]) && is_numeric($parts[0])) {
                $hop_number = $parts[0];
                $hop_name = $parts[1] ?? "Desconhecido";
                $latencies = [];

                // Captura os tempos de resposta em milissegundos
                foreach ($parts as $part) {
                    if (preg_match('/(\d+\.\d+)\s*ms/', $part, $matches)) {
                        $latencies[] = $matches[1] . " ms";
                    }
                }

                $latency_str = implode(", ", $latencies);
                $node_label = "{$hop_name} ({$latency_str})";
                
                // Conecta o no anterior ao no atual no grafo
                $dot .= "\"{$previous_hop}\" -> \"{$node_label}\";\n";
                $previous_hop = $node_label;

                unset($parts[0]);
                $line_content = implode(' ', $parts);
                $formatted_line = "<div class='__traceroute__value'><span class='__traceroute_route'>{$hop_number}</span> {$line_content}</div>";
            } else {
                $formatted_line = "<div class='__traceroute__value'>{$line}</div>";
            }

            // Destaque visual por cores dependendo do RTT
            $formatted_line = preg_replace_callback('/(\d+\.\d+)\s+ms/', function($matches) {
                $ms = floatval($matches[1]);
                $color_class = 'default';
                
                if ($ms < 1) {
                    $color_class = 'green';
                } elseif ($ms < 5) {
                    $color_class = 'blue';
                } else {
                    $color_class = 'red';
                }
                
                return "<span class='latency {$color_class}'>{$matches[0]}</span>";
            }, $formatted_line);

            $formatted_output .= $formatted_line;
        }

        $dot .= "}\n";

        // 4. Definicao dos caminhos de armazenamento
        $storageDir = '/var/www/html/views/tools/traceroute/';
        $dotFilePath = $storageDir . $dotname . '_traceroute_graph.dot';
        $imageFilePath = $storageDir . $dotname . '_traceroute.png';
        
        file_put_contents($dotFilePath, $dot);

        // 5. Compilacao da imagem com Graphviz
        exec("dot -Tpng " . escapeshellarg($dotFilePath) . " -o " . escapeshellarg($imageFilePath));

        // 6. Exibicao no navegador
        if (file_exists($imageFilePath)) {
            echo "<div class='alert alert-info'>" . htmlspecialchars($header) . "</div>";
            echo "<div class='__traceroute__container'><div class='__traceroute__content'>{$formatted_output}</div></div>";
            echo "<div class='mt-4'><div class='__traceroute__image'><img src='views/tools/traceroute/{$dotname}_traceroute.png' alt='Traceroute Graph para {$dotname}'></div></div>";
        } else {
            echo "<div class='alert alert-danger'>Erro ao gerar o grafico.</div>";
        }
    }
} else {
    echo "<div class='alert alert-danger'>Solicitacao invalida.</div>";
}
?>

Boas práticas para ferramentas de diagnóstico web#

Integrar ferramentas de terminal com PHP enriquece bastante a experiência de diagnóstico de rede. No entanto, sempre que utilizar funções de execução de comandos (exec, shell_exec), lembre-se de:

  1. Sanitizar rigorosamente entradas: Utilizar filtros nativos de IP/domínio e funções como escapeshellarg() para impedir injeção de comandos arbitrários no servidor.
  2. Mapear dependências externas: Documentar e incluir utilitários do sistema (como o Graphviz) nos seus playbooks de provisionamento para evitar falhas silenciosas na produção.

Este artigo foi útil?

Deixe uma reação rápida para apoiar o conteúdo:

CC BY-NC

Este post está licenciado sob CC BY-NC.

Comentários

Participe da discussão abaixo.

0 comentários