diff --git a/config/websockets.php b/config/websockets.php index 693f861..caa8507 100644 --- a/config/websockets.php +++ b/config/websockets.php @@ -42,10 +42,19 @@ return [ */ 'max_request_size_in_kb' => 250, - /* - * This model will be used to store the statistics of the WebSocketsServer - */ - 'statistics_model' => \BeyondCode\LaravelWebSockets\Statistics\Models\WebSocketsStatisticsEntry::class, + 'statistics' => [ + /* + * This model will be used to store the statistics of the WebSocketsServer. + * The only requirement is that the model should be or extend + * `WebSocketsStatisticsEntry` provided by this package. + */ + 'model' => \BeyondCode\LaravelWebSockets\Statistics\Models\WebSocketsStatisticsEntry::class, + + /* + * Here you can specify the interval in seconds at which statistics should be logged. + */ + 'interval_in_seconds' => 60, + ], /* * Define the optional SSL context for your WebSocket connections. diff --git a/src/Console/StartWebSocketServer.php b/src/Console/StartWebSocketServer.php index 6be87ba..2778c5c 100644 --- a/src/Console/StartWebSocketServer.php +++ b/src/Console/StartWebSocketServer.php @@ -53,12 +53,11 @@ class StartWebSocketServer extends Command $browser = new Browser($this->loop, $connector); - app()->singleton(StatisticsLoggerInterface::class, function() use ($browser) { return new HttpStatisticsLogger(app(ChannelManager::class), $browser); }); - $this->loop->addPeriodicTimer(5, function() { + $this->loop->addPeriodicTimer(config('websockets.statistics.interval_in_seconds'), function() { StatisticsLogger::save(); }); diff --git a/src/Statistics/Events/StatisticsUpdated.php b/src/Statistics/Events/StatisticsUpdated.php index 1fb1f56..d6fa70b 100644 --- a/src/Statistics/Events/StatisticsUpdated.php +++ b/src/Statistics/Events/StatisticsUpdated.php @@ -3,6 +3,7 @@ namespace BeyondCode\LaravelWebsockets\Statistics\Events; use BeyondCode\LaravelWebSockets\Dashboard\DashboardLogger; +use BeyondCode\LaravelWebSockets\Statistics\Models\WebSocketsStatisticsEntry; use Illuminate\Broadcasting\PrivateChannel; use Illuminate\Contracts\Broadcasting\ShouldBroadcast; use Illuminate\Queue\SerializesModels; @@ -11,27 +12,30 @@ class StatisticsUpdated implements ShouldBroadcast { use SerializesModels; - public $statisticModel; + /** @var \BeyondCode\LaravelWebSockets\Statistics\Models\WebSocketsStatisticsEntry */ + protected $webSocketsStatisticsEntry; - public function __construct($statisticModel) + public function __construct(WebSocketsStatisticsEntry $webSocketsStatisticsEntry) { - $this->statisticModel = $statisticModel; + $this->webSocketsStatisticsEntry = $webSocketsStatisticsEntry; } public function broadcastWith() { return [ - 'time' => (string)$this->statisticModel->created_at, - 'app_id' => $this->statisticModel->app_id, - 'peak_connection_count' => $this->statisticModel->peak_connection_count, - 'websocket_message_count' => $this->statisticModel->websocket_message_count, - 'api_message_count' => $this->statisticModel->api_message_count, + 'time' => $this->webSocketsStatisticsEntry->created_at->timestamp, + 'app_id' => $this->webSocketsStatisticsEntry->appId, + 'peak_connection_count' => $this->webSocketsStatisticsEntry->peakConnectionCount, + 'websocket_message_count' => $this->webSocketsStatisticsEntry->webSocketMessageCount, + 'api_message_count' => $this->webSocketsStatisticsEntry->apiMessageCount, ]; } public function broadcastOn() { - return new PrivateChannel(str_after(DashboardLogger::LOG_CHANNEL_PREFIX . 'statistics', 'private-')); + $channelName = str_after(DashboardLogger::LOG_CHANNEL_PREFIX . 'statistics', 'private-'); + + return new PrivateChannel($channelName); } public function broadcastAs() diff --git a/src/Statistics/Http/Controllers/WebSocketStatisticsEntriesController.php b/src/Statistics/Http/Controllers/WebSocketStatisticsEntriesController.php index 2279a0e..f898b2e 100644 --- a/src/Statistics/Http/Controllers/WebSocketStatisticsEntriesController.php +++ b/src/Statistics/Http/Controllers/WebSocketStatisticsEntriesController.php @@ -17,7 +17,7 @@ class WebSocketStatisticsEntriesController 'api_message_count' => 'required|integer', ]); - $webSocketsStatisticsEntryModelClass = config('websockets.statistics_model'); + $webSocketsStatisticsEntryModelClass = config('websockets.statistics.model'); $statisticModel = $webSocketsStatisticsEntryModelClass::create($validatedAttributes); diff --git a/src/Statistics/Logger/HttpStatisticsLogger.php b/src/Statistics/Logger/HttpStatisticsLogger.php index 847f319..4985ed9 100644 --- a/src/Statistics/Logger/HttpStatisticsLogger.php +++ b/src/Statistics/Logger/HttpStatisticsLogger.php @@ -11,13 +11,13 @@ use Ratchet\ConnectionInterface; class HttpStatisticsLogger implements StatisticsLogger { - /** @var Statistic[] */ + /** @var \BeyondCode\LaravelWebSockets\Statistics\Statistic[] */ protected $statistics = []; /** @var \BeyondCode\LaravelWebSockets\WebSockets\Channels\ChannelManager */ protected $channelManager; - /** @var Browser */ + /** @var \Clue\React\Buzz\Browser */ protected $browser; public function __construct(ChannelManager $channelManager, Browser $browser) @@ -29,37 +29,39 @@ class HttpStatisticsLogger implements StatisticsLogger public function webSocketMessage(ConnectionInterface $connection) { - $this->initializeStatistics($connection->app->id); - - $this->statistics[$connection->app->id]->webSocketMessage(); + $this + ->findOrMakeStatisticForAppId($connection->app->id) + ->webSocketMessage(); } public function apiMessage($appId) { - $this->initializeStatistics($appId); - - $this->statistics[$appId]->apiMessage(); + $this + ->findOrMakeStatisticForAppId($appId) + ->apiMessage(); } public function connection(ConnectionInterface $connection) { - $this->initializeStatistics($connection->app->id); - - $this->statistics[$connection->app->id]->connection(); + $this + ->findOrMakeStatisticForAppId($connection->app->id) + ->connection(); } public function disconnection(ConnectionInterface $connection) { - $this->initializeStatistics($connection->app->id); - - $this->statistics[$connection->app->id]->disconnection(); + $this + ->findOrMakeStatisticForAppId($connection->app->id) + ->disconnection(); } - protected function initializeStatistics($id) + protected function findOrMakeStatisticForAppId($appId): Statistic { - if (!isset($this->statistics[$id])) { - $this->statistics[$id] = new Statistic($id); + if (!isset($this->statistics[$appId])) { + $this->statistics[$appId] = new Statistic($appId); } + + return $this->statistics[$appId]; } public function save() @@ -70,19 +72,15 @@ class HttpStatisticsLogger implements StatisticsLogger continue; } - $this->browser + $this + ->browser ->post( action([WebSocketStatisticsEntriesController::class, 'store']), ['Content-Type' => 'application/json'], stream_for(json_encode($statistic->toArray())) ); - // Reset connection and message count - $currentConnectionCount = collect($this->channelManager->getChannels($appId)) - ->sum(function ($channel) { - return count($channel->getSubscribedConnections()); - }); - + $currentConnectionCount = $this->channelManager->getConnectionCount($appId); $statistic->reset($currentConnectionCount); } } diff --git a/src/WebSockets/Channels/ChannelManager.php b/src/WebSockets/Channels/ChannelManager.php index fa77442..9c7c265 100644 --- a/src/WebSockets/Channels/ChannelManager.php +++ b/src/WebSockets/Channels/ChannelManager.php @@ -46,6 +46,12 @@ class ChannelManager return $this->channels[$appId] ?? []; } + public function getConnectionCount(string $appId): int + { + return collect($this->getChannels($appId)) + ->sum->getSubscribedConnections(); + } + public function removeFromAllChannels(ConnectionInterface $connection) { if (!isset($connection->app)) { diff --git a/src/WebSocketsServiceProvider.php b/src/WebSocketsServiceProvider.php index 0c6901b..36afd03 100644 --- a/src/WebSocketsServiceProvider.php +++ b/src/WebSocketsServiceProvider.php @@ -22,22 +22,21 @@ class WebSocketsServiceProvider extends ServiceProvider public function boot() { $this->publishes([ - __DIR__.'/../config/websockets.php' => base_path('config/websockets.php'), + __DIR__ . '/../config/websockets.php' => base_path('config/websockets.php'), ], 'config'); - if (! class_exists('CreateWebSocketsStatisticsEntries')) { + if (!class_exists('CreateWebSocketsStatisticsEntries')) { $this->publishes([ - __DIR__.'/../database/migrations/create_websockets_statistics_entries_table.php.stub' => database_path('migrations/'.date('Y_m_d_His', time()).'_create_websockets_statistics_entries_table.php'), + __DIR__ . '/../database/migrations/create_websockets_statistics_entries_table.php.stub' => database_path('migrations/' . date('Y_m_d_His', time()) . '_create_websockets_statistics_entries_table.php'), ], 'migrations'); } - $this->registerRouteMacro(); + $this + ->registerRouteMacro() + ->registerStatisticRoute() + ->registerDashboardGate(); - $this->registerStatisticRoute(); - - $this->registerDashboardGate(); - - $this->loadViewsFrom(__DIR__.'/../resources/views/', 'websockets'); + $this->loadViewsFrom(__DIR__ . '/../resources/views/', 'websockets'); $this->commands([ Console\StartWebSocketServer::class, @@ -46,38 +45,42 @@ class WebSocketsServiceProvider extends ServiceProvider public function register() { - $this->mergeConfigFrom(__DIR__.'/../config/websockets.php', 'websockets'); + $this->mergeConfigFrom(__DIR__ . '/../config/websockets.php', 'websockets'); - $this->app->singleton('websockets.router', function() { + $this->app->singleton('websockets.router', function () { return new Router(); }); - $this->app->singleton(ChannelManager::class, function() { + $this->app->singleton(ChannelManager::class, function () { return new ChannelManager(); }); - $this->app->singleton(AppProvider::class, function() { + $this->app->singleton(AppProvider::class, function () { return app(config('websockets.app_provider')); }); } protected function registerRouteMacro() { - Route::macro('webSockets', function($prefix = 'laravel-websockets') { - Route::prefix($prefix)->namespace('\\')->middleware(Authorize::class)->group(function() { - Route::get('/', ShowDashboard::class); - Route::get('/api/{appId}/statistics', DashboardApiController::class.'@getStatistics'); + Route::macro('webSockets', function ($prefix = 'laravel-websockets') { + Route::prefix($prefix)->namespace('\\')->middleware(Authorize::class)->group(function () { + Route::get('/', ShowDashboard::class); + Route::get('/api/{appId}/statistics', DashboardApiController::class . '@getStatistics'); Route::post('auth', AuthenticateDashboard::class); Route::post('event', SendMessage::class); }); }); + + return $this; } protected function registerStatisticRoute() { - Route::prefix('/laravel-websockets')->namespace('\\')->group(function() { + Route::prefix('/laravel-websockets')->namespace('\\')->group(function () { Route::post('statistics', [WebSocketStatisticsEntriesController::class, 'store']); }); + + return $this; } protected function registerDashboardGate() @@ -85,5 +88,7 @@ class WebSocketsServiceProvider extends ServiceProvider Gate::define('viewWebSocketsDashboard', function ($user = null) { return app()->environment('local'); }); + + return $this; } }