Completed
Pull Request — master (#447)
by Marcel
02:29 queued 59s
created

RedisStatisticsLogger::resetAppTraces()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9

Duplication

Lines 9
Ratio 100 %

Importance

Changes 0
Metric Value
dl 9
loc 9
rs 9.9666
c 0
b 0
f 0
cc 1
nc 1
nop 1
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\Cache\RedisLock;
9
use Illuminate\Support\Facades\Cache;
10
11
class RedisStatisticsLogger implements StatisticsLogger
12
{
13
    /**
14
     * The Channel manager.
15
     *
16
     * @var \BeyondCode\LaravelWebSockets\WebSockets\Channels\ChannelManager
17
     */
18
    protected $channelManager;
19
20
    /**
21
     * The statistics driver instance.
22
     *
23
     * @var \BeyondCode\LaravelWebSockets\Statistics\Drivers\StatisticsDriver
24
     */
25
    protected $driver;
26
27
    /**
28
     * The Redis manager instance.
29
     *
30
     * @var \Illuminate\Redis\RedisManager
31
     */
32
    protected $redis;
33
34
    /**
35
     * Initialize the logger.
36
     *
37
     * @param  \BeyondCode\LaravelWebSockets\WebSockets\Channels\ChannelManager  $channelManager
38
     * @param  \BeyondCode\LaravelWebSockets\Statistics\Drivers\StatisticsDriver  $driver
39
     * @return void
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

Loading history...
40
     */
41
    public function __construct(ChannelManager $channelManager, StatisticsDriver $driver)
42
    {
43
        $this->channelManager = $channelManager;
44
        $this->driver = $driver;
45
        $this->redis = Cache::getRedis();
46
    }
47
48
    /**
49
     * Handle the incoming websocket message.
50
     *
51
     * @param  mixed  $appId
52
     * @return void
53
     */
54
    public function webSocketMessage($appId)
55
    {
56
        $this->ensureAppIsSet($appId)
57
            ->hincrby($this->getHash($appId), 'websocket_message_count', 1);
58
    }
59
60
    /**
61
     * Handle the incoming API message.
62
     *
63
     * @param  mixed  $appId
64
     * @return void
65
     */
66
    public function apiMessage($appId)
67
    {
68
        $this->ensureAppIsSet($appId)
69
            ->hincrby($this->getHash($appId), 'api_message_count', 1);
70
    }
71
72
    /**
73
     * Handle the new conection.
74
     *
75
     * @param  mixed  $appId
76
     * @return void
77
     */
78 View Code Duplication
    public function connection($appId)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
79
    {
80
        $currentConnectionCount = $this->ensureAppIsSet($appId)
81
            ->hincrby($this->getHash($appId), 'current_connection_count', 1);
82
83
        $currentPeakConnectionCount = $this->redis->hget($this->getHash($appId), 'peak_connection_count');
84
85
        $peakConnectionCount = is_null($currentPeakConnectionCount)
86
            ? $currentConnectionCount
87
            : max($currentPeakConnectionCount, $currentConnectionCount);
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 View Code Duplication
    public function disconnection($appId)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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
            ? $currentConnectionCount
107
            : max($currentPeakConnectionCount, $currentConnectionCount);
108
109
        $this->redis->hset($this->getHash($appId), 'peak_connection_count', $peakConnectionCount);
110
    }
111
112
    /**
113
     * Save all the stored statistics.
114
     *
115
     * @return void
116
     */
117
    public function save()
118
    {
119
        $this->lock()->get(function () {
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
    /**
142
     * Ensure the app id is stored in the Redis database.
143
     *
144
     * @param  mixed  $appId
145
     * @return \Illuminate\Redis\RedisManager
146
     */
147
    protected function ensureAppIsSet($appId)
148
    {
149
        $this->redis->sadd('laravel-websockets:apps', $appId);
150
151
        return $this->redis;
152
    }
153
154
    /**
155
     * Reset the statistics to a specific connection count.
156
     *
157
     * @param  mixed  $appId
158
     * @param  int  $currentConnectionCount
159
     * @return void
160
     */
161 View Code Duplication
    public function resetStatistics($appId, int $currentConnectionCount)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
162
    {
163
        $this->redis->hset($this->getHash($appId), 'current_connection_count', $currentConnectionCount);
164
        $this->redis->hset($this->getHash($appId), 'peak_connection_count', $currentConnectionCount);
165
        $this->redis->hset($this->getHash($appId), 'websocket_message_count', 0);
166
        $this->redis->hset($this->getHash($appId), 'api_message_count', 0);
167
    }
168
169
    /**
170
     * Remove all app traces from the database if no connections have been set
171
     * in the meanwhile since last save.
172
     *
173
     * @param  mixed  $appId
174
     * @return void
175
     */
176 View Code Duplication
    public function resetAppTraces($appId)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
177
    {
178
        $this->redis->hdel($this->getHash($appId), 'current_connection_count');
179
        $this->redis->hdel($this->getHash($appId), 'peak_connection_count');
180
        $this->redis->hdel($this->getHash($appId), 'websocket_message_count');
181
        $this->redis->hdel($this->getHash($appId), 'api_message_count');
182
183
        $this->redis->srem('laravel-websockets:apps', $appId);
184
    }
185
186
    /**
187
     * Get the Redis hash name for the app.
188
     *
189
     * @param  mixed  $appId
190
     * @return string
191
     */
192
    protected function getHash($appId): string
193
    {
194
        return "laravel-websockets:app:{$appId}";
195
    }
196
197
    /**
198
     * Get a new RedisLock instance to avoid race conditions.
199
     *
200
     * @return \Illuminate\Cache\CacheLock
201
     */
202
    protected function lock()
203
    {
204
        return new RedisLock($this->redis, 'laravel-websockets:lock', 0);
0 ignored issues
show
Documentation introduced by
$this->redis is of type object<Illuminate\Redis\RedisManager>, but the function expects a object<Illuminate\Redis\Connections\Connection>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
205
    }
206
}
207