Первый запуск
This commit is contained in:
230
Tester/LoadTester.py
Normal file
230
Tester/LoadTester.py
Normal 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
48
Tester/PerformenceTest.py
Normal 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
97
Tester/QualityTest.py
Normal 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}")
|
||||
50
Tester/RegressionMetricsCalculator.py
Normal file
50
Tester/RegressionMetricsCalculator.py
Normal 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)
|
||||
}
|
||||
Reference in New Issue
Block a user