Skip to content

Commit f9d4bec

Browse files
authored
Merge pull request #46359 from nextcloud/enh/task-processing-more-datetimes
[TaskProcessing] Add start, stop and schedule time to tasks
2 parents a899ba2 + c004f53 commit f9d4bec

11 files changed

Lines changed: 521 additions & 3 deletions

File tree

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
<?php
2+
/**
3+
* SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
4+
* SPDX-License-Identifier: AGPL-3.0-or-later
5+
*/
6+
namespace OC\Core\Command\TaskProcessing;
7+
8+
use OC\Core\Command\Base;
9+
use OCP\TaskProcessing\IManager;
10+
use OCP\TaskProcessing\Task;
11+
use Symfony\Component\Console\Input\InputInterface;
12+
use Symfony\Component\Console\Input\InputOption;
13+
use Symfony\Component\Console\Output\OutputInterface;
14+
15+
class ListCommand extends Base {
16+
public function __construct(
17+
protected IManager $taskProcessingManager,
18+
) {
19+
parent::__construct();
20+
}
21+
22+
protected function configure() {
23+
$this
24+
->setName('taskprocessing:task:list')
25+
->setDescription('list tasks')
26+
->addOption(
27+
'userIdFilter',
28+
'u',
29+
InputOption::VALUE_OPTIONAL,
30+
'only get the tasks for one user ID'
31+
)
32+
->addOption(
33+
'type',
34+
't',
35+
InputOption::VALUE_OPTIONAL,
36+
'only get the tasks for one task type'
37+
)
38+
->addOption(
39+
'appId',
40+
null,
41+
InputOption::VALUE_OPTIONAL,
42+
'only get the tasks for one app ID'
43+
)
44+
->addOption(
45+
'customId',
46+
null,
47+
InputOption::VALUE_OPTIONAL,
48+
'only get the tasks for one custom ID'
49+
)
50+
->addOption(
51+
'status',
52+
's',
53+
InputOption::VALUE_OPTIONAL,
54+
'only get the tasks that have a specific status (STATUS_UNKNOWN=0, STATUS_SCHEDULED=1, STATUS_RUNNING=2, STATUS_SUCCESSFUL=3, STATUS_FAILED=4, STATUS_CANCELLED=5)'
55+
)
56+
->addOption(
57+
'scheduledAfter',
58+
null,
59+
InputOption::VALUE_OPTIONAL,
60+
'only get the tasks that were scheduled after a specific date (Unix timestamp)'
61+
)
62+
->addOption(
63+
'endedBefore',
64+
null,
65+
InputOption::VALUE_OPTIONAL,
66+
'only get the tasks that ended before a specific date (Unix timestamp)'
67+
);
68+
parent::configure();
69+
}
70+
71+
protected function execute(InputInterface $input, OutputInterface $output): int {
72+
$userIdFilter = $input->getOption('userIdFilter');
73+
if ($userIdFilter === null) {
74+
$userIdFilter = '';
75+
} elseif ($userIdFilter === '') {
76+
$userIdFilter = null;
77+
}
78+
$type = $input->getOption('type');
79+
$appId = $input->getOption('appId');
80+
$customId = $input->getOption('customId');
81+
$status = $input->getOption('status');
82+
$scheduledAfter = $input->getOption('scheduledAfter');
83+
$endedBefore = $input->getOption('endedBefore');
84+
85+
$tasks = $this->taskProcessingManager->getTasks($userIdFilter, $type, $appId, $customId, $status, $scheduledAfter, $endedBefore);
86+
$arrayTasks = array_map(fn (Task $task): array => $task->jsonSerialize(), $tasks);
87+
88+
$this->writeArrayInOutputFormat($input, $output, $arrayTasks);
89+
return 0;
90+
}
91+
}
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
<?php
2+
/**
3+
* SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
4+
* SPDX-License-Identifier: AGPL-3.0-or-later
5+
*/
6+
namespace OC\Core\Command\TaskProcessing;
7+
8+
use OC\Core\Command\Base;
9+
use OCP\TaskProcessing\IManager;
10+
use OCP\TaskProcessing\Task;
11+
use Symfony\Component\Console\Input\InputInterface;
12+
use Symfony\Component\Console\Input\InputOption;
13+
use Symfony\Component\Console\Output\OutputInterface;
14+
15+
class Statistics extends Base {
16+
public function __construct(
17+
protected IManager $taskProcessingManager,
18+
) {
19+
parent::__construct();
20+
}
21+
22+
protected function configure() {
23+
$this
24+
->setName('taskprocessing:task:stats')
25+
->setDescription('get statistics for tasks')
26+
->addOption(
27+
'userIdFilter',
28+
'u',
29+
InputOption::VALUE_OPTIONAL,
30+
'only get the tasks for one user ID'
31+
)
32+
->addOption(
33+
'type',
34+
't',
35+
InputOption::VALUE_OPTIONAL,
36+
'only get the tasks for one task type'
37+
)
38+
->addOption(
39+
'appId',
40+
null,
41+
InputOption::VALUE_OPTIONAL,
42+
'only get the tasks for one app ID'
43+
)
44+
->addOption(
45+
'customId',
46+
null,
47+
InputOption::VALUE_OPTIONAL,
48+
'only get the tasks for one custom ID'
49+
)
50+
->addOption(
51+
'status',
52+
's',
53+
InputOption::VALUE_OPTIONAL,
54+
'only get the tasks that have a specific status (STATUS_UNKNOWN=0, STATUS_SCHEDULED=1, STATUS_RUNNING=2, STATUS_SUCCESSFUL=3, STATUS_FAILED=4, STATUS_CANCELLED=5)'
55+
)
56+
->addOption(
57+
'scheduledAfter',
58+
null,
59+
InputOption::VALUE_OPTIONAL,
60+
'only get the tasks that were scheduled after a specific date (Unix timestamp)'
61+
)
62+
->addOption(
63+
'endedBefore',
64+
null,
65+
InputOption::VALUE_OPTIONAL,
66+
'only get the tasks that ended before a specific date (Unix timestamp)'
67+
);
68+
parent::configure();
69+
}
70+
71+
protected function execute(InputInterface $input, OutputInterface $output): int {
72+
$userIdFilter = $input->getOption('userIdFilter');
73+
if ($userIdFilter === null) {
74+
$userIdFilter = '';
75+
} elseif ($userIdFilter === '') {
76+
$userIdFilter = null;
77+
}
78+
$type = $input->getOption('type');
79+
$appId = $input->getOption('appId');
80+
$customId = $input->getOption('customId');
81+
$status = $input->getOption('status');
82+
$scheduledAfter = $input->getOption('scheduledAfter');
83+
$endedBefore = $input->getOption('endedBefore');
84+
85+
$tasks = $this->taskProcessingManager->getTasks($userIdFilter, $type, $appId, $customId, $status, $scheduledAfter, $endedBefore);
86+
87+
$stats = ['Number of tasks' => count($tasks)];
88+
89+
$maxRunningTime = 0;
90+
$totalRunningTime = 0;
91+
$runningTimeCount = 0;
92+
93+
$maxQueuingTime = 0;
94+
$totalQueuingTime = 0;
95+
$queuingTimeCount = 0;
96+
97+
$maxUserWaitingTime = 0;
98+
$totalUserWaitingTime = 0;
99+
$userWaitingTimeCount = 0;
100+
101+
$maxInputSize = 0;
102+
$maxOutputSize = 0;
103+
$inputCount = 0;
104+
$inputSum = 0;
105+
$outputCount = 0;
106+
$outputSum = 0;
107+
108+
foreach ($tasks as $task) {
109+
// running time
110+
if ($task->getStartedAt() !== null && $task->getEndedAt() !== null) {
111+
$taskRunningTime = $task->getEndedAt() - $task->getStartedAt();
112+
$totalRunningTime += $taskRunningTime;
113+
$runningTimeCount++;
114+
if ($taskRunningTime >= $maxRunningTime) {
115+
$maxRunningTime = $taskRunningTime;
116+
}
117+
}
118+
// queuing time
119+
if ($task->getScheduledAt() !== null && $task->getStartedAt() !== null) {
120+
$taskQueuingTime = $task->getStartedAt() - $task->getScheduledAt();
121+
$totalQueuingTime += $taskQueuingTime;
122+
$queuingTimeCount++;
123+
if ($taskQueuingTime >= $maxQueuingTime) {
124+
$maxQueuingTime = $taskQueuingTime;
125+
}
126+
}
127+
// user waiting time
128+
if ($task->getScheduledAt() !== null && $task->getEndedAt() !== null) {
129+
$taskUserWaitingTime = $task->getEndedAt() - $task->getScheduledAt();
130+
$totalUserWaitingTime += $taskUserWaitingTime;
131+
$userWaitingTimeCount++;
132+
if ($taskUserWaitingTime >= $maxUserWaitingTime) {
133+
$maxUserWaitingTime = $taskUserWaitingTime;
134+
}
135+
}
136+
// input/output sizes
137+
if ($task->getStatus() === Task::STATUS_SUCCESSFUL) {
138+
$outputString = json_encode($task->getOutput());
139+
if ($outputString !== false) {
140+
$outputCount++;
141+
$outputLength = strlen($outputString);
142+
$outputSum += $outputLength;
143+
if ($outputLength > $maxOutputSize) {
144+
$maxOutputSize = $outputLength;
145+
}
146+
}
147+
}
148+
$inputString = json_encode($task->getInput());
149+
if ($inputString !== false) {
150+
$inputCount++;
151+
$inputLength = strlen($inputString);
152+
$inputSum += $inputLength;
153+
if ($inputLength > $maxInputSize) {
154+
$maxInputSize = $inputLength;
155+
}
156+
}
157+
}
158+
159+
if ($runningTimeCount > 0) {
160+
$stats['Max running time'] = $maxRunningTime;
161+
$averageRunningTime = $totalRunningTime / $runningTimeCount;
162+
$stats['Average running time'] = (int)$averageRunningTime;
163+
$stats['Running time count'] = $runningTimeCount;
164+
}
165+
if ($queuingTimeCount > 0) {
166+
$stats['Max queuing time'] = $maxQueuingTime;
167+
$averageQueuingTime = $totalQueuingTime / $queuingTimeCount;
168+
$stats['Average queuing time'] = (int)$averageQueuingTime;
169+
$stats['Queuing time count'] = $queuingTimeCount;
170+
}
171+
if ($userWaitingTimeCount > 0) {
172+
$stats['Max user waiting time'] = $maxUserWaitingTime;
173+
$averageUserWaitingTime = $totalUserWaitingTime / $userWaitingTimeCount;
174+
$stats['Average user waiting time'] = (int)$averageUserWaitingTime;
175+
$stats['User waiting time count'] = $userWaitingTimeCount;
176+
}
177+
if ($outputCount > 0) {
178+
$stats['Max output size (bytes)'] = $maxOutputSize;
179+
$averageOutputSize = $outputSum / $outputCount;
180+
$stats['Average output size (bytes)'] = (int)$averageOutputSize;
181+
$stats['Number of tasks with output'] = $outputCount;
182+
}
183+
if ($inputCount > 0) {
184+
$stats['Max input size (bytes)'] = $maxInputSize;
185+
$averageInputSize = $inputSum / $inputCount;
186+
$stats['Average input size (bytes)'] = (int)$averageInputSize;
187+
$stats['Number of tasks with input'] = $inputCount;
188+
}
189+
190+
$this->writeArrayInOutputFormat($input, $output, $stats);
191+
return 0;
192+
}
193+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
7+
* SPDX-License-Identifier: AGPL-3.0-or-later
8+
*/
9+
namespace OC\Core\Migrations;
10+
11+
use Closure;
12+
use OCP\DB\ISchemaWrapper;
13+
use OCP\DB\Types;
14+
use OCP\Migration\IOutput;
15+
use OCP\Migration\SimpleMigrationStep;
16+
17+
/**
18+
*
19+
*/
20+
class Version30000Date20240708160048 extends SimpleMigrationStep {
21+
22+
/**
23+
* @param IOutput $output
24+
* @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
25+
* @param array $options
26+
* @return null|ISchemaWrapper
27+
*/
28+
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
29+
/** @var ISchemaWrapper $schema */
30+
$schema = $schemaClosure();
31+
32+
if ($schema->hasTable('taskprocessing_tasks')) {
33+
$table = $schema->getTable('taskprocessing_tasks');
34+
35+
$table->addColumn('scheduled_at', Types::INTEGER, [
36+
'notnull' => false,
37+
'default' => null,
38+
'unsigned' => true,
39+
]);
40+
$table->addColumn('started_at', Types::INTEGER, [
41+
'notnull' => false,
42+
'default' => null,
43+
'unsigned' => true,
44+
]);
45+
$table->addColumn('ended_at', Types::INTEGER, [
46+
'notnull' => false,
47+
'default' => null,
48+
'unsigned' => true,
49+
]);
50+
51+
return $schema;
52+
}
53+
54+
return null;
55+
}
56+
}

