dj-queue 0.9.0__tar.gz → 0.9.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {dj_queue-0.9.0 → dj_queue-0.9.1}/PKG-INFO +1 -1
- {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/admin.py +2 -12
- dj_queue-0.9.1/dj_queue/contrib/prometheus.py +26 -0
- {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/dashboard.py +15 -59
- dj_queue-0.9.1/dj_queue/metrics.py +162 -0
- {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/observability.py +1 -16
- dj_queue-0.9.1/dj_queue/operations/_helpers.py +190 -0
- {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/operations/concurrency.py +11 -9
- {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/operations/jobs.py +118 -31
- dj_queue-0.9.1/dj_queue/queue_state.py +118 -0
- {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/runtime/base.py +34 -14
- {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/runtime/supervisor.py +52 -182
- dj_queue-0.9.1/dj_queue/runtime/topology.py +51 -0
- {dj_queue-0.9.0 → dj_queue-0.9.1}/pyproject.toml +1 -1
- dj_queue-0.9.0/dj_queue/contrib/prometheus.py +0 -128
- dj_queue-0.9.0/dj_queue/operations/_helpers.py +0 -41
- {dj_queue-0.9.0 → dj_queue-0.9.1}/LICENSE +0 -0
- {dj_queue-0.9.0 → dj_queue-0.9.1}/README.md +0 -0
- {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/__init__.py +0 -0
- {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/api.py +0 -0
- {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/apps.py +0 -0
- {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/backend.py +0 -0
- {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/config.py +0 -0
- {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/contrib/__init__.py +0 -0
- {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/contrib/asgi.py +0 -0
- {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/contrib/gunicorn.py +0 -0
- {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/cron.py +0 -0
- {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/db.py +0 -0
- {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/exceptions.py +0 -0
- {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/hooks.py +0 -0
- {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/log.py +0 -0
- {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/management/__init__.py +0 -0
- {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/management/commands/__init__.py +0 -0
- {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/management/commands/dj_queue.py +0 -0
- {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/management/commands/dj_queue_health.py +0 -0
- {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/management/commands/dj_queue_prune.py +0 -0
- {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/migrations/0001_initial.py +0 -0
- {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/migrations/0002_pause_semaphore.py +0 -0
- {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/migrations/0003_recurringtask_recurringexecution.py +0 -0
- {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/migrations/0004_dashboard.py +0 -0
- {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/migrations/0005_remove_recurringexecution_dj_queue_recurring_executions_task_key_run_at_unique_and_more.py +0 -0
- {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/migrations/0006_blockedexecution_dj_queue_bl_concurr_2d8393_idx_and_more.py +0 -0
- {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/migrations/0007_recurringtask_next_run_at.py +0 -0
- {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/migrations/0008_remove_blockedexecution_dj_queue_bl_concurr_1ce730_idx_and_more.py +0 -0
- {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/migrations/__init__.py +0 -0
- {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/models/__init__.py +0 -0
- {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/models/jobs.py +0 -0
- {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/models/recurring.py +0 -0
- {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/models/runtime.py +0 -0
- {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/operations/__init__.py +0 -0
- {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/operations/_insert.py +0 -0
- {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/operations/cleanup.py +0 -0
- {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/operations/queues.py +0 -0
- {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/operations/recurring.py +0 -0
- {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/routers.py +0 -0
- {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/runtime/__init__.py +0 -0
- {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/runtime/connection_budget.py +0 -0
- {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/runtime/dispatcher.py +0 -0
- {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/runtime/errors.py +0 -0
- {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/runtime/interruptible.py +0 -0
- {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/runtime/notify.py +0 -0
- {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/runtime/pidfile.py +0 -0
- {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/runtime/pool.py +0 -0
- {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/runtime/procline.py +0 -0
- {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/runtime/scheduler.py +0 -0
- {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/runtime/worker.py +0 -0
- {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/templates/admin/dj_queue/_dashboard_process_rows.html +0 -0
- {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/templates/admin/dj_queue/_dashboard_recurring_rows.html +0 -0
- {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/templates/admin/dj_queue/_dashboard_section_table.html +0 -0
- {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/templates/admin/dj_queue/_dashboard_semaphore_rows.html +0 -0
- {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/templates/admin/dj_queue/_paginator.html +0 -0
- {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/templates/admin/dj_queue/_queue_controls.html +0 -0
- {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/templates/admin/dj_queue/_sortable_header_cells.html +0 -0
- {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/templates/admin/dj_queue/change_form.html +0 -0
- {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/templates/admin/dj_queue/change_list.html +0 -0
- {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/templates/admin/dj_queue/dashboard.html +0 -0
- {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/templates/admin/dj_queue/includes/fieldset.html +0 -0
- {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/templates/admin/dj_queue/queue_jobs.html +0 -0
- {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/templatetags/__init__.py +0 -0
- {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/templatetags/dj_queue_admin.py +0 -0
- {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/urls.py +0 -0
- {dj_queue-0.9.0 → dj_queue-0.9.1}/dj_queue/views.py +0 -0
|
@@ -35,6 +35,7 @@ from dj_queue.operations.jobs import (
|
|
|
35
35
|
retry_failed_job,
|
|
36
36
|
retry_failed_jobs,
|
|
37
37
|
)
|
|
38
|
+
from dj_queue.queue_state import status_rank_expression
|
|
38
39
|
|
|
39
40
|
|
|
40
41
|
class DjQueueFirstAdminSite(admin.AdminSite):
|
|
@@ -503,18 +504,7 @@ class JobAdmin(HiddenSidebarAdminMixin, admin.ModelAdmin):
|
|
|
503
504
|
|
|
504
505
|
def get_queryset(self, request):
|
|
505
506
|
queryset = super().get_queryset(request)
|
|
506
|
-
return queryset.annotate(
|
|
507
|
-
status_rank=Case(
|
|
508
|
-
When(ready_execution__isnull=False, then=Value(0)),
|
|
509
|
-
When(scheduled_execution__isnull=False, then=Value(1)),
|
|
510
|
-
When(claimed_execution__isnull=False, then=Value(2)),
|
|
511
|
-
When(blocked_execution__isnull=False, then=Value(3)),
|
|
512
|
-
When(failed_execution__isnull=False, then=Value(4)),
|
|
513
|
-
When(finished_at__isnull=False, then=Value(5)),
|
|
514
|
-
default=Value(99),
|
|
515
|
-
output_field=IntegerField(),
|
|
516
|
-
)
|
|
517
|
-
)
|
|
507
|
+
return queryset.annotate(status_rank=status_rank_expression())
|
|
518
508
|
|
|
519
509
|
@admin.display(description="status", ordering="status_rank")
|
|
520
510
|
def display_status(self, obj):
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
try:
|
|
2
|
+
from prometheus_client import CollectorRegistry, generate_latest
|
|
3
|
+
from prometheus_client.core import GaugeMetricFamily
|
|
4
|
+
except ImportError:
|
|
5
|
+
DjQueueCollector = None
|
|
6
|
+
registry = None
|
|
7
|
+
generate_latest = None
|
|
8
|
+
else:
|
|
9
|
+
from dj_queue.metrics import metric_families
|
|
10
|
+
|
|
11
|
+
class DjQueueCollector:
|
|
12
|
+
"""Prometheus collector that exposes dj_queue metrics from the shared observability snapshot."""
|
|
13
|
+
|
|
14
|
+
def collect(self):
|
|
15
|
+
for family in metric_families():
|
|
16
|
+
gauge = GaugeMetricFamily(
|
|
17
|
+
family.name,
|
|
18
|
+
family.help_text,
|
|
19
|
+
labels=list(family.labels),
|
|
20
|
+
)
|
|
21
|
+
for sample in family.samples:
|
|
22
|
+
gauge.add_metric(list(sample.labels), sample.value)
|
|
23
|
+
yield gauge
|
|
24
|
+
|
|
25
|
+
registry = CollectorRegistry(auto_describe=False)
|
|
26
|
+
registry.register(DjQueueCollector())
|
|
@@ -22,51 +22,20 @@ from dj_queue.operations.jobs import (
|
|
|
22
22
|
enqueue_job_again,
|
|
23
23
|
retry_failed_jobs,
|
|
24
24
|
)
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
},
|
|
33
|
-
"
|
|
34
|
-
"label": "
|
|
35
|
-
"
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
},
|
|
39
|
-
"scheduled": {
|
|
40
|
-
"label": "scheduled",
|
|
41
|
-
"job_actions": ({"name": "discard", "label": "discard selected"},),
|
|
42
|
-
"query_filter": {"scheduled_execution__isnull": False},
|
|
43
|
-
"query_order": ("scheduled_execution__scheduled_at", "-priority", "scheduled_execution__id"),
|
|
44
|
-
},
|
|
45
|
-
"blocked": {
|
|
46
|
-
"label": "blocked",
|
|
47
|
-
"job_actions": ({"name": "discard", "label": "discard selected"},),
|
|
48
|
-
"query_filter": {"blocked_execution__isnull": False},
|
|
49
|
-
"query_order": ("blocked_execution__expires_at", "-priority", "blocked_execution__id"),
|
|
50
|
-
},
|
|
51
|
-
"failed": {
|
|
52
|
-
"label": "failed",
|
|
53
|
-
"job_actions": (
|
|
54
|
-
{"name": "retry", "label": "retry selected"},
|
|
55
|
-
{"name": "discard", "label": "discard selected"},
|
|
56
|
-
),
|
|
57
|
-
"query_filter": {"failed_execution__isnull": False},
|
|
58
|
-
"query_order": ("-failed_execution__created_at", "id"),
|
|
59
|
-
},
|
|
60
|
-
"finished": {
|
|
61
|
-
"label": "finished",
|
|
62
|
-
"job_actions": ({"name": "enqueue", "label": "enqueue selected again"},),
|
|
63
|
-
"query_filter": {"finished_at__isnull": False},
|
|
64
|
-
"query_order": ("-finished_at", "id"),
|
|
65
|
-
},
|
|
25
|
+
from dj_queue.queue_state import QUEUE_STATE_LABELS, QUEUE_STATES, queue_state_queryset
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
QUEUE_JOB_ACTIONS = {
|
|
29
|
+
"ready": ({"name": "discard", "label": "discard selected"},),
|
|
30
|
+
"claimed": (),
|
|
31
|
+
"scheduled": ({"name": "discard", "label": "discard selected"},),
|
|
32
|
+
"blocked": ({"name": "discard", "label": "discard selected"},),
|
|
33
|
+
"failed": (
|
|
34
|
+
{"name": "retry", "label": "retry selected"},
|
|
35
|
+
{"name": "discard", "label": "discard selected"},
|
|
36
|
+
),
|
|
37
|
+
"finished": ({"name": "enqueue", "label": "enqueue selected again"},),
|
|
66
38
|
}
|
|
67
|
-
|
|
68
|
-
QUEUE_STATES = tuple((state, config["label"]) for state, config in QUEUE_STATE_CONFIG.items())
|
|
69
|
-
QUEUE_STATE_LABELS = {state: config["label"] for state, config in QUEUE_STATE_CONFIG.items()}
|
|
70
39
|
PAGE_SIZE = 100
|
|
71
40
|
OVERVIEW_PAGE_SIZES = {
|
|
72
41
|
"queues": 18,
|
|
@@ -555,7 +524,7 @@ def apply_job_action(*, backend_alias, queue_name, state, action, job_ids):
|
|
|
555
524
|
|
|
556
525
|
|
|
557
526
|
def job_actions_for_state(state):
|
|
558
|
-
return
|
|
527
|
+
return QUEUE_JOB_ACTIONS[state]
|
|
559
528
|
|
|
560
529
|
|
|
561
530
|
def _summary_cards(*, backend_alias, queue_rows, process_rows, recurring_rows, semaphore_rows):
|
|
@@ -1181,20 +1150,7 @@ def _failed_execution_changelist_url(backend_alias, **filters):
|
|
|
1181
1150
|
|
|
1182
1151
|
|
|
1183
1152
|
def _jobs_for_queue_state(*, backend_alias, queue_name, state):
|
|
1184
|
-
|
|
1185
|
-
queryset = (
|
|
1186
|
-
Job.objects.using(alias)
|
|
1187
|
-
.filter(backend_alias=backend_alias, queue_name=queue_name)
|
|
1188
|
-
.select_related(
|
|
1189
|
-
"ready_execution",
|
|
1190
|
-
"scheduled_execution",
|
|
1191
|
-
"claimed_execution__process",
|
|
1192
|
-
"blocked_execution",
|
|
1193
|
-
"failed_execution",
|
|
1194
|
-
)
|
|
1195
|
-
)
|
|
1196
|
-
state_config = QUEUE_STATE_CONFIG[state]
|
|
1197
|
-
return queryset.filter(**state_config["query_filter"]).order_by(*state_config["query_order"])
|
|
1153
|
+
return queue_state_queryset(backend_alias=backend_alias, queue_name=queue_name, state=state)
|
|
1198
1154
|
|
|
1199
1155
|
|
|
1200
1156
|
def _next_run_at(schedule, now):
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
from dj_queue import observability
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass(frozen=True, slots=True)
|
|
7
|
+
class MetricSample:
|
|
8
|
+
labels: tuple[str, ...]
|
|
9
|
+
value: float
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass(frozen=True, slots=True)
|
|
13
|
+
class MetricFamily:
|
|
14
|
+
name: str
|
|
15
|
+
help_text: str
|
|
16
|
+
labels: tuple[str, ...]
|
|
17
|
+
samples: tuple[MetricSample, ...]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def metric_families(*, snapshots=None):
|
|
21
|
+
if snapshots is None:
|
|
22
|
+
snapshots = observability.all_backend_snapshots()
|
|
23
|
+
|
|
24
|
+
queue_jobs = []
|
|
25
|
+
queue_paused = []
|
|
26
|
+
queue_latency = []
|
|
27
|
+
queue_workers = []
|
|
28
|
+
runner_processes = []
|
|
29
|
+
runner_processes_by_kind = []
|
|
30
|
+
recurring_tasks = []
|
|
31
|
+
semaphores = []
|
|
32
|
+
process_rows = []
|
|
33
|
+
seen_queue_databases = set()
|
|
34
|
+
|
|
35
|
+
for snapshot in snapshots:
|
|
36
|
+
backend_alias = snapshot["backend_alias"]
|
|
37
|
+
queue_database_alias = snapshot["queue_database_alias"]
|
|
38
|
+
runner_metrics = snapshot["runner_metrics"]
|
|
39
|
+
|
|
40
|
+
for queue in snapshot["queue_rows"]:
|
|
41
|
+
for state in ("ready", "claimed", "scheduled", "blocked", "failed", "finished"):
|
|
42
|
+
queue_jobs.append(
|
|
43
|
+
MetricSample(
|
|
44
|
+
labels=(backend_alias, queue["name"], state),
|
|
45
|
+
value=queue[f"{state}_count"],
|
|
46
|
+
)
|
|
47
|
+
)
|
|
48
|
+
queue_paused.append(
|
|
49
|
+
MetricSample(
|
|
50
|
+
labels=(backend_alias, queue["name"]),
|
|
51
|
+
value=1 if queue["paused"] else 0,
|
|
52
|
+
)
|
|
53
|
+
)
|
|
54
|
+
if queue["latency_seconds"] is not None:
|
|
55
|
+
queue_latency.append(
|
|
56
|
+
MetricSample(
|
|
57
|
+
labels=(backend_alias, queue["name"]),
|
|
58
|
+
value=queue["latency_seconds"],
|
|
59
|
+
)
|
|
60
|
+
)
|
|
61
|
+
queue_workers.append(
|
|
62
|
+
MetricSample(
|
|
63
|
+
labels=(backend_alias, queue["name"]),
|
|
64
|
+
value=queue["live_worker_count"],
|
|
65
|
+
)
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
for status in ("live", "stale"):
|
|
69
|
+
runner_processes.append(
|
|
70
|
+
MetricSample(
|
|
71
|
+
labels=(backend_alias, status),
|
|
72
|
+
value=runner_metrics[status],
|
|
73
|
+
)
|
|
74
|
+
)
|
|
75
|
+
for kind, counts in sorted(runner_metrics["by_kind"].items()):
|
|
76
|
+
for status in ("live", "stale"):
|
|
77
|
+
runner_processes_by_kind.append(
|
|
78
|
+
MetricSample(
|
|
79
|
+
labels=(backend_alias, kind, status),
|
|
80
|
+
value=counts.get(status, 0),
|
|
81
|
+
)
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
recurring_tasks.append(
|
|
85
|
+
MetricSample(
|
|
86
|
+
labels=(backend_alias,),
|
|
87
|
+
value=len(snapshot["recurring_rows"]),
|
|
88
|
+
)
|
|
89
|
+
)
|
|
90
|
+
process_rows.append(
|
|
91
|
+
MetricSample(
|
|
92
|
+
labels=(backend_alias,),
|
|
93
|
+
value=len(snapshot["process_rows"]),
|
|
94
|
+
)
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
if queue_database_alias in seen_queue_databases:
|
|
98
|
+
continue
|
|
99
|
+
seen_queue_databases.add(queue_database_alias)
|
|
100
|
+
semaphores.append(
|
|
101
|
+
MetricSample(
|
|
102
|
+
labels=(queue_database_alias,),
|
|
103
|
+
value=len(snapshot["semaphore_rows"]),
|
|
104
|
+
)
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
return (
|
|
108
|
+
MetricFamily(
|
|
109
|
+
name="dj_queue_queue_jobs",
|
|
110
|
+
help_text="Current job count by backend, queue, and state",
|
|
111
|
+
labels=("backend", "queue", "state"),
|
|
112
|
+
samples=tuple(queue_jobs),
|
|
113
|
+
),
|
|
114
|
+
MetricFamily(
|
|
115
|
+
name="dj_queue_queue_paused",
|
|
116
|
+
help_text="Whether a queue is paused for a backend",
|
|
117
|
+
labels=("backend", "queue"),
|
|
118
|
+
samples=tuple(queue_paused),
|
|
119
|
+
),
|
|
120
|
+
MetricFamily(
|
|
121
|
+
name="dj_queue_queue_latency_seconds",
|
|
122
|
+
help_text="Latency of the oldest ready job in a backend queue",
|
|
123
|
+
labels=("backend", "queue"),
|
|
124
|
+
samples=tuple(queue_latency),
|
|
125
|
+
),
|
|
126
|
+
MetricFamily(
|
|
127
|
+
name="dj_queue_queue_live_workers",
|
|
128
|
+
help_text="Live workers that can service a backend queue",
|
|
129
|
+
labels=("backend", "queue"),
|
|
130
|
+
samples=tuple(queue_workers),
|
|
131
|
+
),
|
|
132
|
+
MetricFamily(
|
|
133
|
+
name="dj_queue_runner_processes",
|
|
134
|
+
help_text="Current runner process count by backend and liveness",
|
|
135
|
+
labels=("backend", "status"),
|
|
136
|
+
samples=tuple(runner_processes),
|
|
137
|
+
),
|
|
138
|
+
MetricFamily(
|
|
139
|
+
name="dj_queue_runner_processes_by_kind",
|
|
140
|
+
help_text="Current runner process count by backend, kind, and liveness",
|
|
141
|
+
labels=("backend", "kind", "status"),
|
|
142
|
+
samples=tuple(runner_processes_by_kind),
|
|
143
|
+
),
|
|
144
|
+
MetricFamily(
|
|
145
|
+
name="dj_queue_recurring_tasks",
|
|
146
|
+
help_text="Current recurring task count by backend",
|
|
147
|
+
labels=("backend",),
|
|
148
|
+
samples=tuple(recurring_tasks),
|
|
149
|
+
),
|
|
150
|
+
MetricFamily(
|
|
151
|
+
name="dj_queue_semaphores",
|
|
152
|
+
help_text="Current semaphore count by queue database",
|
|
153
|
+
labels=("queue_database",),
|
|
154
|
+
samples=tuple(semaphores),
|
|
155
|
+
),
|
|
156
|
+
MetricFamily(
|
|
157
|
+
name="dj_queue_process_rows",
|
|
158
|
+
help_text="Current process row count by backend",
|
|
159
|
+
labels=("backend",),
|
|
160
|
+
samples=tuple(process_rows),
|
|
161
|
+
),
|
|
162
|
+
)
|
|
@@ -25,6 +25,7 @@ from dj_queue.models import (
|
|
|
25
25
|
ScheduledExecution,
|
|
26
26
|
Semaphore,
|
|
27
27
|
)
|
|
28
|
+
from dj_queue.queue_state import queue_state_counts
|
|
28
29
|
|
|
29
30
|
|
|
30
31
|
@dataclass(frozen=True, slots=True)
|
|
@@ -414,22 +415,6 @@ def semaphore_rows_for_backend(*, backend_alias):
|
|
|
414
415
|
]
|
|
415
416
|
|
|
416
417
|
|
|
417
|
-
def queue_state_counts(*, backend_alias, queue_name):
|
|
418
|
-
alias = get_database_alias(backend_alias)
|
|
419
|
-
base_queryset = Job.objects.using(alias).filter(
|
|
420
|
-
backend_alias=backend_alias,
|
|
421
|
-
queue_name=queue_name,
|
|
422
|
-
)
|
|
423
|
-
return {
|
|
424
|
-
"ready": base_queryset.filter(ready_execution__isnull=False).count(),
|
|
425
|
-
"claimed": base_queryset.filter(claimed_execution__isnull=False).count(),
|
|
426
|
-
"scheduled": base_queryset.filter(scheduled_execution__isnull=False).count(),
|
|
427
|
-
"blocked": base_queryset.filter(blocked_execution__isnull=False).count(),
|
|
428
|
-
"failed": base_queryset.filter(failed_execution__isnull=False).count(),
|
|
429
|
-
"finished": base_queryset.filter(finished_at__isnull=False).count(),
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
|
|
433
418
|
def next_run_at(schedule, now):
|
|
434
419
|
return next_cron_run(schedule, now)
|
|
435
420
|
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
from dj_queue.db import database_capabilities
|
|
4
|
+
from dj_queue.exceptions import EnqueueError
|
|
5
|
+
from dj_queue.models import BlockedExecution, Pause, ReadyExecution, ScheduledExecution
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _normalize_payload(args, kwargs):
|
|
9
|
+
try:
|
|
10
|
+
return json.loads(json.dumps({"args": list(args), "kwargs": dict(kwargs)}))
|
|
11
|
+
except (TypeError, ValueError) as exc:
|
|
12
|
+
raise EnqueueError("payload must be JSON round-trippable") from exc
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _task_option(task, name, default=None):
|
|
16
|
+
if hasattr(task, name):
|
|
17
|
+
return getattr(task, name)
|
|
18
|
+
return getattr(task.func, name, default)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _lock_active_pauses(alias, backend_alias, queue_names=None):
|
|
22
|
+
queryset = Pause.objects.using(alias).select_for_update().filter(backend_alias=backend_alias)
|
|
23
|
+
if queue_names is not None:
|
|
24
|
+
active_queue_names = tuple(queue_name for queue_name in queue_names if queue_name)
|
|
25
|
+
if not active_queue_names:
|
|
26
|
+
return set()
|
|
27
|
+
queryset = queryset.filter(queue_name__in=active_queue_names)
|
|
28
|
+
return set(queryset.values_list("queue_name", flat=True))
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _ready_execution_row(
|
|
32
|
+
job,
|
|
33
|
+
*,
|
|
34
|
+
backend_alias,
|
|
35
|
+
queue_name=None,
|
|
36
|
+
priority=None,
|
|
37
|
+
ready_at=None,
|
|
38
|
+
created_at=None,
|
|
39
|
+
):
|
|
40
|
+
return ReadyExecution(
|
|
41
|
+
**_ready_execution_fields(
|
|
42
|
+
job,
|
|
43
|
+
backend_alias=backend_alias,
|
|
44
|
+
queue_name=queue_name,
|
|
45
|
+
priority=priority,
|
|
46
|
+
ready_at=ready_at,
|
|
47
|
+
created_at=created_at,
|
|
48
|
+
)
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _create_ready_execution(
|
|
53
|
+
alias,
|
|
54
|
+
job,
|
|
55
|
+
*,
|
|
56
|
+
backend_alias,
|
|
57
|
+
queue_name=None,
|
|
58
|
+
priority=None,
|
|
59
|
+
ready_at=None,
|
|
60
|
+
created_at=None,
|
|
61
|
+
):
|
|
62
|
+
queue_name = _execution_queue_name(job, queue_name)
|
|
63
|
+
_lock_active_pauses(alias, backend_alias, {queue_name})
|
|
64
|
+
return ReadyExecution.objects.using(alias).create(
|
|
65
|
+
**_ready_execution_fields(
|
|
66
|
+
job,
|
|
67
|
+
backend_alias=backend_alias,
|
|
68
|
+
queue_name=queue_name,
|
|
69
|
+
priority=priority,
|
|
70
|
+
ready_at=ready_at,
|
|
71
|
+
created_at=created_at,
|
|
72
|
+
)
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _scheduled_execution_row(job, *, backend_alias, scheduled_at=None, created_at=None):
|
|
77
|
+
return ScheduledExecution(
|
|
78
|
+
**_scheduled_execution_fields(
|
|
79
|
+
job,
|
|
80
|
+
backend_alias=backend_alias,
|
|
81
|
+
scheduled_at=scheduled_at,
|
|
82
|
+
created_at=created_at,
|
|
83
|
+
)
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _create_scheduled_execution(alias, job, *, backend_alias, scheduled_at=None):
|
|
88
|
+
return ScheduledExecution.objects.using(alias).create(
|
|
89
|
+
**_scheduled_execution_fields(
|
|
90
|
+
job,
|
|
91
|
+
backend_alias=backend_alias,
|
|
92
|
+
scheduled_at=scheduled_at,
|
|
93
|
+
)
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _create_blocked_execution(
|
|
98
|
+
alias,
|
|
99
|
+
job,
|
|
100
|
+
*,
|
|
101
|
+
backend_alias,
|
|
102
|
+
concurrency_key=None,
|
|
103
|
+
expires_at,
|
|
104
|
+
queue_name=None,
|
|
105
|
+
priority=None,
|
|
106
|
+
):
|
|
107
|
+
return BlockedExecution.objects.using(alias).create(
|
|
108
|
+
**_blocked_execution_fields(
|
|
109
|
+
job,
|
|
110
|
+
backend_alias=backend_alias,
|
|
111
|
+
concurrency_key=concurrency_key,
|
|
112
|
+
expires_at=expires_at,
|
|
113
|
+
queue_name=queue_name,
|
|
114
|
+
priority=priority,
|
|
115
|
+
)
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _ready_execution_fields(
|
|
120
|
+
job,
|
|
121
|
+
*,
|
|
122
|
+
backend_alias,
|
|
123
|
+
queue_name=None,
|
|
124
|
+
priority=None,
|
|
125
|
+
ready_at=None,
|
|
126
|
+
created_at=None,
|
|
127
|
+
):
|
|
128
|
+
fields = {
|
|
129
|
+
"job": job,
|
|
130
|
+
"backend_alias": backend_alias,
|
|
131
|
+
"queue_name": _execution_queue_name(job, queue_name),
|
|
132
|
+
"priority": _execution_priority(job, priority),
|
|
133
|
+
"latency_started_at": ready_at,
|
|
134
|
+
}
|
|
135
|
+
if created_at is not None:
|
|
136
|
+
fields["created_at"] = created_at
|
|
137
|
+
return fields
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _scheduled_execution_fields(job, *, backend_alias, scheduled_at=None, created_at=None):
|
|
141
|
+
fields = {
|
|
142
|
+
"job": job,
|
|
143
|
+
"backend_alias": backend_alias,
|
|
144
|
+
"queue_name": job.queue_name,
|
|
145
|
+
"priority": job.priority,
|
|
146
|
+
"scheduled_at": scheduled_at if scheduled_at is not None else job.scheduled_at,
|
|
147
|
+
}
|
|
148
|
+
if created_at is not None:
|
|
149
|
+
fields["created_at"] = created_at
|
|
150
|
+
return fields
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _blocked_execution_fields(
|
|
154
|
+
job,
|
|
155
|
+
*,
|
|
156
|
+
backend_alias,
|
|
157
|
+
concurrency_key=None,
|
|
158
|
+
expires_at,
|
|
159
|
+
queue_name=None,
|
|
160
|
+
priority=None,
|
|
161
|
+
):
|
|
162
|
+
return {
|
|
163
|
+
"job": job,
|
|
164
|
+
"backend_alias": backend_alias,
|
|
165
|
+
"queue_name": _execution_queue_name(job, queue_name),
|
|
166
|
+
"priority": _execution_priority(job, priority),
|
|
167
|
+
"concurrency_key": concurrency_key if concurrency_key is not None else job.concurrency_key,
|
|
168
|
+
"expires_at": expires_at,
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def _execution_queue_name(job, queue_name):
|
|
173
|
+
return job.queue_name if queue_name is None else queue_name
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _execution_priority(job, priority):
|
|
177
|
+
return job.priority if priority is None else priority
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def _consume_selected_rows(alias, model, rows):
|
|
181
|
+
if not database_capabilities(alias).uses_serialized_writes:
|
|
182
|
+
model.objects.using(alias).filter(pk__in=[row.pk for row in rows]).delete()
|
|
183
|
+
return rows
|
|
184
|
+
|
|
185
|
+
consumed_rows = []
|
|
186
|
+
for row in rows:
|
|
187
|
+
deleted, _ = model.objects.using(alias).filter(pk=row.pk).delete()
|
|
188
|
+
if deleted:
|
|
189
|
+
consumed_rows.append(row)
|
|
190
|
+
return consumed_rows
|
|
@@ -12,7 +12,8 @@ from dj_queue.log import log_event
|
|
|
12
12
|
from dj_queue.models import BlockedExecution, ClaimedExecution, ReadyExecution, Semaphore
|
|
13
13
|
from dj_queue.operations._helpers import (
|
|
14
14
|
_consume_selected_rows,
|
|
15
|
-
|
|
15
|
+
_create_blocked_execution,
|
|
16
|
+
_create_ready_execution,
|
|
16
17
|
_task_option,
|
|
17
18
|
)
|
|
18
19
|
from dj_queue.operations._insert import create_ignore_conflicts
|
|
@@ -170,13 +171,13 @@ def unblock_next_blocked_job(
|
|
|
170
171
|
queue_name = blocked.queue_name
|
|
171
172
|
priority = blocked.priority
|
|
172
173
|
blocked.delete(using=alias)
|
|
173
|
-
|
|
174
|
-
|
|
174
|
+
_create_ready_execution(
|
|
175
|
+
alias,
|
|
175
176
|
job=job,
|
|
176
177
|
backend_alias=backend_alias,
|
|
177
178
|
queue_name=queue_name,
|
|
178
179
|
priority=priority,
|
|
179
|
-
|
|
180
|
+
ready_at=now,
|
|
180
181
|
)
|
|
181
182
|
|
|
182
183
|
log_event(
|
|
@@ -258,20 +259,21 @@ def promote_expired_blocked_jobs(*, batch_size=500, backend_alias="default", use
|
|
|
258
259
|
priority = blocked.priority
|
|
259
260
|
if not uses_serialized_writes:
|
|
260
261
|
blocked.delete(using=alias)
|
|
261
|
-
|
|
262
|
-
|
|
262
|
+
_create_ready_execution(
|
|
263
|
+
alias,
|
|
263
264
|
job=job,
|
|
264
265
|
backend_alias=backend_alias,
|
|
265
266
|
queue_name=queue_name,
|
|
266
267
|
priority=priority,
|
|
267
|
-
|
|
268
|
+
ready_at=now,
|
|
268
269
|
)
|
|
269
270
|
promoted_jobs.append(job)
|
|
270
271
|
else:
|
|
271
272
|
expires_at = now + timedelta(seconds=duration_seconds)
|
|
272
273
|
if uses_serialized_writes:
|
|
273
|
-
|
|
274
|
-
|
|
274
|
+
_create_blocked_execution(
|
|
275
|
+
alias,
|
|
276
|
+
job,
|
|
275
277
|
backend_alias=backend_alias,
|
|
276
278
|
queue_name=blocked.queue_name,
|
|
277
279
|
priority=blocked.priority,
|