From 2efc5e801f649bdd0d528640543e9f7fb7ed2cc4 Mon Sep 17 00:00:00 2001 From: Ian Torres Date: Tue, 8 Jul 2025 22:58:37 -0400 Subject: [PATCH] STATS implemented. --- src/Connection.php | 2 + src/Requests/StatsRequest.php | 37 ++++++++++ src/Responses/StatsResponse.php | 127 ++++++++++++++++++++++++++++++++ src/Service.php | 13 ++++ tests/ServiceTest.php | 73 ++++++++++++++++++ 5 files changed, 252 insertions(+) create mode 100644 src/Requests/StatsRequest.php create mode 100644 src/Responses/StatsResponse.php diff --git a/src/Connection.php b/src/Connection.php index 82849bd..71a3210 100644 --- a/src/Connection.php +++ b/src/Connection.php @@ -26,6 +26,7 @@ use Throttr\SDK\Responses\InfoResponse; use Throttr\SDK\Responses\ListResponse; use Throttr\SDK\Responses\QueryResponse; +use Throttr\SDK\Responses\StatsResponse; use Throttr\SDK\Responses\StatusResponse; /** @@ -177,6 +178,7 @@ private function processResponses(): void RequestType::GET => GetResponse::fromBytes($buffer, $this->size), RequestType::LIST => ListResponse::fromBytes($buffer, $this->size), RequestType::INFO => InfoResponse::fromBytes($buffer, $this->size), + RequestType::STATS => StatsResponse::fromBytes($buffer, $this->size), }; if ($response === null) { diff --git a/src/Requests/StatsRequest.php b/src/Requests/StatsRequest.php new file mode 100644 index 0000000..cbdc604 --- /dev/null +++ b/src/Requests/StatsRequest.php @@ -0,0 +1,37 @@ +type->value); + } +} diff --git a/src/Responses/StatsResponse.php b/src/Responses/StatsResponse.php new file mode 100644 index 0000000..02cd401 --- /dev/null +++ b/src/Responses/StatsResponse.php @@ -0,0 +1,127 @@ +value; + $offset = 0; + + // Less than 1 byte? not enough for status. + if (strlen($data) < 1) return null; + + $status = ord($data[$offset]) === 1; + $offset++; + + if ($status) { + // Less than 1 + N bytes? not enough for quota. + if (strlen($data) < 1 + 8) return null; + + $fragments = unpack(BaseRequest::pack(ValueSize::UINT64), substr($data, $offset, ValueSize::UINT64->value))[1]; + $offset += ValueSize::UINT64->value; + + if ($fragments === 0) return new StatsResponse($data, true, []); + + $keys_container = []; + + for ($i = 0; $i < $fragments; ++$i) { + // Less than offset + 8 bytes? not enough for fragment index. + if (strlen($data) < $offset + ValueSize::UINT64->value) return null; + + $fragment = unpack(BaseRequest::pack(ValueSize::UINT64), substr($data, $offset, ValueSize::UINT64->value))[1]; + $offset += ValueSize::UINT64->value; + + // Less than offset + 8 bytes? not enough for fragment keys count. + if (strlen($data) < $offset + ValueSize::UINT64->value) return null; + + $number_of_keys = unpack(BaseRequest::pack(ValueSize::UINT64), substr($data, $offset, ValueSize::UINT64->value))[1]; + $offset += ValueSize::UINT64->value; + + $keys_in_fragment = []; + + // Per key in fragment + for ($e = 0; $e < $number_of_keys; ++$e) { + // Less than offset + 1 byte? not enough for key size. + if (strlen($data) < $offset + ValueSize::UINT8->value) return null; + + $key_size = unpack(BaseRequest::pack(ValueSize::UINT8), substr($data, $offset, ValueSize::UINT8->value))[1]; + $offset += ValueSize::UINT8->value; + + // Less than offset + 8 bytes? not enough for reads per minute. + if (strlen($data) < $offset + ValueSize::UINT64->value) return null; + + $reads_per_minute = unpack(BaseRequest::pack(ValueSize::UINT64), substr($data, $offset, ValueSize::UINT64->value))[1]; + $offset += ValueSize::UINT64->value; + + // Less than offset + 8 bytes? not enough for writes per minute. + if (strlen($data) < $offset + ValueSize::UINT64->value) return null; + + $writes_per_minute = unpack(BaseRequest::pack(ValueSize::UINT64), substr($data, $offset, ValueSize::UINT64->value))[1]; + $offset += ValueSize::UINT64->value; + + // Less than offset + 8 bytes? not enough for total reads. + if (strlen($data) < $offset + ValueSize::UINT64->value) return null; + + $total_reads = unpack(BaseRequest::pack(ValueSize::UINT64), substr($data, $offset, ValueSize::UINT64->value))[1]; + $offset += ValueSize::UINT64->value; + + // Less than offset + 8 bytes? not enough for total reads. + if (strlen($data) < $offset + ValueSize::UINT64->value) return null; + + $total_writes = unpack(BaseRequest::pack(ValueSize::UINT64), substr($data, $offset, ValueSize::UINT64->value))[1]; + $offset += ValueSize::UINT64->value; + + $keys_in_fragment[] = [ + "size" => $key_size, + "reads_per_minute" => $reads_per_minute, + "writes_per_minute" => $writes_per_minute, + "total_reads" => $total_reads, + "total_writes" => $total_writes, + ]; + } + + $total = array_sum(array_column($keys_in_fragment, 'size')); + + // Less than offset + total keys bytes? not enough for name parsing + if (strlen($data) < $offset + $total) return null; + + for ($e = 0; $e < $number_of_keys; ++$e) { + $keys_in_fragment[$e]["key"] = substr($data, $offset, $keys_in_fragment[$e]["size"]); + $offset += $keys_in_fragment[$e]["size"]; + unset($keys_in_fragment[$e]["size"]); + } + + $keys_container = array_merge($keys_container, $keys_in_fragment); + } + + return new StatsResponse($data, true, $keys_container); + } + + return new StatsResponse($data, false, []); + } +} + diff --git a/src/Service.php b/src/Service.php index 2a97b09..9c39ab2 100644 --- a/src/Service.php +++ b/src/Service.php @@ -31,12 +31,14 @@ use Throttr\SDK\Requests\PurgeRequest; use Throttr\SDK\Requests\QueryRequest; use Throttr\SDK\Requests\SetRequest; +use Throttr\SDK\Requests\StatsRequest; use Throttr\SDK\Requests\UpdateRequest; use Throttr\SDK\Responses\GetResponse; use Throttr\SDK\Responses\InfoResponse; use Throttr\SDK\Responses\IResponse; use Throttr\SDK\Responses\ListResponse; use Throttr\SDK\Responses\QueryResponse; +use Throttr\SDK\Responses\StatsResponse; use Throttr\SDK\Responses\StatusResponse; /** @@ -154,6 +156,17 @@ public function list(): ListResponse return $this->send([$request])[0]; } + /** + * STATS + * + * @return StatsResponse + */ + public function stats(): StatsResponse + { + $request = new StatsRequest(); + return $this->send([$request])[0]; + } + /** * INFO * diff --git a/tests/ServiceTest.php b/tests/ServiceTest.php index 288a1f6..0679d04 100644 --- a/tests/ServiceTest.php +++ b/tests/ServiceTest.php @@ -325,4 +325,77 @@ public function testInfo() $this->assertArrayHasKey("version", $info->attributes); }); } + + public function testStats() { + $this->prepares(function (Service $service) { + $key = 'STATS_KEY'; + $stats = $service->stats(); + $this->assertTrue($stats->status); + $this->assertCount(0, $stats->keys); + + $service->insert( + key: $key, + ttl: 3, + ttlType: TTLType::SECONDS, + quota: 10, + ); + + $stats = $service->stats(); + + $this->assertTrue($stats->status); + $this->assertCount(1, $stats->keys); + $this->assertEquals($key, $stats->keys[0]["key"]); + $this->assertEquals(0, $stats->keys[0]["reads_per_minute"]); + $this->assertEquals(0, $stats->keys[0]["writes_per_minute"]); + $this->assertEquals(0, $stats->keys[0]["total_writes"]); + $this->assertEquals(0, $stats->keys[0]["total_reads"]); + + $service->purge( + key: $key, + ); + + $stats = $service->stats(); + $this->assertTrue($stats->status); + $this->assertCount(0, $stats->keys); + }); + } + + public function testFragmentedStats() { + $this->prepares(function (Service $service) { + $stats = $service->stats(); + $this->assertTrue($stats->status); + $this->assertCount(0, $stats->keys); + + $keys = []; + + for ($i = 0; $i < 1000; $i++) { + $key = "TEST_FRAGMENTED_STATS_$i"; + $insertResponse = $service->insert( + key: $key, + ttl: 15, + ttlType: TTLType::SECONDS, + quota: 10, + ); + $keys[] = $key; + + $this->assertTrue($insertResponse->status); + } + + $stats = $service->stats(); + + $this->assertTrue($stats->status); + $this->assertCount(1000, $stats->keys); + + foreach ($keys as $key) { + $service->purge( + key: $key, + ); + } + + $stats = $service->stats(); + $this->assertTrue($stats->status); + $this->assertCount(0, $stats->keys); + }); + } + }