Первый запуск

This commit is contained in:
KuzarinM
2026-05-02 18:33:38 +03:00
commit cb55eaef01
51 changed files with 2127373 additions and 0 deletions

230
Tester/LoadTester.py Normal file
View File

@@ -0,0 +1,230 @@
import math
import time
import os
import multiprocessing as mp
from multiprocessing import Queue, Process, Value
from queue import Empty
import mp
# Импортируйте ваши классы
from Generator.LogGenerator import LogGenerator
from Processor.StreamingLogCluster import StreamingLogCluster
# --- ПРОЦЕСС 1: ГЕНЕРАТОР НАГРУЗКИ ---
def load_generator(queue: Queue, target_rps: int, total_logs: int):
"""Генерирует логи с заданной частотой (RPS) и кладет в очередь."""
print(f"[ГЕНЕРАТОР] Запущен. Цель: {target_rps} логов/сек, Всего: {total_logs}")
gen = LogGenerator()
delay_between_logs = 1.0 / target_rps
for i in range(total_logs):
start_time = time.time()
# Генерируем лог
term = gen.generate()
log_text = term.render(0.5).text
# Кладем в очередь
queue.put(log_text)
# Пытаемся выдерживать заданный RPS
elapsed = time.time() - start_time
sleep_time = delay_between_logs - elapsed
if sleep_time > 0:
time.sleep(sleep_time)
# Кладем "ядовитую пилюлю" (сигнал остановки для воркера)
queue.put(None)
print(f"[ГЕНЕРАТОР] Завершил работу. Все {total_logs} логов отправлены в очередь.")
def load_generator_sin(
queue: Queue,
min_rps: float,
max_rps: float,
period_sec: float,
duration_sec: float,
current_rps_var: Value):
"""
Генерирует логи волнообразно (по синусоиде) от min_rps до max_rps.
period_sec - за сколько секунд проходит одна полная волна (от минимума до минимума)
duration_sec - общая длительность теста
"""
## print(f"[ГЕНЕРАТОР] Волнообразный старт: {min_rps} -> {max_rps} RPS.")
## print(f"[ГЕНЕРАТОР] Длина волны: {period_sec} сек, Тест идет: {duration_sec} сек.")
gen = LogGenerator()
# Математика волны
amplitude = (max_rps - min_rps) / 2.0 # Размах волны
offset = (max_rps + min_rps) / 2.0 # Центр волны
start_time = time.time()
logs_sent = 0
last_print_sec = -1
while True:
elapsed = time.time() - start_time
if elapsed >= duration_sec:
break
# Вычисляем текущий RPS по формуле: Offset - Amplitude * cos(2 * pi * t / T)
# Начинаем с -cos, чтобы старт был ровно с min_rps, а не с середины
current_rps = offset - amplitude * math.cos(2 * math.pi * elapsed / period_sec)
with current_rps_var.get_lock():
current_rps_var.value = current_rps
# Защита от деления на ноль (если задали min_rps = 0)
current_rps = max(0.1, current_rps)
delay = 1.0 / current_rps
loop_start = time.time()
# 1. Генерируем и отправляем лог
term = gen.generate()
log_text = term.render(0.5).text
queue.put(log_text)
logs_sent += 1
# --- Блок красивого вывода (раз в секунду показываем текущий напор) ---
current_sec = int(elapsed)
if current_sec > last_print_sec:
# Рисуем "градусник" нагрузки для наглядности
bar_len = int((current_rps / max_rps) * 20)
bar = "" * bar_len + "" * (20 - bar_len)
## print(f"[ГЕНЕРАТОР] Нагрузка: {current_rps:5.1f} RPS | {bar} | Отправлено: {logs_sent}")
last_print_sec = current_sec
# ----------------------------------------------------------------------
# 2. Ждем оставшееся время до следующего лога
work_time = time.time() - loop_start
sleep_time = delay - work_time
if sleep_time > 0:
time.sleep(sleep_time)
# Завершаем работу
queue.put(None)
print(f"[ГЕНЕРАТОР] Завершен. Всего сгенерировано логов: {logs_sent}")
# --- ПРОЦЕСС 2: ОБРАБОТЧИК (ВАШ КЛАСС) ---
def log_processor(queue: Queue, model_path: str, db_path: str, processed_count: Value):
"""Достает логи из очереди и обрабатывает их. Замеряет свою скорость."""
## print(f"[ОБРАБОТЧИК] Инициализация модели и БД...")
# ВАЖНО: Инициализировать кластер нужно ВНУТРИ процесса,
# чтобы SQLite и PyTorch не сошли с ума при передаче между процессами.
clusterer = StreamingLogCluster(model_path, db_path)
## print(f"[ОБРАБОТЧИК] Готов к приему данных!")
start_time = time.time()
while True:
try:
# Ждем лог из очереди (не более 5 секунд)
log_text = queue.get(timeout=50)
# Если пришел сигнал остановки - выходим
if log_text is None:
break
# Обрабатываем лог
clusterer.process(log_text)
with processed_count.get_lock():
processed_count.value += 1
# Каждые 50 логов выводим статистику
# if processed_count % 50 == 0:
# q_size = queue.qsize() # Сколько логов скопилось в очереди
# elapsed = time.time() - start_time
# current_rps = processed_count / elapsed
# print(
# f"[ОБРАБОТЧИК] Обработано: {processed_count} | Скорость: {current_rps:.1f} логов/сек | В очереди ждет: {q_size}")
except Empty:
print("[ОБРАБОТЧИК] Очередь пуста слишком долго. Завершаю работу.")
break
total_time = time.time() - start_time
print("-" * 40)
print(f"[ОБРАБОТЧИК] ИТОГИ:")
print(f" Всего обработано: {processed_count.value}")
print(f" Затрачено времени: {total_time:.2f} сек")
print(f" Средняя скорость: {processed_count.value / total_time:.2f} логов/сек")
print("-" * 40)
clusterer.close()
def monitor_process(queue: Queue, duration_sec: float, processed_count: Value, current_rps_generation: Value):
"""Монитор с расчетом реального RPS и состояния очереди."""
start_time = time.time()
last_print_time = 0
last_processed_count = 0 # Сколько логов мы обработали в прошлый раз
print(f"\n{'Время(с)'} | {'RPS (обработка)'} | {'RPS (генератор)'} | {'Очередь (логов)'}")
print("-" * 45)
while True:
elapsed = time.time() - start_time
# Условие выхода: прошло время теста + небольшой запас
if elapsed > duration_sec + 2:
break
# Выводим отчет каждые 2 секунды
if elapsed - last_print_time >= 2.0:
current_processed = processed_count.value
# Считаем RPS за прошедший интервал (2 секунды)
delta_logs = current_processed - last_processed_count
current_rps = delta_logs / (elapsed - last_print_time)
# Размер очереди
q_size = queue.qsize()
print(f"{int(elapsed)} | {current_rps} | {current_rps_generation.value} | {q_size}")
# Обновляем "состояние" для следующей итерации
last_print_time = elapsed
last_processed_count = current_processed
time.sleep(0.5)
# --- ТОЧКА ВХОДА ---
if __name__ == '__main__':
# Настройки Синусоиды
MIN_RPS = 1 # Минимум логов в секунду (на спаде)
MAX_RPS = 100 # Максимум логов в секунду (на пике)
PERIOD_SEC = 20.0 # Полный цикл от минимума до минимума займет 20 секунд
DURATION_SEC = 120.0 # Тестируем ровно 2 минуту (получится ровно 3 волны)
MODEL_PATH = '../Resources/model'
DB_FILE = "../Resources/logs.db"
if os.path.exists(DB_FILE):
os.remove(DB_FILE)
# 1. Общие переменные для мониторинга
processed_counter = Value('i', 0) # Счетчик обработанных логов
current_rps = Value('f', 0.0) # Счетчик генерируемых rps
log_queue = Queue()
# 2. Запуск процессов
proc_processor = Process(target=log_processor, args=(log_queue, MODEL_PATH, DB_FILE, processed_counter))
proc_generator = Process(target=load_generator_sin,
args=(log_queue, MIN_RPS, MAX_RPS, PERIOD_SEC, DURATION_SEC, current_rps))
proc_monitor = Process(target=monitor_process, args=(log_queue, DURATION_SEC, processed_counter, current_rps))
proc_monitor.start()
proc_processor.start()
time.sleep(2)
proc_generator.start()
proc_generator.join()
proc_processor.join()
proc_monitor.join()

