Skip to content

perf: parser próprio, filtro paralelo e fim da paginação de exibição - #10

Merged
afernandes merged 2 commits into
mainfrom
feat/perf-parser-filtro-e-virtualizacao
Aug 2, 2026
Merged

perf: parser próprio, filtro paralelo e fim da paginação de exibição#10
afernandes merged 2 commits into
mainfrom
feat/perf-parser-filtro-e-virtualizacao

Conversation

@afernandes

Copy link
Copy Markdown
Owner

Todos os números abaixo foram medidos sobre os 314.973 eventos reais de C:\TOTVSPDV\Logs (104 arquivos, 322 MB), não sobre dados sintéticos.

⚠️ Antes de mergear: o CI vai ficar vermelho

O app aponta para um pacote local da Omni.Blazor (0.4.1-alpha.local.1) via um NuGet.Config temporário, porque as correções que ele precisa estão em Omni.Blazor#34 e ainda não foram publicadas. No CI a pasta local não existe e o restore falha com NU1101 — de propósito: o packageSourceMapping impede cair no nuget.org, o que traria a 0.4.0 de volta e o congelamento junto, sem ninguém perceber.

Reversão quando a 0.5.0 sair (está escrita no topo do NuGet.Config): apagar o NuGet.Config, trocar a versão no src/ClefExplorer.csproj para 0.5.0, remover a nota do README.

O defeito que motivou a maior parte disto

Ordenar ou agrupar uma coluna trazia "menos coisa do que deveria". A causa: o LogViewer entregava ao grid apenas a página (Skip(...).Take(50)). Clicar em "Data/hora" reordenava os 50 itens da tela, não o log.

Agora lista e tabela recebem o conjunto filtrado inteiro e virtualizam — a rolagem substituiu o paginador.

Desempenho

antes depois
parsing 33k ev/s 92k ev/s
alocação transitória na carga 4.412 MB 1.057 MB
busca VAREJO 138,7 ms 17,8 ms
busca regex 1.038 ms 160 ms
memória retida 1.295 MB 1.043 MB

Parser próprio (LeitorClef, sobre Utf8JsonReader) no lugar do Serilog.Formatting.Compact.Reader/Newtonsoft. O leitor antigo construía uma JObject inteira por linha só para descartá-la, e reparseava o message template a cada evento — 200 mil parses para 5 templates distintos.

Validado por um oráculo que lê os mesmos arquivos com os dois parsers e compara evento a evento (timestamp, nível, template, mensagem renderizada, exceção, e o ToString() de cada valor de propriedade): 0 divergências em 314.973 eventos e nos casos de borda (BOM, CRLF, emoji, escapes JSON, JSON truncado, número maior que long, linha de 1 MB).

Busca paralela com máscara bool[] + gather sequencial, que preserva a ordem sem custo — medido contra ConcurrentBag sem ordem: 183 ms vs 190 ms. O gargalo era LogEventPropertyValue.ToString() materializando uma string por propriedade por evento só para comparar.

PoolDeTextos: nível, @mt e chaves de propriedade eram recriados a cada linha — 3,8 milhões de instâncias para 27 valores distintos.

Modo ao vivo

Tinha um defeito silencioso: o tail varria apenas os arquivos existentes no momento da carga. Com a rotação diária (log20260731.clef), ele morria à meia-noite. Agora redescobre arquivos novos.

O poll também abria os 104 arquivos só para ler o tamanho — 2.494 ms por segundo em pasta de rede, contra 2,7 ms de uma enumeração de diretório. E o merge virou por região (19 ms → 0,29 ms).

UI que não trava

LogStatistics.Compute rodava na thread da UI (544 ms a cada filtragem com 1M de eventos). A exportação disparava um IProgress.Report por evento — 200 mil reports, 3,8 s só de fila de mensagens. Ambos corrigidos, mais indicador de carregamento com progresso e cancelamento no miolo da tela.

