[MEJORA] Salida visual mejorada para pipelines DASUTEN

Se implementó mejora en la salida de comandos para hacerla más intuitiva:

Cambios realizados:
- bkps.rb: Encabezado y cierre visual con box drawing para tareas DASUTEN
- ejecutor.rb: Barra de progreso con timer en tiempo real durante ejecución
- A04.P006: Actualizado con resultados del test completo 2026-04-15

Nueva salida visual:
- Encabezado con contexto del pipeline (nombre, hora de inicio)
- Timer en vivo durante la ejecución ([1m 23s] ejecutando...)
- Cierre con estado (/) y duración total

Resultados del test completo (Pipeline FULL):
- 6 pasos completados exitosamente en 13m 15s
- DBCC CHECKDB: Integridad OK
- Todos los drones atómicos funcionaron correctamente

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Ricardo Monla
2026-04-15 11:18:48 -03:00
co-authored by Claude Opus 4.6
parent 4a0cdd883e
commit db86f86e59
3 changed files with 135 additions and 3 deletions
+27 -2
View File
@@ -750,7 +750,21 @@ module ADN
end end
t_inicio = Time.now t_inicio = Time.now
@logger.info("#{tarea[:texto]} (#{tipo})")
# Encabezado visual para tareas DASUTEN
es_dasuten = (tipo == 'dasuten')
if es_dasuten
puts ""
puts "╔═══════════════════════════════════════════════════════════╗"
puts "║ 🔄 Pipeline DASUTEN — #{tarea[:texto].ljust(35)}"
puts "╠═══════════════════════════════════════════════════════════╣"
puts "║ Inicio: #{Time.now.strftime('%H:%M:%S')}".ljust(55) + ""
puts "╚═══════════════════════════════════════════════════════════╝"
puts ""
else
@logger.info("#{tarea[:texto]} (#{tipo})")
end
procesador = factory.call(@logger) procesador = factory.call(@logger)
# Modo atómico: procesar un solo archivo # Modo atómico: procesar un solo archivo
@@ -764,7 +778,18 @@ module ADN
duracion = (Time.now - t_inicio).to_i duracion = (Time.now - t_inicio).to_i
mins = duracion / 60 mins = duracion / 60
segs = duracion % 60 segs = duracion % 60
@logger.info("#{tarea[:texto]}#{mins}m#{segs}s")
if es_dasuten
estado = resultado ? "✅ Completado" : "❌ Fallido"
puts ""
puts "╔═══════════════════════════════════════════════════════════╗"
puts "#{estado} — Duración: #{mins}m #{segs}s".ljust(55) + ""
puts "╚═══════════════════════════════════════════════════════════╝"
puts ""
else
@logger.info("#{tarea[:texto]}#{mins}m#{segs}s")
end
resultado resultado
end end
+26 -1
View File
@@ -12,6 +12,7 @@
require_relative 'base' require_relative 'base'
require_relative '../../db/core/dron_db' require_relative '../../db/core/dron_db'
require_relative '../../core/colores'
require 'open3' require 'open3'
require 'timeout' require 'timeout'
require 'time' require 'time'
@@ -81,19 +82,36 @@ module Dron
last_out = "" last_out = ""
accumulated_out = [] # Acumular todas las líneas de output para el heartbeat accumulated_out = [] # Acumular todas las líneas de output para el heartbeat
exit_code = nil exit_code = nil
progreso_visible = !ENV['BATCH_MODE'] # Solo mostrar progreso si no es batch
# Mostrar encabezado de progreso
if progreso_visible
puts "\n#{Color::BOLD}#{Color::CYAN}⏳ Ejecutando...#{Color::RESET}"
print " ["
$stdout.flush
end
heartbeat_thread = Thread.new do heartbeat_thread = Thread.new do
tick = 0 tick = 0
start = Time.now
loop do loop do
sleep(HEARTBEAT_INTERVAL) sleep(HEARTBEAT_INTERVAL)
tick += 1 tick += 1
elapsed = (Time.now - start).to_i
# Actualizar tabla interna (dron_logs.heartbeat) # Actualizar tabla interna (dron_logs.heartbeat)
ADN::DB::DronDB.actualizar_heartbeat(dron_id) ADN::DB::DronDB.actualizar_heartbeat(dron_id)
# Actualizar Bitácora Web (events.descripcion) — VISIBLE para el usuario # Actualizar Bitácora Web (events.descripcion) — VISIBLE para el usuario
# Pasamos el output acumulado para mostrar todo el progreso
actualizar_heartbeat_web(evento_id, dron_id, nota, cmd, inicio, tick, accumulated_out.join("\n")) if evento_id actualizar_heartbeat_web(evento_id, dron_id, nota, cmd, inicio, tick, accumulated_out.join("\n")) if evento_id
# Actualizar barra de progreso local
if progreso_visible
mins = elapsed / 60
segs = elapsed % 60
print "\r [#{Color::CYAN}#{mins}m #{segs}s#{Color::RESET}] ejecutando..."
$stdout.flush
end
rescue StandardError => e rescue StandardError => e
Base.logger.warn("⚠️ Error en heartbeat ##{tick}: #{e.message}") Base.logger.warn("⚠️ Error en heartbeat ##{tick}: #{e.message}")
end end
@@ -129,6 +147,13 @@ module Dron
heartbeat_thread.exit heartbeat_thread.exit
end end
# Finalizar barra de progreso
if progreso_visible
estado = exit_code == 0 ? "#{Color::GREEN}#{Color::RESET}" : "#{Color::RED}#{Color::RESET}"
print "\r #{estado} "
puts "#{Color::BOLD}Completado en #{Base.formato_duracion(Time.now - start_time)}#{Color::RESET}"
end
raw_output = output.join("\n").strip raw_output = output.join("\n").strip
safe_output = raw_output.dup.force_encoding('UTF-8').scrub('?') safe_output = raw_output.dup.force_encoding('UTF-8').scrub('?')
{ exit_code: exit_code || 1, output: safe_output, duration: (Time.now - start_time).round(2) } { exit_code: exit_code || 1, output: safe_output, duration: (Time.now - start_time).round(2) }
@@ -18,6 +18,15 @@
| **Fase 3: Pipeline Diferencial** | ✅ Completado | 100% | | **Fase 3: Pipeline Diferencial** | ✅ Completado | 100% |
| **Fase 4: Integración BKPs** | ✅ Completado | 100% | | **Fase 4: Integración BKPs** | ✅ Completado | 100% |
| **Fase 5: Mejora de Latidos** | ✅ Completado | 100% | | **Fase 5: Mejora de Latidos** | ✅ Completado | 100% |
| **Fase 6: Validación Pipeline FULL** | ✅ Completado | 100% |
### Resumen de Validación (2026-04-15)
**Pipeline COMPLETO validado de principio a fin**
- Backup FULL exportado, transferido, subido, bajado, restaurado y verificado
- Integridad de BD confirmada con DBCC CHECKDB: **OK**
- Duración total: **13m 15s** (dentro de lo estimado: ~35-40 min)
- Todos los drones atómicos funcionaron correctamente en modo orquestado
## 🎯 Objetivo ## 🎯 Objetivo
@@ -181,6 +190,79 @@ El procesador `Dasuten` fue integrado en `bkps.rb` como un nuevo tipo de tarea:
| 2026-04-14 15:54 | Completo | 44.8s | 1.33 GB | ✅ | | 2026-04-14 15:54 | Completo | 44.8s | 1.33 GB | ✅ |
| 2026-04-14 16:03 | Diferencial | 2.44s | 0.39 MB | ✅ | | 2026-04-14 16:03 | Diferencial | 2.44s | 0.39 MB | ✅ |
| 2026-04-14 16:35 | Upload Drive | 23.79s | 0.39 MB | ✅ | | 2026-04-14 16:35 | Upload Drive | 23.79s | 0.39 MB | ✅ |
| **2026-04-15 11:01** | **Pipeline FULL** | **13m 15s** | **~1.3 GB** | **✅ COMPLETO** |
### Detalle Pipeline 2026-04-15 (Test Completo)
| Paso | Tarea | Nodo | Duración | Estado |
|------|-------|------|----------|--------|
| 1 | `dasuten_export_full` | srvv-fenix | 46s | ✅ |
| 2 | `dasuten_transferir` | srv-ns8 | 2s | ✅ |
| 3 | `dasuten_upload` | srv-ns8 | 7m 9s | ✅ |
| 4 | `dasuten_download` | dasu-sql4 | 52s | ✅ |
| 5 | `dasuten_restaurar_full` | dasu-sql4 | 13s | ✅ |
| 6 | `dasuten_verificar` | dasu-sql4 | 4m 11s | ✅ |
**Throughput:**
- Upload a Drive: ~3 MB/s (limitado por ancho de banda de subida)
- Download desde Drive: ~25 MB/s (HTTP directo)
- Restore SQL: instantáneo (MOVE de archivos)
- DBCC CHECKDB: ~1.2M páginas verificadas en 4m 11s
## 🧪 Sesión de Test Completa (2026-04-15)
**Objetivo:** Validar el pipeline completo de backup/restore usando **backup FULL** (no diferencial).
### Ejecución Orquestada (REINICIADA)
Se utiliza el comando **orquestado** `dasuten_full` que ejecuta los 6 pasos en secuencia automáticamente.
| Paso | ID Tarea | Dron | Nodo | Estado | Duración |
|------|----------|------|------|--------|----------|
| **1** | `dasuten_export_full` | `dasuten_exportar_srvv-fenix.rb` | srvv-fenix | ⏳ Pendiente | — |
| **2** | `dasuten_transferir` | `dasuten_transferir_srvv-fenix-srv-ns8.rb` | srv-ns8 | ⏳ Pendiente | — |
| **3** | `dasuten_upload` | `dasuten_upload_srv-ns8-drive.rb` | srv-ns8 | ⏳ Pendiente | — |
| **4** | `dasuten_download` | `dasuten_download_drive-dasu-sql4.rb` | dasu-sql4 | ⏳ Pendiente | — |
| **5** | `dasuten_restaurar_full` | `dasuten_restaurar_dasu-sql4.rb` | dasu-sql4 | ⏳ Pendiente | — |
| **6** | `dasuten_verificar` | `dasuten_verificar-integridad_dasu-sql4.rb` | dasu-sql4 | ⏳ Pendiente | — |
**Comando orquestado:**
```bash
# Ejecución completa (6 pasos en secuencia)
./adn/tools/run bkps run dasuten_full --batch
```
**Notas de la sesión:**
- Tipo de backup: **FULL/COMPLETO** (~1.3 GB esperado)
- Ejecución: **Orquestada** (un solo comando, 6 pasos automáticos)
- Heartbeats acumulados: Se prueba la nueva mejora de latidos que muestra las últimas 15 líneas del progreso en Bitácora Web
---
### Registro de Ejecución — Test 2026-04-15
| Hora | Paso | Resultado | Duración | Observaciones |
|------|------|-----------|----------|---------------|
| 11:01 | 🚩 **TEST INICIADO** | — | — | Pipeline FULL orquestado (`dasuten_full --batch`) |
| 11:01 | 1. Exportar | ✅ Completado | 46s | Backup FULL en `X:\` (srvv-fenix) |
| 11:02 | 2. Transferir | ✅ Completado | 2s | SMB: fenix → ns8 (`/var/tmp/`) |
| 11:02 | 3. Upload | ✅ Completado | 7m 9s | rclone: ns8 → Google Drive (~1.3 GB) |
| 11:09 | 4. Download | ✅ Completado | 52s | HTTP: Drive → dasu-sql4 (`F:\BACKUP\`) |
| 11:10 | 5. Restore | ✅ Completado | 13s | RESTORE DATABASE WITH REPLACE + MOVE |
| 11:10 | 6. Verificar | ✅ Completado | 4m 11s | DBCC CHECKDB — Integridad: **OK** |
| 11:14 | 🏁 **PIPELINE COMPLETADO** | ✅ | **13m 15s** | Todos los pasos exitosos |
**Resultados clave:**
- ✅ Backup FULL de ~1.3 GB transferido correctamente
- ✅ Google Drive como intermediario (sin dependencia de Tailscale)
- ✅ Restore completado con MOVE a `F:\DATA` y `F:\LOG`
- ✅ DBCC CHECKDB: **Integridad OK** (base de datos consistente)
---
**Ejecuciones anteriores (descartadas):**
- Paso 4 (Restore diferencial): ✅ 14.7s — ejecutado de forma aislada, no corresponde a esta sesión FULL
## ⚠️ Consideraciones Técnicas ## ⚠️ Consideraciones Técnicas