Skip to content
This repository was archived by the owner on Feb 7, 2024. It is now read-only.

Commit 70eeb00

Browse files
committed
Added redis logger
1 parent 66252c1 commit 70eeb00

File tree

3 files changed

+253
-0
lines changed

3 files changed

+253
-0
lines changed

phpunit.xml.dist

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,6 @@
2121
</filter>
2222
<php>
2323
<env name="DB_CONNECTION" value="testing"/>
24+
<env name="CACHE_DRIVER" value="redis"/>
2425
</php>
2526
</phpunit>
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
<?php
2+
3+
namespace BeyondCode\LaravelWebSockets\Statistics\Logger;
4+
5+
use BeyondCode\LaravelWebSockets\Apps\App;
6+
use BeyondCode\LaravelWebSockets\Statistics\Drivers\StatisticsDriver;
7+
use BeyondCode\LaravelWebSockets\WebSockets\Channels\ChannelManager;
8+
use Illuminate\Support\Facades\Cache;
9+
10+
class RedisStatisticsLogger implements StatisticsLogger
11+
{
12+
/**
13+
* The Channel manager.
14+
*
15+
* @var \BeyondCode\LaravelWebSockets\WebSockets\Channels\ChannelManager
16+
*/
17+
protected $channelManager;
18+
19+
/**
20+
* The statistics driver instance.
21+
*
22+
* @var \BeyondCode\LaravelWebSockets\Statistics\Drivers\StatisticsDriver
23+
*/
24+
protected $driver;
25+
26+
/**
27+
* The Redis manager instance.
28+
*
29+
* @var \Illuminate\Redis\RedisManager
30+
*/
31+
protected $redis;
32+
33+
/**
34+
* Initialize the logger.
35+
*
36+
* @param \BeyondCode\LaravelWebSockets\WebSockets\Channels\ChannelManager $channelManager
37+
* @param \BeyondCode\LaravelWebSockets\Statistics\Drivers\StatisticsDriver $driver
38+
* @return void
39+
*/
40+
public function __construct(ChannelManager $channelManager, StatisticsDriver $driver)
41+
{
42+
$this->channelManager = $channelManager;
43+
$this->driver = $driver;
44+
$this->redis = Cache::getRedis();
45+
}
46+
47+
/**
48+
* Handle the incoming websocket message.
49+
*
50+
* @param mixed $appId
51+
* @return void
52+
*/
53+
public function webSocketMessage($appId)
54+
{
55+
$this->ensureAppIsSet($appId)
56+
->hincrby($this->getHash($appId), 'websocket_message_count', 1);
57+
}
58+
59+
/**
60+
* Handle the incoming API message.
61+
*
62+
* @param mixed $appId
63+
* @return void
64+
*/
65+
public function apiMessage($appId)
66+
{
67+
$this->ensureAppIsSet($appId)
68+
->hincrby($this->getHash($appId), 'api_message_count', 1);
69+
}
70+
71+
/**
72+
* Handle the new conection.
73+
*
74+
* @param mixed $appId
75+
* @return void
76+
*/
77+
public function connection($appId)
78+
{
79+
$currentConnectionCount = $this->ensureAppIsSet($appId)
80+
->hincrby($this->getHash($appId), 'current_connection_count', 1);
81+
82+
$currentPeakConnectionCount = $this->redis->hget($this->getHash($appId), 'peak_connection_count');
83+
84+
$peakConnectionCount = is_null($currentPeakConnectionCount)
85+
? 1
86+
: max($currentPeakConnectionCount, $currentConnectionCount);
87+
88+
89+
$this->redis->hset($this->getHash($appId), 'peak_connection_count', $peakConnectionCount);
90+
}
91+
92+
/**
93+
* Handle disconnections.
94+
*
95+
* @param mixed $appId
96+
* @return void
97+
*/
98+
public function disconnection($appId)
99+
{
100+
$currentConnectionCount = $this->ensureAppIsSet($appId)
101+
->hincrby($this->getHash($appId), 'current_connection_count', -1);
102+
103+
$currentPeakConnectionCount = $this->redis->hget($this->getHash($appId), 'peak_connection_count');
104+
105+
$peakConnectionCount = is_null($currentPeakConnectionCount)
106+
? 0
107+
: max($currentPeakConnectionCount, $currentConnectionCount);
108+
109+
110+
$this->redis->hset($this->getHash($appId), 'peak_connection_count', $peakConnectionCount);
111+
}
112+
113+
/**
114+
* Save all the stored statistics.
115+
*
116+
* @return void
117+
*/
118+
public function save()
119+
{
120+
foreach ($this->redis->smembers('laravel-websockets:apps') as $appId) {
121+
if (! $statistic = $this->redis->hgetall($this->getHash($appId))) {
122+
continue;
123+
}
124+
125+
$this->driver::create([
126+
'app_id' => $appId,
127+
'peak_connection_count' => $statistic['peak_connection_count'] ?? 0,
128+
'websocket_message_count' => $statistic['websocket_message_count'] ?? 0,
129+
'api_message_count' => $statistic['api_message_count'] ?? 0,
130+
]);
131+
132+
$currentConnectionCount = $this->channelManager->getConnectionCount($appId);
133+
134+
$currentConnectionCount === 0
135+
? $this->resetAppTraces($appId)
136+
: $this->resetStatistics($appId, $currentConnectionCount);
137+
}
138+
}
139+
140+
/**
141+
* Ensure the app id is stored in the Redis database.
142+
*
143+
* @param mixed $appId
144+
* @return \Illuminate\Redis\RedisManager
145+
*/
146+
protected function ensureAppIsSet($appId)
147+
{
148+
$this->redis->sadd('laravel-websockets:apps', $appId);
149+
150+
return $this->redis;
151+
}
152+
153+
/**
154+
* Reset the statistics to a specific connection count.
155+
*
156+
* @param mixed $appId
157+
* @param int $currentConnectionCount
158+
* @return void
159+
*/
160+
public function resetStatistics($appId, int $currentConnectionCount)
161+
{
162+
$this->redis->hset($this->getHash($appId), 'current_connection_count', $currentConnectionCount);
163+
$this->redis->hset($this->getHash($appId), 'peak_connection_count', $currentConnectionCount);
164+
$this->redis->hset($this->getHash($appId), 'websocket_message_count', 0);
165+
$this->redis->hset($this->getHash($appId), 'api_message_count', 0);
166+
}
167+
168+
/**
169+
* Remove all app traces from the database if no connections have been set
170+
* in the meanwhile since last save.
171+
*
172+
* @param mixed $appId
173+
* @return void
174+
*/
175+
public function resetAppTraces($appId)
176+
{
177+
$this->redis->hdel($this->getHash($appId), 'current_connection_count');
178+
$this->redis->hdel($this->getHash($appId), 'peak_connection_count');
179+
$this->redis->hdel($this->getHash($appId), 'websocket_message_count');
180+
$this->redis->hdel($this->getHash($appId), 'api_message_count');
181+
182+
$this->redis->srem('laravel-websockets:apps', $appId);
183+
}
184+
185+
/**
186+
* Get the Redis hash name for the app.
187+
*
188+
* @param mixed $appId
189+
* @return string
190+
*/
191+
protected function getHash($appId): string
192+
{
193+
return "laravel-websockets:app:{$appId}";
194+
}
195+
}

