Skip to content

Commit

Permalink
PDO storage adapter - MySQL support.
Browse files Browse the repository at this point in the history
Signed-off-by: Janez Urevc <[email protected]>
  • Loading branch information
slashrsm committed Jul 29, 2024
1 parent 9b16cbd commit af59152
Show file tree
Hide file tree
Showing 6 changed files with 194 additions and 54 deletions.
158 changes: 109 additions & 49 deletions src/Prometheus/Storage/PDO.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,15 @@ class PDO implements Adapter
* PDO database connection.
* @param string $prefix
* Database table prefix (default: "prometheus_").
* @param array{0: int, 1: int} $precision
* Precision of the 'value' DECIMAL column in the database table (default: 16, 2).
*/
public function __construct(\PDO $database, string $prefix = 'prometheus_', array $precision = [16, 2])
public function __construct(\PDO $database, string $prefix = 'prometheus_')
{
if (!in_array($database->getAttribute(\PDO::ATTR_DRIVER_NAME), ['mysql', 'sqlite'], true)) {
throw new \RuntimeException('Only MySQL and SQLite are supported.');
}

$this->database = $database;
$this->prefix = $prefix;
$this->precision = $precision;

$this->createTables();
}
Expand All @@ -67,6 +68,14 @@ public function wipeStorage(): void
$this->database->query("DELETE FROM `{$this->prefix}_histograms`");
}

public function deleteTables(): void
{
$this->database->query("DROP TABLE `{$this->prefix}_metadata`");
$this->database->query("DROP TABLE `{$this->prefix}_values`");
$this->database->query("DROP TABLE `{$this->prefix}_summaries`");
$this->database->query("DROP TABLE `{$this->prefix}_histograms`");
}

