Skip to content
This repository has been archived by the owner on Jun 4, 2020. It is now read-only.

Port ActiveSpanSource implementation from opentracing-python. #3

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

beberlei
Copy link
Contributor

@beberlei beberlei commented Jun 15, 2017

The implementation of active span source and related code is an almost 1:1 port of the current python pull request: opentracing/opentracing-python#52

The specification context is "In-process propagation" discussed in this issue opentracing/specification#23 and the first official implementation in open-tracing java, opentracing/opentracing-java#115

It provides the following semantics:

  • Calling Tracer::getActiveSpan() returns the currently active span.
  • Calling Tracer::startActiveSpan() creates a new span with the currently active span as parent.
  • Calling Span::finish() on an actively managed span, automatically deactivates the Span using ActiveSpanSource::deactivate().

Having this feature in before making an official version of opentracing-php will safe us from the BC Break.

/cc @jcchavezs @felixfbecker

@felixfbecker
Copy link

How does this work with async code? E.g. event loops like ReactPHP or AmPHP, Promises, coroutines, RxPHP, curl_multi_*, mysqli_reap_async_query, streaming with generators...

@beberlei
Copy link
Contributor Author

beberlei commented Jun 15, 2017

@felixfbecker For ReachtPHP/AmPHP i cant say, since i don't know how they work internally.

For coroutines, curl_multi* aand mysqli_reap_async_query, the pattern is to still use manual management after obtaining the parent active span:

<?php

$parent = $tracer->getActiveSpan()->getContext();

$ch1 = curl_init();
$ch2 = curl_init();

// URL und weitere Optionen setzen
curl_setopt($ch1, CURLOPT_URL, "http://lxr.php.net/");
curl_setopt($ch1, CURLOPT_HEADER, 0);
curl_setopt($ch2, CURLOPT_URL, "http://www.php.net/");
curl_setopt($ch2, CURLOPT_HEADER, 0);

$mh = curl_multi_init();

curl_multi_add_handle($mh,$ch1);
$ch1Span = $tracer->startManualSpan('http', ['child_of' => $parent]);
curl_multi_add_handle($mh,$ch2);
$ch2Span = $tracer->startManualSpan('http', ['child_of' => $parent]);

$active = null;
do {
    $mrc = curl_multi_exec($mh, $active);
} while ($mrc == CURLM_CALL_MULTI_PERFORM);

while ($active && $mrc == CURLM_OK) {
    if (curl_multi_select($mh) != -1) {
        do {
            $mrc = curl_multi_exec($mh, $active);
        } while ($mrc == CURLM_CALL_MULTI_PERFORM);
    }
}

curl_multi_remove_handle($mh, $ch1);
$ch1Span->finish();
curl_multi_remove_handle($mh, $ch2);
$ch2Span->finish();
curl_multi_close($mh);

Depending on the implementation of ActiveSpanSource, it might be possible to do this with ReactPHP by maintaining multiple stacks of spans (for every execution context) and whenever the context switches to switch the stack as well.

@felixfbecker
Copy link

felixfbecker commented Jun 15, 2017

I am concerned that if we add this to the official OpenTracing implementation, then all PHP libraries will just use it without thinking about all these cases. If you want to add this to the implementation, the usage implications must be documented and how to support async patterns. If these async patterns are not supported, then the active span container will do more harm than good. Passing spans as parameters on the other hand always works, no matter if async or not.

@beberlei
Copy link
Contributor Author

@felixfbecker this is already added to the official java library, and you can see the PR for the python implementation. This is becoming part of the specification and we need to support it.

Libraries must be written specifically for ReactPHP anyways, because PHP libraries usually block on I/O, that means if they are to support OpenTracing then they can implement this handling right there.

And another argument, how would third libraries even support OpenTracing at the moment? Its not possible, because there is no way to access the current context from a generic third party library.

@felixfbecker
Copy link

You can pass spans as parameters, just like you do in JS (and Go with ctx). That works both with blocking and evented code. But if my application uses an event loop and libraries I use always rely on the global active span without a way to pass a span as parameter, unless I am missing something, then the active span will be wrong as soon as the context switches (next event loop tick), and the library will use a wrong active span. Which is why I asked for documentation/examples of how to trace any application or library that makes use of this.

@beberlei
Copy link
Contributor Author

@felixfbecker I agree on passing context as arugment in async/reactive environments, the problem is none of the libraries support this at their core level, which is a requirement to make this work. So you must hook into the event loops at different abstractions.

For ReactPHP lets take this example from https://blog.wyrihaximus.net/2015/04/reactphp-http/:

<?php

$http->on('request', function (Request $request, Response $response) use ($filesystem, $files) {
    echo 'Request for: ' . $request->getPath(), PHP_EOL;
    $files->then(function (SplObjectStorage $files) use ($filesystem, $request) {
        foreach ($files as $file) {
            if ($file->getPath() == WEBROOT . $request->getPath()) {
                return $file;
            }
        }

        return $filesystem->file(WEBROOT . DIRECTORY_SEPARATOR . '404.txt');
    })->then(function (File $file) {
        return $file->getContents()->then(function ($contents) use ($file) {
            return $file->close()->then(function () use ($contents) {
                return $contents;
            });
        });
    })->then(function ($fileContents) use ($response) {
        $response->writeHead(200, ['Content-Type' => 'text/plain']);
        $response->end($fileContents);
    });
});

It has 4 executions, the request itself, and 2 then() callbacks doing filesystem I/O and one writing the response.

If you where to instrument this using OpenTracing, then the tracing code must be wrapped in decorators around the 4 closures that are executed on, and 3x then. They would need to know "their" request context and modify the ActiveSpanSource accordingly.

This is highly dependent code on the async framework, and same as opentracing-python, where there are cusotm span source examples for every async framework: See this example for asyncio with code that hooks into the event loop at a very low levle: https://github.com/palazzem/ot-examples/blob/master/examples/asyncio.py

@beberlei
Copy link
Contributor Author

beberlei commented Jun 15, 2017

@felixfbecker Actually for ReactPHP it should (untested code) work with a loop decorator like this:

<?php

class TracerLoopDecorator implements LoopInterface
{
    private $parentLoop;
    private $tracer;

    public function __construct(LoopInterface $parent, Tracer $tracer)
    {
        $this->parentLoop = $parent;
        $this->tracer = $tracer;
        $this->activeSpanSource = $tracer->getActiveSpanSource();
    }

    public function addReadStream($stream, callable $listener)
    {
        // is called in the context of a current span
        $span = $this->tracer->getActiveSpan();

        $wrapper = function (...$args) use ($span, $listener) {
            $this->activeSpanSource->activate($span);
            $result = $listener(...$args);
            $this->activeSpanSource->deactivate($span);
            return $result;
        };

        return $this->parentLoop->addReadStream($stream, $wrapper);
    }
    // ....
}

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants