From eed2cdd5b7c821af451f35a3d80f980c1f46ab04 Mon Sep 17 00:00:00 2001 From: Oleksander Piskun Date: Tue, 17 Feb 2026 13:18:08 +0200 Subject: [PATCH] feat(taskprocessing): Add queue_stats API endpoint for external autoscalers --- .../TaskProcessingApiController.php | 36 +++++++++++++++++++ lib/private/TaskProcessing/Db/TaskMapper.php | 20 +++++++++++ lib/private/TaskProcessing/Manager.php | 8 +++++ lib/public/TaskProcessing/IManager.php | 11 ++++++ 4 files changed, 75 insertions(+) diff --git a/core/Controller/TaskProcessingApiController.php b/core/Controller/TaskProcessingApiController.php index 5f2023dd0c242..6b2f266476238 100644 --- a/core/Controller/TaskProcessingApiController.php +++ b/core/Controller/TaskProcessingApiController.php @@ -424,6 +424,42 @@ public function listTasks(?string $taskType, ?string $customId = null): DataResp } } + /** + * Returns queue statistics for task processing + * + * Returns the count of scheduled and running tasks, optionally filtered + * by task type(s). Designed for external scalers (e.g. KEDA) to poll + * for task queue depth. Admin-only endpoint authenticated via app_password. + * + * @param string|null $taskTypeId Comma-separated list of task type IDs to filter by + * @return DataResponse|DataResponse + * + * 200: Queue stats returned + */ + #[NoCSRFRequired] + #[ApiRoute(verb: 'GET', url: '/queue_stats', root: '/taskprocessing')] + public function queueStats(?string $taskTypeId = null): DataResponse { + # TODO: bruteforce protection? + try { + $taskTypeIds = []; + if ($taskTypeId !== null) { + $taskTypeIds = array_map('trim', explode(',', $taskTypeId)); + $taskTypeIds = array_filter($taskTypeIds, fn (string $id) => $id !== ''); + $taskTypeIds = array_values($taskTypeIds); + } + + $scheduled = $this->taskProcessingManager->countTasks(Task::STATUS_SCHEDULED, $taskTypeIds); + $running = $this->taskProcessingManager->countTasks(Task::STATUS_RUNNING, $taskTypeIds); + + return new DataResponse([ + 'scheduled' => $scheduled, + 'running' => $running, + ]); + } catch (Exception) { + return new DataResponse(['message' => $this->l->t('Internal error')], Http::STATUS_INTERNAL_SERVER_ERROR); + } + } + /** * Returns the contents of a file referenced in a task * diff --git a/lib/private/TaskProcessing/Db/TaskMapper.php b/lib/private/TaskProcessing/Db/TaskMapper.php index f62bb41be3b77..34862e234e249 100644 --- a/lib/private/TaskProcessing/Db/TaskMapper.php +++ b/lib/private/TaskProcessing/Db/TaskMapper.php @@ -265,6 +265,26 @@ public function findNOldestScheduledByType(array $taskTypes, array $taskIdsToIgn return $this->findEntities($qb); } + /** + * @param list $taskTypeIds + * @param int $status + * @return int + * @throws Exception + */ + public function countByStatus(array $taskTypeIds, int $status): int { + $qb = $this->db->getQueryBuilder(); + $qb->select($qb->func()->count('id')) + ->from($this->tableName) + ->where($qb->expr()->eq('status', $qb->createNamedParameter($status, IQueryBuilder::PARAM_INT))); + if (!empty($taskTypeIds)) { + $qb->andWhere($qb->expr()->in('type', $qb->createNamedParameter($taskTypeIds, IQueryBuilder::PARAM_STR_ARRAY))); + } + $result = $qb->executeQuery(); + $count = (int)$result->fetchOne(); + $result->closeCursor(); + return $count; + } + /** * @throws Exception */ diff --git a/lib/private/TaskProcessing/Manager.php b/lib/private/TaskProcessing/Manager.php index 884499d34b0c7..ef8ece9c8233d 100644 --- a/lib/private/TaskProcessing/Manager.php +++ b/lib/private/TaskProcessing/Manager.php @@ -1357,6 +1357,14 @@ public function getTasks( } } + public function countTasks(int $status, array $taskTypeIds = []): int { + try { + return $this->taskMapper->countByStatus($taskTypeIds, $status); + } catch (\OCP\DB\Exception $e) { + throw new \OCP\TaskProcessing\Exception\Exception('There was a problem counting the tasks', 0, $e); + } + } + public function getUserTasksByApp(?string $userId, string $appId, ?string $customId = null): array { try { $taskEntities = $this->taskMapper->findUserTasksByApp($userId, $appId, $customId); diff --git a/lib/public/TaskProcessing/IManager.php b/lib/public/TaskProcessing/IManager.php index 878acfc134c0d..2cd0244b52e8d 100644 --- a/lib/public/TaskProcessing/IManager.php +++ b/lib/public/TaskProcessing/IManager.php @@ -258,6 +258,17 @@ public function lockTask(Task $task): bool; */ public function setTaskStatus(Task $task, int $status): void; + /** + * Get the count of tasks filtered by status and optionally by task type(s) + * + * @param int $status The task status to filter by + * @param list $taskTypeIds Optional list of task type IDs to filter by + * @return int The count of matching tasks + * @throws Exception If the query failed + * @since 34.0.0 + */ + public function countTasks(int $status, array $taskTypeIds = []): int; + /** * Extract all input and output file IDs from a task *