From 937465b2f2f8fdffe4cfc01679fdd690306a7290 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Adolfo=20G=C3=B3mez=20Garc=C3=ADa?= <dkmaster@dkmon.com>
Date: Thu, 28 Oct 2021 16:17:26 +0200
Subject: [PATCH] Adding stats events for os manager relevant events (init,
 ready and release right now)

---
 server/src/uds/core/managers/stats.py    | 12 +++-
 server/src/uds/core/util/stats/events.py | 85 ++++++++++++++++++++++--
 2 files changed, 89 insertions(+), 8 deletions(-)

diff --git a/server/src/uds/core/managers/stats.py b/server/src/uds/core/managers/stats.py
index 186a335bb..886bc771f 100644
--- a/server/src/uds/core/managers/stats.py
+++ b/server/src/uds/core/managers/stats.py
@@ -40,6 +40,9 @@ from uds.models import StatsCounters
 from uds.models import getSqlDatetime, getSqlDatetimeAsUnix
 from uds.models import StatsEvents
 
+if typing.TYPE_CHECKING:
+    from django.db import models
+
 logger = logging.getLogger(__name__)
 
 FLDS_EQUIV: typing.Mapping[str, typing.Iterable[str]] = {
@@ -245,7 +248,7 @@ class StatsManager(metaclass=singleton.Singleton):
         ownerType: typing.Union[int, typing.Iterable[int]],
         eventType: typing.Union[int, typing.Iterable[int]],
         **kwargs
-    ):
+    ) -> 'models.QuerySet[StatsEvents]':
         """
         Retrieves counters from item
 
@@ -262,6 +265,13 @@ class StatsManager(metaclass=singleton.Singleton):
         """
         return StatsEvents.get_stats(ownerType, eventType, **kwargs)
 
+    def tailEvents(self, *, fromId: typing.Optional[str] = None, number: typing.Optional[int] = None) -> 'models.QuerySet[StatsEvents]':
+        # If number is not specified, we return five last events
+        number = number or 5
+        if fromId:
+            return StatsEvents.objects.filter(id__gt=fromId).order_by('-id')[:number]
+        return StatsEvents.objects.order_by('-id')[:number]
+
     def cleanupEvents(self):
         """
         Removes all events previous to configured max keep time for stat information from database.
diff --git a/server/src/uds/core/util/stats/events.py b/server/src/uds/core/util/stats/events.py
index 23c68d726..848c72565 100644
--- a/server/src/uds/core/util/stats/events.py
+++ b/server/src/uds/core/util/stats/events.py
@@ -31,19 +31,19 @@
 @author: Adolfo Gómez, dkmaster at dkmon dot com
 """
 import datetime
+import time
 import logging
 import typing
 
-from uds.core.managers.stats import StatsManager
-from uds.models import Provider, Service, ServicePool, Authenticator
+from uds.core.managers.stats import StatsManager, REVERSE_FLDS_EQUIV
+from uds.models import Provider, Service, ServicePool, Authenticator, OSManager
 
 logger = logging.getLogger(__name__)
 
-EventTupleType = typing.Tuple[datetime.datetime, str, str, str, str, int]
-EventClass = typing.Union[Provider, Service, ServicePool, Authenticator]
+# EventTupleType = typing.Tuple[datetime.datetime, str, str, str, str, int]
 
 if typing.TYPE_CHECKING:
-    from django.db.models import Model
+    from django.db import models
 
 # Posible events, note that not all are used by every possible owner type
 (
@@ -60,20 +60,41 @@ if typing.TYPE_CHECKING:
     # Tunnel
     ET_TUNNEL_OPEN,
     ET_TUNNEL_CLOSE,
+    # Os Manager
+    ET_OSMANAGER_INIT,
+    ET_OSMANAGER_READY,
+    ET_OSMANAGER_RELEASE
 ) = range(8)
 