Correções

  • Assets da Omni davam 404 desde a 0.4.0: o BasePath mudou de Omni.Blazor/ para _content/Omni.Blazor/ e o app abria sem estilo nenhum.
  • Regras de CSS miravam .omni-datagridclasse que nunca existiu na lib. O destaque da linha selecionada e a barra vermelha nos erros nunca funcionaram.
  • Agrupar por propriedade ausente em uns eventos e nula em outros dava dois grupos "(vazio)" (3.216 e 46 casos no log real).
  • @r não era gravado na exportação CLEF: exportar e reabrir mudava a mensagem de 38 em 314.973 eventos.

Usabilidade

Filtro de período passou a considerar a hora; a coluna Mensagem agrupa pelo template e os campos dele viram colunas próprias; busca na árvore de arquivos; menu de contexto (mostrar no Explorer, copiar caminho, isolar seleção).

Testes

499 passando (eram 223 no início). Cobrem o parser campo a campo do CLEF, a equivalência paralelo/sequencial do filtro (conteúdo e ordem, por referência), o tail (arquivo novo, truncado, merge por região) e as correções acima.

Nota sobre o histórico

O commit é único e grande porque a árvore já tinha trabalho não commitado de antes desta sessão — os serviços extraídos do LogStore (CaminhosEntrada, DescobertaArquivosLog, FiltroArquivosLogIgnorados, ConsultaLogs), o arrastar-e-soltar interno e seus testes. Separar depois exigiria um git add -p cirúrgico sobre arquivos que várias frentes tocaram.

🤖 Generated with Claude Code

Reúne o trabalho de desempenho e usabilidade da sessão, mais o que já estava
pendente na árvore (serviços extraídos do LogStore, entrada de caminhos,
descoberta de arquivos e arrastar-e-soltar interno).

DESEMPENHO — medido sobre 314.973 eventos reais (104 arquivos, 322 MB)

- Parser CLEF próprio sobre Utf8JsonReader (LeitorClef), no lugar do
  Serilog.Formatting.Compact.Reader/Newtonsoft: 33k -> 92k eventos/s e alocação
  transitória de 4.412 MB -> 1.057 MB. O leitor antigo construía uma JObject
  inteira por linha só para descartá-la e reparseava o message template a cada
  evento (200 mil parses para 5 templates). Validado por um oráculo que compara
  evento a evento com o parser antigo: 0 divergências, inclusive nos casos de
  borda (BOM, CRLF, emoji, JSON truncado, número maior que long).
- Busca textual paralela (máscara bool + gather sequencial, que preserva a
  ordem): 'VAREJO' de 138,7 ms para 17,8 ms; regex de 1.038 ms para 160 ms. O
  custo vinha de LogEventPropertyValue.ToString() materializar uma string por
  propriedade por evento só para comparar.
- PoolDeTextos: nível, template e chaves de propriedade eram recriados a cada
  linha — 3,8 milhões de instâncias para 27 valores distintos. -181 MB.
- Snapshot imutável versionado no LogStore: Snapshot() deixa de copiar o
  conjunto a cada consulta e de segurar o lock enquanto copia.

EXIBIÇÃO

- Fim da paginação: lista e tabela recebem o conjunto filtrado inteiro e
  virtualizam. Antes o grid recebia 50 itens, então ordenar por uma coluna
  reordenava a página, não o log — que era o defeito por trás de "vem menos
  coisa do que deveria".
- Coluna Mensagem agrupa pelo template (@mt), não pelo texto formatado, e os
  campos do template viram colunas próprias mesmo sendo raros.
- Filtro de período passa a considerar a hora.

MODO AO VIVO

- Corrigido o defeito que o matava à meia-noite: o tail só varria os arquivos
  existentes no momento da carga e nunca via o arquivo do dia seguinte.
- O poll deixa de abrir todos os arquivos para ler o tamanho (2.494 ms em rede
  contra 2,7 ms de uma enumeração de diretório) e o merge passou a ser só na
  região afetada (19 ms -> 0,29 ms).

UI QUE NÃO TRAVA

- LogStatistics.Compute saiu da thread da UI (544 ms a cada filtragem em 1M).
- Exportação com progresso throttled: eram 200 mil IProgress.Report, 3,8 s só
  de fila de mensagens.
