Skip to content

Commit 015830e

Browse files
Store current time when TaskResult started (#432)
* store current time when TaskResult started * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * add an example --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 0c1b1b7 commit 015830e

File tree

7 files changed

+86
-5
lines changed

7 files changed

+86
-5
lines changed

django_celery_results/admin.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ class TaskResultAdmin(admin.ModelAdmin):
2222
'status', 'worker')
2323
list_filter = ('status', 'date_done', 'periodic_task_name', 'task_name',
2424
'worker')
25-
readonly_fields = ('date_created', 'date_done', 'result', 'meta')
25+
readonly_fields = ('date_created', 'date_started', 'date_done',
26+
'result', 'meta')
2627
search_fields = ('task_name', 'task_id', 'status', 'task_args',
2728
'task_kwargs')
2829
fieldsets = (
@@ -49,6 +50,7 @@ class TaskResultAdmin(admin.ModelAdmin):
4950
'fields': (
5051
'result',
5152
'date_created',
53+
'date_started',
5254
'date_done',
5355
'traceback',
5456
'meta',

django_celery_results/backends/database.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import binascii
22
import json
33

4-
from celery import maybe_signature
4+
from celery import maybe_signature, states
55
from celery.backends.base import BaseDictBackend, get_current_task
66
from celery.exceptions import ChordError
77
from celery.result import GroupResult, allow_join_result, result_from_tuple
88
from celery.utils.log import get_logger
99
from celery.utils.serialization import b64decode, b64encode
1010
from django.db import connection, router, transaction
11+
from django.db.models.functions import Now
1112
from django.db.utils import InterfaceError
1213
from kombu.exceptions import DecodeError
1314

@@ -144,6 +145,9 @@ def _store_result(
144145
self._get_extended_properties(request, traceback)
145146
)
146147

148+
if status == states.STARTED:
149+
task_props['date_started'] = Now()
150+
147151
self.TaskModel._default_manager.store_result(**task_props)
148152
return result
149153

django_celery_results/managers.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ def store_result(self, content_type, content_encoding,
119119
traceback=None, meta=None,
120120
periodic_task_name=None,
121121
task_name=None, task_args=None, task_kwargs=None,
122-
worker=None, using=None):
122+
worker=None, using=None, **kwargs):
123123
"""Store the result and status of a task.
124124
125125
Arguments:
@@ -161,6 +161,9 @@ def store_result(self, content_type, content_encoding,
161161
'task_kwargs': task_kwargs,
162162
'worker': worker
163163
}
164+
if 'date_started' in kwargs:
165+
fields['date_started'] = kwargs['date_started']
166+
164167
obj, created = self.using(using).get_or_create(task_id=task_id,
165168
defaults=fields)
166169
if not created:
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Generated by Django 4.2.13 on 2024-06-02 07:56
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('django_celery_results', '0011_taskresult_periodic_task_name'),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name='taskresult',
15+
name='date_started',
16+
field=models.DateTimeField(
17+
default=None,
18+
help_text='Datetime field when the task was started in UTC',
19+
null=True,
20+
verbose_name='Started DateTime',
21+
),
22+
),
23+
]

django_celery_results/models.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,10 @@ class TaskResult(models.Model):
7575
auto_now_add=True,
7676
verbose_name=_('Created DateTime'),
7777
help_text=_('Datetime field when the task result was created in UTC'))
78+
date_started = models.DateTimeField(
79+
null=True, default=None,
80+
verbose_name=_('Started DateTime'),
81+
help_text=_('Datetime field when the task was started in UTC'))
7882
date_done = models.DateTimeField(
7983
auto_now=True,
8084
verbose_name=_('Completed DateTime'),

docs/getting_started.rst

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,19 @@ To use :pypi:`django-celery-results` with your project you need to follow these
6161
6262
CELERY_RESULT_EXTENDED = True
6363
64-
If you want to track the execution duration of your tasks (by comparing `date_created` and `date_done` in TaskResult), enable the :setting:`track_started` setting.
65-
64+
If you want to track the execution duration of your tasks (by comparing `date_started` and `date_done` in TaskResult), enable the :setting:`track_started` setting.
65+
6666
.. code-block:: python
6767
6868
CELERY_TASK_TRACK_STARTED = True
69+
70+
For example, if you write [additional codes](https://github.com/celery/django-celery-results/issues/286#issuecomment-1789094153) to record when a task becomes PENDING, you can calculate the waiting time in the queue or the actual processing time of the worker.
71+
72+
.. code-block:: python
73+
74+
task_result = TaskResult.objects.get(task_id='xxx')
75+
76+
waiting_time = task_result.date_started - task_result.date_created
77+
processing_time = task_result.date_done - task_result.date_started
78+
total_time = task_result.date_done - task_result.date_created
79+
print(f'result: {waiting_time=}, {processing_time=}, {total_time=}')

t/unit/backends/test_database.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import datetime
12
import json
23
import pickle
34
import re
@@ -549,6 +550,39 @@ def test_backend__task_result_meta_injection(self):
549550
tr = TaskResult.objects.get(task_id=tid2)
550551
assert json.loads(tr.meta) == {'key': 'value', 'children': []}
551552

553+
def test_backend__task_result_date(self):
554+
tid2 = uuid()
555+
556+
self.b.store_result(tid2, None, states.PENDING)
557+
558+
tr = TaskResult.objects.get(task_id=tid2)
559+
assert tr.status == states.PENDING
560+
assert isinstance(tr.date_created, datetime.datetime)
561+
assert tr.date_started is None
562+
assert isinstance(tr.date_done, datetime.datetime)
563+
564+
date_created = tr.date_created
565+
date_done = tr.date_done
566+
567+
self.b.mark_as_started(tid2)
568+
569+
tr = TaskResult.objects.get(task_id=tid2)
570+
assert tr.status == states.STARTED
571+
assert date_created == tr.date_created
572+
assert isinstance(tr.date_started, datetime.datetime)
573+
assert tr.date_done > date_done
574+
575+
date_started = tr.date_started
576+
date_done = tr.date_done
577+
578+
self.b.mark_as_done(tid2, 42)
579+
580+
tr = TaskResult.objects.get(task_id=tid2)
581+
assert tr.status == states.SUCCESS
582+
assert tr.date_created == date_created
583+
assert tr.date_started == date_started
584+
assert tr.date_done > date_done
585+
552586
def xxx_backend(self):
553587
tid = uuid()
554588

0 commit comments

Comments
 (0)