open-telemetry / opentelemetry-php

The OpenTelemetry PHP Library
https://opentelemetry.io/docs/instrumentation/php/
Apache License 2.0
688 stars 172 forks source link

Can it be applied to swoole? #892

Closed omigafu closed 1 year ago

omigafu commented 1 year ago

Before opening a feature request against this repo, consider whether the feature should/could be implemented in the other OpenTelemetry client libraries. If so, please open an issue in opentelemetry-specification first.

Is your feature request related to a problem? swoole is coroutine framework, but I can't see how to apply it on swoole.

Describe the solution you'd like I need to apply it on swoole.

Describe alternatives you've considered no

Additional context I try to use example to swoole, but there will be concurrency problems in the same process, so I am looking for a solution. thanks.

brettmc commented 1 year ago

Hi @omigafu and welcome. I see you've already found the contrib swoole module: https://github.com/open-telemetry/opentelemetry-php-contrib/tree/main/src/Context/Swoole

I'm aware of a couple of community members successfully using the otel swoole context library with swoole, so hopefully one of them will see this and provide some feedback. We'd love some documentation.

The only clue that I can give to you from #867 is that you need to set context storage to the swoole one. I'm only guessing here, but something like Context::setStorage(new SwooleContextStorage(Context::getStorage()) ?

omigafu commented 1 year ago

@brettmc thanks reply! Your reply makes me suddenly see the light, but I don't know how and where to use it. :(

omigafu commented 1 year ago

Hi @omigafu and welcome. I see you've already found the contrib swoole module: https://github.com/open-telemetry/opentelemetry-php-contrib/tree/main/src/Context/Swoole

I'm aware of a couple of community members successfully using the otel swoole context library with swoole, so hopefully one of them will see this and provide some feedback. We'd love some documentation.

The only clue that I can give to you from #867 is that you need to set context storage to the swoole one. I'm only guessing here, but something like Context::setStorage(new SwooleContextStorage(Context::getStorage()) ?

I think I have solved this problem, My approach is to add code like Context::setStorage(new SwooleContextStorage(Context::storage())); return; to the head of the file initialize_fiber_handler.php or any custom files that can be automatically require. @brettmc thanks for giving me direction.

GuillaumeBrook commented 8 months ago

@omigafu Any chances that you could provide a sample on how you did to export traces with Swoole + Otel ?

brettmc commented 8 months ago

@GuillaumeBrook I haven't used swoole much before, but I was able to generate traces using some of the examples in https://github.com/deminy/swoole-by-examples by:

I used sdk autoloading and console exporter. For bonus points, I added an auto-instrumentation on Swoole\Server::task since the example I was using (servers/http1-integrated.php) called it, and it generated two related spans.

I think the key is setting storage early on, then using OpenTelemetry as you would in any other scenario.

GuillaumeBrook commented 8 months ago

@brettmc Thanks for you prompt answer, it helped me.

I managed to make basic things kinda work. I don't use Swole\Server::task I just use the $server->on('request', ... FYI.

I didn't manage to make hooks on this function, so I made the code directly inside it.

So doing one http call correctly send the trace to DD. Some things are a bit off, like the PDOs spans named "root", but this isn't really important.

However, when doing multiple http calls in //, I have a ton of warnings : ab -c 10 -n 10 -k http://localhost:9501/

Warning: Swoole\Coroutine::getContext(): Context of this coroutine has been destroyed in /var/www/vendor/open-telemetry/context-swoole/src/SwooleContextHandler.php on line 59
Warning: Swoole\Coroutine::getContext(): Context of this coroutine has been destroyed in /var/www/vendor/open-telemetry/context-swoole/src/SwooleContextHandler.php on line 53

Then no traces are send.

Final code looks like that :

<?php

declare(strict_types=1);

use OpenTelemetry\API\Trace\Span;
use OpenTelemetry\API\Trace\SpanKind;
use OpenTelemetry\Context\Context;
use OpenTelemetry\Contrib\Context\Swoole\SwooleContextStorage;
use OpenTelemetry\SDK\Trace\TracerProviderFactory;
use Swoole\Constant;
use Swoole\Http\Request;
use Swoole\Http\Response;
use Swoole\Http\Server;

require_once __DIR__ . '/conf/ini.php';
require_once __DIR__ . '/vendor/autoload.php';
require_once __DIR__ . '/Models/PDOC.php';

$tracerProvider = (new TracerProviderFactory())->create();
$tracer = $tracerProvider->getTracer('datadog');

$server = new Server('0.0.0.0', 9501);
$server->set(
    [
        Constant::OPTION_HTTP_COMPRESSION       => true,
        Constant::OPTION_HTTP_COMPRESSION_LEVEL => 5
    ]
);

