Monitoramento
omegaUp não é um programa, mas uma frota deles: o frontend PHP por trás do nginx, o classificador Go, seus executores, o transmissor e o gitserver, todos conversando com MySQL, Redis e RabbitMQ. Quando um concurso está no ar e alguns milhares de pessoas estão enfrentando os mesmos três problemas, "o site está bom?" deixa de ser uma pergunta sim/não e se torna "a fila está diminuindo, os executores estão vivos e algum método de API lança repentinamente 500?" Essa é a pergunta que a pilha de observabilidade existe para responder, e ela a responde com um conjunto pequeno e deliberadamente enfadonho de ferramentas: Prometheus para os números, New Relic para rastreamentos, erros e logs no contexto, e Metabase para produtos pós-fato e análise de dados. As próprias implantações são monitoradas pelo Argo CD, que reconcilia o que realmente está em execução no cluster Kubernetes com o que o Git diz que deveria estar em execução.
Nada aqui é exótico de propósito. Cada serviço publica texto simples do Prometheus em um endpoint /metrics, cada solicitação PHP enriquece uma transação New Relic se o agente estiver presente e silenciosamente não faz nada se não estiver, e tudo se degrada para "ainda funciona, apenas cego" em um contêiner de desenvolvimento onde nenhum dos agentes está instalado.
Visão geral
flowchart TB
subgraph Services
Frontend["Frontend (PHP 8.1 / php-fpm)"]
Grader[Grader]
Runner[Runner]
Broadcaster[Broadcaster]
end
subgraph Prometheus_side["Metrics (pull)"]
Prom[Prometheus scrapes /metrics]
end
subgraph NewRelic_side["New Relic (push)"]
APM[APM transactions + errors]
Logs[Logs in context]
RUM[Browser agent / RUM]
end
Metabase[(Metabase — MySQL analytics)]
Frontend -->|/metrics.php| Prom
Grader -->|:6060/metrics| Prom
Runner -->|:6060/metrics| Prom
Broadcaster -->|:6060/metrics| Prom
Frontend -->|newrelic ext + Monolog| APM
Frontend --> Logs
Frontend -->|NEW_RELIC_SCRIPT in page head| RUM
MySQL[(MySQL 8.0)] --> Metabase
A divisão importante para manter em sua cabeça: Prometheus puxa, New Relic empurra. Prometheus estende a mão e raspa um ponto final que você expõe; O agente PHP da New Relic e o enriquecedor Monolog enviam dados de dentro da solicitação. É por isso que uma caixa com firewall ou sem agente ainda produz métricas do Prometheus (desde que o raspador possa alcançá-la), mas não produz nenhum dado do New Relic.
Prometeu: os números
A interface PHP
A integração do frontend Prometheus é um único wrapper pequeno, \OmegaUp\Metrics em frontend/server/src/Metrics.php, construído no cliente promphp/prometheus_client_php (atualmente fixado em ^v2.4.0 em composer.json). Na construção, ele escolhe um adaptador de armazenamento com base na disponibilidade do APCu: \Prometheus\Storage\APC em produção (para que os contadores sobrevivam às solicitações na memória compartilhada do pool de trabalhadores php-fpm) e \Prometheus\Storage\InMemory como substituto, que redefine todas as solicitações e é realmente útil apenas em testes. Essa escolha é importante: se o APCu estiver faltando, seus contadores serão redefinidos silenciosamente a cada solicitação e suas taxas parecerão um ruído.
Há exatamente um lugar que grava métricas de aplicativos hoje: o próprio funil de solicitação. \OmegaUp\ApiCaller::call() (frontend/server/src/ApiCaller.php) chama \OmegaUp\Metrics::getInstance()->apiStatus($methodName, $status) duas vezes: uma vez no caminho de sucesso com status 200 e uma vez no caminho de falha com o código HTTP da exceção de API real. Cada chamada atinge dois contadores:
frontend_api_request_status_count{api, status}— um contador digitado pelo nome do método API (por exemplo,/api/run/create/aparece como o método) e o código de status resultante, para que você possa perguntar "quantos 401srun.createlançou nos últimos cinco minutos" com um únicorate().frontend_api_request_total{api}— a mesma coisa sem o rótulo de status, ou seja, total de chamadas por método, que é o denominador quando você deseja uma proporção de erro em vez de uma contagem de erro.
Esses dois são suficientes para calcular os dois sinais que realmente prevêem uma interrupção: taxa de solicitação por endpoint e a fração deles que não são 200. Não há histograma de latência por endpoint no lado do PHP hoje - a latência reside no New Relic (veja abaixo), porque é aí que você também obtém o gráfico em degradê para explicar por que uma chamada foi lenta, o que um número simples do Prometheus não pode fornecer.
O Prometheus raspa o frontend em frontend/www/metrics.php, que é o mais fino que uma página pode ter: requer bootstrap.php e chama \OmegaUp\Metrics::getInstance()->render(). render() define Content-type: text/plain (via \Prometheus\RenderTextFormat::MIME_TYPE) e ecoa o formato de exposição. Aponte um trabalho de raspagem para esse caminho e pronto.
O aluno
O avaliador é o componente que você realmente observa durante uma competição e é o mais instrumentado. Suas métricas residem no repositório Go omegaup/quark em cmd/omegaup-grader/metrics.go, servido por promhttp.Handler() em uma porta dedicada – Metrics.Port, cujo padrão é 6060 (consulte MetricsConfig em common/context.go). Tudo tem o namespace quark com o subsistema grader, portanto, os nomes dos fios são quark_grader_*.
Os medidores de fila são o centro disso, e há um por nível de prioridade porque o avaliador mantém filas separadas em vez de uma grande:
| Métrica | O que isso diz a você |
|---|---|
quark_grader_queue_total_length |
Tudo esperando, em todas as filas. O único número para alertar. |
quark_grader_queue_high_length |
Backlog de alta prioridade – envios interativos/concursos que as pessoas estão olhando. |
quark_grader_queue_normal_length |
Backlog de prioridade normal. |
quark_grader_queue_low_length |
Backlog de baixa prioridade (rejulgamentos e outros trabalhos em massa que não devem privar as filas ativas). |
quark_grader_queue_ephemeral_length |
A fila efêmera, usada pelas execuções scratch "run this in the arena" que nunca tocam o banco de dados. |
Junto com cada fila está um resumo, quark_grader_queue_delay_seconds (e o quark_grader_queue_{high,normal,low,ephemeral}_delay_seconds por camada), que mede quanto tempo uma corrida ficou na fila antes de um corredor retirá-la. Eles são exportados com objetivos quantílicos 0.5, 0.9 e 0.99 (os alvos {0.5: 0.05, 0.9: 0.01, 0.99: 0.001} no código), então quark_grader_queue_delay_seconds{quantile="0.99"} é sua espera p99 - o número honesto "quão ruim é para o remetente mais azarado no momento", que é exatamente o que importa quando o comprimento da fila parece bom em média, mas alguns envios estão presos por um problema lento.
O rendimento e a integridade vêm de contadores e de um vetor de medidor:
quark_grader_runs_total— cada corrida graduada. Seurate()é o seu envio por segundo.quark_grader_ephemeral_runs_total,quark_grader_ci_jobs_total— as variantes de execução temporária e CI problemático, contadas separadamente para que a atividade de CI em massa não se disfarce como carga de concurso.quark_grader_runs_retry,quark_grader_runs_abandoned— uma corrida é repetida quando seu corredor desaparece no meio da rampa; ele é abandonado quando tentar novamente não ajuda. Umruns_abandonedcrescente é a métrica que diz que “as execuções estão sendo descartadas silenciosamente”, o que é muito pior do que uma fila lenta.quark_grader_runs_je— execuções que terminaram com um veredictoJE(Erro do Juiz). Isso deve ser zero; qualquer inclinação significa que a própria motoniveladora está quebrada, e não o código enviado.quark_grader_runner_up{runner_hostname, runner_public_ip}— um medidor definido como1para cada corredor de quem o avaliador ouviu falar recentemente. O avaliador considera um corredor vivo somente se ele tiver feito check-in nos últimos 3 minutos (o ponto de corte-3 * time.MinuteemgaugesUpdate); uma vez que um corredor fica obsoleto, todo o vetor éReset()e repovoado, então um corredor que morre simplesmente desaparece da série. Resumir esse medidor fornece a contagem de corredores ao vivo, e observá-la cair é como você pega um anfitrião de corredor caindo antes que a fila recue visivelmente.
A niveladora também exporta sinais vitais do host como os_cpu_load1 / os_cpu_load5 / os_cpu_load15, os_mem_total / os_mem_used e os_disk_total / os_disk_used, atualizados uma vez por minuto por um ticker em gaugesUpdate() (via gopsutil do load, auxiliares mem e disk). disk_used subindo em direção a disk_total é o clássico eliminador de niveladoras silenciosas – a caixa se enche de entradas de problemas e paradas de nivelamento – então ele ganha seu próprio medidor.
Um endpoint extra que vale a pena conhecer: junto com /metrics, o avaliador atende /metrics/runners, que retorna uma lista JSON dos executores atualmente ativos no formato Prometheus descoberta de serviço de arquivo (targets + labels, novamente usando o corte de atualização de 3 minutos). É assim que o Prometheus aprende quais caixas de corredores devem ser raspadas sem que ninguém edite manualmente uma lista de alvos toda vez que a frota de corredores aumenta ou diminui.
O corredor e o locutor
Cada executor expõe seu próprio /metrics (mesmo namespace quark, subsistema runner). As séries de suporte de carga são quark_runner_validator_errors (uma contagem crescente aqui significa que os validadores personalizados estão travando, o que silenciosamente transforma envios corretos em veredictos errados) além de uma família de medidores quark_benchmark_* - io_time, cpu_time, memory_time e seus companheiros _wall_time / _memory - que registram o desempenho da caixa em relação a um benchmark conhecido, então você pode diferenciar um corredor genuinamente sobrecarregado de outro que acabou de enfrentar um problema pesado. Ele também relata os mesmos sinais vitais do host os_* que a motoniveladora.
A emissora – o serviço que os fãs contestam eventos para navegadores por meio de SSE e WebSockets – exporta (subsistema broadcaster): broadcaster_websockets_count e broadcaster_sse_count (conexões atualmente abertas de cada tipo), broadcaster_messages_total (mensagens enviadas) e broadcaster_channel_drop_total. Esse último é o alarme: uma gravação de canal perdida significa que um cliente estava muito lento para acompanhar e foi cortado, então a subida do channel_drop_total durante uma competição significa que as pessoas estão perdendo atualizações do placar ao vivo. A latência de despacho e processamento vem dos resumos broadcaster_dispatch_latency_seconds e broadcaster_process_latency_seconds.
Cada serviço Go também emite um contador build_info carregando rótulos const version e go_version, que existem apenas para que você possa confirmar no Prometheus qual versão binária está realmente sendo executada em cada host após uma implantação - útil quando uma implementação é aplicada pela metade e metade dos executores estão na versão antiga.
O status da fila voltada para o aplicativo
Prometheus é a visão do operador. Há um segundo caminho de status separado destinado ao próprio aplicativo. \OmegaUp\Grader::status() em frontend/server/src/Grader.php emite uma solicitação curl para OMEGAUP_GRADER_URL . '/grader/status/' (com OMEGAUP_GRADER_URL padronizado como https://localhost:21680) e recebe de volta um pequeno blob JSON - run_queue_length, runner_queue_length, runners, broadcaster_sockets e embedded_runner — apresentado por meio de \OmegaUp\Controllers\Grader::apiStatus(). É isso que renderiza o pequeno indicador de fila dentro do site, não o que o Prometheus raspa. Em um ambiente de desenvolvimento em que OMEGAUP_GRADER_FAKE está definido, status() entra em curto-circuito e retorna uma estrutura totalmente zeros para que a IU não apresente erros quando não houver nenhum avaliador real por trás dele. Não use isso para criar painéis – é um instantâneo de um momento específico sem histórico; é para isso que serve o raspador /metrics.
New Relic: rastreamentos, erros e logs no contexto
Enquanto Prometheus diz que algo está lento ou falhando, New Relic diz qual linha e para quem. A integração tem três pontas, e todas as três são escritas para serem autônomas quando o agente não está instalado, porque os contêineres de desenvolvimento não enviam a extensão PHP newrelic e ninguém quer que o aplicativo seja interrompido lá.
Nomeação de transação. \OmegaUp\Request chama \OmegaUp\NewRelicHelper::nameTransaction("/api/{$this->methodName}") para que cada chamada de API apareça no New Relic com seu próprio nome — run.create, contest.details e assim por diante — em vez de tudo se transformar em uma transação index.php anônima. Sem isso, a latência do APM é inútil porque não é possível saber qual endpoint é o mais lento.Relatório de erros. Quando ApiCaller::call() captura uma exceção que não foi tratada de outra forma, ele a encaminha através de \OmegaUp\NewRelicHelper::noticeError(), que encaminha para newrelic_notice_error() — mas somente depois que isAvailable() confirmar que a extensão foi carregada e as funções existem. NewRelicHelper (frontend/server/src/NewRelicHelper.php) é a costura inteira: noticeError, nameTransaction, addCustomAttribute e um getStatus() que você pode chamar para depurar se o agente está conectado. Cada método protege primeiro o extension_loaded('newrelic'), e é exatamente por isso que o mesmo código funciona bem em um laptop sem agente.
Logs em contexto. O root logger é configurado uma vez no frontend/server/bootstrap.php. Ele constrói um Monolog Logger chamado omegaup gravando em OMEGAUP_LOG_FILE (padrão /var/log/omegaup/omegaup.log) no nível OMEGAUP_LOG_LEVEL (padrão info), e aqui está a parte inteligente: se \NewRelic\Monolog\Enricher\Formatter existir (do pacote newrelic/monolog-enricher), ele usa esse formatador e envia um \NewRelic\Monolog\Enricher\Processor no registrador; caso contrário, ele volta para um \Monolog\Formatter\LineFormatter simples. O enriquecedor carimba cada linha de log com os IDs de rastreamento/entidade do New Relic, que é o que permite pular de uma transação lenta diretamente para as linhas de log exatas que a solicitação foi emitida. Um \Monolog\Processor\WebProcessor é sempre adicionado (URL de solicitação, método, IP), e \Monolog\ErrorHandler::register() conecta os próprios erros do PHP ao mesmo logger para que um fatal não escape sem registro.
Agente de navegador (RUM). O frontend também pode injetar o script de navegador do New Relic no cabeçalho da página. O shell Twig frontend/templates/template.tpl emite {{ NEW_RELIC_SCRIPT|raw }} dentro de {% if NEW_RELIC_SCRIPT %}, portanto, o monitoramento do usuário real só é ativado quando o valor de configuração NEW_RELIC_SCRIPT é definido (o padrão é null, ou seja, desativado, em config.default.php, ao lado de NEW_RELIC_SCRIPT_HASH, que existe para que o script embutido possa ser listado como permitido no Política de Segurança de Conteúdo sem enfraquecê-la). É isso que captura o tempo real de carregamento da página de navegadores reais, em vez de apenas o tempo do lado do servidor.
Metabase e Argo CD
Mais duas ferramentas completam o quadro, e ambas são nomeadas nas notas operacionais do omegaUp, e não na base de código, porque observam o sistema de fora.
Metabase é a camada de análise de dados e relatórios. Ele se conecta ao MySQL de produção e permite que as pessoas criem consultas e painéis sem SQL escrito à mão - as perguntas que ele responde são questões de produto ("quantos usuários resolveram pelo menos um problema este mês") em vez de questões operacionais ("a fila está acabando"). Historicamente, tem sido o mais esquisito do grupo; se estiver mostrando um erro de conexão, o link do Metabase para o banco de dados está inativo, não o site em si, e o site está perfeitamente bem sem ele.
Argo CD monitora implantações em vez de tráfego. É o controlador de entrega contínua para o cluster Kubernetes e trata o Git como a única fonte da verdade: ele compara continuamente o estado desejado declarado no repositório de implantação com o que realmente está em execução no cluster e sinaliza (ou reconcilia) qualquer desvio. Quando você quiser saber "minha mudança realmente foi implementada e todas as réplicas estão na nova versão", o status de sincronização do Argo CD é o primeiro lugar a procurar - e combina naturalmente com a métrica build_info acima, que confirma a mesma coisa da própria boca do binário em execução.
Um exemplo prático: "os envios parecem lentos"
O objetivo de ter essas ferramentas é que um relatório vago resolve uma causa específica em algumas consultas. Quando alguém disser que os envios estão lentos durante um concurso, siga a cadeia em ordem de dependência:
- Foi realmente feito backup da fila? Veja
quark_grader_queue_total_lengthe oquark_grader_queue_high_lengthpor camada. Se o total for estável e baixo, o avaliador está acompanhando e o problema está em outro lugar (frontend, rede). Se estiver subindo, continue. - Os corredores estão desaparecendo? Soma
quark_grader_runner_up. Uma queda aqui – um host executor que parou de fazer check-in dentro da janela de 3 minutos – significa menos capacidade de avaliação e a fila aumentará, não importa quão saudável o avaliador esteja. Verifique com o Argo CD para ver se um lançamento ruim derrubou os corredores. - O culpado é um único problema? Verifique
quark_grader_queue_delay_seconds{quantile="0.99"}em relação à mediana. Um p99 enorme com um p50 normal significa que a maioria das execuções está bem, mas algumas estão presas a um problema caro, não a uma falta geral de capacidade. - A própria motoniveladora está errando em vez de apenas ficar lenta? Observe
quark_grader_runs_retrye especialmentequark_grader_runs_abandonedequark_grader_runs_je. Qualquer inclinação em abandono ou JE significa que as corridas estão sendo abandonadas ou o juiz está quebrado – um incidente de correção, não de desempenho. - Ou é o frontend, e não o avaliador? De volta ao lado do PHP,
rate(frontend_api_request_status_count{api="run.create", status!="200"}[5m])sobrerate(frontend_api_request_total{api="run.create"}[5m])fornece a taxa de erro para envios, e a transaçãorun.createda New Relic (nomeada exatamente por esse motivo) mostra se o tempo está indo para o MySQL, a chamada HTTP do avaliador ou o próprio PHP - com as linhas de log enriquecidas dessa solicitação a um clique de distância.
Uma nota sobre nomes de host
Os painéis, a conta New Relic, a instância do Metabase e o console do Argo CD residem em URLs privados e autenticados que não são publicados aqui propositalmente. Se você precisar de acesso, será uma conversa sobre credenciais e permissões com a equipe de manutenção, não um link colado em um navegador.
Documentação Relacionada
- Solução de problemas — transformando um sintoma em uma solução
- Infraestrutura — como os serviços se encaixam
- Implantação — com o que o Argo CD está se reconciliando