Skip to content

Commit deffe3c

Browse files
authored
Merge pull request #55778 from nextcloud/backport/55716/stable32
[stable32] feat(settings): Introduce TaskProcessingSuccessRate setup check
2 parents 2130201 + 74b7ce6 commit deffe3c

4 files changed

Lines changed: 164 additions & 0 deletions

File tree

apps/settings/composer/composer/autoload_classmap.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@
134134
'OCA\\Settings\\SetupChecks\\SupportedDatabase' => $baseDir . '/../lib/SetupChecks/SupportedDatabase.php',
135135
'OCA\\Settings\\SetupChecks\\SystemIs64bit' => $baseDir . '/../lib/SetupChecks/SystemIs64bit.php',
136136
'OCA\\Settings\\SetupChecks\\TaskProcessingPickupSpeed' => $baseDir . '/../lib/SetupChecks/TaskProcessingPickupSpeed.php',
137+
'OCA\\Settings\\SetupChecks\\TaskProcessingSuccessRate' => $baseDir . '/../lib/SetupChecks/TaskProcessingSuccessRate.php',
137138
'OCA\\Settings\\SetupChecks\\TempSpaceAvailable' => $baseDir . '/../lib/SetupChecks/TempSpaceAvailable.php',
138139
'OCA\\Settings\\SetupChecks\\TransactionIsolation' => $baseDir . '/../lib/SetupChecks/TransactionIsolation.php',
139140
'OCA\\Settings\\SetupChecks\\WellKnownUrls' => $baseDir . '/../lib/SetupChecks/WellKnownUrls.php',