+# Events names
+EVENT_NAMES = {
+    ET_LOGIN: 'Login',
+    ET_LOGOUT: 'Logout',
+    ET_ACCESS: 'Access',
+    ET_CACHE_HIT: 'Cache hit',
+    ET_CACHE_MISS: 'Cache miss',
+    ET_PLATFORM: 'Platform',
+    ET_TUNNEL_OPEN: 'Tunnel open',
+    ET_TUNNEL_CLOSE: 'Tunnel close',
+    ET_OSMANAGER_INIT: 'OS Manager init',
+    ET_OSMANAGER_READY: 'OS Manager ready',
+    ET_OSMANAGER_RELEASE: 'OS Manager release',
+}
+
 (
     OT_PROVIDER,
     OT_SERVICE,
     OT_DEPLOYED,
     OT_AUTHENTICATOR,
+    OT_OSMANAGER
 ) = range(4)
 
-__transDict: typing.Mapping[typing.Type['Model'], int] = {
+__transDict: typing.Mapping[typing.Type['models.Model'], int] = {
     ServicePool: OT_DEPLOYED,
     Service: OT_SERVICE,
     Provider: OT_PROVIDER,
     Authenticator: OT_AUTHENTICATOR,
+    OSManager: OT_OSMANAGER
 }
 
 # Events data (fld1, fld2, fld3, fld4):
@@ -115,6 +136,39 @@ __transDict: typing.Mapping[typing.Type['Model'], int] = {
 #
 # OT_TUNNEL_CLOSE: -> On ServicePool
 #     (duration, sent, received, tunnel_id)
+#
+# OT_OSMANAGER_INIT: -> On OsManager
+#     (servicepool_uuid, srcip, userservice_uuid)
+#
+# OT_OSMANAGER_READY: -> On OsManager
+#     (servicepool_uuid, srcip, userservice_uuid)
+#
+# OT_OSMANAGER_RELEASE: -> On OsManager
+#     (servicepool_uuid, '', userservice_uuid)
+
+class EventTupleType(typing.NamedTuple):
+    stamp: datetime.datetime
+    fld1: str
+    fld2: str
+    fld3: str
+    fld4: str
+    event_type: int
+
+    # aliases for fields
+    def __getitem__(self, item) -> typing.Any:
+        if item in REVERSE_FLDS_EQUIV:
+            item = REVERSE_FLDS_EQUIV[item]
+        return self.__getattribute__(item)
+
+    # Obtains the Event as a string
+    def __str__(self) -> str:
+        # Convert Event type to string first
+        eventName = EVENT_NAMES[self.event_type]
+        return '{} {} {} {} {} {}'.format(self.stamp, eventName, self.fld1, self.fld2, self.fld3, self.fld4 )
+
+
+EventClass = typing.Union[Provider, Service, ServicePool, Authenticator]
+
 
 
 def addEvent(obj: EventClass, eventType: int, **kwargs) -> bool:
@@ -165,7 +219,7 @@ def getEvents(
     for i in StatsManager.manager().getEvents(
         __transDict[type_], eventType, owner_id=owner_id, since=since, to=to
     ):
-        yield (
+        yield EventTupleType(
             datetime.datetime.fromtimestamp(i.stamp),
             i.fld1,
             i.fld2,
@@ -173,3 +227,20 @@ def getEvents(
             i.fld4,
             i.event_type,
         )
+ 
+ # tail the events table
+def tailEvents(sleepTime: int = 2) -> typing.Generator[EventTupleType, None, None]:
+    fromId = None
+    while True:
+        for i in StatsManager.manager().tailEvents(fromId=fromId):
+            yield EventTupleType(
+                datetime.datetime.fromtimestamp(i.stamp),
+                i.fld1,
+                i.fld2,
+                i.fld3,
+                i.fld4,
+                i.event_type,
+            )
+            fromId = i.pk if i.pk > (fromId or 0) else fromId
+        time.sleep(sleepTime)
+