tests/Statistics/Logger/StatisticsLoggerTest.php

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use BeyondCode\LaravelWebSockets\Facades\StatisticsLogger;
66
use BeyondCode\LaravelWebSockets\Statistics\Logger\MemoryStatisticsLogger;
7+
use BeyondCode\LaravelWebSockets\Statistics\Logger\RedisStatisticsLogger;
78
use BeyondCode\LaravelWebSockets\Statistics\Logger\NullStatisticsLogger;
89
use BeyondCode\LaravelWebSockets\Statistics\Models\WebSocketsStatisticsEntry;
910
use BeyondCode\LaravelWebSockets\Tests\TestCase;
@@ -92,4 +93,60 @@ public function it_counts_connections_with_null_logger()
9293

9394
$this->assertCount(0, WebSocketsStatisticsEntry::all());
9495
}
96+
97+
/** @test */
98+
public function it_counts_connections_with_redis_logger_with_no_data()
99+
{
100+
$connection = $this->getConnectedWebSocketConnection(['channel-1']);
101+
102+
$logger = new RedisStatisticsLogger(
103+
$this->channelManager,
104+
$this->statisticsDriver
105+
);
106+
107+
$logger->resetAppTraces('1234');
108+
109+
$logger->webSocketMessage($connection->app->id);
110+
$logger->apiMessage($connection->app->id);
111+
$logger->connection($connection->app->id);
112+
$logger->disconnection($connection->app->id);
113+
114+
$logger->save();
115+
116+
$this->assertCount(1, WebSocketsStatisticsEntry::all());
117+
118+
$entry = WebSocketsStatisticsEntry::first();
119+
120+
$this->assertEquals(1, $entry->peak_connection_count);
121+
$this->assertEquals(1, $entry->websocket_message_count);
122+
$this->assertEquals(1, $entry->api_message_count);
123+
}
124+
125+
/** @test */
126+
public function it_counts_connections_with_redis_logger_with_existing_data()
127+
{
128+
$connection = $this->getConnectedWebSocketConnection(['channel-1']);
129+
130+
$logger = new RedisStatisticsLogger(
131+
$this->channelManager,
132+
$this->statisticsDriver
133+
);
134+
135+
$logger->resetStatistics('1234', 0);
136+
137+
$logger->webSocketMessage($connection->app->id);
138+
$logger->apiMessage($connection->app->id);
139+
$logger->connection($connection->app->id);
140+
$logger->disconnection($connection->app->id);
141+
142+
$logger->save();
143+
144+
$this->assertCount(1, WebSocketsStatisticsEntry::all());
145+
146+
$entry = WebSocketsStatisticsEntry::first();
147+
148+
$this->assertEquals(1, $entry->peak_connection_count);
149+
$this->assertEquals(1, $entry->websocket_message_count);
150+
$this->assertEquals(1, $entry->api_message_count);
151+
}
95152
}

0 commit comments

Comments
 (0)