File size: 2,923 Bytes
86d6248
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
import asyncio
import redis.asyncio as redis
from datetime import datetime
from collections import defaultdict
from typing import Dict


class Analytics:
    def __init__(self, redis_url: str, sync_interval: int = 60):
        """
        Initializes the Analytics class with an async Redis connection and sync interval.

        Parameters:
        - redis_url: Redis connection URL (e.g., 'redis://localhost:6379/0')
        - sync_interval: Interval in seconds for syncing with Redis.
        """
        self.pool = redis.ConnectionPool.from_url(redis_url, decode_responses=True)
        self.redis_client = redis.Redis(connection_pool=self.pool)
        self.local_buffer = defaultdict(
            lambda: defaultdict(int)
        )  # {period: {model_id: count}}
        self.sync_interval = sync_interval
        self.lock = asyncio.Lock()  # Async lock for thread-safe updates
        asyncio.create_task(self._start_sync_task())

    def _get_period_keys(self) -> tuple:
        """
        Returns keys for day, week, month, and year based on the current date.
        """
        now = datetime.utcnow()
        day_key = now.strftime("%Y-%m-%d")
        week_key = f"{now.year}-W{now.strftime('%U')}"
        month_key = now.strftime("%Y-%m")
        year_key = now.strftime("%Y")
        return day_key, week_key, month_key, year_key

    async def access(self, model_id: str):
        """
        Records an access for a specific model_id.
        """
        day_key, week_key, month_key, year_key = self._get_period_keys()

        async with self.lock:
            self.local_buffer[day_key][model_id] += 1
            self.local_buffer[week_key][model_id] += 1
            self.local_buffer[month_key][model_id] += 1
            self.local_buffer[year_key][model_id] += 1
            self.local_buffer["total"][model_id] += 1

    async def stats(self) -> Dict[str, Dict[str, int]]:
        """
        Returns statistics for all models from the local buffer.
        """
        async with self.lock:
            return {
                period: dict(models) for period, models in self.local_buffer.items()
            }

    async def _sync_to_redis(self):
        """
        Synchronizes local buffer data with Redis.
        """
        async with self.lock:
            pipeline = self.redis_client.pipeline()
            for period, models in self.local_buffer.items():
                for model_id, count in models.items():
                    redis_key = f"analytics:{period}"
                    pipeline.hincrby(redis_key, model_id, count)
            await pipeline.execute()
            self.local_buffer.clear()  # Clear the buffer after sync

    async def _start_sync_task(self):
        """
        Starts a background task that periodically syncs data to Redis.
        """
        while True:
            await asyncio.sleep(self.sync_interval)
            await self._sync_to_redis()