core/register_command.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,9 @@
140140
$application->add(Server::get(Command\Security\BruteforceResetAttempts::class));
141141
$application->add(Server::get(Command\SetupChecks::class));
142142
$application->add(Server::get(Command\FilesMetadata\Get::class));
143+
144+
$application->add(Server::get(Command\TaskProcessing\ListCommand::class));
145+
$application->add(Server::get(Command\TaskProcessing\Statistics::class));
143146
} else {
144147
$application->add(Server::get(Command\Maintenance\Install::class));
145148
}

lib/composer/composer/autoload_classmap.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1180,6 +1180,8 @@
11801180
'OC\\Core\\Command\\SystemTag\\Delete' => $baseDir . '/core/Command/SystemTag/Delete.php',
11811181
'OC\\Core\\Command\\SystemTag\\Edit' => $baseDir . '/core/Command/SystemTag/Edit.php',
11821182
'OC\\Core\\Command\\SystemTag\\ListCommand' => $baseDir . '/core/Command/SystemTag/ListCommand.php',
1183+
'OC\\Core\\Command\\TaskProcessing\\ListCommand' => $baseDir . '/core/Command/TaskProcessing/ListCommand.php',
1184+
'OC\\Core\\Command\\TaskProcessing\\Statistics' => $baseDir . '/core/Command/TaskProcessing/Statistics.php',
11831185
'OC\\Core\\Command\\TwoFactorAuth\\Base' => $baseDir . '/core/Command/TwoFactorAuth/Base.php',
11841186
'OC\\Core\\Command\\TwoFactorAuth\\Cleanup' => $baseDir . '/core/Command/TwoFactorAuth/Cleanup.php',
11851187
'OC\\Core\\Command\\TwoFactorAuth\\Disable' => $baseDir . '/core/Command/TwoFactorAuth/Disable.php',
@@ -1326,6 +1328,7 @@
13261328
'OC\\Core\\Migrations\\Version29000Date20240124132202' => $baseDir . '/core/Migrations/Version29000Date20240124132202.php',
13271329
'OC\\Core\\Migrations\\Version29000Date20240131122720' => $baseDir . '/core/Migrations/Version29000Date20240131122720.php',
13281330
'OC\\Core\\Migrations\\Version30000Date20240429122720' => $baseDir . '/core/Migrations/Version30000Date20240429122720.php',
1331+
'OC\\Core\\Migrations\\Version30000Date20240708160048' => $baseDir . '/core/Migrations/Version30000Date20240708160048.php',
13291332
'OC\\Core\\Migrations\\Version30000Date20240717111406' => $baseDir . '/core/Migrations/Version30000Date20240717111406.php',
13301333
'OC\\Core\\Notification\\CoreNotifier' => $baseDir . '/core/Notification/CoreNotifier.php',
13311334
'OC\\Core\\ResponseDefinitions' => $baseDir . '/core/ResponseDefinitions.php',

