traffic_tracking.py 3.4 KB

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