From df086a8c207ec6765a94955f6638fa7aacf4c06a 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 | 3 +- lib/composer/composer/autoload_static.php | 11 ++-- lib/private/TaskProcessing/Db/Task.php | 18 ++++++ lib/private/TaskProcessing/Manager.php | 19 +++++++ lib/public/TaskProcessing/Task.php | 57 ++++++++++++++++++- 6 files changed, 157 insertions(+), 7 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 00000000000..a85eea2c974 --- /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 c074a50f26e..d4c923086dc 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -1326,7 +1326,8 @@ return array( '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\\Version30000Date20240717111406' => $baseDir . '/core/Migrations/Version30000Date20240717111406.php', + 'OC\\Core\\Migrations\\Version30000Date20240708160048' => $baseDir . '/core/Migrations/Version30000Date20240708160048.php', + 'OC\\Core\\Migrations\\Version30000Date20240717111406' => $baseDir . '/core/Migrations/Version30000Date20240717111406.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 61df306bda3..34a2eb8ffe2 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -11,7 +11,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 ); public static $prefixLengthsPsr4 = array ( - 'O' => + 'O' => array ( 'OC\\Core\\' => 8, 'OC\\' => 3, @@ -20,15 +20,15 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 ); public static $prefixDirsPsr4 = array ( - 'OC\\Core\\' => + 'OC\\Core\\' => array ( 0 => __DIR__ . '/../../..' . '/core', ), - 'OC\\' => + 'OC\\' => array ( 0 => __DIR__ . '/../../..' . '/lib/private', ), - 'OCP\\' => + 'OCP\\' => array ( 0 => __DIR__ . '/../../..' . '/lib/public', ), @@ -1359,7 +1359,8 @@ 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\\Version30000Date20240717111406' => __DIR__ . '/../../..' . '/core/Migrations/Version30000Date20240717111406.php', + 'OC\\Core\\Migrations\\Version30000Date20240708160048' => __DIR__ . '/../../..' . '/core/Migrations/Version30000Date20240708160048.php', + 'OC\\Core\\Migrations\\Version30000Date20240717111406' => __DIR__ . '/../../..' . '/core/Migrations/Version30000Date20240717111406.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 9fc999faf1a..ea95f32b252 100644 --- a/lib/private/TaskProcessing/Db/Task.php +++ b/lib/private/TaskProcessing/Db/Task.php @@ -39,6 +39,12 @@ use OCP\TaskProcessing\Task as OCPTask; * @method string getWebhookUri() * @method setWebhookMethod(string $webhookMethod) * @method string getWebhookMethod() + * @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; @@ -54,6 +60,9 @@ class Task extends Entity { protected $progress; protected $webhookUri; protected $webhookMethod; + protected $scheduledAt; + protected $startedAt; + protected $endedAt; /** * @var string[] @@ -82,6 +91,9 @@ class Task extends Entity { $this->addType('progress', 'float'); $this->addType('webhookUri', 'string'); $this->addType('webhookMethod', 'string'); + $this->addType('scheduleAt', 'integer'); + $this->addType('startedAt', 'integer'); + $this->addType('endedAt', 'integer'); } public function toRow(): array { @@ -107,6 +119,9 @@ class Task extends Entity { 'progress' => $task->getProgress(), 'webhookUri' => $task->getWebhookUri(), 'webhookMethod' => $task->getWebhookMethod(), + 'scheduledAt' => $task->getScheduledAt(), + 'startedAt' => $task->getStartedAt(), + 'endedAt' => $task->getEndedAt(), ]); return $taskEntity; } @@ -126,6 +141,9 @@ class Task extends Entity { $task->setProgress($this->getProgress()); $task->setWebhookUri($this->getWebhookUri()); $task->setWebhookMethod($this->getWebhookMethod()); + $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 714a23ce5e2..2ddfc3d6e51 100644 --- a/lib/private/TaskProcessing/Manager.php +++ b/lib/private/TaskProcessing/Manager.php @@ -616,6 +616,7 @@ class Manager implements IManager { // 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'); @@ -656,6 +657,7 @@ class Manager implements IManager { return; } $task->setStatus(Task::STATUS_CANCELLED); + $task->setEndedAt(time()); $taskEntity = \OC\TaskProcessing\Db\Task::fromPublicTask($task); try { $this->taskMapper->update($taskEntity); @@ -671,6 +673,10 @@ class Manager implements IManager { 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); @@ -691,6 +697,7 @@ class Manager implements IManager { } 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) { @@ -725,21 +732,25 @@ class Manager implements IManager { $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]); } catch (InvalidPathException|\OCP\Files\NotFoundException $e) { $task->setProgress(1); $task->setStatus(Task::STATUS_FAILED); + $task->setEndedAt(time()); $error = 'The task was processed successfully but the result file could not be found'; $task->setErrorMessage($error); $this->logger->error($error, ['exception' => $e]); @@ -926,6 +937,14 @@ class Manager implements IManager { * @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 44834c3b846..db8e4d7fab5 100644 --- a/lib/public/TaskProcessing/Task.php +++ b/lib/public/TaskProcessing/Task.php @@ -63,6 +63,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 @@ -201,7 +205,55 @@ final class Task implements \JsonSerializable { } /** - * @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 { @@ -217,6 +269,9 @@ final class Task implements \JsonSerializable { 'customId' => $this->getCustomId(), 'completionExpectedAt' => $this->getCompletionExpectedAt()?->getTimestamp(), 'progress' => $this->getProgress(), + 'scheduledAt' => $this->getScheduledAt(), + 'startedAt' => $this->getStartedAt(), + 'endedAt' => $this->getEndedAt(), ]; }