Skip to content

Commit

Permalink
Transaction support and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
intuibase committed Dec 6, 2024
1 parent 495921f commit 1ba6c4d
Show file tree
Hide file tree
Showing 5 changed files with 770 additions and 13 deletions.
7 changes: 4 additions & 3 deletions docker/mysql/init.sql
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
-- CREATE DATABASE IF NOT EXISTS otel_db;
-- DROP USER IF EXISTS 'otel_user'@'%';
-- CREATE USER 'otel_user'@'%' IDENTIFIED BY 'otel_passwd';
CREATE DATABASE IF NOT EXISTS otel_db2;
CREATE USER 'otel_user2'@'%' IDENTIFIED BY 'otel_passwd';


GRANT ALL PRIVILEGES ON *.* TO 'otel_user'@'%';
GRANT ALL PRIVILEGES ON *.* TO 'otel_user2'@'%';
FLUSH PRIVILEGES;


Expand Down
17 changes: 10 additions & 7 deletions src/Instrumentation/MySqli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ Auto-instrumentation hooks are registered via composer, and client kind spans wi
* `mysqli_connect`
* `mysqli::__construct`
* `mysqli::connect`
* `mysqli::real_connect`
* `mysqli_real_connect`
* `mysqli::real_connect`

* `mysqli_query`
* `mysqli::query`
Expand All @@ -32,14 +32,17 @@ Auto-instrumentation hooks are registered via composer, and client kind spans wi
* `mysqli_next_result`
* `mysqli::next_result`

* `mysqli_stmt::execute`
* `mysqli_begin_transaction`
* `mysqli::begin_transaction`
* `mysqli_rollback`
* `mysqli::rollback`
* `mysqli_commit`
* `mysqli::commit`
*
* `mysqli_stmt_execute`
* `mysqli_stmt::next_result`
* `mysqli_stmt::execute`
* `mysqli_stmt_next_result`

## Limitations

Transactions are not fully supported yet
* `mysqli_stmt::next_result`

## Configuration

Expand Down
144 changes: 141 additions & 3 deletions src/Instrumentation/MySqli/src/MySqliInstrumentation.php
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,69 @@ public static function register(): void
}
);

hook(
null,
'mysqli_begin_transaction',
pre: static function (...$args) use ($instrumentation, $tracker) {
self::beginTransactionPreHook('mysqli_begin_transaction', $instrumentation, $tracker, ...$args);
},
post: static function (...$args) use ($instrumentation, $tracker) {
self::beginTransactionPostHook($instrumentation, $tracker, ...$args);
}
);
hook(
mysqli::class,
'begin_transaction',
pre: static function (...$args) use ($instrumentation, $tracker) {
self::beginTransactionPreHook('mysqli::begin_transaction', $instrumentation, $tracker, ...$args);
},
post: static function (...$args) use ($instrumentation, $tracker) {
self::beginTransactionPostHook($instrumentation, $tracker, ...$args);
}
);

hook(
null,
'mysqli_rollback',
pre: static function (...$args) use ($instrumentation, $tracker) {
self::transactionPreHook('mysqli_rollback', $instrumentation, $tracker, ...$args);
},
post: static function (...$args) use ($instrumentation, $tracker) {
self::transactionPostHook($instrumentation, $tracker, ...$args);
}
);
hook(
mysqli::class,
'rollback',
pre: static function (...$args) use ($instrumentation, $tracker) {
self::transactionPreHook('mysqli::rollback', $instrumentation, $tracker, ...$args);
},
post: static function (...$args) use ($instrumentation, $tracker) {
self::transactionPostHook($instrumentation, $tracker, ...$args);
}
);

hook(
null,
'mysqli_commit',
pre: static function (...$args) use ($instrumentation, $tracker) {
self::transactionPreHook('mysqli_commit', $instrumentation, $tracker, ...$args);
},
post: static function (...$args) use ($instrumentation, $tracker) {
self::transactionPostHook($instrumentation, $tracker, ...$args);
}
);
hook(
mysqli::class,
'commit',
pre: static function (...$args) use ($instrumentation, $tracker) {
self::transactionPreHook('mysqli::commit', $instrumentation, $tracker, ...$args);
},
post: static function (...$args) use ($instrumentation, $tracker) {
self::transactionPostHook($instrumentation, $tracker, ...$args);
}
);