lib/composer/composer/autoload_static.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1213,6 +1213,8 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
12131213
'OC\\Core\\Command\\SystemTag\\Delete' => __DIR__ . '/../../..' . '/core/Command/SystemTag/Delete.php',
12141214
'OC\\Core\\Command\\SystemTag\\Edit' => __DIR__ . '/../../..' . '/core/Command/SystemTag/Edit.php',
12151215
'OC\\Core\\Command\\SystemTag\\ListCommand' => __DIR__ . '/../../..' . '/core/Command/SystemTag/ListCommand.php',
1216+
'OC\\Core\\Command\\TaskProcessing\\ListCommand' => __DIR__ . '/../../..' . '/core/Command/TaskProcessing/ListCommand.php',
1217+
'OC\\Core\\Command\\TaskProcessing\\Statistics' => __DIR__ . '/../../..' . '/core/Command/TaskProcessing/Statistics.php',
12161218
'OC\\Core\\Command\\TwoFactorAuth\\Base' => __DIR__ . '/../../..' . '/core/Command/TwoFactorAuth/Base.php',
12171219
'OC\\Core\\Command\\TwoFactorAuth\\Cleanup' => __DIR__ . '/../../..' . '/core/Command/TwoFactorAuth/Cleanup.php',
12181220
'OC\\Core\\Command\\TwoFactorAuth\\Disable' => __DIR__ . '/../../..' . '/core/Command/TwoFactorAuth/Disable.php',
@@ -1359,6 +1361,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
13591361
'OC\\Core\\Migrations\\Version29000Date20240124132202' => __DIR__ . '/../../..' . '/core/Migrations/Version29000Date20240124132202.php',
13601362
'OC\\Core\\Migrations\\Version29000Date20240131122720' => __DIR__ . '/../../..' . '/core/Migrations/Version29000Date20240131122720.php',
13611363
'OC\\Core\\Migrations\\Version30000Date20240429122720' => __DIR__ . '/../../..' . '/core/Migrations/Version30000Date20240429122720.php',
1364+
'OC\\Core\\Migrations\\Version30000Date20240708160048' => __DIR__ . '/../../..' . '/core/Migrations/Version30000Date20240708160048.php',
13621365
'OC\\Core\\Migrations\\Version30000Date20240717111406' => __DIR__ . '/../../..' . '/core/Migrations/Version30000Date20240717111406.php',
13631366
'OC\\Core\\Notification\\CoreNotifier' => __DIR__ . '/../../..' . '/core/Notification/CoreNotifier.php',
13641367
'OC\\Core\\ResponseDefinitions' => __DIR__ . '/../../..' . '/core/ResponseDefinitions.php',

0 commit comments

Comments
 (0)