perf: parser próprio, filtro paralelo e fim da paginação de exibição - #10
Merged
Merged
Conversation
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>
There was a problem hiding this comment.
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 viaPoolDeTextos. - 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.
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.O app aponta para um pacote local da Omni.Blazor (
0.4.1-alpha.local.1) via umNuGet.Configtemporá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 comNU1101— de propósito: opackageSourceMappingimpede 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 oNuGet.Config, trocar a versão nosrc/ClefExplorer.csprojpara0.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
LogViewerentregava 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
VAREJOParser próprio (
LeitorClef, sobreUtf8JsonReader) no lugar doSerilog.Formatting.Compact.Reader/Newtonsoft. O leitor antigo construía umaJObjectinteira 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 quelong, linha de 1 MB).Busca paralela com máscara
bool[]+ gather sequencial, que preserva a ordem sem custo — medido contraConcurrentBagsem ordem: 183 ms vs 190 ms. O gargalo eraLogEventPropertyValue.ToString()materializando uma string por propriedade por evento só para comparar.PoolDeTextos: nível,@mte 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.Computerodava na thread da UI (544 ms a cada filtragem com 1M de eventos). A exportação disparava umIProgress.Reportpor 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
BasePathmudou deOmni.Blazor/para_content/Omni.Blazor/e o app abria sem estilo nenhum..omni-datagrid— classe que nunca existiu na lib. O destaque da linha selecionada e a barra vermelha nos erros nunca funcionaram.@rnã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 umgit add -pcirúrgico sobre arquivos que várias frentes tocaram.🤖 Generated with Claude Code