From 1aa989b352bb20dbd8fd2b304cfe4cbf50d10475 Mon Sep 17 00:00:00 2001 From: Julien Veyssier Date: Mon, 8 Jul 2024 18:04:46 +0200 Subject: [PATCH] feat(taskprocessing): add start, stop and schedule time to tasks Signed-off-by: Julien Veyssier --- .../Version30000Date20240708160048.php | 56 ++++++++++++++++++ lib/composer/composer/autoload_classmap.php | 1 + lib/composer/composer/autoload_static.php | 1 + lib/private/TaskProcessing/Db/Task.php | 18 ++++++ lib/private/TaskProcessing/Manager.php | 19 ++++++- lib/public/TaskProcessing/Task.php | 57 ++++++++++++++++++- version.php | 2 +- 7 files changed, 151 insertions(+), 3 deletions(-) create mode 100644 core/Migrations/Version30000Date20240708160048.php diff --git a/core/Migrations/Version30000Date20240708160048.php b/core/Migrations/Version30000Date20240708160048.php new file mode 100644 index 0000000000000..a85eea2c9740f --- /dev/null +++ b/core/Migrations/Version30000Date20240708160048.php @@ -0,0 +1,56 @@ +hasTable('taskprocessing_tasks')) { + $table = $schema->getTable('taskprocessing_tasks'); + + $table->addColumn('scheduled_at', Types::INTEGER, [ + 'notnull' => false, + 'default' => 0, + 'unsigned' => true, + ]); + $table->addColumn('started_at', Types::INTEGER, [ + 'notnull' => false, + 'default' => 0, + 'unsigned' => true, + ]); + $table->addColumn('ended_at', Types::INTEGER, [ + 'notnull' => false, + 'default' => 0, + 'unsigned' => true, + ]); + + return $schema; + } + + return null; + } +} diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index f3ab2e6eaaea1..5a9fb1cf9650e 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -1319,6 +1319,7 @@ 'OC\\Core\\Migrations\\Version29000Date20240124132202' => $baseDir . '/core/Migrations/Version29000Date20240124132202.php', 'OC\\Core\\Migrations\\Version29000Date20240131122720' => $baseDir . '/core/Migrations/Version29000Date20240131122720.php', 'OC\\Core\\Migrations\\Version30000Date20240429122720' => $baseDir . '/core/Migrations/Version30000Date20240429122720.php', + 'OC\\Core\\Migrations\\Version30000Date20240708160048' => $baseDir . '/core/Migrations/Version30000Date20240708160048.php', 'OC\\Core\\Notification\\CoreNotifier' => $baseDir . '/core/Notification/CoreNotifier.php', 'OC\\Core\\ResponseDefinitions' => $baseDir . '/core/ResponseDefinitions.php', 'OC\\Core\\Service\\LoginFlowV2Service' => $baseDir . '/core/Service/LoginFlowV2Service.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index f3edbf27f3df5..11f58d252058c 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -1352,6 +1352,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OC\\Core\\Migrations\\Version29000Date20240124132202' => __DIR__ . '/../../..' . '/core/Migrations/Version29000Date20240124132202.php', 'OC\\Core\\Migrations\\Version29000Date20240131122720' => __DIR__ . '/../../..' . '/core/Migrations/Version29000Date20240131122720.php', 'OC\\Core\\Migrations\\Version30000Date20240429122720' => __DIR__ . '/../../..' . '/core/Migrations/Version30000Date20240429122720.php', + 'OC\\Core\\Migrations\\Version30000Date20240708160048' => __DIR__ . '/../../..' . '/core/Migrations/Version30000Date20240708160048.php', 'OC\\Core\\Notification\\CoreNotifier' => __DIR__ . '/../../..' . '/core/Notification/CoreNotifier.php', 'OC\\Core\\ResponseDefinitions' => __DIR__ . '/../../..' . '/core/ResponseDefinitions.php', 'OC\\Core\\Service\\LoginFlowV2Service' => __DIR__ . '/../../..' . '/core/Service/LoginFlowV2Service.php', diff --git a/lib/private/TaskProcessing/Db/Task.php b/lib/private/TaskProcessing/Db/Task.php index 1ac403278990d..8278f18ee93a0 100644 --- a/lib/private/TaskProcessing/Db/Task.php +++ b/lib/private/TaskProcessing/Db/Task.php @@ -35,6 +35,12 @@ * @method null|string getErrorMessage() * @method setProgress(null|float $progress) * @method null|float getProgress() + * @method setScheduledAt(int $scheduledAt) + * @method int getScheduledAt() + * @method setStartedAt(int $startedAt) + * @method int getStartedAt() + * @method setEndedAt(int $endedAt) + * @method int getEndedAt() */ class Task extends Entity { protected $lastUpdated; @@ -48,6 +54,9 @@ class Task extends Entity { protected $completionExpectedAt; protected $errorMessage; protected $progress; + protected $scheduledAt; + protected $startedAt; + protected $endedAt; /** * @var string[] @@ -74,6 +83,9 @@ public function __construct() { $this->addType('completionExpectedAt', 'datetime'); $this->addType('errorMessage', 'string'); $this->addType('progress', 'float'); + $this->addType('scheduleAt', 'integer'); + $this->addType('startedAt', 'integer'); + $this->addType('endedAt', 'integer'); } public function toRow(): array { @@ -97,6 +109,9 @@ public static function fromPublicTask(OCPTask $task): self { 'customId' => $task->getCustomId(), 'completionExpectedAt' => $task->getCompletionExpectedAt(), 'progress' => $task->getProgress(), + 'scheduledAt' => $task->getScheduledAt(), + 'startedAt' => $task->getStartedAt(), + 'endedAt' => $task->getEndedAt(), ]); return $taskEntity; } @@ -114,6 +129,9 @@ public function toPublicTask(): OCPTask { $task->setCompletionExpectedAt($this->getCompletionExpectedAt()); $task->setErrorMessage($this->getErrorMessage()); $task->setProgress($this->getProgress()); + $task->setScheduledAt($this->getScheduledAt()); + $task->setStartedAt($this->getStartedAt()); + $task->setEndedAt($this->getEndedAt()); return $task; } } diff --git a/lib/private/TaskProcessing/Manager.php b/lib/private/TaskProcessing/Manager.php index f0d1d4ba51a33..4fbaf3fe566bd 100644 --- a/lib/private/TaskProcessing/Manager.php +++ b/lib/private/TaskProcessing/Manager.php @@ -578,6 +578,7 @@ public function scheduleTask(Task $task): void { // remove superfluous keys and set input $task->setInput($this->removeSuperfluousArrayKeys($task->getInput(), $inputShape, $optionalInputShape)); $task->setStatus(Task::STATUS_SCHEDULED); + $task->setScheduledAt(time()); $provider = $this->getPreferredProvider($task->getTaskTypeId()); // calculate expected completion time $completionExpectedAt = new \DateTime('now'); @@ -618,6 +619,7 @@ public function cancelTask(int $id): void { return; } $task->setStatus(Task::STATUS_CANCELLED); + $task->setEndedAt(time()); $taskEntity = \OC\TaskProcessing\Db\Task::fromPublicTask($task); try { $this->taskMapper->update($taskEntity); @@ -632,6 +634,10 @@ public function setTaskProgress(int $id, float $progress): bool { if ($task->getStatus() === Task::STATUS_CANCELLED) { return false; } + // only set the start time if the task is going from scheduled to running + if ($task->getstatus() === Task::STATUS_SCHEDULED) { + $task->setStartedAt(time()); + } $task->setStatus(Task::STATUS_RUNNING); $task->setProgress($progress); $taskEntity = \OC\TaskProcessing\Db\Task::fromPublicTask($task); @@ -652,6 +658,7 @@ public function setTaskResult(int $id, ?string $error, ?array $result): void { } if ($error !== null) { $task->setStatus(Task::STATUS_FAILED); + $task->setEndedAt(time()); $task->setErrorMessage($error); $this->logger->warning('A TaskProcessing ' . $task->getTaskTypeId() . ' task with id ' . $id . ' failed with the following message: ' . $error); } elseif ($result !== null) { @@ -668,19 +675,21 @@ public function setTaskResult(int $id, ?string $error, ?array $result): void { $task->setOutput($output); $task->setProgress(1); $task->setStatus(Task::STATUS_SUCCESSFUL); + $task->setEndedAt(time()); } catch (ValidationException $e) { $task->setProgress(1); $task->setStatus(Task::STATUS_FAILED); + $task->setEndedAt(time()); $error = 'The task was processed successfully but the provider\'s output doesn\'t pass validation against the task type\'s outputShape spec and/or the provider\'s own optionalOutputShape spec'; $task->setErrorMessage($error); $this->logger->error($error, ['exception' => $e]); } catch (NotPermittedException $e) { $task->setProgress(1); $task->setStatus(Task::STATUS_FAILED); + $task->setEndedAt(time()); $error = 'The task was processed successfully but storing the output in a file failed'; $task->setErrorMessage($error); $this->logger->error($error, ['exception' => $e]); - } } $taskEntity = \OC\TaskProcessing\Db\Task::fromPublicTask($task); @@ -880,6 +889,14 @@ public function lockTask(Task $task): bool { * @throws Exception */ public function setTaskStatus(Task $task, int $status): void { + $currentTaskStatus = $task->getStatus(); + if ($currentTaskStatus === Task::STATUS_SCHEDULED && $status === Task::STATUS_RUNNING) { + $task->setStartedAt(time()); + } elseif ($currentTaskStatus === Task::STATUS_RUNNING && ($status === Task::STATUS_FAILED || $status === Task::STATUS_CANCELLED)) { + $task->setEndedAt(time()); + } elseif ($currentTaskStatus === Task::STATUS_UNKNOWN && $status === Task::STATUS_SCHEDULED) { + $task->setScheduledAt(time()); + } $task->setStatus($status); $taskEntity = \OC\TaskProcessing\Db\Task::fromPublicTask($task); $this->taskMapper->update($taskEntity); diff --git a/lib/public/TaskProcessing/Task.php b/lib/public/TaskProcessing/Task.php index 2ec367e4a0a3f..9381285487ec6 100644 --- a/lib/public/TaskProcessing/Task.php +++ b/lib/public/TaskProcessing/Task.php @@ -60,6 +60,10 @@ final class Task implements \JsonSerializable { */ protected int $status = self::STATUS_UNKNOWN; + protected ?int $scheduledAt = null; + protected ?int $startedAt = null; + protected ?int $endedAt = null; + /** * @param string $taskTypeId * @param array|numeric|string> $input @@ -198,7 +202,55 @@ final public function setLastUpdated(int $lastUpdated): void { } /** - * @psalm-return array{id: ?int, lastUpdated: int, type: string, status: 'STATUS_CANCELLED'|'STATUS_FAILED'|'STATUS_SUCCESSFUL'|'STATUS_RUNNING'|'STATUS_SCHEDULED'|'STATUS_UNKNOWN', userId: ?string, appId: string, input: array|numeric|string>, output: ?array|numeric|string>, customId: ?string, completionExpectedAt: ?int, progress: ?float} + * @return int|null + * @since 30.0.0 + */ + final public function getScheduledAt(): ?int { + return $this->scheduledAt; + } + + /** + * @param int|null $scheduledAt + * @since 30.0.0 + */ + final public function setScheduledAt(?int $scheduledAt): void { + $this->scheduledAt = $scheduledAt; + } + + /** + * @return int|null + * @since 30.0.0 + */ + final public function getStartedAt(): ?int { + return $this->startedAt; + } + + /** + * @param int|null $startedAt + * @since 30.0.0 + */ + final public function setStartedAt(?int $startedAt): void { + $this->startedAt = $startedAt; + } + + /** + * @return int|null + * @since 30.0.0 + */ + final public function getEndedAt(): ?int { + return $this->endedAt; + } + + /** + * @param int|null $endedAt + * @since 30.0.0 + */ + final public function setEndedAt(?int $endedAt): void { + $this->endedAt = $endedAt; + } + + /** + * @psalm-return array{id: ?int, lastUpdated: int, type: string, status: 'STATUS_CANCELLED'|'STATUS_FAILED'|'STATUS_SUCCESSFUL'|'STATUS_RUNNING'|'STATUS_SCHEDULED'|'STATUS_UNKNOWN', userId: ?string, appId: string, input: array|numeric|string>, output: ?array|numeric|string>, customId: ?string, completionExpectedAt: ?int, progress: ?float, scheduledAt: ?int, startedAt: ?int, endedAt: ?int} * @since 30.0.0 */ final public function jsonSerialize(): array { @@ -214,6 +266,9 @@ final public function jsonSerialize(): array { 'customId' => $this->getCustomId(), 'completionExpectedAt' => $this->getCompletionExpectedAt()?->getTimestamp(), 'progress' => $this->getProgress(), + 'scheduledAt' => $this->getScheduledAt(), + 'startedAt' => $this->getStartedAt(), + 'endedAt' => $this->getEndedAt(), ]; } diff --git a/version.php b/version.php index 28e91a4702c46..44fbf088d31c0 100644 --- a/version.php +++ b/version.php @@ -9,7 +9,7 @@ // between betas, final and RCs. This is _not_ the public version number. Reset minor/patch level // when updating major/minor version number. -$OC_Version = [30, 0, 0, 1]; +$OC_Version = [30, 0, 0, 2]; // The human-readable string $OC_VersionString = '30.0.0 dev';