apps/settings/composer/composer/autoload_static.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ class ComposerStaticInitSettings
149149
'OCA\\Settings\\SetupChecks\\SupportedDatabase' => __DIR__ . '/..' . '/../lib/SetupChecks/SupportedDatabase.php',
150150
'OCA\\Settings\\SetupChecks\\SystemIs64bit' => __DIR__ . '/..' . '/../lib/SetupChecks/SystemIs64bit.php',
151151
'OCA\\Settings\\SetupChecks\\TaskProcessingPickupSpeed' => __DIR__ . '/..' . '/../lib/SetupChecks/TaskProcessingPickupSpeed.php',
152+
'OCA\\Settings\\SetupChecks\\TaskProcessingSuccessRate' => __DIR__ . '/..' . '/../lib/SetupChecks/TaskProcessingSuccessRate.php',
152153
'OCA\\Settings\\SetupChecks\\TempSpaceAvailable' => __DIR__ . '/..' . '/../lib/SetupChecks/TempSpaceAvailable.php',
153154
'OCA\\Settings\\SetupChecks\\TransactionIsolation' => __DIR__ . '/..' . '/../lib/SetupChecks/TransactionIsolation.php',
154155
'OCA\\Settings\\SetupChecks\\WellKnownUrls' => __DIR__ . '/..' . '/../lib/SetupChecks/WellKnownUrls.php',
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
7+
* SPDX-License-Identifier: AGPL-3.0-or-later
8+
*/
9+
10+
namespace OCA\Settings\SetupChecks;
11+
12+
use OCP\AppFramework\Utility\ITimeFactory;
13+
use OCP\IL10N;
14+
use OCP\SetupCheck\ISetupCheck;
15+
use OCP\SetupCheck\SetupResult;
16+
use OCP\TaskProcessing\IManager;
17+
use OCP\TaskProcessing\Task;
18+
19+
class TaskProcessingSuccessRate implements ISetupCheck {
20+
public const MAX_FAILURE_PERCENTAGE = 0.2;
21+
22+
public const MAX_DAYS = 14;
23+
24+
public function __construct(
25+
private IL10N $l10n,
26+
private IManager $taskProcessingManager,
27+
private ITimeFactory $timeFactory,
28+
) {
29+
}
30+
31+
public function getCategory(): string {
32+
return 'ai';
33+
}
34+
35+
public function getName(): string {
36+
return $this->l10n->t('Task Processing pickup speed');
37+
}
38+
39+
public function run(): SetupResult {
40+
$taskCount = 0;
41+
$lastNDays = 0;
42+
while ($taskCount === 0 && $lastNDays < self::MAX_DAYS) {
43+
$lastNDays++;
44+
// userId: '' means no filter, whereas null would mean guest
45+
$tasks = $this->taskProcessingManager->getTasks(userId: '', scheduleAfter: $this->timeFactory->now()->getTimestamp() - (60 * 60 * 24 * $lastNDays));
46+
$taskCount = count($tasks);
47+
}
48+
if ($taskCount === 0) {
49+
return SetupResult::success(
50+
$this->l10n->n(
51+
'No scheduled tasks in the last day.',
52+
'No scheduled tasks in the last %n days.',
53+
$lastNDays
54+
)
55+
);
56+
}
57+
$failedCount = 0;
58+
foreach ($tasks as $task) {
59+
if ($task->getEndedAt() === null) {
60+
continue; // task was not picked up yet
61+
}
62+
$status = $task->getStatus();
63+
if ($status === Task::STATUS_FAILED) {
64+
$failedCount++;
65+
}
66+
}
67+
68+
if (($failedCount / $taskCount) < self::MAX_FAILURE_PERCENTAGE) {
69+
return SetupResult::success(
70+
$this->l10n->n(
71+
'Most tasks were successful in the last day.',
72+
'Most tasks were successful in the last %n days.',
73+
$lastNDays
74+
)
75+
);
76+
} else {
77+
return SetupResult::warning(
78+
$this->l10n->n(
79+
'A lot of tasks failed in the last day. Consider checking the nextcloud log for errors and investigating whether the AI provider apps have been set up correctly.',
80+
'A lot of tasks failed in the last %n days. Consider checking the nextcloud log for errors and investigating whether the AI provider apps have been set up correctly.',
81+
$lastNDays
82+
),
83+
'https://docs.nextcloud.com/server/latest/admin_manual/ai/insight_and_debugging.html'
84+
);
85+
}
86+
}
87+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
7+
* SPDX-License-Identifier: AGPL-3.0-or-later
8+
*/
9+
namespace OCA\Settings\Tests;
10+
11+
use OCA\Settings\SetupChecks\TaskProcessingSuccessRate;
12+
use OCP\AppFramework\Utility\ITimeFactory;
13+
use OCP\IL10N;
14+
use OCP\SetupCheck\SetupResult;
15+
use OCP\TaskProcessing\IManager;
16+
use OCP\TaskProcessing\Task;
17+
use Test\TestCase;
18+
19+
class TaskProcessingSuccessRateTest extends TestCase {
20+
private IL10N $l10n;
21+
private ITimeFactory $timeFactory;
22+
private IManager $taskProcessingManager;
23+
24+
private TaskProcessingSuccessRate $check;
25+
26+
protected function setUp(): void {
27+
parent::setUp();
28+
29+
$this->l10n = $this->getMockBuilder(IL10N::class)->getMock();
30+
$this->timeFactory = $this->getMockBuilder(ITimeFactory::class)->getMock();
31+
$this->taskProcessingManager = $this->getMockBuilder(IManager::class)->getMock();
32+
33+
$this->check = new TaskProcessingSuccessRate(
34+
$this->l10n,
35+
$this->taskProcessingManager,
36+
$this->timeFactory,
37+
);
38+
}
39+
40+
public function testPass(): void {
41+
$tasks = [];
42+
for ($i = 0; $i < 100; $i++) {
43+
$task = new Task('test', ['test' => 'test'], 'settings', 'user' . $i);
44+
$task->setStartedAt(0);
45+
$task->setEndedAt(1);
46+
if ($i < 15) {
47+
$task->setStatus(Task::STATUS_FAILED); // 15% get status FAILED
48+
} else {
49+
$task->setStatus(Task::STATUS_SUCCESSFUL);
50+
}
51+
$tasks[] = $task;
52+
}
53+
$this->taskProcessingManager->method('getTasks')->willReturn($tasks);
54+
55+
$this->assertEquals(SetupResult::SUCCESS, $this->check->run()->getSeverity());
56+
}
57+
58+
public function testFail(): void {
59+
$tasks = [];
60+
for ($i = 0; $i < 100; $i++) {
61+
$task = new Task('test', ['test' => 'test'], 'settings', 'user' . $i);
62+
$task->setStartedAt(0);
63+
$task->setEndedAt(1);
64+
if ($i < 30) {
65+
$task->setStatus(Task::STATUS_FAILED); // 30% get status FAILED
66+
} else {
67+
$task->setStatus(Task::STATUS_SUCCESSFUL);
68+
}
69+
$tasks[] = $task;
70+
}
71+
$this->taskProcessingManager->method('getTasks')->willReturn($tasks);
72+
73+
$this->assertEquals(SetupResult::WARNING, $this->check->run()->getSeverity());
74+
}
75+
}

0 commit comments

Comments
 (0)