Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 7 additions & 19 deletions apps/settings/lib/SetupChecks/TaskProcessingPickupSpeed.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class TaskProcessingPickupSpeed implements ISetupCheck {
public const MAX_SLOW_PERCENTAGE = 0.1;

public const MAX_DAYS = 14;
public const MAX_PICKUP_DELAY = 60 * 4;

public function __construct(
private IL10N $l10n,
Expand All @@ -39,14 +40,12 @@ public function getName(): string {

#[\Override]
public function run(): SetupResult {
$taskCount = 0;
$lastNDays = 1;
while ($taskCount === 0 && $lastNDays < self::MAX_DAYS) {
do {
$lastNDays++;
// userId: '' means no filter, whereas null would mean guest
$tasks = $this->taskProcessingManager->getTasks(userId: '', scheduleAfter: $this->timeFactory->now()->getTimestamp() - (60 * 60 * 24 * $lastNDays));
$taskCount = count($tasks);
}
$scheduleAfter = $this->timeFactory->now()->getTimestamp() - (60 * 60 * 24 * $lastNDays);
$taskCount = $this->taskProcessingManager->countTasks(scheduleAfter: $scheduleAfter);
} while ($taskCount === 0 && $lastNDays < self::MAX_DAYS);
if ($taskCount === 0) {
return SetupResult::success(
$this->l10n->n(
Expand All @@ -56,19 +55,8 @@ public function run(): SetupResult {
)
);
}
$slowCount = 0;
foreach ($tasks as $task) {
if ($task->getStartedAt() === null) {
continue; // task was not picked up yet
}
if ($task->getScheduledAt() === null) {
continue; // task was not scheduled yet -- should not happen, but the API specifies null as return value
}
$pickupDelay = $task->getScheduledAt() - $task->getStartedAt();
if ($pickupDelay > 60 * 4) {
$slowCount++; // task pickup took longer than 4 minutes
}
}
// Tasks that have not been picked up yet are not counted as slow
$slowCount = $this->taskProcessingManager->countTasks(scheduleAfter: $scheduleAfter, minPickupDelay: self::MAX_PICKUP_DELAY);

if (($slowCount / $taskCount) < self::MAX_SLOW_PERCENTAGE) {
return SetupResult::success(
Expand Down
21 changes: 5 additions & 16 deletions apps/settings/lib/SetupChecks/TaskProcessingSuccessRate.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,12 @@ public function getName(): string {

#[\Override]
public function run(): SetupResult {
$taskCount = 0;
$lastNDays = 0;
while ($taskCount === 0 && $lastNDays < self::MAX_DAYS) {
do {
$lastNDays++;
// userId: '' means no filter, whereas null would mean guest
$tasks = $this->taskProcessingManager->getTasks(userId: '', scheduleAfter: $this->timeFactory->now()->getTimestamp() - (60 * 60 * 24 * $lastNDays));
$taskCount = count($tasks);
}
$scheduleAfter = $this->timeFactory->now()->getTimestamp() - (60 * 60 * 24 * $lastNDays);
$taskCount = $this->taskProcessingManager->countTasks(scheduleAfter: $scheduleAfter);
} while ($taskCount === 0 && $lastNDays < self::MAX_DAYS);
if ($taskCount === 0) {
return SetupResult::success(
$this->l10n->n(
Expand All @@ -57,16 +55,7 @@ public function run(): SetupResult {
)
);
}
$failedCount = 0;
foreach ($tasks as $task) {
if ($task->getEndedAt() === null) {
continue; // task was not picked up yet
}
$status = $task->getStatus();
if ($status === Task::STATUS_FAILED) {
$failedCount++;
}
}
$failedCount = $this->taskProcessingManager->countTasks(status: Task::STATUS_FAILED, scheduleAfter: $scheduleAfter);

if (($failedCount / $taskCount) < self::MAX_FAILURE_PERCENTAGE) {
return SetupResult::success(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,7 @@ public function getName(): string {
#[\Override]
public function run(): SetupResult {
$lastNDays = self::HAS_TASKS_IN_LAST_X_DAYS;
$tasks = $this->taskProcessingManager->getTasks(userId: '', scheduleAfter: $this->timeFactory->now()->getTimestamp() - (60 * 60 * 24 * $lastNDays));
$taskCount = count($tasks);
$taskCount = $this->taskProcessingManager->countTasks(scheduleAfter: $this->timeFactory->now()->getTimestamp() - (60 * 60 * 24 * $lastNDays));
if ($taskCount === 0) {
// In case taskprocessing is not used at all
return SetupResult::success(
Expand Down
56 changes: 31 additions & 25 deletions apps/settings/tests/SetupChecks/TaskProcessingPickupSpeedTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
use OCP\IL10N;
use OCP\SetupCheck\SetupResult;
use OCP\TaskProcessing\IManager;
use OCP\TaskProcessing\Task;
use PHPUnit\Framework\MockObject\MockObject;
use Test\TestCase;

Expand All @@ -39,37 +38,44 @@ protected function setUp(): void {
);
}

/**
* @param int $taskCount Tasks scheduled in the window
* @param int $slowCount Tasks of those that were picked up too late
*/
private function mockCounts(int $taskCount, int $slowCount): void {
$this->taskProcessingManager->method('countTasks')
->willReturnCallback(function (?int $status = null, array $taskTypeIds = [], ?int $scheduleAfter = null, ?int $minPickupDelay = null) use ($taskCount, $slowCount): int {
if ($minPickupDelay === null) {
return $taskCount;
}
$this->assertSame(TaskProcessingPickupSpeed::MAX_PICKUP_DELAY, $minPickupDelay);
return $slowCount;
});
}

public function testPass(): void {
$tasks = [];
for ($i = 0; $i < 100; $i++) {
$task = new Task('test', ['test' => 'test'], 'settings', 'user' . $i);
$task->setStartedAt(0);
if ($i < 5) {
$task->setScheduledAt(60 * 5); // 5% get 5mins
} else {
$task->setScheduledAt(60); // the rest gets 1min
}
$tasks[] = $task;
}
$this->taskProcessingManager->method('getTasks')->willReturn($tasks);
// 5% of the tasks were picked up too late
$this->mockCounts(100, 5);
$this->timeFactory->method('now')->willReturn(new \DateTimeImmutable());

$this->assertEquals(SetupResult::SUCCESS, $this->check->run()->getSeverity());
}

public function testFail(): void {
$tasks = [];
for ($i = 0; $i < 100; $i++) {
$task = new Task('test', ['test' => 'test'], 'settings', 'user' . $i);
$task->setStartedAt(0);
if ($i < 30) {
$task->setScheduledAt(60 * 5); // 30% get 5mins
} else {
$task->setScheduledAt(60); // the rest gets 1min
}
$tasks[] = $task;
}
$this->taskProcessingManager->method('getTasks')->willReturn($tasks);
// 30% of the tasks were picked up too late
$this->mockCounts(100, 30);
$this->timeFactory->method('now')->willReturn(new \DateTimeImmutable());

$this->assertEquals(SetupResult::WARNING, $this->check->run()->getSeverity());
}

public function testWidensTheWindowWhileThereAreNoTasks(): void {
$this->timeFactory->method('now')->willReturn(new \DateTimeImmutable());
$this->taskProcessingManager->expects($this->never())->method('getTasks');
$this->taskProcessingManager->expects($this->exactly(TaskProcessingPickupSpeed::MAX_DAYS - 1))
->method('countTasks')
->willReturn(0);

$this->assertEquals(SetupResult::SUCCESS, $this->check->run()->getSeverity());
}
}
53 changes: 27 additions & 26 deletions apps/settings/tests/SetupChecks/TaskProcessingSuccessRateTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,39 +39,40 @@ protected function setUp(): void {
);
}

/**
* @param int $taskCount Tasks scheduled in the window
* @param int $failedCount Tasks of those that failed
*/
private function mockCounts(int $taskCount, int $failedCount): void {
$this->taskProcessingManager->method('countTasks')
->willReturnCallback(function (?int $status = null, array $taskTypeIds = [], ?int $scheduleAfter = null, ?int $minPickupDelay = null) use ($taskCount, $failedCount): int {
return $status === Task::STATUS_FAILED ? $failedCount : $taskCount;
});
}

public function testPass(): void {
$tasks = [];
for ($i = 0; $i < 100; $i++) {
$task = new Task('test', ['test' => 'test'], 'settings', 'user' . $i);
$task->setStartedAt(0);
$task->setEndedAt(1);
if ($i < 5) {
$task->setStatus(Task::STATUS_FAILED); // 5% get status FAILED
} else {
$task->setStatus(Task::STATUS_SUCCESSFUL);
}
$tasks[] = $task;
}
$this->taskProcessingManager->method('getTasks')->willReturn($tasks);
// 5% of the tasks failed
$this->mockCounts(100, 5);
$this->timeFactory->method('now')->willReturn(new \DateTimeImmutable());

$this->assertEquals(SetupResult::SUCCESS, $this->check->run()->getSeverity());
}

public function testFail(): void {
$tasks = [];
for ($i = 0; $i < 100; $i++) {
$task = new Task('test', ['test' => 'test'], 'settings', 'user' . $i);
$task->setStartedAt(0);
$task->setEndedAt(1);
if ($i < 30) {
$task->setStatus(Task::STATUS_FAILED); // 30% get status FAILED
} else {
$task->setStatus(Task::STATUS_SUCCESSFUL);
}
$tasks[] = $task;
}
$this->taskProcessingManager->method('getTasks')->willReturn($tasks);
// 30% of the tasks failed
$this->mockCounts(100, 30);
$this->timeFactory->method('now')->willReturn(new \DateTimeImmutable());

$this->assertEquals(SetupResult::WARNING, $this->check->run()->getSeverity());
}

public function testWidensTheWindowWhileThereAreNoTasks(): void {
$this->timeFactory->method('now')->willReturn(new \DateTimeImmutable());
$this->taskProcessingManager->expects($this->never())->method('getTasks');
$this->taskProcessingManager->expects($this->exactly(TaskProcessingSuccessRate::MAX_DAYS))
->method('countTasks')
->willReturn(0);

$this->assertEquals(SetupResult::SUCCESS, $this->check->run()->getSeverity());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
use OCP\IURLGenerator;
use OCP\SetupCheck\SetupResult;
use OCP\TaskProcessing\IManager;
use OCP\TaskProcessing\Task;
use PHPUnit\Framework\MockObject\MockObject;
use Test\TestCase;

Expand Down Expand Up @@ -48,36 +47,37 @@ protected function setUp(): void {
}

public function testPass(): void {
$tasks = [];
for ($i = 0; $i < 10; $i++) {
$task = new Task('test', ['test' => 'test'], 'settings', 'user' . $i);
$task->setStartedAt($this->timeFactory->now()->getTimestamp());
$task->setScheduledAt($this->timeFactory->now()->getTimestamp());
$task->setEndedAt($this->timeFactory->now()->getTimestamp());
$task->setStatus(Task::STATUS_SUCCESSFUL);
$tasks[] = $task;
}
$this->taskProcessingManager->method('getTasks')->willReturn($tasks);
$this->taskProcessingManager->method('countTasks')->willReturn(10);
$this->timeFactory->method('now')->willReturn(new \DateTimeImmutable());
$this->appConfig->method('getValueString')->willReturn((string)$this->timeFactory->now()->getTimestamp());

$this->assertEquals(SetupResult::SUCCESS, $this->check->run()->getSeverity());
}

public function testFail(): void {
$tasks = [];
for ($i = 0; $i < 10; $i++) {
$task = new Task('test', ['test' => 'test'], 'settings', 'user' . $i);
$task->setStartedAt($this->timeFactory->now()->getTimestamp());
$task->setScheduledAt($this->timeFactory->now()->getTimestamp());
$task->setEndedAt($this->timeFactory->now()->getTimestamp());
$task->setStatus(Task::STATUS_SUCCESSFUL);
$tasks[] = $task;
}
$this->taskProcessingManager->method('getTasks')->willReturn($tasks);
$this->taskProcessingManager->method('countTasks')->willReturn(10);
$this->timeFactory->method('now')->willReturn(new \DateTimeImmutable());
$this->appConfig->method('getValueString')->willReturn((string)($this->timeFactory->now()->getTimestamp() - 60 * 10));

$this->assertEquals(SetupResult::WARNING, $this->check->run()->getSeverity());
}

public function testTasksAreOnlyCounted(): void {
$now = new \DateTimeImmutable();
$this->timeFactory->method('now')->willReturn($now);
// The tasks themselves are never needed, only whether there are any
$this->taskProcessingManager->expects($this->never())->method('getTasks');
$this->taskProcessingManager->expects($this->once())
->method('countTasks')
->willReturnCallback(function (?int $status = null, array $taskTypeIds = [], ?int $scheduleAfter = null, ?int $minPickupDelay = null) use ($now): int {
$this->assertNull($status);
$this->assertSame([], $taskTypeIds);
$this->assertSame($now->getTimestamp() - 60 * 60 * 24 * TaskProcessingWorkerIsRunning::HAS_TASKS_IN_LAST_X_DAYS, $scheduleAfter);
$this->assertNull($minPickupDelay);
return 0;
});
$this->appConfig->expects($this->never())->method('getValueString');

$this->assertEquals(SetupResult::SUCCESS, $this->check->run()->getSeverity());
}
}
6 changes: 6 additions & 0 deletions core/Listener/AddMissingIndicesListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -229,5 +229,11 @@ public function handle(Event $event): void {
'taskp_status_type_upd',
['status', 'type', 'last_updated']
);

$event->addMissingIndex(
'taskprocessing_tasks',
'taskp_tasks_scheduled',
['scheduled_at']
);
}
}
6 changes: 6 additions & 0 deletions core/Migrations/Version30000Date20240708160048.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
use OCP\DB\ISchemaWrapper;
use OCP\DB\Types;
use OCP\Migration\Attributes\AddColumn;
use OCP\Migration\Attributes\AddIndex;
use OCP\Migration\Attributes\ColumnType;
use OCP\Migration\Attributes\IndexType;
use OCP\Migration\IOutput;
use OCP\Migration\SimpleMigrationStep;

Expand All @@ -23,6 +25,7 @@
#[AddColumn(table: 'taskprocessing_tasks', name: 'scheduled_at', type: ColumnType::INTEGER)]
#[AddColumn(table: 'taskprocessing_tasks', name: 'started_at', type: ColumnType::INTEGER)]
#[AddColumn(table: 'taskprocessing_tasks', name: 'ended_at', type: ColumnType::INTEGER)]
#[AddIndex(table: 'taskprocessing_tasks', type: IndexType::INDEX)]
class Version30000Date20240708160048 extends SimpleMigrationStep {

/**
Expand Down Expand Up @@ -60,6 +63,9 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt
'unsigned' => true,
]);
}
if (!$table->hasIndex('taskp_tasks_scheduled')) {
$table->addIndex(['scheduled_at'], 'taskp_tasks_scheduled');
}

return $schema;
}
Expand Down
Loading
Loading