- Indicador de carregamento no miolo da tela com progresso e cancelamento;
  _isBusy só quando não há conteúdo, e corrigido o caso em que ele ficava preso.

CORREÇÕES

- Assets da Omni.Blazor davam 404 desde a 0.4.0: o BasePath mudou de
  "Omni.Blazor/" para "_content/Omni.Blazor/" e o app abria sem estilo.
- Regras de CSS miravam ".omni-datagrid", classe que nunca existiu na lib — o
  destaque da linha selecionada e a barra de erro nunca valeram.
- Agrupar por uma propriedade ausente em uns eventos e nula em outros dava dois
  grupos "(vazio)".
- @r (renderings) não era gravado na exportação CLEF: exportar e reabrir mudava
  a mensagem de 38 em 314.973 eventos.

USABILIDADE

- Busca na árvore de arquivos e menu de contexto (mostrar no Explorer, copiar
  caminho, isolar seleção).

Pacotes atualizados (Omni.Blazor, WebView2, Serilog, Test SDK, coverlet).

TEMPORÁRIO: o app aponta para um pacote local da Omni.Blazor até a 0.5.0 ser
publicada — ver NuGet.Config. O CI fica vermelho até lá.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings August 2, 2026 05:14

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Este PR refatora o pipeline de ingestão/consulta/exportação de logs do ClefExplorer para suportar conjuntos grandes com melhor desempenho e UX (virtualização sem paginação, parser próprio, filtro paralelo, tail mais robusto), além de corrigir regressões e adicionar uma bateria extensa de testes de contrato.

Changes:

  • Substitui parsing e leitura de arquivos CLEF por implementações próprias (leitura em blocos, BOM/CRLF, isolamento de linhas inválidas, suporte a .gz) e adiciona deduplicação de strings via PoolDeTextos.
  • Reescreve filtragem/consulta (varredura paralela com preservação de ordem, timeout de regex, cancelamento) e estatísticas (passagem única + timeline eficiente).
  • Atualiza UI para virtualização sem paginação, melhorias no modo ao vivo e na árvore de arquivos (busca, menu de contexto, integração Explorer), além de exportação incremental com progresso/cancelamento.

Reviewed changes