/**
* @return MetricFamilySamples[]
*/
Expand Down Expand Up @@ -283,26 +292,31 @@ protected function collectGauges(): array
*/
public function updateHistogram(array $data): void
{
// TODO do we update metadata at all? If metadata changes then the old labels might not be correct any more?
$metadata_sql = <<<SQL
INSERT INTO `{$this->prefix}_metadata`
VALUES(:name, :type, :metadata)
ON CONFLICT(name, type) DO UPDATE SET
metadata=excluded.metadata;
SQL;
$statement = $this->database->prepare($metadata_sql);
$statement->execute([
':name' => $data['name'],
':type' => Histogram::TYPE,
':metadata' => $this->encodeMetadata($data),
]);
$this->updateMetadata($data, Histogram::TYPE);

$values_sql = <<<SQL
switch ($this->database->getAttribute(\PDO::ATTR_DRIVER_NAME)) {
case 'sqlite':
$values_sql = <<<SQL
INSERT INTO `{$this->prefix}_histograms`(`name`, `labels_hash`, `labels`, `value`, `bucket`)
VALUES(:name,:hash,:labels,:value,:bucket)
ON CONFLICT(name, labels_hash, bucket) DO UPDATE SET
`value` = `value` + excluded.value;
SQL;
break;

case 'mysql':
$values_sql = <<<SQL
INSERT INTO `{$this->prefix}_histograms`(`name`, `labels_hash`, `labels`, `value`, `bucket`)
VALUES(:name,:hash,:labels,:value,:bucket)
ON DUPLICATE KEY UPDATE
`value` = `value` + VALUES(`value`);
SQL;
break;

default:
throw new \RuntimeException('Unsupported database type');
}


$statement = $this->database->prepare($values_sql);
$label_values = $this->encodeLabelValues($data);
Expand Down Expand Up @@ -337,22 +351,9 @@ public function updateHistogram(array $data): void
*/
public function updateSummary(array $data): void
{
// TODO do we update metadata at all? If metadata changes then the old labels might not be correct any more?
$metadata_sql = <<<SQL
INSERT INTO `{$this->prefix}_metadata`
VALUES(:name, :type, :metadata)
ON CONFLICT(name, type) DO UPDATE SET
metadata=excluded.metadata;
SQL;

$statement = $this->database->prepare($metadata_sql);
$statement->execute([
':name' => $data['name'],
':type' => Summary::TYPE,
':metadata' => $this->encodeMetadata($data),
]);
$this->updateMetadata($data, Summary::TYPE);

$values_sql = <<<SQL
$values_sql = <<<SQL
INSERT INTO `{$this->prefix}_summaries`(`name`, `labels_hash`, `labels`, `value`, `time`)
VALUES(:name,:hash,:labels,:value,:time)
SQL;
Expand Down Expand Up @@ -387,37 +388,85 @@ public function updateCounter(array $data): void
/**
* @param mixed[] $data
*/
protected function updateStandard(array $data, string $type): void
protected function updateMetadata(array $data, string $type): void
{
// TODO do we update metadata at all? If metadata changes then the old labels might not be correct any more?
$metadata_sql = <<<SQL
switch ($this->database->getAttribute(\PDO::ATTR_DRIVER_NAME)) {
case 'sqlite':
$metadata_sql = <<<SQL
INSERT INTO `{$this->prefix}_metadata`
VALUES(:name, :type, :metadata)
ON CONFLICT(name, type) DO UPDATE SET
metadata=excluded.metadata;
`metadata` = excluded.metadata;
SQL;
break;

case 'mysql':
$metadata_sql = <<<SQL
INSERT INTO `{$this->prefix}_metadata`
VALUES(:name, :type, :metadata)
ON DUPLICATE KEY UPDATE
`metadata` = VALUES(`metadata`);
SQL;
break;

default:
throw new \RuntimeException('Unsupported database type');
}
$statement = $this->database->prepare($metadata_sql);
$statement->execute([
':name' => $data['name'],
':type' => $type,
':metadata' => $this->encodeMetadata($data),
]);
}

if ($data['command'] === Adapter::COMMAND_SET) {
$values_sql = <<<SQL
/**
* @param mixed[] $data
*/
protected function updateStandard(array $data, string $type): void
{
$this->updateMetadata($data, $type);

switch ($this->database->getAttribute(\PDO::ATTR_DRIVER_NAME)) {
case 'sqlite':
if ($data['command'] === Adapter::COMMAND_SET) {
$values_sql = <<<SQL
INSERT INTO `{$this->prefix}_values`(`name`, `type`, `labels_hash`, `labels`, `value`)
VALUES(:name,:type,:hash,:labels,:value)
ON CONFLICT(name, type, labels_hash) DO UPDATE SET
`value` = excluded.value;
SQL;
} else {
$values_sql = <<<SQL
} else {
$values_sql = <<<SQL
INSERT INTO `{$this->prefix}_values`(`name`, `type`, `labels_hash`, `labels`, `value`)
VALUES(:name,:type,:hash,:labels,:value)
ON CONFLICT(name, type, labels_hash) DO UPDATE SET
`value` = `value` + excluded.value;
SQL;
}
break;

case 'mysql':
if ($data['command'] === Adapter::COMMAND_SET) {
$values_sql = <<<SQL
INSERT INTO `{$this->prefix}_values`(`name`, `type`, `labels_hash`, `labels`, `value`)
VALUES(:name,:type,:hash,:labels,:value)
ON DUPLICATE KEY UPDATE
`value` = VALUES(`value`);
SQL;
} else {
$values_sql = <<<SQL
INSERT INTO `{$this->prefix}_values`(`name`, `type`, `labels_hash`, `labels`, `value`)
VALUES(:name,:type,:hash,:labels,:value)
ON DUPLICATE KEY UPDATE
`value` = `value` + VALUES(`value`);
SQL;
}
break;

default:
throw new \RuntimeException('Unsupported database type');
}

$statement = $this->database->prepare($values_sql);
Expand All @@ -433,6 +482,7 @@ protected function updateStandard(array $data, string $type): void

protected function createTables(): void
{
$driver = $this->database->getAttribute(\PDO::ATTR_DRIVER_NAME);
$sql = <<<SQL
CREATE TABLE IF NOT EXISTS `{$this->prefix}_metadata` (
`name` varchar(255) NOT NULL,
Expand All @@ -443,36 +493,46 @@ protected function createTables(): void
SQL;
$this->database->query($sql);

$hash_size = $driver == 'sqlite' ? 32 : 64;
$sql = <<<SQL
CREATE TABLE IF NOT EXISTS `{$this->prefix}_values` (
`name` varchar(255) NOT NULL,
`type` varchar(9) NOT NULL,
`labels_hash` varchar(32) NOT NULL,
`labels_hash` varchar({$hash_size}) NOT NULL,
`labels` TEXT NOT NULL,
`value` DECIMAL({$this->precision[0]},{$this->precision[1]}) DEFAULT 0.0,
`value` double DEFAULT 0.0,
PRIMARY KEY (`name`, `type`, `labels_hash`)
)
SQL;
$this->database->query($sql);

$timestamp_type = $driver == 'sqlite' ? 'timestamp' : 'int';
$sql = <<<SQL
CREATE TABLE IF NOT EXISTS `{$this->prefix}_summaries` (
`name` varchar(255) NOT NULL,
`labels_hash` varchar(32) NOT NULL,
`labels_hash` varchar({$hash_size}) NOT NULL,
`labels` TEXT NOT NULL,
`value` DECIMAL({$this->precision[0]},{$this->precision[1]}) DEFAULT 0.0,
`time` timestamp NOT NULL
);
CREATE INDEX `name` ON `{$this->prefix}_summaries`(`name`);
`value` double DEFAULT 0.0,
`time` {$timestamp_type} NOT NULL
SQL;
switch ($driver) {
case 'sqlite':
$sql .= "); CREATE INDEX `name` ON `{$this->prefix}_summaries`(`name`);";
break;

case 'mysql':
$sql .= ", KEY `name` (`name`));";
break;
}

$this->database->query($sql);

$sql = <<<SQL
CREATE TABLE IF NOT EXISTS `{$this->prefix}_histograms` (
`name` varchar(255) NOT NULL,
`labels_hash` varchar(32) NOT NULL,
`labels_hash` varchar({$hash_size}) NOT NULL,
`labels` TEXT NOT NULL,
`value` DECIMAL({$this->precision[0]},{$this->precision[1]}) DEFAULT 0.0,
`value` double DEFAULT 0.0,
`bucket` varchar(255) NOT NULL,
PRIMARY KEY (`name`, `labels_hash`, `bucket`)
);
Expand Down
18 changes: 17 additions & 1 deletion tests/Test/Prometheus/PDO/CollectorRegistryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,25 @@

class CollectorRegistryTest extends AbstractCollectorRegistryTest
{
/**
* @var \PDO|null
*/
private $pdo;

public function configureAdapter(): void
{
$this->adapter = new PDO(new \PDO('sqlite::memory:'));
$this->pdo = new \PDO('sqlite::memory:');
//$this->pdo = new \PDO('mysql:host=db;dbname=db', 'db', 'db');
$prefix = 'test' . substr(hash('sha256', uniqid()), 0, 6) . '_';
$this->adapter = new PDO($this->pdo, $prefix);
$this->adapter->wipeStorage();
}

protected function tearDown(): void
{
parent::tearDown();
$this->adapter->deleteTables(); /** @phpstan-ignore-line */
$this->adapter = null; /** @phpstan-ignore-line */
$this->pdo = null;
}
}
18 changes: 17 additions & 1 deletion tests/Test/Prometheus/PDO/CounterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,25 @@
*/
class CounterTest extends AbstractCounterTest
{
/**
* @var \PDO|null
*/
private $pdo;

public function configureAdapter(): void
{
$this->adapter = new PDO(new \PDO('sqlite::memory:'));
$this->pdo = new \PDO('sqlite::memory:');
//$this->pdo = new \PDO('mysql:host=db;dbname=db', 'db', 'db');
$prefix = 'test' . substr(hash('sha256', uniqid()), 0, 6) . '_';
$this->adapter = new PDO($this->pdo, $prefix);
$this->adapter->wipeStorage();
}

protected function tearDown(): void
{
parent::tearDown();
$this->adapter->deleteTables(); /** @phpstan-ignore-line */
$this->adapter = null; /** @phpstan-ignore-line */
$this->pdo = null;
}
}
18 changes: 17 additions & 1 deletion tests/Test/Prometheus/PDO/GaugeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,25 @@
*/
class GaugeTest extends AbstractGaugeTest
{
/**
* @var \PDO|null
*/
private $pdo;

public function configureAdapter(): void
{
$this->adapter = new PDO(new \PDO('sqlite::memory:'));
$this->pdo = new \PDO('sqlite::memory:');
//$this->pdo = new \PDO('mysql:host=db;dbname=db', 'db', 'db');
$prefix = 'test' . substr(hash('sha256', uniqid()), 0, 6) . '_';
$this->adapter = new PDO($this->pdo, $prefix);
$this->adapter->wipeStorage();
}

protected function tearDown(): void
{
parent::tearDown();
$this->adapter->deleteTables(); /** @phpstan-ignore-line */
$this->adapter = null; /** @phpstan-ignore-line */
$this->pdo = null;
}
}
18 changes: 17 additions & 1 deletion tests/Test/Prometheus/PDO/HistogramTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,25 @@
*/
class HistogramTest extends AbstractHistogramTest
{
/**
* @var \PDO|null
*/
private $pdo;

public function configureAdapter(): void
{
$this->adapter = new PDO(new \PDO('sqlite::memory:'));
$this->pdo = new \PDO('sqlite::memory:');
//$this->pdo = new \PDO('mysql:host=db;dbname=db', 'db', 'db');
$prefix = 'test' . substr(hash('sha256', uniqid()), 0, 6) . '_';
$this->adapter = new PDO($this->pdo, $prefix);
$this->adapter->wipeStorage();
}

public function tearDown(): void
{
parent::tearDown();
$this->adapter->deleteTables(); /** @phpstan-ignore-line */
$this->adapter = null; /** @phpstan-ignore-line */
$this->pdo = null;
}
}
18 changes: 17 additions & 1 deletion tests/Test/Prometheus/PDO/SummaryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,25 @@
*/
class SummaryTest extends AbstractSummaryTest
{
/**
* @var \PDO|null
*/
private $pdo;

public function configureAdapter(): void
{
$this->adapter = new PDO(new \PDO('sqlite::memory:'));
$this->pdo = new \PDO('sqlite::memory:');
//$this->pdo = new \PDO('mysql:host=db;dbname=db', 'db', 'db');
$prefix = 'test' . substr(hash('sha256', uniqid()), 0, 6) . '_';
$this->adapter = new PDO($this->pdo, $prefix);
$this->adapter->wipeStorage();
}

public function tearDown(): void
{
parent::tearDown();
$this->adapter->deleteTables(); /** @phpstan-ignore-line */
$this->adapter = null; /** @phpstan-ignore-line */
$this->pdo = null;
}
}

0 comments on commit af59152

Please sign in to comment.