$server->on(
    'request',
    function (Request $request, Response $response) use ($tracer) {
        $span = \OpenTelemetry\API\Globals::tracerProvider()
            ->getTracer('manual-instrumentation')
            ->spanBuilder('api-http-request')
            ->setSpanKind(SpanKind::KIND_SERVER)
            ->setAttribute('service.name', 'api-http-request')
            ->startSpan();
        Context::setStorage(new SwooleContextStorage(Context::storage()));
        Context::storage()->attach($span->storeInContext(Context::getCurrent()));

        $pdoc = new Models\PDOC();
        $pdoc->doQuery();
        $response->status(200);
        $response->end("Hello OKKKKK");

        $scope = Context::storage()->scope();
        $scope->detach();
        $span = Span::fromContext($scope->context());
        /*if ($exception) {
            $span->recordException($exception);
            $span->setStatus(\OpenTelemetry\API\Trace\StatusCode::STATUS_ERROR);
        }*/
        $span->end();
    }
);

$server->start();

I don't really know opentelemetry for now so the code about spans might not really ok.

I'll look into it tomorrow.

brettmc commented 7 months ago

I also got some errors like that when playing around, and it was due to an error on the server side (usually misconfiguration on my part). Looking at your code:

GuillaumeBrook commented 7 months ago

Hello,

Yes indeed, setting it at the top stops the warnings. Yes that's something I should do but I wanted to try another thing : co-routines.

I used the following code :

<?php

declare(strict_types=1);

use OpenTelemetry\API\Trace\Span;
use OpenTelemetry\API\Trace\SpanKind;
use OpenTelemetry\Context\Context;
use OpenTelemetry\Contrib\Context\Swoole\SwooleContextStorage;
use Swoole\Constant;
use Swoole\Http\Request;
use Swoole\Http\Response;
use Swoole\Http\Server;

require_once __DIR__ . '/conf/ini.php';
require_once __DIR__ . '/autoload.php';

Context::setStorage(new SwooleContextStorage(Context::storage()));

$server = new Server('0.0.0.0', 9501);
$server->set(
    [
        Constant::OPTION_HTTP_COMPRESSION       => true,
        Constant::OPTION_HTTP_COMPRESSION_LEVEL => 5,

        Constant::OPTION_ENABLE_COROUTINE => true,
        Constant::OPTION_HOOK_FLAGS => SWOOLE_HOOK_ALL // important for swoole to hook on PDO and CURL methods
    ]
);

$dbConnectionsPool = new Swoole\Database\PDOPool(
    (
    new \Swoole\Database\PDOConfig())
        ->withHost('...')
        ->withDbname('...')
        ->withUsername('...')
        ->withPassword('...')
        ->withCharset('utf8mb4')
        ->withDriver('mysql'),
    16
);

$server->on(
    'request',
    function (Request $request, Response $response) use ($dbConnectionsPool) {
        $span = \OpenTelemetry\API\Globals::tracerProvider()
            ->getTracer('manual-instrumentation')
            ->spanBuilder('api-http-request')
            ->setSpanKind(SpanKind::KIND_SERVER)
            ->setAttribute('service.name', 'api-http-request')
            ->setAttribute('http.url', 'https://www.example.com/')
            ->setAttribute('http.status_code', 200)
            ->setAttribute('http.method', 'GET')
            ->startSpan();
        Context::storage()->attach($span->storeInContext(Context::getCurrent()));

        $chan = new \Swoole\Coroutine\Channel();
        go(function() use ($chan, $dbConnectionsPool) {
            $dbConnection = $dbConnectionsPool->get();
            $statement = $dbConnection->__call('query', ['select sleep(1)']);
            $results = $statement->__call('fetchAll', []);
            $chan->push(count($results));
            $dbConnectionsPool->put($dbConnection);
        });
        go(function() use ($chan, $dbConnectionsPool) {
            $dbConnection = $dbConnectionsPool->get();
            $statement = $dbConnection->__call('query', ['select sleep(2)']);
            $results = $statement->__call('fetchAll', []);
            $chan->push(count($results));
            $dbConnectionsPool->put($dbConnection);
        });
        go(function() use ($chan, $dbConnectionsPool) {
            $dbConnection = $dbConnectionsPool->get();
            $statement = $dbConnection->__call('query', ['select sleep(3)']);
            $results = $statement->__call('fetchAll', []);
            $chan->push(count($results));
            $dbConnectionsPool->put($dbConnection);
        });
        go(function() use ($chan, $dbConnectionsPool) {
            $dbConnection = $dbConnectionsPool->get();
            $statement = $dbConnection->__call('query', ['select sleep(4)']);
            $results = $statement->__call('fetchAll', []);
            $chan->push(count($results));
            $dbConnectionsPool->put($dbConnection);
        });
        $chan->pop();
        $chan->pop();
        $chan->pop();
        $chan->pop();

        //$finalCountdown = $chan->pop() + $chan->pop();
        $response->status(200);
        $response->end('Hello World');

        $scope = Context::storage()->scope();
        $scope->detach();
        $span = Span::fromContext($scope->context());

        /*if ($exception) {
            $span->recordException($exception);
            $span->setStatus(\OpenTelemetry\API\Trace\StatusCode::STATUS_ERROR);
        }*/
        $span->end();
    }
);

