-
Notifications
You must be signed in to change notification settings - Fork 70
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feat performance #145
base: master
Are you sure you want to change the base?
Feat performance #145
Changes from all commits
bd6259c
dceb49d
c40c523
2205275
4072f6a
831f2e2
b401496
f8eac4e
e7dbc18
8995a2f
0cc420d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace JustBetter\Sentry\Model; | ||
|
||
use Sentry\State\Scope; | ||
use Sentry\Tracing\Span; | ||
|
||
class PerformanceTracingDto | ||
{ | ||
public function __construct( | ||
private Scope $scope, | ||
private ?Span $parentSpan = null, | ||
private ?Span $span = null | ||
) { | ||
} | ||
|
||
public function getScope(): Scope | ||
{ | ||
return $this->scope; | ||
} | ||
|
||
public function getParentSpan(): ?Span | ||
{ | ||
return $this->parentSpan; | ||
} | ||
|
||
public function getSpan(): ?Span | ||
{ | ||
return $this->span; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,153 @@ | ||||||||||||||||||
<?php | ||||||||||||||||||
|
||||||||||||||||||
declare(strict_types=1); | ||||||||||||||||||
|
||||||||||||||||||
namespace JustBetter\Sentry\Model; | ||||||||||||||||||
|
||||||||||||||||||
// phpcs:disable Magento2.Functions.DiscouragedFunction | ||||||||||||||||||
|
||||||||||||||||||
use JustBetter\Sentry\Helper\Data; | ||||||||||||||||||
use Laminas\Http\Response; | ||||||||||||||||||
use Magento\Framework\App\Http; | ||||||||||||||||||
use Magento\Framework\App\Request\Http as HttpRequest; | ||||||||||||||||||
use Magento\Framework\App\ResponseInterface; | ||||||||||||||||||
use Magento\Framework\App\State; | ||||||||||||||||||
use Magento\Framework\AppInterface; | ||||||||||||||||||
use Magento\Framework\Exception\LocalizedException; | ||||||||||||||||||
use Magento\Framework\ObjectManagerInterface; | ||||||||||||||||||
use Sentry\SentrySdk; | ||||||||||||||||||
use Sentry\Tracing\SpanContext; | ||||||||||||||||||
use Sentry\Tracing\Transaction; | ||||||||||||||||||
use Sentry\Tracing\TransactionContext; | ||||||||||||||||||
use Sentry\Tracing\TransactionSource; | ||||||||||||||||||
use Throwable; | ||||||||||||||||||
|
||||||||||||||||||
use function Sentry\startTransaction; | ||||||||||||||||||
|
||||||||||||||||||
class SentryPerformance | ||||||||||||||||||
{ | ||||||||||||||||||
private ?Transaction $transaction = null; | ||||||||||||||||||
|
||||||||||||||||||
public function __construct( | ||||||||||||||||||
private HttpRequest $request, | ||||||||||||||||||
private ObjectManagerInterface $objectManager, | ||||||||||||||||||
private Data $helper | ||||||||||||||||||
) { | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
public function startTransaction(AppInterface $app): void | ||||||||||||||||||
{ | ||||||||||||||||||
if (!$app instanceof Http) { | ||||||||||||||||||
// actually, we only support profiling of http requests. | ||||||||||||||||||
return; | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
$requestStartTime = $this->request->getServer('REQUEST_TIME_FLOAT', microtime(true)); | ||||||||||||||||||
|
||||||||||||||||||
$context = TransactionContext::fromHeaders( | ||||||||||||||||||
$this->request->getHeader('sentry-trace') ?: '', | ||||||||||||||||||
$this->request->getHeader('baggage') ?: '' | ||||||||||||||||||
); | ||||||||||||||||||
|
||||||||||||||||||
$requestPath = '/'.ltrim($this->request->getRequestUri(), '/'); | ||||||||||||||||||
|
||||||||||||||||||
$context->setName($requestPath); | ||||||||||||||||||
$context->setSource(TransactionSource::url()); | ||||||||||||||||||
$context->setStartTimestamp($requestStartTime); | ||||||||||||||||||
|
||||||||||||||||||
$context->setData([ | ||||||||||||||||||
'url' => $requestPath, | ||||||||||||||||||
'method' => strtoupper($this->request->getMethod()), | ||||||||||||||||||
]); | ||||||||||||||||||
|
||||||||||||||||||
// Start the transaction | ||||||||||||||||||
$transaction = startTransaction($context); | ||||||||||||||||||
|
||||||||||||||||||
// If this transaction is not sampled, don't set it either and stop doing work from this point on | ||||||||||||||||||
if (!$transaction->getSampled()) { | ||||||||||||||||||
return; | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
$this->transaction = $transaction; | ||||||||||||||||||
SentrySdk::getCurrentHub()->setSpan($transaction); | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
public function finishTransaction(ResponseInterface|int $statusCode): void | ||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we maybe wrap this in a try/catch as well like captureException so issues when collecting performance metrics can't interfere with rendering the page to a customer |
||||||||||||||||||
{ | ||||||||||||||||||
if ($this->transaction === null) { | ||||||||||||||||||
return; | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
try { | ||||||||||||||||||
$state = $this->objectManager->get(State::class); | ||||||||||||||||||
$areaCode = $state->getAreaCode(); | ||||||||||||||||||
} catch (LocalizedException) { | ||||||||||||||||||
// we wont track transaction without an area | ||||||||||||||||||
return; | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
if (in_array($areaCode, $this->helper->getPerformanceTrackingExcludedAreas())) { | ||||||||||||||||||
return; | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
if ($statusCode instanceof Response) { | ||||||||||||||||||
$statusCode = (int) $statusCode->getStatusCode(); | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
if (is_numeric($statusCode)) { | ||||||||||||||||||
$this->transaction->setHttpStatus($statusCode); | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
if (in_array($state->getAreaCode(), ['frontend', 'webapi_rest', 'adminhtml'])) { | ||||||||||||||||||
if (!empty($this->request->getFullActionName())) { | ||||||||||||||||||
$this->transaction->setName(strtoupper($this->request->getMethod()). ' ' .$this->request->getFullActionName()); | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
$this->transaction->setOp('http'); | ||||||||||||||||||
|
||||||||||||||||||
$this->transaction->setData(array_merge( | ||||||||||||||||||
$this->transaction->getData(), | ||||||||||||||||||
$this->request->__debugInfo(), | ||||||||||||||||||
[ | ||||||||||||||||||
'module' => $this->request->getModuleName(), | ||||||||||||||||||
'action' => $this->request->getFullActionName(), | ||||||||||||||||||
] | ||||||||||||||||||
)); | ||||||||||||||||||
} elseif ($state->getAreaCode() === 'graphql') { | ||||||||||||||||||
$this->transaction->setOp('graphql'); | ||||||||||||||||||
} else { | ||||||||||||||||||
$this->transaction->setOp($state->getAreaCode()); | ||||||||||||||||||
} | ||||||||||||||||||
Comment on lines
+116
to
+120
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Was there anything special that needed to happen here? If not we can remove the elseif
Suggested change
|
||||||||||||||||||
|
||||||||||||||||||
try { | ||||||||||||||||||
// Finish the transaction, this submits the transaction and it's span to Sentry | ||||||||||||||||||
$this->transaction->finish(); | ||||||||||||||||||
} catch (Throwable) { | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
$this->transaction = null; | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
public static function traceStart(SpanContext $context): PerformanceTracingDto | ||||||||||||||||||
{ | ||||||||||||||||||
$scope = SentrySdk::getCurrentHub()->pushScope(); | ||||||||||||||||||
$span = null; | ||||||||||||||||||
|
||||||||||||||||||
$parentSpan = $scope->getSpan(); | ||||||||||||||||||
if ($parentSpan !== null && $parentSpan->getSampled()) { | ||||||||||||||||||
$span = $parentSpan->startChild($context); | ||||||||||||||||||
$scope->setSpan($span); | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
return new PerformanceTracingDto($scope, $parentSpan, $span); | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
public static function traceEnd(PerformanceTracingDto $context): void | ||||||||||||||||||
{ | ||||||||||||||||||
if ($context->getSpan()) { | ||||||||||||||||||
$context->getSpan()->finish(); | ||||||||||||||||||
$context->getScope()->setSpan($context->getParentSpan()); | ||||||||||||||||||
} | ||||||||||||||||||
SentrySdk::getCurrentHub()->popScope(); | ||||||||||||||||||
} | ||||||||||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace JustBetter\Sentry\Plugin\Profiling; | ||
|
||
use JustBetter\Sentry\Model\PerformanceTracingDto; | ||
use JustBetter\Sentry\Model\SentryPerformance; | ||
use Magento\Framework\DB\LoggerInterface; | ||
use Sentry\Tracing\SpanContext; | ||
|
||
class DbQueryLoggerPlugin | ||
{ | ||
private ?PerformanceTracingDto $tracingDto = null; | ||
|
||
public function beforeStartTimer(LoggerInterface $subject): void | ||
{ | ||
$this->tracingDto = SentryPerformance::traceStart( | ||
SpanContext::make() | ||
->setOp('db.sql.query') | ||
->setStartTimestamp(microtime(true)) | ||
); | ||
} | ||
|
||
public function beforeLogStats(LoggerInterface $subject, $type, $sql, $bind = [], $result = null): void | ||
{ | ||
if ($this->tracingDto === null) { | ||
return; | ||
} | ||
|
||
$this->tracingDto->getSpan()?->setDescription($sql); | ||
SentryPerformance::traceEnd($this->tracingDto); | ||
$this->tracingDto = null; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just to make sure. Starting a transaction here won't count towards your Sentry usage and limits right?
Since we'll be executing it every request
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no. it only counts if the report is sent to the gateway (at the latest moment)
But i did not tested it explicitly, because we do not have any limits.