This commit is contained in:
2026-04-29 02:07:21 +03:00
parent ba6bfc5ed3
commit 0aa057c991
14 changed files with 4257 additions and 139 deletions

107
analyze_speed.py Normal file
View File

@@ -0,0 +1,107 @@
import sqlite3
from datetime import datetime
from collections import defaultdict
conn = sqlite3.connect('/app/state/progress.db')
conn.row_factory = sqlite3.Row
rows = conn.execute('''
SELECT h.created_at, h.chapter_number, h.volume, m.title, h.manga_url
FROM history h
LEFT JOIN mangas m ON h.manga_url = m.url
WHERE h.event_type IN ("downloaded","auto_downloaded")
ORDER BY h.created_at
''').fetchall()
if not rows:
print("История пуста")
conn.close()
exit()
times = [datetime.fromisoformat(r["created_at"]) for r in rows]
total_dur = (times[-1] - times[0]).total_seconds()
print("=== ОБЩАЯ СТАТИСТИКА ===")
print(f"Глав скачано: {len(rows)}")
print(f"Период: {times[0].strftime('%d.%m %H:%M:%S')}{times[-1].strftime('%d.%m %H:%M:%S')}")
print(f"Общее время: {total_dur/3600:.2f} ч ({total_dur/60:.0f} мин)")
print(f"Средняя скорость: {len(rows)/(total_dur/60):.2f} глав/мин ({total_dur/len(rows):.1f} сек/глава)")
# --- По мангам ---
print("\n=== ПО МАНГАМ ===")
by_manga = defaultdict(list)
for i, r in enumerate(rows):
by_manga[r["manga_url"]].append(times[i])
for url, ts in sorted(by_manga.items(), key=lambda x: x[1][0]):
title = next((r["title"] for r in rows if r["manga_url"] == url and r["title"]), url[-40:])
dur = (ts[-1] - ts[0]).total_seconds() if len(ts) > 1 else 0
rate = len(ts) / (dur / 60) if dur > 0 else 0
print(f" {(title or url)[:38]:38} {len(ts):4d} гл {dur/60:5.0f} мин {rate:.2f} гл/мин")
# --- По часам ---
print("\n=== ГЛАВЫ ПО ЧАСАМ ===")
by_hour = defaultdict(int)
for t in times:
by_hour[t.strftime('%d.%m %H:00')] += 1
for hour, cnt in sorted(by_hour.items()):
bar = '' * min(cnt, 60)
print(f" {hour} {cnt:4d} глав {bar}")
# --- Паузы > 5 мин ---
print("\n=== ПАУЗЫ > 5 МИН (между главами) ===")
big_gaps = []
for i in range(len(times) - 1):
sec = (times[i+1] - times[i]).total_seconds()
if sec > 300:
big_gaps.append((times[i], times[i+1], sec, rows[i]["title"], rows[i+1]["title"]))
if big_gaps:
for t1, t2, sec, m1, m2 in big_gaps:
same = (rows[big_gaps.index((t1,t2,sec,m1,m2)) if False else 0])
label = f"{(m1 or '')[:20]}{(m2 or '')[:20]}" if m1 != m2 else (m1 or "")[:40]
print(f" {t1.strftime('%H:%M:%S')}{t2.strftime('%H:%M:%S')} {sec/60:5.1f} мин [{label}]")
else:
print(" Пауз > 5 мин не обнаружено")
# --- Скорость по 10-мин окнам ---
print("\n=== СКОРОСТЬ ПО 10-МИНУТНЫМ ОКНАМ ===")
window = 10 * 60
bucket_start = times[0]
bucket_count = 0
windows = []
for t in times:
if (t - bucket_start).total_seconds() < window:
bucket_count += 1
else:
elapsed = (t - bucket_start).total_seconds()
windows.append((bucket_start, bucket_count, elapsed))
bucket_start = t
bucket_count = 1
if bucket_count:
windows.append((bucket_start, bucket_count, (times[-1] - bucket_start).total_seconds() or 1))
max_cnt = max(w[1] for w in windows) if windows else 1
for ws, cnt, elapsed in windows:
rate = cnt / (elapsed / 60) if elapsed > 0 else 0
bar_len = int(cnt / max_cnt * 40)
bar = '' * bar_len + '' * (40 - bar_len)
print(f" {ws.strftime('%H:%M')} {cnt:3d} гл {rate:4.1f}/мин |{bar}|")
# --- Перцентили интервалов ---
gaps_sec = sorted((times[i+1] - times[i]).total_seconds() for i in range(len(times)-1))
if gaps_sec:
n = len(gaps_sec)
print(f"\n=== ИНТЕРВАЛЫ МЕЖДУ ГЛАВАМИ ===")
print(f" Минимум: {gaps_sec[0]:.1f} сек")
print(f" Медиана: {gaps_sec[n//2]:.1f} сек")
print(f" P90: {gaps_sec[int(n*0.9)]:.1f} сек")
print(f" P99: {gaps_sec[int(n*0.99)]:.1f} сек")
print(f" Максимум: {gaps_sec[-1]:.1f} сек ({gaps_sec[-1]/60:.1f} мин)")
over_2min = sum(1 for g in gaps_sec if g > 120)
over_5min = sum(1 for g in gaps_sec if g > 300)
print(f" > 2 мин: {over_2min} ({over_2min/n*100:.1f}%)")
print(f" > 5 мин: {over_5min} ({over_5min/n*100:.1f}%)")
conn.close()