Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/Connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand Down Expand Up @@ -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) {
Expand Down
37 changes: 37 additions & 0 deletions src/Requests/StatsRequest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

namespace Throttr\SDK\Requests;

use Throttr\SDK\Enum\RequestType;
use Throttr\SDK\Enum\ValueSize;

/**
* List request
*/
class StatsRequest extends BaseRequest
{
/**
* Type
*
* @var RequestType
*/
public RequestType $type = RequestType::STATS;

/**
* Constructor
*/
public function __construct()
{
}

/**
* To bytes
*
* @param ValueSize $size
* @return string
*/
public function toBytes(ValueSize $size): string
{
return pack(static::pack(ValueSize::UINT8), $this->type->value);
}
}
127 changes: 127 additions & 0 deletions src/Responses/StatsResponse.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
<?php

namespace Throttr\SDK\Responses;

use Throttr\SDK\Enum\KeyType;
use Throttr\SDK\Enum\TTLType;
use Throttr\SDK\Enum\ValueSize;
use Throttr\SDK\Requests\BaseRequest;

/**
* StatsResponse
*/
class StatsResponse extends Response implements IResponse {
/**
* Constructor
*
* @param string $data
* @param bool $status
* @param array $keys
*/
public function __construct(public string $data, public bool $status, public array $keys) {}

/**
* From bytes
*
* @param string $data
* @param ValueSize $size
* @return StatsResponse|null
*/
public static function fromBytes(string $data, ValueSize $size) : StatsResponse|null {
$valueSize = $size->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, []);
}
}

13 changes: 13 additions & 0 deletions src/Service.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand Down Expand Up @@ -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
*
Expand Down
73 changes: 73 additions & 0 deletions tests/ServiceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
}

}