48
Tester/PerformenceTest.py Normal file
View File

@@ -0,0 +1,48 @@
import difflib
import os
import re
import numpy as np
from Generator.LogGenerator import LogGenerator
from Processor.StreamingLogCluster import StreamingLogCluster
from Tester.RegressionMetricsCalculator import RegressionMetricsCalculator
if __name__ == '__main__':
gen = LogGenerator()
MODEL_PATH = '../Resources/model'
DB_FILE = "../Resources/logs.db"
if os.path.exists(DB_FILE):
os.remove(DB_FILE)
print("--- ЗАПУСК: Delta Mode ---")
clusterer = StreamingLogCluster(MODEL_PATH, db_path=DB_FILE)
sm = 0
for j in range(1000):
data = []
count = 500
sm += count
# Генерируем 10 примеров
for i in range(count):
# 1. Получаем объект Term
term = gen.generate()
# 3. Используем данные (например, сохраняем в JSON для обучения)
template = term.structure().text
log = term.render(0.5)
measure = clusterer.process_time_measure(log.text)
data.append(measure)
arr = np.array(data)
means = arr.mean(axis=0) * 1000
print(f"{sm}|{"|".join(map(str,means))}")

97
Tester/QualityTest.py Normal file
View File

@@ -0,0 +1,97 @@
import difflib
import os
import re
from Generator.LogGenerator import LogGenerator
from Processor.StreamingLogCluster import StreamingLogCluster
from Tester.RegressionMetricsCalculator import RegressionMetricsCalculator
def evaluate_template_similarity(gt_template: str, gen_template: str) -> dict:
"""
Оценивает схожесть сгенерированного шаблона (gen) с эталонным (gt - Ground Truth).
"""
# 1. Разбиваем шаблоны на сегменты (текст и теги <...>)
gt_parts = [p for p in re.split(r'(<[^>]+>)', gt_template) if p]
gen_parts = [p for p in re.split(r'(<[^>]+>)', gen_template) if p]
# --- СТРОГАЯ ПРОВЕРКА (Regex) ---
# Создаем регулярное выражение из эталона:
# Текст должен совпасть жестко, а переменные эталона могут проглотить что угодно (.*)
regex_pattern = '^'
for part in gt_parts:
if part.startswith('<') and part.endswith('>'):
regex_pattern += '(.*)'
else:
regex_pattern += re.escape(part)
regex_pattern += '$'
# Подготавливаем Gen: заменяем его переменные на нулевой байт,
# чтобы они поглотились `(.*)`, но не совпали с реальным текстом случайно
gen_string_for_regex = re.sub(r'<[^>]+>', '\x00', gen_template)
is_perfect_structure = bool(re.match(regex_pattern, gen_string_for_regex, flags=re.DOTALL))
# --- МЯГКАЯ ОЦЕНКА В ПРОЦЕНТАХ (Preservation Score) ---
# Достаем только жесткие константы, выбрасывая все переменные
gt_consts = "".join(p for p in gt_parts if not (p.startswith('<') and p.endswith('>')))
gen_consts = "".join(p for p in gen_parts if not (p.startswith('<') and p.endswith('>')))
# Сравниваем, насколько "скелет" Gen содержит внутри себя "скелет" Эталона
matcher = difflib.SequenceMatcher(None, gt_consts, gen_consts)
# Считаем сумму символов эталона, которые остались на своих местах
matched_chars = sum(block.size for block in matcher.get_matching_blocks())
# Считаем процент от 0.0 до 1.0
preservation_score = matched_chars / len(gt_consts) if gt_consts else 1.0
return {
"is_perfect": is_perfect_structure, # True, если структура не нарушена вообще
"score": round(preservation_score, 4), # 1.0 = Идеал, < 1.0 = Переменные "съели" константы
}
if __name__ == '__main__':
gen = LogGenerator()
metrics = RegressionMetricsCalculator()
MODEL_PATH = '../Resources/model'
DB_FILE = "../Resources/logs.db"
if os.path.exists(DB_FILE):
os.remove(DB_FILE)
print("--- ЗАПУСК: Delta Mode ---")
clusterer = StreamingLogCluster(MODEL_PATH, db_path=DB_FILE)
# Генерируем 10 примеров
for i in range(1):
# 1. Получаем объект Term
term = gen.generate()
# 3. Используем данные (например, сохраняем в JSON для обучения)
print(f"--- Sample {i + 1} ---")
template = term.structure().text
print(f"Template :{template}")
for j in range(10):
# 2. Рендерим его в строку и метаданные
log = term.render(0.5)
processed = clusterer.process(log.text)
eval_result = evaluate_template_similarity(template, processed['template_view'])
score = eval_result['score']
metrics.add_sample(score)
print(f"Positive {j}: {processed['template_view']}")
#print(score)
print(f"Template : {template}")
# # --- ВЫВОДИТ ИТОГОВЫЕ МЕТРИКИ В КОНЦЕ СКРИПТА ---
# print("\n" + "=" * 40)
# print("Метрики:")
# print("=" * 40)
# results = metrics.calculate()
# for metric_name, value in results.items():
# print(f"{metric_name:<10}: {value}")

