diff --git a/app/DB.php b/app/DB.php index 5cd19ecc4..9148c8670 100644 --- a/app/DB.php +++ b/app/DB.php @@ -186,8 +186,9 @@ class DB extends Base { SELECT count(*) FROM performance_schema.processlist WHERE COMMAND NOT IN ('Sleep') - AND TIME > 1200; - "); + AND TIME > ? + ", MYSQL_SLOW_QUERY_TIMEOUT + ); } public static function lookupDirection(string $direction): Direction { diff --git a/app/DB/MysqlTable.php b/app/DB/MysqlTable.php index 7d208e724..707877d3c 100644 --- a/app/DB/MysqlTable.php +++ b/app/DB/MysqlTable.php @@ -87,6 +87,17 @@ class MysqlTable extends AbstractTable { ); } + public function recentUpdate(int $seconds): bool { + return (bool)self::$db->scalar(" + SELECT 1 + FROM information_schema.tables + WHERE table_schema = ? + AND table_name = ? + AND update_time > now() - INTERVAL ? SECOND + ", MYSQL_DB, $this->name, $seconds + ); + } + public function stats(): array { return self::$db->rowAssoc(" SELECT t.TABLE_ROWS, diff --git a/app/Tracker.php b/app/Tracker.php index f33b6702e..dc9d365e3 100644 --- a/app/Tracker.php +++ b/app/Tracker.php @@ -341,4 +341,14 @@ class Tracker extends Base { "); return self::$db->to_array(false, MYSQLI_ASSOC); } + + public function recentUpdate(int $seconds): bool { + return (bool)self::$db->scalar(" + SELECT 1 + FROM xbt_files_users + WHERE mtime > unix_timestamp(now() - INTERVAL ? SECOND) + LIMIT 1 + ", $seconds + ); + } } diff --git a/app/User/Activity.php b/app/User/Activity.php index ee8b5703f..d48239a73 100644 --- a/app/User/Activity.php +++ b/app/User/Activity.php @@ -75,22 +75,6 @@ class Activity extends \Gazelle\BaseUser { return $this; } - public function setDb(\Gazelle\DB $dbMan): static { - if ($this->user->permitted('admin_site_debug')) { - $longRunning = $dbMan->longRunning(); - if ($longRunning > 0) { - $message = "$longRunning long-running DB operation" . plural($longRunning); - $this->setAlert("DB"); - } - // If Ocelot can no longer write to xbt_files_users, it will drain after an hour - // Look for database locks and check the Ocelot log - if (!self::$db->scalar('SELECT fid FROM xbt_files_users LIMIT 1')) { - $this->setAlert('Ocelot not updating!'); - } - } - return $this; - } - public function setPayment(\Gazelle\Manager\Payment $payMan): static { if ($this->user->permitted('admin_manage_payments')) { $soon = $payMan->soon(); diff --git a/app/View.php b/app/View.php index d0db9c2e1..e113982a3 100644 --- a/app/View.php +++ b/app/View.php @@ -48,7 +48,6 @@ class View extends Base { ->setReport(new Stats\Report()) ->setPayment($payMan) ->setApplicant(new Manager\Applicant()) - ->setDb(new DB()) ->setScheduler(new TaskScheduler()) ->setSSLHost(new Manager\SSLHost()) ->setAutoReport( @@ -58,6 +57,23 @@ class View extends Base { ) ); + if ($user->permitted('admin_site_debug')) { + $longRunning = new DB()->longRunning(); + if ($longRunning > 0) { + $message = "$longRunning long-running DB operation" . plural($longRunning); + $activity->setAlert("DB"); + } + // Check that Ocelot is still writing to xbt_files_users. + // If not, look for database locks and check the Ocelot log + + if (!new Tracker()->recentUpdate(TRACKER_REFRESH_TIMEOUT)) { + $activity->setAlert('TRACKER' + ); + } + } + $threshold = new Manager\SiteOption() ->findValueByName('download-warning-threshold'); if ($threshold) { diff --git a/lib/config.php b/lib/config.php index 3b3c2f29d..d348675b3 100644 --- a/lib/config.php +++ b/lib/config.php @@ -194,6 +194,9 @@ defined('MYSQL_RO_USER') or define('MYSQL_RO_USER', 'gazro'); // The password of the above account. defined('MYSQL_RO_PASS') or define('MYSQL_RO_PASS', 'passro'); +// Warn if there is a query that has been running for too long +defined('MYSQL_SLOW_QUERY_TIMEOUT') or define('MYSQL_SLOW_QUERY_TIMEOUT', 1200); + // The username of the Phinx account (used for schema modifications). // In production, this account will have a different set of grants compared // to the website account (so that if the website account is compromised, it @@ -292,6 +295,12 @@ defined('TRACKER_NAME') or define('TRACKER_NAME', '127.0.0.1' . ":" . TRACKER_PO // be exactly 32 alphanumeric characters. defined('TRACKER_SECRET') or define('TRACKER_SECRET', '00000000000000000000000000000000'); +// How long to wait in the absense of an update from the tracker to warn of a +// problem? Busy with millions of peears receive multiple updates per second. +// Sites with a few thousand peers or less may not see an update for several +// seconds. +defined('TRACKER_REFRESH_TIMEOUT') or define('TRACKER_REFRESH_TIMEOUT', 5); + // Second shared secret that is compiled into Ocelot (see config.cpp). Must // be exactly 32 alphanumeric characters. defined('TRACKER_REPORTKEY') or define('TRACKER_REPORTKEY', '00000000000000000000000000000000'); diff --git a/tests/phpunit/DbTest.php b/tests/phpunit/DbTest.php index 187449b44..a88b6777a 100644 --- a/tests/phpunit/DbTest.php +++ b/tests/phpunit/DbTest.php @@ -363,6 +363,8 @@ class DbTest extends TestCase { ), 'mysql-fkey-list' ); + // not a problem that the table does not exist, the SQL is being tested + $this->assertFalse($bad->recentUpdate(600), 'mysql-table-recent-update'); } public function testPgTable(): void { diff --git a/tests/phpunit/TrackerTest.php b/tests/phpunit/TrackerTest.php index 67e911d48..ff97f430e 100644 --- a/tests/phpunit/TrackerTest.php +++ b/tests/phpunit/TrackerTest.php @@ -182,4 +182,21 @@ class TrackerTest extends TestCase { $this->assertEquals(1, $tracker->expireFreeleechTokens("$userId:$torrentId,$userId:$fakeId"), 'tracker-expire-tokens'); $downloader->remove(); } + + public function testTrackerUpdate(): void { + $this->user = Helper::makeUser('trkfree.' . randomString(10), 'tracker'); + $this->user->requestContext()->setViewer($this->user); + $this->torrent = Helper::makeTorrentMusic( + Helper::makeTGroupMusic( + name: 'tracker ' . randomString(10), + artistName: [[ARTIST_MAIN], ['Tracker Girl ' . randomString(12)]], + tagName: ['trap'], + user: $this->user, + ), + user: $this->user, + title: 'tracker ' . randomString(10), + ); + Helper::generateTorrentSeed($this->torrent, $this->user); + $this->assertTrue(new Tracker()->recentUpdate(60), 'tracker-recent-update'); + } } diff --git a/tests/phpunit/UserActivityTest.php b/tests/phpunit/UserActivityTest.php index 31a804936..ed0a0ca77 100644 --- a/tests/phpunit/UserActivityTest.php +++ b/tests/phpunit/UserActivityTest.php @@ -30,7 +30,6 @@ class UserActivityTest extends TestCase { $this->assertInstanceOf(User\Activity::class, $activity->configure(), 'user-activity-configure'); $this->assertInstanceOf(User\Activity::class, $activity->setApplicant(new Manager\Applicant()), 'user-activity-applicant'); - $this->assertInstanceOf(User\Activity::class, $activity->setDb(new DB()), 'user-activity-db'); $this->assertInstanceOf(User\Activity::class, $activity->setPayment(new Manager\Payment()), 'user-activity-payment'); $this->assertInstanceOf(User\Activity::class, $activity->setReferral(new Manager\Referral()), 'user-activity-referral'); $this->assertInstanceOf(User\Activity::class, $activity->setReport(new Stats\Report()), 'user-activity-report');