PluginProbe
WCPOS – Point of Sale (POS) plugin for WooCommerce / 1.10.23
WCPOS – Point of Sale (POS) plugin for WooCommerce v1.10.23
1.10.24 1.10.23 1.10.22 1.10.21 1.10.20 1.10.19 1.10.18 1.10.17 1.10.16 1.10.15 1.10.13 1.10.14 1.10.12 1.10.11 1.10.10 1.10.9 1.10.8 untagged-3d9b7ccddc54df87c672 1.10.7 1.10.6 1.10.5 1.10.3 1.10.4 1.10.2 1.10.1 All 168 releases
woocommerce-pos / vendor_prefixed / sentry / sentry / src / Profiling / Profile.php

Profile.php in WCPOS – Point of Sale (POS) plugin for WooCommerce 1.10.23, at vendor_prefixed/sentry/sentry/src/Profiling/Profile.php

296 lines 10.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 declare (strict_types=1);
4 namespace WCPOS\Vendor\Sentry\Profiling;
5
6 use WCPOS\Vendor\Psr\Log\LoggerInterface;
7 use WCPOS\Vendor\Psr\Log\NullLogger;
8 use WCPOS\Vendor\Sentry\Context\OsContext;
9 use WCPOS\Vendor\Sentry\Context\RuntimeContext;
10 use WCPOS\Vendor\Sentry\Event;
11 use WCPOS\Vendor\Sentry\EventId;
12 use WCPOS\Vendor\Sentry\Options;
13 use WCPOS\Vendor\Sentry\Util\PrefixStripper;
14 use WCPOS\Vendor\Sentry\Util\SentryUid;
15 /**
16 * Type definition of the Sentry profile format.
17 * All fields are none otpional.
18 *
19 * @see https://develop.sentry.dev/sdk/sample-format/
20 *
21 * @phpstan-type SentryProfileFrame array{
22 * abs_path: string,
23 * filename: string,
24 * function: string,
25 * module: string|null,
26 * lineno: int|null,
27 * }
28 * @phpstan-type SentryProfile array{
29 * device: array{
30 * architecture: string,
31 * },
32 * event_id: string,
33 * os: array{
34 * name: string,
35 * version: string,
36 * build_number: string,
37 * },
38 * platform: 'php',
39 * release: string,
40 * environment: string,
41 * runtime: array{
42 * name: string,
43 * version: string,
44 * },
45 * timestamp: string,
46 * transaction: array{
47 * id: string,
48 * name: string,
49 * trace_id: string,
50 * active_thread_id: string,
51 * },
52 * version: string,
53 * profile: array{
54 * frames: array<int, SentryProfileFrame>,
55 * samples: array<int, array{
56 * elapsed_since_start_ns: int,
57 * stack_id: int,
58 * thread_id: string,
59 * }>,
60 * stacks: array<int, array<int, int>>,
61 * },
62 * }
63 * @phpstan-type ExcimerLogStackEntryTrace array{
64 * file: string,
65 * line: int,
66 * class?: string,
67 * function?: string,
68 * closure_line?: int,
69 * }
70 * @phpstan-type ExcimerLogStackEntry array{
71 * trace: array<int, ExcimerLogStackEntryTrace>,
72 * timestamp: float
73 * }
74 *
75 * @internal
76 */
77 final class Profile
78 {
79 use PrefixStripper;
80 /**
81 * @var string The version of the profile format
82 */
83 private const VERSION = '1';
84 /**
85 * @var string The thread ID
86 */
87 private const THREAD_ID = '0';
88 /**
89 * @var int The minimum number of samples required for a profile
90 */
91 private const MIN_SAMPLE_COUNT = 2;
92 /**
93 * @var int The maximum duration of a profile in seconds
94 */
95 private const MAX_PROFILE_DURATION = 30;
96 /**
97 * @var float The start time of the profile as a Unix timestamp with microseconds
98 */
99 private $startTimeStamp;
100 /**
101 * @var \ExcimerLog|array<int, ExcimerLogStackEntry> The data of the profile
102 */
103 private $excimerLog;
104 /**
105 * @var EventId|null The event ID of the profile
106 */
107 private $eventId;
108 /**
109 * @var Options|null
110 */
111 private $options;
112 /**
113 * @var LoggerInterface
114 */
115 private $logger;
116 public function __construct(?Options $options = null)
117 {
118 $this->options = $options;
119 $this->logger = $options !== null ? $options->getLoggerOrNullLogger() : new NullLogger();
120 }
121 public function setStartTimeStamp(float $startTimeStamp) : void
122 {
123 $this->startTimeStamp = $startTimeStamp;
124 }
125 /**
126 * @param \ExcimerLog|array<int, ExcimerLogStackEntry> $excimerLog
127 */
128 public function setExcimerLog($excimerLog) : void
129 {
130 $this->excimerLog = $excimerLog;
131 }
132 public function setEventId(EventId $eventId) : void
133 {
134 $this->eventId = $eventId;
135 }
136 /**
137 * @return SentryProfile|null
138 */
139 public function getFormattedData(Event $event) : ?array
140 {
141 if (!$this->validateExcimerLog()) {
142 $this->logger->warning('The profile does not contain enough samples, the profile will be discarded.');
143 return null;
144 }
145 $osContext = $event->getOsContext();
146 if (!$this->validateOsContext($osContext)) {
147 $this->logger->warning('The OS context is not missing or invalid, the profile will be discarded.');
148 return null;
149 }
150 $runtimeContext = $event->getRuntimeContext();
151 if (!$this->validateRuntimeContext($runtimeContext)) {
152 $this->logger->warning('The runtime context is not missing or invalid, the profile will be discarded.');
153 return null;
154 }
155 if (!$this->validateEvent($event)) {
156 $this->logger->warning('The event is missing a transaction and/or trace ID, the profile will be discarded.');
157 return null;
158 }
159 $frames = [];
160 $frameHashMap = [];
161 $stacks = [];
162 $stackHashMap = [];
163 $registerStack = static function (array $stack) use(&$stacks, &$stackHashMap) : int {
164 $stackHash = \md5(\serialize($stack));
165 if (\array_key_exists($stackHash, $stackHashMap) === \false) {
166 $stackHashMap[$stackHash] = \count($stacks);
167 $stacks[] = $stack;
168 }
169 return $stackHashMap[$stackHash];
170 };
171 $samples = [];
172 $duration = 0;
173 $loggedStacks = $this->prepareStacks();
174 foreach ($loggedStacks as $stack) {
175 $stackFrames = [];
176 foreach ($stack['trace'] as $frame) {
177 $absolutePath = $frame['file'];
178 $lineno = $frame['line'];
179 $frameKey = "{$absolutePath}:{$lineno}";
180 $frameIndex = $frameHashMap[$frameKey] ?? null;
181 if ($frameIndex === null) {
182 $file = $this->stripPrefixFromFilePath($this->options, $absolutePath);
183 $module = null;
184 if (isset($frame['class'], $frame['function'])) {
185 // Class::method
186 $function = $frame['class'] . '::' . $frame['function'];
187 $module = $frame['class'];
188 } elseif (isset($frame['function'])) {
189 // {closure}
190 $function = $frame['function'];
191 } else {
192 // /index.php
193 $function = $file;
194 }
195 $frameHashMap[$frameKey] = $frameIndex = \count($frames);
196 $frames[] = ['filename' => $file, 'abs_path' => $absolutePath, 'module' => $module, 'function' => $function, 'lineno' => $lineno];
197 }
198 $stackFrames[] = $frameIndex;
199 }
200 $stackId = $registerStack($stackFrames);
201 $duration = $stack['timestamp'];
202 $samples[] = ['stack_id' => $stackId, 'thread_id' => self::THREAD_ID, 'elapsed_since_start_ns' => (int) \round($duration * 1000000000.0)];
203 }
204 if (!$this->validateMaxDuration((float) $duration)) {
205 $this->logger->warning(\sprintf('The profile is %ss which is longer than the allowed %ss, the profile will be discarded.', (float) $duration, self::MAX_PROFILE_DURATION));
206 return null;
207 }
208 $startTime = \DateTime::createFromFormat('U.u', \number_format($this->startTimeStamp, 4, '.', ''), new \DateTimeZone('UTC'));
209 if ($startTime === \false) {
210 $this->logger->warning(\sprintf('The start time (%s) of the profile is not valid, the profile will be discarded.', $this->startTimeStamp));
211 return null;
212 }
213 return ['device' => ['architecture' => $osContext->getMachineType()], 'event_id' => $this->eventId ? (string) $this->eventId : SentryUid::generate(), 'os' => ['name' => $osContext->getName(), 'version' => $osContext->getVersion(), 'build_number' => $osContext->getBuild() ?? ''], 'platform' => 'php', 'release' => $event->getRelease() ?? '', 'environment' => $event->getEnvironment() ?? Event::DEFAULT_ENVIRONMENT, 'runtime' => ['name' => $runtimeContext->getName(), 'sapi' => $runtimeContext->getSAPI(), 'version' => $runtimeContext->getVersion()], 'timestamp' => $startTime->format(\DATE_RFC3339_EXTENDED), 'transaction' => ['id' => (string) $event->getId(), 'name' => $event->getTransaction(), 'trace_id' => $event->getTraceId(), 'active_thread_id' => self::THREAD_ID], 'version' => self::VERSION, 'profile' => ['frames' => $frames, 'samples' => $samples, 'stacks' => $stacks]];
214 }
215 /**
216 * This method is mainly used to be able to mock the ExcimerLog class in the tests.
217 *
218 * @return array<int, ExcimerLogStackEntry>
219 */
220 private function prepareStacks() : array
221 {
222 $stacks = [];
223 foreach ($this->excimerLog as $stack) {
224 if ($stack instanceof \WCPOS\Vendor\ExcimerLogEntry) {
225 $stacks[] = ['trace' => $stack->getTrace(), 'timestamp' => $stack->getTimestamp()];
226 } else {
227 /** @var ExcimerLogStackEntry $stack */
228 $stacks[] = $stack;
229 }
230 }
231 return $stacks;
232 }
233 private function validateExcimerLog() : bool
234 {
235 if (\is_array($this->excimerLog)) {
236 $sampleCount = \count($this->excimerLog);
237 } else {
238 $sampleCount = $this->excimerLog->count();
239 }
240 return $sampleCount >= self::MIN_SAMPLE_COUNT;
241 }
242 private function validateMaxDuration(float $duration) : bool
243 {
244 if ($duration > self::MAX_PROFILE_DURATION) {
245 return \false;
246 }
247 return \true;
248 }
249 /**
250 * @phpstan-assert-if-true OsContext $osContext
251 * @phpstan-assert-if-true !null $osContext->getVersion()
252 * @phpstan-assert-if-true !null $osContext->getMachineType()
253 */
254 private function validateOsContext(?OsContext $osContext) : bool
255 {
256 if ($osContext === null) {
257 return \false;
258 }
259 if ($osContext->getVersion() === null) {
260 return \false;
261 }
262 if ($osContext->getMachineType() === null) {
263 return \false;
264 }
265 return \true;
266 }
267 /**
268 * @phpstan-assert-if-true RuntimeContext $runtimeContext
269 * @phpstan-assert-if-true !null $runtimeContext->getVersion()
270 */
271 private function validateRuntimeContext(?RuntimeContext $runtimeContext) : bool
272 {
273 if ($runtimeContext === null) {
274 return \false;
275 }
276 if ($runtimeContext->getVersion() === null) {
277 return \false;
278 }
279 return \true;
280 }
281 /**
282 * @phpstan-assert-if-true !null $event->getTransaction()
283 * @phpstan-assert-if-true !null $event->getTraceId()
284 */
285 private function validateEvent(Event $event) : bool
286 {
287 if ($event->getTransaction() === null) {
288 return \false;
289 }
290 if ($event->getTraceId() === null) {
291 return \false;
292 }
293 return \true;
294 }
295 }
296