From bac691aae10e0ad692c43ec1f1002a44f3d9ddc3 Mon Sep 17 00:00:00 2001 From: Claudecio Martins Date: Tue, 20 Oct 2026 15:47:38 -0300 Subject: [PATCH] test(bench): add automated benchmark scripts and document baseline metrics --- docs/performance.md | 57 ++++++++ scripts/bench/BenchmarkRunner.java | 200 ++++++++++++++++++++++++++++ scripts/bench/bench_startup_rss.sh | 60 +++++++++ scripts/bench/run_all_benchmarks.sh | 54 ++++++++ 4 files changed, 371 insertions(+) create mode 100644 docs/performance.md create mode 100644 scripts/bench/BenchmarkRunner.java create mode 100755 scripts/bench/bench_startup_rss.sh create mode 100755 scripts/bench/run_all_benchmarks.sh diff --git a/docs/performance.md b/docs/performance.md new file mode 100644 index 0000000..4c1510f --- /dev/null +++ b/docs/performance.md @@ -0,0 +1,57 @@ +# Relatório de Desempenho e Benchmarks — CyberCore Signer + +Este documento consolida as medições empíricas de tempo de inicialização, pegada de memória (Resident Set Size - RSS) e latência de operações criptográficas (enumeração e assinatura digital PAdES com selo visual) no agente desktop. + +--- + +## 1. Metodologia de Medição + +As medições foram executadas utilizando a suíte de scripts automatizada em `scripts/bench/`: +- **Tempo de Inicialização do Servidor CCSP:** Medido em milissegundos desde o spawn do processo Java até a resposta HTTP 200 no endpoint de *healthcheck* local `/ccsp/v1/status`. Média aritmética de 3 execuções consecutivas. +- **Memória (RSS) em Repouso (Idle):** Medido via `ps -o rss= -p ` após o servidor estar pronto e aguardar 2 segundos sem carga. +- **Enumeração de Certificados:** Tempo de descoberta e parsing de certificados nos provedores ativos (`CertificateManager.discoverCertificates()`). +- **Latência de Assinatura PAdES (ISO 32000-1 / ETSI):** Tempo de envelope CMS/PKCS#7, renderização Java2D do selo visual em 300 DPI e gravação incremental para arquivos reais de **100 KB**, **5 MB** e **50 MB** (após ciclo de aquecimento JIT). + +### Ambiente de Teste +- **Sistema Operacional:** Linux x86_64 (Kernel 6.6+) +- **Runtime Java:** OpenJDK 25 LTS (build 25.0.4.1+1) +- **Heap / JVM:** Sem flags restritivas de memória (comportamento padrão do SO). + +--- + +## 2. Tabela Comparativa de Performance (Antes vs Depois) + +| Métrica Avaliada | Baseline Inicial (Antes) | Após Otimizações (Depois) | Variação (%) | Status | +| :--- | :--- | :--- | :--- | :--- | +| **Tempo de Inicialização CCSP** | 1.304 ms | *Em medição* | - | Em andamento | +| **Consumo de Memória (RSS Idle)** | 101,60 MB | *Em medição* | - | Em andamento | +| **Enumeração de Certificados A1/A3** | 23,26 ms | *Em medição* | - | Em andamento | +| **Assinatura PAdES — PDF 100 KB** | 120,06 ms | *Em medição* | - | Em andamento | +| **Assinatura PAdES — PDF 5 MB** | 438,19 ms | *Em medição* | - | Em andamento | +| **Assinatura PAdES — PDF 50 MB** | 1.796,38 ms | *Em medição* | - | Em andamento | + +--- + +## 3. Detalhamento do Baseline Inicial + +### 3.1 Inicialização e Memória em Repouso +- Execução 1: Startup = 1508 ms | RSS = 101.21 MB +- Execução 2: Startup = 1306 ms | RSS = 101.69 MB +- Execução 3: Startup = 1100 ms | RSS = 101.89 MB +- **Média de Startup:** **1.304 ms** +- **Média de RSS em Repouso:** **101,60 MB** + +### 3.2 Latência de Operações Criptográficas +- **Enumeração de Certificados:** **23,26 ms** +- **Assinatura PAdES (PDF 100 KB):** **120,06 ms** (arquivo assinado final: 141 KB) +- **Assinatura PAdES (PDF 5 MB):** **438,19 ms** (arquivo assinado final: 5,04 MB) +- **Assinatura PAdES (PDF 50 MB):** **1.796,38 ms** (arquivo assinado final: 50,04 MB) + +--- + +## 4. Roteiro de Otimizações + +1. **Desacoplamento da UI:** Inicialização imediata do CCSP sem carregar o toolkit JavaFX no bootstrap principal. +2. **Streaming e RandomAccessRead:** Eliminação de buffers em heap para arquivos grandes via PDFBox 3. +3. **Detecção Adaptativa e Cache Sob Demanda:** Polling inteligente no `SmartCardWatcher` e cache com TTL no `CertificateManager`. +4. **Virtual Threads:** I/O não bloqueante no servidor local CCSP. diff --git a/scripts/bench/BenchmarkRunner.java b/scripts/bench/BenchmarkRunner.java new file mode 100644 index 0000000..f2f53d9 --- /dev/null +++ b/scripts/bench/BenchmarkRunner.java @@ -0,0 +1,200 @@ +package scripts.bench; + +import br.com.cybercore.signer.core.domain.ManagedCertificate; +import br.com.cybercore.signer.core.pdf.PdfSealPosition; +import br.com.cybercore.signer.core.pdf.PdfSignatureOptions; +import br.com.cybercore.signer.core.pdf.PdfSigningService; +import br.com.cybercore.signer.core.provider.CertificateManager; +import br.com.cybercore.signer.core.provider.Pkcs12CertificateProvider; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.PDPageContentStream; +import org.apache.pdfbox.pdmodel.font.PDType1Font; +import org.apache.pdfbox.pdmodel.font.Standard14Fonts; +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.asn1.x509.BasicConstraints; +import org.bouncycastle.asn1.x509.Extension; +import org.bouncycastle.asn1.x509.KeyUsage; +import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; +import org.bouncycastle.cert.X509CertificateHolder; +import org.bouncycastle.cert.X509v3CertificateBuilder; +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; +import org.bouncycastle.operator.ContentSigner; +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.math.BigInteger; +import java.nio.file.Files; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.KeyStore; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Date; +import java.util.List; + +/** + * Utilitário de benchmark para medição de latência de certificados e assinatura PAdES (100KB, 5MB e 50MB). + */ +public class BenchmarkRunner { + + private static final String TARGET_DIR = "target/bench-data"; + + public static void main(String[] args) throws Exception { + System.out.println("==============================================================="); + System.out.println(" CyberCore Signer — Benchmark de Desempenho (Certificados & PAdES)"); + System.out.println("==============================================================="); + + File dir = new File(TARGET_DIR); + if (!dir.exists()) { + dir.mkdirs(); + } + + // 1. Prepara Certificado A1 de Teste + System.out.print("[1/3] Inicializando provedor e certificado A1 de teste... "); + long tCertGenStart = System.currentTimeMillis(); + CertificateManager certManager = new CertificateManager(); + Pkcs12CertificateProvider a1Provider = new Pkcs12CertificateProvider(); + + KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA"); + kpg.initialize(2048); + KeyPair keyPair = kpg.generateKeyPair(); + + X500Name name = new X500Name("CN=BENCHMARK TESTE:12345678900, O=ICP-Brasil Teste, C=BR"); + Date notBefore = Date.from(Instant.now().minus(1, ChronoUnit.DAYS)); + Date notAfter = Date.from(Instant.now().plus(30, ChronoUnit.DAYS)); + X509v3CertificateBuilder builder = new X509v3CertificateBuilder( + name, BigInteger.ONE, notBefore, notAfter, name, + SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded()) + ); + builder.addExtension(Extension.basicConstraints, true, new BasicConstraints(false)); + builder.addExtension(Extension.keyUsage, true, new KeyUsage(KeyUsage.digitalSignature | KeyUsage.nonRepudiation)); + ContentSigner signer = new JcaContentSignerBuilder("SHA256withRSA").build(keyPair.getPrivate()); + X509CertificateHolder holder = builder.build(signer); + X509Certificate cert = new JcaX509CertificateConverter().getCertificate(holder); + + KeyStore ks = KeyStore.getInstance("PKCS12"); + ks.load(null, null); + ks.setKeyEntry("bench-alias", keyPair.getPrivate(), "1234".toCharArray(), new Certificate[]{cert}); + ByteArrayOutputStream baosKs = new ByteArrayOutputStream(); + ks.store(baosKs, "1234".toCharArray()); + a1Provider.loadFromBytes(baosKs.toByteArray(), "1234".toCharArray()); + certManager.registerProvider(a1Provider); + System.out.println("OK (" + (System.currentTimeMillis() - tCertGenStart) + " ms)"); + + // 2. Mede Tempo de Enumeração de Certificados + System.out.print("[2/3] Medindo tempo de enumeração de certificados (discoverCertificates)... "); + long tDiscStart = System.nanoTime(); + List certs = certManager.discoverCertificates(); + long tDiscEnd = System.nanoTime(); + double discMs = (tDiscEnd - tDiscStart) / 1_000_000.0; + System.out.printf("OK (%.2f ms | %d certificado(s) listado(s))\n", discMs, certs.size()); + + String certId = certs.get(0).id(); + + // 3. Prepara arquivos PDF de 100 KB, 5 MB e 50 MB + System.out.println("[3/3] Preparando e assinando documentos PDF com PAdES e selo visual..."); + File pdf100Kb = preparePdf(new File(dir, "doc_100kb.pdf"), 100 * 1024); + File pdf5Mb = preparePdf(new File(dir, "doc_5mb.pdf"), 5 * 1024 * 1024); + File pdf50Mb = preparePdf(new File(dir, "doc_50mb.pdf"), 50 * 1024 * 1024); + + PdfSigningService pdfService = new PdfSigningService(certManager); + PdfSignatureOptions options = PdfSignatureOptions.builder(certId) + .position(PdfSealPosition.BOTTOM_RIGHT) + .visibleSeal(true) + .reason("Homologacao de Performance") + .location("Brasilia, DF") + .pinOrPassword("1234".toCharArray()) + .build(); + + // Warmup JIT (PDF pequeno) + pdfService.signPdf(Files.readAllBytes(pdf100Kb.toPath()), options); + + // Medição 100 KB + long t100Start = System.nanoTime(); + byte[] signed100 = pdfService.signPdf(Files.readAllBytes(pdf100Kb.toPath()), options); + long t100End = System.nanoTime(); + double ms100 = (t100End - t100Start) / 1_000_000.0; + + // Medição 5 MB + long t5mStart = System.nanoTime(); + byte[] signed5m = pdfService.signPdf(Files.readAllBytes(pdf5Mb.toPath()), options); + long t5mEnd = System.nanoTime(); + double ms5m = (t5mEnd - t5mStart) / 1_000_000.0; + + // Medição 50 MB + long t50mStart = System.nanoTime(); + byte[] signed50m = pdfService.signPdf(Files.readAllBytes(pdf50Mb.toPath()), options); + long t50mEnd = System.nanoTime(); + double ms50m = (t50mEnd - t50mStart) / 1_000_000.0; + + System.out.println(); + System.out.println("==================== RESULTADOS DE LATÊNCIA PAdES ===================="); + System.out.printf(" - Enumeração de Certificados: %.2f ms\n", discMs); + System.out.printf(" - Assinatura PAdES 100 KB: %.2f ms (saída: %d KB)\n", ms100, signed100.length / 1024); + System.out.printf(" - Assinatura PAdES 5 MB: %.2f ms (saída: %.2f MB)\n", ms5m, signed5m.length / (1024.0 * 1024.0)); + System.out.printf(" - Assinatura PAdES 50 MB: %.2f ms (saída: %.2f MB)\n", ms50m, signed50m.length / (1024.0 * 1024.0)); + System.out.println("======================================================================="); + } + + private static File preparePdf(File file, int targetSizeBytes) throws Exception { + if (file.exists() && file.length() >= targetSizeBytes * 0.95) { + return file; + } + System.out.printf(" -> Gerando %s (~%d KB)... ", file.getName(), targetSizeBytes / 1024); + try (PDDocument doc = new PDDocument()) { + PDPage page = new PDPage(); + doc.addPage(page); + + try (PDPageContentStream cs = new PDPageContentStream(doc, page)) { + cs.beginText(); + cs.setFont(new PDType1Font(Standard14Fonts.FontName.HELVETICA_BOLD), 14); + cs.newLineAtOffset(50, 750); + cs.showText("CyberCore Signer — Documento de Benchmark de Performance"); + cs.endText(); + } + + // Para arquivos de 5MB e 50MB, adiciona páginas e streams para atingir a meta de tamanho + int approxPageSize = 2500; + int pagesNeeded = Math.max(1, targetSizeBytes / approxPageSize); + for (int p = 1; p < pagesNeeded && p < 1200; p++) { + PDPage extraPage = new PDPage(); + doc.addPage(extraPage); + try (PDPageContentStream cs = new PDPageContentStream(doc, extraPage)) { + cs.beginText(); + cs.setFont(new PDType1Font(Standard14Fonts.FontName.HELVETICA), 10); + cs.newLineAtOffset(50, 700); + cs.showText("Pagina de teste de volume " + p + " com metadados para teste PAdES."); + cs.endText(); + } + } + + // Grava documento + try (FileOutputStream fos = new FileOutputStream(file)) { + doc.save(fos); + } + } + + // Se ainda não atingiu o tamanho exato desejado (especialmente para 50MB), preenche comentários PDF (% padding) + long currentLen = file.length(); + if (currentLen < targetSizeBytes) { + try (FileOutputStream fos = new FileOutputStream(file, true)) { + byte[] chunk = new byte[64 * 1024]; + java.util.Arrays.fill(chunk, (byte) 'A'); + chunk[0] = '%'; + chunk[chunk.length - 1] = '\n'; + while (file.length() < targetSizeBytes) { + long remaining = targetSizeBytes - file.length(); + int toWrite = (int) Math.min(chunk.length, remaining); + fos.write(chunk, 0, toWrite); + } + } + } + System.out.printf("OK (%d KB)\n", file.length() / 1024); + return file; + } +} diff --git a/scripts/bench/bench_startup_rss.sh b/scripts/bench/bench_startup_rss.sh new file mode 100755 index 0000000..cbb989c --- /dev/null +++ b/scripts/bench/bench_startup_rss.sh @@ -0,0 +1,60 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Script de medição de tempo de inicialização e memória RSS em idle do CyberCore Signer +PORT=21325 +JAR_FILE="modules/cybercore-app/target/cybercore-signer.jar" +JAVA_BIN="${JAVA_HOME:-/home/claudecio/.jdks/jdk-25.0.4.1+1}/bin/java" + +if [ ! -f "$JAR_FILE" ]; then + echo "Erro: JAR $JAR_FILE não encontrado. Execute ./mvnw package primeiro." + exit 1 +fi + +# Garante que a porta esteja livre antes de iniciar +fuser -k "${PORT}/tcp" 2>/dev/null || true +sleep 1 + +# Timestamp inicial em milissegundos +START_TIME=$(date +%s%3N) + +# Inicia o agente em modo headless +"$JAVA_BIN" -jar "$JAR_FILE" --headless --port "$PORT" > /dev/null 2>&1 & +APP_PID=$! + +# Aguarda até que o endpoint CCSP /status responda HTTP 200 +MAX_WAIT_SECONDS=15 +READY=0 +while true; do + HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "http://127.0.0.1:${PORT}/ccsp/v1/status" 2>/dev/null || echo "000") + if [ "$HTTP_CODE" = "200" ]; then + READY=1 + break + fi + CURRENT_TIME=$(date +%s%3N) + ELAPSED=$((CURRENT_TIME - START_TIME)) + if [ "$ELAPSED" -gt $((MAX_WAIT_SECONDS * 1000)) ]; then + echo "Timeout aguardando inicialização do servidor CCSP." + kill -9 "$APP_PID" 2>/dev/null || true + exit 1 + fi + sleep 0.05 +done + +END_TIME=$(date +%s%3N) +STARTUP_MS=$((END_TIME - START_TIME)) + +# Aguarda 2 segundos para estabilização de memória em repouso (idle) +sleep 2 + +# Mede RSS via ps (em KB) +RSS_KB=$(ps -o rss= -p "$APP_PID" | tr -d ' ' || echo "0") +RSS_MB=$(awk "BEGIN {printf \"%.2f\", $RSS_KB / 1024}") + +# Encerra o processo limpo +kill "$APP_PID" 2>/dev/null || true +wait "$APP_PID" 2>/dev/null || true + +echo "STARTUP_MS=${STARTUP_MS}" +echo "RSS_KB=${RSS_KB}" +echo "RSS_MB=${RSS_MB}" diff --git a/scripts/bench/run_all_benchmarks.sh b/scripts/bench/run_all_benchmarks.sh new file mode 100755 index 0000000..5f71030 --- /dev/null +++ b/scripts/bench/run_all_benchmarks.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Script mestre de execução e consolidação de benchmarks de performance do CyberCore Signer +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +cd "$ROOT_DIR" + +JAVA_BIN="${JAVA_HOME:-/home/claudecio/.jdks/jdk-25.0.4.1+1}/bin/java" +JAVAC_BIN="${JAVA_HOME:-/home/claudecio/.jdks/jdk-25.0.4.1+1}/bin/javac" +JAR_FILE="modules/cybercore-app/target/cybercore-signer.jar" + +echo "=======================================================================" +echo " CYBERCORE SIGNER — SUÍTE DE MEDIÇÃO DE PERFORMANCE" +echo "=======================================================================" +echo "Ambiente: $(uname -s) $(uname -m)" +echo "Java: $("$JAVA_BIN" -version 2>&1 | head -n 1)" +echo + +# 1. Medições de Tempo de Inicialização e RSS (Média de 3 execuções) +echo "-----------------------------------------------------------------------" +echo "[1/2] Medindo Tempo de Inicialização do Servidor CCSP e Memória RSS..." +echo "-----------------------------------------------------------------------" + +STARTUP_SUM=0 +RSS_SUM=0 +RUNS=3 + +for i in $(seq 1 $RUNS); do + echo -n " -> Execução $i de $RUNS: " + OUT=$(./scripts/bench/bench_startup_rss.sh) + S_MS=$(echo "$OUT" | grep "STARTUP_MS=" | cut -d'=' -f2) + R_MB=$(echo "$OUT" | grep "RSS_MB=" | cut -d'=' -f2) + echo "Startup = ${S_MS} ms | RSS = ${R_MB} MB" + STARTUP_SUM=$((STARTUP_SUM + S_MS)) + RSS_SUM=$(awk "BEGIN {print $RSS_SUM + $R_MB}") + sleep 1 +done + +AVG_STARTUP=$((STARTUP_SUM / RUNS)) +AVG_RSS=$(awk "BEGIN {printf \"%.2f\", $RSS_SUM / $RUNS}") + +echo +echo "=> Média de Inicialização CCSP: ${AVG_STARTUP} ms" +echo "=> Média de Memória (RSS) em Idle: ${AVG_RSS} MB" +echo + +# 2. Compilação e Execução do BenchmarkRunner (Certificados e PAdES) +echo "-----------------------------------------------------------------------" +echo "[2/2] Medindo Enumeração de Certificados e Latência PAdES..." +echo "-----------------------------------------------------------------------" + +mkdir -p target/bench-classes +"$JAVAC_BIN" -cp "$JAR_FILE" -d target/bench-classes scripts/bench/BenchmarkRunner.java +"$JAVA_BIN" -cp "target/bench-classes:$JAR_FILE" scripts.bench.BenchmarkRunner