Skip to content

Commit 8632abe

Browse files
committed
Implement a route caching system
1 parent 04b862f commit 8632abe

14 files changed

Lines changed: 244 additions & 30 deletions

src/DDTrace/Integrations/CakePHP/CakePHPIntegration.php

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,15 @@ public static function init(): int
7373
if ($rootSpan !== null) {
7474
$template = $app->template;
7575
$rootSpan->meta[Tag::HTTP_ROUTE] = $template;
76-
$normalizedRoute = \DDTrace\Util\RouteNormalizer::normalizeFromCakePHP($template);
77-
if ($normalizedRoute !== null) {
76+
$cacheKey = $template;
77+
$normalizedRoute = \DDTrace\routing_cache_get($cacheKey);
78+
if ($normalizedRoute === false) {
79+
$normalizedRoute = \DDTrace\Util\RouteNormalizer::normalizeFromCakePHP($template);
80+
if ($normalizedRoute !== null) {
81+
\DDTrace\routing_cache_set($cacheKey, $normalizedRoute);
82+
}
83+
}
84+
if ($normalizedRoute !== null && $normalizedRoute !== false) {
7885
$rootSpan->meta[Tag::APPSEC_NORMALIZED_ROUTE] = $normalizedRoute;
7986
}
8087
}

src/DDTrace/Integrations/CodeIgniter/V2/CodeIgniterIntegration.php

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,20 @@ function (SpanData $span, $args, $retval, $ex) use ($adapter, $service) {
222222
/*
223223
* Replicate CodeIgniter's route parsing, as matching key is never stored or returned in the framework.
224224
*/
225+
private static function setNormalizedRoute($rootSpan, string $pattern): void {
226+
$cacheKey = $pattern;
227+
$normalizedRoute = \DDTrace\routing_cache_get($cacheKey);
228+
if ($normalizedRoute === false) {
229+
$normalizedRoute = \DDTrace\Util\RouteNormalizer::normalizeFromCodeIgniter($pattern);
230+
if ($normalizedRoute !== null) {
231+
\DDTrace\routing_cache_set($cacheKey, $normalizedRoute);
232+
}
233+
}
234+
if ($normalizedRoute !== null && $normalizedRoute !== false) {
235+
$rootSpan->meta[Tag::APPSEC_NORMALIZED_ROUTE] = $normalizedRoute;
236+
}
237+
}
238+
225239
private static function setHttpRoute($router, $rootSpan) {
226240
// Turn the segment array into a URI string
227241
$uri = implode('/', $router->uri->segments);
@@ -230,10 +244,7 @@ private static function setHttpRoute($router, $rootSpan) {
230244
if (isset($router->routes[$uri]))
231245
{
232246
$rootSpan->meta[Tag::HTTP_ROUTE] = $uri;
233-
$normalizedRoute = \DDTrace\Util\RouteNormalizer::normalizeFromCodeIgniter($uri);
234-
if ($normalizedRoute !== null) {
235-
$rootSpan->meta[Tag::APPSEC_NORMALIZED_ROUTE] = $normalizedRoute;
236-
}
247+
self::setNormalizedRoute($rootSpan, $uri);
237248
return;
238249
}
239250

@@ -248,20 +259,14 @@ private static function setHttpRoute($router, $rootSpan) {
248259
if (preg_match('#^'.$key.'$#', $uri))
249260
{
250261
$rootSpan->meta[Tag::HTTP_ROUTE] = $origKey;
251-
$normalizedRoute = \DDTrace\Util\RouteNormalizer::normalizeFromCodeIgniter($origKey);
252-
if ($normalizedRoute !== null) {
253-
$rootSpan->meta[Tag::APPSEC_NORMALIZED_ROUTE] = $normalizedRoute;
254-
}
262+
self::setNormalizedRoute($rootSpan, $origKey);
255263
return;
256264
}
257265
}
258266

259267
// If we got this far it means we didn't encounter a
260268
// matching route so we'll set the site default route
261269
$rootSpan->meta[Tag::HTTP_ROUTE] = $uri;
262-
$normalizedRoute = \DDTrace\Util\RouteNormalizer::normalizeFromCodeIgniter($uri);
263-
if ($normalizedRoute !== null) {
264-
$rootSpan->meta[Tag::APPSEC_NORMALIZED_ROUTE] = $normalizedRoute;
265-
}
270+
self::setNormalizedRoute($rootSpan, $uri);
266271
}
267272
}

src/DDTrace/Integrations/Laminas/LaminasIntegration.php

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -281,8 +281,17 @@ static function (SpanData $span) use ($controller, $action) {
281281
&& $routeName !== null
282282
&& $routeName !== ''
283283
) {
284-
$httpRoute = LaminasIntegration::httpRouteTemplateFromNamedRouteStack($this, (string) $routeName);
285-
if ($httpRoute !== null && $httpRoute !== '') {
284+
$cacheKey = (string) $routeName;
285+
$cachedRoute = \DDTrace\routing_cache_get($cacheKey);
286+
if ($cachedRoute !== false) {
287+
$httpRoute = $cachedRoute;
288+
} else {
289+
$httpRoute = LaminasIntegration::httpRouteTemplateFromNamedRouteStack($this, (string) $routeName);
290+
if ($httpRoute !== null && $httpRoute !== '') {
291+
\DDTrace\routing_cache_set($cacheKey, $httpRoute);
292+
}
293+
}
294+
if ($httpRoute !== null && $httpRoute !== false && $httpRoute !== '') {
286295
$rootSpan->meta[Tag::HTTP_ROUTE] = $httpRoute;
287296
$allParams = method_exists($routeMatch, 'getParams') ? ($routeMatch->getParams() ?? []) : [];
288297
$urlPath = method_exists($request, 'getUri') ? $request->getUri()->getPath() : null;

src/DDTrace/Integrations/Laravel/LaravelIntegration.php

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -142,9 +142,15 @@ static function ($This, $scope, $args, $route) {
142142
if (\method_exists($route, 'uri')) {
143143
$httpRoute = $route->uri();
144144
$rootSpan->meta[Tag::HTTP_ROUTE] = $httpRoute;
145-
$matchedParams = \method_exists($route, 'parameters') ? ($route->parameters() ?? []) : [];
146-
$normalizedRoute = \DDTrace\Util\RouteNormalizer::normalizeFromLaravel($httpRoute, $matchedParams);
147-
if ($normalizedRoute !== null) {
145+
$normalizedRoute = \DDTrace\routing_cache_get($httpRoute);
146+
if ($normalizedRoute === false) {
147+
$matchedParams = \method_exists($route, 'parameters') ? ($route->parameters() ?? []) : [];
148+
$normalizedRoute = \DDTrace\Util\RouteNormalizer::normalizeFromLaravel($httpRoute, $matchedParams);
149+
if ($normalizedRoute !== null) {
150+
\DDTrace\routing_cache_set($httpRoute, $normalizedRoute);
151+
}
152+
}
153+
if ($normalizedRoute !== null && $normalizedRoute !== false) {
148154
$rootSpan->meta[Tag::APPSEC_NORMALIZED_ROUTE] = $normalizedRoute;
149155
}
150156
}

src/DDTrace/Integrations/Symfony/SymfonyIntegration.php

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -442,15 +442,25 @@ static function() {
442442
return;
443443
}
444444

445-
/** @var ContainerInterface $container */
446-
$container = self::$kernel->getContainer();
447-
$path = EndpointCatalog::pathForRoute($route_name, $container);
448-
449-
// Try with locale suffix (Symfony i18n routing convention)
450-
if ($path === null) {
451-
$locale = $request->attributes->get('_locale');
452-
if ($locale !== null) {
453-
$path = EndpointCatalog::pathForRoute($route_name . '.' . $locale, $container);
445+
$cacheKey = $route_name;
446+
$cachedPath = \DDTrace\routing_cache_get($cacheKey);
447+
if ($cachedPath !== false) {
448+
$path = $cachedPath;
449+
} else {
450+
/** @var ContainerInterface $container */
451+
$container = self::$kernel->getContainer();
452+
$path = EndpointCatalog::pathForRoute($route_name, $container);
453+
454+
// Try with locale suffix (Symfony i18n routing convention)
455+
if ($path === null) {
456+
$locale = $request->attributes->get('_locale');
457+
if ($locale !== null) {
458+
$path = EndpointCatalog::pathForRoute($route_name . '.' . $locale, $container);
459+
}
460+
}
461+
462+
if ($path !== null) {
463+
\DDTrace\routing_cache_set($cacheKey, $path);
454464
}
455465
}
456466

src/DDTrace/Integrations/Yii/YiiIntegration.php

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,8 +156,15 @@ function (SpanData $span, $args) use (&$firstController) {
156156

157157
$rootSpan->meta['app.route.path'] = $routePath;
158158
$rootSpan->meta[Tag::HTTP_ROUTE] = $routePath;
159-
$normalizedRoute = \DDTrace\Util\RouteNormalizer::normalizeFromYii($routePath);
160-
if ($normalizedRoute !== null) {
159+
$cacheKey = $routePath;
160+
$normalizedRoute = \DDTrace\routing_cache_get($cacheKey);
161+
if ($normalizedRoute === false) {
162+
$normalizedRoute = \DDTrace\Util\RouteNormalizer::normalizeFromYii($routePath);
163+
if ($normalizedRoute !== null) {
164+
\DDTrace\routing_cache_set($cacheKey, $normalizedRoute);
165+
}
166+
}
167+
if ($normalizedRoute !== null && $normalizedRoute !== false) {
161168
$rootSpan->meta[Tag::APPSEC_NORMALIZED_ROUTE] = $normalizedRoute;
162169
}
163170

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
--TEST--
2+
DDTrace\routing_cache evicts least-recently-used entry when capacity (500) is exceeded
3+
--FILE--
4+
<?php
5+
6+
// Fill the cache to capacity (500 entries)
7+
for ($i = 0; $i < 500; $i++) {
8+
DDTrace\routing_cache_set("key$i", "value$i");
9+
}
10+
11+
// key0 is the LRU — access key1..key499 to keep them hot, leaving key0 as LRU
12+
// Actually they were inserted oldest-first, so key0 is at tail (LRU).
13+
// Verify key0 is still present before eviction
14+
var_dump(DDTrace\routing_cache_get('key0'));
15+
16+
// Access key0 to move it to MRU position so key1 becomes LRU
17+
DDTrace\routing_cache_get('key0');
18+
19+
// Insert one more entry; key1 should be evicted (it's now LRU)
20+
DDTrace\routing_cache_set('key500', 'value500');
21+
22+
var_dump(DDTrace\routing_cache_get('key1')); // evicted
23+
var_dump(DDTrace\routing_cache_get('key0')); // still present (was accessed)
24+
var_dump(DDTrace\routing_cache_get('key500')); // newly inserted
25+
26+
?>
27+
--EXPECT--
28+
string(6) "value0"
29+
bool(false)
30+
string(6) "value0"
31+
string(8) "value500"
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
--TEST--
2+
DDTrace\routing_cache_get returns false on cache miss
3+
--FILE--
4+
<?php
5+
6+
var_dump(DDTrace\routing_cache_get('nonexistent'));
7+
var_dump(DDTrace\routing_cache_get(''));
8+
var_dump(DDTrace\routing_cache_get('laminas:/api/users/{id}'));
9+
10+
?>
11+
--EXPECT--
12+
bool(false)
13+
bool(false)
14+
bool(false)
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
--TEST--
2+
DDTrace\routing_cache_set stores and DDTrace\routing_cache_get retrieves values
3+
--FILE--
4+
<?php
5+
6+
DDTrace\routing_cache_set('laminas:/api/users/{id}', '/api/users/{id}');
7+
var_dump(DDTrace\routing_cache_get('laminas:/api/users/{id}'));
8+
9+
DDTrace\routing_cache_set('symfony:/blog/{slug}', '/blog/{slug}');
10+
var_dump(DDTrace\routing_cache_get('symfony:/blog/{slug}'));
11+
12+
// keys are isolated
13+
var_dump(DDTrace\routing_cache_get('laminas:/api/users/{id}'));
14+
var_dump(DDTrace\routing_cache_get('other:key'));
15+
16+
?>
17+
--EXPECT--
18+
string(15) "/api/users/{id}"
19+
string(12) "/blog/{slug}"
20+
string(15) "/api/users/{id}"
21+
bool(false)
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
--TEST--
2+
DDTrace\routing_cache_set updates value for existing key
3+
--FILE--
4+
<?php
5+
6+
DDTrace\routing_cache_set('mykey', 'first');
7+
var_dump(DDTrace\routing_cache_get('mykey'));
8+
9+
DDTrace\routing_cache_set('mykey', 'updated');
10+
var_dump(DDTrace\routing_cache_get('mykey'));
11+
12+
?>
13+
--EXPECT--
14+
string(5) "first"
15+
string(7) "updated"

0 commit comments

Comments
 (0)