1111use function explode ;
1212use function is_numeric ;
1313use function preg_match ;
14+ use function preg_match_all ;
1415use function preg_replace ;
16+ use function rtrim ;
1517use function str_contains ;
1618use function str_ends_with ;
1719use function str_starts_with ;
@@ -27,6 +29,20 @@ class PHPMem {
2729
2830 private ?string $ server_version = null ;
2931
32+ private const NO_END_COMMANDS = [
33+ 'me ' => true , 'incr ' => true , 'decr ' => true , 'version ' => true ,
34+ 'ms ' => true , 'md ' => true , 'ma ' => true , 'cache_memlimit ' => true ,
35+ 'mn ' => true , 'quit ' => true , 'mg ' => true ,
36+ ];
37+
38+ private const END_MARKERS = [
39+ "ERROR \r\n" , "CLIENT_ERROR \r\n" , "SERVER_ERROR \r\n" , "STORED \r\n" ,
40+ "NOT_STORED \r\n" , "EXISTS \r\n" , "NOT_FOUND \r\n" , "TOUCHED \r\n" ,
41+ "DELETED \r\n" , "OK \r\n" , "END \r\n" , "BUSY \r\n" , "BADCLASS \r\n" ,
42+ "NOSPARE \r\n" , "NOTFULL \r\n" , "UNSAFE \r\n" , "SAME \r\n" , "RESET \r\n" ,
43+ "EN \r\n" ,
44+ ];
45+
3046 /**
3147 * @param array<string, int|string> $server
3248 */
@@ -177,27 +193,35 @@ public function getKeys(): array {
177193 return $ lines ;
178194 }
179195
180- $ slabs = $ this ->runCommand ('stats items ' );
181- $ lines = explode ("\n" , $ slabs );
182- $ slab_ids = [];
196+ $ slabs_raw = $ this ->runCommand ('stats items ' );
197+ $ keys = [];
198+ $ seen_slabs = [];
199+ $ seen_keys = [];
183200
184- foreach ($ lines as $ line ) {
185- if (preg_match ('/STAT items:(\d+):/ ' , $ line , $ matches )) {
186- $ slab_ids [] = $ matches [1 ];
187- }
188- }
201+ foreach (explode ("\n" , $ slabs_raw ) as $ line ) {
202+ if (preg_match ('/STAT items:(\d+):/ ' , $ line , $ m )) {
203+ $ slab_id = $ m [1 ];
189204
190- $ keys = [];
205+ if (isset ($ seen_slabs [$ slab_id ])) {
206+ continue ;
207+ }
208+
209+ $ seen_slabs [$ slab_id ] = true ;
191210
192- foreach (array_unique ($ slab_ids ) as $ slab_id ) {
193- $ dump = $ this ->runCommand ('stats cachedump ' .$ slab_id .' 0 ' );
194- $ dump_lines = explode ("\n" , $ dump );
211+ $ dump = $ this ->runCommand ('stats cachedump ' .$ slab_id .' 100000 ' );
195212
196- foreach ($ dump_lines as $ line ) {
197- if (preg_match ('/ITEM (\S+) \[(\d+) b; (\d+) s]/ ' , $ line , $ matches )) {
198- $ exp = (int ) $ matches [3 ] === 0 ? -1 : (int ) $ matches [3 ];
199- // Intentionally formatted as lru_crawler output
200- $ keys [] = 'key= ' .$ matches [1 ].' exp= ' .$ exp .' la=0 cas=0 fetch=no cls=1 size= ' .$ matches [2 ];
213+ if (preg_match_all ('/ITEM (\S+) \[(\d+) b; (\d+) s]/ ' , $ dump , $ matches , PREG_SET_ORDER )) {
214+ foreach ($ matches as $ item ) {
215+ $ key_name = $ item [1 ];
216+
217+ if (isset ($ seen_keys [$ key_name ])) {
218+ continue ;
219+ }
220+ $ seen_keys [$ key_name ] = true ;
221+
222+ $ exp = ((int ) $ item [3 ] === 0 ) ? -1 : (int ) $ item [3 ];
223+ $ keys [] = 'key= ' .$ key_name .' exp= ' .$ exp .' la=0 cas=0 fetch=no cls=1 size= ' .$ item [2 ];
224+ }
201225 }
202226 }
203227 }
@@ -212,7 +236,8 @@ public function getKeys(): array {
212236 */
213237 public function parseLine (string $ line ): array {
214238 $ data = [];
215- if (preg_match_all ('/(\w+)=(\S+)/ ' , $ line , $ matches , PREG_SET_ORDER )) {
239+
240+ if (preg_match_all ('/(key|exp|la|size)=(\S+)/ ' , $ line , $ matches , PREG_SET_ORDER )) {
216241 foreach ($ matches as [, $ key , $ val ]) {
217242 switch ($ key ) {
218243 case 'key ' :
@@ -405,8 +430,27 @@ private function streamConnection(string $command, string $command_name): string
405430
406431 $ buffer = '' ;
407432 $ start = microtime (true );
433+ $ select_timeout_usec = 100_000 ; // 100ms
408434
409435 while (microtime (true ) - $ start < 5 ) {
436+ $ read = [$ this ->stream ];
437+ $ write = null ;
438+ $ except = null ;
439+
440+ $ num = @stream_select ($ read , $ write , $ except , 0 , $ select_timeout_usec );
441+
442+ if ($ num === false ) {
443+ usleep (1000 );
444+ continue ;
445+ }
446+
447+ if ($ num === 0 ) {
448+ if ($ this ->checkCommandEnd ($ buffer )) {
449+ break ;
450+ }
451+ continue ;
452+ }
453+
410454 $ chunk = fread ($ this ->stream , 65536 );
411455
412456 if ($ chunk === false ) {
@@ -423,10 +467,7 @@ private function streamConnection(string $command, string $command_name): string
423467 $ buffer .= $ chunk ;
424468
425469 // Commands without a specific end string.
426- if ($ command_name === 'incr ' || $ command_name === 'decr ' || $ command_name === 'version ' ||
427- $ command_name === 'me ' || $ command_name === 'mg ' || $ command_name === 'ms ' ||
428- $ command_name === 'md ' || $ command_name === 'ma ' || $ command_name === 'mn ' ||
429- $ command_name === 'cache_memlimit ' || $ command_name === 'quit ' ) {
470+ if (isset (self ::NO_END_COMMANDS [$ command_name ])) {
430471 break ;
431472 }
432473
@@ -440,25 +481,16 @@ private function streamConnection(string $command, string $command_name): string
440481 }
441482
442483 private function checkCommandEnd (string $ buffer ): bool {
443- return
444- str_ends_with ($ buffer , "ERROR \r\n" ) ||
445- str_ends_with ($ buffer , "CLIENT_ERROR \r\n" ) ||
446- str_ends_with ($ buffer , "SERVER_ERROR \r\n" ) ||
447- str_ends_with ($ buffer , "STORED \r\n" ) ||
448- str_ends_with ($ buffer , "NOT_STORED \r\n" ) ||
449- str_ends_with ($ buffer , "EXISTS \r\n" ) ||
450- str_ends_with ($ buffer , "NOT_FOUND \r\n" ) ||
451- str_ends_with ($ buffer , "TOUCHED \r\n" ) ||
452- str_ends_with ($ buffer , "DELETED \r\n" ) ||
453- str_ends_with ($ buffer , "OK \r\n" ) ||
454- str_ends_with ($ buffer , "END \r\n" ) ||
455- str_ends_with ($ buffer , "BUSY \r\n" ) ||
456- str_ends_with ($ buffer , "BADCLASS \r\n" ) ||
457- str_ends_with ($ buffer , "NOSPARE \r\n" ) ||
458- str_ends_with ($ buffer , "NOTFULL \r\n" ) ||
459- str_ends_with ($ buffer , "UNSAFE \r\n" ) ||
460- str_ends_with ($ buffer , "SAME \r\n" ) ||
461- str_ends_with ($ buffer , "RESET \r\n" ) ||
462- str_ends_with ($ buffer , "EN \r\n" );
484+ if ($ buffer === '' ) {
485+ return false ;
486+ }
487+
488+ foreach (self ::END_MARKERS as $ marker ) {
489+ if (str_ends_with ($ buffer , $ marker )) {
490+ return true ;
491+ }
492+ }
493+
494+ return false ;
463495 }
464496}
0 commit comments