Copilot reviewed 45 out of 46 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
test/ClefExplorer.Tests/PoolDeTextosTests.cs Testes de contrato do pool de strings (identidade e concorrência).
test/ClefExplorer.Tests/PeriodoFiltroTests.cs Testes de ajustes de período (minuto/fim do dia).
test/ClefExplorer.Tests/LogStoreTests.cs Amplia cobertura de carga/tail/cancelamento e invariantes de estado.
test/ClefExplorer.Tests/LogStatisticsTests.cs Cobertura para bordas de exceções e equivalência do algoritmo reescrito.
test/ClefExplorer.Tests/LogFilterTests.cs Valida timeout de regex, cancelamento e varredura paralela preservando ordem.
test/ClefExplorer.Tests/LogExporterTests.cs Testa exportação incremental, segurança CSV e roundtrip CLEF (tipos/@r).
test/ClefExplorer.Tests/LogColumnDiscoveryTests.cs Testa colunas por template, inferência de tipo e ordenação por valor tipado.
test/ClefExplorer.Tests/LeitorArquivoLogTests.cs Testes do leitor de arquivo (BOM/CRLF/linhas grandes/.gz/linhas inválidas).
test/ClefExplorer.Tests/FiltroArvoreArquivosTests.cs Testes do filtro de visibilidade na árvore de arquivos.
test/ClefExplorer.Tests/ExploradorArquivosTests.cs Testes do serviço de integração com Explorer (quoting/paths inválidos).
test/ClefExplorer.Tests/ConsultaLogsTests.cs Testes do coordenador de consultas concorrentes (geração/cancelamento).
test/ClefExplorer.Tests/ClefExplorer.Tests.csproj Atualiza SDK/collector do projeto de testes.
test/ClefExplorer.Tests/CaminhosEntradaTests.cs Testes de normalização de caminhos de entrada (relativo/absoluto).
src/wwwroot/js/internal-drag.js Workaround para coexistir drag interno do grid com drop externo do WebView2.
src/wwwroot/index.html Corrige paths de static assets da Omni.Blazor e registra internal-drag.js.
src/wwwroot/css/app.css Ajustes de layout/virtualização e correção de seletores do grid Omni.
src/Services/WebViewDropMode.cs Ponte estática para alternar AllowExternalDrop durante drag interno.
src/Services/PoolDeTextos.cs Implementa deduplicação de strings com ConcurrentDictionary.
src/Services/LogFilter.cs Reescrita do filtro: paralelo com máscara, cancelamento por bloco, regex timeout, busca eficiente em propriedades.
src/Services/LogExporter.cs Exportação incremental/atômica, preservação de tipos, reemissão de @r, mitigação de CSV formula injection, progresso throttled.
src/Services/LeitorArquivoLog.cs Leitor de arquivo em blocos + parser por linha + BOM/CRLF/.gz + leitura de trecho para tail.
src/Services/FiltroArquivosLogIgnorados.cs Compila curingas de arquivos ignorados para regex com cache/snapshot.
src/Services/ExploradorArquivos.cs Serviço para abrir/revelar caminhos no Explorer com fallbacks.
src/Services/DescobertaArquivosLog.cs Descoberta incremental/deduplicada de arquivos (tolerante a ACLs).
src/Services/ConsultaLogs.cs Coordena consultas concorrentes via geração + cancelamento.
src/Services/CaminhosEntrada.cs Normaliza argumentos antes de troca de diretório do host.
src/Program.cs Integra normalização de args, DI para novos serviços e validação do container.
src/Models/FileTreeNode.cs Modelo compartilhado da árvore virtual de arquivos.
src/MainForm.cs Melhorias de init do WebView2, drop handling, e UI de erro de inicialização.
src/Helpers/PeriodoFiltro.cs Regras testáveis de ajuste de período para critérios do filtro.
src/Helpers/LogStatistics.cs Reescrita para reduzir passadas/alocações, mantendo comportamento via testes-oráculo.
src/Helpers/LogColumnDiscovery.cs Descoberta de colunas por propriedades e por campos do template, inferência de tipo e valor de ordenação tipado.
src/Helpers/FiltroArvoreArquivos.cs Filtra nós visíveis preservando instâncias (checkboxes por referência).
src/Components/LogToolbar.razor Toolbar com indicadores de filtragem/carga/exportação e cancelamentos; remove paginação.
src/Components/LogSidebar.razor Periodo com hora, busca da árvore e uso de PeriodoFiltro.
src/Components/LogHeader.razor Remove seletor de “itens por página” junto com paginação.
src/Components/LogGrid.razor Grid virtualizado sem paginação, agrupamento por data, colunas por template e UI busy-state.
src/Components/LogFileTree.razor Árvore com filtro + menu de contexto (copiar/isolar/explorer) preservando nós.
src/ClefExplorer.csproj Atualiza dependências (inclui pacote local temporário da Omni.Blazor) e remove Reader antigo.
README.md Documenta dependência local temporária para Omni.Blazor.
NuGet.Config Fonte local + packageSourceMapping para forçar pacote Omni local e “quebrar” no CI intencionalmente.
.gitignore Ignora .playwright-mcp/.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/Components/LogGrid.razor
Comment thread src/Helpers/LogColumnDiscovery.cs
A 0.5.0 saiu no nuget.org com a virtualização do modo agrupado, o MaxGroups, o
auto-colapso e o GroupHierarchy de data — tudo que o app passou a exigir. Sai o
NuGet.Config temporário que apontava para o pacote local, junto com a nota do
README; a referência volta a ser o feed público.

ClefVersion 1.1.0 -> 1.2.0: parser CLEF próprio, filtro paralelo, fim da
paginação de exibição e o agrupamento hierárquico por data são mudança de
comportamento visível, não correção.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@afernandes
afernandes merged commit 92b5c92 into main Aug 2, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants