getCommand(); $command->setArguments($actualArguments); $this->assertSame($expectedArguments, $command->getArguments()); } /** * @group disconnected */ public function testParseResponse(): void { $this->assertSame(1, $this->getCommand()->parseResponse(1)); } /** * @group connected * @requiresRedisVersion >= 8.5.0 * @return void */ public function testRetrieveHotKeys() { $redis = $this->getClient(); // Starts hotkeys tracking (CPU only) $this->assertEquals('OK', $redis->hotkeys->start([Container::CPU])); $this->assertEquals('OK', $redis->set('key', 'value')); $this->assertEquals('OK', $redis->hotkeys->stop()); $hotkeysInfo = $redis->hotkeys->get()[0]; $this->assertContains('key', $hotkeysInfo['by-cpu-time-us']); // Starts hotkeys tracking (CPU and NET) $this->assertEquals('OK', $redis->hotkeys->start([Container::CPU, Container::NET])); $this->assertEquals('OK', $redis->set('key', 'value')); $this->assertEquals('OK', $redis->set('key1', 'value1')); $this->assertEquals('OK', $redis->hotkeys->stop()); $hotkeysInfo = $redis->hotkeys->get()[0]; $this->assertContains('key', $hotkeysInfo['by-cpu-time-us']); $this->assertContains('key1', $hotkeysInfo['by-cpu-time-us']); $this->assertContains('key', $hotkeysInfo['by-net-bytes']); $this->assertContains('key1', $hotkeysInfo['by-net-bytes']); // Starts hotkeys tracking (limited COUNT) $this->assertEquals('OK', $redis->hotkeys->start([Container::CPU, Container::NET], 12)); for ($i = 0; $i < 13; $i++) { $redis->set("key:$i", "value:$i"); } $this->assertEquals('OK', $redis->hotkeys->stop()); $hotkeysInfo = $redis->hotkeys->get()[0]; $tracked = count($hotkeysInfo['by-cpu-time-us']); // COUNT 12 is an upper bound on tracked keys (24 = 12 key/score pairs); // sampling may return fewer entries, but always in even-sized key/score pairs. $this->assertNotEmpty($hotkeysInfo['by-cpu-time-us']); $this->assertLessThanOrEqual(24, $tracked); $this->assertSame(0, $tracked % 2); // Starts hotkeys tracking (with DURATION, SAMPLE) $this->assertEquals( 'OK', $redis->hotkeys->start([Container::CPU, Container::NET], null, 1, 10) ); $this->sleep(1.2); $hotkeysInfo = $redis->hotkeys->get()[0]; $this->assertEquals(0, $hotkeysInfo['tracking-active']); $this->assertEquals(10, $hotkeysInfo['sample-ratio']); $this->assertEquals('OK', $redis->hotkeys->reset()); $hotkeysInfo = $redis->hotkeys->get()[0]; $this->assertEquals(0, $hotkeysInfo['tracking-active']); $this->assertNull($hotkeysInfo['sample-ratio']); $this->assertEmpty($hotkeysInfo['selected-slots']); $this->assertEmpty($hotkeysInfo['by-cpu-time-us']); $this->assertEmpty($hotkeysInfo['by-net-bytes']); } /** * @group connected * @requiresRedisVersion >= 8.5.0 * @return void */ public function testRetrieveHotKeysResp3() { $redis = $this->getResp3Client(); // Starts hotkeys tracking (CPU only) $this->assertEquals('OK', $redis->hotkeys->start([Container::CPU])); $this->assertEquals('OK', $redis->set('key', 'value')); $this->assertEquals('OK', $redis->hotkeys->stop()); $hotkeysInfo = $redis->hotkeys->get()[0]; $this->assertContains('key', $hotkeysInfo['by-cpu-time-us']); // Starts hotkeys tracking (CPU and NET) $this->assertEquals('OK', $redis->hotkeys->start([Container::CPU, Container::NET])); $this->assertEquals('OK', $redis->set('key', 'value')); $this->assertEquals('OK', $redis->set('key1', 'value1')); $this->assertEquals('OK', $redis->hotkeys->stop()); $hotkeysInfo = $redis->hotkeys->get()[0]; $this->assertContains('key', $hotkeysInfo['by-cpu-time-us']); $this->assertContains('key1', $hotkeysInfo['by-cpu-time-us']); $this->assertContains('key', $hotkeysInfo['by-net-bytes']); $this->assertContains('key1', $hotkeysInfo['by-net-bytes']); // Starts hotkeys tracking (limited COUNT) $this->assertEquals('OK', $redis->hotkeys->start([Container::CPU, Container::NET], 12)); for ($i = 0; $i < 13; $i++) { $redis->set("key:$i", "value:$i"); } $this->assertEquals('OK', $redis->hotkeys->stop()); $hotkeysInfo = $redis->hotkeys->get()[0]; $tracked = count($hotkeysInfo['by-cpu-time-us']); // COUNT 12 is an upper bound on tracked keys (24 = 12 key/score pairs); // sampling may return fewer entries, but always in even-sized key/score pairs. $this->assertNotEmpty($hotkeysInfo['by-cpu-time-us']); $this->assertLessThanOrEqual(24, $tracked); $this->assertSame(0, $tracked % 2); // Starts hotkeys tracking (with DURATION, SAMPLE) $this->assertEquals( 'OK', $redis->hotkeys->start([Container::CPU, Container::NET], null, 1, 10) ); $this->sleep(1.2); $hotkeysInfo = $redis->hotkeys->get()[0]; $this->assertEquals(0, $hotkeysInfo['tracking-active']); $this->assertEquals(10, $hotkeysInfo['sample-ratio']); $this->assertEquals('OK', $redis->hotkeys->reset()); $hotkeysInfo = $redis->hotkeys->get()[0]; $this->assertEquals(0, $hotkeysInfo['tracking-active']); $this->assertNull($hotkeysInfo['sample-ratio']); $this->assertEmpty($hotkeysInfo['selected-slots']); $this->assertEmpty($hotkeysInfo['by-cpu-time-us']); $this->assertEmpty($hotkeysInfo['by-net-bytes']); } /** * @group connected * @group cluster * @requiresRedisVersion >= 8.5.0 * @return void */ public function testHotkeysStartDisabledInClusterClient() { $redis = $this->getClient(); $this->expectException(NotSupportedException::class); $this->expectExceptionMessage("Cannot use 'HOTKEYS' with redis-cluster"); $redis->hotkeys->start([Container::CPU]); } /** * @group disconnected */ public function testThrowsExceptionOnInvalidSampleValue(): void { $command = $this->getCommand(); $this->expectException(ValueError::class); $this->expectExceptionMessage('Sample value should be greater than 0'); $command->setArguments(['START', ['metric1', 'metric2'], null, null, 0]); } /** * @group disconnected */ public function testThrowsExceptionOnNegativeSampleValue(): void { $command = $this->getCommand(); $this->expectException(ValueError::class); $this->expectExceptionMessage('Sample value should be greater than 0'); $command->setArguments(['START', ['metric1', 'metric2'], null, null, -1]); } /** * @group disconnected */ public function testThrowsExceptionOnCountValueTooLow(): void { $command = $this->getCommand(); $this->expectException(ValueError::class); $this->expectExceptionMessage('Count value should be between 10 and 64'); $command->setArguments(['START', ['metric1', 'metric2'], 9]); } /** * @group disconnected */ public function testThrowsExceptionOnCountValueTooHigh(): void { $command = $this->getCommand(); $this->expectException(ValueError::class); $this->expectExceptionMessage('Count value should be between 10 and 64'); $command->setArguments(['START', ['metric1', 'metric2'], 65]); } /** * @group disconnected */ public function testThrowsExceptionOnCountValueZero(): void { $command = $this->getCommand(); $this->expectException(ValueError::class); $this->expectExceptionMessage('Count value should be between 10 and 64'); $command->setArguments(['START', ['metric1', 'metric2'], 0]); } public function argumentsProvider(): array { return [ 'with non-START subcommand' => [ ['STOP'], ['STOP'], ], 'with START and metrics only' => [ ['START', ['metric1', 'metric2']], ['START', 'METRICS', 2, 'metric1', 'metric2'], ], 'with START, metrics and COUNT' => [ ['START', ['metric1', 'metric2'], 50], ['START', 'METRICS', 2, 'metric1', 'metric2', 'COUNT', 50], ], 'with START, metrics, COUNT and DURATION' => [ ['START', ['metric1', 'metric2'], 50, 60], ['START', 'METRICS', 2, 'metric1', 'metric2', 'COUNT', 50, 'DURATION', 60], ], 'with START, metrics, COUNT, DURATION and SAMPLE' => [ ['START', ['metric1', 'metric2'], 50, 60, 10], ['START', 'METRICS', 2, 'metric1', 'metric2', 'COUNT', 50, 'DURATION', 60, 'SAMPLE', 10], ], 'with START, metrics, COUNT, DURATION, SAMPLE and SLOTS' => [ ['START', ['metric1', 'metric2'], 50, 60, 10, [1, 2, 3]], ['START', 'METRICS', 2, 'metric1', 'metric2', 'COUNT', 50, 'DURATION', 60, 'SAMPLE', 10, 'SLOTS', 3, 1, 2, 3], ], 'with START, metrics and SLOTS (no COUNT, DURATION, SAMPLE)' => [ ['START', ['metric1'], null, null, null, [5, 10]], ['START', 'METRICS', 1, 'metric1', 'SLOTS', 2, 5, 10], ], 'with START, metrics, COUNT and SLOTS (no DURATION, SAMPLE)' => [ ['START', ['metric1'], 50, null, null, [1, 2]], ['START', 'METRICS', 1, 'metric1', 'COUNT', 50, 'SLOTS', 2, 1, 2], ], 'with START, metrics, COUNT, DURATION and SLOTS (no SAMPLE)' => [ ['START', ['metric1'], 50, 30, null, [1]], ['START', 'METRICS', 1, 'metric1', 'COUNT', 50, 'DURATION', 30, 'SLOTS', 1, 1], ], 'with START and single metric' => [ ['START', ['metric1']], ['START', 'METRICS', 1, 'metric1'], ], 'with START and multiple metrics' => [ ['START', ['metric1', 'metric2', 'metric3']], ['START', 'METRICS', 3, 'metric1', 'metric2', 'metric3'], ], 'with START, metrics and minimum valid COUNT (10)' => [ ['START', ['metric1'], 10], ['START', 'METRICS', 1, 'metric1', 'COUNT', 10], ], 'with START, metrics and maximum valid COUNT (64)' => [ ['START', ['metric1'], 64], ['START', 'METRICS', 1, 'metric1', 'COUNT', 64], ], ]; } }