diff --git a/src/Contracts/StatisticsCollector.php b/src/Contracts/StatisticsCollector.php index a46e757..316a83b 100644 --- a/src/Contracts/StatisticsCollector.php +++ b/src/Contracts/StatisticsCollector.php @@ -66,4 +66,13 @@ interface StatisticsCollector * @return PromiseInterface[\BeyondCode\LaravelWebSockets\Statistics\Statistic|null] */ public function getAppStatistics($appId): PromiseInterface; + + /** + * Remove all app traces from the database if no connections have been set + * in the meanwhile since last save. + * + * @param string|int $appId + * @return void + */ + public function resetAppTraces($appId); } diff --git a/src/Statistics/Collectors/MemoryCollector.php b/src/Statistics/Collectors/MemoryCollector.php index 2bb2630..8de17aa 100644 --- a/src/Statistics/Collectors/MemoryCollector.php +++ b/src/Statistics/Collectors/MemoryCollector.php @@ -96,6 +96,12 @@ class MemoryCollector implements StatisticsCollector continue; } + if ($statistic->shouldHaveTracesRemoved()) { + $this->resetAppTraces($appId); + + continue; + } + $this->createRecord($statistic, $appId); $this->channelManager @@ -142,6 +148,18 @@ class MemoryCollector implements StatisticsCollector ); } + /** + * Remove all app traces from the database if no connections have been set + * in the meanwhile since last save. + * + * @param string|int $appId + * @return void + */ + public function resetAppTraces($appId) + { + unset($this->statistics[$appId]); + } + /** * Find or create a defined statistic for an app. * diff --git a/src/Statistics/Collectors/RedisCollector.php b/src/Statistics/Collectors/RedisCollector.php index c37b940..bb5c688 100644 --- a/src/Statistics/Collectors/RedisCollector.php +++ b/src/Statistics/Collectors/RedisCollector.php @@ -170,6 +170,10 @@ class RedisCollector extends MemoryCollector $appId, Helpers::redisListToArray($list) ); + if ($statistic->shouldHaveTracesRemoved()) { + return $this->resetAppTraces($appId); + } + $this->createRecord($statistic, $appId); $this->channelManager @@ -265,7 +269,7 @@ class RedisCollector extends MemoryCollector ->getPublishClient() ->hset( $this->channelManager->getRedisKey($appId, null, ['stats']), - 'peak_connections_count', $currentConnectionCount + 'peak_connections_count', max(0, $currentConnectionCount) ); $this->channelManager @@ -292,6 +296,8 @@ class RedisCollector extends MemoryCollector */ public function resetAppTraces($appId) { + parent::resetAppTraces($appId); + $this->channelManager ->getPublishClient() ->hdel( diff --git a/src/Statistics/Statistic.php b/src/Statistics/Statistic.php index 1a92488..b31d547 100644 --- a/src/Statistics/Statistic.php +++ b/src/Statistics/Statistic.php @@ -178,11 +178,23 @@ class Statistic public function reset(int $currentConnectionsCount) { $this->currentConnectionsCount = $currentConnectionsCount; - $this->peakConnectionsCount = $currentConnectionsCount; + $this->peakConnectionsCount = max(0, $currentConnectionsCount); $this->webSocketMessagesCount = 0; $this->apiMessagesCount = 0; } + /** + * Check if the current statistic entry is empty. This means + * that the statistic entry can be easily deleted if no activity + * occured for a while. + * + * @return bool + */ + public function shouldHaveTracesRemoved(): bool + { + return $this->currentConnectionsCount === 0 && $this->peakConnectionsCount === 0; + } + /** * Transform the statistic to array. * diff --git a/tests/StatisticsStoreTest.php b/tests/StatisticsStoreTest.php index 6fe6cc2..419341b 100644 --- a/tests/StatisticsStoreTest.php +++ b/tests/StatisticsStoreTest.php @@ -16,6 +16,21 @@ class StatisticsStoreTest extends TestCase $this->assertEquals('2', $records[0]['peak_connections_count']); $this->assertEquals('2', $records[0]['websocket_messages_count']); $this->assertEquals('0', $records[0]['api_messages_count']); + + $this->pusherServer->onClose($rick); + $this->pusherServer->onClose($morty); + + $this->statisticsCollector->save(); + + $this->assertCount(2, $records = $this->statisticsStore->getRecords()); + + $this->assertEquals('2', $records[1]['peak_connections_count']); + + $this->statisticsCollector->save(); + + // The last one should not generate any more records + // since the current state is empty. + $this->assertCount(2, $records = $this->statisticsStore->getRecords()); } public function test_store_statistics_on_private_channel() @@ -30,19 +45,51 @@ class StatisticsStoreTest extends TestCase $this->assertEquals('2', $records[0]['peak_connections_count']); $this->assertEquals('2', $records[0]['websocket_messages_count']); $this->assertEquals('0', $records[0]['api_messages_count']); + + $this->pusherServer->onClose($rick); + $this->pusherServer->onClose($morty); + + $this->statisticsCollector->save(); + + $this->assertCount(2, $records = $this->statisticsStore->getRecords()); + + $this->assertEquals('2', $records[1]['peak_connections_count']); + + $this->statisticsCollector->save(); + + // The last one should not generate any more records + // since the current state is empty. + $this->assertCount(2, $records = $this->statisticsStore->getRecords()); } public function test_store_statistics_on_presence_channel() { $rick = $this->newPresenceConnection('presence-channel', ['user_id' => 1]); $morty = $this->newPresenceConnection('presence-channel', ['user_id' => 2]); + $pickleRick = $this->newPresenceConnection('presence-channel', ['user_id' => 1]); $this->statisticsCollector->save(); $this->assertCount(1, $records = $this->statisticsStore->getRecords()); - $this->assertEquals('2', $records[0]['peak_connections_count']); - $this->assertEquals('2', $records[0]['websocket_messages_count']); + $this->assertEquals('3', $records[0]['peak_connections_count']); + $this->assertEquals('3', $records[0]['websocket_messages_count']); $this->assertEquals('0', $records[0]['api_messages_count']); + + $this->pusherServer->onClose($rick); + $this->pusherServer->onClose($morty); + $this->pusherServer->onClose($pickleRick); + + $this->statisticsCollector->save(); + + $this->assertCount(2, $records = $this->statisticsStore->getRecords()); + + $this->assertEquals('3', $records[1]['peak_connections_count']); + + $this->statisticsCollector->save(); + + // The last one should not generate any more records + // since the current state is empty. + $this->assertCount(2, $records = $this->statisticsStore->getRecords()); } }