att base de dados

This commit is contained in:
JotaChina committed 2026-03-28 12:50:52 -03:00
1 parent ceb7908da4
commit 73693e5da9
14 files changed
+3010 -1

No files matched your search

+339
View File
@@ -0,0 +1,339 @@
# benchmark.py
import os
import shutil
import subprocess
import multiprocessing
import logging
import time
import platform
import sys
import re
import unicodedata
import csv
import psutil
from datetime import datetime, timedelta
# Importando apenas o necessário (sem sobrescrever as pastas do utils nativo no processamento global)
from file_parser import parse_mmp_file
from file_saver import save_to_json, save_to_yaml
from utils import SRC_MMPSEARCH, MMP_FOLDER
# === ISOLAMENTO DE DIRETÓRIOS PARA O BENCHMARK ===
# Tudo será gerado dentro de "benchmark_output" para não afetar o Jekyll
BENCHMARK_OUT = os.path.join(SRC_MMPSEARCH, "benchmark_output")
DATA_FOLDER = os.path.join(BENCHMARK_OUT, "data")
METADATA_FOLDER = os.path.join(BENCHMARK_OUT, "metadata")
WAV_FOLDER = os.path.join(BENCHMARK_OUT, "wav")
MMPZ_FOLDER = os.path.join(BENCHMARK_OUT, "mmpz")
LOG_FOLDER = os.path.join(BENCHMARK_OUT, "logs")
SAIDA_ANALISES = os.path.join(BENCHMARK_OUT, "analises")
LOTES_FOLDER = os.path.join(BENCHMARK_OUT, "lotes_yaml")
# === CONFIGURAÇÕES GERAIS ===
TIMEOUT_RENDER_SECONDS = 300
MIN_RAM_FREE_MB = 500
global_counter = None
def slugify(value):
value = str(value)
value = (
unicodedata.normalize("NFKD", value).encode("ascii", "ignore").decode("ascii")
)
value = value.lower()
value = re.sub(r"[^\w\s-]", "", value)
value = re.sub(r"[-\s_]+", "-", value)
return value.strip("-_")
def get_cpu_safe_count():
try:
count = multiprocessing.cpu_count()
return max(1, count - 1)
except:
return 1
def init_worker(counter):
global global_counter
global_counter = counter
# === FUNÇÃO ORIGINAL DO SEU CÓDIGO (Com leve ajuste de segurança de arquivos) ===
def process_single_file(args):
file_name, clean_slug, total_files = args
file_path = os.path.join(MMP_FOLDER, file_name)
pid = os.getpid()
global global_counter
current_idx = 0
if global_counter:
with global_counter.get_lock():
global_counter.value += 1
current_idx = global_counter.value
start_time = time.time()
result = {
"success": False,
"file": file_name,
"slug": clean_slug,
"data": None,
"error": None,
"duration_s": 0.0,
"ram_mb": 0.0,
"file_size_mb": 0.0,
}
try:
if os.path.exists(file_path):
result["file_size_mb"] = os.path.getsize(file_path) / (1024 * 1024)
mem = psutil.virtual_memory()
if (mem.available / (1024 * 1024)) < MIN_RAM_FREE_MB:
result["error"] = "RAM Insuficiente"
return result
original_base_name = os.path.splitext(file_name)[0]
ogg_name = clean_slug + ".ogg"
json_name = clean_slug + ".json"
yml_name = clean_slug + ".yml"
target_mmp_path = ""
if file_name.endswith(".mmpz"):
destination_path = os.path.join(MMPZ_FOLDER, file_name)
# ALERTA DE BENCHMARK: Trocado move por copy2 para não destruir o dataset de testes!
if not os.path.exists(destination_path):
shutil.copy2(file_path, destination_path)
mmp_temp_name = clean_slug + ".mmp"
output_mmp_path = os.path.join(MMP_FOLDER, mmp_temp_name)
abs_dest = os.path.abspath(destination_path)
abs_mmp_out = os.path.abspath(output_mmp_path)
with open(abs_mmp_out, "w") as outfile:
subprocess.run(
["lmms", "--dump", abs_dest],
stdout=outfile,
stderr=subprocess.PIPE,
check=True,
timeout=60,
env={"QT_QPA_PLATFORM": "offscreen", **os.environ},
)
target_mmp_path = output_mmp_path
elif file_name.endswith(".mmp"):
target_mmp_path = file_path
# Renderização do Áudio
if os.path.exists(target_mmp_path):
abs_target_mmp = os.path.abspath(target_mmp_path)
abs_ogg_out = os.path.abspath(os.path.join(WAV_FOLDER, ogg_name))
# Aqui estava o errinho do "wav_cmd" que arrumamos na revisão!
ogg_cmd = ["lmms", "-r", abs_target_mmp, "-o", abs_ogg_out, "-f", "ogg"]
render_process = subprocess.run(
ogg_cmd,
check=False,
capture_output=True,
text=True,
timeout=TIMEOUT_RENDER_SECONDS,
env={"QT_QPA_PLATFORM": "offscreen", **os.environ},
)
if render_process.returncode != 0:
raise subprocess.CalledProcessError(
render_process.returncode, "lmms render", stderr="Erro LMMS"
)
# Parsing do XML
if os.path.exists(target_mmp_path):
mmp_data = parse_mmp_file(target_mmp_path)
if mmp_data:
mmp_data["file"] = clean_slug
mmp_data["original_title"] = original_base_name
save_to_json(mmp_data, os.path.join(METADATA_FOLDER, json_name))
save_to_yaml(mmp_data, os.path.join(DATA_FOLDER, yml_name))
result["success"] = True
result["data"] = mmp_data
try:
process = psutil.Process(pid)
# rss = Resident Set Size (RAM real usada pelo processo)
result["ram_mb"] = process.memory_info().rss / (1024 * 1024)
except:
result["ram_mb"] = 0.0
except Exception as e:
result["error"] = str(e)
result["duration_s"] = time.time() - start_time
return result
# === MOTOR DE BENCHMARK ===
def execute_batch(limit_files, mode):
"""
Executa um lote de arquivos.
Se mode == 'sequencial', usa apenas 1 núcleo.
Se mode == 'paralelo', usa o safe count de núcleos.
"""
start_time_global = time.time()
# Prepara pastas limpas para o teste
for folder in [
MMPZ_FOLDER,
WAV_FOLDER,
METADATA_FOLDER,
DATA_FOLDER,
LOTES_FOLDER,
SAIDA_ANALISES,
]:
os.makedirs(folder, exist_ok=True)
all_files_raw = [f for f in os.listdir(MMP_FOLDER) if f.endswith((".mmp", ".mmpz"))]
if limit_files > 0:
all_files_raw = all_files_raw[:limit_files]
total_files = len(all_files_raw)
if total_files == 0:
return 0, 0, 0, 0, 0
shared_counter = multiprocessing.Value("i", 0)
tasks = []
for idx, file_name in enumerate(all_files_raw):
slug = slugify(os.path.splitext(file_name)[0])
if not slug:
slug = f"proj-{idx}"
tasks.append((file_name, slug, total_files))
num_cores = 1 if mode == "sequencial" else get_cpu_safe_count()
print(
f"\n[{mode.upper()}] Processando {total_files} arquivos com {num_cores} núcleo(s)..."
)
# Execução
with multiprocessing.Pool(
processes=num_cores, initializer=init_worker, initargs=(shared_counter,)
) as pool:
results = pool.map(process_single_file, tasks)
# --- CÁLCULO DE MÉDIAS E SUCESSOS ---
sucessos = [r for r in results if r["success"]]
qtd_sucessos = len(sucessos)
avg_time = (
sum(r["duration_s"] for r in sucessos) / qtd_sucessos if qtd_sucessos > 0 else 0
)
avg_size = (
sum(r["file_size_mb"] for r in sucessos) / qtd_sucessos
if qtd_sucessos > 0
else 0
)
avg_ram = (
sum(r["ram_mb"] for r in sucessos) / qtd_sucessos if qtd_sucessos > 0 else 0
)
duration_total = time.time() - start_time_global
# --- SALVAR RELATÓRIO DETALHADO POR LOTE ---
detalhado_csv = os.path.join(
BENCHMARK_OUT, f"auditoria_detalhada_{mode}_{limit_files}.csv"
)
with open(detalhado_csv, "w", newline="", encoding="utf-8") as f:
writer = csv.writer(f)
writer.writerow(
["Arquivo", "Tamanho_MB", "Tempo_s", "RAM_MB", "Status", "Erro"]
)
for r in results:
status_str = "SUCESSO" if r["success"] else "FALHA"
writer.writerow(
[
r["file"],
f"{r['file_size_mb']:.2f}",
f"{r['duration_s']:.2f}",
f"{r['ram_mb']:.2f}",
status_str,
r["error"] or "",
]
)
print(
f"[{mode.upper()}] Concluído em {duration_total:.2f}s (Sucessos: {qtd_sucessos}/{total_files})"
)
print(
f"[{mode.upper()}] Médias -> Tempo: {avg_time:.2f}s | Tamanho: {avg_size:.2f}MB | RAM: {avg_ram:.2f}MB"
)
return duration_total, qtd_sucessos, avg_time, avg_size, avg_ram
def run_benchmark():
print("==================================================")
print(" INICIANDO TESTE DE SPEEDUP (SEQUENCIAL VS PARALELO)")
print("==================================================")
test_sizes = [1, 10, 100, 1000]
modes = ["sequencial", "paralelo"]
results_table = []
for size in test_sizes:
print(f"\n>>> INICIANDO BATERIA PARA {size} ARQUIVO(S) <<<")
row = {"Tamanho": size}
for mode in modes:
duration, success_count, avg_time, avg_size, avg_ram = execute_batch(
size, mode
)
row[f"Tempo_Total_{mode}_(s)"] = round(duration, 2)
row[f"Tempo_Medio_Proj_{mode}_(s)"] = round(avg_time, 2)
row[f"Tamanho_Medio_Proj_(MB)"] = round(
avg_size, 2
) # O tamanho é igual para ambos, mas guardamos
row[f"RAM_Media_{mode}_(MB)"] = round(avg_ram, 2)
row[f"Sucesso_{mode}"] = success_count
# Calcula o Speedup (Tempo Total Sequencial / Tempo Total Paralelo)
speedup = (
row["Tempo_Total_sequencial_(s)"] / row["Tempo_Total_paralelo_(s)"]
if row["Tempo_Total_paralelo_(s)"] > 0
else 0
)
row["Speedup"] = round(speedup, 2)
results_table.append(row)
print(f"--- Fim da bateria de {size}. Speedup Alcançado: {speedup:.2f}x ---")
# Salva o resultado final num CSV consolidado
os.makedirs(BENCHMARK_OUT, exist_ok=True)
csv_file = os.path.join(BENCHMARK_OUT, "resultado_grafico_speedup.csv")
# Lista de colunas para o CSV final
colunas = [
"Tamanho",
"Speedup",
"Tempo_Total_sequencial_(s)",
"Tempo_Total_paralelo_(s)",
"Tempo_Medio_Proj_sequencial_(s)",
"Tempo_Medio_Proj_paralelo_(s)",
"RAM_Media_sequencial_(MB)",
"RAM_Media_paralelo_(MB)",
"Tamanho_Medio_Proj_(MB)",
"Sucesso_sequencial",
"Sucesso_paralelo",
]
with open(csv_file, "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=colunas)
writer.writeheader()
writer.writerows(results_table)
print("\n==================================================")
print(f"BENCHMARK FINALIZADO! Resultados salvos em:\n{BENCHMARK_OUT}")
print("==================================================")
if __name__ == "__main__":
run_benchmark()
+53
View File
@@ -0,0 +1,53 @@
nohup: ignoring input
==================================================
INICIANDO TESTE DE SPEEDUP (SEQUENCIAL VS PARALELO)
==================================================
>>> INICIANDO BATERIA PARA 1 ARQUIVO(S) <<<
[SEQUENCIAL] Processando 1 arquivos com 1 núcleo(s)...
[SEQUENCIAL] Concluído em 43.78s (Sucessos: 1/1)
[SEQUENCIAL] Médias -> Tempo: 43.69s | Tamanho: 0.54MB | RAM: 27.22MB
[PARALELO] Processando 1 arquivos com 11 núcleo(s)...
[PARALELO] Concluído em 46.51s (Sucessos: 1/1)
[PARALELO] Médias -> Tempo: 46.48s | Tamanho: 0.54MB | RAM: 27.58MB
--- Fim da bateria de 1. Speedup Alcançado: 0.94x ---
>>> INICIANDO BATERIA PARA 10 ARQUIVO(S) <<<
[SEQUENCIAL] Processando 10 arquivos com 1 núcleo(s)...
Process ForkPoolWorker-13:
Traceback (most recent call last):
File "/usr/lib/python3.12/multiprocessing/pool.py", line 131, in worker
put((job, i, result))
File "/usr/lib/python3.12/multiprocessing/queues.py", line 399, in put
self._writer.send_bytes(obj)
File "/usr/lib/python3.12/multiprocessing/connection.py", line 200, in send_bytes
self._send_bytes(m[offset:offset + size])
File "/usr/lib/python3.12/multiprocessing/connection.py", line 420, in _send_bytes
self._send(header)
File "/usr/lib/python3.12/multiprocessing/connection.py", line 384, in _send
n = write(self._handle, buf)
^^^^^^^^^^^^^^^^^^^^^^^^
BrokenPipeError: [Errno 32] Broken pipe
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/usr/lib/python3.12/multiprocessing/process.py", line 314, in _bootstrap
self.run()
File "/usr/lib/python3.12/multiprocessing/process.py", line 108, in run
self._target(*self._args, **self._kwargs)
File "/usr/lib/python3.12/multiprocessing/pool.py", line 136, in worker
put((job, i, (False, wrapped)))
File "/usr/lib/python3.12/multiprocessing/queues.py", line 399, in put
self._writer.send_bytes(obj)
File "/usr/lib/python3.12/multiprocessing/connection.py", line 200, in send_bytes
self._send_bytes(m[offset:offset + size])
File "/usr/lib/python3.12/multiprocessing/connection.py", line 420, in _send_bytes
self._send(header)
File "/usr/lib/python3.12/multiprocessing/connection.py", line 384, in _send
n = write(self._handle, buf)
^^^^^^^^^^^^^^^^^^^^^^^^
BrokenPipeError: [Errno 32] Broken pipe
+230
View File
@@ -0,0 +1,230 @@
import os
import time
import threading
import requests
import csv
import psutil
import re
import unicodedata
from datetime import datetime, timedelta
from bs4 import BeautifulSoup
from urllib.parse import urljoin, urlparse, parse_qs
from concurrent.futures import ThreadPoolExecutor, as_completed
from utils import (
SRC_MMPSEARCH,
MMP_FOLDER,
)
# ================= CONFIGURAÇÕES PADRONIZADAS =================
BASE_DIR = SRC_MMPSEARCH
PASTA_DESTINO = MMP_FOLDER # Pasta de entrada do pipeline
LOG_DIR = os.path.join(BASE_DIR, "logs/crawler")
ARQUIVO_AUDITORIA = os.path.join(LOG_DIR, "audit_crawler.csv")
# Segurança de Hardware
MIN_RAM_FREE_MB = 500 # Pausa se tiver menos que 500MB livre
MAX_WORKERS_SAFE = 4 # I/O Bound não precisa de tantos workers quanto CPU, mas 4 é seguro
HEADERS = {
'User-Agent': 'Mozilla/5.0 (Compatible; MMPResearchBot/1.0; +http://seu-site.com)'
}
# Cria pastas
for p in [PASTA_DESTINO, LOG_DIR]:
os.makedirs(p, exist_ok=True)
# ================= FUNÇÕES AUXILIARES =================
def slugify(value):
"""Sanitização robusta de nomes de arquivo (Padrão do Pipeline)"""
value = str(value)
value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore').decode('ascii')
value = value.lower()
value = re.sub(r'[^\w\s-]', '', value)
value = re.sub(r'[-\s_]+', '-', value)
return value.strip('-_')
def verificar_memoria_segura():
"""Retorna True se é seguro continuar"""
try:
mem = psutil.virtual_memory()
return (mem.available / (1024 * 1024)) > MIN_RAM_FREE_MB
except:
return True
# ================= CLASSE DE AUDITORIA =================
class AuditoriaCrawler:
def __init__(self, arquivo_csv):
self.arquivo_csv = arquivo_csv
self.inicio_global = time.time()
self.lock = threading.Lock()
# Cria CSV se não existir
if not os.path.exists(self.arquivo_csv):
with open(self.arquivo_csv, 'w', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
writer.writerow([
"Timestamp", "Nome_Original", "Slug_Final",
"Tamanho_MB", "Tempo_DL_s", "Velocidade_KBps",
"Status", "URL_Origem"
])
def registrar_download(self, nome_orig, slug, tamanho_bytes, tempo_s, url, status="SUCESSO"):
with self.lock:
try:
tamanho_mb = tamanho_bytes / (1024 * 1024)
velocidade = (tamanho_bytes / 1024) / tempo_s if tempo_s > 0 else 0
ts = datetime.now().strftime("%H:%M:%S")
with open(self.arquivo_csv, 'a', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
writer.writerow([
ts, nome_orig, slug,
f"{tamanho_mb:.2f}", f"{tempo_s:.2f}",
f"{velocidade:.2f}", status, url
])
# Log Console Simplificado
print(f"[{ts}] {status}: {slug} ({tamanho_mb:.2f}MB @ {velocidade:.0f}KB/s)")
except Exception as e:
print(f"Erro ao auditar: {e}")
# ================= WORKER =================
def baixar_projeto_worker(args):
url_detalhes, auditor = args
# Check de Segurança (Throttle)
if not verificar_memoria_segura():
time.sleep(5) # Espera RAM liberar
if not verificar_memoria_segura():
return (False, "RAM Crítica - Download Abortado")
try:
# 1. Pega página de detalhes
resp = requests.get(url_detalhes, headers=HEADERS, timeout=15)
if resp.status_code != 200:
auditor.registrar_download("N/A", "N/A", 0, 0, url_detalhes, f"HTTP_{resp.status_code}")
return (False, f"HTTP {resp.status_code}")
soup = BeautifulSoup(resp.text, 'html.parser')
# Lógica de encontrar link de download (Manteve a sua, que é boa)
link_tag = soup.find('a', href=lambda h: h and 'download_file.php' in h)
if not link_tag:
link_tag = soup.find('a', string='Download')
if link_tag:
url_download = urljoin(url_detalhes, link_tag['href'])
inicio_dl = time.time()
# Request do Arquivo
with requests.get(url_download, headers=HEADERS, stream=True, timeout=60) as r_file:
r_file.raise_for_status()
# --- DESCIFRAR NOME (COM SANITIZAÇÃO) ---
nome_bruto = ""
if "Content-Disposition" in r_file.headers:
cd = r_file.headers["Content-Disposition"]
if "filename=" in cd:
nome_bruto = cd.split("filename=")[1].strip('"')
if not nome_bruto:
query = parse_qs(urlparse(url_download).query)
nome_bruto = query['name'][0] if 'name' in query else f"proj_{int(time.time())}.mmpz"
# Aplica Slugify (Padrão do Pipeline)
nome_base, ext = os.path.splitext(nome_bruto)
if not ext: ext = ".mmpz" # Default seguro
slug = slugify(nome_base)
nome_final = f"{slug}{ext}"
caminho_final = os.path.join(PASTA_DESTINO, nome_final)
# Skip se já existe
if os.path.exists(caminho_final):
auditor.registrar_download(nome_bruto, slug, os.path.getsize(caminho_final), 0, url_detalhes, "SKIPPED_EXISTS")
return (True, "Já existe")
# Gravação em chunks (Seguro para RAM)
bytes_downloaded = 0
with open(caminho_final, 'wb') as f:
for chunk in r_file.iter_content(chunk_size=8192):
if chunk:
f.write(chunk)
bytes_downloaded += len(chunk)
tempo_total = time.time() - inicio_dl
# Auditoria de Sucesso
auditor.registrar_download(nome_bruto, slug, bytes_downloaded, tempo_total, url_detalhes, "SUCESSO")
return (True, "OK")
else:
auditor.registrar_download("N/A", "N/A", 0, 0, url_detalhes, "LINK_NOT_FOUND")
return (False, "Link não encontrado")
except Exception as e:
auditor.registrar_download("Erro", "Erro", 0, 0, url_detalhes, f"EXCEPT_{str(e)[:20]}")
return (False, str(e))
# ================= MAIN =================
def crawler_manager(url_base, max_paginas=2):
print(f"\n=== INICIANDO CRAWLER PADRONIZADO ===")
print(f"Destino: {PASTA_DESTINO}")
print(f"Auditoria: {ARQUIVO_AUDITORIA}")
auditor = AuditoriaCrawler(ARQUIVO_AUDITORIA)
pagina_atual = 1
total_sucesso = 0
# Executor seguro
with ThreadPoolExecutor(max_workers=MAX_WORKERS_SAFE) as executor:
while pagina_atual <= max_paginas:
print(f"\n--- Varrendo Página {pagina_atual} ---")
operador = '&' if '?' in url_base else '?'
url_pag = f"{url_base}{operador}page={pagina_atual}"
try:
# Pega a lista de projetos da página
r = requests.get(url_pag, headers=HEADERS)
soup = BeautifulSoup(r.text, 'html.parser')
# Filtro específico do LMMS Sharing Platform
links = soup.find_all('a', href=lambda h: h and '?action=show&file=' in h)
urls_unicas = sorted(list(set([l['href'] for l in links])))
if not urls_unicas:
print("Nenhum projeto encontrado nesta página. Encerrando.")
break
# Submete tarefas
futures = []
for u in urls_unicas:
url_completa = urljoin(url_base, u)
futures.append(executor.submit(baixar_projeto_worker, (url_completa, auditor)))
# Processa resultados conforme chegam
for future in as_completed(futures):
ok, msg = future.result()
if ok: total_sucesso += 1
except Exception as e:
print(f"Erro na paginação: {e}")
pagina_atual += 1
time.sleep(1) # Respeito ao servidor
tempo_total = time.time() - auditor.inicio_global
print(f"\n=== CRAWLER FINALIZADO ===")
print(f"Tempo: {timedelta(seconds=int(tempo_total))}")
print(f"Downloads com Sucesso: {total_sucesso}")
if __name__ == "__main__":
# URL de exemplo (Projetos ordenados por data)
URL_ALVO = 'https://lmms.io/lsp/?action=browse&category=Projects&sort=date'
crawler_manager(URL_ALVO, max_paginas=499)