$server->start();

This code starts the 4 queries at the same time. However, the finals spans are not correct see the full trace :

otel_swoole_collector | 2023-11-16T13:57:57.620Z        info    ResourceSpans #0
otel_swoole_collector | Resource SchemaURL: https://opentelemetry.io/schemas/1.22.0
otel_swoole_collector | Resource attributes:
otel_swoole_collector |      -> service.name: Str(__root__)
otel_swoole_collector |      -> service.version: Str(1.0.0+no-version-set)
otel_swoole_collector |      -> telemetry.sdk.name: Str(opentelemetry)
otel_swoole_collector |      -> telemetry.sdk.language: Str(php)
otel_swoole_collector |      -> telemetry.sdk.version: Str(1.0.0)
otel_swoole_collector |      -> telemetry.distro.name: Str(opentelemetry-php-instrumentation)
otel_swoole_collector |      -> telemetry.distro.version: Str(1.0.0)
otel_swoole_collector |      -> process.runtime.name: Str(cli)
otel_swoole_collector |      -> process.runtime.version: Str(8.2.11)
otel_swoole_collector |      -> process.pid: Int(14)
otel_swoole_collector |      -> process.executable.path: Str(/usr/local/bin/php)
otel_swoole_collector |      -> process.command: Str(/var/www/server.php)
otel_swoole_collector |      -> process.command_args: Slice(["/var/www/server.php"])
otel_swoole_collector |      -> process.owner: Str(root)
otel_swoole_collector |      -> os.type: Str(linux)
otel_swoole_collector |      -> os.description: Str(5.14.0-1054-oem)
otel_swoole_collector |      -> os.name: Str(Linux)
otel_swoole_collector |      -> os.version: Str(#61-Ubuntu SMP Fri Oct 14 13:05:50 UTC 2022)
otel_swoole_collector |      -> host.name: Str(47c5e43b10cc)
otel_swoole_collector |      -> host.arch: Str(x86_64)
otel_swoole_collector | ScopeSpans #0
otel_swoole_collector | ScopeSpans SchemaURL: 
otel_swoole_collector | InstrumentationScope io.opentelemetry.contrib.php.pdo 
otel_swoole_collector | Span #0
otel_swoole_collector |     Trace ID       : 9cdd2a7dd6f52e5973e524d4f01896a2
otel_swoole_collector |     Parent ID      : f61d1c3a3b1d288b
otel_swoole_collector |     ID             : a1730e0381818f52
otel_swoole_collector |     Name           : PDOStatement::fetchAll
otel_swoole_collector |     Kind           : Client
otel_swoole_collector |     Start time     : 2023-11-16 13:57:48.496910048 +0000 UTC
otel_swoole_collector |     End time       : 2023-11-16 13:57:48.496955112 +0000 UTC
otel_swoole_collector |     Status code    : Unset
otel_swoole_collector |     Status message : 
otel_swoole_collector | Attributes:
otel_swoole_collector |      -> code.function: Str(fetchAll)
otel_swoole_collector |      -> code.namespace: Str(PDOStatement)
otel_swoole_collector | Span #1
otel_swoole_collector |     Trace ID       : 9cdd2a7dd6f52e5973e524d4f01896a2
otel_swoole_collector |     Parent ID      : e33e112f488cf41f
otel_swoole_collector |     ID             : 5042bef4eb8a7373
otel_swoole_collector |     Name           : PDOStatement::fetchAll
otel_swoole_collector |     Kind           : Client
otel_swoole_collector |     Start time     : 2023-11-16 13:57:49.496971344 +0000 UTC
otel_swoole_collector |     End time       : 2023-11-16 13:57:49.497036509 +0000 UTC
otel_swoole_collector |     Status code    : Unset
otel_swoole_collector |     Status message : 
otel_swoole_collector | Attributes:
otel_swoole_collector |      -> code.function: Str(fetchAll)
otel_swoole_collector |      -> code.namespace: Str(PDOStatement)
otel_swoole_collector | Span #2
otel_swoole_collector |     Trace ID       : 9cdd2a7dd6f52e5973e524d4f01896a2
otel_swoole_collector |     Parent ID      : 33e19f162e1bed0a
otel_swoole_collector |     ID             : c92c0e95c4da5dcd
otel_swoole_collector |     Name           : PDO::query
otel_swoole_collector |     Kind           : Client
otel_swoole_collector |     Start time     : 2023-11-16 13:57:46.496659247 +0000 UTC
otel_swoole_collector |     End time       : 2023-11-16 13:57:50.496928952 +0000 UTC
otel_swoole_collector |     Status code    : Unset
otel_swoole_collector |     Status message : 
otel_swoole_collector | Attributes:
otel_swoole_collector |      -> code.function: Str(query)
otel_swoole_collector |      -> code.namespace: Str(PDO)
otel_swoole_collector |      -> db.statement: Str(select sleep(4))
otel_swoole_collector | Span #3
otel_swoole_collector |     Trace ID       : 9cdd2a7dd6f52e5973e524d4f01896a2
otel_swoole_collector |     Parent ID      : 33e19f162e1bed0a
otel_swoole_collector |     ID             : 1b95b92ac37557d5
otel_swoole_collector |     Name           : PDOStatement::fetchAll
otel_swoole_collector |     Kind           : Client
otel_swoole_collector |     Start time     : 2023-11-16 13:57:50.496985697 +0000 UTC
otel_swoole_collector |     End time       : 2023-11-16 13:57:50.497021701 +0000 UTC
otel_swoole_collector |     Status code    : Unset
otel_swoole_collector |     Status message : 
otel_swoole_collector | Attributes:
otel_swoole_collector |      -> code.function: Str(fetchAll)
otel_swoole_collector |      -> code.namespace: Str(PDOStatement)
otel_swoole_collector | Span #4
otel_swoole_collector |     Trace ID       : 976667aca137b506dec220e7963c8b47
otel_swoole_collector |     Parent ID      : 5e2ac2f1313c28de
otel_swoole_collector |     ID             : 7f23a635647af6dd
otel_swoole_collector |     Name           : PDOStatement::fetchAll
otel_swoole_collector |     Kind           : Client
otel_swoole_collector |     Start time     : 2023-11-16 13:57:56.886741029 +0000 UTC
otel_swoole_collector |     End time       : 2023-11-16 13:57:56.886783805 +0000 UTC
otel_swoole_collector |     Status code    : Unset
otel_swoole_collector |     Status message : 
otel_swoole_collector | Attributes:
otel_swoole_collector |      -> code.function: Str(fetchAll)
otel_swoole_collector |      -> code.namespace: Str(PDOStatement)
otel_swoole_collector | ScopeSpans #1
otel_swoole_collector | ScopeSpans SchemaURL: 
otel_swoole_collector | InstrumentationScope manual-instrumentation 
otel_swoole_collector | Span #0
otel_swoole_collector |     Trace ID       : 9cdd2a7dd6f52e5973e524d4f01896a2
otel_swoole_collector |     Parent ID      : 
otel_swoole_collector |     ID             : 33e19f162e1bed0a
otel_swoole_collector |     Name           : api-http-request
otel_swoole_collector |     Kind           : Server
otel_swoole_collector |     Start time     : 2023-11-16 13:57:46.496381796 +0000 UTC
otel_swoole_collector |     End time       : 2023-11-16 13:57:50.497098065 +0000 UTC
otel_swoole_collector |     Status code    : Unset
otel_swoole_collector |     Status message : 
otel_swoole_collector | Attributes:
otel_swoole_collector |      -> service.name: Str(api-http-request)
otel_swoole_collector |      -> http.url: Str(https://www.example.com/)
otel_swoole_collector |      -> http.status_code: Int(200)
otel_swoole_collector |      -> http.method: Str(GET)
otel_swoole_collector |         {"kind": "exporter", "data_type": "traces", "name": "debug"}

I added a debug and I'm really entering this class and the pre query hook : vendor/open-telemetry/opentelemetry-auto-pdo/src/PDOInstrumentation.php:

hook(
            \PDO::class,
            'query',
            pre: static function (\PDO $pdo, array $params, string $class, string $function, ?string $filename, ?int $lineno) use ($attributeTracker, $instrumentation) {
                var_dump('Query hook with stmt: ' . $params[0]);
                etc...

However, only the longest query (sleep 4) has a span. In fact, when adding a debug in the post query hook :

post: static function (\PDO $pdo, array $params, mixed $statement, ?Throwable $exception) {
    var_dump('Query hook end stmt: ' . $params[0]);
    self::end($exception);
}

The print inside the console is :

"Query hook with stmt: select sleep(1)"
"Query hook with stmt: select sleep(3)"
"Query hook with stmt: select sleep(2)"
"Query hook with stmt: select sleep(4)"
"Query hook end stmt: select sleep(4)"

So I don't know if this is actually possible to have correct span when using co-routines. I guess it's the point of the open-telemetry/context-swoole repo. For now, I don't know if I did something wrong or if this is even possible.

brettmc commented 7 months ago

For now, I don't know if I did something wrong or if this is even possible.

It looks like it should, coroutines feature heavily through the swoole context handler. Can you turn your example into a self-contained docker-based example in your github?