diff --git a/ayon_server/auth/session.py b/ayon_server/auth/session.py index cd43eade..8b427f60 100644 --- a/ayon_server/auth/session.py +++ b/ayon_server/auth/session.py @@ -94,9 +94,14 @@ async def check(cls, token: str, request: Request | None) -> SessionModel | None if remaining_ttl < ayonconfig.session_ttl - 120: session.last_used = time.time() await Redis.set(cls.ns, token, json_dumps(session.dict())) + await cls.on_extend(session) return session + @classmethod + async def on_extend(cls, session: SessionModel) -> None: + pass + @classmethod async def create( cls, diff --git a/ayon_server/metrics/__init__.py b/ayon_server/metrics/__init__.py index 53fbfe6d..e8e2de10 100644 --- a/ayon_server/metrics/__init__.py +++ b/ayon_server/metrics/__init__.py @@ -28,7 +28,7 @@ from .settings import SettingsOverrides, get_studio_settings_overrides from .system import SystemMetricsData, system_metrics from .traffic import TrafficStat, get_traffic_stats -from .users import UserCounts, get_user_counts +from .users import UserCounts, UserStat, get_user_counts, get_user_stats def docfm(obj) -> str: @@ -131,6 +131,7 @@ class Metrics(OPModel): ) traffic_stats: list[TrafficStat] | None = Field(None, title="Traffic stats") + user_stats: list[UserStat] | None = Field(None, title="User stats") METRICS_SNAPSHOT = {} @@ -182,6 +183,11 @@ class Metrics(OPModel): "getter": get_traffic_stats, "ttl": 24, }, + { + "name": "user_stats", + "getter": get_user_stats, + "ttl": 24, + }, ] diff --git a/ayon_server/metrics/traffic.py b/ayon_server/metrics/traffic.py index 2b6e4ee5..d831628b 100644 --- a/ayon_server/metrics/traffic.py +++ b/ayon_server/metrics/traffic.py @@ -19,7 +19,7 @@ async def get_traffic_stats( return None result = [] - query = "SELECT * FROM traffic_stats" + query = "SELECT * FROM traffic_stats ORDER BY date DESC limit 65" async for row in Postgres.iterate(query): date = row.get("date").strftime("%Y-%m-%d") service = row.get("service") diff --git a/ayon_server/metrics/users.py b/ayon_server/metrics/users.py index 48889fba..1d1157a6 100644 --- a/ayon_server/metrics/users.py +++ b/ayon_server/metrics/users.py @@ -2,6 +2,7 @@ from ayon_server.lib.postgres import Postgres from ayon_server.types import Field, OPModel +from ayon_server.utils import get_nickname class UserCounts(OPModel): @@ -11,6 +12,11 @@ class UserCounts(OPModel): managers: Annotated[int, Field(title="Manager users", example=8)] = 0 +class UserStat(OPModel): + date: str + users: dict[str, str | None] # map active users to their pools + + async def get_user_counts( saturated: bool = False, system: bool = False ) -> UserCounts | None: @@ -37,3 +43,29 @@ async def get_user_counts( admins=row["admins"] or 0, managers=row["managers"] or 0, ) + + +async def get_user_stats( + saturated: bool = False, system: bool = False +) -> list[UserStat] | None: + _ = saturated + if not system: + # Collect traffic stats only when we collect system metrics + return None + + result = [] + query = "SELECT date, users FROM user_stats ORDER BY date DESC limit 65" + + async for row in Postgres.iterate(query): + date = row.get("date").strftime("%Y-%m-%d") + users = row.get("users", {}) + if not users: + continue + + if not saturated: + users = {get_nickname(u): pdata for u, pdata in users.items()} + + stat = UserStat(date=date, users=users) + result.append(stat) + + return result or None diff --git a/maintenance/tasks/push_metrics.py b/maintenance/tasks/push_metrics.py index fe41c44f..78949323 100644 --- a/maintenance/tasks/push_metrics.py +++ b/maintenance/tasks/push_metrics.py @@ -9,8 +9,9 @@ async def stats_cleanup(): now = datetime.datetime.now() begin = now - datetime.timedelta(days=60) - query = "DELETE FROM traffic_stats WHERE date < $1" - await Postgres().execute(query, begin) + for table in ["user_stats", "traffic_stats"]: + query = f"DELETE FROM {table} WHERE date < $1" + await Postgres().execute(query, begin) class PushMetrics(StudioMaintenanceTask): diff --git a/schemas/schema.public.sql b/schemas/schema.public.sql index 32a0e507..358905fc 100644 --- a/schemas/schema.public.sql +++ b/schemas/schema.public.sql @@ -227,6 +227,11 @@ CREATE TABLE IF NOT EXISTS public.traffic_stats( PRIMARY KEY (date, service) ); +CREATE TABLE IF NOT EXISTS public.user_stats( + date DATE NOT NULL PRIMARY KEY, + users JSONB NOT NULL DEFAULT '{}'::JSONB +); + -- CREATE THE SITE ID INSERT INTO config VALUES ('instanceId', to_jsonb(gen_random_uuid()::text)) ON CONFLICT DO NOTHING;