// Statement hooks

hook(
Expand Down Expand Up @@ -373,7 +436,9 @@ private static function constructPostHook(int $paramsOffset, CachedInstrumentati
/** @param non-empty-string $spanName */
private static function queryPreHook(string $spanName, CachedInstrumentation $instrumentation, MySqliTracker $tracker, $obj, array $params, ?string $class, ?string $function, ?string $filename, ?int $lineno): void
{
self::startSpan($spanName, $instrumentation, $class, $function, $filename, $lineno, []);
$span = self::startSpan($spanName, $instrumentation, $class, $function, $filename, $lineno, []);
$mysqli = $obj ? $obj : $params[0];
self::addTransactionLink($tracker, $span, $mysqli);
}

private static function queryPostHook(CachedInstrumentation $instrumentation, MySqliTracker $tracker, $obj, array $params, mixed $retVal, ?\Throwable $exception)
Expand Down Expand Up @@ -431,6 +496,7 @@ private static function nextResultPreHook(string $spanName, CachedInstrumentatio
$span->addLink($spanContext);
}

self::addTransactionLink($tracker, $span, $mysqli);
}

private static function nextResultPostHook(CachedInstrumentation $instrumentation, MySqliTracker $tracker, $obj, array $params, mixed $retVal, ?\Throwable $exception)
Expand Down Expand Up @@ -509,6 +575,64 @@ private static function preparePostHook(CachedInstrumentation $instrumentation,

}

/** @param non-empty-string $spanName */
private static function beginTransactionPreHook(string $spanName, CachedInstrumentation $instrumentation, MySqliTracker $tracker, $obj, array $params, ?string $class, ?string $function, ?string $filename, ?int $lineno): void
{
self::startSpan($spanName, $instrumentation, $class, $function, $filename, $lineno, []);
}

private static function beginTransactionPostHook(CachedInstrumentation $instrumentation, MySqliTracker $tracker, $obj, array $params, mixed $retVal, ?\Throwable $exception)
{
$mysqli = $obj ? $obj : $params[0];
$transactionName = $params[$obj ? 1 : 2] ?? null;

$attributes = $tracker->getMySqliAttributes($mysqli);

if ($transactionName) {
$attributes['db.transaction.name'] = $transactionName;
}

if ($retVal === false || $exception) {
//TODO use constant from comment after sem-conv update
$attributes[/*TraceAttributes::DB_RESPONSE_STATUS_CODE*/ 'db.response.status_code'] = $mysqli->errno;
} else {
$tracker->trackMySqliTransaction($mysqli, Span::getCurrent()->getContext());
}

$errorStatus = ($retVal === false && !$exception) ? $mysqli->error : null;
self::endSpan($attributes, $exception, $errorStatus);
}

/** @param non-empty-string $spanName */
private static function transactionPreHook(string $spanName, CachedInstrumentation $instrumentation, MySqliTracker $tracker, $obj, array $params, ?string $class, ?string $function, ?string $filename, ?int $lineno): void
{
$span = self::startSpan($spanName, $instrumentation, $class, $function, $filename, $lineno, []);
$mysqli = $obj ? $obj : $params[0];
self::addTransactionLink($tracker, $span, $mysqli);
}

private static function transactionPostHook(CachedInstrumentation $instrumentation, MySqliTracker $tracker, $obj, array $params, mixed $retVal, ?\Throwable $exception)
{
$mysqli = $obj ? $obj : $params[0];
$transactionName = $params[$obj ? 1 : 2] ?? null;

$attributes = $tracker->getMySqliAttributes($mysqli);

if ($transactionName) {
$attributes['db.transaction.name'] = $transactionName;
}

if ($retVal === false || $exception) {
//TODO use constant from comment after sem-conv update
$attributes[/*TraceAttributes::DB_RESPONSE_STATUS_CODE*/ 'db.response.status_code'] = $mysqli->errno;
}

$tracker->untrackMySqliTransaction($mysqli);

$errorStatus = ($retVal === false && !$exception) ? $mysqli->error : null;
self::endSpan($attributes, $exception, $errorStatus);
}

private static function stmtInitPostHook(CachedInstrumentation $instrumentation, MySqliTracker $tracker, $mySqliObj, array $params, mixed $retVal, ?\Throwable $exception)
{
if ($retVal !== false) {
Expand Down Expand Up @@ -550,7 +674,8 @@ private static function stmtConstructPostHook(CachedInstrumentation $instrumenta
/** @param non-empty-string $spanName */
private static function stmtExecutePreHook(string $spanName, CachedInstrumentation $instrumentation, MySqliTracker $tracker, $obj, array $params, ?string $class, ?string $function, ?string $filename, ?int $lineno): void
{
self::startSpan($spanName, $instrumentation, $class, $function, $filename, $lineno, []);
$span = self::startSpan($spanName, $instrumentation, $class, $function, $filename, $lineno, []);
self::addTransactionLink($tracker, $span, $obj ? $obj : $params[0]);
}

private static function stmtExecutePostHook(CachedInstrumentation $instrumentation, MySqliTracker $tracker, $obj, array $params, mixed $retVal, ?\Throwable $exception)
Expand Down Expand Up @@ -580,7 +705,7 @@ private static function stmtNextResultPreHook(string $spanName, CachedInstrument
if ($spanContext = $tracker->getStatementSpan($stmt)) {
$span->addLink($spanContext);
}

self::addTransactionLink($tracker, $span, $stmt);
}

private static function stmtNextResultPostHook(CachedInstrumentation $instrumentation, MySqliTracker $tracker, $obj, array $params, mixed $retVal, ?\Throwable $exception)
Expand Down Expand Up @@ -663,6 +788,19 @@ private static function dropSpan()
$scope->detach();
}

private static function addTransactionLink(MySqliTracker $tracker, SpanInterface $span, $mysqliOrStatement)
{
$mysqli = $mysqliOrStatement;

if ($mysqli instanceof mysqli_stmt) {
$mysqli = $tracker->getMySqliFromStatement($mysqli);
}

if ($mysqli instanceof mysqli && ($spanContext = $tracker->getMySqliTransaction($mysqli))) {
$span->addLink($spanContext);
}
}

private static function extractQueryCommand($query) : ?string
{
$query = preg_replace("/\r\n|\n\r|\r/", "\n", $query);
Expand Down
29 changes: 29 additions & 0 deletions src/Instrumentation/MySqli/src/MySqliTracker.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ final class MySqliTracker
private WeakMap $statementAttributes;
private WeakMap $statementSpan;
private WeakMap $mySqliSpan;
private WeakMap $mySqliTransaction;

public function __construct()
{
Expand All @@ -30,6 +31,7 @@ public function __construct()
$this->statementAttributes = new WeakMap();
$this->statementSpan = new WeakMap();
$this->mySqliSpan = new WeakMap();
$this->mySqliTransaction = new WeakMap();
}

public function storeMySqliMultiQuery(mysqli $mysqli, string $query)
Expand Down Expand Up @@ -77,6 +79,12 @@ public function trackMySqliFromStatement(mysqli $mysqli, mysqli_stmt $mysqli_stm
$this->statementToMySqli[$mysqli_stmt] = WeakReference::create($mysqli);
}

public function getMySqliFromStatement(mysqli_stmt $mysqli_stmt) : ?mysqli
{
return ($this->statementToMySqli[$mysqli_stmt] ?? null)?->get();
;
}

public function getMySqliAttributesFromStatement(mysqli_stmt $stmt) : array
{
$mysqli = ($this->statementToMySqli[$stmt] ?? null)?->get();
Expand Down Expand Up @@ -132,6 +140,27 @@ public function getMySqliSpan(mysqli $mysqli) : ?SpanContextInterface
return $this->mySqliSpan[$mysqli]->get();
}

public function trackMySqliTransaction(mysqli $mysqli, SpanContextInterface $spanContext)
{
$this->mySqliTransaction[$mysqli] = WeakReference::create($spanContext);
}

public function getMySqliTransaction(mysqli $mysqli) : ?SpanContextInterface
{
if (!$this->mySqliTransaction->offsetExists($mysqli)) {
return null;
}

return $this->mySqliTransaction[$mysqli]->get();
}

public function untrackMySqliTransaction(mysqli $mysqli)
{
if ($this->mySqliTransaction->offsetExists($mysqli)) {
unset($this->mySqliTransaction[$mysqli]);
}
}

private function splitQueries(string $sql)
{
// Normalize line endings to \n
Expand Down
Loading

0 comments on commit 1ba6c4d

Please sign in to comment.