traffic_tracking.py 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. import logging
  2. import os
  3. import pickle
  4. import threading
  5. import time
  6. from typing import Dict, Set
  7. from starlette.requests import Request
  8. from nicegui import ui
  9. VISITS_FILE = 'traffic_data/visits.pickle'
  10. SESSIONS_FILE = 'traffic_data/sessions.pickle'
  11. REFERRERS_FILE = 'traffic_data/referrers.pickle'
  12. visits: Dict[int, int] = {}
  13. sessions: Dict[int, Set[str]] = {}
  14. referrers: Dict[int, Dict[str, int]] = {}
  15. os.makedirs(os.path.dirname(VISITS_FILE), exist_ok=True)
  16. os.makedirs(os.path.dirname(SESSIONS_FILE), exist_ok=True)
  17. os.makedirs(os.path.dirname(REFERRERS_FILE), exist_ok=True)
  18. try:
  19. with open(VISITS_FILE, 'rb') as f:
  20. visits = pickle.load(f)
  21. with open(SESSIONS_FILE, 'rb') as f:
  22. sessions = pickle.load(f)
  23. with open(REFERRERS_FILE, 'rb') as f:
  24. referrers = pickle.load(f)
  25. except FileNotFoundError:
  26. pass
  27. except:
  28. logging.exception('Error loading traffic data')
  29. should_exit = threading.Event()
  30. def keep_backups() -> None:
  31. def _save() -> None:
  32. while not should_exit.is_set():
  33. try:
  34. with open(VISITS_FILE, 'wb') as f:
  35. pickle.dump(visits, f)
  36. with open(SESSIONS_FILE, 'wb') as f:
  37. pickle.dump(sessions, f)
  38. with open(REFERRERS_FILE, 'wb') as f:
  39. pickle.dump(referrers, f)
  40. except:
  41. logging.exception('Error saving traffic data')
  42. time.sleep(1)
  43. t = threading.Thread(target=_save, name='Save Traffic Data')
  44. t.start()
  45. ui.on_startup(keep_backups)
  46. ui.on_shutdown(should_exit.set)
  47. def on_connect(request: Request) -> None:
  48. # ignore monitoring, web crawlers and the like
  49. agent = request.headers['user-agent'].lower()
  50. if any(s in agent for s in ('bot', 'spider', 'crawler', 'monitor', 'curl', 'wget', 'python-requests', 'kuma')):
  51. return
  52. origin_url = request.headers.get('referer', 'unknown')
  53. print(f'new connection from {agent}, coming from {origin_url}', flush=True)
  54. def seconds_to_day(seconds: float) -> int: return int(seconds / 60 / 60 / 24)
  55. #print(f'traffic data: {[datetime.fromtimestamp(day_to_milliseconds(t)/1000) for t in visits.keys()]}')
  56. today = seconds_to_day(time.time())
  57. visits[today] = visits.get(today, 0) + 1
  58. referrers[today] = referrers.get(today, {})
  59. referrers[today][origin_url] = referrers[today].get(origin_url, 0) + 1
  60. print(referrers, flush=True)
  61. if today not in sessions:
  62. sessions[today] = set()
  63. sessions[today].add(request.session_id)
  64. class chart(ui.chart):
  65. def __init__(self) -> None:
  66. super().__init__({
  67. 'title': {'text': 'Page Visits'},
  68. 'navigation': {'buttonOptions': {'enabled': False}},
  69. 'chart': {'type': 'line'},
  70. 'yAxis': {'title': False, 'type': 'logarithmic', },
  71. 'xAxis': {
  72. 'type': 'datetime',
  73. 'labels': {'format': '{value:%b %e}', },
  74. },
  75. 'series': [
  76. {'name': 'Views', 'data': []},
  77. {'name': 'Sessions', 'data': []},
  78. ],
  79. })
  80. self.visible = len(visits.keys()) >= 3 and len(sessions.keys()) >= 3
  81. ui.timer(10, self.update)
  82. def update(self) -> None:
  83. def day_to_milliseconds(day: int) -> float: return day * 24 * 60 * 60 * 1000
  84. self.options.series[0].data[:] = [[day_to_milliseconds(day), count] for day, count in visits.items()]
  85. # remove first day because data are inconclusive depending on deployment time
  86. self.options.series[0].data[:] = self.options.series[0].data[1:]
  87. self.options.series[1].data[:] = [[day_to_milliseconds(day), len(s)] for day, s in sessions.items()]
  88. # remove first day because data are inconclusive depending on deployment time
  89. self.options.series[1].data[:] = self.options.series[1].data[1:]
  90. super().update()