traffic_tracking.py 3.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
  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. class TrafficChard(ui.chart):
  12. def __init__(self) -> None:
  13. self.visits: Dict[int, int] = {}
  14. self.sessions: Dict[int, Set[str]] = {}
  15. self.load()
  16. ui.on_connect(self.on_connect)
  17. ui.timer(10, self.save)
  18. ui.timer(10, self.update_visibility)
  19. super().__init__({
  20. 'title': {'text': 'Page Visits'},
  21. 'navigation': {'buttonOptions': {'enabled': False}},
  22. 'chart': {'type': 'line'},
  23. 'yAxis': {'title': False, 'type': 'logarithmic', },
  24. 'xAxis': {
  25. 'type': 'datetime',
  26. 'labels': {'format': '{value:%b %e}', },
  27. },
  28. 'series': [
  29. {'name': 'Views', 'data': []},
  30. {'name': 'Sessions', 'data': []},
  31. ],
  32. })
  33. def on_connect(self, request: Request) -> None:
  34. # ignore monitoring, web crawlers and the like
  35. agent = request.headers['user-agent'].lower()
  36. if any(s in agent for s in ('bot', 'spider', 'crawler', 'monitor', 'curl', 'wget', 'python-requests', 'kuma')):
  37. return
  38. def seconds_to_day(seconds: float) -> int: return int(seconds / 60 / 60 / 24)
  39. def day_to_milliseconds(day: int) -> float: return day * 24 * 60 * 60 * 1000
  40. today = seconds_to_day(time.time())
  41. self.visits[today] = self.visits.get(today, 0) + 1
  42. self.options.series[0].data[:] = [[day_to_milliseconds(day), count] for day, count in self.visits.items()]
  43. if today not in self.sessions:
  44. self.sessions[today] = set()
  45. self.sessions[today].add(request.session_id)
  46. self.options.series[1].data[:] = [[day_to_milliseconds(day), len(s)] for day, s in self.sessions.items()]
  47. self.update()
  48. def load(self) -> None:
  49. os.makedirs(os.path.dirname(VISITS_FILE), exist_ok=True)
  50. os.makedirs(os.path.dirname(SESSIONS_FILE), exist_ok=True)
  51. try:
  52. with open(VISITS_FILE, 'rb') as f:
  53. self.visits = pickle.load(f)
  54. with open(SESSIONS_FILE, 'rb') as f:
  55. self.sessions = pickle.load(f)
  56. except FileNotFoundError:
  57. pass
  58. except:
  59. logging.exception('Error loading traffic data')
  60. def save(self) -> None:
  61. def _save() -> None:
  62. try:
  63. with open(VISITS_FILE, 'wb') as f:
  64. pickle.dump(self.visits, f)
  65. with open(SESSIONS_FILE, 'wb') as f:
  66. pickle.dump(self.sessions, f)
  67. except:
  68. logging.exception('Error saving traffic data')
  69. t = threading.Thread(target=_save, name='Save Traffic Data')
  70. t.start()
  71. def update_visibility(self) -> None:
  72. self.visible = len(self.visits.keys()) >= 3 and len(self.sessions.keys()) >= 3