Repository: goauthentik/authentik

Issue:
Tasks API `start_timestamp` is not set, `task_duration` is wrong
**Describe the bug**
I noticed that System Tasks durations in the web UI are not right. The source of that data is the tasks API, and it's being calculated incorrectly there. `start_timestamp` is not part of the task schema, so it's not possible to calculate it yourself either.

**To Reproduce**
Check System Tasks page in admin interface, or `GET /admin/system_tasks/`

**Expected behavior**
Duration is correct, and `start_timestamp` is set

**Screenshots**
![image](https://user-images.githubusercontent.com/16788697/224092395-bacc6038-e263-41bf-801b-c0b3c00de36f.png)
![image](https://user-images.githubusercontent.com/16788697/224098644-eb942b08-9918-4cbb-9068-d99172dd62c1.png)

**Version and Deployment (please complete the following information):**
 - authentik version: `gh-main`
 - Deployment: docker-compose



Discussion:
`start_timestamp` is set here https://github.com/goauthentik/authentik/blob/main/authentik/events/monitored_tasks.py#L141 and here https://github.com/goauthentik/authentik/blob/main/authentik/events/monitored_tasks.py#L165

I think the incorrect value in the webui comes from the fact that the timestamps are from `default_timer`, which uses https://docs.python.org/3/library/time.html#time.perf_counter, which is maybe not the best thing to do here (I'm pretty sure I went with that as a lot of tasks can be sub 1-second long)
Having no knowledge of the middlewares used in authentik makes things interesting! GitHub's search is also just terrible because it did not give me anything from [TaskInfo](https://github.com/goauthentik/authentik/blob/0ef333f8eaf3921e24176de1e9d171e8cb7daec6/authentik/events/monitored_tasks.py#L53) when I searched for `start_timestamp`, so I only saw [this](https://github.com/goauthentik/authentik/blob/0ef333f8eaf3921e24176de1e9d171e8cb7daec6/authentik/admin/api/tasks.py#L45). And I didn't realize the serialized class [is not the actual task class](https://github.com/goauthentik/authentik/blob/0ef333f8eaf3921e24176de1e9d171e8cb7daec6/authentik/admin/api/tasks.py#L43) used.

Well my bad, but it still stands that the durations are super wrong (all those tasks took about 2 seconds to finish). I've updated the issue.

Resolving patch:
diff --git a/authentik/events/monitored_tasks.py b/authentik/events/monitored_tasks.py
--- a/authentik/events/monitored_tasks.py
+++ b/authentik/events/monitored_tasks.py
@@ -111,6 +111,7 @@ class MonitoredTask(Task):
     _result: Optional[TaskResult]
 
     _uid: Optional[str]
+    start: Optional[float] = None
 
     def __init__(self, *args, **kwargs) -> None:
         super().__init__(*args, **kwargs)
@@ -118,7 +119,6 @@ def __init__(self, *args, **kwargs) -> None:
         self._uid = None
         self._result = None
         self.result_timeout_hours = 6
-        self.start = default_timer()
 
     def set_uid(self, uid: str):
         """Set UID, so in the case of an unexpected error its saved correctly"""
@@ -128,6 +128,10 @@ def set_status(self, result: TaskResult):
         """Set result for current run, will overwrite previous result."""
         self._result = result
 
+    def before_start(self, task_id, args, kwargs):
+        self.start = default_timer()
+        return super().before_start(task_id, args, kwargs)
+
     # pylint: disable=too-many-arguments
     def after_return(self, status, retval, task_id, args: list[Any], kwargs: dict[str, Any], einfo):
         super().after_return(status, retval, task_id, args, kwargs, einfo=einfo)
@@ -138,7 +142,7 @@ def after_return(self, status, retval, task_id, args: list[Any], kwargs: dict[st
         info = TaskInfo(
             task_name=self.__name__,
             task_description=self.__doc__,
-            start_timestamp=self.start,
+            start_timestamp=self.start or default_timer(),
             finish_timestamp=default_timer(),
             finish_time=datetime.now(),
             result=self._result,
@@ -162,7 +166,7 @@ def on_failure(self, exc, task_id, args, kwargs, einfo):
         TaskInfo(
             task_name=self.__name__,
             task_description=self.__doc__,
-            start_timestamp=self.start,
+            start_timestamp=self.start or default_timer(),
             finish_timestamp=default_timer(),
             finish_time=datetime.now(),
             result=self._result,


Test patch: