This commit is contained in:
Kilian Hofmann
2021-06-01 19:55:55 +02:00
parent 052cbe3038
commit d8c489c714
328 changed files with 36645 additions and 0 deletions
+167
View File
@@ -0,0 +1,167 @@
<?php
namespace Nuwave\Lighthouse\Tracing;
use Carbon\Carbon;
use GraphQL\Type\Definition\ResolveInfo;
use Nuwave\Lighthouse\Events\StartRequest;
use Nuwave\Lighthouse\Events\ManipulateAST;
use Nuwave\Lighthouse\Schema\AST\ASTHelper;
use Nuwave\Lighthouse\Events\StartExecution;
use Nuwave\Lighthouse\Schema\AST\PartialParser;
use Nuwave\Lighthouse\Execution\ExtensionsResponse;
use Nuwave\Lighthouse\Events\BuildExtensionsResponse;
class Tracing
{
/**
* The timestamp the request was initially started.
*
* @var \Carbon\Carbon
*/
protected $requestStart;
/**
* The precise point in time where the request was initially started.
*
* This is either in seconds with microsecond precision (float) or nanoseconds (int).
*
* @var float|int
*/
protected $requestStartPrecise;
/**
* Trace entries for a single query execution.
*
* Is reset between batches.
*
* @var array[]
*/
protected $resolverTraces = [];
/**
* Set the tracing directive on all fields of the query to enable tracing them.
*
* @param \Nuwave\Lighthouse\Events\ManipulateAST $manipulateAST
* @return void
*/
public function handleManipulateAST(ManipulateAST $manipulateAST): void
{
ASTHelper::attachDirectiveToObjectTypeFields(
$manipulateAST->documentAST,
PartialParser::directive('@tracing')
);
}
/**
* Handle request start.
*
* @param \Nuwave\Lighthouse\Events\StartRequest $startRequest
* @return void
*/
public function handleStartRequest(StartRequest $startRequest): void
{
$this->requestStart = Carbon::now();
$this->requestStartPrecise = $this->getTime();
}
/**
* Handle batch request start.
*
* @param \Nuwave\Lighthouse\Events\StartExecution $startExecution
* @return void
*/
public function handleStartExecution(StartExecution $startExecution): void
{
$this->resolverTraces = [];
}
/**
* Return additional information for the result.
*
* @param \Nuwave\Lighthouse\Events\BuildExtensionsResponse $buildExtensionsResponse
* @return \Nuwave\Lighthouse\Execution\ExtensionsResponse
*/
public function handleBuildExtensionsResponse(BuildExtensionsResponse $buildExtensionsResponse): ExtensionsResponse
{
$requestEnd = Carbon::now();
$requestEndPrecise = $this->getTime();
return new ExtensionsResponse(
'tracing',
[
'version' => 1,
'startTime' => $this->requestStart->format(Carbon::RFC3339_EXTENDED),
'endTime' => $requestEnd->format(Carbon::RFC3339_EXTENDED),
'duration' => $this->diffTimeInNanoseconds($this->requestStartPrecise, $requestEndPrecise),
'execution' => [
'resolvers' => $this->resolverTraces,
],
]
);
}
/**
* Record resolver execution time.
*
* @param \GraphQL\Type\Definition\ResolveInfo $resolveInfo
* @param float|int $start
* @param float|int $end
* @return void
*/
public function record(ResolveInfo $resolveInfo, $start, $end): void
{
$this->resolverTraces [] = [
'path' => $resolveInfo->path,
'parentType' => $resolveInfo->parentType->name,
'returnType' => $resolveInfo->returnType->__toString(),
'fieldName' => $resolveInfo->fieldName,
'startOffset' => $this->diffTimeInNanoseconds($this->requestStartPrecise, $start),
'duration' => $this->diffTimeInNanoseconds($start, $end),
];
}
/**
* Get the system's highest resolution of time possible.
*
* This is either in seconds with microsecond precision (float) or nanoseconds (int).
*
* @return float|int
*/
public function getTime()
{
return $this->platformSupportsNanoseconds()
? hrtime(true)
: microtime(true);
}
/**
* Diff the time results to each other and convert to nanoseconds if needed.
*
* @param float|int $start
* @param float|int $end
* @return int
*/
protected function diffTimeInNanoseconds($start, $end): int
{
if ($this->platformSupportsNanoseconds()) {
return $end - $start;
}
// Difference is in seconds (with microsecond precision)
// * 1000 to get to milliseconds
// * 1000 to get to microseconds
// * 1000 to get to nanoseconds
return (int) (($end - $start) * 1000 * 1000 * 1000);
}
/**
* Test if the current PHP version has the `hrtime` function available to get a nanosecond precision point in time.
*
* @return bool
*/
protected function platformSupportsNanoseconds(): bool
{
return function_exists('hrtime');
}
}
@@ -0,0 +1,86 @@
<?php
namespace Nuwave\Lighthouse\Tracing;
use Closure;
use GraphQL\Deferred;
use GraphQL\Type\Definition\ResolveInfo;
use Nuwave\Lighthouse\Schema\Values\FieldValue;
use Nuwave\Lighthouse\Schema\Directives\BaseDirective;
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext;
use Nuwave\Lighthouse\Support\Contracts\FieldMiddleware;
use Nuwave\Lighthouse\Support\Contracts\DefinedDirective;
class TracingDirective extends BaseDirective implements FieldMiddleware, DefinedDirective
{
const NAME = 'tracing';
/**
* @var \Nuwave\Lighthouse\Tracing\Tracing
*/
protected $tracing;
/**
* TracingDirective constructor.
*
* @param \Nuwave\Lighthouse\Tracing\Tracing $tracing
* @return void
*/
public function __construct(Tracing $tracing)
{
$this->tracing = $tracing;
}
/**
* Name of the directive.
*
* @return string
*/
public function name(): string
{
return self::NAME;
}
public static function definition(): string
{
return /* @lang GraphQL */ <<<'SDL'
"""
Do not use this directive directly, it is automatically added to the schema
when using the tracing extension.
"""
directive @tracing on FIELD_DEFINITION
SDL;
}
/**
* Resolve the field directive.
*
* @param \Nuwave\Lighthouse\Schema\Values\FieldValue $fieldValue
* @param \Closure $next
* @return \Nuwave\Lighthouse\Schema\Values\FieldValue
*/
public function handleField(FieldValue $fieldValue, Closure $next): FieldValue
{
$fieldValue = $next($fieldValue);
$resolver = $fieldValue->getResolver();
return $fieldValue->setResolver(function ($root, array $args, GraphQLContext $context, ResolveInfo $resolveInfo) use ($resolver) {
$start = $this->tracing->getTime();
$result = $resolver($root, $args, $context, $resolveInfo);
$end = $this->tracing->getTime();
if ($result instanceof Deferred) {
$result->then(function () use ($resolveInfo, $start, $end): void {
$this->tracing->record($resolveInfo, $start, $end);
});
} else {
$this->tracing->record($resolveInfo, $start, $end);
}
return $result;
});
}
}
@@ -0,0 +1,59 @@
<?php
namespace Nuwave\Lighthouse\Tracing;
use Illuminate\Support\ServiceProvider;
use Nuwave\Lighthouse\Events\StartRequest;
use Nuwave\Lighthouse\Events\ManipulateAST;
use Nuwave\Lighthouse\Events\StartExecution;
use Nuwave\Lighthouse\Events\BuildExtensionsResponse;
use Nuwave\Lighthouse\Schema\Factories\DirectiveFactory;
use Illuminate\Contracts\Events\Dispatcher as EventsDispatcher;
class TracingServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* @param \Nuwave\Lighthouse\Schema\Factories\DirectiveFactory $directiveFactory
* @param \Illuminate\Contracts\Events\Dispatcher $eventsDispatcher
* @return void
*/
public function boot(DirectiveFactory $directiveFactory, EventsDispatcher $eventsDispatcher): void
{
$directiveFactory->addResolved(
TracingDirective::NAME,
TracingDirective::class
);
$eventsDispatcher->listen(
ManipulateAST::class,
Tracing::class.'@handleManipulateAST'
);
$eventsDispatcher->listen(
StartRequest::class,
Tracing::class.'@handleStartRequest'
);
$eventsDispatcher->listen(
StartExecution::class,
Tracing::class.'@handleStartExecution'
);
$eventsDispatcher->listen(
BuildExtensionsResponse::class,
Tracing::class.'@handleBuildExtensionsResponse'
);
}
/**
* Register any application services.
*
* @return void
*/
public function register(): void
{
$this->app->singleton(Tracing::class);
}
}