View File

@@ -0,0 +1,50 @@
import math
from typing import List
class RegressionMetricsCalculator:
def __init__(self):
self.errors: List[float] =[]
def add_sample(self, score: float):
"""
score: число от 0.0 до 1.0 (результат evaluate_template_similarity)
Идеал - это 1.0. Ошибка - это то, насколько мы отклонились от 1.0.
"""
# Защита от кривых значений (если вдруг score вылезет за пределы)
score = max(0.0, min(1.0, score))
error = 1.0 - score
self.errors.append(error)
def calculate(self) -> dict:
n = len(self.errors)
if n == 0:
return {}
# 1. MAE (Mean Absolute Error) - Средняя абсолютная ошибка
mae = sum(abs(e) for e in self.errors) / n
# 2. MSE (Mean Squared Error) - Среднеквадратичная ошибка
mse = sum(e**2 for e in self.errors) / n
# 3. RMSE (Root Mean Squared Error) - Корень из MSE
rmse = math.sqrt(mse)
# 4. MAPE (Mean Absolute Percentage Error) - в процентах
# Так как наше "истинное" значение всегда 1.0, деление на 1.0 ничего не меняет,
# мы просто умножаем на 100 для получения процентов.
mape = (sum(abs(e) / 1.0 for e in self.errors) / n) * 100
# 5. MAD (Mean Absolute Deviation)
# В статистике часто означает среднее отклонение от СРЕДНЕЙ ошибки
# (чтобы показать разброс ошибок вокруг их собственного среднего).
mean_error = sum(self.errors) / n
mad = sum(abs(e - mean_error) for e in self.errors) / n
return {
"MAE": round(mae, 4),
"MAPE (%)": round(mape, 2),
"MAD": round(mad, 4),
"MSE": round(mse, 6),
"RMSE": round(rmse, 4)
}